Compare commits

..

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

1732 changed files with 163577 additions and 196514 deletions

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 \
@ -9,13 +9,18 @@ RUN apt-get update && \
file \ file \
g++ \ g++ \
git \ git \
liblzma-dev \
libmariadb-dev-compat \ libmariadb-dev-compat \
libprotobuf-dev \ libprotobuf-dev \
libqt6sql6-mysql \ libqt5multimedia5-plugins \
libqt5sql5-mysql \
libqt5svg5-dev \
libqt5websockets5-dev \
ninja-build \ ninja-build \
protobuf-compiler \ protobuf-compiler \
qt6-tools-dev \ qt5-image-formats-plugins \
qt6-tools-dev-tools \ qtmultimedia5-dev \
qt6-websockets-dev \ qttools5-dev \
qttools5-dev-tools \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*

View file

@ -1,4 +1,4 @@
FROM fedora:43 FROM fedora:41
RUN dnf install -y \ RUN dnf install -y \
ccache \ ccache \

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,9 +1,8 @@
FROM debian:13 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

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

View file

@ -10,11 +10,8 @@
# --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 # uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR
# --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
# (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
@ -49,10 +46,6 @@ while [[ $# != 0 ]]; do
MAKE_SERVER=1 MAKE_SERVER=1
shift shift
;; ;;
'--no-client')
MAKE_NO_CLIENT=1
shift
;;
'--test') '--test')
MAKE_TEST=1 MAKE_TEST=1
shift shift
@ -73,19 +66,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')
USE_VCPKG=1
shift
;;
'--dir') '--dir')
shift shift
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
@ -95,24 +75,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')
shift
if [[ $# == 0 ]]; then
echo "::error file=$0::--target-macos-version expects an argument"
exit 3
fi
TARGET_MACOS_VERSION="$1"
shift
;;
*) *)
echo "::error file=$0::unrecognized option: $1" echo "::error file=$0::unrecognized option: $1"
exit 3 exit 3
@ -133,17 +95,11 @@ fi
mkdir -p "$BUILD_DIR" mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR" cd "$BUILD_DIR"
# Set minimum CMake Version
export CMAKE_POLICY_VERSION_MINIMUM=3.10
# Add cmake flags # Add cmake flags
flags=("-DCMAKE_BUILD_TYPE=$BUILDTYPE") flags=("-DCMAKE_BUILD_TYPE=$BUILDTYPE")
if [[ $MAKE_SERVER ]]; then if [[ $MAKE_SERVER ]]; then
flags+=("-DWITH_SERVER=1") flags+=("-DWITH_SERVER=1")
fi fi
if [[ $MAKE_NO_CLIENT ]]; then
flags+=("-DWITH_CLIENT=0" "-DWITH_ORACLE=0")
fi
if [[ $MAKE_TEST ]]; then if [[ $MAKE_TEST ]]; then
flags+=("-DTEST=1") flags+=("-DTEST=1")
fi fi
@ -157,9 +113,6 @@ fi
if [[ $PACKAGE_TYPE ]]; then if [[ $PACKAGE_TYPE ]]; then
flags+=("-DCPACK_GENERATOR=$PACKAGE_TYPE") flags+=("-DCPACK_GENERATOR=$PACKAGE_TYPE")
fi fi
if [[ $USE_VCPKG ]]; then
flags+=("-DUSE_VCPKG=1")
fi
# Add cmake --build flags # Add cmake --build flags
buildflags=(--config "$BUILDTYPE") buildflags=(--config "$BUILDTYPE")
@ -176,51 +129,9 @@ function ccachestatsverbose() {
# Compile # Compile
if [[ $RUNNER_OS == macOS ]]; then if [[ $RUNNER_OS == macOS ]]; then
# QTDIR is needed for macOS since we actually only use the cached thin Qt binaries instead of the install-qt-action,
# which sets a few environment variables
if QTDIR=$(find "$GITHUB_WORKSPACE/Qt" -depth -maxdepth 2 -name macos -type d -print -quit); then
echo "found QTDIR at $QTDIR"
else
echo "could not find QTDIR!"
exit 2
fi
# the qtdir is located at Qt/[qtversion]/macos
# we use find to get the first subfolder with the name "macos"
# this works independent of the qt version as there should be only one version installed on the runner at a time
export QTDIR
if [[ $TARGET_MACOS_VERSION ]]; then
# CMAKE_OSX_DEPLOYMENT_TARGET is a vanilla cmake flag needed to compile to target macOS version
flags+=("-DCMAKE_OSX_DEPLOYMENT_TARGET=$TARGET_MACOS_VERSION")
# vcpkg dependencies need a vcpkg triplet file to compile to the target macOS version
# an easy way is to copy the x64-osx.cmake file and modify it
triplets_dir="/tmp/cmake/triplets"
triplet_version="custom-triplet"
triplet_file="$triplets_dir/$triplet_version.cmake"
arch=$(uname -m)
if [[ $arch == x86_64 ]]; then
arch="x64"
fi
mkdir -p "$triplets_dir"
triplet_source="../vcpkg/triplets/$arch-osx.cmake"
if [[ ! -f "$triplet_source" ]]; then
triplet_source="../vcpkg/triplets/community/$arch-osx.cmake"
fi
cp "$triplet_source" "$triplet_file"
echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")
flags+=("-DVCPKG_HOST_TRIPLET=$triplet_version")
flags+=("-DVCPKG_TARGET_TRIPLET=$triplet_version")
echo "::group::Generated triplet $triplet_file"
cat "$triplet_file"
echo "::endgroup::"
fi
echo "::group::Signing Certificate" echo "::group::Signing Certificate"
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]; then if [[ -n "$MACOS_CERTIFICATE_NAME" ]]; then
echo "$MACOS_CERTIFICATE" | base64 --decode >"certificate.p12" echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security default-keychain -s build.keychain security default-keychain -s build.keychain
security set-keychain-settings -t 3600 -l build.keychain security set-keychain-settings -t 3600 -l build.keychain
@ -232,29 +143,6 @@ if [[ $RUNNER_OS == macOS ]]; then
echo "No signing certificate configured. Skipping set up of keychain in macOS environment." echo "No signing certificate configured. Skipping set up of keychain in macOS environment."
fi fi
echo "::endgroup::" echo "::endgroup::"
if [[ $MAKE_PACKAGE ]]; then
# Workaround https://github.com/actions/runner-images/issues/7522
# have hdiutil repeat the command 10 times in hope of success
hdiutil_script="/tmp/hdiutil.sh"
# shellcheck disable=SC2016
echo '#!/bin/bash
i=0
while ! hdiutil "$@"; do
if (( ++i >= 10 )); then
echo "Error: hdiutil failed $i times!" >&2
break
fi
sleep 1
done' >"$hdiutil_script"
chmod +x "$hdiutil_script"
flags+=(-DCPACK_COMMAND_HDIUTIL="$hdiutil_script")
fi
elif [[ $RUNNER_OS == Windows ]]; then
# Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
# and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
buildflags+=(-- -p:UseMultiToolTask=true -p:EnableClServerMode=true)
fi fi
if [[ $USE_CCACHE ]]; then if [[ $USE_CCACHE ]]; then
@ -265,39 +153,23 @@ fi
echo "::group::Configure cmake" echo "::group::Configure cmake"
cmake --version cmake --version
echo "Running cmake with flags: ${flags[*]}"
cmake .. "${flags[@]}" cmake .. "${flags[@]}"
echo "::endgroup::" echo "::endgroup::"
echo "::group::Build project" echo "::group::Build project"
echo "Running cmake --build with flags: ${buildflags[*]}" if [[ $RUNNER_OS == Windows ]]; then
cmake --build . "${buildflags[@]}" # Enable MTT, see https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
# and https://devblogs.microsoft.com/cppblog/cpp-build-throughput-investigation-and-tune-up/#multitooltask-mtt
cmake --build . "${buildflags[@]}" -- -p:UseMultiToolTask=true -p:EnableClServerMode=true
else
cmake --build . "${buildflags[@]}"
fi
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
if [[ $RUNNER_OS == macOS ]]; then
echo "::group::Inspect Mach-O binaries"
for app in cockatrice oracle servatrice; do
binary="$GITHUB_WORKSPACE/build/$app/$app.app/Contents/MacOS/$app"
echo "Inspecting $app..."
vtool -show-build "$binary"
file "$binary"
lipo -info "$binary"
echo ""
done
echo "::endgroup::"
fi fi
if [[ $MAKE_TEST ]]; then if [[ $MAKE_TEST ]]; then
@ -314,6 +186,12 @@ fi
if [[ $MAKE_PACKAGE ]]; then if [[ $MAKE_PACKAGE ]]; then
echo "::group::Create package" echo "::group::Create package"
if [[ $RUNNER_OS == macOS ]]; then
# Workaround https://github.com/actions/runner-images/issues/7522
echo "killing XProtectBehaviorService"; sudo pkill -9 XProtect >/dev/null || true;
echo "waiting for XProtectBehaviorService kill"; while pgrep "XProtect"; do sleep 3; done;
fi
cmake --build . --target package --config "$BUILDTYPE" cmake --build . --target package --config "$BUILDTYPE"
echo "::endgroup::" echo "::endgroup::"

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
@ -153,11 +137,10 @@ if [[ $SAVE ]]; then
fi fi
# Set compile function, runs the compile script on the image, passes arguments to the script # Set compile function, runs the compile script on the image, passes arguments to the script
# shellcheck disable=2120
function RUN () function RUN ()
{ {
echo "running image:" echo "running image:"
if [[ $(docker images) =~ $IMAGE_NAME ]]; then if [[ $(docker images) =~ "$IMAGE_NAME" ]]; then
local args=(--mount "type=bind,source=$PWD,target=/src") local args=(--mount "type=bind,source=$PWD,target=/src")
args+=(--workdir "/src") args+=(--workdir "/src")
args+=(--user "$(id -u):$(id -g)") args+=(--user "$(id -u):$(id -g)")
@ -165,11 +148,9 @@ 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
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
@ -183,6 +164,5 @@ function RUN ()
if [[ $INTERACTIVE ]]; then if [[ $INTERACTIVE ]]; then
export BUILD_SCRIPT="-i" export BUILD_SCRIPT="-i"
export RUN_ARGS="$RUN_ARGS -it" export RUN_ARGS="$RUN_ARGS -it"
# shellcheck disable=2119
RUN RUN
fi fi

View file

@ -11,9 +11,9 @@ if ! git merge-base origin/master HEAD; then
fi fi
# Check formatting using format.sh # Check formatting using format.sh
echo "Checking your code using format.sh..." echo "Checking your code using clang-format/cmake-format..."
./format.sh --color-diff --cmake --shell --print-version --branch origin/master diff="$(./format.sh --diff --cmake --cf-version --branch origin/master)"
err=$? err=$?
case $err in case $err in
@ -28,10 +28,20 @@ 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:
${diff%%
----------
*}
The following changes should be made:
${diff#*
----------
}
Exiting... Exiting...
EOM EOM
exit 2 exit 2

View file

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

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

@ -10,6 +10,7 @@ Available pre-compiled binaries for installation:
<b>Windows</b> <b>Windows</b>
<kbd>Windows 10+</kbd> <kbd>Windows 10+</kbd>
<kbd>Windows 7+</kbd>
<b>macOS</b> <b>macOS</b>
<kbd>macOS 15+</kbd> <sub><i>Sequoia</i></sub> <sub>Apple M</sub> <kbd>macOS 15+</kbd> <sub><i>Sequoia</i></sub> <sub>Apple M</sub>
@ -17,17 +18,15 @@ 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>Debian 13</kbd> <sub><i>Trixie</i></sub> <kbd>Ubuntu 22.04 LTS</kbd> <sub><i>Jammy Jellyfish</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 42</kbd>
<kbd>Fedora 41</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

@ -1,49 +0,0 @@
#!/bin/bash
# This script is used to resolve the latest patch version of Qt using aqtinstall.
# It interprets wildcards to get the latest patch version. E.g. "6.6.*" -> "6.6.3".
# This script is meant to be used by the ci enironment.
# It uses the runner's GITHUB_OUTPUT env variable.
# Usage example: .ci/resolve_latest_aqt_qt_version.sh "6.6.*"
qt_spec=$1
if [[ ! $qt_spec ]]; then
echo "usage: $0 [version]"
exit 2
fi
# If version is already specific (no wildcard), use it as-is
if [[ $qt_spec != *"*" ]]; then
echo "version $qt_spec is already resolved"
echo "version=$qt_spec" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! hash aqt; then
echo "aqt could not be found, has aqtinstall been installed?"
exit 2
fi
# Resolve latest patch
if [[ $RUNNER_OS == macOS ]]; then
if ! qt_resolved=$(aqt list-qt mac desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
elif [[ $RUNNER_OS == Windows ]]; then
if ! qt_resolved=$(aqt list-qt windows desktop --spec "$qt_spec" --latest-version); then
exit 1
fi
else
echo "aqt command for $RUNNER_OS not defined."
exit 1
fi
echo "resolved $qt_spec to $qt_resolved"
if [[ ! $qt_resolved ]]; then
echo "Error: Could not resolve Qt version for $qt_spec"
exit 1
fi
echo "version=$qt_resolved" >> "$GITHUB_OUTPUT"

View file

@ -1,25 +0,0 @@
#!/bin/bash
# The macos binaries from aqt are fat (universal), so we thin them to the target architecture to reduce the size of
# the packages and caches using lipo.
# This script is meant to be used by the ci enironment on macos runners only.
# It uses the runner's GITHUB_WORKSPACE env variable.
arch=$(uname -m)
nproc=$(sysctl -n hw.ncpu)
function thin() {
local libfile=$1
if [[ $(file -b --mime-type "$libfile") == application/x-mach-binary* ]]; then
echo "Processing $libfile"
lipo "$libfile" -thin "$arch" -output "$libfile"
fi
return 0
}
export -f thin # export to allow use in xargs
export arch
set -eo pipefail
echo "::group::Thinning Qt libraries to $arch using $nproc cores"
find "$GITHUB_WORKSPACE/Qt" -type f -print0 | xargs -0 -n1 -P"$nproc" -I{} bash -c "thin '{}'"
echo "::endgroup::"

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,13 @@ 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
StatementAttributeLikeMacros: [emit] IncludeBlocks: Regroup
--- ---
Language: Proto Language: Proto
AllowShortFunctionsOnASingleLine: None AllowShortFunctionsOnASingleLine: None

View file

@ -1,5 +1,3 @@
<!--! @page contributing Contributing -->
&nbsp; [Introduction](#contributing-to-cockatrice) | [Code Style Guide]( &nbsp; [Introduction](#contributing-to-cockatrice) | [Code Style Guide](
#code-style-guide) | [Translations](#translations) | [Release Management]( #code-style-guide) | [Translations](#translations) | [Release Management](
#release-management) #release-management)
@ -209,16 +207,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
@ -471,11 +459,7 @@ revoke the tag by doing the following:
git push --delete upstream $TAG_NAME git push --delete upstream $TAG_NAME
git tag -d $TAG_NAME git tag -d $TAG_NAME
``` ```
You can also do this on GitHub. You can also do this on GitHub, you'll also want to delete the false release.
> [!NOTE]
> If you want to push a new release to replace it immediately with the same
> name you have to delete the automatically created release first!
In the first lines of [CMakeLists.txt]( In the first lines of [CMakeLists.txt](
https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt) https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt)

View file

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

View file

@ -2,21 +2,19 @@
version: 2 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,54 +1,44 @@
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
on: on:
push: push:
branches: branches:
- master - master
paths: paths-ignore:
- '*/**' # matches all files not in root - '**.md'
- '!**.md' - 'webclient/**'
- '!.github/**' - '.github/workflows/web-*.yml'
- '!.tx/**' - '.github/workflows/translations-*.yml'
- '!doc/**' - '.github/workflows/docker-release.yml'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg' # needed to match submodule bumps (gitlink)
tags: tags:
- '*' - '*'
pull_request: pull_request:
paths: paths-ignore:
- '*/**' # matches all files not in root - '**.md'
- '!**.md' - 'webclient/**'
- '!.github/**' - '.github/workflows/web-*.yml'
- '!.tx/**' - '.github/workflows/translations-*.yml'
- '!doc/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- 'vcpkg' # needed to match submodule bumps (gitlink)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release) # Cancel earlier, unfinished runs of this workflow on the same branch (unless on 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,423 +54,262 @@ 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@v4
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: Servatrice_Debian
version: 12
package: DEB
server_only: yes
test: skip
- distro: Debian - distro: Debian
version: 12 version: 11
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: 12
package: DEB package: DEB
- distro: Fedora - distro: Fedora
version: 43 version: 41
package: RPM package: RPM
test: skip # Running tests on all distros is superfluous test: skip # Running tests on all distros is superfluous
- distro: Fedora - distro: Fedora
version: 44 version: 42
package: RPM package: RPM
- distro: Ubuntu
version: 22.04
package: DEB
test: skip # Running tests on all distros is superfluous
- 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: 500M
CMAKE_GENERATOR: 'Ninja' CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
steps: steps:
- name: "Checkout" - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
- 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@v4
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}}'
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"
[[ $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: Save compiler cache (ccache)
- name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
echo "Cache deleted successfully"
fi
- name: "Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v5 uses: actions/cache/save@v4
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@v4
with: with:
archive: false name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}}
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_path: ${{steps.build.outputs.path}}
tag_name: ${{ needs.configure.outputs.tag }} asset_name: ${{steps.build.outputs.name}}
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-build-provenance@v2
with: with:
show-summary: false subject-name: ${{steps.build.outputs.name}}
subject-path: ${{ steps.build.outputs.path }} subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
- 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}} -R Cockatrice/Cockatrice
build-vcpkg: build-macos:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: macOS - target: 13
target: 13
runner: macos-15-intel
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Intel soc: Intel
os: macos-13
xcode: "14.3.1"
type: Release type: Release
use_ccache: 1
xcode: "16.4"
- os: macOS
target: 14
runner: macos-14
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1 make_package: 1
package_suffix: "-macOS14"
qt_version: 6.11.0 - target: 14
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple soc: Apple
type: Release os: macos-14
use_ccache: 1
xcode: "15.4" xcode: "15.4"
- os: macOS
target: 15
runner: macos-15
ccache_eviction_age: 7d
cmake_generator: Ninja
make_package: 1
package_suffix: "-macOS15"
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple
type: Release type: Release
use_ccache: 1 make_package: 1
xcode: "16.4"
- os: macOS - target: 15
target: 15
runner: macos-15
ccache_eviction_age: 7d
cmake_generator: Ninja
qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
soc: Apple soc: Apple
os: macos-15
xcode: "16.2"
type: Release
make_package: 1
- target: 15
soc: Apple
os: macos-15
xcode: "16.2"
type: Debug type: Debug
use_ccache: 1
xcode: "16.4"
- os: Windows name: macOS ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
target: 10
runner: windows-2025
cmake_generator: "Visual Studio 18 2026"
cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
qt_version: 6.11.0
qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
type: Release
name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure needs: configure
runs-on: ${{ matrix.runner }} runs-on: ${{matrix.os}}
timeout-minutes: 100 continue-on-error: ${{matrix.allow-failure == 'yes'}}
env: env:
CCACHE_DIR: ${{ github.workspace }}/.cache/ CCACHE_DIR: ${{github.workspace}}/.ccache/${{matrix.os}}-${{matrix.type}}
CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy CCACHE_SIZE: 500M
DEVELOPER_DIR:
/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer
CMAKE_GENERATOR: 'Ninja'
steps: steps:
- name: "Checkout" - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v4
with:
submodules: recursive
- name: "[Windows] Add msbuild to PATH" - name: Install dependencies using Homebrew
if: matrix.os == 'Windows' shell: bash
id: add-msbuild # CMake cannot find the MySQL connector
uses: microsoft/setup-msbuild@v3 # Neither of these works: mariadb-connector-c mysql-connector-c++
with: env:
msbuild-architecture: x64 HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
run: |
brew update
brew install ccache protobuf qt --force-bottle
- name: "[macOS] Setup ccache" - name: Restore compiler cache (ccache)
if: matrix.os == 'macOS' && matrix.use_ccache == 1
run: brew install ccache
- name: "[macOS] Restore compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: ccache_restore id: ccache_restore
uses: actions/cache/restore@v5 uses: actions/cache/restore@v4
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.os}}-${{matrix.type}}-${{env.BRANCH_NAME}}
restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}- restore-keys: ccache-${{matrix.os}}-${{matrix.type}}-
- name: "Install aqtinstall" - name: Build on Xcode ${{matrix.xcode}}
run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- name: "Resolve latest Qt patch version"
id: resolve_qt_version
shell: bash shell: bash
run: .ci/resolve_latest_aqt_qt_version.sh "${{ matrix.qt_version }}"
- name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
if: matrix.os == 'macOS'
id: restore_qt
uses: actions/cache/restore@v5
with:
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew
# Qt build using vcpkg either just fails or takes too long to build
- name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
arch: ${{ matrix.qt_arch }}
cache: false
dir: ${{ github.workspace }}
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
- name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- name: "[macOS] Cache thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
- name: "[Windows] Install Qt ${{ matrix.qt_version }}"
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
# Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
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 }}
- name: "[Windows] Install NSIS"
if: matrix.os == 'Windows'
shell: bash
run: choco install nsis
- name: "Setup vcpkg cache"
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Uses environment variables, see compile.sh for more details
- name: "Build Cockatrice"
id: build id: build
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: '-macOS${{matrix.target}}_${{matrix.soc}}'
CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} 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 }} MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
MAKE_PACKAGE: '${{ matrix.make_package }}' CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}' run: .ci/compile.sh --server --test --ccache "$CCACHE_SIZE"
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
USE_CCACHE: ${{ matrix.use_ccache }}
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
VCPKG_DISABLE_METRICS: 1
run: .ci/compile.sh --server --test --vcpkg
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: Save compiler cache (ccache)
- name: "[macOS] Delete remote compiler cache (ccache)" if: github.ref == 'refs/heads/master'
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit uses: actions/cache/save@v4
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
echo "Cache deleted successfully"
fi
- name: "[macOS] Save updated compiler cache (ccache)"
if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v5
with: 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.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
id: sign_macos
env: env:
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
@ -488,83 +317,177 @@ 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: matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
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
# Store the notarization credentials so that we can prevent a UI password dialog from blocking the CI # Store the notarization credentials so that we can prevent a UI password dialog from blocking the CI
echo "Create keychain profile" echo "Create keychain profile"
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$MACOS_NOTARIZATION_APPLE_ID" --team-id "$MACOS_NOTARIZATION_TEAM_ID" --password "$MACOS_NOTARIZATION_PWD" xcrun notarytool store-credentials "notarytool-profile" --apple-id "$MACOS_NOTARIZATION_APPLE_ID" --team-id "$MACOS_NOTARIZATION_TEAM_ID" --password "$MACOS_NOTARIZATION_PWD"
# We can't notarize an app bundle directly, but we need to compress it as an archive. # We can't notarize an app bundle directly, but we need to compress it as an archive.
# 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
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if # characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
# you're curious # you're curious
echo "Notarize app" echo "Notarize app"
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
# 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
id: upload_artifact id: upload_artifact
uses: actions/upload-artifact@v7 if: matrix.make_package
uses: actions/upload-artifact@v4
with: with:
archive: false name: macOS${{matrix.target}}${{ matrix.soc == 'Intel' && '_Intel' || '' }}${{ matrix.type == 'Debug' && '_Debug' || '' }}-package
path: ${{steps.build.outputs.path}}
if-no-files-found: error if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: "[Windows] Upload PDBs (Program Databases)" - name: Upload to release
if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
with:
if-no-files-found: error
name: ${{ steps.build.outputs.name }}-PDBs
path: |
build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb
- name: "Upload to release"
if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release id: upload_release
if: matrix.make_package && 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_path: ${{steps.build.outputs.path}}
tag_name: ${{ needs.configure.outputs.tag }} asset_name: ${{steps.build.outputs.name}}
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'
id: attestation id: attestation
uses: actions/attest@v4 if: steps.upload_release.outcome == 'success'
uses: actions/attest-build-provenance@v2
with: with:
show-summary: false subject-name: ${{steps.build.outputs.name}}
subject-path: ${{ steps.build.outputs.path }} subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
- 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}} -R Cockatrice/Cockatrice
build-windows:
strategy:
fail-fast: false
matrix:
include:
- target: 7
qt_version: 5.15.*
qt_arch: msvc2019_64
- target: 10
qt_version: 6.6.*
qt_arch: msvc2019_64
qt_modules: "qtimageformats qtmultimedia qtwebsockets"
name: Windows ${{matrix.target}}
needs: configure
runs-on: windows-2022
env:
CMAKE_GENERATOR: 'Visual Studio 17 2022'
steps:
- name: Add msbuild to PATH
id: add-msbuild
uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x64
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Qt ${{matrix.qt_version}}
uses: jurplel/install-qt-action@v4
with:
cache: true
setup-python: true
version: ${{matrix.qt_version}}
arch: win64_${{matrix.qt_arch}}
tools: ${{matrix.qt_tools}}
modules: ${{matrix.qt_modules}}
- name: Setup vcpkg cache
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Build Cockatrice
id: build
shell: bash
env:
PACKAGE_SUFFIX: '-Win${{matrix.target}}'
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
CMAKE_GENERATOR_PLATFORM: 'x64'
QTDIR: '${{github.workspace}}\Qt\${{matrix.qt_version}}\win64_${{matrix.qt_arch}}'
VCPKG_DISABLE_METRICS: 1
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
# No need for --parallel flag, MTT is added in the compile script to let cmake/msbuild manage core count,
# project and process parallelism: https://devblogs.microsoft.com/cppblog/improved-parallelism-in-msbuild/
run: .ci/compile.sh --server --release --test --package
- name: Upload artifact
id: upload_artifact
uses: actions/upload-artifact@v4
with:
name: Windows${{matrix.target}}-installer
path: ${{steps.build.outputs.path}}
if-no-files-found: error
- name: Upload pdb database
uses: actions/upload-artifact@v4
with:
name: Windows${{matrix.target}}-debug-pdbs
path: |
build/cockatrice/Release/*.pdb
build/servatrice/Release/*.pdb
if-no-files-found: error
- name: Upload to release
id: upload_release
if: needs.configure.outputs.tag != null
shell: bash
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
asset_path: ${{steps.build.outputs.path}}
asset_name: ${{steps.build.outputs.name}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance
id: attestation
if: steps.upload_release.outcome == 'success'
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{steps.build.outputs.name}}
subject-digest: sha256:${{ steps.upload_artifact.outputs.artifact-digest }}
- name: Verify binary attestation
if: steps.attestation.outcome == 'success'
shell: bash
env:
GH_TOKEN: ${{github.token}}
run: gh attestation verify ${{steps.build.outputs.path}} -R Cockatrice/Cockatrice

View file

@ -1,40 +1,30 @@
name: Code Style (C++) name: Code Style (C++)
on: on:
# Push trigger not needed for linting, we do not allow direct pushes to master
pull_request: pull_request:
paths: paths-ignore:
- '*/**' # matches all files not in root - '**.md'
- '!**.md' - 'webclient/**'
- '!.ci/**' - '.github/workflows/web-*.yml'
- '!.github/**' - '.github/workflows/translations-*.yml'
- '!.tx/**' - '.github/workflows/docker-release.yml'
- '!doc/**'
- '.ci/lint_cpp.sh'
- '.github/workflows/desktop-lint.yml'
- '.clang-format'
- '.cmake-format.json'
- 'format.sh'
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@v4
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
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:
@ -13,65 +11,61 @@ on:
- master - master
paths: paths:
- '.github/workflows/docker-release.yml' - '.github/workflows/docker-release.yml'
- 'CMakeLists.txt'
- 'Dockerfile' - 'Dockerfile'
release: - 'servatrice/**'
types: - 'common/**'
- released # publishing of stable releases - 'cmake/**'
- '!**.md'
# 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:
steps: contents: read
- name: "Checkout" packages: write
uses: actions/checkout@v6
- name: "Docker metadata" steps:
- name: Checkout
uses: actions/checkout@v4
- name: Docker metadata
id: metadata id: metadata
uses: docker/metadata-action@v6 uses: docker/metadata-action@v5
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@v3
- name: "Set up Docker buildx" - name: Set up Docker buildx
uses: docker/setup-buildx-action@v4 uses: docker/setup-buildx-action@v3
- 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@v6
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 }}
cache-from: type=gha
cache-to: type=gha,mode=max

View file

@ -1,63 +0,0 @@
name: Generate Docs
on:
pull_request:
paths:
- 'doc/doxygen/**'
- '.github/workflows/documentation-build.yml'
- 'Doxyfile'
release:
types:
- published # publishing of stable releases and pre-releases
workflow_dispatch:
env:
COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name
jobs:
docs:
name: Doxygen
runs-on: ubuntu-latest
steps:
- name: "Checkout code"
uses: actions/checkout@v6
with:
submodules: recursive
- name: "Install Graphviz"
run: |
sudo apt-get install -y graphviz
dot -V
- name: "Install Doxygen"
uses: ssciwr/doxygen-install@v2
with:
version: "1.16.1"
- name: "Update Doxygen Configuration"
run: |
git diff Doxyfile
doxygen -u Doxyfile
if git diff --quiet Doxyfile; then
echo "::notice::No config changes in Doxyfile detected."
else
echo "::error::Config changes in Doxyfile detected! Please update the file by running 'doxygen -u Doxyfile'."
echo ""
git diff --color=always Doxyfile
exit 1
fi
- name: "Generate Documentation"
if: always()
run: doxygen Doxyfile
- name: "Deploy to cockatrice.github.io"
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
destination_dir: docs # docs will be available at https://cockatrice.github.io/docs/
external_repository: Cockatrice/cockatrice.github.io
publish_branch: master
publish_dir: ./docs/html

View file

@ -1,14 +1,13 @@
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/**'
- '.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,29 +15,35 @@ 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@v4
- 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@v7
with: with:
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 +53,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,13 @@
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'
- '.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,37 +15,37 @@ 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@v4
- 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: | env:
FILE="cockatrice/cockatrice_en@source.ts" FILE: 'cockatrice/cockatrice_en@source.ts'
export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')" DIRS: 'cockatrice/src common'
FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh run: .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,26 +53,27 @@ 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@v7
with: with:
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:

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

@ -0,0 +1,52 @@
name: Build Web
on:
push:
branches:
- master
paths:
- '.github/workflows/web-*.yml'
- 'webclient/**'
- '!**.md'
pull_request:
paths:
- '.github/workflows/web-*.yml'
- 'webclient/**'
- '!**.md'
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@v4
- name: Setup Node.js
uses: actions/setup-node@v4
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

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

@ -0,0 +1,32 @@
name: Code Style (TypeScript)
on:
pull_request:
paths:
- '.github/workflows/web-*.yml'
- 'webclient/**'
- '!**.md'
jobs:
ESLint:
runs-on: ubuntu-latest
defaults:
run:
working-directory: webclient
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
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

1
.gitignore vendored
View file

@ -14,4 +14,3 @@ compile_commands.json
.cache .cache
.gdb_history .gdb_history
cockatrice/resources/config/qtlogging.ini cockatrice/resources/config/qtlogging.ini
docs/

3
.gitmodules vendored
View file

@ -1,6 +1,3 @@
[submodule "vcpkg"] [submodule "vcpkg"]
path = vcpkg path = vcpkg
url = https://github.com/microsoft/vcpkg.git url = https://github.com/microsoft/vcpkg.git
[submodule "doxygen-awesome-css"]
path = doc/doxygen/theme
url = https://github.com/jothepro/doxygen-awesome-css.git

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

@ -20,10 +20,10 @@ option(WITH_SERVER "build servatrice" OFF)
option(WITH_CLIENT "build cockatrice" ON) option(WITH_CLIENT "build cockatrice" ON)
# Compile oracle # Compile oracle
option(WITH_ORACLE "build oracle" ON) option(WITH_ORACLE "build oracle" ON)
# Compile dbconverter
option(WITH_DBCONVERTER "build dbconverter" ON)
# Compile tests # Compile tests
option(TEST "build tests" OFF) option(TEST "build tests" OFF)
# Use vcpkg regardless of OS
option(USE_VCPKG "Use vcpkg regardless of OS" OFF)
# Default to "Release" build type # Default to "Release" build type
# User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call # User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call
@ -48,8 +48,8 @@ if(USE_CCACHE)
endif() endif()
endif() endif()
if(WIN32 OR USE_VCPKG) if(WIN32)
# Use vcpkg toolchain on Windows (and on macOS in CI) # Use vcpkg toolchain on Windows
set(CMAKE_TOOLCHAIN_FILE set(CMAKE_TOOLCHAIN_FILE
${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake
CACHE STRING "Vcpkg toolchain file" CACHE STRING "Vcpkg toolchain 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})
@ -255,9 +254,7 @@ endif()
set(CPACK_PACKAGE_CONTACT "Zach Halpern <zach@cockatrice.us>") set(CPACK_PACKAGE_CONTACT "Zach Halpern <zach@cockatrice.us>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_NAME}")
set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team") set(CPACK_PACKAGE_VENDOR "Cockatrice Development Team")
set(CPACK_PACKAGE_DESCRIPTION set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md")
"Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline."
)
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
@ -304,7 +301,6 @@ if(UNIX)
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
if(Qt6_FOUND) if(Qt6_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6multimedia6, libqt6svg6, qt6-qpa-plugins, qt6-image-formats-plugins")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "libqt6sql6-mysql") # for connecting servatrice to a mysql db
elseif(Qt5_FOUND) elseif(Qt5_FOUND)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins, libqt5svg5")
endif() endif()
@ -329,24 +325,7 @@ endif()
include(CPack) include(CPack)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_interfaces ${CMAKE_BINARY_DIR}/libcockatrice_interfaces) add_subdirectory(common)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_protocol ${CMAKE_BINARY_DIR}/libcockatrice_protocol)
if(WITH_CLIENT
OR WITH_SERVER
OR WITH_ORACLE
)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_network ${CMAKE_BINARY_DIR}/libcockatrice_network)
endif()
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_deck_list ${CMAKE_BINARY_DIR}/libcockatrice_deck_list)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_rng ${CMAKE_BINARY_DIR}/libcockatrice_rng)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_card ${CMAKE_BINARY_DIR}/libcockatrice_card)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_utility ${CMAKE_BINARY_DIR}/libcockatrice_utility)
if(WITH_ORACLE OR WITH_CLIENT)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_settings ${CMAKE_BINARY_DIR}/libcockatrice_settings)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_models ${CMAKE_BINARY_DIR}/libcockatrice_models)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_filters ${CMAKE_BINARY_DIR}/libcockatrice_filters)
endif()
if(WITH_SERVER) if(WITH_SERVER)
add_subdirectory(servatrice) add_subdirectory(servatrice)
set(CPACK_INSTALL_CMAKE_PROJECTS "Servatrice;Servatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) set(CPACK_INSTALL_CMAKE_PROJECTS "Servatrice;Servatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
@ -362,6 +341,11 @@ if(WITH_ORACLE)
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif() endif()
if(WITH_DBCONVERTER)
add_subdirectory(dbconverter)
set(CPACK_INSTALL_CMAKE_PROJECTS "Dbconverter;Dbconverter;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(TEST) if(TEST)
include(CTest) include(CTest)
add_subdirectory(tests) add_subdirectory(tests)

View file

@ -1,9 +1,8 @@
# -------- Build Stage -------- FROM ubuntu:24.04
FROM ubuntu:26.04 AS build
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y\
build-essential \ build-essential \
cmake \ cmake \
file \ file \
@ -17,28 +16,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
qt6-tools-dev \ qt6-tools-dev \
qt6-tools-dev-tools qt6-tools-dev-tools
WORKDIR /src COPY ./CMakeLists.txt ./LICENSE ./README.md /home/servatrice/code/
COPY . . COPY ./cmake /home/servatrice/code/cmake
RUN mkdir build && cd build && \ COPY ./common /home/servatrice/code/common
cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 && \ COPY ./servatrice /home/servatrice/code/servatrice
make -j$(nproc) && \
make install
WORKDIR /home/servatrice/code
# -------- Runtime Stage (clean) -------- WORKDIR build
FROM ubuntu:26.04 RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=0 &&\
make &&\
RUN apt-get update && apt-get install -y --no-install-recommends \ make install
libprotobuf32t64 \
libqt6sql6-mysql \
libqt6websockets6 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Only copy installed binaries, not source
COPY --from=build /usr/local /usr/local
WORKDIR /home/servatrice WORKDIR /home/servatrice
EXPOSE 4748 EXPOSE 4748
ENTRYPOINT [ "servatrice", "--log-to-console" ] ENTRYPOINT [ "servatrice", "--log-to-console" ]

3015
Doxyfile

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
Version 2, June 1991 Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
<https://fsf.org/> 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
@ -304,7 +304,8 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program; if not, see <https://www.gnu.org/licenses/>. with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
@ -328,8 +329,8 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker. `Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Moe Ghoul>, 1 April 1989 <signature of Ty Coon>, 1 April 1989
Moe Ghoul, President of Vice Ty Coon, President of Vice
This General Public License does not permit incorporating your program into This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may proprietary programs. If your program is a subroutine library, you may

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)
@ -43,43 +44,24 @@ Latest <kbd>beta</kbd> version:
# Related Repositories # Related Repositories
- [Magic-Token](https://github.com/Cockatrice/Magic-Token): File with MtG token data for use in Cockatrice - [Magic-Token](https://github.com/Cockatrice/Magic-Token): MtG token data to 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 - [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Script to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) to 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 webpage of the Cockatrice project
- [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)
Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other project contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out. Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
- [Official Website](https://cockatrice.github.io) - [Official Website](https://cockatrice.github.io)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) - [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
- [Official Discord](https://discord.gg/3Z9yzmA) - [Official Discord](https://discord.gg/3Z9yzmA)
- [reddit r/Cockatrice](https://reddit.com/r/cockatrice) - [reddit r/Cockatrice](https://reddit.com/r/cockatrice)
> [!IMPORTANT] >[!IMPORTANT]
> For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software. >For support regarding specific servers, please contact that server's admin/mods and use their dedicated communication channels rather than contacting the team building the software.
# Contribute # Contribute
<p>
<a href="#code">Code</a> <b>|</b>
<a href="#documentation-">Documentation</a> <b>|</b>
<a href="#translation-">Translation</a>
</p>
#### Repository Activity
![Cockatrice Repo Analytics](https://repobeats.axiom.co/api/embed/c7cec938789a5bbaeb4182a028b4dbb96db8f181.svg "Cockatrice Repo Analytics by Repobeats")
<details>
<summary><b>Kudos to all our amazing contributors ❤️</b></summary>
<br>
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
</a><br>
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
</details>
### Code ### Code
@ -93,26 +75,25 @@ This tag is used for issues that we are looking for somebody to pick up. Often t
For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.<br> For both tags, we're willing to provide help to contributors in showing them where and how they can make changes, as well as code reviews for submitted changes.<br>
We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on! We'll happily advice on how best to implement a feature, or we can show you where the codebase is doing something similar before you get too far along - put a note on an issue you want to discuss more on!
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush)
There are various places where useful information for different needs are maintained:
- [Official Code Documentation](https://cockatrice.github.io/docs/)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki) `Community supported`
- [Official Webpage](https://cockatrice.github.io/)
- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file`
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide. Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/) <details>
<summary><b>Kudos to our amazing contributors ❤️</b></summary>
<br>
<a href="https://github.com/Cockatrice/Cockatrice/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Cockatrice/Cockatrice" />
</a><br>
<sub><i>Made with <a href="https://contrib.rocks">contrib.rocks</a></i></sub>
</details>
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> ### Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)
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> 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 invovled, and join a group of hundreds of others!<br>
# Build [![CI Desktop](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [![CI Docker](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml/badge.svg?branch=master&event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) # 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/)
@ -143,8 +124,8 @@ You can then
make package make package
``` ```
> [!NOTE] >[!NOTE]
> Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice) >Detailed compiling instructions can be found in the Cockatrice wiki at [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)
<br> <br>

View file

@ -42,6 +42,7 @@ tell disk image_name
set position of item "Cockatrice.app" to { 139, 214 } set position of item "Cockatrice.app" to { 139, 214 }
set position of item "Oracle.app" to { 139, 414 } set position of item "Oracle.app" to { 139, 414 }
set position of item "Servatrice.app" to { 139, 614 } set position of item "Servatrice.app" to { 139, 614 }
set position of item "dbconverter.app" to { 1400, 1400 }
set position of item "Applications" to { 861, 414 } set position of item "Applications" to { 861, 414 }
end tell end tell
update without registering applications update without registering applications

View file

@ -1,11 +1,12 @@
# Find a compatible Qt version # Find a compatible Qt version
# Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, FORCE_USE_QT5 # Inputs: WITH_SERVER, WITH_CLIENT, WITH_ORACLE, WITH_DBCONVERTER, FORCE_USE_QT5
# Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system # Optional Input: QT6_DIR -- Hint as to where Qt6 lives on the system
# Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system # Optional Input: QT5_DIR -- Hint as to where Qt5 lives on the system
# Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6 # Output: COCKATRICE_QT_VERSION_NAME -- Example values: Qt5, Qt6
# Output: SERVATRICE_QT_MODULES # Output: SERVATRICE_QT_MODULES
# Output: COCKATRICE_QT_MODULES # Output: COCKATRICE_QT_MODULES
# Output: ORACLE_QT_MODULES # Output: ORACLE_QT_MODULES
# Output: DBCONVERTER_QT_MODULES
# Output: TEST_QT_MODULES # Output: TEST_QT_MODULES
set(REQUIRED_QT_COMPONENTS Core) set(REQUIRED_QT_COMPONENTS Core)
@ -28,21 +29,22 @@ endif()
if(WITH_ORACLE) if(WITH_ORACLE)
set(_ORACLE_NEEDED Concurrent Network Svg Widgets) set(_ORACLE_NEEDED Concurrent Network Svg Widgets)
endif() endif()
if(WITH_DBCONVERTER)
set(_DBCONVERTER_NEEDED Network Widgets)
endif()
if(TEST) if(TEST)
# Union of Qt modules required across all test targets (independent of application targets). set(_TEST_NEEDED Widgets)
# When adding a new test that needs additional Qt modules, add them here rather than in the test's CMakeLists.txt.
set(_TEST_NEEDED Concurrent Network Svg Widgets)
endif() endif()
set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED} set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED}
${_TEST_NEEDED} ${_DBCONVERTER_NEEDED} ${_TEST_NEEDED}
) )
list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS) 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}
) )
@ -61,7 +63,7 @@ if(Qt6_FOUND)
endif() endif()
else() else()
find_package( find_package(
Qt5 5.15.2 Qt5 5.8.0
COMPONENTS ${REQUIRED_QT_COMPONENTS} COMPONENTS ${REQUIRED_QT_COMPONENTS}
QUIET HINTS ${Qt5_DIR} QUIET HINTS ${Qt5_DIR}
) )
@ -110,9 +112,7 @@ message(DEBUG "QT_LIBRARY_DIR = ${QT_LIBRARY_DIR}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" SERVATRICE_QT_MODULES "${_SERVATRICE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" SERVATRICE_QT_MODULES "${_SERVATRICE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_QT_MODULES "${_COCKATRICE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" COCKATRICE_QT_MODULES "${_COCKATRICE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" ORACLE_QT_MODULES "${_ORACLE_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" DB_CONVERTER_QT_MODULES "${_DBCONVERTER_NEEDED}")
string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}") string(REGEX REPLACE "([^;]+)" "${COCKATRICE_QT_VERSION_NAME}::\\1" TEST_QT_MODULES "${_TEST_NEEDED}")
# Core-only export (useful for headless libs)
set(QT_CORE_MODULE "${COCKATRICE_QT_VERSION_NAME}::Core")
message(STATUS "Found Qt ${${COCKATRICE_QT_VERSION_NAME}_VERSION} at: ${${COCKATRICE_QT_VERSION_NAME}_DIR}") message(STATUS "Found Qt ${${COCKATRICE_QT_VERSION_NAME}_VERSION} at: ${${COCKATRICE_QT_VERSION_NAME}_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
@ -27,25 +26,14 @@ Var ReinstallMode
!define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of Cockatrice.$\r$\n$\r$\nClick Next to continue." !define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of Cockatrice.$\r$\n$\r$\nClick Next to continue."
!define MUI_FINISHPAGE_RUN "$INSTDIR/cockatrice.exe" !define MUI_FINISHPAGE_RUN "$INSTDIR/cockatrice.exe"
!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_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 +72,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 +89,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 +96,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 +125,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 +158,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 +167,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 +183,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
@ -303,6 +213,7 @@ ${AndIf} ${FileExists} "$INSTDIR\portable.dat"
Delete "$INSTDIR\uninstall.exe" Delete "$INSTDIR\uninstall.exe"
Delete "$INSTDIR\cockatrice.exe" Delete "$INSTDIR\cockatrice.exe"
Delete "$INSTDIR\oracle.exe" Delete "$INSTDIR\oracle.exe"
Delete "$INSTDIR\dbconverter.exe"
Delete "$INSTDIR\servatrice.exe" Delete "$INSTDIR\servatrice.exe"
Delete "$INSTDIR\Qt*.dll" Delete "$INSTDIR\Qt*.dll"
Delete "$INSTDIR\libmysql.dll" Delete "$INSTDIR\libmysql.dll"
@ -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

@ -3,7 +3,7 @@
string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN) string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN)
if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0) if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0)
set(APPLICATIONS "cockatrice" "servatrice" "oracle") set(APPLICATIONS "cockatrice" "servatrice" "oracle" "dbconverter")
foreach(app_name IN LISTS APPLICATIONS) foreach(app_name IN LISTS APPLICATIONS)
set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app") set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app")

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,348 +7,258 @@ 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/game_logic/abstract_client.cpp
src/client/network/update/client/update_downloader.cpp src/client/game_logic/key_signals.cpp
src/client/network/interfaces/deck_stats_interface.cpp src/client/get_text_with_max.cpp
src/client/network/interfaces/tapped_out_interface.cpp src/client/menus/deck_editor/deck_editor_menu.cpp
src/client/network/client_update_checker.cpp
src/client/network/release_channel.cpp
src/client/network/replay_timeline_widget.cpp
src/client/network/sets_model.cpp
src/client/network/spoiler_background_updater.cpp
src/client/network/parsers/deck_link_to_api_transformer.cpp src/client/network/parsers/deck_link_to_api_transformer.cpp
src/client/network/update/client/client_update_checker.cpp src/client/replay_manager.cpp
src/client/network/update/client/release_channel.cpp
src/client/network/update/card_spoiler/spoiler_background_updater.cpp
src/client/sound_engine.cpp src/client/sound_engine.cpp
src/client/settings/cache_settings.cpp src/client/tabs/abstract_tab_deck_editor.cpp
src/client/settings/card_counter_settings.cpp src/client/tabs/api/edhrec/tab_edhrec.cpp
src/client/settings/shortcut_treeview.cpp src/client/tabs/api/edhrec/tab_edhrec_main.cpp
src/client/settings/shortcuts_settings.cpp src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp
src/interface/deck_loader/card_node_function.cpp src/client/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp
src/interface/deck_loader/deck_file_format.cpp src/client/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp
src/interface/deck_loader/deck_loader.cpp src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp
src/interface/deck_loader/loaded_deck.cpp src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.cpp
src/interface/widgets/dialogs/dlg_connect.cpp src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp
src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp src/client/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.cpp
src/interface/widgets/dialogs/dlg_create_game.cpp src/client/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp
src/interface/widgets/dialogs/dlg_default_tags_editor.cpp src/client/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp
src/interface/widgets/dialogs/dlg_edit_avatar.cpp src/client/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp
src/interface/widgets/dialogs/dlg_edit_password.cpp src/client/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp
src/interface/widgets/dialogs/dlg_edit_tokens.cpp src/client/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp
src/interface/widgets/dialogs/dlg_edit_user.cpp src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.cpp
src/interface/widgets/dialogs/dlg_filter_games.cpp src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.cpp
src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.cpp
src/interface/widgets/dialogs/dlg_forgot_password_request.cpp src/client/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.cpp
src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp src/client/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.cpp
src/interface/widgets/dialogs/dlg_load_deck.cpp src/client/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp src/client/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp src/client/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp src/client/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
src/interface/widgets/dialogs/dlg_local_game_options.cpp src/client/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.cpp
src/interface/widgets/dialogs/dlg_manage_sets.cpp src/client/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.cpp
src/interface/widgets/dialogs/dlg_register.cpp src/client/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.cpp
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp src/client/tabs/tab.cpp
src/interface/widgets/dialogs/dlg_settings.cpp src/client/tabs/tab_account.cpp
src/interface/widgets/dialogs/dlg_startup_card_check.cpp src/client/tabs/tab_admin.cpp
src/interface/widgets/dialogs/dlg_tip_of_the_day.cpp src/client/tabs/tab_deck_editor.cpp
src/interface/widgets/dialogs/dlg_update.cpp src/client/tabs/tab_deck_storage.cpp
src/interface/widgets/dialogs/dlg_view_log.cpp src/client/tabs/tab_game.cpp
src/interface/widgets/dialogs/override_printing_warning.cpp src/client/tabs/tab_logs.cpp
src/interface/widgets/dialogs/tip_of_the_day.cpp src/client/tabs/tab_message.cpp
src/filters/deck_filter_string.cpp src/client/tabs/tab_replays.cpp
src/filters/filter_builder.cpp src/client/tabs/tab_room.cpp
src/filters/filter_tree_model.cpp src/client/tabs/tab_server.cpp
src/filters/syntax_help.cpp src/client/tabs/tab_supervisor.cpp
src/game/abstract_game.cpp src/client/tabs/tab_visual_database_display.cpp
src/game/arrow_registry.cpp src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp
src/game_graphics/board/abstract_card_drag_item.cpp src/client/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp
src/game_graphics/board/abstract_card_item.cpp src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/game_graphics/board/abstract_counter.cpp src/client/tapped_out_interface.cpp
src/game/board/arrow_data.cpp src/client/translate_counter_name.cpp
src/game_graphics/board/arrow_item.cpp src/client/ui/layouts/flow_layout.cpp
src/game_graphics/board/arrow_target.cpp src/client/ui/layouts/overlap_layout.cpp
src/game_graphics/board/card_drag_item.cpp src/client/ui/line_edit_completer.cpp
src/game_graphics/board/card_item.cpp src/client/ui/phases_toolbar.cpp
src/client/ui/picture_loader/picture_loader.cpp
src/client/ui/picture_loader/picture_loader_local.cpp
src/client/ui/picture_loader/picture_loader_request_status_display_widget.cpp
src/client/ui/picture_loader/picture_loader_status_bar.cpp
src/client/ui/picture_loader/picture_loader_worker.cpp
src/client/ui/picture_loader/picture_loader_worker_work.cpp
src/client/ui/picture_loader/picture_to_load.cpp
src/client/ui/pixel_map_generator.cpp
src/client/ui/theme_manager.cpp
src/client/ui/tip_of_the_day.cpp
src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp
src/client/ui/widgets/cards/additional_info/mana_cost_widget.cpp
src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp
src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
src/client/ui/widgets/cards/card_info_display_widget.cpp
src/client/ui/widgets/cards/card_info_frame_widget.cpp
src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
src/client/ui/widgets/cards/card_info_picture_widget.cpp
src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
src/client/ui/widgets/cards/card_info_text_widget.cpp
src/client/ui/widgets/cards/card_size_widget.cpp
src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp
src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp
src/client/ui/widgets/deck_analytics/deck_analytics_widget.cpp
src/client/ui/widgets/deck_analytics/mana_base_widget.cpp
src/client/ui/widgets/deck_analytics/mana_curve_widget.cpp
src/client/ui/widgets/deck_analytics/mana_devotion_widget.cpp
src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp
src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
src/client/ui/widgets/general/display/banner_widget.cpp
src/client/ui/widgets/general/display/bar_widget.cpp
src/client/ui/widgets/general/display/dynamic_font_size_label.cpp
src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp
src/client/ui/widgets/general/display/labeled_input.cpp
src/client/ui/widgets/general/display/percent_bar_widget.cpp
src/client/ui/widgets/general/display/shadow_background_label.cpp
src/client/ui/widgets/general/layout_containers/flow_widget.cpp
src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp
src/client/ui/widgets/general/layout_containers/overlap_widget.cpp
src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp
src/client/ui/widgets/printing_selector/card_amount_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
src/client/ui/widgets/quick_settings/settings_button_widget.cpp
src/client/ui/widgets/quick_settings/settings_popup_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
src/client/ui/widgets/visual_database_display/visual_database_filter_display_widget.cpp
src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
src/client/ui/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/client/ui/window_main.cpp
src/client/update_downloader.cpp
src/deck/custom_line_edit.cpp
src/deck/deck_list_model.cpp
src/deck/deck_loader.cpp
src/deck/deck_stats_interface.cpp
src/dialogs/dlg_connect.cpp
src/dialogs/dlg_convert_deck_to_cod_format.cpp
src/dialogs/dlg_create_game.cpp
src/dialogs/dlg_create_token.cpp
src/dialogs/dlg_default_tags_editor.cpp
src/dialogs/dlg_edit_avatar.cpp
src/dialogs/dlg_edit_password.cpp
src/dialogs/dlg_edit_tokens.cpp
src/dialogs/dlg_edit_user.cpp
src/dialogs/dlg_filter_games.cpp
src/dialogs/dlg_forgot_password_challenge.cpp
src/dialogs/dlg_forgot_password_request.cpp
src/dialogs/dlg_forgot_password_reset.cpp
src/dialogs/dlg_load_deck.cpp
src/dialogs/dlg_load_deck_from_clipboard.cpp
src/dialogs/dlg_load_deck_from_website.cpp
src/dialogs/dlg_load_remote_deck.cpp
src/dialogs/dlg_manage_sets.cpp
src/dialogs/dlg_move_top_cards_until.cpp
src/dialogs/dlg_register.cpp
src/dialogs/dlg_roll_dice.cpp
src/dialogs/dlg_select_set_for_cards.cpp
src/dialogs/dlg_settings.cpp
src/dialogs/dlg_startup_card_check.cpp
src/dialogs/dlg_tip_of_the_day.cpp
src/dialogs/dlg_update.cpp
src/dialogs/dlg_view_log.cpp
src/game/board/abstract_card_drag_item.cpp
src/game/board/abstract_card_item.cpp
src/game/board/abstract_counter.cpp
src/game/board/abstract_graphics_item.cpp
src/game/board/arrow_item.cpp
src/game/board/arrow_target.cpp
src/game/board/card_drag_item.cpp
src/game/board/card_item.cpp
src/game/board/card_list.cpp src/game/board/card_list.cpp
src/game/board/card_state.cpp src/game/board/counter_general.cpp
src/game_graphics/board/counter_general.cpp src/game/cards/card_completer_proxy_model.cpp
src/game/board/counter_state.cpp src/game/cards/card_database.cpp
src/game_graphics/board/translate_counter_name.cpp src/game/cards/card_database_manager.cpp
src/game_graphics/deckview/deck_view.cpp src/game/cards/card_database_model.cpp
src/game_graphics/deckview/deck_view_container.cpp src/game/cards/card_database_parser/card_database_parser.cpp
src/game_graphics/deckview/tabbed_deck_view_container.cpp src/game/cards/card_database_parser/cockatrice_xml_3.cpp
src/game_graphics/dialogs/dlg_create_token.cpp src/game/cards/card_database_parser/cockatrice_xml_4.cpp
src/game_graphics/dialogs/dlg_move_top_cards_until.cpp src/game/cards/card_info.cpp
src/game_graphics/dialogs/dlg_roll_dice.cpp src/game/cards/card_search_model.cpp
src/game/game.cpp src/game/cards/exact_card.cpp
src/game/game_event_handler.cpp src/game/deckview/deck_view.cpp
src/game/game_meta_info.cpp src/game/deckview/deck_view_container.cpp
src/game_graphics/game_scene.cpp src/game/filters/deck_filter_string.cpp
src/game/game_state.cpp src/game/filters/filter_builder.cpp
src/game_graphics/game_view.cpp src/game/filters/filter_card.cpp
src/game_graphics/hand_counter.cpp src/game/filters/filter_string.cpp
src/game_graphics/log/message_log_widget.cpp src/game/filters/filter_tree.cpp
src/game/filters/filter_tree_model.cpp
src/game/filters/syntax_help.cpp
src/game/game_scene.cpp
src/game/game_selector.cpp
src/game/game_view.cpp
src/game/games_model.cpp
src/game/hand_counter.cpp
src/game/phase.cpp src/game/phase.cpp
src/game_graphics/phases_toolbar.cpp src/game/player/player.cpp
src/game_graphics/player/menu/card_menu.cpp src/game/player/player_list_widget.cpp
src/game_graphics/player/menu/custom_zone_menu.cpp src/game/player/player_target.cpp
src/game_graphics/player/menu/grave_menu.cpp src/game/zones/card_zone.cpp
src/game_graphics/player/menu/hand_menu.cpp src/game/zones/hand_zone.cpp
src/game_graphics/player/menu/library_menu.cpp src/game/zones/pile_zone.cpp
src/game_graphics/player/menu/move_menu.cpp src/game/zones/select_zone.cpp
src/game_graphics/player/menu/player_menu.cpp src/game/zones/stack_zone.cpp
src/game_graphics/player/menu/pt_menu.cpp src/game/zones/table_zone.cpp
src/game_graphics/player/menu/rfg_menu.cpp src/game/zones/view_zone.cpp
src/game_graphics/player/menu/say_menu.cpp src/game/zones/view_zone_widget.cpp
src/game_graphics/player/menu/sideboard_menu.cpp
src/game_graphics/player/menu/utility_menu.cpp
src/game/player/player_actions.cpp
src/game_graphics/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp
src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_info.cpp
src/game_graphics/player/player_list_widget.cpp
src/game/player/player_logic.cpp
src/game/player/player_manager.cpp
src/game_graphics/player/player_target.cpp
src/game/replay.cpp
src/game/zones/card_zone_logic.cpp
src/game/zones/hand_zone_logic.cpp
src/game/zones/pile_zone_logic.cpp
src/game/zones/stack_zone_logic.cpp
src/game/zones/table_zone_logic.cpp
src/game/zones/view_zone_logic.cpp
src/game_graphics/zones/card_zone.cpp
src/game_graphics/zones/hand_zone.cpp
src/game_graphics/zones/pile_zone.cpp
src/game_graphics/zones/select_zone.cpp
src/game_graphics/zones/stack_zone.cpp
src/game_graphics/zones/table_zone.cpp
src/game_graphics/zones/view_zone.cpp
src/game_graphics/zones/view_zone_widget.cpp
src/game_graphics/board/abstract_graphics_item.cpp
src/interface/card_picture_loader/card_picture_loader.cpp
src/interface/card_picture_loader/card_picture_loader_local.cpp
src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.cpp
src/interface/card_picture_loader/card_picture_loader_status_bar.cpp
src/interface/card_picture_loader/card_picture_loader_worker.cpp
src/interface/card_picture_loader/card_picture_loader_worker_work.cpp
src/interface/card_picture_loader/card_picture_to_load.cpp
src/interface/layouts/flow_layout.cpp
src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.cpp
src/interface/pixel_map_generator.cpp
src/interface/theme_config.cpp
src/interface/theme_manager.cpp
src/interface/palette_editor/color_button.cpp
src/interface/palette_editor/palette_generator.cpp
src/interface/palette_editor/quick_setup_panel.cpp
src/interface/palette_editor/palette_grid_widget.cpp
src/interface/palette_editor/palette_editor_dialog.cpp
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp
src/interface/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp
src/interface/widgets/cards/card_info_display_widget.cpp
src/interface/widgets/cards/card_info_frame_widget.cpp
src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp
src/interface/widgets/cards/card_info_picture_enlarged_widget.cpp
src/interface/widgets/cards/card_info_picture_widget.cpp
src/interface/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
src/interface/widgets/cards/card_info_text_widget.cpp
src/interface/widgets/cards/card_size_widget.cpp
src/interface/widgets/cards/deck_card_zone_display_widget.cpp
src/interface/widgets/cards/deck_preview_card_picture_widget.cpp
src/interface/widgets/deck_analytics/abstract_analytics_panel_widget.cpp
src/interface/widgets/deck_analytics/add_analytics_panel_dialog.cpp
src/interface/widgets/deck_analytics/analytics_panel_widget_factory.cpp
src/interface/widgets/deck_analytics/analytics_panel_widget_registrar.cpp
src/interface/widgets/deck_analytics/deck_analytics_widget.cpp
src/interface/widgets/deck_analytics/deck_list_statistics_analyzer.cpp
src/interface/widgets/deck_analytics/resizable_panel.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/draw_probability/draw_probability_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_base/mana_base_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_devotion/mana_devotion_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_config_dialog.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_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_category_widget.cpp
src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_database_display_widget.cpp
src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_filter_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp
src/interface/widgets/deck_editor/deck_list_style_proxy.cpp
src/interface/widgets/deck_editor/deck_state_manager.cpp
src/interface/widgets/deck_editor/printing_disabled_info_widget.cpp
src/interface/widgets/general/background_sources.cpp
src/interface/widgets/general/display/background_plate_widget.cpp
src/interface/widgets/general/display/banner_widget.cpp
src/interface/widgets/general/display/dynamic_font_size_label.cpp
src/interface/widgets/general/display/dynamic_font_size_push_button.cpp
src/interface/widgets/general/display/labeled_input.cpp
src/interface/widgets/general/display/shadow_background_label.cpp
src/interface/widgets/general/display/charts/bars/bar_widget.cpp
src/interface/widgets/general/display/charts/bars/color_bar.cpp
src/interface/widgets/general/display/charts/bars/percent_bar_widget.cpp
src/interface/widgets/general/display/charts/bars/bar_chart_widget.cpp
src/interface/widgets/general/display/charts/bars/bar_chart_background_widget.cpp
src/interface/widgets/general/display/charts/bars/segmented_bar_widget.cpp
src/interface/widgets/general/display/charts/pies/color_pie.cpp
src/interface/widgets/general/home_styled_button.cpp
src/interface/widgets/general/home_widget.cpp
src/interface/widgets/general/layout_containers/flow_widget.cpp
src/interface/widgets/general/layout_containers/overlap_control_widget.cpp
src/interface/widgets/general/layout_containers/overlap_widget.cpp
src/interface/widgets/menus/deck_editor_menu.cpp
src/interface/widgets/printing_selector/all_zones_card_amount_widget.cpp
src/interface/widgets/printing_selector/card_amount_widget.cpp
src/interface/widgets/printing_selector/printing_selector.cpp
src/interface/widgets/printing_selector/printing_selector_card_display_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
src/interface/widgets/printing_selector/printing_selector_placeholder_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_search_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp
src/interface/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
src/interface/widgets/quick_settings/settings_button_widget.cpp
src/interface/widgets/quick_settings/settings_popup_widget.cpp
src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp
src/interface/widgets/server/game_filter_configs.cpp
src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp
src/interface/widgets/server/handle_public_servers.cpp
src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp
src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
src/interface/widgets/server/user/user_context_menu.cpp
src/interface/widgets/server/user/user_info_box.cpp
src/interface/widgets/server/user/user_info_connection.cpp
src/interface/widgets/server/user/user_list_manager.cpp
src/interface/widgets/server/user/user_list_widget.cpp
src/interface/widgets/settings_page/appearance_settings_page.cpp
src/interface/widgets/settings_page/deck_editor_settings_page.cpp
src/interface/widgets/settings_page/general_settings_page.cpp
src/interface/widgets/settings_page/messages_settings_page.cpp
src/interface/widgets/settings_page/shortcut_settings_page.cpp
src/interface/widgets/settings_page/sound_settings_page.cpp
src/interface/widgets/settings_page/storage_settings_page.cpp
src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/sequence_edit.cpp
src/interface/widgets/utility/visibility_change_listener.cpp
src/interface/widgets/utility/visibility_change_listener.h
src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp
src/interface/widgets/visual_database_display/visual_database_display_widget.cpp
src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_placeholder_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp
src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_quick_settings_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/interface/window_main.cpp
src/main.cpp src/main.cpp
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp src/server/chat_view/chat_view.cpp
src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp src/server/handle_public_servers.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_deck_listing_api_response.cpp src/server/local_client.cpp
src/interface/widgets/tabs/api/archidekt/api_response/archidekt_formats.h src/server/local_server.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card.cpp src/server/local_server_interface.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_card_entry.cpp src/server/message_log_widget.cpp
src/interface/widgets/tabs/api/archidekt/api_response/card/archidekt_api_response_edition.cpp src/server/pending_command.cpp
src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck.cpp src/server/remote/remote_client.cpp
src/interface/widgets/tabs/api/archidekt/api_response/deck/archidekt_api_response_deck_category.cpp src/server/remote/remote_decklist_tree_widget.cpp
src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_listing_container.cpp src/server/remote/remote_replay_list_tree_widget.cpp
src/interface/widgets/tabs/api/archidekt/api_response/deck_listings/archidekt_api_response_deck_owner.cpp src/server/user/user_context_menu.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp src/server/user/user_info_box.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp src/server/user/user_info_connection.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp src/server/user/user_list_manager.cpp
src/interface/widgets/tabs/api/archidekt/display/archidekt_deck_preview_image_display_widget.cpp src/server/user/user_list_widget.cpp
src/interface/widgets/tabs/api/edhrec/api_response/archidekt_links/edhrec_api_response_archidekt_links.cpp src/settings/cache_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_average_deck_api_response.cpp src/settings/card_counter_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp src/settings/card_database_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/card_prices/edhrec_api_response_card_prices.cpp src/settings/card_override_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_container.cpp src/settings/debug_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_details.cpp src/settings/download_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_api_response_card_list.cpp src/settings/game_filters_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/cards/edhrec_commander_api_response_commander_details.cpp src/settings/layouts_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response.cpp src/settings/message_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/commander/edhrec_commander_api_response_average_deck_statistics.cpp src/settings/recents_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/top_cards/edhrec_top_cards_api_response.cpp src/settings/servers_settings.cpp
src/interface/widgets/tabs/api/edhrec/api_response/top_commanders/edhrec_top_commanders_api_response.cpp src/settings/settings_manager.cpp
src/interface/widgets/tabs/api/edhrec/api_response/top_tags/edhrec_top_tags_api_response.cpp src/settings/shortcut_treeview.cpp
src/interface/widgets/tabs/api/edhrec/display/card_prices/edhrec_api_response_card_prices_display_widget.cpp src/settings/shortcuts_settings.cpp
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp src/utility/card_info_comparator.cpp
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_inclusion_display_widget.cpp src/utility/levenshtein.cpp
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_list_display_widget.cpp src/utility/logger.cpp
src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_synergy_display_widget.cpp src/utility/sequence_edit.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp
src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp
src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp
src/interface/widgets/tabs/tab.cpp
src/interface/widgets/tabs/tab_account.cpp
src/interface/widgets/tabs/tab_admin.cpp
src/interface/widgets/tabs/tab_deck_editor.cpp
src/interface/widgets/tabs/tab_deck_storage.cpp
src/interface/widgets/tabs/tab_game.cpp
src/interface/widgets/tabs/tab_home.cpp
src/interface/widgets/tabs/tab_logs.cpp
src/interface/widgets/tabs/tab_message.cpp
src/interface/widgets/tabs/tab_replays.cpp
src/interface/widgets/tabs/tab_room.cpp
src/interface/widgets/tabs/tab_server.cpp
src/interface/widgets/tabs/tab_supervisor.cpp
src/interface/widgets/tabs/tab_visual_database_display.cpp
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp
src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.cpp
src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
src/interface/key_signals.cpp
src/interface/logger.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
src/interface/widgets/utility/compact_push_button.cpp
src/interface/widgets/utility/compact_push_button.h
) )
add_subdirectory(sounds) add_subdirectory(sounds)
@ -360,23 +270,15 @@ configure_file(
set(cockatrice_RESOURCES cockatrice.qrc) set(cockatrice_RESOURCES cockatrice.qrc)
if(UPDATE_TRANSLATIONS) if(UPDATE_TRANSLATIONS)
# Cockatrice main sources
file(GLOB_RECURSE translate_cockatrice_SRCS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp file(GLOB_RECURSE translate_cockatrice_SRCS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp
${CMAKE_SOURCE_DIR}/cockatrice/src/*.h ${CMAKE_SOURCE_DIR}/cockatrice/src/*.h
) )
file(GLOB_RECURSE translate_common_SRCS ${CMAKE_SOURCE_DIR}/common/*.cpp ${CMAKE_SOURCE_DIR}/common/*.h)
# All libcockatrice_* libraries (recursively) set(translate_SRCS ${translate_cockatrice_SRCS} ${translate_common_SRCS})
file(GLOB_RECURSE translate_lib_SRCS ${CMAKE_SOURCE_DIR}/libcockatrice_*/**/*.cpp
${CMAKE_SOURCE_DIR}/libcockatrice_*/**/*.h
)
# Combine all sources for translation
set(translate_SRCS ${translate_cockatrice_SRCS} ${translate_lib_SRCS})
set(cockatrice_TS "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice_en@source.ts") set(cockatrice_TS "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice_en@source.ts")
else() else()
file(GLOB cockatrice_TS "${CMAKE_CURRENT_SOURCE_DIR}/translations/*.ts") file(GLOB cockatrice_TS "${CMAKE_CURRENT_SOURCE_DIR}/translations/*.ts")
endif() endif(UPDATE_TRANSLATIONS)
if(WIN32) if(WIN32)
set(cockatrice_SOURCES ${cockatrice_SOURCES} cockatrice.rc) set(cockatrice_SOURCES ${cockatrice_SOURCES} cockatrice.rc)
@ -406,6 +308,12 @@ set(DESKTOPDIR
CACHE STRING "desktop file destination" CACHE STRING "desktop file destination"
) )
# Include directories
include_directories(../common)
include_directories(${PROTOBUF_INCLUDE_DIR})
include_directories(${CMAKE_BINARY_DIR}/common)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations") set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translations")
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations") set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations") set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
@ -445,31 +353,9 @@ elseif(Qt5_FOUND)
endif() endif()
if(Qt5_FOUND) if(Qt5_FOUND)
target_link_libraries( target_link_libraries(cockatrice cockatrice_common ${COCKATRICE_QT_MODULES})
cockatrice
libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
else() else()
target_link_libraries( target_link_libraries(cockatrice PUBLIC cockatrice_common ${COCKATRICE_QT_MODULES})
cockatrice
PUBLIC libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_utility
libcockatrice_network
libcockatrice_models
libcockatrice_rng
libcockatrice_settings
${COCKATRICE_QT_MODULES}
)
endif() endif()
if(UNIX) if(UNIX)
@ -563,7 +449,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 +474,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

@ -7,46 +7,32 @@
<file>resources/icons/arrow_bottom_green.svg</file> <file>resources/icons/arrow_bottom_green.svg</file>
<file>resources/icons/arrow_down_green.svg</file> <file>resources/icons/arrow_down_green.svg</file>
<file>resources/icons/arrow_history.svg</file>
<file>resources/icons/arrow_left_green.svg</file> <file>resources/icons/arrow_left_green.svg</file>
<file>resources/icons/arrow_redo.svg</file>
<file>resources/icons/arrow_right_blue.svg</file> <file>resources/icons/arrow_right_blue.svg</file>
<file>resources/icons/arrow_right_green.svg</file> <file>resources/icons/arrow_right_green.svg</file>
<file>resources/icons/arrow_top_green.svg</file> <file>resources/icons/arrow_top_green.svg</file>
<file>resources/icons/arrow_up_green.svg</file> <file>resources/icons/arrow_up_green.svg</file>
<file>resources/icons/arrow_undo.svg</file>
<file>resources/icons/circle_half_stroke.svg</file>
<file>resources/icons/clearsearch.svg</file> <file>resources/icons/clearsearch.svg</file>
<file>resources/icons/cogwheel.svg</file> <file>resources/icons/cogwheel.svg</file>
<file>resources/icons/conceded.svg</file> <file>resources/icons/conceded.svg</file>
<file>resources/icons/decrement.svg</file> <file>resources/icons/decrement.svg</file>
<file>resources/icons/delete.svg</file> <file>resources/icons/delete.svg</file>
<file>resources/icons/dragon.svg</file>
<file>resources/icons/dropdown_collapsed.svg</file> <file>resources/icons/dropdown_collapsed.svg</file>
<file>resources/icons/dropdown_expanded.svg</file> <file>resources/icons/dropdown_expanded.svg</file>
<file>resources/icons/filter.svg</file>
<file>resources/icons/floppy_disk.svg</file>
<file>resources/icons/forgot_password.svg</file> <file>resources/icons/forgot_password.svg</file>
<file>resources/icons/gear.svg</file>
<file>resources/icons/increment.svg</file> <file>resources/icons/increment.svg</file>
<file>resources/icons/info.svg</file> <file>resources/icons/info.svg</file>
<file>resources/icons/lock.svg</file> <file>resources/icons/lock.svg</file>
<file>resources/icons/not_ready_start.svg</file> <file>resources/icons/not_ready_start.svg</file>
<file>resources/icons/pen_to_square.svg</file>
<file>resources/icons/pencil.svg</file> <file>resources/icons/pencil.svg</file>
<file>resources/icons/pin.svg</file>
<file>resources/icons/player.svg</file> <file>resources/icons/player.svg</file>
<file>resources/icons/ready_start.svg</file> <file>resources/icons/ready_start.svg</file>
<file>resources/icons/reload.svg</file> <file>resources/icons/reload.svg</file>
<file>resources/icons/remove_row.svg</file> <file>resources/icons/remove_row.svg</file>
<file>resources/icons/rename.svg</file> <file>resources/icons/rename.svg</file>
<file>resources/icons/scale_balanced.svg</file>
<file>resources/icons/scales.svg</file> <file>resources/icons/scales.svg</file>
<file>resources/icons/scroll.svg</file>
<file>resources/icons/search.svg</file> <file>resources/icons/search.svg</file>
<file>resources/icons/settings.svg</file> <file>resources/icons/settings.svg</file>
<file>resources/icons/share.svg</file>
<file>resources/icons/sort_arrow_down.svg</file>
<file>resources/icons/spectator.svg</file> <file>resources/icons/spectator.svg</file>
<file>resources/icons/swap.svg</file> <file>resources/icons/swap.svg</file>
<file>resources/icons/sync.svg</file> <file>resources/icons/sync.svg</file>
@ -55,22 +41,16 @@
<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>
<file>resources/icons/mana/W.svg</file> <file>resources/icons/mana/W.svg</file>
<file>resources/backgrounds/home.png</file>
<file>resources/backgrounds/card_triplet.svg</file>
<file>resources/backgrounds/placeholder_printing_selector.svg</file>
<file>resources/config/general.svg</file> <file>resources/config/general.svg</file>
<file>resources/config/appearance.svg</file> <file>resources/config/appearance.svg</file>
<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

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="250"
id="svg13"
height="231.66667"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg">
<defs
id="defs13" />
<g
transform="matrix(1.705559,0,0,1.705559,-18.310328,-4.2419088)"
id="g13">
<path
d="M 90.069854,3.479957 C 89.356513,1.2235709 86.980392,-0.01102897 84.723451,0.70218215 L 3.4767601,26.377781 C 1.2199188,27.090982 -0.01486587,29.46663 0.69839437,31.723116 L 33.512365,135.52112 c 0.713341,2.25639 3.089462,3.49099 5.346403,2.77777 l 81.246672,-25.6756 c 2.25684,-0.71319 3.49163,-3.08884 2.77837,-5.34533 L 90.074852,3.479957 Z"
style="display:none;fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path1" />
<path
d="m 110.61293,7.4983294 c -0.36657,-2.337853 -2.53055,-3.9150142 -4.86886,-3.5484627 L 21.563382,17.14452 c -2.338314,0.366502 -3.915784,2.529976 -3.549207,4.867929 L 34.876507,129.55893 c 0.366577,2.33786 2.530549,3.91502 4.868863,3.54847 l 84.18069,-13.19466 c 2.33831,-0.3665 3.91578,-2.52997 3.5492,-4.86793 L 110.61093,7.4983294 Z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path4" />
<path
d="m 130.53623,15.555064 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 41.067426 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 124.41102 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208344 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7" />
<path
d="m 149.43988,26.480639 c 0.38018,-2.335754 -1.1846,-4.508374 -3.52082,-4.88852 L 61.817351,7.9076636 C 59.481136,7.5275576 57.308066,9.0920839 56.927894,11.427736 L 39.439773,118.87426 c -0.380182,2.33576 1.184602,4.50838 3.520816,4.88852 l 84.102711,13.68346 c 2.33622,0.38011 4.50929,-1.18442 4.88946,-3.52007 L 149.43688,26.479639 Z"
style="display:inline;fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path10" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

View file

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->
<svg
version="1.1"
width="172.65051"
id="svg13"
height="213.30714"
xml:space="preserve"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"><defs
id="defs13" /><g
transform="matrix(1.705559,0,0,1.705559,-97.653345,-68.741256)"
id="g13"><path
d="m 151.48519,45.063813 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 62.016385 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 153.91977 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208345 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7" /><path
d="m 154.70135,48.441704 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 65.232545 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 V 157.29767 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208345 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7-5" /><path
d="m 157.98403,51.75453 c 0,-2.366441 -1.89356,-4.259575 -4.26046,-4.259575 H 68.515228 c -2.366905,0 -4.260468,1.893134 -4.260468,4.259575 v 108.85596 c 0,2.36644 1.893563,4.25957 4.260468,4.25957 h 85.208342 c 2.3669,0 4.26046,-1.89313 4.26046,-4.25957 z"
style="fill:#c0c0c0;fill-opacity:1;stroke:#989898;stroke-width:1;stroke-opacity:1"
id="path7-6" /></g><path
d="m 196.24576,207.42361 c 0,0.11213 0,0.22413 0,0.33621 -0.0498,4.54511 -4.18399,7.63329 -8.72909,7.63329 h -12.19086 c -3.29988,0 -5.97713,2.67727 -5.97713,5.97712 0,0.4234 0.0498,0.83433 0.12449,1.23279 0.26149,1.27014 0.80939,2.49046 1.34485,3.72325 0.75959,1.71843 1.50674,3.4244 1.50674,5.22998 0,3.95986 -2.68971,7.55859 -6.64956,7.72046 -0.43583,0.0128 -0.87166,0.025 -1.31995,0.025 -17.60761,0 -31.878,-14.27038 -31.878,-31.878 0,-17.60761 14.28284,-31.878 31.89046,-31.878 17.60762,0 31.87801,14.27039 31.87801,31.878 z m -47.81703,3.98475 c 0,-2.20407 -1.78067,-3.98475 -3.98473,-3.98475 -2.20407,0 -3.98477,1.78068 -3.98477,3.98475 0,2.20406 1.7807,3.98475 3.98477,3.98475 2.20406,0 3.98473,-1.78069 3.98473,-3.98475 z m 0,-11.95426 c 2.20407,0 3.98477,-1.78068 3.98477,-3.98475 0,-2.20407 -1.7807,-3.98475 -3.98477,-3.98475 -2.20405,0 -3.98473,1.78068 -3.98473,3.98475 0,2.20407 1.78068,3.98475 3.98473,3.98475 z m 19.92376,-11.95424 c 0,-2.20408 -1.78068,-3.98477 -3.98473,-3.98477 -2.20407,0 -3.98477,1.78069 -3.98477,3.98477 0,2.20405 1.7807,3.98474 3.98477,3.98474 2.20405,0 3.98473,-1.78069 3.98473,-3.98474 z m 11.95426,11.95424 c 2.20407,0 3.98475,-1.78068 3.98475,-3.98475 0,-2.20407 -1.78068,-3.98475 -3.98475,-3.98475 -2.20406,0 -3.98475,1.78068 -3.98475,3.98475 0,2.20407 1.78069,3.98475 3.98475,3.98475 z"
id="path1"
style="display:none;fill:#3b3b3b;fill-opacity:1;stroke:#000000;stroke-width:2.53798;stroke-dasharray:none;stroke-opacity:1" /><path
d="M 126.20915,54.574783 82.324247,83.8512 c -5.76807,3.845383 -9.435059,10.089163 -10.029703,16.90777 12.348823,2.53716 22.081191,12.26955 24.638191,24.63819 6.838435,-0.59465 13.062395,-4.26163 16.907775,-10.0297 l 29.2566,-43.904722 c 1.32804,-2.001984 2.04162,-4.340923 2.04162,-6.75915 0,-6.719506 -5.45092,-12.170428 -12.17043,-12.170428 -2.3984,0 -4.75718,0.713573 -6.75915,2.041623 z M 88.052677,131.81933 c 0,-12.26953 -9.930593,-22.20012 -22.200138,-22.20012 -12.269532,0 -22.200126,9.93059 -22.200126,22.20012 0,0.77305 0.03966,1.54609 0.118929,2.2993 0.356787,3.46877 -2.021792,7.21505 -5.510393,7.21505 h -0.951431 c -3.508409,0 -6.342895,2.83447 -6.342895,6.3429 0,3.5084 2.834486,6.34289 6.342895,6.34289 h 28.543021 c 12.269545,0 22.200138,-9.93059 22.200138,-22.20014 z"
id="path1-2"
style="fill:#989898;fill-opacity:1;stroke-width:0.198215" /></svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

@ -1,10 +1,6 @@
[Rules] [Rules]
# The default log level is info # The default log level is info
*.debug = false *.debug = false
#*.info = true
#*.warning = true
#*.critical = true
#*.fatal = true
# Uncomment a rule to see debug level logs for that category, # Uncomment a rule to see debug level logs for that category,
# or set <category> = false to disable logging # or set <category> = false to disable logging
@ -28,8 +24,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
@ -43,14 +37,12 @@
#card_zone = true #card_zone = true
#view_zone = true #view_zone = true
#game_event_handler = true
#user_info_connection = true #user_info_connection = true
#card_picture_loader = true #picture_loader = true
#card_picture_loader.worker = true #picture_loader.worker = true
#card_picture_loader.card_back_cache_fail = true #picture_loader.card_back_cache_fail = true
#card_picture_loader.picture_to_load = true #picture_loader.picture_to_load = true
#deck_loader = true #deck_loader = true
#card_database = true #card_database = true
#card_database.loading = true #card_database.loading = 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,13 +1,9 @@
@page deck_search_syntax_help Deck Search Syntax Help
## Deck Search Syntax Help ## Deck Search Syntax Help
-----
The search bar recognizes a set of special commands.<br> The search bar recognizes a set of special commands.<br>
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all
searches are case insensitive. searches are case insensitive.
<dl> <dl>
<dt>Display Name (The deck name, or the filename if the deck name isn't set):</dt> <dt>Display Name (The deck name, or the filename if the deck name isn't set):</dt>
<dd>[red deck wins](#red deck wins) <small>(Any deck with a display name containing the words red, deck, and wins)</small></dd> <dd>[red deck wins](#red deck wins) <small>(Any deck with a display name containing the words red, deck, and wins)</small></dd>
<dd>["red deck wins"](#%22red deck wins%22) <small>(Any deck with a display name containing the exact phrase "red deck wins")</small></dd> <dd>["red deck wins"](#%22red deck wins%22) <small>(Any deck with a display name containing the exact phrase "red deck wins")</small></dd>
@ -17,23 +13,15 @@ searches are case insensitive.
<dd>[n:red n:deck n:wins](#n:red n:deck n:wins) <small>(Any deck with a name containing the words red, deck, and wins)</small></dd> <dd>[n:red n:deck n:wins](#n:red n:deck n:wins) <small>(Any deck with a name containing the words red, deck, and wins)</small></dd>
<dd>[n:"red deck wins"](#n:%22red deck wins%22) <small>(Any deck with a name containing the exact phrase "red deck wins")</small></dd> <dd>[n:"red deck wins"](#n:%22red deck wins%22) <small>(Any deck with a name containing the exact phrase "red deck wins")</small></dd>
<dt><u>F</u>ile <u>N</u>ame:</dt> <dt><u>F</u>ile Name:</dt>
<dd>[fn:aggro](#fn:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd> <dd>[f:aggro](#f:aggro) <small>(Any deck with a filename containing the word aggro)</small></dd>
<dd>[fn:red fn:deck fn:wins](#fn:red fn:deck fn:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd> <dd>[f:red f:deck f:wins](#f:red f:deck f:wins) <small>(Any deck with a filename containing the words red, deck, and wins)</small></dd>
<dd>[fn:"red deck wins"](#fn:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd> <dd>[f:"red deck wins"](#f:%22red deck wins%22) <small>(Any deck with a filename containing the exact phrase "red deck wins")</small></dd>
<dt>Relative <u>P</u>ath (starting from the deck folder):</dt> <dt>Relative <u>P</u>ath (starting from the deck folder):</dt>
<dd>[p:aggro](#p:aggro) <small>(Any deck that has "aggro" somewhere in its relative path)</small></dd> <dd>[p:aggro](#p:aggro) <small>(Any deck that has "aggro" somewhere in its relative path)</small></dd>
<dd>[p:edh/](#p:edh/) <small>(Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)</small></dd> <dd>[p:edh/](#p:edh/) <small>(Any deck with "edh/" in its relative path, A.K.A. decks in the "edh" folder)</small></dd>
<dt><u>F</u>ormat:</dt>
<dd>[f:standard](#f:standard) <small>(Any deck with format set to standard)</small></dd>
<dt><u>C</u>omments:</dt>
<dd>[c:good](#c:good) <small>(Any deck with comments containing the word good)</small></dd>
<dd>[c:good c:deck](#c:good c:deck) <small>(Any deck with comments containing the words good and deck)</small></dd>
<dd>[c:"good deck"](#c:%22good deck%22) <small>(Any deck with comments containing the exact phrase "good deck")</small></dd>
<dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt> <dt>Deck Contents (Uses [card search expressions](#cardSearchSyntaxHelp)):</dt>
<dd><a href="#[[plains]]">[[plains]]</a> <small>(Any deck that contains at least one card with "plains" in its name)</small></dd> <dd><a href="#[[plains]]">[[plains]]</a> <small>(Any deck that contains at least one card with "plains" in its name)</small></dd>
<dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd> <dd><a href="#[[t:legendary]]">[[t:legendary]]</a> <small>(Any deck that contains at least one legendary)</small></dd>
@ -47,6 +35,6 @@ searches are case insensitive.
<dd>[t:aggro OR o:control](#t:aggro OR o:control) <small>(Any deck filename that contains either aggro or control)</small></dd> <dd>[t:aggro OR o:control](#t:aggro OR o:control) <small>(Any deck filename that contains either aggro or control)</small></dd>
<dt>Grouping:</dt> <dt>Grouping:</dt>
<dd><a href="#red -([[]]:100 OR aggro)">red -([[]]:100 OR aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd> <dd><a href="#red -([[]]:100 or aggro)">red -([[]]:100 or aggro)</a> <small>(Any deck that has red in its filename but is not 100 cards or has aggro in its filename)</small></dd>
</dl> </dl>

View file

@ -1,12 +1,8 @@
@page search_syntax_help Search Syntax Help
## Search Syntax Help ## Search Syntax Help
-----
The search bar recognizes a set of special commands similar to some other card databases.<br> The search bar recognizes a set of special commands similar to some other card databases.<br>
In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive. In this list of examples below, each entry has an explanation and can be clicked to test the query. Note that all searches are case insensitive.
<dl> <dl>
<dt>Name:</dt> <dt>Name:</dt>
<dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd> <dd>[birds of paradise](#birds of paradise) <small>(Any card name containing the words birds, of, and paradise)</small></dd>
<dd>["birds of paradise"](#%22birds of paradise%22) <small>(Any card name containing the exact phrase "birds of paradise")</small></dd> <dd>["birds of paradise"](#%22birds of paradise%22) <small>(Any card name containing the exact phrase "birds of paradise")</small></dd>
@ -51,20 +47,20 @@ In this list of examples below, each entry has an explanation and can be clicked
<dt><u>E</u>dition:</dt> <dt><u>E</u>dition:</dt>
<dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd> <dd>[set:lea](#set:lea) <small>(Cards that appear in Alpha, which has the set code LEA)</small></dd>
<dd>[e:lea OR e:leb](#e:lea OR e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd> <dd>[e:lea or e:leb](#e:lea or e:leb) <small>(Cards that appear in Alpha or Beta)</small></dd>
<dt>Negate:</dt> <dt>Negate:</dt>
<dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd> <dd>[c:wu -c:m](#c:wu -c:m) <small>(Any card that is white or blue, but not multicolored)</small></dd>
<dt>Branching:</dt> <dt>Branching:</dt>
<dd>[t:sliver OR o:changeling](#t:sliver OR o:changeling) <small>(Any card that is either a sliver or has changeling)</small></dd> <dd>[t:sliver or o:changeling](#t:sliver or o:changeling) <small>(Any card that is either a sliver or has changeling)</small></dd>
<dt>Grouping:</dt> <dt>Grouping:</dt>
<dd><a href="#t:angel -(angel OR c:w)">t:angel -(angel OR c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd> <dd><a href="#t:angel -(angel or c:w)">t:angel -(angel or c:w)</a> <small>(Any angel that doesn't have angel in its name and isn't white)</small></dd>
<dt>Regular Expression:</dt> <dt>Regular Expression:</dt>
<dd>[/^fell/](#/^fell/) <small>(Any card name that begins with "fell")</small></dd> <dd>[/^fell/](#/^fell/) <small>(Any card name that begins with "fell")</small></dd>
<dd>[o:/counter target .* spell/](#o:/counter target .* spell/) <small>(Any card text with "counter target *something* spell")</small></dd> <dd>[o:/counter target .* spell/](#o:/counter target .* spell/) <small>(Any card text with "counter target *something* spell")</small></dd>
<dd>[o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) <small>(/'s can be escaped with a \)</small></dd> <dd>[o:/for each .* and\/or .*/](#o:/for each .* and\/or .*/) <small>(/'s can be escaped with a \)</small></dd>
</dl> </dl>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<title>
history
</title>
<path d="M9 6v5h.06l2.48 2.47 1.41-1.41L11 10.11V6H9z"/>
<path d="M10 1a9 9 0 0 0-7.85 13.35L.5 16H6v-5.5l-2.38 2.38A7 7 0 1 1 10 17v2a9 9 0 0 0 0-18z"/>
</svg>

Before

Width:  |  Height:  |  Size: 332 B

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="windows-1252"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px"
y="0px" width="485.212px" height="485.212px" viewBox="0 0 485.212 485.212"
style="enable-background:new 0 0 485.212 485.212;" xml:space="preserve">
<g>
<path d="M242.607,424.559c-75.252,0-136.468-61.209-136.468-136.465c0-75.252,61.216-136.466,136.468-136.466v90.978 l151.629-121.302L242.607,0v90.978c-108.687,0-197.117,88.432-197.117,197.117c0,108.691,88.43,197.118,197.117,197.118 c108.687,0,197.114-88.427,197.114-197.118h-60.645C379.077,363.35,317.859,424.559,242.607,424.559z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1,018 B

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="windows-1252"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px"
y="0px" width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;"
xml:space="preserve">
<g>
<path d="M256,448c79.406,0,144-64.594,144-144s-64.594-144-144-144v96L96,128L256,0v96c114.688,0,208,93.313,208,208 c0,114.688-93.312,208-208,208c-114.687,0-208-93.312-208-208h64C112,383.406,176.594,448,256,448z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 874 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M512 320C512 214 426 128 320 128L320 512C426 512 512 426 512 320zM64 320C64 178.6 178.6 64 320 64C461.4 64 576 178.6 576 320C576 461.4 461.4 576 320 576C178.6 576 64 461.4 64 320z"/></svg>

Before

Width:  |  Height:  |  Size: 410 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M352 188.5L300.1 175.5C293.6 173.9 288.8 168.4 288.1 161.7C287.4 155 290.9 148.6 296.8 145.6L337.6 125.2L294.3 92.7C288.8 88.6 286.5 81.4 288.7 74.8C290.9 68.2 297.1 64 304 64L464 64C494.2 64 522.7 78.2 540.8 102.4L598.4 179.2C604.6 187.5 608 197.6 608 208C608 234.5 586.5 256 560 256L538.5 256C521.5 256 505.2 249.3 493.2 237.3L479.9 224L447.9 224L447.9 245.5C447.9 270.3 460.7 293.4 481.7 306.6L588.3 373.2C620.4 393.3 639.9 428.4 639.9 466.3C639.9 526.9 590.8 576.1 530.1 576.1L32.3 576C29 576 25.7 575.6 22.7 574.6C13.5 571.8 6 565 2.3 556C1 552.7 .1 549.1 0 545.3C-.2 541.6 .3 538 1.3 534.6C4.1 525.4 10.9 517.9 19.9 514.2C22.9 513 26.1 512.2 29.4 512L433.3 476C441.6 475.3 448 468.3 448 459.9C448 455.6 446.3 451.5 443.3 448.5L398.9 404.1C368.9 374.1 352 333.4 352 291L352 188.5zM512 136.3C512 136.2 512 136.1 512 136C512 135.9 512 135.8 512 135.7L512 136.3zM510.7 143.7L464.3 132.1C464.1 133.4 464 134.7 464 136C464 149.3 474.7 160 488 160C498.6 160 507.5 153.2 510.7 143.7zM130.9 180.5C147.2 166 171.3 164.3 189.4 176.4L320 263.4L320 290.9C320 323.7 328.4 355.7 344 383.9L112 383.9C105.3 383.9 99.3 379.7 97 373.5C94.7 367.3 96.5 360.2 101.6 355.8L171 296.3L18.4 319.8C11.4 320.9 4.5 317.2 1.5 310.8C-1.5 304.4 .1 296.8 5.4 292L130.9 180.5z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

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

Before

Width:  |  Height:  |  Size: 676 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"/></svg>

Before

Width:  |  Height:  |  Size: 693 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 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 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M535.6 85.7C513.7 63.8 478.3 63.8 456.4 85.7L432 110.1L529.9 208L554.3 183.6C576.2 161.7 576.2 126.3 554.3 104.4L535.6 85.7zM236.4 305.7C230.3 311.8 225.6 319.3 222.9 327.6L193.3 416.4C190.4 425 192.7 434.5 199.1 441C205.5 447.5 215 449.7 223.7 446.8L312.5 417.2C320.7 414.5 328.2 409.8 334.4 403.7L496 241.9L398.1 144L236.4 305.7zM160 128C107 128 64 171 64 224L64 480C64 533 107 576 160 576L416 576C469 576 512 533 512 480L512 384C512 366.3 497.7 352 480 352C462.3 352 448 366.3 448 384L448 480C448 497.7 433.7 512 416 512L160 512C142.3 512 128 497.7 128 480L128 224C128 206.3 142.3 192 160 192L256 192C273.7 192 288 177.7 288 160C288 142.3 273.7 128 256 128L160 128z"/></svg>

Before

Width:  |  Height:  |  Size: 899 B

View file

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="64" height="64">
<g transform="matrix(0 1 -1 0 99.465813 0)" opacity="0.7">
<path fill="#000000" fill-rule="evenodd" clip-rule="evenodd"
stroke="#ffffff"
stroke-width="4"
stroke-linejoin="round"
stroke-linecap="round"
d="M65.5 62
L78 49
C73.5 44.5 69.5 42 63 44
L45 31 C47 25 46 22 41.5 18
L19 41.5
C23 45.5 25 46.5 31 45
L44 62.5
C42.3 69 45 73.5 49 78
L61.5 65.5
L84 87
L87 87
L87.5 86.5
L87.5 83.5 Z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 736 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M384 96L512 96C529.7 96 544 110.3 544 128C544 145.7 529.7 160 512 160L398.4 160C393.2 185.8 375.5 207.1 352 217.3L352 512L512 512C529.7 512 544 526.3 544 544C544 561.7 529.7 576 512 576L128 576C110.3 576 96 561.7 96 544C96 526.3 110.3 512 128 512L288 512L288 217.3C264.5 207 246.8 185.7 241.6 160L128 160C110.3 160 96 145.7 96 128C96 110.3 110.3 96 128 96L256 96C270.6 76.6 293.8 64 320 64C346.2 64 369.4 76.6 384 96zM439.6 384L584.4 384L512 259.8L439.6 384zM512 480C449.1 480 396.8 446 386 401.1C383.4 390.1 387 378.8 392.7 369L487.9 205.8C492.9 197.2 502.1 192 512 192C521.9 192 531.1 197.3 536.1 205.8L631.3 369C637 378.8 640.6 390.1 638 401.1C627.2 445.9 574.9 480 512 480zM126.8 259.8L54.4 384L199.3 384L126.8 259.8zM.9 401.1C-1.7 390.1 1.9 378.8 7.6 369L102.8 205.8C107.8 197.2 117 192 126.9 192C136.8 192 146 197.3 151 205.8L246.2 369C251.9 378.8 255.5 390.1 252.9 401.1C242.1 445.9 189.8 480 126.9 480C64 480 11.7 446 .9 401.1z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M32 176C32 134.5 63.6 100.4 104 96.4L104 96L384 96C437 96 480 139 480 192L480 368L304 368C264.2 368 232 400.2 232 440L232 500C232 524.3 212.3 544 188 544C163.7 544 144 524.3 144 500L144 272L80 272C53.5 272 32 250.5 32 224L32 176zM268.8 544C275.9 530.9 280 515.9 280 500L280 440C280 426.7 290.7 416 304 416L552 416C565.3 416 576 426.7 576 440L576 464C576 508.2 540.2 544 496 544L268.8 544zM112 144C94.3 144 80 158.3 80 176L80 224L144 224L144 176C144 158.3 129.7 144 112 144z"/></svg>

Before

Width:  |  Height:  |  Size: 704 B

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800"
width="800"
version="1.1"
id="_x32_"
viewBox="0 0 512 512"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs1"/>
<style type="text/css"
id="style1">
.st0{fill:#64C0FF;stroke:black;stroke-width:3;stroke-miterlimit:4;stroke-opacity:1}
</style>
<g id="g1"
transform="matrix(0.87097097,0,0,1.0008579,38.609049,-0.21963163)"
style="stroke-width:3.42738;stroke-dasharray:none">
<path class="st0"
d="M 512,255.995 277.045,65.394 v 103.574 c -17.255,0 -36.408,0 -57.542,0 -208.59,0 -249.35,153.44 -201.394,266.128 9.586,-103.098 142.053,-100.701 237.358,-100.701 7.247,0 14.446,0 21.578,0 v 112.211 z"
id="path1"
style="stroke-width:20;stroke:#000000;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.--><path d="M278.6 438.6L182.6 534.6C170.1 547.1 149.8 547.1 137.3 534.6L41.3 438.6C28.8 426.1 28.8 405.8 41.3 393.3C53.8 380.8 74.1 380.8 86.6 393.3L128 434.7L128 128C128 110.3 142.3 96 160 96C177.7 96 192 110.3 192 128L192 434.7L233.4 393.3C245.9 380.8 266.2 380.8 278.7 393.3C291.2 405.8 291.2 426.1 278.7 438.6zM352 544C334.3 544 320 529.7 320 512C320 494.3 334.3 480 352 480L384 480C401.7 480 416 494.3 416 512C416 529.7 401.7 544 384 544L352 544zM352 416C334.3 416 320 401.7 320 384C320 366.3 334.3 352 352 352L448 352C465.7 352 480 366.3 480 384C480 401.7 465.7 416 448 416L352 416zM352 288C334.3 288 320 273.7 320 256C320 238.3 334.3 224 352 224L512 224C529.7 224 544 238.3 544 256C544 273.7 529.7 288 512 288L352 288zM352 160C334.3 160 320 145.7 320 128C320 110.3 334.3 96 352 96L576 96C593.7 96 608 110.3 608 128C608 145.7 593.7 160 576 160L352 160z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -350,11 +350,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z " d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)" transform="translate(0,952.36218)"
id="left" /> id="right" />
<path <path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z" d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="right" id="left"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
<path <path
style="display:inline;fill:url(#linearGradient3);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.77952756;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" style="display:inline;fill:url(#linearGradient3);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:3.77952756;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -321,11 +321,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z " d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)" transform="translate(0,952.36218)"
id="left" /> id="right" />
<path <path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z" d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="right" id="left"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
<path <path
d="m 46.656521,12.167234 18.055171,18.054184 a 6.6081919,6.6078288 0 0 1 -0.126303,9.352065 6.6804126,6.6800456 0 0 1 -8.233169,1.011048 l -7.944268,7.943843 6.463762,6.445343 a 6.9331851,6.9328042 0 0 1 5.741536,2.022073 l 28.057729,28.092294 a 6.9962797,6.9958953 0 0 1 -9.894222,9.893685 L 50.719018,66.907526 A 7.0595711,7.0591833 0 0 1 49.18433,59.270613 l -5.741527,-5.741238 -7.944298,7.943843 A 6.716523,6.7161541 0 0 1 25.134866,69.832263 L 7.079684,51.778091 a 6.716523,6.7161541 0 0 1 8.39566,-10.345064 L 36.31101,20.59853 a 6.716523,6.7161541 0 0 1 10.345612,-8.431329 z" d="m 46.656521,12.167234 18.055171,18.054184 a 6.6081919,6.6078288 0 0 1 -0.126303,9.352065 6.6804126,6.6800456 0 0 1 -8.233169,1.011048 l -7.944268,7.943843 6.463762,6.445343 a 6.9331851,6.9328042 0 0 1 5.741536,2.022073 l 28.057729,28.092294 a 6.9962797,6.9958953 0 0 1 -9.894222,9.893685 L 50.719018,66.907526 A 7.0595711,7.0591833 0 0 1 49.18433,59.270613 l -5.741527,-5.741238 -7.944298,7.943843 A 6.716523,6.7161541 0 0 1 25.134866,69.832263 L 7.079684,51.778091 a 6.716523,6.7161541 0 0 1 8.39566,-10.345064 L 36.31101,20.59853 a 6.716523,6.7161541 0 0 1 10.345612,-8.431329 z"

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -340,11 +340,11 @@
style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" style="fill-opacity:1;stroke:black;stroke-width:2.78220296;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z " d="M 49.84375 1.71875 C 36.719738 1.71875 26.0625 12.375988 26.0625 25.5 C 26.0625 32.977454 29.538325 39.612734 34.9375 43.96875 C 24.439951 49.943698 17.919149 62.196126 14.3125 75.65625 C 9.0380874 95.34065 30.224013 98.21875 49.84375 98.21875 C 69.463486 98.21875 90.549327 94.96715 85.375 75.65625 C 81.693381 61.916246 75.224585 49.827177 64.8125 43.9375 C 70.181573 39.580662 73.59375 32.953205 73.59375 25.5 C 73.59375 12.375988 62.967762 1.71875 49.84375 1.71875 z "
transform="translate(0,952.36218)" transform="translate(0,952.36218)"
id="left" /> id="right" />
<path <path
style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.73577702;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z" d="m 51.28696,1001.834 0,-46.98372 1.434151,0.16768 c 5.155008,0.60274 9.462857,2.72154 12.938257,6.36366 4.74393,4.9715 6.87913,11.35611 6.16464,18.43328 -0.53702,5.31935 -3.09008,10.59498 -6.83833,14.13074 l -1.94072,1.83069 3.04083,2.20427 c 3.58084,2.5957 7.18975,6.4912 9.55296,10.3116 4.89572,7.9144 9.23593,21.4918 8.50487,26.6055 -0.81312,5.6877 -5.43872,9.6977 -13.62216,11.8093 -3.80822,0.9826 -7.68056,1.4713 -14.763321,1.8633 l -4.471177,0.2474 0,-46.9837 z"
id="right" id="left"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
<path <path
sodipodi:type="star" sodipodi:type="star"
@ -363,6 +363,6 @@
d="m 38.011063,984.77381 -10.143601,-5.23583 -10.063711,5.38779 1.845023,-11.2651 -8.233948,-7.90624 11.283888,-1.72639 4.974851,-10.27411 5.128803,10.19813 11.308575,1.55649 -8.114112,8.02918 z" d="m 38.011063,984.77381 -10.143601,-5.23583 -10.063711,5.38779 1.845023,-11.2651 -8.233948,-7.90624 11.283888,-1.72639 4.974851,-10.27411 5.128803,10.19813 11.308575,1.55649 -8.114112,8.02918 z"
inkscape:transform-center-x="0.094945927" inkscape:transform-center-x="0.094945927"
inkscape:transform-center-y="-3.9764964" inkscape:transform-center-y="-3.9764964"
transform="matrix(-2.3768784,0,0,2.4799382,115.920285,-1400.1716)" /> transform="matrix(2.3768784,0,0,2.4799382,-15.920285,-1400.1716)" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -1,24 +1,25 @@
#include "abstract_client.h" #include "abstract_client.h"
#include "../../server/pending_command.h"
#include "featureset.h"
#include "get_pb_extension.h"
#include "pb/commands.pb.h"
#include "pb/event_add_to_list.pb.h"
#include "pb/event_connection_closed.pb.h"
#include "pb/event_game_joined.pb.h"
#include "pb/event_list_rooms.pb.h"
#include "pb/event_notify_user.pb.h"
#include "pb/event_remove_from_list.pb.h"
#include "pb/event_replay_added.pb.h"
#include "pb/event_server_identification.pb.h"
#include "pb/event_server_message.pb.h"
#include "pb/event_server_shutdown.pb.h"
#include "pb/event_user_joined.pb.h"
#include "pb/event_user_left.pb.h"
#include "pb/event_user_message.pb.h"
#include "pb/server_message.pb.h"
#include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.h>
#include <libcockatrice/protocol/featureset.h>
#include <libcockatrice/protocol/get_pb_extension.h>
#include <libcockatrice/protocol/pb/commands.pb.h>
#include <libcockatrice/protocol/pb/event_add_to_list.pb.h>
#include <libcockatrice/protocol/pb/event_connection_closed.pb.h>
#include <libcockatrice/protocol/pb/event_game_joined.pb.h>
#include <libcockatrice/protocol/pb/event_list_rooms.pb.h>
#include <libcockatrice/protocol/pb/event_notify_user.pb.h>
#include <libcockatrice/protocol/pb/event_remove_from_list.pb.h>
#include <libcockatrice/protocol/pb/event_replay_added.pb.h>
#include <libcockatrice/protocol/pb/event_server_identification.pb.h>
#include <libcockatrice/protocol/pb/event_server_message.pb.h>
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
#include <libcockatrice/protocol/pb/event_user_joined.pb.h>
#include <libcockatrice/protocol/pb/event_user_left.pb.h>
#include <libcockatrice/protocol/pb/event_user_message.pb.h>
#include <libcockatrice/protocol/pb/server_message.pb.h>
#include <libcockatrice/protocol/pending_command.h>
AbstractClient::AbstractClient(QObject *parent) AbstractClient::AbstractClient(QObject *parent)
: QObject(parent), nextCmdId(0), status(StatusDisconnected), serverSupportsPasswordHash(false) : QObject(parent), nextCmdId(0), status(StatusDisconnected), serverSupportsPasswordHash(false)
@ -66,9 +67,8 @@ void AbstractClient::processProtocolItem(const ServerMessage &item)
const int cmdId = response.cmd_id(); const int cmdId = response.cmd_id();
PendingCommand *pend = pendingCommands.value(cmdId, 0); PendingCommand *pend = pendingCommands.value(cmdId, 0);
if (!pend) { if (!pend)
return; return;
}
pendingCommands.remove(cmdId); pendingCommands.remove(cmdId);
pend->processResponse(response); pend->processResponse(response);

View file

@ -1,16 +1,12 @@
/**
* @file abstract_client.h
* @ingroup Client
*/
//! \todo Document this file.
#ifndef ABSTRACTCLIENT_H #ifndef ABSTRACTCLIENT_H
#define ABSTRACTCLIENT_H #define ABSTRACTCLIENT_H
#include "pb/response.pb.h"
#include "pb/serverinfo_user.pb.h"
#include <QMutex> #include <QMutex>
#include <QObject>
#include <QVariant> #include <QVariant>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
class PendingCommand; class PendingCommand;
class CommandContainer; class CommandContainer;

View file

@ -6,33 +6,29 @@ bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
{ {
QKeyEvent *kevent; QKeyEvent *kevent;
if (event->type() != QEvent::KeyPress) { if (event->type() != QEvent::KeyPress)
return false; return false;
}
kevent = static_cast<QKeyEvent *>(event); kevent = static_cast<QKeyEvent *>(event);
switch (kevent->key()) { switch (kevent->key()) {
case Qt::Key_Return: case Qt::Key_Return:
case Qt::Key_Enter: case Qt::Key_Enter:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltEnter(); emit onCtrlAltEnter();
} else if (kevent->modifiers() & Qt::ControlModifier) { else if (kevent->modifiers() & Qt::ControlModifier)
emit onCtrlEnter(); emit onCtrlEnter();
} else { else
emit onEnter(); emit onEnter();
}
break; break;
case Qt::Key_Right: case Qt::Key_Right:
if (kevent->modifiers() & Qt::ShiftModifier) { if (kevent->modifiers() & Qt::ShiftModifier)
emit onShiftRight(); emit onShiftRight();
}
break; break;
case Qt::Key_Left: case Qt::Key_Left:
if (kevent->modifiers() & Qt::ShiftModifier) { if (kevent->modifiers() & Qt::ShiftModifier)
emit onShiftLeft(); emit onShiftLeft();
}
break; break;
case Qt::Key_Delete: case Qt::Key_Delete:
@ -41,39 +37,33 @@ bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
break; break;
case Qt::Key_Minus: case Qt::Key_Minus:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltMinus(); emit onCtrlAltMinus();
}
break; break;
case Qt::Key_Equal: case Qt::Key_Equal:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltEqual(); emit onCtrlAltEqual();
}
break; break;
case Qt::Key_BracketLeft: case Qt::Key_BracketLeft:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltLBracket(); emit onCtrlAltLBracket();
}
break; break;
case Qt::Key_BracketRight: case Qt::Key_BracketRight:
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier)) { if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
emit onCtrlAltRBracket(); emit onCtrlAltRBracket();
}
break; break;
case Qt::Key_S: case Qt::Key_S:
if (kevent->modifiers() & Qt::ShiftModifier) { if (kevent->modifiers() & Qt::ShiftModifier)
emit onShiftS(); emit onShiftS();
}
break; break;
case Qt::Key_C: case Qt::Key_C:
if (kevent->modifiers() & Qt::ControlModifier) { if (kevent->modifiers() & Qt::ControlModifier)
emit onCtrlC(); emit onCtrlC();
}
break; break;
default: default:

View file

@ -1,10 +1,3 @@
/**
* @file key_signals.h
* @ingroup Core
* @ingroup UI
*/
//! \todo Document this file.
#ifndef KEYSIGNALS_H #ifndef KEYSIGNALS_H
#define KEYSIGNALS_H #define KEYSIGNALS_H

View file

@ -1,7 +1,5 @@
#include "get_text_with_max.h" #include "get_text_with_max.h"
#include <QInputDialog>
QString getTextWithMax(QWidget *parent, QString getTextWithMax(QWidget *parent,
const QString &title, const QString &title,
const QString &label, const QString &label,

View file

@ -1,15 +1,10 @@
/** // custom QInputDialog::getText implementation that allows configuration of the max length
* @file get_text_with_max.h
* @ingroup UI
* @brief Custom QInputDialog::getText implementation that allows configuration of the max length
*/
#ifndef GETTEXTWITHMAX_H #ifndef GETTEXTWITHMAX_H
#define GETTEXTWITHMAX_H #define GETTEXTWITHMAX_H
#include <QLineEdit> #include "trice_limits.h"
#include <QWidget>
#include <libcockatrice/utility/trice_limits.h> #include <QInputDialog>
QString getTextWithMax(QWidget *parent, QString getTextWithMax(QWidget *parent,
const QString &title, const QString &title,

View file

@ -1,8 +1,7 @@
#include "../../../interface/widgets/menus/deck_editor_menu.h" #include "deck_editor_menu.h"
#include "../../../client/settings/cache_settings.h" #include "../../../settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h" #include "../../../settings/shortcuts_settings.h"
#include "../tabs/abstract_tab_deck_editor.h"
DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), deckEditor(parent) DeckEditorMenu::DeckEditorMenu(AbstractTabDeckEditor *parent) : QMenu(parent), deckEditor(parent)
{ {

View file

@ -1,12 +1,8 @@
/**
* @file deck_editor_menu.h
* @ingroup DeckEditors
*/
//! \todo Document this file.
#ifndef DECK_EDITOR_MENU_H #ifndef DECK_EDITOR_MENU_H
#define DECK_EDITOR_MENU_H #define DECK_EDITOR_MENU_H
#include "../../tabs/abstract_tab_deck_editor.h"
#include <QMenu> #include <QMenu>
class AbstractTabDeckEditor; class AbstractTabDeckEditor;

View file

@ -1,6 +1,6 @@
#include "client_update_checker.h" #include "client_update_checker.h"
#include "../../../settings/cache_settings.h" #include "../../settings/cache_settings.h"
#include "release_channel.h" #include "release_channel.h"
ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent) ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent)

View file

@ -1,9 +1,3 @@
/**
* @file client_update_checker.h
* @ingroup ClientUpdate
*/
//! \todo Document this file.
#ifndef CLIENT_UPDATE_CHECKER_H #ifndef CLIENT_UPDATE_CHECKER_H
#define CLIENT_UPDATE_CHECKER_H #define CLIENT_UPDATE_CHECKER_H
#include <QObject> #include <QObject>

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

@ -1,9 +1,3 @@
/**
* @file deck_link_to_api_transformer.h
* @ingroup ApiInterfaces
*/
//! \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,39 +1,30 @@
/**
* @file interface_json_deck_parser.h
* @ingroup ApiInterfaces
*/
//! \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
#include "../../../deck/deck_loader.h"
#include "../../../interface/deck_loader/card_node_function.h"
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <libcockatrice/card/import/card_name_normalizer.h>
#include <libcockatrice/deck_list/deck_list.h>
class IJsonDeckParser class IJsonDeckParser
{ {
public: public:
virtual ~IJsonDeckParser() = default; virtual ~IJsonDeckParser() = default;
virtual DeckList parse(const QJsonObject &obj) = 0; virtual DeckLoader *parse(const QJsonObject &obj) = 0;
}; };
class ArchidektJsonParser : public IJsonDeckParser class ArchidektJsonParser : public IJsonDeckParser
{ {
public: public:
DeckList parse(const QJsonObject &obj) override DeckLoader *parse(const QJsonObject &obj) override
{ {
DeckList deckList; DeckLoader *list = new DeckLoader();
QString deckName = obj.value("name").toString(); QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString(); QString deckDescription = obj.value("description").toString();
deckList.setName(deckName); list->setName(deckName);
deckList.setComments(deckDescription); list->setComments(deckDescription);
QString outputText; QString outputText;
QTextStream outStream(&outputText); QTextStream outStream(&outputText);
@ -50,25 +41,25 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
} }
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); list->loadFromStream_Plain(outStream, false);
deckList.forEachCard(CardNodeFunction::ResolveProviderId()); list->resolveSetNameAndNumberToProviderID();
return deckList; return list;
} }
}; };
class MoxfieldJsonParser : public IJsonDeckParser class MoxfieldJsonParser : public IJsonDeckParser
{ {
public: public:
DeckList parse(const QJsonObject &obj) override DeckLoader *parse(const QJsonObject &obj) override
{ {
DeckList deckList; DeckLoader *list = new DeckLoader();
QString deckName = obj.value("name").toString(); QString deckName = obj.value("name").toString();
QString deckDescription = obj.value("description").toString(); QString deckDescription = obj.value("description").toString();
deckList.setName(deckName); list->setName(deckName);
deckList.setComments(deckDescription); list->setComments(deckDescription);
QString outputText; QString outputText;
QTextStream outStream(&outputText); QTextStream outStream(&outputText);
@ -97,8 +88,8 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n'; outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
} }
deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer()); list->loadFromStream_Plain(outStream, false);
deckList.forEachCard(CardNodeFunction::ResolveProviderId()); list->resolveSetNameAndNumberToProviderID();
QJsonObject commandersObj = obj.value("commanders").toObject(); QJsonObject commandersObj = obj.value("commanders").toObject();
if (!commandersObj.isEmpty()) { if (!commandersObj.isEmpty()) {
@ -109,12 +100,12 @@ public:
QString collectorNumber = cardData.value("cn").toString(); QString collectorNumber = cardData.value("cn").toString();
QString providerId = cardData.value("scryfall_id").toString(); QString providerId = cardData.value("scryfall_id").toString();
deckList.setBannerCard({commanderName, providerId}); list->setBannerCard({commanderName, providerId});
deckList.addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId); list->addCard(commanderName, DECK_ZONE_MAIN, -1, setName, collectorNumber, providerId);
} }
} }
return deckList; return list;
} }
}; };

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,9 +1,3 @@
/**
* @file release_channel.h
* @ingroup ClientUpdate
*/
//! \todo Document this file.
#ifndef RELEASECHANNEL_H #ifndef RELEASECHANNEL_H
#define RELEASECHANNEL_H #define RELEASECHANNEL_H
@ -57,27 +51,27 @@ protected:
} }
public: public:
[[nodiscard]] QString getName() const QString getName() const
{ {
return name; return name;
} }
[[nodiscard]] QString getDescriptionUrl() const QString getDescriptionUrl() const
{ {
return descriptionUrl; return descriptionUrl;
} }
[[nodiscard]] QString getDownloadUrl() const QString getDownloadUrl() const
{ {
return downloadUrl; return downloadUrl;
} }
[[nodiscard]] QString getCommitHash() const QString getCommitHash() const
{ {
return commitHash; return commitHash;
} }
[[nodiscard]] QDate getPublishDate() const QDate getPublishDate() const
{ {
return publishDate; return publishDate;
} }
[[nodiscard]] bool isCompatibleVersionFound() const bool isCompatibleVersionFound() const
{ {
return compatibleVersionFound; return compatibleVersionFound;
} }
@ -97,15 +91,15 @@ protected:
protected: protected:
static bool downloadMatchesCurrentOS(const QString &fileName); static bool downloadMatchesCurrentOS(const QString &fileName);
[[nodiscard]] virtual QString getReleaseChannelUrl() const = 0; virtual QString getReleaseChannelUrl() const = 0;
public: public:
Release *getLastRelease() Release *getLastRelease()
{ {
return lastRelease; return lastRelease;
} }
[[nodiscard]] virtual QString getManualDownloadUrl() const = 0; virtual QString getManualDownloadUrl() const = 0;
[[nodiscard]] virtual QString getName() const = 0; virtual QString getName() const = 0;
void checkForUpdates(); void checkForUpdates();
signals: signals:
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release); void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
@ -122,12 +116,12 @@ public:
explicit StableReleaseChannel() = default; explicit StableReleaseChannel() = default;
~StableReleaseChannel() override = default; ~StableReleaseChannel() override = default;
[[nodiscard]] QString getManualDownloadUrl() const override; QString getManualDownloadUrl() const override;
[[nodiscard]] QString getName() const override; QString getName() const override;
protected: protected:
[[nodiscard]] QString getReleaseChannelUrl() const override; QString getReleaseChannelUrl() const override;
protected slots: protected slots:
void releaseListFinished() override; void releaseListFinished() override;
@ -143,12 +137,12 @@ public:
BetaReleaseChannel() = default; BetaReleaseChannel() = default;
~BetaReleaseChannel() override = default; ~BetaReleaseChannel() override = default;
[[nodiscard]] QString getManualDownloadUrl() const override; QString getManualDownloadUrl() const override;
[[nodiscard]] QString getName() const override; QString getName() const override;
protected: protected:
[[nodiscard]] QString getReleaseChannelUrl() const override; QString getReleaseChannelUrl() const override;
protected slots: protected slots:
void releaseListFinished() override; void releaseListFinished() override;

View file

@ -1,9 +1,8 @@
#include "replay_timeline_widget.h" #include "replay_timeline_widget.h"
#include "../../../client/settings/cache_settings.h"
#include <QPainter> #include <QPainter>
#include <QPainterPath> #include <QPainterPath>
#include <QPalette>
#include <QTimer> #include <QTimer>
ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
@ -27,23 +26,20 @@ void ReplayTimelineWidget::setTimeline(const QList<int> &_replayTimeline)
for (int i : replayTimeline) { for (int i : replayTimeline) {
if (i > binEndTime) { if (i > binEndTime) {
histogram.append(binValue); histogram.append(binValue);
if (binValue > maxBinValue) { if (binValue > maxBinValue)
maxBinValue = binValue; maxBinValue = binValue;
}
while (i > binEndTime + BIN_LENGTH) { while (i > binEndTime + BIN_LENGTH) {
histogram.append(0); histogram.append(0);
binEndTime += BIN_LENGTH; binEndTime += BIN_LENGTH;
} }
binValue = 1; binValue = 1;
binEndTime += BIN_LENGTH; binEndTime += BIN_LENGTH;
} else { } else
++binValue; ++binValue;
}
} }
histogram.append(binValue); histogram.append(binValue);
if (!replayTimeline.isEmpty()) { if (!replayTimeline.isEmpty())
maxTime = replayTimeline.last(); maxTime = replayTimeline.last();
}
update(); update();
} }
@ -56,9 +52,8 @@ void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */)
qreal binWidth = (qreal)width() / histogram.size(); qreal binWidth = (qreal)width() / histogram.size();
QPainterPath path; QPainterPath path;
path.moveTo(0, height() - 1); path.moveTo(0, height() - 1);
for (int i = 0; i < histogram.size(); ++i) { for (int i = 0; i < histogram.size(); ++i)
path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue)); path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue));
}
path.lineTo(width() - 1, height() - 1); path.lineTo(width() - 1, height() - 1);
path.lineTo(0, height() - 1); path.lineTo(0, height() - 1);
painter.fillPath(path, Qt::black); painter.fillPath(path, Qt::black);
@ -103,12 +98,8 @@ void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering)
update(); update();
} }
/** /// @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind
* @brief Handles a backwards skip in the replay timeline. /// is processed at the end. When false, the backwards skip will always cause an immediate rewind
*
* @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind
* is processed at the end. When false, the backwards skip will always cause an immediate rewind.
*/
void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering) void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering)
{ {
if (doRewindBuffering) { if (doRewindBuffering) {
@ -150,29 +141,26 @@ void ReplayTimelineWidget::replayTimerTimeout()
processNewEvents(NORMAL_PLAYBACK); processNewEvents(NORMAL_PLAYBACK);
if (!(currentVisualTime % 1000)) { if (!(currentVisualTime % 1000))
update(); update();
}
} }
/** @brief Processes all unprocessed events up to the current time. */ /// Processes all unprocessed events up to the current time.
void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode) void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode)
{ {
currentProcessedTime = currentVisualTime; currentProcessedTime = currentVisualTime;
while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) { while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) {
EventProcessingOptions options; Player::EventProcessingOptions options;
// backwards skip => always skip reveal windows // backwards skip => always skip reveal windows
// forwards skip => skip reveal windows that don't happen within a big skip of the target // forwards skip => skip reveal windows that don't happen within a big skip of the target
if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) { if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS)
options |= SKIP_REVEAL_WINDOW; options |= Player::EventProcessingOption::SKIP_REVEAL_WINDOW;
}
// backwards skip => always skip tap animation // backwards skip => always skip tap animation
if (playbackMode == BACKWARD_SKIP) { if (playbackMode == BACKWARD_SKIP)
options |= SKIP_TAP_ANIMATION; options |= Player::EventProcessingOption::SKIP_TAP_ANIMATION;
}
emit processNextEvent(options); emit processNextEvent(options);
++currentEvent; ++currentEvent;

View file

@ -1,14 +1,9 @@
/**
* @file replay_timeline_widget.h
* @ingroup Replay
*/
//! \todo Document this file.
#ifndef REPLAY_TIMELINE_WIDGET #ifndef REPLAY_TIMELINE_WIDGET
#define REPLAY_TIMELINE_WIDGET #define REPLAY_TIMELINE_WIDGET
#include "../../../game/player/event_processing_options.h" #include "../../game/player/player.h"
#include <QList>
#include <QMouseEvent> #include <QMouseEvent>
#include <QWidget> #include <QWidget>
@ -19,7 +14,7 @@ class ReplayTimelineWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
signals: signals:
void processNextEvent(EventProcessingOptions options); void processNextEvent(Player::EventProcessingOptions options);
void replayFinished(); void replayFinished();
void rewound(); void rewound();
@ -59,10 +54,10 @@ public:
explicit ReplayTimelineWidget(QWidget *parent = nullptr); explicit ReplayTimelineWidget(QWidget *parent = nullptr);
void setTimeline(const QList<int> &_replayTimeline); void setTimeline(const QList<int> &_replayTimeline);
[[nodiscard]] QSize sizeHint() const override; QSize sizeHint() const override;
[[nodiscard]] QSize minimumSizeHint() const override; QSize minimumSizeHint() const override;
void setTimeScaleFactor(qreal _timeScaleFactor); void setTimeScaleFactor(qreal _timeScaleFactor);
[[nodiscard]] int getCurrentEvent() const int getCurrentEvent() const
{ {
return currentEvent; return currentEvent;
} }

View file

@ -1,4 +1,4 @@
#include "card_sets_model.h" #include "sets_model.h"
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
@ -6,9 +6,8 @@ SetsModel::SetsModel(CardDatabase *_db, QObject *parent) : QAbstractTableModel(p
{ {
sets.sortByKey(); sets.sortByKey();
for (const CardSetPtr &set : sets) { for (const CardSetPtr &set : sets) {
if (set->getEnabled()) { if (set->getEnabled())
enabledSets.insert(set); enabledSets.insert(set);
}
} }
} }
@ -16,18 +15,16 @@ SetsModel::~SetsModel() = default;
int SetsModel::rowCount(const QModelIndex &parent) const int SetsModel::rowCount(const QModelIndex &parent) const
{ {
if (parent.isValid()) { if (parent.isValid())
return 0; return 0;
} else { else
return sets.size(); return sets.size();
}
} }
QVariant SetsModel::data(const QModelIndex &index, int role) const QVariant SetsModel::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid() || (index.column() >= NUM_COLS) || (index.row() >= rowCount())) { if (!index.isValid() || (index.column() >= NUM_COLS) || (index.row() >= rowCount()))
return QVariant(); return QVariant();
}
CardSetPtr set = sets[index.row()]; CardSetPtr set = sets[index.row()];
@ -43,13 +40,12 @@ QVariant SetsModel::data(const QModelIndex &index, int role) const
} }
} }
if (role != Qt::DisplayRole && role != SortRole) { if (role != Qt::DisplayRole && role != SortRole)
return QVariant(); return QVariant();
}
switch (index.column()) { switch (index.column()) {
case SortKeyCol: case SortKeyCol:
return QString("%1").arg(index.row(), 8, 10, QChar('0')); return QString("%1").arg(set->getSortKey(), 8, 10, QChar('0'));
case IsKnownCol: case IsKnownCol:
return set->getIsKnown(); return set->getIsKnown();
case SetTypeCol: case SetTypeCol:
@ -76,9 +72,8 @@ bool SetsModel::setData(const QModelIndex &index, const QVariant &value, int rol
QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) { if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal))
return QVariant(); return QVariant();
}
switch (section) { switch (section) {
case SortKeyCol: case SortKeyCol:
return QString("Key"); /* no tr() for translations needed, column just used for sorting --> hidden */ return QString("Key"); /* no tr() for translations needed, column just used for sorting --> hidden */
@ -102,15 +97,13 @@ QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int rol
Qt::ItemFlags SetsModel::flags(const QModelIndex &index) const Qt::ItemFlags SetsModel::flags(const QModelIndex &index) const
{ {
if (!index.isValid()) { if (!index.isValid())
return Qt::NoItemFlags; return Qt::NoItemFlags;
}
Qt::ItemFlags flags = QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; Qt::ItemFlags flags = QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
if (index.column() == EnabledCol) { if (index.column() == EnabledCol)
flags |= Qt::ItemIsUserCheckable; flags |= Qt::ItemIsUserCheckable;
}
return flags; return flags;
} }
@ -122,9 +115,8 @@ Qt::DropActions SetsModel::supportedDropActions() const
QMimeData *SetsModel::mimeData(const QModelIndexList &indexes) const QMimeData *SetsModel::mimeData(const QModelIndexList &indexes) const
{ {
if (indexes.isEmpty()) { if (indexes.isEmpty())
return 0; return 0;
}
SetsMimeData *result = new SetsMimeData(indexes[0].row()); SetsMimeData *result = new SetsMimeData(indexes[0].row());
return qobject_cast<QMimeData *>(result); return qobject_cast<QMimeData *>(result);
@ -136,19 +128,16 @@ bool SetsModel::dropMimeData(const QMimeData *data,
int /*column*/, int /*column*/,
const QModelIndex &parent) const QModelIndex &parent)
{ {
if (action != Qt::MoveAction) { if (action != Qt::MoveAction)
return false; return false;
}
if (row == -1) { if (row == -1) {
if (!parent.isValid()) { if (!parent.isValid())
return false; return false;
}
row = parent.row(); row = parent.row();
} }
int oldRow = qobject_cast<const SetsMimeData *>(data)->getOldRow(); int oldRow = qobject_cast<const SetsMimeData *>(data)->getOldRow();
if (oldRow < row) { if (oldRow < row)
row--; row--;
}
swapRows(oldRow, row); swapRows(oldRow, row);
@ -159,11 +148,10 @@ void SetsModel::toggleRow(int row, bool enable)
{ {
CardSetPtr temp = sets.at(row); CardSetPtr temp = sets.at(row);
if (enable) { if (enable)
enabledSets.insert(temp); enabledSets.insert(temp);
} else { else
enabledSets.remove(temp); enabledSets.remove(temp);
}
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol)); emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
} }
@ -172,15 +160,13 @@ void SetsModel::toggleRow(int row)
{ {
CardSetPtr tmp = sets.at(row); CardSetPtr tmp = sets.at(row);
if (tmp == nullptr) { if (tmp == nullptr)
return; return;
}
if (enabledSets.contains(tmp)) { if (enabledSets.contains(tmp))
enabledSets.remove(tmp); enabledSets.remove(tmp);
} else { else
enabledSets.insert(tmp); enabledSets.insert(tmp);
}
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol)); emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
} }
@ -189,11 +175,9 @@ void SetsModel::toggleAll(bool enabled)
{ {
enabledSets.clear(); enabledSets.clear();
if (enabled) { if (enabled)
for (CardSetPtr set : sets) { for (CardSetPtr set : sets)
enabledSets.insert(set); enabledSets.insert(set);
}
}
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
} }
@ -224,9 +208,8 @@ void SetsModel::sort(int column, Qt::SortOrder order)
int numRows = rowCount(); int numRows = rowCount();
int row; int row;
for (row = 0; row < numRows; ++row) { for (row = 0; row < numRows; ++row)
setMap.insert(index(row, column).data(SetsModel::SortRole).toString(), sets.at(row)); setMap.insert(index(row, column).data(SetsModel::SortRole).toString(), sets.at(row));
}
QList<CardSetPtr> tmp = setMap.values(); QList<CardSetPtr> tmp = setMap.values();
sets.clear(); sets.clear();
@ -245,19 +228,16 @@ void SetsModel::sort(int column, Qt::SortOrder order)
void SetsModel::save(CardDatabase *db) void SetsModel::save(CardDatabase *db)
{ {
QVector<ICardSetPriorityController::SetSaveData> saveData; // order
saveData.reserve(sets.size()); for (int i = 0; i < sets.size(); i++)
sets[i]->setSortKey(static_cast<unsigned int>(i + 1));
for (int i = 0; i < sets.size(); ++i) { // enabled sets
const unsigned int sortKey = static_cast<unsigned int>(i + 1); for (const CardSetPtr &set : sets)
const bool enabled = enabledSets.contains(sets[i]); set->setEnabled(enabledSets.contains(set));
sets[i]->setSortKeyInMemory(sortKey);
sets[i]->setEnabledInMemory(enabled);
saveData.append({sets[i]->getShortName(), sortKey, enabled});
}
db->getPriorityController()->saveSets(saveData);
sets.sortByKey(); sets.sortByKey();
db->notifyEnabledSetsChanged(); db->notifyEnabledSetsChanged();
} }
@ -270,9 +250,8 @@ void SetsModel::restore(CardDatabase *db)
// enabled sets // enabled sets
enabledSets.clear(); enabledSets.clear();
for (const CardSetPtr &set : sets) { for (const CardSetPtr &set : sets) {
if (set->getEnabled()) { if (set->getEnabled())
enabledSets.insert(set); enabledSets.insert(set);
}
} }
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
@ -304,7 +283,11 @@ bool SetsDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex &source
auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent); auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent);
auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent); auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
const auto filter = filterRegularExpression(); const auto filter = filterRegularExpression();
#else
const auto filter = filterRegExp();
#endif
return (sourceModel()->data(typeIndex).toString().contains(filter) || return (sourceModel()->data(typeIndex).toString().contains(filter) ||
sourceModel()->data(nameIndex).toString().contains(filter) || sourceModel()->data(nameIndex).toString().contains(filter) ||

View file

@ -1,17 +1,12 @@
/**
* @file sets_model.h
* @ingroup CardDatabaseModels
*/
//! \todo Document this file.
#ifndef SETSMODEL_H #ifndef SETSMODEL_H
#define SETSMODEL_H #define SETSMODEL_H
#include "../../game/cards/card_database.h"
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <QMimeData> #include <QMimeData>
#include <QSet> #include <QSet>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <libcockatrice/card/database/card_database.h>
class SetsProxyModel; class SetsProxyModel;
@ -25,11 +20,11 @@ public:
SetsMimeData(int _oldRow) : oldRow(_oldRow) SetsMimeData(int _oldRow) : oldRow(_oldRow)
{ {
} }
[[nodiscard]] int getOldRow() const int getOldRow() const
{ {
return oldRow; return oldRow;
} }
[[nodiscard]] QStringList formats() const QStringList formats() const
{ {
return QStringList() << "application/x-cockatricecardset"; return QStringList() << "application/x-cockatricecardset";
} }
@ -42,7 +37,7 @@ class SetsModel : public QAbstractTableModel
private: private:
static const int NUM_COLS = 7; static const int NUM_COLS = 7;
CardSetList sets; SetList sets;
QSet<CardSetPtr> enabledSets; QSet<CardSetPtr> enabledSets;
public: public:
@ -64,23 +59,22 @@ public:
explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr); explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr);
~SetsModel() override; ~SetsModel() override;
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
[[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override int columnCount(const QModelIndex &parent = QModelIndex()) const override
{ {
Q_UNUSED(parent); Q_UNUSED(parent);
return NUM_COLS; return NUM_COLS;
} }
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool setData(const QModelIndex &index, const QVariant &value, int role) override;
[[nodiscard]] QVariant QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override;
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; Qt::DropActions supportedDropActions() const override;
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
[[nodiscard]] QMimeData *mimeData(const QModelIndexList &indexes) const override; QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool bool
dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
[[nodiscard]] QStringList mimeTypes() const override; QStringList mimeTypes() const override;
void swapRows(int oldRow, int newRow); void swapRows(int oldRow, int newRow);
void toggleRow(int row, bool enable); void toggleRow(int row, bool enable);
void toggleRow(int row); void toggleRow(int row);
@ -98,8 +92,8 @@ public:
explicit SetsDisplayModel(QObject *parent = nullptr); explicit SetsDisplayModel(QObject *parent = nullptr);
protected: protected:
[[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
void fetchMore(const QModelIndex &index) override; void fetchMore(const QModelIndex &index) override;
}; };

View file

@ -1,20 +1,21 @@
#include "spoiler_background_updater.h" #include "spoiler_background_updater.h"
#include "../../../../interface/window_main.h" #include "../../game/cards/card_database.h"
#include "../../../../main.h" #include "../../game/cards/card_database_manager.h"
#include "../../../settings/cache_settings.h" #include "../../main.h"
#include "../../settings/cache_settings.h"
#include "../ui/window_main.h"
#include <QApplication>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QLocale> #include <QLocale>
#include <QMessageBox>
#include <QNetworkReply> #include <QNetworkReply>
#include <QUrl> #include <QUrl>
#include <QtConcurrent> #include <QtConcurrent>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <version_string.h>
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled" #define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml" #define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
@ -40,9 +41,7 @@ void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool sav
void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults) void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
{ {
auto *nam = new QNetworkAccessManager(this); auto *nam = new QNetworkAccessManager(this);
auto request = QNetworkRequest(url); QNetworkReply *reply = nam->get(QNetworkRequest(url));
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
QNetworkReply *reply = nam->get(request);
if (saveResults) { if (saveResults) {
// This will write out to the file (used for spoiler.xml) // This will write out to the file (used for spoiler.xml)

View file

@ -1,9 +1,3 @@
/**
* @file spoiler_background_updater.h
* @ingroup Client
*/
//! \todo Document this file.
#ifndef COCKATRICE_SPOILER_DOWNLOADER_H #ifndef COCKATRICE_SPOILER_DOWNLOADER_H
#define COCKATRICE_SPOILER_DOWNLOADER_H #define COCKATRICE_SPOILER_DOWNLOADER_H
@ -22,7 +16,7 @@ public:
inline QString getCardUpdaterBinaryName() inline QString getCardUpdaterBinaryName()
{ {
return "oracle"; return "oracle";
} };
QByteArray getHash(const QString fileName); QByteArray getHash(const QString fileName);
QByteArray getHash(QByteArray data); QByteArray getHash(QByteArray data);
static bool deleteSpoilerFile(); static bool deleteSpoilerFile();

View file

@ -1,6 +1,6 @@
#include "replay_manager.h" #include "replay_manager.h"
#include "../interface/widgets/tabs/tab_game.h" #include "tabs/tab_game.h"
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QToolButton> #include <QToolButton>
@ -11,7 +11,7 @@ ReplayManager::ReplayManager(TabGame *parent, GameReplay *_replay)
aReplaySkipBackwardBig(nullptr) aReplaySkipBackwardBig(nullptr)
{ {
if (replay) { if (replay) {
game->getGame()->loadReplay(replay); game->loadReplay(replay);
// Create list: event number -> time [ms] // Create list: event number -> time [ms]
// Distribute simultaneous events evenly across 1 second. // Distribute simultaneous events evenly across 1 second.
@ -19,19 +19,16 @@ ReplayManager::ReplayManager(TabGame *parent, GameReplay *_replay)
const int eventCount = replay->event_list_size(); const int eventCount = replay->event_list_size();
for (int i = 0; i < eventCount; ++i) { for (int i = 0; i < eventCount; ++i) {
int j = i + 1; int j = i + 1;
while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) { while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp))
++j; ++j;
}
const int numberEventsThisSecond = j - i; const int numberEventsThisSecond = j - i;
for (int k = 0; k < numberEventsThisSecond; ++k) { for (int k = 0; k < numberEventsThisSecond; ++k)
replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 +
(int)((qreal)k / (qreal)numberEventsThisSecond * 1000)); (int)((qreal)k / (qreal)numberEventsThisSecond * 1000));
}
if (j < eventCount) { if (j < eventCount)
lastEventTimestamp = replay->event_list(j).seconds_elapsed(); lastEventTimestamp = replay->event_list(j).seconds_elapsed();
}
i += numberEventsThisSecond - 1; i += numberEventsThisSecond - 1;
} }
} }
@ -96,9 +93,9 @@ ReplayManager::ReplayManager(TabGame *parent, GameReplay *_replay)
refreshShortcuts(); refreshShortcuts();
} }
void ReplayManager::replayNextEvent(EventProcessingOptions options) void ReplayManager::replayNextEvent(Player::EventProcessingOptions options)
{ {
emit eventReplayed(replay->event_list(timelineWidget->getCurrentEvent()), options); game->processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), nullptr, options);
} }
void ReplayManager::replayFinished() void ReplayManager::replayFinished()

View file

@ -1,20 +1,9 @@
/**
* @file replay_manager.h
* @ingroup Core
* @ingroup Replay
*/
//! \todo Document this file.
#ifndef REPLAY_MANAGER_H #ifndef REPLAY_MANAGER_H
#define REPLAY_MANAGER_H #define REPLAY_MANAGER_H
#include "network/replay_timeline_widget.h"
#include "pb/game_replay.pb.h"
#include "replay_timeline_widget.h"
#include <QToolButton>
#include <QWidget> #include <QWidget>
#include <libcockatrice/protocol/pb/game_replay.pb.h>
class TabGame;
class ReplayManager : public QWidget class ReplayManager : public QWidget
{ {
@ -27,7 +16,6 @@ public:
signals: signals:
void requestChatAndPhaseReset(); void requestChatAndPhaseReset();
void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options);
private: private:
// Replay related members // Replay related members
@ -38,7 +26,7 @@ private:
QAction *aReplaySkipForward, *aReplaySkipBackward, *aReplaySkipForwardBig, *aReplaySkipBackwardBig; QAction *aReplaySkipForward, *aReplaySkipBackward, *aReplaySkipForwardBig, *aReplaySkipBackwardBig;
private slots: private slots:
void replayNextEvent(EventProcessingOptions options); void replayNextEvent(Player::EventProcessingOptions options);
void replayFinished(); void replayFinished();
void replayPlayButtonToggled(bool checked); void replayPlayButtonToggled(bool checked);
void replayFastForwardButtonToggled(bool checked); void replayFastForwardButtonToggled(bool checked);

View file

@ -1,6 +1,6 @@
#include "sound_engine.h" #include "sound_engine.h"
#include "settings/cache_settings.h" #include "../settings/cache_settings.h"
#include <QDir> #include <QDir>
#include <QMediaPlayer> #include <QMediaPlayer>
@ -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,9 +1,3 @@
/**
* @file sound_engine.h
* @ingroup Core
*/
//! \todo Document this file.
#ifndef SOUNDENGINE_H #ifndef SOUNDENGINE_H
#define SOUNDENGINE_H #define SOUNDENGINE_H

View file

@ -0,0 +1,608 @@
#include "abstract_tab_deck_editor.h"
#include "../../client/game_logic/abstract_client.h"
#include "../../client/tapped_out_interface.h"
#include "../../client/ui/widgets/cards/card_info_frame_widget.h"
#include "../../deck/deck_stats_interface.h"
#include "../../dialogs/dlg_load_deck.h"
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
#include "../../dialogs/dlg_load_deck_from_website.h"
#include "../../game/cards/card_database_manager.h"
#include "../../game/cards/card_database_model.h"
#include "../../server/pending_command.h"
#include "../../settings/cache_settings.h"
#include "../ui/picture_loader/picture_loader.h"
#include "../ui/pixel_map_generator.h"
#include "pb/command_deck_upload.pb.h"
#include "pb/response.pb.h"
#include "tab_supervisor.h"
#include "trice_limits.h"
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QCloseEvent>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QFileDialog>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenuBar>
#include <QMessageBox>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QProcessEnvironment>
#include <QPushButton>
#include <QRegularExpression>
#include <QSplitter>
#include <QTextStream>
#include <QTreeView>
#include <QUrl>
AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
{
setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this);
deckDockWidget = new DeckEditorDeckDockWidget(this);
cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this);
filterDockWidget = new DeckEditorFilterDockWidget(this);
printingSelectorDockWidget = new DeckEditorPrintingSelectorDockWidget(this);
connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged);
connect(deckDockWidget, &DeckEditorDeckDockWidget::deckModified, this, &AbstractTabDeckEditor::onDeckModified);
connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard);
connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this,
&AbstractTabDeckEditor::updateCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, this,
&AbstractTabDeckEditor::actAddCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, this,
&AbstractTabDeckEditor::actAddCardToSideboard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, this,
&AbstractTabDeckEditor::actDecrementCard);
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, this,
&AbstractTabDeckEditor::actDecrementCardFromSideboard);
connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, databaseDisplayDockWidget,
&DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters);
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&AbstractTabDeckEditor::refreshShortcuts);
}
void AbstractTabDeckEditor::updateCard(const ExactCard &card)
{
cardInfoDockWidget->updateCard(card);
printingSelectorDockWidget->printingSelector->setCard(card.getCardPtr(), DECK_ZONE_MAIN);
}
void AbstractTabDeckEditor::onDeckChanged()
{
}
void AbstractTabDeckEditor::onDeckModified()
{
setModified(!isBlankNewDeck());
deckMenu->setSaveStatus(!isBlankNewDeck());
}
void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneName)
{
if (!card)
return;
if (card.getInfo().getIsToken())
zoneName = DECK_ZONE_TOKENS;
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
// recursiveExpand(newCardIndex);
deckDockWidget->deckView->clearSelection();
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
setModified(true);
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());
}
void AbstractTabDeckEditor::actAddCard(const ExactCard &card)
{
if (QApplication::keyboardModifiers() & Qt::ControlModifier)
actAddCardToSideboard(card);
else
addCardHelper(card, DECK_ZONE_MAIN);
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::actAddCardToSideboard(const ExactCard &card)
{
addCardHelper(card, DECK_ZONE_SIDE);
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::actDecrementCard(const ExactCard &card)
{
emit decrementCard(card, DECK_ZONE_MAIN);
}
void AbstractTabDeckEditor::actDecrementCardFromSideboard(const ExactCard &card)
{
emit decrementCard(card, DECK_ZONE_SIDE);
}
void AbstractTabDeckEditor::actSwapCard(const ExactCard &card, const QString &zoneName)
{
QString providerId = card.getPrinting().getUuid();
QString collectorNumber = card.getPrinting().getProperty("num");
QModelIndex foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName, providerId, collectorNumber);
if (!foundCard.isValid()) {
foundCard = deckDockWidget->deckModel->findCard(card.getName(), zoneName);
}
deckDockWidget->swapCard(foundCard);
}
/**
* Opens the deck in this tab.
* @param deck The deck. Takes ownership of the object
*/
void AbstractTabDeckEditor::openDeck(DeckLoader *deck)
{
setDeck(deck);
if (!deck->getLastFileName().isEmpty()) {
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastFileName());
}
}
/**
* Sets the currently active deck for this tab
* @param _deck The deck. Takes ownership of the object
*/
void AbstractTabDeckEditor::setDeck(DeckLoader *_deck)
{
deckDockWidget->setDeck(_deck);
PictureLoader::cacheCardPixmaps(CardDatabaseManager::getInstance()->getCards(getDeckList()->getCardRefList()));
setModified(false);
// If they load a deck, make the deck list appear
aDeckDockVisible->setChecked(true);
deckDockWidget->setVisible(aDeckDockVisible->isChecked());
}
DeckLoader *AbstractTabDeckEditor::getDeckList() const
{
return deckDockWidget->getDeckList();
}
void AbstractTabDeckEditor::setModified(bool _modified)
{
modified = _modified;
emit tabTextChanged(this, getTabText());
}
/**
* @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action.
*/
bool AbstractTabDeckEditor::isBlankNewDeck() const
{
DeckLoader *deck = getDeckList();
return !modified && deck->hasNotBeenLoaded();
}
void AbstractTabDeckEditor::actNewDeck()
{
auto deckOpenLocation = confirmOpen(false);
if (deckOpenLocation == CANCELLED) {
return;
}
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(nullptr);
return;
}
cleanDeckAndResetModified();
}
void AbstractTabDeckEditor::cleanDeckAndResetModified()
{
deckMenu->setSaveStatus(false);
deckDockWidget->cleanDeck();
setModified(false);
}
/**
* @brief Displays the save confirmation dialogue that is shown before loading a deck, if required. Takes into
* account the `openDeckInNewTab` settting.
*
* @param openInSameTabIfBlank Open the deck in the same tab instead of a new tab if the current tab is completely
* blank. Only relevant when the `openDeckInNewTab` setting is enabled.
*
* @returns An enum that indicates if and where to load the deck
*/
AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank)
{
// handle `openDeckInNewTab` setting
if (SettingsCache::instance().getOpenDeckInNewTab()) {
if (openInSameTabIfBlank && isBlankNewDeck()) {
return SAME_TAB;
} else {
return NEW_TAB;
}
}
// early return if deck is unmodified
if (!modified) {
return SAME_TAB;
}
// do the save confirmation dialogue
tabSupervisor->setCurrentWidget(this);
QMessageBox *msgBox = createSaveConfirmationWindow();
QPushButton *newTabButton = msgBox->addButton(tr("Open in new tab"), QMessageBox::ApplyRole);
int ret = msgBox->exec();
// `exec()` returns an opaque value if a non-standard button was clicked.
// Directly check if newTabButton was clicked before switching over the standard buttons.
if (msgBox->clickedButton() == newTabButton) {
return NEW_TAB;
}
switch (ret) {
case QMessageBox::Save:
return actSaveDeck() ? SAME_TAB : CANCELLED;
case QMessageBox::Discard:
return SAME_TAB;
default:
return CANCELLED;
}
}
/**
* @brief Creates the base save confirmation dialogue box.
*
* @returns A QMessageBox that can be further modified
*/
QMessageBox *AbstractTabDeckEditor::createSaveConfirmationWindow()
{
QMessageBox *msgBox = new QMessageBox(this);
msgBox->setIcon(QMessageBox::Warning);
msgBox->setWindowTitle(tr("Are you sure?"));
msgBox->setText(tr("The decklist has been modified.\nDo you want to save the changes?"));
msgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
return msgBox;
}
void AbstractTabDeckEditor::actLoadDeck()
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeck dialog(this);
if (!dialog.exec())
return;
QString fileName = dialog.selectedFiles().at(0);
openDeckFromFile(fileName, deckOpenLocation);
deckDockWidget->updateBannerCardComboBox();
}
void AbstractTabDeckEditor::actOpenRecent(const QString &fileName)
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
openDeckFromFile(fileName, deckOpenLocation);
}
/**
* Actually opens the deck from file
* @param fileName The path of the deck to open
* @param deckOpenLocation Which tab to open the deck
*/
void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation)
{
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
auto *l = new DeckLoader;
if (l->loadFromFile(fileName, fmt, true)) {
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l);
l->deleteLater();
} else {
deckMenu->setSaveStatus(false);
openDeck(l);
}
} else {
l->deleteLater();
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
}
deckMenu->setSaveStatus(true);
}
bool AbstractTabDeckEditor::actSaveDeck()
{
DeckLoader *const deck = getDeckList();
if (deck->getLastRemoteDeckId() != -1) {
QString deckString = deck->writeToString_Native();
if (deckString.length() > MAX_FILE_LENGTH) {
QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck"));
return false;
}
Command_DeckUpload cmd;
cmd.set_deck_id(static_cast<google::protobuf::uint32>(deck->getLastRemoteDeckId()));
cmd.set_deck_list(deckString.toStdString());
PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
connect(pend, &PendingCommand::finished, this, &AbstractTabDeckEditor::saveDeckRemoteFinished);
tabSupervisor->getClient()->sendCommand(pend);
return true;
} else if (deck->getLastFileName().isEmpty())
return actSaveDeckAs();
else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) {
setModified(false);
return true;
}
QMessageBox::critical(
this, tr("Error"),
tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
return false;
}
bool AbstractTabDeckEditor::actSaveDeckAs()
{
QFileDialog dialog(this, tr("Save deck"));
dialog.setDirectory(SettingsCache::instance().getDeckPath());
dialog.setAcceptMode(QFileDialog::AcceptSave);
dialog.setDefaultSuffix("cod");
dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS);
dialog.selectFile(getDeckList()->getName().trimmed());
if (!dialog.exec())
return false;
QString fileName = dialog.selectedFiles().at(0);
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
if (!getDeckList()->saveToFile(fileName, fmt)) {
QMessageBox::critical(
this, tr("Error"),
tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
return false;
}
setModified(false);
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
return true;
}
void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response)
{
if (response.response_code() != Response::RespOk)
QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved."));
else
setModified(false);
}
void AbstractTabDeckEditor::actLoadDeckFromClipboard()
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeckFromClipboard dlg(this);
if (!dlg.exec())
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeckList());
} else {
setDeck(dlg.getDeckList());
setModified(true);
}
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::editDeckInClipboard(bool annotated)
{
DlgEditDeckInClipboard dlg(*getDeckList(), annotated, this);
if (!dlg.exec())
return;
setDeck(dlg.getDeckList());
setModified(true);
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::actEditDeckInClipboard()
{
editDeckInClipboard(true);
}
void AbstractTabDeckEditor::actEditDeckInClipboardRaw()
{
editDeckInClipboard(false);
}
void AbstractTabDeckEditor::actSaveDeckToClipboard()
{
getDeckList()->saveToClipboard(true, true);
}
void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo()
{
getDeckList()->saveToClipboard(true, false);
}
void AbstractTabDeckEditor::actSaveDeckToClipboardRaw()
{
getDeckList()->saveToClipboard(false, true);
}
void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo()
{
getDeckList()->saveToClipboard(false, false);
}
void AbstractTabDeckEditor::actPrintDeck()
{
auto *dlg = new QPrintPreviewDialog(this);
connect(dlg, &QPrintPreviewDialog::paintRequested, deckDockWidget->deckModel, &DeckListModel::printDeckList);
dlg->exec();
}
void AbstractTabDeckEditor::actLoadDeckFromWebsite()
{
auto deckOpenLocation = confirmOpen();
if (deckOpenLocation == CANCELLED) {
return;
}
DlgLoadDeckFromWebsite dlg(this);
if (!dlg.exec())
return;
if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeck());
} else {
setDeck(dlg.getDeck());
setModified(true);
}
deckMenu->setSaveStatus(true);
}
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
{
// check if deck is not null
if (DeckLoader *const deck = getDeckList()) {
// Get the decklist url string from the deck loader class.
QString decklistUrlString = deck->exportDeckToDecklist(website);
// Check to make sure the string isn't empty.
if (QString::compare(decklistUrlString, "", Qt::CaseInsensitive) == 0) {
// Show an error if the deck is empty, and return.
QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported"));
return;
}
// Encode the string recieved from the model to make sure all characters are encoded.
// first we put it into a qurl object
QUrl decklistUrl = QUrl(decklistUrlString);
// we get the correctly encoded url.
decklistUrlString = decklistUrl.toEncoded();
// We open the url in the user's default browser
QDesktopServices::openUrl(decklistUrlString);
} else {
// if there's no deck loader object, return an error
QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be exported."));
}
}
/**
* Exports the deck to www.decklist.org (the old website)
*/
void AbstractTabDeckEditor::actExportDeckDecklist()
{
exportToDecklistWebsite(DeckLoader::DecklistOrg);
}
/**
* Exports the deck to www.decklist.xyz (the new website)
*/
void AbstractTabDeckEditor::actExportDeckDecklistXyz()
{
exportToDecklistWebsite(DeckLoader::DecklistXyz);
}
void AbstractTabDeckEditor::actAnalyzeDeckDeckstats()
{
auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(),
this); // it deletes itself when done
interface->analyzeDeck(getDeckList());
}
void AbstractTabDeckEditor::actAnalyzeDeckTappedout()
{
auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(),
this); // it deletes itself when done
interface->analyzeDeck(getDeckList());
}
void AbstractTabDeckEditor::filterTreeChanged(FilterTree *filterTree)
{
databaseDisplayDockWidget->setFilterTree(filterTree);
}
void AbstractTabDeckEditor::closeEvent(QCloseEvent *event)
{
emit deckEditorClosing(this);
event->accept();
}
// Method uses to sync docks state with menu items state
bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Close) {
if (o == cardInfoDockWidget) {
aCardInfoDockVisible->setChecked(false);
aCardInfoDockFloating->setEnabled(false);
} else if (o == deckDockWidget) {
aDeckDockVisible->setChecked(false);
aDeckDockFloating->setEnabled(false);
} else if (o == filterDockWidget) {
aFilterDockVisible->setChecked(false);
aFilterDockFloating->setEnabled(false);
} else if (o == printingSelectorDockWidget) {
aPrintingSelectorDockVisible->setChecked(false);
aPrintingSelectorDockFloating->setEnabled(false);
}
}
if (o == this && e->type() == QEvent::Hide) {
LayoutsSettings &layouts = SettingsCache::instance().layouts();
layouts.setDeckEditorLayoutState(saveState());
layouts.setDeckEditorGeometry(saveGeometry());
layouts.setDeckEditorCardSize(cardInfoDockWidget->size());
layouts.setDeckEditorFilterSize(filterDockWidget->size());
layouts.setDeckEditorDeckSize(deckDockWidget->size());
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size());
}
return false;
}
bool AbstractTabDeckEditor::confirmClose()
{
if (modified) {
tabSupervisor->setCurrentWidget(this);
int ret = createSaveConfirmationWindow()->exec();
if (ret == QMessageBox::Save)
return actSaveDeck();
else if (ret == QMessageBox::Cancel)
return false;
}
return true;
}
bool AbstractTabDeckEditor::closeRequest()
{
if (!confirmClose()) {
return false;
}
return close();
}

View file

@ -0,0 +1,160 @@
#ifndef TAB_GENERIC_DECK_EDITOR_H
#define TAB_GENERIC_DECK_EDITOR_H
#include "../../game/cards/card_info.h"
#include "../menus/deck_editor/deck_editor_menu.h"
#include "../ui/widgets/deck_editor/deck_editor_card_info_dock_widget.h"
#include "../ui/widgets/deck_editor/deck_editor_database_display_widget.h"
#include "../ui/widgets/deck_editor/deck_editor_deck_dock_widget.h"
#include "../ui/widgets/deck_editor/deck_editor_filter_dock_widget.h"
#include "../ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h"
#include "../ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
#include "tab.h"
class CardDatabaseModel;
class CardDatabaseDisplayModel;
class CardInfoFrameWidget;
class DeckLoader;
class DeckEditorMenu;
class DeckEditorCardInfoDockWidget;
class DeckEditorDatabaseDisplayWidget;
class DeckEditorDeckDockWidget;
class DeckEditorFilterDockWidget;
class DeckEditorPrintingSelectorDockWidget;
class DeckPreviewDeckTagsDisplayWidget;
class Response;
class FilterTreeModel;
class FilterBuilder;
class QTreeView;
class QTextEdit;
class QLabel;
class QComboBox;
class QGroupBox;
class QMessageBox;
class QHBoxLayout;
class QVBoxLayout;
class QPushButton;
class QDockWidget;
class QMenu;
class QAction;
class AbstractTabDeckEditor : public Tab
{
Q_OBJECT
friend class DeckEditorMenu;
public:
explicit AbstractTabDeckEditor(TabSupervisor *_tabSupervisor);
// UI and Navigation
virtual void createMenus() = 0;
[[nodiscard]] virtual QString getTabText() const override = 0;
bool confirmClose();
virtual void retranslateUi() override = 0;
// Deck Management
void openDeck(DeckLoader *deck);
DeckLoader *getDeckList() const;
void setModified(bool _windowModified);
// UI Elements
DeckEditorMenu *deckMenu;
DeckEditorDatabaseDisplayWidget *databaseDisplayDockWidget;
DeckEditorCardInfoDockWidget *cardInfoDockWidget;
DeckEditorDeckDockWidget *deckDockWidget;
DeckEditorFilterDockWidget *filterDockWidget;
DeckEditorPrintingSelectorDockWidget *printingSelectorDockWidget;
public slots:
virtual void onDeckChanged();
virtual void onDeckModified();
void updateCard(const ExactCard &card);
void actAddCard(const ExactCard &card);
void actAddCardToSideboard(const ExactCard &card);
void actDecrementCard(const ExactCard &card);
void actDecrementCardFromSideboard(const ExactCard &card);
void actOpenRecent(const QString &fileName);
void filterTreeChanged(FilterTree *filterTree);
bool closeRequest() override;
virtual void showPrintingSelector() = 0;
virtual void dockTopLevelChanged(bool topLevel) = 0;
signals:
void openDeckEditor(const DeckLoader *deckLoader);
void deckEditorClosing(AbstractTabDeckEditor *tab);
void decrementCard(const ExactCard &card, QString zoneName);
protected slots:
// Deck Operations
virtual void actNewDeck();
void cleanDeckAndResetModified();
virtual void actLoadDeck();
bool actSaveDeck();
virtual bool actSaveDeckAs();
virtual void actLoadDeckFromClipboard();
void actEditDeckInClipboard();
void actEditDeckInClipboardRaw();
void actSaveDeckToClipboard();
void actSaveDeckToClipboardNoSetInfo();
void actSaveDeckToClipboardRaw();
void actSaveDeckToClipboardRawNoSetInfo();
void actPrintDeck();
void actLoadDeckFromWebsite();
void actExportDeckDecklist();
void actExportDeckDecklistXyz();
void actAnalyzeDeckDeckstats();
void actAnalyzeDeckTappedout();
// Remote Save
void saveDeckRemoteFinished(const Response &r);
// UI Layout Management
virtual void loadLayout() = 0;
virtual void restartLayout() = 0;
virtual void freeDocksSize() = 0;
virtual void refreshShortcuts() = 0;
void closeEvent(QCloseEvent *event) override;
bool eventFilter(QObject *o, QEvent *e) override;
virtual void dockVisibleTriggered() = 0;
virtual void dockFloatingTriggered() = 0;
private:
virtual void setDeck(DeckLoader *_deck);
void editDeckInClipboard(bool annotated);
void exportToDecklistWebsite(DeckLoader::DecklistWebsite website);
protected:
/**
* @brief Enum for selecting deck open location
*/
enum DeckOpenLocation
{
CANCELLED,
SAME_TAB,
NEW_TAB
};
DeckOpenLocation confirmOpen(bool openInSameTabIfBlank = true);
QMessageBox *createSaveConfirmationWindow();
bool isBlankNewDeck() const;
// Helper functions for card actions
void addCardHelper(const ExactCard &card, QString zoneName);
void actSwapCard(const ExactCard &card, const QString &zoneName);
virtual void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
// UI Menu Elements
QMenu *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu;
QAction *aResetLayout;
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating;
QAction *aFilterDockVisible, *aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
bool modified = false;
};
#endif // TAB_GENERIC_DECK_EDITOR_H

View file

@ -2,6 +2,7 @@
#include <QDebug> #include <QDebug>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
void EdhrecApiResponseArchidektLink::fromJson(const QJsonObject &json) void EdhrecApiResponseArchidektLink::fromJson(const QJsonObject &json)

View file

@ -1,15 +1,12 @@
#ifndef ARCHIDEKTENTRY_H #ifndef ARCHIDEKTENTRY_H
#define ARCHIDEKTENTRY_H #define ARCHIDEKTENTRY_H
#include <QDebug>
#include <QJsonObject> #include <QJsonObject>
#include <QString> #include <QString>
#include <QVector> #include <QVector>
/** // Represents a single Archidekt entry
* @class EdhrecApiResponseArchidektLink
* @ingroup ApiResponses
* @brief Represents a single Archidekt entry
*/
class EdhrecApiResponseArchidektLink class EdhrecApiResponseArchidektLink
{ {
public: public:
@ -22,11 +19,7 @@ public:
void debugPrint() const; void debugPrint() const;
}; };
/** // Represents the Archidekt section as a list of entries
* @class EdhrecCommanderApiResponseArchidektLinks
* @ingroup ApiResponses
* @brief Represents the Archidekt section as a list of entries
*/
class EdhrecCommanderApiResponseArchidektLinks class EdhrecCommanderApiResponseArchidektLinks
{ {
public: public:

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