Compare commits

..

2 commits

Author SHA1 Message Date
Lukas Brübach
dd1f0d3eec [Replay] Build a compact event history index
Turn replay protocol events into short localized summaries with an actor, badge, and time so the history UI can render them without retaining the replay or duplicating the chat log.
2026-10-20 22:15:24 +02:00
Lukas Brübach
b863a53b35 [Replay] Seek to the exact time of an event
Timeline seeks only expose whole timer intervals, so jumping to an event could leave that event unprocessed until the next tick. Add an event-time seek that also applies events at the target timestamp.
2026-10-20 22:15:24 +02:00
391 changed files with 3592 additions and 9993 deletions

View file

@ -459,206 +459,6 @@ if [[ $MAKE_COVERAGE ]]; then
echo "::endgroup::"
fi
if [[ $RUNNER_OS == Windows && ! $MAKE_NO_CLIENT ]]; then
echo "::group::Probe the installer's holder lookup"
# The working directory is still the build dir here: the package steps below are what
# leave and re-enter it, and that is what made a $BUILD_DIR-prefixed path name
# build/build. Both paths are relative to the dir the probe was built into.
probe="./installer-holder-probe.exe"
if [[ ! -f $probe ]]; then
echo "::error file=$0::installer-holder-probe.exe was not built"
exit 1
fi
# The probe exists to name a holder that a process-and-module scan cannot see: one
# that has a file open without having loaded it. Holding a temporary DLL with
# FileShare.None is exactly that case, so this asserts the reason the probe was
# added instead of only that the Restart Manager answers at all. It needs no NSIS
# and no packaging, so it runs on every Windows build that gets this far.
# The holder is a process of its own: the probe never names the process that started it,
# which is what keeps an in-place uninstaller from being named as a holder of the
# directory it is emptying. The same DLL held by the script that starts the probe
# therefore has to come back as nothing, and that is the second case below.
probe_check="./holder-probe-check.ps1"
cat >"$probe_check" <<'POWERSHELL'
$ErrorActionPreference = "Stop"
$dir = Join-Path ([IO.Path]::GetTempPath()) "cockatrice-holder-probe-$PID"
Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force $dir | Out-Null
$probe = (Resolve-Path -LiteralPath $env:PROBE_EXE).Path
function Invoke-Probe([string] $guardDir) {
$start = New-Object System.Diagnostics.ProcessStartInfo
$start.FileName = $probe
$start.UseShellExecute = $false
$start.RedirectStandardOutput = $true
$start.EnvironmentVariables["COCKATRICE_GUARD_DIR"] = $guardDir
$process = [System.Diagnostics.Process]::Start($start)
$read = $process.StandardOutput.ReadToEndAsync()
if (-not $process.WaitForExit(20000)) {
try { $process.Kill() } catch { }
throw "the holder probe did not finish within 20 s for $guardDir"
}
$output = $read.Result
$code = $process.ExitCode
$process.Dispose()
return @{ Code = $code; Output = $output }
}
function Wait-UntilLocked([string] $path) {
$deadline = (Get-Date).AddSeconds(15)
while ((Get-Date) -lt $deadline) {
try {
$open = [IO.File]::Open($path, "Open", "ReadWrite", "None")
$open.Dispose()
} catch {
return
}
Start-Sleep -Milliseconds 100
}
throw "the holder never locked $path"
}
try {
$file = Join-Path $dir "locked.dll"
[IO.File]::WriteAllText($file, "x")
# The holder is a process of its own, not this script: the probe never names the process
# that started it (the case below asserts that), so a file held here would be left out on
# purpose and the check would pass for the wrong reason.
$holderScript = Join-Path $dir "hold.ps1"
Set-Content -LiteralPath $holderScript -Value @'
param([string] $Path)
$handle = [IO.File]::Open($Path, "Open", "ReadWrite", "None")
Start-Sleep -Seconds 120
'@
$powershell = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
$holder = Start-Process -FilePath $powershell -PassThru -ArgumentList @(
"-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$holderScript`"", "-Path", "`"$file`"")
try {
Wait-UntilLocked $file
$locked = Invoke-Probe $dir
if ($locked.Code -ne 1) { throw "expected exit 1 for a locked file, got $($locked.Code) : $($locked.Output)" }
if ($locked.Output -notmatch "pid\s+$($holder.Id)\b") {
throw "the lookup did not name the process holding the file: $($locked.Output)"
}
} finally {
try { $holder.Kill() } catch { }
try { $holder.WaitForExit(5000) | Out-Null } catch { }
}
# The probe is started by the installer, or by an uninstaller that runs in place from the
# directory it is emptying, so the asking process holds files there itself. Naming it would
# ask the user to close the program that is asking - a question neither the close nor the
# end offer can answer, so the guard repeated it until the update was cancelled. A holder
# that is only the asking process has to come back as nothing.
$handle = [IO.File]::Open($file, "Open", "ReadWrite", "None")
try {
$self = Invoke-Probe $dir
} finally {
$handle.Dispose()
}
if ($self.Code -ne 0) { throw "expected exit 0 when the asking process is the only holder, got $($self.Code) : $($self.Output)" }
if ($self.Output -match "pid\s+$PID\b") { throw "the lookup named the process that started it: $($self.Output)" }
# A payload executable that refuses to be replaced is running, whether or not this probe
# may name it: the Restart Manager does not report every mapping, and the image path of a
# process under another account cannot be read from here, so both process lookups can
# come back empty while the program is still there. Held here by the asking process, so
# that it cannot be named - the answer has to be "could not name it", never "nothing is
# holding it", which is what sent an update into an uninstall that could only fail
# halfway. Exit 3 is the code the guard answers by asking the user to close it and retry.
$image = Join-Path $dir "oracle.exe"
[IO.File]::WriteAllText($image, "x")
$handle = [IO.File]::Open($image, "Open", "ReadWrite", "None")
try {
$unnameable = Invoke-Probe $dir
} finally {
$handle.Dispose()
}
if ($unnameable.Code -ne 3) {
throw "expected exit 3 for a payload executable that is in use but cannot be named, got $($unnameable.Code) : $($unnameable.Output)"
}
if ($unnameable.Output -notmatch "oracle\.exe is in use") {
throw "the answer did not name the executable it could not replace: $($unnameable.Output)"
}
$empty = Join-Path $dir "empty"
New-Item -ItemType Directory -Force $empty | Out-Null
$nothing = Invoke-Probe $empty
if ($nothing.Code -ne 0) { throw "expected exit 0 for a directory with nothing open, got $($nothing.Code) : $($nothing.Output)" }
# A process running from the directory is a holder even when it has no file open: an
# instance holds its payload as mapped images, not as handles, and that is the state
# the guard used to miss - it passed, the purge failed, and the install ended in a
# warning with no offer to close and try again. The image is a copy of a native
# Windows program, so it starts without its usual directory next to it, and it carries
# one of the payload's own names because that is what the lookup names.
$copy = Join-Path $dir "cockatrice.exe"
Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $copy
$running = Start-Process -FilePath $copy -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru
try {
Start-Sleep -Milliseconds 1500
$byImage = Invoke-Probe $dir
} finally {
try { $running.Kill() } catch { }
# Wait for the image to be released, or the cleanup below leaves the copy behind.
try { $running.WaitForExit(5000) | Out-Null } catch { }
}
if ($byImage.Code -ne 1) { throw "expected exit 1 for a process running from the directory, got $($byImage.Code) : $($byImage.Output)" }
if ($byImage.Output -notmatch "cockatrice\.exe \(pid\s+$($running.Id)") {
throw "the lookup did not name the process running from the directory: $($byImage.Output)"
}
# A program in a folder below the directory is not an instance of the installation, even
# when it carries a payload program's name: the install directory can be a folder the
# user keeps other applications in, each in its own subfolder, and naming one of those
# asked for a close the probe never sends and an end it refuses to perform, so the guard
# could only abort the update. A program that really does hold a payload file is still
# named, by the Restart Manager lookup exercised above.
$nested = Join-Path $dir "other"
New-Item -ItemType Directory -Force $nested | Out-Null
$nestedImage = Join-Path $nested "cockatrice.exe"
Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $nestedImage
$nestedRunning = Start-Process -FilePath $nestedImage -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru
try {
Start-Sleep -Milliseconds 1500
$inSubfolder = Invoke-Probe $dir
} finally {
try { $nestedRunning.Kill() } catch { }
try { $nestedRunning.WaitForExit(5000) | Out-Null } catch { }
}
if ($inSubfolder.Code -ne 0) {
throw "expected exit 0 for a program in a folder below the directory, got $($inSubfolder.Code) : $($inSubfolder.Output)"
}
# A program the user keeps in the directory itself is not a payload program either: the
# guard ends only what the payload installs, and naming anything else asks for an answer
# that cannot free a single file.
$foreign = Join-Path $dir "tool.exe"
Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $foreign
$foreignRunning = Start-Process -FilePath $foreign -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru
try {
Start-Sleep -Milliseconds 1500
$ignored = Invoke-Probe $dir
} finally {
try { $foreignRunning.Kill() } catch { }
try { $foreignRunning.WaitForExit(5000) | Out-Null } catch { }
}
if ($ignored.Code -ne 0) {
throw "expected exit 0 for a program the payload does not install, got $($ignored.Code) : $($ignored.Output)"
}
Write-Host "holder lookup named pid $($holder.Id) for a file another process holds, named nothing for a file only the asking process holds, reported a payload executable it could not name as in use instead of free, named nothing for an empty directory, named pid $($running.Id) for a payload program running from the directory, and named nothing for a payload program in a folder below it or for a program the payload does not install"
} finally {
Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue
}
POWERSHELL
if ! PROBE_EXE="$probe" powershell -NoProfile -File "$probe_check"; then
rm -f "$probe_check"
exit 1
fi
rm -f "$probe_check"
echo "::endgroup::"
fi
if [[ $MAKE_INSTALL ]]; then
echo "::group::Install"
cmake --build . --target install --config "$BUILDTYPE"

View file

@ -584,17 +584,11 @@ jobs:
# job. A scope nobody has written to yet therefore leaves the directory missing
# and fails the job, which is exactly the state every scope starts in. Make the
# directory up front so an empty scope is a cache miss instead of an error.
# always() covers the cache step itself failing: the action's post save still runs
# then, and it should report the real error instead of ENOENT. It cannot rescue a
# failure before the cache step, since a post step only runs for an action whose
# main step did. The fallback keeps this guard from failing when the cache step was
# skipped and left no path output.
- name: "Create vcpkg cache directory"
if: always()
shell: bash
env:
CACHE_DIR: '${{ steps.vcpkg-cache.outputs.path }}'
run: mkdir -p "${CACHE_DIR:-$GITHUB_WORKSPACE/.vcpkg-cache}"
run: mkdir -p "$CACHE_DIR"
# Uses environment variables, see compile.sh for more details
- name: "Build Cockatrice"

View file

@ -250,15 +250,6 @@ message(STATUS "Update Translations: ${UPDATE_TRANSLATIONS}")
include(FindQtRuntime)
# Reject APIs that were deprecated before the oldest Qt release we support
# (6.4). Qt 6.5 renamed the switch, so use whichever spelling the found
# version understands while still compiling everywhere.
if(Qt6_VERSION VERSION_GREATER_EQUAL 6.5)
add_compile_definitions(QT_DISABLE_DEPRECATED_UP_TO=0x060400)
else()
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x060400)
endif()
set(CMAKE_AUTOMOC TRUE)
# Find other needed libraries
@ -429,9 +420,6 @@ if(WITH_ORACLE OR WITH_CLIENT)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_models ${CMAKE_BINARY_DIR}/libcockatrice_models)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_filters ${CMAKE_BINARY_DIR}/libcockatrice_filters)
endif()
if(WITH_CLIENT)
add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_game ${CMAKE_BINARY_DIR}/libcockatrice_game)
endif()
if(WITH_SERVER)
add_subdirectory(servatrice)
@ -443,13 +431,6 @@ if(WITH_CLIENT)
set(CPACK_INSTALL_CMAKE_PROJECTS "Cockatrice;Cockatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
if(WIN32 AND WITH_CLIENT)
# Runs from the installer's $PLUGINSDIR when a purge fails, and is never installed
# (see LogProcessesUsingInstallDir in cmake/NSIS.template.in), so it has no install
# rules and is not part of the client's staged tree.
add_subdirectory(${CMAKE_SOURCE_DIR}/tools/installer-holder-probe ${CMAKE_BINARY_DIR}/tools/installer-holder-probe)
endif()
if(WITH_ORACLE)
add_subdirectory(oracle)
set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
@ -462,5 +443,5 @@ endif()
if(Qt6_FOUND AND Qt6_VERSION_MINOR GREATER_EQUAL 3)
# Qt6.3+ requires project finalization to support translations
qt_finalize_project()
qt6_finalize_project()
endif()

View file

@ -15,10 +15,9 @@
---
<br><pre><p align='center'>
<b>To get started with Cockatrice &#8674; [view our webpage](https://cockatrice.github.io/)</b>
<b>To get support, or suggest changes to the app &#8674; [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))</b><br>
<b>To learn about the app and its code &#8674; [read our code documentation](https://cockatrice.github.io/docs/)</b>
<b>To help with development &#8674; learn [how to contribute](#contribute)</b>
<b>To get started with Cockatrice &#8674; [view our webpage](https://cockatrice.github.io/)</b><br>
<b>To get support, or suggest changes to the app &#8674; [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))</b>
<b>To help with development &#8674; learn [how to contribute](#contribute-)</b>
</pre><p><br>
@ -84,7 +83,7 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p
### Code
To commit code to the project, please review our [Contribution Guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.<br>
To contribute code to the project, please review our [guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.<br>
We maintain two tags for contributors to easier find issues to potentially work on:
- [![Good first issue](https://img.shields.io/github/issues/cockatrice/cockatrice/Good%20First%20Issue)](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Good%20first%20issue%22%20)<br>
Issues tagged in this way provide a simple way to get started. They don't require much experience to be worked on.
@ -96,7 +95,7 @@ We'll happily advice on how best to implement a feature, or we can show you wher
You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code).
### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Arelease+event%3Aworkflow_dispatch)
### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush)
There are various places where useful information for different needs are maintained:
- [Official Code Documentation](https://cockatrice.github.io/docs/)
@ -105,8 +104,7 @@ There are various places where useful information for different needs are mainta
- [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file`
- [Official CONTRIBUTING](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md)
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation.
We encourage everybody to improve the documentation by suggesting edits based on this guide.
Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide.
### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/)

File diff suppressed because it is too large Load diff

View file

@ -57,29 +57,6 @@ try {
}
}
# --- What was holding the files, read from the log as well -------------------
# The guard and the close attempt each log one line naming every holder and what
# happened to it. Carrying the last of each as event data keeps an abort caused by
# a running instance actionable without opening the attachment. Kept out of the
# message so the issue still groups on the reason alone.
$Holders = ''
$CloseResult = ''
$EndResult = ''
if (Test-Path -LiteralPath $LogPath) {
$holderLine = Select-String -LiteralPath $LogPath -Pattern 'guard: still in use by (.*)$' | Select-Object -Last 1
if ($holderLine) {
$Holders = $holderLine.Matches[0].Groups[1].Value
}
$closeLine = Select-String -LiteralPath $LogPath -Pattern '(close-holders: .*)$' | Select-Object -Last 1
if ($closeLine) {
$CloseResult = $closeLine.Matches[0].Groups[1].Value
}
$endLine = Select-String -LiteralPath $LogPath -Pattern '(end-holders: .*)$' | Select-Object -Last 1
if ($endLine) {
$EndResult = $endLine.Matches[0].Groups[1].Value
}
}
# --- Event ---------------------------------------------------------------
$eventId = [guid]::NewGuid().ToString('N')
$now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
@ -98,9 +75,6 @@ try {
extra = @{
reason = $Reason
os = [System.Environment]::OSVersion.VersionString
holders = $Holders
close_result = $CloseResult
end_result = $EndResult
}
}
$eventJson = $sentryEvent | ConvertTo-Json -Compress -Depth 6

View file

@ -25,7 +25,9 @@ set(cockatrice_SOURCES
src/client/settings/shortcut_treeview.cpp
src/client/settings/shortcuts_settings.cpp
src/interface/deck_loader/card_node_function.cpp
src/interface/deck_loader/deck_file_format.cpp
src/interface/deck_loader/deck_loader.cpp
src/interface/deck_loader/loaded_deck.cpp
src/interface/widgets/dialogs/dlg_connect.cpp
src/interface/widgets/dialogs/dlg_convert_deck_to_cod_format.cpp
src/interface/widgets/dialogs/dlg_create_game.cpp
@ -69,27 +71,36 @@ set(cockatrice_SOURCES
src/filters/filter_builder.cpp
src/filters/filter_tree_model.cpp
src/filters/syntax_help.cpp
src/game/abstract_game.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_registry.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/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_annotation.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_graphics/game_scene.cpp
src/game/game_state.cpp
src/game_graphics/game_view.cpp
src/game_graphics/hand_counter.cpp
src/game_graphics/log/message_log_widget.cpp
src/game/phase.cpp
src/game_graphics/phases_toolbar.cpp
src/game_graphics/player/menu/card_menu.cpp
src/game_graphics/player/menu/custom_zone_menu.cpp
@ -107,11 +118,23 @@ set(cockatrice_SOURCES
src/game_graphics/tally/stats_tally.cpp
src/game_graphics/tally/subtype_tally.cpp
src/game_graphics/tally/tally.cpp
src/game/player/player_actions.cpp
src/game_graphics/player/player_area.cpp
src/game_graphics/player/player_dialogs.cpp
src/game/player/player_event_handler.cpp
src/game_graphics/player/player_graphics_item.cpp
src/game/player/player_info.cpp
src/game_graphics/player/player_list_widget.cpp
src/game/player/player_logic.cpp
src/game/player/player_manager.cpp
src/game_graphics/player/player_target.cpp
src/game/replay.cpp
src/game/zones/card_zone_logic.cpp
src/game/zones/hand_zone_logic.cpp
src/game/zones/pile_zone_logic.cpp
src/game/zones/stack_zone_logic.cpp
src/game/zones/table_zone_logic.cpp
src/game/zones/view_zone_logic.cpp
src/game_graphics/zones/card_zone.cpp
src/game_graphics/zones/hand_zone.cpp
src/game_graphics/zones/pile_zone.cpp
@ -323,7 +346,6 @@ set(cockatrice_SOURCES
src/interface/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
src/interface/window_main.cpp
src/installer_close_server.cpp
src/main.cpp
src/single_instance_manager.cpp
src/interface/widgets/tabs/abstract_tab_deck_editor.cpp
@ -429,7 +451,6 @@ set(cockatrice_SOURCES
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/installer_close_server.h
src/single_instance_manager.h
src/client/url_scheme_event_filter.h
src/interface/intents/intent_connect_to_server.cpp
@ -491,7 +512,7 @@ if(APPLE)
set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns)
endif(APPLE)
qt_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
# Declare path variables
set(ICONDIR
@ -512,7 +533,7 @@ set(COCKATRICE_MAC_QM_INSTALL_DIR "cockatrice.app/Contents/Resources/translation
set(COCKATRICE_UNIX_QM_INSTALL_DIR "share/cockatrice/translations")
set(COCKATRICE_WIN32_QM_INSTALL_DIR "translations")
qt_add_executable(
qt6_add_executable(
cockatrice
WIN32
MACOSX_BUNDLE
@ -524,7 +545,7 @@ qt_add_executable(
target_precompile_headers(cockatrice PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtwidgets_pch.h")
qt_add_shaders(
qt6_add_shaders(
cockatrice
"onboarding_shaders"
PREFIX
@ -536,7 +557,7 @@ qt_add_shaders(
src/interface/widgets/onboarding/shaders/brand_plate.frag
)
qt_add_resources(
qt6_add_resources(
cockatrice
"onboarding_qml"
PREFIX
@ -552,7 +573,6 @@ target_link_libraries(
PUBLIC libcockatrice_card
libcockatrice_deck_list
libcockatrice_filters
libcockatrice_game
libcockatrice_utility
libcockatrice_network
libcockatrice_models
@ -769,7 +789,7 @@ endif()
if(Qt6LinguistTools_FOUND)
#Qt6 Translations happen after the executable is built up
if(UPDATE_TRANSLATIONS)
qt_add_translations(
qt6_add_translations(
cockatrice
TS_FILES
${cockatrice_TS}
@ -779,7 +799,7 @@ if(Qt6LinguistTools_FOUND)
cockatrice_QM
)
else()
qt_add_translations(cockatrice TS_FILES ${cockatrice_TS} QM_FILES_OUTPUT_VARIABLE cockatrice_QM)
qt6_add_translations(cockatrice TS_FILES ${cockatrice_TS} QM_FILES_OUTPUT_VARIABLE cockatrice_QM)
endif()
if(UNIX)
@ -805,4 +825,4 @@ if(Qt6LinguistTools_FOUND)
endforeach()
endif()
qt_finalize_target(cockatrice)
qt6_finalize_target(cockatrice)

View file

@ -14,7 +14,6 @@
#include <QObject>
#include <QPoint>
#include <QVBoxLayout>
#include <libcockatrice/utility/qt_utils.h>
#include <qnamespace.h>
LatencyStatusWidget::LatencyStatusWidget(QWidget *parent) : QWidget(parent)
@ -25,7 +24,9 @@ LatencyStatusWidget::LatencyStatusWidget(QWidget *parent) : QWidget(parent)
latencyGraph = new LatencyGraphWidget(this);
latencyGraph->setFixedSize(90, 14);
auto *layout = QtUtils::makeLayout<QHBoxLayout>(this, 4);
auto *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(4);
layout->addWidget(latencyGraph);
layout->addWidget(pingLabel);

View file

@ -24,7 +24,6 @@
#include <libcockatrice/network/client/remote/remote_client.h>
#include <libcockatrice/protocol/pb/response.pb.h>
#include <libcockatrice/settings/servers_settings.h>
#include <libcockatrice/utility/qt_utils.h>
#include <qnamespace.h>
class QWidget;
@ -244,27 +243,17 @@ void ConnectionController::onLoginError(int r,
{
switch (static_cast<Response::ResponseCode>(r)) {
case Response::RespClientUpdateRequired: {
QString formatted = "Missing Features: ";
for (int i = 0; i < missingFeatures.size(); ++i) {
formatted.append(QString("\n %1").arg(QChar(0x2022)) + " " + missingFeatures.value(i));
}
QMessageBox msgBox(dialogParent);
msgBox.setIcon(QMessageBox::Critical);
msgBox.setWindowTitle(tr("Failed Login"));
if (missingFeatures.isEmpty()) {
// Without a feature list this is either a login the client stopped itself or a
// strict server asking for a password; the reason string already says which and
// what to do. No client update can fix any of those causes, so show it instead of
// the update advice.
msgBox.setText(reasonStr.isEmpty() ? tr("The login did not complete.") : reasonStr);
} else {
QString formatted = tr("Missing Features:");
for (const QString &feature : missingFeatures) {
formatted.append(QString("\n %1 ").arg(QChar(0x2022)) + feature);
}
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.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;
}
@ -311,8 +300,9 @@ void ConnectionController::onLoginError(int r,
}
case Response::RespRegistrationRequired: {
if (QtUtils::confirm(dialogParent, tr("Error"),
tr("This server requires user registration. Do you want to register now?"))) {
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
@ -527,38 +517,14 @@ void ConnectionController::onForgotPasswordSuccess()
SettingsCache::instance().servers().setFPPlayerName("");
}
void ConnectionController::onForgotPasswordError(int responseCode)
void ConnectionController::onForgotPasswordError()
{
// Rate limiting is retryable, so it must not clear the prefill.
bool keepPrefill = false;
QString message;
switch (static_cast<Response::ResponseCode>(responseCode)) {
case Response::RespTooManyRequests:
// Retryable: keep the host/port/user prefill so the user can try again. The window
// is server-configured (an hour by default), so do not promise a shorter wait.
keepPrefill = true;
message = tr("Too many password reset attempts from your address. Please wait before "
"requesting a new reset.");
break;
case Response::RespFunctionNotAllowed:
// Also returned for an unknown user, a banned user, and an expired token or
// challenge, so the message must not claim a single cause.
message = tr("The server did not accept the reset request. Check the user name, or contact "
"the server operator to reset your password.");
break;
default:
message = tr("Failed to reset user account password, please contact the server operator to reset your "
"password.");
break;
}
QMessageBox::warning(dialogParent, tr("Reset Password"), message);
if (!keepPrefill) {
SettingsCache::instance().servers().setFPHostName("");
SettingsCache::instance().servers().setFPPort("");
SettingsCache::instance().servers().setFPPlayerName("");
}
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()

View file

@ -87,7 +87,7 @@ private slots:
// Forgot-password flow
void onForgotPasswordSuccess();
void onForgotPasswordError(int responseCode);
void onForgotPasswordError();
void onPromptForgotPasswordReset();
void onPromptForgotPasswordChallenge();

View file

@ -20,12 +20,11 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <libcockatrice/network/network_manager.h>
#include <version_string.h>
DeckStatsInterface::DeckStatsInterface(QObject *parent) : QObject(parent)
{
manager = NetworkManagerFactory::create(this);
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished);
}

View file

@ -26,12 +26,11 @@
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/tree/deck_list_card_node.h>
#include <libcockatrice/network/network_manager.h>
#include <version_string.h>
TappedOutInterface::TappedOutInterface(QObject *parent) : QObject(parent)
{
manager = NetworkManagerFactory::create(this);
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
}

View file

@ -27,7 +27,6 @@
#include <functional>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/network/network_manager.h>
#include <libcockatrice/settings/download_settings.h>
#include <libcockatrice/settings/paths_settings.h>
#include <tuple>
@ -57,7 +56,7 @@ void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool sav
void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
{
auto *nam = NetworkManagerFactory::create(this);
auto *nam = new QNetworkAccessManager(this);
auto request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
QNetworkReply *reply = nam->get(request);

View file

@ -20,7 +20,6 @@
#include <QVariant>
#include <QVariantMap>
#include <QtGlobal>
#include <libcockatrice/network/network_manager.h>
#include <optional>
#include <qnamespace.h>
#include <qprocessordetection.h>
@ -63,7 +62,7 @@ std::optional<int> getProductVersionMajor()
} // namespace
ReleaseChannel::ReleaseChannel() : netMan(NetworkManagerFactory::create(this)), response(nullptr), lastRelease(nullptr)
ReleaseChannel::ReleaseChannel() : netMan(new QNetworkAccessManager(this)), response(nullptr), lastRelease(nullptr)
{
}

View file

@ -9,11 +9,10 @@
#include <QNetworkRequest>
#include <QUrl>
#include <QVariant>
#include <libcockatrice/network/network_manager.h>
UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr)
{
netMan = NetworkManagerFactory::create(this);
netMan = new QNetworkAccessManager(this);
}
void UpdateDownloader::beginDownload(QUrl downloadUrl)

View file

@ -19,6 +19,7 @@ inline Q_LOGGING_CATEGORY(SettingsCacheLog, "settings_cache");
class ReleaseChannel;
#define DEFAULT_LANG_NAME "English"
#define CLIENT_INFO_NOT_SET "notset"
#define DEFAULT_FONT_SIZE 12

View file

@ -2,18 +2,13 @@
#define COCKATRICE_SETTINGS_CARD_PREFERENCE_PROVIDER_H
#include "../../client/settings/cache_settings.h"
#include <QObject>
#include <libcockatrice/interfaces/interface_card_preference_provider.h>
#include <libcockatrice/settings/card_override_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
class SettingsCardPreferenceProvider : public QObject, public ICardPreferenceProvider
class SettingsCardPreferenceProvider : public ICardPreferenceProvider
{
public:
explicit SettingsCardPreferenceProvider(QObject *parent = nullptr) : QObject(parent)
{
}
[[nodiscard]] QString getCardPreferenceOverride(const QString &cardName) const override
{
return SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardName);

View file

@ -1,5 +1,6 @@
#include "deck_filter_string.h"
#include "../interface/deck_loader/loaded_deck.h"
#include "libcockatrice/card/card_localization.h"
#include "libcockatrice/card/database/card_database_querier.h"
#include "libcockatrice/deck_list/deck_list.h"
@ -14,7 +15,6 @@
#include <algorithm>
#include <any>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/loaded_deck.h>
#include <libcockatrice/filters/filter_string.h>
#include <libcockatrice/utility/peglib.h>
#include <mutex>

View file

@ -11,7 +11,6 @@
#include <QString>
#include <functional>
#include <libcockatrice/card/card_localization.h>
#include <libcockatrice/deck_list/loaded_deck.h>
struct CardSearchLanguage;
struct LoadedDeck;

View file

@ -13,7 +13,6 @@
#include <QVariant>
#include <QtGlobal>
#include <libcockatrice/filters/filter_card.h>
#include <libcockatrice/utility/qt_utils.h>
#include <stddef.h>
FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
@ -40,8 +39,9 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent)
edit->setClearButtonEnabled(true);
edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QGridLayout *layout = QtUtils::makeLayout<QGridLayout>();
QGridLayout *layout = new QGridLayout;
layout->setObjectName("layout");
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(typeCombo, 0, 0, 1, 2);
layout->addWidget(filterCombo, 0, 2, 1, 2);

View file

@ -18,6 +18,8 @@ AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent)
{
gameMetaInfo = new GameMetaInfo(this);
gameEventHandler = new GameEventHandler(this);
activeCard = nullptr;
}
bool AbstractGame::isHost() const
@ -46,7 +48,12 @@ void AbstractGame::loadReplay(const GameReplay *replay)
gameMetaInfo->setSpectatorsOmniscient(true);
}
CardState *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const
void AbstractGame::setActiveCard(CardItem *card)
{
activeCard = card;
}
CardItem *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const
{
PlayerLogic *player = playerManager->getPlayer(playerId);
if (!player) {

View file

@ -11,14 +11,14 @@
#include <QString>
#include <qtmetamacros.h>
class CardItem;
class AbstractClient;
class CardState;
class GameEventHandler;
class GameMetaInfo;
class GameReplay;
class GameState;
class PlayerManager;
class CardState;
class AbstractGame : public QObject
{
Q_OBJECT
@ -30,6 +30,7 @@ public:
GameState *gameState;
GameEventHandler *gameEventHandler;
PlayerManager *playerManager;
CardItem *activeCard;
GameMetaInfo *getGameMetaInfo()
{
@ -57,7 +58,13 @@ public:
void loadReplay(const GameReplay *replay);
CardState *getCard(int playerId, const QString &zoneName, int cardId) const;
CardItem *getCard(int playerId, const QString &zoneName, int cardId) const;
void setActiveCard(CardItem *card);
CardItem *getActiveCard() const
{
return activeCard;
}
};
#endif // COCKATRICE_ABSTRACT_GAME_H

View file

@ -1,9 +1,9 @@
#include "arrow_registry.h"
#include "arrow_item.h"
#include "../game_graphics/board/arrow_item.h"
#include "board/arrow_data.h"
#include <QSharedPointer>
#include <libcockatrice/game/board/arrow_data.h>
void ArrowRegistry::insert(QSharedPointer<ArrowData> data, ArrowItem *arrow)
{

View file

@ -4,7 +4,6 @@
#include <QList>
#include <QMap>
#include <QSet>
#include <libcockatrice/game/board/arrow_data.h>
class ArrowItem;
struct ArrowData;

View file

@ -1,6 +1,7 @@
#include "card_list.h"
#include "card_state.h"
#include "../../game_graphics/board/card_item.h"
#include "libcockatrice/card/printing/exact_card.h"
#include <QChar>
#include <QDebug>
@ -10,26 +11,26 @@
#include <compare>
#include <libcockatrice/card/card_info.h>
CardList::CardList(bool _contentsKnown) : QList<CardState *>(), contentsKnown(_contentsKnown)
CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), contentsKnown(_contentsKnown)
{
}
/**
* @brief Finds the CardState with the given id in the list.
* @brief Finds the CardItem with the given id in the list.
* If contentsKnown is false, then this just returns the first element of the list.
*
* @param cardId The id of the card to find.
*
* @returns A pointer to the CardState, or a nullptr if not found.
* @returns A pointer to the CardItem, or a nullptr if not found.
*/
CardState *CardList::findCard(const int cardId) const
CardItem *CardList::findCard(const int cardId) const
{
if (!contentsKnown && !empty()) {
return at(0);
} else {
for (auto *cardState : *this) {
if (cardState->getId() == cardId) {
return cardState;
for (auto *cardItem : *this) {
if (cardItem->getId() == cardId) {
return cardItem;
}
}
}
@ -37,7 +38,7 @@ CardState *CardList::findCard(const int cardId) const
}
/**
* @brief sorts the list by using string comparison on properties extracted from the CardState
* @brief sorts the list by using string comparison on properties extracted from the CardItem
* The cards are compared using each property in order.
* If two cards have the same value for a property, then the next property in the list is used.
*
@ -50,7 +51,7 @@ void CardList::sortBy(const QList<SortOption> &option)
return;
}
auto comparator = [&option](CardState *a, CardState *b) {
auto comparator = [&option](CardItem *a, CardItem *b) {
for (auto prop : option) {
auto extractor = getExtractorFor(prop);
QString t1 = extractor(a);
@ -115,26 +116,26 @@ static QString getColorSortString(const CardInfo &c, bool appendAtEnd)
}
/**
* @brief returns the function that extracts the given property from the CardState.
* @brief returns the function that extracts the given property from the CardItem.
*/
std::function<QString(CardState *)> CardList::getExtractorFor(SortOption option)
std::function<QString(CardItem *)> CardList::getExtractorFor(SortOption option)
{
switch (option) {
case NoSort:
return [](CardState *) { return ""; };
return [](CardItem *) { return ""; };
case SortByMainType:
return [](CardState *c) { return c->getCardInfo().getMainCardType(); };
return [](CardItem *c) { return c->getCardInfo().getMainCardType(); };
case SortByManaValue:
// getCmc returns the int as a string. We pad with 0's so that string comp also works on it
return [](CardState *c) { return c->getCard() ? c->getCardInfo().getCmc().rightJustified(4, '0') : ""; };
return [](CardItem *c) { return c->getCard() ? c->getCardInfo().getCmc().rightJustified(4, '0') : ""; };
case SortByColorGrouping:
return [](CardState *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), false) : ""; };
return [](CardItem *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), false) : ""; };
case SortByName:
return [](CardState *c) { return c->getName(); };
return [](CardItem *c) { return c->getName(); };
case SortByType:
return [](CardState *c) { return c->getCardInfo().getCardType(); };
return [](CardItem *c) { return c->getCardInfo().getCardType(); };
case SortByManaCost:
return [](CardState *c) {
return [](CardItem *c) {
if (!c->getCard()) {
return QString();
}
@ -146,18 +147,18 @@ std::function<QString(CardState *)> CardList::getExtractorFor(SortOption option)
return QString("%1%2").arg(info.getCmc(), 4, QChar('0')).arg(info.getManaCost());
};
case SortByColors:
return [](CardState *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), true) : ""; };
return [](CardItem *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), true) : ""; };
case SortByPt:
// do the same padding trick as above
return
[](CardState *c) { return c->getCard() ? c->getCardInfo().getPowTough().rightJustified(10, '0') : ""; };
[](CardItem *c) { return c->getCard() ? c->getCardInfo().getPowTough().rightJustified(10, '0') : ""; };
case SortBySet:
return [](CardState *c) { return c->getCardInfo().getSetsNames(); };
return [](CardItem *c) { return c->getCardInfo().getSetsNames(); };
case SortByPrinting:
return [](CardState *c) { return c->getProviderId(); };
return [](CardItem *c) { return c->getProviderId(); };
}
// this line should never be reached
qCWarning(CardListLog) << "cardlist.cpp: Could not find extractor for SortOption" << option;
return [](CardState *) { return ""; };
return [](CardItem *) { return ""; };
}

View file

@ -14,9 +14,9 @@
inline Q_LOGGING_CATEGORY(CardListLog, "card_list");
class CardState;
class CardItem;
class CardList : public QList<CardState *>
class CardList : public QList<CardItem *>
{
protected:
bool contentsKnown;
@ -43,7 +43,7 @@ public:
SortByPrinting
};
explicit CardList(bool _contentsKnown);
CardState *findCard(const int cardId) const;
CardItem *findCard(const int cardId) const;
bool getContentsKnown() const
{
return contentsKnown;
@ -51,7 +51,7 @@ public:
void sortBy(const QList<SortOption> &options);
static std::function<QString(CardState *)> getExtractorFor(SortOption option);
static std::function<QString(CardItem *)> getExtractorFor(SortOption option);
};
#endif

View file

@ -0,0 +1,113 @@
#include "card_state.h"
#include <QSharedPointer>
void CardState::resetState(bool keepAnnotations)
{
attacking = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
annotation.clear();
}
attachedTo = nullptr;
}
void CardState::setZone(CardZoneLogic *_zone)
{
if (zone == _zone) {
return;
}
zone = _zone;
emit zoneChanged(this, zone);
emit stateChanged();
}
void CardState::setAttacking(bool _attacking)
{
if (attacking == _attacking) {
return;
}
attacking = _attacking;
emit attackingChanged(_attacking);
emit stateChanged();
}
void CardState::insertCounter(int id, int value)
{
counters.insert(id, value);
emit countersChanged(counters);
emit stateChanged();
}
void CardState::setCounter(int id, int value)
{
if (value) {
counters[id] = value;
} else {
counters.remove(id);
}
emit countersChanged(counters);
emit stateChanged();
}
void CardState::clearCounters()
{
counters.clear();
emit countersChanged(counters);
emit stateChanged();
}
void CardState::setAnnotation(const QString &_annotation)
{
if (annotation == _annotation) {
return;
}
annotation = _annotation;
emit annotationChanged(annotation);
emit stateChanged();
}
void CardState::setPT(const QString &_pt)
{
if (pt == _pt) {
return;
}
pt = _pt;
emit ptChanged(pt);
emit stateChanged();
}
void CardState::setDoesntUntap(bool _doesntUntap)
{
if (doesntUntap == _doesntUntap) {
return;
}
doesntUntap = _doesntUntap;
emit doesntUntapChanged(_doesntUntap);
emit stateChanged();
}
void CardState::setDestroyOnZoneChange(bool _destroyOnZoneChange)
{
if (destroyOnZoneChange == _destroyOnZoneChange) {
return;
}
destroyOnZoneChange = _destroyOnZoneChange;
emit destroyOnZoneChangeChanged(_destroyOnZoneChange);
emit stateChanged();
}
void CardState::setAttachedTo(CardItem *_attachedTo)
{
if (attachedTo == _attachedTo) {
return;
}
attachedTo = _attachedTo;
emit attachedToChanged(_attachedTo);
emit stateChanged();
}

View file

@ -0,0 +1,106 @@
#ifndef COCKATRICE_CARD_STATE_H
#define COCKATRICE_CARD_STATE_H
#include <QMap>
#include <QObject>
#include <QString>
#include <qtmetamacros.h>
class CardZoneLogic;
class CardItem;
class CardState : public QObject
{
Q_OBJECT
private:
bool attacking = false;
QMap<int, int> counters;
QString annotation;
QString pt;
bool doesntUntap = false;
bool destroyOnZoneChange = false;
CardItem *attachedTo = nullptr;
CardZoneLogic *zone = nullptr;
signals:
void stateChanged();
void attackingChanged(bool newValue);
void countersChanged(const QMap<int, int> &newCounters);
void annotationChanged(const QString &newAnnotation);
void ptChanged(const QString &newPt);
void doesntUntapChanged(bool newValue);
void destroyOnZoneChangeChanged(bool newValue);
void attachedToChanged(CardItem *newAttachedTo);
void zoneChanged(CardState *changedCard, CardZoneLogic *newZone);
public:
explicit CardState(QObject *parent, CardZoneLogic *_zone) : QObject(parent), zone(_zone)
{
}
void resetState(bool keepAnnotations);
CardZoneLogic *getZone() const
{
return zone;
}
void setZone(CardZoneLogic *_zone);
bool getAttacking() const
{
return attacking;
}
void setAttacking(bool _attacking);
const QMap<int, int> &getCounters() const
{
return counters;
}
void insertCounter(int id, int value);
void setCounter(int id, int value);
void clearCounters();
QString getAnnotation() const
{
return annotation;
}
void setAnnotation(const QString &_annotation);
QString getPT() const
{
return pt;
}
void setPT(const QString &_pt);
bool getDoesntUntap() const
{
return doesntUntap;
}
void setDoesntUntap(bool _doesntUntap);
bool getDestroyOnZoneChange() const
{
return destroyOnZoneChange;
}
void setDestroyOnZoneChange(bool _destroyOnZoneChange);
CardItem *getAttachedTo() const
{
return attachedTo;
}
void setAttachedTo(CardItem *_attachedTo);
};
#endif // COCKATRICE_CARD_STATE_H

View file

@ -16,16 +16,13 @@ Game::Game(QObject *_parent,
bool isLocalGame,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes,
IInterfaceSettingsProvider *interfaceSettings,
ICardPreferenceProvider *cardPreferences)
const QMap<int, QString> &_roomGameTypes)
: AbstractGame(_parent)
{
gameMetaInfo->setFromProto(event.game_info());
gameMetaInfo->setRoomGameTypes(_roomGameTypes);
gameState = new GameState(this, 0, event.host_id(), isLocalGame, _clients, false, event.resuming(), -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator(), interfaceSettings,
cardPreferences);
playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator());
gameMetaInfo->setStarted(false);
}

View file

@ -18,9 +18,6 @@ class QString;
template <class Key, class T> class QMap;
template <typename T> class QList;
class ICardPreferenceProvider;
class IInterfaceSettingsProvider;
class Game : public AbstractGame
{
Q_OBJECT
@ -30,9 +27,7 @@ public:
bool isLocalGame,
QList<AbstractClient *> &_clients,
const Event_GameJoined &event,
const QMap<int, QString> &_roomGameTypes,
IInterfaceSettingsProvider *interfaceSettings = nullptr,
ICardPreferenceProvider *cardPreferences = nullptr);
const QMap<int, QString> &_roomGameTypes);
};
#endif // COCKATRICE_GAME_H

View file

@ -28,20 +28,12 @@
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/get_pb_extension.h>
#include <libcockatrice/protocol/pb/command_concede.pb.h>
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
#include <libcockatrice/protocol/pb/command_leave_game.pb.h>
#include <libcockatrice/protocol/pb/command_next_turn.pb.h>
#include <libcockatrice/protocol/pb/command_ready_start.pb.h>
#include <libcockatrice/protocol/pb/command_reverse_turn.pb.h>
#include <libcockatrice/protocol/pb/command_set_active_phase.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/protocol/pb/command_set_playmat.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
#include <libcockatrice/protocol/pb/context_connection_state_changed.pb.h>
#include <libcockatrice/protocol/pb/context_deck_select.pb.h>
#include <libcockatrice/protocol/pb/event_game_closed.pb.h>
#include <libcockatrice/protocol/pb/event_game_host_changed.pb.h>
@ -56,7 +48,6 @@
#include <libcockatrice/protocol/pb/event_set_active_player.pb.h>
#include <libcockatrice/protocol/pb/game_event_container.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/zone_names.h>
#include <string>
#include <utility>
@ -232,89 +223,6 @@ void GameEventHandler::handleActivePhaseChanged(int phase)
sendGameCommand(cmd);
}
void GameEventHandler::handleUntapAll()
{
Command_SetCardAttr cmd;
cmd.set_zone(ZoneNames::TABLE);
cmd.set_attribute(AttrTapped);
cmd.set_attr_value("0");
sendGameCommand(cmd);
}
void GameEventHandler::handleDrawCard()
{
Command_DrawCards cmd;
cmd.set_number(1);
sendGameCommand(cmd);
}
PendingCommand *GameEventHandler::prepareDeckSelect(const QString &deckList)
{
Command_DeckSelect cmd;
cmd.set_deck(deckList.toStdString());
return prepareGameCommand(cmd);
}
PendingCommand *GameEventHandler::prepareDeckSelectId(int deckId)
{
Command_DeckSelect cmd;
cmd.set_deck_id(deckId);
return prepareGameCommand(cmd);
}
PendingCommand *GameEventHandler::prepareSetPlaymat(const PlaymatInfo &playmat)
{
Command_SetPlaymat cmd;
auto *pp = cmd.mutable_playmat_params();
pp->set_card_name(playmat.card.name.toStdString());
pp->set_card_provider_id(playmat.card.providerId.toStdString());
pp->set_margin_pct_l(playmat.params.marginPctL);
pp->set_margin_pct_r(playmat.params.marginPctR);
pp->set_vertical_offset(playmat.params.verticalOffset);
pp->set_zoom(playmat.params.zoom);
return prepareGameCommand(cmd);
}
void GameEventHandler::sendReadyStart(bool ready, int playerId)
{
Command_ReadyStart cmd;
cmd.set_ready(ready);
sendGameCommand(cmd, playerId);
}
void GameEventHandler::sendForceStart(int playerId)
{
Command_ReadyStart cmd;
cmd.set_force_start(true);
cmd.set_ready(true);
sendGameCommand(cmd, playerId);
}
void GameEventHandler::sendSetSideboardLock(bool locked, int playerId)
{
Command_SetSideboardLock cmd;
cmd.set_locked(locked);
sendGameCommand(cmd, playerId);
}
void GameEventHandler::sendSetSideboardPlan(const QList<MoveCard_ToZone> &plan, int playerId)
{
Command_SetSideboardPlan cmd;
for (const auto &zone : plan) {
cmd.add_move_list()->CopyFrom(zone);
}
sendGameCommand(cmd, playerId);
}
void GameEventHandler::handleGameLeft()
{
sendGameCommand(Command_LeaveGame());

View file

@ -29,14 +29,13 @@
// GameEventContext, ServerInfo_Player and ServerInfo_User are passed by value
// to signals and slots, so the moc-generated code needs the complete types.
#include <libcockatrice/protocol/pb/game_event_context.pb.h>
#include <libcockatrice/protocol/pb/move_card_to_zone.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
#include <libcockatrice/utility/playmat_params.h>
class AbstractClient;
class AbstractGame;
class GameEventContainer;
class GameEventContext;
class PendingCommand;
class PlayerLogic;
class Response;
@ -128,12 +127,6 @@ public:
*/
void handleActivePhaseChanged(int phase);
/** @brief Request untapping every permanent on the table. */
void handleUntapAll();
/** @brief Request drawing a single card. */
void handleDrawCard();
/** @brief Leave the current game session. */
void handleGameLeft();
@ -152,70 +145,6 @@ public:
void handleArrowDeletion(int creatorId, int arrowId);
void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId);
/**
* @brief Prepare a deck selection from its native deck list text.
*
* The returned command is not sent yet, so the caller can connect to
* PendingCommand::finished before handing it to sendGameCommand().
*
* @param deckList Serialized deck list.
* @return The pending command awaiting the deck download response.
*/
PendingCommand *prepareDeckSelect(const QString &deckList);
/**
* @brief Prepare a deck selection for a deck stored on the server.
*
* The returned command is not sent yet, so the caller can connect to
* PendingCommand::finished before handing it to sendGameCommand().
*
* @param deckId Server-side deck identifier.
* @return The pending command awaiting the deck download response.
*/
PendingCommand *prepareDeckSelectId(int deckId);
/**
* @brief Build the playmat command for a player without sending it.
*
* The returned command is not sent yet, so the caller can connect to
* PendingCommand::finished before handing it to sendGameCommand().
*
* @param playmat Resolved playmat card and positioning parameters.
* @return The pending command awaiting the playmat response.
*/
PendingCommand *prepareSetPlaymat(const PlaymatInfo &playmat);
/**
* @brief Set a player's ready state.
*
* @param ready Whether the player is ready.
* @param playerId Player the state applies to.
*/
void sendReadyStart(bool ready, int playerId);
/**
* @brief Force-start the game, kicking every player who is not ready.
*
* @param playerId Player issuing the force start.
*/
void sendForceStart(int playerId);
/**
* @brief Lock or unlock a player's sideboard.
*
* @param locked Whether the sideboard is locked.
* @param playerId Player the lock applies to.
*/
void sendSetSideboardLock(bool locked, int playerId);
/**
* @brief Send a player's sideboard plan.
*
* @param plan Cards to move between zones when leaving sideboarding.
* @param playerId Player the plan belongs to.
*/
void sendSetSideboardPlan(const QList<MoveCard_ToZone> &plan, int playerId);
/** @} */
/** @name Incoming event processing

View file

@ -1,8 +1,13 @@
#include "player_actions.h"
#include "../../client/settings/cache_settings.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/dialogs/dlg_create_token.h"
#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h"
#include "../../game_graphics/player/card_menu_action_type.h"
#include "../../game_graphics/zones/table_zone.h"
#include "../../interface/deck_loader/loaded_deck.h"
#include "../abstract_game.h"
#include "../board/arrow_data.h"
#include "../board/card_state.h"
#include "../board/counter_state.h"
#include "../game_event_handler.h"
#include "../zones/card_zone_logic.h"
@ -26,6 +31,8 @@
#include <QChar>
#include <QColor>
#include <QFlags>
#include <QInputDialog>
#include <QKeyEvent>
#include <QList>
#include <QMap>
#include <QMapIterator>
@ -44,15 +51,10 @@
#include <google/protobuf/stubs/port.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/deck_list/loaded_deck.h>
#include <libcockatrice/interfaces/interface_card_preference_provider.h>
#include <libcockatrice/interfaces/interface_interface_settings_provider.h>
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
#include <libcockatrice/protocol/pb/command_create_token.pb.h>
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
#include <libcockatrice/protocol/pb/command_dump_zone.pb.h>
#include <libcockatrice/protocol/pb/command_flip_card.pb.h>
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
@ -62,12 +64,11 @@
#include <libcockatrice/protocol/pb/command_roll_die.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_counter.pb.h>
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/settings/card_override_settings.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/utility/clamped_arithmetic.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/counter_limits.h>
#include <libcockatrice/utility/expression.h>
#include <libcockatrice/utility/zone_names.h>
@ -79,11 +80,8 @@
// milliseconds in between triggers of the move top cards until action
static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100;
PlayerActions::PlayerActions(PlayerLogic *_player,
IInterfaceSettingsProvider *_interfaceSettings,
ICardPreferenceProvider *_cardPreferences)
: QObject(_player), player(_player), interfaceSettings(_interfaceSettings), cardPreferences(_cardPreferences),
lastTokenTableRow(0), movingCardsUntil(false)
PlayerActions::PlayerActions(PlayerLogic *_player)
: QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false)
{
connect(this, &PlayerActions::requestZoneViewToggle, player, &PlayerLogic::onRequestZoneViewToggle);
@ -93,27 +91,7 @@ PlayerActions::PlayerActions(PlayerLogic *_player,
connect(moveTopCardTimer, &QTimer::timeout, [this]() { actMoveTopCardToPlay(); });
}
bool PlayerActions::getPlayToStack() const
{
return interfaceSettings ? interfaceSettings->getPlayToStack() : true;
}
int PlayerActions::getStartingHandSize() const
{
return interfaceSettings ? interfaceSettings->getStartingHandSize() : 7;
}
bool PlayerActions::getAnnotateTokens() const
{
return interfaceSettings ? interfaceSettings->getAnnotateTokens() : false;
}
QString PlayerActions::getCardPreferenceOverride(const QString &cardName) const
{
return cardPreferences ? cardPreferences->getCardPreferenceOverride(cardName) : QString();
}
void PlayerActions::playCard(CardState *card, bool faceDown)
void PlayerActions::playCard(CardItem *card, bool faceDown)
{
if (card == nullptr) {
return;
@ -134,7 +112,7 @@ void PlayerActions::playCard(CardState *card, bool faceDown)
const CardInfo &info = exactCard.getInfo();
int tableRow = info.getUiAttributes().tableRow;
bool playToStack = getPlayToStack();
bool playToStack = SettingsCache::instance().userInterface().getPlayToStack();
QString currentZone = card->getZone()->getName();
if (!faceDown && currentZone == ZoneNames::STACK && tableRow == 3) {
cmd.set_target_zone(ZoneNames::GRAVE);
@ -147,7 +125,7 @@ void PlayerActions::playCard(CardState *card, bool faceDown)
cmd.set_y(0);
} else {
tableRow = faceDown ? 2 : info.getUiAttributes().tableRow;
QPoint gridPoint = QPoint(-1, TableZoneLogic::tableRowToGridY(tableRow));
QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow));
cardToMove->set_face_down(faceDown);
if (!faceDown) {
cardToMove->set_pt(info.getPowTough().toStdString());
@ -166,7 +144,7 @@ void PlayerActions::playCard(CardState *card, bool faceDown)
* Like {@link PlayerActions::playCard}, but forces the card to be played to the table zone.
* Cards with tablerow 3 (the stack) will be played to tablerow 1 (the noncreatures row).
*/
void PlayerActions::playCardToTable(const CardState *card, bool faceDown)
void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
{
if (card == nullptr) {
return;
@ -187,7 +165,7 @@ void PlayerActions::playCardToTable(const CardState *card, bool faceDown)
const CardInfo &info = exactCard.getInfo();
int tableRow = faceDown ? 2 : info.getUiAttributes().tableRow;
QPoint gridPoint = QPoint(-1, TableZoneLogic::tableRowToGridY(tableRow));
QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow));
cardToMove->set_face_down(faceDown);
if (!faceDown) {
cardToMove->set_pt(info.getPowTough().toStdString());
@ -199,60 +177,6 @@ void PlayerActions::playCardToTable(const CardState *card, bool faceDown)
sendGameCommand(cmd);
}
void PlayerActions::attachCards(CardState *startCard, const CardState *targetCard)
{
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
return;
}
// move card onto table first if attaching from some other zone
if (startCard->getZone()->getName() != ZoneNames::TABLE) {
playCardToTable(startCard, false);
}
Command_AttachCard cmd;
cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetCard->getZone()->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
sendGameCommand(cmd);
}
void PlayerActions::createArrow(CardState *startCard, const ArrowData &arrow, int deleteInPhase)
{
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(arrow.color));
cmd.set_start_player_id(arrow.startPlayerId);
cmd.set_start_zone(arrow.startZone.toStdString());
cmd.set_start_card_id(arrow.startCardId);
cmd.set_target_player_id(arrow.targetPlayerId);
if (!arrow.isPlayerTargeted()) {
cmd.set_target_zone(arrow.targetZone.toStdString());
cmd.set_target_card_id(arrow.targetCardId);
}
// if the card is in hand then it has just been moved to the stack or table as part of drawing the arrow
if (arrow.startZone == ZoneNames::HAND) {
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) {
cmd.set_start_zone(ZoneNames::STACK);
} else {
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
}
if (deleteInPhase != 0) {
cmd.set_delete_in_phase(deleteInPhase);
}
sendGameCommand(cmd);
}
void PlayerActions::actViewLibrary()
{
emit requestZoneViewToggle(ZoneNames::DECK, -1);
@ -431,7 +355,7 @@ void PlayerActions::actDrawCard()
void PlayerActions::actRequestMulliganDialog()
{
int startSize = getStartingHandSize();
int startSize = SettingsCache::instance().userInterface().getStartingHandSize();
int handSize = player->getHandZone()->getCards().size();
int deckSize = player->getDeckZone()->getCards().size() + handSize;
@ -447,7 +371,7 @@ void PlayerActions::actMulligan(int number)
}
doMulligan(number);
emit startingHandSizeChosen(number);
SettingsCache::instance().userInterface().setStartingHandSize(number);
}
void PlayerActions::actMulliganSameSize()
@ -625,7 +549,7 @@ void PlayerActions::moveTopCardsUntil(const QString &expr, MoveTopCardsUntilOpti
}
}
void PlayerActions::moveOneCardUntil(CardState *card)
void PlayerActions::moveOneCardUntil(CardItem *card)
{
moveTopCardTimer->stop();
@ -817,6 +741,61 @@ void PlayerActions::actMoveBottomCardToTop()
sendGameCommand(cmd);
}
/**
* Selects all cards in the given zone.
*
* @param zone The zone to select from
* @param filter A predicate to filter which cards are selected. Defaults to always returning true.
*/
static void selectCardsInZone(
const CardZoneLogic *zone,
std::function<bool(const CardItem *)> filter = [](const CardItem *) { return true; })
{
if (!zone) {
return;
}
for (auto &cardItem : zone->getCards()) {
if (cardItem && filter(cardItem)) {
cardItem->setSelected(true);
}
}
}
void PlayerActions::actSelectAll()
{
const CardItem *card = player->getGame()->getActiveCard();
if (!card) {
return;
}
selectCardsInZone(card->getZone());
}
void PlayerActions::actSelectRow()
{
const CardItem *card = player->getGame()->getActiveCard();
if (!card) {
return;
}
auto isSameRow = [card](const CardItem *cardItem) {
return qAbs(card->scenePos().y() - cardItem->scenePos().y()) < 50;
};
selectCardsInZone(card->getZone(), isSameRow);
}
void PlayerActions::actSelectColumn()
{
const CardItem *card = player->getGame()->getActiveCard();
if (!card) {
return;
}
auto isSameColumn = [card](const CardItem *cardItem) { return cardItem->x() == card->x(); };
selectCardsInZone(card->getZone(), isSameColumn);
}
void PlayerActions::actDrawBottomCard()
{
if (player->getDeckZone()->getCards().empty()) {
@ -950,7 +929,7 @@ void PlayerActions::actCreateToken(TokenInfo tokenToCreate)
if (correctedCard) {
lastTokenInfo.name = correctedCard.getName();
int tableRow = lastTokenInfo.faceDown ? 2 : correctedCard.getInfo().getUiAttributes().tableRow;
lastTokenTableRow = TableZoneLogic::tableRowToGridY(tableRow);
lastTokenTableRow = TableZone::tableRowToGridY(tableRow);
if (lastTokenInfo.pt.isEmpty()) {
lastTokenInfo.pt = correctedCard.getInfo().getPowTough();
}
@ -996,14 +975,15 @@ void PlayerActions::setLastTokenInfo(CardInfoPtr cardInfo)
return;
}
lastTokenInfo = {.name = cardInfo->getName(),
.color = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(),
.pt = cardInfo->getPowTough(),
.annotation = getAnnotateTokens() ? cardInfo->getText() : "",
.destroy = true,
.providerId = getCardPreferenceOverride(cardInfo->getName())};
lastTokenInfo = {
.name = cardInfo->getName(),
.color = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(),
.pt = cardInfo->getPowTough(),
.annotation = SettingsCache::instance().userInterface().getAnnotateTokens() ? cardInfo->getText() : "",
.destroy = true,
.providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())};
lastTokenTableRow = TableZoneLogic::tableRowToGridY(cardInfo->getUiAttributes().tableRow);
lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow);
emit requestEnableAndSetCreateAnotherTokenAction(lastTokenInfo.name);
}
@ -1021,20 +1001,25 @@ void PlayerActions::actCreatePredefinedToken()
actCreateAnotherToken();
}
void PlayerActions::actCreateRelatedCard(const CardState *sourceCard, int index)
void PlayerActions::actCreateRelatedCard()
{
const CardItem *sourceCard = player->getGame()->getActiveCard();
if (!sourceCard) {
return;
}
auto *action = static_cast<QAction *>(sender());
// If there is a better way of passing a CardRelation through a QAction, please add it here.
auto relatedCards = sourceCard->getCardInfo().getAllRelatedCards();
CardRelation *cardRelation = relatedCards.at(index);
CardRelation *cardRelation = relatedCards.at(action->data().toInt());
actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation);
}
void PlayerActions::actCreateAllRelatedCards(const CardState *sourceCard)
void PlayerActions::actCreateAllRelatedCards()
{
const CardItem *sourceCard = player->getGame()->getActiveCard();
if (!sourceCard) {
return;
}
@ -1120,13 +1105,13 @@ void PlayerActions::actCreateAllRelatedCards(const CardState *sourceCard)
}
}
void PlayerActions::actRequestCreateRelatedFromRelationDialog(const CardState *sourceCard,
void PlayerActions::actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard,
const CardRelation *cardRelation)
{
emit requestCreateRelatedFromRelationDialog(sourceCard, cardRelation);
}
bool PlayerActions::createRelatedFromRelation(const CardState *sourceCard,
bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard,
const CardRelation *cardRelation,
int variableCount)
{
@ -1178,7 +1163,7 @@ bool PlayerActions::createRelatedFromRelation(const CardState *sourceCard,
return true;
}
void PlayerActions::onRelatedCardCreated(const CardState *sourceCard, const CardRelation *cardRelation)
void PlayerActions::onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation)
{
if (sourceCard == nullptr || cardRelation == nullptr) {
return;
@ -1198,7 +1183,7 @@ void PlayerActions::onRelatedCardCreated(const CardState *sourceCard, const Card
setLastToken(relatedCard.getCardPtr());
}
void PlayerActions::createCard(const CardState *sourceCard,
void PlayerActions::createCard(const CardItem *sourceCard,
const QString &dbCardName,
CardRelationType attachType,
bool persistent,
@ -1210,7 +1195,7 @@ void PlayerActions::createCard(const CardState *sourceCard,
return;
}
QPoint gridPoint = QPoint(-1, TableZoneLogic::tableRowToGridY(cardInfo->getUiAttributes().tableRow));
QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow));
// create the token for the related card
Command_CreateToken cmd;
@ -1229,7 +1214,7 @@ void PlayerActions::createCard(const CardState *sourceCard,
}
cmd.set_pt(cardInfo->getPowTough().toStdString());
if (getAnnotateTokens()) {
if (SettingsCache::instance().userInterface().getAnnotateTokens()) {
cmd.set_annotation(cardInfo->getText().toStdString());
} else {
cmd.set_annotation("");
@ -1284,7 +1269,7 @@ void PlayerActions::actRequestMoveCardXCardsFromTopDialog()
emit requestMoveCardXCardsFromTopDialog(defaultNumberTopCardsToPlaceBelow, deckSize);
}
void PlayerActions::actMoveCardXCardsFromTop(QList<CardState *> selectedCards, int number)
void PlayerActions::actMoveCardXCardsFromTop(QList<CardItem *> selectedCards, int number)
{
defaultNumberTopCardsToPlaceBelow = number;
@ -1318,14 +1303,14 @@ void PlayerActions::actMoveCardXCardsFromTop(QList<CardState *> selectedCards, i
}
}
void PlayerActions::actIncPT(QList<CardState *> selectedCards, int deltaP, int deltaT)
void PlayerActions::actIncPT(QList<CardItem *> selectedCards, int deltaP, int deltaT)
{
int playerid = player->getPlayerInfo()->getId();
QList<const ::google::protobuf::Message *> commandList;
for (auto card : selectedCards) {
QString pt = card->getPT();
const auto ptList = CardState::parsePT(pt);
const auto ptList = CardItem::parsePT(pt);
QString newpt;
if (ptList.isEmpty()) {
newpt = QString::number(deltaP) + (deltaT ? "/" + QString::number(deltaT) : "");
@ -1351,7 +1336,7 @@ void PlayerActions::actIncPT(QList<CardState *> selectedCards, int deltaP, int d
player->getGame()->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid);
}
void PlayerActions::actResetPT(QList<CardState *> selectedCards)
void PlayerActions::actResetPT(QList<CardItem *> selectedCards)
{
int playerid = player->getPlayerInfo()->getId();
QList<const ::google::protobuf::Message *> commandList;
@ -1384,7 +1369,7 @@ void PlayerActions::actResetPT(QList<CardState *> selectedCards)
}
}
void PlayerActions::actRequestSetPTDialog(QList<CardState *> selectedCards)
void PlayerActions::actRequestSetPTDialog(QList<CardItem *> selectedCards)
{
QString oldPT;
@ -1397,11 +1382,11 @@ void PlayerActions::actRequestSetPTDialog(QList<CardState *> selectedCards)
emit requestSetPTDialog(oldPT);
}
void PlayerActions::actSetPT(QList<CardState *> selectedCards, const QString &pt)
void PlayerActions::actSetPT(QList<CardItem *> selectedCards, const QString &pt)
{
int playerid = player->getPlayerInfo()->getId();
const auto ptList = CardState::parsePT(pt);
const auto ptList = CardItem::parsePT(pt);
bool empty = ptList.isEmpty();
QList<const ::google::protobuf::Message *> commandList;
@ -1409,7 +1394,7 @@ void PlayerActions::actSetPT(QList<CardState *> selectedCards, const QString &pt
auto *cmd = new Command_SetCardAttr;
QString newpt = QString();
if (!empty) {
const auto oldpt = CardState::parsePT(card->getPT());
const auto oldpt = CardItem::parsePT(card->getPT());
int ptIter = 0;
for (const auto &_item : ptList) {
if (_item.typeId() == QMetaType::Type::Int) {
@ -1437,65 +1422,55 @@ void PlayerActions::actSetPT(QList<CardState *> selectedCards, const QString &pt
player->getGame()->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid);
}
void PlayerActions::actIncP(QList<CardState *> selectedCards)
void PlayerActions::actDrawArrow()
{
auto *card = player->getGame()->getActiveCard();
if (card) {
card->drawArrow(Qt::red);
}
}
void PlayerActions::actIncP(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, 1, 0);
}
void PlayerActions::actDecP(QList<CardState *> selectedCards)
void PlayerActions::actDecP(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, -1, 0);
}
void PlayerActions::actIncT(QList<CardState *> selectedCards)
void PlayerActions::actIncT(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, 0, 1);
}
void PlayerActions::actDecT(QList<CardState *> selectedCards)
void PlayerActions::actDecT(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, 0, -1);
}
void PlayerActions::actIncPT(QList<CardState *> selectedCards)
void PlayerActions::actIncPT(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, 1, 1);
}
void PlayerActions::actDecPT(QList<CardState *> selectedCards)
void PlayerActions::actDecPT(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, -1, -1);
}
void PlayerActions::actFlowP(QList<CardState *> selectedCards)
void PlayerActions::actFlowP(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, 1, -1);
}
void PlayerActions::actFlowT(QList<CardState *> selectedCards)
void PlayerActions::actFlowT(QList<CardItem *> selectedCards)
{
actIncPT(selectedCards, -1, 1);
}
void PlayerActions::actIncrementCounter(int counterId, int delta)
{
Command_IncCounter cmd;
cmd.set_counter_id(counterId);
cmd.set_delta(delta);
sendGameCommand(cmd);
}
void PlayerActions::actSetCounter(int counterId, int value)
{
Command_SetCounter cmd;
cmd.set_counter_id(counterId);
cmd.set_value(value);
sendGameCommand(cmd);
}
void PlayerActions::actReduceLifeByPower(QList<CardState *> selectedCards)
void PlayerActions::actReduceLifeByPower(QList<CardItem *> selectedCards)
{
// find life counter
auto lifeCounter = player->getLifeCounter();
@ -1506,7 +1481,7 @@ void PlayerActions::actReduceLifeByPower(QList<CardState *> selectedCards)
// calculate total power;
int total = 0;
for (auto card : selectedCards) {
QVariantList parsed = CardState::parsePT(card->getPT());
QVariantList parsed = CardItem::parsePT(card->getPT());
if (!parsed.isEmpty()) {
int power = parsed.first().toInt(); // toInt will default to 0 if it's not an int
total += qMax(power, 0);
@ -1520,7 +1495,17 @@ void PlayerActions::actReduceLifeByPower(QList<CardState *> selectedCards)
sendGameCommand(prepareGameCommand(cmd));
}
void PlayerActions::actRequestSetAnnotationDialog(QList<CardState *> selectedCards)
void AnnotationDialog::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return && event->modifiers() & Qt::ControlModifier) {
event->accept();
accept();
return;
}
QInputDialog::keyPressEvent(event);
}
void PlayerActions::actRequestSetAnnotationDialog(QList<CardItem *> selectedCards)
{
QString oldAnnotation;
for (auto card : selectedCards) {
@ -1532,7 +1517,7 @@ void PlayerActions::actRequestSetAnnotationDialog(QList<CardState *> selectedCar
emit requestSetAnnotationDialog(oldAnnotation);
}
void PlayerActions::actSetAnnotation(QList<CardState *> selectedCards, const QString &annotation)
void PlayerActions::actSetAnnotation(QList<CardItem *> selectedCards, const QString &annotation)
{
QList<const ::google::protobuf::Message *> commandList;
for (auto card : selectedCards) {
@ -1546,7 +1531,17 @@ void PlayerActions::actSetAnnotation(QList<CardState *> selectedCards, const QSt
sendGameCommand(prepareGameCommand(commandList));
}
void PlayerActions::actUnattach(QList<CardState *> selectedCards)
void PlayerActions::actAttach()
{
auto *card = player->getGame()->getActiveCard();
if (!card) {
return;
}
card->drawAttachArrow();
}
void PlayerActions::actUnattach(QList<CardItem *> selectedCards)
{
QList<const ::google::protobuf::Message *> commandList;
for (auto card : selectedCards) {
@ -1562,17 +1557,17 @@ void PlayerActions::actUnattach(QList<CardState *> selectedCards)
sendGameCommand(prepareGameCommand(commandList));
}
void PlayerActions::actAddCardCounter(QList<CardState *> selectedCards, int counterId)
void PlayerActions::actAddCardCounter(QList<CardItem *> selectedCards, int counterId)
{
offsetCardCounter(selectedCards, counterId, 1);
}
void PlayerActions::actRemoveCardCounter(QList<CardState *> selectedCards, int counterId)
void PlayerActions::actRemoveCardCounter(QList<CardItem *> selectedCards, int counterId)
{
offsetCardCounter(selectedCards, counterId, -1);
}
void PlayerActions::offsetCardCounter(QList<CardState *> selectedCards, int counterId, int offset)
void PlayerActions::offsetCardCounter(QList<CardItem *> selectedCards, int counterId, int offset)
{
QList<const ::google::protobuf::Message *> commandList;
for (auto card : selectedCards) {
@ -1598,7 +1593,7 @@ void PlayerActions::offsetCardCounter(QList<CardState *> selectedCards, int coun
sendGameCommand(prepareGameCommand(commandList));
}
void PlayerActions::actRequestSetCardCounterDialog(QList<CardState *> selectedCards, int counterId)
void PlayerActions::actRequestSetCardCounterDialog(QList<CardItem *> selectedCards, int counterId)
{
// If a single card is selected, we show the old value in the dialog. Otherwise, we show "x"
QString oldValueForDlg = "x";
@ -1610,7 +1605,7 @@ void PlayerActions::actRequestSetCardCounterDialog(QList<CardState *> selectedCa
emit requestSetCardCounterDialog(counterId, oldValueForDlg);
}
void PlayerActions::actSetCardCounter(QList<CardState *> selectedCards, int counterId, const QString &counterValue)
void PlayerActions::actSetCardCounter(QList<CardItem *> selectedCards, int counterId, const QString &counterValue)
{
QList<const ::google::protobuf::Message *> commandList;
for (auto card : selectedCards) {
@ -1631,13 +1626,11 @@ void PlayerActions::actSetCardCounter(QList<CardState *> selectedCards, int coun
sendGameCommand(prepareGameCommand(commandList));
}
void PlayerActions::actIncrementAllCardCounters(QList<CardState *> cardsToUpdate)
void PlayerActions::actIncrementAllCardCounters(QList<CardItem *> cardsToUpdate)
{
if (cardsToUpdate.isEmpty()) {
// If no cards selected, update all cards on table
for (auto *state : player->getTableZone()->getCards()) {
cardsToUpdate.append(state);
}
cardsToUpdate = static_cast<QList<CardItem *>>(player->getTableZone()->getCards());
}
QList<const ::google::protobuf::Message *> commandList;
@ -1680,7 +1673,7 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
return false;
}
void PlayerActions::playSelectedCards(QList<CardState *> selectedCards, const bool faceDown)
void PlayerActions::playSelectedCards(QList<CardItem *> selectedCards, const bool faceDown)
{
// CardIds will get shuffled downwards when cards leave the deck.
// We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play
@ -1695,26 +1688,27 @@ void PlayerActions::playSelectedCards(QList<CardState *> selectedCards, const bo
}
}
void PlayerActions::actPlay(QList<CardState *> selectedCards)
void PlayerActions::actPlay(QList<CardItem *> selectedCards)
{
playSelectedCards(selectedCards, false);
}
void PlayerActions::actPlayFacedown(QList<CardState *> selectedCards)
void PlayerActions::actPlayFacedown(QList<CardItem *> selectedCards)
{
playSelectedCards(selectedCards, true);
}
void PlayerActions::actHide(QList<CardState *> selectedCards)
void PlayerActions::actHide(QList<CardItem *> selectedCards)
{
for (const auto &card : selectedCards) {
if (isUnwritableRevealZone(card->getZone())) {
for (const auto &item : selectedCards) {
auto *card = static_cast<CardItem *>(item);
if (card && isUnwritableRevealZone(card->getZone())) {
card->getZone()->removeCard(card);
}
}
}
void PlayerActions::actReveal(QList<CardState *> selectedCards, QAction *action)
void PlayerActions::actReveal(QList<CardItem *> selectedCards, QAction *action)
{
const int otherPlayerId = action->data().toInt();
@ -1805,9 +1799,9 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId)
sendGameCommand(cmd);
}
void PlayerActions::cardMenuAction(QList<CardState *> selectedCards, CardMenuActionType type)
void PlayerActions::cardMenuAction(QList<CardItem *> selectedCards, CardMenuActionType type)
{
QList<CardState *> cardList = selectedCards;
QList<CardItem *> cardList = selectedCards;
QList<const ::google::protobuf::Message *> commandList;
if (type <= cmClone) {
@ -1997,7 +1991,7 @@ void PlayerActions::cardMenuAction(QList<CardState *> selectedCards, CardMenuAct
ctm->set_tapped(info.getUiAttributes().cipt);
}
cmd->set_y(TableZoneLogic::tableRowToGridY(tableRow));
cmd->set_y(TableZone::tableRowToGridY(tableRow));
commandList.append(cmd);
}
break;
@ -2046,76 +2040,6 @@ PendingCommand *PlayerActions::prepareGameCommand(const QList<const ::google::pr
}
}
void PlayerActions::moveCards(CardZoneLogic *startZone,
CardZoneLogic *targetZone,
int x,
int y,
const QList<CardMoveRequest> &cards)
{
if (!startZone || !targetZone) {
return;
}
Command_MoveCard cmd;
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_x(x);
cmd.set_y(y);
if (auto *viewLogic = qobject_cast<ZoneViewZoneLogic *>(targetZone)) {
cmd.set_is_reversed(viewLogic->getIsReversed());
}
for (const CardMoveRequest &card : cards) {
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(card.cardId);
if (card.faceDown) {
cardToMove->set_face_down(true);
}
if (!card.pt.isEmpty()) {
cardToMove->set_pt(card.pt.toStdString());
}
}
sendGameCommand(cmd);
}
PendingCommand *PlayerActions::prepareZoneDump(CardZoneLogic *zone, int numberCards)
{
if (!zone) {
return nullptr;
}
Command_DumpZone cmd;
cmd.set_player_id(zone->getPlayer()->getPlayerInfo()->getId());
cmd.set_zone_name(zone->getName().toStdString());
cmd.set_number_cards(numberCards);
if (auto *viewLogic = qobject_cast<ZoneViewZoneLogic *>(zone)) {
cmd.set_is_reversed(viewLogic->getIsReversed());
}
return prepareGameCommand(cmd);
}
void PlayerActions::actToggleTapped(const QString &zoneName, const QList<CardState *> &cards, bool tapped)
{
QList<const ::google::protobuf::Message *> cmdList;
for (CardState *card : cards) {
if (card->getTapped() != tapped) {
auto *cmd = new Command_SetCardAttr;
cmd->set_zone(zoneName.toStdString());
cmd->set_card_id(card->getId());
cmd->set_attribute(AttrTapped);
cmd->set_attr_value(tapped ? "1" : "0");
cmdList.append(cmd);
}
}
sendGameCommand(prepareGameCommand(cmdList));
}
void PlayerActions::sendGameCommand(const google::protobuf::Message &command)
{
if (player->getPlayerInfo()->getJudge() && !player->getPlayerInfo()->getLocal()) {

View file

@ -8,13 +8,12 @@
#ifndef COCKATRICE_PLAYER_ACTIONS_H
#define COCKATRICE_PLAYER_ACTIONS_H
#include "../../game_graphics/board/card_item.h" // IWYU pragma: keep - moc instantiates the metatype for QList<CardItem *>, which Qt 6.4 needs complete
#include "../../game_graphics/dialogs/dlg_create_token.h"
#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h"
#include "../../game_graphics/player/card_menu_action_type.h"
#include "../board/card_list.h"
#include "../board/card_state.h"
#include "card_menu_action_type.h"
#include "card_move_request.h"
#include "libcockatrice/card/card_info.h"
#include "move_top_cards_until_options.h"
#include "token_info.h"
#include <QObject>
#include <QString>
@ -23,6 +22,7 @@
#include <libcockatrice/filters/filter_string.h>
#include <qtmetamacros.h>
class CardItem;
class CardRelation;
class QAction;
class QTimer;
@ -36,16 +36,10 @@ class Message;
}
} // namespace google
class CardState;
class CardZoneLogic;
class Command_MoveCard;
class GameEventContext;
class ICardPreferenceProvider;
class IInterfaceSettingsProvider;
class PendingCommand;
class PlayerLogic;
class QAction;
struct ArrowData;
class PlayerActions : public QObject
{
Q_OBJECT
@ -56,9 +50,7 @@ public:
RANDOM_CARD_FROM_ZONE = -2
};
explicit PlayerActions(PlayerLogic *player,
IInterfaceSettingsProvider *interfaceSettings = nullptr,
ICardPreferenceProvider *cardPreferences = nullptr);
explicit PlayerActions(PlayerLogic *player);
void sendGameCommand(PendingCommand *pend);
void sendGameCommand(const google::protobuf::Message &command);
@ -66,52 +58,7 @@ public:
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
/**
* @brief Moves the requested cards between zones on behalf of a view.
* @param startZone Zone the cards are dragged out of.
* @param targetZone Zone the cards are dropped onto.
* @param x Horizontal target index within the zone.
* @param y Vertical target index within the zone.
* @param cards The cards to move, as chosen by the view.
*/
void
moveCards(CardZoneLogic *startZone, CardZoneLogic *targetZone, int x, int y, const QList<CardMoveRequest> &cards);
/**
* @brief Prepares a dump of the given zone, for the caller to connect to and send.
* @param zone Zone to dump.
* @param numberCards How many cards to dump, -1 for all.
*/
PendingCommand *prepareZoneDump(CardZoneLogic *zone, int numberCards);
/**
* @brief Sets the tapped state of the cards that do not already have it.
* @param zoneName Zone the cards belong to.
* @param cards Cards the view wants toggled.
* @param tapped Desired tapped state.
*/
void actToggleTapped(const QString &zoneName, const QList<CardState *> &cards, bool tapped);
/**
* @brief Attaches one card to another on the table on behalf of a view.
* @param startCard Card being attached.
* @param targetCard Card being attached to.
*/
void attachCards(CardState *startCard, const CardState *targetCard);
/**
* @brief Draws an arrow from a card to another card or a player on behalf of a view.
*
* The view resolves the endpoints before playing the card out of hand, so the
* arrow still records the zone the card started in.
*
* @param startCard Card the arrow starts from.
* @param arrow Arrow endpoints and color, as resolved by the view.
* @param deleteInPhase Phase the arrow expires in, 0 to keep it until removed.
*/
void createArrow(CardState *startCard, const ArrowData &arrow, int deleteInPhase);
void moveOneCardUntil(CardState *card);
void moveOneCardUntil(CardItem *card);
void stopMoveTopCardsUntil();
[[nodiscard]] bool isMovingCardsUntil() const
@ -140,7 +87,7 @@ signals:
void requestDrawBottomCardsDialog(int defaultNumberBottomCards, int maxCards);
void requestRollDieDialog();
void requestCreateTokenDialog(const QStringList &predefinedTokens);
void requestCreateRelatedFromRelationDialog(const CardState *sourceCard, const CardRelation *cardRelation);
void requestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation);
void requestMoveCardXCardsFromTopDialog(int defaultNumberTopCardsToPlaceBelow, int deckSize);
void requestSetPTDialog(const QString &oldPT);
void requestSetAnnotationDialog(const QString &oldAnnotation);
@ -149,18 +96,12 @@ signals:
void requestSortHand(const QList<CardList::SortOption> &options);
void requestEnableAndSetCreateAnotherTokenAction(const QString &lastTokenName);
void requestSetLastToken(CardInfoPtr lastToken);
/**
* @brief The player chose a mulligan size to remember as the starting hand size.
*
* Game logic must not write client settings itself, so persistence is left to the application.
*/
void startingHandSizeChosen(int number);
public slots:
void setLastToken(CardInfoPtr cardInfo);
void setLastTokenInfo(CardInfoPtr cardInfo);
void playCard(CardState *c, bool faceDown);
void playCardToTable(const CardState *c, bool faceDown);
void playCard(CardItem *c, bool faceDown);
void playCardToTable(const CardItem *c, bool faceDown);
void actUntapAll();
void actRequestRollDieDialog();
@ -169,9 +110,9 @@ public slots:
void actRequestCreateTokenDialog(const QStringList &predefinedTokens);
void actCreateToken(TokenInfo tokenToCreate);
void actCreateAnotherToken();
void actRequestCreateRelatedFromRelationDialog(const CardState *sourceCard, const CardRelation *cardRelation);
bool createRelatedFromRelation(const CardState *sourceCard, const CardRelation *cardRelation, int variableCount);
void onRelatedCardCreated(const CardState *sourceCard, const CardRelation *cardRelation);
void actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation);
bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation, int variableCount);
void onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation);
void setLastRelatedCreationSucceeded(bool succeeded)
{
lastRelatedCreationSucceeded = succeeded;
@ -191,9 +132,9 @@ public slots:
void actMulliganMinusOne();
void doMulligan(int number);
void actPlay(QList<CardState *> selectedCards);
void actPlayFacedown(QList<CardState *> selectedCards);
void actHide(QList<CardState *> selectedCards);
void actPlay(QList<CardItem *> selectedCards);
void actPlayFacedown(QList<CardItem *> selectedCards);
void actHide(QList<CardItem *> selectedCards);
void actMoveTopCardToPlay();
void actMoveTopCardToPlayFaceDown();
@ -223,6 +164,10 @@ public slots:
void actRequestMoveBottomCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void moveBottomCardsTo(int number, const QString &targetZone, bool faceDown);
void actSelectAll();
void actSelectRow();
void actSelectColumn();
void actViewLibrary();
void actViewHand();
void actRequestViewTopCardsDialog();
@ -242,55 +187,47 @@ public slots:
void actOpenDeckInDeckEditor();
void actCreatePredefinedToken();
void actCreateRelatedCard(const CardState *sourceCard, int index);
void actCreateAllRelatedCards(const CardState *sourceCard);
void actCreateRelatedCard();
void actCreateAllRelatedCards();
void actRequestMoveCardXCardsFromTopDialog();
void actMoveCardXCardsFromTop(QList<CardState *> selectedCards, int number);
void actRemoveCardCounter(QList<CardState *> selectedCards, int counterId);
void actAddCardCounter(QList<CardState *> selectedCards, int counterId);
void actRequestSetCardCounterDialog(QList<CardState *> selectedCards, int counterId);
void actSetCardCounter(QList<CardState *> selectedCards, int counterId, const QString &counterValue);
void actIncrementAllCardCounters(QList<CardState *> cardsToUpdate);
void actUnattach(QList<CardState *> selectedCards);
void actIncPT(QList<CardState *> selectedCards, int deltaP, int deltaT);
void actResetPT(QList<CardState *> selectedCards);
void actRequestSetPTDialog(QList<CardState *> selectedCards);
void actSetPT(QList<CardState *> selectedCards, const QString &pt);
void actIncP(QList<CardState *> selectedCards);
void actDecP(QList<CardState *> selectedCards);
void actIncT(QList<CardState *> selectedCards);
void actDecT(QList<CardState *> selectedCards);
void actIncPT(QList<CardState *> selectedCards);
void actDecPT(QList<CardState *> selectedCards);
void actFlowP(QList<CardState *> selectedCards);
void actFlowT(QList<CardState *> selectedCards);
void actMoveCardXCardsFromTop(QList<CardItem *> selectedCards, int number);
void actRemoveCardCounter(QList<CardItem *> selectedCards, int counterId);
void actAddCardCounter(QList<CardItem *> selectedCards, int counterId);
void actRequestSetCardCounterDialog(QList<CardItem *> selectedCards, int counterId);
void actSetCardCounter(QList<CardItem *> selectedCards, int counterId, const QString &counterValue);
void actIncrementAllCardCounters(QList<CardItem *> cardsToUpdate);
void actAttach();
void actUnattach(QList<CardItem *> selectedCards);
void actDrawArrow();
void actIncPT(QList<CardItem *> selectedCards, int deltaP, int deltaT);
void actResetPT(QList<CardItem *> selectedCards);
void actRequestSetPTDialog(QList<CardItem *> selectedCards);
void actSetPT(QList<CardItem *> selectedCards, const QString &pt);
void actIncP(QList<CardItem *> selectedCards);
void actDecP(QList<CardItem *> selectedCards);
void actIncT(QList<CardItem *> selectedCards);
void actDecT(QList<CardItem *> selectedCards);
void actIncPT(QList<CardItem *> selectedCards);
void actDecPT(QList<CardItem *> selectedCards);
void actFlowP(QList<CardItem *> selectedCards);
void actFlowT(QList<CardItem *> selectedCards);
/** @brief Adjusts a player counter by a signed delta. */
void actIncrementCounter(int counterId, int delta);
void actReduceLifeByPower(QList<CardItem *> selectedCards);
/** @brief Sets a player counter to an absolute value. */
void actSetCounter(int counterId, int value);
void actReduceLifeByPower(QList<CardState *> selectedCards);
void actRequestSetAnnotationDialog(QList<CardState *> selectedCards);
void actSetAnnotation(QList<CardState *> selectedCards, const QString &annotation);
void actReveal(QList<CardState *> selectedCards, QAction *action);
void actRequestSetAnnotationDialog(QList<CardItem *> selectedCards);
void actSetAnnotation(QList<CardItem *> selectedCards, const QString &annotation);
void actReveal(QList<CardItem *> selectedCards, QAction *action);
void actRevealHand(int revealToPlayerId);
void actRevealRandomHandCard(int revealToPlayerId);
void actRevealLibrary(int revealToPlayerId);
void actSortHand();
void cardMenuAction(QList<CardState *> selectedCards, CardMenuActionType type);
void cardMenuAction(QList<CardItem *> selectedCards, CardMenuActionType type);
private:
PlayerLogic *player;
// Borrowed settings providers (owned by the application). Null outside the client, in which case the compiled-in
// defaults below apply.
IInterfaceSettingsProvider *interfaceSettings;
ICardPreferenceProvider *cardPreferences;
int defaultNumberTopCards = 1;
int defaultNumberTopCardsToPlaceBelow = 1;
@ -308,23 +245,18 @@ private:
bool lastRelatedCreationSucceeded = false;
[[nodiscard]] bool getPlayToStack() const;
[[nodiscard]] int getStartingHandSize() const;
[[nodiscard]] bool getAnnotateTokens() const;
[[nodiscard]] QString getCardPreferenceOverride(const QString &cardName) const;
void createCard(const CardState *sourceCard,
void createCard(const CardItem *sourceCard,
const QString &dbCardName,
CardRelationType attach = CardRelationType::DoesNotAttach,
bool persistent = false,
bool faceDown = false);
void playSelectedCards(QList<CardState *> selectedCards, bool faceDown = false);
void playSelectedCards(QList<CardItem *> selectedCards, bool faceDown = false);
void cmdSetTopCard(Command_MoveCard &cmd);
void cmdSetBottomCard(Command_MoveCard &cmd);
void offsetCardCounter(QList<CardState *> selectedCards, int counterId, int offset);
void offsetCardCounter(QList<CardItem *> selectedCards, int counterId, int offset);
};
#endif // COCKATRICE_PLAYER_ACTIONS_H

View file

@ -1,14 +1,14 @@
#include "player_event_handler.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../abstract_game.h"
#include "../board/arrow_data.h"
#include "../board/card_list.h"
#include "../board/card_state.h"
#include "../board/counter_state.h"
#include "../zones/card_zone_logic.h"
#include "../zones/hand_zone_logic.h"
#include "../zones/pile_zone_logic.h"
#include "../zones/view_zone_logic.h"
#include "libcockatrice/card/card_info.h"
#include "libcockatrice/card/printing/exact_card.h"
#include "libcockatrice/protocol/pb/card_attributes.pb.h"
@ -27,9 +27,6 @@
#include <QSharedPointer>
#include <QtGlobal>
#include <algorithm>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/database/card_database_querier.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/protocol/pb/context_undo_draw.pb.h>
#include <libcockatrice/protocol/pb/event_attach_card.pb.h>
@ -52,7 +49,6 @@
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
#include <libcockatrice/protocol/pb/event_set_counter.pb.h>
#include <libcockatrice/protocol/pb/event_shuffle.pb.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
#include <qlogging.h>
#include <string>
@ -80,12 +76,12 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event)
}
// close all views that contain shuffled cards
for (auto *viewLogic : zone->getViews()) {
if (viewLogic != nullptr) {
int length = viewLogic->getCards().length();
for (ZoneViewZone *view : zone->getViews()) {
if (view != nullptr) {
int length = view->getLogic()->getCards().length();
// we want to close empty views as well
if (length == 0 || length > absStart) { // note this assumes views always start at the top of the library
viewLogic->requestClose();
view->close();
}
} else {
qWarning() << zone->getName() << "of" << player->getPlayerInfo()->getName() << "holds empty zoneview!";
@ -159,29 +155,23 @@ void PlayerEventHandler::eventCreateToken(const Event_CreateToken &event)
}
CardRef cardRef = {QString::fromStdString(event.card_name()), QString::fromStdString(event.card_provider_id())};
CardItem *card = new CardItem(player, nullptr, cardRef, event.card_id());
// use db PT if not provided in event and not face-down
QString pt = QString::fromStdString(event.pt());
if (pt.isEmpty() && !event.face_down()) {
ExactCard dbCard = CardDatabaseManager::query()->getCard(cardRef);
if (!QString::fromStdString(event.pt()).isEmpty()) {
card->setPT(QString::fromStdString(event.pt()));
} else if (!event.face_down()) {
ExactCard dbCard = card->getCard();
if (dbCard) {
pt = dbCard.getInfo().getPowTough();
card->setPT(dbCard.getInfo().getPowTough());
}
}
card->setColor(QString::fromStdString(event.color()));
card->setAnnotation(QString::fromStdString(event.annotation()));
card->setDestroyOnZoneChange(event.destroy_on_zone_change());
card->setFaceDown(event.face_down());
emit logCreateToken(player, cardRef.name, pt, event.face_down());
ServerInfo_Card cardInfo;
cardInfo.set_id(event.card_id());
cardInfo.set_name(event.card_name());
cardInfo.set_provider_id(event.card_provider_id());
cardInfo.set_pt(pt.toStdString());
cardInfo.set_color(event.color());
cardInfo.set_annotation(event.annotation());
cardInfo.set_destroy_on_zone_change(event.destroy_on_zone_change());
cardInfo.set_face_down(event.face_down());
cardInfo.set_x(event.x());
cardInfo.set_y(event.y());
emit zone->createCardRequested(cardInfo, true);
emit logCreateToken(player, card->getName(), card->getPT(), card->getFaceDown());
zone->addCard(card, true, event.x(), event.y());
}
void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event,
@ -203,7 +193,7 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event,
emit logSetTapped(player, nullptr, event.attr_value() == "1");
}
} else {
CardState *card = zone->getCard(event.card_id());
CardItem *card = zone->getCard(event.card_id());
if (!card) {
qWarning() << "PlayerEventHandler::eventSetCardAttr: card id=" << event.card_id() << "not found";
return;
@ -213,7 +203,7 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event,
}
void PlayerEventHandler::setCardAttrHelper(const GameEventContext &context,
CardState *card,
CardItem *card,
CardAttribute attribute,
const QString &avalue,
bool allCards,
@ -274,7 +264,7 @@ void PlayerEventHandler::eventSetCardCounter(const Event_SetCardCounter &event)
return;
}
CardState *card = zone->getCard(event.card_id());
CardItem *card = zone->getCard(event.card_id());
if (!card) {
return;
}
@ -351,13 +341,13 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv
if (x == -1) {
x = 0;
}
CardState *card = startZone->takeCard(position, event.card_id(), startZone != targetZone);
CardItem *card = startZone->takeCard(position, event.card_id(), startZone != targetZone);
if (card == nullptr) {
return;
}
const bool zoneChanged = startZone != targetZone;
const bool ownerChanged = zoneChanged && startZone->getPlayer() != targetZone->getPlayer();
emit cardViewRefreshRequested(card, zoneChanged, ownerChanged ? targetZone->getPlayer() : nullptr);
if (startZone != targetZone) {
card->deleteCardInfoPopup();
}
if (event.has_card_name()) {
QString name = QString::fromStdString(event.card_name());
QString providerId =
@ -366,19 +356,27 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv
}
if (card->getAttachedTo() && (startZone != targetZone)) {
CardState *parentCard = card->getAttachedTo();
// CardState::setAttachedTo removes the card from the old parent's attachedCards list and emits the zone events.
CardItem *parentCard = card->getAttachedTo();
card->setAttachedTo(nullptr);
parentCard->getZone()->reorganizeCards();
}
card->deleteDragItem();
card->setId(event.new_card_id());
card->setFaceDown(event.face_down());
if (startZone != targetZone) {
const QList<CardState *> &attachedCards = card->getAttachedCards();
card->setBeingPointedAt(false);
card->setHovered(false);
const QList<CardItem *> &attachedCards = card->getAttachedCards();
for (auto attachedCard : attachedCards) {
emit targetZone->cardAdded(attachedCard);
}
if (startZone->getPlayer() != targetZone->getPlayer()) {
card->setOwner(targetZone->getPlayer());
}
}
// The log event has to be sent before the card is added to the target zone
@ -406,7 +404,7 @@ void PlayerEventHandler::eventFlipCard(const Event_FlipCard &event)
if (!zone) {
return;
}
CardState *card = zone->getCard(event.card_id());
CardItem *card = zone->getCard(event.card_id());
if (!card) {
return;
}
@ -429,28 +427,20 @@ void PlayerEventHandler::eventDestroyCard(const Event_DestroyCard &event)
return;
}
CardState *card = zone->getCard(event.card_id());
CardItem *card = zone->getCard(event.card_id());
if (!card) {
return;
}
QList<CardState *> attachedCards = card->getAttachedCards();
QList<CardItem *> attachedCards = card->getAttachedCards();
// This list is always empty except for buggy server implementations.
for (auto &attachedCard : attachedCards) {
card->removeAttachedCard(attachedCard);
attachedCard->setGridPoint(QPoint(-1, attachedCard->getGridPoint().y()));
attachedCard->setAttachedTo(nullptr);
if (attachedCard->getZone() == nullptr) {
attachedCard->deleteView();
} else {
emit attachedCard->getZone()->cardAdded(attachedCard);
attachedCard->getZone()->reorganizeCards();
}
}
emit logDestroyCard(player, card->getName());
zone->takeCard(-1, event.card_id(), true);
card->deleteView();
card->deleteLater();
}
void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event)
@ -458,7 +448,7 @@ void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event)
const QMap<int, PlayerLogic *> &playerList = player->getGame()->getPlayerManager()->getPlayers();
PlayerLogic *targetPlayer = nullptr;
CardZoneLogic *targetZone = nullptr;
CardState *targetCard = nullptr;
CardItem *targetCard = nullptr;
if (event.has_target_player_id()) {
targetPlayer = playerList.value(event.target_player_id(), 0);
if (targetPlayer) {
@ -474,12 +464,12 @@ void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event)
return;
}
CardState *startCard = startZone->getCard(event.card_id());
CardItem *startCard = startZone->getCard(event.card_id());
if (!startCard) {
return;
}
CardState *oldParent = startCard->getAttachedTo();
CardItem *oldParent = startCard->getAttachedTo();
startCard->setAttachedTo(targetCard);
@ -508,7 +498,7 @@ void PlayerEventHandler::eventDrawCards(const Event_DrawCards &event)
if (listSize) {
for (int i = 0; i < listSize; ++i) {
const ServerInfo_Card &cardInfo = event.cards(i);
CardState *card = _deck->takeCard(0, cardInfo.id());
CardItem *card = _deck->takeCard(0, cardInfo.id());
QString cardName = QString::fromStdString(cardInfo.name());
QString providerId = QString::fromStdString(cardInfo.provider_id());
card->setCardRef({cardName, providerId});
@ -556,7 +546,7 @@ void PlayerEventHandler::eventRevealCards(const Event_RevealCards &event, EventP
for (const auto &card : cardList) {
QString cardName = QString::fromStdString(card->name());
QString providerId = QString::fromStdString(card->provider_id());
CardState *cardItem = zone->getCard(card->id());
CardItem *cardItem = zone->getCard(card->id());
if (!cardItem) {
continue;
}

View file

@ -27,7 +27,7 @@
#include <qtmetamacros.h>
#include <sys/types.h>
class CardState;
class CardItem;
class CardZoneLogic;
class PlayerLogic;
class Event_AttachCard;
@ -61,7 +61,7 @@ template <typename T> class QList;
* Design notes:
* - All event handlers assume events are authoritative and already validated
* by the server.
* - Most handlers mutate both logical state (CardState, CardZoneLogic, counters)
* - Most handlers mutate both logical state (CardItem, CardZoneLogic, counters)
* and visual/UI state (views, arrows, menus).
* - Logging signals are emitted *after* or *during* state mutation, depending
* on whether later mutations would invalidate log data.
@ -238,7 +238,7 @@ signals:
void logUndoDraw(PlayerLogic *player, QString cardName);
void logUndoDrawFailed(PlayerLogic *player);
void logMoveCard(PlayerLogic *player,
CardState *card,
CardItem *card,
CardZoneLogic *startZone,
int oldX,
CardZoneLogic *targetZone,
@ -248,11 +248,11 @@ signals:
void logAttachCard(PlayerLogic *player, QString cardName, PlayerLogic *targetPlayer, QString targetCardName);
void logUnattachCard(PlayerLogic *player, QString cardName);
void logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue);
void logSetTapped(PlayerLogic *player, CardState *card, bool tapped);
void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped);
void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue);
void logSetDoesntUntap(PlayerLogic *player, CardState *card, bool doesntUntap);
void logSetPT(PlayerLogic *player, CardState *card, QString newPT);
void logSetAnnotation(PlayerLogic *player, CardState *card, QString newAnnotation);
void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap);
void logSetPT(PlayerLogic *player, CardItem *card, QString newPT);
void logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation);
void logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed = false);
void logRevealCards(PlayerLogic *player,
CardZoneLogic *zone,
@ -266,23 +266,15 @@ signals:
void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal);
/** @} */
void cardZoneChanged(CardState *card, bool sameZone);
void requestCardMenuUpdate(CardState *card);
/**
* @brief A card was just moved by the server and its view needs refreshing.
*
* @param card The card that moved.
* @param zoneChanged Whether the card crossed a zone boundary.
* @param newOwner The player the card now belongs to, or nullptr to leave the owner alone.
*/
void cardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner);
void cardZoneChanged(CardItem *card, bool sameZone);
void requestCardMenuUpdate(const CardItem *card);
private:
/** Owning player instance. */
PlayerLogic *player;
void setCardAttrHelper(const GameEventContext &context,
CardState *card,
CardItem *card,
CardAttribute attribute,
const QString &avalue,
bool allCards,

View file

@ -1,9 +1,8 @@
#include "player_logic.h"
#include "../../game_graphics/board/card_item.h"
#include "../abstract_game.h"
#include "../board/arrow_data.h"
#include "../board/card_list.h"
#include "../board/card_state.h"
#include "../board/counter_state.h"
#include "../game_meta_info.h"
#include "../zones/hand_zone_logic.h"
@ -34,17 +33,10 @@
#include <qalgorithms.h>
#include <string>
PlayerLogic::PlayerLogic(const ServerInfo_User &info,
int _id,
bool _local,
bool _judge,
AbstractGame *_parent,
IInterfaceSettingsProvider *interfaceSettings,
ICardPreferenceProvider *cardPreferences)
PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent)
: QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)),
playerEventHandler(new PlayerEventHandler(this)),
playerActions(new PlayerActions(this, interfaceSettings, cardPreferences)), active(false), conceded(false),
zoneId(0), dialogSemaphore(false)
playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false),
conceded(false), zoneId(0), dialogSemaphore(false)
{
initializeZones();
}
@ -180,20 +172,22 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
const int cardListSize = zoneInfo.card_list_size();
if (!cardListSize) {
for (int j = 0; j < zoneInfo.card_count(); ++j) {
emit zone->createCardRequested(ServerInfo_Card(), false);
zone->addCard(new CardItem(this), false, -1);
}
} else {
for (int j = 0; j < cardListSize; ++j) {
ServerInfo_Card cardInfo = zoneInfo.card_list(j);
const ServerInfo_Card &cardInfo = zoneInfo.card_list(j);
auto *card = new CardItem(this);
card->processCardInfo(cardInfo);
// Zones without coordinates (hand, piles, stack) preserve the order
// they arrive in on the server in the positions of their cards list.
// The x coordinate of such cards is always 0, so inserting at it
// would reverse the list on reconnect. Append instead.
if (!zoneInfo.with_coords()) {
cardInfo.set_x(-1);
cardInfo.set_y(-1);
if (zoneInfo.with_coords()) {
zone->addCard(card, false, cardInfo.x(), cardInfo.y());
} else {
zone->addCard(card, false, -1);
}
emit zone->createCardRequested(cardInfo, false);
}
}
if (zoneInfo.has_always_reveal_top_card()) {
@ -225,8 +219,8 @@ void PlayerLogic::processCardAttachment(const ServerInfo_Player &info)
for (int j = 0; j < cardListSize; ++j) {
const ServerInfo_Card &cardInfo = zoneInfo.card_list(j);
if (cardInfo.has_attach_player_id()) {
CardState *startCard = zone->getCard(cardInfo.id());
CardState *targetCard =
CardItem *startCard = zone->getCard(cardInfo.id());
CardItem *targetCard =
game->getCard(cardInfo.attach_player_id(), QString::fromStdString(cardInfo.attach_zone()),
cardInfo.attach_card_id());
if (!targetCard) {
@ -245,19 +239,19 @@ void PlayerLogic::processCardAttachment(const ServerInfo_Player &info)
}
}
void PlayerLogic::addCard(AbstractCardItem *card)
void PlayerLogic::addCard(CardItem *card)
{
emit newCardAdded(card);
}
void PlayerLogic::deleteCard(CardState *card)
void PlayerLogic::deleteCard(CardItem *card)
{
if (card == nullptr) {
return;
} else if (dialogSemaphore) {
cardsToDelete.append(card);
} else {
card->deleteView();
card->deleteLater();
}
}
@ -338,7 +332,7 @@ bool PlayerLogic::clearCardsToDelete()
for (auto &i : cardsToDelete) {
if (i != nullptr) {
i->deleteView();
i->deleteLater();
}
}
cardsToDelete.clear();

View file

@ -7,8 +7,6 @@
#ifndef PLAYER_H
#define PLAYER_H
#include "../board/arrow_data.h"
#include "../board/counter_state.h"
#include "../zones/card_zone_logic.h"
#include "../zones/hand_zone_logic.h"
#include "../zones/pile_zone_logic.h"
@ -19,16 +17,12 @@
#include "libcockatrice/utility/playmat_params.h"
#include <QColor>
#include <QInputDialog>
#include <QList>
#include <QLoggingCategory>
#include <QMap>
#include <QObject>
#include <QString>
#include <QTimer>
#include <libcockatrice/deck_list/loaded_deck.h>
#include <libcockatrice/filters/filter_string.h>
#include <libcockatrice/protocol/pb/card_attributes.pb.h>
#include <libcockatrice/protocol/pb/game_event.pb.h>
#include <libcockatrice/utility/zone_names.h>
#include <qtmetamacros.h>
@ -37,6 +31,7 @@
// signatures, which the moc-generated code needs to be complete.
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
class CardItem;
class CounterState;
class QWidget;
class ServerInfo_PlayerProperties;
@ -47,8 +42,6 @@ template <class T> class QSharedPointer;
inline Q_LOGGING_CATEGORY(PlayerLog, "player");
class AbstractCardItem;
class AbstractGame;
class ICardPreferenceProvider;
class IInterfaceSettingsProvider;
class PlayerInfo;
class PlayerEventHandler;
class PlayerActions;
@ -74,7 +67,7 @@ signals:
/** @brief Emitted when the remote playmat (card/params) is updated from player properties. */
void playmatChanged();
void newCardAdded(AbstractCardItem *card);
void requestCardMenuUpdate(CardState *card);
void requestCardMenuUpdate(const CardItem *card);
void counterAdded(CounterState *state);
void counterRemoved(int counterId);
void rearrangeCounters();
@ -94,13 +87,7 @@ public slots:
void onRequestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed);
public:
PlayerLogic(const ServerInfo_User &info,
int _id,
bool _local,
bool _judge,
AbstractGame *_parent,
IInterfaceSettingsProvider *interfaceSettings = nullptr,
ICardPreferenceProvider *cardPreferences = nullptr);
PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent);
~PlayerLogic() override;
void initializeZones();
@ -110,8 +97,8 @@ public:
void processPlayerInfo(const ServerInfo_Player &info);
void processCardAttachment(const ServerInfo_Player &info);
void addCard(AbstractCardItem *c);
void deleteCard(CardState *c);
void addCard(CardItem *c);
void deleteCard(CardItem *c);
bool clearCardsToDelete();
@ -263,7 +250,7 @@ private:
QMap<int, CounterState *> counters;
bool dialogSemaphore;
QList<CardState *> cardsToDelete;
QList<CardItem *> cardsToDelete;
// Playmat from player properties (for opponent display)
CardRef remotePlaymatCard;
@ -271,4 +258,15 @@ private:
bool hasRemotePlaymat = false;
};
class AnnotationDialog : public QInputDialog
{
Q_OBJECT
void keyPressEvent(QKeyEvent *e) override;
public:
explicit AnnotationDialog(QWidget *parent = nullptr) : QInputDialog(parent)
{
}
};
#endif

View file

@ -12,12 +12,9 @@
PlayerManager::PlayerManager(AbstractGame *_game,
int _localPlayerId,
bool _localPlayerIsJudge,
bool localPlayerIsSpectator,
IInterfaceSettingsProvider *_interfaceSettings,
ICardPreferenceProvider *_cardPreferences)
bool localPlayerIsSpectator)
: QObject(_game), game(_game), players(QMap<int, PlayerLogic *>()), localPlayerId(_localPlayerId),
localPlayerIsJudge(_localPlayerIsJudge), localPlayerIsSpectator(localPlayerIsSpectator),
interfaceSettings(_interfaceSettings), cardPreferences(_cardPreferences)
localPlayerIsJudge(_localPlayerIsJudge), localPlayerIsSpectator(localPlayerIsSpectator)
{
}
@ -55,7 +52,7 @@ bool PlayerManager::isLocalPlayer(int playerId)
PlayerLogic *PlayerManager::addPlayer(int playerId, const ServerInfo_User &info)
{
auto *newPlayer = new PlayerLogic(info, playerId, isLocalPlayer(playerId) || game->getGameState()->getIsLocalGame(),
isJudge(), getGame(), interfaceSettings, cardPreferences);
isJudge(), getGame());
connect(newPlayer, &PlayerLogic::concededChanged, this, &PlayerManager::onPlayerConceded);
players.insert(playerId, newPlayer);
emit playerAdded(newPlayer);

View file

@ -16,8 +16,6 @@
#include <qtmetamacros.h>
class AbstractGame;
class ICardPreferenceProvider;
class IInterfaceSettingsProvider;
class PlayerLogic;
class PlayerManager : public QObject
@ -25,12 +23,7 @@ class PlayerManager : public QObject
Q_OBJECT
public:
PlayerManager(AbstractGame *_game,
int _localPlayerId,
bool _localPlayerIsJudge,
bool localPlayerIsSpectator,
IInterfaceSettingsProvider *interfaceSettings = nullptr,
ICardPreferenceProvider *cardPreferences = nullptr);
PlayerManager(AbstractGame *_game, int _localPlayerId, bool _localPlayerIsJudge, bool localPlayerIsSpectator);
AbstractGame *game;
QMap<int, PlayerLogic *> players;
@ -38,9 +31,6 @@ public:
bool localPlayerIsJudge;
bool localPlayerIsSpectator;
QMap<int, ServerInfo_User> spectators;
// Borrowed settings providers forwarded to each PlayerLogic (owned by the application).
IInterfaceSettingsProvider *interfaceSettings;
ICardPreferenceProvider *cardPreferences;
[[nodiscard]] bool isSpectator() const
{

View file

@ -9,15 +9,10 @@
class GameReplay;
class QObject;
Replay::Replay(QObject *_parent,
const GameReplay *_replay,
bool isLocalGame,
IInterfaceSettingsProvider *interfaceSettings,
ICardPreferenceProvider *cardPreferences)
: AbstractGame(_parent)
Replay::Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent)
{
gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false);
connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged);
playerManager = new PlayerManager(this, -1, false, true, interfaceSettings, cardPreferences);
playerManager = new PlayerManager(this, -1, false, true);
loadReplay(_replay);
}

View file

@ -0,0 +1,26 @@
/**
* @file replay.h
* @ingroup GameLogic
* @ingroup Replay
*/
//! \todo Document this file.
#ifndef COCKATRICE_REPLAY_H
#define COCKATRICE_REPLAY_H
#include "abstract_game.h"
#include <qtmetamacros.h>
class GameReplay;
class QObject;
class Replay : public AbstractGame
{
Q_OBJECT
public:
explicit Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame);
};
#endif // COCKATRICE_REPLAY_H

View file

@ -5,9 +5,9 @@ namespace CardZoneAlgorithms
{
/**
* Shared insertion logic for zones where cards follow the standard pattern:
* clamp index, insert, clear identity if contents unknown, reset state.
* Visibility and parenting are handled by the graphics zone's onCardAdded().
* Shared insertion logic for zones where cards become visible on add and follow
* the standard pattern: clamp index, insert, clear identity if contents unknown,
* reset state, show card.
*
* Zones with different post-add behavior (signal connections, positional resets,
* hidden cards, or coordinate-based placement) should NOT use this — implement
@ -18,7 +18,8 @@ namespace CardZoneAlgorithms
*
* @tparam CardList Must provide: size() -> int, insert(int, CardType*),
* getContentsKnown() -> bool
* @tparam CardType Must provide: setId(int), setCardRef(CardRefType), resetState(bool)
* @tparam CardType Must provide: setId(int), setCardRef(CardRefType),
* resetState(bool), setVisible(bool)
* @param keepAnnotations Forwarded to card->resetState(). Stack-like zones preserve
* annotations across zone transitions; hand-like zones clear them.
*/
@ -36,6 +37,7 @@ void addCardToList(CardList &cards, CardType *card, int x, bool keepAnnotations)
}
card->resetState(keepAnnotations);
card->setVisible(true);
}
} // namespace CardZoneAlgorithms

View file

@ -1,11 +1,13 @@
#include "card_zone_logic.h"
#include "../../client/translation.h"
#include "../../game_graphics/board/card_item.h"
#include "../../game_graphics/zones/card_zone.h"
#include "../../game_graphics/zones/view_zone.h"
#include "../board/card_list.h"
#include "../board/card_state.h"
#include "../player/player_actions.h"
#include "../player/player_info.h"
#include "../player/player_logic.h"
#include "../translation.h"
#include "libcockatrice/card/database/card_database.h"
#include "view_zone_logic.h"
@ -42,24 +44,19 @@ CardZoneLogic::CardZoneLogic(PlayerLogic *_player,
&CardZoneLogic::refreshCardInfos);
}
void CardZoneLogic::addCard(CardState *card, const bool reorganize, const int x, const int y)
void CardZoneLogic::addCard(CardItem *card, const bool reorganize, const int x, const int y)
{
if (!card) {
qCWarning(CardZoneLogicLog) << "CardZoneLogic::addCard() card is null; this shouldn't normally happen";
qCWarning(CardZoneLog) << "CardZoneLogic::addCard() card is null; this shouldn't normally happen";
return;
}
for (auto *viewLogic : views) {
if (viewLogic->prepareAddCard(x)) {
ServerInfo_Card cardInfo;
const CardRef &cardRef = card->getCardRef();
cardInfo.set_name(cardRef.name.toStdString());
cardInfo.set_provider_id(cardRef.providerId.toStdString());
cardInfo.set_id(card->getId());
cardInfo.set_face_down(card->getFaceDown());
cardInfo.set_x(x);
cardInfo.set_y(y);
emit viewLogic->createCardRequested(cardInfo, reorganize);
for (auto *view : views) {
if (qobject_cast<ZoneViewZoneLogic *>(view->getLogic())->prepareAddCard(x)) {
auto copy = new CardItem(player, nullptr, card->getCardRef(), card->getId());
copy->setFaceDown(card->getFaceDown());
view->getLogic()->addCard(copy, reorganize, x, y);
}
}
@ -74,7 +71,7 @@ void CardZoneLogic::addCard(CardState *card, const bool reorganize, const int x,
emit cardCountChanged();
}
CardState *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone)
CardItem *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone)
{
if (position == -1) {
// position == -1 means either that the zone is indexed by card id
@ -93,11 +90,11 @@ CardState *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone)
return nullptr;
}
for (auto *viewLogic : views) {
viewLogic->removeCard(position, toNewZone);
for (auto *view : views) {
qobject_cast<ZoneViewZoneLogic *>(view->getLogic())->removeCard(position, toNewZone);
}
CardState *c = cards.takeAt(position);
CardItem *c = cards.takeAt(position);
c->setId(cardId);
@ -106,11 +103,11 @@ CardState *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone)
return c;
}
CardState *CardZoneLogic::getCard(int cardId)
CardItem *CardZoneLogic::getCard(int cardId)
{
CardState *c = cards.findCard(cardId);
CardItem *c = cards.findCard(cardId);
if (!c) {
qCWarning(CardZoneLogicLog) << "CardZoneLogic::getCard: card id=" << cardId << "not found";
qCWarning(CardZoneLog) << "CardZoneLogic::getCard: card id=" << cardId << "not found";
return nullptr;
}
// If the card's id is -1, this zone is invisible,
@ -122,10 +119,10 @@ CardState *CardZoneLogic::getCard(int cardId)
return c;
}
void CardZoneLogic::removeCard(CardState *card)
void CardZoneLogic::removeCard(CardItem *card)
{
if (!card) {
qCWarning(CardZoneLogicLog) << "CardZoneLogic::removeCard: card is null, this shouldn't normally happen";
qCWarning(CardZoneLog) << "CardZoneLogic::removeCard: card is null, this shouldn't normally happen";
return;
}
@ -138,8 +135,8 @@ void CardZoneLogic::removeCard(CardState *card)
void CardZoneLogic::refreshCardInfos()
{
for (const auto &cardState : cards) {
cardState->refreshCardInfo();
for (const auto &cardItem : cards) {
cardItem->refreshCardInfo();
}
}
@ -171,17 +168,17 @@ void CardZoneLogic::clearContents()
const CardList toClear = cards;
// Detach and notify attached cards and zones *before* deleting anything.
for (CardState *card : toClear) {
for (CardItem *card : toClear) {
// If an incorrectly implemented server doesn't return attached cards to whom they belong before dropping a
// player, we have to return them to avoid a crash.
const QList<CardState *> &attachedCards = card->getAttachedCards();
for (CardState *attachedCard : attachedCards) {
const QList<CardItem *> &attachedCards = card->getAttachedCards();
for (CardItem *attachedCard : attachedCards) {
emit attachedCard->getZone()->cardAdded(attachedCard);
}
}
// Now request deletions after all manipulations are done.
for (CardState *card : toClear) {
for (CardItem *card : toClear) {
player->deleteCard(card);
}

View file

@ -7,30 +7,28 @@
#ifndef COCKATRICE_CARD_ZONE_LOGIC_H
#define COCKATRICE_CARD_ZONE_LOGIC_H
#include "../../client/translation.h"
#include "../board/card_list.h"
#include "../translation.h"
#include <QList>
#include <QLoggingCategory>
#include <QObject>
#include <QString>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
#include <qtmetamacros.h>
class CardItem;
inline Q_LOGGING_CATEGORY(CardZoneLogicLog, "card_zone_logic");
class PlayerLogic;
class ZoneViewZoneLogic;
class CardDragItem;
class ZoneViewZone;
class CardZoneLogic : public QObject
{
Q_OBJECT
signals:
void cardAdded(CardState *addedCard);
/** @brief Asks the graphics side to build the card item for @p cardInfo and add it to this zone. */
void createCardRequested(const ServerInfo_Card &cardInfo, bool reorganize);
void cardAdded(CardItem *addedCard);
void cardCountChanged();
void reorganizeCards();
void updateGraphics();
@ -45,17 +43,16 @@ public:
bool _contentsKnown,
QObject *parent = nullptr);
void addCard(CardState *card, bool reorganize, int x, int y = -1);
void addCard(CardItem *card, bool reorganize, int x, int y = -1);
// getCard() finds a card by id.
CardState *getCard(int cardId);
void removeCard(CardState *card);
CardItem *getCard(int cardId);
void removeCard(CardItem *card);
// takeCard() finds a card by position and removes it from the zone and from all of its views.
virtual CardState *takeCard(int position, int cardId, bool canResize = true);
virtual CardItem *takeCard(int position, int cardId, bool canResize = true);
void rawInsertCard(CardState *card, int index)
void rawInsertCard(CardItem *card, int index)
{
cards.insert(index, card);
emit cardCountChanged();
}
[[nodiscard]] const CardList &getCards() const
@ -66,7 +63,6 @@ public:
void sortCards(const QList<CardList::SortOption> &options)
{
cards.sortBy(options);
emit cardCountChanged();
}
[[nodiscard]] QString getName() const
{
@ -81,7 +77,7 @@ public:
{
return cards.getContentsKnown();
}
QList<ZoneViewZoneLogic *> &getViews()
QList<ZoneViewZone *> &getViews()
{
return views;
}
@ -113,12 +109,12 @@ protected:
PlayerLogic *player;
QString name;
CardList cards;
QList<ZoneViewZoneLogic *> views;
QList<ZoneViewZone *> views;
bool hasCardAttr;
bool isShufflable;
bool alwaysRevealTopCard;
virtual void addCardImpl(CardState *card, int x, int y) = 0;
virtual void addCardImpl(CardItem *card, int x, int y) = 0;
};
#endif // COCKATRICE_CARD_ZONE_LOGIC_H

View file

@ -1,6 +1,6 @@
#include "hand_zone_logic.h"
#include "../board/card_state.h"
#include "../../game_graphics/board/card_item.h"
#include "card_zone_algorithms.h"
class PlayerLogic;
@ -16,7 +16,7 @@ HandZoneLogic::HandZoneLogic(PlayerLogic *_player,
{
}
void HandZoneLogic::addCardImpl(CardState *card, int x, int /*y*/)
void HandZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
{
CardZoneAlgorithms::addCardToList(cards, card, x, false);
}

View file

@ -26,7 +26,7 @@ public:
QObject *parent = nullptr);
protected:
void addCardImpl(CardState *card, int x, int y) override;
void addCardImpl(CardItem *card, int x, int y) override;
};
#endif // COCKATRICE_HAND_ZONE_LOGIC_H

View file

@ -1,6 +1,10 @@
#include "pile_zone_logic.h"
#include "../board/card_state.h"
#include "../../game_graphics/board/card_item.h"
#include "../board/card_list.h"
class PlayerLogic;
class QObject;
PileZoneLogic::PileZoneLogic(PlayerLogic *_player,
const QString &_name,
@ -12,14 +16,15 @@ PileZoneLogic::PileZoneLogic(PlayerLogic *_player,
{
}
void PileZoneLogic::addCardImpl(CardState *card, int x, int /*y*/)
void PileZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
{
connect(card, &CardState::cardPixmapUpdated, this, &PileZoneLogic::callUpdate);
connect(card, &CardItem::sigPixmapUpdated, this, &PileZoneLogic::callUpdate);
// if x is negative set it to add at end
if (x < 0 || x >= cards.size()) {
x = cards.size();
}
cards.insert(x, card);
card->setPos(0, 0);
if (!contentsKnown()) {
card->setCardRef({});
card->setId(-1);
@ -28,5 +33,6 @@ void PileZoneLogic::addCardImpl(CardState *card, int x, int /*y*/)
cards.at(x + 1)->setCardRef({});
}
}
card->resetState(false);
card->setVisible(false);
card->resetState();
}

View file

@ -31,7 +31,7 @@ public:
QObject *parent = nullptr);
protected:
void addCardImpl(CardState *card, int x, int y) override;
void addCardImpl(CardItem *card, int x, int y) override;
};
#endif // COCKATRICE_PILE_ZONE_LOGIC_H

View file

@ -1,6 +1,6 @@
#include "stack_zone_logic.h"
#include "../board/card_state.h"
#include "../../game_graphics/board/card_item.h"
#include "card_zone_algorithms.h"
class PlayerLogic;
@ -16,7 +16,7 @@ StackZoneLogic::StackZoneLogic(PlayerLogic *_player,
{
}
void StackZoneLogic::addCardImpl(CardState *card, int x, int /*y*/)
void StackZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
{
CardZoneAlgorithms::addCardToList(cards, card, x, true);
}

View file

@ -26,7 +26,7 @@ public:
QObject *parent = nullptr);
protected:
void addCardImpl(CardState *card, int x, int y) override;
void addCardImpl(CardItem *card, int x, int y) override;
};
#endif // COCKATRICE_STACK_ZONE_LOGIC_H

View file

@ -1,9 +1,14 @@
#include "table_zone_logic.h"
#include "../board/card_state.h"
#include "../../game_graphics/board/card_item.h"
#include "../board/card_list.h"
#include "libcockatrice/card/card_info.h"
#include <QPoint>
class PlayerLogic;
class QObject;
TableZoneLogic::TableZoneLogic(PlayerLogic *_player,
const QString &_name,
bool _hasCardAttr,
@ -14,7 +19,7 @@ TableZoneLogic::TableZoneLogic(PlayerLogic *_player,
{
}
void TableZoneLogic::addCardImpl(CardState *card, int _x, int _y)
void TableZoneLogic::addCardImpl(CardItem *card, int _x, int _y)
{
cards.append(card);
if (!card->getFaceDown() && card->getPT().isEmpty()) {
@ -24,33 +29,15 @@ void TableZoneLogic::addCardImpl(CardState *card, int _x, int _y)
card->setDoesntUntap(true);
}
card->setGridPoint(QPoint(_x, _y));
card->setVisible(true);
}
CardState *TableZoneLogic::takeCard(int position, int cardId, bool toNewZone)
CardItem *TableZoneLogic::takeCard(int position, int cardId, bool toNewZone)
{
CardState *result = CardZoneLogic::takeCard(position, cardId);
CardItem *result = CardZoneLogic::takeCard(position, cardId);
if (toNewZone) {
emit contentSizeChanged();
}
return result;
}
int TableZoneLogic::clampValidTableRow(const int row)
{
if (row < 0) {
return 0;
}
if (row >= ROW_COUNT) {
return ROW_COUNT - 1;
}
return row;
}
int TableZoneLogic::tableRowToGridY(int tableRow)
{
if (tableRow > 2) {
tableRow = 1;
}
return clampValidTableRow(2 - tableRow);
}
}

View file

@ -29,23 +29,8 @@ public:
bool _contentsKnown,
QObject *parent = nullptr);
/** @brief Number of rows in the table zone grid (0=creatures, 1=noncreatures, 2=lands). */
static const int ROW_COUNT = 3;
/**
* Clamps a grid row index into [0, ROW_COUNT - 1].
*/
static int clampValidTableRow(const int row);
/**
* Converts a card's logical table row (0=creatures, 1=noncreatures, 2=lands)
* to the corresponding grid Y coordinate. Cards with tableRow > 2 (e.g.,
* instants/sorceries) default to the noncreatures row.
*/
static int tableRowToGridY(int tableRow);
protected:
void addCardImpl(CardState *card, int x, int y) override;
void addCardImpl(CardItem *card, int x, int y) override;
/**
* @brief Removes a card from view.
@ -53,9 +38,9 @@ protected:
* @param position card position
* @param cardId id of card to take
* @param toNewZone Whether the destination of the card is not the same as the starting zone. Defaults to true
* @return CardState that has been removed
* @return CardItem that has been removed
*/
CardState *takeCard(int position, int cardId, bool toNewZone = true) override;
CardItem *takeCard(int position, int cardId, bool toNewZone = true) override;
};
#endif // COCKATRICE_TABLE_ZONE_LOGIC_H

View file

@ -1,11 +1,10 @@
#include "view_zone_logic.h"
#include "../board/card_state.h"
#include "../player/player_actions.h"
#include "../player/player_logic.h"
#include "../../client/settings/cache_settings.h"
#include "../../game_graphics/board/card_item.h"
#include "../board/card_list.h"
#include <QtGlobal>
#include <libcockatrice/interfaces/interface_interface_settings_provider.h>
#include <libcockatrice/settings/interface_settings.h>
class PlayerLogic;
@ -23,19 +22,13 @@ ZoneViewZoneLogic::ZoneViewZoneLogic(PlayerLogic *_player,
bool _revealZone,
bool _writeableRevealZone,
bool _isReversed,
IInterfaceSettingsProvider *_interfaceSettings,
QObject *parent)
: CardZoneLogic(_player, _origZone->getName(), false, false, true, parent), origZone(_origZone),
numberCards(_numberCards), revealZone(_revealZone), writeableRevealZone(_writeableRevealZone),
isReversed(_isReversed), interfaceSettings(_interfaceSettings)
isReversed(_isReversed)
{
}
bool ZoneViewZoneLogic::getCloseEmptyCardView() const
{
return interfaceSettings ? interfaceSettings->getCloseEmptyCardView() : true;
}
/**
* Checks if inserting a card at the given position requires an actual new card to be created and added to the view.
* Also does any cardId updates that would be required if a card is inserted in that position.
@ -43,7 +36,7 @@ bool ZoneViewZoneLogic::getCloseEmptyCardView() const
* Note that this method can end up modifying the cardIds despite returning false.
* (for example, if the card is inserted into a hidden portion of the deck while the view is reversed)
*
* Make sure to call this method once before calling addCard(), so that you skip creating the card and calling
* Make sure to call this method once before calling addCard(), so that you skip creating a new CardItem and calling
* addCard() if it's not required.
*
* @param x The position to insert the card at.
@ -71,7 +64,7 @@ bool ZoneViewZoneLogic::prepareAddCard(int x)
// autoclose check is done both here and in removeCard
if (cards.isEmpty() && !doInsert && getCloseEmptyCardView()) {
if (cards.isEmpty() && !doInsert && SettingsCache::instance().userInterface().getCloseEmptyCardView()) {
emit closeView();
}
@ -82,7 +75,7 @@ bool ZoneViewZoneLogic::prepareAddCard(int x)
* Make sure prepareAddCard() was called before calling addCard().
* This method assumes we already checked that the card is being inserted into the visible portion
*/
void ZoneViewZoneLogic::addCardImpl(CardState *card, int x, int /*y*/)
void ZoneViewZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
{
if (!isReversed) {
// if x is negative set it to add at end
@ -151,14 +144,14 @@ void ZoneViewZoneLogic::removeCard(int position, bool toNewZone)
return;
}
CardState *card = cards.takeAt(position);
card->deleteView();
CardItem *card = cards.takeAt(position);
card->deleteLater();
// The toNewZone check is to prevent the view from auto-closing if the view contains only a single card and that
// card gets dragged within the view.
// Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an
// unrevealed portion of the same zone.
if (cards.isEmpty() && getCloseEmptyCardView() && toNewZone) {
if (cards.isEmpty() && SettingsCache::instance().userInterface().getCloseEmptyCardView() && toNewZone) {
emit closeView();
return;
}
@ -167,18 +160,12 @@ void ZoneViewZoneLogic::removeCard(int position, bool toNewZone)
reorganizeCards();
}
void ZoneViewZoneLogic::requestClose()
{
emit closeView();
}
void ZoneViewZoneLogic::clearCards()
{
for (auto card : cards) {
card->deleteView();
card->deleteLater();
}
cards.clear();
emit cardCountChanged();
}
void ZoneViewZoneLogic::setWriteableRevealZone(bool _writeableRevealZone)
@ -190,16 +177,4 @@ void ZoneViewZoneLogic::setWriteableRevealZone(bool _writeableRevealZone)
emit removeFromViews();
}
writeableRevealZone = _writeableRevealZone;
}
PendingCommand *ZoneViewZoneLogic::prepareZoneDump()
{
return getPlayer()->getPlayerActions()->prepareZoneDump(this, numberCards);
}
void ZoneViewZoneLogic::sendZoneDump(PendingCommand *pendingCommand)
{
if (pendingCommand != nullptr) {
getPlayer()->getPlayerActions()->sendGameCommand(pendingCommand);
}
}
}

View file

@ -12,8 +12,6 @@
class PlayerLogic;
class QObject;
class PendingCommand;
class IInterfaceSettingsProvider;
class ZoneViewZoneLogic : public CardZoneLogic
{
@ -28,8 +26,6 @@ private:
int numberCards;
bool revealZone, writeableRevealZone;
bool isReversed;
// Borrowed settings provider (owned by the application). Null outside the client, in which case views stay open.
IInterfaceSettingsProvider *interfaceSettings;
public:
enum CardAction
@ -45,25 +41,15 @@ public:
bool _revealZone,
bool _writeableRevealZone,
bool _isReversed,
IInterfaceSettingsProvider *interfaceSettings = nullptr,
QObject *parent = nullptr);
bool prepareAddCard(int x);
void removeCard(int position, bool toNewZone);
void updateCardIds(CardAction action);
/**
* @brief Asks the graphics view showing this zone to close itself.
*
* Logic code has no handle on the view, so it goes through the logic object the view was built from.
*/
void requestClose();
/** @brief Removes all cards from the view, without touching anything else. Used in replay rewind */
void clearCards();
[[nodiscard]] bool getCloseEmptyCardView() const;
int getNumberCards() const
{
return numberCards;
@ -87,25 +73,8 @@ public:
return origZone;
}
/**
* @brief Builds the zone-dump command for this view without sending it.
* @return The pending command, or nullptr when there is no zone to dump.
*
* Keeps command construction in game/ so graphics views don't reach
* through the player into PlayerActions to set it up. The response can
* arrive synchronously (local games), so callers must connect to
* PendingCommand::finished before calling sendZoneDump().
*/
PendingCommand *prepareZoneDump();
/**
* @brief Sends a command returned by prepareZoneDump().
* @param pendingCommand The prepared command; nullptr is ignored.
*/
void sendZoneDump(PendingCommand *pendingCommand);
protected:
void addCardImpl(CardState *card, int x, int y) override;
void addCardImpl(CardItem *card, int x, int y) override;
};
#endif // COCKATRICE_VIEW_ZONE_LOGIC_H

View file

@ -34,6 +34,7 @@
#include <QVariant>
#include <QtGlobal>
#include <algorithm>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/settings/appearance_settings.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/debug_settings.h>
@ -42,23 +43,13 @@
class QSizeF;
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
: ArrowTarget(_owner, parent), state(new CardState(this, nullptr, cardRef, _id)), tapAngle(0),
bgColor(Qt::transparent), isHovered(false), realZValue(0)
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
bgColor(Qt::transparent), isHovered(false), realZValue(0), pendingSelectionCollapse(false)
{
setCursor(Qt::OpenHandCursor);
setFlag(ItemIsSelectable);
setCacheMode(DeviceCoordinateCache);
connect(state, &CardState::cardPixmapUpdated, this, &AbstractCardItem::pixmapUpdated);
connect(state, &CardState::cardInfoChanged, this, &AbstractCardItem::onCardInfoChanged);
connect(state, &CardState::cardRefChanged, this, &AbstractCardItem::onCardRefChanged);
connect(state, &CardState::tappedChanged, this, &AbstractCardItem::onTappedChanged);
connect(state, &CardState::facedownChanged, this, [this] { update(); });
connect(state, &CardState::colorChanged, this, [this] {
cacheBgColor();
update();
});
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::displayCardNamesChanged, this,
[this] { update(); });
connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this,
@ -76,7 +67,7 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef
AbstractCardItem::~AbstractCardItem()
{
emit deleteCardInfoPopup(state->getCardRef().name);
emit deleteCardInfoPopup(cardRef.name);
}
QRectF AbstractCardItem::boundingRect() const
@ -99,20 +90,21 @@ void AbstractCardItem::pixmapUpdated()
emit sigPixmapUpdated();
}
void AbstractCardItem::onCardInfoChanged()
{
cacheBgColor();
update();
}
void AbstractCardItem::onCardRefChanged(const CardRef &oldCardRef, const CardRef & /*newCardRef*/)
{
emit deleteCardInfoPopup(oldCardRef.name);
}
void AbstractCardItem::refreshCardInfo()
{
state->refreshCardInfo();
exactCard = CardDatabaseManager::query()->getCard(cardRef);
if (!exactCard && !cardRef.name.isEmpty()) {
CardInfo::UiAttributes attributes = {.tableRow = -1};
auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, attributes);
exactCard = ExactCard(info);
}
if (exactCard) {
connect(exactCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &AbstractCardItem::pixmapUpdated);
}
cacheBgColor();
update();
}
/**
@ -121,7 +113,7 @@ void AbstractCardItem::refreshCardInfo()
*/
const CardInfo &AbstractCardItem::getCardInfo() const
{
return state->getCardInfo();
return exactCard.getInfo();
}
void AbstractCardItem::setRealZValue(qreal _zValue)
@ -166,13 +158,13 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
QPixmap translatedPixmap;
bool paintImage = true;
if (state->getFaceDown() || state->getCardRef().name.isEmpty()) {
if (facedown || cardRef.name.isEmpty()) {
// never reveal card color, always paint the card back
CardPictureLoader::getCardBackPixmap(translatedPixmap, translatedSize.toSize());
} else {
// don't even spend time trying to load the picture if our size is too small
if (translatedSize.width() > 10) {
CardPictureLoader::getPixmap(translatedPixmap, state->getCard(), translatedSize.toSize());
CardPictureLoader::getPixmap(translatedPixmap, exactCard, translatedSize.toSize());
if (translatedPixmap.isNull()) {
paintImage = false;
}
@ -193,20 +185,19 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
painter->drawPath(shape());
}
if (translatedPixmap.isNull() || SettingsCache::instance().cardsDisplay().getDisplayCardNames() ||
state->getFaceDown()) {
if (translatedPixmap.isNull() || SettingsCache::instance().cardsDisplay().getDisplayCardNames() || facedown) {
painter->save();
transformPainter(painter, translatedSize, angle);
painter->setPen(Qt::white);
painter->setBackground(Qt::black);
painter->setBackgroundMode(Qt::OpaqueMode);
QString nameStr;
if (state->getFaceDown()) {
nameStr = "# " + QString::number(state->getId());
if (facedown) {
nameStr = "# " + QString::number(id);
} else {
QString prefix = "";
if (SettingsCache::instance().debug().getShowCardId()) {
prefix = "#" + QString::number(state->getId()) + " ";
prefix = "#" + QString::number(id) + " ";
}
nameStr = prefix + CardLocalization::displayName(getCardInfo());
}
@ -244,6 +235,21 @@ void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *
painter->restore();
}
void AbstractCardItem::setCardRef(const CardRef &_cardRef)
{
if (cardRef == _cardRef) {
return;
}
emit deleteCardInfoPopup(cardRef.name);
if (exactCard) {
disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr);
}
cardRef = _cardRef;
refreshCardInfo();
}
void AbstractCardItem::setHovered(bool _hovered)
{
if (isHovered == _hovered) {
@ -269,17 +275,18 @@ void AbstractCardItem::setHovered(bool _hovered)
void AbstractCardItem::setColor(const QString &_color)
{
state->setColor(_color);
color = _color;
cacheBgColor();
update();
}
void AbstractCardItem::cacheBgColor()
{
QChar colorChar;
const QString cardColor = state->getColor();
if (cardColor.isEmpty()) {
colorChar = state->getCardInfo().getColorChar();
if (color.isEmpty()) {
colorChar = exactCard.getInfo().getColorChar();
} else {
colorChar = cardColor.at(0);
colorChar = color.at(0);
}
switch (colorChar.toLower().toLatin1()) {
@ -307,12 +314,17 @@ void AbstractCardItem::cacheBgColor()
}
}
void AbstractCardItem::onTappedChanged(bool newTapped, bool canAnimate)
void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
{
if (tapped == _tapped) {
return;
}
tapped = _tapped;
if (SettingsCache::instance().cardsDisplay().getTapAnimation() && canAnimate) {
static_cast<GameScene *>(scene())->registerAnimationItem(this);
} else {
tapAngle = newTapped ? 90 : 0;
tapAngle = tapped ? 90 : 0;
setTransform(QTransform()
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(tapAngle)
@ -326,6 +338,12 @@ bool AbstractCardItem::animationEvent()
return false;
}
void AbstractCardItem::setFaceDown(bool _facedown)
{
facedown = _facedown;
update();
}
void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
cancelPendingSelectionCollapse();
@ -339,7 +357,7 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
}
}
if ((event->modifiers() & Qt::AltModifier) && event->button() == Qt::LeftButton) {
emit cardShiftClicked(state->getCardRef().name);
emit cardShiftClicked(cardRef.name);
} else if ((event->modifiers() & Qt::ControlModifier)) {
setSelected(!isSelected());
} else if (!isSelected()) {
@ -353,7 +371,7 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
if (event->button() == Qt::LeftButton) {
setCursor(Qt::ClosedHandCursor);
} else if (event->button() == Qt::MiddleButton) {
emit showCardInfoPopup(event->screenPos(), state->getCardRef());
emit showCardInfoPopup(event->screenPos(), cardRef);
}
event->accept();
}
@ -387,7 +405,7 @@ void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
}
if (event->button() == Qt::MiddleButton) {
emit deleteCardInfoPopup(state->getCardRef().name);
emit deleteCardInfoPopup(cardRef.name);
}
// This function ensures the parent function doesn't mess around with our selection.

View file

@ -16,7 +16,8 @@
#include <QSizeF>
#include <QString>
#include <QtGlobal>
#include <libcockatrice/game/board/card_state.h>
#include <libcockatrice/card/printing/exact_card.h>
#include <libcockatrice/utility/card_ref.h>
#include <qtmetamacros.h>
class PlayerLogic;
@ -28,8 +29,13 @@ class AbstractCardItem : public ArrowTarget, public IAnimatedItem
{
Q_OBJECT
protected:
CardState *state;
ExactCard exactCard;
int id;
CardRef cardRef;
bool tapped;
bool facedown;
int tapAngle;
QString color;
QColor bgColor;
private:
@ -39,9 +45,6 @@ private:
int selectionCollapseGeneration = 0; ///< Guards the deferred collapse timer against stale firings
private slots:
void pixmapUpdated();
void onCardInfoChanged();
void onCardRefChanged(const CardRef &oldCardRef, const CardRef &newCardRef);
void onTappedChanged(bool newTapped, bool canAnimate);
public slots:
void refreshCardInfo();
@ -77,38 +80,31 @@ public:
QSizeF getTranslatedSize(QPainter *painter) const;
void paintPicture(QPainter *painter, const QSizeF &translatedSize, int angle);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] CardState *getState() const
{
return state;
}
ExactCard getCard() const
{
return state->getCard();
return exactCard;
}
const CardInfo &getCardInfo() const;
int getId() const
{
return state->getId();
return id;
}
void setId(int _id)
{
state->setId(_id);
id = _id;
}
QString getName() const
{
return state->getCardRef().name;
return cardRef.name;
}
QString getProviderId() const
{
return state->getCardRef().providerId;
}
void setCardRef(const CardRef &_cardRef)
{
state->setCardRef(_cardRef);
return cardRef.providerId;
}
void setCardRef(const CardRef &_cardRef);
CardRef getCardRef() const
{
return state->getCardRef();
return cardRef;
}
qreal getRealZValue() const
{
@ -120,33 +116,27 @@ public:
{
return isHovered;
}
[[nodiscard]] QString getColor() const
QString getColor() const
{
return state->getColor();
return color;
}
void setColor(const QString &_color);
bool getTapped() const
{
return state->getTapped();
}
void setTapped(bool _tapped, bool canAnimate = false)
{
state->setTapped(_tapped, canAnimate);
return tapped;
}
void setTapped(bool _tapped, bool canAnimate = false);
bool getFaceDown() const
{
return state->getFaceDown();
}
void setFaceDown(bool _facedown)
{
state->setFaceDown(_facedown);
return facedown;
}
void setFaceDown(bool _facedown);
void processHoverEvent();
/** @brief Drop a pending collapse and disarm any deferred collapse timer. */
void cancelPendingSelectionCollapse();
void deleteCardInfoPopup()
{
emit deleteCardInfoPopup(state->getCardRef().name);
emit deleteCardInfoPopup(cardRef.name);
}
/** @brief Default: no per-tick animation. Subclasses override to animate. */

View file

@ -2,6 +2,10 @@
#include "../../client/settings/cache_settings.h"
#include "../../client/settings/shortcuts_settings.h"
#include "../../game/board/counter_state.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_info.h"
#include "../../game/player/player_logic.h"
#include "../../game_graphics/board/translate_counter_name.h"
#include "../../interface/widgets/menus/tearoff_menu.h"
@ -19,10 +23,8 @@
#include <QString>
#include <QVariant>
#include <QtGlobal>
#include <libcockatrice/game/board/counter_state.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
#include <libcockatrice/utility/expression.h>
#include <qnamespace.h>
@ -149,7 +151,10 @@ void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
menu->exec(event->screenPos());
}
} else {
player->getPlayerActions()->actIncrementCounter(id, event->button() == Qt::LeftButton ? 1 : -1);
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(event->button() == Qt::LeftButton ? 1 : -1);
player->getPlayerActions()->sendGameCommand(cmd);
}
event->accept();
}
@ -167,7 +172,10 @@ void AbstractCounter::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
void AbstractCounter::incrementCounter()
{
player->getPlayerActions()->actIncrementCounter(id, static_cast<QAction *>(sender())->data().toInt());
Command_IncCounter cmd;
cmd.set_counter_id(id);
cmd.set_delta(static_cast<QAction *>(sender())->data().toInt());
player->getPlayerActions()->sendGameCommand(cmd);
}
void AbstractCounter::setCounter()
@ -191,7 +199,10 @@ void AbstractCounter::setCounter()
}
Expression exp(value);
player->getPlayerActions()->actSetCounter(id, static_cast<int>(exp.parse(dlg.textValue())));
Command_SetCounter cmd;
cmd.set_counter_id(id);
cmd.set_value(static_cast<int>(exp.parse(dlg.textValue())));
player->getPlayerActions()->sendGameCommand(cmd);
}
AbstractCounterDialog::AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent)

View file

@ -2,6 +2,11 @@
#include "arrow_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game/board/arrow_data.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_info.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/card_zone_logic.h"
#include "../game_scene.h"
#include "../player/player_target.h"
#include "../z_values.h"
@ -22,12 +27,12 @@
#include <QTransform>
#include <QtGlobal>
#include <QtMath>
#include <libcockatrice/game/board/arrow_data.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
#include <libcockatrice/settings/cards_display_settings.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
#include <math.h>
#include <qnamespace.h>
@ -421,19 +426,19 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
CardZoneLogic *startZone = startCard->getZone();
ArrowData arrow;
arrow.color = data->color;
arrow.startPlayerId = startZone->getPlayer()->getPlayerInfo()->getId();
arrow.startZone = startZone->getName();
arrow.startCardId = startCard->getId();
Command_CreateArrow cmd;
cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(data->color));
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_start_zone(startZone->getName().toStdString());
cmd.set_start_card_id(startCard->getId());
if (auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem)) {
CardZoneLogic *targetZone = targetCard->getZone();
arrow.targetPlayerId = targetZone->getPlayer()->getPlayerInfo()->getId();
arrow.targetZone = targetZone->getName();
arrow.targetCardId = targetCard->getId();
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
} else if (auto *targetPlayer = qgraphicsitem_cast<PlayerTarget *>(targetItem)) {
arrow.targetPlayerId = targetPlayer->getOwner()->getPlayerInfo()->getId();
cmd.set_target_player_id(targetPlayer->getOwner()->getPlayerInfo()->getId());
} else {
delArrow();
return;
@ -442,9 +447,22 @@ void ArrowDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
// if the card is in hand then we will move the card to stack or table as part of drawing the arrow
if (startZone->getName() == ZoneNames::HAND) {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().userInterface().getPlayToStack();
if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
(playToStack && ci->getUiAttributes().tableRow != 0 &&
startCard->getZone()->getName() != ZoneNames::STACK))) {
cmd.set_start_zone(ZoneNames::STACK);
} else {
cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
}
player->getPlayerActions()->createArrow(startCard->getState(), arrow, deleteInPhase);
if (deleteInPhase != 0) {
cmd.set_delete_in_phase(deleteInPhase);
}
player->getPlayerActions()->sendGameCommand(cmd);
}
delArrow();
@ -536,7 +554,7 @@ void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
auto *startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto *targetCard = qgraphicsitem_cast<CardItem *>(targetItem);
if (startCard && targetCard) {
player->getPlayerActions()->attachCards(startCard->getState(), targetCard->getState());
attachCards(startCard, targetCard);
}
}
@ -545,3 +563,23 @@ void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
child->mouseReleaseEvent(event);
}
}
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
return;
}
// move card onto table first if attaching from some other zone
if (startCard->getZone()->getName() != ZoneNames::TABLE) {
player->getPlayerActions()->playCardToTable(startCard, false);
}
Command_AttachCard cmd;
cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetCard->getZone()->getName().toStdString());
cmd.set_target_card_id(targetCard->getId());
player->getPlayerActions()->sendGameCommand(cmd);
}

View file

@ -1,6 +1,7 @@
#ifndef ARROWITEM_H
#define ARROWITEM_H
#include "../../game/board/arrow_data.h"
#include "../animated_item.h"
#include "arrow_target.h"
#include "graphics_item_type.h"
@ -17,7 +18,6 @@
#include <QRectF>
#include <QSharedPointer>
#include <QtGlobal>
#include <libcockatrice/game/board/arrow_data.h>
#include <qtmetamacros.h>
class CardItem;
@ -131,6 +131,7 @@ private:
PlayerLogic *player;
QList<ArrowAttachItem *> childArrows;
QMetaObject::Connection positionConnection;
void attachCards(CardItem *startCard, const CardItem *targetCard);
public:
explicit ArrowAttachItem(ArrowTarget *_startItem);

View file

@ -1,6 +1,17 @@
#include "card_item.h"
#include "../../client/settings/cache_settings.h"
#include "../../game/abstract_game.h"
#include "../../game/board/card_state.h"
#include "../../game/game_state.h"
#include "../../game/phase.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_info.h"
#include "../../game/player/player_logic.h"
#include "../../game/player/player_manager.h"
#include "../../game/zones/card_zone_logic.h"
#include "../../game/zones/table_zone_logic.h"
#include "../../game/zones/view_zone_logic.h"
#include "../card_dimensions.h"
#include "../game_scene.h"
#include "arrow_item.h"
@ -30,17 +41,6 @@
#include <QVariant>
#include <QtGlobal>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/board/card_state.h>
#include <libcockatrice/game/game_state.h>
#include <libcockatrice/game/phase.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <libcockatrice/game/zones/table_zone_logic.h>
#include <libcockatrice/game/zones/view_zone_logic.h>
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/utility/card_ref.h>
@ -53,9 +53,8 @@ CardItem::CardItem(PlayerLogic *_owner,
const CardRef &cardRef,
int _cardid,
CardZoneLogic *_zone)
: AbstractCardItem(parent, cardRef, _owner, _cardid), dragItem(nullptr)
: AbstractCardItem(parent, cardRef, _owner, _cardid), state(new CardState(this, _zone)), dragItem(nullptr)
{
state->setZone(_zone);
owner->addCard(this);
connect(&SettingsCache::instance().cardCounters(), &CardCounterSettings::colorChanged, this, [this](int counterId) {
@ -63,44 +62,25 @@ CardItem::CardItem(PlayerLogic *_owner,
update();
}
});
connect(state, &CardState::stateReset, this, [this] {
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
update();
});
// The retyped logic layer drives CardState setters directly (bypassing the CardItem wrappers that called
// update()), so repaint on every state change: attacking, annotation, doesntUntap, P/T and counters.
connect(state, &CardState::stateChanged, this, [this] { update(); });
connect(state, &CardState::viewDeleteRequested, this, &CardItem::deleteLater);
}
void CardItem::prepareDelete()
{
if (owner != nullptr) {
auto *gameScene = qobject_cast<GameScene *>(scene());
if (gameScene && gameScene->getActiveCard() == this) {
if (owner->getGame()->getActiveCard() == this) {
emit owner->requestCardMenuUpdate(nullptr);
gameScene->setActiveCard(nullptr);
owner->getGame()->setActiveCard(nullptr);
}
owner = nullptr;
}
const QList<CardState *> &attachedStates = state->getAttachedCards();
while (!attachedStates.isEmpty()) {
auto *attachedItem = qobject_cast<CardItem *>(attachedStates.first()->parent());
if (attachedItem == nullptr) {
break;
}
attachedItem->setZone(nullptr); // so that it won't try to call reorganizeCards()
attachedItem->setAttachedTo(nullptr);
while (!attachedCards.isEmpty()) {
attachedCards.first()->setZone(nullptr); // so that it won't try to call reorganizeCards()
attachedCards.first()->setAttachedTo(nullptr);
}
if (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(state);
state->getAttachedTo()->removeAttachedCard(this);
state->setAttachedTo(nullptr);
}
}
@ -147,7 +127,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
painter->save();
transformPainter(painter, translatedSize, tapAngle);
if (!getFaceDown() && state->getPT() == state->getCardInfo().getPowTough()) {
if (!getFaceDown() && state->getPT() == exactCard.getInfo().getPowTough()) {
painter->setPen(Qt::white);
} else {
painter->setPen(QColor(255, 150, 0)); // dark orange
@ -229,13 +209,36 @@ void CardItem::setPT(const QString &_pt)
void CardItem::setAttachedTo(CardItem *_attachedTo)
{
CardState *newParent = _attachedTo == nullptr ? nullptr : _attachedTo->getState();
state->setAttachedTo(newParent);
if (state->getAttachedTo() != nullptr) {
state->getAttachedTo()->removeAttachedCard(this);
}
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its attached
// cards
if (newParent != nullptr ? newParent->getZone() == nullptr : state->getZone() == nullptr) {
deleteLater();
gridPoint.setX(-1);
state->setAttachedTo(_attachedTo);
if (state->getAttachedTo() != nullptr) {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards
if (state->getAttachedTo()->getZone() == nullptr) {
deleteLater();
} else {
emit state->getAttachedTo()->getZone()->cardAdded(this);
state->getAttachedTo()->addAttachedCard(this);
if (state->getZone() != state->getAttachedTo()->getZone()) {
state->getAttachedTo()->getZone()->reorganizeCards();
}
}
} else {
// If the zone is being torn down, it might already be null by the time a card tries to un-attach all its
// attached cards
if (state->getZone() == nullptr) {
deleteLater();
} else {
emit state->getZone()->cardAdded(this);
}
}
if (state->getZone() != nullptr) {
state->getZone()->reorganizeCards();
}
}
@ -245,6 +248,13 @@ void CardItem::setAttachedTo(CardItem *_attachedTo)
void CardItem::resetState(bool keepAnnotations)
{
state->resetState(keepAnnotations);
attachedCards.clear();
setTapped(false, false);
setDoesntUntap(false);
if (scene()) {
static_cast<GameScene *>(scene())->unregisterAnimationItem(this);
}
update();
}
void CardItem::processCardInfo(const ServerInfo_Card &_info)
@ -380,7 +390,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
// Use the buttonDownPos to align the hot spot with the position when
// the user originally clicked
createDragItem(state->getId(), event->buttonDownPos(Qt::LeftButton), event->scenePos(), forceFaceDown);
createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), forceFaceDown);
dragItem->grabMouse();
int childIndex = 0;
@ -423,11 +433,42 @@ void CardItem::playCard(bool faceDown)
emit playSelected(this);
}
} else {
state->getZone()->getPlayer()->getPlayerActions()->playCard(state, faceDown);
state->getZone()->getPlayer()->getPlayerActions()->playCard(this, faceDown);
}
}
}
QVariantList CardItem::parsePT(const QString &pt)
{
QVariantList ptList = QVariantList();
if (!pt.isEmpty()) {
int sep = pt.indexOf('/');
if (sep == 0) {
ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string
} else {
int start = 0;
for (;;) {
QString item = pt.mid(start, sep - start);
if (item.isEmpty()) {
ptList.append(QVariant(QString()));
} else if (item[0] == '+') {
ptList.append(QVariant(item.mid(1).toInt())); // add as int
} else if (item[0] == '-') {
ptList.append(QVariant(item.toInt())); // add as int
} else {
ptList.append(QVariant(item)); // add as qstring
}
if (sep == -1) {
break;
}
start = sep + 1;
sep = pt.indexOf('/', start);
}
}
}
return ptList;
}
/**
* @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone
* is nullptr.
@ -452,7 +493,7 @@ void CardItem::handleClickedToPlay(bool shiftHeld)
if (SettingsCache::instance().userInterface().getClickPlaysAllSelected()) {
emit hideSelected(this);
} else {
state->getZone()->removeCard(state);
state->getZone()->removeCard(this);
}
} else {
playCard(shiftHeld);
@ -496,16 +537,16 @@ bool CardItem::animationEvent()
{
int rotation = ROTATION_DEGREES_PER_FRAME;
bool animationIncomplete = true;
if (!state->getTapped()) {
if (!tapped) {
rotation *= -1;
}
tapAngle += rotation;
if (state->getTapped() && (tapAngle > 90)) {
if (tapped && (tapAngle > 90)) {
tapAngle = 90;
animationIncomplete = false;
}
if (!state->getTapped() && (tapAngle < 0)) {
if (!tapped && (tapAngle < 0)) {
tapAngle = 0;
animationIncomplete = false;
}
@ -526,9 +567,7 @@ QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value)
bool selected = value.toBool();
if (selected) {
if (auto *gameScene = qobject_cast<GameScene *>(scene())) {
gameScene->setActiveCard(this);
}
owner->getGame()->setActiveCard(this);
}
emit selectionChanged(this, selected);

View file

@ -7,6 +7,7 @@
#ifndef CARDITEM_H
#define CARDITEM_H
#include "../../game/board/card_state.h"
#include "abstract_card_item.h"
#include "graphics_item_type.h"
@ -15,7 +16,6 @@
#include <QPointF>
#include <QString>
#include <QVariantList>
#include <libcockatrice/game/board/card_state.h>
#include <qtmetamacros.h>
class CardDragItem;
@ -33,7 +33,11 @@ class CardItem : public AbstractCardItem
{
Q_OBJECT
private:
CardState *state;
QPoint gridPoint;
CardDragItem *dragItem;
QList<CardItem *> attachedCards;
void prepareDelete();
void handleClickedToPlay(bool shiftHeld);
@ -56,6 +60,10 @@ public:
CardZoneLogic *_zone = nullptr);
void retranslateUi();
[[nodiscard]] CardState *getState() const
{
return state;
}
[[nodiscard]] CardZoneLogic *getZone() const
{
return state->getZone();
@ -64,15 +72,15 @@ public:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
[[nodiscard]] QPoint getGridPoint() const
{
return state->getGridPoint();
return gridPoint;
}
void setGridPoint(const QPoint &_gridPoint)
{
state->setGridPoint(_gridPoint);
gridPoint = _gridPoint;
}
[[nodiscard]] QPoint getGridPos() const
{
return state->getGridPoint();
return gridPoint;
}
[[nodiscard]] PlayerLogic *getOwner() const
{
@ -115,22 +123,22 @@ public:
{
state->setDestroyOnZoneChange(_destroy);
}
[[nodiscard]] CardState *getAttachedTo() const
[[nodiscard]] CardItem *getAttachedTo() const
{
return state->getAttachedTo();
}
void setAttachedTo(CardItem *_attachedTo);
void addAttachedCard(CardState *card)
void addAttachedCard(CardItem *card)
{
state->addAttachedCard(card);
attachedCards.append(card);
}
void removeAttachedCard(CardState *card)
void removeAttachedCard(CardItem *card)
{
state->removeAttachedCard(card);
attachedCards.removeOne(card);
}
[[nodiscard]] const QList<CardState *> &getAttachedCards() const
[[nodiscard]] const QList<CardItem *> &getAttachedCards() const
{
return state->getAttachedCards();
return attachedCards;
}
void resetState(bool keepAnnotations = false);
void processCardInfo(const ServerInfo_Card &_info);
@ -142,6 +150,26 @@ public:
void drawAttachArrow();
void playCard(bool faceDown);
/**
* @brief Parses a string representing a p/t in order to extract the values from it.
*
* If the string contains '/', the string will be split at the '/' and each side will be parsed separately,
* which means the result list will have two elements.
*
* If '/' is not found, then the entire string is parsed together, which means the result list will
* have a single element.
*
* If either side of the split is empty, there will also only be a single element in the result list.
*
* This function will attempt to parse each substring as an int first, handling plus and minus prefixes.
* If successful, it will put the parsed value into the QVariant as an int.
* If failed, it will just put the substring into the QVariant as a QString.
*
* @param pt The p/t string
* @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString
*/
static QVariantList parsePT(const QString &pt);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;

View file

@ -2,8 +2,13 @@
#include "../../client/settings/cache_settings.h"
#include "../../client/settings/shortcuts_settings.h"
#include "../../game/abstract_game.h"
#include "../../game/game_event_handler.h"
#include "../../game/game_state.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../../interface/deck_loader/deck_file_format.h"
#include "../../interface/deck_loader/deck_loader.h"
#include "../../interface/deck_loader/loaded_deck.h"
#include "../../interface/widgets/dialogs/dlg_load_deck.h"
#include "../../interface/widgets/dialogs/dlg_load_deck_from_clipboard.h"
#include "../../interface/widgets/dialogs/dlg_load_deck_from_website.h"
@ -28,17 +33,16 @@
#include <QRect>
#include <QVBoxLayout>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/deck_list/deck_file_format.h>
#include <libcockatrice/deck_list/loaded_deck.h>
#include <libcockatrice/deck_list/playmat_resolver.h>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/game_event_handler.h>
#include <libcockatrice/game/game_state.h>
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
#include <libcockatrice/protocol/pb/command_ready_start.pb.h>
#include <libcockatrice/protocol/pb/command_set_playmat.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/settings/visual_deck_storage_settings.h>
#include <libcockatrice/utility/qt_utils.h>
#include <libcockatrice/utility/string_limits.h>
#include <optional>
#include <qnamespace.h>
@ -107,9 +111,10 @@ DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent)
connect(deckView, &DeckView::newCardAdded, this, &DeckViewContainer::newCardAdded);
connect(deckView, &DeckView::sideboardPlanChanged, this, &DeckViewContainer::sideboardPlanChanged);
deckViewLayout = QtUtils::makeLayout<QVBoxLayout>();
deckViewLayout = new QVBoxLayout;
deckViewLayout->addLayout(buttonHBox);
deckViewLayout->addWidget(deckView);
deckViewLayout->setContentsMargins(0, 0, 0, 0);
setLayout(deckViewLayout);
retranslateUi();
@ -309,7 +314,9 @@ void DeckViewContainer::loadDeckFromDeckList(const DeckList &deck)
return;
}
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareDeckSelect(deckString);
Command_DeckSelect cmd;
cmd.set_deck(deckString.toStdString());
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(cmd);
connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(pend, playerId);
@ -338,7 +345,15 @@ void DeckViewContainer::resolveAndSendPlaymat()
lastResolvedPlaymat = resolved;
PendingCommand *playmatPend = parentGame->getGame()->getGameEventHandler()->prepareSetPlaymat(resolved);
Command_SetPlaymat playmatCmd;
auto *pp = playmatCmd.mutable_playmat_params();
pp->set_card_name(resolved.card.name.toStdString());
pp->set_card_provider_id(resolved.card.providerId.toStdString());
pp->set_margin_pct_l(resolved.params.marginPctL);
pp->set_margin_pct_r(resolved.params.marginPctR);
pp->set_vertical_offset(resolved.params.verticalOffset);
pp->set_zoom(resolved.params.zoom);
PendingCommand *playmatPend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(playmatCmd);
const QString sentName = resolved.card.name;
connect(playmatPend, &PendingCommand::finished, this, [this, sentName](const Response &r) {
// Only RespFunctionNotAllowed signals a card art DENY rule. Older servers that predate
@ -369,7 +384,9 @@ void DeckViewContainer::loadRemoteDeck()
{
DlgLoadRemoteDeck dlg(parentGame->getGame()->getClientForPlayer(playerId), this);
if (dlg.exec()) {
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareDeckSelectId(dlg.getDeckId());
Command_DeckSelect cmd;
cmd.set_deck_id(dlg.getDeckId());
PendingCommand *pend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(cmd);
connect(pend, &PendingCommand::finished, this, &DeckViewContainer::deckSelectFinished);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(pend, playerId);
}
@ -425,21 +442,34 @@ void DeckViewContainer::readyStart()
void DeckViewContainer::forceStart()
{
const auto msg = tr("Are you sure you want to force start?\nThis will kick all non-ready players from the game.");
if (!QtUtils::confirm(this, tr("Cockatrice"), msg, QMessageBox::Yes)) {
const auto res =
QMessageBox::question(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (res == QMessageBox::No) {
return;
}
parentGame->getGame()->getGameEventHandler()->sendForceStart(playerId);
Command_ReadyStart cmd;
cmd.set_force_start(true);
cmd.set_ready(true);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::sideboardLockButtonClicked()
{
parentGame->getGame()->getGameEventHandler()->sendSetSideboardLock(sideboardLockButton->getState(), playerId);
Command_SetSideboardLock cmd;
cmd.set_locked(sideboardLockButton->getState());
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
void DeckViewContainer::sideboardPlanChanged()
{
parentGame->getGame()->getGameEventHandler()->sendSetSideboardPlan(deckView->getSideboardPlan(), playerId);
Command_SetSideboardPlan cmd;
const QList<MoveCard_ToZone> &newPlan = deckView->getSideboardPlan();
for (const auto &i : newPlan) {
cmd.add_move_list()->CopyFrom(i);
}
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
/**
@ -451,7 +481,9 @@ void DeckViewContainer::sendReadyStartCommand(bool ready)
resolveAndSendPlaymat();
}
parentGame->getGame()->getGameEventHandler()->sendReadyStart(ready, playerId);
Command_ReadyStart cmd;
cmd.set_ready(ready);
parentGame->getGame()->getGameEventHandler()->sendGameCommand(cmd, playerId);
}
/**

View file

@ -1,13 +0,0 @@
#include "dlg_annotation.h"
#include <QKeyEvent>
void AnnotationDialog::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Return && event->modifiers() & Qt::ControlModifier) {
event->accept();
accept();
return;
}
QInputDialog::keyPressEvent(event);
}

View file

@ -1,22 +0,0 @@
/**
* @file dlg_annotation.h
* @ingroup GameDialogs
*/
#ifndef COCKATRICE_DLG_ANNOTATION_H
#define COCKATRICE_DLG_ANNOTATION_H
#include <QInputDialog>
class AnnotationDialog : public QInputDialog
{
Q_OBJECT
void keyPressEvent(QKeyEvent *e) override;
public:
explicit AnnotationDialog(QWidget *parent = nullptr) : QInputDialog(parent)
{
}
};
#endif // COCKATRICE_DLG_ANNOTATION_H

View file

@ -11,7 +11,6 @@
#include <QList>
#include <QString>
#include <QStringList>
#include <libcockatrice/game/player/token_info.h>
#include <qtmetamacros.h>
class QLabel;
@ -26,6 +25,17 @@ class CardInfoPictureWidget;
class QModelIndex;
class QWidget;
struct TokenInfo
{
QString name;
QString color;
QString pt;
QString annotation;
bool destroy = true;
bool faceDown = false;
QString providerId;
};
class DlgCreateToken : public QDialog
{
Q_OBJECT

View file

@ -11,7 +11,6 @@
#include <QList>
#include <QString>
#include <QStringList>
#include <libcockatrice/game/player/move_top_cards_until_options.h>
#include <qtmetamacros.h>
class FilterString;
@ -22,6 +21,13 @@ class QLabel;
class QSpinBox;
class QWidget;
struct MoveTopCardsUntilOptions
{
QStringList exprs = {};
int numberOfHits = 1;
bool autoPlay = false;
};
class DlgMoveTopCardsUntil : public QDialog
{
Q_OBJECT

View file

@ -1,10 +1,18 @@
#include "game_scene.h"
#include "../client/settings/cache_settings.h"
#include "../game/abstract_game.h"
#include "../game/arrow_registry.h"
#include "../game/board/arrow_data.h"
#include "../game/player/player_actions.h"
#include "../game/player/player_event_handler.h"
#include "../game/player/player_info.h"
#include "../game/player/player_logic.h"
#include "../game/zones/card_zone_logic.h"
#include "../game/zones/view_zone_logic.h"
#include "animated_item.h"
#include "board/abstract_card_item.h"
#include "board/arrow_item.h"
#include "board/arrow_registry.h"
#include "board/card_item.h"
#include "phases_toolbar.h"
#include "player/menu/player_menu.h"
@ -32,15 +40,6 @@
#include <QSharedPointer>
#include <QtMath>
#include <algorithm>
#include <functional>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/board/arrow_data.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_event_handler.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <libcockatrice/game/zones/view_zone_logic.h>
#include <libcockatrice/settings/interface_settings.h>
#include <libcockatrice/utility/zone_names.h>
#include <numeric>
@ -113,15 +112,6 @@ void GameScene::retranslateUi()
}
}
QList<CardState *> GameScene::selectedCardStates() const
{
QList<CardState *> selectedStates;
for (auto *card : selectedCards()) {
selectedStates.append(card->getState());
}
return selectedStates;
}
QList<CardItem *> GameScene::selectedCards() const
{
QList<CardItem *> selectedCards;
@ -134,67 +124,6 @@ QList<CardItem *> GameScene::selectedCards() const
return selectedCards;
}
/**
* @brief Selects every card of @p zone for which @p filter holds.
* @param filter A predicate to filter which cards are selected.
*/
static void selectCardsInZone(
const CardZoneLogic *zone,
std::function<bool(const CardItem *)> filter = [](const CardItem *) { return true; })
{
if (!zone) {
return;
}
for (auto *cardState : zone->getCards()) {
auto *cardItem = cardState == nullptr ? nullptr : qobject_cast<CardItem *>(cardState->parent());
if (cardItem && filter(cardItem)) {
cardItem->setSelected(true);
}
}
}
void GameScene::selectAllInZone(const CardItem *card)
{
if (!card) {
return;
}
selectCardsInZone(card->getZone());
}
void GameScene::selectSameRow(const CardItem *card)
{
if (!card) {
return;
}
auto isSameRow = [card](const CardItem *cardItem) {
return qAbs(card->scenePos().y() - cardItem->scenePos().y()) < 50;
};
selectCardsInZone(card->getZone(), isSameRow);
}
void GameScene::selectSameColumn(const CardItem *card)
{
if (!card) {
return;
}
auto isSameColumn = [card](const CardItem *cardItem) { return cardItem->x() == card->x(); };
selectCardsInZone(card->getZone(), isSameColumn);
}
CardItem *GameScene::getActiveCard() const
{
return activeCard;
}
void GameScene::setActiveCard(CardItem *card)
{
activeCard = card;
}
void GameScene::onCardSelectionChanged(AbstractCardItem *abstractCard, bool selected)
{
CardItem *card = qobject_cast<CardItem *>(abstractCard);
@ -205,12 +134,12 @@ void GameScene::onCardSelectionChanged(AbstractCardItem *abstractCard, bool sele
auto *owner = card->getOwner();
if (selected) {
owner->requestCardMenuUpdate(card->getState());
owner->requestCardMenuUpdate(card);
return;
}
if (selectedItems().isEmpty()) {
setActiveCard(nullptr);
owner->getGame()->setActiveCard(nullptr);
owner->requestCardMenuUpdate(nullptr);
}
}
@ -229,9 +158,9 @@ void GameScene::onCardRightClicked(AbstractCardItem *abstractCard, QPoint screen
return;
}
setActiveCard(card);
card->getOwner()->getGame()->setActiveCard(card);
if (auto *menu = view->getPlayerMenu()->updateCardMenu(card->getState())) {
if (auto *menu = view->getPlayerMenu()->updateCardMenu(card)) {
menu->popup(screenPos);
}
}
@ -244,7 +173,7 @@ void GameScene::playSelected(AbstractCardItem *card)
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlay(selectedCardStates());
card->getOwner()->getPlayerActions()->actPlay(selectedCards());
}
void GameScene::playSelectedFaceDown(AbstractCardItem *card)
@ -255,7 +184,7 @@ void GameScene::playSelectedFaceDown(AbstractCardItem *card)
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actPlayFacedown(selectedCardStates());
card->getOwner()->getPlayerActions()->actPlayFacedown(selectedCards());
}
void GameScene::hideSelected(AbstractCardItem *card)
@ -266,7 +195,7 @@ void GameScene::hideSelected(AbstractCardItem *card)
if (!card->getOwner()) {
return;
}
card->getOwner()->getPlayerActions()->actHide(selectedCardStates());
card->getOwner()->getPlayerActions()->actHide(selectedCards());
}
/**
@ -301,8 +230,6 @@ void GameScene::addPlayer(PlayerLogic *player)
[this, id = player->getPlayerInfo()->getId()]() { clearArrowsForPlayerLocally(id); });
connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardZoneChanged, this, &GameScene::onCardZoneChanged);
connect(player->getPlayerEventHandler(), &PlayerEventHandler::cardViewRefreshRequested, this,
&GameScene::onCardViewRefreshRequested);
rearrange();
}
@ -606,11 +533,7 @@ void GameScene::addArrow(QSharedPointer<ArrowData> data)
return;
}
CardState *startCardState = startZone->getCard(data->startCardId);
if (!startCardState) {
return;
}
CardItem *startCard = qobject_cast<CardItem *>(startCardState->parent());
CardItem *startCard = startZone->getCard(data->startCardId);
if (!startCard) {
return;
}
@ -621,8 +544,7 @@ void GameScene::addArrow(QSharedPointer<ArrowData> data)
} else {
auto *zone = targetView->getLogic()->getZones().value(data->targetZone);
if (zone) {
CardState *targetCardState = zone->getCard(data->targetCardId);
targetItem = targetCardState == nullptr ? nullptr : qobject_cast<CardItem *>(targetCardState->parent());
targetItem = zone->getCard(data->targetCardId);
}
}
if (!targetItem) {
@ -650,15 +572,11 @@ void GameScene::requestArrowDeletion(int playerId, int arrowId)
}
}
void GameScene::onCardZoneChanged(CardState *card, bool sameZone)
void GameScene::onCardZoneChanged(CardItem *card, bool sameZone)
{
auto *item = qobject_cast<CardItem *>(card->parent());
if (!item) {
return;
}
QList<ArrowItem *> toDelete;
for (auto *arrow : arrowRegistry.all()) {
if (arrow->getStartItem() == item || arrow->getTargetItem() == item) {
if (arrow->getStartItem() == card || arrow->getTargetItem() == card) {
if (sameZone) {
arrow->updatePath();
} else {
@ -671,25 +589,6 @@ void GameScene::onCardZoneChanged(CardState *card, bool sameZone)
}
}
void GameScene::onCardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner)
{
auto *item = qobject_cast<CardItem *>(card->parent());
if (!item) {
return;
}
if (zoneChanged) {
item->deleteCardInfoPopup();
}
item->deleteDragItem();
if (zoneChanged) {
item->setBeingPointedAt(false);
item->setHovered(false);
}
if (newOwner) {
item->setOwner(newOwner);
}
}
void GameScene::clearArrowsForPlayer(int playerId)
{
for (int arrowId : arrowRegistry.idsForPlayer(playerId)) {

View file

@ -1,9 +1,7 @@
#ifndef GAMESCENE_H
#define GAMESCENE_H
#include "animated_item.h"
#include "board/arrow_item.h"
#include "board/arrow_registry.h"
#include "../game/arrow_registry.h"
#include <QGraphicsScene>
#include <QHash>
@ -18,8 +16,6 @@
#include <QString>
#include <QTransform>
#include <QtGlobal>
#include <libcockatrice/game/board/arrow_data.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <qtmetamacros.h>
// IWYU pragma: keep
@ -43,7 +39,6 @@ class ZoneViewWidget;
class CardZone;
class AbstractCardItem;
class CardItem;
class CardState;
class ServerInfo_Card;
class PhasesToolbar;
class QBasicTimer;
@ -74,7 +69,6 @@ private:
QList<ZoneViewWidget *> zoneViews; ///< Active zone view widgets
QSize viewSize; ///< Current view size
QPointer<CardItem> hoveredCard; ///< Currently hovered card
QPointer<CardItem> activeCard; ///< Card the currently open context menu targets
QBasicTimer *animationTimer; ///< Timer for scene animations
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
QHash<QObject *, QMetaObject::Connection>
@ -113,22 +107,6 @@ public:
/** @brief Gets all selected CardItems. */
QList<CardItem *> selectedCards() const;
QList<CardState *> selectedCardStates() const;
/** @brief Selects every card sharing the zone of @p card. */
void selectAllInZone(const CardItem *card);
/** @brief Selects the cards laid out in the same visual row as @p card. */
void selectSameRow(const CardItem *card);
/** @brief Selects the cards laid out in the same visual column as @p card. */
void selectSameColumn(const CardItem *card);
/** @brief Card the currently open context menu targets, or null. */
CardItem *getActiveCard() const;
/** @brief Sets the card the currently open context menu targets. */
void setActiveCard(CardItem *card);
/**
* @brief Adds a player to the scene and stores their graphics item.
@ -283,15 +261,7 @@ public slots:
/// Queues up arrow deletion but doesn't directly modify the scene
void requestArrowDeletion(int playerId, int arrowId);
void onCardZoneChanged(CardState *card, bool sameZone);
/**
* @brief Refreshes the view of a card that the server just moved.
*
* @param card The card that moved.
* @param zoneChanged Whether the card crossed a zone boundary.
* @param newOwner The player the card now belongs to, or nullptr to leave the owner alone.
*/
void onCardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner);
void onCardZoneChanged(CardItem *card, bool sameZone);
protected:
/** @brief Handles hover updates. */

View file

@ -1,5 +1,7 @@
#include "hand_counter.h"
#include "../game/board/card_list.h"
#include "../game/zones/card_zone_logic.h"
#include "../interface/pixel_map_generator.h"
#include <QGraphicsSceneMouseEvent>
@ -12,8 +14,6 @@
#include <QString>
#include <QStringLiteral>
#include <QTransform>
#include <libcockatrice/game/board/card_list.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <qnamespace.h>
class QGraphicsItem;

View file

@ -3,6 +3,16 @@
#include "../../client/settings/cache_settings.h"
#include "../../client/settings/card_counter_settings.h"
#include "../../client/sound_engine.h"
#include "../../client/translation.h"
#include "../../game/abstract_game.h"
#include "../../game/board/card_list.h"
#include "../../game/game_state.h"
#include "../../game/phase.h"
#include "../../game/player/player_event_handler.h"
#include "../../game/player/player_info.h"
#include "../../game/player/player_logic.h"
#include "../../game/player/player_manager.h"
#include "../../game/zones/card_zone_logic.h"
#include "../../interface/card_localization.h"
#include "../../interface/widgets/server/chat_view/chat_view.h"
#include "../board/card_item.h"
@ -14,16 +24,6 @@
#include <QList>
#include <QSharedPointer>
#include <QStringList>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/board/card_list.h>
#include <libcockatrice/game/game_state.h>
#include <libcockatrice/game/phase.h>
#include <libcockatrice/game/player/player_event_handler.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
#include <libcockatrice/game/translation.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
#include <libcockatrice/settings/chat_settings.h>
@ -283,7 +283,7 @@ void MessageLogWidget::logDestroyCard(PlayerLogic *player, QString cardName)
}
void MessageLogWidget::logMoveCard(PlayerLogic *player,
CardState *card,
CardItem *card,
CardZoneLogic *startZone,
int oldX,
CardZoneLogic *targetZone,
@ -662,7 +662,7 @@ void MessageLogWidget::logSetActivePlayer(PlayerLogic *player)
QString(tr("%1's turn.")).arg(player->getPlayerInfo()->getName()) + "</b></font><br>");
}
void MessageLogWidget::logSetAnnotation(PlayerLogic *player, CardState *card, QString newAnnotation)
void MessageLogWidget::logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation)
{
appendHtmlServerMessage(
QString(tr("%1 sets annotation of %2 to %3."))
@ -706,7 +706,7 @@ void MessageLogWidget::logSetCounter(PlayerLogic *player, QString counterName, i
.arg(value - oldValue));
}
void MessageLogWidget::logSetDoesntUntap(PlayerLogic *player, CardState *card, bool doesntUntap)
void MessageLogWidget::logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap)
{
QString str;
if (doesntUntap) {
@ -717,7 +717,7 @@ void MessageLogWidget::logSetDoesntUntap(PlayerLogic *player, CardState *card, b
appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(card->getName())));
}
void MessageLogWidget::logSetPT(PlayerLogic *player, CardState *card, QString newPT)
void MessageLogWidget::logSetPT(PlayerLogic *player, CardItem *card, QString newPT)
{
if (currentContext == MessageContext_MoveCard) {
return;
@ -755,7 +755,7 @@ void MessageLogWidget::logSetSideboardLock(PlayerLogic *player, bool locked)
}
}
void MessageLogWidget::logSetTapped(PlayerLogic *player, CardState *card, bool tapped)
void MessageLogWidget::logSetTapped(PlayerLogic *player, CardItem *card, bool tapped)
{
if (currentContext == MessageContext_MoveCard) {
return;

View file

@ -14,7 +14,6 @@
#include <qtmetamacros.h>
#include <sys/types.h>
class CardState;
class AbstractGame;
class CardItem;
class GameEventContext;
@ -77,7 +76,7 @@ public slots:
void logLeaveSpectator(QString name, QString reason);
void logNotReadyStart(PlayerLogic *player);
void logMoveCard(PlayerLogic *player,
CardState *card,
CardItem *card,
CardZoneLogic *startZone,
int oldX,
CardZoneLogic *targetZone,
@ -98,13 +97,13 @@ public slots:
void logSay(PlayerLogic *player, QString message);
void logSetActivePhase(int phase);
void logSetActivePlayer(PlayerLogic *player);
void logSetAnnotation(PlayerLogic *player, CardState *card, QString newAnnotation);
void logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation);
void logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue);
void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue);
void logSetDoesntUntap(PlayerLogic *player, CardState *card, bool doesntUntap);
void logSetPT(PlayerLogic *player, CardState *card, QString newPT);
void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap);
void logSetPT(PlayerLogic *player, CardItem *card, QString newPT);
void logSetSideboardLock(PlayerLogic *player, bool locked);
void logSetTapped(PlayerLogic *player, CardState *card, bool tapped);
void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped);
void logShuffle(PlayerLogic *player, CardZoneLogic *zone, int start, int end);
void logSpectatorSay(const ServerInfo_User &spectator, QString message);
void logUnattachCard(PlayerLogic *player, QString cardName);

View file

@ -14,6 +14,11 @@
#include <QTransform>
#include <QtGlobal>
#include <google/protobuf/stubs/port.h>
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
#include <libcockatrice/protocol/pb/command_next_turn.pb.h>
#include <libcockatrice/protocol/pb/command_set_active_phase.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/utility/zone_names.h>
#include <qnamespace.h>
#include <qnumeric.h>
@ -115,9 +120,9 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent)
: QGraphicsItem(parent), width(100), height(100), ySpacing(1), symbolSize(8)
{
auto *aUntapAll = new QAction(this);
connect(aUntapAll, &QAction::triggered, this, &PhasesToolbar::untapAllRequested);
connect(aUntapAll, &QAction::triggered, this, &PhasesToolbar::actUntapAll);
auto *aDrawCard = new QAction(this);
connect(aDrawCard, &QAction::triggered, this, &PhasesToolbar::drawCardRequested);
connect(aDrawCard, &QAction::triggered, this, &PhasesToolbar::actDrawCard);
PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll);
PhaseButton *upkeepButton = new PhaseButton("upkeep", this);
@ -139,7 +144,7 @@ PhasesToolbar::PhasesToolbar(QGraphicsItem *parent)
}
nextTurnButton = new PhaseButton("nextturn", this, nullptr, false);
connect(nextTurnButton, &PhaseButton::clicked, this, &PhasesToolbar::nextTurnRequested);
connect(nextTurnButton, &PhaseButton::clicked, this, &PhasesToolbar::actNextTurn);
rearrangeButtons();
@ -260,5 +265,31 @@ void PhasesToolbar::phaseButtonClicked()
button->triggerDoubleClickAction();
}
emit setActivePhaseRequested(buttonList.indexOf(button));
Command_SetActivePhase cmd;
cmd.set_phase(static_cast<google::protobuf::uint32>(buttonList.indexOf(button)));
emit sendGameCommand(cmd, -1);
}
void PhasesToolbar::actNextTurn()
{
emit sendGameCommand(Command_NextTurn(), -1);
}
void PhasesToolbar::actUntapAll()
{
Command_SetCardAttr cmd;
cmd.set_zone(ZoneNames::TABLE);
cmd.set_attribute(AttrTapped);
cmd.set_attr_value("0");
emit sendGameCommand(cmd, -1);
}
void PhasesToolbar::actDrawCard()
{
Command_DrawCards cmd;
cmd.set_number(1);
emit sendGameCommand(cmd, -1);
}

View file

@ -17,6 +17,14 @@
class QAction;
class QTimer;
namespace google
{
namespace protobuf
{
class Message;
}
} // namespace google
class PhaseButton : public QObject, public QGraphicsItem
{
Q_OBJECT
@ -87,16 +95,11 @@ public slots:
void triggerPhaseAction(int phase);
private slots:
void phaseButtonClicked();
void actNextTurn();
void actUntapAll();
void actDrawCard();
signals:
/** @name Intent signals
* The view reports what the user asked for; GameEventHandler builds the commands.
* @{
*/
void setActivePhaseRequested(int phase);
void nextTurnRequested();
void untapAllRequested();
void drawCardRequested();
/** @} */
void sendGameCommand(const ::google::protobuf::Message &command, int playerId);
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override;

View file

@ -1,6 +1,6 @@
/**
* @file card_menu_action_type.h
* @ingroup GameLogicPlayers
* @ingroup GameMenusPlayers
*/
//! \todo Document this file.

View file

@ -3,8 +3,16 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/card_counter_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../../game/abstract_game.h"
#include "../../../game/player/player_info.h"
#include "../../../game/player/player_manager.h"
#include "../../../game/zones/card_zone_logic.h"
#include "../../board/card_item.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game/zones/view_zone_logic.h"
#include "../../game_scene.h"
#include "../card_menu_action_type.h"
#include "../player_graphics_item.h"
#include "libcockatrice/card/card_info.h"
#include "libcockatrice/card/database/card_database_querier.h"
@ -27,14 +35,6 @@
#include <QtGlobal>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/player/card_menu_action_type.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
#include <libcockatrice/game/zones/card_zone_logic.h>
#include <libcockatrice/game/zones/view_zone_logic.h>
#include <libcockatrice/utility/zone_names.h>
#include <qnamespace.h>
#include <utility>
@ -67,7 +67,7 @@ static QAction *makeAction(QObject *parent, Slot &&slot, bool checkable = false,
return a;
}
CardMenu::CardMenu(PlayerGraphicsItem *_player, CardItem *_card, bool _shortcutsActive)
CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _shortcutsActive)
: player(_player), card(_card), shortcutsActive(_shortcutsActive)
{
const QList<PlayerLogic *> &players = player->getLogic()->getGame()->getPlayerManager()->getPlayers().values();
@ -86,7 +86,7 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, CardItem *_card, bool _shortcuts
auto *gameScene = player->getGameScene();
// Single selection resolver used by all lambdas — called at trigger time
auto sel = [gameScene]() { return gameScene->selectedCardStates(); };
auto sel = [gameScene]() { return gameScene->selectedCards(); };
// Unified dispatcher for card menu actions
auto invoke = [actions, sel](CardMenuActionType type) {
@ -108,18 +108,18 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, CardItem *_card, bool _shortcuts
aHide = makeAction(this, [actions, sel]() { actions->actHide(sel()); });
aReduceLifeByPower = makeAction(this, [actions, sel]() { actions->actReduceLifeByPower(sel()); });
// Actions bound to the card this menu was opened for, not to the current selection
// Actions that use activeCard, not selection — direct connection
aAttach = new QAction(this);
aDrawArrow = new QAction(this);
aSelectAll = new QAction(this);
aSelectRow = new QAction(this);
aSelectColumn = new QAction(this);
connect(aAttach, &QAction::triggered, this, &CardMenu::attachCard);
connect(aDrawArrow, &QAction::triggered, this, &CardMenu::drawArrow);
connect(aSelectAll, &QAction::triggered, this, [this, gameScene]() { gameScene->selectAllInZone(card); });
connect(aSelectRow, &QAction::triggered, this, [this, gameScene]() { gameScene->selectSameRow(card); });
connect(aSelectColumn, &QAction::triggered, this, [this, gameScene]() { gameScene->selectSameColumn(card); });
connect(aAttach, &QAction::triggered, actions, &PlayerActions::actAttach);
connect(aDrawArrow, &QAction::triggered, actions, &PlayerActions::actDrawArrow);
connect(aSelectAll, &QAction::triggered, actions, &PlayerActions::actSelectAll);
connect(aSelectRow, &QAction::triggered, actions, &PlayerActions::actSelectRow);
connect(aSelectColumn, &QAction::triggered, actions, &PlayerActions::actSelectColumn);
aRevealToAll = new QAction(this);
@ -337,7 +337,7 @@ void CardMenu::createHandOrCustomZoneMenu(bool canModifyCard)
initContextualPlayersMenu(revealMenu, aRevealToAll);
connect(revealMenu, &QMenu::triggered, this, [this](QAction *action) {
player->getLogic()->getPlayerActions()->actReveal(player->getGameScene()->selectedCardStates(), action);
player->getLogic()->getPlayerActions()->actReveal(player->getGameScene()->selectedCards(), action);
});
addSeparator();
@ -441,7 +441,6 @@ void CardMenu::addRelatedCardActions()
if (relatedCards.isEmpty()) {
return;
}
auto *playerActions = player->getLogic()->getPlayerActions();
addSeparator();
int index = 0;
@ -485,10 +484,9 @@ void CardMenu::addRelatedCardActions()
}
auto *createRelated = new QAction(text, this);
const int relatedIndex = index++;
connect(createRelated, &QAction::triggered, this, [this, playerActions, relatedIndex] {
playerActions->actCreateRelatedCard(card->getState(), relatedIndex);
});
createRelated->setData(QVariant(index++));
connect(createRelated, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actCreateRelatedCard);
addAction(createRelated);
}
@ -497,26 +495,12 @@ void CardMenu::addRelatedCardActions()
createRelatedCards->setShortcuts(
SettingsCache::instance().shortcuts().getShortcut("Player/aCreateRelatedTokens"));
}
connect(createRelatedCards, &QAction::triggered, this,
[this, playerActions] { playerActions->actCreateAllRelatedCards(card->getState()); });
connect(createRelatedCards, &QAction::triggered, player->getLogic()->getPlayerActions(),
&PlayerActions::actCreateAllRelatedCards);
addAction(createRelatedCards);
}
}
void CardMenu::attachCard()
{
if (card) {
card->drawAttachArrow();
}
}
void CardMenu::drawArrow()
{
if (card) {
card->drawArrow(Qt::red);
}
}
void CardMenu::retranslateUi()
{
aSelectAll->setText(tr("&Select All"));

View file

@ -27,7 +27,7 @@ signals:
void cardInfoRequested(const CardRef &cardRef);
public:
explicit CardMenu(PlayerGraphicsItem *player, CardItem *card, bool shortcutsActive);
explicit CardMenu(PlayerGraphicsItem *player, const CardItem *card, bool shortcutsActive);
void removePlayer(PlayerLogic *playerToRemove);
void createTableMenu(bool canModifyCard);
void createStackMenu(bool canModifyCard);
@ -53,13 +53,11 @@ public:
private:
PlayerGraphicsItem *player;
CardItem *card;
const CardItem *card;
QList<QPair<QString, int>> playersInfo;
bool shortcutsActive;
void addRelatedCardActions();
void attachCard();
void drawArrow();
void retranslateUi();
void initContextualPlayersMenu(QMenu *menu, QAction *allPlayersAction);
void setShortcutsActive();

View file

@ -1,13 +1,13 @@
#include "custom_zone_menu.h"
#include "../../../game/player/player_info.h"
#include "../../game/player/player_logic.h"
#include "../../game_scene.h"
#include "../player_graphics_item.h"
#include <QAction>
#include <QList>
#include <QVariant>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
CustomZoneMenu::CustomZoneMenu(PlayerGraphicsItem *_player) : player(_player)
{

View file

@ -2,7 +2,13 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../../game/player/player_info.h"
#include "../../../game/player/player_manager.h"
#include "../../../game/zones/pile_zone_logic.h"
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "../../game/abstract_game.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
#include <QAction>
@ -13,12 +19,6 @@
#include <QObject>
#include <QString>
#include <QVariant>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
#include <libcockatrice/game/zones/pile_zone_logic.h>
#include <libcockatrice/utility/zone_names.h>
class QWidget;

View file

@ -2,7 +2,14 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../../game/board/card_list.h"
#include "../../../game/player/player_info.h"
#include "../../../game/player/player_manager.h"
#include "../../../game/zones/hand_zone_logic.h"
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "../../game/abstract_game.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
#include <QAction>
@ -13,13 +20,6 @@
#include <QObject>
#include <QString>
#include <QVariant>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/board/card_list.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
#include <libcockatrice/game/zones/hand_zone_logic.h>
#include <libcockatrice/utility/zone_names.h>
class QWidget;

View file

@ -2,7 +2,14 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../../game/board/card_list.h"
#include "../../../game/player/player_info.h"
#include "../../../game/player/player_manager.h"
#include "../../../game/zones/pile_zone_logic.h"
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "../../game/abstract_game.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
#include <QAction>
@ -16,13 +23,6 @@
#include <QObject>
#include <QString>
#include <QVariant>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/board/card_list.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
#include <libcockatrice/game/zones/pile_zone_logic.h>
class QWidget;

View file

@ -2,13 +2,13 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game_scene.h"
#include "../card_menu_action_type.h"
#include "../player_graphics_item.h"
#include <QAction>
#include <libcockatrice/game/player/card_menu_action_type.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_logic.h>
MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
{
@ -30,7 +30,7 @@ MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
auto invoke = [player](CardMenuActionType type) {
return [type, player]() {
player->getLogic()->getPlayerActions()->cardMenuAction(player->getGameScene()->selectedCardStates(), type);
player->getLogic()->getPlayerActions()->cardMenuAction(player->getGameScene()->selectedCards(), type);
};
};

View file

@ -2,6 +2,10 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../../game/abstract_game.h"
#include "../../../game/player/player_info.h"
#include "../../../game/player/player_logic.h"
#include "../../../game/player/player_manager.h"
#include "../../../game_graphics/zones/hand_zone.h"
#include "../../../game_graphics/zones/pile_zone.h"
#include "../../../game_graphics/zones/table_zone.h"
@ -22,10 +26,6 @@
#include <QMenu>
#include <QString>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_player)
{
@ -87,24 +87,22 @@ void PlayerMenu::setMenusForGraphicItems()
}
}
QMenu *PlayerMenu::updateCardMenu(CardState *card)
QMenu *PlayerMenu::updateCardMenu(const CardItem *card)
{
if (!card) {
emit cardMenuUpdated(nullptr);
return nullptr;
}
auto *cardItem = qobject_cast<CardItem *>(card->parent());
// If is spectator (as spectators don't need card menus), return
// only update the menu if the card is actually selected
if ((player->getLogic()->getGame()->getPlayerManager()->isSpectator() &&
!player->getLogic()->getGame()->getPlayerManager()->isJudge()) ||
cardItem == nullptr || player->getGameScene()->getActiveCard() != cardItem) {
player->getLogic()->getGame()->getActiveCard() != card) {
return nullptr;
}
CardMenu *menu = new CardMenu(player, cardItem, shortcutsActive);
CardMenu *menu = new CardMenu(player, card, shortcutsActive);
connect(menu, &CardMenu::cardInfoRequested, this, &PlayerMenu::cardInfoRequested);
emit cardMenuUpdated(menu);

View file

@ -14,7 +14,7 @@
#include <qtmetamacros.h>
#include <utility>
class CardState;
class CardItem;
class CardMenu;
class PlayerGraphicsItem;
class AbstractPlayerComponent;
@ -43,7 +43,7 @@ signals:
public slots:
void setMenusForGraphicItems();
QMenu *updateCardMenu(CardState *card);
QMenu *updateCardMenu(const CardItem *card);
private slots:
void refreshShortcuts();

View file

@ -2,12 +2,12 @@
#include "../../../client/settings/cache_settings.h"
#include "../../../client/settings/shortcuts_settings.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../../game_scene.h"
#include "../player_graphics_item.h"
#include <QAction>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_logic.h>
PtMenu::PtMenu(PlayerGraphicsItem *player) : QMenu(tr("Power / toughness"))
{
@ -15,35 +15,34 @@ PtMenu::PtMenu(PlayerGraphicsItem *player) : QMenu(tr("Power / toughness"))
aIncP = new QAction(this);
connect(aIncP, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actIncP(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actIncP(player->getGameScene()->selectedCards()); });
aDecP = new QAction(this);
connect(aDecP, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actDecP(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actDecP(player->getGameScene()->selectedCards()); });
aIncT = new QAction(this);
connect(aIncT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actIncT(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actIncT(player->getGameScene()->selectedCards()); });
aDecT = new QAction(this);
connect(aDecT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actDecT(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actDecT(player->getGameScene()->selectedCards()); });
aIncPT = new QAction(this);
connect(aIncPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actIncPT(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actIncPT(player->getGameScene()->selectedCards()); });
aDecPT = new QAction(this);
connect(aDecPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actDecPT(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actDecPT(player->getGameScene()->selectedCards()); });
aFlowP = new QAction(this);
connect(aFlowP, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actFlowP(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actFlowP(player->getGameScene()->selectedCards()); });
aFlowT = new QAction(this);
connect(aFlowT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actFlowT(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actFlowT(player->getGameScene()->selectedCards()); });
aSetPT = new QAction(this);
connect(aSetPT, &QAction::triggered, playerActions, [player, playerActions] {
playerActions->actRequestSetPTDialog(player->getGameScene()->selectedCardStates());
});
connect(aSetPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actRequestSetPTDialog(player->getGameScene()->selectedCards()); });
aResetPT = new QAction(this);
connect(aResetPT, &QAction::triggered, playerActions,
[player, playerActions] { playerActions->actResetPT(player->getGameScene()->selectedCardStates()); });
[player, playerActions] { playerActions->actResetPT(player->getGameScene()->selectedCards()); });
addAction(aIncP);
addAction(aDecP);

View file

@ -1,6 +1,10 @@
#include "rfg_menu.h"
#include "../../../game/player/player_info.h"
#include "../../../game/zones/pile_zone_logic.h"
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
#include "../player_graphics_item.h"
#include <QAction>
@ -8,10 +12,6 @@
#include <QMenu>
#include <QString>
#include <QVariant>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_info.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/zones/pile_zone_logic.h>
#include <libcockatrice/utility/zone_names.h>
class QWidget;

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