Compare commits

..

No commits in common. "master" and "2026-03-16-Development-2.11.0-beta.55" have entirely different histories.

1073 changed files with 90502 additions and 51440 deletions

26
.ci/Debian11/Dockerfile Normal file
View file

@ -0,0 +1,26 @@
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/*

View file

@ -1,4 +1,4 @@
FROM fedora:44 FROM fedora:42
RUN dnf install -y \ RUN dnf install -y \
ccache \ ccache \

View file

@ -1,4 +1,4 @@
FROM debian:12 FROM debian:11
RUN apt-get update && \ RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@ -11,11 +11,11 @@ RUN apt-get update && \
git \ git \
libmariadb-dev-compat \ libmariadb-dev-compat \
libprotobuf-dev \ libprotobuf-dev \
libqt6sql6-mysql \ libqt5sql5-mysql \
libqt5websockets5-dev \
ninja-build \ ninja-build \
protobuf-compiler \ protobuf-compiler \
qt6-tools-dev \ qttools5-dev \
qt6-tools-dev-tools \ qttools5-dev-tools \
qt6-websockets-dev \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*

View file

@ -1,9 +1,8 @@
FROM ubuntu:26.04 FROM ubuntu:22.04
RUN apt-get update && \ RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \ build-essential \
ca-certificates \
ccache \ ccache \
clang-format \ clang-format \
cmake \ cmake \
@ -16,14 +15,14 @@ RUN apt-get update && \
libprotobuf-dev \ libprotobuf-dev \
libqt6multimedia6 \ libqt6multimedia6 \
libqt6sql6-mysql \ libqt6sql6-mysql \
libqt6svg6-dev \
libqt6websockets6-dev \
ninja-build \ ninja-build \
protobuf-compiler \ protobuf-compiler \
qt6-image-formats-plugins \ qt6-image-formats-plugins \
qt6-l10n-tools \ qt6-l10n-tools \
qt6-multimedia-dev \ qt6-multimedia-dev \
qt6-svg-dev \
qt6-tools-dev \ qt6-tools-dev \
qt6-tools-dev-tools \ qt6-tools-dev-tools \
qt6-websockets-dev \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*

View file

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

View file

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

View file

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

View file

@ -89,8 +89,6 @@ else
echo "'$previous' to '$TAG' ($count commits)" echo "'$previous' to '$TAG' ($count commits)"
# --> is the markdown comment escape sequence, emojis are way better # --> is the markdown comment escape sequence, emojis are way better
generated_list="${generated_list//-->/→}" 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-GENERATED-LIST--/$generated_list}"
body="${body//--REPLACE-WITH-COMMIT-COUNT--/$count}" body="${body//--REPLACE-WITH-COMMIT-COUNT--/$count}"
body="${body//--REPLACE-WITH-PREVIOUS-RELEASE-TAG--/$previous}" body="${body//--REPLACE-WITH-PREVIOUS-RELEASE-TAG--/$previous}"

View file

@ -17,17 +17,16 @@ Available pre-compiled binaries for installation:
<kbd>macOS 13+</kbd> <sub><i>Ventura</i></sub> <sub>Intel</sub> <kbd>macOS 13+</kbd> <sub><i>Ventura</i></sub> <sub>Intel</sub>
<b>Linux</b> <b>Linux</b>
<kbd>Ubuntu 26.04 LTS</kbd> <sub><i>Resolute Racoon</i></sub>
<kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub> <kbd>Ubuntu 24.04 LTS</kbd> <sub><i>Noble Numbat</i></sub>
<kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</i></sub>
<kbd>Debian 13</kbd> <sub><i>Trixie</i></sub> <kbd>Debian 13</kbd> <sub><i>Trixie</i></sub>
<kbd>Debian 12</kbd> <sub><i>Bookworm</i></sub> <kbd>Debian 12</kbd> <sub><i>Bookworm</i></sub>
<kbd>Fedora 44</kbd> <kbd>Debian 11</kbd> <sub><i>Bullseye</i></sub>
<kbd>Fedora 43</kbd> <kbd>Fedora 43</kbd>
<kbd>Fedora 42</kbd>
<sub>We are also packaged in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>, courtesy of @FFY00.</sub> <sub>We are also packaged in <kbd>Arch Linux</kbd>'s <a href="https://archlinux.org/packages/extra/x86_64/cockatrice">official extra repository</a>, courtesy of @FFY00.</sub>
<sub>General Linux support is available via a <kbd>flatpak</kbd> package at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a>!</sub> <sub>General Linux support is available via a <kbd>flatpak</kbd> package at <a href="https://flathub.org/apps/io.github.Cockatrice.cockatrice">Flathub</a>!</sub>
<sub>We provide a <kbd>Docker</kbd> image for "Servatrice" in <a href="https://github.com/Cockatrice/Cockatrice/pkgs/container/servatrice">GHCR</a>. You can docker pull it or use our Docker Compose files!</sub>
</pre> </pre>
@ -83,6 +82,7 @@ Remove empty headers when done.
### Under the Hood ### Under the Hood
### Oracle ### Oracle
### Servatrice ### Servatrice
### Webatrice
</details> </details>

View file

@ -3,9 +3,7 @@ AccessModifierOffset: -4
ColumnLimit: 120 ColumnLimit: 120
--- ---
Language: Cpp Language: Cpp
AllowAllParametersOfDeclarationOnNextLine: false BreakBeforeBraces: Custom
AllowShortFunctionsOnASingleLine: None
BinPackParameters: false
BraceWrapping: BraceWrapping:
AfterClass: true AfterClass: true
AfterControlStatement: false AfterControlStatement: false
@ -20,14 +18,16 @@ BraceWrapping:
SplitEmptyFunction: true SplitEmptyFunction: true
SplitEmptyRecord: true SplitEmptyRecord: true
SplitEmptyNamespace: true SplitEmptyNamespace: true
BreakBeforeBraces: Custom AllowShortFunctionsOnASingleLine: None
IncludeBlocks: Regroup BinPackParameters: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentCaseLabels: true IndentCaseLabels: true
InsertBraces: true
PointerAlignment: Right PointerAlignment: Right
RemoveSemicolon: true
SortIncludes: true SortIncludes: true
IncludeBlocks: Regroup
StatementAttributeLikeMacros: [emit] StatementAttributeLikeMacros: [emit]
# requires clang-format 16
# RemoveSemicolon: true
--- ---
Language: Proto Language: Proto
AllowShortFunctionsOnASingleLine: None AllowShortFunctionsOnASingleLine: None

View file

@ -209,16 +209,6 @@ nowadays and clean it up for you.
Lines should be 120 characters or less. Please break up lines that are too long 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. 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 ### ### Memory Management ###
New code should be written using references over pointers and stack allocation New code should be written using references over pointers and stack allocation

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,14 @@
name: Update Translations name: Update Translations
on: on:
workflow_dispatch:
schedule:
# runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
pull_request: pull_request:
paths: paths:
- '.tx/**' - '.tx/**'
- '.github/workflows/translations-pull.yml' - '.github/workflows/translations-pull.yml'
schedule:
# Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- cron: '0 0 15 1,4,7,10 *'
workflow_dispatch:
jobs: jobs:
translations: translations:
@ -16,21 +16,21 @@ jobs:
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice' if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
name: Pull languages name: Pull languages
runs-on: ubuntu-slim runs-on: ubuntu-latest
steps: steps:
- name: "Checkout repo" - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: "Pull translated strings from Transifex" - name: Pull translated strings from Transifex
uses: transifex/cli-action@v2 uses: transifex/cli-action@v2
with: with:
# Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config # used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
# Docs: https://github.com/transifex/cli#pulling-files-from-transifex # https://github.com/transifex/cli#pulling-files-from-transifex
args: pull --force --all
token: ${{ secrets.TX_TOKEN }} token: ${{ secrets.TX_TOKEN }}
args: pull --force --all
- name: "Create pull request" - name: Create pull request
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
id: create_pr id: create_pr
uses: peter-evans/create-pull-request@v8 uses: peter-evans/create-pull-request@v8
@ -38,7 +38,13 @@ jobs:
add-paths: | add-paths: |
cockatrice/translations/*.ts cockatrice/translations/*.ts
oracle/translations/*.ts oracle/translations/*.ts
author: github-actions <github-actions@github.com> # owner of the commit webclient/public/locales/*/translation.json
commit-message: Update translation files
# author is the owner of the commit
author: github-actions <github-actions@github.com>
branch: ci-update_translations
delete-branch: true
title: 'Update translations'
body: | body: |
Pulled all translated strings from [Transifex][1]. Pulled all translated strings from [Transifex][1].
@ -48,16 +54,12 @@ jobs:
[1]: https://explore.transifex.com/cockatrice/cockatrice/ [1]: https://explore.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster [2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
branch: ci-update_translations
commit-message: Update translation files
delete-branch: true
draft: false
labels: | labels: |
CI CI
Translation Translation
title: 'Update translations' draft: false
- name: "PR Status" - name: PR Status
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
shell: bash shell: bash
env: env:

View file

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

54
.github/workflows/web-build.yml vendored Normal file
View file

@ -0,0 +1,54 @@
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

33
.github/workflows/web-lint.yml vendored Normal file
View file

@ -0,0 +1,33 @@
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

7
.husky/pre-commit Executable file
View file

@ -0,0 +1,7 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
cd webclient
npm run translate
git add src/i18n-default.json

View file

@ -16,3 +16,11 @@ source_file = oracle/oracle_en@source.ts
file_filter = oracle/translations/oracle_<lang>.ts file_filter = oracle/translations/oracle_<lang>.ts
type = QT type = QT
minimum_perc = 10 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/<lang>/translation.json
type = KEYVALUEJSON
minimum_perc = 10

View file

@ -74,11 +74,11 @@ endif()
# A project name is needed for CPack # A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake # Version can be overriden by git tags, see cmake/getversion.cmake
project("Cockatrice" VERSION 3.1.0) project("Cockatrice" VERSION 2.11.0)
# Set release name if not provided via env/cmake var # Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME) if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Graduation Day") set(GIT_TAG_RELEASENAME "Omenpath")
endif() endif()
# Use c++20 for all targets # Use c++20 for all targets
@ -174,7 +174,6 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
-Wno-error=delete-non-virtual-dtor -Wno-error=delete-non-virtual-dtor
-Wno-error=sign-compare -Wno-error=sign-compare
-Wno-error=missing-declarations -Wno-error=missing-declarations
-Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
) )
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS}) foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})

View file

@ -1,5 +1,5 @@
# -------- Build Stage -------- # -------- Build Stage --------
FROM ubuntu:26.04 AS build FROM ubuntu:24.04 AS build
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
@ -26,7 +26,7 @@ RUN mkdir build && cd build && \
# -------- Runtime Stage (clean) -------- # -------- Runtime Stage (clean) --------
FROM ubuntu:26.04 FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
libprotobuf32t64 \ libprotobuf32t64 \

View file

@ -1,4 +1,4 @@
# Doxyfile 1.16.1 # Doxyfile 1.14.0
# This file describes the settings to be used by the documentation system # This file describes the settings to be used by the documentation system
# Doxygen (www.doxygen.org) for a project. # Doxygen (www.doxygen.org) for a project.
@ -361,20 +361,6 @@ EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES 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 # 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 # to that level are automatically included in the table of contents, even if
# they do not have an id attribute. # they do not have an id attribute.
@ -406,8 +392,8 @@ AUTOLINK_SUPPORT = YES
# This tag specifies a list of words that, when matching the start of a word in # 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 # the documentation, will suppress auto links generation, if it is enabled via
# AUTOLINK_SUPPORT. This list does not affect links explicitly created using # # AUTOLINK_SUPPORT. This list does not affect links explicitly created using \#
# or the \link or \ref commands. # or the \link or commands.
# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. # This tag requires that the tag AUTOLINK_SUPPORT is set to YES.
AUTOLINK_IGNORE_WORDS = AUTOLINK_IGNORE_WORDS =
@ -524,9 +510,9 @@ LOOKUP_CACHE_SIZE = 0
# which effectively disables parallel processing. Please report any issues you # which effectively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the # encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting. # DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 512, default value: 1. # Minimum value: 0, maximum value: 32, default value: 1.
NUM_PROC_THREADS = 0 NUM_PROC_THREADS = 1
# If the TIMESTAMP tag is set different from NO then each generated page will # 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 # contain the date or date and time when the page was generated. Setting this to
@ -793,27 +779,6 @@ GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= 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 # The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if <section_label> ... \endif and \cond <section_label> # sections, marked by \if <section_label> ... \endif and \cond <section_label>
# ... \endcond blocks. # ... \endcond blocks.
@ -1105,7 +1070,8 @@ EXCLUDE = build/ \
cmake/ \ cmake/ \
doc/doxygen/theme/docs/ \ doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \ doc/doxygen/theme/include/ \
vcpkg/ vcpkg/ \
webclient/
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # 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 # directories that are symbolic links (a Unix file system feature) are excluded
@ -1921,7 +1887,7 @@ USE_MATHJAX = NO
# regards to the different settings, so it is possible that also other MathJax # regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax # settings have to be changed when switching between the different MathJax
# versions. # versions.
# Possible values are: MathJax_2, MathJax_3 and MathJax_4. # Possible values are: MathJax_2 and MathJax_3.
# The default value is: MathJax_2. # The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES. # This tag requires that the tag USE_MATHJAX is set to YES.
@ -1930,10 +1896,9 @@ MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for # 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 # the MathJax output. For more details about the output format see MathJax
# version 2 (see: # version 2 (see:
# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4
# (see: # (see:
# https://docs.mathjax.org/en/v4.0/output/index.htm). # http://docs.mathjax.org/en/latest/web/components/output.html).
# Possible values are: HTML-CSS (which is slower, but has the best # 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 # 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 # this will be translated into chtml), NativeMML (i.e. MathML. Only supported
@ -1946,50 +1911,36 @@ MATHJAX_VERSION = MathJax_2
MATHJAX_FORMAT = HTML-CSS MATHJAX_FORMAT = HTML-CSS
# When MathJax is enabled you need to specify the location relative to the HTML # When MathJax is enabled you need to specify the location relative to the HTML
# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the # output directory using the MATHJAX_RELPATH option. The destination directory
# destination directory should contain the MathJax.js script. For instance, if # should contain the MathJax.js script. For instance, if the mathjax directory
# the mathjax directory is located at the same level as the HTML output # is located at the same level as the HTML output directory, then
# directory, then MATHJAX_RELPATH should be ../mathjax.s For Mathjax versions 3 # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
# and 4 the destination directory should contain the tex-<format>.js script # Content Delivery Network so you can quickly see the result without installing
# (where <format> is either chtml or svg). The default value points to the # MathJax. However, it is strongly recommended to install a local copy of
# MathJax Content Delivery Network so you can quickly see the result without # MathJax from https://www.mathjax.org before deployment. The default value is:
# 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 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 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. # This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = MATHJAX_RELPATH =
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example # extension names that should be enabled during MathJax rendering. For example
# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html): # for MathJax version 2 (see
# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see # For example for MathJax version 3 (see
# https://docs.mathjax.org/en/v3.2/input/tex/extensions/): # http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
# MATHJAX_EXTENSIONS = ams # 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. # This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS = MATHJAX_EXTENSIONS =
# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces # 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 # of code that will be used on startup of the MathJax code. See the MathJax site
# for more details: # (see:
# - MathJax version 2 (see: # http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
# https://docs.mathjax.org/en/v2.7/) # example see the documentation.
# - 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. # This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_CODEFILE = MATHJAX_CODEFILE =
@ -2650,7 +2601,7 @@ HAVE_DOT = YES
# processors available in the system. You can set it explicitly to a value # 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 # larger than 0 to get control over the balance between CPU load and processing
# speed. # speed.
# Minimum value: 0, maximum value: 512, default value: 0. # Minimum value: 0, maximum value: 32, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES. # This tag requires that the tag HAVE_DOT is set to YES.
DOT_NUM_THREADS = 0 DOT_NUM_THREADS = 0

View file

@ -8,7 +8,7 @@
<a href="#related-repositories">Related</a> <b>|</b> <a href="#related-repositories">Related</a> <b>|</b>
<a href="#community-resources-">Community</a> <b>|</b> <a href="#community-resources-">Community</a> <b>|</b>
<a href="#contribute">Contribute</a> <b>|</b> <a href="#contribute">Contribute</a> <b>|</b>
<a href="#build--">Build</a> <b>|</b> <a href="#build---">Build</a> <b>|</b>
<a href="#run">Run</a> <a href="#run">Run</a>
</p> </p>
@ -25,6 +25,7 @@
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.<br><br> 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.<br><br>
This project uses <kbd>C++</kbd> and the <kbd>Qt</kbd> libraries.<br> This project uses <kbd>C++</kbd> and the <kbd>Qt</kbd> libraries.<br>
First work on a webclient with <kbd>Typescript</kbd> was started as well.<br>
# Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0) # Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
@ -47,7 +48,6 @@ Latest <kbd>beta</kbd> 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 - [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 - [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) - [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 [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) # Community Resources [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA)
@ -107,12 +107,12 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/) ### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)
Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd> and <kbd>Oracle</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/). The [Webatrice](https://github.com/seavor/Webatrice) web client manages its own translations in its repo.<br> Cockatrice uses Transifex to manage translations. You can help us bring <kbd>Cockatrice</kbd>, <kbd>Oracle</kbd> and <kbd>Webatrice</kbd> to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).<br>
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!<br> Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!<br>
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) # Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [![CI Web](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))* Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
- [Qt](https://www.qt.io/developers/) - [Qt](https://www.qt.io/developers/)

View file

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

View file

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

View file

@ -213,8 +213,7 @@ set(PROJECT_VERSION_FRIENDLY "${PROJECT_VERSION} (${GIT_COMMIT_DATE_FRIENDLY})")
# Format: <program name>[-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label] # Format: <program name>[-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label]
set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}") set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}")
if(PROJECT_VERSION_RELEASENAME) if(PROJECT_VERSION_RELEASENAME)
string(REPLACE " " "-" PROJECT_VERSION_RELEASENAME_SAFE "${PROJECT_VERSION_RELEASENAME}") set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME}")
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME_SAFE}")
endif() endif()
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}") set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}")

View file

@ -1,16 +1,15 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.2)
project(gtest-download LANGUAGES NONE) project(gtest-download LANGUAGES NONE)
include(ExternalProject) include(ExternalProject)
externalproject_add( ExternalProject_Add(googletest
googletest URL https://github.com/google/googletest/archive/release-1.11.0.zip
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip URL_HASH SHA1=9ffb7b5923f4a8fcdabf2f42c6540cce299f44c0
URL_HASH SHA1=f638fa0e724760e2ba07ff8cfba32cd644e1ce28
SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src" SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build" BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_COMMAND "" INSTALL_COMMAND ""
TEST_COMMAND "" TEST_COMMAND ""
) )

View file

@ -7,7 +7,6 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${
set(cockatrice_SOURCES set(cockatrice_SOURCES
${VERSION_STRING_CPP} ${VERSION_STRING_CPP}
# sort by alphabetical order, so that there is no debate about where to add new sources to the list # 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/update/client/update_downloader.cpp
src/client/network/interfaces/deck_stats_interface.cpp src/client/network/interfaces/deck_stats_interface.cpp
src/client/network/interfaces/tapped_out_interface.cpp src/client/network/interfaces/tapped_out_interface.cpp
@ -56,73 +55,68 @@ set(cockatrice_SOURCES
src/filters/filter_tree_model.cpp src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp src/filters/syntax_help.cpp
src/game/abstract_game.cpp src/game/abstract_game.cpp
src/game/arrow_registry.cpp src/game/board/abstract_card_drag_item.cpp
src/game_graphics/board/abstract_card_drag_item.cpp src/game/board/abstract_card_item.cpp
src/game_graphics/board/abstract_card_item.cpp src/game/board/abstract_counter.cpp
src/game_graphics/board/abstract_counter.cpp src/game/board/arrow_item.cpp
src/game/board/arrow_data.cpp src/game/board/arrow_target.cpp
src/game_graphics/board/arrow_item.cpp src/game/board/card_drag_item.cpp
src/game_graphics/board/arrow_target.cpp src/game/board/card_item.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/card_list.cpp
src/game/board/card_state.cpp src/game/board/counter_general.cpp
src/game_graphics/board/counter_general.cpp src/game/board/translate_counter_name.cpp
src/game/board/counter_state.cpp src/game/deckview/deck_view.cpp
src/game_graphics/board/translate_counter_name.cpp src/game/deckview/deck_view_container.cpp
src/game_graphics/deckview/deck_view.cpp src/game/deckview/tabbed_deck_view_container.cpp
src/game_graphics/deckview/deck_view_container.cpp src/game/dialogs/dlg_create_token.cpp
src/game_graphics/deckview/tabbed_deck_view_container.cpp src/game/dialogs/dlg_move_top_cards_until.cpp
src/game_graphics/dialogs/dlg_create_token.cpp src/game/dialogs/dlg_roll_dice.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.cpp
src/game/game_event_handler.cpp src/game/game_event_handler.cpp
src/game/game_meta_info.cpp src/game/game_meta_info.cpp
src/game_graphics/game_scene.cpp src/game/game_scene.cpp
src/game/game_state.cpp src/game/game_state.cpp
src/game_graphics/game_view.cpp src/game/game_view.cpp
src/game_graphics/hand_counter.cpp src/game/hand_counter.cpp
src/game_graphics/log/message_log_widget.cpp src/game/log/message_log_widget.cpp
src/game/phase.cpp src/game/phase.cpp
src/game_graphics/phases_toolbar.cpp src/game/phases_toolbar.cpp
src/game_graphics/player/menu/card_menu.cpp src/game/player/menu/card_menu.cpp
src/game_graphics/player/menu/custom_zone_menu.cpp src/game/player/menu/custom_zone_menu.cpp
src/game_graphics/player/menu/grave_menu.cpp src/game/player/menu/grave_menu.cpp
src/game_graphics/player/menu/hand_menu.cpp src/game/player/menu/hand_menu.cpp
src/game_graphics/player/menu/library_menu.cpp src/game/player/menu/library_menu.cpp
src/game_graphics/player/menu/move_menu.cpp src/game/player/menu/move_menu.cpp
src/game_graphics/player/menu/player_menu.cpp src/game/player/menu/player_menu.cpp
src/game_graphics/player/menu/pt_menu.cpp src/game/player/menu/pt_menu.cpp
src/game_graphics/player/menu/rfg_menu.cpp src/game/player/menu/rfg_menu.cpp
src/game_graphics/player/menu/say_menu.cpp src/game/player/menu/say_menu.cpp
src/game_graphics/player/menu/sideboard_menu.cpp src/game/player/menu/sideboard_menu.cpp
src/game_graphics/player/menu/utility_menu.cpp src/game/player/menu/utility_menu.cpp
src/game/player/player.cpp
src/game/player/player_actions.cpp src/game/player/player_actions.cpp
src/game_graphics/player/player_area.cpp src/game/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp src/game/player/player_event_handler.cpp
src/game_graphics/player/player_graphics_item.cpp src/game/player/player_graphics_item.cpp
src/game/player/player_info.cpp src/game/player/player_info.cpp
src/game_graphics/player/player_list_widget.cpp src/game/player/player_list_widget.cpp
src/game/player/player_logic.cpp
src/game/player/player_manager.cpp src/game/player/player_manager.cpp
src/game_graphics/player/player_target.cpp src/game/player/player_target.cpp
src/game/replay.cpp src/game/replay.cpp
src/game/zones/card_zone_logic.cpp src/game/zones/card_zone.cpp
src/game/zones/hand_zone_logic.cpp src/game/zones/hand_zone.cpp
src/game/zones/pile_zone_logic.cpp src/game/zones/logic/card_zone_logic.cpp
src/game/zones/stack_zone_logic.cpp src/game/zones/logic/hand_zone_logic.cpp
src/game/zones/table_zone_logic.cpp src/game/zones/logic/pile_zone_logic.cpp
src/game/zones/view_zone_logic.cpp src/game/zones/logic/stack_zone_logic.cpp
src/game_graphics/zones/card_zone.cpp src/game/zones/logic/table_zone_logic.cpp
src/game_graphics/zones/hand_zone.cpp src/game/zones/logic/view_zone_logic.cpp
src/game_graphics/zones/pile_zone.cpp src/game/zones/pile_zone.cpp
src/game_graphics/zones/select_zone.cpp src/game/zones/select_zone.cpp
src/game_graphics/zones/stack_zone.cpp src/game/zones/stack_zone.cpp
src/game_graphics/zones/table_zone.cpp src/game/zones/table_zone.cpp
src/game_graphics/zones/view_zone.cpp src/game/zones/view_zone.cpp
src/game_graphics/zones/view_zone_widget.cpp src/game/zones/view_zone_widget.cpp
src/game_graphics/board/abstract_graphics_item.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.cpp
src/interface/card_picture_loader/card_picture_loader_local.cpp src/interface/card_picture_loader/card_picture_loader_local.cpp
@ -135,13 +129,7 @@ set(cockatrice_SOURCES
src/interface/layouts/overlap_layout.cpp src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.cpp src/interface/widgets/utility/line_edit_completer.cpp
src/interface/pixel_map_generator.cpp src/interface/pixel_map_generator.cpp
src/interface/theme_config.cpp
src/interface/theme_manager.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/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
@ -183,7 +171,6 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
@ -229,7 +216,6 @@ set(cockatrice_SOURCES
src/interface/widgets/replay/replay_manager.cpp src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp src/interface/widgets/replay/replay_timeline_widget.cpp
src/interface/widgets/server/chat_view/chat_view.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.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp src/interface/widgets/server/games_model.cpp
@ -241,14 +227,6 @@ set(cockatrice_SOURCES
src/interface/widgets/server/user/user_info_connection.cpp src/interface/widgets/server/user/user_info_connection.cpp
src/interface/widgets/server/user/user_list_manager.cpp src/interface/widgets/server/user/user_list_manager.cpp
src/interface/widgets/server/user/user_list_widget.cpp src/interface/widgets/server/user/user_list_widget.cpp
src/interface/widgets/settings_page/appearance_settings_page.cpp
src/interface/widgets/settings_page/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/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/sequence_edit.cpp src/interface/widgets/utility/sequence_edit.cpp
@ -347,8 +325,6 @@ 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_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.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
src/interface/widgets/utility/compact_push_button.cpp
src/interface/widgets/utility/compact_push_button.h
) )
add_subdirectory(sounds) add_subdirectory(sounds)
@ -563,7 +539,6 @@ if(WIN32)
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/" DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
DESTINATION ./ DESTINATION ./
FILES_MATCHING FILES_MATCHING
PATTERN "CMakeFiles" EXCLUDE
PATTERN "*.ini" PATTERN "*.ini"
) )
@ -589,9 +564,6 @@ if(WIN32)
PATTERN "styles/qopensslbackend.dll" PATTERN "styles/qopensslbackend.dll"
PATTERN "styles/qschannelbackend.dll" PATTERN "styles/qschannelbackend.dll"
PATTERN "styles/qwindowsvistastyle.dll" PATTERN "styles/qwindowsvistastyle.dll"
PATTERN "styles/qwindows11style.dll"
PATTERN "styles/qmodernwindowsstyle.dll"
PATTERN "styles/qmodernwindowsstyled.dll"
PATTERN "tls/qcertonlybackend.dll" PATTERN "tls/qcertonlybackend.dll"
PATTERN "tls/qopensslbackend.dll" PATTERN "tls/qopensslbackend.dll"
PATTERN "tls/qschannelbackend.dll" PATTERN "tls/qschannelbackend.dll"

View file

@ -55,7 +55,6 @@
<file>resources/icons/view.svg</file> <file>resources/icons/view.svg</file>
<file>resources/icons/mana/B.svg</file> <file>resources/icons/mana/B.svg</file>
<file>resources/icons/mana/C.svg</file>
<file>resources/icons/mana/G.svg</file> <file>resources/icons/mana/G.svg</file>
<file>resources/icons/mana/R.svg</file> <file>resources/icons/mana/R.svg</file>
<file>resources/icons/mana/U.svg</file> <file>resources/icons/mana/U.svg</file>
@ -70,7 +69,6 @@
<file>resources/config/interface.svg</file> <file>resources/config/interface.svg</file>
<file>resources/config/messages.svg</file> <file>resources/config/messages.svg</file>
<file>resources/config/deckeditor.svg</file> <file>resources/config/deckeditor.svg</file>
<file>resources/config/storage.svg</file>
<file>resources/config/shorcuts.svg</file> <file>resources/config/shorcuts.svg</file>
<file>resources/config/sound.svg</file> <file>resources/config/sound.svg</file>
<file>resources/config/debug.ini</file> <file>resources/config/debug.ini</file>

File diff suppressed because it is too large Load diff

View file

@ -28,8 +28,6 @@
#dlg_tip_of_the_day = true #dlg_tip_of_the_day = true
#dlg_update = true #dlg_update = true
#general_settings_page = true
#settings_cache = true #settings_cache = true
#servers_settings = true #servers_settings = true
#shortcuts_settings = true #shortcuts_settings = true

View file

@ -1,799 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
width="62.636364"
height="62.090908"
id="svg2"
sodipodi:version="0.32"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="storage.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.0"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"><defs
id="defs4"><linearGradient
id="linearGradient3169"><stop
style="stop-color:#0000ff;stop-opacity:1;"
offset="0"
id="stop3171" /><stop
style="stop-color:#000067;stop-opacity:1;"
offset="1"
id="stop3173" /></linearGradient><linearGradient
id="linearGradient4766"><stop
style="stop-color:#784421;stop-opacity:1;"
offset="0"
id="stop4768" /><stop
style="stop-color:#3d2210;stop-opacity:0;"
offset="1"
id="stop4770" /></linearGradient><linearGradient
id="linearGradient4758"><stop
style="stop-color:#a05a2c;stop-opacity:1;"
offset="0"
id="stop4760" /><stop
style="stop-color:#3d2210;stop-opacity:1;"
offset="1"
id="stop4762" /></linearGradient><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" /><inkscape:perspective
id="perspective2484"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 526.18109 : 1"
sodipodi:type="inkscape:persp3d" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4758"
id="linearGradient4764"
x1="466.09601"
y1="485.96021"
x2="715.14801"
y2="485.96021"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4766"
id="linearGradient4772"
x1="496.548"
y1="485.26816"
x2="683.31201"
y2="485.26816"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3169"
id="radialGradient3175"
cx="120.07376"
cy="56.138123"
fx="120.07376"
fy="56.138123"
r="82.790039"
gradientTransform="matrix(1,0,0,0.2116376,0,44.257186)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6482"
id="linearGradient6488"
x1="32.18182"
y1="3.2835093"
x2="32.18182"
y2="13.02554"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.0281354,0,0,1.0429299,85.21874,131.0326)" /><linearGradient
id="linearGradient6482"><stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop6484" /><stop
style="stop-color:#00ff00;stop-opacity:0;"
offset="1"
id="stop6486" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient6464"
id="linearGradient6470"
x1="32.090908"
y1="1.8181819"
x2="31.09091"
y2="62.909088"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,-0.1818182)" /><linearGradient
id="linearGradient6464"><stop
style="stop-color:#0061ff;stop-opacity:1;"
offset="0"
id="stop6466" /><stop
style="stop-color:#001c4c;stop-opacity:1;"
offset="1"
id="stop6468" /></linearGradient><linearGradient
y2="62.909088"
x2="31.09091"
y1="1.8181819"
x1="32.090908"
gradientTransform="translate(86.2151,131.5372)"
gradientUnits="userSpaceOnUse"
id="linearGradient4477"
xlink:href="#linearGradient6464"
inkscape:collect="always" /><linearGradient
inkscape:collect="always"
id="linearGradient2916"><stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop2918" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2920" /></linearGradient><linearGradient
inkscape:collect="always"
id="linearGradient2902"><stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop2905" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop2907" /></linearGradient><linearGradient
id="linearGradient2064"><stop
id="stop2066"
offset="0"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:#555753;stop-opacity:0.60000002;"
offset="0.5"
id="stop2070" /><stop
id="stop2068"
offset="1"
style="stop-color:#555753;stop-opacity:0;" /></linearGradient><linearGradient
id="linearGradient9641"><stop
style="stop-color:white;stop-opacity:1"
offset="0"
id="stop9643" /><stop
style="stop-color:#888a85;stop-opacity:1"
offset="1"
id="stop9645" /></linearGradient><linearGradient
id="linearGradient9633"><stop
style="stop-color:#eeeeec;stop-opacity:1"
offset="0"
id="stop9635" /><stop
style="stop-color:#888a85;stop-opacity:1"
offset="1"
id="stop9639" /></linearGradient><linearGradient
id="linearGradient9613"><stop
style="stop-color:white;stop-opacity:1"
offset="0"
id="stop9615" /><stop
id="stop9619"
offset="0.5"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:#cccfca;stop-opacity:1"
offset="1"
id="stop9617" /></linearGradient><linearGradient
id="linearGradient8710"><stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop8712" /><stop
style="stop-color:white;stop-opacity:1;"
offset="1"
id="stop8714" /></linearGradient><linearGradient
id="linearGradient8631"><stop
id="stop8633"
offset="0"
style="stop-color:#eeeeec;stop-opacity:1" /><stop
style="stop-color:#eeeeec;stop-opacity:1;"
offset="0.2"
id="stop8637" /><stop
id="stop8635"
offset="1"
style="stop-color:#babdb6;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient8625"><stop
id="stop8627"
offset="0"
style="stop-color:white;stop-opacity:1" /><stop
id="stop8629"
offset="1"
style="stop-color:#babdb6;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient8613"><stop
style="stop-color:#babdb6;stop-opacity:1"
offset="0"
id="stop8615" /><stop
style="stop-color:#2e3436;stop-opacity:1"
offset="1"
id="stop8617" /></linearGradient><linearGradient
id="linearGradient5740"><stop
style="stop-color:#d0d0cb;stop-opacity:1;"
offset="0"
id="stop5742" /><stop
style="stop-color:#babdb6;stop-opacity:1"
offset="1"
id="stop5744" /></linearGradient><linearGradient
id="linearGradient5690"><stop
style="stop-color:white;stop-opacity:1;"
offset="0"
id="stop5692" /><stop
style="stop-color:#888a85;stop-opacity:0.59848487"
offset="1"
id="stop5694" /></linearGradient><linearGradient
id="linearGradient2899"><stop
id="stop2901"
offset="0"
style="stop-color:#555753;stop-opacity:1" /><stop
id="stop2903"
offset="1"
style="stop-color:#2e3436;stop-opacity:1" /></linearGradient><linearGradient
id="linearGradient3468"><stop
style="stop-color:#fdfdfc;stop-opacity:1"
offset="0"
id="stop3470" /><stop
style="stop-color:white;stop-opacity:0.37121212"
offset="1"
id="stop3472" /></linearGradient><linearGradient
id="linearGradient2909"><stop
style="stop-color:white;stop-opacity:0;"
offset="0"
id="stop2911" /><stop
id="stop2917"
offset="0.5"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2913" /></linearGradient><linearGradient
id="linearGradient2839"><stop
style="stop-color:white;stop-opacity:0.25773194;"
offset="0"
id="stop2841" /><stop
id="stop2847"
offset="0.5472973"
style="stop-color:white;stop-opacity:1;" /><stop
style="stop-color:white;stop-opacity:0.24705882;"
offset="0.66243607"
id="stop2849" /><stop
id="stop2851"
offset="0.875"
style="stop-color:white;stop-opacity:0.83505154;" /><stop
style="stop-color:white;stop-opacity:0;"
offset="1"
id="stop2843" /></linearGradient><linearGradient
id="linearGradient2900"><stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop2902" /><stop
id="stop2908"
offset="0.5"
style="stop-color:black;stop-opacity:1;" /><stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop2904" /></linearGradient><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3468"
id="linearGradient3474"
x1="24.748737"
y1="35.354588"
x2="24.998737"
y2="14.997767"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.995556,0,-3.931113)" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="radialGradient4700"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.095822,0,0,3.101282,-9.53921,-94.5433)"
cx="0"
cy="17"
fx="0"
fy="17"
r="2" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="radialGradient4702"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(4.095822,0,0,3.101282,38.20996,-10.90025)"
cx="0"
cy="17"
fx="0"
fy="17"
r="2" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2900"
id="linearGradient4704"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.047911,0,0,2.067521,1.347566,6.673675)"
x1="9.8994951"
y1="20"
x2="9.8994951"
y2="13.979153" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4711"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,1.42294,10.5,-14.95703)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4713"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,1.42294,-0.875,-15.04578)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2909"
id="linearGradient4715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.459833,0,-0.391165,1.370105,40.62503,-13.29892)"
x1="15.335379"
y1="33.06237"
x2="20.329321"
y2="36.37693" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5740"
id="radialGradient5748"
cx="25.251999"
cy="16.47991"
fx="25.251999"
fy="16.47991"
r="21.980215"
gradientTransform="matrix(1.032991,-0.596398,0.575121,0.99614,-12.23456,11.55448)"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2064"
id="linearGradient5790"
gradientUnits="userSpaceOnUse"
x1="18.048874"
y1="25.461344"
x2="22.211937"
y2="12.143078"
gradientTransform="matrix(0.940224,0,0,0.931632,1.331811,1.401537)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8631"
id="linearGradient5865"
x1="24"
y1="36.638382"
x2="25.818018"
y2="6.8314762"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2839"
id="linearGradient7658"
gradientUnits="userSpaceOnUse"
x1="27.057796"
y1="12.669416"
x2="32.042896"
y2="31.219666" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5690"
id="linearGradient8603"
x1="20.304037"
y1="24.035707"
x2="18.498415"
y2="40.647167"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8619"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9613"
id="radialGradient8623"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.389748,0,0,1.348872,-2.91982,-10.63815)"
cx="7.5191436"
cy="30.304251"
fx="7.5191436"
fy="30.304251"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9633"
id="radialGradient8664"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.569487,0,0,1.523325,-4.288627,-15.92107)"
cx="7.5336008"
cy="30.307562"
fx="7.5336008"
fy="30.307562"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8666"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8625"
id="radialGradient8676"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.4792061"
cy="30.36071"
fx="7.4792061"
fy="30.36071"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8678"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9641"
id="radialGradient8680"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.4893188"
cy="30.337601"
fx="7.4893188"
fy="30.337601"
r="0.53125" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient8613"
id="radialGradient8682"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.662477,0,0,1.61358,-4.989175,-18.65647)"
cx="7.5177727"
cy="30.573555"
fx="7.5177727"
fy="30.573555"
r="0.53125" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient8716"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9605"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="matrix(0.602867,-0.797841,0.797841,0.602867,-41.12611,44.62773)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9649"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="rotate(-30.000012,-5.5813167,76.089146)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient8710"
id="linearGradient9654"
gradientUnits="userSpaceOnUse"
x1="40.617188"
y1="30.554688"
x2="40.710938"
y2="30.359375"
gradientTransform="matrix(0.707107,0.527555,-0.707107,0.527555,29.0058,-24.09196)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2916"
id="linearGradient2973"
x1="12.5"
y1="43.1875"
x2="12.5"
y2="34.045513"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2899"
id="linearGradient5655"
gradientUnits="userSpaceOnUse"
x1="53.812813"
y1="43.573235"
x2="-2.8138931"
y2="35.500015"
gradientTransform="translate(0,50)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="linearGradient2992"
x1="21.9375"
y1="39"
x2="21.9375"
y2="37.995617"
gradientUnits="userSpaceOnUse" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2902"
id="linearGradient2910"
x1="22.101398"
y1="27.658131"
x2="22.971142"
y2="20.903238"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,2)" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2916"
id="linearGradient2922"
x1="24.847851"
y1="28.908398"
x2="24.847851"
y2="25.757175"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(0,2)" /></defs><sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.8830488"
inkscape:cx="-3.9945275"
inkscape:cy="-14.363301"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1408"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050" /><metadata
id="metadata7"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-86.987816,-132.85536)"><rect
style="fill:url(#linearGradient4477);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-opacity:1"
id="rect6462"
width="61.636364"
height="61.090908"
x="87.487816"
y="133.35536"
ry="5.6363635" /><rect
style="fill:url(#linearGradient6488);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect6472"
width="59.796619"
height="13.251164"
x="88.407707"
y="134.45705"
ry="4.7325583" /><g
inkscape:label="Livello 1"
id="layer1-3"
style="display:inline"
transform="matrix(1.1537183,0,0,1.1537183,91.003924,136.40297)"><g
id="g3519"
style="opacity:0.7"
transform="matrix(1.030831,0,0,1.151147,-0.73609,-12.57431)"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"><rect
transform="scale(-1)"
y="-48.024086"
x="-9.5392103"
height="12.405126"
width="8.1916437"
id="rect2884"
style="opacity:1;fill:url(#radialGradient4700);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect
y="35.618961"
x="38.209965"
height="12.405126"
width="8.1916437"
id="rect2894"
style="opacity:1;fill:url(#radialGradient4702);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect
y="35.618961"
x="9.5392103"
height="12.405126"
width="28.670753"
id="rect2898"
style="opacity:1;fill:url(#linearGradient4704);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /></g><g
id="g5672"
transform="translate(0,-48.99747)"><path
sodipodi:nodetypes="ccccccccccccc"
id="rect2010"
d="M 4.5182287,80.500013 H 43.481768 c 0.564099,0 1.018229,0.45413 1.018229,1.018229 v 2.963543 c 0,1.315584 -0.450231,3.018228 -2.455729,3.018228 L 40.5,87.5 v 1 h -33 v -1 l -1.8567713,1.3e-5 c -1.2712053,0 -2.1432282,-0.884627 -2.1432282,-2.255665 v -3.726106 c 0,-0.564099 0.4541297,-1.018229 1.0182282,-1.018229 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient5655);fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><path
transform="translate(0,50)"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2973);stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="m 4.59375,31.59375 v 3.729743 c 0,0.599619 0.3756505,1.104854 0.8863276,1.104854 H 42.426407 c 0.512469,0 0.979843,-0.507235 0.979843,-1.016466 V 31.59375 Z"
id="path2076"
sodipodi:nodetypes="ccccccc" /><g
transform="translate(0,50)"
style="opacity:0.5"
id="g4706"><path
style="opacity:0.109524;fill:url(#linearGradient4711);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 26.144738,32.088747 c 0,0 -1.502602,5.533939 -3.226175,5.911253 0,0 6.231378,-0.125771 6.231378,-0.125771 1.387072,-0.317461 3.358758,-5.785482 3.358758,-5.785482 z"
id="path2907"
sodipodi:nodetypes="ccccc"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" /><path
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
sodipodi:nodetypes="ccccc"
id="path2892"
d="m 14.769738,32 c 0,0 -1.502602,5.533939 -3.226175,5.911253 0,0 6.231378,-0.125771 6.231378,-0.125771 C 19.162013,37.468021 21.133699,32 21.133699,32 Z"
style="opacity:0.109524;fill:url(#linearGradient4713);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
sodipodi:nodetypes="ccccc"
id="path2896"
d="m 34.886139,32 c 0,0 -2.212224,5.328458 -3.108503,5.691761 0,0 2.899969,-0.121101 2.899969,-0.121101 C 35.402697,37.264987 37.8125,32 37.8125,32 Z"
style="opacity:0.109524;fill:url(#linearGradient4715);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></g><path
style="fill:url(#radialGradient5748);fill-opacity:1;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
d="m 11.693127,10.498788 h 24.572566 c 1.68417,0 2.396517,0.117479 3.040019,2.385005 l 5.074491,17.881119 c 0.501024,1.765471 -1.355848,2.735101 -3.040018,2.735101 H 6.6186312 c -1.868408,0 -3.4893833,-1.181417 -3.0400182,-2.735101 L 8.8290448,12.611497 c 0.5683008,-1.964905 1.1799122,-2.112709 2.8640822,-2.112709 z"
id="rect1879"
sodipodi:nodetypes="cczzcczzc"
inkscape:export-filename="/home/lapo/Desktop/uhm.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" /><path
sodipodi:type="inkscape:offset"
inkscape:radius="-0.5"
inkscape:original="M 11.6875 10.5 C 10.00333 10.5 9.4120513 10.660095 8.84375 12.625 L 3.59375 30.75 C 3.1443849 32.303684 4.7565918 33.500002 6.625 33.5 L 41.34375 33.5 C 43.02792 33.5 44.876024 32.515471 44.375 30.75 L 39.3125 12.875 C 38.668998 10.607474 37.965419 10.5 36.28125 10.5 L 11.6875 10.5 z "
style="display:inline;opacity:0.462406;fill:url(#linearGradient7658);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
id="path5806"
d="m 11.6875,11 c -0.826242,0 -1.28475,0.05742 -1.5625,0.242188 -0.2777497,0.184768 -0.5284825,0.580009 -0.8007812,1.521484 l -5.2500001,18.125 c -0.1708248,0.590628 0.021709,1.039316 0.4902344,1.4375 C 5.0329784,32.724356 5.7975106,33.000001 6.625,33 h 34.71875 c 0.744655,0 1.538941,-0.232575 2.03125,-0.609375 0.492309,-0.3768 0.719298,-0.799984 0.519531,-1.503906 l -5.0625,-17.875 C 38.52278,11.922001 38.224454,11.462814 37.910156,11.253906 37.595859,11.044998 37.112699,11 36.28125,11 Z" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8623);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8621"
transform="matrix(-2.628602,0,0,1.777765,27.79309,-23.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><path
style="fill:none;fill-opacity:1;stroke:url(#linearGradient5790);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 16.110953,12.552805 c -0.573581,0 -1.02837,0.431821 -1.02837,0.989859 l -0.940223,3.230801 c -2.859962,1.276514 -4.6423552,3.099073 -4.6423552,5.123976 0,3.856957 6.4790242,6.987239 14.4853222,6.98724 8.006296,0 14.514705,-3.130284 14.514704,-6.98724 0,-2.039034 -1.835591,-3.875388 -4.730501,-5.153089 l -0.940224,-3.201688 c 0,-0.558038 -0.454788,-0.989859 -1.02837,-0.989859 z"
id="path2784"
sodipodi:nodetypes="cccssscccc" /><path
style="display:inline;fill:none;fill-opacity:1;stroke:url(#linearGradient3474);stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
d="m 11.6875,11.500005 c -0.803124,0 -1.097168,0.07051 -1.21875,0.155556 -0.121582,0.08504 -0.357707,0.40212 -0.6875,1.306667 l -5.25,18.137786 c -0.1337204,0.366765 -0.054827,0.533865 0.3125,0.84 0.3673267,0.306136 1.066693,0.56 1.78125,0.560001 h 34.71875 c 0.639793,0 1.393345,-0.237954 1.78125,-0.52889 0.387905,-0.290935 0.488311,-0.382809 0.3125,-0.871111 L 38.375,13.242228 c -0.377206,-1.04766 -0.68208,-1.439297 -0.84375,-1.555556 -0.16167,-0.116259 -0.443711,-0.186667 -1.25,-0.186667 z"
id="path3394"
sodipodi:nodetypes="csccsccsccscc" /><g
id="g5657"
transform="translate(7,-1)"
style="opacity:0.302857"><rect
ry="0.74712253"
rx="0.75130093"
y="35.500008"
x="18.499996"
height="1.9999924"
width="14.000004"
id="rect5641"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#eeeeec;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="19"
height="1"
width="1"
id="rect5645"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="22"
height="1"
width="1"
id="rect5647"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="24"
height="1"
width="1"
id="rect5649"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="26"
height="1"
width="1"
id="rect5651"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /><rect
y="36"
x="29"
height="1"
width="2"
id="rect5653"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /></g><path
sodipodi:type="inkscape:offset"
inkscape:radius="-0.44194174"
inkscape:original="M 16.125 12.5625 C 15.55142 12.5625 15.09375 12.973212 15.09375 13.53125 L 14.15625 16.78125 C 11.296288 18.057765 9.5 19.881347 9.5 21.90625 C 9.5 25.763206 15.993702 28.874999 24 28.875 C 32.006296 28.874999 38.500001 25.763206 38.5 21.90625 C 38.5 19.867215 36.67616 18.027701 33.78125 16.75 L 32.84375 13.53125 C 32.843748 12.973212 32.386082 12.5625 31.8125 12.5625 L 16.125 12.5625 z "
style="display:inline;fill:url(#linearGradient5865);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path5857"
d="m 16.125,13.003906 c -0.362612,0 -0.589844,0.210942 -0.589844,0.527344 a 0.44198593,0.44198593 0 0 1 -0.01758,0.123047 l -0.9375,3.25 a 0.44198593,0.44198593 0 0 1 -0.24414,0.28125 c -1.389903,0.620369 -2.504368,1.368471 -3.25586,2.177734 -0.751491,0.809263 -1.1386718,1.661199 -1.1386717,2.542969 0,1.680455 1.4530107,3.311153 3.9980467,4.533203 2.545037,1.22205 6.114654,1.99414 10.060547,1.994141 3.945892,-1e-6 7.51551,-0.772091 10.060547,-1.994141 2.545037,-1.22205 3.998047,-2.852748 3.998047,-4.533203 0,-0.887751 -0.391823,-1.747213 -1.154297,-2.5625 -0.762474,-0.815287 -1.893636,-1.568394 -3.300781,-2.189453 a 0.44198593,0.44198593 0 0 1 -0.246094,-0.28125 l -0.9375,-3.21875 a 0.44198593,0.44198593 0 0 1 -0.01758,-0.123047 c -10e-7,-0.316404 -0.22723,-0.527344 -0.589844,-0.527344 z" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.9;fill:#000000;fill-opacity:0.0530303;fill-rule:nonzero;stroke:url(#linearGradient8603);stroke-width:2.52015;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8595"
transform="matrix(0.449978,0,0,0.349909,16.36363,12.21469)"
cx="16.970562"
cy="25.107418"
rx="7.7781744"
ry="4.2868347" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8619);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8611"
transform="matrix(1.411772,0,0,0.969697,-3.014767,0.848485)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8664);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.462594;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8660"
transform="matrix(-2.628602,0,0,1.777765,60.79309,-23.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8666);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8662"
transform="matrix(1.411772,0,0,0.969697,29.98523,0.848485)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8676);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8668"
transform="matrix(-2.628602,0,0,1.777765,31.79309,-40.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8678);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8670"
transform="matrix(1.411772,0,0,0.969697,0.985233,-16.15152)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8680);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8672"
transform="matrix(-2.628602,0,0,1.777765,56.3029,-40.77739)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><ellipse
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:url(#radialGradient8682);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.4;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="path8674"
transform="matrix(1.411772,0,0,0.969697,25.49504,-16.15152)"
cx="7.625"
cy="30.578125"
rx="0.53125"
ry="0.515625" /><path
style="opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient8716);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 40.328109,30.261401 0.874999,0.430332"
id="path8700" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9605);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.330186,30.695906 8.201031,30.257228"
id="path9603" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9649);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.263531,13.446473 0.972937,-0.06482"
id="path9647" /><path
style="display:inline;opacity:0.4;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9654);stroke-width:0.3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 36.124038,13.147874 0.314427,0.688634"
id="path9652" /><rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.12;fill:url(#linearGradient2992);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.681836;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
id="rect2984"
width="32.03125"
height="1"
x="8"
y="38" /><path
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.12;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2910);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none"
d="M 10.460155,15.082355 6.8513979,27.675762 C 8.2982685,28.375511 10.625,29.167061 10.429825,31.533131 H 37.299883 C 37.869398,29.640915 39.875,28.375 41.34614,28.25 L 37.498106,15.082355 32.350135,12.523347 H 14.318912 Z"
id="path1997"
sodipodi:nodetypes="ccccccccc" /><path
sodipodi:nodetypes="ccccc"
id="path2912"
d="m 7.9763979,27.050762 c 1.4468706,0.699749 3.1789321,1.433241 3.4256991,3.357369 H 36.857941 C 37.427456,28.515915 38.875,27.5 40.34614,27.375 Z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.834286;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2922);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 40 KiB

View file

@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="169.33115mm"
height="169.59981mm"
viewBox="0 0 169.33115 169.59981"
version="1.1"
id="svg1"
inkscape:export-filename="C.svg"
inkscape:export-xdpi="96.000015"
inkscape:export-ydpi="96.000015"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="colorless.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.71535494"
inkscape:cx="397.00572"
inkscape:cy="536.09751"
inkscape:window-width="1853"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:page
x="0"
y="0"
width="169.33115"
height="169.59981"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-20.334425,-63.700102)">
<ellipse
style="fill:#cccccc;stroke:#000000;stroke-width:0.165333;stroke-linecap:round"
id="path1"
cx="-30.617094"
cy="179.22736"
transform="matrix(0.70654605,-0.70766707,0.70654608,0.70766704,0,0)"
rx="84.650612"
ry="84.65062" />
<rect
style="fill:none;stroke:#000000;stroke-width:14.5003;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none"
id="rect1"
width="84.683701"
height="84.683769"
x="-221.60611"
y="-73.174698"
ry="0.084683768"
transform="matrix(-0.7073973,-0.70681615,0.7073973,-0.70681615,0,0)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1,587 +0,0 @@
#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 <QDateTime>
#include <QLineEdit>
#include <QMessageBox>
#include <QThread>
#include <libcockatrice/network/client/remote/remote_client.h>
#include <libcockatrice/protocol/pb/response.pb.h>
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<QString> mf) {
onLoginError(static_cast<int>(r), rs, et, mf);
});
connect(remoteClient, &RemoteClient::registerError, this,
[this](Response::ResponseCode r, QString rs, quint32 et) { onRegisterError(static_cast<int>(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<unsigned int>(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<unsigned int>(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<unsigned int>(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<QString> &missingFeatures)
{
switch (static_cast<Response::ResponseCode>(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<Response::ResponseCode>(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<unsigned int>(dlg.getPort()),
dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
}
}
void ConnectionController::onPromptForgotPasswordChallenge()
{
DlgForgotPasswordChallenge dlg(dialogParent);
if (dlg.exec()) {
remoteClient->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast<unsigned int>(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.") + "<br/>";
QStringList rules = in.split(QChar('|'));
if (rules.size() == 7 || rules.size() == 9) {
out += tr("Your username must respect these rules:") + "<ul>";
out += "<li>" + tr("is %1 - %2 characters long").arg(rules.at(0)).arg(rules.at(1)) + "</li>";
out += "<li>" + tr("can %1 contain lowercase characters").arg((rules.at(2).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
out += "<li>" + tr("can %1 contain uppercase characters").arg((rules.at(3).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
out +=
"<li>" + tr("can %1 contain numeric characters").arg((rules.at(4).toInt() > 0) ? "" : tr("NOT")) + "</li>";
if (rules.at(6).size() > 0) {
out += "<li>" + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "</li>";
}
out += "<li>" +
tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) +
"</li>";
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 += "<li>" + line + "</li>";
}
} else {
out += "<li>" + tr("can not contain any of the following words: %1").arg(words) + "</li>";
}
}
if (rules.at(8).size() > 0) {
out += "<li>" +
tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) +
"</li>";
}
}
out += "</ul>";
} else {
out += tr("You may only use A-Z, a-z, 0-9, _, ., and - in your username.");
}
return out;
}

View file

@ -1,98 +0,0 @@
#ifndef COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
#define COCKATRICE_REMOTE_CONNECTION_CONTROLLER_H
#include "abstract_client.h"
#include <QMessageBox>
#include <QObject>
#include <QString>
#include <QThread>
#include <libcockatrice/protocol/pb/event_connection_closed.pb.h>
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
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<QString> &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

View file

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

View file

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

View file

@ -6,12 +6,12 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QRegularExpression> #include <QRegularExpression>
#include <QUrlQuery> #include <QUrlQuery>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h> #include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h> #include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <version_string.h> #include <version_string.h>
TappedOutInterface::TappedOutInterface(QObject *parent) : QObject(parent) TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
: QObject(parent), cardDatabase(_cardDatabase)
{ {
manager = new QNetworkAccessManager(this); manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished); connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
@ -89,26 +89,22 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck)
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/")); QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING)); 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); manager->post(request, data);
} }
void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard) void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
{ {
auto copyMainOrSide = [&mainboard, &sideboard](const auto node, const auto card) { auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName()); CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
if (!dbCard || dbCard->getIsToken()) { if (!dbCard || dbCard->getIsToken())
return; return;
}
DecklistCardNode *addedCard; DecklistCardNode *addedCard;
if (node->getName() == DECK_ZONE_SIDE) { if (node->getName() == DECK_ZONE_SIDE)
addedCard = sideboard.addCard(card->getName(), node->getName(), -1); addedCard = sideboard.addCard(card->getName(), node->getName(), -1);
} else { else
addedCard = mainboard.addCard(card->getName(), node->getName(), -1); addedCard = mainboard.addCard(card->getName(), node->getName(), -1);
}
addedCard->setNumber(card->getNumber()); addedCard->setNumber(card->getNumber());
}; };

View file

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

View file

@ -1,8 +1,8 @@
/** /**
* @file deck_link_to_api_transformer.h * @file deck_link_to_api_transformer.h
* @ingroup ApiInterfaces * @ingroup ApiInterfaces
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef DECK_LINK_TO_API_TRANSFORMER_H #ifndef DECK_LINK_TO_API_TRANSFORMER_H
#define DECK_LINK_TO_API_TRANSFORMER_H #define DECK_LINK_TO_API_TRANSFORMER_H

View file

@ -1,8 +1,8 @@
/** /**
* @file interface_json_deck_parser.h * @file interface_json_deck_parser.h
* @ingroup ApiInterfaces * @ingroup ApiInterfaces
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef INTERFACE_JSON_DECK_PARSER_H #ifndef INTERFACE_JSON_DECK_PARSER_H
#define INTERFACE_JSON_DECK_PARSER_H #define INTERFACE_JSON_DECK_PARSER_H

View file

@ -1,8 +1,8 @@
/** /**
* @file spoiler_background_updater.h * @file spoiler_background_updater.h
* @ingroup Client * @ingroup Client
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef COCKATRICE_SPOILER_DOWNLOADER_H #ifndef COCKATRICE_SPOILER_DOWNLOADER_H
#define COCKATRICE_SPOILER_DOWNLOADER_H #define COCKATRICE_SPOILER_DOWNLOADER_H

View file

@ -1,8 +1,8 @@
/** /**
* @file client_update_checker.h * @file client_update_checker.h
* @ingroup ClientUpdate * @ingroup ClientUpdate
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef CLIENT_UPDATE_CHECKER_H #ifndef CLIENT_UPDATE_CHECKER_H
#define CLIENT_UPDATE_CHECKER_H #define CLIENT_UPDATE_CHECKER_H

View file

@ -129,9 +129,8 @@ void StableReleaseChannel::releaseListFinished()
return; return;
} }
if (!lastRelease) { if (!lastRelease)
lastRelease = new Release; lastRelease = new Release;
}
lastRelease->setName(resultMap["name"].toString()); lastRelease->setName(resultMap["name"].toString());
lastRelease->setDescriptionUrl(resultMap["html_url"].toString()); lastRelease->setDescriptionUrl(resultMap["html_url"].toString());
@ -247,9 +246,8 @@ void BetaReleaseChannel::releaseListFinished()
return; return;
} }
if (lastRelease == nullptr) { if (lastRelease == nullptr)
lastRelease = new Release; lastRelease = new Release;
}
lastRelease->setCommitHash(resultMap["target_commitish"].toString()); lastRelease->setCommitHash(resultMap["target_commitish"].toString());
lastRelease->setPublishDate(resultMap["published_at"].toDate()); lastRelease->setPublishDate(resultMap["published_at"].toDate());

View file

@ -1,8 +1,8 @@
/** /**
* @file release_channel.h * @file release_channel.h
* @ingroup ClientUpdate * @ingroup ClientUpdate
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef RELEASECHANNEL_H #ifndef RELEASECHANNEL_H
#define RELEASECHANNEL_H #define RELEASECHANNEL_H

View file

@ -10,9 +10,8 @@ UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(
void UpdateDownloader::beginDownload(QUrl downloadUrl) void UpdateDownloader::beginDownload(QUrl downloadUrl)
{ {
// Save the original URL because we need it for the filename // Save the original URL because we need it for the filename
if (originalUrl.isEmpty()) { if (originalUrl.isEmpty())
originalUrl = downloadUrl; originalUrl = downloadUrl;
}
response = netMan->get(QNetworkRequest(downloadUrl)); response = netMan->get(QNetworkRequest(downloadUrl));
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished); connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
@ -22,9 +21,8 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl)
void UpdateDownloader::downloadError(QNetworkReply::NetworkError) void UpdateDownloader::downloadError(QNetworkReply::NetworkError)
{ {
if (response == nullptr) { if (response == nullptr)
return; return;
}
emit error(response->errorString().toUtf8()); emit error(response->errorString().toUtf8());
} }

View file

@ -1,8 +1,8 @@
/** /**
* @file update_downloader.h * @file update_downloader.h
* @ingroup ClientUpdate * @ingroup ClientUpdate
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef COCKATRICE_UPDATEDOWNLOADER_H #ifndef COCKATRICE_UPDATEDOWNLOADER_H
#define COCKATRICE_UPDATEDOWNLOADER_H #define COCKATRICE_UPDATEDOWNLOADER_H

View file

@ -1,7 +1,5 @@
#include "cache_settings.h" #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 "../network/update/client/release_channel.h"
#include "card_counter_settings.h" #include "card_counter_settings.h"
#include "version_string.h" #include "version_string.h"
@ -26,11 +24,10 @@ SettingsCache &SettingsCache::instance()
QString SettingsCache::getDataPath() QString SettingsCache::getDataPath()
{ {
if (isPortableBuild) { if (isPortableBuild)
return qApp->applicationDirPath() + "/data"; return qApp->applicationDirPath() + "/data";
} else { else
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
}
} }
QString SettingsCache::getSettingsPath() QString SettingsCache::getSettingsPath()
@ -40,11 +37,10 @@ QString SettingsCache::getSettingsPath()
QString SettingsCache::getCachePath() const QString SettingsCache::getCachePath() const
{ {
if (isPortableBuild) { if (isPortableBuild)
return qApp->applicationDirPath() + "/cache"; return qApp->applicationDirPath() + "/cache";
} else { else
return QStandardPaths::writableLocation(QStandardPaths::CacheLocation); return QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
}
} }
QString SettingsCache::getNetworkCachePath() const QString SettingsCache::getNetworkCachePath() const
@ -54,17 +50,14 @@ QString SettingsCache::getNetworkCachePath() const
void SettingsCache::translateLegacySettings() void SettingsCache::translateLegacySettings()
{ {
if (isPortableBuild) { if (isPortableBuild)
return; return;
}
// Layouts // Layouts
QFile layoutFile(getSettingsPath() + "layouts/deckLayout.ini"); QFile layoutFile(getSettingsPath() + "layouts/deckLayout.ini");
if (layoutFile.exists()) { if (layoutFile.exists())
if (layoutFile.copy(getSettingsPath() + "layouts.ini")) { if (layoutFile.copy(getSettingsPath() + "layouts.ini"))
layoutFile.remove(); layoutFile.remove();
}
}
QStringList usedKeys; QStringList usedKeys;
QSettings legacySetting; QSettings legacySetting;
@ -123,11 +116,10 @@ void SettingsCache::translateLegacySettings()
gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool()); gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool());
gameFilters().setMinPlayers(legacySetting.value("min_players").toInt()); 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()); 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 gameFilters().setMaxPlayers(99); // This prevents a bug where no games will show if max was not set before
}
QStringList allFilters = legacySetting.allKeys(); QStringList allFilters = legacySetting.allKeys();
for (int i = 0; i < allFilters.size(); ++i) { for (int i = 0; i < allFilters.size(); ++i) {
@ -143,9 +135,8 @@ void SettingsCache::translateLegacySettings()
QStringList allLegacyKeys = legacySetting.allKeys(); QStringList allLegacyKeys = legacySetting.allKeys();
for (int i = 0; i < allLegacyKeys.size(); ++i) { for (int i = 0; i < allLegacyKeys.size(); ++i) {
if (usedKeys.contains(allLegacyKeys.at(i))) { if (usedKeys.contains(allLegacyKeys.at(i)))
continue; continue;
}
settings->setValue(allLegacyKeys.at(i), legacySetting.value(allLegacyKeys.at(i))); settings->setValue(allLegacyKeys.at(i), legacySetting.value(allLegacyKeys.at(i)));
} }
} }
@ -156,9 +147,8 @@ QString SettingsCache::getSafeConfigPath(QString configEntry, QString defaultPat
// if the config settings is empty or refers to a not-existing folder, // if the config settings is empty or refers to a not-existing folder,
// ensure that the defaut path exists and return it // ensure that the defaut path exists and return it
if (tmp.isEmpty() || !QDir(tmp).exists()) { if (tmp.isEmpty() || !QDir(tmp).exists()) {
if (!QDir().mkpath(defaultPath)) { if (!QDir().mkpath(defaultPath))
qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath; qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath;
}
tmp = defaultPath; tmp = defaultPath;
} }
return tmp; return tmp;
@ -169,9 +159,8 @@ QString SettingsCache::getSafeConfigFilePath(QString configEntry, QString defaul
QString tmp = settings->value(configEntry).toString(); QString tmp = settings->value(configEntry).toString();
// if the config settings is empty or refers to a not-existing file, // if the config settings is empty or refers to a not-existing file,
// return the default Path // return the default Path
if (!QFile::exists(tmp) || tmp.isEmpty()) { if (!QFile::exists(tmp) || tmp.isEmpty())
tmp = std::move(defaultPath); tmp = std::move(defaultPath);
}
return tmp; return tmp;
} }
@ -179,9 +168,8 @@ SettingsCache::SettingsCache()
{ {
// first, figure out if we are running in portable mode // first, figure out if we are running in portable mode
isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat"); isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
if (isPortableBuild) { if (isPortableBuild)
qCInfo(SettingsCacheLog) << "Portable mode enabled"; qCInfo(SettingsCacheLog) << "Portable mode enabled";
}
// define a dummy context that will be used where needed // define a dummy context that will be used where needed
QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
@ -201,9 +189,8 @@ SettingsCache::SettingsCache()
cardCounterSettings = new CardCounterSettings(settingsPath, this); cardCounterSettings = new CardCounterSettings(settingsPath, this);
if (!QFile(settingsPath + "global.ini").exists()) { if (!QFile(settingsPath + "global.ini").exists())
translateLegacySettings(); translateLegacySettings();
}
// updates - don't reorder them or their index in the settings won't match // 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. // append channels one by one, or msvc will add them in the wrong order.
@ -224,7 +211,6 @@ SettingsCache::SettingsCache()
startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool(); startupCardUpdateCheckAlwaysUpdate = settings->value("personal/startupCardUpdateCheckAlwaysUpdate", false).toBool();
cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt(); cardUpdateCheckInterval = settings->value("personal/cardUpdateCheckInterval", 7).toInt();
lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate(); lastCardUpdateCheck = settings->value("personal/lastCardUpdateCheck", QDateTime::currentDateTime().date()).toDate();
alwaysEnableNewSets = settings->value("personal/alwaysEnableNewSets", false).toBool();
notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool();
notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool(); notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool();
@ -270,26 +256,14 @@ SettingsCache::SettingsCache()
settings->setValue("personal/pixmapCacheSize", pixmapCacheSize); settings->setValue("personal/pixmapCacheSize", pixmapCacheSize);
settings->setValue("personal/picturedownloadhq", false); settings->setValue("personal/picturedownloadhq", false);
settings->setValue("revert/pixmapCacheSize", true); settings->setValue("revert/pixmapCacheSize", true);
} else { } else
pixmapCacheSize = settings->value("personal/pixmapCacheSize", PIXMAPCACHE_SIZE_DEFAULT).toInt(); pixmapCacheSize = settings->value("personal/pixmapCacheSize", PIXMAPCACHE_SIZE_DEFAULT).toInt();
}
// sanity check // sanity check
if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX) { if (pixmapCacheSize < PIXMAPCACHE_SIZE_MIN || pixmapCacheSize > PIXMAPCACHE_SIZE_MAX)
pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT; pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
}
networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt(); networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt();
redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt(); redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt();
cardPictureLoaderCacheMethod =
settings
->value("personal/cardPictureLoaderCacheMethod",
static_cast<int>(CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE))
.toInt();
localCardImageStorageNamingScheme =
settings
->value("personal/localCardImageStorageNamingScheme",
static_cast<int>(CardPictureLoaderLocalSchemes::NamingScheme::Set_Folder_Name_Set_Collector))
.toInt();
picDownload = settings->value("personal/picturedownload", true).toBool(); picDownload = settings->value("personal/picturedownload", true).toBool();
showStatusBar = settings->value("personal/showStatusBar", false).toBool(); showStatusBar = settings->value("personal/showStatusBar", false).toBool();
@ -388,7 +362,6 @@ SettingsCache::SettingsCache()
ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool(); ignoreUnregisteredUsers = settings->value("chat/ignore_unregistered", false).toBool();
ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool(); ignoreUnregisteredUserMessages = settings->value("chat/ignore_unregistered_messages", false).toBool();
ignoreNonBuddyUserMessages = settings->value("chat/ignore_nonbuddy_messages", false).toBool();
scaleCards = settings->value("cards/scaleCards", true).toBool(); scaleCards = settings->value("cards/scaleCards", true).toBool();
verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt(); verticalCardOverlapPercent = settings->value("cards/verticalCardOverlapPercent", 33).toInt();
@ -796,9 +769,8 @@ void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize)
void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards) void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards)
{ {
if (includeRebalancedCards == _includeRebalancedCards) { if (includeRebalancedCards == _includeRebalancedCards)
return; return;
}
includeRebalancedCards = _includeRebalancedCards; includeRebalancedCards = _includeRebalancedCards;
settings->setValue("cards/includerebalancedcards", includeRebalancedCards); settings->setValue("cards/includerebalancedcards", includeRebalancedCards);
@ -1118,12 +1090,6 @@ void SettingsCache::setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignore
settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages); settings->setValue("chat/ignore_unregistered_messages", ignoreUnregisteredUserMessages);
} }
void SettingsCache::setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages)
{
ignoreNonBuddyUserMessages = static_cast<bool>(_ignoreNonBuddyUserMessages);
settings->setValue("chat/ignore_nonbuddy_messages", ignoreNonBuddyUserMessages);
}
void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize) void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
{ {
pixmapCacheSize = _pixmapCacheSize; pixmapCacheSize = _pixmapCacheSize;
@ -1131,13 +1097,6 @@ void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
emit pixmapCacheSizeChanged(pixmapCacheSize); emit pixmapCacheSizeChanged(pixmapCacheSize);
} }
void SettingsCache::setCardImageCacheMethod(const CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod)
{
cardPictureLoaderCacheMethod = static_cast<int>(_cardImageCachingMethod);
settings->setValue("personal/cardPictureLoaderCacheMethod", cardPictureLoaderCacheMethod);
emit cardPictureLoaderCacheMethodChanged(cardPictureLoaderCacheMethod);
}
void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize) void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize)
{ {
networkCacheSize = _networkCacheSize; networkCacheSize = _networkCacheSize;
@ -1152,14 +1111,6 @@ void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl)
emit redirectCacheTtlChanged(redirectCacheTtl); emit redirectCacheTtlChanged(redirectCacheTtl);
} }
void SettingsCache::setLocalCardImageStorageNamingScheme(
const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme)
{
localCardImageStorageNamingScheme = static_cast<int>(_localCardImageStorageNamingScheme);
settings->setValue("personal/localCardImageStorageNamingScheme", localCardImageStorageNamingScheme);
emit localCardImageStorageNamingSchemeChanged(localCardImageStorageNamingScheme);
}
void SettingsCache::setClientID(const QString &_clientID) void SettingsCache::setClientID(const QString &_clientID)
{ {
clientID = _clientID; clientID = _clientID;
@ -1295,12 +1246,6 @@ void SettingsCache::setLastCardUpdateCheck(QDate value)
settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck); settings->setValue("personal/lastCardUpdateCheck", lastCardUpdateCheck);
} }
void SettingsCache::setAlwaysEnableNewSets(bool value)
{
alwaysEnableNewSets = value;
settings->setValue("personal/alwaysEnableNewSets", alwaysEnableNewSets);
}
void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
{ {
rememberGameSettings = _rememberGameSettings; rememberGameSettings = _rememberGameSettings;
@ -1358,9 +1303,8 @@ void SettingsCache::setMaxFontSize(int _max)
void SettingsCache::setRoundCardCorners(bool _roundCardCorners) void SettingsCache::setRoundCardCorners(bool _roundCardCorners)
{ {
if (_roundCardCorners == roundCardCorners) { if (_roundCardCorners == roundCardCorners)
return; return;
}
roundCardCorners = _roundCardCorners; roundCardCorners = _roundCardCorners;
settings->setValue("cards/roundcardcorners", _roundCardCorners); settings->setValue("cards/roundcardcorners", _roundCardCorners);

View file

@ -1,14 +1,12 @@
/** /**
* @file cache_settings.h * @file cache_settings.h
* @ingroup Settings * @ingroup Settings
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef SETTINGSCACHE_H #ifndef SETTINGSCACHE_H
#define 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 "shortcuts_settings.h"
#include <QDate> #include <QDate>
@ -183,12 +181,9 @@ signals:
void soundThemeChanged(); void soundThemeChanged();
void ignoreUnregisteredUsersChanged(); void ignoreUnregisteredUsersChanged();
void ignoreUnregisteredUserMessagesChanged(); void ignoreUnregisteredUserMessagesChanged();
void ignoreNonBuddyUserMessagesChanged();
void pixmapCacheSizeChanged(int newSizeInMBs); void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs); void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl); void redirectCacheTtlChanged(int newTtl);
void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod);
void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme);
void masterVolumeChanged(int value); void masterVolumeChanged(int value);
void chatMentionCompleterChanged(); void chatMentionCompleterChanged();
void downloadSpoilerTimeIndexChanged(); void downloadSpoilerTimeIndexChanged();
@ -221,7 +216,6 @@ private:
bool checkCardUpdatesOnStartup; bool checkCardUpdatesOnStartup;
int cardUpdateCheckInterval; int cardUpdateCheckInterval;
QDate lastCardUpdateCheck; QDate lastCardUpdateCheck;
bool alwaysEnableNewSets;
bool notifyAboutUpdates; bool notifyAboutUpdates;
bool notifyAboutNewVersion; bool notifyAboutNewVersion;
bool showTipsOnStartup; bool showTipsOnStartup;
@ -295,7 +289,6 @@ private:
QString soundThemeName; QString soundThemeName;
bool ignoreUnregisteredUsers; bool ignoreUnregisteredUsers;
bool ignoreUnregisteredUserMessages; bool ignoreUnregisteredUserMessages;
bool ignoreNonBuddyUserMessages;
QString picUrl; QString picUrl;
QString picUrlFallback; QString picUrlFallback;
QString clientID; QString clientID;
@ -309,8 +302,6 @@ private:
int pixmapCacheSize; int pixmapCacheSize;
int networkCacheSize; int networkCacheSize;
int redirectCacheTtl; int redirectCacheTtl;
int cardPictureLoaderCacheMethod;
int localCardImageStorageNamingScheme;
bool scaleCards; bool scaleCards;
int verticalCardOverlapPercent; int verticalCardOverlapPercent;
bool showMessagePopups; bool showMessagePopups;
@ -511,10 +502,6 @@ public:
return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() && return getLastCardUpdateCheck().daysTo(QDateTime::currentDateTime().date()) >= getCardUpdateCheckInterval() &&
getLastCardUpdateCheck() != QDateTime::currentDateTime().date(); getLastCardUpdateCheck() != QDateTime::currentDateTime().date();
} }
[[nodiscard]] bool getAlwaysEnableNewSets() const
{
return alwaysEnableNewSets;
}
[[nodiscard]] bool getNotifyAboutUpdates() const override [[nodiscard]] bool getNotifyAboutUpdates() const override
{ {
return notifyAboutUpdates; return notifyAboutUpdates;
@ -790,18 +777,10 @@ public:
{ {
return ignoreUnregisteredUserMessages; return ignoreUnregisteredUserMessages;
} }
[[nodiscard]] bool getIgnoreNonBuddyUserMessages() const
{
return ignoreNonBuddyUserMessages;
}
[[nodiscard]] int getPixmapCacheSize() const [[nodiscard]] int getPixmapCacheSize() const
{ {
return pixmapCacheSize; return pixmapCacheSize;
} }
[[nodiscard]] CardPictureLoaderCacheMethod::CacheMethod getCardPictureLoaderCacheMethod() const
{
return static_cast<CardPictureLoaderCacheMethod::CacheMethod>(cardPictureLoaderCacheMethod);
}
[[nodiscard]] int getNetworkCacheSizeInMB() const [[nodiscard]] int getNetworkCacheSizeInMB() const
{ {
return networkCacheSize; return networkCacheSize;
@ -810,10 +789,6 @@ public:
{ {
return redirectCacheTtl; return redirectCacheTtl;
} }
[[nodiscard]] CardPictureLoaderLocalSchemes::NamingScheme getLocalCardImageStorageNamingScheme() const
{
return static_cast<CardPictureLoaderLocalSchemes::NamingScheme>(localCardImageStorageNamingScheme);
}
[[nodiscard]] bool getScaleCards() const [[nodiscard]] bool getScaleCards() const
{ {
return scaleCards; return scaleCards;
@ -1117,13 +1092,9 @@ public slots:
void setSoundThemeName(const QString &_soundThemeName); void setSoundThemeName(const QString &_soundThemeName);
void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers); void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages); void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
void setIgnoreNonBuddyUserMessages(QT_STATE_CHANGED_T _ignoreNonBuddyUserMessages);
void setPixmapCacheSize(const int _pixmapCacheSize); void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod);
void setNetworkCacheSizeInMB(const int _networkCacheSize); void setNetworkCacheSizeInMB(const int _networkCacheSize);
void setNetworkRedirectCacheTtl(const int _redirectCacheTtl); void setNetworkRedirectCacheTtl(const int _redirectCacheTtl);
void setLocalCardImageStorageNamingScheme(
const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme);
void setCardScaling(const QT_STATE_CHANGED_T _scaleCards); void setCardScaling(const QT_STATE_CHANGED_T _scaleCards);
void setStackCardOverlapPercent(const int _verticalCardOverlapPercent); void setStackCardOverlapPercent(const int _verticalCardOverlapPercent);
void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups); void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups);
@ -1154,7 +1125,6 @@ public slots:
void setStartupCardUpdateCheckAlwaysUpdate(bool value); void setStartupCardUpdateCheckAlwaysUpdate(bool value);
void setCardUpdateCheckInterval(int value); void setCardUpdateCheckInterval(int value);
void setLastCardUpdateCheck(QDate value); void setLastCardUpdateCheck(QDate value);
void setAlwaysEnableNewSets(bool value);
void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate); void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate);
void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion); void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion);
void setUpdateReleaseChannelIndex(int value); void setUpdateReleaseChannelIndex(int value);

View file

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

View file

@ -1,8 +1,8 @@
/** /**
* @file card_counter_settings.h * @file card_counter_settings.h
* @ingroup GameSettings * @ingroup GameSettings
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef CARD_COUNTER_SETTINGS_H #ifndef CARD_COUNTER_SETTINGS_H
#define CARD_COUNTER_SETTINGS_H #define CARD_COUNTER_SETTINGS_H

View file

@ -1,8 +1,8 @@
/** /**
* @file shortcut_treeview.h * @file shortcut_treeview.h
* @ingroup CoreSettings * @ingroup CoreSettings
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef SHORTCUT_TREEVIEW_H #ifndef SHORTCUT_TREEVIEW_H
#define SHORTCUT_TREEVIEW_H #define SHORTCUT_TREEVIEW_H

View file

@ -64,13 +64,8 @@ ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *paren
} }
} }
/** /// PR 5079 changes Textbox/unfocusTextBox to Player/unfocusTextBox and tab_game/aFocusChat to Player/aFocusChat.
* @brief Migrates legacy shortcut key names to current naming scheme. /// A migration is necessary to let players keep their already configured shortcuts.
*
* 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() void ShortcutsSettings::migrateShortcuts()
{ {
if (QFile(settingsFilePath).exists()) { if (QFile(settingsFilePath).exists()) {
@ -241,7 +236,9 @@ bool ShortcutsSettings::isValid(const QString &name, const QString &sequences) c
return findOverlaps(name, sequences).isEmpty(); return findOverlaps(name, sequences).isEmpty();
} }
/** @brief Checks if the shortcut is a shortcut that is active in all windows. */ /**
* Checks if the shortcut is a shortcut that is active in all windows
*/
static bool isAlwaysActiveShortcut(const QString &shortcutName) static bool isAlwaysActiveShortcut(const QString &shortcutName)
{ {
return shortcutName.startsWith("MainWindow") || shortcutName.startsWith("Tabs"); return shortcutName.startsWith("MainWindow") || shortcutName.startsWith("Tabs");

View file

@ -1,8 +1,8 @@
/** /**
* @file shortcuts_settings.h * @file shortcuts_settings.h
* @ingroup CoreSettings * @ingroup CoreSettings
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef SHORTCUTSSETTINGS_H #ifndef SHORTCUTSSETTINGS_H
#define SHORTCUTSSETTINGS_H #define SHORTCUTSSETTINGS_H
@ -501,7 +501,7 @@ private:
{"Player/aUntapAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Untap All"), {"Player/aUntapAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Untap All"),
parseSequenceString("Ctrl+U"), parseSequenceString("Ctrl+U"),
ShortcutGroup::Playing_Area)}, ShortcutGroup::Playing_Area)},
{"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping"), {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Untap"),
parseSequenceString("Alt+U"), parseSequenceString("Alt+U"),
ShortcutGroup::Playing_Area)}, ShortcutGroup::Playing_Area)},
{"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"), {"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"),
@ -513,9 +513,6 @@ private:
{"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card"), {"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card"),
parseSequenceString(""), parseSequenceString(""),
ShortcutGroup::Playing_Area)}, ShortcutGroup::Playing_Area)},
{"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Play Card, Face Down"),
parseSequenceString(""),
ShortcutGroup::Playing_Area)},
{"Player/aAttach", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Attach Card..."), {"Player/aAttach", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Attach Card..."),
parseSequenceString("Ctrl+Alt+A"), parseSequenceString("Ctrl+Alt+A"),
ShortcutGroup::Playing_Area)}, ShortcutGroup::Playing_Area)},
@ -537,9 +534,6 @@ private:
{"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."), {"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."),
parseSequenceString("Alt+N"), parseSequenceString("Alt+N"),
ShortcutGroup::Playing_Area)}, 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"), {"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"),
parseSequenceString("Ctrl+A"), parseSequenceString("Ctrl+A"),
ShortcutGroup::Playing_Area)}, ShortcutGroup::Playing_Area)},
@ -566,9 +560,12 @@ private:
{"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"), {"Player/aMoveToTopLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"),
parseSequenceString(""), parseSequenceString(""),
ShortcutGroup::Move_selected)}, ShortcutGroup::Move_selected)},
{"Player/aMoveToTable", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"), {"Player/aPlayFacedown", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield, Face Down"),
parseSequenceString(""), parseSequenceString(""),
ShortcutGroup::Move_selected)}, ShortcutGroup::Move_selected)},
{"Player/aPlay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aViewHand", {"Player/aViewHand",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)}, ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)},
{"Player/aViewGraveyard", {"Player/aViewGraveyard",
@ -667,9 +664,6 @@ private:
{"Player/aRollDie", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Roll Dice..."), {"Player/aRollDie", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Roll Dice..."),
parseSequenceString("Ctrl+I"), parseSequenceString("Ctrl+I"),
ShortcutGroup::Gameplay)}, 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"), {"Player/aShuffle", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Library"),
parseSequenceString("Ctrl+S"), parseSequenceString("Ctrl+S"),
ShortcutGroup::Gameplay)}, ShortcutGroup::Gameplay)},

View file

@ -105,9 +105,8 @@ QStringMap &SoundEngine::getAvailableThemes()
dir.setPath(SettingsCache::instance().getDataPath() + "/sounds"); dir.setPath(SettingsCache::instance().getDataPath() + "/sounds");
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { 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)); availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
}
} }
// load themes from cockatrice system dir // load themes from cockatrice system dir
@ -122,9 +121,8 @@ QStringMap &SoundEngine::getAvailableThemes()
); );
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) { 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)); availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
}
} }
return availableThemes; return availableThemes;

View file

@ -1,8 +1,8 @@
/** /**
* @file sound_engine.h * @file sound_engine.h
* @ingroup Core * @ingroup Core
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef SOUNDENGINE_H #ifndef SOUNDENGINE_H
#define SOUNDENGINE_H #define SOUNDENGINE_H

View file

@ -88,27 +88,20 @@ static void setupParserRules()
const auto arg = std::any_cast<int>(sv[1]); const auto arg = std::any_cast<int>(sv[1]);
const auto op = std::any_cast<QString>(sv[0]); const auto op = std::any_cast<QString>(sv[0]);
if (op == ">") { if (op == ">")
return [=](const int s) { return s > arg; }; return [=](const int s) { return s > arg; };
} if (op == ">=")
if (op == ">=") {
return [=](const int s) { return s >= arg; }; return [=](const int s) { return s >= arg; };
} if (op == "<")
if (op == "<") {
return [=](const int s) { return s < arg; }; return [=](const int s) { return s < arg; };
} if (op == "<=")
if (op == "<=") {
return [=](const int s) { return s <= arg; }; return [=](const int s) { return s <= arg; };
} if (op == "=")
if (op == "=") {
return [=](const int s) { return s == arg; }; return [=](const int s) { return s == arg; };
} if (op == ":")
if (op == ":") {
return [=](const int s) { return s == arg; }; return [=](const int s) { return s == arg; };
} if (op == "!=")
if (op == "!=") {
return [=](const int s) { return s != arg; }; return [=](const int s) { return s != arg; };
}
return [](int) { return false; }; return [](int) { return false; };
}; };

View file

@ -1,8 +1,8 @@
/** /**
* @file deck_filter_string.h * @file deck_filter_string.h
* @ingroup DeckStorageWidgets * @ingroup DeckStorageWidgets
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef DECK_FILTER_STRING_H #ifndef DECK_FILTER_STRING_H
#define DECK_FILTER_STRING_H #define DECK_FILTER_STRING_H

View file

@ -11,15 +11,13 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
{ {
filterCombo = new QComboBox; filterCombo = new QComboBox;
filterCombo->setObjectName("filterCombo"); 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<CardFilter::Attr>(i)), QVariant(i)); filterCombo->addItem(CardFilter::attrName(static_cast<CardFilter::Attr>(i)), QVariant(i));
}
typeCombo = new QComboBox; typeCombo = new QComboBox;
typeCombo->setObjectName("typeCombo"); 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<CardFilter::Type>(i)), QVariant(i)); typeCombo->addItem(CardFilter::typeName(static_cast<CardFilter::Type>(i)), QVariant(i));
}
QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString()); QPushButton *ok = new QPushButton(QPixmap("theme:icons/increment"), QString());
ok->setObjectName("ok"); ok->setObjectName("ok");
@ -55,9 +53,8 @@ FilterBuilder::~FilterBuilder()
void FilterBuilder::destroyFilter() void FilterBuilder::destroyFilter()
{ {
if (fltr) { if (fltr)
delete fltr; delete fltr;
}
} }
static int comboCurrentIntData(const QComboBox *combo) static int comboCurrentIntData(const QComboBox *combo)
@ -70,9 +67,8 @@ void FilterBuilder::emit_add()
QString txt; QString txt;
txt = edit->text(); txt = edit->text();
if (txt.length() < 1) { if (txt.length() < 1)
return; return;
}
destroyFilter(); destroyFilter();
fltr = new CardFilter(txt, static_cast<CardFilter::Type>(comboCurrentIntData(typeCombo)), fltr = new CardFilter(txt, static_cast<CardFilter::Type>(comboCurrentIntData(typeCombo)),

View file

@ -1,8 +1,8 @@
/** /**
* @file filter_builder.h * @file filter_builder.h
* @ingroup CardDatabaseModelFilters * @ingroup CardDatabaseModelFilters
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef FILTERBUILDER_H #ifndef FILTERBUILDER_H
#define FILTERBUILDER_H #define FILTERBUILDER_H

View file

@ -23,9 +23,8 @@ void FilterTreeModel::proxyBeginInsertRow(const FilterTreeNode *node, int i)
int idx; int idx;
idx = node->index(); idx = node->index();
if (idx >= 0) { if (idx >= 0)
beginInsertRows(createIndex(idx, 0, (void *)node), i, i); beginInsertRows(createIndex(idx, 0, (void *)node), i, i);
}
} }
void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int) void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
@ -33,9 +32,8 @@ void FilterTreeModel::proxyEndInsertRow(const FilterTreeNode *node, int)
int idx; int idx;
idx = node->index(); idx = node->index();
if (idx >= 0) { if (idx >= 0)
endInsertRows(); endInsertRows();
}
} }
void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i) void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
@ -43,9 +41,8 @@ void FilterTreeModel::proxyBeginRemoveRow(const FilterTreeNode *node, int i)
int idx; int idx;
idx = node->index(); idx = node->index();
if (idx >= 0) { if (idx >= 0)
beginRemoveRows(createIndex(idx, 0, (void *)node), i, i); beginRemoveRows(createIndex(idx, 0, (void *)node), i, i);
}
} }
void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int) void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
@ -53,9 +50,8 @@ void FilterTreeModel::proxyEndRemoveRow(const FilterTreeNode *node, int)
int idx; int idx;
idx = node->index(); idx = node->index();
if (idx >= 0) { if (idx >= 0)
endRemoveRows(); endRemoveRows();
}
} }
FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
@ -63,14 +59,12 @@ FilterTreeNode *FilterTreeModel::indexToNode(const QModelIndex &idx) const
void *ip; void *ip;
FilterTreeNode *node; FilterTreeNode *node;
if (!idx.isValid()) { if (!idx.isValid())
return fTree; return fTree;
}
ip = idx.internalPointer(); ip = idx.internalPointer();
if (ip == NULL) { if (ip == NULL)
return fTree; return fTree;
}
node = static_cast<FilterTreeNode *>(ip); node = static_cast<FilterTreeNode *>(ip);
return node; return node;
@ -151,16 +145,14 @@ int FilterTreeModel::rowCount(const QModelIndex &parent) const
const FilterTreeNode *node; const FilterTreeNode *node;
int result; int result;
if (parent.column() > 0) { if (parent.column() > 0)
return 0; return 0;
}
node = indexToNode(parent); node = indexToNode(parent);
if (node) { if (node)
result = node->childCount(); result = node->childCount();
} else { else
result = 0; result = 0;
}
return result; return result;
} }
@ -174,17 +166,14 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
{ {
const FilterTreeNode *node; const FilterTreeNode *node;
if (!index.isValid()) { if (!index.isValid())
return QVariant(); return QVariant();
} if (index.column() >= columnCount())
if (index.column() >= columnCount()) {
return QVariant(); return QVariant();
}
node = indexToNode(index); node = indexToNode(index);
if (node == NULL) { if (node == NULL)
return QVariant(); return QVariant();
}
switch (role) { switch (role) {
case Qt::FontRole: case Qt::FontRole:
@ -201,11 +190,10 @@ QVariant FilterTreeModel::data(const QModelIndex &index, int role) const
case Qt::WhatsThisRole: case Qt::WhatsThisRole:
return node->text(); return node->text();
case Qt::CheckStateRole: case Qt::CheckStateRole:
if (node->isEnabled()) { if (node->isEnabled())
return Qt::Checked; return Qt::Checked;
} else { else
return Qt::Unchecked; return Qt::Unchecked;
}
default: default:
return QVariant(); return QVariant();
} }
@ -217,27 +205,22 @@ bool FilterTreeModel::setData(const QModelIndex &index, const QVariant &value, i
{ {
FilterTreeNode *node; FilterTreeNode *node;
if (!index.isValid()) { if (!index.isValid())
return false; return false;
} if (index.column() >= columnCount())
if (index.column() >= columnCount()) {
return false; return false;
} if (role != Qt::CheckStateRole)
if (role != Qt::CheckStateRole) {
return false; return false;
}
node = indexToNode(index); node = indexToNode(index);
if (node == NULL || node == fTree) { if (node == NULL || node == fTree)
return false; return false;
}
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt()); Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
if (state == Qt::Checked) { if (state == Qt::Checked)
node->enable(); node->enable();
} else { else
node->disable(); node->disable();
}
emit dataChanged(index, index); emit dataChanged(index, index);
return true; return true;
@ -248,19 +231,16 @@ Qt::ItemFlags FilterTreeModel::flags(const QModelIndex &index) const
const FilterTreeNode *node; const FilterTreeNode *node;
Qt::ItemFlags result; Qt::ItemFlags result;
if (!index.isValid()) { if (!index.isValid())
return Qt::NoItemFlags; return Qt::NoItemFlags;
}
node = indexToNode(index); node = indexToNode(index);
if (node == NULL) { if (node == NULL)
return Qt::NoItemFlags; return Qt::NoItemFlags;
}
result = Qt::ItemIsEnabled; result = Qt::ItemIsEnabled;
if (node == fTree) { if (node == fTree)
return result; return result;
}
result |= Qt::ItemIsSelectable; result |= Qt::ItemIsSelectable;
result |= Qt::ItemIsUserCheckable; result |= Qt::ItemIsUserCheckable;
@ -272,9 +252,8 @@ QModelIndex FilterTreeModel::nodeIndex(const FilterTreeNode *node, int row, int
{ {
FilterTreeNode *child; FilterTreeNode *child;
if (column > 0 || row >= node->childCount()) { if (column > 0 || row >= node->childCount())
return QModelIndex(); return QModelIndex();
}
child = node->nodeAt(row); child = node->nodeAt(row);
return createIndex(row, column, child); return createIndex(row, column, child);
@ -284,14 +263,12 @@ QModelIndex FilterTreeModel::index(int row, int column, const QModelIndex &paren
{ {
const FilterTreeNode *node; const FilterTreeNode *node;
if (!hasIndex(row, column, parent)) { if (!hasIndex(row, column, parent))
return QModelIndex(); return QModelIndex();
}
node = indexToNode(parent); node = indexToNode(parent);
if (node == NULL) { if (node == NULL)
return QModelIndex(); return QModelIndex();
}
return nodeIndex(node, row, column); return nodeIndex(node, row, column);
} }
@ -302,21 +279,18 @@ QModelIndex FilterTreeModel::parent(const QModelIndex &ind) const
FilterTreeNode *parent; FilterTreeNode *parent;
QModelIndex idx; QModelIndex idx;
if (!ind.isValid()) { if (!ind.isValid())
return QModelIndex(); return QModelIndex();
}
node = indexToNode(ind); node = indexToNode(ind);
if (node == NULL || node == fTree) { if (node == NULL || node == fTree)
return QModelIndex(); return QModelIndex();
}
parent = node->parent(); parent = node->parent();
if (parent) { if (parent) {
int row = parent->index(); int row = parent->index();
if (row < 0) { if (row < 0)
return QModelIndex(); return QModelIndex();
}
idx = createIndex(row, 0, parent); idx = createIndex(row, 0, parent);
return idx; return idx;
} }
@ -330,22 +304,18 @@ bool FilterTreeModel::removeRows(int row, int count, const QModelIndex &parent)
int i, last; int i, last;
last = row + count - 1; last = row + count - 1;
if (!parent.isValid() || count < 1 || row < 0) { if (!parent.isValid() || count < 1 || row < 0)
return false; return false;
}
node = indexToNode(parent); node = indexToNode(parent);
if (node == NULL || last >= node->childCount()) { if (node == NULL || last >= node->childCount())
return false; return false;
}
for (i = 0; i < count; i++) { for (i = 0; i < count; i++)
node->deleteAt(row); node->deleteAt(row);
}
if (node != fTree && node->childCount() < 1) { if (node != fTree && node->childCount() < 1)
return removeRow(parent.row(), parent.parent()); return removeRow(parent.row(), parent.parent());
}
return true; return true;
} }

View file

@ -1,8 +1,8 @@
/** /**
* @file filter_tree_model.h * @file filter_tree_model.h
* @ingroup CardDatabaseModelFilters * @ingroup CardDatabaseModelFilters
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef FILTERTREEMODEL_H #ifndef FILTERTREEMODEL_H
#define FILTERTREEMODEL_H #define FILTERTREEMODEL_H

View file

@ -2,8 +2,8 @@
* @file syntax_help.h * @file syntax_help.h
* @ingroup CardDatabaseModelFilters * @ingroup CardDatabaseModelFilters
* @ingroup DeckStorageWidgets * @ingroup DeckStorageWidgets
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef SEARCH_SYNTAX_HELP_H #ifndef SEARCH_SYNTAX_HELP_H
#define SEARCH_SYNTAX_HELP_H #define SEARCH_SYNTAX_HELP_H

View file

@ -1,9 +1,8 @@
#include "abstract_game.h" #include "abstract_game.h"
#include "../interface/widgets/tabs/tab_game.h" #include "player/player.h"
#include "player/player_logic.h"
AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent) AbstractGame::AbstractGame(TabGame *_tab) : tab(_tab)
{ {
gameMetaInfo = new GameMetaInfo(this); gameMetaInfo = new GameMetaInfo(this);
gameEventHandler = new GameEventHandler(this); gameEventHandler = new GameEventHandler(this);
@ -24,11 +23,10 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const
} }
return gameState->getClients().at(playerId); return gameState->getClients().at(playerId);
} else if (gameState->getClients().isEmpty()) { } else if (gameState->getClients().isEmpty())
return nullptr; return nullptr;
} else { else
return gameState->getClients().first(); return gameState->getClients().first();
}
} }
void AbstractGame::loadReplay(GameReplay *replay) void AbstractGame::loadReplay(GameReplay *replay)
@ -44,15 +42,13 @@ void AbstractGame::setActiveCard(CardItem *card)
CardItem *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const CardItem *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const
{ {
PlayerLogic *player = playerManager->getPlayer(playerId); Player *player = playerManager->getPlayer(playerId);
if (!player) { if (!player)
return nullptr; return nullptr;
}
CardZoneLogic *zone = player->getZones().value(zoneName, 0); CardZoneLogic *zone = player->getZones().value(zoneName, 0);
if (!zone) { if (!zone)
return nullptr; return nullptr;
}
return zone->getCard(cardId); return zone->getCard(cardId);
} }

View file

@ -1,8 +1,8 @@
/** /**
* @file abstract_game.h * @file abstract_game.h
* @ingroup GameLogic * @ingroup GameLogic
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef COCKATRICE_ABSTRACT_GAME_H #ifndef COCKATRICE_ABSTRACT_GAME_H
#define COCKATRICE_ABSTRACT_GAME_H #define COCKATRICE_ABSTRACT_GAME_H
@ -16,19 +16,26 @@
#include <libcockatrice/protocol/pb/game_replay.pb.h> #include <libcockatrice/protocol/pb/game_replay.pb.h>
class CardItem; class CardItem;
class TabGame;
class AbstractGame : public QObject class AbstractGame : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit AbstractGame(QObject *parent); explicit AbstractGame(TabGame *tab);
TabGame *tab;
GameMetaInfo *gameMetaInfo; GameMetaInfo *gameMetaInfo;
GameState *gameState; GameState *gameState;
GameEventHandler *gameEventHandler; GameEventHandler *gameEventHandler;
PlayerManager *playerManager; PlayerManager *playerManager;
CardItem *activeCard; CardItem *activeCard;
TabGame *getTab() const
{
return tab;
}
GameMetaInfo *getGameMetaInfo() GameMetaInfo *getGameMetaInfo()
{ {
return gameMetaInfo; return gameMetaInfo;

View file

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

View file

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

View file

@ -25,12 +25,11 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
setCursor(Qt::ClosedHandCursor); setCursor(Qt::ClosedHandCursor);
setZValue(ZValues::DRAG_ITEM); setZValue(ZValues::DRAG_ITEM);
} }
if (item->getTapped()) { if (item->getTapped())
setTransform(QTransform() setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(90) .rotate(90)
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F)); .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
}
setCacheMode(DeviceCoordinateCache); setCacheMode(DeviceCoordinateCache);

View file

@ -1,8 +1,8 @@
/** /**
* @file abstract_card_drag_item.h * @file abstract_card_drag_item.h
* @ingroup GameGraphicsCards * @ingroup GameGraphicsCards
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef ABSTRACTCARDDRAGITEM_H #ifndef ABSTRACTCARDDRAGITEM_H
#define ABSTRACTCARDDRAGITEM_H #define ABSTRACTCARDDRAGITEM_H

View file

@ -13,7 +13,7 @@
#include <libcockatrice/card/database/card_database.h> #include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h> #include <libcockatrice/card/database/card_database_manager.h>
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id) AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, Player *_owner, int _id)
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0), : ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
bgColor(Qt::transparent), isHovered(false), realZValue(0) bgColor(Qt::transparent), isHovered(false), realZValue(0)
{ {
@ -85,12 +85,7 @@ const CardInfo &AbstractCardItem::getCardInfo() const
void AbstractCardItem::setRealZValue(qreal _zValue) void AbstractCardItem::setRealZValue(qreal _zValue)
{ {
realZValue = _zValue; realZValue = _zValue;
// During hover, zValue is overridden to HOVERED_CARD. Layout operations setZValue(_zValue);
// like reorganizeCards() call setRealZValue() on all cards including the
// hovered one — skip setZValue() here to avoid clobbering the override.
if (!isHovered) {
setZValue(_zValue);
}
} }
QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
@ -131,9 +126,8 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
// don't even spend time trying to load the picture if our size is too small // don't even spend time trying to load the picture if our size is too small
if (translatedSize.width() > 10) { if (translatedSize.width() > 10) {
CardPictureLoader::getPixmap(translatedPixmap, exactCard, translatedSize.toSize()); CardPictureLoader::getPixmap(translatedPixmap, exactCard, translatedSize.toSize());
if (translatedPixmap.isNull()) { if (translatedPixmap.isNull())
paintImage = false; paintImage = false;
}
} else { } else {
paintImage = false; paintImage = false;
} }
@ -158,9 +152,9 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
painter->setBackground(Qt::black); painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode); painter->setBackgroundMode(Qt::OpaqueMode);
QString nameStr; QString nameStr;
if (facedown) { if (facedown)
nameStr = "# " + QString::number(id); nameStr = "# " + QString::number(id);
} else { else {
QString prefix = ""; QString prefix = "";
if (SettingsCache::instance().debug().getShowCardId()) { if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(id) + " "; prefix = "#" + QString::number(id) + " ";
@ -187,12 +181,10 @@ void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *
if (isSelected() || isHovered) { if (isSelected() || isHovered) {
QPen pen; QPen pen;
if (isHovered) { if (isHovered)
pen.setColor(Qt::yellow); pen.setColor(Qt::yellow);
} if (isSelected())
if (isSelected()) {
pen.setColor(Qt::red); pen.setColor(Qt::red);
}
pen.setWidth(0); // Cosmetic pen pen.setWidth(0); // Cosmetic pen
painter->setPen(pen); painter->setPen(pen);
painter->drawPath(shape()); painter->drawPath(shape());
@ -218,20 +210,11 @@ void AbstractCardItem::setCardRef(const CardRef &_cardRef)
void AbstractCardItem::setHovered(bool _hovered) void AbstractCardItem::setHovered(bool _hovered)
{ {
if (isHovered == _hovered) { if (isHovered == _hovered)
return; return;
}
if (_hovered) { if (_hovered)
processHoverEvent(); processHoverEvent();
} else {
// Mark the hovered card's current scene footprint dirty so overlapped
// sibling zones (e.g. StackZone) repaint after the card moves away.
if (scene()) {
scene()->update(sceneBoundingRect());
}
}
isHovered = _hovered; isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue); setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1); setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
@ -282,14 +265,13 @@ void AbstractCardItem::cacheBgColor()
void AbstractCardItem::setTapped(bool _tapped, bool canAnimate) void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
{ {
if (tapped == _tapped) { if (tapped == _tapped)
return; return;
}
tapped = _tapped; tapped = _tapped;
if (SettingsCache::instance().getTapAnimation() && canAnimate) { if (SettingsCache::instance().getTapAnimation() && canAnimate)
static_cast<GameScene *>(scene())->registerAnimationItem(this); static_cast<GameScene *>(scene())->registerAnimationItem(this);
} else { else {
tapAngle = tapped ? 90 : 0; tapAngle = tapped ? 90 : 0;
setTransform(QTransform() setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F) .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
@ -315,19 +297,17 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
scene()->clearSelection(); scene()->clearSelection();
setSelected(true); setSelected(true);
} }
if (event->button() == Qt::LeftButton) { if (event->button() == Qt::LeftButton)
setCursor(Qt::ClosedHandCursor); setCursor(Qt::ClosedHandCursor);
} else if (event->button() == Qt::MiddleButton) { else if (event->button() == Qt::MiddleButton)
emit showCardInfoPopup(event->screenPos(), cardRef); emit showCardInfoPopup(event->screenPos(), cardRef);
}
event->accept(); event->accept();
} }
void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
if (event->button() == Qt::MiddleButton) { if (event->button() == Qt::MiddleButton)
emit deleteCardInfoPopup(cardRef.name); emit deleteCardInfoPopup(cardRef.name);
}
// This function ensures the parent function doesn't mess around with our selection. // This function ensures the parent function doesn't mess around with our selection.
event->accept(); event->accept();
@ -343,7 +323,6 @@ QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change,
if (change == ItemSelectedHasChanged) { if (change == ItemSelectedHasChanged) {
update(); update();
return value; return value;
} else { } else
return ArrowTarget::itemChange(change, value); return ArrowTarget::itemChange(change, value);
}
} }

View file

@ -1,20 +1,20 @@
/** /**
* @file abstract_card_item.h * @file abstract_card_item.h
* @ingroup GameGraphicsCards * @ingroup GameGraphicsCards
* @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic. * @brief TODO: Document this.
*/ */
#ifndef ABSTRACTCARDITEM_H #ifndef ABSTRACTCARDITEM_H
#define ABSTRACTCARDITEM_H #define ABSTRACTCARDITEM_H
#include "../../game_graphics/board/graphics_item_type.h"
#include "../card_dimensions.h" #include "../card_dimensions.h"
#include "arrow_target.h" #include "arrow_target.h"
#include "graphics_item_type.h"
#include <libcockatrice/card/printing/exact_card.h> #include <libcockatrice/card/printing/exact_card.h>
#include <libcockatrice/utility/card_ref.h> #include <libcockatrice/utility/card_ref.h>
class PlayerLogic; class Player;
class AbstractCardItem : public ArrowTarget class AbstractCardItem : public ArrowTarget
{ {
@ -44,11 +44,6 @@ signals:
void deleteCardInfoPopup(QString cardName); void deleteCardInfoPopup(QString cardName);
void sigPixmapUpdated(); void sigPixmapUpdated();
void cardShiftClicked(QString cardName); void cardShiftClicked(QString cardName);
void rightClicked(AbstractCardItem *card, QPoint screenPos);
void playSelected(AbstractCardItem *card);
void playSelectedFaceDown(AbstractCardItem *card);
void hideSelected(AbstractCardItem *card);
void selectionChanged(AbstractCardItem *card, bool selected);
public: public:
enum enum
@ -61,7 +56,7 @@ public:
} }
explicit AbstractCardItem(QGraphicsItem *parent = nullptr, explicit AbstractCardItem(QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {}, const CardRef &cardRef = {},
PlayerLogic *_owner = nullptr, Player *_owner = nullptr,
int _id = -1); int _id = -1);
~AbstractCardItem() override; ~AbstractCardItem() override;
QRectF boundingRect() const override; QRectF boundingRect() const override;
@ -101,10 +96,6 @@ public:
} }
void setRealZValue(qreal _zValue); void setRealZValue(qreal _zValue);
void setHovered(bool _hovered); void setHovered(bool _hovered);
bool getIsHovered() const
{
return isHovered;
}
QString getColor() const QString getColor() const
{ {
return color; return color;

View file

@ -1,15 +1,14 @@
#include "abstract_counter.h" #include "abstract_counter.h"
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game_graphics/board/translate_counter_name.h"
#include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/tabs/tab_game.h"
#include "../player/player.h"
#include "../player/player_actions.h"
#include "translate_counter_name.h"
#include <QAction> #include <QAction>
#include <QApplication> #include <QApplication>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QKeyEvent> #include <QKeyEvent>
#include <QMenu> #include <QMenu>
#include <QString> #include <QString>
@ -17,24 +16,24 @@
#include <libcockatrice/protocol/pb/command_set_counter.pb.h> #include <libcockatrice/protocol/pb/command_set_counter.pb.h>
#include <libcockatrice/utility/expression.h> #include <libcockatrice/utility/expression.h>
AbstractCounter::AbstractCounter(CounterState *state, AbstractCounter::AbstractCounter(Player *_player,
PlayerLogic *_player, int _id,
const QString &_name,
bool _shownInCounterArea, bool _shownInCounterArea,
int _value,
bool _useNameForShortcut, bool _useNameForShortcut,
QGraphicsItem *parent) QGraphicsItem *parent)
: QGraphicsItem(parent), player(_player), id(state->getId()), name(state->getName()), value(state->getValue()), : QGraphicsItem(parent), player(_player), id(_id), name(_name), value(_value),
color(state->getColor()), radius(state->getRadius()), useNameForShortcut(_useNameForShortcut), useNameForShortcut(_useNameForShortcut), hovered(false), aDec(nullptr), aInc(nullptr), dialogSemaphore(false),
shownInCounterArea(_shownInCounterArea) deleteAfterDialog(false), shownInCounterArea(_shownInCounterArea)
{ {
setAcceptHoverEvents(true); setAcceptHoverEvents(true);
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) { shortcutActive = false;
value = newValue;
update();
});
if (player->getPlayerInfo()->getLocalOrJudge()) { if (player->getPlayerInfo()->getLocalOrJudge()) {
menu = new TearOffMenu(TranslateCounterName::getDisplayName(state->getName())); QString displayName = TranslateCounterName::getDisplayName(_name);
menu = new TearOffMenu(displayName);
aSet = new QAction(this); aSet = new QAction(this);
connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter); connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter);
menu->addAction(aSet); menu->addAction(aSet);
@ -42,18 +41,16 @@ AbstractCounter::AbstractCounter(CounterState *state,
for (int i = 10; i >= -10; --i) { for (int i = 10; i >= -10; --i) {
if (i == 0) { if (i == 0) {
menu->addSeparator(); menu->addSeparator();
continue; } else {
QAction *aIncrement = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this);
if (i == -1)
aDec = aIncrement;
else if (i == 1)
aInc = aIncrement;
aIncrement->setData(i);
connect(aIncrement, &QAction::triggered, this, &AbstractCounter::incrementCounter);
menu->addAction(aIncrement);
} }
auto *a = new QAction(QString(i < 0 ? "%1" : "+%1").arg(i), this);
if (i == -1) {
aDec = a;
}
if (i == 1) {
aInc = a;
}
a->setData(i);
connect(a, &QAction::triggered, this, &AbstractCounter::incrementCounter);
menu->addAction(a);
} }
} else { } else {
menu = nullptr; menu = nullptr;
@ -72,35 +69,39 @@ AbstractCounter::~AbstractCounter()
void AbstractCounter::delCounter() void AbstractCounter::delCounter()
{ {
if (dialogSemaphore) { if (dialogSemaphore)
deleteAfterDialog = true; deleteAfterDialog = true;
} else { else
deleteLater(); deleteLater();
}
} }
void AbstractCounter::retranslateUi() void AbstractCounter::retranslateUi()
{ {
if (aSet) { if (menu) {
aSet->setText(tr("&Set counter...")); aSet->setText(tr("&Set counter..."));
} }
} }
void AbstractCounter::setShortcutsActive() void AbstractCounter::setShortcutsActive()
{ {
if (!menu || !player->getPlayerInfo()->getLocal()) { if (!menu) {
return; return;
} }
ShortcutsSettings &sc = SettingsCache::instance().shortcuts(); if (!player->getPlayerInfo()->getLocal()) {
shortcutActive = true; return;
}
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
if (name == "life") { if (name == "life") {
aSet->setShortcuts(sc.getShortcut("Player/aSet")); shortcutActive = true;
aDec->setShortcuts(sc.getShortcut("Player/aDec")); aSet->setShortcuts(shortcuts.getShortcut("Player/aSet"));
aInc->setShortcuts(sc.getShortcut("Player/aInc")); aDec->setShortcuts(shortcuts.getShortcut("Player/aDec"));
aInc->setShortcuts(shortcuts.getShortcut("Player/aInc"));
} else if (useNameForShortcut) { } else if (useNameForShortcut) {
aSet->setShortcuts(sc.getShortcut("Player/aSetCounter_" + name)); shortcutActive = true;
aDec->setShortcuts(sc.getShortcut("Player/aDecCounter_" + name)); aSet->setShortcuts(shortcuts.getShortcut("Player/aSetCounter_" + name));
aInc->setShortcuts(sc.getShortcut("Player/aIncCounter_" + name)); aDec->setShortcuts(shortcuts.getShortcut("Player/aDecCounter_" + name));
aInc->setShortcuts(shortcuts.getShortcut("Player/aIncCounter_" + name));
} }
} }
@ -125,32 +126,43 @@ void AbstractCounter::refreshShortcuts()
} }
} }
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) void AbstractCounter::setValue(int _value)
{ {
if (!isUnderMouse() || !player->getPlayerInfo()->getLocalOrJudge()) { value = _value;
event->ignore(); update();
return;
}
if (event->button() == Qt::MiddleButton || QApplication::keyboardModifiers() & Qt::ShiftModifier) {
if (menu) {
menu->exec(event->screenPos());
}
} else {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(event->button() == Qt::LeftButton ? 1 : -1);
player->getPlayerActions()->sendGameCommand(cmd);
}
event->accept();
} }
void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent *) void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (isUnderMouse() && player->getPlayerInfo()->getLocalOrJudge()) {
if (event->button() == Qt::MiddleButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
if (menu)
menu->exec(event->screenPos());
event->accept();
} else if (event->button() == Qt::LeftButton) {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(1);
player->getPlayerActions()->sendGameCommand(cmd);
event->accept();
} else if (event->button() == Qt::RightButton) {
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(-1);
player->getPlayerActions()->sendGameCommand(cmd);
event->accept();
}
} else
event->ignore();
}
void AbstractCounter::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
{ {
hovered = true; hovered = true;
update(); update();
} }
void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
{ {
hovered = false; hovered = false;
update(); update();
@ -158,36 +170,34 @@ void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
void AbstractCounter::incrementCounter() void AbstractCounter::incrementCounter()
{ {
const int delta = static_cast<QAction *>(sender())->data().toInt();
Command_IncCounter cmd; Command_IncCounter cmd;
cmd.set_counter_id(id); cmd.set_counter_id(id);
cmd.set_delta(static_cast<QAction *>(sender())->data().toInt()); cmd.set_delta(delta);
player->getPlayerActions()->sendGameCommand(cmd); player->getPlayerActions()->sendGameCommand(cmd);
} }
void AbstractCounter::setCounter() void AbstractCounter::setCounter()
{ {
QWidget *parent = nullptr;
if (auto *view = scene() ? scene()->views().value(0) : nullptr) {
parent = view->window();
}
dialogSemaphore = true; dialogSemaphore = true;
AbstractCounterDialog dlg(name, QString::number(value), parent); AbstractCounterDialog dialog(name, QString::number(value), player->getGame()->getTab());
const int ok = dlg.exec(); const int ok = dialog.exec();
dialogSemaphore = false;
if (deleteAfterDialog) { if (deleteAfterDialog) {
deleteLater(); deleteLater();
return; return;
} }
if (!ok) { dialogSemaphore = false;
if (!ok)
return; return;
}
Expression exp(value); Expression exp(value);
int newValue = static_cast<int>(exp.parse(dialog.textValue()));
Command_SetCounter cmd; Command_SetCounter cmd;
cmd.set_counter_id(id); cmd.set_counter_id(id);
cmd.set_value(static_cast<int>(exp.parse(dlg.textValue()))); cmd.set_value(newValue);
player->getPlayerActions()->sendGameCommand(cmd); player->getPlayerActions()->sendGameCommand(cmd);
} }
@ -221,9 +231,8 @@ void AbstractCounterDialog::changeValue(int diff)
{ {
bool ok; bool ok;
int curValue = textValue().toInt(&ok); int curValue = textValue().toInt(&ok);
if (!ok) { if (!ok)
return; return;
}
curValue += diff; curValue += diff;
setTextValue(QString::number(curValue)); setTextValue(QString::number(curValue));
} }

View file

@ -1,51 +1,45 @@
/** /**
* @file abstract_counter.h * @file abstract_counter.h
* @ingroup GameGraphicsPlayers * @ingroup GameGraphicsPlayers
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef COUNTER_H #ifndef COUNTER_H
#define COUNTER_H #define COUNTER_H
#include "../../game/board/counter_state.h"
#include "../../interface/widgets/menus/tearoff_menu.h" #include "../../interface/widgets/menus/tearoff_menu.h"
#include "../player/menu/abstract_player_component.h"
#include <QGraphicsItem> #include <QGraphicsItem>
#include <QInputDialog> #include <QInputDialog>
class PlayerLogic; class Player;
class QAction; class QAction;
class QKeyEvent; class QKeyEvent;
class QMenu; class QMenu;
class QString; class QString;
class AbstractCounter : public QObject, public QGraphicsItem, public AbstractPlayerComponent class AbstractCounter : public QObject, public QGraphicsItem
{ {
Q_OBJECT Q_OBJECT
Q_INTERFACES(QGraphicsItem) Q_INTERFACES(QGraphicsItem)
protected: protected:
PlayerLogic *player; Player *player;
int id; int id;
QString name; QString name;
int value; int value;
QColor color; bool useNameForShortcut, hovered;
int radius;
bool hovered = false;
bool useNameForShortcut;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private: private:
QAction *aSet = nullptr, *aDec = nullptr, *aInc = nullptr; QAction *aSet, *aDec, *aInc;
TearOffMenu *menu = nullptr; TearOffMenu *menu;
bool dialogSemaphore = false; bool dialogSemaphore, deleteAfterDialog;
bool deleteAfterDialog = false;
bool shownInCounterArea; bool shownInCounterArea;
bool shortcutActive = false; bool shortcutActive;
private slots: private slots:
void refreshShortcuts(); void refreshShortcuts();
@ -53,22 +47,26 @@ private slots:
void setCounter(); void setCounter();
public: public:
AbstractCounter(CounterState *state, AbstractCounter(Player *_player,
PlayerLogic *player, int _id,
bool shownInCounterArea, const QString &_name,
bool useNameForShortcut = false, bool _shownInCounterArea,
int _value,
bool _useNameForShortcut = false,
QGraphicsItem *parent = nullptr); QGraphicsItem *parent = nullptr);
~AbstractCounter() override; ~AbstractCounter() override;
void retranslateUi() override; void retranslateUi();
void setShortcutsActive() override; void setValue(int _value);
void setShortcutsInactive() override; void setShortcutsActive();
void setShortcutsInactive();
void delCounter(); void delCounter();
QMenu *getMenu() const QMenu *getMenu() const
{ {
return menu; return menu;
} }
int getId() const int getId() const
{ {
return id; return id;
@ -77,22 +75,14 @@ public:
{ {
return name; return name;
} }
QColor getColor() const bool getShownInCounterArea() const
{ {
return color; return shownInCounterArea;
}
int getRadius() const
{
return radius;
} }
int getValue() const int getValue() const
{ {
return value; return value;
} }
bool getShownInCounterArea() const
{
return shownInCounterArea;
}
}; };
class AbstractCounterDialog : public QInputDialog class AbstractCounterDialog : public QInputDialog

View file

@ -1,21 +0,0 @@
#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;
}

View file

@ -1,30 +0,0 @@
#ifndef COCKATRICE_ARROW_DATA_H
#define COCKATRICE_ARROW_DATA_H
#include <QColor>
#include <QString>
#include <libcockatrice/protocol/pb/serverinfo_arrow.pb.h>
#include <libcockatrice/utility/color.h>
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

View file

@ -2,8 +2,8 @@
#include "arrow_item.h" #include "arrow_item.h"
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../../game/player/player_actions.h" #include "../player/player.h"
#include "../../game/player/player_logic.h" #include "../player/player_actions.h"
#include "../player/player_target.h" #include "../player/player_target.h"
#include "../z_values.h" #include "../z_values.h"
#include "../zones/card_zone.h" #include "../zones/card_zone.h"
@ -21,49 +21,46 @@
#include <libcockatrice/utility/color.h> #include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem) ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
: data(std::move(_data)), startItem(_startItem), targetItem(_targetItem) : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false),
color(_color), fullColor(true)
{ {
setZValue(ZValues::ARROWS); setZValue(ZValues::ARROWS);
auto doUpdate = [this]() { if (startItem)
if (startItem && targetItem) { startItem->addArrowFrom(this);
updatePath(); if (targetItem)
} targetItem->addArrowTo(this);
};
if (startItem) { if (startItem && targetItem)
connect(startItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(startItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (targetItem) {
connect(targetItem, &ArrowTarget::scenePositionChanged, this, doUpdate);
connect(targetItem, &QObject::destroyed, this, &ArrowItem::onTargetDestroyed);
}
if (startItem && targetItem) {
updatePath(); updatePath();
}
} }
void ArrowItem::onTargetDestroyed() ArrowItem::~ArrowItem()
{ {
emit requestDeletion(data->creatorId, data->id);
} }
void ArrowItem::delArrow() void ArrowItem::delArrow()
{ {
if (startItem) {
startItem->removeArrowFrom(this);
startItem = 0;
}
if (targetItem) { if (targetItem) {
targetItem->setBeingPointedAt(false); targetItem->setBeingPointedAt(false);
targetItem->removeArrowTo(this);
targetItem = 0;
} }
player->removeArrow(this);
deleteLater(); deleteLater();
} }
void ArrowItem::updatePath() void ArrowItem::updatePath()
{ {
if (!targetItem) { if (!targetItem)
return; return;
}
QPointF endPoint = targetItem->mapToScene( QPointF endPoint = targetItem->mapToScene(
QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2)); QPointF(targetItem->boundingRect().width() / 2, targetItem->boundingRect().height() / 2));
@ -78,9 +75,8 @@ void ArrowItem::updatePath(const QPointF &endPoint)
headWidth / qPow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++ headWidth / qPow(2, 0.5); // aka headWidth / sqrt (2) but this produces a compile error with MSVC++
const double phi = 15; const double phi = 15;
if (!startItem) { if (!startItem)
return; return;
}
QPointF startPoint = QPointF startPoint =
startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2)); startItem->mapToScene(QPointF(startItem->boundingRect().width() / 2, startItem->boundingRect().height() / 2));
@ -88,9 +84,9 @@ void ArrowItem::updatePath(const QPointF &endPoint)
qreal lineLength = line.length(); qreal lineLength = line.length();
prepareGeometryChange(); prepareGeometryChange();
if (lineLength < 30) { if (lineLength < 30)
path = QPainterPath(); path = QPainterPath();
} else { else {
QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength); QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength);
QPainterPath centerLine; QPainterPath centerLine;
@ -126,24 +122,24 @@ void ArrowItem::updatePath(const QPointF &endPoint)
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{ {
QColor paintColor(data->color); QColor paintColor(color);
if (fullColor) { if (fullColor)
paintColor.setAlpha(200); paintColor.setAlpha(200);
} else { else
paintColor.setAlpha(150); paintColor.setAlpha(150);
}
painter->setBrush(paintColor); painter->setBrush(paintColor);
painter->drawPath(path); painter->drawPath(path);
} }
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ {
if (!data->isLocalCreator) { if (!player->getPlayerInfo()->getLocal()) {
event->ignore(); event->ignore();
return; return;
} }
for (auto *item : scene()->items(event->scenePos())) { QList<QGraphicsItem *> colliding = scene()->items(event->scenePos());
for (QGraphicsItem *item : colliding) {
if (qgraphicsitem_cast<CardItem *>(item)) { if (qgraphicsitem_cast<CardItem *>(item)) {
event->ignore(); event->ignore();
return; return;
@ -152,109 +148,95 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
event->accept(); event->accept();
if (event->button() == Qt::RightButton) { if (event->button() == Qt::RightButton) {
emit requestDeletion(data->creatorId, data->id); Command_DeleteArrow cmd;
cmd.set_arrow_id(id);
player->getPlayerActions()->sendGameCommand(cmd);
} }
} }
// ArrowDragItem ArrowDragItem::ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(_owner, -1, _startItem, 0, _color), deleteInPhase(_deleteInPhase)
ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _owner->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = _color}),
_startItem,
nullptr),
player(_owner), deleteInPhase(_deleteInPhase)
{ {
} }
void ArrowDragItem::addChildArrow(ArrowDragItem *child) void ArrowDragItem::addChildArrow(ArrowDragItem *childArrow)
{ {
childArrows.append(child); childArrows.append(childArrow);
} }
void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{ {
if (targetLocked || !startItem) { // This ensures that if a mouse move event happens after a call to delArrow(),
// the event will be discarded as it would create some stray pointers.
if (targetLocked || !startItem)
return; return;
}
const QPointF endPos = event->scenePos(); QPointF endPos = event->scenePos();
ArrowTarget *cursorItem = nullptr; QList<QGraphicsItem *> colliding = scene()->items(endPos);
ArrowTarget *cursorItem = 0;
qreal cursorItemZ = -1; qreal cursorItemZ = -1;
for (auto *item : scene()->items(endPos)) { for (int i = colliding.size() - 1; i >= 0; i--) {
ArrowTarget *candidate = nullptr; if (qgraphicsitem_cast<PlayerTarget *>(colliding.at(i)) || qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) { if (colliding.at(i)->zValue() > cursorItemZ) {
candidate = card; cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
} else if (auto *pt = qgraphicsitem_cast<PlayerTarget *>(item)) { cursorItemZ = cursorItem->zValue();
candidate = pt; }
}
if (candidate && candidate->zValue() > cursorItemZ) {
cursorItem = candidate;
cursorItemZ = candidate->zValue();
} }
} }
if (cursorItem != targetItem) { if ((cursorItem != targetItem) && targetItem) {
if (targetItem) { targetItem->setBeingPointedAt(false);
disconnect(positionConnection); targetItem->removeArrowTo(this);
targetItem->setBeingPointedAt(false); }
} if (!cursorItem) {
fullColor = false;
targetItem = cursorItem; targetItem = 0;
fullColor = (cursorItem != nullptr); updatePath(endPos);
} else {
if (cursorItem && cursorItem != startItem) { if (cursorItem != targetItem) {
cursorItem->setBeingPointedAt(true); fullColor = true;
positionConnection = if (cursorItem != startItem) {
connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); }); cursorItem->setBeingPointedAt(true);
} cursorItem->addArrowTo(this);
}
targetItem = cursorItem;
}
updatePath();
} }
targetItem ? updatePath() : updatePath(endPos);
update(); update();
for (auto *child : childArrows) { for (ArrowDragItem *child : childArrows) {
child->mouseMoveEvent(event); child->mouseMoveEvent(event);
} }
} }
void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
if (!startItem) { if (!startItem)
return; return;
}
if (targetItem && targetItem != startItem) { if (targetItem && (targetItem != startItem)) {
CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem); CardZoneLogic *startZone = static_cast<CardItem *>(startItem)->getZone();
// For now, we can safely assume that the start item is always a card. // For now, we can safely assume that the start item is always a card.
// The target item can be a player as well. // The target item can be a player as well.
if (!startCard) { CardItem *startCard = qgraphicsitem_cast<CardItem *>(startItem);
delArrow(); CardItem *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
return;
}
CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd; Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color)); cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString()); cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId()); cmd.set_start_card_id(startCard->getId());
if (auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem)) { if (targetCard) {
CardZoneLogic *targetZone = targetCard->getZone(); CardZoneLogic *targetZone = targetCard->getZone();
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString()); cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId()); cmd.set_target_card_id(targetCard->getId());
} else if (auto *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem)) { } else { // failed to cast target to card, this means it's a player
PlayerTarget *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem);
cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId()); cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId());
} else {
delArrow();
return;
} }
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow // if the card is in hand then we will move the card to stack or table as part of drawing the arrow
@ -264,11 +246,10 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
bool playToStack = SettingsCache::instance().getPlayToStack(); bool playToStack = SettingsCache::instance().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) || if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 && (playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) { startCard->getZone()->getName() != ZoneNames::STACK)))
cmd.set_start_zone(ZoneNames::STACK); cmd.set_start_zone(ZoneNames::STACK);
} else { else
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE); cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
} }
if (deleteInPhase != 0) { if (deleteInPhase != 0) {
@ -277,116 +258,111 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
player->getPlayerActions()->sendGameCommand(cmd); player->getPlayerActions()->sendGameCommand(cmd);
} }
delArrow(); delArrow();
for (auto *child : childArrows) {
for (ArrowDragItem *child : childArrows) {
child->mouseReleaseEvent(event); child->mouseReleaseEvent(event);
} }
} }
// ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem) ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem( : ArrowItem(_startItem->getOwner(), -1, _startItem, 0, Qt::green)
QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _startItem->getOwner()->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = Qt::green}),
_startItem,
nullptr),
player(_startItem->getOwner())
{ {
} }
void ArrowAttachItem::addChildArrow(ArrowAttachItem *child) void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow)
{ {
childArrows.append(child); childArrows.append(childArrow);
} }
void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{ {
if (targetLocked || !startItem) { if (targetLocked || !startItem)
return; return;
}
const QPointF endPos = event->scenePos(); QPointF endPos = event->scenePos();
ArrowTarget *cursorItem = nullptr; QList<QGraphicsItem *> colliding = scene()->items(endPos);
ArrowTarget *cursorItem = 0;
qreal cursorItemZ = -1; qreal cursorItemZ = -1;
for (auto *item : scene()->items(endPos)) { for (int i = colliding.size() - 1; i >= 0; i--) {
if (auto *card = qgraphicsitem_cast<CardItem *>(item)) { if (qgraphicsitem_cast<CardItem *>(colliding.at(i))) {
if (card->zValue() > cursorItemZ) { if (colliding.at(i)->zValue() > cursorItemZ) {
cursorItem = card; cursorItem = static_cast<ArrowTarget *>(colliding.at(i));
cursorItemZ = card->zValue(); cursorItemZ = cursorItem->zValue();
} }
} }
} }
if (cursorItem != targetItem) { if ((cursorItem != targetItem) && targetItem) {
if (targetItem) { targetItem->setBeingPointedAt(false);
disconnect(positionConnection); }
targetItem->setBeingPointedAt(false); if (!cursorItem) {
} fullColor = false;
targetItem = 0;
targetItem = cursorItem; updatePath(endPos);
fullColor = (cursorItem != nullptr); } else {
fullColor = true;
if (cursorItem && cursorItem != startItem) { if (cursorItem != startItem) {
cursorItem->setBeingPointedAt(true); cursorItem->setBeingPointedAt(true);
positionConnection = }
connect(cursorItem, &ArrowTarget::scenePositionChanged, this, [this]() { updatePath(); }); targetItem = cursorItem;
} updatePath();
} }
targetItem ? updatePath() : updatePath(endPos);
update(); update();
for (auto *child : childArrows) { for (ArrowAttachItem *child : childArrows) {
child->mouseMoveEvent(event); child->mouseMoveEvent(event);
} }
} }
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem) {
return;
}
// Attaching could move startItem under the current cursor position, causing all children to retarget to it right
// before they are processed. Prevent that.
for (auto *child : childArrows) {
child->setTargetLocked(true);
}
if (targetItem && targetItem != startItem) {
auto *startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (startCard && targetCard) {
attachCards(startCard, targetCard);
}
}
delArrow();
for (auto *child : childArrows) {
child->mouseReleaseEvent(event);
}
}
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard) void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{ {
// do nothing if target is already attached to another card or is not in play
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) { if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
return; return;
} }
CardZoneLogic *startZone = startCard->getZone();
CardZoneLogic *targetZone = targetCard->getZone();
// move card onto table first if attaching from some other zone // move card onto table first if attaching from some other zone
if (startCard->getZone()->getName() != ZoneNames::TABLE) { if (startZone->getName() != ZoneNames::TABLE) {
player->getPlayerActions()->playCardToTable(startCard, false); player->getPlayerActions()->playCardToTable(startCard, false);
} }
Command_AttachCard cmd; Command_AttachCard cmd;
cmd.set_start_zone(ZoneNames::TABLE); cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_card_id(startCard->getId()); cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId()); cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetCard->getZone()->getName().toStdString()); cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId()); cmd.set_target_card_id(targetCard->getId());
player->getPlayerActions()->sendGameCommand(cmd); player->getPlayerActions()->sendGameCommand(cmd);
} }
void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem)
return;
// Attaching could move startItem under the current cursor position, causing all children to retarget to it right
// before they are processed. Prevent that.
for (ArrowAttachItem *child : childArrows) {
child->setTargetLocked(true);
}
if (targetItem && (targetItem != startItem)) {
auto startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (startCard && targetCard) {
attachCards(startCard, targetCard);
}
}
delArrow();
for (ArrowAttachItem *child : childArrows) {
child->mouseReleaseEvent(event);
}
}

View file

@ -1,44 +1,40 @@
/**
* @file arrow_item.h
* @ingroup GameGraphics
* @brief TODO: Document this.
*/
#ifndef ARROWITEM_H #ifndef ARROWITEM_H
#define ARROWITEM_H #define ARROWITEM_H
#include "../../game/board/arrow_data.h"
#include "arrow_target.h"
#include <QGraphicsItem> #include <QGraphicsItem>
#include <QPointer>
#include <QSharedPointer>
class CardItem; class CardItem;
class QGraphicsSceneMouseEvent; class QGraphicsSceneMouseEvent;
class PlayerLogic; class QMenu;
class Player;
class ArrowTarget;
class ArrowItem : public QObject, public QGraphicsItem class ArrowItem : public QObject, public QGraphicsItem
{ {
Q_OBJECT Q_OBJECT
Q_INTERFACES(QGraphicsItem) Q_INTERFACES(QGraphicsItem)
signals:
void requestDeletion(int creatorId, int id);
private: private:
QPainterPath path; QPainterPath path;
QMenu *menu;
protected: protected:
QSharedPointer<const ArrowData> data; Player *player;
QPointer<ArrowTarget> startItem; int id;
QPointer<ArrowTarget> targetItem; ArrowTarget *startItem, *targetItem;
bool targetLocked = false; bool targetLocked;
bool fullColor = true; QColor color;
bool fullColor;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public: public:
ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem); ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color);
~ArrowItem() override;
void onTargetDestroyed();
void delArrow();
void updatePath();
void updatePath(const QPointF &endPoint);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override [[nodiscard]] QRectF boundingRect() const override
{ {
@ -48,13 +44,24 @@ public:
{ {
return path; return path;
} }
void updatePath();
void updatePath(const QPointF &endPoint);
[[nodiscard]] int getId() const [[nodiscard]] int getId() const
{ {
return data->id; return id;
} }
[[nodiscard]] int getCreatorId() const [[nodiscard]] Player *getPlayer() const
{ {
return data->creatorId; return player;
}
void setStartItem(ArrowTarget *_item)
{
startItem = _item;
}
void setTargetItem(ArrowTarget *_item)
{
targetItem = _item;
} }
[[nodiscard]] ArrowTarget *getStartItem() const [[nodiscard]] ArrowTarget *getStartItem() const
{ {
@ -68,20 +75,19 @@ public:
{ {
targetLocked = _targetLocked; targetLocked = _targetLocked;
} }
void delArrow();
}; };
class ArrowDragItem : public ArrowItem class ArrowDragItem : public ArrowItem
{ {
Q_OBJECT Q_OBJECT
private: private:
PlayerLogic *player;
int deleteInPhase; int deleteInPhase;
QList<ArrowDragItem *> childArrows; QList<ArrowDragItem *> childArrows;
QMetaObject::Connection positionConnection;
public: public:
ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase); ArrowDragItem(Player *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase);
void addChildArrow(ArrowDragItem *child); void addChildArrow(ArrowDragItem *childArrow);
protected: protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
@ -92,18 +98,17 @@ class ArrowAttachItem : public ArrowItem
{ {
Q_OBJECT Q_OBJECT
private: private:
PlayerLogic *player;
QList<ArrowAttachItem *> childArrows; QList<ArrowAttachItem *> childArrows;
QMetaObject::Connection positionConnection;
void attachCards(CardItem *startCard, const CardItem *targetCard); void attachCards(CardItem *startCard, const CardItem *targetCard);
public: public:
explicit ArrowAttachItem(ArrowTarget *_startItem); explicit ArrowAttachItem(ArrowTarget *_startItem);
void addChildArrow(ArrowAttachItem *child); void addChildArrow(ArrowAttachItem *childArrow);
protected: protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
}; };
#endif #endif // ARROWITEM_H

View file

@ -0,0 +1,41 @@
#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);
}

View file

@ -0,0 +1,70 @@
/**
* @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 <QList>
class Player;
class ArrowItem;
class ArrowTarget : public AbstractGraphicsItem
{
Q_OBJECT
protected:
Player *owner;
private:
bool beingPointedAt;
QList<ArrowItem *> 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<ArrowItem *> &getArrowsFrom() const
{
return arrowsFrom;
}
void addArrowFrom(ArrowItem *arrow)
{
arrowsFrom.append(arrow);
}
void removeArrowFrom(ArrowItem *arrow)
{
arrowsFrom.removeOne(arrow);
}
[[nodiscard]] const QList<ArrowItem *> &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

View file

@ -24,9 +24,8 @@ void CardDragItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
{ {
AbstractCardDragItem::paint(painter, option, widget); AbstractCardDragItem::paint(painter, option, widget);
if (occupied) { if (occupied)
painter->fillPath(shape(), QColor(200, 0, 0, 100)); painter->fillPath(shape(), QColor(200, 0, 0, 100));
}
} }
void CardDragItem::updatePosition(const QPointF &cursorScenePos) void CardDragItem::updatePosition(const QPointF &cursorScenePos)
@ -39,19 +38,16 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos)
ZoneViewZone *zoneViewZone = 0; ZoneViewZone *zoneViewZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--) { for (int i = colliding.size() - 1; i >= 0; i--) {
CardZone *temp = qgraphicsitem_cast<CardZone *>(colliding.at(i)); CardZone *temp = qgraphicsitem_cast<CardZone *>(colliding.at(i));
if (!cardZone) { if (!cardZone)
cardZone = temp; cardZone = temp;
} if (!zoneViewZone)
if (!zoneViewZone) {
zoneViewZone = qobject_cast<ZoneViewZone *>(temp); zoneViewZone = qobject_cast<ZoneViewZone *>(temp);
}
} }
CardZone *cursorZone = 0; CardZone *cursorZone = 0;
if (zoneViewZone) { if (zoneViewZone)
cursorZone = zoneViewZone; cursorZone = zoneViewZone;
} else if (cardZone) { else if (cardZone)
cursorZone = cardZone; cursorZone = cardZone;
}
// Always update the current zone, even if its null, to cancel the drag // Always update the current zone, even if its null, to cancel the drag
// instead of dropping cards into an non-intuitive location. // instead of dropping cards into an non-intuitive location.
@ -63,9 +59,8 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos)
QPointF newPos = cursorScenePos - hotSpot; QPointF newPos = cursorScenePos - hotSpot;
if (newPos != pos()) { if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) { for (int i = 0; i < childDrags.size(); i++)
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos); setPos(newPos);
} }
@ -83,27 +78,23 @@ void CardDragItem::updatePosition(const QPointF &cursorScenePos)
// position. // position.
TableZone *tableZone = qobject_cast<TableZone *>(cursorZone); TableZone *tableZone = qobject_cast<TableZone *>(cursorZone);
QPointF closestGridPoint; QPointF closestGridPoint;
if (tableZone) { if (tableZone)
closestGridPoint = tableZone->closestGridPoint(cursorPosInZone); closestGridPoint = tableZone->closestGridPoint(cursorPosInZone);
} else { else
closestGridPoint = cursorPosInZone - hotSpot; closestGridPoint = cursorPosInZone - hotSpot;
}
QPointF newPos = zonePos + closestGridPoint; QPointF newPos = zonePos + closestGridPoint;
if (newPos != pos()) { if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) { for (int i = 0; i < childDrags.size(); i++)
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos); setPos(newPos);
bool newOccupied = false; bool newOccupied = false;
TableZone *table = qobject_cast<TableZone *>(cursorZone); TableZone *table = qobject_cast<TableZone *>(cursorZone);
if (table) { if (table)
if (table->getCardFromCoords(closestGridPoint)) { if (table->getCardFromCoords(closestGridPoint))
newOccupied = true; newOccupied = true;
}
}
if (newOccupied != occupied) { if (newOccupied != occupied) {
occupied = newOccupied; occupied = newOccupied;
update(); update();

View file

@ -1,8 +1,8 @@
/** /**
* @file card_drag_item.h * @file card_drag_item.h
* @ingroup GameGraphicsCards * @ingroup GameGraphicsCards
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef CARDDRAGITEM_H #ifndef CARDDRAGITEM_H
#define CARDDRAGITEM_H #define CARDDRAGITEM_H

View file

@ -1,12 +1,12 @@
#include "card_item.h" #include "card_item.h"
#include "../../client/settings/cache_settings.h" #include "../../client/settings/cache_settings.h"
#include "../../game/phase.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/view_zone_logic.h"
#include "../../interface/widgets/tabs/tab_game.h" #include "../../interface/widgets/tabs/tab_game.h"
#include "../game_scene.h" #include "../game_scene.h"
#include "../phase.h"
#include "../player/player.h"
#include "../player/player_actions.h"
#include "../zones/logic/view_zone_logic.h"
#include "../zones/table_zone.h" #include "../zones/table_zone.h"
#include "../zones/view_zone.h" #include "../zones/view_zone.h"
#include "arrow_item.h" #include "arrow_item.h"
@ -20,19 +20,15 @@
#include <libcockatrice/card/card_info.h> #include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h> #include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
CardItem::CardItem(PlayerLogic *_owner, CardItem::CardItem(Player *_owner, QGraphicsItem *parent, const CardRef &cardRef, int _cardid, CardZoneLogic *_zone)
QGraphicsItem *parent, : AbstractCardItem(parent, cardRef, _owner, _cardid), zone(_zone), attacking(false), destroyOnZoneChange(false),
const CardRef &cardRef, doesntUntap(false), dragItem(nullptr), attachedTo(nullptr)
int _cardid,
CardZoneLogic *_zone)
: AbstractCardItem(parent, cardRef, _owner, _cardid), state(new CardState(this, _zone)), dragItem(nullptr)
{ {
owner->addCard(this); owner->addCard(this);
connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) { connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) {
if (state->getCounters().contains(counterId)) { if (counters.contains(counterId))
update(); update();
}
}); });
} }
@ -40,7 +36,7 @@ void CardItem::prepareDelete()
{ {
if (owner != nullptr) { if (owner != nullptr) {
if (owner->getGame()->getActiveCard() == this) { if (owner->getGame()->getActiveCard() == this) {
emit owner->requestCardMenuUpdate(nullptr); owner->getPlayerMenu()->updateCardMenu(nullptr);
owner->getGame()->setActiveCard(nullptr); owner->getGame()->setActiveCard(nullptr);
} }
owner = nullptr; owner = nullptr;
@ -51,24 +47,23 @@ void CardItem::prepareDelete()
attachedCards.first()->setAttachedTo(nullptr); attachedCards.first()->setAttachedTo(nullptr);
} }
if (state->getAttachedTo() != nullptr) { if (attachedTo != nullptr) {
state->getAttachedTo()->removeAttachedCard(this); attachedTo->removeAttachedCard(this);
state->setAttachedTo(nullptr); attachedTo = nullptr;
} }
} }
void CardItem::deleteLater() void CardItem::deleteLater()
{ {
prepareDelete(); prepareDelete();
if (scene()) { if (scene())
static_cast<GameScene *>(scene())->unregisterAnimationItem(this); static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
AbstractCardItem::deleteLater(); AbstractCardItem::deleteLater();
} }
void CardItem::setZone(CardZoneLogic *_zone) void CardItem::setZone(CardZoneLogic *_zone)
{ {
state->setZone(_zone); zone = _zone;
} }
void CardItem::retranslateUi() void CardItem::retranslateUi()
@ -83,23 +78,23 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
AbstractCardItem::paint(painter, option, widget); AbstractCardItem::paint(painter, option, widget);
int i = 0; int i = 0;
QMapIterator<int, int> counterIterator(state->getCounters()); QMapIterator<int, int> counterIterator(counters);
while (counterIterator.hasNext()) { while (counterIterator.hasNext()) {
counterIterator.next(); counterIterator.next();
QColor _color = cardCounterSettings.color(counterIterator.key()); QColor _color = cardCounterSettings.color(counterIterator.key());
paintNumberEllipse(counterIterator.value(), 14, _color, i, state->getCounters().size(), painter); paintNumberEllipse(counterIterator.value(), 14, _color, i, counters.size(), painter);
++i; ++i;
} }
QSizeF translatedSize = getTranslatedSize(painter); QSizeF translatedSize = getTranslatedSize(painter);
qreal scaleFactor = translatedSize.width() / boundingRect().width(); qreal scaleFactor = translatedSize.width() / boundingRect().width();
if (!state->getPT().isEmpty()) { if (!pt.isEmpty()) {
painter->save(); painter->save();
transformPainter(painter, translatedSize, tapAngle); transformPainter(painter, translatedSize, tapAngle);
if (!getFaceDown() && state->getPT() == exactCard.getInfo().getPowTough()) { if (!getFaceDown() && pt == exactCard.getInfo().getPowTough()) {
painter->setPen(Qt::white); painter->setPen(Qt::white);
} else { } else {
painter->setPen(QColor(255, 150, 0)); // dark orange painter->setPen(QColor(255, 150, 0)); // dark orange
@ -110,11 +105,11 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 10 * scaleFactor, painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 10 * scaleFactor,
translatedSize.height() - 8 * scaleFactor), translatedSize.height() - 8 * scaleFactor),
Qt::AlignRight | Qt::AlignBottom, state->getPT()); Qt::AlignRight | Qt::AlignBottom, pt);
painter->restore(); painter->restore();
} }
if (!state->getAnnotation().isEmpty()) { if (!annotation.isEmpty()) {
painter->save(); painter->save();
transformPainter(painter, translatedSize, tapAngle); transformPainter(painter, translatedSize, tapAngle);
@ -124,7 +119,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor, painter->drawText(QRectF(4 * scaleFactor, 4 * scaleFactor, translatedSize.width() - 8 * scaleFactor,
translatedSize.height() - 8 * scaleFactor), translatedSize.height() - 8 * scaleFactor),
Qt::AlignCenter | Qt::TextWrapAnywhere, state->getAnnotation()); Qt::AlignCenter | Qt::TextWrapAnywhere, annotation);
painter->restore(); painter->restore();
} }
@ -132,7 +127,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100))); painter->fillPath(shape(), QBrush(QColor(255, 0, 0, 100)));
} }
if (state->getDoesntUntap()) { if (doesntUntap) {
painter->save(); painter->save();
painter->setRenderHint(QPainter::Antialiasing, false); painter->setRenderHint(QPainter::Antialiasing, false);
@ -151,66 +146,69 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
void CardItem::setAttacking(bool _attacking) void CardItem::setAttacking(bool _attacking)
{ {
state->setAttacking(_attacking); attacking = _attacking;
update(); update();
} }
void CardItem::setCounter(int _id, int _value) void CardItem::setCounter(int _id, int _value)
{ {
state->setCounter(_id, _value); if (_value)
counters.insert(_id, _value);
else
counters.remove(_id);
update(); update();
} }
void CardItem::setAnnotation(const QString &_annotation) void CardItem::setAnnotation(const QString &_annotation)
{ {
state->setAnnotation(_annotation); annotation = _annotation;
update(); update();
} }
void CardItem::setDoesntUntap(bool _doesntUntap) void CardItem::setDoesntUntap(bool _doesntUntap)
{ {
state->setDoesntUntap(_doesntUntap); doesntUntap = _doesntUntap;
update(); update();
} }
void CardItem::setPT(const QString &_pt) void CardItem::setPT(const QString &_pt)
{ {
state->setPT(_pt); pt = _pt;
update(); update();
} }
void CardItem::setAttachedTo(CardItem *_attachedTo) void CardItem::setAttachedTo(CardItem *_attachedTo)
{ {
if (state->getAttachedTo() != nullptr) { if (attachedTo != nullptr) {
state->getAttachedTo()->removeAttachedCard(this); attachedTo->removeAttachedCard(this);
} }
gridPoint.setX(-1); gridPoint.setX(-1);
state->setAttachedTo(_attachedTo); attachedTo = _attachedTo;
if (state->getAttachedTo() != nullptr) { if (attachedTo != nullptr) {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its // If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards // attached cards
if (state->getAttachedTo()->getZone() == nullptr) { if (attachedTo->zone == nullptr) {
deleteLater(); deleteLater();
} else { } else {
emit state->getAttachedTo()->getZone()->cardAdded(this); emit attachedTo->zone->cardAdded(this);
state->getAttachedTo()->addAttachedCard(this); attachedTo->addAttachedCard(this);
if (state->getZone() != state->getAttachedTo()->getZone()) { if (zone != attachedTo->getZone()) {
state->getAttachedTo()->getZone()->reorganizeCards(); attachedTo->getZone()->reorganizeCards();
} }
} }
} else { } else {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its // If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards // attached cards
if (state->getZone() == nullptr) { if (zone == nullptr) {
deleteLater(); deleteLater();
} else { } else {
emit state->getZone()->cardAdded(this); emit zone->cardAdded(this);
} }
} }
if (state->getZone() != nullptr) { if (zone != nullptr) {
state->getZone()->reorganizeCards(); zone->reorganizeCards();
} }
} }
@ -219,23 +217,28 @@ void CardItem::setAttachedTo(CardItem *_attachedTo)
*/ */
void CardItem::resetState(bool keepAnnotations) void CardItem::resetState(bool keepAnnotations)
{ {
state->resetState(keepAnnotations); attacking = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
annotation.clear();
}
attachedTo = 0;
attachedCards.clear(); attachedCards.clear();
setTapped(false, false); setTapped(false, false);
setDoesntUntap(false); setDoesntUntap(false);
if (scene()) { if (scene())
static_cast<GameScene *>(scene())->unregisterAnimationItem(this); static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
update(); update();
} }
void CardItem::processCardInfo(const ServerInfo_Card &_info) void CardItem::processCardInfo(const ServerInfo_Card &_info)
{ {
state->clearCounters(); counters.clear();
const int counterListSize = _info.counter_list_size(); const int counterListSize = _info.counter_list_size();
for (int i = 0; i < counterListSize; ++i) { for (int i = 0; i < counterListSize; ++i) {
const ServerInfo_CardCounter &counterInfo = _info.counter_list(i); const ServerInfo_CardCounter &counterInfo = _info.counter_list(i);
state->insertCounter(counterInfo.id(), counterInfo.value()); counters.insert(counterInfo.id(), counterInfo.value());
} }
setId(_info.id()); setId(_info.id());
@ -272,12 +275,11 @@ void CardItem::deleteDragItem()
void CardItem::drawArrow(const QColor &arrowColor) void CardItem::drawArrow(const QColor &arrowColor)
{ {
if (owner->getGame()->getPlayerManager()->isSpectator()) { if (owner->getGame()->getPlayerManager()->isSpectator())
return; return;
}
auto *game = owner->getGame(); auto *game = owner->getGame();
PlayerLogic *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); Player *arrowOwner = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
int phase = 0; // 0 means to not set the phase int phase = 0; // 0 means to not set the phase
if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) { if (SettingsCache::instance().getDoNotDeleteArrowsInSubPhases()) {
int currentPhase = game->getGameState()->getCurrentPhase(); int currentPhase = game->getGameState()->getCurrentPhase();
@ -289,12 +291,10 @@ void CardItem::drawArrow(const QColor &arrowColor)
for (const auto &item : scene()->selectedItems()) { for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item); CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr || card == this) { if (card == nullptr || card == this)
continue; continue;
} if (card->getZone() != zone)
if (card->getZone() != state->getZone()) {
continue; continue;
}
ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase); ArrowDragItem *childArrow = new ArrowDragItem(arrowOwner, card, arrowColor, phase);
scene()->addItem(childArrow); scene()->addItem(childArrow);
@ -304,9 +304,8 @@ void CardItem::drawArrow(const QColor &arrowColor)
void CardItem::drawAttachArrow() void CardItem::drawAttachArrow()
{ {
if (owner->getGame()->getPlayerManager()->isSpectator()) { if (owner->getGame()->getPlayerManager()->isSpectator())
return; return;
}
auto *arrow = new ArrowAttachItem(this); auto *arrow = new ArrowAttachItem(this);
scene()->addItem(arrow); scene()->addItem(arrow);
@ -314,12 +313,10 @@ void CardItem::drawAttachArrow()
for (const auto &item : scene()->selectedItems()) { for (const auto &item : scene()->selectedItems()) {
CardItem *card = qgraphicsitem_cast<CardItem *>(item); CardItem *card = qgraphicsitem_cast<CardItem *>(item);
if (card == nullptr) { if (card == nullptr)
continue; continue;
} if (card->getZone() != zone)
if (card->getZone() != state->getZone()) {
continue; continue;
}
ArrowAttachItem *childArrow = new ArrowAttachItem(card); ArrowAttachItem *childArrow = new ArrowAttachItem(card);
scene()->addItem(childArrow); scene()->addItem(childArrow);
@ -331,32 +328,27 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{ {
if (event->buttons().testFlag(Qt::RightButton)) { if (event->buttons().testFlag(Qt::RightButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() < if ((event->screenPos() - event->buttonDownScreenPos(Qt::RightButton)).manhattanLength() <
2 * QApplication::startDragDistance()) { 2 * QApplication::startDragDistance())
return; return;
}
QColor arrowColor = Qt::red; QColor arrowColor = Qt::red;
if (event->modifiers().testFlag(Qt::ControlModifier)) { if (event->modifiers().testFlag(Qt::ControlModifier))
arrowColor = Qt::yellow; arrowColor = Qt::yellow;
} else if (event->modifiers().testFlag(Qt::AltModifier)) { else if (event->modifiers().testFlag(Qt::AltModifier))
arrowColor = Qt::blue; arrowColor = Qt::blue;
} else if (event->modifiers().testFlag(Qt::ShiftModifier)) { else if (event->modifiers().testFlag(Qt::ShiftModifier))
arrowColor = Qt::green; arrowColor = Qt::green;
}
drawArrow(arrowColor); drawArrow(arrowColor);
} else if (event->buttons().testFlag(Qt::LeftButton)) { } else if (event->buttons().testFlag(Qt::LeftButton)) {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
2 * QApplication::startDragDistance()) { 2 * QApplication::startDragDistance())
return; return;
} if (const ZoneViewZoneLogic *view = qobject_cast<const ZoneViewZoneLogic *>(zone)) {
if (const ZoneViewZoneLogic *view = qobject_cast<const ZoneViewZoneLogic *>(state->getZone())) { if (view->getRevealZone() && !view->getWriteableRevealZone())
if (view->getRevealZone() && !view->getWriteableRevealZone()) {
return; return;
} } else if (!owner->getPlayerInfo()->getLocalOrJudge())
} else if (!owner->getPlayerInfo()->getLocalOrJudge()) {
return; return;
}
bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier); bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
@ -368,16 +360,14 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
int childIndex = 0; int childIndex = 0;
for (const auto &item : scene()->selectedItems()) { for (const auto &item : scene()->selectedItems()) {
CardItem *card = static_cast<CardItem *>(item); CardItem *card = static_cast<CardItem *>(item);
if ((card == this) || (card->getZone() != state->getZone())) { if ((card == this) || (card->getZone() != zone))
continue; continue;
}
++childIndex; ++childIndex;
QPointF childPos; QPointF childPos;
if (state->getZone()->getHasCardAttr()) { if (zone->getHasCardAttr())
childPos = card->pos() - pos(); childPos = card->pos() - pos();
} else { else
childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0); childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0);
}
CardDragItem *drag = CardDragItem *drag =
new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem); new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem);
drag->setPos(dragItem->pos() + childPos); drag->setPos(dragItem->pos() + childPos);
@ -390,57 +380,22 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void CardItem::playCard(bool faceDown) void CardItem::playCard(bool faceDown)
{ {
// Do nothing if the card belongs to another player // Do nothing if the card belongs to another player
if (!owner->getPlayerInfo()->getLocalOrJudge()) { if (!owner->getPlayerInfo()->getLocalOrJudge())
return; return;
}
TableZoneLogic *tz = qobject_cast<TableZoneLogic *>(state->getZone()); TableZoneLogic *tz = qobject_cast<TableZoneLogic *>(zone);
if (tz) { if (tz)
emit tz->toggleTapped(); emit tz->toggleTapped();
} else { else {
if (SettingsCache::instance().getClickPlaysAllSelected()) { if (SettingsCache::instance().getClickPlaysAllSelected()) {
if (faceDown) { faceDown ? zone->getPlayer()->getPlayerActions()->actPlayFacedown()
emit playSelectedFaceDown(this); : zone->getPlayer()->getPlayerActions()->actPlay();
} else {
emit playSelected(this);
}
} else { } else {
state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown); zone->getPlayer()->getPlayerActions()->playCard(this, faceDown);
} }
} }
} }
QVariantList CardItem::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;
}
/** /**
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone * @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
* is nullptr. * is nullptr.
@ -461,11 +416,11 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
*/ */
void CardItem::handleClickedToPlay(bool shiftHeld) void CardItem::handleClickedToPlay(bool shiftHeld)
{ {
if (isUnwritableRevealZone(state->getZone())) { if (isUnwritableRevealZone(zone)) {
if (SettingsCache::instance().getClickPlaysAllSelected()) { if (SettingsCache::instance().getClickPlaysAllSelected()) {
emit hideSelected(this); zone->getPlayer()->getPlayerActions()->actHide();
} else { } else {
state->getZone()->removeCard(this); zone->removeCard(this);
} }
} else { } else {
playCard(shiftHeld); playCard(shiftHeld);
@ -474,15 +429,21 @@ void CardItem::handleClickedToPlay(bool shiftHeld)
void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
if (event->button() == Qt::RightButton && owner != nullptr) { if (event->button() == Qt::RightButton) {
emit rightClicked(this, event->screenPos());
return; if (owner != nullptr) {
} owner->getGame()->setActiveCard(this);
if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) && if (QMenu *cardMenu = owner->getPlayerMenu()->updateCardMenu(this)) {
(!SettingsCache::instance().getDoubleClickToPlay())) { cardMenu->popup(event->screenPos());
return;
}
}
} else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) &&
(!SettingsCache::instance().getDoubleClickToPlay())) {
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
} }
if (owner != nullptr) {
if (owner != nullptr) { // cards without owner will be deleted
setCursor(Qt::OpenHandCursor); setCursor(Qt::OpenHandCursor);
} }
AbstractCardItem::mouseReleaseEvent(event); AbstractCardItem::mouseReleaseEvent(event);
@ -501,9 +462,8 @@ bool CardItem::animationEvent()
{ {
int rotation = ROTATION_DEGREES_PER_FRAME; int rotation = ROTATION_DEGREES_PER_FRAME;
bool animationIncomplete = true; bool animationIncomplete = true;
if (!tapped) { if (!tapped)
rotation *= -1; rotation *= -1;
}
tapAngle += rotation; tapAngle += rotation;
if (tapped && (tapAngle > 90)) { if (tapped && (tapAngle > 90)) {
@ -528,14 +488,14 @@ bool CardItem::animationEvent()
QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
{ {
if ((change == ItemSelectedHasChanged) && owner != nullptr) { if ((change == ItemSelectedHasChanged) && owner != nullptr) {
bool selected = value.toBool(); if (value == true) {
if (selected) {
owner->getGame()->setActiveCard(this); owner->getGame()->setActiveCard(this);
owner->getPlayerMenu()->updateCardMenu(this);
} else if (owner->getGameScene()->selectedItems().isEmpty()) {
owner->getGame()->setActiveCard(nullptr);
owner->getPlayerMenu()->updateCardMenu(nullptr);
} }
emit selectionChanged(this, selected);
} }
return AbstractCardItem::itemChange(change, value); return AbstractCardItem::itemChange(change, value);
} }

View file

@ -1,37 +1,42 @@
/** /**
* @file card_item.h * @file card_item.h
* @ingroup GameGraphicsCards * @ingroup GameGraphicsCards
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef CARDITEM_H #ifndef CARDITEM_H
#define CARDITEM_H #define CARDITEM_H
#include "../../game/board/card_state.h" #include "../zones/logic/card_zone_logic.h"
#include "../../game/zones/card_zone_logic.h"
#include "abstract_card_item.h" #include "abstract_card_item.h"
#include <libcockatrice/network/server/remote/game/server_card.h> #include <libcockatrice/network/server/remote/game/server_card.h>
#include <libcockatrice/utility/trice_limits.h>
class CardDatabase; class CardDatabase;
class CardDragItem; class CardDragItem;
class CardZone; class CardZone;
class ServerInfo_Card; class ServerInfo_Card;
class PlayerLogic; class Player;
class QAction; class QAction;
class QColor; class QColor;
const int MAX_COUNTERS_ON_CARD = 999;
const int ROTATION_DEGREES_PER_FRAME = 10; const int ROTATION_DEGREES_PER_FRAME = 10;
class CardItem : public AbstractCardItem class CardItem : public AbstractCardItem
{ {
Q_OBJECT Q_OBJECT
private: private:
CardState *state; CardZoneLogic *zone;
bool attacking;
QMap<int, int> counters;
QString annotation;
QString pt;
bool destroyOnZoneChange;
bool doesntUntap;
QPoint gridPoint; QPoint gridPoint;
CardDragItem *dragItem; CardDragItem *dragItem;
CardItem *attachedTo;
QList<CardItem *> attachedCards; QList<CardItem *> attachedCards;
void prepareDelete(); void prepareDelete();
@ -48,20 +53,16 @@ public:
{ {
return Type; return Type;
} }
explicit CardItem(PlayerLogic *_owner, explicit CardItem(Player *_owner,
QGraphicsItem *parent = nullptr, QGraphicsItem *parent = nullptr,
const CardRef &cardRef = {}, const CardRef &cardRef = {},
int _cardid = -1, int _cardid = -1,
CardZoneLogic *_zone = nullptr); CardZoneLogic *_zone = nullptr);
void retranslateUi(); void retranslateUi();
[[nodiscard]] CardState *getState() const
{
return state;
}
[[nodiscard]] CardZoneLogic *getZone() const [[nodiscard]] CardZoneLogic *getZone() const
{ {
return state->getZone(); return zone;
} }
void setZone(CardZoneLogic *_zone); void setZone(CardZoneLogic *_zone);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
@ -77,50 +78,50 @@ public:
{ {
return gridPoint; return gridPoint;
} }
[[nodiscard]] PlayerLogic *getOwner() const [[nodiscard]] Player *getOwner() const
{ {
return owner; return owner;
} }
void setOwner(PlayerLogic *_owner) void setOwner(Player *_owner)
{ {
owner = _owner; owner = _owner;
} }
[[nodiscard]] bool getAttacking() const [[nodiscard]] bool getAttacking() const
{ {
return state->getAttacking(); return attacking;
} }
void setAttacking(bool _attacking); void setAttacking(bool _attacking);
[[nodiscard]] const QMap<int, int> &getCounters() const [[nodiscard]] const QMap<int, int> &getCounters() const
{ {
return state->getCounters(); return counters;
} }
void setCounter(int _id, int _value); void setCounter(int _id, int _value);
[[nodiscard]] QString getAnnotation() const [[nodiscard]] QString getAnnotation() const
{ {
return state->getAnnotation(); return annotation;
} }
void setAnnotation(const QString &_annotation); void setAnnotation(const QString &_annotation);
[[nodiscard]] bool getDoesntUntap() const [[nodiscard]] bool getDoesntUntap() const
{ {
return state->getDoesntUntap(); return doesntUntap;
} }
void setDoesntUntap(bool _doesntUntap); void setDoesntUntap(bool _doesntUntap);
[[nodiscard]] QString getPT() const [[nodiscard]] QString getPT() const
{ {
return state->getPT(); return pt;
} }
void setPT(const QString &_pt); void setPT(const QString &_pt);
[[nodiscard]] bool getDestroyOnZoneChange() const [[nodiscard]] bool getDestroyOnZoneChange() const
{ {
return state->getDestroyOnZoneChange(); return destroyOnZoneChange;
} }
void setDestroyOnZoneChange(bool _destroy) void setDestroyOnZoneChange(bool _destroy)
{ {
state->setDestroyOnZoneChange(_destroy); destroyOnZoneChange = _destroy;
} }
[[nodiscard]] CardItem *getAttachedTo() const [[nodiscard]] CardItem *getAttachedTo() const
{ {
return state->getAttachedTo(); return attachedTo;
} }
void setAttachedTo(CardItem *_attachedTo); void setAttachedTo(CardItem *_attachedTo);
void addAttachedCard(CardItem *card) void addAttachedCard(CardItem *card)
@ -145,26 +146,6 @@ public:
void drawAttachArrow(); void drawAttachArrow();
void playCard(bool faceDown); void playCard(bool faceDown);
/**
* @brief Parses a string representing a p/t in order to extract the values from it.
*
* If the string contains '/', the string will be split at the '/' and each side will be parsed separately,
* which means the result list will have two elements.
*
* If '/' is not found, then the entire string is parsed together, which means the result list will
* have a single element.
*
* If either side of the split is empty, there will also only be a single element in the result list.
*
* This function will attempt to parse each substring as an int first, handling plus and minus prefixes.
* If successful, it will put the parsed value into the QVariant as an int.
* If failed, it will just put the substring into the QVariant as a QString.
*
* @param pt The p/t string
* @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString
*/
static QVariantList parsePT(const QString &pt);
protected: protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;

View file

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

View file

@ -1,8 +1,8 @@
/** /**
* @file card_list.h * @file card_list.h
* @ingroup GameLogicCards * @ingroup GameLogicCards
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef CARDLIST_H #ifndef CARDLIST_H
#define CARDLIST_H #define CARDLIST_H

View file

@ -1,111 +0,0 @@
#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();
}

View file

@ -1,103 +0,0 @@
#ifndef COCKATRICE_CARD_STATE_H
#define COCKATRICE_CARD_STATE_H
#include <QMap>
#include <QObject>
class CardZoneLogic;
class CardItem;
class CardState : public QObject
{
Q_OBJECT
private:
bool attacking = false;
QMap<int, int> 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<int, int> &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<int, int> &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

View file

@ -1,12 +1,19 @@
#include "counter_general.h" #include "counter_general.h"
#include "../../game_graphics/board/abstract_graphics_item.h"
#include "../../interface/pixel_map_generator.h" #include "../../interface/pixel_map_generator.h"
#include "abstract_graphics_item.h"
#include <QPainter> #include <QPainter>
GeneralCounter::GeneralCounter(CounterState *state, PlayerLogic *player, bool useNameForShortcut, QGraphicsItem *parent) GeneralCounter::GeneralCounter(Player *_player,
: AbstractCounter(state, player, true, useNameForShortcut, parent) int _id,
const QString &_name,
const QColor &_color,
int _radius,
int _value,
bool useNameForShortcut,
QGraphicsItem *parent)
: AbstractCounter(_player, _id, _name, true, _value, useNameForShortcut, parent), color(_color), radius(_radius)
{ {
setCacheMode(DeviceCoordinateCache); setCacheMode(DeviceCoordinateCache);
} }

View file

@ -1,8 +1,8 @@
/** /**
* @file counter_general.h * @file counter_general.h
* @ingroup GameGraphicsPlayers * @ingroup GameGraphicsPlayers
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef COUNTER_GENERAL_H #ifndef COUNTER_GENERAL_H
#define COUNTER_GENERAL_H #define COUNTER_GENERAL_H
@ -12,10 +12,17 @@
class GeneralCounter : public AbstractCounter class GeneralCounter : public AbstractCounter
{ {
Q_OBJECT Q_OBJECT
private:
QColor color;
int radius;
public: public:
GeneralCounter(CounterState *state, GeneralCounter(Player *_player,
PlayerLogic *player, int _id,
const QString &_name,
const QColor &_color,
int _radius,
int _value,
bool useNameForShortcut = false, bool useNameForShortcut = false,
QGraphicsItem *parent = nullptr); QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override; QRectF boundingRect() const override;

View file

@ -1,24 +0,0 @@
#include "counter_state.h"
#include <libcockatrice/utility/color.h>
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);
}

View file

@ -1,51 +0,0 @@
#ifndef COCKATRICE_COUNTER_STATE_H
#define COCKATRICE_COUNTER_STATE_H
#include <QColor>
#include <QObject>
#include <QString>
#include <libcockatrice/protocol/pb/serverinfo_counter.pb.h>
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

View file

@ -1,8 +1,8 @@
/** /**
* @file translate_counter_name.h * @file translate_counter_name.h
* @ingroup GameGraphicsPlayers * @ingroup GameGraphicsPlayers
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef TRANSLATECOUNTERNAME_H #ifndef TRANSLATECOUNTERNAME_H
#define TRANSLATECOUNTERNAME_H #define TRANSLATECOUNTERNAME_H

View file

@ -12,16 +12,16 @@
*/ */
namespace CardDimensions namespace CardDimensions
{ {
/** @brief Card width in pixels. */ /// Card width in pixels
constexpr int WIDTH = 72; constexpr int WIDTH = 72;
/** @brief Card height in pixels. */ /// Card height in pixels
constexpr int HEIGHT = 102; constexpr int HEIGHT = 102;
/** @brief Pre-converted for floating-point contexts (Z-value calculations). */ /// Pre-converted for floating-point contexts (Z-value calculations)
constexpr qreal WIDTH_F = static_cast<qreal>(WIDTH); constexpr qreal WIDTH_F = static_cast<qreal>(WIDTH);
constexpr qreal HEIGHT_F = static_cast<qreal>(HEIGHT); constexpr qreal HEIGHT_F = static_cast<qreal>(HEIGHT);
/** @brief Half-dimensions for centering and rotation transforms. */ /// Half-dimensions for centering and rotation transforms
constexpr qreal WIDTH_HALF_F = WIDTH_F / 2; constexpr qreal WIDTH_HALF_F = WIDTH_F / 2;
constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2; constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2;
} // namespace CardDimensions } // namespace CardDimensions

View file

@ -24,21 +24,17 @@ void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos)
QList<QGraphicsItem *> colliding = scene()->items(cursorScenePos); QList<QGraphicsItem *> colliding = scene()->items(cursorScenePos);
DeckViewCardContainer *cursorZone = 0; DeckViewCardContainer *cursorZone = 0;
for (int i = colliding.size() - 1; i >= 0; i--) { for (int i = colliding.size() - 1; i >= 0; i--)
if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i)))) { if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i))))
break; break;
} if (!cursorZone)
}
if (!cursorZone) {
return; return;
}
currentZone = cursorZone; currentZone = cursorZone;
QPointF newPos = cursorScenePos; QPointF newPos = cursorScenePos;
if (newPos != pos()) { if (newPos != pos()) {
for (int i = 0; i < childDrags.size(); i++) { for (int i = 0; i < childDrags.size(); i++)
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot()); childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
}
setPos(newPos); setPos(newPos);
} }
} }
@ -108,13 +104,11 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{ {
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() < if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
2 * QApplication::startDragDistance()) { 2 * QApplication::startDragDistance())
return; return;
}
if (static_cast<DeckViewScene *>(scene())->getLocked()) { if (static_cast<DeckViewScene *>(scene())->getLocked())
return; return;
}
delete dragItem; delete dragItem;
dragItem = new DeckViewCardDragItem(this, event->pos()); dragItem = new DeckViewCardDragItem(this, event->pos());
@ -126,9 +120,8 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
int j = 0; int j = 0;
for (int i = 0; i < sel.size(); i++) { for (int i = 0; i < sel.size(); i++) {
auto *c = static_cast<DeckViewCard *>(sel.at(i)); auto *c = static_cast<DeckViewCard *>(sel.at(i));
if (c == this) { if (c == this)
continue; continue;
}
++j; ++j;
auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0); auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0);
auto *drag = new DeckViewCardDragItem(c, childPos, dragItem); auto *drag = new DeckViewCardDragItem(c, childPos, dragItem);
@ -140,9 +133,8 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void DeckView::mouseDoubleClickEvent(QMouseEvent *event) void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
{ {
if (static_cast<DeckViewScene *>(scene())->getLocked()) { if (static_cast<DeckViewScene *>(scene())->getLocked())
return; return;
}
if (event->button() == Qt::LeftButton) { if (event->button() == Qt::LeftButton) {
QList<MoveCard_ToZone> result; QList<MoveCard_ToZone> result;
@ -155,13 +147,12 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
m.set_card_name(c->getName().toStdString()); m.set_card_name(c->getName().toStdString());
m.set_start_zone(zone->getName().toStdString()); m.set_start_zone(zone->getName().toStdString());
if (zone->getName() == DECK_ZONE_MAIN) { if (zone->getName() == DECK_ZONE_MAIN)
m.set_target_zone(DECK_ZONE_SIDE); m.set_target_zone(DECK_ZONE_SIDE);
} else if (zone->getName() == DECK_ZONE_SIDE) { else if (zone->getName() == DECK_ZONE_SIDE)
m.set_target_zone(DECK_ZONE_MAIN); m.set_target_zone(DECK_ZONE_MAIN);
} else { // Trying to move from another zone else // Trying to move from another zone
m.set_target_zone(zone->getName().toStdString()); m.set_target_zone(zone->getName().toStdString());
}
result.append(m); result.append(m);
} }
@ -241,9 +232,8 @@ QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
{ {
QList<QPair<int, int>> result; QList<QPair<int, int>> result;
QList<QString> cardTypeList = cardsByType.uniqueKeys(); QList<QString> cardTypeList = cardsByType.uniqueKeys();
for (int i = 0; i < cardTypeList.size(); ++i) { for (int i = 0; i < cardTypeList.size(); ++i)
result.append(QPair<int, int>(1, cardsByType.count(cardTypeList[i]))); result.append(QPair<int, int>(1, cardsByType.count(cardTypeList[i])));
}
return result; return result;
} }
@ -272,9 +262,8 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>>
// Calculate space needed for cards // Calculate space needed for cards
for (int i = 0; i < rowsAndCols.size(); ++i) { for (int i = 0; i < rowsAndCols.size(); ++i) {
totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY; totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY;
if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth) { if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth)
totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second; totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second;
}
} }
return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY); return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
@ -282,9 +271,8 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>>
bool DeckViewCardContainer::sortCardsByName(DeckViewCard *c1, DeckViewCard *c2) bool DeckViewCardContainer::sortCardsByName(DeckViewCard *c1, DeckViewCard *c2)
{ {
if (c1 && c2) { if (c1 && c2)
return c1->getName() < c2->getName(); return c1->getName() < c2->getName();
}
return false; return false;
} }
@ -334,17 +322,15 @@ DeckViewScene::~DeckViewScene()
void DeckViewScene::clearContents() void DeckViewScene::clearContents()
{ {
QMapIterator<QString, DeckViewCardContainer *> i(cardContainers); QMapIterator<QString, DeckViewCardContainer *> i(cardContainers);
while (i.hasNext()) { while (i.hasNext())
delete i.next().value(); delete i.next().value();
}
cardContainers.clear(); cardContainers.clear();
} }
void DeckViewScene::setDeck(const DeckList &_deck) void DeckViewScene::setDeck(const DeckList &_deck)
{ {
if (deck) { if (deck)
delete deck; delete deck;
}
deck = new DeckList(_deck.writeToString_Native()); deck = new DeckList(_deck.writeToString_Native());
rebuildTree(); rebuildTree();
@ -356,9 +342,8 @@ void DeckViewScene::rebuildTree()
{ {
clearContents(); clearContents();
if (!deck) { if (!deck)
return; return;
}
for (auto *currentZone : deck->getZoneNodes()) { for (auto *currentZone : deck->getZoneNodes()) {
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0); DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
@ -370,9 +355,8 @@ void DeckViewScene::rebuildTree()
for (int j = 0; j < currentZone->size(); j++) { for (int j = 0; j < currentZone->size(); j++) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j)); auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard) { if (!currentCard)
continue; continue;
}
for (int k = 0; k < currentCard->getNumber(); ++k) { for (int k = 0; k < currentCard->getNumber(); ++k) {
auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName()); auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
@ -389,21 +373,18 @@ void DeckViewScene::applySideboardPlan(const QList<MoveCard_ToZone> &plan)
const MoveCard_ToZone &m = plan[i]; const MoveCard_ToZone &m = plan[i];
DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone())); DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone()));
DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone())); DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone()));
if (!start || !target) { if (!start || !target)
continue; continue;
}
DeckViewCard *card = 0; DeckViewCard *card = 0;
const QList<DeckViewCard *> &cardList = start->getCards(); const QList<DeckViewCard *> &cardList = start->getCards();
for (int j = 0; j < cardList.size(); ++j) { for (int j = 0; j < cardList.size(); ++j)
if (cardList[j]->getName() == QString::fromStdString(m.card_name())) { if (cardList[j]->getName() == QString::fromStdString(m.card_name())) {
card = cardList[j]; card = cardList[j];
break; break;
} }
} if (!card)
if (!card) {
continue; continue;
}
start->removeCard(card); start->removeCard(card);
target->addCard(card); target->addCard(card);
@ -424,9 +405,8 @@ void DeckViewScene::rearrangeItems()
rowsAndColsList.append(rowsAndCols); rowsAndColsList.append(rowsAndCols);
cardCountList.append(QList<int>()); cardCountList.append(QList<int>());
for (int j = 0; j < rowsAndCols.size(); ++j) { for (int j = 0; j < rowsAndCols.size(); ++j)
cardCountList[i].append(rowsAndCols[j].second); cardCountList[i].append(rowsAndCols[j].second);
}
} }
qreal totalHeight, totalWidth; qreal totalHeight, totalWidth;
@ -437,27 +417,23 @@ void DeckViewScene::rearrangeItems()
for (int i = 0; i < contList.size(); ++i) { for (int i = 0; i < contList.size(); ++i) {
QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]); QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]);
totalHeight += contSize.height() + spacing; totalHeight += contSize.height() + spacing;
if (contSize.width() > totalWidth) { if (contSize.width() > totalWidth)
totalWidth = contSize.width(); totalWidth = contSize.width();
}
} }
// We're done when the aspect ratio shifts from too high to too low. // We're done when the aspect ratio shifts from too high to too low.
if (totalWidth / totalHeight <= optimalAspectRatio) { if (totalWidth / totalHeight <= optimalAspectRatio)
break; break;
}
// Find category with highest column count // Find category with highest column count
int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0; int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0;
for (int i = 0; i < rowsAndColsList.size(); ++i) { for (int i = 0; i < rowsAndColsList.size(); ++i)
for (int j = 0; j < rowsAndColsList[i].size(); ++j) { for (int j = 0; j < rowsAndColsList[i].size(); ++j)
if (rowsAndColsList[i][j].second > maxCols) { if (rowsAndColsList[i][j].second > maxCols) {
maxIndex1 = i; maxIndex1 = i;
maxIndex2 = j; maxIndex2 = j;
maxCols = rowsAndColsList[i][j].second; maxCols = rowsAndColsList[i][j].second;
} }
}
}
// Add row to category // Add row to category
const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first; const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first;
@ -475,9 +451,8 @@ void DeckViewScene::rearrangeItems()
} }
totalWidth = totalHeight * optimalAspectRatio; totalWidth = totalHeight * optimalAspectRatio;
for (int i = 0; i < contList.size(); ++i) { for (int i = 0; i < contList.size(); ++i)
contList[i]->setWidth(totalWidth); contList[i]->setWidth(totalWidth);
}
setSceneRect(QRectF(0, 0, totalWidth, totalHeight)); setSceneRect(QRectF(0, 0, totalWidth, totalHeight));
} }
@ -495,7 +470,7 @@ QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
while (containerIterator.hasNext()) { while (containerIterator.hasNext()) {
DeckViewCardContainer *cont = containerIterator.next().value(); DeckViewCardContainer *cont = containerIterator.next().value();
const QList<DeckViewCard *> cardList = cont->getCards(); const QList<DeckViewCard *> cardList = cont->getCards();
for (int i = 0; i < cardList.size(); ++i) { for (int i = 0; i < cardList.size(); ++i)
if (cardList[i]->getOriginZone() != cont->getName()) { if (cardList[i]->getOriginZone() != cont->getName()) {
MoveCard_ToZone m; MoveCard_ToZone m;
m.set_card_name(cardList[i]->getName().toStdString()); m.set_card_name(cardList[i]->getName().toStdString());
@ -503,7 +478,6 @@ QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
m.set_target_zone(cont->getName().toStdString()); m.set_target_zone(cont->getName().toStdString());
result.append(m); result.append(m);
} }
}
} }
return result; return result;
} }

View file

@ -1,8 +1,8 @@
/** /**
* @file deck_view.h * @file deck_view.h
* @ingroup Lobby * @ingroup Lobby
* @brief TODO: Document this.
*/ */
//! \todo Document this file.
#ifndef DECKVIEW_H #ifndef DECKVIEW_H
#define DECKVIEW_H #define DECKVIEW_H

View file

@ -251,9 +251,8 @@ void DeckViewContainer::unloadDeck()
void DeckViewContainer::loadLocalDeck() void DeckViewContainer::loadLocalDeck()
{ {
DlgLoadDeck dialog(this); DlgLoadDeck dialog(this);
if (!dialog.exec()) { if (!dialog.exec())
return; return;
}
loadDeckFromFile(dialog.selectedFiles().at(0)); loadDeckFromFile(dialog.selectedFiles().at(0));
} }
@ -365,9 +364,8 @@ void DeckViewContainer::sideboardPlanChanged()
{ {
Command_SetSideboardPlan cmd; Command_SetSideboardPlan cmd;
const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan(); const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan();
for (const auto &i : newPlan) { for (const auto &i : newPlan)
cmd.add_move_list()->CopyFrom(i); cmd.add_move_list()->CopyFrom(i);
}
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId); parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
} }
@ -406,9 +404,8 @@ void DeckViewContainer::setSideboardLocked(bool locked)
{ {
sideboardLockButton->setState(!locked); sideboardLockButton->setState(!locked);
deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState()); deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState());
if (locked) { if (locked)
deckView->resetSideboardPlan(); deckView->resetSideboardPlan();
}
} }
void DeckViewContainer::setDeck(const DeckList &deck) void DeckViewContainer::setDeck(const DeckList &deck)

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