From 2cb16c9fd03b67c2010c87f7349425d74e84167d Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Sun, 22 Feb 2026 20:54:58 -0800
Subject: [PATCH 001/109] [CardDatabaseDisplay] Reduce width by using icons
(#6603)
* [CardDatabaseDisplay] Reduce width by using icons
* use public domain filter svg icon
---
cockatrice/cockatrice.qrc | 1 +
cockatrice/resources/icons/filter.svg | 12 ++++
...database_display_filter_toolbar_widget.cpp | 59 +++++++++++++------
...l_database_display_filter_toolbar_widget.h | 2 +
4 files changed, 56 insertions(+), 18 deletions(-)
create mode 100644 cockatrice/resources/icons/filter.svg
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index f087bfe66..37fb145f0 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -24,6 +24,7 @@
resources/icons/dragon.svg
resources/icons/dropdown_collapsed.svg
resources/icons/dropdown_expanded.svg
+ resources/icons/filter.svg
resources/icons/floppy_disk.svg
resources/icons/forgot_password.svg
resources/icons/gear.svg
diff --git a/cockatrice/resources/icons/filter.svg b/cockatrice/resources/icons/filter.svg
new file mode 100644
index 000000000..d53d9ba29
--- /dev/null
+++ b/cockatrice/resources/icons/filter.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
index 324236ab7..3cc1bf23b 100644
--- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
+++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp
@@ -2,11 +2,14 @@
#include "visual_database_display_widget.h"
+#include
+
VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidget(VisualDatabaseDisplayWidget *_parent)
: QWidget(_parent), visualDatabaseDisplay(_parent)
{
filterContainerLayout = new QHBoxLayout(this);
filterContainerLayout->setContentsMargins(11, 0, 11, 0);
+ filterContainerLayout->setSpacing(2);
setLayout(filterContainerLayout);
filterContainerLayout->setAlignment(Qt::AlignLeft);
@@ -15,9 +18,17 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg
connect(this, &VisualDatabaseDisplayFilterToolbarWidget::searchModelChanged, visualDatabaseDisplay,
&VisualDatabaseDisplayWidget::onSearchModelChanged);
- filterByLabel = new QLabel(this);
+ sortGroupBox = new QGroupBox(this);
+ filterGroupBox = new QGroupBox(this);
+
+ auto scalePixmap = [](const QString &fileName) { return QIcon(QPixmap(fileName)).pixmap({20, 20}); };
sortByLabel = new QLabel(this);
+ sortByLabel->setPixmap(scalePixmap("theme:icons/sort_arrow_down"));
+
+ filterByLabel = new QLabel(this);
+ filterByLabel->setPixmap(scalePixmap("theme:icons/filter"));
+
sortColumnCombo = new QComboBox(this);
sortColumnCombo->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToContents);
sortOrderCombo = new QComboBox(this);
@@ -76,14 +87,20 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg
void VisualDatabaseDisplayFilterToolbarWidget::initialize()
{
- sortByLabel->setVisible(true);
- filterByLabel->setVisible(true);
+ // create groupbox layouts
+ auto sortLayout = new QHBoxLayout(this);
+ sortLayout->setContentsMargins(0, 0, 0, 0);
+ sortLayout->setSpacing(0);
+ sortGroupBox->setLayout(sortLayout);
+ sortLayout->setAlignment(Qt::AlignLeft);
- quickFilterSaveLoadWidget->setVisible(true);
- quickFilterNameWidget->setVisible(true);
- quickFilterSubTypeWidget->setVisible(true);
- quickFilterSetWidget->setVisible(true);
+ auto filterLayout = new QHBoxLayout(this);
+ filterLayout->setContentsMargins(0, 0, 0, 0);
+ filterLayout->setSpacing(2);
+ filterGroupBox->setLayout(filterLayout);
+ filterLayout->setAlignment(Qt::AlignLeft);
+ // create settings widgets
auto filterModel = visualDatabaseDisplay->filterModel;
saveLoadWidget = new VisualDatabaseDisplayFilterSaveLoadWidget(this, filterModel);
@@ -101,23 +118,29 @@ void VisualDatabaseDisplayFilterToolbarWidget::initialize()
quickFilterSetWidget->addSettingsWidget(setFilterWidget);
quickFilterFormatLegalityWidget->addSettingsWidget(formatLegalityWidget);
- filterContainerLayout->addWidget(sortByLabel);
- filterContainerLayout->addWidget(sortColumnCombo);
- filterContainerLayout->addWidget(sortOrderCombo);
- filterContainerLayout->addWidget(filterByLabel);
- filterContainerLayout->addWidget(quickFilterNameWidget);
- filterContainerLayout->addWidget(quickFilterMainTypeWidget);
- filterContainerLayout->addWidget(quickFilterSubTypeWidget);
- filterContainerLayout->addWidget(quickFilterSetWidget);
- filterContainerLayout->addWidget(quickFilterFormatLegalityWidget);
+ // fill groupbox layouts
+ sortLayout->addWidget(sortByLabel);
+ sortLayout->addWidget(sortColumnCombo);
+ sortLayout->addWidget(sortOrderCombo);
+
+ filterLayout->addWidget(filterByLabel);
+ filterLayout->addWidget(quickFilterNameWidget);
+ filterLayout->addWidget(quickFilterMainTypeWidget);
+ filterLayout->addWidget(quickFilterSubTypeWidget);
+ filterLayout->addWidget(quickFilterSetWidget);
+ filterLayout->addWidget(quickFilterFormatLegalityWidget);
+
+ // put everything into main layout
+ filterContainerLayout->addWidget(sortGroupBox);
+ filterContainerLayout->addWidget(filterGroupBox);
filterContainerLayout->addStretch();
filterContainerLayout->addWidget(quickFilterSaveLoadWidget);
}
void VisualDatabaseDisplayFilterToolbarWidget::retranslateUi()
{
- sortByLabel->setText(tr("Sort by:"));
- filterByLabel->setText(tr("Filter by:"));
+ sortByLabel->setToolTip(tr("Sort by"));
+ filterByLabel->setToolTip(tr("Filter by"));
quickFilterSaveLoadWidget->setToolTip(tr("Save and load filters"));
quickFilterNameWidget->setToolTip(tr("Filter by exact card name"));
diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h
index a6c614656..5cca5187a 100644
--- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h
+++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.h
@@ -25,9 +25,11 @@ public:
private:
VisualDatabaseDisplayWidget *visualDatabaseDisplay;
+ QGroupBox *sortGroupBox;
QLabel *sortByLabel;
QComboBox *sortColumnCombo, *sortOrderCombo;
+ QGroupBox *filterGroupBox;
QLabel *filterByLabel;
QHBoxLayout *filterContainerLayout;
From 12c667afd7f823f4532907543ebcf1b25e87aeed Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Mon, 23 Feb 2026 16:34:23 -0800
Subject: [PATCH 002/109] [Search] Fix OR usage in examples (#6628)
---
cockatrice/resources/help/deck_search.md | 2 +-
cockatrice/resources/help/search.md | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/cockatrice/resources/help/deck_search.md b/cockatrice/resources/help/deck_search.md
index 3263daf04..163137335 100644
--- a/cockatrice/resources/help/deck_search.md
+++ b/cockatrice/resources/help/deck_search.md
@@ -47,6 +47,6 @@ searches are case insensitive.
[t:aggro OR o:control](#t:aggro OR o:control) (Any deck filename that contains either aggro or control)
Grouping:
-red -([[]]:100 or aggro) (Any deck that has red in its filename but is not 100 cards or has aggro in its filename)
+red -([[]]:100 OR aggro) (Any deck that has red in its filename but is not 100 cards or has aggro in its filename)
\ No newline at end of file
diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md
index 63e6e74c3..0c8bdb450 100644
--- a/cockatrice/resources/help/search.md
+++ b/cockatrice/resources/help/search.md
@@ -51,16 +51,16 @@ In this list of examples below, each entry has an explanation and can be clicked
E dition:
[set:lea](#set:lea) (Cards that appear in Alpha, which has the set code LEA)
-[e:lea or e:leb](#e:lea or e:leb) (Cards that appear in Alpha or Beta)
+[e:lea OR e:leb](#e:lea OR e:leb) (Cards that appear in Alpha or Beta)
Negate:
[c:wu -c:m](#c:wu -c:m) (Any card that is white or blue, but not multicolored)
Branching:
-[t:sliver or o:changeling](#t:sliver or o:changeling) (Any card that is either a sliver or has changeling)
+[t:sliver OR o:changeling](#t:sliver OR o:changeling) (Any card that is either a sliver or has changeling)
Grouping:
-t:angel -(angel or c:w) (Any angel that doesn't have angel in its name and isn't white)
+t:angel -(angel OR c:w) (Any angel that doesn't have angel in its name and isn't white)
Regular Expression:
[/^fell/](#/^fell/) (Any card name that begins with "fell")
From 7ad2481e3d2f595631f9fa6091c66d4cee12c928 Mon Sep 17 00:00:00 2001
From: Rob Blanckaert
Date: Wed, 25 Feb 2026 23:55:34 -0800
Subject: [PATCH 003/109] Update UnescapedStringListPart to include parentheses
(#6631)
* Update UnescapedStringListPart to include parentheses
* also update deck_filter_string
* add unit test
---------
Co-authored-by: RickyRister
---
cockatrice/src/filters/deck_filter_string.cpp | 2 +-
libcockatrice_filters/libcockatrice/filters/filter_string.cpp | 2 +-
tests/carddatabase/filter_string_test.cpp | 2 ++
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/cockatrice/src/filters/deck_filter_string.cpp b/cockatrice/src/filters/deck_filter_string.cpp
index 42a77fe5a..6b671831d 100644
--- a/cockatrice/src/filters/deck_filter_string.cpp
+++ b/cockatrice/src/filters/deck_filter_string.cpp
@@ -31,7 +31,7 @@ GenericQuery <- String
NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["].
NonSingleQuoteUnlessEscaped <- "\\\'". / !['].
-UnescapedStringListPart <- !['":<>=! ].
+UnescapedStringListPart <- !['":<>()=! ].
SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)*
String <- SingleApostropheString / UnescapedStringListPart+ / ["] ["] / ['] [']
diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
index 0d29f7897..704e8fadb 100644
--- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
+++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp
@@ -46,7 +46,7 @@ FieldQuery <- String [:] MatcherString / String ws? NumericExpression
NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["].
NonSingleQuoteUnlessEscaped <- "\\\'". / !['].
-UnescapedStringListPart <- !['":<>=! ].
+UnescapedStringListPart <- !['":<>()=! ].
SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)*
String <- SingleApostropheString / UnescapedStringListPart+ / ["] ["] / ['] [']
StringValue <- String / [(] StringList [)]
diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp
index c3e8c6fb7..c6d68be1f 100644
--- a/tests/carddatabase/filter_string_test.cpp
+++ b/tests/carddatabase/filter_string_test.cpp
@@ -71,6 +71,8 @@ QUERY(Color2, cat, "c:gw", true)
QUERY(Color3, cat, "c!g", true)
QUERY(Color4, cat, "c!gw", false)
+QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true)
+
} // namespace
int main(int argc, char **argv)
From 6f8f9f016a3b4f81ee269c403cc718dca51c575b Mon Sep 17 00:00:00 2001
From: tooomm
Date: Thu, 26 Feb 2026 22:31:18 +0100
Subject: [PATCH 004/109] CI: `windows-2025` runner (#6632)
* `windows-2025` runner
* Install NSIS
* fix naming
---
.github/workflows/desktop-build.yml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index fbb0e2af3..a0878046e 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -310,7 +310,7 @@ jobs:
- os: Windows
target: 10
- runner: windows-2022
+ runner: windows-2025
type: Release
make_package: 1
package_suffix: "-Win10"
@@ -410,6 +410,11 @@ jobs:
modules: ${{matrix.qt_modules}}
cache: true
+ - name: Install NSIS
+ if: matrix.os == 'Windows'
+ shell: bash
+ run: choco install nsis
+
- name: Setup vcpkg cache
id: vcpkg-cache
uses: TAServers/vcpkg-cache@v3
From 208ccc3a1a67ff94d71b520dc3aaaa1ca305e195 Mon Sep 17 00:00:00 2001
From: DawnFire42
Date: Fri, 27 Feb 2026 00:53:39 -0500
Subject: [PATCH 005/109] fix(docs): correct typos in README (#6640)
Fix 'projet' -> 'project' and 'invovled' -> 'involved'.
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index c08aee0e7..8ad9a092c 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ Latest beta version:
# Community Resources [](https://discord.gg/3Z9yzmA)
-Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other projet contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
+Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other project contributors (`#dev` channel) or fellow users of the app. Come here to talk about the application, features, or just to hang out.
- [Official Website](https://cockatrice.github.io)
- [Official Wiki](https://github.com/Cockatrice/Cockatrice/wiki)
- [Official Discord](https://discord.gg/3Z9yzmA)
@@ -109,7 +109,7 @@ Cockatrice tries to use the [Google Developer Documentation Style Guide](https:/
Cockatrice uses Transifex to manage translations. You can help us bring Cockatrice , Oracle and Webatrice to your language and just adjust single wordings right from within your browser by visiting our [Transifex project page](https://explore.transifex.com/cockatrice/cockatrice/).
-Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting invovled, and join a group of hundreds of others!
+Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about getting involved, and join a group of hundreds of others!
# Build [](https://github.com/Cockatrice/Cockatrice/actions/workflows/desktop-build.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/docker-release.yml?query=branch%3Amaster+event%3Apush) [](https://github.com/Cockatrice/Cockatrice/actions/workflows/web-build.yml?query=branch%3Amaster+event%3Apush)
From 59ce70afc516d428706d09748287a3a13db2e308 Mon Sep 17 00:00:00 2001
From: tooomm
Date: Mon, 2 Mar 2026 01:59:18 +0100
Subject: [PATCH 006/109] Add Code Docs link to issue template (#6649)
---
.github/ISSUE_TEMPLATE/config.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index c5354332b..f9abf643d 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,9 +1,12 @@
blank_issues_enabled: false
contact_links:
- name: 💬 Discord Community (Get help with server issues, e.g. Login)
- url: https://discord.gg/3Z9yzmA
+ url: https://discord.com/invite/3Z9yzmA
about: Need help with using the client? Want to find some games? Try the Discord server!
- name: 🌐 Translations (Help improve the localization of the app)
url: https://explore.transifex.com/cockatrice/cockatrice/
# it is not possible to add a link to the wiki to this description
about: For more information and guidance check our Translation FAQ on our wiki!
+ - name: 📖 Code Documentation
+ url: https://cockatrice.github.io/docs/
+ about:
From f978407a198c2127866e78635a68657da9997582 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:04:15 +0100
Subject: [PATCH 007/109] Bump actions/upload-artifact from 6 to 7 (#6652)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)
---
updated-dependencies:
- dependency-name: actions/upload-artifact
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/desktop-build.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index a0878046e..e48cc7d3f 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -212,7 +212,7 @@ jobs:
- name: Upload artifact
id: upload_artifact
if: matrix.package != 'skip'
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}}
@@ -499,7 +499,7 @@ jobs:
- name: Upload artifact
id: upload_artifact
if: matrix.make_package
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: ${{matrix.artifact_name}}
path: ${{steps.build.outputs.path}}
@@ -507,7 +507,7 @@ jobs:
- name: Upload PDBs (Program Databases)
if: matrix.os == 'Windows'
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: Windows${{matrix.target}}-PDBs
path: |
From e57ee8e9c9745a1ec20dfdc2439223fe3a0d5847 Mon Sep 17 00:00:00 2001
From: ebbit1q
Date: Mon, 2 Mar 2026 23:06:05 +0100
Subject: [PATCH 008/109] fix set sorting (#6630)
---
.../widgets/dialogs/dlg_manage_sets.cpp | 32 +++++++++----------
.../widgets/dialogs/dlg_manage_sets.h | 8 +++--
.../database/card_set/card_sets_model.cpp | 2 +-
3 files changed, 22 insertions(+), 20 deletions(-)
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp
index 5e6a6a62d..a4f54564f 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.cpp
@@ -20,8 +20,6 @@
#include
#include
-#define SORT_RESET -1
-
WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
{
setOrderIsSorted = false;
@@ -97,7 +95,6 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
view->setDropIndicatorShown(true);
view->setDragDropMode(QAbstractItemView::InternalMove);
- view->sortByColumn(SetsModel::SortKeyCol, Qt::AscendingOrder);
view->setColumnHidden(SetsModel::SortKeyCol, true);
view->setColumnHidden(SetsModel::IsKnownCol, true);
view->setColumnHidden(SetsModel::PriorityCol, true);
@@ -196,10 +193,10 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent)
auto headerState = SettingsCache::instance().layouts().getSetsDialogHeaderState();
if (!headerState.isEmpty()) {
view->header()->restoreState(headerState);
- view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder);
} else {
view->header()->resizeSections(QHeaderView::ResizeToContents);
}
+ resetSort();
connect(view->header(), &QHeaderView::geometriesChanged, this, &WndSets::saveHeaderState);
}
@@ -239,6 +236,13 @@ void WndSets::rebuildMainLayout(int actionToTake)
}
}
+void WndSets::resetSort()
+{
+ view->sortByColumn(SetsModel::SortKeyCol, Qt::AscendingOrder);
+ sortIndex = -1;
+ sortWarning->setVisible(false);
+}
+
void WndSets::includeRebalancedCardsChanged(bool _includeRebalancedCards)
{
includeRebalancedCards = _includeRebalancedCards;
@@ -260,9 +264,9 @@ void WndSets::actRestore()
void WndSets::actRestoreOriginalOrder()
{
- view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder);
model->restoreOriginalOrder();
- sortWarning->setVisible(false);
+ view->selectionModel()->reset();
+ resetSort();
}
void WndSets::actDisableResetButton(const QString &filterString)
@@ -288,11 +292,12 @@ void WndSets::actSort(int index)
sortIndex = index;
sortWarning->setVisible(true);
} else {
- view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder);
- sortIndex = -1;
- sortWarning->setVisible(false);
+ resetSort();
}
}
+ if (!view->selectionModel()->selection().empty()) {
+ view->scrollTo(view->selectionModel()->selectedRows().first());
+ }
}
void WndSets::actIgnoreWarning()
@@ -301,23 +306,18 @@ void WndSets::actIgnoreWarning()
return;
}
model->sort(sortIndex, sortOrder);
- view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder);
- sortIndex = -1;
- sortWarning->setVisible(false);
+ resetSort();
}
void WndSets::actDisableSortButtons(int index)
{
- if (index != SORT_RESET) {
+ if (index != SetsModel::SortKeyCol) {
view->setDragEnabled(false);
setOrderIsSorted = true;
} else {
setOrderIsSorted = false;
view->setDragEnabled(true);
}
- if (!view->selectionModel()->selection().empty()) {
- view->scrollTo(view->selectionModel()->selectedRows().first());
- }
actToggleButtons(view->selectionModel()->selection(), QItemSelection());
}
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h
index d7341caff..d9b77a76e 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_manage_sets.h
@@ -45,9 +45,6 @@ private:
QHBoxLayout *filterBox;
int sortIndex;
Qt::SortOrder sortOrder;
- void closeEvent(QCloseEvent *ev) override;
- void saveHeaderState();
- void rebuildMainLayout(int actionToTake);
bool setOrderIsSorted;
bool includeRebalancedCards;
enum
@@ -56,6 +53,11 @@ private:
SOME_SETS_SELECTED
};
+ void closeEvent(QCloseEvent *ev) override;
+ void saveHeaderState();
+ void rebuildMainLayout(int actionToTake);
+ void resetSort();
+
public:
explicit WndSets(QWidget *parent = nullptr);
~WndSets() override;
diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp
index b678e8276..f6dc4f9cf 100644
--- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp
+++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp
@@ -45,7 +45,7 @@ QVariant SetsModel::data(const QModelIndex &index, int role) const
switch (index.column()) {
case SortKeyCol:
- return QString("%1").arg(set->getSortKey(), 8, 10, QChar('0'));
+ return QString("%1").arg(index.row(), 8, 10, QChar('0'));
case IsKnownCol:
return set->getIsKnown();
case SetTypeCol:
From 9794893b63ad1865d98913d601323e9bd58f3506 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 2 Mar 2026 23:06:35 +0100
Subject: [PATCH 009/109] Bump actions/attest-build-provenance from 3 to 4
(#6653)
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3 to 4.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/v3...v4)
---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
dependency-version: '4'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/desktop-build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index e48cc7d3f..e95898097 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -232,7 +232,7 @@ jobs:
- name: Attest binary provenance
id: attestation
if: steps.upload_release.outcome == 'success'
- uses: actions/attest-build-provenance@v3
+ uses: actions/attest-build-provenance@v4
with:
subject-name: ${{steps.build.outputs.name}}
subject-path: ${{steps.build.outputs.path}}
@@ -530,7 +530,7 @@ jobs:
- name: Attest binary provenance
id: attestation
if: steps.upload_release.outcome == 'success'
- uses: actions/attest-build-provenance@v3
+ uses: actions/attest-build-provenance@v4
with:
subject-name: ${{steps.build.outputs.name}}
subject-path: ${{steps.build.outputs.path}}
From 2828854d3203b0b85159309596c997820b34cb10 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Tue, 3 Mar 2026 14:17:35 -0800
Subject: [PATCH 010/109] [Server] Support face-down cards in all public zones
(#6539)
* [Server] Support face-down cards in all public zones
* add null check
* Check using zone names instead
* add comment
---
.../remote/game/server_abstract_player.cpp | 34 +++++++++++++++++--
.../protocol/pb/command_move_card.proto | 4 ++-
2 files changed, 34 insertions(+), 4 deletions(-)
diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp
index b08495bad..00561bef2 100644
--- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp
+++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp
@@ -198,6 +198,35 @@ shouldDestroyOnMove(const Server_Card *card, const Server_CardZone *startZone, c
return true;
}
+/**
+ * @brief Determines whether the moved card should be face-down
+ */
+static bool
+shouldBeFaceDown(const MoveCardStruct &cardStruct, const Server_CardZone *startZone, const Server_CardZone *targetZone)
+{
+ if (!targetZone) {
+ return false;
+ }
+
+ // being face-down only makes sense for public zones
+ if (targetZone->getType() != ServerInfo_Zone::PublicZone) {
+ return false;
+ }
+
+ // face-down property in proto takes precedence
+ if (cardStruct.cardToMove->has_face_down()) {
+ return cardStruct.cardToMove->face_down();
+ }
+
+ // Default to keep face-down the same if zone didn't change.
+ // Compare using zone names because face-down is maintained when changing controllers.
+ if (startZone && startZone->getName() == targetZone->getName()) {
+ return cardStruct.card->getFaceDown();
+ }
+
+ return false;
+}
+
Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
Server_CardZone *startzone,
const QList &_cards,
@@ -257,10 +286,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
for (auto cardStruct : cardsToMove) {
Server_Card *card = cardStruct.card;
- const CardToMove *thisCardProperties = cardStruct.cardToMove;
int originalPosition = cardStruct.position;
- bool faceDown = targetzone->hasCoords() &&
- (thisCardProperties->has_face_down() ? thisCardProperties->face_down() : card->getFaceDown());
bool sourceBeingLookedAt;
int position = startzone->removeCard(card, sourceBeingLookedAt);
@@ -315,6 +341,8 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
++xIndex;
int newX = isReversed ? targetzone->getCards().size() - xCoord + xIndex : xCoord + xIndex;
+ bool faceDown = shouldBeFaceDown(cardStruct, startzone, targetzone);
+
if (targetzone->hasCoords()) {
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
} else {
diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto
index 6e9301d27..a5b96da2e 100644
--- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto
+++ b/libcockatrice_protocol/libcockatrice/protocol/pb/command_move_card.proto
@@ -6,7 +6,9 @@ message CardToMove {
// Id of the card in its current zone
optional sint32 card_id = 1 [default = -1];
- // Places the card face down, hiding its name
+ // If true, places the card face down, hiding its name.
+ // If false, forcibly turns the card face up.
+ // If not set, defers the resulting face down state to the server.
optional bool face_down = 2;
// When moving add this value to the power/toughness field of the card
From 2fba5dcd202c9a98381fbfbe6022199dcad0f230 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Tue, 3 Mar 2026 14:18:21 -0800
Subject: [PATCH 011/109] [Game] Refactor CardDragItem faceDown logic (#6552)
* Rename properties and only pass forceFaceDown
* [Game] Refactor CardDragItem faceDown logic
* revert refactor
* leave face_down unset unless forced
---
cockatrice/src/game/board/card_drag_item.cpp | 5 +++--
cockatrice/src/game/board/card_drag_item.h | 8 ++++----
cockatrice/src/game/board/card_item.cpp | 9 ++++-----
cockatrice/src/game/board/card_item.h | 2 +-
cockatrice/src/game/zones/pile_zone.cpp | 4 ++--
cockatrice/src/game/zones/table_zone.cpp | 6 ++++--
6 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/cockatrice/src/game/board/card_drag_item.cpp b/cockatrice/src/game/board/card_drag_item.cpp
index 9eadadeda..5ae56ccba 100644
--- a/cockatrice/src/game/board/card_drag_item.cpp
+++ b/cockatrice/src/game/board/card_drag_item.cpp
@@ -13,9 +13,10 @@
CardDragItem::CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
- bool _faceDown,
+ bool _forceFaceDown,
AbstractCardDragItem *parentDrag)
- : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), faceDown(_faceDown), occupied(false), currentZone(0)
+ : AbstractCardDragItem(_item, _hotSpot, parentDrag), id(_id), forceFaceDown(_forceFaceDown), occupied(false),
+ currentZone(0)
{
}
diff --git a/cockatrice/src/game/board/card_drag_item.h b/cockatrice/src/game/board/card_drag_item.h
index 257fde150..930c6be6f 100644
--- a/cockatrice/src/game/board/card_drag_item.h
+++ b/cockatrice/src/game/board/card_drag_item.h
@@ -16,7 +16,7 @@ class CardDragItem : public AbstractCardDragItem
Q_OBJECT
private:
int id;
- bool faceDown;
+ bool forceFaceDown;
bool occupied;
CardZone *currentZone;
@@ -24,15 +24,15 @@ public:
CardDragItem(CardItem *_item,
int _id,
const QPointF &_hotSpot,
- bool _faceDown,
+ bool _forceFaceDown,
AbstractCardDragItem *parentDrag = 0);
int getId() const
{
return id;
}
- bool getFaceDown() const
+ bool isForceFaceDown() const
{
- return faceDown;
+ return forceFaceDown;
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void updatePosition(const QPointF &cursorScenePos) override;
diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp
index 34c2f9a98..baf04e993 100644
--- a/cockatrice/src/game/board/card_item.cpp
+++ b/cockatrice/src/game/board/card_item.cpp
@@ -251,10 +251,10 @@ void CardItem::processCardInfo(const ServerInfo_Card &_info)
setDoesntUntap(_info.doesnt_untap());
}
-CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown)
+CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown)
{
deleteDragItem();
- dragItem = new CardDragItem(this, _id, _pos, faceDown);
+ dragItem = new CardDragItem(this, _id, _pos, forceFaceDown);
dragItem->setVisible(false);
scene()->addItem(dragItem);
dragItem->updatePosition(_scenePos);
@@ -352,7 +352,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
// Use the buttonDownPos to align the hot spot with the position when
// the user originally clicked
- createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), facedown || forceFaceDown);
+ createDragItem(id, event->buttonDownPos(Qt::LeftButton), event->scenePos(), forceFaceDown);
dragItem->grabMouse();
int childIndex = 0;
@@ -366,8 +366,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
childPos = card->pos() - pos();
else
childPos = QPointF(childIndex * CARD_WIDTH / 2, 0);
- CardDragItem *drag =
- new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem);
+ CardDragItem *drag = new CardDragItem(card, card->getId(), childPos, forceFaceDown, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
}
diff --git a/cockatrice/src/game/board/card_item.h b/cockatrice/src/game/board/card_item.h
index 86c4fe594..875c3f4d0 100644
--- a/cockatrice/src/game/board/card_item.h
+++ b/cockatrice/src/game/board/card_item.h
@@ -142,7 +142,7 @@ public:
void processCardInfo(const ServerInfo_Card &_info);
bool animationEvent();
- CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool faceDown);
+ CardDragItem *createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown);
void deleteDragItem();
void drawArrow(const QColor &arrowColor);
void drawAttachArrow();
diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp
index 2d1390826..521d13b99 100644
--- a/cockatrice/src/game/zones/pile_zone.cpp
+++ b/cockatrice/src/game/zones/pile_zone.cpp
@@ -101,12 +101,12 @@ void PileZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
if (getLogic()->getCards().isEmpty())
return;
- bool faceDown = event->modifiers().testFlag(Qt::ShiftModifier);
+ bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier);
CardItem *card = bottomCard ? getLogic()->getCards().last() : getLogic()->getCards().first();
const int cardid =
getLogic()->contentsKnown() ? card->getId() : (bottomCard ? getLogic()->getCards().size() - 1 : 0);
- CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), faceDown);
+ CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), forceFaceDown);
drag->grabMouse();
setCursor(Qt::OpenHandCursor);
}
diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp
index c76514350..ca1052d20 100644
--- a/cockatrice/src/game/zones/table_zone.cpp
+++ b/cockatrice/src/game/zones/table_zone.cpp
@@ -134,8 +134,10 @@ void TableZone::handleDropEventByGrid(const QList &dragItems,
for (const auto &item : dragItems) {
CardToMove *ctm = cmd.mutable_cards_to_move()->add_card();
ctm->set_card_id(item->getId());
- ctm->set_face_down(item->getFaceDown());
- if (startZone->getName() != getLogic()->getName() && !item->getFaceDown()) {
+ if (item->isForceFaceDown()) {
+ ctm->set_face_down(true);
+ }
+ if (startZone->getName() != getLogic()->getName() && !item->isForceFaceDown()) {
const auto &card = item->getItem()->getCard();
if (card) {
ctm->set_pt(card.getInfo().getPowTough().toStdString());
From 846ecb7e8daa078022434acaf25f368259f3f748 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Tue, 3 Mar 2026 14:19:26 -0800
Subject: [PATCH 012/109] [Client] Support face-down cards in all public zones
(#6602)
* [Server] Support face-down cards in all public zones
* add null check
* Check using zone names instead
* add comment
* Rename properties and only pass forceFaceDown
* [Game] Refactor CardDragItem faceDown logic
* revert refactor
* leave face_down unset unless forced
* [Client] Support face-down cards in all public zones
* leave face_down unset unless forced
* log face down
* update remaining logs
---
cockatrice/src/game/board/card_item.cpp | 4 +++-
.../src/game/log/message_log_widget.cpp | 24 +++++++++++++++----
.../src/game/zones/logic/card_zone_logic.cpp | 6 +++--
cockatrice/src/game/zones/pile_zone.cpp | 9 +++++--
cockatrice/src/game/zones/stack_zone.cpp | 6 ++++-
cockatrice/src/game/zones/view_zone.cpp | 21 ++++++++++++----
6 files changed, 55 insertions(+), 15 deletions(-)
diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp
index baf04e993..8b9549f07 100644
--- a/cockatrice/src/game/board/card_item.cpp
+++ b/cockatrice/src/game/board/card_item.cpp
@@ -212,10 +212,12 @@ void CardItem::setAttachedTo(CardItem *_attachedTo)
}
}
+/**
+ * @brief Resets the fields that should be reset after a zone transition
+ */
void CardItem::resetState(bool keepAnnotations)
{
attacking = false;
- facedown = false;
counters.clear();
pt.clear();
if (!keepAnnotations) {
diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp
index 1ff3e32ff..645974994 100644
--- a/cockatrice/src/game/log/message_log_widget.cpp
+++ b/cockatrice/src/game/log/message_log_widget.cpp
@@ -314,9 +314,17 @@ void MessageLogWidget::logMoveCard(Player *player,
finalStr = tr("%1 puts %2 into play%3.");
}
} else if (targetZoneName == GRAVE_ZONE_NAME) {
- finalStr = tr("%1 puts %2%3 into their graveyard.");
+ if (card->getFaceDown()) {
+ finalStr = tr("%1 puts %2%3 into their graveyard face down.");
+ } else {
+ finalStr = tr("%1 puts %2%3 into their graveyard.");
+ }
} else if (targetZoneName == EXILE_ZONE_NAME) {
- finalStr = tr("%1 exiles %2%3.");
+ if (card->getFaceDown()) {
+ finalStr = tr("%1 exiles %2%3 face down.");
+ } else {
+ finalStr = tr("%1 exiles %2%3.");
+ }
} else if (targetZoneName == HAND_ZONE_NAME) {
finalStr = tr("%1 moves %2%3 to their hand.");
} else if (targetZoneName == DECK_ZONE_NAME) {
@@ -335,10 +343,18 @@ void MessageLogWidget::logMoveCard(Player *player,
finalStr = tr("%1 moves %2%3 to sideboard.");
} else if (targetZoneName == STACK_ZONE_NAME) {
soundEngine->playSound("play_card");
- finalStr = tr("%1 plays %2%3.");
+ if (card->getFaceDown()) {
+ finalStr = tr("%1 plays %2%3 face down.");
+ } else {
+ finalStr = tr("%1 plays %2%3.");
+ }
} else {
fourthArg = targetZoneName;
- finalStr = tr("%1 moves %2%3 to custom zone '%4'.");
+ if (card->getFaceDown()) {
+ finalStr = tr("%1 moves %2%3 to custom zone '%4' face down.");
+ } else {
+ finalStr = tr("%1 moves %2%3 to custom zone '%4'.");
+ }
}
QString message = finalStr.arg(sanitizeHtml(player->getPlayerInfo()->getName()), cardStr, nameFrom.second);
diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/logic/card_zone_logic.cpp
index bd32eab3e..1872ba95e 100644
--- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp
+++ b/cockatrice/src/game/zones/logic/card_zone_logic.cpp
@@ -43,8 +43,10 @@ void CardZoneLogic::addCard(CardItem *card, const bool reorganize, const int x,
for (auto *view : views) {
if (qobject_cast(view->getLogic())->prepareAddCard(x)) {
- view->getLogic()->addCard(new CardItem(player, nullptr, card->getCardRef(), card->getId()), reorganize, x,
- y);
+ auto copy = new CardItem(player, nullptr, card->getCardRef(), card->getId());
+ copy->setFaceDown(card->getFaceDown());
+
+ view->getLogic()->addCard(copy, reorganize, x, y);
}
}
diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp
index 521d13b99..87a60fc03 100644
--- a/cockatrice/src/game/zones/pile_zone.cpp
+++ b/cockatrice/src/game/zones/pile_zone.cpp
@@ -68,8 +68,13 @@ void PileZone::handleDropEvent(const QList &dragItems, CardZoneL
cmd.set_x(0);
cmd.set_y(0);
- for (int i = 0; i < dragItems.size(); ++i)
- cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
+ for (int i = 0; i < dragItems.size(); ++i) {
+ auto cardToMove = cmd.mutable_cards_to_move()->add_card();
+ cardToMove->set_card_id(dragItems[i]->getId());
+ if (dragItems[i]->isForceFaceDown()) {
+ cardToMove->set_face_down(true);
+ }
+ }
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
}
diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp
index 820008b27..a9d649a4a 100644
--- a/cockatrice/src/game/zones/stack_zone.cpp
+++ b/cockatrice/src/game/zones/stack_zone.cpp
@@ -78,7 +78,11 @@ void StackZone::handleDropEvent(const QList &dragItems,
for (CardDragItem *item : dragItems) {
if (item) {
- cmd.mutable_cards_to_move()->add_card()->set_card_id(item->getId());
+ auto cardToMove = cmd.mutable_cards_to_move()->add_card();
+ cardToMove->set_card_id(item->getId());
+ if (item->isForceFaceDown()) {
+ cardToMove->set_face_down(true);
+ }
}
}
diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp
index 4ae25989e..348ed6e87 100644
--- a/cockatrice/src/game/zones/view_zone.cpp
+++ b/cockatrice/src/game/zones/view_zone.cpp
@@ -73,7 +73,10 @@ void ZoneViewZone::initializeCards(const QList &cardLis
for (int i = 0; i < cardList.size(); ++i) {
auto card = cardList[i];
CardRef cardRef = {QString::fromStdString(card->name()), QString::fromStdString(card->provider_id())};
- getLogic()->addCard(new CardItem(getLogic()->getPlayer(), this, cardRef, card->id()), false, i);
+ auto copy = new CardItem(getLogic()->getPlayer(), this, cardRef, card->id());
+ copy->setFaceDown(card->face_down());
+
+ getLogic()->addCard(copy, false, i);
}
reorganizeCards();
} else if (!qobject_cast(getLogic())->getOriginalZone()->contentsKnown()) {
@@ -91,8 +94,10 @@ void ZoneViewZone::initializeCards(const QList &cardLis
int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size());
for (int i = 0; i < number; i++) {
CardItem *card = c.at(i);
- getLogic()->addCard(new CardItem(getLogic()->getPlayer(), this, card->getCardRef(), card->getId()), false,
- i);
+ auto copy = new CardItem(getLogic()->getPlayer(), this, card->getCardRef(), card->getId());
+ copy->setFaceDown(card->getFaceDown());
+
+ getLogic()->addCard(copy, false, i);
}
reorganizeCards();
}
@@ -107,6 +112,7 @@ void ZoneViewZone::zoneDumpReceived(const Response &r)
auto cardName = QString::fromStdString(cardInfo.name());
auto cardProviderId = QString::fromStdString(cardInfo.provider_id());
auto card = new CardItem(getLogic()->getPlayer(), this, {cardName, cardProviderId}, cardInfo.id(), getLogic());
+ card->setFaceDown(cardInfo.face_down());
getLogic()->rawInsertCard(card, i);
}
@@ -279,8 +285,13 @@ void ZoneViewZone::handleDropEvent(const QList &dragItems,
cmd.set_y(0);
cmd.set_is_reversed(qobject_cast(getLogic())->getIsReversed());
- for (int i = 0; i < dragItems.size(); ++i)
- cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
+ for (int i = 0; i < dragItems.size(); ++i) {
+ auto cardToMove = cmd.mutable_cards_to_move()->add_card();
+ cardToMove->set_card_id(dragItems[i]->getId());
+ if (dragItems[i]->isForceFaceDown()) {
+ cardToMove->set_face_down(true);
+ }
+ }
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
}
From 43acac5f5db0a8ddf5221ececf35f57af6d6ea9e Mon Sep 17 00:00:00 2001
From: ebbit1q
Date: Wed, 4 Mar 2026 00:16:19 +0100
Subject: [PATCH 013/109] empty card info when switching back to theme
background (#6657)
---
cockatrice/src/interface/widgets/general/home_widget.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp
index 269326257..5176dde83 100644
--- a/cockatrice/src/interface/widgets/general/home_widget.cpp
+++ b/cockatrice/src/interface/widgets/general/home_widget.cpp
@@ -63,6 +63,7 @@ void HomeWidget::initializeBackgroundFromSource()
switch (backgroundSourceType) {
case BackgroundSources::Theme:
background = QPixmap("theme:backgrounds/home");
+ backgroundSourceCard->setCard(ExactCard());
updateButtonsToBackgroundColor();
update();
break;
From 566c876bdc7f716ee56cf11f08c1aa6626a5c4a4 Mon Sep 17 00:00:00 2001
From: ebbit1q
Date: Wed, 4 Mar 2026 00:16:45 +0100
Subject: [PATCH 014/109] fix scaling for background images on home tab (#6656)
---
.../card_info_picture_art_crop_widget.cpp | 12 ++++--------
.../cards/card_info_picture_art_crop_widget.h | 4 ++--
.../interface/widgets/general/home_widget.cpp | 19 +++++++++++--------
3 files changed, 17 insertions(+), 18 deletions(-)
diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp
index 451104d17..e5b0c4872 100644
--- a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp
+++ b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.cpp
@@ -8,7 +8,7 @@ CardInfoPictureArtCropWidget::CardInfoPictureArtCropWidget(QWidget *parent)
hide();
}
-QPixmap CardInfoPictureArtCropWidget::getProcessedBackground(const QSize &targetSize)
+QPixmap CardInfoPictureArtCropWidget::getBackground()
{
// Load the full-resolution card image, not a pre-scaled one
QPixmap fullResPixmap;
@@ -17,10 +17,8 @@ QPixmap CardInfoPictureArtCropWidget::getProcessedBackground(const QSize &target
} else {
CardPictureLoader::getCardBackPixmap(fullResPixmap, QSize(745, 1040));
}
-
- // Fail-safe if loading failed
if (fullResPixmap.isNull()) {
- return QPixmap(targetSize);
+ return QPixmap(); // return null qpixmap
}
const QSize sz = fullResPixmap.size();
@@ -33,9 +31,7 @@ QPixmap CardInfoPictureArtCropWidget::getProcessedBackground(const QSize &target
foilRect = foilRect.intersected(fullResPixmap.rect()); // always clamp to source bounds
- // Crop first, then scale for best quality
+ // return full resolution image crop
QPixmap cropped = fullResPixmap.copy(foilRect);
- QPixmap scaled = cropped.scaled(targetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
-
- return scaled;
+ return cropped;
}
diff --git a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h
index aed951cc6..0185f758c 100644
--- a/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h
+++ b/cockatrice/src/interface/widgets/cards/card_info_picture_art_crop_widget.h
@@ -16,8 +16,8 @@ class CardInfoPictureArtCropWidget : public CardInfoPictureWidget
public:
explicit CardInfoPictureArtCropWidget(QWidget *parent = nullptr);
- // Returns a processed (cropped & scaled) version of the pixmap
- QPixmap getProcessedBackground(const QSize &targetSize);
+ // Returns a cropped version of the pixmap
+ QPixmap getBackground();
};
#endif // CARD_INFO_PICTURE_ART_CROP_WIDGET_H
diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp
index 5176dde83..7d0bad813 100644
--- a/cockatrice/src/interface/widgets/general/home_widget.cpp
+++ b/cockatrice/src/interface/widgets/general/home_widget.cpp
@@ -125,7 +125,7 @@ void HomeWidget::updateRandomCard()
connect(newCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &HomeWidget::updateBackgroundProperties);
backgroundSourceCard->setCard(newCard);
- background = backgroundSourceCard->getProcessedBackground(size());
+ background = backgroundSourceCard->getBackground();
if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) {
cardChangeTimer->stop();
@@ -143,7 +143,7 @@ void HomeWidget::onBackgroundShuffleFrequencyChanged()
void HomeWidget::updateBackgroundProperties()
{
- background = backgroundSourceCard->getProcessedBackground(size());
+ background = backgroundSourceCard->getBackground();
updateButtonsToBackgroundColor();
update(); // Triggers repaint
}
@@ -301,14 +301,17 @@ void HomeWidget::paintEvent(QPaintEvent *event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
- background = background.scaled(size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
+ if (!background.isNull()) {
+ QSize widgetSize = size() * devicePixelRatio();
+ QPixmap toDraw = background.scaled(widgetSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
- // Draw already-scaled background centered
- QSize widgetSize = size();
- QSize bgSize = background.size();
- QPoint topLeft((widgetSize.width() - bgSize.width()) / 2, (widgetSize.height() - bgSize.height()) / 2);
+ // Draw scaled background centered
+ QSize bgSize = toDraw.size();
+ QPoint topLeft((widgetSize.width() - bgSize.width()) / (devicePixelRatio() * 2), // undo scaling for painter
+ (widgetSize.height() - bgSize.height()) / (devicePixelRatio() * 2));
- painter.drawPixmap(topLeft, background);
+ painter.drawPixmap(topLeft, toDraw);
+ }
// Draw translucent black overlay with rounded corners
QRectF overlayRect(5, 5, width() - 10, height() - 10);
From b36ab66583ec60f1dbb9366f3246d274daa11595 Mon Sep 17 00:00:00 2001
From: ebbit1q
Date: Wed, 4 Mar 2026 00:49:50 +0100
Subject: [PATCH 015/109] nullcheck printing's set in home tab background art
crop (#6646)
* nullcheck printing's set in home tab background art crop
* set warning and properly set timer
* fix merge
---
.../interface/widgets/general/home_widget.cpp | 46 ++++++++++++-------
.../interface/widgets/general/home_widget.h | 1 +
2 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp
index 7d0bad813..750ddd000 100644
--- a/cockatrice/src/interface/widgets/general/home_widget.cpp
+++ b/cockatrice/src/interface/widgets/general/home_widget.cpp
@@ -58,21 +58,24 @@ void HomeWidget::initializeBackgroundFromSource()
auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource());
- cardChangeTimer->stop();
-
switch (backgroundSourceType) {
case BackgroundSources::Theme:
+ cardChangeTimer->stop();
background = QPixmap("theme:backgrounds/home");
+ backgroundSourceDeck = DeckList();
backgroundSourceCard->setCard(ExactCard());
updateButtonsToBackgroundColor();
update();
break;
case BackgroundSources::RandomCardArt:
- cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
+ backgroundSourceDeck = DeckList();
+ updateRandomCard();
+ onBackgroundShuffleFrequencyChanged();
break;
case BackgroundSources::DeckFileArt:
loadBackgroundSourceDeck();
- cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
+ updateRandomCard();
+ onBackgroundShuffleFrequencyChanged();
break;
}
}
@@ -84,6 +87,20 @@ void HomeWidget::loadBackgroundSourceDeck()
backgroundSourceDeck = deckOpt.has_value() ? deckOpt.value().deckList : DeckList();
}
+void HomeWidget::setRandomCard(ExactCard &newCard)
+{
+ static constexpr int ATTEMPTS = 10;
+ for (int i = 0; i < ATTEMPTS; ++i) {
+ ExactCard tmpCard = CardDatabaseManager::query()->getRandomCard();
+ if (tmpCard != backgroundSourceCard->getCard() && tmpCard.getCardPtr()->getProperty("layout") == "normal" &&
+ tmpCard.getPrinting().getSet() != nullptr) {
+ newCard = tmpCard;
+ return;
+ }
+ }
+ qWarning() << "failed to set random card image after" << ATTEMPTS << "attempts";
+}
+
void HomeWidget::updateRandomCard()
{
auto backgroundSourceType = BackgroundSources::fromId(SettingsCache::instance().getHomeTabBackgroundSource());
@@ -94,10 +111,7 @@ void HomeWidget::updateRandomCard()
case BackgroundSources::Theme:
break;
case BackgroundSources::RandomCardArt:
- do {
- newCard = CardDatabaseManager::query()->getRandomCard();
- } while (newCard == backgroundSourceCard->getCard() &&
- newCard.getCardPtr()->getProperty("layout") != "normal");
+ setRandomCard(newCard);
break;
case BackgroundSources::DeckFileArt:
QList cardRefs = backgroundSourceDeck.getCardRefList();
@@ -126,19 +140,14 @@ void HomeWidget::updateRandomCard()
connect(newCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &HomeWidget::updateBackgroundProperties);
backgroundSourceCard->setCard(newCard);
background = backgroundSourceCard->getBackground();
-
- if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) {
- cardChangeTimer->stop();
- }
}
void HomeWidget::onBackgroundShuffleFrequencyChanged()
{
cardChangeTimer->stop();
- if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() <= 0) {
- return;
+ if (SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() > 0) {
+ cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
}
- cardChangeTimer->start(SettingsCache::instance().getHomeTabBackgroundShuffleFrequency() * 1000);
}
void HomeWidget::updateBackgroundProperties()
@@ -325,8 +334,11 @@ void HomeWidget::paintEvent(QPaintEvent *event)
QString cardName;
ExactCard card = backgroundSourceCard->getCard();
if (card) {
- cardName = card.getCardPtr()->getName() + " (" + card.getPrinting().getSet()->getCorrectedShortName() + ") " +
- card.getPrinting().getProperty("num");
+ cardName = card.getCardPtr()->getName();
+ if (card.getPrinting().getSet() != nullptr) {
+ cardName += " (" + card.getPrinting().getSet()->getCorrectedShortName() + ") " +
+ card.getPrinting().getProperty("num");
+ }
}
if (!cardName.isEmpty() && SettingsCache::instance().getHomeTabDisplayCardName()) {
diff --git a/cockatrice/src/interface/widgets/general/home_widget.h b/cockatrice/src/interface/widgets/general/home_widget.h
index 233fda543..b30bb5407 100644
--- a/cockatrice/src/interface/widgets/general/home_widget.h
+++ b/cockatrice/src/interface/widgets/general/home_widget.h
@@ -45,6 +45,7 @@ private:
QPair gradientColors;
HomeStyledButton *connectButton;
+ void setRandomCard(ExactCard &newCard);
void loadBackgroundSourceDeck();
};
From e7a3ad86eb8bc294da4b8d027392c4fb48a3bbb9 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Wed, 4 Mar 2026 06:00:18 -0800
Subject: [PATCH 016/109] [Game] Refactor move cards from library actions
(#6658)
* refactor move top/bottom cards actions
* minor cleanup
* translate zone display names
---
cockatrice/src/game/player/player_actions.cpp | 84 +++++--------------
cockatrice/src/game/player/player_actions.h | 3 +
2 files changed, 25 insertions(+), 62 deletions(-)
diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp
index ed77808b0..1a09836e4 100644
--- a/cockatrice/src/game/player/player_actions.cpp
+++ b/cockatrice/src/game/player/player_actions.cpp
@@ -424,37 +424,15 @@ void PlayerActions::actMoveTopCardToExile()
void PlayerActions::actMoveTopCardsToGrave()
{
- const int maxCards = player->getDeckZone()->getCards().size();
- if (maxCards == 0) {
- return;
- }
-
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to grave"),
- tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
- maxCards, 1, &ok);
- if (!ok) {
- return;
- } else if (number > maxCards) {
- number = maxCards;
- }
- defaultNumberTopCards = number;
-
- Command_MoveCard cmd;
- cmd.set_start_zone("deck");
- cmd.set_target_player_id(player->getPlayerInfo()->getId());
- cmd.set_target_zone("grave");
- cmd.set_x(0);
- cmd.set_y(0);
-
- for (int i = number - 1; i >= 0; --i) {
- cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
- }
-
- sendGameCommand(cmd);
+ moveTopCardsTo("grave", tr("grave"));
}
void PlayerActions::actMoveTopCardsToExile()
+{
+ moveTopCardsTo("rfg", tr("exile"));
+}
+
+void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
@@ -462,12 +440,14 @@ void PlayerActions::actMoveTopCardsToExile()
}
bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to exile"),
+ int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move top cards to %1").arg(zoneDisplayName),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberTopCards, 1,
maxCards, 1, &ok);
if (!ok) {
return;
- } else if (number > maxCards) {
+ }
+
+ if (number > maxCards) {
number = maxCards;
}
defaultNumberTopCards = number;
@@ -475,7 +455,7 @@ void PlayerActions::actMoveTopCardsToExile()
Command_MoveCard cmd;
cmd.set_start_zone("deck");
cmd.set_target_player_id(player->getPlayerInfo()->getId());
- cmd.set_target_zone("rfg");
+ cmd.set_target_zone(targetZone.toStdString());
cmd.set_x(0);
cmd.set_y(0);
@@ -628,37 +608,15 @@ void PlayerActions::actMoveBottomCardToExile()
void PlayerActions::actMoveBottomCardsToGrave()
{
- const int maxCards = player->getDeckZone()->getCards().size();
- if (maxCards == 0) {
- return;
- }
-
- bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to grave"),
- tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
- maxCards, 1, &ok);
- if (!ok) {
- return;
- } else if (number > maxCards) {
- number = maxCards;
- }
- defaultNumberBottomCards = number;
-
- Command_MoveCard cmd;
- cmd.set_start_zone("deck");
- cmd.set_target_player_id(player->getPlayerInfo()->getId());
- cmd.set_target_zone("grave");
- cmd.set_x(0);
- cmd.set_y(0);
-
- for (int i = maxCards - number; i < maxCards; ++i) {
- cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
- }
-
- sendGameCommand(cmd);
+ moveBottomCardsTo("grave", tr("grave"));
}
void PlayerActions::actMoveBottomCardsToExile()
+{
+ moveBottomCardsTo("rfg", tr("exile"));
+}
+
+void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
@@ -666,12 +624,14 @@ void PlayerActions::actMoveBottomCardsToExile()
}
bool ok;
- int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to exile"),
+ int number = QInputDialog::getInt(player->getGame()->getTab(), tr("Move bottom cards to %1").arg(zoneDisplayName),
tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1,
maxCards, 1, &ok);
if (!ok) {
return;
- } else if (number > maxCards) {
+ }
+
+ if (number > maxCards) {
number = maxCards;
}
defaultNumberBottomCards = number;
@@ -679,7 +639,7 @@ void PlayerActions::actMoveBottomCardsToExile()
Command_MoveCard cmd;
cmd.set_start_zone("deck");
cmd.set_target_player_id(player->getPlayerInfo()->getId());
- cmd.set_target_zone("rfg");
+ cmd.set_target_zone(targetZone.toStdString());
cmd.set_x(0);
cmd.set_y(0);
diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h
index b294b5946..beed2c241 100644
--- a/cockatrice/src/game/player/player_actions.h
+++ b/cockatrice/src/game/player/player_actions.h
@@ -180,6 +180,9 @@ private:
FilterString movingCardsUntilFilter;
int movingCardsUntilCounter = 0;
+ void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName);
+ void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName);
+
void createCard(const CardItem *sourceCard,
const QString &dbCardName,
CardRelationType attach = CardRelationType::DoesNotAttach,
From 1bcea27a445c912b5d51462e5a35ab732942ded4 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Wed, 4 Mar 2026 18:19:41 -0800
Subject: [PATCH 017/109] [Game] Add face down versions of move cards from
library actions (#6661)
* implement actions
* add new actions to menu
* update shortcuts
---
.../src/client/settings/shortcuts_settings.h | 16 +++++++
.../src/game/player/menu/library_menu.cpp | 28 ++++++++++++
.../src/game/player/menu/library_menu.h | 4 ++
cockatrice/src/game/player/player_actions.cpp | 44 +++++++++++++++----
cockatrice/src/game/player/player_actions.h | 8 +++-
5 files changed, 90 insertions(+), 10 deletions(-)
diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h
index 156ee82ea..47332b8c4 100644
--- a/cockatrice/src/client/settings/shortcuts_settings.h
+++ b/cockatrice/src/client/settings/shortcuts_settings.h
@@ -598,11 +598,19 @@ private:
{"Player/aMoveTopCardsToGraveyard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"),
parseSequenceString("Alt+M"),
ShortcutGroup::Move_top)},
+ {"Player/aMoveTopCardsToGraveyardFaceDown",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple), Face Down"),
+ parseSequenceString(""),
+ ShortcutGroup::Move_top)},
{"Player/aMoveTopCardToExile",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_top)},
+ {"Player/aMoveTopCardsToExileFaceDown",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple), Face Down"),
+ parseSequenceString(""),
+ ShortcutGroup::Move_top)},
{"Player/aMoveTopCardsUntil", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Stack Until Found"),
parseSequenceString("Ctrl+Shift+Y"),
ShortcutGroup::Move_top)},
@@ -620,11 +628,19 @@ private:
{"Player/aMoveBottomCardsToGrave", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
+ {"Player/aMoveBottomCardsToGraveFaceDown",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Graveyard (Multiple), Face Down"),
+ parseSequenceString(""),
+ ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardToExile",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile"), parseSequenceString(""), ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardsToExile", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple)"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
+ {"Player/aMoveBottomCardsToExileFaceDown",
+ ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Exile (Multiple), Face Down"),
+ parseSequenceString(""),
+ ShortcutGroup::Move_bottom)},
{"Player/aMoveBottomCardToTop", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top of Library"),
parseSequenceString(""),
ShortcutGroup::Move_bottom)},
diff --git a/cockatrice/src/game/player/menu/library_menu.cpp b/cockatrice/src/game/player/menu/library_menu.cpp
index 749439558..1bb647d06 100644
--- a/cockatrice/src/game/player/menu/library_menu.cpp
+++ b/cockatrice/src/game/player/menu/library_menu.cpp
@@ -51,8 +51,10 @@ LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent)
topLibraryMenu->addSeparator();
topLibraryMenu->addAction(aMoveTopCardToGraveyard);
topLibraryMenu->addAction(aMoveTopCardsToGraveyard);
+ topLibraryMenu->addAction(aMoveTopCardsToGraveyardFaceDown);
topLibraryMenu->addAction(aMoveTopCardToExile);
topLibraryMenu->addAction(aMoveTopCardsToExile);
+ topLibraryMenu->addAction(aMoveTopCardsToExileFaceDown);
topLibraryMenu->addAction(aMoveTopCardsUntil);
topLibraryMenu->addSeparator();
topLibraryMenu->addAction(aShuffleTopCards);
@@ -66,8 +68,10 @@ LibraryMenu::LibraryMenu(Player *_player, QWidget *parent) : TearOffMenu(parent)
bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aMoveBottomCardToGraveyard);
bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyard);
+ bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyardFaceDown);
bottomLibraryMenu->addAction(aMoveBottomCardToExile);
bottomLibraryMenu->addAction(aMoveBottomCardsToExile);
+ bottomLibraryMenu->addAction(aMoveBottomCardsToExileFaceDown);
bottomLibraryMenu->addSeparator();
bottomLibraryMenu->addAction(aShuffleBottomCards);
@@ -136,8 +140,14 @@ void LibraryMenu::createMoveActions()
connect(aMoveTopCardToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardToExile);
aMoveTopCardsToGraveyard = new QAction(this);
connect(aMoveTopCardsToGraveyard, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToGrave);
+ aMoveTopCardsToGraveyardFaceDown = new QAction(this);
+ connect(aMoveTopCardsToGraveyardFaceDown, &QAction::triggered, playerActions,
+ &PlayerActions::actMoveTopCardsToGraveFaceDown);
aMoveTopCardsToExile = new QAction(this);
connect(aMoveTopCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsToExile);
+ aMoveTopCardsToExileFaceDown = new QAction(this);
+ connect(aMoveTopCardsToExileFaceDown, &QAction::triggered, playerActions,
+ &PlayerActions::actMoveTopCardsToExileFaceDown);
aMoveTopCardsUntil = new QAction(this);
connect(aMoveTopCardsUntil, &QAction::triggered, playerActions, &PlayerActions::actMoveTopCardsUntil);
aMoveTopCardToBottom = new QAction(this);
@@ -156,8 +166,14 @@ void LibraryMenu::createMoveActions()
aMoveBottomCardsToGraveyard = new QAction(this);
connect(aMoveBottomCardsToGraveyard, &QAction::triggered, playerActions,
&PlayerActions::actMoveBottomCardsToGrave);
+ aMoveBottomCardsToGraveyardFaceDown = new QAction(this);
+ connect(aMoveBottomCardsToGraveyardFaceDown, &QAction::triggered, playerActions,
+ &PlayerActions::actMoveBottomCardsToGraveFaceDown);
aMoveBottomCardsToExile = new QAction(this);
connect(aMoveBottomCardsToExile, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardsToExile);
+ aMoveBottomCardsToExileFaceDown = new QAction(this);
+ connect(aMoveBottomCardsToExileFaceDown, &QAction::triggered, playerActions,
+ &PlayerActions::actMoveBottomCardsToExileFaceDown);
aMoveBottomCardToTop = new QAction(this);
connect(aMoveBottomCardToTop, &QAction::triggered, playerActions, &PlayerActions::actMoveBottomCardToTop);
}
@@ -216,7 +232,9 @@ void LibraryMenu::retranslateUi()
aMoveTopCardToGraveyard->setText(tr("Move top card to grave&yard"));
aMoveTopCardToExile->setText(tr("Move top card to e&xile"));
aMoveTopCardsToGraveyard->setText(tr("Move top cards to &graveyard..."));
+ aMoveTopCardsToGraveyardFaceDown->setText(tr("Move top cards to graveyard face down..."));
aMoveTopCardsToExile->setText(tr("Move top cards to &exile..."));
+ aMoveTopCardsToExileFaceDown->setText(tr("Move top cards to exile face down..."));
aMoveTopCardsUntil->setText(tr("Put top cards on stack &until..."));
aShuffleTopCards->setText(tr("Shuffle top cards..."));
@@ -227,7 +245,9 @@ void LibraryMenu::retranslateUi()
aMoveBottomCardToGraveyard->setText(tr("Move bottom card to grave&yard"));
aMoveBottomCardToExile->setText(tr("Move bottom card to e&xile"));
aMoveBottomCardsToGraveyard->setText(tr("Move bottom cards to &graveyard..."));
+ aMoveBottomCardsToGraveyardFaceDown->setText(tr("Move bottom cards to graveyard face down..."));
aMoveBottomCardsToExile->setText(tr("Move bottom cards to &exile..."));
+ aMoveBottomCardsToExileFaceDown->setText(tr("Move bottom cards to exile face down..."));
aMoveBottomCardToTop->setText(tr("Put bottom card on &top"));
aShuffleBottomCards->setText(tr("Shuffle bottom cards..."));
}
@@ -335,8 +355,10 @@ void LibraryMenu::setShortcutsActive()
aMoveTopToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopToPlayFaceDown"));
aMoveTopCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToGraveyard"));
aMoveTopCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyard"));
+ aMoveTopCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToGraveyardFaceDown"));
aMoveTopCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToExile"));
aMoveTopCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExile"));
+ aMoveTopCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsToExileFaceDown"));
aMoveTopCardsUntil->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardsUntil"));
aMoveTopCardToBottom->setShortcuts(shortcuts.getShortcut("Player/aMoveTopCardToBottom"));
aDrawBottomCard->setShortcuts(shortcuts.getShortcut("Player/aDrawBottomCard"));
@@ -345,8 +367,10 @@ void LibraryMenu::setShortcutsActive()
aMoveBottomToPlayFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomToPlayFaceDown"));
aMoveBottomCardToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToGrave"));
aMoveBottomCardsToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGrave"));
+ aMoveBottomCardsToGraveyardFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToGraveFaceDown"));
aMoveBottomCardToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToExile"));
aMoveBottomCardsToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExile"));
+ aMoveBottomCardsToExileFaceDown->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardsToExileFaceDown"));
aMoveBottomCardToTop->setShortcuts(shortcuts.getShortcut("Player/aMoveBottomCardToTop"));
}
@@ -367,8 +391,10 @@ void LibraryMenu::setShortcutsInactive()
aMoveTopToPlayFaceDown->setShortcut(QKeySequence());
aMoveTopCardToGraveyard->setShortcut(QKeySequence());
aMoveTopCardsToGraveyard->setShortcut(QKeySequence());
+ aMoveTopCardsToGraveyardFaceDown->setShortcut(QKeySequence());
aMoveTopCardToExile->setShortcut(QKeySequence());
aMoveTopCardsToExile->setShortcut(QKeySequence());
+ aMoveTopCardsToExileFaceDown->setShortcut(QKeySequence());
aMoveTopCardsUntil->setShortcut(QKeySequence());
aDrawBottomCard->setShortcut(QKeySequence());
aDrawBottomCards->setShortcut(QKeySequence());
@@ -376,6 +402,8 @@ void LibraryMenu::setShortcutsInactive()
aMoveBottomToPlayFaceDown->setShortcut(QKeySequence());
aMoveBottomCardToGraveyard->setShortcut(QKeySequence());
aMoveBottomCardsToGraveyard->setShortcut(QKeySequence());
+ aMoveBottomCardsToGraveyardFaceDown->setShortcut(QKeySequence());
aMoveBottomCardToExile->setShortcut(QKeySequence());
aMoveBottomCardsToExile->setShortcut(QKeySequence());
+ aMoveBottomCardsToExileFaceDown->setShortcut(QKeySequence());
}
diff --git a/cockatrice/src/game/player/menu/library_menu.h b/cockatrice/src/game/player/menu/library_menu.h
index 96d4b82b1..c0883107c 100644
--- a/cockatrice/src/game/player/menu/library_menu.h
+++ b/cockatrice/src/game/player/menu/library_menu.h
@@ -88,7 +88,9 @@ public:
QAction *aMoveTopCardToGraveyard = nullptr;
QAction *aMoveTopCardToExile = nullptr;
QAction *aMoveTopCardsToGraveyard = nullptr;
+ QAction *aMoveTopCardsToGraveyardFaceDown = nullptr;
QAction *aMoveTopCardsToExile = nullptr;
+ QAction *aMoveTopCardsToExileFaceDown = nullptr;
QAction *aMoveTopCardsUntil = nullptr;
QAction *aShuffleTopCards = nullptr;
@@ -100,7 +102,9 @@ public:
QAction *aMoveBottomCardToGraveyard = nullptr;
QAction *aMoveBottomCardToExile = nullptr;
QAction *aMoveBottomCardsToGraveyard = nullptr;
+ QAction *aMoveBottomCardsToGraveyardFaceDown = nullptr;
QAction *aMoveBottomCardsToExile = nullptr;
+ QAction *aMoveBottomCardsToExileFaceDown = nullptr;
QAction *aShuffleBottomCards = nullptr;
int defaultNumberTopCards = 1;
diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp
index 1a09836e4..514ac2e67 100644
--- a/cockatrice/src/game/player/player_actions.cpp
+++ b/cockatrice/src/game/player/player_actions.cpp
@@ -424,15 +424,25 @@ void PlayerActions::actMoveTopCardToExile()
void PlayerActions::actMoveTopCardsToGrave()
{
- moveTopCardsTo("grave", tr("grave"));
+ moveTopCardsTo("grave", tr("grave"), false);
+}
+
+void PlayerActions::actMoveTopCardsToGraveFaceDown()
+{
+ moveTopCardsTo("grave", tr("grave"), true);
}
void PlayerActions::actMoveTopCardsToExile()
{
- moveTopCardsTo("rfg", tr("exile"));
+ moveTopCardsTo("rfg", tr("exile"), false);
}
-void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName)
+void PlayerActions::actMoveTopCardsToExileFaceDown()
+{
+ moveTopCardsTo("rfg", tr("exile"), true);
+}
+
+void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
@@ -460,7 +470,11 @@ void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zon
cmd.set_y(0);
for (int i = number - 1; i >= 0; --i) {
- cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
+ auto card = cmd.mutable_cards_to_move()->add_card();
+ card->set_card_id(i);
+ if (faceDown) {
+ card->set_face_down(true);
+ }
}
sendGameCommand(cmd);
@@ -608,15 +622,25 @@ void PlayerActions::actMoveBottomCardToExile()
void PlayerActions::actMoveBottomCardsToGrave()
{
- moveBottomCardsTo("grave", tr("grave"));
+ moveBottomCardsTo("grave", tr("grave"), false);
+}
+
+void PlayerActions::actMoveBottomCardsToGraveFaceDown()
+{
+ moveBottomCardsTo("grave", tr("grave"), true);
}
void PlayerActions::actMoveBottomCardsToExile()
{
- moveBottomCardsTo("rfg", tr("exile"));
+ moveBottomCardsTo("rfg", tr("exile"), false);
}
-void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName)
+void PlayerActions::actMoveBottomCardsToExileFaceDown()
+{
+ moveBottomCardsTo("rfg", tr("exile"), true);
+}
+
+void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
{
const int maxCards = player->getDeckZone()->getCards().size();
if (maxCards == 0) {
@@ -644,7 +668,11 @@ void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &
cmd.set_y(0);
for (int i = maxCards - number; i < maxCards; ++i) {
- cmd.mutable_cards_to_move()->add_card()->set_card_id(i);
+ auto card = cmd.mutable_cards_to_move()->add_card();
+ card->set_card_id(i);
+ if (faceDown) {
+ card->set_face_down(true);
+ }
}
sendGameCommand(cmd);
diff --git a/cockatrice/src/game/player/player_actions.h b/cockatrice/src/game/player/player_actions.h
index beed2c241..1d695578d 100644
--- a/cockatrice/src/game/player/player_actions.h
+++ b/cockatrice/src/game/player/player_actions.h
@@ -98,7 +98,9 @@ public slots:
void actMoveTopCardToGrave();
void actMoveTopCardToExile();
void actMoveTopCardsToGrave();
+ void actMoveTopCardsToGraveFaceDown();
void actMoveTopCardsToExile();
+ void actMoveTopCardsToExileFaceDown();
void actMoveTopCardsUntil();
void actMoveTopCardToBottom();
void actDrawBottomCard();
@@ -108,7 +110,9 @@ public slots:
void actMoveBottomCardToGrave();
void actMoveBottomCardToExile();
void actMoveBottomCardsToGrave();
+ void actMoveBottomCardsToGraveFaceDown();
void actMoveBottomCardsToExile();
+ void actMoveBottomCardsToExileFaceDown();
void actMoveBottomCardToTop();
void actSelectAll();
@@ -180,8 +184,8 @@ private:
FilterString movingCardsUntilFilter;
int movingCardsUntilCounter = 0;
- void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName);
- void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName);
+ void moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
+ void moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown);
void createCard(const CardItem *sourceCard,
const QString &dbCardName,
From 04f06206b7ae3ff3b4f21d50af3310965adac350 Mon Sep 17 00:00:00 2001
From: DawnFire42
Date: Thu, 5 Mar 2026 14:47:14 -0500
Subject: [PATCH 018/109] Refactor/z value constants (#6651)
* feat(z-values): add centralized Z-value constants infrastructure
Magic numbers scattered across the codebase make Z-value layering
hard to understand and maintain. Centralizing them provides:
- Self-documenting layer hierarchy
- Validation utilities for development
- Single source of truth for Z-value ranges
Two-tier header design:
- z_value_layer_manager.h: Foundation with constants and validation
- z_values.h: User-facing namespace with semantic constants
* refactor(z-values): replace magic Z-value numbers with ZValues constants
Magic numbers like 2000000007 are impossible to understand without
context. Named constants (ZValues::DRAG_ITEM) are self-documenting.
This intentionally renumbers overlay Z-values to use a cleaner offset
sequence. The relative stacking order is preserved, which is what
matters for correct rendering.
Each consumer now includes z_values.h and uses semantic constants
instead of magic numbers.
* refactor(z-values): removed redundant inline with contexpr, Updated doc, magic numbers removed from TableZone.
---
.../game/board/abstract_card_drag_item.cpp | 5 +-
.../src/game/board/abstract_card_item.cpp | 3 +-
cockatrice/src/game/board/arrow_item.cpp | 3 +-
cockatrice/src/game/z_value_layer_manager.h | 136 ++++++++++++++++++
cockatrice/src/game/z_values.h | 83 +++++++++++
cockatrice/src/game/zones/table_zone.cpp | 5 +-
.../src/game/zones/view_zone_widget.cpp | 9 +-
7 files changed, 234 insertions(+), 10 deletions(-)
create mode 100644 cockatrice/src/game/z_value_layer_manager.h
create mode 100644 cockatrice/src/game/z_values.h
diff --git a/cockatrice/src/game/board/abstract_card_drag_item.cpp b/cockatrice/src/game/board/abstract_card_drag_item.cpp
index c961cbcb6..c9a964048 100644
--- a/cockatrice/src/game/board/abstract_card_drag_item.cpp
+++ b/cockatrice/src/game/board/abstract_card_drag_item.cpp
@@ -1,6 +1,7 @@
#include "abstract_card_drag_item.h"
#include "../../client/settings/cache_settings.h"
+#include "../z_values.h"
#include
#include
@@ -18,13 +19,13 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
{
if (parentDrag) {
parentDrag->addChildDrag(this);
- setZValue(2000000007 + hotSpot.x() * 1000000 + hotSpot.y() * 1000 + 1000);
+ setZValue(ZValues::childDragZValue(hotSpot.x(), hotSpot.y()));
connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
} else {
hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast(CARD_WIDTH - 1)),
qBound(0.0, hotSpot.y(), static_cast(CARD_HEIGHT - 1))};
setCursor(Qt::ClosedHandCursor);
- setZValue(2000000007);
+ setZValue(ZValues::DRAG_ITEM);
}
if (item->getTapped())
setTransform(QTransform()
diff --git a/cockatrice/src/game/board/abstract_card_item.cpp b/cockatrice/src/game/board/abstract_card_item.cpp
index d5538011d..f8365c3d6 100644
--- a/cockatrice/src/game/board/abstract_card_item.cpp
+++ b/cockatrice/src/game/board/abstract_card_item.cpp
@@ -3,6 +3,7 @@
#include "../../client/settings/cache_settings.h"
#include "../../interface/card_picture_loader/card_picture_loader.h"
#include "../game_scene.h"
+#include "../z_values.h"
#include
#include
@@ -215,7 +216,7 @@ void AbstractCardItem::setHovered(bool _hovered)
if (_hovered)
processHoverEvent();
isHovered = _hovered;
- setZValue(_hovered ? 2000000004 : realZValue);
+ setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0);
update();
diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp
index 1352b3a05..257d96f8a 100644
--- a/cockatrice/src/game/board/arrow_item.cpp
+++ b/cockatrice/src/game/board/arrow_item.cpp
@@ -5,6 +5,7 @@
#include "../player/player.h"
#include "../player/player_actions.h"
#include "../player/player_target.h"
+#include "../z_values.h"
#include "../zones/card_zone.h"
#include "card_item.h"
@@ -23,7 +24,7 @@ ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTar
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false),
color(_color), fullColor(true)
{
- setZValue(2000000005);
+ setZValue(ZValues::ARROWS);
if (startItem)
startItem->addArrowFrom(this);
diff --git a/cockatrice/src/game/z_value_layer_manager.h b/cockatrice/src/game/z_value_layer_manager.h
new file mode 100644
index 000000000..303edb8a5
--- /dev/null
+++ b/cockatrice/src/game/z_value_layer_manager.h
@@ -0,0 +1,136 @@
+/**
+ * @file z_value_layer_manager.h
+ * @ingroup GameGraphics
+ * @brief Semantic Z-value layer management for game scene rendering.
+ *
+ * This file provides a structured approach to Z-value allocation in the game scene.
+ * Z-values in Qt determine stacking order - higher values render on top of lower values.
+ *
+ * ## Layer Architecture
+ *
+ * The game scene is organized into three conceptual layers:
+ *
+ * 1. **Zone Layer (0-999)**: Zone backgrounds, containers, and static elements
+ * - Zone backgrounds (0.5-1.0)
+ * - Cards within zones (1.0 base + index)
+ *
+ * 2. **Card Layer (1-40,000,000)**: Dynamic card rendering on the table zone
+ * - Cards use formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100
+ * - Maximum card Z-value: ~40,000,000 (with 3 rows, actualY <= ~289)
+ *
+ * 3. **Overlay Layer (2,000,000,000+)**: UI elements that must appear above all cards
+ * - Hovered cards (+1)
+ * - Arrows (+3)
+ * - Zone views (+4)
+ * - Drag items (+5, +6)
+ * - Top UI elements (+7)
+ *
+ * ## Design Rationale
+ *
+ * The large gap between card Z-values (max ~40M) and overlay base (2B) provides
+ * safety margin for future table zone expansions while ensuring overlays always
+ * render above cards regardless of table position.
+ *
+ * ## Usage
+ *
+ * Prefer using the semantic constants from ZValues namespace:
+ * @code
+ * card->setZValue(ZValues::HOVERED_CARD);
+ * arrow->setZValue(ZValues::ARROWS);
+ * @endcode
+ *
+ * Use validation functions to verify card Z-values during development:
+ * @code
+ * Q_ASSERT(ZValueLayerManager::isValidCardZValue(cardZ));
+ * @endcode
+ */
+
+#ifndef Z_VALUE_LAYER_MANAGER_H
+#define Z_VALUE_LAYER_MANAGER_H
+
+#include
+
+/**
+ * @namespace ZValueLayerManager
+ * @brief Utilities for Z-value validation and layer management.
+ */
+namespace ZValueLayerManager
+{
+
+/**
+ * @enum Layer
+ * @brief Semantic layer identifiers for Z-value allocation.
+ *
+ * These represent conceptual rendering layers, not actual Z-values.
+ * Use the corresponding ZValues constants for actual rendering.
+ */
+enum class Layer
+{
+ /// Zone-level elements like backgrounds and containers
+ Zone,
+ /// Cards rendered in zones (uses sequential Z-values)
+ Card,
+ /// Temporary UI elements like hovered cards and drag items
+ Overlay
+};
+
+/**
+ * @brief Maximum Z-value a card can have on the table zone.
+ *
+ * Based on table zone formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100
+ * With maximum 3 rows and CARD_HEIGHT ~96, actualY <= ~289.
+ * Maximum: (289 + 96) * 100000 + 100 * 100 = 38,510,000
+ *
+ * We use 40,000,000 as a safe upper bound with margin.
+ */
+constexpr qreal CARD_Z_VALUE_MAX = 40000000.0;
+
+/**
+ * @brief Base Z-value for overlay elements.
+ *
+ * Must exceed CARD_Z_VALUE_MAX to ensure overlays render above all cards.
+ * The 50x margin (2B vs 40M) provides safety for future expansion.
+ */
+constexpr qreal OVERLAY_BASE = 2000000000.0;
+
+/**
+ * @brief Validates that a Z-value is within the valid card range.
+ *
+ * Cards should have Z-values between CARD_BASE (1.0) and CARD_Z_VALUE_MAX.
+ * Values outside this range may interfere with overlay rendering.
+ *
+ * @param zValue The Z-value to validate
+ * @return true if the Z-value is valid for a card
+ */
+[[nodiscard]] constexpr bool isValidCardZValue(qreal zValue)
+{
+ return zValue >= 1.0 && zValue <= CARD_Z_VALUE_MAX;
+}
+
+/**
+ * @brief Validates that a Z-value is in the overlay layer.
+ *
+ * Overlay elements should have Z-values at or above OVERLAY_BASE.
+ *
+ * @param zValue The Z-value to validate
+ * @return true if the Z-value is valid for an overlay element
+ */
+[[nodiscard]] constexpr bool isOverlayZValue(qreal zValue)
+{
+ return zValue >= OVERLAY_BASE;
+}
+
+/**
+ * @brief Returns the Z-value for a specific overlay element.
+ *
+ * @param offset Offset from OVERLAY_BASE (0-7 for current elements)
+ * @return The absolute Z-value for the overlay element
+ */
+[[nodiscard]] constexpr qreal overlayZValue(qreal offset)
+{
+ return OVERLAY_BASE + offset;
+}
+
+} // namespace ZValueLayerManager
+
+#endif // Z_VALUE_LAYER_MANAGER_H
diff --git a/cockatrice/src/game/z_values.h b/cockatrice/src/game/z_values.h
new file mode 100644
index 000000000..2c7fe9066
--- /dev/null
+++ b/cockatrice/src/game/z_values.h
@@ -0,0 +1,83 @@
+#ifndef Z_VALUES_H
+#define Z_VALUES_H
+
+#include "z_value_layer_manager.h"
+
+/**
+ * @file z_values.h
+ * @ingroup GameGraphics
+ * @brief Centralized Z-value constants for rendering layer order.
+ *
+ * Z-values in Qt determine stacking order. Higher values render on top.
+ * These constants define the visual layering hierarchy for the game scene.
+ *
+ * ## Layer Architecture
+ *
+ * See z_value_layer_manager.h for detailed documentation on the three-layer
+ * architecture (Zone, Card, Overlay) and the rationale for Z-value choices.
+ *
+ * ## Quick Reference
+ *
+ * | Layer | Z-Value Range | Purpose |
+ * |----------|------------------|-----------------------------------|
+ * | Zone | 0.5 - 1.0 | Zone backgrounds, containers |
+ * | Card | 1.0 - 40,000,000 | Cards on table (position-based) |
+ * | Overlay | 2,000,000,000+ | UI elements above all cards |
+ */
+
+namespace ZValues
+{
+
+// Expose base for callers that need it
+constexpr qreal OVERLAY_BASE = ZValueLayerManager::OVERLAY_BASE;
+
+// Overlay layer Z-values for items that should appear above normal cards
+constexpr qreal HOVERED_CARD = ZValueLayerManager::overlayZValue(1.0);
+constexpr qreal ARROWS = ZValueLayerManager::overlayZValue(3.0);
+constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0);
+constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0);
+constexpr qreal DRAG_ITEM_CHILD = ZValueLayerManager::overlayZValue(6.0);
+constexpr qreal TOP_UI = ZValueLayerManager::overlayZValue(7.0);
+
+/**
+ * @brief Compute Z-value for child drag items based on hotspot position.
+ *
+ * When dragging multiple cards together, each child card needs a unique Z-value
+ * to prevent Z-fighting (flickering/flashing). The Z-values are derived from
+ * their position when grabbed to conserve original stacking. The formula encodes
+ * 2D coordinates into a single value where X has higher weight, ensuring
+ * deterministic visual stacking.
+ *
+ * @param hotSpotX The X coordinate of the grab position
+ * @param hotSpotY The Y coordinate of the grab position
+ * @return Unique Z-value for the child drag item
+ */
+[[nodiscard]] constexpr qreal childDragZValue(qreal hotSpotX, qreal hotSpotY)
+{
+ return DRAG_ITEM_CHILD + hotSpotX * 1000000 + hotSpotY * 1000 + 1000;
+}
+
+/**
+ * @brief Compute Z-value for cards on the table zone based on position.
+ *
+ * Cards lower on the table (higher Y) render above cards higher up,
+ * and cards to the right (higher X) render above cards to the left.
+ * This creates natural visual stacking for overlapping cards.
+ *
+ * @param x The X coordinate of the card position
+ * @param y The Y coordinate of the card position
+ * @return Z-value for the card's table position
+ */
+[[nodiscard]] constexpr qreal tableCardZValue(qreal x, qreal y)
+{
+ constexpr qreal CARD_HEIGHT_FOR_Z = 102.0;
+ return (y + CARD_HEIGHT_FOR_Z) * 100000.0 + (x + 1) * 100.0;
+}
+
+// Card layering (general architecture, not command-zone specific)
+constexpr qreal CARD_BASE = 1.0;
+constexpr qreal CARD_MAX = ZValueLayerManager::CARD_Z_VALUE_MAX;
+
+} // namespace ZValues
+
+#endif // Z_VALUES_H
diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp
index ca1052d20..33c867c43 100644
--- a/cockatrice/src/game/zones/table_zone.cpp
+++ b/cockatrice/src/game/zones/table_zone.cpp
@@ -7,6 +7,7 @@
#include "../board/card_item.h"
#include "../player/player.h"
#include "../player/player_actions.h"
+#include "../z_values.h"
#include "logic/table_zone_logic.h"
#include
@@ -169,7 +170,7 @@ void TableZone::reorganizeCards()
actualY += 15;
getLogic()->getCards()[i]->setPos(actualX, actualY);
- getLogic()->getCards()[i]->setRealZValue((actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100);
+ getLogic()->getCards()[i]->setRealZValue(ZValues::tableCardZValue(actualX, actualY));
QListIterator attachedCardIterator(getLogic()->getCards()[i]->getAttachedCards());
int j = 0;
@@ -179,7 +180,7 @@ void TableZone::reorganizeCards()
qreal childX = actualX - j * STACKED_CARD_OFFSET_X;
qreal childY = y + 5;
attachedCard->setPos(childX, childY);
- attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100);
+ attachedCard->setRealZValue(ZValues::tableCardZValue(childX, childY));
}
}
diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp
index 3ea4eb119..0ad3b10f1 100644
--- a/cockatrice/src/game/zones/view_zone_widget.cpp
+++ b/cockatrice/src/game/zones/view_zone_widget.cpp
@@ -7,6 +7,7 @@
#include "../game_scene.h"
#include "../player/player.h"
#include "../player/player_actions.h"
+#include "../z_values.h"
#include "view_zone.h"
#include
@@ -47,7 +48,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
{
setAcceptHoverEvents(true);
setAttribute(Qt::WA_DeleteOnClose);
- setZValue(2000000006);
+ setZValue(ZValues::ZONE_VIEW_WIDGET);
setFlag(ItemIgnoresTransformations);
QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical);
@@ -71,7 +72,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget;
searchEditProxy->setWidget(&searchEdit);
- searchEditProxy->setZValue(2000000007);
+ searchEditProxy->setZValue(ZValues::DRAG_ITEM);
vbox->addItem(searchEditProxy);
// top row
@@ -80,13 +81,13 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
// groupBy options
QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget;
groupBySelectorProxy->setWidget(&groupBySelector);
- groupBySelectorProxy->setZValue(2000000008);
+ groupBySelectorProxy->setZValue(ZValues::TOP_UI);
hTopRow->addItem(groupBySelectorProxy);
// sortBy options
QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget;
sortBySelectorProxy->setWidget(&sortBySelector);
- sortBySelectorProxy->setZValue(2000000007);
+ sortBySelectorProxy->setZValue(ZValues::DRAG_ITEM);
hTopRow->addItem(sortBySelectorProxy);
vbox->addItem(hTopRow);
From e39bbd2b316753f254be8ae8b60c5c8808160b6b Mon Sep 17 00:00:00 2001
From: ebbit1q
Date: Fri, 6 Mar 2026 00:19:10 +0100
Subject: [PATCH 019/109] take default theme name from startup instead of using
empty (#6663)
---
cockatrice/src/interface/theme_manager.cpp | 4 +++-
cockatrice/src/interface/theme_manager.h | 1 +
cockatrice/src/interface/widgets/general/home_widget.cpp | 2 ++
3 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp
index abacb2d68..1dc61fdb7 100644
--- a/cockatrice/src/interface/theme_manager.cpp
+++ b/cockatrice/src/interface/theme_manager.cpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -89,6 +90,7 @@ struct PaletteColorInfo
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
{
+ defaultStyleName = qApp->style()->objectName();
ensureThemeDirectoryExists();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0))
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &ThemeManager::themeChangedSlot);
@@ -346,7 +348,7 @@ void ThemeManager::themeChangedSlot()
qApp->setStyle(QStyleFactory::create("Fusion"));
qApp->setPalette(createDarkGreenFusionPalette());
} else {
- qApp->setStyle("");
+ qApp->setStyle(defaultStyleName); // setting the style also sets the palette
}
if (dirPath.isEmpty()) {
diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h
index 594ede2bf..d942f0fef 100644
--- a/cockatrice/src/interface/theme_manager.h
+++ b/cockatrice/src/interface/theme_manager.h
@@ -40,6 +40,7 @@ public:
};
private:
+ QString defaultStyleName;
std::array brushes;
QStringMap availableThemes;
/*
diff --git a/cockatrice/src/interface/widgets/general/home_widget.cpp b/cockatrice/src/interface/widgets/general/home_widget.cpp
index 750ddd000..ea20ef6a0 100644
--- a/cockatrice/src/interface/widgets/general/home_widget.cpp
+++ b/cockatrice/src/interface/widgets/general/home_widget.cpp
@@ -46,6 +46,8 @@ HomeWidget::HomeWidget(QWidget *parent, TabSupervisor *_tabSupervisor)
&HomeWidget::onBackgroundShuffleFrequencyChanged);
// Lambda is cleaner to read than overloading this
connect(&SettingsCache::instance(), &SettingsCache::homeTabDisplayCardNameChanged, this, [this] { repaint(); });
+ connect(&SettingsCache::instance(), &SettingsCache::themeChanged, this,
+ &HomeWidget::updateButtonsToBackgroundColor);
}
void HomeWidget::initializeBackgroundFromSource()
From 14f1925edc8a47045e771e71746e07290fef3679 Mon Sep 17 00:00:00 2001
From: tooomm
Date: Fri, 6 Mar 2026 01:50:15 +0100
Subject: [PATCH 020/109] Add icon to exe (#6655)
---
cmake/NSIS.template.in | 1 +
1 file changed, 1 insertion(+)
diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in
index a0d51fa2b..2fdc61fb9 100644
--- a/cmake/NSIS.template.in
+++ b/cmake/NSIS.template.in
@@ -26,6 +26,7 @@ Var PortableMode
!define MUI_WELCOMEPAGE_TEXT "This wizard will guide you through the installation of Cockatrice.$\r$\n$\r$\nClick Next to continue."
!define MUI_FINISHPAGE_RUN "$INSTDIR/cockatrice.exe"
!define MUI_FINISHPAGE_RUN_TEXT "Run 'Cockatrice' now"
+!define MUI_ICON "${NSIS_SOURCE_PATH}\cockatrice\resources\appicon.ico"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "${NSIS_SOURCE_PATH}\LICENSE"
From bd5cbb89d47c2cdfac9eb59e550e737d51321775 Mon Sep 17 00:00:00 2001
From: DawnFire42
Date: Thu, 5 Mar 2026 22:13:58 -0500
Subject: [PATCH 021/109] refactor: extract CARD_HEIGHT to shared
CardDimensions header (#6668)
* refactor: extract CARD_HEIGHT to shared CardDimensions header
Move duplicated CARD_WIDTH/CARD_HEIGHT constants to card_dimensions.h.
Fixed documentation in z_value_layer_manager.h.
* WIDTH_F used directly instead of casting
* Improved consistency and added missing newlines at end of files
---
.../game/board/abstract_card_drag_item.cpp | 12 ++++----
.../src/game/board/abstract_card_drag_item.h | 2 +-
.../src/game/board/abstract_card_item.cpp | 10 +++----
.../src/game/board/abstract_card_item.h | 4 +--
cockatrice/src/game/board/card_item.cpp | 9 +++---
cockatrice/src/game/board/card_item.h | 2 --
cockatrice/src/game/card_dimensions.h | 29 +++++++++++++++++++
cockatrice/src/game/deckview/deck_view.cpp | 22 +++++++-------
.../src/game/player/player_graphics_item.cpp | 22 +++++++-------
cockatrice/src/game/z_value_layer_manager.h | 8 ++---
cockatrice/src/game/z_values.h | 4 +--
cockatrice/src/game/zones/hand_zone.cpp | 2 +-
cockatrice/src/game/zones/pile_zone.cpp | 12 ++++----
cockatrice/src/game/zones/table_zone.cpp | 22 +++++++-------
cockatrice/src/game/zones/table_zone.h | 4 +--
cockatrice/src/game/zones/view_zone.cpp | 16 +++++-----
.../src/game/zones/view_zone_widget.cpp | 4 +--
.../cards/card_info_display_widget.cpp | 2 +-
18 files changed, 108 insertions(+), 78 deletions(-)
create mode 100644 cockatrice/src/game/card_dimensions.h
diff --git a/cockatrice/src/game/board/abstract_card_drag_item.cpp b/cockatrice/src/game/board/abstract_card_drag_item.cpp
index c9a964048..8e3def4ca 100644
--- a/cockatrice/src/game/board/abstract_card_drag_item.cpp
+++ b/cockatrice/src/game/board/abstract_card_drag_item.cpp
@@ -8,8 +8,6 @@
#include
#include
-static const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
-static const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
const QColor GHOST_MASK = QColor(255, 255, 255, 50);
AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
@@ -22,16 +20,16 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
setZValue(ZValues::childDragZValue(hotSpot.x(), hotSpot.y()));
connect(parentDrag, &QObject::destroyed, this, &AbstractCardDragItem::deleteLater);
} else {
- hotSpot = QPointF{qBound(0.0, hotSpot.x(), static_cast(CARD_WIDTH - 1)),
- qBound(0.0, hotSpot.y(), static_cast(CARD_HEIGHT - 1))};
+ hotSpot = QPointF{qBound(0.0, hotSpot.x(), CardDimensions::WIDTH_F - 1),
+ qBound(0.0, hotSpot.y(), CardDimensions::HEIGHT_F - 1)};
setCursor(Qt::ClosedHandCursor);
setZValue(ZValues::DRAG_ITEM);
}
if (item->getTapped())
setTransform(QTransform()
- .translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
+ .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(90)
- .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
+ .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
setCacheMode(DeviceCoordinateCache);
@@ -48,7 +46,7 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item,
QPainterPath AbstractCardDragItem::shape() const
{
QPainterPath shape;
- qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
+ qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
diff --git a/cockatrice/src/game/board/abstract_card_drag_item.h b/cockatrice/src/game/board/abstract_card_drag_item.h
index 1d741eded..fe3b87983 100644
--- a/cockatrice/src/game/board/abstract_card_drag_item.h
+++ b/cockatrice/src/game/board/abstract_card_drag_item.h
@@ -34,7 +34,7 @@ public:
AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0);
[[nodiscard]] QRectF boundingRect() const override
{
- return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
+ return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
}
[[nodiscard]] QPainterPath shape() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
diff --git a/cockatrice/src/game/board/abstract_card_item.cpp b/cockatrice/src/game/board/abstract_card_item.cpp
index f8365c3d6..9ec6ada9a 100644
--- a/cockatrice/src/game/board/abstract_card_item.cpp
+++ b/cockatrice/src/game/board/abstract_card_item.cpp
@@ -39,13 +39,13 @@ AbstractCardItem::~AbstractCardItem()
QRectF AbstractCardItem::boundingRect() const
{
- return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
+ return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
}
QPainterPath AbstractCardItem::shape() const
{
QPainterPath shape;
- qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
+ qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
@@ -218,7 +218,7 @@ void AbstractCardItem::setHovered(bool _hovered)
isHovered = _hovered;
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
- setTransformOriginPoint(_hovered ? CARD_WIDTH / 2 : 0, _hovered ? CARD_HEIGHT / 2 : 0);
+ setTransformOriginPoint(_hovered ? CardDimensions::WIDTH_HALF_F : 0, _hovered ? CardDimensions::HEIGHT_HALF_F : 0);
update();
}
@@ -274,9 +274,9 @@ void AbstractCardItem::setTapped(bool _tapped, bool canAnimate)
else {
tapAngle = tapped ? 90 : 0;
setTransform(QTransform()
- .translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
+ .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(tapAngle)
- .translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
+ .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
update();
}
}
diff --git a/cockatrice/src/game/board/abstract_card_item.h b/cockatrice/src/game/board/abstract_card_item.h
index dad73dc14..7d2c29cae 100644
--- a/cockatrice/src/game/board/abstract_card_item.h
+++ b/cockatrice/src/game/board/abstract_card_item.h
@@ -8,6 +8,7 @@
#define ABSTRACTCARDITEM_H
#include "../../game_graphics/board/graphics_item_type.h"
+#include "../card_dimensions.h"
#include "arrow_target.h"
#include
@@ -15,9 +16,6 @@
class Player;
-const int CARD_WIDTH = 72;
-const int CARD_HEIGHT = 102;
-
class AbstractCardItem : public ArrowTarget
{
Q_OBJECT
diff --git a/cockatrice/src/game/board/card_item.cpp b/cockatrice/src/game/board/card_item.cpp
index 8b9549f07..62de4a02e 100644
--- a/cockatrice/src/game/board/card_item.cpp
+++ b/cockatrice/src/game/board/card_item.cpp
@@ -367,8 +367,9 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
if (zone->getHasCardAttr())
childPos = card->pos() - pos();
else
- childPos = QPointF(childIndex * CARD_WIDTH / 2, 0);
- CardDragItem *drag = new CardDragItem(card, card->getId(), childPos, forceFaceDown, dragItem);
+ childPos = QPointF(childIndex * CardDimensions::WIDTH_HALF_F, 0);
+ CardDragItem *drag =
+ new CardDragItem(card, card->getId(), childPos, card->getFaceDown() || forceFaceDown, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
}
@@ -475,9 +476,9 @@ bool CardItem::animationEvent()
}
setTransform(QTransform()
- .translate(CARD_WIDTH_HALF, CARD_HEIGHT_HALF)
+ .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(tapAngle)
- .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF));
+ .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
setHovered(false);
update();
diff --git a/cockatrice/src/game/board/card_item.h b/cockatrice/src/game/board/card_item.h
index 875c3f4d0..da2097a2c 100644
--- a/cockatrice/src/game/board/card_item.h
+++ b/cockatrice/src/game/board/card_item.h
@@ -21,8 +21,6 @@ class QAction;
class QColor;
const int MAX_COUNTERS_ON_CARD = 999;
-const float CARD_WIDTH_HALF = CARD_WIDTH / 2;
-const float CARD_HEIGHT_HALF = CARD_HEIGHT / 2;
const int ROTATION_DEGREES_PER_FRAME = 10;
class CardItem : public AbstractCardItem
diff --git a/cockatrice/src/game/card_dimensions.h b/cockatrice/src/game/card_dimensions.h
new file mode 100644
index 000000000..255d0bc04
--- /dev/null
+++ b/cockatrice/src/game/card_dimensions.h
@@ -0,0 +1,29 @@
+#ifndef CARD_DIMENSIONS_H
+#define CARD_DIMENSIONS_H
+
+#include
+
+/**
+ * @file card_dimensions.h
+ * @brief Canonical card dimension constants for layout and Z-value calculations.
+ *
+ * These values represent the logical pixel dimensions of a standard card graphic.
+ * They are used throughout the game scene for layout, rendering, and Z-value computation.
+ */
+namespace CardDimensions
+{
+/// Card width in pixels
+constexpr int WIDTH = 72;
+/// Card height in pixels
+constexpr int HEIGHT = 102;
+
+/// Pre-converted for floating-point contexts (Z-value calculations)
+constexpr qreal WIDTH_F = static_cast(WIDTH);
+constexpr qreal HEIGHT_F = static_cast(HEIGHT);
+
+/// Half-dimensions for centering and rotation transforms
+constexpr qreal WIDTH_HALF_F = WIDTH_F / 2;
+constexpr qreal HEIGHT_HALF_F = HEIGHT_F / 2;
+} // namespace CardDimensions
+
+#endif // CARD_DIMENSIONS_H
diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp
index 383545cf3..620dfaa5f 100644
--- a/cockatrice/src/game/deckview/deck_view.cpp
+++ b/cockatrice/src/game/deckview/deck_view.cpp
@@ -95,8 +95,9 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
pen.setJoinStyle(Qt::MiterJoin);
pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
painter->setPen(pen);
- qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CARD_WIDTH - 3) : 0.0;
- painter->drawRoundedRect(QRectF(1.5, 1.5, CARD_WIDTH - 3., CARD_HEIGHT - 3.), cardRadius, cardRadius);
+ qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CardDimensions::WIDTH_F - 3) : 0.0;
+ painter->drawRoundedRect(QRectF(1.5, 1.5, CardDimensions::WIDTH_F - 3, CardDimensions::HEIGHT_F - 3), cardRadius,
+ cardRadius);
painter->restore();
}
@@ -122,7 +123,7 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
if (c == this)
continue;
++j;
- auto childPos = QPointF(j * CARD_WIDTH / 2, 0);
+ auto childPos = QPointF(j * CardDimensions::WIDTH_HALF_F, 0);
auto *drag = new DeckViewCardDragItem(c, childPos, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
@@ -204,7 +205,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI
painter->setPen(QColor(255, 255, 255, 100));
painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2));
}
- qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first;
+ qreal thisRowHeight = CardDimensions::HEIGHT_F * currentRowsAndCols[i].first;
QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight);
yUntilNow += thisRowHeight + paddingY;
@@ -260,9 +261,9 @@ QSizeF DeckViewCardContainer::calculateBoundingRect(const QList>
// Calculate space needed for cards
for (int i = 0; i < rowsAndCols.size(); ++i) {
- totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY;
- if (CARD_WIDTH * rowsAndCols[i].second > totalWidth)
- totalWidth = CARD_WIDTH * rowsAndCols[i].second;
+ totalHeight += CardDimensions::HEIGHT_F * rowsAndCols[i].first + paddingY;
+ if (CardDimensions::WIDTH_F * rowsAndCols[i].second > totalWidth)
+ totalWidth = CardDimensions::WIDTH_F * rowsAndCols[i].second;
}
return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
@@ -289,9 +290,10 @@ void DeckViewCardContainer::rearrangeItems(const QList> &rowsAnd
std::sort(row.begin(), row.end(), DeckViewCardContainer::sortCardsByName);
for (int j = 0; j < row.size(); ++j) {
DeckViewCard *card = row[j];
- card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT);
+ card->setPos(x + (j % tempCols) * CardDimensions::WIDTH_F,
+ yUntilNow + (j / tempCols) * CardDimensions::HEIGHT_F);
}
- yUntilNow += tempRows * CARD_HEIGHT + paddingY;
+ yUntilNow += tempRows * CardDimensions::HEIGHT_F + paddingY;
}
prepareGeometryChange();
@@ -392,7 +394,7 @@ void DeckViewScene::applySideboardPlan(const QList &plan)
void DeckViewScene::rearrangeItems()
{
- const int spacing = CARD_HEIGHT / 3;
+ const int spacing = CardDimensions::HEIGHT / 3;
QList contList = cardContainers.values();
// Initialize space requirements
diff --git a/cockatrice/src/game/player/player_graphics_item.cpp b/cockatrice/src/game/player/player_graphics_item.cpp
index 30648c926..bcc4b7f72 100644
--- a/cockatrice/src/game/player/player_graphics_item.cpp
+++ b/cockatrice/src/game/player/player_graphics_item.cpp
@@ -19,7 +19,8 @@ PlayerGraphicsItem::PlayerGraphicsItem(Player *_player) : player(_player)
playerArea = new PlayerArea(this);
playerTarget = new PlayerTarget(player, playerArea);
- qreal avatarMargin = (counterAreaWidth + CARD_HEIGHT + 15 - playerTarget->boundingRect().width()) / 2.0;
+ qreal avatarMargin =
+ (counterAreaWidth + CardDimensions::HEIGHT_F + 15 - playerTarget->boundingRect().width()) / 2.0;
playerTarget->setPos(QPointF(avatarMargin, avatarMargin));
initializeZones();
@@ -55,8 +56,9 @@ void PlayerGraphicsItem::onPlayerActiveChanged(bool _active)
void PlayerGraphicsItem::initializeZones()
{
deckZoneGraphicsItem = new PileZone(player->getDeckZone(), this);
- auto base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0,
- 10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0);
+ auto base = QPointF(counterAreaWidth + (CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F + 15) / 2.0,
+ 10 + playerTarget->boundingRect().height() + 5 -
+ (CardDimensions::HEIGHT_F - CardDimensions::WIDTH_F) / 2.0);
deckZoneGraphicsItem->setPos(base);
qreal h = deckZoneGraphicsItem->boundingRect().width() + 5;
@@ -95,7 +97,7 @@ QRectF PlayerGraphicsItem::boundingRect() const
qreal PlayerGraphicsItem::getMinimumWidth() const
{
- qreal result = tableZoneGraphicsItem->getMinimumWidth() + CARD_HEIGHT + 15 + counterAreaWidth +
+ qreal result = tableZoneGraphicsItem->getMinimumWidth() + CardDimensions::HEIGHT_F + 15 + counterAreaWidth +
stackZoneGraphicsItem->boundingRect().width();
if (!SettingsCache::instance().getHorizontalHand()) {
result += handZoneGraphicsItem->boundingRect().width();
@@ -112,8 +114,8 @@ void PlayerGraphicsItem::paint(QPainter * /*painter*/,
void PlayerGraphicsItem::processSceneSizeChange(int newPlayerWidth)
{
// Extend table (and hand, if horizontal) to accommodate the new player width.
- qreal tableWidth =
- newPlayerWidth - CARD_HEIGHT - 15 - counterAreaWidth - stackZoneGraphicsItem->boundingRect().width();
+ qreal tableWidth = newPlayerWidth - CardDimensions::HEIGHT_F - 15 - counterAreaWidth -
+ stackZoneGraphicsItem->boundingRect().width();
if (!SettingsCache::instance().getHorizontalHand()) {
tableWidth -= handZoneGraphicsItem->boundingRect().width();
}
@@ -152,7 +154,7 @@ void PlayerGraphicsItem::rearrangeCounters()
void PlayerGraphicsItem::rearrangeZones()
{
- auto base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0);
+ auto base = QPointF(CardDimensions::HEIGHT_F + counterAreaWidth + 15, 0);
if (SettingsCache::instance().getHorizontalHand()) {
if (mirrored) {
if (player->getHandZone()->contentsKnown()) {
@@ -203,7 +205,7 @@ void PlayerGraphicsItem::rearrangeZones()
void PlayerGraphicsItem::updateBoundingRect()
{
prepareGeometryChange();
- qreal width = CARD_HEIGHT + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width();
+ qreal width = CardDimensions::HEIGHT_F + 15 + counterAreaWidth + stackZoneGraphicsItem->boundingRect().width();
if (SettingsCache::instance().getHorizontalHand()) {
qreal handHeight =
player->getPlayerInfo()->getHandVisible() ? handZoneGraphicsItem->boundingRect().height() : 0;
@@ -214,7 +216,7 @@ void PlayerGraphicsItem::updateBoundingRect()
0, 0, width + handZoneGraphicsItem->boundingRect().width() + tableZoneGraphicsItem->boundingRect().width(),
tableZoneGraphicsItem->boundingRect().height());
}
- playerArea->setSize(CARD_HEIGHT + counterAreaWidth + 15, bRect.height());
+ playerArea->setSize(CardDimensions::HEIGHT_F + counterAreaWidth + 15, bRect.height());
emit sizeChanged();
-}
\ No newline at end of file
+}
diff --git a/cockatrice/src/game/z_value_layer_manager.h b/cockatrice/src/game/z_value_layer_manager.h
index 303edb8a5..4eb864486 100644
--- a/cockatrice/src/game/z_value_layer_manager.h
+++ b/cockatrice/src/game/z_value_layer_manager.h
@@ -15,7 +15,7 @@
* - Cards within zones (1.0 base + index)
*
* 2. **Card Layer (1-40,000,000)**: Dynamic card rendering on the table zone
- * - Cards use formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100
+ * - Cards use formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100
* - Maximum card Z-value: ~40,000,000 (with 3 rows, actualY <= ~289)
*
* 3. **Overlay Layer (2,000,000,000+)**: UI elements that must appear above all cards
@@ -77,9 +77,9 @@ enum class Layer
/**
* @brief Maximum Z-value a card can have on the table zone.
*
- * Based on table zone formula: (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100
- * With maximum 3 rows and CARD_HEIGHT ~96, actualY <= ~289.
- * Maximum: (289 + 96) * 100000 + 100 * 100 = 38,510,000
+ * Based on table zone formula: (actualY + CardDimensions::HEIGHT) * 100000 + (actualX + 1) * 100
+ * With maximum 3 rows and CardDimensions::HEIGHT = 102, actualY <= ~289.
+ * Maximum: (289 + 102) * 100000 + 100 * 100 = 39,110,000
*
* We use 40,000,000 as a safe upper bound with margin.
*/
diff --git a/cockatrice/src/game/z_values.h b/cockatrice/src/game/z_values.h
index 2c7fe9066..c6e7f2c8a 100644
--- a/cockatrice/src/game/z_values.h
+++ b/cockatrice/src/game/z_values.h
@@ -1,6 +1,7 @@
#ifndef Z_VALUES_H
#define Z_VALUES_H
+#include "card_dimensions.h"
#include "z_value_layer_manager.h"
/**
@@ -70,8 +71,7 @@ constexpr qreal TOP_UI = ZValueLayerManager::overlayZValue(7.0);
*/
[[nodiscard]] constexpr qreal tableCardZValue(qreal x, qreal y)
{
- constexpr qreal CARD_HEIGHT_FOR_Z = 102.0;
- return (y + CARD_HEIGHT_FOR_Z) * 100000.0 + (x + 1) * 100.0;
+ return (y + CardDimensions::HEIGHT_F) * 100000.0 + (x + 1) * 100.0;
}
// Card layering (general architecture, not command-zone specific)
diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp
index cc3b44910..7badfcca4 100644
--- a/cockatrice/src/game/zones/hand_zone.cpp
+++ b/cockatrice/src/game/zones/hand_zone.cpp
@@ -56,7 +56,7 @@ void HandZone::handleDropEvent(const QList &dragItems,
QRectF HandZone::boundingRect() const
{
if (SettingsCache::instance().getHorizontalHand())
- return QRectF(0, 0, width, CARD_HEIGHT + 10);
+ return QRectF(0, 0, width, CardDimensions::HEIGHT_F + 10);
else
return QRectF(0, 0, 100, zoneHeight);
}
diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp
index 87a60fc03..d85b5f4e2 100644
--- a/cockatrice/src/game/zones/pile_zone.cpp
+++ b/cockatrice/src/game/zones/pile_zone.cpp
@@ -19,9 +19,9 @@ PileZone::PileZone(PileZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_log
setCursor(Qt::OpenHandCursor);
setTransform(QTransform()
- .translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2)
+ .translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
.rotate(90)
- .translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2));
+ .translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
Q_UNUSED(_roundCardCorners);
@@ -33,13 +33,13 @@ PileZone::PileZone(PileZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_log
QRectF PileZone::boundingRect() const
{
- return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT);
+ return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
}
QPainterPath PileZone::shape() const
{
QPainterPath shape;
- qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0;
+ qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
return shape;
}
@@ -52,9 +52,9 @@ void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*optio
getLogic()->getCards().at(0)->paintPicture(painter, getLogic()->getCards().at(0)->getTranslatedSize(painter),
90);
- painter->translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2);
+ painter->translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F);
painter->rotate(-90);
- painter->translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2);
+ painter->translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F);
paintNumberEllipse(getLogic()->getCards().size(), 28, Qt::white, -1, -1, painter);
}
diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp
index 33c867c43..a020e4255 100644
--- a/cockatrice/src/game/zones/table_zone.cpp
+++ b/cockatrice/src/game/zones/table_zone.cpp
@@ -31,7 +31,7 @@ TableZone::TableZone(TableZoneLogic *_logic, QGraphicsItem *parent) : SelectZone
updateBg();
- height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CARD_HEIGHT + (TABLEROWS - 1) * PADDING_Y;
+ height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CardDimensions::HEIGHT + (TABLEROWS - 1) * PADDING_Y;
width = MIN_WIDTH;
currentMinimumWidth = width;
@@ -106,7 +106,7 @@ void TableZone::paintLandDivider(QPainter *painter)
{
// Place the line 2 grid heights down then back it off just enough to allow
// some space between a 3-card stack and the land area.
- qreal separatorY = MARGIN_TOP + 2 * (CARD_HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
+ qreal separatorY = MARGIN_TOP + 2 * (CardDimensions::HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
if (isInverted())
separatorY = height - separatorY;
painter->setPen(QColor(255, 255, 255, 40));
@@ -232,7 +232,7 @@ void TableZone::resizeToContents()
// Minimum width is the rightmost card position plus enough room for
// another card with padding, then margin.
- currentMinimumWidth = xMax + (2 * CARD_WIDTH) + PADDING_X + MARGIN_RIGHT;
+ currentMinimumWidth = xMax + (2 * CardDimensions::WIDTH) + PADDING_X + MARGIN_RIGHT;
if (currentMinimumWidth < MIN_WIDTH)
currentMinimumWidth = MIN_WIDTH;
@@ -282,10 +282,10 @@ void TableZone::computeCardStackWidths()
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
const int stackCount = cardStackCount.value(key, 0);
if (stackCount == 1)
- cardStackWidth.insert(key, CARD_WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() *
- STACKED_CARD_OFFSET_X);
+ cardStackWidth.insert(key, CardDimensions::WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() *
+ STACKED_CARD_OFFSET_X);
else
- cardStackWidth.insert(key, CARD_WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X);
+ cardStackWidth.insert(key, CardDimensions::WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X);
}
}
@@ -299,7 +299,7 @@ QPointF TableZone::mapFromGrid(QPoint gridPoint) const
// Add in width of card stack plus padding for each column
for (int i = 0; i < gridPoint.x() / 3; ++i) {
const int key = getCardStackMapKey(i, gridPoint.y());
- x += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X;
+ x += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
}
if (isInverted())
@@ -310,7 +310,7 @@ QPointF TableZone::mapFromGrid(QPoint gridPoint) const
// Add in card size and padding for each row
for (int i = 0; i < gridPoint.y(); ++i)
- y += CARD_HEIGHT + PADDING_Y;
+ y += CardDimensions::HEIGHT + PADDING_Y;
return QPointF(x, y);
}
@@ -324,7 +324,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
int y = mapPoint.y() - MARGIN_TOP;
// Below calculation effectively rounds to the nearest grid point.
- const int gridPointHeight = CARD_HEIGHT + PADDING_Y;
+ const int gridPointHeight = CardDimensions::HEIGHT + PADDING_Y;
int gridPointY = (y + PADDING_Y / 2) / gridPointHeight;
gridPointY = clampValidTableRow(gridPointY);
@@ -340,7 +340,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
// Maximum value is a card width from the right margin, referenced to the
// grid area.
- const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CARD_WIDTH;
+ const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CardDimensions::WIDTH;
int xStack = 0;
int xNextStack = 0;
@@ -348,7 +348,7 @@ QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
while ((xNextStack <= x) && (xNextStack <= xMax)) {
xStack = xNextStack;
const int key = getCardStackMapKey(nextStackCol, gridPointY);
- xNextStack += cardStackWidth.value(key, CARD_WIDTH) + PADDING_X;
+ xNextStack += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
nextStackCol++;
}
int stackCol = qMax(nextStackCol - 1, 0);
diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h
index 3b353e76c..61eb48d7b 100644
--- a/cockatrice/src/game/zones/table_zone.h
+++ b/cockatrice/src/game/zones/table_zone.h
@@ -41,12 +41,12 @@ private:
/*
Minimum width of the table zone including margins.
*/
- static const int MIN_WIDTH = MARGIN_LEFT + (5 * CARD_WIDTH) + MARGIN_RIGHT;
+ static const int MIN_WIDTH = MARGIN_LEFT + (5 * CardDimensions::WIDTH) + MARGIN_RIGHT;
/*
Offset sizes when cards are stacked on each other in the grid
*/
- static const int STACKED_CARD_OFFSET_X = CARD_WIDTH / 3;
+ static const int STACKED_CARD_OFFSET_X = CardDimensions::WIDTH / 3;
static const int STACKED_CARD_OFFSET_Y = PADDING_Y / 3;
/*
diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp
index 348ed6e87..d2fd1e971 100644
--- a/cockatrice/src/game/zones/view_zone.cpp
+++ b/cockatrice/src/game/zones/view_zone.cpp
@@ -118,7 +118,9 @@ void ZoneViewZone::zoneDumpReceived(const Response &r)
qobject_cast(getLogic())->updateCardIds(ZoneViewZoneLogic::INITIALIZE);
reorganizeCards();
- emit getLogic()->cardCountChanged();
+ // clang-format off
+ emit getLogic()->cardCountChanged(); // emit keyword causes spurious spacing around ->
+ // clang-format on
}
// Because of boundingRect(), this function must not be called before the zone was added to a scene.
@@ -166,8 +168,8 @@ void ZoneViewZone::reorganizeCards()
// determine bounding rect
qreal aleft = 0;
qreal atop = 0;
- qreal awidth = gridSize.cols * CARD_WIDTH + (CARD_WIDTH / 2) + HORIZONTAL_PADDING;
- qreal aheight = (gridSize.rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3;
+ qreal awidth = gridSize.cols * CardDimensions::WIDTH_F + CardDimensions::WIDTH_HALF_F + HORIZONTAL_PADDING;
+ qreal aheight = (gridSize.rows * CardDimensions::HEIGHT_F) / 3 + CardDimensions::HEIGHT_F * 1.3;
optimumRect = QRectF(aleft, atop, awidth, aheight);
updateGeometry();
@@ -210,8 +212,8 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca
}
lastColumnProp = columnProp;
- qreal x = col * CARD_WIDTH;
- qreal y = row * CARD_HEIGHT / 3;
+ qreal x = col * CardDimensions::WIDTH_F;
+ qreal y = row * CardDimensions::HEIGHT_F / 3;
c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y);
c->setRealZValue(i);
longestRow = qMax(row, longestRow);
@@ -238,8 +240,8 @@ ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, Ca
for (int i = 0; i < cardCount; i++) {
CardItem *c = cards.at(i);
- qreal x = (i / rows) * CARD_WIDTH;
- qreal y = (i % rows) * CARD_HEIGHT / 3;
+ qreal x = (i / rows) * CardDimensions::WIDTH_F;
+ qreal y = (i % rows) * CardDimensions::HEIGHT_F / 3;
c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y);
c->setRealZValue(i);
}
diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp
index 0ad3b10f1..23d7d6a19 100644
--- a/cockatrice/src/game/zones/view_zone_widget.cpp
+++ b/cockatrice/src/game/zones/view_zone_widget.cpp
@@ -458,7 +458,7 @@ void ZoneViewWidget::resizeScrollbar(const qreal newZoneHeight)
*/
static qreal rowsToHeight(int rows)
{
- const qreal cardsHeight = (rows + 1) * (CARD_HEIGHT / 3);
+ const qreal cardsHeight = (rows + 1) * (CardDimensions::HEIGHT_F / 3);
return cardsHeight + 5; // +5 padding to make the cutoff look nicer
}
@@ -574,4 +574,4 @@ void ZoneViewWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
if (event->pos().y() <= 0) {
expandWindow();
}
-}
\ No newline at end of file
+}
diff --git a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp
index f8b581e33..4930fbbfd 100644
--- a/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp
+++ b/cockatrice/src/interface/widgets/cards/card_info_display_widget.cpp
@@ -10,7 +10,7 @@
#include
CardInfoDisplayWidget::CardInfoDisplayWidget(const CardRef &cardRef, QWidget *parent, Qt::WindowFlags flags)
- : QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH)
+ : QFrame(parent, flags), aspectRatio(CardDimensions::HEIGHT_F / CardDimensions::WIDTH_F)
{
setContentsMargins(3, 3, 3, 3);
pic = new CardInfoPictureWidget();
From dead99363909f1b010ce4d468dc311d0a84218a1 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Fri, 6 Mar 2026 10:39:04 -0800
Subject: [PATCH 022/109] [DeckList] Refactor load from plaintext to take
normalizer as param (#6664)
* [DeckList] Refactor load from plaintext to take normalizer as param
* update usages
* weaken unit test
* weaken unit test more
* revert unit test
* move CardNameNormalizer to libcockatrice_card
* update unit test
* formatting
---
.../parsers/interface_json_deck_parser.h | 7 +--
.../src/interface/deck_loader/deck_loader.cpp | 5 ++-
.../dialogs/dlg_load_deck_from_clipboard.cpp | 3 +-
.../dialogs/dlg_load_deck_from_website.cpp | 3 +-
.../dialogs/dlg_load_deck_from_website.h | 1 +
...idekt_api_response_deck_display_widget.cpp | 5 +--
.../average_deck/edhrec_deck_api_response.cpp | 5 +--
libcockatrice_card/CMakeLists.txt | 2 +
.../card/import/card_name_normalizer.cpp | 45 +++++++++++++++++++
.../card/import/card_name_normalizer.h | 15 +++++++
.../libcockatrice/deck_list/deck_list.cpp | 43 ++++--------------
.../libcockatrice/deck_list/deck_list.h | 6 ++-
tests/loading_from_clipboard/CMakeLists.txt | 3 +-
.../clipboard_testing.cpp | 3 +-
14 files changed, 94 insertions(+), 52 deletions(-)
create mode 100644 libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp
create mode 100644 libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h
diff --git a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h
index e19e4f4e4..1818aa35c 100644
--- a/cockatrice/src/client/network/parsers/interface_json_deck_parser.h
+++ b/cockatrice/src/client/network/parsers/interface_json_deck_parser.h
@@ -8,10 +8,11 @@
#define INTERFACE_JSON_DECK_PARSER_H
#include "../../../interface/deck_loader/card_node_function.h"
-#include "../../../interface/deck_loader/deck_loader.h"
#include
#include
+#include
+#include
class IJsonDeckParser
{
@@ -49,7 +50,7 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
- deckList.loadFromStream_Plain(outStream, false);
+ deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
return deckList;
@@ -96,7 +97,7 @@ public:
outStream << quantity << ' ' << cardName << " (" << setName << ") " << collectorNumber << '\n';
}
- deckList.loadFromStream_Plain(outStream, false);
+ deckList.loadFromStream_Plain(outStream, false, CardNameNormalizer());
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
QJsonObject commandersObj = obj.value("commanders").toObject();
diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp
index 3481e245b..d71dca24a 100644
--- a/cockatrice/src/interface/deck_loader/deck_loader.cpp
+++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
#include
#include
@@ -42,7 +43,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo
DeckList deckList;
switch (fmt) {
case DeckFileFormat::PlainText:
- result = deckList.loadFromFile_Plain(&file);
+ result = deckList.loadFromFile_Plain(&file, CardNameNormalizer());
break;
case DeckFileFormat::Cockatrice: {
result = deckList.loadFromFile_Native(&file);
@@ -50,7 +51,7 @@ DeckLoader::loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bo
qCInfo(DeckLoaderLog) << "Failed to load " << fileName
<< "as cockatrice format; retrying as plain format";
file.seek(0);
- result = deckList.loadFromFile_Plain(&file);
+ result = deckList.loadFromFile_Plain(&file, CardNameNormalizer());
fmt = DeckFileFormat::PlainText;
}
break;
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
index b72130c81..9ceb35d78 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
/**
* Creates the main layout and connects the signals that are common to all versions of this window
@@ -81,7 +82,7 @@ bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckList &deckList) const
QTextStream stream(&buffer);
- if (deckList.loadFromStream_Plain(stream, true)) {
+ if (deckList.loadFromStream_Plain(stream, true, CardNameNormalizer())) {
if (loadSetNameAndNumberCheckBox->isChecked()) {
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
} else {
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
index da4135246..ba00873d9 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
DlgLoadDeckFromWebsite::DlgLoadDeckFromWebsite(QWidget *parent) : QDialog(parent)
@@ -99,7 +100,7 @@ void DlgLoadDeckFromWebsite::accept()
// Parse the plain text deck here
DeckList deckList;
QTextStream stream(&deckText);
- deckList.loadFromStream_Plain(stream, false);
+ deckList.loadFromStream_Plain(stream, false, CardNameNormalizer());
deckList.forEachCard(CardNodeFunction::ResolveProviderId());
deck = deckList;
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h
index fe6f0a7e9..1ac98d206 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_website.h
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp
index f40b9094f..8b17cd49e 100644
--- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp
+++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp
@@ -2,7 +2,6 @@
#include "../../../../../deck_loader/card_node_function.h"
#include "../../../../../deck_loader/deck_loader.h"
-#include "../../../../cards/card_info_picture_with_text_overlay_widget.h"
#include "../../../../cards/card_size_widget.h"
#include "../../../../cards/deck_card_zone_display_widget.h"
#include "../../../../visual_deck_editor/visual_deck_display_options_widget.h"
@@ -10,7 +9,7 @@
#include "../api_response/deck/archidekt_api_response_deck.h"
#include
-#include
+#include
ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWidget *parent,
ArchidektApiResponseDeck _response,
@@ -80,7 +79,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi
connect(model, &DeckListModel::modelReset, this, &ArchidektApiResponseDeckDisplayWidget::decklistModelReset);
auto decklist = QSharedPointer(new DeckList);
- decklist->loadFromStream_Plain(deckStream, false);
+ decklist->loadFromStream_Plain(deckStream, false, CardNameNormalizer());
model->setDeckList(decklist);
model->forEachCard(CardNodeFunction::ResolveProviderId());
diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
index a744a9b14..1cb070c30 100644
--- a/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
+++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/api_response/average_deck/edhrec_deck_api_response.cpp
@@ -1,11 +1,10 @@
#include "edhrec_deck_api_response.h"
-#include "../../../../../../deck_loader/deck_loader.h"
-
#include
#include
#include
#include
+#include
void EdhrecDeckApiResponse::fromJson(const QJsonArray &json)
{
@@ -15,7 +14,7 @@ void EdhrecDeckApiResponse::fromJson(const QJsonArray &json)
}
QTextStream stream(&deckList);
- deck.loadFromStream_Plain(stream, true);
+ deck.loadFromStream_Plain(stream, true, CardNameNormalizer());
}
void EdhrecDeckApiResponse::debugPrint() const
diff --git a/libcockatrice_card/CMakeLists.txt b/libcockatrice_card/CMakeLists.txt
index f516cde00..dd3799e33 100644
--- a/libcockatrice_card/CMakeLists.txt
+++ b/libcockatrice_card/CMakeLists.txt
@@ -12,6 +12,7 @@ set(HEADERS
libcockatrice/card/database/parser/card_database_parser.h
libcockatrice/card/database/parser/cockatrice_xml_3.h
libcockatrice/card/database/parser/cockatrice_xml_4.h
+ libcockatrice/card/import/card_name_normalizer.h
libcockatrice/card/printing/exact_card.h
libcockatrice/card/printing/printing_info.h
libcockatrice/card/set/card_set.h
@@ -36,6 +37,7 @@ add_library(
libcockatrice/card/database/parser/card_database_parser.cpp
libcockatrice/card/database/parser/cockatrice_xml_3.cpp
libcockatrice/card/database/parser/cockatrice_xml_4.cpp
+ libcockatrice/card/import/card_name_normalizer.cpp
libcockatrice/card/printing/exact_card.cpp
libcockatrice/card/printing/printing_info.cpp
libcockatrice/card/relation/card_relation.cpp
diff --git a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp
new file mode 100644
index 000000000..2349dc3e8
--- /dev/null
+++ b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp
@@ -0,0 +1,45 @@
+#include "card_name_normalizer.h"
+
+#include
+
+QString CardNameNormalizer::operator()(const QString &cardNameString) const
+{
+ QString cardName = cardNameString;
+
+ // Regex for advanced card parsing
+ static const QRegularExpression reSplitCard(R"( ?\/\/ ?)");
+ static const QRegularExpression reBrace(R"( ?[\[\{][^\]\}]*[\]\}] ?)"); // not nested
+ static const QRegularExpression reRoundBrace(R"(^\([^\)]*\) ?)"); // () are only matched at start of string
+ static const QRegularExpression reDigitBrace(R"( ?\(\d*\) ?)"); // () are matched if containing digits
+ static const QRegularExpression reBraceDigit(
+ R"( ?\([\dA-Z]+\) *\d+$)"); // () are matched if containing setcode then a number
+ static const QRegularExpression reDoubleFacedMarker(R"( ?\(Transform\) ?)");
+
+ static const QHash differences{{QRegularExpression("’"), "'"},
+ {QRegularExpression("Æ"), "Ae"},
+ {QRegularExpression("æ"), "ae"},
+ {QRegularExpression(" ?[|/]+ ?"), " // "}};
+
+ // Handle advanced card types
+ if (cardName.contains(reSplitCard)) {
+ cardName = cardName.split(reSplitCard).join(" // ");
+ }
+
+ if (cardName.contains(reDoubleFacedMarker)) {
+ QStringList faces = cardName.split(reDoubleFacedMarker);
+ cardName = faces.first().trimmed();
+ }
+
+ // Remove unnecessary characters
+ cardName.remove(reBrace);
+ cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards
+ cardName.remove(reDigitBrace); // from un-sets that have a word in between round braces at the end
+ cardName.remove(reBraceDigit); // very specific format with the set code in () and collectors number after
+
+ // Normalize characters
+ for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) {
+ cardName.replace(diff.key(), diff.value());
+ }
+
+ return cardName;
+}
\ No newline at end of file
diff --git a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h
new file mode 100644
index 000000000..716e7da80
--- /dev/null
+++ b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.h
@@ -0,0 +1,15 @@
+#ifndef COCKATRICE_CARD_NAME_NORMALIZER_H
+#define COCKATRICE_CARD_NAME_NORMALIZER_H
+
+#include
+
+/**
+ * Functor that normalizes the raw card name parsed during a plaintext deck import into the card name that Cockatrice
+ * uses.
+ */
+struct CardNameNormalizer
+{
+ QString operator()(const QString &cardNameString) const;
+};
+
+#endif // COCKATRICE_CARD_NAME_NORMALIZER_H
diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp
index 3d1070f15..e3e7b41c0 100644
--- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp
+++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp
@@ -199,9 +199,12 @@ bool DeckList::saveToFile_Native(QIODevice *device) const
*
* @param in The text to load
* @param preserveMetadata If true, don't clear the existing metadata
+ * @param cardNameNormalizer Function that takes the parsed card name string in the text and
* @return False if the input was empty, true otherwise.
*/
-bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
+bool DeckList::loadFromStream_Plain(QTextStream &in,
+ bool preserveMetadata,
+ const std::function &cardNameNormalizer)
{
const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption);
const QRegularExpression reEmpty("^\\s*$");
@@ -213,23 +216,11 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
// Regex for advanced card parsing
const QRegularExpression reMultiplier(R"(^[xX\(\[]*(\d+)[xX\*\)\]]* ?(.+))");
- const QRegularExpression reSplitCard(R"( ?\/\/ ?)");
- const QRegularExpression reBrace(R"( ?[\[\{][^\]\}]*[\]\}] ?)"); // not nested
- const QRegularExpression reRoundBrace(R"(^\([^\)]*\) ?)"); // () are only matched at start of string
- const QRegularExpression reDigitBrace(R"( ?\(\d*\) ?)"); // () are matched if containing digits
- const QRegularExpression reBraceDigit(
- R"( ?\([\dA-Z]+\) *\d+$)"); // () are matched if containing setcode then a number
- const QRegularExpression reDoubleFacedMarker(R"( ?\(Transform\) ?)");
// Regex for extracting set code and collector number with attached symbols
const QRegularExpression reHyphenFormat(R"(\((\w{3,})\)\s+(\w{3,})-(\d+[^\w\s]*))");
const QRegularExpression reRegularFormat(R"(\((\w{3,})\)\s+(\d+[^\w\s]*))");
- const QHash differences{{QRegularExpression("’"), QString("'")},
- {QRegularExpression("Æ"), QString("Ae")},
- {QRegularExpression("æ"), QString("ae")},
- {QRegularExpression(" ?[|/]+ ?"), QString(" // ")}};
-
cleanList(preserveMetadata);
auto inputs = in.readAll().trimmed().split('\n');
@@ -355,26 +346,8 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
cardName = match.captured(2);
}
- // Handle advanced card types
- if (cardName.contains(reSplitCard)) {
- cardName = cardName.split(reSplitCard).join(" // ");
- }
-
- if (cardName.contains(reDoubleFacedMarker)) {
- QStringList faces = cardName.split(reDoubleFacedMarker);
- cardName = faces.first().trimmed();
- }
-
- // Remove unnecessary characters
- cardName.remove(reBrace);
- cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards
- cardName.remove(reDigitBrace); // from un-sets that have a word in between round braces at the end
- cardName.remove(reBraceDigit); // very specific format with the set code in () and collectors number after
-
- // Normalize names
- for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) {
- cardName.replace(diff.key(), diff.value());
- }
+ // Normalize the card name
+ cardName = cardNameNormalizer(cardName);
// Determine the zone (mainboard/sideboard)
QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
@@ -387,10 +360,10 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
return true;
}
-bool DeckList::loadFromFile_Plain(QIODevice *device)
+bool DeckList::loadFromFile_Plain(QIODevice *device, const std::function &cardNameNormalizer)
{
QTextStream in(device);
- return loadFromStream_Plain(in, false);
+ return loadFromStream_Plain(in, false, cardNameNormalizer);
}
bool DeckList::saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) const
diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h
index 91e56fc9f..a96adeb38 100644
--- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h
+++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h
@@ -206,8 +206,10 @@ public:
/// @name Serialization (Plain text)
///@{
- bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata);
- bool loadFromFile_Plain(QIODevice *device);
+ bool loadFromStream_Plain(QTextStream &stream,
+ bool preserveMetadata,
+ const std::function &cardNameNormalizer);
+ bool loadFromFile_Plain(QIODevice *device, const std::function &cardNameNormalizer);
bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards) const;
bool
saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false) const;
diff --git a/tests/loading_from_clipboard/CMakeLists.txt b/tests/loading_from_clipboard/CMakeLists.txt
index 40e85c66e..eecaafcbb 100644
--- a/tests/loading_from_clipboard/CMakeLists.txt
+++ b/tests/loading_from_clipboard/CMakeLists.txt
@@ -10,6 +10,7 @@ set(TEST_QT_MODULES ${COCKATRICE_QT_VERSION_NAME}::Concurrent ${COCKATRICE_QT_VE
)
target_link_libraries(
- loading_from_clipboard_test libcockatrice_deck_list Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
+ loading_from_clipboard_test libcockatrice_deck_list libcockatrice_card Threads::Threads ${GTEST_BOTH_LIBRARIES}
+ ${TEST_QT_MODULES}
)
add_test(NAME loading_from_clipboard_test COMMAND loading_from_clipboard_test)
diff --git a/tests/loading_from_clipboard/clipboard_testing.cpp b/tests/loading_from_clipboard/clipboard_testing.cpp
index 29535c1dc..a9977b800 100644
--- a/tests/loading_from_clipboard/clipboard_testing.cpp
+++ b/tests/loading_from_clipboard/clipboard_testing.cpp
@@ -1,6 +1,7 @@
#include "clipboard_testing.h"
#include
+#include
#include
DeckList getDeckList(const QString &clipboard)
@@ -8,7 +9,7 @@ DeckList getDeckList(const QString &clipboard)
DeckList deckList;
QString cp(clipboard);
QTextStream stream(&cp); // text stream requires local copy
- deckList.loadFromStream_Plain(stream, false);
+ deckList.loadFromStream_Plain(stream, false, CardNameNormalizer());
return deckList;
}
From 2f10634ca2478521f02a85dc4bc4cfe010940077 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Fri, 6 Mar 2026 11:48:17 -0800
Subject: [PATCH 023/109] [DeckList] Fix double-faced cards not importing
correctly (#6665)
* [DeckList] Fix double-faced cards not importing correctly
* make tests compile
---
.../card/import/card_name_normalizer.cpp | 21 +++++++++++++++++++
tests/loading_from_clipboard/CMakeLists.txt | 2 +-
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp
index 2349dc3e8..91ebd6647 100644
--- a/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp
+++ b/libcockatrice_card/libcockatrice/card/import/card_name_normalizer.cpp
@@ -1,7 +1,25 @@
#include "card_name_normalizer.h"
+#include "../database/card_database_manager.h"
+#include "../printing/exact_card.h"
+
#include
+/**
+ * @brief Resolves the complete display name of a card.
+ * @param cardName Base name.
+ * @return Full display name, or the cardName unchanged if a display name is not found.
+ */
+static QString getCompleteCardName(const QString &cardName)
+{
+ ExactCard temp = CardDatabaseManager::query()->guessCard({cardName});
+ if (temp) {
+ return temp.getName();
+ }
+
+ return cardName;
+}
+
QString CardNameNormalizer::operator()(const QString &cardNameString) const
{
QString cardName = cardNameString;
@@ -41,5 +59,8 @@ QString CardNameNormalizer::operator()(const QString &cardNameString) const
cardName.replace(diff.key(), diff.value());
}
+ // Resolve complete card name
+ cardName = getCompleteCardName(cardName);
+
return cardName;
}
\ No newline at end of file
diff --git a/tests/loading_from_clipboard/CMakeLists.txt b/tests/loading_from_clipboard/CMakeLists.txt
index eecaafcbb..85133a8e8 100644
--- a/tests/loading_from_clipboard/CMakeLists.txt
+++ b/tests/loading_from_clipboard/CMakeLists.txt
@@ -1,5 +1,5 @@
add_definitions("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"")
-add_executable(loading_from_clipboard_test clipboard_testing.cpp loading_from_clipboard_test.cpp)
+add_executable(loading_from_clipboard_test ${VERSION_STRING_CPP} clipboard_testing.cpp loading_from_clipboard_test.cpp)
if(NOT GTEST_FOUND)
add_dependencies(loading_from_clipboard_test gtest)
From f15b70e4aef3994ab2fa9e371ee4cbbcb9b41a15 Mon Sep 17 00:00:00 2001
From: tooomm
Date: Sun, 8 Mar 2026 17:53:16 +0100
Subject: [PATCH 024/109] Use new attest action (#6671)
---
.github/workflows/desktop-build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index e95898097..2cd25fc32 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -232,7 +232,7 @@ jobs:
- name: Attest binary provenance
id: attestation
if: steps.upload_release.outcome == 'success'
- uses: actions/attest-build-provenance@v4
+ uses: actions/attest@v4
with:
subject-name: ${{steps.build.outputs.name}}
subject-path: ${{steps.build.outputs.path}}
@@ -530,7 +530,7 @@ jobs:
- name: Attest binary provenance
id: attestation
if: steps.upload_release.outcome == 'success'
- uses: actions/attest-build-provenance@v4
+ uses: actions/attest@v4
with:
subject-name: ${{steps.build.outputs.name}}
subject-path: ${{steps.build.outputs.path}}
From c375cdbb1ae84faedc142eadf11ca5a63332bdfe Mon Sep 17 00:00:00 2001
From: tooomm
Date: Sun, 8 Mar 2026 18:03:01 +0100
Subject: [PATCH 025/109] CI: Upload artifact files directly (#6654)
* Upload artifact files directly
* match pdbs name
* Update name variable
---
.ci/name_build.sh | 10 +++++-----
.github/workflows/desktop-build.yml | 6 +++---
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/.ci/name_build.sh b/.ci/name_build.sh
index 060f8a68e..7b964e21c 100755
--- a/.ci/name_build.sh
+++ b/.ci/name_build.sh
@@ -35,15 +35,15 @@ if ! cd "$path"; then
fi
# set filename
-name="${file%.*}" # remove all after last .
-new_name="$name$SUFFIX."
+name="${file%.*}" # remove file extension
+new_name="$name$SUFFIX"
if [[ $MAKE_ZIP ]]; then
- filename="${new_name}zip"
+ filename="${new_name}.zip"
echo "creating zip '$filename' from '$file'"
zip "$filename" "$file"
else
extension="${file##*.}" # remove all before last .
- filename="$new_name$extension"
+ filename="$new_name.$extension"
echo "renaming '$file' to '$filename'"
mv "$file" "$filename"
fi
@@ -52,4 +52,4 @@ cd "$oldpwd"
relative_path="$path/$filename"
ls -l "$relative_path"
echo "path=$relative_path" >>"$GITHUB_OUTPUT"
-echo "name=$filename" >>"$GITHUB_OUTPUT"
+echo "name=$new_name" >>"$GITHUB_OUTPUT"
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index 2cd25fc32..bc49db2da 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -214,8 +214,8 @@ jobs:
if: matrix.package != 'skip'
uses: actions/upload-artifact@v7
with:
- name: ${{matrix.distro}}${{matrix.version}}-package
path: ${{steps.build.outputs.path}}
+ archive: false
if-no-files-found: error
- name: Upload to release
@@ -501,15 +501,15 @@ jobs:
if: matrix.make_package
uses: actions/upload-artifact@v7
with:
- name: ${{matrix.artifact_name}}
path: ${{steps.build.outputs.path}}
+ archive: false
if-no-files-found: error
- name: Upload PDBs (Program Databases)
if: matrix.os == 'Windows'
uses: actions/upload-artifact@v7
with:
- name: Windows${{matrix.target}}-PDBs
+ name: ${{steps.build.outputs.name}}-PDBs
path: |
build/cockatrice/Release/*.pdb
build/oracle/Release/*.pdb
From 4606fcdbd59ed762b8cedb8472cfda771b90642b Mon Sep 17 00:00:00 2001
From: tooomm
Date: Sun, 8 Mar 2026 23:27:15 +0100
Subject: [PATCH 026/109] Add description for code docs (#6679)
---
.github/ISSUE_TEMPLATE/config.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index f9abf643d..02bdc148a 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -9,4 +9,4 @@ contact_links:
about: For more information and guidance check our Translation FAQ on our wiki!
- name: 📖 Code Documentation
url: https://cockatrice.github.io/docs/
- about:
+ about: Helpful source focusing on developers, but there are also references for users!
From 852429f24861ae6381dd4b18d6576e74621b980d Mon Sep 17 00:00:00 2001
From: tooomm
Date: Sun, 8 Mar 2026 23:48:30 +0100
Subject: [PATCH 027/109] remove unused zip command (#6676)
---
.ci/name_build.sh | 21 +++++++--------------
1 file changed, 7 insertions(+), 14 deletions(-)
diff --git a/.ci/name_build.sh b/.ci/name_build.sh
index 7b964e21c..b111aded6 100755
--- a/.ci/name_build.sh
+++ b/.ci/name_build.sh
@@ -2,7 +2,6 @@
# used by the ci to rename build artifacts
# renames the file to [original name][SUFFIX].[original extension]
# where SUFFIX is either available in the environment or as the first arg
-# if MAKE_ZIP is set instead a zip is made
# expected to be run in the build directory unless BUILD_DIR is set
# adds output to GITHUB_OUTPUT
builddir="${BUILD_DIR:=.}"
@@ -22,8 +21,8 @@ set -e
# find file
found="$(find "$builddir" -maxdepth 1 -type f -name "$findrx" -print -quit)"
-path="${found%/*}" # remove all after last /
-file="${found##*/}" # remove all before last /
+path="${found%/*}" # remove all including first "/" from right side
+file="${found##*/}" # remove all including last "/" from left side
if [[ ! $file ]]; then
echo "::error file=$0::could not find package"
exit 1
@@ -35,18 +34,12 @@ if ! cd "$path"; then
fi
# set filename
-name="${file%.*}" # remove file extension
+name="${file%.*}" # remove all including first "." from right side
new_name="$name$SUFFIX"
-if [[ $MAKE_ZIP ]]; then
- filename="${new_name}.zip"
- echo "creating zip '$filename' from '$file'"
- zip "$filename" "$file"
-else
- extension="${file##*.}" # remove all before last .
- filename="$new_name.$extension"
- echo "renaming '$file' to '$filename'"
- mv "$file" "$filename"
-fi
+extension="${file##*.}" # remove all including last "." from left side
+filename="$new_name.$extension"
+echo "renaming '$file' to '$filename'"
+mv "$file" "$filename"
cd "$oldpwd"
relative_path="$path/$filename"
From fd293444c5a1700638fbbdb3b70d9f9677fd1821 Mon Sep 17 00:00:00 2001
From: tooomm
Date: Sun, 8 Mar 2026 23:50:33 +0100
Subject: [PATCH 028/109] tweaks (#6674)
---
.github/workflows/docker-release.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 1846766bf..2fe646374 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -62,5 +62,6 @@ jobs:
push: ${{ github.ref_type == 'tag' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
- cache-from: type=gha
- cache-to: type=gha,mode=max
+ annotations: ${{ steps.metadata.outputs.annotations }}
+ cache-from: type=gha,scope=servatrice
+ cache-to: type=gha,mode=max,scope=servatrice
From 15a1d5440bc8aa794a2ae157fbd0e9b624ca3de4 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Sun, 8 Mar 2026 15:50:54 -0700
Subject: [PATCH 029/109] [DeckEditor] Fix undo/redo resetting deck sorting
(#6673)
---
.../src/interface/widgets/deck_editor/deck_state_manager.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp
index 8afbfaaa2..8da27b63c 100644
--- a/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp
+++ b/cockatrice/src/interface/widgets/deck_editor/deck_state_manager.cpp
@@ -320,6 +320,7 @@ void DeckStateManager::undo(int steps)
deckListModel->rebuildTree();
emit deckListModel->layoutChanged();
+ emit deckReplaced();
}
void DeckStateManager::redo(int steps)
@@ -338,6 +339,7 @@ void DeckStateManager::redo(int steps)
deckListModel->rebuildTree();
emit deckListModel->layoutChanged();
+ emit deckReplaced();
}
void DeckStateManager::requestHistorySave(const QString &reason)
From e79bbc67b9be76d31f149b04fb7cadeb2ef6c224 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Sun, 8 Mar 2026 15:57:39 -0700
Subject: [PATCH 030/109] [DeckEditor] Fix undo/redo clearing legality (#6675)
---
.../libcockatrice/models/deck_list/deck_list_model.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp
index 8cbf23be4..e43c612f0 100644
--- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp
+++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp
@@ -85,6 +85,8 @@ void DeckListModel::rebuildTree()
}
endResetModel();
+
+ refreshCardFormatLegalities();
}
int DeckListModel::rowCount(const QModelIndex &parent) const
@@ -649,7 +651,6 @@ void DeckListModel::setDeckList(const QSharedPointer &_deck)
deckList = _deck;
}
rebuildTree();
- refreshCardFormatLegalities();
emit deckReplaced();
}
From 413b4b637b8949e2b52c5109d02daed0f8deeab5 Mon Sep 17 00:00:00 2001
From: tooomm
Date: Mon, 9 Mar 2026 21:20:38 +0100
Subject: [PATCH 031/109] sign+notarize only releases (#6678)
---
.github/workflows/desktop-build.yml | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index bc49db2da..d627906fd 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -220,7 +220,7 @@ jobs:
- name: Upload to release
id: upload_release
- if: needs.configure.outputs.tag != null && matrix.package != 'skip'
+ if: matrix.package != 'skip' && needs.configure.outputs.tag != null
shell: bash
env:
GH_TOKEN: ${{github.token}}
@@ -453,7 +453,8 @@ jobs:
key: ccache-${{matrix.runner}}-${{matrix.soc}}-${{matrix.type}}-${{env.BRANCH_NAME}}
- name: Sign app bundle
- if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
+ if: matrix.os == 'macOS' && matrix.make_package && needs.configure.outputs.tag != null
+ id: sign_macos
env:
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
@@ -465,7 +466,7 @@ jobs:
fi
- name: Notarize app bundle
- if: matrix.os == 'macOS' && matrix.make_package && (github.ref == 'refs/heads/master' || needs.configure.outputs.tag != null)
+ if: steps.sign_macos.outcome == 'success'
env:
MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
@@ -497,8 +498,8 @@ jobs:
fi
- name: Upload artifact
- id: upload_artifact
if: matrix.make_package
+ id: upload_artifact
uses: actions/upload-artifact@v7
with:
path: ${{steps.build.outputs.path}}
@@ -506,7 +507,7 @@ jobs:
if-no-files-found: error
- name: Upload PDBs (Program Databases)
- if: matrix.os == 'Windows'
+ if: matrix.os == 'Windows' && github.ref_type != 'tag'
uses: actions/upload-artifact@v7
with:
name: ${{steps.build.outputs.name}}-PDBs
@@ -517,8 +518,8 @@ jobs:
if-no-files-found: error
- name: Upload to release
+ if: needs.configure.outputs.tag != null && matrix.make_package == '1'
id: upload_release
- if: needs.configure.outputs.tag != null
shell: bash
env:
GH_TOKEN: ${{github.token}}
@@ -528,8 +529,8 @@ jobs:
run: gh release upload "$tag_name" "$asset_path#$asset_name"
- name: Attest binary provenance
- id: attestation
if: steps.upload_release.outcome == 'success'
+ id: attestation
uses: actions/attest@v4
with:
subject-name: ${{steps.build.outputs.name}}
From 8268311fab199a86e000e7b753027afac75996dc Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 9 Mar 2026 21:22:26 +0100
Subject: [PATCH 032/109] Bump docker/setup-qemu-action from 3 to 4 (#6683)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)
---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
dependency-version: '4'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/docker-release.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 2fe646374..02a06e592 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -41,7 +41,7 @@ jobs:
org.opencontainers.image.description=Server for Cockatrice, a cross-platform virtual tabletop for multiplayer card games
- name: Set up QEMU
- uses: docker/setup-qemu-action@v3
+ uses: docker/setup-qemu-action@v4
- name: Set up Docker buildx
uses: docker/setup-buildx-action@v3
From 95d7e027fc8f3f4ed44b3d76b8144a07a1421058 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 9 Mar 2026 21:30:47 +0100
Subject: [PATCH 033/109] Bump docker/setup-buildx-action from 3 to 4 (#6682)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)
---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
dependency-version: '4'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/docker-release.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
index 02a06e592..65ccd1322 100644
--- a/.github/workflows/docker-release.yml
+++ b/.github/workflows/docker-release.yml
@@ -44,7 +44,7 @@ jobs:
uses: docker/setup-qemu-action@v4
- name: Set up Docker buildx
- uses: docker/setup-buildx-action@v3
+ uses: docker/setup-buildx-action@v4
- name: Login to GitHub Container Registry
if: github.ref_type == 'tag'
From 42cec1045725a29b72b1278ace7d037c5662f9d4 Mon Sep 17 00:00:00 2001
From: tooomm
Date: Mon, 9 Mar 2026 21:32:47 +0100
Subject: [PATCH 034/109] CI: Cleanup (#6677)
* Cleanup vcpkg matrix
* Add filename with extension as output
* fix name -> fullname, cleanup
---
.ci/name_build.sh | 1 +
.github/workflows/desktop-build.yml | 10 ++--------
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/.ci/name_build.sh b/.ci/name_build.sh
index b111aded6..85818bbd9 100755
--- a/.ci/name_build.sh
+++ b/.ci/name_build.sh
@@ -46,3 +46,4 @@ relative_path="$path/$filename"
ls -l "$relative_path"
echo "path=$relative_path" >>"$GITHUB_OUTPUT"
echo "name=$new_name" >>"$GITHUB_OUTPUT"
+echo "fullname=$filename" >>"$GITHUB_OUTPUT"
diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
index d627906fd..233d9e488 100644
--- a/.github/workflows/desktop-build.yml
+++ b/.github/workflows/desktop-build.yml
@@ -225,7 +225,7 @@ jobs:
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
- asset_name: ${{steps.build.outputs.name}}
+ asset_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
@@ -234,7 +234,6 @@ jobs:
if: steps.upload_release.outcome == 'success'
uses: actions/attest@v4
with:
- subject-name: ${{steps.build.outputs.name}}
subject-path: ${{steps.build.outputs.path}}
show-summary: false
@@ -259,7 +258,6 @@ jobs:
override_target: 13
make_package: 1
package_suffix: "-macOS13_Intel"
- artifact_name: macOS13_Intel-package
qt_version: 6.10.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
@@ -274,7 +272,6 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS14"
- artifact_name: macOS14-package
qt_version: 6.10.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
@@ -289,7 +286,6 @@ jobs:
type: Release
make_package: 1
package_suffix: "-macOS15"
- artifact_name: macOS15-package
qt_version: 6.10.*
qt_arch: clang_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
@@ -314,7 +310,6 @@ jobs:
type: Release
make_package: 1
package_suffix: "-Win10"
- artifact_name: Windows10-installer
qt_version: 6.10.*
qt_arch: win64_msvc2022_64
qt_modules: qtimageformats qtmultimedia qtwebsockets
@@ -524,7 +519,7 @@ jobs:
env:
GH_TOKEN: ${{github.token}}
tag_name: ${{needs.configure.outputs.tag}}
- asset_name: ${{steps.build.outputs.name}}
+ asset_name: ${{steps.build.outputs.fullname}}
asset_path: ${{steps.build.outputs.path}}
run: gh release upload "$tag_name" "$asset_path#$asset_name"
@@ -533,7 +528,6 @@ jobs:
id: attestation
uses: actions/attest@v4
with:
- subject-name: ${{steps.build.outputs.name}}
subject-path: ${{steps.build.outputs.path}}
show-summary: false
From 9e2276a59fa0df05b16bb4e9dbd0a2a377dd572a Mon Sep 17 00:00:00 2001
From: DawnFire42
Date: Wed, 11 Mar 2026 19:34:05 -0400
Subject: [PATCH 035/109] Refactor zone names (#6686)
* Add ZoneNames constants for protocol zone identifiers. Introduce a centralized ZoneNames namespace providing constexpr constants for zone identifiers used in the client-server protocol. This establishes a single source of truth for zone names like TABLE, GRAVE, EXILE, HAND, DECK, SIDEBOARD, and STACK. The protocol values remain unchanged (e.g., EXILE maps to rfg for backwards compatibility) while providing meaningful constant names.
* refactor(server): use ZoneNames constants in server game logic
Replace hardcoded zone name strings with ZoneNames:: constants in:
- server_player.cpp: zone setup, draw, shuffle, mulligan operations
- server_abstract_player.cpp: card movement and token destruction
- server_game.cpp: returning cards when players leave
No functional changes - purely mechanical string literal replacement.
* refactor(client): use ZoneNames constants in core player/zone logic
Update the foundational player and zone classes to use ZoneNames::
constants instead of string literals. Changes include:
- player.h/cpp: zone initialization and builtinZones set
- card_zone_logic.cpp: zone name translation for UI display
- table_zone.cpp: table zone operations
No functional changes - purely mechanical string literal replacement.
* refactor(client): use ZoneNames constants in player actions and events
Replace zone name strings with ZoneNames:: constants in the player
action and event handling code. player_actions.cpp contains the most
extensive changes (~90+ replacements) covering all card movement
commands.
No functional changes - purely mechanical string literal replacement.
* refactor(client): use ZoneNames constants in zone menu handlers
Update all zone-specific menu files to use ZoneNames:: constants
for QAction data values and zone targeting. This covers context menus
for cards, graveyard, hand, and exile (RFG) zones.
No functional changes - purely mechanical string literal replacement.
* refactor(client): use ZoneNames constants in game scene components
Update remaining game scene components to use ZoneNames:: constants:
- arrow_item.cpp: arrow drawing between cards
- game_scene.cpp: zone view positioning
- message_log_widget.cpp: removes duplicate local static constants
that were previously defining zone names redundantly
- phases_toolbar.cpp: phase actions (untap all)
Notable: message_log_widget.cpp previously had its own local constants
(TABLE_ZONE_NAME, GRAVE_ZONE_NAME, etc.) which are now removed in favor
of the centralized ZoneNames:: constants.
* formatting fix
---
cockatrice/src/game/board/arrow_item.cpp | 19 +--
cockatrice/src/game/game_scene.cpp | 5 +-
.../src/game/log/message_log_widget.cpp | 43 +++---
cockatrice/src/game/phases_toolbar.cpp | 3 +-
cockatrice/src/game/player/menu/card_menu.cpp | 14 +-
.../src/game/player/menu/grave_menu.cpp | 9 +-
cockatrice/src/game/player/menu/hand_menu.cpp | 9 +-
cockatrice/src/game/player/menu/rfg_menu.cpp | 10 +-
cockatrice/src/game/player/player.cpp | 22 +--
cockatrice/src/game/player/player.h | 15 +-
cockatrice/src/game/player/player_actions.cpp | 137 +++++++++---------
.../src/game/player/player_event_handler.cpp | 5 +-
.../src/game/zones/logic/card_zone_logic.cpp | 11 +-
cockatrice/src/game/zones/table_zone.cpp | 3 +-
.../remote/game/server_abstract_player.cpp | 9 +-
.../server/remote/game/server_game.cpp | 5 +-
.../server/remote/game/server_player.cpp | 33 +++--
libcockatrice_utility/CMakeLists.txt | 9 +-
.../libcockatrice/utility/zone_names.h | 19 +++
19 files changed, 207 insertions(+), 173 deletions(-)
create mode 100644 libcockatrice_utility/libcockatrice/utility/zone_names.h
diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp
index 257d96f8a..60585a774 100644
--- a/cockatrice/src/game/board/arrow_item.cpp
+++ b/cockatrice/src/game/board/arrow_item.cpp
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false),
@@ -239,16 +240,16 @@ 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() == "hand") {
+ if (startZone->getName() == ZoneNames::HAND) {
startCard->playCard(false);
CardInfoPtr ci = startCard->getCard().getCardPtr();
bool playToStack = SettingsCache::instance().getPlayToStack();
- if (ci &&
- ((!playToStack && ci->getUiAttributes().tableRow == 3) ||
- (playToStack && ci->getUiAttributes().tableRow != 0 && startCard->getZone()->getName() != "stack")))
- cmd.set_start_zone("stack");
+ 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 ? "stack" : "table");
+ cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE);
}
if (deleteInPhase != 0) {
@@ -318,7 +319,7 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
// do nothing if target is already attached to another card or is not in play
- if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != "table") {
+ if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) {
return;
}
@@ -326,12 +327,12 @@ void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCar
CardZoneLogic *targetZone = targetCard->getZone();
// move card onto table first if attaching from some other zone
- if (startZone->getName() != "table") {
+ if (startZone->getName() != ZoneNames::TABLE) {
player->getPlayerActions()->playCardToTable(startCard, false);
}
Command_AttachCard cmd;
- cmd.set_start_zone("table");
+ cmd.set_start_zone(ZoneNames::TABLE);
cmd.set_card_id(startCard->getId());
cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone->getName().toStdString());
diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp
index 77037cd6e..5dc3b48f7 100644
--- a/cockatrice/src/game/game_scene.cpp
+++ b/cockatrice/src/game/game_scene.cpp
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
/**
@@ -410,9 +411,9 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb
connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView);
addItem(item);
- if (zoneName == "grave")
+ if (zoneName == ZoneNames::GRAVE)
item->setPos(360, 100);
- else if (zoneName == "rfg")
+ else if (zoneName == ZoneNames::EXILE)
item->setPos(380, 120);
else
item->setPos(340, 80);
diff --git a/cockatrice/src/game/log/message_log_widget.cpp b/cockatrice/src/game/log/message_log_widget.cpp
index 645974994..c38e433eb 100644
--- a/cockatrice/src/game/log/message_log_widget.cpp
+++ b/cockatrice/src/game/log/message_log_widget.cpp
@@ -10,16 +10,9 @@
#include <../../client/settings/card_counter_settings.h>
#include
#include
+#include
#include
-static const QString TABLE_ZONE_NAME = "table";
-static const QString GRAVE_ZONE_NAME = "grave";
-static const QString EXILE_ZONE_NAME = "rfg";
-static const QString HAND_ZONE_NAME = "hand";
-static const QString DECK_ZONE_NAME = "deck";
-static const QString SIDEBOARD_ZONE_NAME = "sb";
-static const QString STACK_ZONE_NAME = "stack";
-
static QString sanitizeHtml(QString dirty)
{
return dirty.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """);
@@ -37,15 +30,15 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
QString fromStr;
QString zoneName = zone->getName();
- if (zoneName == TABLE_ZONE_NAME) {
+ if (zoneName == ZoneNames::TABLE) {
fromStr = tr(" from play");
- } else if (zoneName == GRAVE_ZONE_NAME) {
+ } else if (zoneName == ZoneNames::GRAVE) {
fromStr = tr(" from their graveyard");
- } else if (zoneName == EXILE_ZONE_NAME) {
+ } else if (zoneName == ZoneNames::EXILE) {
fromStr = tr(" from exile");
- } else if (zoneName == HAND_ZONE_NAME) {
+ } else if (zoneName == ZoneNames::HAND) {
fromStr = tr(" from their hand");
- } else if (zoneName == DECK_ZONE_NAME) {
+ } else if (zoneName == ZoneNames::DECK) {
if (position == 0) {
if (cardName.isEmpty()) {
if (ownerChange) {
@@ -83,9 +76,9 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
fromStr = tr(" from their library");
}
}
- } else if (zoneName == SIDEBOARD_ZONE_NAME) {
+ } else if (zoneName == ZoneNames::SIDEBOARD) {
fromStr = tr(" from sideboard");
- } else if (zoneName == STACK_ZONE_NAME) {
+ } else if (zoneName == ZoneNames::STACK) {
fromStr = tr(" from the stack");
} else {
fromStr = tr(" from custom zone '%1'").arg(zoneName);
@@ -275,9 +268,9 @@ void MessageLogWidget::logMoveCard(Player *player,
bool ownerChanged = startZone->getPlayer() != targetZone->getPlayer();
// do not log if moved within the same zone
- if ((startZoneName == TABLE_ZONE_NAME && targetZoneName == TABLE_ZONE_NAME && !ownerChanged) ||
- (startZoneName == HAND_ZONE_NAME && targetZoneName == HAND_ZONE_NAME) ||
- (startZoneName == EXILE_ZONE_NAME && targetZoneName == EXILE_ZONE_NAME)) {
+ if ((startZoneName == ZoneNames::TABLE && targetZoneName == ZoneNames::TABLE && !ownerChanged) ||
+ (startZoneName == ZoneNames::HAND && targetZoneName == ZoneNames::HAND) ||
+ (startZoneName == ZoneNames::EXILE && targetZoneName == ZoneNames::EXILE)) {
return;
}
@@ -306,28 +299,28 @@ void MessageLogWidget::logMoveCard(Player *player,
QString finalStr;
std::optional fourthArg;
- if (targetZoneName == TABLE_ZONE_NAME) {
+ if (targetZoneName == ZoneNames::TABLE) {
soundEngine->playSound("play_card");
if (card->getFaceDown()) {
finalStr = tr("%1 puts %2 into play%3 face down.");
} else {
finalStr = tr("%1 puts %2 into play%3.");
}
- } else if (targetZoneName == GRAVE_ZONE_NAME) {
+ } else if (targetZoneName == ZoneNames::GRAVE) {
if (card->getFaceDown()) {
finalStr = tr("%1 puts %2%3 into their graveyard face down.");
} else {
finalStr = tr("%1 puts %2%3 into their graveyard.");
}
- } else if (targetZoneName == EXILE_ZONE_NAME) {
+ } else if (targetZoneName == ZoneNames::EXILE) {
if (card->getFaceDown()) {
finalStr = tr("%1 exiles %2%3 face down.");
} else {
finalStr = tr("%1 exiles %2%3.");
}
- } else if (targetZoneName == HAND_ZONE_NAME) {
+ } else if (targetZoneName == ZoneNames::HAND) {
finalStr = tr("%1 moves %2%3 to their hand.");
- } else if (targetZoneName == DECK_ZONE_NAME) {
+ } else if (targetZoneName == ZoneNames::DECK) {
if (newX == -1) {
finalStr = tr("%1 puts %2%3 into their library.");
} else if (newX >= targetZone->getCards().size()) {
@@ -339,9 +332,9 @@ void MessageLogWidget::logMoveCard(Player *player,
fourthArg = QString::number(newX);
finalStr = tr("%1 puts %2%3 into their library %4 cards from the top.");
}
- } else if (targetZoneName == SIDEBOARD_ZONE_NAME) {
+ } else if (targetZoneName == ZoneNames::SIDEBOARD) {
finalStr = tr("%1 moves %2%3 to sideboard.");
- } else if (targetZoneName == STACK_ZONE_NAME) {
+ } else if (targetZoneName == ZoneNames::STACK) {
soundEngine->playSound("play_card");
if (card->getFaceDown()) {
finalStr = tr("%1 plays %2%3 face down.");
diff --git a/cockatrice/src/game/phases_toolbar.cpp b/cockatrice/src/game/phases_toolbar.cpp
index 5106e40de..2341a1d7f 100644
--- a/cockatrice/src/game/phases_toolbar.cpp
+++ b/cockatrice/src/game/phases_toolbar.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_doubleClickAction, bool _highlightable)
: QObject(), QGraphicsItem(parent), name(_name), active(false), highlightable(_highlightable),
@@ -259,7 +260,7 @@ void PhasesToolbar::actNextTurn()
void PhasesToolbar::actUntapAll()
{
Command_SetCardAttr cmd;
- cmd.set_zone("table");
+ cmd.set_zone(ZoneNames::TABLE);
cmd.set_attribute(AttrTapped);
cmd.set_attr_value("0");
diff --git a/cockatrice/src/game/player/menu/card_menu.cpp b/cockatrice/src/game/player/menu/card_menu.cpp
index ddf0a55f3..ef95f052e 100644
--- a/cockatrice/src/game/player/menu/card_menu.cpp
+++ b/cockatrice/src/game/player/menu/card_menu.cpp
@@ -12,6 +12,7 @@
#include
#include
+#include
CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive)
: player(_player), card(_card), shortcutsActive(_shortcutsActive)
@@ -115,11 +116,12 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
} else if (writeableCard) {
if (card->getZone()) {
- if (card->getZone()->getName() == "table") {
+ if (card->getZone()->getName() == ZoneNames::TABLE) {
createTableMenu();
- } else if (card->getZone()->getName() == "stack") {
+ } else if (card->getZone()->getName() == ZoneNames::STACK) {
createStackMenu();
- } else if (card->getZone()->getName() == "rfg" || card->getZone()->getName() == "grave") {
+ } else if (card->getZone()->getName() == ZoneNames::EXILE ||
+ card->getZone()->getName() == ZoneNames::GRAVE) {
createGraveyardOrExileMenu();
} else {
createHandOrCustomZoneMenu();
@@ -128,7 +130,7 @@ CardMenu::CardMenu(Player *_player, const CardItem *_card, bool _shortcutsActive
addMenu(new MoveMenu(player));
}
} else {
- if (card->getZone() && card->getZone()->getName() != "hand") {
+ if (card->getZone() && card->getZone()->getName() != ZoneNames::HAND) {
addAction(aDrawArrow);
addSeparator();
addRelatedCardView();
@@ -285,7 +287,7 @@ void CardMenu::createHandOrCustomZoneMenu()
addMenu(new MoveMenu(player));
// actions that are really wonky when done from deck or sideboard
- if (card->getZone()->getName() == "hand") {
+ if (card->getZone()->getName() == ZoneNames::HAND) {
addSeparator();
addAction(aAttach);
addAction(aDrawArrow);
@@ -298,7 +300,7 @@ void CardMenu::createHandOrCustomZoneMenu()
}
addRelatedCardView();
- if (card->getZone()->getName() == "hand") {
+ if (card->getZone()->getName() == ZoneNames::HAND) {
addRelatedCardActions();
}
}
diff --git a/cockatrice/src/game/player/menu/grave_menu.cpp b/cockatrice/src/game/player/menu/grave_menu.cpp
index d4faca42b..2af62c08a 100644
--- a/cockatrice/src/game/player/menu/grave_menu.cpp
+++ b/cockatrice/src/game/player/menu/grave_menu.cpp
@@ -6,6 +6,7 @@
#include
#include
+#include
GraveyardMenu::GraveyardMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
@@ -39,16 +40,16 @@ void GraveyardMenu::createMoveActions()
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) {
aMoveGraveToTopLibrary = new QAction(this);
- aMoveGraveToTopLibrary->setData(QList() << "deck" << 0);
+ aMoveGraveToTopLibrary->setData(QList() << ZoneNames::DECK << 0);
aMoveGraveToBottomLibrary = new QAction(this);
- aMoveGraveToBottomLibrary->setData(QList() << "deck" << -1);
+ aMoveGraveToBottomLibrary->setData(QList() << ZoneNames::DECK << -1);
aMoveGraveToHand = new QAction(this);
- aMoveGraveToHand->setData(QList() << "hand" << 0);
+ aMoveGraveToHand->setData(QList() << ZoneNames::HAND << 0);
aMoveGraveToRfg = new QAction(this);
- aMoveGraveToRfg->setData(QList() << "rfg" << 0);
+ aMoveGraveToRfg->setData(QList() << ZoneNames::EXILE << 0);
connect(aMoveGraveToTopLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);
connect(aMoveGraveToBottomLibrary, &QAction::triggered, grave, &PileZoneLogic::moveAllToZone);
diff --git a/cockatrice/src/game/player/menu/hand_menu.cpp b/cockatrice/src/game/player/menu/hand_menu.cpp
index 019ab925c..d65c136bf 100644
--- a/cockatrice/src/game/player/menu/hand_menu.cpp
+++ b/cockatrice/src/game/player/menu/hand_menu.cpp
@@ -9,6 +9,7 @@
#include
#include
+#include
HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : TearOffMenu(parent), player(_player)
{
@@ -76,13 +77,13 @@ HandMenu::HandMenu(Player *_player, PlayerActions *actions, QWidget *parent) : T
if (player->getPlayerInfo()->local || player->getPlayerInfo()->judge) {
aMoveHandToTopLibrary = new QAction(this);
- aMoveHandToTopLibrary->setData(QList() << "deck" << 0);
+ aMoveHandToTopLibrary->setData(QList() << ZoneNames::DECK << 0);
aMoveHandToBottomLibrary = new QAction(this);
- aMoveHandToBottomLibrary->setData(QList() << "deck" << -1);
+ aMoveHandToBottomLibrary->setData(QList() << ZoneNames::DECK << -1);
aMoveHandToGrave = new QAction(this);
- aMoveHandToGrave->setData(QList() << "grave" << 0);
+ aMoveHandToGrave->setData(QList() << ZoneNames::GRAVE << 0);
aMoveHandToRfg = new QAction(this);
- aMoveHandToRfg->setData(QList() << "rfg" << 0);
+ aMoveHandToRfg->setData(QList() << ZoneNames::EXILE << 0);
auto hand = player->getHandZone();
diff --git a/cockatrice/src/game/player/menu/rfg_menu.cpp b/cockatrice/src/game/player/menu/rfg_menu.cpp
index 25e162581..8a101c04c 100644
--- a/cockatrice/src/game/player/menu/rfg_menu.cpp
+++ b/cockatrice/src/game/player/menu/rfg_menu.cpp
@@ -3,6 +3,8 @@
#include "../player.h"
#include "../player_actions.h"
+#include
+
RfgMenu::RfgMenu(Player *_player, QWidget *parent) : TearOffMenu(parent), player(_player)
{
createMoveActions();
@@ -30,13 +32,13 @@ void RfgMenu::createMoveActions()
auto rfg = player->getRfgZone();
aMoveRfgToTopLibrary = new QAction(this);
- aMoveRfgToTopLibrary->setData(QList() << "deck" << 0);
+ aMoveRfgToTopLibrary->setData(QList() << ZoneNames::DECK << 0);
aMoveRfgToBottomLibrary = new QAction(this);
- aMoveRfgToBottomLibrary->setData(QList() << "deck" << -1);
+ aMoveRfgToBottomLibrary->setData(QList() << ZoneNames::DECK << -1);
aMoveRfgToHand = new QAction(this);
- aMoveRfgToHand->setData(QList() << "hand" << 0);
+ aMoveRfgToHand->setData(QList() << ZoneNames::HAND << 0);
aMoveRfgToGrave = new QAction(this);
- aMoveRfgToGrave->setData(QList() << "grave" << 0);
+ aMoveRfgToGrave->setData(QList() << ZoneNames::GRAVE << 0);
connect(aMoveRfgToTopLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);
connect(aMoveRfgToBottomLibrary, &QAction::triggered, rfg, &PileZoneLogic::moveAllToZone);
diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp
index 0723ae6bc..ac4149f0e 100644
--- a/cockatrice/src/game/player/player.cpp
+++ b/cockatrice/src/game/player/player.cpp
@@ -61,15 +61,15 @@ void Player::forwardActionSignalsToEventHandler()
void Player::initializeZones()
{
- addZone(new PileZoneLogic(this, "deck", false, true, false, this));
- addZone(new PileZoneLogic(this, "grave", false, false, true, this));
- addZone(new PileZoneLogic(this, "rfg", false, false, true, this));
- addZone(new PileZoneLogic(this, "sb", false, false, false, this));
- addZone(new TableZoneLogic(this, "table", true, false, true, this));
- addZone(new StackZoneLogic(this, "stack", true, false, true, this));
+ addZone(new PileZoneLogic(this, ZoneNames::DECK, false, true, false, this));
+ addZone(new PileZoneLogic(this, ZoneNames::GRAVE, false, false, true, this));
+ addZone(new PileZoneLogic(this, ZoneNames::EXILE, false, false, true, this));
+ addZone(new PileZoneLogic(this, ZoneNames::SIDEBOARD, false, false, false, this));
+ addZone(new TableZoneLogic(this, ZoneNames::TABLE, true, false, true, this));
+ addZone(new StackZoneLogic(this, ZoneNames::STACK, true, false, true, this));
bool visibleHand = playerInfo->getLocalOrJudge() ||
(game->getPlayerManager()->isSpectator() && game->getGameMetaInfo()->spectatorsOmniscient());
- addZone(new HandZoneLogic(this, "hand", false, false, visibleHand, this));
+ addZone(new HandZoneLogic(this, ZoneNames::HAND, false, false, visibleHand, this));
}
Player::~Player()
@@ -119,13 +119,13 @@ void Player::setZoneId(int _zoneId)
void Player::processPlayerInfo(const ServerInfo_Player &info)
{
static QSet builtinZones{/* PileZones */
- "deck", "grave", "rfg", "sb",
+ ZoneNames::DECK, ZoneNames::GRAVE, ZoneNames::EXILE, ZoneNames::SIDEBOARD,
/* TableZone */
- "table",
+ ZoneNames::TABLE,
/* StackZone */
- "stack",
+ ZoneNames::STACK,
/* HandZone */
- "hand"};
+ ZoneNames::HAND};
clearCounters();
clearArrows();
diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h
index 0a03b3abe..e9c008821 100644
--- a/cockatrice/src/game/player/player.h
+++ b/cockatrice/src/game/player/player.h
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
inline Q_LOGGING_CATEGORY(PlayerLog, "player");
@@ -155,37 +156,37 @@ public:
PileZoneLogic *getDeckZone()
{
- return qobject_cast(zones.value("deck"));
+ return qobject_cast(zones.value(ZoneNames::DECK));
}
PileZoneLogic *getGraveZone()
{
- return qobject_cast(zones.value("grave"));
+ return qobject_cast(zones.value(ZoneNames::GRAVE));
}
PileZoneLogic *getRfgZone()
{
- return qobject_cast(zones.value("rfg"));
+ return qobject_cast(zones.value(ZoneNames::EXILE));
}
PileZoneLogic *getSideboardZone()
{
- return qobject_cast(zones.value("sb"));
+ return qobject_cast(zones.value(ZoneNames::SIDEBOARD));
}
TableZoneLogic *getTableZone()
{
- return qobject_cast(zones.value("table"));
+ return qobject_cast(zones.value(ZoneNames::TABLE));
}
StackZoneLogic *getStackZone()
{
- return qobject_cast(zones.value("stack"));
+ return qobject_cast(zones.value(ZoneNames::STACK));
}
HandZoneLogic *getHandZone()
{
- return qobject_cast(zones.value("hand"));
+ return qobject_cast(zones.value(ZoneNames::HAND));
}
AbstractCounter *addCounter(const ServerInfo_Counter &counter);
diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp
index 514ac2e67..20e526727 100644
--- a/cockatrice/src/game/player/player_actions.cpp
+++ b/cockatrice/src/game/player/player_actions.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
// milliseconds in between triggers of the move top cards until action
static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100;
@@ -63,13 +64,13 @@ void PlayerActions::playCard(CardItem *card, bool faceDown)
int tableRow = info.getUiAttributes().tableRow;
bool playToStack = SettingsCache::instance().getPlayToStack();
QString currentZone = card->getZone()->getName();
- if (currentZone == "stack" && tableRow == 3) {
- cmd.set_target_zone("grave");
+ if (currentZone == ZoneNames::STACK && tableRow == 3) {
+ cmd.set_target_zone(ZoneNames::GRAVE);
cmd.set_x(0);
cmd.set_y(0);
- } else if (!faceDown &&
- ((!playToStack && tableRow == 3) || ((playToStack && tableRow != 0) && currentZone != "stack"))) {
- cmd.set_target_zone("stack");
+ } else if (!faceDown && ((!playToStack && tableRow == 3) ||
+ ((playToStack && tableRow != 0) && currentZone != ZoneNames::STACK))) {
+ cmd.set_target_zone(ZoneNames::STACK);
cmd.set_x(-1);
cmd.set_y(0);
} else {
@@ -81,7 +82,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown)
}
cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt);
if (tableRow != 3)
- cmd.set_target_zone("table");
+ cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y());
}
@@ -124,7 +125,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
cardToMove->set_pt(info.getPowTough().toStdString());
}
cardToMove->set_tapped(!faceDown && info.getUiAttributes().cipt);
- cmd.set_target_zone("table");
+ cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_x(gridPoint.x());
cmd.set_y(gridPoint.y());
sendGameCommand(cmd);
@@ -132,12 +133,12 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown)
void PlayerActions::actViewLibrary()
{
- player->getGameScene()->toggleZoneView(player, "deck", -1);
+ player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, -1);
}
void PlayerActions::actViewHand()
{
- player->getGameScene()->toggleZoneView(player, "hand", -1);
+ player->getGameScene()->toggleZoneView(player, ZoneNames::HAND, -1);
}
/**
@@ -181,7 +182,7 @@ void PlayerActions::actViewTopCards()
deckSize, 1, &ok);
if (ok) {
defaultNumberTopCards = number;
- player->getGameScene()->toggleZoneView(player, "deck", number);
+ player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number);
}
}
@@ -194,14 +195,14 @@ void PlayerActions::actViewBottomCards()
deckSize, 1, &ok);
if (ok) {
defaultNumberBottomCards = number;
- player->getGameScene()->toggleZoneView(player, "deck", number, true);
+ player->getGameScene()->toggleZoneView(player, ZoneNames::DECK, number, true);
}
}
void PlayerActions::actAlwaysRevealTopCard()
{
Command_ChangeZoneProperties cmd;
- cmd.set_zone_name("deck");
+ cmd.set_zone_name(ZoneNames::DECK);
cmd.set_always_reveal_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysRevealTopCardChecked());
sendGameCommand(cmd);
@@ -210,7 +211,7 @@ void PlayerActions::actAlwaysRevealTopCard()
void PlayerActions::actAlwaysLookAtTopCard()
{
Command_ChangeZoneProperties cmd;
- cmd.set_zone_name("deck");
+ cmd.set_zone_name(ZoneNames::DECK);
cmd.set_always_look_at_top_card(player->getPlayerMenu()->getLibraryMenu()->isAlwaysLookAtTopCardChecked());
sendGameCommand(cmd);
@@ -223,17 +224,17 @@ void PlayerActions::actOpenDeckInDeckEditor()
void PlayerActions::actViewGraveyard()
{
- player->getGameScene()->toggleZoneView(player, "grave", -1);
+ player->getGameScene()->toggleZoneView(player, ZoneNames::GRAVE, -1);
}
void PlayerActions::actViewRfg()
{
- player->getGameScene()->toggleZoneView(player, "rfg", -1);
+ player->getGameScene()->toggleZoneView(player, ZoneNames::EXILE, -1);
}
void PlayerActions::actViewSideboard()
{
- player->getGameScene()->toggleZoneView(player, "sb", -1);
+ player->getGameScene()->toggleZoneView(player, ZoneNames::SIDEBOARD, -1);
}
void PlayerActions::actShuffle()
@@ -263,7 +264,7 @@ void PlayerActions::actShuffleTop()
defaultNumberTopCards = number;
Command_Shuffle cmd;
- cmd.set_zone_name("deck");
+ cmd.set_zone_name(ZoneNames::DECK);
cmd.set_start(0);
cmd.set_end(number - 1); // inclusive, the indexed card at end will be shuffled
@@ -292,7 +293,7 @@ void PlayerActions::actShuffleBottom()
defaultNumberBottomCards = number;
Command_Shuffle cmd;
- cmd.set_zone_name("deck");
+ cmd.set_zone_name(ZoneNames::DECK);
cmd.set_start(-number);
cmd.set_end(-1);
@@ -376,7 +377,7 @@ void PlayerActions::actUndoDraw()
void PlayerActions::cmdSetTopCard(Command_MoveCard &cmd)
{
- cmd.set_start_zone("deck");
+ cmd.set_start_zone(ZoneNames::DECK);
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(0);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
@@ -386,7 +387,7 @@ void PlayerActions::cmdSetBottomCard(Command_MoveCard &cmd)
{
CardZoneLogic *zone = player->getDeckZone();
int lastCard = zone->getCards().size() - 1;
- cmd.set_start_zone("deck");
+ cmd.set_start_zone(ZoneNames::DECK);
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(lastCard);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
@@ -400,7 +401,7 @@ void PlayerActions::actMoveTopCardToGrave()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
- cmd.set_target_zone("grave");
+ cmd.set_target_zone(ZoneNames::GRAVE);
cmd.set_x(0);
cmd.set_y(0);
@@ -415,7 +416,7 @@ void PlayerActions::actMoveTopCardToExile()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
- cmd.set_target_zone("rfg");
+ cmd.set_target_zone(ZoneNames::EXILE);
cmd.set_x(0);
cmd.set_y(0);
@@ -424,22 +425,22 @@ void PlayerActions::actMoveTopCardToExile()
void PlayerActions::actMoveTopCardsToGrave()
{
- moveTopCardsTo("grave", tr("grave"), false);
+ moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), false);
}
void PlayerActions::actMoveTopCardsToGraveFaceDown()
{
- moveTopCardsTo("grave", tr("grave"), true);
+ moveTopCardsTo(ZoneNames::GRAVE, tr("grave"), true);
}
void PlayerActions::actMoveTopCardsToExile()
{
- moveTopCardsTo("rfg", tr("exile"), false);
+ moveTopCardsTo(ZoneNames::EXILE, tr("exile"), false);
}
void PlayerActions::actMoveTopCardsToExileFaceDown()
{
- moveTopCardsTo("rfg", tr("exile"), true);
+ moveTopCardsTo(ZoneNames::EXILE, tr("exile"), true);
}
void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
@@ -463,7 +464,7 @@ void PlayerActions::moveTopCardsTo(const QString &targetZone, const QString &zon
defaultNumberTopCards = number;
Command_MoveCard cmd;
- cmd.set_start_zone("deck");
+ cmd.set_start_zone(ZoneNames::DECK);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone.toStdString());
cmd.set_x(0);
@@ -549,7 +550,7 @@ void PlayerActions::actMoveTopCardToBottom()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
- cmd.set_target_zone("deck");
+ cmd.set_target_zone(ZoneNames::DECK);
cmd.set_x(-1); // bottom of deck
cmd.set_y(0);
@@ -564,7 +565,7 @@ void PlayerActions::actMoveTopCardToPlay()
Command_MoveCard cmd;
cmdSetTopCard(cmd);
- cmd.set_target_zone("stack");
+ cmd.set_target_zone(ZoneNames::STACK);
cmd.set_x(-1);
cmd.set_y(0);
@@ -578,12 +579,12 @@ void PlayerActions::actMoveTopCardToPlayFaceDown()
}
Command_MoveCard cmd;
- cmd.set_start_zone("deck");
+ cmd.set_start_zone(ZoneNames::DECK);
CardToMove *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(0);
cardToMove->set_face_down(true);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
- cmd.set_target_zone("table");
+ cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_x(-1);
cmd.set_y(0);
@@ -598,7 +599,7 @@ void PlayerActions::actMoveBottomCardToGrave()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
- cmd.set_target_zone("grave");
+ cmd.set_target_zone(ZoneNames::GRAVE);
cmd.set_x(0);
cmd.set_y(0);
@@ -613,7 +614,7 @@ void PlayerActions::actMoveBottomCardToExile()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
- cmd.set_target_zone("rfg");
+ cmd.set_target_zone(ZoneNames::EXILE);
cmd.set_x(0);
cmd.set_y(0);
@@ -622,22 +623,22 @@ void PlayerActions::actMoveBottomCardToExile()
void PlayerActions::actMoveBottomCardsToGrave()
{
- moveBottomCardsTo("grave", tr("grave"), false);
+ moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), false);
}
void PlayerActions::actMoveBottomCardsToGraveFaceDown()
{
- moveBottomCardsTo("grave", tr("grave"), true);
+ moveBottomCardsTo(ZoneNames::GRAVE, tr("grave"), true);
}
void PlayerActions::actMoveBottomCardsToExile()
{
- moveBottomCardsTo("rfg", tr("exile"), false);
+ moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), false);
}
void PlayerActions::actMoveBottomCardsToExileFaceDown()
{
- moveBottomCardsTo("rfg", tr("exile"), true);
+ moveBottomCardsTo(ZoneNames::EXILE, tr("exile"), true);
}
void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &zoneDisplayName, bool faceDown)
@@ -661,7 +662,7 @@ void PlayerActions::moveBottomCardsTo(const QString &targetZone, const QString &
defaultNumberBottomCards = number;
Command_MoveCard cmd;
- cmd.set_start_zone("deck");
+ cmd.set_start_zone(ZoneNames::DECK);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
cmd.set_target_zone(targetZone.toStdString());
cmd.set_x(0);
@@ -686,7 +687,7 @@ void PlayerActions::actMoveBottomCardToTop()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
- cmd.set_target_zone("deck");
+ cmd.set_target_zone(ZoneNames::DECK);
cmd.set_x(0); // top of deck
cmd.set_y(0);
@@ -756,7 +757,7 @@ void PlayerActions::actDrawBottomCard()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
- cmd.set_target_zone("hand");
+ cmd.set_target_zone(ZoneNames::HAND);
cmd.set_x(0);
cmd.set_y(0);
@@ -782,9 +783,9 @@ void PlayerActions::actDrawBottomCards()
defaultNumberBottomCards = number;
Command_MoveCard cmd;
- cmd.set_start_zone("deck");
+ cmd.set_start_zone(ZoneNames::DECK);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
- cmd.set_target_zone("hand");
+ cmd.set_target_zone(ZoneNames::HAND);
cmd.set_x(0);
cmd.set_y(0);
@@ -803,7 +804,7 @@ void PlayerActions::actMoveBottomCardToPlay()
Command_MoveCard cmd;
cmdSetBottomCard(cmd);
- cmd.set_target_zone("stack");
+ cmd.set_target_zone(ZoneNames::STACK);
cmd.set_x(-1);
cmd.set_y(0);
@@ -820,13 +821,13 @@ void PlayerActions::actMoveBottomCardToPlayFaceDown()
int lastCard = zone->getCards().size() - 1;
Command_MoveCard cmd;
- cmd.set_start_zone("deck");
+ cmd.set_start_zone(ZoneNames::DECK);
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
cardToMove->set_card_id(lastCard);
cardToMove->set_face_down(true);
cmd.set_target_player_id(player->getPlayerInfo()->getId());
- cmd.set_target_zone("table");
+ cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_x(-1);
cmd.set_y(0);
@@ -836,7 +837,7 @@ void PlayerActions::actMoveBottomCardToPlayFaceDown()
void PlayerActions::actUntapAll()
{
Command_SetCardAttr cmd;
- cmd.set_zone("table");
+ cmd.set_zone(ZoneNames::TABLE);
cmd.set_attribute(AttrTapped);
cmd.set_attr_value("0");
@@ -886,7 +887,7 @@ void PlayerActions::actCreateAnotherToken()
}
Command_CreateToken cmd;
- cmd.set_zone("table");
+ cmd.set_zone(ZoneNames::TABLE);
cmd.set_card_name(lastTokenInfo.name.toStdString());
cmd.set_card_provider_id(lastTokenInfo.providerId.toStdString());
cmd.set_color(lastTokenInfo.color.toStdString());
@@ -1067,7 +1068,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, const
// move card onto table first if attaching from some other zone
// we only do this for AttachTo because cross-zone TransformInto is already handled server-side
- if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != "table") {
+ if (attachType == CardRelationType::AttachTo && sourceCard->getZone()->getName() != ZoneNames::TABLE) {
playCardToTable(sourceCard, false);
}
@@ -1093,7 +1094,7 @@ void PlayerActions::createCard(const CardItem *sourceCard,
// create the token for the related card
Command_CreateToken cmd;
- cmd.set_zone("table");
+ cmd.set_zone(ZoneNames::TABLE);
cmd.set_card_name(cardInfo->getName().toStdString());
switch (cardInfo->getColors().size()) {
case 0:
@@ -1122,12 +1123,12 @@ void PlayerActions::createCard(const CardItem *sourceCard,
switch (attachType) {
case CardRelationType::DoesNotAttach:
- cmd.set_target_zone("table");
+ cmd.set_target_zone(ZoneNames::TABLE);
cmd.set_card_provider_id(relatedCard.getPrinting().getUuid().toStdString());
break;
case CardRelationType::AttachTo:
- cmd.set_target_zone("table"); // We currently only support creating tokens on the table
+ cmd.set_target_zone(ZoneNames::TABLE); // We currently only support creating tokens on the table
cmd.set_card_provider_id(relatedCard.getPrinting().getUuid().toStdString());
cmd.set_target_card_id(sourceCard->getId());
cmd.set_target_mode(Command_CreateToken::ATTACH_TO);
@@ -1135,7 +1136,7 @@ void PlayerActions::createCard(const CardItem *sourceCard,
case CardRelationType::TransformInto:
// allow cards to directly transform on stack
- cmd.set_zone(sourceCard->getZone()->getName() == "stack" ? "stack" : "table");
+ cmd.set_zone(sourceCard->getZone()->getName() == ZoneNames::STACK ? ZoneNames::STACK : ZoneNames::TABLE);
// Transform card zone changes are handled server-side
cmd.set_target_zone(sourceCard->getZone()->getName().toStdString());
cmd.set_target_card_id(sourceCard->getId());
@@ -1250,7 +1251,7 @@ void PlayerActions::actMoveCardXCardsFromTop()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
- cmd->set_target_zone("deck");
+ cmd->set_target_zone(ZoneNames::DECK);
cmd->set_x(number);
cmd->set_y(0);
commandList.append(cmd);
@@ -1639,7 +1640,7 @@ void PlayerActions::playSelectedCards(const bool faceDown)
[](const auto &card1, const auto &card2) { return card1->getId() > card2->getId(); });
for (auto &card : selectedCards) {
- if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != "table") {
+ if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != ZoneNames::TABLE) {
playCard(card, faceDown);
}
}
@@ -1692,7 +1693,7 @@ void PlayerActions::actRevealHand(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
- cmd.set_zone_name("hand");
+ cmd.set_zone_name(ZoneNames::HAND);
sendGameCommand(cmd);
}
@@ -1703,7 +1704,7 @@ void PlayerActions::actRevealRandomHandCard(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
- cmd.set_zone_name("hand");
+ cmd.set_zone_name(ZoneNames::HAND);
cmd.add_card_id(RANDOM_CARD_FROM_ZONE);
sendGameCommand(cmd);
@@ -1715,7 +1716,7 @@ void PlayerActions::actRevealLibrary(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
- cmd.set_zone_name("deck");
+ cmd.set_zone_name(ZoneNames::DECK);
sendGameCommand(cmd);
}
@@ -1726,7 +1727,7 @@ void PlayerActions::actLendLibrary(int lendToPlayerId)
if (lendToPlayerId != -1) {
cmd.set_player_id(lendToPlayerId);
}
- cmd.set_zone_name("deck");
+ cmd.set_zone_name(ZoneNames::DECK);
cmd.set_grant_write_access(true);
sendGameCommand(cmd);
@@ -1739,7 +1740,7 @@ void PlayerActions::actRevealTopCards(int revealToPlayerId, int amount)
cmd.set_player_id(revealToPlayerId);
}
- cmd.set_zone_name("deck");
+ cmd.set_zone_name(ZoneNames::DECK);
cmd.set_top_cards(amount);
// backward compatibility: servers before #1051 only permits to reveal the first card
cmd.add_card_id(0);
@@ -1753,7 +1754,7 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId)
if (revealToPlayerId != -1) {
cmd.set_player_id(revealToPlayerId);
}
- cmd.set_zone_name("grave");
+ cmd.set_zone_name(ZoneNames::GRAVE);
cmd.add_card_id(RANDOM_CARD_FROM_ZONE);
sendGameCommand(cmd);
}
@@ -1816,7 +1817,7 @@ void PlayerActions::cardMenuAction()
}
case cmClone: {
auto *cmd = new Command_CreateToken;
- cmd->set_zone("table");
+ cmd->set_zone(ZoneNames::TABLE);
cmd->set_card_name(card->getName().toStdString());
cmd->set_card_provider_id(card->getProviderId().toStdString());
cmd->set_color(card->getColor().toStdString());
@@ -1858,13 +1859,13 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
- cmd->set_target_zone("deck");
+ cmd->set_target_zone(ZoneNames::DECK);
cmd->set_x(0);
cmd->set_y(0);
if (idList.card_size() > 1) {
auto *scmd = new Command_Shuffle;
- scmd->set_zone_name("deck");
+ scmd->set_zone_name(ZoneNames::DECK);
scmd->set_start(0);
scmd->set_end(idList.card_size() - 1); // inclusive, the indexed card at end will be shuffled
// Server process events backwards, so...
@@ -1880,13 +1881,13 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
- cmd->set_target_zone("deck");
+ cmd->set_target_zone(ZoneNames::DECK);
cmd->set_x(-1);
cmd->set_y(0);
if (idList.card_size() > 1) {
auto *scmd = new Command_Shuffle;
- scmd->set_zone_name("deck");
+ scmd->set_zone_name(ZoneNames::DECK);
scmd->set_start(-idList.card_size());
scmd->set_end(-1);
// Server process events backwards, so...
@@ -1902,7 +1903,7 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
- cmd->set_target_zone("hand");
+ cmd->set_target_zone(ZoneNames::HAND);
cmd->set_x(0);
cmd->set_y(0);
commandList.append(cmd);
@@ -1914,7 +1915,7 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
- cmd->set_target_zone("grave");
+ cmd->set_target_zone(ZoneNames::GRAVE);
cmd->set_x(0);
cmd->set_y(0);
commandList.append(cmd);
@@ -1926,7 +1927,7 @@ void PlayerActions::cardMenuAction()
cmd->set_start_zone(startZone.toStdString());
cmd->mutable_cards_to_move()->CopyFrom(idList);
cmd->set_target_player_id(player->getPlayerInfo()->getId());
- cmd->set_target_zone("rfg");
+ cmd->set_target_zone(ZoneNames::EXILE);
cmd->set_x(0);
cmd->set_y(0);
commandList.append(cmd);
diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp
index 331605918..f4c3840e0 100644
--- a/cockatrice/src/game/player/player_event_handler.cpp
+++ b/cockatrice/src/game/player/player_event_handler.cpp
@@ -30,6 +30,7 @@
#include
#include
#include
+#include
PlayerEventHandler::PlayerEventHandler(Player *_player) : player(_player)
{
@@ -321,8 +322,8 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv
}
player->getPlayerMenu()->updateCardMenu(card);
- if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == "deck" &&
- targetZone->getName() == "stack") {
+ if (player->getPlayerActions()->isMovingCardsUntil() && startZoneString == ZoneNames::DECK &&
+ targetZone->getName() == ZoneNames::STACK) {
player->getPlayerActions()->moveOneCardUntil(card);
}
}
diff --git a/cockatrice/src/game/zones/logic/card_zone_logic.cpp b/cockatrice/src/game/zones/logic/card_zone_logic.cpp
index 1872ba95e..e917e4ad7 100644
--- a/cockatrice/src/game/zones/logic/card_zone_logic.cpp
+++ b/cockatrice/src/game/zones/logic/card_zone_logic.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
/**
* @param _player the player that the zone belongs to
@@ -174,9 +175,9 @@ void CardZoneLogic::clearContents()
QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) const
{
QString ownerName = player->getPlayerInfo()->getName();
- if (name == "hand")
+ if (name == ZoneNames::HAND)
return (theirOwn ? tr("their hand", "nominative") : tr("%1's hand", "nominative").arg(ownerName));
- else if (name == "deck")
+ else if (name == ZoneNames::DECK)
switch (gc) {
case CaseLookAtZone:
return (theirOwn ? tr("their library", "look at zone")
@@ -192,11 +193,11 @@ QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) cons
default:
return (theirOwn ? tr("their library", "nominative") : tr("%1's library", "nominative").arg(ownerName));
}
- else if (name == "grave")
+ else if (name == ZoneNames::GRAVE)
return (theirOwn ? tr("their graveyard", "nominative") : tr("%1's graveyard", "nominative").arg(ownerName));
- else if (name == "rfg")
+ else if (name == ZoneNames::EXILE)
return (theirOwn ? tr("their exile", "nominative") : tr("%1's exile", "nominative").arg(ownerName));
- else if (name == "sb")
+ else if (name == ZoneNames::SIDEBOARD)
switch (gc) {
case CaseLookAtZone:
return (theirOwn ? tr("their sideboard", "look at zone")
diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp
index a020e4255..b6ac2150b 100644
--- a/cockatrice/src/game/zones/table_zone.cpp
+++ b/cockatrice/src/game/zones/table_zone.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
const QColor TableZone::BACKGROUND_COLOR = QColor(100, 100, 100);
const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80);
@@ -195,7 +196,7 @@ void TableZone::toggleTapped()
auto isCardOnTable = [](const QGraphicsItem *item) {
if (auto card = qgraphicsitem_cast(item)) {
- return card->getZone()->getName() == "table";
+ return card->getZone()->getName() == ZoneNames::TABLE;
}
return false;
};
diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp
index 00561bef2..f04bcc849 100644
--- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp
+++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp
@@ -48,6 +48,7 @@
#include
#include
#include
+#include
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
int _playerId,
@@ -190,8 +191,8 @@ shouldDestroyOnMove(const Server_Card *card, const Server_CardZone *startZone, c
}
// Allow tokens on the stack
- if ((startZone->getName() == "table" || startZone->getName() == "stack") &&
- (targetZone->getName() == "table" || targetZone->getName() == "stack")) {
+ if ((startZone->getName() == ZoneNames::TABLE || startZone->getName() == ZoneNames::STACK) &&
+ (targetZone->getName() == ZoneNames::TABLE || targetZone->getName() == ZoneNames::STACK)) {
return false;
}
@@ -264,7 +265,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
}
// do not allow attached cards to move around on the table
- if (card->getParentCard() && targetzone->getName() == "table") {
+ if (card->getParentCard() && targetzone->getName() == ZoneNames::TABLE) {
continue;
}
@@ -347,7 +348,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
newX = targetzone->getFreeGridColumn(newX, yCoord, card->getName(), faceDown);
} else {
yCoord = 0;
- card->resetState(targetzone->getName() == "stack");
+ card->resetState(targetzone->getName() == ZoneNames::STACK);
}
targetzone->insertCard(card, newX, yCoord);
diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp
index eae23dacf..2224ddb13 100644
--- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp
+++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp
@@ -51,6 +51,7 @@
#include
#include
#include
+#include
Server_Game::Server_Game(const ServerInfo_User &_creatorInfo,
int _gameId,
@@ -832,7 +833,7 @@ void Server_Game::returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPl
QMutexLocker locker(&gameMutex);
// Return cards to their rightful owners before conceding the game
static const QRegularExpression ownerRegex{"Owner: ?([^\n]+)"};
- const auto &playerTable = player->getZones().value("table");
+ const auto &playerTable = player->getZones().value(ZoneNames::TABLE);
for (const auto &card : playerTable->getCards()) {
if (card == nullptr) {
continue;
@@ -858,7 +859,7 @@ void Server_Game::returnCardsFromPlayer(GameEventStorage &ges, Server_AbstractPl
continue;
}
- const auto &targetZone = otherPlayer->getZones().value("table");
+ const auto &targetZone = otherPlayer->getZones().value(ZoneNames::TABLE);
if (playerTable == nullptr || targetZone == nullptr) {
continue;
diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp
index e62f861a9..1175e4b57 100644
--- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp
+++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp
@@ -47,6 +47,7 @@
#include
#include
#include
+#include
Server_Player::Server_Player(Server_Game *_game,
int _playerId,
@@ -80,15 +81,15 @@ void Server_Player::setupZones()
// ------------------------------------------------------------------
// Create zones
- auto *deckZone = new Server_CardZone(this, "deck", false, ServerInfo_Zone::HiddenZone);
+ auto *deckZone = new Server_CardZone(this, ZoneNames::DECK, false, ServerInfo_Zone::HiddenZone);
addZone(deckZone);
- auto *sbZone = new Server_CardZone(this, "sb", false, ServerInfo_Zone::HiddenZone);
+ auto *sbZone = new Server_CardZone(this, ZoneNames::SIDEBOARD, false, ServerInfo_Zone::HiddenZone);
addZone(sbZone);
- addZone(new Server_CardZone(this, "table", true, ServerInfo_Zone::PublicZone));
- addZone(new Server_CardZone(this, "hand", false, ServerInfo_Zone::PrivateZone));
- addZone(new Server_CardZone(this, "stack", false, ServerInfo_Zone::PublicZone));
- addZone(new Server_CardZone(this, "grave", false, ServerInfo_Zone::PublicZone));
- addZone(new Server_CardZone(this, "rfg", false, ServerInfo_Zone::PublicZone));
+ addZone(new Server_CardZone(this, ZoneNames::TABLE, true, ServerInfo_Zone::PublicZone));
+ addZone(new Server_CardZone(this, ZoneNames::HAND, false, ServerInfo_Zone::PrivateZone));
+ addZone(new Server_CardZone(this, ZoneNames::STACK, false, ServerInfo_Zone::PublicZone));
+ addZone(new Server_CardZone(this, ZoneNames::GRAVE, false, ServerInfo_Zone::PublicZone));
+ addZone(new Server_CardZone(this, ZoneNames::EXILE, false, ServerInfo_Zone::PublicZone));
addCounter(new Server_Counter(0, "life", makeColor(255, 255, 255), 25, game->getStartingLifeTotal()));
addCounter(new Server_Counter(1, "w", makeColor(255, 255, 150), 20, 0));
@@ -164,8 +165,8 @@ void Server_Player::addCounter(Server_Counter *counter)
Response::ResponseCode Server_Player::drawCards(GameEventStorage &ges, int number)
{
- Server_CardZone *deckZone = zones.value("deck");
- Server_CardZone *handZone = zones.value("hand");
+ Server_CardZone *deckZone = zones.value(ZoneNames::DECK);
+ Server_CardZone *handZone = zones.value(ZoneNames::HAND);
if (deckZone->getCards().size() < number) {
number = deckZone->getCards().size();
}
@@ -210,7 +211,7 @@ void Server_Player::onCardBeingMoved(GameEventStorage &ges,
// "Undo draw" should only remain valid if the just-drawn card stays within the user's hand (e.g., they only
// reorder their hand). If a just-drawn card leaves the hand then remove cards before it from the list
// (Ignore the case where the card is currently being un-drawn.)
- if (startzone->getName() == "hand" && targetzone->getName() != "hand" && !undoingDraw) {
+ if (startzone->getName() == ZoneNames::HAND && targetzone->getName() != ZoneNames::HAND && !undoingDraw) {
int index = lastDrawList.lastIndexOf(card->getId());
if (index != -1) {
lastDrawList.erase(lastDrawList.begin(), lastDrawList.begin() + index);
@@ -326,11 +327,11 @@ Server_Player::cmdShuffle(const Command_Shuffle &cmd, ResponseContainer & /*rc*/
return Response::RespContextError;
}
- if (cmd.has_zone_name() && cmd.zone_name() != "deck") {
+ if (cmd.has_zone_name() && cmd.zone_name() != ZoneNames::DECK) {
return Response::RespFunctionNotAllowed;
}
- Server_CardZone *zone = zones.value("deck");
+ Server_CardZone *zone = zones.value(ZoneNames::DECK);
if (!zone) {
return Response::RespNameNotFound;
}
@@ -357,8 +358,8 @@ Server_Player::cmdMulligan(const Command_Mulligan &cmd, ResponseContainer & /*rc
return Response::RespContextError;
}
- Server_CardZone *hand = zones.value("hand");
- Server_CardZone *_deck = zones.value("deck");
+ Server_CardZone *hand = zones.value(ZoneNames::HAND);
+ Server_CardZone *_deck = zones.value(ZoneNames::DECK);
int number = cmd.number();
if (!hand->getCards().isEmpty()) {
@@ -414,8 +415,8 @@ Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer &
Response::ResponseCode retVal;
auto *cardToMove = new CardToMove;
cardToMove->set_card_id(lastDrawList.takeLast());
- retVal = moveCard(ges, zones.value("hand"), QList() << cardToMove, zones.value("deck"), 0, 0,
- false, true);
+ retVal = moveCard(ges, zones.value(ZoneNames::HAND), QList() << cardToMove,
+ zones.value(ZoneNames::DECK), 0, 0, false, true);
delete cardToMove;
return retVal;
diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt
index 0575c260f..c0c7d8cc9 100644
--- a/libcockatrice_utility/CMakeLists.txt
+++ b/libcockatrice_utility/CMakeLists.txt
@@ -10,8 +10,13 @@ set(UTILITY_SOURCES libcockatrice/utility/expression.cpp libcockatrice/utility/l
)
set(UTILITY_HEADERS
- libcockatrice/utility/color.h libcockatrice/utility/expression.h libcockatrice/utility/levenshtein.h
- libcockatrice/utility/macros.h libcockatrice/utility/passwordhasher.h libcockatrice/utility/trice_limits.h
+ libcockatrice/utility/color.h
+ libcockatrice/utility/expression.h
+ libcockatrice/utility/levenshtein.h
+ libcockatrice/utility/macros.h
+ libcockatrice/utility/passwordhasher.h
+ libcockatrice/utility/trice_limits.h
+ libcockatrice/utility/zone_names.h
)
add_library(libcockatrice_utility STATIC ${UTILITY_SOURCES} ${UTILITY_HEADERS})
diff --git a/libcockatrice_utility/libcockatrice/utility/zone_names.h b/libcockatrice_utility/libcockatrice/utility/zone_names.h
new file mode 100644
index 000000000..d1463de6a
--- /dev/null
+++ b/libcockatrice_utility/libcockatrice/utility/zone_names.h
@@ -0,0 +1,19 @@
+#ifndef ZONE_NAMES_H
+#define ZONE_NAMES_H
+
+namespace ZoneNames
+{
+// Protocol-level zone identifiers shared between client and server.
+// These must match exactly across all components.
+
+constexpr const char *TABLE = "table";
+constexpr const char *GRAVE = "grave";
+constexpr const char *EXILE = "rfg"; // "removed from game"
+constexpr const char *HAND = "hand";
+constexpr const char *DECK = "deck";
+constexpr const char *SIDEBOARD = "sb";
+constexpr const char *STACK = "stack";
+
+} // namespace ZoneNames
+
+#endif // ZONE_NAMES_H
From 20ad9af989ffd2caace45170fc01af46fd43d818 Mon Sep 17 00:00:00 2001
From: RickyRister <42636155+RickyRister@users.noreply.github.com>
Date: Thu, 12 Mar 2026 11:00:32 -0700
Subject: [PATCH 036/109] [CacheSettings] Refactor country list creation
(#6687)
---
.../src/client/settings/cache_settings.cpp | 266 +-----------------
1 file changed, 15 insertions(+), 251 deletions(-)
diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp
index 30093fd52..e7a5d114e 100644
--- a/cockatrice/src/client/settings/cache_settings.cpp
+++ b/cockatrice/src/client/settings/cache_settings.cpp
@@ -1115,257 +1115,21 @@ void SettingsCache::setClientVersion(const QString &_clientVersion)
QStringList SettingsCache::getCountries() const
{
- static QStringList countries = QStringList() << "ad"
- << "ae"
- << "af"
- << "ag"
- << "ai"
- << "al"
- << "am"
- << "ao"
- << "aq"
- << "ar"
- << "as"
- << "at"
- << "au"
- << "aw"
- << "ax"
- << "az"
- << "ba"
- << "bb"
- << "bd"
- << "be"
- << "bf"
- << "bg"
- << "bh"
- << "bi"
- << "bj"
- << "bl"
- << "bm"
- << "bn"
- << "bo"
- << "bq"
- << "br"
- << "bs"
- << "bt"
- << "bv"
- << "bw"
- << "by"
- << "bz"
- << "ca"
- << "cc"
- << "cd"
- << "cf"
- << "cg"
- << "ch"
- << "ci"
- << "ck"
- << "cl"
- << "cm"
- << "cn"
- << "co"
- << "cr"
- << "cu"
- << "cv"
- << "cw"
- << "cx"
- << "cy"
- << "cz"
- << "de"
- << "dj"
- << "dk"
- << "dm"
- << "do"
- << "dz"
- << "ec"
- << "ee"
- << "eg"
- << "eh"
- << "er"
- << "es"
- << "et"
- << "eu"
- << "fi"
- << "fj"
- << "fk"
- << "fm"
- << "fo"
- << "fr"
- << "ga"
- << "gb"
- << "gd"
- << "ge"
- << "gf"
- << "gg"
- << "gh"
- << "gi"
- << "gl"
- << "gm"
- << "gn"
- << "gp"
- << "gq"
- << "gr"
- << "gs"
- << "gt"
- << "gu"
- << "gw"
- << "gy"
- << "hk"
- << "hm"
- << "hn"
- << "hr"
- << "ht"
- << "hu"
- << "id"
- << "ie"
- << "il"
- << "im"
- << "in"
- << "io"
- << "iq"
- << "ir"
- << "is"
- << "it"
- << "je"
- << "jm"
- << "jo"
- << "jp"
- << "ke"
- << "kg"
- << "kh"
- << "ki"
- << "km"
- << "kn"
- << "kp"
- << "kr"
- << "kw"
- << "ky"
- << "kz"
- << "la"
- << "lb"
- << "lc"
- << "li"
- << "lk"
- << "lr"
- << "ls"
- << "lt"
- << "lu"
- << "lv"
- << "ly"
- << "ma"
- << "mc"
- << "md"
- << "me"
- << "mf"
- << "mg"
- << "mh"
- << "mk"
- << "ml"
- << "mm"
- << "mn"
- << "mo"
- << "mp"
- << "mq"
- << "mr"
- << "ms"
- << "mt"
- << "mu"
- << "mv"
- << "mw"
- << "mx"
- << "my"
- << "mz"
- << "na"
- << "nc"
- << "ne"
- << "nf"
- << "ng"
- << "ni"
- << "nl"
- << "no"
- << "np"
- << "nr"
- << "nu"
- << "nz"
- << "om"
- << "pa"
- << "pe"
- << "pf"
- << "pg"
- << "ph"
- << "pk"
- << "pl"
- << "pm"
- << "pn"
- << "pr"
- << "ps"
- << "pt"
- << "pw"
- << "py"
- << "qa"
- << "re"
- << "ro"
- << "rs"
- << "ru"
- << "rw"
- << "sa"
- << "sb"
- << "sc"
- << "sd"
- << "se"
- << "sg"
- << "sh"
- << "si"
- << "sj"
- << "sk"
- << "sl"
- << "sm"
- << "sn"
- << "so"
- << "sr"
- << "ss"
- << "st"
- << "sv"
- << "sx"
- << "sy"
- << "sz"
- << "tc"
- << "td"
- << "tf"
- << "tg"
- << "th"
- << "tj"
- << "tk"
- << "tl"
- << "tm"
- << "tn"
- << "to"
- << "tr"
- << "tt"
- << "tv"
- << "tw"
- << "tz"
- << "ua"
- << "ug"
- << "um"
- << "us"
- << "uy"
- << "uz"
- << "va"
- << "vc"
- << "ve"
- << "vg"
- << "vi"
- << "vn"
- << "vu"
- << "wf"
- << "ws"
- << "xk"
- << "ye"
- << "yt"
- << "za"
- << "zm"
- << "zw";
+ static const QStringList countries = {
+ "ad", "ae", "af", "ag", "ai", "al", "am", "ao", "aq", "ar", "as", "at", "au", "aw", "ax", "az", "ba", "bb",
+ "bd", "be", "bf", "bg", "bh", "bi", "bj", "bl", "bm", "bn", "bo", "bq", "br", "bs", "bt", "bv", "bw", "by",
+ "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cu", "cv", "cw", "cx",
+ "cy", "cz", "de", "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er", "es", "et", "eu", "fi", "fj",
+ "fk", "fm", "fo", "fr", "ga", "gb", "gd", "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq", "gr",
+ "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq",
+ "ir", "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz",
+ "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mf", "mg", "mh",
+ "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na", "nc",
+ "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl",
+ "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", "sc", "sd", "se",
+ "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "ss", "st", "sv", "sx", "sy", "sz", "tc", "td",
+ "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us",
+ "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wf", "ws", "xk", "ye", "yt", "za", "zm", "zw"};
return countries;
}
From aa4592dc9e26c2ea0d08ae68ab7d3b09b77797a8 Mon Sep 17 00:00:00 2001
From: DawnFire42
Date: Thu, 12 Mar 2026 17:30:01 -0400
Subject: [PATCH 037/109] Add local game options (#6669)
* Add local game options dialog. Introduces LocalGameOptions struct and DlgLocalGameOptions dialog to replace the previous QInputDialog for starting local games. Encapsulates game configuration with a simple interface that prevents parameter explosion as options are added. The dialog provides UI with settings persistence via SettingsCache
* integrate local game options into main window. Replaces QInputDialog with DlgLocalGameOptions in actSinglePlayer(). The startLocalGame() function now accepts LocalGameOptions, enabling configuration of starting life total and spectator visibility in addition to player count. Also adds user documentation for the local game options flow.
* Removed superfluous documentation file
* removed spectator option and moved structure definition
* Now remember settings separately and & shortcuts removed
* re-run checks
---
cockatrice/CMakeLists.txt | 1 +
.../src/client/settings/cache_settings.cpp | 25 ++++++
.../src/client/settings/cache_settings.h | 21 +++++
.../dialogs/dlg_local_game_options.cpp | 83 +++++++++++++++++++
.../widgets/dialogs/dlg_local_game_options.h | 53 ++++++++++++
cockatrice/src/interface/window_main.cpp | 22 ++---
cockatrice/src/interface/window_main.h | 4 +-
.../extra-pages/user_documentation/index.md | 2 +-
8 files changed, 199 insertions(+), 12 deletions(-)
create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp
create mode 100644 cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index d675b809d..1ca3c77c2 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -39,6 +39,7 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
src/interface/widgets/dialogs/dlg_load_remote_deck.cpp
+ src/interface/widgets/dialogs/dlg_local_game_options.cpp
src/interface/widgets/dialogs/dlg_manage_sets.cpp
src/interface/widgets/dialogs/dlg_register.cpp
src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp
diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp
index e7a5d114e..1d8121b19 100644
--- a/cockatrice/src/client/settings/cache_settings.cpp
+++ b/cockatrice/src/client/settings/cache_settings.cpp
@@ -385,6 +385,13 @@ SettingsCache::SettingsCache()
defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool();
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
+
+ // Local game settings use "localgameoptions/" prefix to keep them separate
+ // from server game settings which use "game/" prefix
+ localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool();
+ localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt();
+ localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).toInt();
+
clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
}
@@ -1242,6 +1249,24 @@ void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
settings->setValue("game/remembergamesettings", rememberGameSettings);
}
+void SettingsCache::setLocalGameRememberSettings(bool value)
+{
+ localGameRememberSettings = value;
+ settings->setValue("localgameoptions/remembersettings", value);
+}
+
+void SettingsCache::setLocalGameMaxPlayers(int value)
+{
+ localGameMaxPlayers = value;
+ settings->setValue("localgameoptions/maxplayers", value);
+}
+
+void SettingsCache::setLocalGameStartingLifeTotal(int value)
+{
+ localGameStartingLifeTotal = value;
+ settings->setValue("localgameoptions/startinglifetotal", value);
+}
+
void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate)
{
notifyAboutUpdates = static_cast(_notifyaboutupdate);
diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h
index 5c5054105..2bbf85352 100644
--- a/cockatrice/src/client/settings/cache_settings.h
+++ b/cockatrice/src/client/settings/cache_settings.h
@@ -330,6 +330,12 @@ private:
[[nodiscard]] QString getSafeConfigFilePath(QString configEntry, QString defaultPath) const;
void loadPaths();
bool rememberGameSettings;
+
+ // Local game settings (separate from server game settings in game/*)
+ bool localGameRememberSettings;
+ int localGameMaxPlayers;
+ int localGameStartingLifeTotal;
+
QList releaseChannels;
bool isPortableBuild;
bool roundCardCorners;
@@ -862,6 +868,18 @@ public:
{
return rememberGameSettings;
}
+ [[nodiscard]] bool getLocalGameRememberSettings() const
+ {
+ return localGameRememberSettings;
+ }
+ [[nodiscard]] int getLocalGameMaxPlayers() const
+ {
+ return localGameMaxPlayers;
+ }
+ [[nodiscard]] int getLocalGameStartingLifeTotal() const
+ {
+ return localGameStartingLifeTotal;
+ }
[[nodiscard]] int getKeepAlive() const override
{
return keepalive;
@@ -1089,6 +1107,9 @@ public slots:
void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal);
void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad);
void setRememberGameSettings(const bool _rememberGameSettings);
+ void setLocalGameRememberSettings(bool value);
+ void setLocalGameMaxPlayers(int value);
+ void setLocalGameStartingLifeTotal(int value);
void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value);
void setStartupCardUpdateCheckPromptForUpdate(bool value);
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp
new file mode 100644
index 000000000..52466ff10
--- /dev/null
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.cpp
@@ -0,0 +1,83 @@
+#include "dlg_local_game_options.h"
+
+#include "../../../client/settings/cache_settings.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent)
+{
+ numberPlayersLabel = new QLabel(tr("Players:"), this);
+ numberPlayersEdit = new QSpinBox(this);
+ numberPlayersEdit->setMinimum(1);
+ numberPlayersEdit->setMaximum(8);
+ numberPlayersEdit->setValue(1);
+ numberPlayersLabel->setBuddy(numberPlayersEdit);
+
+ auto *generalGrid = new QGridLayout;
+ generalGrid->addWidget(numberPlayersLabel, 0, 0);
+ generalGrid->addWidget(numberPlayersEdit, 0, 1);
+ generalGroupBox = new QGroupBox(tr("General"), this);
+ generalGroupBox->setLayout(generalGrid);
+
+ startingLifeTotalLabel = new QLabel(tr("Starting life total:"), this);
+ startingLifeTotalEdit = new QSpinBox(this);
+ startingLifeTotalEdit->setMinimum(1);
+ startingLifeTotalEdit->setMaximum(99999);
+ startingLifeTotalEdit->setValue(20);
+ startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
+
+ auto *gameSetupGrid = new QGridLayout;
+ gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0);
+ gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1);
+ gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this);
+ gameSetupOptionsGroupBox->setLayout(gameSetupGrid);
+
+ rememberSettingsCheckBox = new QCheckBox(tr("Remember settings"), this);
+
+ buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
+ connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLocalGameOptions::actOK);
+ connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLocalGameOptions::reject);
+
+ auto *mainLayout = new QVBoxLayout;
+ mainLayout->addWidget(generalGroupBox);
+ mainLayout->addWidget(gameSetupOptionsGroupBox);
+ mainLayout->addWidget(rememberSettingsCheckBox);
+ mainLayout->addWidget(buttonBox);
+ setLayout(mainLayout);
+
+ rememberSettingsCheckBox->setChecked(SettingsCache::instance().getLocalGameRememberSettings());
+ if (rememberSettingsCheckBox->isChecked()) {
+ numberPlayersEdit->setValue(SettingsCache::instance().getLocalGameMaxPlayers());
+ startingLifeTotalEdit->setValue(SettingsCache::instance().getLocalGameStartingLifeTotal());
+ }
+
+ setWindowTitle(tr("Local game options"));
+ setFixedHeight(sizeHint().height());
+
+ numberPlayersEdit->setFocus();
+}
+
+void DlgLocalGameOptions::actOK()
+{
+ SettingsCache::instance().setLocalGameRememberSettings(rememberSettingsCheckBox->isChecked());
+ if (rememberSettingsCheckBox->isChecked()) {
+ SettingsCache::instance().setLocalGameMaxPlayers(numberPlayersEdit->value());
+ SettingsCache::instance().setLocalGameStartingLifeTotal(startingLifeTotalEdit->value());
+ }
+
+ accept();
+}
+
+LocalGameOptions DlgLocalGameOptions::getOptions() const
+{
+ return LocalGameOptions{
+ .numberPlayers = numberPlayersEdit->value(),
+ .startingLifeTotal = startingLifeTotalEdit->value(),
+ };
+}
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h
new file mode 100644
index 000000000..4307581a4
--- /dev/null
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_local_game_options.h
@@ -0,0 +1,53 @@
+/**
+ * @file dlg_local_game_options.h
+ * @ingroup RoomDialogs
+ * @brief Dialog for configuring local game options.
+ *
+ * Provides a user interface for setting up local games with configurable
+ * number of players and starting life total.
+ */
+
+#ifndef DLG_LOCAL_GAME_OPTIONS_H
+#define DLG_LOCAL_GAME_OPTIONS_H
+
+#include
+
+struct LocalGameOptions
+{
+ int numberPlayers = 1;
+ int startingLifeTotal = 20;
+};
+
+class QCheckBox;
+class QDialogButtonBox;
+class QGroupBox;
+class QLabel;
+class QSpinBox;
+
+class DlgLocalGameOptions : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit DlgLocalGameOptions(QWidget *parent = nullptr);
+
+ [[nodiscard]] LocalGameOptions getOptions() const;
+
+private slots:
+ void actOK();
+
+private:
+ QGroupBox *generalGroupBox;
+ QGroupBox *gameSetupOptionsGroupBox;
+
+ QLabel *numberPlayersLabel;
+ QSpinBox *numberPlayersEdit;
+
+ QLabel *startingLifeTotalLabel;
+ QSpinBox *startingLifeTotalEdit;
+
+ QCheckBox *rememberSettingsCheckBox;
+
+ QDialogButtonBox *buttonBox;
+};
+
+#endif // DLG_LOCAL_GAME_OPTIONS_H
diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp
index 3724ff29d..fbba6c3f5 100644
--- a/cockatrice/src/interface/window_main.cpp
+++ b/cockatrice/src/interface/window_main.cpp
@@ -27,6 +27,7 @@
#include "../interface/widgets/dialogs/dlg_forgot_password_challenge.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_request.h"
#include "../interface/widgets/dialogs/dlg_forgot_password_reset.h"
+#include "../interface/widgets/dialogs/dlg_local_game_options.h"
#include "../interface/widgets/dialogs/dlg_manage_sets.h"
#include "../interface/widgets/dialogs/dlg_register.h"
#include "../interface/widgets/dialogs/dlg_settings.h"
@@ -49,7 +50,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -225,16 +225,15 @@ void MainWindow::actDisconnect()
void MainWindow::actSinglePlayer()
{
- bool ok;
- int numberPlayers =
- QInputDialog::getInt(this, tr("Number of players"), tr("Please enter the number of players."), 1, 1, 8, 1, &ok);
- if (!ok)
+ DlgLocalGameOptions dlg(this);
+ if (dlg.exec() != QDialog::Accepted) {
return;
+ }
- startLocalGame(numberPlayers);
+ startLocalGame(dlg.getOptions());
}
-void MainWindow::startLocalGame(int numberPlayers)
+void MainWindow::startLocalGame(const LocalGameOptions &options)
{
aConnect->setEnabled(false);
aRegister->setEnabled(false);
@@ -248,7 +247,7 @@ void MainWindow::startLocalGame(int numberPlayers)
QList localClients;
localClients.append(mainClient);
- for (int i = 0; i < numberPlayers - 1; ++i) {
+ for (int i = 0; i < options.numberPlayers - 1; ++i) {
LocalServerInterface *slaveLsi = localServer->newConnection();
LocalClient *slaveClient =
new LocalClient(slaveLsi, tr("Player %1").arg(i + 2), SettingsCache::instance().getClientID(), this);
@@ -257,7 +256,8 @@ void MainWindow::startLocalGame(int numberPlayers)
tabSupervisor->startLocal(localClients);
Command_CreateGame createCommand;
- createCommand.set_max_players(static_cast(numberPlayers));
+ createCommand.set_max_players(static_cast(options.numberPlayers));
+ createCommand.set_starting_life_total(options.startingLifeTotal);
mainClient->sendCommand(LocalClient::prepareRoomCommand(createCommand, 0));
}
@@ -913,7 +913,9 @@ MainWindow::MainWindow(QWidget *parent)
void MainWindow::startupConfigCheck()
{
if (SettingsCache::instance().debug().getLocalGameOnStartup()) {
- startLocalGame(SettingsCache::instance().debug().getLocalGamePlayerCount());
+ LocalGameOptions options;
+ options.numberPlayers = SettingsCache::instance().debug().getLocalGamePlayerCount();
+ startLocalGame(options);
}
if (SettingsCache::instance().getCheckUpdatesOnStartup()) {
diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h
index 75b0f5062..ed6de5b0d 100644
--- a/cockatrice/src/interface/window_main.h
+++ b/cockatrice/src/interface/window_main.h
@@ -25,6 +25,8 @@
#ifndef WINDOW_H
#define WINDOW_H
+#include "widgets/dialogs/dlg_local_game_options.h"
+
#include
#include
#include
@@ -137,7 +139,7 @@ private:
void createCardUpdateProcess(bool background = false);
void exitCardDatabaseUpdate();
- void startLocalGame(int numberPlayers);
+ void startLocalGame(const LocalGameOptions &options);
QList tabMenus;
QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu;
diff --git a/doc/doxygen/extra-pages/user_documentation/index.md b/doc/doxygen/extra-pages/user_documentation/index.md
index 3f10a0ac6..d7e9d529d 100644
--- a/doc/doxygen/extra-pages/user_documentation/index.md
+++ b/doc/doxygen/extra-pages/user_documentation/index.md
@@ -1,5 +1,5 @@
@page user_reference User Reference
-
+
## Deck Management
- @subpage creating_decks
From 2b2a6db0810937ffaf935170d1001cbbe3305cac Mon Sep 17 00:00:00 2001
From: "transifex-integration[bot]"
<43880903+transifex-integration[bot]@users.noreply.github.com>
Date: Fri, 13 Mar 2026 21:22:48 +0100
Subject: [PATCH 038/109] Translate oracle/oracle_en@source.ts in it (#6692)
100% translated source file: 'oracle/oracle_en@source.ts'
on 'it'.
Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com>
---
oracle/translations/oracle_it.ts | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts
index 1765bbbca..bea5af275 100644
--- a/oracle/translations/oracle_it.ts
+++ b/oracle/translations/oracle_it.ts
@@ -173,7 +173,7 @@ e pedine che verranno usate da Cockatrice.
spoiler
-
+ spoiler
@@ -193,7 +193,7 @@ e pedine che verranno usate da Cockatrice.
Local file:
-
+ File nel pc:
@@ -203,7 +203,7 @@ e pedine che verranno usate da Cockatrice.