Merge branch 'master' into tooomm-ci_sign_mac

This commit is contained in:
tooomm 2026-06-08 20:36:56 +02:00 committed by GitHub
commit 0685e1614f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 483 additions and 414 deletions

View file

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

View file

@ -1,10 +1,10 @@
name: Build Desktop name: Build Desktop
permissions: permissions:
actions: write # needed to delete entries in GHA cache (update ccache)
attestations: write # needed to persist the attestation.
contents: write contents: write
id-token: write id-token: write # needed for signing certificate in attestation
attestations: write
actions: write # needed for ccache action to be able to delete gha caches
on: on:
push: push:
@ -19,7 +19,7 @@ on:
- '.github/workflows/desktop-build.yml' - '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt' - 'CMakeLists.txt'
- 'vcpkg.json' - 'vcpkg.json'
- 'vcpkg' - 'vcpkg' # needed to match submodule bumps (gitlink)
tags: tags:
- '*' - '*'
pull_request: pull_request:
@ -32,7 +32,7 @@ on:
- '.github/workflows/desktop-build.yml' - '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt' - 'CMakeLists.txt'
- 'vcpkg.json' - 'vcpkg.json'
- 'vcpkg' - 'vcpkg' # needed to match submodule bumps (gitlink)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release) # Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency: concurrency:
@ -44,11 +44,11 @@ jobs:
name: Configure name: Configure
runs-on: ubuntu-slim runs-on: ubuntu-slim
outputs: outputs:
tag: ${{steps.configure.outputs.tag}} tag: ${{ steps.configure.outputs.tag }}
sha: ${{steps.configure.outputs.sha}} sha: ${{ steps.configure.outputs.sha }}
steps: steps:
- name: Configure - name: "Configure"
id: configure id: configure
shell: bash shell: bash
run: | run: |
@ -64,146 +64,150 @@ jobs:
fi fi
echo "sha=$sha" >>"$GITHUB_OUTPUT" echo "sha=$sha" >>"$GITHUB_OUTPUT"
- name: Checkout - name: "Checkout"
if: steps.configure.outputs.tag != null if: steps.configure.outputs.tag != null
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0 # fetch all history for all branches and tags
- name: Prepare release parameters - name: "Prepare release parameters"
id: prepare id: prepare
if: steps.configure.outputs.tag != null if: steps.configure.outputs.tag != null
shell: bash shell: bash
env: env:
TAG: ${{steps.configure.outputs.tag}} TAG: ${{ steps.configure.outputs.tag }}
run: .ci/prep_release.sh run: .ci/prep_release.sh
- name: Create release - name: "Create release"
if: steps.configure.outputs.tag != null if: steps.configure.outputs.tag != null
id: create_release id: create_release
shell: bash shell: bash
env: env:
GH_TOKEN: ${{github.token}} GH_TOKEN: ${{ github.token }}
tag_name: ${{steps.configure.outputs.tag}} tag_name: ${{ steps.configure.outputs.tag }}
target: ${{steps.configure.outputs.sha}} target: ${{ steps.configure.outputs.sha }}
release_name: ${{steps.prepare.outputs.title}} release_name: ${{ steps.prepare.outputs.title }}
body_path: ${{steps.prepare.outputs.body_path}} body_path: ${{ steps.prepare.outputs.body_path }}
prerelease: ${{steps.prepare.outputs.is_beta}} prerelease: ${{ steps.prepare.outputs.is_beta }}
run: | run: |
if [[ $prerelease == yes ]]; then args=()
args="--prerelease" [[ $prerelease == yes ]] && args+=(--prerelease)
fi
gh release create "$tag_name" --draft --verify-tag $args \ gh release create "$tag_name" --verify-tag --draft "${args[@]}" \
--target "$target" --title "$release_name" \ --target "$target" \
--notes-file "$body_path" --title "$release_name" \
--notes-file "$body_path"
build-linux: build-linux:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# These names correspond to the files in ".ci/$distro$version" # The files in ".ci/$distro$version" correspond to the values given here
include: include:
- distro: Arch - distro: Arch
package: skip # We are packaged in Arch already
allow-failure: yes allow-failure: yes
package: skip # We are packaged in Arch already
- distro: Servatrice_Debian - distro: Servatrice_Debian
version: 12 version: 12
package: DEB package: DEB
test: skip
server_only: yes server_only: yes
test: skip
- distro: Debian - distro: Debian
version: 12 version: 12
package: DEB package: DEB
test: skip # Running tests on all distros is superfluous test: skip # Running tests on all distros is superfluous
- distro: Debian - distro: Debian
version: 13 version: 13
package: DEB package: DEB
- distro: Fedora - distro: Fedora
version: 43 version: 43
package: RPM package: RPM
test: skip # Running tests on all distros is superfluous test: skip # Running tests on all distros is superfluous
- distro: Fedora - distro: Fedora
version: 44 version: 44
package: RPM package: RPM
- distro: Ubuntu - distro: Ubuntu
version: 24.04 version: 24.04
package: DEB package: DEB
test: skip # Running tests on all distros is superfluous test: skip # Running tests on all distros is superfluous
- distro: Ubuntu - distro: Ubuntu
version: 26.04 version: 26.04
package: DEB package: DEB
name: ${{matrix.distro}} ${{matrix.version}} name: ${{ matrix.distro }} ${{ matrix.version }}
needs: configure needs: configure
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: ${{matrix.allow-failure == 'yes'}} continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70 timeout-minutes: 70
env: env:
NAME: ${{matrix.distro}}${{matrix.version}} CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache
# Cache size over the entire repo is 10Gi:
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
CCACHE_EVICTION_AGE: 7d CCACHE_EVICTION_AGE: 7d
CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja' CMAKE_GENERATOR: 'Ninja'
NAME: ${{ matrix.distro }}${{ matrix.version }}
steps: steps:
- name: Checkout - name: "Checkout"
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Restore compiler cache (ccache) - name: "Restore compiler cache (ccache)"
id: ccache_restore id: ccache_restore
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
env: env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with: with:
path: ${{env.CACHE}} key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }}
key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}} path: ${{ env.CACHE }}
restore-keys: ccache-${{matrix.distro}}${{matrix.version}}- restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
- name: Build ${{matrix.distro}} ${{matrix.version}} Docker image - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash shell: bash
run: source .ci/docker.sh --build run: source .ci/docker.sh --build
- name: Build debug and test - name: "Build debug and test"
if: matrix.test != 'skip' if: matrix.test != 'skip'
shell: bash shell: bash
run: | run: |
source .ci/docker.sh source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" \ RUN --server --debug --test --ccache "$CCACHE_SIZE" \
--cmake-generator "$CMAKE_GENERATOR" --cmake-generator "$CMAKE_GENERATOR"
- name: Build release package - name: "Build release package"
id: build id: build
if: matrix.package != 'skip' if: matrix.package != 'skip'
shell: bash shell: bash
env: env:
SUFFIX: '-${{matrix.distro}}${{matrix.version}}' SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
package: '${{matrix.package}}' package: '${{ matrix.package }}'
server_only: '${{matrix.server_only}}' server_only: '${{ matrix.server_only }}'
run: | run: |
source .ci/docker.sh source .ci/docker.sh
args=() args=()
if [[ $server_only == yes ]]; then [[ $server_only == yes ]] && args+=(--no-client)
args+=(--no-client) [[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
fi
if [[ $GITHUB_REF == "refs/heads/master" ]]; then
args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
fi
args+=(--ccache "$CCACHE_SIZE") args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR") args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX") args+=(--suffix "$SUFFIX")
RUN --server --release --package "$package" "${args[@]}" 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) - name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true continue-on-error: true
env: env:
@ -213,47 +217,47 @@ jobs:
echo "Cache deleted successfully" echo "Cache deleted successfully"
fi fi
- name: Save updated compiler cache (ccache) - name: "Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v5 uses: actions/cache/save@v5
with: with:
path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }} key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CACHE }}
- name: Upload artifact - name: "Upload artifact"
id: upload_artifact id: upload_artifact
if: matrix.package != 'skip' if: matrix.package != 'skip'
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
path: ${{steps.build.outputs.path}}
archive: false archive: false
if-no-files-found: error if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: Upload to release - name: "Upload to release"
id: upload_release id: upload_release
if: matrix.package != 'skip' && needs.configure.outputs.tag != null if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash shell: bash
env: env:
GH_TOKEN: ${{github.token}} asset_name: ${{ steps.build.outputs.fullname }}
tag_name: ${{needs.configure.outputs.tag}} asset_path: ${{ steps.build.outputs.path }}
asset_name: ${{steps.build.outputs.fullname}} GH_TOKEN: ${{ github.token }}
asset_path: ${{steps.build.outputs.path}} tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name" run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance - name: "Attest binary provenance"
id: attestation id: attestation
if: steps.upload_release.outcome == 'success' if: steps.upload_release.outcome == 'success'
uses: actions/attest@v4 uses: actions/attest@v4
with: with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: Verify binary attestation - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success' if: steps.attestation.outcome == 'success'
shell: bash shell: bash
env: env:
GH_TOKEN: ${{github.token}} GH_TOKEN: ${{ github.token }}
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice run: gh attestation verify "${{ steps.build.outputs.path }}" --repo Cockatrice/Cockatrice
build-vcpkg: build-vcpkg:
strategy: strategy:
@ -263,196 +267,198 @@ jobs:
- os: macOS - os: macOS
target: 13 target: 13
runner: macos-15-intel runner: macos-15-intel
soc: Intel
xcode: "16.4" ccache_eviction_age: 7d
type: Release cmake_generator: Ninja
override_target: 13
make_package: 1 make_package: 1
override_target: 13
package_suffix: "-macOS13_Intel" package_suffix: "-macOS13_Intel"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Intel
type: Release
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "16.4"
- os: macOS - os: macOS
target: 14 target: 14
runner: macos-14 runner: macos-14
soc: Apple
xcode: "15.4" ccache_eviction_age: 7d
type: Release cmake_generator: Ninja
make_package: 1 make_package: 1
package_suffix: "-macOS14" package_suffix: "-macOS14"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Apple
type: Release
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "15.4"
- os: macOS - os: macOS
target: 15 target: 15
runner: macos-15 runner: macos-15
soc: Apple
xcode: "16.4" ccache_eviction_age: 7d
type: Release cmake_generator: Ninja
make_package: 1 make_package: 1
package_suffix: "-macOS15" package_suffix: "-macOS15"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Apple
type: Release
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "16.4"
- os: macOS - os: macOS
target: 15 target: 15
runner: macos-15 runner: macos-15
soc: Apple
xcode: "16.4" ccache_eviction_age: 7d
type: Debug cmake_generator: Ninja
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: clang_64 qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: Ninja soc: Apple
type: Debug
use_ccache: 1 use_ccache: 1
ccache_eviction_age: 7d xcode: "16.4"
- os: Windows - os: Windows
target: 10 target: 10
runner: windows-2025 runner: windows-2025
type: Release
cmake_generator: "Visual Studio 17 2022"
cmake_generator_platform: x64
make_package: 1 make_package: 1
package_suffix: "-Win10" package_suffix: "-Win10"
qt_version: 6.11.0 qt_version: 6.11.0
qt_arch: win64_msvc2022_64 qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets qt_modules: qtimageformats qtmultimedia qtwebsockets
cmake_generator: "Visual Studio 17 2022" type: Release
cmake_generator_platform: x64
name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure needs: configure
runs-on: ${{matrix.runner}} runs-on: ${{ matrix.runner }}
timeout-minutes: 100 timeout-minutes: 100
env: env:
CCACHE_DIR: ${{github.workspace}}/.cache/ CCACHE_DIR: ${{ github.workspace }}/.cache/
# Cache size over the entire repo is 10Gi: CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CCACHE_SIZE: 550M
steps: steps:
- name: Checkout - name: "Checkout"
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Add msbuild to PATH - name: "[Windows] Add msbuild to PATH"
if: matrix.os == 'Windows' if: matrix.os == 'Windows'
id: add-msbuild id: add-msbuild
uses: microsoft/setup-msbuild@v3 uses: microsoft/setup-msbuild@v3
with: with:
msbuild-architecture: x64 msbuild-architecture: x64
- name: Setup ccache - name: "[macOS] Setup ccache"
if: matrix.use_ccache == 1 && matrix.os == 'macOS' if: matrix.os == 'macOS' && matrix.use_ccache == 1
run: brew install ccache run: brew install ccache
- name: Restore compiler cache (ccache) - name: "[macOS] Restore compiler cache (ccache)"
if: matrix.use_ccache == 1 if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: ccache_restore id: ccache_restore
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
env: env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with: with:
path: ${{env.CCACHE_DIR}} key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}} path: ${{ env.CCACHE_DIR }}
restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}- restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
- name: Install aqtinstall - name: "Install aqtinstall"
run: pipx install aqtinstall run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases # Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- name: Resolve latest Qt patch version - name: "Resolve latest Qt patch version"
id: resolve_qt_version id: resolve_qt_version
shell: bash shell: bash
run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}" run: .ci/resolve_latest_aqt_qt_version.sh "${{ matrix.qt_version }}"
- name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS) - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
if: matrix.os == 'macOS' if: matrix.os == 'macOS'
id: restore_qt id: restore_qt
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
with: with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew # Using jurplel/install-qt-action to install Qt without using brew
# qt build using vcpkg either just fails or takes too long to build # Qt build using vcpkg either just fails or takes too long to build
- name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS) - name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
version: ${{ steps.resolve_qt_version.outputs.version }} arch: ${{ matrix.qt_arch }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: false cache: false
dir: ${{github.workspace}} dir: ${{ github.workspace }}
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
- name: Thin Qt libraries (${{ matrix.soc }} macOS) - name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh run: .ci/thin_macos_qtlib.sh
- name: Cache thin Qt libraries (${{ matrix.soc }} macOS) - name: "[macOS] Cache thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true' if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: actions/cache/save@v5 uses: actions/cache/save@v5
with: with:
path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }} key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
path: ${{ github.workspace }}/Qt
- name: Install Qt ${{matrix.qt_version}} (Windows) - name: "[Windows] Install Qt ${{ matrix.qt_version }}"
if: matrix.os == 'Windows' if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
# qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released # 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 aqtsource: git+https://github.com/miurahr/aqtinstall.git
version: ${{ steps.resolve_qt_version.outputs.version }} arch: ${{ matrix.qt_arch }}
arch: ${{matrix.qt_arch}}
modules: ${{matrix.qt_modules}}
cache: true cache: true
modules: ${{ matrix.qt_modules }}
version: ${{ steps.resolve_qt_version.outputs.version }}
- name: Install NSIS - name: "[Windows] Install NSIS"
if: matrix.os == 'Windows' if: matrix.os == 'Windows'
shell: bash shell: bash
run: choco install nsis run: choco install nsis
- name: Setup vcpkg cache - name: "Setup vcpkg cache"
id: vcpkg-cache id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3 uses: TAServers/vcpkg-cache@v3
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
# uses environment variables, see compile.sh for more details # Uses environment variables, see compile.sh for more details
- name: Build Cockatrice - name: "Build Cockatrice"
id: build id: build
shell: bash shell: bash
env: env:
BUILDTYPE: '${{matrix.type}}' BUILDTYPE: '${{ matrix.type }}'
MAKE_PACKAGE: '${{matrix.make_package}}'
PACKAGE_SUFFIX: '${{matrix.package_suffix}}'
CMAKE_GENERATOR: ${{matrix.cmake_generator}}
CMAKE_GENERATOR_PLATFORM: ${{matrix.cmake_generator_platform}}
USE_CCACHE: ${{matrix.use_ccache}}
VCPKG_DISABLE_METRICS: 1
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
# macOS-specific environment variables, will be ignored on Windows
DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }} CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
MAKE_PACKAGE: '${{ matrix.make_package }}'
PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
TARGET_MACOS_VERSION: ${{ matrix.override_target }}
USE_CCACHE: ${{ matrix.use_ccache }}
VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
VCPKG_DISABLE_METRICS: 1
run: .ci/compile.sh --server --test --vcpkg 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) - name: "[macOS] Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true continue-on-error: true
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
@ -461,14 +467,14 @@ jobs:
echo "Cache deleted successfully" echo "Cache deleted successfully"
fi fi
- name: Save updated compiler cache (ccache) - name: "[macOS] Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
uses: actions/cache/save@v5 uses: actions/cache/save@v5
with: with:
path: ${{env.CCACHE_DIR}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }} key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
path: ${{ env.CCACHE_DIR }}
- name: Sign & notarize app bundle - name: "[macOS] Sign & notarize app bundle"
# if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null # if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
if: matrix.os == 'macOS' if: matrix.os == 'macOS'
shell: bash shell: bash
@ -482,48 +488,48 @@ jobs:
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
run: .ci/sign_macos_bundle.sh "${{ steps.build.outputs.path }}" run: .ci/sign_macos_bundle.sh "${{ steps.build.outputs.path }}"
- name: Upload artifact - name: "Upload artifact"
if: matrix.make_package if: matrix.make_package
id: upload_artifact id: upload_artifact
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
path: ${{steps.build.outputs.path}}
archive: false archive: false
if-no-files-found: error if-no-files-found: error
path: ${{ steps.build.outputs.path }}
- name: Upload PDBs (Program Databases) - name: "[Windows] Upload PDBs (Program Databases)"
if: matrix.os == 'Windows' && github.ref_type != 'tag' if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: ${{steps.build.outputs.name}}-PDBs if-no-files-found: error
name: ${{ steps.build.outputs.name }}-PDBs
path: | path: |
build/cockatrice/Release/*.pdb build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb build/servatrice/Release/*.pdb
if-no-files-found: error
- name: Upload to release - name: "Upload to release"
if: needs.configure.outputs.tag != null && matrix.make_package == '1' if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release id: upload_release
shell: bash shell: bash
env: env:
GH_TOKEN: ${{github.token}} asset_name: ${{ steps.build.outputs.fullname }}
tag_name: ${{needs.configure.outputs.tag}} asset_path: ${{ steps.build.outputs.path }}
asset_name: ${{steps.build.outputs.fullname}} GH_TOKEN: ${{ github.token }}
asset_path: ${{steps.build.outputs.path}} tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name" run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance - name: "Attest binary provenance"
if: steps.upload_release.outcome == 'success' if: steps.upload_release.outcome == 'success'
id: attestation id: attestation
uses: actions/attest@v4 uses: actions/attest@v4
with: with:
subject-path: ${{steps.build.outputs.path}}
show-summary: false show-summary: false
subject-path: ${{ steps.build.outputs.path }}
- name: Verify binary attestation - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success' if: steps.attestation.outcome == 'success'
shell: bash shell: bash
env: env:
GH_TOKEN: ${{github.token}} GH_TOKEN: ${{ github.token }}
run: gh attestation verify "${{steps.build.outputs.path}}" --repo Cockatrice/Cockatrice run: gh attestation verify "${{ steps.build.outputs.path }}" --repo Cockatrice/Cockatrice

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -56,6 +56,7 @@ set(cockatrice_SOURCES
src/filters/filter_tree_model.cpp src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp src/filters/syntax_help.cpp
src/game/abstract_game.cpp src/game/abstract_game.cpp
src/game/arrow_registry.cpp
src/game/board/abstract_card_drag_item.cpp src/game/board/abstract_card_drag_item.cpp
src/game/board/abstract_card_item.cpp src/game/board/abstract_card_item.cpp
src/game/board/abstract_counter.cpp src/game/board/abstract_counter.cpp

View file

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

View file

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

View file

@ -1,8 +1,10 @@
#include "arrow_data.h" #include "arrow_data.h"
ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow) ArrowData ArrowData::fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator)
{ {
ArrowData data; ArrowData data;
data.creatorId = creatorId;
data.isLocalCreator = isLocalCreator;
data.id = arrow.id(); data.id = arrow.id();
data.startPlayerId = arrow.start_player_id(); data.startPlayerId = arrow.start_player_id();
data.startZone = QString::fromStdString(arrow.start_zone()); data.startZone = QString::fromStdString(arrow.start_zone());

View file

@ -8,16 +8,18 @@
struct ArrowData struct ArrowData
{ {
int id; int creatorId = -1;
int startPlayerId; bool isLocalCreator = false;
QString startZone; int id = -1;
int startCardId; int startPlayerId = -1;
int targetPlayerId; QString startZone = "";
QString targetZone; // empty = targeting a player int startCardId = -1;
int targetCardId = -1; // -1 = targeting a player int targetPlayerId = -1;
QColor color; QString targetZone = "";
int targetCardId = -1;
QColor color = "";
static ArrowData fromProto(const ServerInfo_Arrow &arrow); static ArrowData fromProto(const ServerInfo_Arrow &arrow, int creatorId, bool isLocalCreator);
bool isPlayerTargeted() const bool isPlayerTargeted() const
{ {

View file

@ -21,12 +21,8 @@
#include <libcockatrice/utility/color.h> #include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h> #include <libcockatrice/utility/zone_names.h>
ArrowItem::ArrowItem(PlayerLogic *_player, ArrowItem::ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem)
int _id, : data(std::move(_data)), startItem(_startItem), targetItem(_targetItem)
ArrowTarget *_startItem,
ArrowTarget *_targetItem,
const QColor &_color)
: player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color)
{ {
setZValue(ZValues::ARROWS); setZValue(ZValues::ARROWS);
@ -52,7 +48,7 @@ ArrowItem::ArrowItem(PlayerLogic *_player,
void ArrowItem::onTargetDestroyed() void ArrowItem::onTargetDestroyed()
{ {
emit requestDeletion(id); emit requestDeletion(data->creatorId, data->id);
} }
void ArrowItem::delArrow() void ArrowItem::delArrow()
@ -130,7 +126,7 @@ void ArrowItem::updatePath(const QPointF &endPoint)
void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
{ {
QColor paintColor(color); QColor paintColor(data->color);
if (fullColor) { if (fullColor) {
paintColor.setAlpha(200); paintColor.setAlpha(200);
} else { } else {
@ -142,7 +138,7 @@ void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event) void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ {
if (!player->getPlayerInfo()->getLocal()) { if (!data->isLocalCreator) {
event->ignore(); event->ignore();
return; return;
} }
@ -156,14 +152,20 @@ void ArrowItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
event->accept(); event->accept();
if (event->button() == Qt::RightButton) { if (event->button() == Qt::RightButton) {
emit requestDeletion(id); emit requestDeletion(data->creatorId, data->id);
} }
} }
// ArrowDragItem // ArrowDragItem
ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase) ArrowDragItem::ArrowDragItem(PlayerLogic *_owner, ArrowTarget *_startItem, const QColor &_color, int _deleteInPhase)
: ArrowItem(_owner, -1, _startItem, nullptr, _color), deleteInPhase(_deleteInPhase) : ArrowItem(QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _owner->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = _color}),
_startItem,
nullptr),
player(_owner), deleteInPhase(_deleteInPhase)
{ {
} }
@ -238,7 +240,7 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
CardZoneLogic *startZone = startCard->getZone(); CardZoneLogic *startZone = startCard->getZone();
Command_CreateArrow cmd; Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(color)); cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId()); cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString()); cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId()); cmd.set_start_card_id(startCard->getId());
@ -284,7 +286,14 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
// ArrowAttachItem // ArrowAttachItem
ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem) ArrowAttachItem::ArrowAttachItem(ArrowTarget *_startItem)
: ArrowItem(_startItem->getOwner(), -1, _startItem, nullptr, Qt::green) : ArrowItem(
QSharedPointer<ArrowData>::create(ArrowData{.creatorId = _startItem->getOwner()->getPlayerInfo()->getId(),
.isLocalCreator = true,
.id = -1,
.color = Qt::green}),
_startItem,
nullptr),
player(_startItem->getOwner())
{ {
} }

View file

@ -1,20 +1,15 @@
/**
* @file arrow_item.h
* @ingroup GameGraphics
*/
//! \todo Document this file.
#ifndef ARROWITEM_H #ifndef ARROWITEM_H
#define ARROWITEM_H #define ARROWITEM_H
#include "arrow_data.h"
#include "arrow_target.h" #include "arrow_target.h"
#include <QGraphicsItem> #include <QGraphicsItem>
#include <QPointer> #include <QPointer>
#include <QSharedPointer>
class CardItem; class CardItem;
class QGraphicsSceneMouseEvent; class QGraphicsSceneMouseEvent;
class QMenu;
class PlayerLogic; class PlayerLogic;
class ArrowItem : public QObject, public QGraphicsItem class ArrowItem : public QObject, public QGraphicsItem
@ -22,25 +17,27 @@ class ArrowItem : public QObject, public QGraphicsItem
Q_OBJECT Q_OBJECT
Q_INTERFACES(QGraphicsItem) Q_INTERFACES(QGraphicsItem)
signals: signals:
void requestDeletion(int id); void requestDeletion(int creatorId, int id);
private: private:
QPainterPath path; QPainterPath path;
protected: protected:
PlayerLogic *player; QSharedPointer<const ArrowData> data;
int id;
QPointer<ArrowTarget> startItem; QPointer<ArrowTarget> startItem;
QPointer<ArrowTarget> targetItem; QPointer<ArrowTarget> targetItem;
bool targetLocked = false; bool targetLocked = false;
QColor color;
bool fullColor = true; bool fullColor = true;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
public: public:
ArrowItem(PlayerLogic *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color); ArrowItem(QSharedPointer<const ArrowData> _data, ArrowTarget *_startItem, ArrowTarget *_targetItem);
void onTargetDestroyed(); void onTargetDestroyed();
void delArrow();
void updatePath();
void updatePath(const QPointF &endPoint);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QRectF boundingRect() const override [[nodiscard]] QRectF boundingRect() const override
@ -51,17 +48,13 @@ public:
{ {
return path; return path;
} }
void updatePath();
void updatePath(const QPointF &endPoint);
[[nodiscard]] int getId() const [[nodiscard]] int getId() const
{ {
return id; return data->id;
} }
[[nodiscard]] PlayerLogic *getPlayer() const [[nodiscard]] int getCreatorId() const
{ {
return player; return data->creatorId;
} }
[[nodiscard]] ArrowTarget *getStartItem() const [[nodiscard]] ArrowTarget *getStartItem() const
{ {
@ -75,14 +68,13 @@ public:
{ {
targetLocked = _targetLocked; targetLocked = _targetLocked;
} }
void delArrow();
}; };
class ArrowDragItem : public ArrowItem class ArrowDragItem : public ArrowItem
{ {
Q_OBJECT Q_OBJECT
private: private:
PlayerLogic *player;
int deleteInPhase; int deleteInPhase;
QList<ArrowDragItem *> childArrows; QList<ArrowDragItem *> childArrows;
QMetaObject::Connection positionConnection; QMetaObject::Connection positionConnection;
@ -100,6 +92,7 @@ class ArrowAttachItem : public ArrowItem
{ {
Q_OBJECT Q_OBJECT
private: private:
PlayerLogic *player;
QList<ArrowAttachItem *> childArrows; QList<ArrowAttachItem *> childArrows;
QMetaObject::Connection positionConnection; QMetaObject::Connection positionConnection;
void attachCards(CardItem *startCard, const CardItem *targetCard); void attachCards(CardItem *startCard, const CardItem *targetCard);
@ -113,4 +106,4 @@ protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
}; };
#endif // ARROWITEM_H #endif

View file

@ -213,23 +213,24 @@ void GameEventHandler::handleChatMessageSent(const QString &chatMessage)
sendGameCommand(cmd); sendGameCommand(cmd);
} }
void GameEventHandler::handleArrowDeletion(int arrowId) void GameEventHandler::handleArrowDeletion(int creatorId, int arrowId)
{ {
Command_DeleteArrow cmd; Command_DeleteArrow cmd;
cmd.set_arrow_id(arrowId); cmd.set_arrow_id(arrowId);
auto preparedCommand = prepareGameCommand(cmd); auto preparedCommand = prepareGameCommand(cmd);
connect(preparedCommand, &PendingCommand::finished, this, connect(preparedCommand, &PendingCommand::finished, this, [creatorId, arrowId, this](const Response &response) {
[arrowId, this](const Response &response) { handleArrowDeletionFinished(response, arrowId); }); handleArrowDeletionFinished(response, creatorId, arrowId);
});
sendGameCommand(preparedCommand); sendGameCommand(preparedCommand);
} }
void GameEventHandler::handleArrowDeletionFinished(const Response &response, int arrowId) void GameEventHandler::handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId)
{ {
if (response.response_code() == Response::RespNameNotFound) { if (response.response_code() == Response::RespNameNotFound) {
emit arrowDeleted(arrowId); emit arrowDeleted(creatorId, arrowId);
} }
} }

View file

@ -60,8 +60,8 @@ public:
void handleActivePhaseChanged(int phase); void handleActivePhaseChanged(int phase);
void handleGameLeft(); void handleGameLeft();
void handleChatMessageSent(const QString &chatMessage); void handleChatMessageSent(const QString &chatMessage);
void handleArrowDeletion(int arrowId); void handleArrowDeletion(int creatorId, int arrowId);
void handleArrowDeletionFinished(const Response &response, int arrowId); void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId);
void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context);
void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
@ -113,7 +113,7 @@ signals:
void containerProcessingStarted(GameEventContext context); void containerProcessingStarted(GameEventContext context);
void setContextJudgeName(QString judgeName); void setContextJudgeName(QString judgeName);
void containerProcessingDone(); void containerProcessingDone();
void arrowDeleted(int arrowId); void arrowDeleted(int creatorId, int arrowId);
void logSpectatorSay(ServerInfo_User userInfo, QString message); void logSpectatorSay(ServerInfo_User userInfo, QString message);
void logSpectatorLeave(QString name, QString reason); void logSpectatorLeave(QString name, QString reason);
void logGameStart(); void logGameStart();

View file

@ -96,8 +96,8 @@ void GameScene::addPlayer(PlayerLogic *player)
connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::deleteArrow); connect(player, &PlayerLogic::arrowDeleted, this, &GameScene::deleteArrow);
connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::addArrow); connect(player, &PlayerLogic::arrowCreateRequested, this, &GameScene::addArrow);
connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::requestArrowDeletion); connect(player, &PlayerLogic::arrowDeleteRequested, this, &GameScene::requestArrowDeletion);
connect(player, &PlayerLogic::arrowsCleared, this, connect(player, &PlayerLogic::arrowsClearedLocally, this,
[this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayer(id); }); [this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayerLocally(id); });
connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged); connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged);
@ -367,86 +367,60 @@ void GameScene::resizeColumnsAndPlayers(const QList<qreal> &minWidthByColumn, qr
} }
} }
void GameScene::addArrow(const ArrowData &data) void GameScene::addArrow(QSharedPointer<ArrowData> data)
{ {
auto *startView = playerViews.value(data.startPlayerId); auto *startView = playerViews.value(data->startPlayerId);
auto *targetView = playerViews.value(data.targetPlayerId); auto *targetView = playerViews.value(data->targetPlayerId);
if (!startView || !targetView) { if (!startView || !targetView) {
return; return;
} }
PlayerLogic *startLogic = startView->getPlayer(); auto *startZone = startView->getPlayer()->getZones().value(data->startZone);
auto *startZone = startLogic->getZones().value(data.startZone);
if (!startZone) { if (!startZone) {
return; return;
} }
CardItem *startCard = startZone->getCard(data.startCardId); CardItem *startCard = startZone->getCard(data->startCardId);
if (!startCard) { if (!startCard) {
return; return;
} }
ArrowTarget *targetItem = nullptr; ArrowTarget *targetItem = nullptr;
if (data.isPlayerTargeted()) { if (data->isPlayerTargeted()) {
targetItem = targetView->getPlayerTarget(); targetItem = targetView->getPlayerTarget();
} else { } else {
auto *zone = targetView->getPlayer()->getZones().value(data.targetZone); if (auto *zone = targetView->getPlayer()->getZones().value(data->targetZone)) {
if (zone) { targetItem = zone->getCard(data->targetCardId);
targetItem = zone->getCard(data.targetCardId);
} }
} }
if (!targetItem) { if (!targetItem) {
return; return;
} }
auto *arrow = new ArrowItem(startView->getPlayer(), data.id, startCard, targetItem, data.color); auto *arrow = new ArrowItem(data, startCard, targetItem);
addItem(arrow); addItem(arrow);
arrowRegistry.insert(data.id, arrow); arrowRegistry.insert(data, arrow);
connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion); connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion);
} }
void GameScene::deleteArrow(int arrowId) void GameScene::deleteArrow(int playerId, int arrowId)
{ {
if (arrowRegistry.contains(arrowId)) { if (auto *arrow = arrowRegistry.take(playerId, arrowId)) {
arrowRegistry.take(arrowId)->delArrow(); arrow->delArrow();
} }
} }
void GameScene::clearArrowsForPlayer(int playerId) void GameScene::requestArrowDeletion(int playerId, int arrowId)
{ {
QList<int> toDelete; if (arrowRegistry.contains(playerId, arrowId)) {
for (auto i = arrowRegistry.cbegin(); i != arrowRegistry.cend(); ++i) { emit arrowDeletionRequested(playerId, arrowId);
auto *arrow = i.value();
if (arrow->getPlayer()->getPlayerInfo()->getId() == playerId) {
toDelete.append(i.key());
}
}
for (int arrowId : toDelete) {
arrowRegistry.take(arrowId)->delArrow();
}
}
void GameScene::requestArrowDeletion(int arrowId)
{
if (arrowRegistry.contains(arrowId)) {
emit arrowDeletionRequested(arrowId);
}
}
void GameScene::requestClearArrowsForPlayer(int playerId)
{
for (auto *arrow : arrowRegistry.values()) {
if (arrow->getPlayer()->getPlayerInfo()->getId() == playerId) {
emit requestArrowDeletion(arrow->getId());
}
} }
} }
void GameScene::onCardZoneChanged(CardItem *card, bool sameZone) void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
{ {
QList<ArrowItem *> toDelete; QList<ArrowItem *> toDelete;
for (auto *arrow : arrowRegistry.values()) { for (auto *arrow : arrowRegistry.all()) {
if (arrow->getStartItem() == card || arrow->getTargetItem() == card) { if (arrow->getStartItem() == card || arrow->getTargetItem() == card) {
if (sameZone) { if (sameZone) {
arrow->updatePath(); arrow->updatePath();
@ -456,7 +430,21 @@ void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
} }
} }
for (auto *arrow : toDelete) { for (auto *arrow : toDelete) {
deleteArrow(arrow->getId()); deleteArrow(arrow->getCreatorId(), arrow->getId());
}
}
void GameScene::clearArrowsForPlayer(int playerId)
{
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
emit requestArrowDeletion(playerId, arrowId);
}
}
void GameScene::clearArrowsForPlayerLocally(int playerId)
{
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {
arrowRegistry.take(playerId, arrowId)->delArrow();
} }
} }

View file

@ -1,6 +1,7 @@
#ifndef GAMESCENE_H #ifndef GAMESCENE_H
#define GAMESCENE_H #define GAMESCENE_H
#include "arrow_registry.h"
#include "board/arrow_data.h" #include "board/arrow_data.h"
#include "board/arrow_item.h" #include "board/arrow_item.h"
#include "zones/card_zone_logic.h" #include "zones/card_zone_logic.h"
@ -45,7 +46,7 @@ private:
PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases PhasesToolbar *phasesToolbar; ///< Toolbar showing game phases
QMap<int, PlayerGraphicsItem *> playerViews; ///< ID lookup for player graphics items QMap<int, PlayerGraphicsItem *> playerViews; ///< ID lookup for player graphics items
QList<QList<PlayerGraphicsItem *>> playersByColumn; ///< Players organized by column QList<QList<PlayerGraphicsItem *>> playersByColumn; ///< Players organized by column
QMap<int, ArrowItem *> arrowRegistry; ///< ID registry for arrow graphics items ArrowRegistry arrowRegistry; ///< ID registry for arrow graphics items
QList<ZoneViewWidget *> zoneViews; ///< Active zone view widgets QList<ZoneViewWidget *> zoneViews; ///< Active zone view widgets
QSize viewSize; ///< Current view size QSize viewSize; ///< Current view size
QPointer<CardItem> hoveredCard; ///< Currently hovered card QPointer<CardItem> hoveredCard; ///< Currently hovered card
@ -202,13 +203,13 @@ public slots:
QTransform getViewportTransform() const; QTransform getViewportTransform() const;
/// Directly modifies the scene /// Directly modifies the scene
void addArrow(const ArrowData &data); void addArrow(QSharedPointer<ArrowData> data);
void deleteArrow(int arrowId); void deleteArrow(int playerId, int arrowId);
void clearArrowsForPlayer(int playerId); void clearArrowsForPlayer(int playerId);
void clearArrowsForPlayerLocally(int playerId);
/// Queues up arrow deletion but doesn't directly modify the scene /// Queues up arrow deletion but doesn't directly modify the scene
void requestArrowDeletion(int arrowId); void requestArrowDeletion(int playerId, int arrowId);
void requestClearArrowsForPlayer(int playerId);
void onCardZoneChanged(CardItem *card, bool sameZone); void onCardZoneChanged(CardItem *card, bool sameZone);
@ -223,7 +224,7 @@ signals:
void sigStartRubberBand(const QPointF &selectionOrigin); void sigStartRubberBand(const QPointF &selectionOrigin);
void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount); void sigResizeRubberBand(const QPointF &cursorPoint, int selectedCount);
void sigStopRubberBand(); void sigStopRubberBand();
void arrowDeletionRequested(int arrowId); void arrowDeletionRequested(int creatorId, int arrowId);
}; };
#endif #endif

View file

@ -92,26 +92,24 @@ void PlayerEventHandler::eventRollDie(const Event_RollDie &event)
void PlayerEventHandler::eventCreateArrow(const Event_CreateArrow &event) void PlayerEventHandler::eventCreateArrow(const Event_CreateArrow &event)
{ {
const ArrowData data = ArrowData::fromProto(event.arrow_info()); auto data = QSharedPointer<ArrowData>::create(ArrowData::fromProto(
event.arrow_info(), player->getPlayerInfo()->getId(), player->getPlayerInfo()->getLocal()));
// Resolve names for logging
const auto &playerList = player->getGame()->getPlayerManager()->getPlayers(); const auto &playerList = player->getGame()->getPlayerManager()->getPlayers();
PlayerLogic *startPlayer = playerList.value(data.startPlayerId); PlayerLogic *startPlayer = playerList.value(data->startPlayerId);
PlayerLogic *targetPlayer = playerList.value(data.targetPlayerId); PlayerLogic *targetPlayer = playerList.value(data->targetPlayerId);
QString startCardName, targetCardName; QString startCardName, targetCardName;
if (startPlayer) { if (startPlayer) {
auto *zone = startPlayer->getZones().value(data.startZone); if (auto *zone = startPlayer->getZones().value(data->startZone)) {
if (zone) { if (auto *card = zone->getCard(data->startCardId)) {
if (auto *card = zone->getCard(data.startCardId)) {
startCardName = card->getName(); startCardName = card->getName();
} }
} }
} }
if (!data.isPlayerTargeted() && targetPlayer) { if (!data->isPlayerTargeted() && targetPlayer) {
auto *zone = targetPlayer->getZones().value(data.targetZone); if (auto *zone = targetPlayer->getZones().value(data->targetZone)) {
if (zone) { if (auto *card = zone->getCard(data->targetCardId)) {
if (auto *card = zone->getCard(data.targetCardId)) {
targetCardName = card->getName(); targetCardName = card->getName();
} }
} }
@ -119,16 +117,15 @@ void PlayerEventHandler::eventCreateArrow(const Event_CreateArrow &event)
emit player->arrowCreateRequested(data); emit player->arrowCreateRequested(data);
const bool validForLogging = !startCardName.isEmpty() && (data.isPlayerTargeted() || !targetCardName.isEmpty()); if (startPlayer && targetPlayer && !startCardName.isEmpty() &&
(data->isPlayerTargeted() || !targetCardName.isEmpty())) {
if (startPlayer && targetPlayer && validForLogging) { emit logCreateArrow(player, startPlayer, startCardName, targetPlayer, targetCardName, data->isPlayerTargeted());
emit logCreateArrow(player, startPlayer, startCardName, targetPlayer, targetCardName, data.isPlayerTargeted());
} }
} }
void PlayerEventHandler::eventDeleteArrow(const Event_DeleteArrow &event) void PlayerEventHandler::eventDeleteArrow(const Event_DeleteArrow &event)
{ {
emit player->arrowDeleted(event.arrow_id()); emit player->arrowDeleted(player->getPlayerInfo()->getId(), event.arrow_id());
} }
void PlayerEventHandler::eventCreateToken(const Event_CreateToken &event) void PlayerEventHandler::eventCreateToken(const Event_CreateToken &event)

View file

@ -74,7 +74,7 @@ PlayerLogic::~PlayerLogic()
void PlayerLogic::clear() void PlayerLogic::clear()
{ {
emit arrowsCleared(); emit arrowsClearedLocally();
QMapIterator<QString, CardZoneLogic *> i(zones); QMapIterator<QString, CardZoneLogic *> i(zones);
while (i.hasNext()) { while (i.hasNext()) {
@ -115,7 +115,7 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
/* HandZone */ /* HandZone */
ZoneNames::HAND}; ZoneNames::HAND};
clearCounters(); clearCounters();
emit arrowsCleared(); emit arrowsClearedLocally();
QMutableMapIterator<QString, CardZoneLogic *> zoneIt(zones); QMutableMapIterator<QString, CardZoneLogic *> zoneIt(zones);
while (zoneIt.hasNext()) { while (zoneIt.hasNext()) {
@ -231,7 +231,8 @@ void PlayerLogic::processCardAttachment(const ServerInfo_Player &info)
const int arrowListSize = info.arrow_list_size(); const int arrowListSize = info.arrow_list_size();
for (int i = 0; i < arrowListSize; ++i) { for (int i = 0; i < arrowListSize; ++i) {
emit arrowCreateRequested(ArrowData::fromProto(info.arrow_list(i))); emit arrowCreateRequested(QSharedPointer<ArrowData>::create(
ArrowData::fromProto(info.arrow_list(i), getPlayerInfo()->getId(), getPlayerInfo()->getLocal())));
} }
} }

View file

@ -78,10 +78,10 @@ signals:
void clearCustomZonesMenu(); void clearCustomZonesMenu();
void addViewCustomZoneActionToCustomZoneMenu(QString zoneName); void addViewCustomZoneActionToCustomZoneMenu(QString zoneName);
void resetTopCardMenuActions(); void resetTopCardMenuActions();
void arrowCreateRequested(ArrowData data); void arrowCreateRequested(QSharedPointer<ArrowData> data);
void arrowDeleteRequested(int arrowId); void arrowDeleteRequested(int creatorId, int arrowId);
void arrowDeleted(int arrowId); void arrowDeleted(int creatorId, int arrowId);
void arrowsCleared(); // fires on clear() and processPlayerInfo void arrowsClearedLocally(); // fires on clear() and processPlayerInfo
public slots: public slots:
void setActive(bool _active); void setActive(bool _active);

View file

@ -98,8 +98,7 @@ ReplayManager::ReplayManager(TabGame *parent, GameReplay *_replay)
void ReplayManager::replayNextEvent(EventProcessingOptions options) void ReplayManager::replayNextEvent(EventProcessingOptions options)
{ {
game->getGame()->getGameEventHandler()->processGameEventContainer( emit eventReplayed(replay->event_list(timelineWidget->getCurrentEvent()), options);
replay->event_list(timelineWidget->getCurrentEvent()), nullptr, options);
} }
void ReplayManager::replayFinished() void ReplayManager::replayFinished()

View file

@ -27,6 +27,7 @@ public:
signals: signals:
void requestChatAndPhaseReset(); void requestChatAndPhaseReset();
void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options);
private: private:
// Replay related members // Replay related members

View file

@ -608,7 +608,7 @@ void TabGame::actRemoveLocalArrows()
{ {
auto *local = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer()); auto *local = game->getPlayerManager()->getActiveLocalPlayer(game->getGameState()->getActivePlayer());
if (local) { if (local) {
scene->requestClearArrowsForPlayer(local->getPlayerInfo()->getId()); scene->clearArrowsForPlayer(local->getPlayerInfo()->getId());
} }
} }
@ -895,11 +895,6 @@ void TabGame::newCardAdded(AbstractCardItem *card)
connect(card, &AbstractCardItem::showCardInfoPopup, this, &TabGame::showCardInfoPopup); connect(card, &AbstractCardItem::showCardInfoPopup, this, &TabGame::showCardInfoPopup);
connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
connect(card, &AbstractCardItem::cardShiftClicked, this, &TabGame::linkCardToChat); connect(card, &AbstractCardItem::cardShiftClicked, this, &TabGame::linkCardToChat);
CardItem *cardItem = qobject_cast<CardItem *>(card);
if (cardItem) {
connect(cardItem->getState(), &CardState::zoneChanged, scene,
[this, cardItem]() { scene->onCardZoneChanged(cardItem, false); });
}
} }
QString TabGame::getTabText() const QString TabGame::getTabText() const
@ -1174,6 +1169,11 @@ void TabGame::createReplayDock(GameReplay *replay)
QDockWidget::DockWidgetMovable); QDockWidget::DockWidgetMovable);
replayDock->setWidget(replayManager); replayDock->setWidget(replayManager);
replayDock->setFloating(false); replayDock->setFloating(false);
connect(replayManager, &ReplayManager::eventReplayed, game->getGameEventHandler(),
[this](const auto &event, auto options) {
game->getGameEventHandler()->processGameEventContainer(event, nullptr, options);
});
} }
void TabGame::createDeckViewContainerWidget(bool bReplay) void TabGame::createDeckViewContainerWidget(bool bReplay)

View file

@ -3,7 +3,7 @@
# This script will run clang-format on all modified, non-3rd-party C++/Header files. # This script will run clang-format on all modified, non-3rd-party C++/Header files.
# Optionally runs cmake-format on all modified cmake files. # Optionally runs cmake-format on all modified cmake files.
# Optionally runs shellcheck on all modified shell files. # Optionally runs shellcheck on all modified shell files.
# Uses clang-format cmake-format git diff find shellcheck # Uses clang-format, cmake-format, git, diff, find and shellcheck
# Never, ever, should this receive a path with a newline in it. Don't bother proofing it for that. # Never, ever, should this receive a path with a newline in it. Don't bother proofing it for that.
set -o pipefail set -o pipefail
@ -103,11 +103,11 @@ OPTIONS:
Do not check any source files for clang-format. Do not check any source files for clang-format.
--print-version --print-version
Print the version of clang-format being used before continuing. Print the lint tool version being used before continuing.
--shell --shell
Use shellcheck to lint shell files. Not available in the default inline Use shellcheck to lint shell files.
mode. Not available in the default inline mode.
-t, --test -t, --test
Do not edit files in place. Set exit code to 1 if changes are required. Do not edit files in place. Set exit code to 1 if changes are required.

View file

@ -81,17 +81,6 @@ int Server_AbstractPlayer::newCardId()
return nextCardId++; return nextCardId++;
} }
int Server_AbstractPlayer::newArrowId() const
{
int id = 0;
for (Server_Arrow *a : arrows) {
if (a->getId() > id) {
id = a->getId();
}
}
return id + 1;
}
void Server_AbstractPlayer::setupZones() void Server_AbstractPlayer::setupZones()
{ {
nextCardId = 0; nextCardId = 0;
@ -1144,7 +1133,7 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo
Event_CreateArrow createEvent; Event_CreateArrow createEvent;
ServerInfo_Arrow *arrowInfo = createEvent.mutable_arrow_info(); ServerInfo_Arrow *arrowInfo = createEvent.mutable_arrow_info();
const int newId = player->newArrowId(); const int newId = game->generateArrowId();
arrow->setId(newId); arrow->setId(newId);
arrowInfo->set_id(newId); arrowInfo->set_id(newId);
arrowInfo->set_start_player_id(player->getPlayerId()); arrowInfo->set_start_player_id(player->getPlayerId());
@ -1267,7 +1256,8 @@ Server_AbstractPlayer::cmdCreateArrow(const Command_CreateArrow &cmd, ResponseCo
int currentPhase = game->getActivePhase(); int currentPhase = game->getActivePhase();
int deletionPhase = cmd.has_delete_in_phase() ? cmd.delete_in_phase() : currentPhase; int deletionPhase = cmd.has_delete_in_phase() ? cmd.delete_in_phase() : currentPhase;
auto arrow = new Server_Arrow(newArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase, deletionPhase); auto arrow = new Server_Arrow(game->generateArrowId(), startCard, targetItem, cmd.arrow_color(), currentPhase,
deletionPhase);
addArrow(arrow); addArrow(arrow);
Event_CreateArrow event; Event_CreateArrow event;

View file

@ -74,7 +74,6 @@ public:
} }
int newCardId(); int newCardId();
int newArrowId() const;
void addZone(Server_CardZone *zone); void addZone(Server_CardZone *zone);
void addArrow(Server_Arrow *arrow); void addArrow(Server_Arrow *arrow);

View file

@ -697,6 +697,11 @@ void Server_Game::setActivePhase(int newPhase)
sendGameEventContainer(prepareGameEvent(event, -1)); sendGameEventContainer(prepareGameEvent(event, -1));
} }
qint64 Server_Game::generateArrowId()
{
return nextArrowId++;
}
void Server_Game::removeArrows(int newPhase, bool force) void Server_Game::removeArrows(int newPhase, bool force)
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);

View file

@ -49,6 +49,7 @@ class Server_Game : public QObject
private: private:
Server_Room *room; Server_Room *room;
int nextPlayerId; int nextPlayerId;
std::atomic<qint64> nextArrowId = 1;
int hostId; int hostId;
ServerInfo_User *creatorInfo; ServerInfo_User *creatorInfo;
QMap<int, Server_AbstractParticipant *> participants; QMap<int, Server_AbstractParticipant *> participants;
@ -196,6 +197,7 @@ public:
} }
void setActivePlayer(int newPlayer); void setActivePlayer(int newPlayer);
void setActivePhase(int newPhase); void setActivePhase(int newPhase);
qint64 generateArrowId();
void removeArrows(int newPhase, bool force = false); void removeArrows(int newPhase, bool force = false);
void nextTurn(); void nextTurn();
int getSecondsElapsed() const int getSecondsElapsed() const