diff --git a/.ci/Debian11/Dockerfile b/.ci/Debian11/Dockerfile
deleted file mode 100644
index b994863bf..000000000
--- a/.ci/Debian11/Dockerfile
+++ /dev/null
@@ -1,26 +0,0 @@
-FROM debian:11
-
-RUN apt-get update && \
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
- build-essential \
- ccache \
- clang-format \
- cmake \
- file \
- g++ \
- git \
- liblzma-dev \
- libmariadb-dev-compat \
- libprotobuf-dev \
- libqt5multimedia5-plugins \
- libqt5sql5-mysql \
- libqt5svg5-dev \
- libqt5websockets5-dev \
- ninja-build \
- protobuf-compiler \
- qt5-image-formats-plugins \
- qtmultimedia5-dev \
- qttools5-dev \
- qttools5-dev-tools \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/*
diff --git a/.ci/Fedora42/Dockerfile b/.ci/Fedora44/Dockerfile
similarity index 95%
rename from .ci/Fedora42/Dockerfile
rename to .ci/Fedora44/Dockerfile
index ee4a856f2..e6c8da7f3 100644
--- a/.ci/Fedora42/Dockerfile
+++ b/.ci/Fedora44/Dockerfile
@@ -1,4 +1,4 @@
-FROM fedora:42
+FROM fedora:44
RUN dnf install -y \
ccache \
diff --git a/.ci/Servatrice_Debian11/Dockerfile b/.ci/Servatrice_Debian12/Dockerfile
similarity index 75%
rename from .ci/Servatrice_Debian11/Dockerfile
rename to .ci/Servatrice_Debian12/Dockerfile
index fadc9e0e7..21f6a036e 100644
--- a/.ci/Servatrice_Debian11/Dockerfile
+++ b/.ci/Servatrice_Debian12/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:11
+FROM debian:12
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
@@ -11,11 +11,11 @@ RUN apt-get update && \
git \
libmariadb-dev-compat \
libprotobuf-dev \
- libqt5sql5-mysql \
- libqt5websockets5-dev \
+ libqt6sql6-mysql \
ninja-build \
protobuf-compiler \
- qttools5-dev \
- qttools5-dev-tools \
+ qt6-tools-dev \
+ qt6-tools-dev-tools \
+ qt6-websockets-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
diff --git a/.ci/Ubuntu22.04/Dockerfile b/.ci/Ubuntu22.04/Dockerfile
deleted file mode 100644
index 93c8fdea9..000000000
--- a/.ci/Ubuntu22.04/Dockerfile
+++ /dev/null
@@ -1,26 +0,0 @@
-FROM ubuntu:22.04
-
-RUN apt-get update && \
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
- build-essential \
- ccache \
- clang-format \
- cmake \
- file \
- g++ \
- git \
- liblzma-dev \
- libmariadb-dev-compat \
- libprotobuf-dev \
- libqt5multimedia5-plugins \
- libqt5sql5-mysql \
- libqt5svg5-dev \
- libqt5websockets5-dev \
- ninja-build \
- protobuf-compiler \
- qt5-image-formats-plugins \
- qtmultimedia5-dev \
- qttools5-dev \
- qttools5-dev-tools \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/*
diff --git a/.ci/compile.sh b/.ci/compile.sh
index 7ebdd6e4e..ee846897b 100755
--- a/.ci/compile.sh
+++ b/.ci/compile.sh
@@ -203,7 +203,11 @@ if [[ $RUNNER_OS == macOS ]]; then
arch="x64"
fi
mkdir -p "$triplets_dir"
- cp "../vcpkg/triplets/$arch-osx.cmake" "$triplet_file"
+ triplet_source="../vcpkg/triplets/$arch-osx.cmake"
+ if [[ ! -f "$triplet_source" ]]; then
+ triplet_source="../vcpkg/triplets/community/$arch-osx.cmake"
+ fi
+ cp "$triplet_source" "$triplet_file"
echo "set(VCPKG_CMAKE_SYSTEM_VERSION $TARGET_MACOS_VERSION)" >>"$triplet_file"
echo "set(VCPKG_OSX_DEPLOYMENT_TARGET $TARGET_MACOS_VERSION)" >>"$triplet_file"
flags+=("-DVCPKG_OVERLAY_TRIPLETS=$triplets_dir")
diff --git a/.ci/lint_cpp.sh b/.ci/lint_cpp.sh
index cfb1e1f07..9786a83fc 100755
--- a/.ci/lint_cpp.sh
+++ b/.ci/lint_cpp.sh
@@ -13,17 +13,9 @@ fi
# Check formatting using format.sh
echo "Checking your code using format.sh..."
-diff="$(./format.sh --diff --cmake --shell --print-version --branch origin/master)"
+./format.sh --color-diff --cmake --shell --print-version --branch origin/master
err=$?
-sep="
-----------
-"
-used_version="${diff%%"$sep"*}"
-diff="${diff#*"$sep"}"
-changes_to_make="${diff%%"$sep"*}"
-files_to_edit="${diff#*"$sep"}"
-
case $err in
1)
cat < is the markdown comment escape sequence, emojis are way better
generated_list="${generated_list//-->/→}"
+ # Escape & to preserve it from commit message into markdown output
+ generated_list="${generated_list//&/\\&}"
body="${body//--REPLACE-WITH-GENERATED-LIST--/$generated_list}"
body="${body//--REPLACE-WITH-COMMIT-COUNT--/$count}"
body="${body//--REPLACE-WITH-PREVIOUS-RELEASE-TAG--/$previous}"
diff --git a/.ci/release_template.md b/.ci/release_template.md
index 1ce9f4bf7..ac78a193a 100644
--- a/.ci/release_template.md
+++ b/.ci/release_template.md
@@ -19,12 +19,10 @@ Available pre-compiled binaries for installation:
Linux
• Ubuntu 26.04 LTSResolute Racoon
• Ubuntu 24.04 LTSNoble Numbat
- • Ubuntu 22.04 LTSJammy Jellyfish
• Debian 13Trixie
• Debian 12Bookworm
- • Debian 11Bullseye
+ • Fedora 44
• Fedora 43
- • Fedora 42We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00.General Linux support is available via a flatpak package at Flathub!
@@ -85,7 +83,6 @@ Remove empty headers when done.
### Under the Hood
### Oracle
### Servatrice
-### Webatrice
diff --git a/.clang-format b/.clang-format
index 1ef3ca6db..1db97481a 100644
--- a/.clang-format
+++ b/.clang-format
@@ -3,7 +3,9 @@ AccessModifierOffset: -4
ColumnLimit: 120
---
Language: Cpp
-BreakBeforeBraces: Custom
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortFunctionsOnASingleLine: None
+BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: false
@@ -18,16 +20,14 @@ BraceWrapping:
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
-AllowShortFunctionsOnASingleLine: None
-BinPackParameters: false
-AllowAllParametersOfDeclarationOnNextLine: false
-IndentCaseLabels: true
-PointerAlignment: Right
-SortIncludes: true
+BreakBeforeBraces: Custom
IncludeBlocks: Regroup
+IndentCaseLabels: true
+InsertBraces: true
+PointerAlignment: Right
+RemoveSemicolon: true
+SortIncludes: true
StatementAttributeLikeMacros: [emit]
-# requires clang-format 16
-# RemoveSemicolon: true
---
Language: Proto
AllowShortFunctionsOnASingleLine: None
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index b293b3937..56ad64283 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -209,6 +209,16 @@ nowadays and clean it up for you.
Lines should be 120 characters or less. Please break up lines that are too long
into smaller parts, for example at spaces or after opening a brace.
+### Documentation Comments ###
+
+Use [Doxygen](https://www.doxygen.nl/) for code documentation:
+
+- **Doc blocks**: Use `/** @brief Description */` (Javadoc-style), not `///`
+- **Member comments**: Use trailing `///<` for inline member documentation
+- **TODOs**: Use `//! \todo Description` (Qt-style), Doxygen collects them into a Todo List
+ (uses [Qt-style comments](https://www.doxygen.nl/manual/docblocks.html) with
+ Doxygen's [\todo command](https://www.doxygen.nl/manual/commands.html#cmdtodo))
+
### Memory Management ###
New code should be written using references over pointers and stack allocation
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 88bed3663..404cac62e 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -10,7 +10,7 @@ updates:
# Look for `.gitmodules` in the `root` directory
directory: "/"
ignore:
- # Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used) & macOS Intel triplet broken with newer releases)
+ # Ignore updates for vcpkg (Bump to latest tag not working (no SemVer used)
- dependency-name: "vcpkg"
# Check for updates once a month
schedule:
@@ -39,13 +39,3 @@ updates:
interval: "weekly"
# Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
open-pull-requests-limit: 2
-
- # # Enable version updates for npm
- # - package-ecosystem: "npm"
- # # Look for `package.json` and `lock` files in the `webclient` subdirectory
- # directory: "/webclient"
- # # Check the npm registry for updates once a week
- # schedule:
- # interval: "weekly"
- # # Limit the amout of open PR's (default = 5, disabled = 0, security updates are not impacted)
- # open-pull-requests-limit: 5
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index 3c8e915a8..a6b9d5340 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -1,10 +1,10 @@
name: Build Desktop
permissions:
+ actions: write # needed to delete entries in GHA cache (update ccache)
+ attestations: write # needed to persist the attestation.
contents: write
- id-token: write
- attestations: write
- actions: write # needed for ccache action to be able to delete gha caches
+ id-token: write # needed for signing certificate in attestation
on:
push:
@@ -14,14 +14,12 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- - '!.husky/**'
- '!.tx/**'
- '!doc/**'
- - '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- - 'vcpkg'
+ - 'vcpkg' # needed to match submodule bumps (gitlink)
tags:
- '*'
pull_request:
@@ -29,14 +27,12 @@ on:
- '*/**' # matches all files not in root
- '!**.md'
- '!.github/**'
- - '!.husky/**'
- '!.tx/**'
- '!doc/**'
- - '!webclient/**'
- '.github/workflows/desktop-build.yml'
- 'CMakeLists.txt'
- 'vcpkg.json'
- - 'vcpkg'
+ - 'vcpkg' # needed to match submodule bumps (gitlink)
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
@@ -48,224 +44,217 @@ jobs:
name: Configure
runs-on: ubuntu-slim
outputs:
- tag: ${{steps.configure.outputs.tag}}
- sha: ${{steps.configure.outputs.sha}}
+ tag: ${{ steps.configure.outputs.tag }}
+ sha: ${{ steps.configure.outputs.sha }}
- steps:
- - name: Configure
+ steps:
+ - name: "Configure"
+ env:
+ RESOLVED_SHA: ${{ case(github.event_name == 'pull_request', github.event.pull_request.head.sha, github.sha) }}
id: configure
shell: bash
run: |
- tag_regex='^refs/tags/'
- if [[ $GITHUB_EVENT_NAME == pull-request ]]; then # pull request
- sha="${{github.event.pull_request.head.sha}}"
- elif [[ $GITHUB_REF =~ $tag_regex ]]; then # release
- sha="$GITHUB_SHA"
- tag="${GITHUB_REF/refs\/tags\//}"
- echo "tag=$tag" >>"$GITHUB_OUTPUT"
- else # push to branch
- sha="$GITHUB_SHA"
+ if [[ "$GITHUB_REF_TYPE" == 'tag' ]]; then # release
+ echo "tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
fi
- echo "sha=$sha" >>"$GITHUB_OUTPUT"
+ echo "sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT"
- - name: Checkout
+ - name: "Checkout"
if: steps.configure.outputs.tag != null
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
with:
- fetch-depth: 0
+ fetch-depth: 0 # fetch all history for all branches and tags
- - name: Prepare release parameters
+ - name: "Prepare release parameters"
id: prepare
if: steps.configure.outputs.tag != null
shell: bash
env:
- TAG: ${{steps.configure.outputs.tag}}
+ TAG: ${{ steps.configure.outputs.tag }}
run: .ci/prep_release.sh
- - name: Create release
+ - name: "Create release"
if: steps.configure.outputs.tag != null
id: create_release
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- tag_name: ${{steps.configure.outputs.tag}}
- target: ${{steps.configure.outputs.sha}}
- release_name: ${{steps.prepare.outputs.title}}
- body_path: ${{steps.prepare.outputs.body_path}}
- prerelease: ${{steps.prepare.outputs.is_beta}}
+ GH_TOKEN: ${{ github.token }}
+ tag_name: ${{ steps.configure.outputs.tag }}
+ target: ${{ steps.configure.outputs.sha }}
+ release_name: ${{ steps.prepare.outputs.title }}
+ body_path: ${{ steps.prepare.outputs.body_path }}
+ prerelease: ${{ steps.prepare.outputs.is_beta }}
run: |
- if [[ $prerelease == yes ]]; then
- args="--prerelease"
- fi
- gh release create "$tag_name" --draft --verify-tag $args \
- --target "$target" --title "$release_name" \
- --notes-file "$body_path"
+ args=()
+ [[ $prerelease == yes ]] && args+=(--prerelease)
+
+ gh release create "$tag_name" --verify-tag --draft "${args[@]}" \
+ --target "$target" \
+ --title "$release_name" \
+ --notes-file "$body_path"
build-linux:
strategy:
fail-fast: false
matrix:
- # These names correspond to the files in ".ci/$distro$version"
+ # The files in ".ci/$distro$version" correspond to the values given here
include:
- distro: Arch
- package: skip # We are packaged in Arch already
- allow-failure: yes
- - distro: Debian
- version: 11
- package: DEB
+ allow-failure: yes
+ package: skip # We are packaged in Arch already
- distro: Servatrice_Debian
- version: 11
+ version: 12
+
package: DEB
- test: skip
server_only: yes
+ test: skip
- distro: Debian
version: 12
+
package: DEB
test: skip # Running tests on all distros is superfluous
- distro: Debian
version: 13
- package: DEB
- - distro: Fedora
- version: 42
- package: RPM
- test: skip # Running tests on all distros is superfluous
+ package: DEB
- distro: Fedora
version: 43
+
+ package: RPM
+ test: skip # Running tests on all distros is superfluous
+
+ - distro: Fedora
+ version: 44
+
package: RPM
- distro: Ubuntu
- version: 22.04
+ version: 24.04
+
package: DEB
test: skip # Running tests on all distros is superfluous
- - distro: Ubuntu
- version: 24.04
- package: DEB
-
- distro: Ubuntu
version: 26.04
+
package: DEB
- name: ${{matrix.distro}} ${{matrix.version}}
+ name: ${{ matrix.distro }} ${{ matrix.version }}
needs: configure
runs-on: ubuntu-latest
- continue-on-error: ${{matrix.allow-failure == 'yes'}}
+ continue-on-error: ${{ matrix.allow-failure == 'yes' }}
timeout-minutes: 70
env:
- NAME: ${{matrix.distro}}${{matrix.version}}
- CACHE: ${{github.workspace}}/.cache/${{matrix.distro}}${{matrix.version}} # directory for caching docker image and ccache
- # Cache size over the entire repo is 10Gi:
- # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
- CCACHE_SIZE: 550M
+ CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache
CCACHE_EVICTION_AGE: 7d
+ CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
CMAKE_GENERATOR: 'Ninja'
+ NAME: ${{ matrix.distro }}${{ matrix.version }}
steps:
- - name: Checkout
- uses: actions/checkout@v6
+ - name: "Checkout"
+ uses: actions/checkout@v7
- - name: Restore compiler cache (ccache)
+ - name: "Restore compiler cache (ccache)"
id: ccache_restore
- uses: actions/cache/restore@v5
+ uses: actions/cache/restore@v6
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
- path: ${{env.CACHE}}
- key: ccache-${{matrix.distro}}${{matrix.version}}-${{env.BRANCH_NAME}}
- restore-keys: ccache-${{matrix.distro}}${{matrix.version}}-
+ key: ccache-${{ matrix.distro }}${{ matrix.version }}-${{ env.BRANCH_NAME }}
+ path: ${{ env.CACHE }}
+ restore-keys: ccache-${{ matrix.distro }}${{ matrix.version }}-
- - name: Build ${{matrix.distro}} ${{matrix.version}} Docker image
+ - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image"
shell: bash
run: source .ci/docker.sh --build
- - name: Build debug and test
+ - name: "Build debug and test"
if: matrix.test != 'skip'
shell: bash
run: |
source .ci/docker.sh
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
- --cmake-generator "$CMAKE_GENERATOR"
+ --cmake-generator "$CMAKE_GENERATOR"
- - name: Build release package
+ - name: "Build release package"
id: build
if: matrix.package != 'skip'
shell: bash
env:
- SUFFIX: '-${{matrix.distro}}${{matrix.version}}'
- package: '${{matrix.package}}'
- server_only: '${{matrix.server_only}}'
+ SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}'
+ package: '${{ matrix.package }}'
+ server_only: '${{ matrix.server_only }}'
run: |
source .ci/docker.sh
args=()
- if [[ $server_only == yes ]]; then
- args+=(--no-client)
- fi
- if [[ $GITHUB_REF == "refs/heads/master" ]]; then
- args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
- fi
+ [[ $server_only == yes ]] && args+=(--no-client)
+ [[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
args+=(--suffix "$SUFFIX")
+
RUN --server --release --package "$package" "${args[@]}"
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- - name: Delete remote compiler cache (ccache)
+ - name: "Delete remote compiler cache (ccache)"
if: github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
+ CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
- if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
+ if gh cache delete --repo "$GITHUB_REPOSITORY" "$CACHE_PRIMARY_KEY"; then
echo "Cache deleted successfully"
fi
- - name: Save updated compiler cache (ccache)
+ - name: "Save updated compiler cache (ccache)"
if: github.ref == 'refs/heads/master'
- uses: actions/cache/save@v5
+ uses: actions/cache/save@v6
with:
- path: ${{env.CACHE}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ path: ${{ env.CACHE }}
- - name: Upload artifact
+ - name: "Upload artifact"
id: upload_artifact
if: matrix.package != 'skip'
uses: actions/upload-artifact@v7
with:
- path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
+ path: ${{ steps.build.outputs.path }}
- - name: Upload to release
+ - name: "Upload to release"
id: upload_release
if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- tag_name: ${{needs.configure.outputs.tag}}
- asset_name: ${{steps.build.outputs.fullname}}
- asset_path: ${{steps.build.outputs.path}}
+ asset_name: ${{ steps.build.outputs.fullname }}
+ asset_path: ${{ steps.build.outputs.path }}
+ GH_TOKEN: ${{ github.token }}
+ tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- - name: Attest binary provenance
+ - name: "Attest binary provenance"
id: attestation
if: steps.upload_release.outcome == 'success'
uses: actions/attest@v4
with:
- subject-path: ${{steps.build.outputs.path}}
show-summary: false
+ subject-path: ${{ steps.build.outputs.path }}
- - name: Verify binary attestation
+ - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success'
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
+ BUILD_PATH: ${{ steps.build.outputs.path }}
+ GH_TOKEN: ${{ github.token }}
+ run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
build-vcpkg:
strategy:
@@ -275,234 +264,241 @@ jobs:
- os: macOS
target: 13
runner: macos-15-intel
- soc: Intel
- xcode: "16.4"
- type: Release
- override_target: 13
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
make_package: 1
+ override_target: 13
package_suffix: "-macOS13_Intel"
- qt_version: 6.11.*
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Intel
+ type: Release
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "16.4"
- os: macOS
target: 14
runner: macos-14
- soc: Apple
- xcode: "15.4"
- type: Release
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
make_package: 1
package_suffix: "-macOS14"
- qt_version: 6.11.*
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Apple
+ type: Release
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "15.4"
- os: macOS
target: 15
runner: macos-15
- soc: Apple
- xcode: "16.4"
- type: Release
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
make_package: 1
package_suffix: "-macOS15"
- qt_version: 6.11.*
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Apple
+ type: Release
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "16.4"
- os: macOS
target: 15
runner: macos-15
- soc: Apple
- xcode: "16.4"
- type: Debug
- qt_version: 6.11.*
+
+ ccache_eviction_age: 7d
+ cmake_generator: Ninja
+ qt_version: 6.11.0
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: Ninja
+ soc: Apple
+ type: Debug
use_ccache: 1
- ccache_eviction_age: 7d
+ xcode: "16.4"
- os: Windows
target: 10
runner: windows-2025
- type: Release
+
+ cmake_generator: "Visual Studio 18 2026"
+ cmake_generator_platform: x64
make_package: 1
package_suffix: "-Win10"
- qt_version: 6.11.*
+ qt_version: 6.11.0
qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
- cmake_generator: "Visual Studio 17 2022"
- cmake_generator_platform: x64
+ type: Release
- name: ${{matrix.os}} ${{matrix.target}}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
+ name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }}
needs: configure
- runs-on: ${{matrix.runner}}
+ runs-on: ${{ matrix.runner }}
timeout-minutes: 100
env:
- CCACHE_DIR: ${{github.workspace}}/.cache/
- # Cache size over the entire repo is 10Gi:
- # https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
- CCACHE_SIZE: 550M
+ CCACHE_DIR: ${{ github.workspace }}/.cache/
+ CCACHE_SIZE: 550M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy
steps:
- - name: Checkout
- uses: actions/checkout@v6
+ - name: "Checkout"
+ uses: actions/checkout@v7
with:
submodules: recursive
- - name: Add msbuild to PATH
+ - name: "[Windows] Add msbuild to PATH"
if: matrix.os == 'Windows'
id: add-msbuild
uses: microsoft/setup-msbuild@v3
with:
msbuild-architecture: x64
- - name: Setup ccache
- if: matrix.use_ccache == 1 && matrix.os == 'macOS'
+ - name: "[macOS] Setup ccache"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1
run: brew install ccache
- - name: Restore compiler cache (ccache)
- if: matrix.use_ccache == 1
+ - name: "[macOS] Restore compiler cache (ccache)"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1
id: ccache_restore
- uses: actions/cache/restore@v5
+ uses: actions/cache/restore@v6
env:
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
with:
- path: ${{env.CCACHE_DIR}}
- key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
- restore-keys: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-
+ key: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-${{ env.BRANCH_NAME }}
+ path: ${{ env.CCACHE_DIR }}
+ restore-keys: ccache-${{ matrix.runner }}-${{ matrix.soc }}-${{ matrix.type }}-
- - name: Install aqtinstall
+ - name: "Install aqtinstall"
run: pipx install aqtinstall
# Resolve given wildcard versions (e.g. Qt 6.6.*) to latest version via aqtinstall to avoid stale caches on new releases
- - name: Resolve latest Qt patch version
+ - name: "Resolve latest Qt patch version"
+ env:
+ QT_VERSION: ${{ matrix.qt_version }}
id: resolve_qt_version
shell: bash
- run: .ci/resolve_latest_aqt_qt_version.sh "${{matrix.qt_version}}"
+ run: .ci/resolve_latest_aqt_qt_version.sh "$QT_VERSION"
- - name: Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries (${{ matrix.soc }} macOS)
+ - name: "[macOS] Restore thin Qt ${{ steps.resolve_qt_version.outputs.version }} libraries"
if: matrix.os == 'macOS'
id: restore_qt
- uses: actions/cache/restore@v5
+ uses: actions/cache/restore@v6
with:
- path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
+ path: ${{ github.workspace }}/Qt
# Using jurplel/install-qt-action to install Qt without using brew
- # qt build using vcpkg either just fails or takes too long to build
- - name: Install fat Qt ${{ steps.resolve_qt_version.outputs.version }} (${{ matrix.soc }} macOS)
+ # Qt build using vcpkg either just fails or takes too long to build
+ - name: "[macOS] Install fat Qt ${{ steps.resolve_qt_version.outputs.version }}"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
uses: jurplel/install-qt-action@v4
with:
- version: ${{ steps.resolve_qt_version.outputs.version }}
- arch: ${{matrix.qt_arch}}
- modules: ${{matrix.qt_modules}}
+ arch: ${{ matrix.qt_arch }}
cache: false
- dir: ${{github.workspace}}
+ dir: ${{ github.workspace }}
+ modules: ${{ matrix.qt_modules }}
+ version: ${{ steps.resolve_qt_version.outputs.version }}
- - name: Thin Qt libraries (${{ matrix.soc }} macOS)
+ - name: "[macOS] Create thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
run: .ci/thin_macos_qtlib.sh
- - name: Cache thin Qt libraries (${{ matrix.soc }} macOS)
+ - name: "[macOS] Cache thin Qt libraries"
if: matrix.os == 'macOS' && steps.restore_qt.outputs.cache-hit != 'true'
- uses: actions/cache/save@v5
+ uses: actions/cache/save@v6
with:
- path: ${{ github.workspace }}/Qt
key: thin-qt-macos-${{ matrix.soc }}-${{ steps.resolve_qt_version.outputs.version }}
+ path: ${{ github.workspace }}/Qt
- - name: Install Qt ${{matrix.qt_version}} (Windows)
+ - name: "[Windows] Install Qt ${{ matrix.qt_version }}"
if: matrix.os == 'Windows'
uses: jurplel/install-qt-action@v4
with:
- # qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
+ # Qt 6.11.0 only works with aqtinstall directly from git until aqtinstall 3.4 is released
aqtsource: git+https://github.com/miurahr/aqtinstall.git
- version: ${{ steps.resolve_qt_version.outputs.version }}
- arch: ${{matrix.qt_arch}}
- modules: ${{matrix.qt_modules}}
+ arch: ${{ matrix.qt_arch }}
cache: true
+ modules: ${{ matrix.qt_modules }}
+ version: ${{ steps.resolve_qt_version.outputs.version }}
- - name: Install NSIS
+ - name: "[Windows] Install NSIS"
if: matrix.os == 'Windows'
shell: bash
run: choco install nsis
- - name: Setup vcpkg cache
+ - name: "Setup vcpkg cache"
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- # uses environment variables, see compile.sh for more details
- - name: Build Cockatrice
+ # Uses environment variables, see compile.sh for more details
+ - name: "Build Cockatrice"
id: build
shell: bash
env:
- BUILDTYPE: '${{matrix.type}}'
- MAKE_PACKAGE: '${{matrix.make_package}}'
- PACKAGE_SUFFIX: '${{matrix.package_suffix}}'
- CMAKE_GENERATOR: ${{matrix.cmake_generator}}
- CMAKE_GENERATOR_PLATFORM: ${{matrix.cmake_generator_platform}}
- USE_CCACHE: ${{matrix.use_ccache}}
- VCPKG_DISABLE_METRICS: 1
- VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
- # macOS-specific environment variables, will be ignored on Windows
- MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
- MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
- MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
- MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
- DEVELOPER_DIR: '/Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer'
- TARGET_MACOS_VERSION: ${{ matrix.override_target }}
+ BUILDTYPE: '${{ matrix.type }}'
CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }}
+ CMAKE_GENERATOR: ${{ matrix.cmake_generator }}
+ CMAKE_GENERATOR_PLATFORM: ${{ matrix.cmake_generator_platform }}
+ DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer'
+ MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
+ MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
+ MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
+ MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
+ MAKE_PACKAGE: '${{ matrix.make_package }}'
+ PACKAGE_SUFFIX: '${{ matrix.package_suffix }}'
+ TARGET_MACOS_VERSION: ${{ matrix.override_target }}
+ USE_CCACHE: ${{ matrix.use_ccache }}
+ VCPKG_BINARY_SOURCES: 'clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite'
+ VCPKG_DISABLE_METRICS: 1
run: .ci/compile.sh --server --test --vcpkg
# Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342
- - name: Delete remote compiler cache (ccache)
- if: github.ref == 'refs/heads/master' && matrix.use_ccache == 1 && steps.ccache_restore.outputs.cache-hit
+ - name: "[macOS] Delete remote compiler cache (ccache)"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master' && steps.ccache_restore.outputs.cache-hit
continue-on-error: true
env:
+ CACHE_PRIMARY_KEY: ${{ steps.ccache_restore.outputs.cache-primary-key }}
GH_TOKEN: ${{ github.token }}
run: |
- if gh cache delete --repo ${{ github.repository }} ${{ steps.ccache_restore.outputs.cache-primary-key }}; then
+ if gh cache delete --repo "$GITHUB_REPOSITORY" "$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
- uses: actions/cache/save@v5
+ - name: "[macOS] Save updated compiler cache (ccache)"
+ if: matrix.os == 'macOS' && matrix.use_ccache == 1 && github.ref == 'refs/heads/master'
+ uses: actions/cache/save@v6
with:
- path: ${{env.CCACHE_DIR}}
key: ${{ steps.ccache_restore.outputs.cache-primary-key }}
+ path: ${{ env.CCACHE_DIR }}
- - name: Sign app bundle
+ - name: "[macOS] Sign app bundle"
if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
id: sign_macos
env:
+ BUILD_PATH: ${{ steps.build.outputs.path }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
if [[ -n "$MACOS_CERTIFICATE_NAME" ]]
then
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
- /usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose ${{steps.build.outputs.path}}
+ /usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose "$BUILD_PATH"
fi
- - name: Notarize app bundle
- if: steps.sign_macos.outcome == 'success'
+ - name: "[macOS] Notarize app bundle"
+ if: matrix.os == 'macOS' && steps.sign_macos.outcome == 'success'
env:
+ BUILD_PATH: ${{ steps.build.outputs.path }}
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
- MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
+ MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
run: |
if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]]
then
@@ -514,7 +510,7 @@ jobs:
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
# notarization service
echo "Creating temp notarization archive"
- ditto -c -k --keepParent ${{steps.build.outputs.path}} "notarization.zip"
+ ditto -c -k --keepParent "$BUILD_PATH" "notarization.zip"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
@@ -526,51 +522,52 @@ jobs:
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available.
echo "Attach staple"
- xcrun stapler staple ${{steps.build.outputs.path}}
+ xcrun stapler staple "$BUILD_PATH"
fi
- - name: Upload artifact
+ - name: "Upload artifact"
if: matrix.make_package
id: upload_artifact
uses: actions/upload-artifact@v7
with:
- path: ${{steps.build.outputs.path}}
archive: false
if-no-files-found: error
+ path: ${{ steps.build.outputs.path }}
- - name: Upload PDBs (Program Databases)
+ - name: "[Windows] Upload PDBs (Program Databases)"
if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
with:
- name: ${{steps.build.outputs.name}}-PDBs
+ if-no-files-found: error
+ name: ${{ steps.build.outputs.name }}-PDBs
path: |
build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb
build/servatrice/Release/*.pdb
- if-no-files-found: error
- - name: Upload to release
+ - name: "Upload to release"
if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- tag_name: ${{needs.configure.outputs.tag}}
- asset_name: ${{steps.build.outputs.fullname}}
- asset_path: ${{steps.build.outputs.path}}
+ asset_name: ${{ steps.build.outputs.fullname }}
+ asset_path: ${{ steps.build.outputs.path }}
+ GH_TOKEN: ${{ github.token }}
+ tag_name: ${{ needs.configure.outputs.tag }}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- - name: Attest binary provenance
+ - name: "Attest binary provenance"
if: steps.upload_release.outcome == 'success'
id: attestation
uses: actions/attest@v4
with:
- subject-path: ${{steps.build.outputs.path}}
show-summary: false
+ subject-path: ${{ steps.build.outputs.path }}
- - name: Verify binary attestation
+ - name: "Verify binary attestation"
if: steps.attestation.outcome == 'success'
shell: bash
env:
- GH_TOKEN: ${{github.token}}
- run: gh attestation verify ${{steps.build.outputs.path}} --repo Cockatrice/Cockatrice
+ BUILD_PATH: ${{ steps.build.outputs.path }}
+ GH_TOKEN: ${{ github.token }}
+ run: gh attestation verify "$BUILD_PATH" --repo Cockatrice/Cockatrice
diff --git a/.github/workflows/desktop-lint.yml b/.github/workflows/desktop-lint.yml
index fe7be0287..5f31ea59c 100644
--- a/.github/workflows/desktop-lint.yml
+++ b/.github/workflows/desktop-lint.yml
@@ -1,17 +1,15 @@
name: Code Style (C++)
on:
- # push trigger not needed for linting, we do not allow direct pushes to master
+ # Push trigger not needed for linting, we do not allow direct pushes to master
pull_request:
paths:
- '*/**' # matches all files not in root
- '!**.md'
- '!.ci/**'
- '!.github/**'
- - '!.husky/**'
- '!.tx/**'
- '!doc/**'
- - '!webclient/**'
- '.ci/lint_cpp.sh'
- '.github/workflows/desktop-lint.yml'
- '.clang-format'
@@ -23,17 +21,20 @@ jobs:
runs-on: ubuntu-slim
steps:
- - name: Checkout
- uses: actions/checkout@v6
+ - name: "Checkout"
+ uses: actions/checkout@v7
with:
fetch-depth: 20 # should be enough to find merge base
- - name: Install dependencies
+ - name: "Install dependencies"
shell: bash
run: |
sudo apt-get update
- sudo apt-get install -y --no-install-recommends clang-format cmake-format shellcheck
+ sudo apt-get install -y --no-install-recommends \
+ clang-format \
+ cmake-format \
+ shellcheck
- - name: Check code formatting
+ - name: "Check code formatting"
shell: bash
run: ./.ci/lint_cpp.sh
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index fca1e97d4..b479322d0 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -1,9 +1,11 @@
name: Build Docker Image
+permissions:
+ contents: read
+ packages: write
+
on:
push:
- tags:
- - '*Release*'
branches:
- master
pull_request:
@@ -12,61 +14,64 @@ on:
paths:
- '.github/workflows/docker-release.yml'
- 'Dockerfile'
+ release:
+ types:
+ - released # publishing of stable releases
# Cancel earlier, unfinished runs of this workflow on the same branch (unless on release)
concurrency:
group: "${{ github.workflow }} @ ${{ github.ref_name }}"
- cancel-in-progress: ${{ github.ref_type != 'tag' }}
+ cancel-in-progress: ${{ github.event_name != 'release' }}
jobs:
docker:
name: amd64 & arm64
+ if: ${{ github.repository_owner == 'Cockatrice' }}
runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
-
+
steps:
- - name: Checkout
- uses: actions/checkout@v6
+ - name: "Checkout"
+ uses: actions/checkout@v7
- - name: Docker metadata
+ - name: "Docker metadata"
id: metadata
uses: docker/metadata-action@v6
+ env:
+ DOCKER_METADATA_ANNOTATIONS_LEVELS: index # needed for GHCR
with:
+ annotations: |
+ org.opencontainers.image.title=Servatrice
+ org.opencontainers.image.url=https://cockatrice.github.io/
+ org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
images: |
ghcr.io/cockatrice/servatrice
labels: |
org.opencontainers.image.title=Servatrice
org.opencontainers.image.url=https://cockatrice.github.io/
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- annotations: |
- org.opencontainers.image.title=Servatrice
- org.opencontainers.image.url=https://cockatrice.github.io/
- org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- - name: Set up QEMU
+ - name: "Set up QEMU"
uses: docker/setup-qemu-action@v4
- - name: Set up Docker buildx
+ - name: "Set up Docker buildx"
uses: docker/setup-buildx-action@v4
- - name: Login to GitHub Container Registry
- if: github.ref_type == 'tag'
+ - name: "Login to GitHub Container Registry"
+ if: contains(github.event.release.tag_name, 'Release') && github.event.release.target_commitish == 'master'
uses: docker/login-action@v4
with:
+ password: ${{ github.token }}
registry: ghcr.io
username: ${{ github.actor }}
- password: ${{ github.token }}
- - name: Build and push Docker image
+ - name: "Build and push Docker image"
uses: docker/build-push-action@v7
with:
- context: .
- platforms: linux/amd64,linux/arm64
- push: ${{ github.ref_type == 'tag' }}
- tags: ${{ steps.metadata.outputs.tags }}
- labels: ${{ steps.metadata.outputs.labels }}
annotations: ${{ steps.metadata.outputs.annotations }}
cache-from: type=gha,scope=servatrice
cache-to: type=gha,mode=max,scope=servatrice
+ context: .
+ labels: ${{ steps.metadata.outputs.labels }}
+ platforms: linux/amd64,linux/arm64
+ push: ${{ github.ref_type == 'tag' }}
+ tags: ${{ steps.metadata.outputs.tags }}
diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml
index ce331d113..4b9ca79ab 100644
--- a/.github/workflows/documentation-build.yml
+++ b/.github/workflows/documentation-build.yml
@@ -1,18 +1,18 @@
name: Generate Docs
on:
- push:
- tags:
- - '*' # Only re-generate docs when a new tagged version is pushed
pull_request:
paths:
- 'doc/doxygen/**'
- '.github/workflows/documentation-build.yml'
- 'Doxyfile'
+ release:
+ types:
+ - published # publishing of stable releases and pre-releases
workflow_dispatch:
env:
- COCKATRICE_REF: ${{ github.ref_name }} # Tag name if the commit is tagged, otherwise branch name
+ COCKATRICE_REF: ${{ github.ref_name }} # tag name if the commit is tagged, otherwise branch name
jobs:
docs:
@@ -20,22 +20,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- - name: Checkout code
- uses: actions/checkout@v6
+ - name: "Checkout code"
+ uses: actions/checkout@v7
with:
submodules: recursive
- - name: Install Graphviz
+ - name: "Install Graphviz"
run: |
sudo apt-get install -y graphviz
dot -V
- - name: Install Doxygen
+ - name: "Install Doxygen"
uses: ssciwr/doxygen-install@v2
with:
- version: "1.14.0"
+ version: "1.16.1"
- - name: Update Doxygen Configuration
+ - name: "Update Doxygen Configuration"
run: |
git diff Doxyfile
doxygen -u Doxyfile
@@ -48,16 +48,16 @@ jobs:
exit 1
fi
- - name: Generate Documentation
+ - name: "Generate Documentation"
if: always()
run: doxygen Doxyfile
- - name: Deploy to cockatrice.github.io
- if: github.event_name != 'pull_request'
+ - name: "Deploy to cockatrice.github.io"
+ if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }}
+ destination_dir: docs # docs will be available at https://cockatrice.github.io/docs/
external_repository: Cockatrice/cockatrice.github.io
publish_branch: master
publish_dir: ./docs/html
- destination_dir: docs # Docs will live under https://cockatrice.github.io/docs/
diff --git a/.github/workflows/translations-pull.yml b/.github/workflows/translations-pull.yml
index ca9069192..a3db5f86d 100644
--- a/.github/workflows/translations-pull.yml
+++ b/.github/workflows/translations-pull.yml
@@ -1,14 +1,14 @@
name: Update Translations
on:
- workflow_dispatch:
- schedule:
- # runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
- - cron: '0 0 15 1,4,7,10 *'
pull_request:
paths:
- '.tx/**'
- '.github/workflows/translations-pull.yml'
+ schedule:
+ # Runs in the middle of each month starting a quarter (UTC) = two weeks after new strings are built
+ - cron: '0 0 15 1,4,7,10 *'
+ workflow_dispatch:
jobs:
translations:
@@ -19,18 +19,20 @@ jobs:
runs-on: ubuntu-slim
steps:
- - name: Checkout repo
- uses: actions/checkout@v6
+ - name: "Checkout repo"
+ uses: actions/checkout@v7
- - name: Pull translated strings from Transifex
+ - name: "Pull translated strings from Transifex"
+ # Do not run this step for PR's from forks, they don't have access to the secret
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
uses: transifex/cli-action@v2
with:
- # used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
- # https://github.com/transifex/cli#pulling-files-from-transifex
- token: ${{ secrets.TX_TOKEN }}
+ # Used config file: https://github.com/Cockatrice/Cockatrice/blob/master/.tx/config
+ # Docs: https://github.com/transifex/cli#pulling-files-from-transifex
args: pull --force --all
+ token: ${{ secrets.TX_TOKEN }}
- - name: Create pull request
+ - name: "Create pull request"
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v8
@@ -38,36 +40,32 @@ jobs:
add-paths: |
cockatrice/translations/*.ts
oracle/translations/*.ts
- webclient/public/locales/*/translation.json
- commit-message: Update translation files
- # author is the owner of the commit
- author: github-actions
- branch: ci-update_translations
- delete-branch: true
- title: 'Update translations'
+ author: github-actions # owner of the commit
body: |
Pulled all translated strings from [Transifex][1].
-
+
---
*This PR is automatically generated and updated by the workflow at `.github/workflows/translations-pull.yml`. Review [action runs][2].*
*After merging, all new languages and translations are available in the next build.*
-
+
[1]: https://explore.transifex.com/cockatrice/cockatrice/
[2]: https://github.com/Cockatrice/Cockatrice/actions/workflows/translations-pull.yml?query=branch%3Amaster
+ branch: ci-update_translations
+ commit-message: Update translation files
+ delete-branch: true
+ draft: false
labels: |
CI
Translation
- draft: false
+ title: 'Update translations'
- - name: PR Status
+ - name: "PR Status"
if: github.event_name != 'pull_request'
shell: bash
env:
- STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
+ PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }}
+ PR_URL: ${{ steps.create_pr.outputs.pull-request-url }}
+ STATUS: ${{ case(steps.create_pr.outputs.pull-request-operation == 'none', 'unchanged', steps.create_pr.outputs.pull-request-operation) }}
run: |
- if [[ "$STATUS" == "none" ]]; then
- echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
- else
- echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
- fi
- echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY
+ echo "PR #$PR_NUMBER $STATUS!" >> "$GITHUB_STEP_SUMMARY"
+ echo "URL: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/translations-push.yml b/.github/workflows/translations-push.yml
index e926a58ed..c4d3f61fb 100644
--- a/.github/workflows/translations-push.yml
+++ b/.github/workflows/translations-push.yml
@@ -1,14 +1,14 @@
name: Update Translation Source
on:
- workflow_dispatch:
- schedule:
- # runs at the start of each quarter (UTC)
- - cron: '0 0 1 1,4,7,10 *'
pull_request:
paths:
- '.ci/update_translation_source_strings.sh'
- '.github/workflows/translations-push.yml'
+ schedule:
+ # Runs at the start of each quarter (UTC)
+ - cron: '0 0 1 1,4,7,10 *'
+ workflow_dispatch:
jobs:
translations:
@@ -19,32 +19,33 @@ jobs:
runs-on: ubuntu-slim
steps:
- - name: Checkout repo
- uses: actions/checkout@v6
+ - name: "Checkout repo"
+ uses: actions/checkout@v7
- - name: Install lupdate
+ - name: "Install lupdate"
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends qttools5-dev-tools
- - name: Update Cockatrice translation source
+ - name: "Update Cockatrice translation source"
+ env:
+ FILE: cockatrice/cockatrice_en@source.ts
id: cockatrice
shell: bash
- run: |
- FILE="cockatrice/cockatrice_en@source.ts"
- export DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
- FILE="$FILE" DIRS="$DIRS" .ci/update_translation_source_strings.sh
+ run: >
+ DIRS="cockatrice/src $(find . -maxdepth 1 -type d -name 'libcockatrice_*')"
+ .ci/update_translation_source_strings.sh
- - name: Update Oracle translation source
+ - name: "Update Oracle translation source"
id: oracle
shell: bash
env:
- FILE: 'oracle/oracle_en@source.ts'
DIRS: 'oracle/src'
+ FILE: 'oracle/oracle_en@source.ts'
run: .ci/update_translation_source_strings.sh
- - name: Render template
+ - name: "Render template"
id: template
uses: chuhlomin/render-template/binary@v1
with:
@@ -54,7 +55,7 @@ jobs:
oracle_output: ${{ steps.oracle.outputs.output }}
commit: ${{ github.sha }}
- - name: Create pull request
+ - name: "Create pull request"
if: github.event_name != 'pull_request'
id: create_pr
uses: peter-evans/create-pull-request@v8
@@ -62,27 +63,24 @@ jobs:
add-paths: |
cockatrice/cockatrice_en@source.ts
oracle/oracle_en@source.ts
- commit-message: Update translation source strings
- # author is the owner of the commit
- author: github-actions
- branch: ci-update_translation_source
- delete-branch: true
- title: 'Update source strings'
+ author: github-actions # owner of the commit
body: ${{ steps.template.outputs.result }}
+ branch: ci-update_translation_source
+ commit-message: Update translation source strings
+ delete-branch: true
+ draft: false
labels: |
CI
Translation
- draft: false
+ title: 'Update source strings'
- - name: PR Status
+ - name: "PR Status"
if: github.event_name != 'pull_request'
shell: bash
env:
- STATUS: ${{ steps.create_pr.outputs.pull-request-operation }}
+ PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }}
+ PR_URL: ${{ steps.create_pr.outputs.pull-request-url }}
+ STATUS: ${{ case(steps.create_pr.outputs.pull-request-operation == 'none', 'unchanged', steps.create_pr.outputs.pull-request-operation) }}
run: |
- if [[ "$STATUS" == "none" ]]; then
- echo "PR #${{ steps.create_pr.outputs.pull-request-number }} unchanged!" >> $GITHUB_STEP_SUMMARY
- else
- echo "PR #${{ steps.create_pr.outputs.pull-request-number }} $STATUS!" >> $GITHUB_STEP_SUMMARY
- fi
- echo "URL: ${{ steps.create_pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY
+ echo "PR #$PR_NUMBER $STATUS!" >> "$GITHUB_STEP_SUMMARY"
+ echo "URL: $PR_URL" >> "$GITHUB_STEP_SUMMARY"
diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml
deleted file mode 100644
index 8d756da02..000000000
--- a/.github/workflows/web-build.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: Build Web
-
-on:
- push:
- branches:
- - master
- paths:
- - '.husky/**'
- - 'webclient/**'
- - '!**.md'
- - '.github/workflows/web-build.yml'
- pull_request:
- paths:
- - '.husky/**'
- - 'webclient/**'
- - '!**.md'
- - '.github/workflows/web-build.yml'
-
-jobs:
- build-web:
- name: React (Node ${{matrix.node_version}})
-
- runs-on: ubuntu-latest
-
- defaults:
- run:
- working-directory: webclient
-
- strategy:
- fail-fast: false
- matrix:
- node_version:
- - 16
- - lts/*
-
- steps:
- - name: Checkout
- uses: actions/checkout@v6
-
- - name: Setup Node.js
- uses: actions/setup-node@v6
- with:
- node-version: ${{matrix.node_version}}
- cache: 'npm'
- cache-dependency-path: 'webclient/package-lock.json'
-
- - name: Install dependencies
- run: npm clean-install
-
- - name: Build app
- run: npm run build
-
- - name: Test app
- run: npm run test
diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml
deleted file mode 100644
index ecc6d14d1..000000000
--- a/.github/workflows/web-lint.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Code Style (TypeScript)
-
-on:
- # push trigger not needed for linting, we do not allow direct pushes to master
- pull_request:
- paths:
- - 'webclient/**'
- - '!**.md'
- - '.github/workflows/web-lint.yml'
-
-jobs:
- ESLint:
- runs-on: ubuntu-slim
-
- defaults:
- run:
- working-directory: webclient
-
- steps:
- - name: Checkout
- uses: actions/checkout@v6
-
- - name: Setup Node.js
- uses: actions/setup-node@v6
- with:
- cache: 'npm'
- cache-dependency-path: 'webclient/package-lock.json'
-
- - name: Install ESLint
- run: npm clean-install --ignore-scripts
-
- - name: Run ESLint
- run: npm run lint
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index 369435d6b..000000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-
-cd webclient
-npm run translate
-
-git add src/i18n-default.json
diff --git a/.tx/config b/.tx/config
index 9bc5ce950..8174a4dfd 100644
--- a/.tx/config
+++ b/.tx/config
@@ -16,11 +16,3 @@ source_file = oracle/oracle_en@source.ts
file_filter = oracle/translations/oracle_.ts
type = QT
minimum_perc = 10
-
-[o:cockatrice:p:cockatrice:r:webclient-src-i18n-default-json--master]
-resource_name = Webclient
-source_lang = en
-source_file = webclient/src/i18n-default.json
-file_filter = webclient/public/locales//translation.json
-type = KEYVALUEJSON
-minimum_perc = 10
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4a5e944c4..c10e1db68 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -74,7 +74,7 @@ endif()
# A project name is needed for CPack
# Version can be overriden by git tags, see cmake/getversion.cmake
-project("Cockatrice" VERSION 3.0.0)
+project("Cockatrice" VERSION 3.1.0)
# Set release name if not provided via env/cmake var
if(NOT DEFINED GIT_TAG_RELEASENAME)
@@ -174,6 +174,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
-Wno-error=delete-non-virtual-dtor
-Wno-error=sign-compare
-Wno-error=missing-declarations
+ -Wno-error=sfinae-incomplete # GCC 16+: Qt MOC + protobuf forward decls trigger this
)
foreach(FLAG ${ADDITIONAL_DEBUG_FLAGS})
diff --git a/Dockerfile b/Dockerfile
index d185b746a..7c5c773c9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# -------- Build Stage --------
-FROM ubuntu:24.04 AS build
+FROM ubuntu:26.04 AS build
ARG DEBIAN_FRONTEND=noninteractive
@@ -26,7 +26,7 @@ RUN mkdir build && cd build && \
# -------- Runtime Stage (clean) --------
-FROM ubuntu:24.04
+FROM ubuntu:26.04
RUN apt-get update && apt-get install -y --no-install-recommends \
libprotobuf32t64 \
diff --git a/Doxyfile b/Doxyfile
index 0f2642fd8..fc96b5f22 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,4 +1,4 @@
-# Doxyfile 1.14.0
+# Doxyfile 1.16.1
# This file describes the settings to be used by the documentation system
# Doxygen (www.doxygen.org) for a project.
@@ -361,6 +361,20 @@ EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
+# If the MARKDOWN_STRICT tag is enabled then Doxygen treats text in comments as
+# Markdown formatted also in cases where Doxygen's native markup format
+# conflicts with that of Markdown. This is only relevant in cases where
+# backticks are used. Doxygen's native markup style allows a single quote to end
+# a text fragment started with a backtick and then treat it as a piece of quoted
+# text, whereas in Markdown such text fragment is treated as verbatim and only
+# ends when a second matching backtick is found. Also, Doxygen's native markup
+# format requires double quotes to be escaped when they appear in a backtick
+# section, whereas this is not needed for Markdown.
+# The default value is: YES.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+MARKDOWN_STRICT = YES
+
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
@@ -392,8 +406,8 @@ AUTOLINK_SUPPORT = YES
# This tag specifies a list of words that, when matching the start of a word in
# the documentation, will suppress auto links generation, if it is enabled via
-# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \#
-# or the \link or commands.
+# AUTOLINK_SUPPORT. This list does not affect links explicitly created using #
+# or the \link or \ref commands.
# This tag requires that the tag AUTOLINK_SUPPORT is set to YES.
AUTOLINK_IGNORE_WORDS =
@@ -510,9 +524,9 @@ LOOKUP_CACHE_SIZE = 0
# which effectively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
-# Minimum value: 0, maximum value: 32, default value: 1.
+# Minimum value: 0, maximum value: 512, default value: 1.
-NUM_PROC_THREADS = 1
+NUM_PROC_THREADS = 0
# If the TIMESTAMP tag is set different from NO then each generated page will
# contain the date or date and time when the page was generated. Setting this to
@@ -779,6 +793,27 @@ GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
+# The GENERATE_REQUIREMENTS tag can be used to enable (YES) or disable (NO) the
+# requirements page. When enabled, this page is automatically created when at
+# least one comment block with a \requirement command appears in the input.
+# The default value is: YES.
+
+GENERATE_REQUIREMENTS = YES
+
+# The REQ_TRACEABILITY_INFO tag controls if traceability information is shown on
+# the requirements page (only relevant when using \requirement comment blocks).
+# The setting NO will disable the traceablility information altogether. The
+# setting UNSATISFIED_ONLY will show a list of requirements that are missing a
+# satisfies relation (through the command: \satisfies). Similarly the setting
+# UNVERIFIED_ONLY will show a list of requirements that are missing a verifies
+# relation (through the command: \verifies). Setting the tag to YES (the
+# default) will show both lists if applicable.
+# Possible values are: YES, NO, UNSATISFIED_ONLY and UNVERIFIED_ONLY.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_REQUIREMENTS is set to YES.
+
+REQ_TRACEABILITY_INFO = YES
+
# The ENABLED_SECTIONS tag can be used to enable conditional documentation
# sections, marked by \if ... \endif and \cond
# ... \endcond blocks.
@@ -1070,8 +1105,7 @@ EXCLUDE = build/ \
cmake/ \
doc/doxygen/theme/docs/ \
doc/doxygen/theme/include/ \
- vcpkg/ \
- webclient/
+ vcpkg/
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
# directories that are symbolic links (a Unix file system feature) are excluded
@@ -1887,7 +1921,7 @@ USE_MATHJAX = NO
# regards to the different settings, so it is possible that also other MathJax
# settings have to be changed when switching between the different MathJax
# versions.
-# Possible values are: MathJax_2 and MathJax_3.
+# Possible values are: MathJax_2, MathJax_3 and MathJax_4.
# The default value is: MathJax_2.
# This tag requires that the tag USE_MATHJAX is set to YES.
@@ -1896,9 +1930,10 @@ MATHJAX_VERSION = MathJax_2
# When MathJax is enabled you can set the default output format to be used for
# the MathJax output. For more details about the output format see MathJax
# version 2 (see:
-# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# https://docs.mathjax.org/en/v2.7/output.html), MathJax version 3 (see:
+# https://docs.mathjax.org/en/v3.2/output/index.html) and MathJax version 4
# (see:
-# http://docs.mathjax.org/en/latest/web/components/output.html).
+# https://docs.mathjax.org/en/v4.0/output/index.htm).
# Possible values are: HTML-CSS (which is slower, but has the best
# compatibility. This is the name for Mathjax version 2, for MathJax version 3
# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
@@ -1911,36 +1946,50 @@ MATHJAX_VERSION = MathJax_2
MATHJAX_FORMAT = HTML-CSS
# When MathJax is enabled you need to specify the location relative to the HTML
-# output directory using the MATHJAX_RELPATH option. The destination directory
-# should contain the MathJax.js script. For instance, if the mathjax directory
-# is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
-# Content Delivery Network so you can quickly see the result without installing
-# MathJax. However, it is strongly recommended to install a local copy of
-# MathJax from https://www.mathjax.org before deployment. The default value is:
+# output directory using the MATHJAX_RELPATH option. For Mathjax version 2 the
+# destination directory should contain the MathJax.js script. For instance, if
+# the mathjax directory is located at the same level as the HTML output
+# directory, then MATHJAX_RELPATH should be ../mathjax.s For Mathjax versions 3
+# and 4 the destination directory should contain the tex-.js script
+# (where is either chtml or svg). The default value points to the
+# MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax. However, it is strongly recommended to install a local
+# copy of MathJax from https://www.mathjax.org before deployment. The default
+# value is:
# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# - in case of MathJax version 4: https://cdn.jsdelivr.net/npm/mathjax@4
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH =
# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
# extension names that should be enabled during MathJax rendering. For example
-# for MathJax version 2 (see
-# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7/tex.html):
# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
# For example for MathJax version 3 (see
-# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# https://docs.mathjax.org/en/v3.2/input/tex/extensions/):
# MATHJAX_EXTENSIONS = ams
+# For example for MathJax version 4 (see
+# https://docs.mathjax.org/en/v4.0/input/tex/extensions/):
+# MATHJAX_EXTENSIONS = units
+# Note that for Mathjax version 4 quite a few extensions are already
+# automatically loaded. To disable a package in Mathjax version 4 one can use
+# the package name prepended with a minus sign (- like MATHJAX_EXTENSIONS +=
+# -textmacros)
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_EXTENSIONS =
# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces
-# of code that will be used on startup of the MathJax code. See the MathJax site
-# (see:
-# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
-# example see the documentation.
+# of code that will be used on startup of the MathJax code. See the Mathjax site
+# for more details:
+# - MathJax version 2 (see:
+# https://docs.mathjax.org/en/v2.7/)
+# - MathJax version 3 (see:
+# https://docs.mathjax.org/en/v3.2/)
+# - MathJax version 4 (see:
+# https://docs.mathjax.org/en/v4.0/) For an example see the documentation.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_CODEFILE =
@@ -2601,7 +2650,7 @@ HAVE_DOT = YES
# processors available in the system. You can set it explicitly to a value
# larger than 0 to get control over the balance between CPU load and processing
# speed.
-# Minimum value: 0, maximum value: 32, default value: 0.
+# Minimum value: 0, maximum value: 512, default value: 0.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NUM_THREADS = 0
diff --git a/README.md b/README.md
index 8ad9a092c..9a27601cd 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
Related|Community|Contribute|
- Build|
+ Build|Run
@@ -25,7 +25,6 @@
Cockatrice is an open-source, multiplatform application for playing tabletop card games over a network. The program's server design prevents users from manipulating the game for unfair advantage. The client also provides a single-player mode, which allows users to brew while offline.
This project uses C++ and the Qt libraries.
-First work on a webclient with Typescript was started as well.
# Download [](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice&search=0)
@@ -48,6 +47,7 @@ Latest beta version:
- [Magic-Spoiler](https://github.com/Cockatrice/Magic-Spoiler): Code to generate MtG spoiler data from [MTGJSON](https://github.com/mtgjson/mtgjson) for use in Cockatrice
- [cockatrice.github.io](https://github.com/Cockatrice/cockatrice.github.io): Code of the official Cockatrice webpage
- [io.github.Cockatrice.cockatrice](https://github.com/flathub/io.github.Cockatrice.cockatrice): Configuration of our Linux `flatpak` package hosted at [Flathub](https://flathub.org/en/apps/io.github.Cockatrice.cockatrice)
+- [Webatrice](https://github.com/Cockatrice/Webatrice): Web client for Cockatrice servers (TypeScript / React)
# Community Resources [](https://discord.gg/3Z9yzmA)
@@ -107,12 +107,12 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
### Translation [](https://explore.transifex.com/cockatrice/cockatrice/)
-Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice, Oracle and Webatrice to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).
+Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice and Oracle to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/). The [Webatrice](https://github.com/seavor/Webatrice) web client manages its own translations in its repo.
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!
-# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
+# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush)
Dependencies: *(for minimum versions search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))*
- [Qt](https://www.qt.io/developers/)
diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in
index 7b52b7bcc..5af116470 100644
--- a/cmake/NSIS.template.in
+++ b/cmake/NSIS.template.in
@@ -117,21 +117,22 @@ ${If} $InstDir == ""
; we need to set a default based on the install mode
StrCpy $InstDir $0
${EndIf}
-Call SetModeDestinationFromInstdir
-; --- Detect portable install when using /R ---
+; --- Detect portable install when using /R (must come BEFORE SetModeDestinationFromInstdir) ---
${If} $ReinstallMode = 1
IfFileExists "$InstDir\portable.dat" 0 not_portable
StrCpy $PortableMode 1
Goto portable_done
-
not_portable:
StrCpy $PortableMode 0
-
portable_done:
${EndIf}
+; Now that $PortableMode reflects reality, commit InstDir into the correct slot
+Call SetModeDestinationFromInstdir
+
${If} $ReinstallMode = 1
+${AndIf} $PortableMode = 0
Call AutoUninstallIfNeeded
${EndIf}
diff --git a/cmake/getversion.cmake b/cmake/getversion.cmake
index cb27ebd87..b24b520d5 100644
--- a/cmake/getversion.cmake
+++ b/cmake/getversion.cmake
@@ -213,7 +213,8 @@ set(PROJECT_VERSION_FRIENDLY "${PROJECT_VERSION} (${GIT_COMMIT_DATE_FRIENDLY})")
# Format: [-ReleaseName]-MAJ.MIN.PATCH[-prerelease_label]
set(PROJECT_VERSION_FILENAME "${PROJECT_NAME}")
if(PROJECT_VERSION_RELEASENAME)
- set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME}")
+ string(REPLACE " " "-" PROJECT_VERSION_RELEASENAME_SAFE "${PROJECT_VERSION_RELEASENAME}")
+ set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION_RELEASENAME_SAFE}")
endif()
set(PROJECT_VERSION_FILENAME "${PROJECT_VERSION_FILENAME}-${PROJECT_VERSION}")
diff --git a/cmake/gtest-CMakeLists.txt.in b/cmake/gtest-CMakeLists.txt.in
index 2d71a55e5..2062d7f8c 100644
--- a/cmake/gtest-CMakeLists.txt.in
+++ b/cmake/gtest-CMakeLists.txt.in
@@ -1,15 +1,16 @@
-cmake_minimum_required(VERSION 3.2)
+cmake_minimum_required(VERSION 3.10)
project(gtest-download LANGUAGES NONE)
include(ExternalProject)
-ExternalProject_Add(googletest
- URL https://github.com/google/googletest/archive/release-1.11.0.zip
- URL_HASH SHA1=9ffb7b5923f4a8fcdabf2f42c6540cce299f44c0
+externalproject_add(
+ googletest
+ URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip
+ URL_HASH SHA1=f638fa0e724760e2ba07ff8cfba32cd644e1ce28
SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
CONFIGURE_COMMAND ""
- BUILD_COMMAND ""
- INSTALL_COMMAND ""
- TEST_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
)
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index 12733afe6..166b807d9 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -7,6 +7,7 @@ project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${
set(cockatrice_SOURCES
${VERSION_STRING_CPP}
# sort by alphabetical order, so that there is no debate about where to add new sources to the list
+ src/client/network/connection_controller/remote_connection_controller.cpp
src/client/network/update/client/update_downloader.cpp
src/client/network/interfaces/deck_stats_interface.cpp
src/client/network/interfaces/tapped_out_interface.cpp
@@ -55,68 +56,74 @@ set(cockatrice_SOURCES
src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp
src/game/abstract_game.cpp
- src/game/board/abstract_card_drag_item.cpp
- src/game/board/abstract_card_item.cpp
- src/game/board/abstract_counter.cpp
- src/game/board/arrow_item.cpp
- src/game/board/arrow_target.cpp
- src/game/board/card_drag_item.cpp
- src/game/board/card_item.cpp
+ src/game/arrow_registry.cpp
+ src/game_graphics/board/abstract_card_drag_item.cpp
+ src/game_graphics/board/abstract_card_item.cpp
+ src/game_graphics/board/abstract_counter.cpp
+ src/game/board/arrow_data.cpp
+ src/game_graphics/board/arrow_item.cpp
+ src/game_graphics/board/arrow_target.cpp
+ src/game_graphics/board/card_drag_item.cpp
+ src/game_graphics/board/card_item.cpp
src/game/board/card_list.cpp
- src/game/board/counter_general.cpp
- src/game/board/translate_counter_name.cpp
- src/game/deckview/deck_view.cpp
- src/game/deckview/deck_view_container.cpp
- src/game/deckview/tabbed_deck_view_container.cpp
- src/game/dialogs/dlg_create_token.cpp
- src/game/dialogs/dlg_move_top_cards_until.cpp
- src/game/dialogs/dlg_roll_dice.cpp
+ src/game/board/card_state.cpp
+ src/game_graphics/board/counter_general.cpp
+ src/game/board/counter_state.cpp
+ src/game_graphics/board/translate_counter_name.cpp
+ src/game_graphics/deckview/deck_view.cpp
+ src/game_graphics/deckview/deck_view_container.cpp
+ src/game_graphics/deckview/tabbed_deck_view_container.cpp
+ src/game_graphics/dialogs/dlg_create_token.cpp
+ src/game_graphics/dialogs/dlg_move_top_cards_until.cpp
+ src/game_graphics/dialogs/dlg_roll_dice.cpp
src/game/game.cpp
src/game/game_event_handler.cpp
src/game/game_meta_info.cpp
- src/game/game_scene.cpp
+ src/game_graphics/game_scene.cpp
src/game/game_state.cpp
- src/game/game_view.cpp
- src/game/hand_counter.cpp
- src/game/log/message_log_widget.cpp
+ src/game_graphics/game_view.cpp
+ src/game_graphics/hand_counter.cpp
+ src/game/selection_subtype_tally.cpp
+ src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp
- src/game/phases_toolbar.cpp
- src/game/player/menu/card_menu.cpp
- src/game/player/menu/custom_zone_menu.cpp
- src/game/player/menu/grave_menu.cpp
- src/game/player/menu/hand_menu.cpp
- src/game/player/menu/library_menu.cpp
- src/game/player/menu/move_menu.cpp
- src/game/player/menu/player_menu.cpp
- src/game/player/menu/pt_menu.cpp
- src/game/player/menu/rfg_menu.cpp
- src/game/player/menu/say_menu.cpp
- src/game/player/menu/sideboard_menu.cpp
- src/game/player/menu/utility_menu.cpp
- src/game/player/player.cpp
+ src/game_graphics/phases_toolbar.cpp
+ src/game_graphics/player/menu/card_menu.cpp
+ src/game_graphics/player/menu/custom_zone_menu.cpp
+ src/game_graphics/player/menu/grave_menu.cpp
+ src/game_graphics/player/menu/hand_menu.cpp
+ src/game_graphics/player/menu/library_menu.cpp
+ src/game_graphics/player/menu/move_menu.cpp
+ src/game_graphics/player/menu/player_menu.cpp
+ src/game_graphics/player/menu/pt_menu.cpp
+ src/game_graphics/player/menu/rfg_menu.cpp
+ src/game_graphics/player/menu/say_menu.cpp
+ src/game_graphics/player/menu/sideboard_menu.cpp
+ src/game_graphics/player/menu/utility_menu.cpp
src/game/player/player_actions.cpp
- src/game/player/player_area.cpp
+ src/game_graphics/player/player_area.cpp
+ src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp
- src/game/player/player_graphics_item.cpp
+ src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_info.cpp
- src/game/player/player_list_widget.cpp
+ src/game_graphics/player/player_list_widget.cpp
+ src/game/player/player_logic.cpp
src/game/player/player_manager.cpp
- src/game/player/player_target.cpp
+ src/game_graphics/player/player_target.cpp
src/game/replay.cpp
- src/game/zones/card_zone.cpp
- src/game/zones/hand_zone.cpp
- src/game/zones/logic/card_zone_logic.cpp
- src/game/zones/logic/hand_zone_logic.cpp
- src/game/zones/logic/pile_zone_logic.cpp
- src/game/zones/logic/stack_zone_logic.cpp
- src/game/zones/logic/table_zone_logic.cpp
- src/game/zones/logic/view_zone_logic.cpp
- src/game/zones/pile_zone.cpp
- src/game/zones/select_zone.cpp
- src/game/zones/stack_zone.cpp
- src/game/zones/table_zone.cpp
- src/game/zones/view_zone.cpp
- src/game/zones/view_zone_widget.cpp
+ src/game/zones/card_zone_logic.cpp
+ src/game/zones/hand_zone_logic.cpp
+ src/game/zones/pile_zone_logic.cpp
+ src/game/zones/stack_zone_logic.cpp
+ src/game/zones/table_zone_logic.cpp
+ src/game/zones/view_zone_logic.cpp
+ src/game_graphics/zones/card_zone.cpp
+ src/game_graphics/zones/hand_zone.cpp
+ src/game_graphics/zones/pile_zone.cpp
+ src/game_graphics/zones/select_zone.cpp
+ src/game_graphics/zones/stack_zone.cpp
+ src/game_graphics/zones/table_zone.cpp
+ src/game_graphics/zones/view_zone.cpp
+ src/game_graphics/zones/view_zone_widget.cpp
src/game_graphics/board/abstract_graphics_item.cpp
src/interface/card_picture_loader/card_picture_loader.cpp
src/interface/card_picture_loader/card_picture_loader_local.cpp
@@ -129,7 +136,13 @@ set(cockatrice_SOURCES
src/interface/layouts/overlap_layout.cpp
src/interface/widgets/utility/line_edit_completer.cpp
src/interface/pixel_map_generator.cpp
+ src/interface/theme_config.cpp
src/interface/theme_manager.cpp
+ src/interface/palette_editor/color_button.cpp
+ src/interface/palette_editor/palette_generator.cpp
+ src/interface/palette_editor/quick_setup_panel.cpp
+ src/interface/palette_editor/palette_grid_widget.cpp
+ src/interface/palette_editor/palette_editor_dialog.cpp
src/interface/widgets/cards/additional_info/color_identity_widget.cpp
src/interface/widgets/cards/additional_info/mana_cost_widget.cpp
src/interface/widgets/cards/additional_info/mana_symbol_widget.cpp
@@ -171,6 +184,7 @@ set(cockatrice_SOURCES
src/interface/widgets/deck_analytics/analyzer_modules/mana_distribution/mana_distribution_single_display_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_total_widget.cpp
src/interface/widgets/deck_analytics/analyzer_modules/mana_curve/mana_curve_category_widget.cpp
+ src/interface/widgets/deck_editor/card_database_view.cpp
src/interface/widgets/deck_editor/deck_list_history_manager_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_database_dock_widget.cpp
src/interface/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp
@@ -216,17 +230,30 @@ set(cockatrice_SOURCES
src/interface/widgets/replay/replay_manager.cpp
src/interface/widgets/replay/replay_timeline_widget.cpp
src/interface/widgets/server/chat_view/chat_view.cpp
+ src/interface/widgets/server/game_filter_configs.cpp
src/interface/widgets/server/game_selector.cpp
src/interface/widgets/server/game_selector_quick_filter_toolbar.cpp
src/interface/widgets/server/games_model.cpp
src/interface/widgets/server/handle_public_servers.cpp
src/interface/widgets/server/remote/remote_decklist_tree_widget.cpp
src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp
+ src/interface/widgets/server/user/user_avatar_provider.cpp
+ src/interface/widgets/server/user/user_card_art_provider.cpp
+ src/interface/widgets/server/user/user_card_settings_dialog.cpp
src/interface/widgets/server/user/user_context_menu.cpp
src/interface/widgets/server/user/user_info_box.cpp
src/interface/widgets/server/user/user_info_connection.cpp
src/interface/widgets/server/user/user_list_manager.cpp
+ src/interface/widgets/server/user/user_list_painter.cpp
src/interface/widgets/server/user/user_list_widget.cpp
+ src/interface/widgets/settings_page/appearance_settings_page.cpp
+ src/interface/widgets/settings_page/deck_editor_settings_page.cpp
+ src/interface/widgets/settings_page/general_settings_page.cpp
+ src/interface/widgets/settings_page/messages_settings_page.cpp
+ src/interface/widgets/settings_page/shortcut_settings_page.cpp
+ src/interface/widgets/settings_page/sound_settings_page.cpp
+ src/interface/widgets/settings_page/storage_settings_page.cpp
+ src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/sequence_edit.cpp
@@ -305,6 +332,7 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/tab.cpp
src/interface/widgets/tabs/tab_account.cpp
src/interface/widgets/tabs/tab_admin.cpp
+ src/interface/widgets/tabs/tab_card_art_rules.cpp
src/interface/widgets/tabs/tab_deck_editor.cpp
src/interface/widgets/tabs/tab_deck_storage.cpp
src/interface/widgets/tabs/tab_game.cpp
@@ -325,6 +353,10 @@ set(cockatrice_SOURCES
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_bracket_navigation_widget.h
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.cpp
src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_budget_navigation_widget.h
+ src/interface/widgets/utility/compact_push_button.cpp
+ src/interface/widgets/utility/compact_push_button.h
+ src/interface/widgets/server/user/user_info_popup.cpp
+ src/interface/widgets/server/user/user_info_popup.h
)
add_subdirectory(sounds)
@@ -539,6 +571,7 @@ if(WIN32)
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
DESTINATION ./
FILES_MATCHING
+ PATTERN "CMakeFiles" EXCLUDE
PATTERN "*.ini"
)
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index 37fb145f0..9c34929b7 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -55,6 +55,7 @@
resources/icons/view.svgresources/icons/mana/B.svg
+ resources/icons/mana/C.svgresources/icons/mana/G.svgresources/icons/mana/R.svgresources/icons/mana/U.svg
@@ -69,6 +70,7 @@
resources/config/interface.svgresources/config/messages.svgresources/config/deckeditor.svg
+ resources/config/storage.svgresources/config/shorcuts.svgresources/config/sound.svgresources/config/debug.ini
diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini
index 20aa206ce..7ac0d9ca4 100644
--- a/cockatrice/resources/config/qtlogging.ini
+++ b/cockatrice/resources/config/qtlogging.ini
@@ -28,6 +28,8 @@
#dlg_tip_of_the_day = true
#dlg_update = true
+#general_settings_page = true
+
#settings_cache = true
#servers_settings = true
#shortcuts_settings = true
diff --git a/cockatrice/resources/config/storage.svg b/cockatrice/resources/config/storage.svg
new file mode 100644
index 000000000..de85228dc
--- /dev/null
+++ b/cockatrice/resources/config/storage.svg
@@ -0,0 +1,799 @@
+
+
+
+
diff --git a/cockatrice/resources/icons/mana/C.svg b/cockatrice/resources/icons/mana/C.svg
new file mode 100644
index 000000000..eb09fb872
--- /dev/null
+++ b/cockatrice/resources/icons/mana/C.svg
@@ -0,0 +1,72 @@
+
+
+
+
diff --git a/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
new file mode 100644
index 000000000..75cedcafc
--- /dev/null
+++ b/cockatrice/src/client/network/connection_controller/remote_connection_controller.cpp
@@ -0,0 +1,587 @@
+#include "remote_connection_controller.h"
+
+#include "../../settings/cache_settings.h"
+#include "../interface/widgets/dialogs/dlg_connect.h"
+#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
+#include "../interface/widgets/dialogs/dlg_forgot_password_request.h"
+#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h"
+#include "../interface/widgets/dialogs/dlg_register.h"
+#include "../interface/widgets/utility/get_text_with_max.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+ConnectionController::ConnectionController(QWidget *dialogParent, QObject *parent)
+ : QObject(parent), dialogParent(dialogParent)
+{
+ remoteClient = new RemoteClient(nullptr, &SettingsCache::instance());
+
+ clientThread = new QThread(this);
+ remoteClient->moveToThread(clientThread);
+ clientThread->start();
+
+ wireClientSignals();
+}
+
+ConnectionController::~ConnectionController()
+{
+ remoteClient->deleteLater();
+ clientThread->wait();
+}
+
+void ConnectionController::wireClientSignals()
+{
+ connect(remoteClient, &RemoteClient::connectionClosedEventReceived, this,
+ &ConnectionController::onConnectionClosedEvent);
+
+ connect(remoteClient, &RemoteClient::serverShutdownEventReceived, this,
+ &ConnectionController::onServerShutdownEvent);
+
+ connect(remoteClient, &RemoteClient::statusChanged, this, &ConnectionController::onStatusChanged);
+
+ connect(remoteClient, &RemoteClient::userInfoChanged, this, &ConnectionController::onUserInfoReceived,
+ Qt::BlockingQueuedConnection);
+
+ connect(remoteClient, &RemoteClient::loginError, this,
+ [this](Response::ResponseCode r, QString rs, quint32 et, QList mf) {
+ onLoginError(static_cast(r), rs, et, mf);
+ });
+
+ connect(remoteClient, &RemoteClient::registerError, this,
+ [this](Response::ResponseCode r, QString rs, quint32 et) { onRegisterError(static_cast(r), rs, et); });
+
+ connect(remoteClient, &RemoteClient::activateError, this, &ConnectionController::onActivateError);
+ connect(remoteClient, &RemoteClient::socketError, this, &ConnectionController::onSocketError);
+ connect(remoteClient, &RemoteClient::serverTimeout, this, &ConnectionController::onServerTimeout);
+
+ connect(remoteClient, &RemoteClient::protocolVersionMismatch, this,
+ &ConnectionController::onProtocolVersionMismatch);
+
+ connect(remoteClient, &RemoteClient::registerAccepted, this, &ConnectionController::onRegisterAccepted);
+
+ connect(remoteClient, &RemoteClient::registerAcceptedNeedsActivate, this,
+ &ConnectionController::onRegisterAcceptedNeedsActivate);
+
+ connect(remoteClient, &RemoteClient::activateAccepted, this, &ConnectionController::onActivateAccepted);
+
+ connect(remoteClient, &RemoteClient::notifyUserAboutUpdate, this, &ConnectionController::onNotifyUserAboutUpdate);
+
+ connect(remoteClient, &RemoteClient::sigForgotPasswordSuccess, this,
+ &ConnectionController::onForgotPasswordSuccess);
+
+ connect(remoteClient, &RemoteClient::sigForgotPasswordError, this, &ConnectionController::onForgotPasswordError);
+
+ connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordReset, this,
+ &ConnectionController::onPromptForgotPasswordReset);
+
+ connect(remoteClient, &RemoteClient::sigPromptForForgotPasswordChallenge, this,
+ &ConnectionController::onPromptForgotPasswordChallenge);
+}
+
+void ConnectionController::connectToServer()
+{
+ dlgConnect = new DlgConnect(dialogParent);
+ connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &ConnectionController::forgotPasswordRequest);
+
+ if (dlgConnect->exec()) {
+ remoteClient->connectToServer(dlgConnect->getHost(), static_cast(dlgConnect->getPort()),
+ dlgConnect->getPlayerName(), dlgConnect->getPassword());
+ }
+}
+
+void ConnectionController::connectToServerDirect(const QString &host,
+ unsigned int port,
+ const QString &playerName,
+ const QString &password)
+{
+ remoteClient->connectToServer(host, port, playerName, password);
+}
+
+void ConnectionController::disconnectFromServer()
+{
+ remoteClient->disconnectFromServer();
+}
+
+void ConnectionController::registerToServer()
+{
+ DlgRegister dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->registerToServer(dlg.getHost(), static_cast(dlg.getPort()), dlg.getPlayerName(),
+ dlg.getPassword(), dlg.getEmail(), dlg.getCountry(), dlg.getRealName());
+ }
+}
+
+void ConnectionController::forgotPasswordRequest()
+{
+ DlgForgotPasswordRequest dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->requestForgotPasswordToServer(dlg.getHost(), static_cast(dlg.getPort()),
+ dlg.getPlayerName());
+ }
+}
+
+void ConnectionController::onConnectionClosedEvent(const Event_ConnectionClosed &event)
+{
+ remoteClient->disconnectFromServer();
+
+ QString reasonStr;
+ switch (event.reason()) {
+ case Event_ConnectionClosed::USER_LIMIT_REACHED: {
+ reasonStr = tr("The server has reached its maximum user capacity, please check back later.");
+ break;
+ }
+ case Event_ConnectionClosed::TOO_MANY_CONNECTIONS: {
+ reasonStr = tr("There are too many concurrent connections from your address.");
+ break;
+ }
+ case Event_ConnectionClosed::BANNED: {
+ reasonStr = tr("Banned by moderator");
+ if (event.has_end_time()) {
+ reasonStr.append(
+ "\n" + tr("Expected end time: %1").arg(QDateTime::fromSecsSinceEpoch(event.end_time()).toString()));
+ } else {
+ reasonStr.append("\n" + tr("This ban lasts indefinitely."));
+ }
+ if (event.has_reason_str()) {
+ reasonStr.append("\n\n" + QString::fromStdString(event.reason_str()));
+ }
+ break;
+ }
+ case Event_ConnectionClosed::SERVER_SHUTDOWN: {
+ reasonStr = tr("Scheduled server shutdown.");
+ break;
+ }
+ case Event_ConnectionClosed::USERNAMEINVALID: {
+ reasonStr = tr("Invalid username.");
+ break;
+ }
+ case Event_ConnectionClosed::LOGGEDINELSEWERE: {
+ reasonStr = tr("You have been logged out due to logging in at another location.");
+ break;
+ }
+ default:
+ reasonStr = QString::fromStdString(event.reason_str());
+ }
+
+ QMessageBox::critical(dialogParent, tr("Connection closed"),
+ tr("The server has terminated your connection.\nReason: %1").arg(reasonStr));
+}
+
+void ConnectionController::onServerShutdownEvent(const Event_ServerShutdown &event)
+{
+ serverShutdownMessageBox.setInformativeText(tr("The server is going to be restarted in %n minute(s).\nAll running "
+ "games will be lost.\nReason for shutdown: %1",
+ "", event.minutes())
+ .arg(QString::fromStdString(event.reason())));
+ serverShutdownMessageBox.setIconPixmap(QPixmap("theme:cockatrice").scaled(64, 64));
+ serverShutdownMessageBox.setText(tr("Scheduled server shutdown"));
+ serverShutdownMessageBox.setWindowModality(Qt::ApplicationModal);
+ serverShutdownMessageBox.setVisible(true);
+}
+
+void ConnectionController::onStatusChanged(ClientStatus status)
+{
+ // Update the window title first, then let MainWindow handle its own UI
+ // state via the forwarded signal
+ updateWindowTitle();
+ emit statusChanged(status);
+
+ // TabSupervisor::stop() needs calling on disconnect; start() is driven by
+ // onUserInfoReceived → tabSupervisorStartRequested.
+ if (status == StatusDisconnected) {
+ emit tabSupervisorStopRequested();
+ }
+}
+
+void ConnectionController::onUserInfoReceived(const ServerInfo_User &info)
+{
+ emit tabSupervisorStartRequested(info);
+}
+
+void ConnectionController::onLoginError(int r,
+ QString reasonStr,
+ quint32 endTime,
+ const QList &missingFeatures)
+{
+ switch (static_cast(r)) {
+ case Response::RespClientUpdateRequired: {
+ QString formatted = "Missing Features: ";
+ for (int i = 0; i < missingFeatures.size(); ++i) {
+ formatted.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i));
+ }
+
+ QMessageBox msgBox(dialogParent);
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setWindowTitle(tr("Failed Login"));
+ msgBox.setText(tr("Your client seems to be missing features this server requires for connection.") +
+ "\n\n" + tr("To update your client, go to 'Help -> Check for Client Updates'."));
+ msgBox.setDetailedText(formatted);
+ msgBox.exec();
+ break;
+ }
+
+ case Response::RespWrongPassword: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Incorrect username or password. "
+ "Please check your authentication information and try again."));
+ break;
+ }
+
+ case Response::RespWouldOverwriteOldSession: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("There is already an active session using this user name.\n"
+ "Please close that session first and re-login."));
+ break;
+ }
+
+ case Response::RespUserIsBanned: {
+ QString bannedStr =
+ endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
+ : tr("You are banned indefinitely.");
+ if (!reasonStr.isEmpty()) {
+ bannedStr.append("\n\n" + reasonStr);
+ }
+ QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
+ break;
+ }
+
+ case Response::RespUsernameInvalid: {
+ QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
+ break;
+ }
+
+ case Response::RespRegistrationRequired: {
+ if (QMessageBox::question(dialogParent, tr("Error"),
+ tr("This server requires user registration. Do you want to register now?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ registerToServer();
+ }
+ return; // don't re-prompt connect
+ }
+
+ case Response::RespClientIdRequired: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("This server requires client IDs. Your client is either failing to generate an "
+ "ID or you are running a modified client.\n"
+ "Please close and reopen your client to try again."));
+ break;
+ }
+
+ case Response::RespContextError: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("An internal error has occurred, please close and reopen Cockatrice before "
+ "trying again.\nIf the error persists, ensure you are running the latest "
+ "version of the software and if needed contact the software developers."));
+ break;
+ }
+
+ case Response::RespAccountNotActivated: {
+ bool ok = false;
+ QString token =
+ getTextWithMax(dialogParent, tr("Account activation"),
+ tr("Your account has not been activated yet.\n"
+ "You need to provide the activation token received in the activation email."),
+ QLineEdit::Normal, QString(), &ok);
+
+ if (ok && !token.isEmpty()) {
+ remoteClient->activateToServer(token);
+ return;
+ }
+ remoteClient->disconnectFromServer();
+ return;
+ }
+
+ case Response::RespServerFull: {
+ QMessageBox::critical(dialogParent, tr("Server Full"),
+ tr("The server has reached its maximum user capacity, please check back later."));
+ break;
+ }
+
+ default: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Unknown login error: %1").arg(r) +
+ tr("\nThis usually means that your client version is out of date, and the server "
+ "sent a reply your client doesn't understand."));
+ break;
+ }
+ }
+
+ // Re-open the connect dialog after any handled error
+ connectToServer();
+}
+
+void ConnectionController::onRegisterError(int r, QString reasonStr, quint32 endTime)
+{
+ switch (static_cast(r)) {
+ case Response::RespRegistrationDisabled: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("Registration is currently disabled on this server"));
+ break;
+ }
+ case Response::RespUserAlreadyExists: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("There is already an existing account with the same user name."));
+ break;
+ }
+ case Response::RespEmailRequiredToRegister: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("It's mandatory to specify a valid email address when registering."));
+ break;
+ }
+ case Response::RespEmailBlackListed: {
+ if (reasonStr.isEmpty()) {
+ reasonStr =
+ "The email address provider used during registration has been blocked from use on this server.";
+ }
+ QMessageBox::critical(dialogParent, tr("Registration denied"), reasonStr);
+ break;
+ }
+ case Response::RespTooManyRequests: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"),
+ tr("It appears you are attempting to register a new account on this server yet you "
+ "already have an account registered with the email provided. This server "
+ "restricts the number of accounts a user can register per address. Please "
+ "contact the server operator for further assistance or to obtain your "
+ "credential information."));
+ break;
+ }
+ case Response::RespPasswordTooShort: {
+ QMessageBox::critical(dialogParent, tr("Registration denied"), tr("Password too short."));
+ break;
+ }
+ case Response::RespUserIsBanned: {
+ QString bannedStr =
+ endTime ? tr("You are banned until %1.").arg(QDateTime::fromSecsSinceEpoch(endTime).toString())
+ : tr("You are banned indefinitely.");
+ if (!reasonStr.isEmpty()) {
+ bannedStr.append("\n\n" + reasonStr);
+ }
+ QMessageBox::critical(dialogParent, tr("Error"), bannedStr);
+ break;
+ }
+ case Response::RespUsernameInvalid: {
+ QMessageBox::critical(dialogParent, tr("Error"), extractInvalidUsernameMessage(reasonStr));
+ break;
+ }
+ case Response::RespRegistrationFailed: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Registration failed for a technical problem on the server."));
+ break;
+ }
+ case Response::RespNotConnected: {
+ QMessageBox::critical(dialogParent, tr("Error"), tr("The connection to the server has been lost."));
+ break;
+ }
+ default: {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Unknown registration error: %1").arg(r) +
+ tr("\nThis usually means that your client version is out of date, and the server "
+ "sent a reply your client doesn't understand."));
+ break;
+ }
+ }
+
+ registerToServer();
+}
+
+void ConnectionController::onActivateError()
+{
+ QMessageBox::critical(dialogParent, tr("Error"), tr("Account activation failed"));
+ remoteClient->disconnectFromServer();
+ connectToServer();
+}
+
+void ConnectionController::onSocketError(const QString &errorStr)
+{
+ QMessageBox::critical(dialogParent, tr("Error"), tr("Socket error: %1").arg(errorStr));
+ connectToServer();
+}
+
+void ConnectionController::onServerTimeout()
+{
+ QMessageBox::critical(dialogParent, tr("Error"), tr("Server timeout"));
+ connectToServer();
+}
+
+void ConnectionController::onProtocolVersionMismatch(int localVersion, int remoteVersion)
+{
+ if (localVersion > remoteVersion) {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("You are trying to connect to an obsolete server. Please downgrade your Cockatrice "
+ "version or connect to a suitable server.\n"
+ "Local version is %1, remote version is %2.")
+ .arg(localVersion)
+ .arg(remoteVersion));
+ } else {
+ QMessageBox::critical(dialogParent, tr("Error"),
+ tr("Your Cockatrice client is obsolete. Please update your Cockatrice version.\n"
+ "Local version is %1, remote version is %2.")
+ .arg(localVersion)
+ .arg(remoteVersion));
+ }
+}
+
+void ConnectionController::onRegisterAccepted()
+{
+ QMessageBox::information(dialogParent, tr("Success"), tr("Registration accepted.\nWill now login."));
+}
+
+void ConnectionController::onRegisterAcceptedNeedsActivate()
+{
+ // Server will send activation email; nothing to display here.
+}
+
+void ConnectionController::onActivateAccepted()
+{
+ QMessageBox::information(dialogParent, tr("Success"), tr("Account activation accepted.\nWill now login."));
+}
+
+void ConnectionController::onNotifyUserAboutUpdate()
+{
+ QMessageBox::information(
+ dialogParent, tr("Information"),
+ tr("This server supports additional features that your client doesn't have.\n"
+ "This is most likely not a problem, but this message might mean there is a new version of "
+ "Cockatrice available or this server is running a custom or pre-release version.\n\n"
+ "To update your client, go to Help -> Check for Updates."));
+}
+
+void ConnectionController::onForgotPasswordSuccess()
+{
+ QMessageBox::information(
+ dialogParent, tr("Reset Password"),
+ tr("Your password has been reset successfully, you can now log in using the new credentials."));
+ SettingsCache::instance().servers().setFPHostName("");
+ SettingsCache::instance().servers().setFPPort("");
+ SettingsCache::instance().servers().setFPPlayerName("");
+}
+
+void ConnectionController::onForgotPasswordError()
+{
+ QMessageBox::warning(
+ dialogParent, tr("Reset Password"),
+ tr("Failed to reset user account password, please contact the server operator to reset your password."));
+ SettingsCache::instance().servers().setFPHostName("");
+ SettingsCache::instance().servers().setFPPort("");
+ SettingsCache::instance().servers().setFPPlayerName("");
+}
+
+void ConnectionController::onPromptForgotPasswordReset()
+{
+ QMessageBox::information(dialogParent, tr("Reset Password"),
+ tr("Activation request received, please check your email for an activation token."));
+ DlgForgotPasswordReset dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->submitForgotPasswordResetToServer(dlg.getHost(), static_cast(dlg.getPort()),
+ dlg.getPlayerName(), dlg.getToken(), dlg.getPassword());
+ }
+}
+
+void ConnectionController::onPromptForgotPasswordChallenge()
+{
+ DlgForgotPasswordChallenge dlg(dialogParent);
+ if (dlg.exec()) {
+ remoteClient->submitForgotPasswordChallengeToServer(dlg.getHost(), static_cast(dlg.getPort()),
+ dlg.getPlayerName(), dlg.getEmail());
+ }
+}
+
+void ConnectionController::updateWindowTitle()
+{
+ const QString appName = QStringLiteral("Cockatrice");
+ QString title;
+
+ switch (remoteClient->getStatus()) {
+ case StatusConnecting: {
+ title = appName + " - " + tr("Connecting to %1...").arg(remoteClient->peerName());
+ break;
+ }
+ case StatusRegistering: {
+ title = appName + " - " +
+ tr("Registering to %1 as %2...").arg(remoteClient->peerName()).arg(remoteClient->getUserName());
+ break;
+ }
+ case StatusDisconnected: {
+ title = appName + " - " + tr("Disconnected");
+ break;
+ }
+ case StatusLoggingIn: {
+ title = appName + " - " + tr("Connected, logging in at %1").arg(remoteClient->peerName());
+ break;
+ }
+ case StatusLoggedIn: {
+ title = remoteClient->getUserName() + "@" + remoteClient->peerName();
+ break;
+ }
+ case StatusRequestingForgotPassword:
+ case StatusSubmitForgotPasswordChallenge:
+ case StatusSubmitForgotPasswordReset:
+ title = appName + " - " +
+ tr("Requesting forgotten password to %1 as %2...")
+ .arg(remoteClient->peerName())
+ .arg(remoteClient->getUserName());
+ break;
+ default:
+ title = appName;
+ }
+
+ emit windowTitleChanged(title);
+}
+
+// static
+QString ConnectionController::extractInvalidUsernameMessage(QString &in)
+{
+ QString out = tr("Invalid username.") + " ";
+ QStringList rules = in.split(QChar('|'));
+
+ if (rules.size() == 7 || rules.size() == 9) {
+ out += tr("Your username must respect these rules:") + "
" + tr("can contain the following punctuation: %1").arg(rules.at(6).toHtmlEscaped()) + "
";
+ }
+
+ out += "
" +
+ tr("first character can %1 be a punctuation mark").arg((rules.at(5).toInt() > 0) ? "" : tr("NOT")) +
+ "
";
+
+ if (rules.size() == 9) {
+ if (rules.at(7).size() > 0) {
+ QString words = rules.at(7).toHtmlEscaped();
+ if (words.startsWith("\n")) {
+ out += tr("no unacceptable language as specified by these server rules:",
+ "note that the following lines will not be translated");
+ for (QString &line : words.split("\n", Qt::SkipEmptyParts)) {
+ out += "
" + line + "
";
+ }
+ } else {
+ out += "
" + tr("can not contain any of the following words: %1").arg(words) + "
";
+ }
+ }
+
+ if (rules.at(8).size() > 0) {
+ out += "
" +
+ tr("can not match any of the following expressions: %1").arg(rules.at(8).toHtmlEscaped()) +
+ "