From 711386574d80bfe0bcdd502bed259732f6c0d7a2 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:50:57 +0200 Subject: [PATCH] [Client] Tell the user when their playmat art is blocked (#7533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Client] Tell the user when their playmat art is blocked A server DENY rule silently drops banned playmat art: SetPlaymat fails with no handler and DeckSelect strips the mat while answering RespOk. Surface both cases with the same critical message box the banner card art flow uses. DeckSelect strips are detected by comparing the requested mat, passed via PendingCommand extraData, against the server-echoed deck. Notices dedupe per card and reset on success. * [Client] Gate playmat blocked notice on RespFunctionNotAllowed per review Only SetPlaymat with RespFunctionNotAllowed signals a card art DENY rule. Older servers answer RespInvalidCommand and strip the mat from DeckSelect, so drop the DeckSelect comparison to avoid false blacklisted popups on pre-playmat servers. --------- Co-authored-by: Lukas BrĂ¼bach Co-authored-by: BruebachL --- .../deckview/deck_view_container.cpp | 22 +++++++++++++++++++ .../deckview/deck_view_container.h | 8 ++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/game_graphics/deckview/deck_view_container.cpp b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp index c97767ec7..a6e541fca 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view_container.cpp +++ b/cockatrice/src/game_graphics/deckview/deck_view_container.cpp @@ -354,6 +354,19 @@ void DeckViewContainer::resolveAndSendPlaymat() pp->set_vertical_offset(resolved.params.verticalOffset); pp->set_zoom(resolved.params.zoom); PendingCommand *playmatPend = parentGame->getGame()->getGameEventHandler()->prepareGameCommand(playmatCmd); + const QString sentName = resolved.card.name; + connect(playmatPend, &PendingCommand::finished, this, [this, sentName](const Response &r) { + // Only RespFunctionNotAllowed signals a card art DENY rule. Older servers that predate + // playmats answer RespInvalidCommand and strip the mat from DeckSelect, which must stay silent. + if (r.response_code() == Response::RespFunctionNotAllowed) { + if (!sentName.isEmpty()) { + notifyPlaymatBlacklisted( + sentName, tr("The selected playmat card is blacklisted on this server, so it was not applied.")); + } + } else if (r.response_code() == Response::RespOk && !sentName.isEmpty()) { + lastBlacklistedPlaymatNotice.clear(); + } + }); parentGame->getGame()->getGameEventHandler()->sendGameCommand(playmatPend, playerId); } @@ -412,6 +425,15 @@ void DeckViewContainer::deckSelectFinished(const Response &r) switchToDeckLoadedView(); } +void DeckViewContainer::notifyPlaymatBlacklisted(const QString &cardName, const QString &message) +{ + if (cardName.isEmpty() || cardName == lastBlacklistedPlaymatNotice) { + return; + } + lastBlacklistedPlaymatNotice = cardName; + QMessageBox::critical(this, tr("Error"), message); +} + void DeckViewContainer::readyStart() { sendReadyStartCommand(!readyStartButton->getState()); diff --git a/cockatrice/src/game_graphics/deckview/deck_view_container.h b/cockatrice/src/game_graphics/deckview/deck_view_container.h index 80b82b6e5..c064a5b5a 100644 --- a/cockatrice/src/game_graphics/deckview/deck_view_container.h +++ b/cockatrice/src/game_graphics/deckview/deck_view_container.h @@ -60,12 +60,14 @@ private: VisualDeckStorageWidget *visualDeckStorageWidget; TabGame *parentGame; int playerId; - int playmatRotationIndex = 0; ///< Per-match cursor for round-robin playmat mode. - DeckList currentDeck; ///< Cached deck for live settings re-resolution. - PlaymatInfo lastResolvedPlaymat; ///< Tracks last sent playmat to avoid repeats in random mode. + int playmatRotationIndex = 0; ///< Per-match cursor for round-robin playmat mode. + DeckList currentDeck; ///< Cached deck for live settings re-resolution. + PlaymatInfo lastResolvedPlaymat; ///< Tracks last sent playmat to avoid repeats in random mode. + QString lastBlacklistedPlaymatNotice; ///< Card name already reported as blacklisted, to avoid repeat popups. void tryCreateVisualDeckStorageWidget(); void sendReadyStartCommand(bool ready); + void notifyPlaymatBlacklisted(const QString &cardName, const QString &message); private slots: void switchToDeckSelectView(); void switchToDeckLoadedView();