From 59ce70afc516d428706d09748287a3a13db2e308 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 2 Mar 2026 01:59:18 +0100 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] [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 6/9] [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 7/9] [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 8/9] 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 9/9] 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);