mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
Compare commits
31 commits
2026-04-07
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1fe4c85d3 | ||
|
|
a20f3c0fb4 | ||
|
|
9226bc9ddd | ||
|
|
501c4b96d4 | ||
|
|
6ab947418c | ||
|
|
6765831b92 | ||
|
|
98c4e829f8 | ||
|
|
ccb9901b28 | ||
|
|
58a8c7d3df | ||
|
|
655a8e52a1 | ||
|
|
db235ecae8 | ||
|
|
682ac4ed0c | ||
|
|
77978c7178 | ||
|
|
e918856fa4 | ||
|
|
87c4216e80 | ||
|
|
832f70496a | ||
|
|
f97864b72b | ||
|
|
338c56678a | ||
|
|
8cc65b8967 | ||
|
|
2e10b2f5d5 | ||
|
|
92fe406c22 | ||
|
|
d677e2bb70 | ||
|
|
e977f123ce | ||
|
|
f56b672307 | ||
|
|
29c1d7f3e4 | ||
|
|
36aba81b1b | ||
|
|
f9fb03b26b | ||
|
|
d2732ac742 | ||
|
|
9aa5702e14 | ||
|
|
2d412bfe52 | ||
|
|
ac06fb9d1c |
178 changed files with 29500 additions and 23407 deletions
|
|
@ -9,20 +9,18 @@ RUN apt-get update && \
|
|||
file \
|
||||
g++ \
|
||||
git \
|
||||
libgl-dev \
|
||||
liblzma-dev \
|
||||
libmariadb-dev-compat \
|
||||
libprotobuf-dev \
|
||||
libqt6multimedia6 \
|
||||
libqt6sql6-mysql \
|
||||
libqt6svg6-dev \
|
||||
libqt6websockets6-dev \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5sql5-mysql \
|
||||
libqt5svg5-dev \
|
||||
libqt5websockets5-dev \
|
||||
ninja-build \
|
||||
protobuf-compiler \
|
||||
qt6-image-formats-plugins \
|
||||
qt6-l10n-tools \
|
||||
qt6-multimedia-dev \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools \
|
||||
qt5-image-formats-plugins \
|
||||
qtmultimedia5-dev \
|
||||
qttools5-dev \
|
||||
qttools5-dev-tools \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@
|
|||
# --test runs tests
|
||||
# --debug or --release sets the build type ie CMAKE_BUILD_TYPE
|
||||
# --ccache [<size>] uses ccache and shows stats, optionally provide size
|
||||
# --evict-ccache <age> runs ccache eviction based on given age after build
|
||||
# --dir <dir> sets the name of the build dir, default is "build"
|
||||
# --cmake-generator <generator> sets CMAKE_GENERATOR as used by cmake
|
||||
# --target-macos-version <version> sets the min os version - only used for macOS builds
|
||||
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION
|
||||
# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE 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>)
|
||||
# exitcode: 1 for failure, 3 for invalid arguments
|
||||
|
||||
|
|
@ -71,6 +73,15 @@ while [[ $# != 0 ]]; do
|
|||
shift
|
||||
fi
|
||||
;;
|
||||
'--evict-ccache')
|
||||
shift
|
||||
if [[ $# == 0 ]]; then
|
||||
echo "::error file=$0::--evict-ccache expects an argument"
|
||||
exit 3
|
||||
fi
|
||||
CCACHE_EVICTION_AGE=$1
|
||||
shift
|
||||
;;
|
||||
'--vcpkg')
|
||||
USE_VCPKG=1
|
||||
shift
|
||||
|
|
@ -84,6 +95,15 @@ while [[ $# != 0 ]]; do
|
|||
BUILD_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
'--cmake-generator')
|
||||
shift
|
||||
if [[ $# == 0 ]]; then
|
||||
echo "::error file=$0::--cmake-generator expects an argument"
|
||||
exit 3
|
||||
fi
|
||||
export CMAKE_GENERATOR=$1
|
||||
shift
|
||||
;;
|
||||
'--target-macos-version')
|
||||
shift
|
||||
if [[ $# == 0 ]]; then
|
||||
|
|
@ -251,9 +271,16 @@ cmake --build . "${buildflags[@]}"
|
|||
echo "::endgroup::"
|
||||
|
||||
if [[ $USE_CCACHE ]]; then
|
||||
if [[ $CCACHE_EVICTION_AGE ]]; then
|
||||
echo "::group::evict ccache files older than $CCACHE_EVICTION_AGE"
|
||||
ccache --evict-older-than "$CCACHE_EVICTION_AGE"
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
echo "::group::Show ccache stats again"
|
||||
ccachestatsverbose
|
||||
echo "::endgroup::"
|
||||
elif [[ $CCACHE_EVICTION_AGE ]]; then
|
||||
echo "::error file=$0::ccache eviction is enabled while ccache is disabled!"
|
||||
fi
|
||||
|
||||
if [[ $RUNNER_OS == macOS ]]; then
|
||||
|
|
|
|||
|
|
@ -3,17 +3,28 @@
|
|||
# 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.
|
||||
# <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
|
||||
# --build builds the image from the Dockerfile in .ci/$NAME
|
||||
# --save stores the image, if an image was loaded it will not be stored
|
||||
# --interactive immediately starts the image interactively for debugging
|
||||
# --set-cache <location> sets the location to cache the image or for ccache
|
||||
#
|
||||
# requires: docker
|
||||
# uses env: NAME CACHE BUILD GET SAVE INTERACTIVE
|
||||
# (correspond to args: <name> --set-cache <cache> --build --get --save --interactive)
|
||||
# sets env: RUN CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT
|
||||
# exitcode: 1 for failure, 2 for missing dockerfile, 3 for invalid arguments
|
||||
#
|
||||
# exported RUN function will run the BUILD_SCRIPT inside of the docker container.
|
||||
# note that the docker container will not inherit any environment variables!
|
||||
#
|
||||
# usage: RUN [arguments for build script]
|
||||
# roughly equivalent to `docker run $IMAGE_NAME bash $BUILD_SCRIPT $@`
|
||||
# uses env: CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT
|
||||
# exitcode: 3 for invalid arguments, returns the returncode of docker run
|
||||
export BUILD_SCRIPT=".ci/compile.sh"
|
||||
|
||||
project_name="cockatrice"
|
||||
|
|
@ -41,12 +52,17 @@ while [[ $# != 0 ]]; do
|
|||
shift
|
||||
;;
|
||||
'--set-cache')
|
||||
CACHE=$2
|
||||
shift
|
||||
if [[ $# == 0 ]]; then
|
||||
echo "--set-cache expects an argument" >&2
|
||||
exit 3
|
||||
fi
|
||||
CACHE=$1
|
||||
shift
|
||||
if ! [[ -d $CACHE ]]; then
|
||||
echo "could not find cache path: $CACHE" >&2
|
||||
return 3
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
if [[ ${1:0:1} == - ]]; then
|
||||
|
|
@ -149,10 +165,11 @@ function RUN ()
|
|||
args+=(--mount "type=bind,source=$CCACHE_DIR,target=/.ccache")
|
||||
args+=(--env "CCACHE_DIR=/.ccache")
|
||||
fi
|
||||
if [[ -n "$CMAKE_GENERATOR" ]]; then
|
||||
args+=(--env "CMAKE_GENERATOR=$CMAKE_GENERATOR")
|
||||
if [[ $GITHUB_OUTPUT ]]; then
|
||||
args+=(--mount "type=bind,source=$GITHUB_OUTPUT,target=/gh_output")
|
||||
args+=(--env "GITHUB_OUTPUT=/gh_output")
|
||||
fi
|
||||
# shellcheck disable=2086
|
||||
# shellcheck disable=2086
|
||||
docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@"
|
||||
return $?
|
||||
else
|
||||
|
|
|
|||
65
.github/workflows/desktop-build.yml
vendored
65
.github/workflows/desktop-build.yml
vendored
|
|
@ -38,15 +38,15 @@ on:
|
|||
- 'vcpkg.json'
|
||||
- 'vcpkg'
|
||||
|
||||
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on master)
|
||||
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
|
||||
concurrency:
|
||||
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
|
||||
cancel-in-progress: ${{ github.ref_name != 'master' }}
|
||||
cancel-in-progress: ${{ github.ref_type != 'tag' }}
|
||||
|
||||
jobs:
|
||||
configure:
|
||||
name: Configure
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
tag: ${{steps.configure.outputs.tag}}
|
||||
sha: ${{steps.configure.outputs.sha}}
|
||||
|
|
@ -156,12 +156,14 @@ jobs:
|
|||
needs: configure
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{matrix.allow-failure == 'yes'}}
|
||||
timeout-minutes: 70
|
||||
env:
|
||||
NAME: ${{matrix.distro}}${{matrix.version}}
|
||||
CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache
|
||||
# Cache size over the entire repo is 10Gi:
|
||||
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
|
||||
CCACHE_SIZE: 550M
|
||||
CCACHE_EVICTION_AGE: 7d
|
||||
CMAKE_GENERATOR: 'Ninja'
|
||||
|
||||
steps:
|
||||
|
|
@ -185,34 +187,43 @@ jobs:
|
|||
- name: Build debug and test
|
||||
if: matrix.test != 'skip'
|
||||
shell: bash
|
||||
env:
|
||||
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
|
||||
run: |
|
||||
source .ci/docker.sh
|
||||
RUN --server --debug --test --ccache "$CCACHE_SIZE"
|
||||
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
|
||||
--cmake-generator "$CMAKE_GENERATOR"
|
||||
|
||||
- name: Build release package
|
||||
id: build
|
||||
if: matrix.package != 'skip'
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
|
||||
package: '${{matrix.package}}'
|
||||
CMAKE_GENERATOR: '${{env.CMAKE_GENERATOR}}'
|
||||
NO_CLIENT: ${{matrix.server_only == 'yes' && '--no-client' || '' }}
|
||||
server_only: '${{matrix.server_only}}'
|
||||
run: |
|
||||
source .ci/docker.sh
|
||||
RUN --server --release --package "$package" --dir "$BUILD_DIR" \
|
||||
--ccache "$CCACHE_SIZE" $NO_CLIENT
|
||||
.ci/name_build.sh
|
||||
args=()
|
||||
if [[ $server_only == yes ]]; then
|
||||
args+=(--no-client)
|
||||
fi
|
||||
if [[ $GITHUB_REF == "refs/heads/master" ]]; then
|
||||
args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
|
||||
fi
|
||||
args+=(--ccache "$CCACHE_SIZE")
|
||||
args+=(--cmake-generator "$CMAKE_GENERATOR")
|
||||
args+=(--suffix "$SUFFIX")
|
||||
RUN --server --release --package "$package" "${args[@]}"
|
||||
|
||||
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342.
|
||||
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
|
||||
- name: Delete remote compiler cache (ccache)
|
||||
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
||||
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'
|
||||
|
|
@ -270,11 +281,12 @@ jobs:
|
|||
override_target: 13
|
||||
make_package: 1
|
||||
package_suffix: "-macOS13_Intel"
|
||||
qt_version: 6.10.*
|
||||
qt_version: 6.11.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
ccache_eviction_age: 7d
|
||||
|
||||
- os: macOS
|
||||
target: 14
|
||||
|
|
@ -284,11 +296,12 @@ jobs:
|
|||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-macOS14"
|
||||
qt_version: 6.10.*
|
||||
qt_version: 6.11.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
ccache_eviction_age: 7d
|
||||
|
||||
- os: macOS
|
||||
target: 15
|
||||
|
|
@ -298,11 +311,12 @@ jobs:
|
|||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-macOS15"
|
||||
qt_version: 6.10.*
|
||||
qt_version: 6.11.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
ccache_eviction_age: 7d
|
||||
|
||||
- os: macOS
|
||||
target: 15
|
||||
|
|
@ -310,11 +324,12 @@ jobs:
|
|||
soc: Apple
|
||||
xcode: "16.4"
|
||||
type: Debug
|
||||
qt_version: 6.10.*
|
||||
qt_version: 6.11.*
|
||||
qt_arch: clang_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: Ninja
|
||||
use_ccache: 1
|
||||
ccache_eviction_age: 7d
|
||||
|
||||
- os: Windows
|
||||
target: 10
|
||||
|
|
@ -322,7 +337,7 @@ jobs:
|
|||
type: Release
|
||||
make_package: 1
|
||||
package_suffix: "-Win10"
|
||||
qt_version: 6.10.*
|
||||
qt_version: 6.11.*
|
||||
qt_arch: win64_msvc2022_64
|
||||
qt_modules: qtimageformats qtmultimedia qtwebsockets
|
||||
cmake_generator: "Visual Studio 17 2022"
|
||||
|
|
@ -331,6 +346,7 @@ jobs:
|
|||
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
|
||||
needs: configure
|
||||
runs-on: ${{matrix.runner}}
|
||||
timeout-minutes: 100
|
||||
env:
|
||||
CCACHE_DIR: ${{github.workspace}}/.cache/
|
||||
# Cache size over the entire repo is 10Gi:
|
||||
|
|
@ -409,6 +425,8 @@ jobs:
|
|||
if: matrix.os == 'Windows'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
# qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
|
||||
aqtsource: git+https://github.com/miurahr/aqtinstall.git
|
||||
version: ${{ steps.resolve_qt_version.outputs.version }}
|
||||
arch: ${{matrix.qt_arch}}
|
||||
modules: ${{matrix.qt_modules}}
|
||||
|
|
@ -445,14 +463,19 @@ jobs:
|
|||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
||||
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
|
||||
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
|
||||
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
|
||||
run: .ci/compile.sh --server --test --vcpkg
|
||||
|
||||
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342.
|
||||
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
|
||||
- name: Delete remote compiler cache (ccache)
|
||||
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}
|
||||
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' && matrix.use_ccache == 1
|
||||
|
|
|
|||
4
.github/workflows/desktop-lint.yml
vendored
4
.github/workflows/desktop-lint.yml
vendored
|
|
@ -20,13 +20,13 @@ on:
|
|||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 20 # should be enough to find merge base
|
||||
fetch-depth: 20 # should be enough to find merge base
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
|
|
|
|||
5
.github/workflows/docker-release.yml
vendored
5
.github/workflows/docker-release.yml
vendored
|
|
@ -13,6 +13,11 @@ on:
|
|||
- '.github/workflows/docker-release.yml'
|
||||
- 'Dockerfile'
|
||||
|
||||
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
|
||||
concurrency:
|
||||
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
|
||||
cancel-in-progress: ${{ github.ref_type != 'tag' }}
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: amd64 & arm64
|
||||
|
|
|
|||
2
.github/workflows/translations-pull.yml
vendored
2
.github/workflows/translations-pull.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
|
||||
|
||||
name: Pull languages
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
|
|
|
|||
4
.github/workflows/translations-push.yml
vendored
4
.github/workflows/translations-push.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
if: github.event_name != 'schedule' || github.repository_owner == 'Cockatrice'
|
||||
|
||||
name: Push strings
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
|
|
@ -46,7 +46,7 @@ jobs:
|
|||
|
||||
- name: Render template
|
||||
id: template
|
||||
uses: chuhlomin/render-template@v1
|
||||
uses: chuhlomin/render-template/binary@v1
|
||||
with:
|
||||
template: .ci/update_translation_source_strings_template.md
|
||||
vars: |
|
||||
|
|
|
|||
2
.github/workflows/web-lint.yml
vendored
2
.github/workflows/web-lint.yml
vendored
|
|
@ -10,7 +10,7 @@ on:
|
|||
|
||||
jobs:
|
||||
ESLint:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
|
|
|||
|
|
@ -74,11 +74,11 @@ endif()
|
|||
|
||||
# A project name is needed for CPack
|
||||
# Version can be overriden by git tags, see cmake/getversion.cmake
|
||||
project("Cockatrice" VERSION 2.11.0)
|
||||
project("Cockatrice" VERSION 3.0.0)
|
||||
|
||||
# Set release name if not provided via env/cmake var
|
||||
if(NOT DEFINED GIT_TAG_RELEASENAME)
|
||||
set(GIT_TAG_RELEASENAME "Omenpath")
|
||||
set(GIT_TAG_RELEASENAME "Graduation Day")
|
||||
endif()
|
||||
|
||||
# Use c++20 for all targets
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ list(REMOVE_DUPLICATES REQUIRED_QT_COMPONENTS)
|
|||
if(NOT FORCE_USE_QT5)
|
||||
# Linguist is now a component in Qt6 instead of an external package
|
||||
find_package(
|
||||
Qt6 6.2.3
|
||||
Qt6 6.4.2
|
||||
COMPONENTS ${REQUIRED_QT_COMPONENTS} Linguist
|
||||
QUIET HINTS ${Qt6_DIR}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ SetCompressor LZMA
|
|||
Var NormalDestDir
|
||||
Var PortableDestDir
|
||||
Var PortableMode
|
||||
Var ReinstallMode
|
||||
|
||||
!include LogicLib.nsh
|
||||
!include FileFunc.nsh
|
||||
|
|
@ -28,13 +29,23 @@ Var PortableMode
|
|||
!define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now"
|
||||
!define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico"
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE"
|
||||
|
||||
Page Custom PortableModePageCreate PortableModePageLeave
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE componentsPagePre
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfReinstall
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
|
@ -73,6 +84,7 @@ ${IfNot} ${Errors}
|
|||
MessageBox MB_ICONINFORMATION|MB_SETFOREGROUND "\
|
||||
/PORTABLE : Install in portable mode$\n\
|
||||
/S : Silent install$\n\
|
||||
/R : Silent upgrade$\n\
|
||||
/D=%directory% : Specify destination directory$\n"
|
||||
Quit
|
||||
${EndIf}
|
||||
|
|
@ -90,6 +102,16 @@ ${Else}
|
|||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
ClearErrors
|
||||
${GetOptions} $9 "/R" $8
|
||||
${IfNot} ${Errors}
|
||||
StrCpy $ReinstallMode 1
|
||||
SetSilent silent
|
||||
SetAutoClose true
|
||||
${Else}
|
||||
StrCpy $ReinstallMode 0
|
||||
${EndIf}
|
||||
|
||||
${If} $InstDir == ""
|
||||
; User did not use /D to specify a directory,
|
||||
; we need to set a default based on the install mode
|
||||
|
|
@ -97,6 +119,22 @@ ${If} $InstDir == ""
|
|||
${EndIf}
|
||||
Call SetModeDestinationFromInstdir
|
||||
|
||||
; --- Detect portable install when using /R ---
|
||||
${If} $ReinstallMode = 1
|
||||
IfFileExists "$InstDir\portable.dat" 0 not_portable
|
||||
StrCpy $PortableMode 1
|
||||
Goto portable_done
|
||||
|
||||
not_portable:
|
||||
StrCpy $PortableMode 0
|
||||
|
||||
portable_done:
|
||||
${EndIf}
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
Call AutoUninstallIfNeeded
|
||||
${EndIf}
|
||||
|
||||
FunctionEnd
|
||||
|
||||
Function un.onInit
|
||||
|
|
@ -126,8 +164,46 @@ ${Else}
|
|||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function SkipIfReinstall
|
||||
${If} $ReinstallMode = 1
|
||||
Abort
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function AutoUninstallIfNeeded
|
||||
|
||||
SetShellVarContext all
|
||||
|
||||
; --- 32-bit uninstall ---
|
||||
SetRegView 32
|
||||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
||||
|
||||
StrCmp $R0 "" done32
|
||||
DetailPrint "Removing previous version (32-bit)..."
|
||||
ExecWait '$R0'
|
||||
|
||||
done32:
|
||||
|
||||
; --- 64-bit uninstall ---
|
||||
${If} ${RunningX64}
|
||||
SetRegView 64
|
||||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "QuietUninstallString"
|
||||
|
||||
StrCmp $R0 "" done64
|
||||
DetailPrint "Removing previous version (64-bit)..."
|
||||
ExecWait '$R0'
|
||||
|
||||
done64:
|
||||
${EndIf}
|
||||
|
||||
FunctionEnd
|
||||
|
||||
Function PortableModePageCreate
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
Call SetModeDestinationFromInstdir ; If the user clicks BACK on the directory page we will remember their mode specific directory
|
||||
!insertmacro MUI_HEADER_TEXT "Install Mode" "Choose how you want to install Cockatrice."
|
||||
nsDialogs::Create 1018
|
||||
|
|
@ -159,6 +235,11 @@ ${EndIf}
|
|||
FunctionEnd
|
||||
|
||||
Function componentsPagePre
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
Return
|
||||
${EndIf}
|
||||
|
||||
${If} $PortableMode = 0
|
||||
SetShellVarContext all
|
||||
|
||||
|
|
@ -168,8 +249,12 @@ ${If} $PortableMode = 0
|
|||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
|
||||
StrCmp $R0 "" done32
|
||||
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
|
||||
Abort
|
||||
${If} $ReinstallMode = 0
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst32
|
||||
Abort
|
||||
${Else}
|
||||
Goto uninst32
|
||||
${EndIf}
|
||||
|
||||
uninst32:
|
||||
ClearErrors
|
||||
|
|
@ -184,8 +269,12 @@ ${If} $PortableMode = 0
|
|||
ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "UninstallString"
|
||||
StrCmp $R0 "" done64
|
||||
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
|
||||
Abort
|
||||
${If} $ReinstallMode = 0
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A previous version of Cockatrice must be uninstalled before installing the new one." IDOK uninst64
|
||||
Abort
|
||||
${Else}
|
||||
Goto uninst64
|
||||
${EndIf}
|
||||
|
||||
uninst64:
|
||||
ClearErrors
|
||||
|
|
@ -277,6 +366,12 @@ ${Else}
|
|||
FileWrite $0 "PORTABLE"
|
||||
FileClose $0
|
||||
${EndIf}
|
||||
|
||||
${If} $ReinstallMode = 1
|
||||
IfFileExists "$INSTDIR\cockatrice.exe" 0 +2
|
||||
Exec '"$INSTDIR\cockatrice.exe"'
|
||||
${EndIf}
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section "Start menu item" SecStartMenu
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ void TappedOutInterface::analyzeDeck(const DeckList &deck)
|
|||
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
// we interpret the redirect and open it in the browser instead, do not follow redirects
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
|
||||
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#include "abstract_game.h"
|
||||
|
||||
#include "../interface/widgets/tabs/tab_game.h"
|
||||
#include "player/player.h"
|
||||
|
||||
AbstractGame::AbstractGame(TabGame *_tab) : tab(_tab)
|
||||
AbstractGame::AbstractGame(TabGame *_tab) : QObject(_tab), tab(_tab)
|
||||
{
|
||||
gameMetaInfo = new GameMetaInfo(this);
|
||||
gameEventHandler = new GameEventHandler(this);
|
||||
|
|
|
|||
|
|
@ -460,9 +460,6 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
|||
|
||||
bool CardItem::animationEvent()
|
||||
{
|
||||
if (owner == nullptr) {
|
||||
return false;
|
||||
}
|
||||
int rotation = ROTATION_DEGREES_PER_FRAME;
|
||||
bool animationIncomplete = true;
|
||||
if (!tapped)
|
||||
|
|
|
|||
|
|
@ -429,13 +429,13 @@ void GameEventHandler::eventLeave(const Event_Leave &event, int eventPlayerId, c
|
|||
if (!player)
|
||||
return;
|
||||
|
||||
player->clear();
|
||||
emit playerLeft(eventPlayerId);
|
||||
|
||||
emit logLeave(player, getLeaveReason(event.reason()));
|
||||
|
||||
game->getPlayerManager()->removePlayer(eventPlayerId);
|
||||
|
||||
player->clear();
|
||||
player->deleteLater();
|
||||
|
||||
// Rearrange all remaining zones so that attachment relationship updates take place
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
|
|||
fromStr = tr(" from the top of their library");
|
||||
}
|
||||
}
|
||||
} else if (position >= zone->getCards().size() - 1) {
|
||||
} else if (position == zone->getCards().size()) {
|
||||
if (cardName.isEmpty()) {
|
||||
if (ownerChange) {
|
||||
cardName = tr("the bottom card of %1's library").arg(zone->getPlayer()->getPlayerInfo()->getName());
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#include <libcockatrice/protocol/pb/command_reveal_cards.pb.h>
|
||||
|
||||
PlayerMenu::PlayerMenu(Player *_player) : player(_player)
|
||||
PlayerMenu::PlayerMenu(Player *_player) : QObject(_player), player(_player)
|
||||
{
|
||||
playerMenu = new TearOffMenu();
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ private slots:
|
|||
void refreshShortcuts();
|
||||
|
||||
public:
|
||||
PlayerMenu(Player *player);
|
||||
explicit PlayerMenu(Player *player);
|
||||
/// Lifecycle methods: delegate to all managedComponents, plus counters separately via player->getCounters().
|
||||
void retranslateUi();
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@
|
|||
// milliseconds in between triggers of the move top cards until action
|
||||
static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100;
|
||||
|
||||
PlayerActions::PlayerActions(Player *_player) : player(_player), lastTokenTableRow(0), movingCardsUntil(false)
|
||||
PlayerActions::PlayerActions(Player *_player)
|
||||
: QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false)
|
||||
{
|
||||
moveTopCardTimer = new QTimer(this);
|
||||
moveTopCardTimer->setInterval(MOVE_TOP_CARD_UNTIL_INTERVAL);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
#include <libcockatrice/protocol/pb/event_shuffle.pb.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
|
||||
PlayerEventHandler::PlayerEventHandler(Player *_player) : player(_player)
|
||||
PlayerEventHandler::PlayerEventHandler(Player *_player) : QObject(_player), player(_player)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +60,6 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
|
|||
// we want to close empty views as well
|
||||
if (length == 0 || length > absStart) { // note this assumes views always start at the top of the library
|
||||
view->close();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
qWarning() << zone->getName() << "of" << player->getPlayerInfo()->getName() << "holds empty zoneview!";
|
||||
|
|
@ -595,4 +594,4 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
|
|||
qWarning() << "unhandled game event" << type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ CardPictureLoader::CardPictureLoader() : QObject(nullptr)
|
|||
connect(&SettingsCache::instance(), &SettingsCache::picDownloadChanged, this,
|
||||
&CardPictureLoader::picDownloadChanged);
|
||||
|
||||
qRegisterMetaType<ExactCard>();
|
||||
connect(worker, &CardPictureLoaderWorker::imageLoaded, this, &CardPictureLoader::imageLoaded);
|
||||
|
||||
statusBar = new CardPictureLoaderStatusBar(nullptr);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <QPrinter>
|
||||
#include <QTextCursor>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <optional>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader");
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <QMap>
|
||||
#include <QPixmap>
|
||||
#include <libcockatrice/network/server/remote/user_level.h>
|
||||
#include <optional>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(PixelMapGeneratorLog, "pixel_map_generator");
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,10 @@ struct PaletteColorInfo
|
|||
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
defaultStyleName = qApp->style()->objectName();
|
||||
// FIXME workaround for windows11 style being broken
|
||||
if (defaultStyleName == "windows11") {
|
||||
defaultStyleName = "windowsvista";
|
||||
}
|
||||
ensureThemeDirectoryExists();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
|
||||
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &ThemeManager::themeChangedSlot);
|
||||
|
|
|
|||
|
|
@ -343,11 +343,9 @@ void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event)
|
|||
QWidget::mousePressEvent(event);
|
||||
if (event->button() == Qt::RightButton) {
|
||||
createRightClickMenu()->popup(QCursor::pos());
|
||||
} else {
|
||||
emit cardClicked();
|
||||
}
|
||||
|
||||
emit cardClicked();
|
||||
emit cardClicked(event);
|
||||
}
|
||||
|
||||
void CardInfoPictureWidget::hideEvent(QHideEvent *event)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ signals:
|
|||
void hoveredOnCard(const ExactCard &hoveredCard);
|
||||
void cardScaleFactorChanged(int _scale);
|
||||
void cardChanged(const ExactCard &card);
|
||||
void cardClicked();
|
||||
void cardClicked(QMouseEvent *event);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ void CardInfoTextWidget::setCard(const ExactCard &exactCard)
|
|||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
||||
.arg(tr("Name:"), card->getName().toHtmlEscaped());
|
||||
|
||||
if (exactCard.getPrinting() != PrintingInfo()) {
|
||||
if (!exactCard.getPrinting().isEmpty()) {
|
||||
QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped();
|
||||
QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped();
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,14 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
removeButton = new QPushButton(this);
|
||||
saveButton = new QPushButton(this);
|
||||
loadButton = new QPushButton(this);
|
||||
includeSideboardCheckBox = new QCheckBox(this);
|
||||
includeSideboardCheckBox->setChecked(false);
|
||||
|
||||
controlLayout->addWidget(addButton);
|
||||
controlLayout->addWidget(removeButton);
|
||||
controlLayout->addWidget(saveButton);
|
||||
controlLayout->addWidget(loadButton);
|
||||
controlLayout->addWidget(includeSideboardCheckBox);
|
||||
|
||||
layout->addWidget(controlContainer);
|
||||
|
||||
|
|
@ -40,6 +44,7 @@ DeckAnalyticsWidget::DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnal
|
|||
connect(removeButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::onRemoveSelected);
|
||||
connect(saveButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::saveLayout);
|
||||
connect(loadButton, &QPushButton::clicked, this, &DeckAnalyticsWidget::loadLayout);
|
||||
connect(includeSideboardCheckBox, &QCheckBox::clicked, this, &DeckAnalyticsWidget::includeSideboardChanged);
|
||||
|
||||
// Scroll area and container
|
||||
scrollArea = new QScrollArea(this);
|
||||
|
|
@ -66,6 +71,13 @@ void DeckAnalyticsWidget::retranslateUi()
|
|||
removeButton->setText(tr("Remove Panel"));
|
||||
saveButton->setText(tr("Save Layout"));
|
||||
loadButton->setText(tr("Load Layout"));
|
||||
includeSideboardCheckBox->setText(tr("Include Sideboard"));
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::includeSideboardChanged(bool checked)
|
||||
{
|
||||
statsAnalyzer->getConfig().includeSideboard = checked;
|
||||
updateDisplays();
|
||||
}
|
||||
|
||||
void DeckAnalyticsWidget::updateDisplays()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "deck_list_statistics_analyzer.h"
|
||||
#include "resizable_panel.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QJsonObject>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
|
@ -29,6 +30,7 @@ public slots:
|
|||
public:
|
||||
explicit DeckAnalyticsWidget(QWidget *parent, DeckListStatisticsAnalyzer *analyzer);
|
||||
void retranslateUi();
|
||||
void includeSideboardChanged(bool checked);
|
||||
|
||||
private slots:
|
||||
void onAddPanel();
|
||||
|
|
@ -57,6 +59,8 @@ private:
|
|||
QPushButton *saveButton;
|
||||
QPushButton *loadButton;
|
||||
|
||||
QCheckBox *includeSideboardCheckBox;
|
||||
|
||||
QScrollArea *scrollArea;
|
||||
QWidget *panelContainer;
|
||||
QVBoxLayout *panelLayout;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,13 @@ void DeckListStatisticsAnalyzer::analyze()
|
|||
{
|
||||
clearData();
|
||||
|
||||
QList<const DecklistCardNode *> nodes = model->getCardNodes();
|
||||
QList<const DecklistCardNode *> nodes;
|
||||
|
||||
if (config.includeSideboard) {
|
||||
nodes = model->getCardNodes();
|
||||
} else {
|
||||
nodes = model->getCardNodesForZone(DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
for (auto node : nodes) {
|
||||
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(node->getName());
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ struct DeckListStatisticsAnalyzerConfig
|
|||
bool computeCategories = true;
|
||||
bool computeCurveBreakdowns = true;
|
||||
bool computeProbabilities = true;
|
||||
bool includeSideboard = false;
|
||||
};
|
||||
|
||||
class DeckListStatisticsAnalyzer : public QObject
|
||||
|
|
@ -133,6 +134,11 @@ public:
|
|||
return model;
|
||||
}
|
||||
|
||||
DeckListStatisticsAnalyzerConfig &getConfig()
|
||||
{
|
||||
return config;
|
||||
}
|
||||
|
||||
signals:
|
||||
void statsUpdated();
|
||||
|
||||
|
|
|
|||
|
|
@ -1785,7 +1785,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent)
|
|||
contentsWidget->setSpacing(5);
|
||||
|
||||
pagesWidget = new QStackedWidget;
|
||||
pagesWidget->addWidget(new GeneralSettingsPage);
|
||||
pagesWidget->addWidget(makeScrollable(new GeneralSettingsPage));
|
||||
pagesWidget->addWidget(makeScrollable(new AppearanceSettingsPage));
|
||||
pagesWidget->addWidget(makeScrollable(new UserInterfaceSettingsPage));
|
||||
pagesWidget->addWidget(new DeckEditorSettingsPage);
|
||||
|
|
|
|||
|
|
@ -219,7 +219,9 @@ void DlgUpdate::downloadSuccessful(const QUrl &filepath)
|
|||
{
|
||||
setLabel(tr("Installing..."));
|
||||
// Try to open the installer. If it opens, quit Cockatrice
|
||||
if (QDesktopServices::openUrl(filepath)) {
|
||||
if (QProcess::startDetached(filepath.toLocalFile(),
|
||||
QStringList()
|
||||
<< "/R" << QString("/D=%1").arg(QCoreApplication::applicationDirPath()))) {
|
||||
QMetaObject::invokeMethod(static_cast<MainWindow *>(parent()), "close", Qt::QueuedConnection);
|
||||
qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice";
|
||||
close();
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ GameSelector::GameSelector(AbstractClient *_client,
|
|||
bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults();
|
||||
clearFilterButton->setEnabled(!filtersSetToDefault);
|
||||
connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter);
|
||||
connect(gameListProxyModel, &GamesProxyModel::filtersChanged, this, &GameSelector::checkClearFilterButtonState);
|
||||
|
||||
if (room) {
|
||||
createButton = new QPushButton;
|
||||
|
|
@ -188,15 +189,16 @@ void GameSelector::actSetFilter()
|
|||
dlg.getShowOnlyIfSpectatorsCanChat(), dlg.getShowOnlyIfSpectatorsCanSeeHands());
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::checkClearFilterButtonState()
|
||||
{
|
||||
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
|
||||
}
|
||||
|
||||
void GameSelector::actClearFilter()
|
||||
{
|
||||
clearFilterButton->setEnabled(false);
|
||||
|
||||
gameListProxyModel->resetFilterParameters();
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ private slots:
|
|||
* Updates the proxy model with selected filter parameters and refreshes the displayed game list.
|
||||
*/
|
||||
void actSetFilter();
|
||||
void checkClearFilterButtonState();
|
||||
|
||||
/**
|
||||
* @brief Clears all filters applied to the game list.
|
||||
|
|
|
|||
|
|
@ -19,32 +19,46 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
|
|||
mainLayout->setSpacing(5);
|
||||
|
||||
searchBar = new QLineEdit(this);
|
||||
searchBar->setText(model->getCreatorNameFilters().join(", "));
|
||||
connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) { model->setGameNameFilter(text); });
|
||||
searchBar->setText(model->getGameNameFilter());
|
||||
connect(searchBar, &QLineEdit::textChanged, this, [this](const QString &text) {
|
||||
applyFilters([&](auto &, auto &, auto &, auto &, auto &, auto &, auto &, QString &gameNameFilter, auto &,
|
||||
auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &) { gameNameFilter = text; });
|
||||
});
|
||||
|
||||
hideGamesNotCreatedByBuddiesCheckBox = new QCheckBox(this);
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideBuddiesOnlyGames());
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideNotBuddyCreatedGames());
|
||||
connect(hideGamesNotCreatedByBuddiesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
if (checked) {
|
||||
QStringList buddyNames;
|
||||
for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) {
|
||||
buddyNames << QString::fromStdString(buddy.name());
|
||||
applyFilters([&](auto &, auto &, auto &, auto &, auto &, bool &hideNotBuddyCreatedGames, auto &, auto &,
|
||||
QStringList &creatorNameFilters, auto &, auto &, auto &, auto &, auto &, auto &, auto &,
|
||||
auto &) {
|
||||
hideNotBuddyCreatedGames = checked;
|
||||
|
||||
if (checked) {
|
||||
QStringList buddyNames;
|
||||
for (auto buddy : tabSupervisor->getUserListManager()->getBuddyList().values()) {
|
||||
buddyNames << QString::fromStdString(buddy.name());
|
||||
}
|
||||
creatorNameFilters = buddyNames;
|
||||
} else {
|
||||
creatorNameFilters.clear();
|
||||
}
|
||||
model->setCreatorNameFilters(buddyNames);
|
||||
} else {
|
||||
model->setCreatorNameFilters({});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
hideFullGamesCheckBox = new QCheckBox(this);
|
||||
hideFullGamesCheckBox->setChecked(model->getHideFullGames());
|
||||
connect(hideFullGamesCheckBox, &QCheckBox::toggled, this,
|
||||
[this](bool checked) { model->setHideFullGames(checked); });
|
||||
connect(hideFullGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
applyFilters([&](auto &, auto &, bool &hideFullGames, auto &, auto &, auto &, auto &, auto &, auto &, auto &,
|
||||
auto &, auto &, auto &, auto &, auto &, auto &, auto &) { hideFullGames = checked; });
|
||||
});
|
||||
|
||||
hideStartedGamesCheckBox = new QCheckBox(this);
|
||||
hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted());
|
||||
connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this,
|
||||
[this](bool checked) { model->setHideGamesThatStarted(checked); });
|
||||
connect(hideStartedGamesCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
|
||||
applyFilters([&](auto &, auto &, auto &, bool &hideGamesThatStarted, auto &, auto &, auto &, auto &, auto &,
|
||||
auto &, auto &, auto &, auto &, auto &, auto &, auto &,
|
||||
auto &) { hideGamesThatStarted = checked; });
|
||||
});
|
||||
|
||||
filterToFormatComboBox = new QComboBox(this);
|
||||
|
||||
|
|
@ -69,13 +83,15 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
|
|||
|
||||
// Update proxy model on selection change
|
||||
connect(filterToFormatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
|
||||
QVariant data = filterToFormatComboBox->itemData(index);
|
||||
if (!data.isValid()) {
|
||||
model->setGameTypeFilter({}); // empty = no filter
|
||||
} else {
|
||||
int typeId = data.toInt();
|
||||
model->setGameTypeFilter({typeId});
|
||||
}
|
||||
applyFilters([&](auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &, auto &,
|
||||
QSet<int> &gameTypeFilter, auto &, auto &, auto &, auto &, auto &, auto &, auto &) {
|
||||
QVariant data = filterToFormatComboBox->itemData(index);
|
||||
if (!data.isValid()) {
|
||||
gameTypeFilter.clear();
|
||||
} else {
|
||||
gameTypeFilter = {data.toInt()};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setMinimumSize(20, 20);
|
||||
|
|
@ -96,9 +112,87 @@ GameSelectorQuickFilterToolBar::GameSelectorQuickFilterToolBar(QWidget *parent,
|
|||
|
||||
setLayout(mainLayout);
|
||||
|
||||
syncFromModel();
|
||||
|
||||
connect(model, &GamesProxyModel::filtersChanged, this, &GameSelectorQuickFilterToolBar::syncFromModel);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void GameSelectorQuickFilterToolBar::syncFromModel()
|
||||
{
|
||||
QSignalBlocker b1(searchBar);
|
||||
QSignalBlocker b2(filterToFormatComboBox);
|
||||
QSignalBlocker b3(hideGamesNotCreatedByBuddiesCheckBox);
|
||||
QSignalBlocker b4(hideFullGamesCheckBox);
|
||||
QSignalBlocker b5(hideStartedGamesCheckBox);
|
||||
|
||||
searchBar->setText(model->getGameNameFilter());
|
||||
|
||||
hideGamesNotCreatedByBuddiesCheckBox->setChecked(model->getHideNotBuddyCreatedGames());
|
||||
hideFullGamesCheckBox->setChecked(model->getHideFullGames());
|
||||
hideStartedGamesCheckBox->setChecked(model->getHideGamesThatStarted());
|
||||
|
||||
QSet<int> types = model->getGameTypeFilter();
|
||||
if (types.size() == 1) {
|
||||
int idx = filterToFormatComboBox->findData(*types.begin());
|
||||
filterToFormatComboBox->setCurrentIndex(idx >= 0 ? idx : 0);
|
||||
} else {
|
||||
filterToFormatComboBox->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
void GameSelectorQuickFilterToolBar::applyFilters(std::function<void(bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
QString &,
|
||||
QStringList &,
|
||||
QSet<int> &,
|
||||
int &,
|
||||
int &,
|
||||
QTime &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &)> mutator)
|
||||
{
|
||||
bool hideBuddiesOnlyGames = model->getHideBuddiesOnlyGames();
|
||||
bool hideIgnoredUserGames = model->getHideIgnoredUserGames();
|
||||
bool hideFullGames = model->getHideFullGames();
|
||||
bool hideGamesThatStarted = model->getHideGamesThatStarted();
|
||||
bool hidePasswordProtectedGames = model->getHidePasswordProtectedGames();
|
||||
bool hideNotBuddyCreatedGames = model->getHideNotBuddyCreatedGames();
|
||||
bool hideOpenDecklistGames = model->getHideOpenDecklistGames();
|
||||
|
||||
QString gameNameFilter = model->getGameNameFilter();
|
||||
QStringList creatorNameFilters = model->getCreatorNameFilters();
|
||||
QSet<int> gameTypeFilter = model->getGameTypeFilter();
|
||||
|
||||
int minPlayers = model->getMaxPlayersFilterMin();
|
||||
int maxPlayers = model->getMaxPlayersFilterMax();
|
||||
QTime maxGameAge = model->getMaxGameAge();
|
||||
|
||||
bool showOnlyIfSpectatorsCanWatch = model->getShowOnlyIfSpectatorsCanWatch();
|
||||
bool showSpectatorPasswordProtected = model->getShowSpectatorPasswordProtected();
|
||||
bool showOnlyIfSpectatorsCanChat = model->getShowOnlyIfSpectatorsCanChat();
|
||||
bool showOnlyIfSpectatorsCanSeeHands = model->getShowOnlyIfSpectatorsCanSeeHands();
|
||||
|
||||
mutator(hideBuddiesOnlyGames, hideIgnoredUserGames, hideFullGames, hideGamesThatStarted, hidePasswordProtectedGames,
|
||||
hideNotBuddyCreatedGames, hideOpenDecklistGames, gameNameFilter, creatorNameFilters, gameTypeFilter,
|
||||
minPlayers, maxPlayers, maxGameAge, showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected,
|
||||
showOnlyIfSpectatorsCanChat, showOnlyIfSpectatorsCanSeeHands);
|
||||
|
||||
model->setGameFilters(hideBuddiesOnlyGames, hideIgnoredUserGames, hideFullGames, hideGamesThatStarted,
|
||||
hidePasswordProtectedGames, hideNotBuddyCreatedGames, hideOpenDecklistGames, gameNameFilter,
|
||||
creatorNameFilters, gameTypeFilter, minPlayers, maxPlayers, maxGameAge,
|
||||
showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat,
|
||||
showOnlyIfSpectatorsCanSeeHands);
|
||||
}
|
||||
|
||||
void GameSelectorQuickFilterToolBar::retranslateUi()
|
||||
{
|
||||
searchBar->setPlaceholderText(tr("Filter by game name..."));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,24 @@ public:
|
|||
TabSupervisor *tabSupervisor,
|
||||
GamesProxyModel *model,
|
||||
const QMap<int, QString> &allGameTypes);
|
||||
void syncFromModel();
|
||||
void applyFilters(std::function<void(bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
QString &,
|
||||
QStringList &,
|
||||
QSet<int> &,
|
||||
int &,
|
||||
int &,
|
||||
QTime &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &,
|
||||
bool &)> mutator);
|
||||
void retranslateUi();
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames,
|
|||
#else
|
||||
invalidateFilter();
|
||||
#endif
|
||||
emit filtersChanged();
|
||||
}
|
||||
|
||||
int GamesProxyModel::getNumFilteredGames() const
|
||||
|
|
|
|||
|
|
@ -138,6 +138,9 @@ private:
|
|||
bool showOnlyIfSpectatorsCanChat;
|
||||
bool showOnlyIfSpectatorsCanSeeHands;
|
||||
|
||||
signals:
|
||||
void filtersChanged();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a GamesProxyModel.
|
||||
|
|
|
|||
|
|
@ -4,10 +4,29 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json)
|
|||
{
|
||||
id = json.value("id").toInt();
|
||||
|
||||
categories.clear();
|
||||
|
||||
auto categoriesJson = json.value("categories").toArray();
|
||||
|
||||
for (auto category : categoriesJson) {
|
||||
categories.append(category.toString());
|
||||
for (const auto &categoryValue : categoriesJson) {
|
||||
Category cat;
|
||||
|
||||
if (categoryValue.isObject()) {
|
||||
QJsonObject obj = categoryValue.toObject();
|
||||
|
||||
cat.id = obj.value("id").toInt();
|
||||
cat.name = obj.value("name").toString();
|
||||
cat.isPremier = obj.value("isPremier").toBool();
|
||||
cat.includedInDeck = obj.value("includedInDeck").toBool();
|
||||
cat.includedInPrice = obj.value("includedInPrice").toBool();
|
||||
} else if (categoryValue.isString()) {
|
||||
cat.name = categoryValue.toString();
|
||||
|
||||
// assume mainboard unless known otherwise
|
||||
cat.includedInDeck = true;
|
||||
}
|
||||
|
||||
categories.append(cat);
|
||||
}
|
||||
|
||||
companion = json.value("companion").toBool();
|
||||
|
|
@ -27,7 +46,13 @@ void ArchidektApiResponseCardEntry::fromJson(const QJsonObject &json)
|
|||
void ArchidektApiResponseCardEntry::debugPrint() const
|
||||
{
|
||||
qDebug() << "Id:" << id;
|
||||
qDebug() << "Categories:" << categories;
|
||||
for (auto category : categories) {
|
||||
qDebug() << "Category ID:" << category.id;
|
||||
qDebug() << "Category Name:" << category.name;
|
||||
qDebug() << "Category Premier:" << category.isPremier;
|
||||
qDebug() << "Category Included in Deck:" << category.includedInDeck;
|
||||
qDebug() << "Category Included in Price:" << category.includedInPrice;
|
||||
}
|
||||
qDebug() << "Companion:" << companion;
|
||||
qDebug() << "FlippedDefault:" << flippedDefault;
|
||||
qDebug() << "Label:" << label;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@
|
|||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
struct Category
|
||||
{
|
||||
int id;
|
||||
QString name;
|
||||
bool isPremier;
|
||||
bool includedInDeck;
|
||||
bool includedInPrice;
|
||||
};
|
||||
|
||||
class ArchidektApiResponseCardEntry
|
||||
{
|
||||
public:
|
||||
|
|
@ -26,7 +35,7 @@ public:
|
|||
return card;
|
||||
};
|
||||
|
||||
QStringList getCategories() const
|
||||
QList<Category> getCategories() const
|
||||
{
|
||||
return categories;
|
||||
}
|
||||
|
|
@ -38,7 +47,7 @@ public:
|
|||
|
||||
private:
|
||||
int id;
|
||||
QStringList categories;
|
||||
QList<Category> categories;
|
||||
bool companion;
|
||||
bool flippedDefault;
|
||||
QString label;
|
||||
|
|
|
|||
|
|
@ -63,16 +63,60 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
|
|||
QString tempDeck;
|
||||
QTextStream deckStream(&tempDeck);
|
||||
|
||||
for (auto card : response.getCards()) {
|
||||
QString mainboardText;
|
||||
QString sideboardText;
|
||||
|
||||
QTextStream mainStream(&mainboardText);
|
||||
QTextStream sideStream(&sideboardText);
|
||||
|
||||
for (const auto &card : response.getCards()) {
|
||||
QString fullName = card.getCard().getOracleCard().value("name").toString();
|
||||
// We don't really care about the second card, the card database already has it as a relation
|
||||
QString cleanName = fullName.split("//").first().trimmed();
|
||||
|
||||
tempDeck += QString("%1 %2 (%3) %4\n")
|
||||
.arg(card.getQuantity())
|
||||
.arg(cleanName)
|
||||
.arg(card.getCard().getEdition().getEditionCode().toUpper())
|
||||
.arg(card.getCard().getCollectorNumber());
|
||||
QString line = QString("%1 %2 (%3) %4\n")
|
||||
.arg(card.getQuantity())
|
||||
.arg(cleanName)
|
||||
.arg(card.getCard().getEdition().getEditionCode().toUpper())
|
||||
.arg(card.getCard().getCollectorNumber());
|
||||
|
||||
bool isCommander = false;
|
||||
bool isSideboardCategory = false;
|
||||
bool includedInDeck = false;
|
||||
|
||||
for (const auto &cat : card.getCategories()) {
|
||||
|
||||
if (cat.name.compare("Commander", Qt::CaseInsensitive) == 0) {
|
||||
isCommander = true;
|
||||
}
|
||||
|
||||
if (cat.name.compare("Sideboard", Qt::CaseInsensitive) == 0 ||
|
||||
cat.name.compare("Maybeboard", Qt::CaseInsensitive) == 0) {
|
||||
isSideboardCategory = true;
|
||||
}
|
||||
|
||||
if (cat.includedInDeck) {
|
||||
includedInDeck = true;
|
||||
}
|
||||
}
|
||||
|
||||
QString target;
|
||||
|
||||
if (isCommander || isSideboardCategory) {
|
||||
sideStream << line;
|
||||
} else if (includedInDeck) {
|
||||
mainStream << line;
|
||||
} else {
|
||||
sideStream << line;
|
||||
}
|
||||
}
|
||||
|
||||
// Combine with blank line separator
|
||||
tempDeck = mainboardText;
|
||||
|
||||
if (!sideboardText.isEmpty()) {
|
||||
tempDeck += "\n";
|
||||
tempDeck += sideboardText;
|
||||
}
|
||||
|
||||
model = new DeckListModel(this);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "../../../../../general/display/background_plate_widget.h"
|
||||
#include "../../tab_edhrec_main.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
||||
EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWidget(
|
||||
|
|
@ -48,7 +49,7 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi
|
|||
if (parentTab) {
|
||||
cardPictureWidget->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value());
|
||||
connect(cardPictureWidget, &CardInfoPictureWidget::cardClicked, this,
|
||||
&EdhrecApiResponseCardDetailsDisplayWidget::actRequestPageNavigation);
|
||||
&EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent);
|
||||
connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, cardPictureWidget,
|
||||
&CardInfoPictureWidget::setScaleFactor);
|
||||
connect(this, &EdhrecApiResponseCardDetailsDisplayWidget::requestUrl, parentTab,
|
||||
|
|
@ -59,7 +60,9 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi
|
|||
void EdhrecApiResponseCardDetailsDisplayWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mousePressEvent(event);
|
||||
actRequestPageNavigation();
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
actRequestPageNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
|
|
|
|||
|
|
@ -259,6 +259,9 @@ TabGame::~TabGame()
|
|||
if (replayManager) {
|
||||
delete replayManager->replay;
|
||||
}
|
||||
for (auto &player : game->getPlayerManager()->getPlayers()) {
|
||||
player->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TabGame::updatePlayerListDockTitle()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
|
||||
#define COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
const QString visualDatabaseDisplayFilterButtonStyle = QString(R"(
|
||||
QPushButton {
|
||||
background-color: palette(button);
|
||||
color: palette(button-text);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid palette(dark);
|
||||
}
|
||||
QPushButton:checked {
|
||||
background-color: palette(highlight);
|
||||
color: palette(highlighted-text);
|
||||
border: 1px solid palette(shadow);
|
||||
}
|
||||
)");
|
||||
|
||||
#endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FILTER_BUTTON_H
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "visual_database_display_format_legality_filter_widget.h"
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
|
@ -80,8 +81,7 @@ void VisualDatabaseDisplayFormatLegalityFilterWidget::createFormatButtons()
|
|||
for (auto it = allFormatsWithCount.begin(); it != allFormatsWithCount.end(); ++it) {
|
||||
auto *button = new QPushButton(it.key(), flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
formatButtons[it.key()] = button;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "visual_database_display_main_type_filter_widget.h"
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
|
@ -75,8 +76,8 @@ void VisualDatabaseDisplayMainTypeFilterWidget::createMainTypeButtons()
|
|||
for (auto it = allMainCardTypesWithCount.begin(); it != allMainCardTypesWithCount.end(); ++it) {
|
||||
auto *button = new QPushButton(it.key(), flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
typeButtons[it.key()] = button;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "../../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h"
|
||||
#include "../../../interface/widgets/tabs/abstract_tab_deck_editor.h"
|
||||
#include "../deck_editor/deck_state_manager.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
|
|
@ -95,8 +96,8 @@ void VisualDatabaseDisplayNameFilterWidget::createNameFilter(const QString &name
|
|||
|
||||
// Create a button for the filter
|
||||
auto *button = new QPushButton(name, flowWidget);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:hover { background-color: red; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
connect(button, &QPushButton::clicked, this, [this, name]() {
|
||||
removeNameFilter(name);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
|
|
@ -101,8 +102,8 @@ void VisualDatabaseDisplaySetFilterWidget::createSetButtons()
|
|||
|
||||
auto *button = new QPushButton(longName + " (" + shortName + ")", flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
setButtons[shortName] = button;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "visual_database_display_sub_type_filter_widget.h"
|
||||
|
||||
#include "../../../filters/filter_tree_model.h"
|
||||
#include "visual_database_display_filter_button.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
|
|
@ -80,8 +81,8 @@ void VisualDatabaseDisplaySubTypeFilterWidget::createSubTypeButtons()
|
|||
for (auto it = allSubCardTypesWithCount.begin(); it != allSubCardTypesWithCount.end(); ++it) {
|
||||
auto *button = new QPushButton(it.key(), flowWidget);
|
||||
button->setCheckable(true);
|
||||
button->setStyleSheet("QPushButton { background-color: lightgray; border: 1px solid gray; padding: 5px; }"
|
||||
"QPushButton:checked { background-color: green; color: white; }");
|
||||
|
||||
button->setStyleSheet(visualDatabaseDisplayFilterButtonStyle);
|
||||
|
||||
flowWidget->addWidget(button);
|
||||
typeButtons[it.key()] = button;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -133,7 +133,7 @@ ExactCard CardDatabaseQuerier::getRandomCard() const
|
|||
ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const PrintingInfo &otherPrinting) const
|
||||
{
|
||||
// The source card does not have a printing defined, which means we can't get a card from the same set.
|
||||
if (otherPrinting == PrintingInfo()) {
|
||||
if (otherPrinting.isEmpty()) {
|
||||
return getCard({cardName});
|
||||
}
|
||||
|
||||
|
|
@ -360,4 +360,4 @@ QMap<QString, int> CardDatabaseQuerier::getAllFormatsWithCount() const
|
|||
}
|
||||
|
||||
return formatCounts;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,5 +114,6 @@ public:
|
|||
*/
|
||||
void emitPixmapUpdated() const;
|
||||
};
|
||||
Q_DECLARE_METATYPE(ExactCard)
|
||||
|
||||
#endif // EXACT_CARD_H
|
||||
|
|
|
|||
|
|
@ -54,6 +54,16 @@ public:
|
|||
return this->set == other.set && this->properties == other.properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief check if the info is empty, as if default constructed.
|
||||
*
|
||||
* @return True if both set and properties are empty, otherwise false.
|
||||
*/
|
||||
bool isEmpty() const
|
||||
{
|
||||
return set == nullptr && properties.isEmpty();
|
||||
}
|
||||
|
||||
private:
|
||||
CardSetPtr set; ///< The set this variation belongs to.
|
||||
QVariantHash properties; ///< Key-value store for variation-specific attributes.
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
#include <libcockatrice/rng/rng_abstract.h>
|
||||
#include <libcockatrice/utility/trice_limits.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
#include <ranges>
|
||||
|
||||
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
|
||||
int _playerId,
|
||||
|
|
@ -228,6 +229,37 @@ shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZ
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines whether a set of moved cards is from the bottom of the deck
|
||||
*/
|
||||
static bool shouldBeFromTheBottom(const Server_CardZone *startZone, const std::set<MoveCardStruct> &cardsToMove)
|
||||
{
|
||||
if (!startZone) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (startZone->getName() != ZoneNames::DECK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int movedCount = static_cast<int>(cardsToMove.size());
|
||||
int tailStart = startZone->getCards().size() - movedCount;
|
||||
if (tailStart <= 0) { // if the entire deck is moved it should not be considered from the bottom
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the move is a contiguous block at the end of the deck, fail fast when not
|
||||
int expectedPosition = tailStart;
|
||||
for (const auto &card : cardsToMove) {
|
||||
if (card.position != expectedPosition) {
|
||||
return false;
|
||||
}
|
||||
++expectedPosition;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
||||
Server_CardZone *startzone,
|
||||
const QList<const CardToMove *> &_cards,
|
||||
|
|
@ -244,8 +276,11 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
|||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
if (!targetzone->hasCoords() && (xCoord <= -1)) {
|
||||
xCoord = targetzone->getCards().size();
|
||||
if (!targetzone->hasCoords()) {
|
||||
yCoord = 0;
|
||||
if (xCoord <= -1) {
|
||||
xCoord = targetzone->getCards().size();
|
||||
}
|
||||
}
|
||||
|
||||
std::set<MoveCardStruct> cardsToMove;
|
||||
|
|
@ -285,164 +320,21 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
|||
bool revealTopStart = false;
|
||||
bool revealTopTarget = false;
|
||||
|
||||
for (auto cardStruct : cardsToMove) {
|
||||
Server_Card *card = cardStruct.card;
|
||||
int originalPosition = cardStruct.position;
|
||||
bool isFromBottom = shouldBeFromTheBottom(startzone, cardsToMove);
|
||||
|
||||
bool sourceBeingLookedAt;
|
||||
int position = startzone->removeCard(card, sourceBeingLookedAt);
|
||||
|
||||
// Attachment relationships can be retained when moving a card onto the opponent's table
|
||||
if (startzone->getName() != targetzone->getName()) {
|
||||
// Delete all attachment relationships
|
||||
if (card->getParentCard()) {
|
||||
card->setParentCard(nullptr);
|
||||
}
|
||||
|
||||
// Make a copy of the list because the original one gets modified during the loop
|
||||
QList<Server_Card *> attachedCards = card->getAttachedCards();
|
||||
for (auto &attachedCard : attachedCards) {
|
||||
attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard);
|
||||
}
|
||||
if (isFromBottom) {
|
||||
std::ranges::reverse_view reversedCardsToMove{cardsToMove};
|
||||
for (auto card : reversedCardsToMove) {
|
||||
processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget,
|
||||
isReversed, undoingDraw);
|
||||
}
|
||||
|
||||
if (startzone != targetzone) {
|
||||
// Delete all arrows from and to the card
|
||||
for (auto *player : game->getPlayers().values()) {
|
||||
QList<int> arrowsToDelete;
|
||||
for (Server_Arrow *arrow : player->getArrows()) {
|
||||
if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card))
|
||||
arrowsToDelete.append(arrow->getId());
|
||||
}
|
||||
for (int j : arrowsToDelete) {
|
||||
player->deleteArrow(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDestroyOnMove(card, startzone, targetzone)) {
|
||||
Event_DestroyCard event;
|
||||
event.set_zone_name(startzone->getName().toStdString());
|
||||
event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
|
||||
if (Server_Card *stashedCard = card->takeStashedCard()) {
|
||||
stashedCard->setId(newCardId());
|
||||
ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()),
|
||||
playerId);
|
||||
card->deleteLater();
|
||||
card = stashedCard;
|
||||
} else {
|
||||
card->deleteLater();
|
||||
card = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (card) {
|
||||
++xIndex;
|
||||
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
|
||||
|
||||
bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone);
|
||||
|
||||
if (targetzone->hasCoords()) {
|
||||
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
|
||||
} else {
|
||||
yCoord = 0;
|
||||
card->resetState(targetzone->getName() == ZoneNames::STACK);
|
||||
}
|
||||
|
||||
targetzone->insertCard(card, newX, yCoord);
|
||||
int targetLookedCards = targetzone->getCardsBeingLookedAt();
|
||||
bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown());
|
||||
if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) {
|
||||
if (sourceKnownToPlayer) {
|
||||
targetLookedCards += 1;
|
||||
} else {
|
||||
targetLookedCards = newX;
|
||||
}
|
||||
targetzone->setCardsBeingLookedAt(targetLookedCards);
|
||||
}
|
||||
|
||||
bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone);
|
||||
bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone);
|
||||
|
||||
int oldCardId = card->getId();
|
||||
if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) {
|
||||
card->setId(targetzone->getPlayer()->newCardId());
|
||||
}
|
||||
card->setFaceDown(faceDown);
|
||||
|
||||
Event_MoveCard eventOthers;
|
||||
eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId());
|
||||
eventOthers.set_start_zone(startzone->getName().toStdString());
|
||||
eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId());
|
||||
if (startzone != targetzone) {
|
||||
eventOthers.set_target_zone(targetzone->getName().toStdString());
|
||||
}
|
||||
eventOthers.set_y(yCoord);
|
||||
eventOthers.set_face_down(faceDown);
|
||||
|
||||
Event_MoveCard eventPrivate(eventOthers);
|
||||
if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone ||
|
||||
startzone->getType() != ServerInfo_Zone::HiddenZone) {
|
||||
eventPrivate.set_card_id(oldCardId);
|
||||
eventPrivate.set_new_card_id(card->getId());
|
||||
} else {
|
||||
eventPrivate.set_card_id(-1);
|
||||
eventPrivate.set_new_card_id(-1);
|
||||
}
|
||||
if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) {
|
||||
QString privateCardName = card->getName();
|
||||
eventPrivate.set_card_name(privateCardName.toStdString());
|
||||
eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString());
|
||||
}
|
||||
if (startzone->getType() == ServerInfo_Zone::HiddenZone) {
|
||||
eventPrivate.set_position(position);
|
||||
} else {
|
||||
eventPrivate.set_position(-1);
|
||||
}
|
||||
|
||||
eventPrivate.set_x(newX);
|
||||
|
||||
if (
|
||||
// cards from public zones have their id known, their previous position is already known, the event does
|
||||
// not accomodate for previous locations in zones with coordinates (which are always public)
|
||||
(startzone->getType() != ServerInfo_Zone::PublicZone) &&
|
||||
// other players are not allowed to be able to track which card is which in private zones like the hand
|
||||
(startzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
||||
eventOthers.set_position(position);
|
||||
}
|
||||
if (
|
||||
// other players are not allowed to be able to track which card is which in private zones like the hand
|
||||
(targetzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
||||
eventOthers.set_x(newX);
|
||||
}
|
||||
|
||||
if ((startzone->getType() == ServerInfo_Zone::PublicZone) ||
|
||||
(targetzone->getType() == ServerInfo_Zone::PublicZone)) {
|
||||
eventOthers.set_card_id(oldCardId);
|
||||
if (!(sourceHiddenToOthers && targetHiddenToOthers)) {
|
||||
QString publicCardName = card->getName();
|
||||
eventOthers.set_card_name(publicCardName.toStdString());
|
||||
eventOthers.set_new_card_provider_id(card->getProviderId().toStdString());
|
||||
}
|
||||
eventOthers.set_new_card_id(card->getId());
|
||||
}
|
||||
|
||||
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
|
||||
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
|
||||
|
||||
if (originalPosition == 0) {
|
||||
revealTopStart = true;
|
||||
}
|
||||
if (newX == 0) {
|
||||
revealTopTarget = true;
|
||||
}
|
||||
|
||||
// handle side effects for this card
|
||||
onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw);
|
||||
} else {
|
||||
for (auto card : cardsToMove) {
|
||||
processMoveCard(ges, startzone, targetzone, card, xCoord, yCoord, xIndex, revealTopStart, revealTopTarget,
|
||||
isReversed, undoingDraw);
|
||||
}
|
||||
}
|
||||
|
||||
if (revealTopStart) {
|
||||
revealTopCardIfNeeded(startzone, ges);
|
||||
}
|
||||
|
|
@ -462,6 +354,174 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
|
|||
return Response::RespOk;
|
||||
}
|
||||
|
||||
void Server_AbstractPlayer::processMoveCard(GameEventStorage &ges,
|
||||
Server_CardZone *startzone,
|
||||
Server_CardZone *targetzone,
|
||||
MoveCardStruct cardStruct,
|
||||
int xCoord,
|
||||
int yCoord,
|
||||
int &xIndex,
|
||||
bool &revealTopStart,
|
||||
bool &revealTopTarget,
|
||||
bool isReversed,
|
||||
bool undoingDraw)
|
||||
{
|
||||
Server_Card *card = cardStruct.card;
|
||||
int originalPosition = cardStruct.position;
|
||||
|
||||
bool sourceBeingLookedAt;
|
||||
int position = startzone->removeCard(card, sourceBeingLookedAt);
|
||||
|
||||
// Attachment relationships can be retained when moving a card onto the opponent's table
|
||||
if (startzone->getName() != targetzone->getName()) {
|
||||
// Delete all attachment relationships
|
||||
if (card->getParentCard()) {
|
||||
card->setParentCard(nullptr);
|
||||
}
|
||||
|
||||
// Make a copy of the list because the original one gets modified during the loop
|
||||
QList<Server_Card *> attachedCards = card->getAttachedCards();
|
||||
for (auto &attachedCard : attachedCards) {
|
||||
attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard);
|
||||
}
|
||||
}
|
||||
|
||||
if (startzone != targetzone) {
|
||||
// Delete all arrows from and to the card
|
||||
for (auto *player : game->getPlayers().values()) {
|
||||
QList<int> arrowsToDelete;
|
||||
for (Server_Arrow *arrow : player->getArrows()) {
|
||||
if ((arrow->getStartCard() == card) || (arrow->getTargetItem() == card))
|
||||
arrowsToDelete.append(arrow->getId());
|
||||
}
|
||||
for (int j : arrowsToDelete) {
|
||||
player->deleteArrow(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldDestroyOnMove(card, startzone, targetzone)) {
|
||||
Event_DestroyCard event;
|
||||
event.set_zone_name(startzone->getName().toStdString());
|
||||
event.set_card_id(static_cast<google::protobuf::uint32>(card->getId()));
|
||||
ges.enqueueGameEvent(event, playerId);
|
||||
|
||||
if (Server_Card *stashedCard = card->takeStashedCard()) {
|
||||
stashedCard->setId(newCardId());
|
||||
ges.enqueueGameEvent(makeCreateTokenEvent(startzone, stashedCard, card->getX(), card->getY()), playerId);
|
||||
card->deleteLater();
|
||||
card = stashedCard;
|
||||
} else {
|
||||
card->deleteLater();
|
||||
card = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (card) {
|
||||
++xIndex;
|
||||
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
|
||||
|
||||
bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone);
|
||||
|
||||
if (targetzone->hasCoords()) {
|
||||
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
|
||||
} else {
|
||||
card->resetState(targetzone->getName() == ZoneNames::STACK);
|
||||
}
|
||||
|
||||
targetzone->insertCard(card, newX, yCoord);
|
||||
int targetLookedCards = targetzone->getCardsBeingLookedAt();
|
||||
bool sourceKnownToPlayer = isReversed || (sourceBeingLookedAt && !card->getFaceDown());
|
||||
if (targetzone->getType() == ServerInfo_Zone::HiddenZone && targetLookedCards >= newX) {
|
||||
if (sourceKnownToPlayer) {
|
||||
targetLookedCards += 1;
|
||||
} else {
|
||||
targetLookedCards = newX;
|
||||
}
|
||||
targetzone->setCardsBeingLookedAt(targetLookedCards);
|
||||
}
|
||||
|
||||
bool targetHiddenToOthers = faceDown || (targetzone->getType() != ServerInfo_Zone::PublicZone);
|
||||
bool sourceHiddenToOthers = card->getFaceDown() || (startzone->getType() != ServerInfo_Zone::PublicZone);
|
||||
|
||||
int oldCardId = card->getId();
|
||||
if ((faceDown && (startzone != targetzone)) || (targetzone->getPlayer() != startzone->getPlayer())) {
|
||||
card->setId(targetzone->getPlayer()->newCardId());
|
||||
}
|
||||
card->setFaceDown(faceDown);
|
||||
|
||||
Event_MoveCard eventOthers;
|
||||
eventOthers.set_start_player_id(startzone->getPlayer()->getPlayerId());
|
||||
eventOthers.set_start_zone(startzone->getName().toStdString());
|
||||
eventOthers.set_target_player_id(targetzone->getPlayer()->getPlayerId());
|
||||
if (startzone != targetzone) {
|
||||
eventOthers.set_target_zone(targetzone->getName().toStdString());
|
||||
}
|
||||
eventOthers.set_y(yCoord);
|
||||
eventOthers.set_face_down(faceDown);
|
||||
|
||||
Event_MoveCard eventPrivate(eventOthers);
|
||||
if (sourceBeingLookedAt || targetzone->getType() != ServerInfo_Zone::HiddenZone ||
|
||||
startzone->getType() != ServerInfo_Zone::HiddenZone) {
|
||||
eventPrivate.set_card_id(oldCardId);
|
||||
eventPrivate.set_new_card_id(card->getId());
|
||||
} else {
|
||||
eventPrivate.set_card_id(-1);
|
||||
eventPrivate.set_new_card_id(-1);
|
||||
}
|
||||
if (sourceKnownToPlayer || !(faceDown || targetzone->getType() == ServerInfo_Zone::HiddenZone)) {
|
||||
QString privateCardName = card->getName();
|
||||
eventPrivate.set_card_name(privateCardName.toStdString());
|
||||
eventPrivate.set_new_card_provider_id(card->getProviderId().toStdString());
|
||||
}
|
||||
if (startzone->getType() == ServerInfo_Zone::HiddenZone) {
|
||||
eventPrivate.set_position(position);
|
||||
} else {
|
||||
eventPrivate.set_position(-1);
|
||||
}
|
||||
|
||||
eventPrivate.set_x(newX);
|
||||
|
||||
if (
|
||||
// cards from public zones have their id known, their previous position is already known, the event does
|
||||
// not accomodate for previous locations in zones with coordinates (which are always public)
|
||||
(startzone->getType() != ServerInfo_Zone::PublicZone) &&
|
||||
// other players are not allowed to be able to track which card is which in private zones like the hand
|
||||
(startzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
||||
eventOthers.set_position(position);
|
||||
}
|
||||
if (
|
||||
// other players are not allowed to be able to track which card is which in private zones like the hand
|
||||
(targetzone->getType() != ServerInfo_Zone::PrivateZone)) {
|
||||
eventOthers.set_x(newX);
|
||||
}
|
||||
|
||||
if ((startzone->getType() == ServerInfo_Zone::PublicZone) ||
|
||||
(targetzone->getType() == ServerInfo_Zone::PublicZone)) {
|
||||
eventOthers.set_card_id(oldCardId);
|
||||
if (!(sourceHiddenToOthers && targetHiddenToOthers)) {
|
||||
QString publicCardName = card->getName();
|
||||
eventOthers.set_card_name(publicCardName.toStdString());
|
||||
eventOthers.set_new_card_provider_id(card->getProviderId().toStdString());
|
||||
}
|
||||
eventOthers.set_new_card_id(card->getId());
|
||||
}
|
||||
|
||||
ges.enqueueGameEvent(eventPrivate, playerId, GameEventStorageItem::SendToPrivate, playerId);
|
||||
ges.enqueueGameEvent(eventOthers, playerId, GameEventStorageItem::SendToOthers);
|
||||
|
||||
if (originalPosition == 0) {
|
||||
revealTopStart = true;
|
||||
}
|
||||
if (newX == 0) {
|
||||
revealTopTarget = true;
|
||||
}
|
||||
|
||||
// handle side effects for this card
|
||||
onCardBeingMoved(ges, cardStruct, startzone, targetzone, undoingDraw);
|
||||
}
|
||||
}
|
||||
|
||||
void Server_AbstractPlayer::onCardBeingMoved(GameEventStorage &ges,
|
||||
const MoveCardStruct &cardStruct,
|
||||
Server_CardZone *startzone,
|
||||
|
|
|
|||
|
|
@ -93,6 +93,19 @@ public:
|
|||
bool fixFreeSpaces = true,
|
||||
bool undoingDraw = false,
|
||||
bool isReversed = false);
|
||||
|
||||
void processMoveCard(GameEventStorage &ges,
|
||||
Server_CardZone *startzone,
|
||||
Server_CardZone *targetzone,
|
||||
MoveCardStruct cardStruct,
|
||||
int xCoord,
|
||||
int yCoord,
|
||||
int &xIndex,
|
||||
bool &revealTopStart,
|
||||
bool &revealTopTarget,
|
||||
bool isReversed,
|
||||
bool undoingDraw);
|
||||
|
||||
virtual void onCardBeingMoved(GameEventStorage &ges,
|
||||
const MoveCardStruct &cardStruct,
|
||||
Server_CardZone *startzone,
|
||||
|
|
|
|||
|
|
@ -360,14 +360,12 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList
|
|||
}
|
||||
|
||||
// split cards are considered a single card, enqueue for later merging
|
||||
if (layout == "split" || layout == "aftermath" || layout == "adventure") {
|
||||
if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") {
|
||||
auto _faceName = getStringPropertyFromMap(card, "faceName");
|
||||
SplitCardPart split(_faceName, text, properties, printingInfo);
|
||||
auto found_iter = splitCards.find(name + numProperty);
|
||||
if (found_iter == splitCards.end()) {
|
||||
splitCards.insert(name + numProperty, {{split}, name});
|
||||
} else if (layout == "adventure") {
|
||||
found_iter->first.insert(0, split);
|
||||
} else {
|
||||
found_iter->first.append(split);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const QMap<QString, CardSet::Priority> setTypePriorities{
|
|||
{"archenemy", CardSet::PriorityReprint},
|
||||
{"arsenal", CardSet::PriorityReprint},
|
||||
{"box", CardSet::PriorityReprint},
|
||||
{"eternal", CardSet::PriorityReprint},
|
||||
{"from_the_vault", CardSet::PriorityReprint},
|
||||
{"masterpiece", CardSet::PriorityReprint},
|
||||
{"masters", CardSet::PriorityReprint},
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Platzhalter Edition mit Spielsteinen</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle Importer</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Εικονικό σετ που περιέχει tokens</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Εισαγωγέας Oracle</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Dummy set containing tokens</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle Importer</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Set dedicado para tokens</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Importador de Oracle</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Nukk-komplekt mis sisaldab märgistusi</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle sissetooja</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Tokeneja sisältävä mallisetti</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle-lataaja</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Fausse édition contenant les jetons</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Importateur Oracle</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ e pedine che verranno usate da Cockatrice.</translation>
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Set finto contenente i token</translation>
|
||||
</message>
|
||||
|
|
@ -285,7 +285,7 @@ e pedine che verranno usate da Cockatrice.</translation>
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle Importer</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>ダミーセットを含むトークン</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle Importer - オラクル・インポーター</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>토큰 정보가 들어있는 더미 확장판</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>오라클</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Dummy sett som inneholder tokens</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle importerer</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Token voorbeeldset</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle importer</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Dodatek-atrapa, zawierający tokeny.</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle – kreator importu</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Set básico contendo fichas</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Importar Oracle</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Esta expansão contém fichas.</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Importador Oracle</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>Пример сета с фишками</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Импортер Oracle</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation type="unfinished"/>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation type="unfinished"/>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation type="unfinished"/>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation type="unfinished"/>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>包含衍生物的虚拟牌組</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle導入器</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@
|
|||
<context>
|
||||
<name>OracleImporter</name>
|
||||
<message>
|
||||
<location filename="src/oracleimporter.cpp" line="541"/>
|
||||
<location filename="src/oracleimporter.cpp" line="540"/>
|
||||
<source>Dummy set containing tokens</source>
|
||||
<translation>包含衍生物的虚拟牌组</translation>
|
||||
</message>
|
||||
|
|
@ -284,7 +284,7 @@
|
|||
<context>
|
||||
<name>OracleWizard</name>
|
||||
<message>
|
||||
<location filename="src/oraclewizard.cpp" line="70"/>
|
||||
<location filename="src/oraclewizard.cpp" line="97"/>
|
||||
<source>Oracle Importer</source>
|
||||
<translation>Oracle导入器</translation>
|
||||
</message>
|
||||
|
|
|
|||
|
|
@ -65,4 +65,5 @@ target_link_libraries(
|
|||
add_subdirectory(card_zone_algorithms)
|
||||
add_subdirectory(carddatabase)
|
||||
add_subdirectory(loading_from_clipboard)
|
||||
add_subdirectory(movecard_tests)
|
||||
add_subdirectory(oracle)
|
||||
|
|
|
|||
16
tests/movecard_tests/CMakeLists.txt
Executable file
16
tests/movecard_tests/CMakeLists.txt
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
add_executable(reverse_card_move_test reverse_card_move_test.cpp)
|
||||
|
||||
if(NOT GTEST_FOUND)
|
||||
add_dependencies(reverse_card_move_test gtest)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
reverse_card_move_test
|
||||
PRIVATE libcockatrice_network_server_remote
|
||||
PRIVATE libcockatrice_rng
|
||||
PRIVATE Threads::Threads
|
||||
PRIVATE ${GTEST_BOTH_LIBRARIES}
|
||||
PRIVATE ${TEST_QT_MODULES}
|
||||
)
|
||||
|
||||
add_test(NAME reverse_card_move_test COMMAND reverse_card_move_test)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue