From be2883ff012e9aae9fd6c748e2d54c77c1af94eb Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Sun, 26 Jul 2026 12:14:15 -0600 Subject: [PATCH 1/8] Progress. --- cockatrice/src/client/settings/shortcuts_settings.h | 3 +++ cockatrice/src/game/board/card_state.h | 1 + cockatrice/src/game_graphics/board/card_item.h | 5 +++++ cockatrice/src/game_graphics/player/menu/card_menu.cpp | 4 ++++ cockatrice/src/game_graphics/player/menu/card_menu.h | 2 +- 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 45e2c4fca..8ec6da63a 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -504,6 +504,9 @@ private: {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping"), parseSequenceString("Alt+U"), ShortcutGroup::Playing_Area)}, + {"Player/aDoesntUntapOnce", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping Once"), + parseSequenceString(""), + ShortcutGroup::Playing_Area)}, {"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"), parseSequenceString("Alt+F"), ShortcutGroup::Playing_Area)}, diff --git a/cockatrice/src/game/board/card_state.h b/cockatrice/src/game/board/card_state.h index 0498b1aa2..6d13030e8 100644 --- a/cockatrice/src/game/board/card_state.h +++ b/cockatrice/src/game/board/card_state.h @@ -16,6 +16,7 @@ private: QString annotation; QString pt; bool doesntUntap = false; + bool doesntUntapOnce = false; bool destroyOnZoneChange = false; CardItem *attachedTo = nullptr; diff --git a/cockatrice/src/game_graphics/board/card_item.h b/cockatrice/src/game_graphics/board/card_item.h index 8efcd085d..7d22c16e1 100644 --- a/cockatrice/src/game_graphics/board/card_item.h +++ b/cockatrice/src/game_graphics/board/card_item.h @@ -105,6 +105,11 @@ public: return state->getDoesntUntap(); } void setDoesntUntap(bool _doesntUntap); + [[nodiscard]] bool getDoesntUntapOnce() const + { + return state->getDoesntUntapOnce(); + } + void setDoesntUntapOnce(bool _doesntUntapOnce); [[nodiscard]] QString getPT() const { return state->getPT(); diff --git a/cockatrice/src/game_graphics/player/menu/card_menu.cpp b/cockatrice/src/game_graphics/player/menu/card_menu.cpp index aa94c3be7..fdf4bb50f 100644 --- a/cockatrice/src/game_graphics/player/menu/card_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/card_menu.cpp @@ -73,6 +73,7 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho // Actions using invoke (type dispatch, need selection) aTap = makeAction(this, invoke(cmTap)); aDoesntUntap = makeAction(this, invoke(cmDoesntUntap), /*checkable=*/true, card && card->getDoesntUntap()); + aDoesntUntapOnce = makeAction(this, invoke(cmDoesntUntapOnce), /*checkable=*/true, card && card->getDoesntUntapOnce()); aFlip = makeAction(this, invoke(cmFlip)); aPeek = makeAction(this, invoke(cmPeek)); aClone = makeAction(this, invoke(cmClone)); @@ -196,6 +197,7 @@ void CardMenu::createTableMenu(bool canModifyCard) addAction(aTap); addAction(aDoesntUntap); + addAction(aDoesntUntapOnce); addAction(aFlip); if (card->getFaceDown()) { addAction(aPeek); @@ -491,6 +493,7 @@ void CardMenu::retranslateUi() //: Turn sideways or back again aTap->setText(tr("&Tap / Untap")); aDoesntUntap->setText(tr("Skip &untapping")); + aDoesntUntapOnce->setText(tr("Skip &untapping once")); //: Turn face up/face down aFlip->setText(tr("T&urn Over")); // Only the user facing names in client got renamed to "turn over" // All code and proto bits are still unchanged (flip) for compatibility reasons @@ -529,6 +532,7 @@ void CardMenu::setShortcutsActive() aTap->setShortcuts(shortcuts.getShortcut("Player/aTap")); aDoesntUntap->setShortcuts(shortcuts.getShortcut("Player/aDoesntUntap")); + aDoesntUntapOnce->setShortcuts(shortcuts.getShortcut("Player/aDoesntUntapOnce")); aFlip->setShortcuts(shortcuts.getShortcut("Player/aFlip")); aPeek->setShortcuts(shortcuts.getShortcut("Player/aPeek")); aClone->setShortcuts(shortcuts.getShortcut("Player/aClone")); diff --git a/cockatrice/src/game_graphics/player/menu/card_menu.h b/cockatrice/src/game_graphics/player/menu/card_menu.h index d67ef3876..e6bb8616b 100644 --- a/cockatrice/src/game_graphics/player/menu/card_menu.h +++ b/cockatrice/src/game_graphics/player/menu/card_menu.h @@ -37,7 +37,7 @@ public: QAction *aClone; QAction *aSelectAll, *aSelectRow, *aSelectColumn; QAction *aDrawArrow; - QAction *aTap, *aDoesntUntap; + QAction *aTap, *aDoesntUntap, *aDoesntUntapOnce; QAction *aFlip, *aPeek; QAction *aAttach, *aUnattach; QAction *aSetAnnotation; From 76c45562898760d7c06f78825bdec3de64df272b Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Wed, 12 Aug 2026 01:53:59 -0600 Subject: [PATCH 2/8] Progress. --- cockatrice/src/client/settings/shortcuts_settings.h | 2 +- cockatrice/src/game/board/card_state.cpp | 10 ++++++++++ cockatrice/src/game/board/card_state.h | 8 ++++++++ cockatrice/src/game/player/player_event_handler.cpp | 6 ++++++ cockatrice/src/game/player/player_event_handler.h | 1 + cockatrice/src/game_graphics/board/card_item.cpp | 8 ++++++++ .../src/game_graphics/log/message_log_widget.cpp | 12 ++++++++++++ .../src/game_graphics/log/message_log_widget.h | 1 + .../game_graphics/player/card_menu_action_type.h | 1 + .../src/game_graphics/player/menu/card_menu.cpp | 2 +- .../server/remote/game/server_abstract_player.cpp | 7 +++++++ .../network/server/remote/game/server_card.cpp | 13 ++++++++++++- .../network/server/remote/game/server_card.h | 9 +++++++++ .../libcockatrice/protocol/pb/card_attributes.proto | 1 + .../libcockatrice/protocol/pb/serverinfo_card.proto | 3 +++ 15 files changed, 81 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 8ec6da63a..276184a8c 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -504,7 +504,7 @@ private: {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping"), parseSequenceString("Alt+U"), ShortcutGroup::Playing_Area)}, - {"Player/aDoesntUntapOnce", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping Once"), + {"Player/aDoesntUntapOnce", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip (One Turn)"), parseSequenceString(""), ShortcutGroup::Playing_Area)}, {"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"), diff --git a/cockatrice/src/game/board/card_state.cpp b/cockatrice/src/game/board/card_state.cpp index 4319400d7..fda57111c 100644 --- a/cockatrice/src/game/board/card_state.cpp +++ b/cockatrice/src/game/board/card_state.cpp @@ -89,6 +89,16 @@ void CardState::setDoesntUntap(bool _doesntUntap) emit stateChanged(); } +void CardState::setDoesntUntapOnce(bool _doesntUntapOnce) +{ + if (doesntUntapOnce == _doesntUntapOnce) { + return; + } + doesntUntapOnce = _doesntUntapOnce; + emit doesntUntapOnceChanged(_doesntUntapOnce); + emit stateChanged(); +} + void CardState::setDestroyOnZoneChange(bool _destroyOnZoneChange) { if (destroyOnZoneChange == _destroyOnZoneChange) { diff --git a/cockatrice/src/game/board/card_state.h b/cockatrice/src/game/board/card_state.h index 6d13030e8..3a84621dc 100644 --- a/cockatrice/src/game/board/card_state.h +++ b/cockatrice/src/game/board/card_state.h @@ -30,6 +30,7 @@ signals: void annotationChanged(const QString &newAnnotation); void ptChanged(const QString &newPt); void doesntUntapChanged(bool newValue); + void doesntUntapOnceChanged(bool newValue); void destroyOnZoneChangeChanged(bool newValue); void attachedToChanged(CardItem *newAttachedTo); void zoneChanged(CardState *changedCard, CardZoneLogic *newZone); @@ -86,6 +87,13 @@ public: void setDoesntUntap(bool _doesntUntap); + bool getDoesntUntapOnce() const + { + return doesntUntapOnce; + } + + void setDoesntUntapOnce(bool _doesntUntapOnce); + bool getDestroyOnZoneChange() const { return destroyOnZoneChange; diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index bc48298f7..701474299 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -231,6 +231,12 @@ void PlayerEventHandler::setCardAttrHelper(const GameEventContext &context, card->setDoesntUntap(value); break; } + case AttrDoesntUntapOnce: { + bool value = (avalue == "1"); + emit logSetDoesntUntapOnce(player, card, value); + card->setDoesntUntapOnce(value); + break; + } case AttrPT: { emit logSetPT(player, card, avalue); card->setPT(avalue); diff --git a/cockatrice/src/game/player/player_event_handler.h b/cockatrice/src/game/player/player_event_handler.h index cfd82933f..044250465 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/cockatrice/src/game/player/player_event_handler.h @@ -69,6 +69,7 @@ signals: void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped); void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue); void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap); + void logSetDoesntUntapOnce(PlayerLogic *player, CardItem *card, bool doesntUntapOnce); void logSetPT(PlayerLogic *player, CardItem *card, QString newPT); void logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation); void logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed = false); diff --git a/cockatrice/src/game_graphics/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp index cabe988c2..78cab4985 100644 --- a/cockatrice/src/game_graphics/board/card_item.cpp +++ b/cockatrice/src/game_graphics/board/card_item.cpp @@ -173,6 +173,12 @@ void CardItem::setDoesntUntap(bool _doesntUntap) update(); } +void CardItem::setDoesntUntapOnce(bool _doesntUntapOnce) +{ + state->setDoesntUntapOnce(_doesntUntapOnce); + update(); +} + void CardItem::setPT(const QString &_pt) { state->setPT(_pt); @@ -223,6 +229,7 @@ void CardItem::resetState(bool keepAnnotations) attachedCards.clear(); setTapped(false, false); setDoesntUntap(false); + setDoesntUntapOnce(false); if (scene()) { static_cast(scene())->unregisterAnimationItem(this); } @@ -248,6 +255,7 @@ void CardItem::processCardInfo(const ServerInfo_Card &_info) setTapped(_info.tapped()); setDestroyOnZoneChange(_info.destroy_on_zone_change()); setDoesntUntap(_info.doesnt_untap()); + setDoesntUntapOnce(_info.doesnt_untap_once()); } CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPointF &_scenePos, bool forceFaceDown) diff --git a/cockatrice/src/game_graphics/log/message_log_widget.cpp b/cockatrice/src/game_graphics/log/message_log_widget.cpp index ccd903b04..c0b449b4a 100644 --- a/cockatrice/src/game_graphics/log/message_log_widget.cpp +++ b/cockatrice/src/game_graphics/log/message_log_widget.cpp @@ -691,6 +691,17 @@ void MessageLogWidget::logSetDoesntUntap(PlayerLogic *player, CardItem *card, bo appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(card->getName()))); } +void MessageLogWidget::logSetDoesntUntapOnce(PlayerLogic *player, CardItem *card, bool doesntUntapOnce) +{ + QString str; + if (doesntUntapOnce) { + str = tr("%1 sets %2 to not untap during the next untap step."); + } else { + str = tr("%1 sets %2 to untap normally."); + } + appendHtmlServerMessage(str.arg(sanitizeHtml(player->getPlayerInfo()->getName())).arg(cardLink(card->getName()))); +} + void MessageLogWidget::logSetPT(PlayerLogic *player, CardItem *card, QString newPT) { if (currentContext == MessageContext_MoveCard) { @@ -837,6 +848,7 @@ void MessageLogWidget::connectToPlayerEventHandler(PlayerEventHandler *playerEve connect(playerEventHandler, &PlayerEventHandler::logSetCardCounter, this, &MessageLogWidget::logSetCardCounter); connect(playerEventHandler, &PlayerEventHandler::logSetTapped, this, &MessageLogWidget::logSetTapped); connect(playerEventHandler, &PlayerEventHandler::logSetDoesntUntap, this, &MessageLogWidget::logSetDoesntUntap); + connect(playerEventHandler, &PlayerEventHandler::logSetDoesntUntapOnce, this, &MessageLogWidget::logSetDoesntUntapOnce); connect(playerEventHandler, &PlayerEventHandler::logSetPT, this, &MessageLogWidget::logSetPT); connect(playerEventHandler, &PlayerEventHandler::logSetAnnotation, this, &MessageLogWidget::logSetAnnotation); connect(playerEventHandler, &PlayerEventHandler::logMoveCard, this, &MessageLogWidget::logMoveCard); diff --git a/cockatrice/src/game_graphics/log/message_log_widget.h b/cockatrice/src/game_graphics/log/message_log_widget.h index a145d358d..bfd60acf0 100644 --- a/cockatrice/src/game_graphics/log/message_log_widget.h +++ b/cockatrice/src/game_graphics/log/message_log_widget.h @@ -92,6 +92,7 @@ public slots: void logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue); void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue); void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap); + void logSetDoesntUntapOnce(PlayerLogic *player, CardItem *card, bool doesntUntapOnce); void logSetPT(PlayerLogic *player, CardItem *card, QString newPT); void logSetSideboardLock(PlayerLogic *player, bool locked); void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped); diff --git a/cockatrice/src/game_graphics/player/card_menu_action_type.h b/cockatrice/src/game_graphics/player/card_menu_action_type.h index 4cae22716..3697adf75 100644 --- a/cockatrice/src/game_graphics/player/card_menu_action_type.h +++ b/cockatrice/src/game_graphics/player/card_menu_action_type.h @@ -13,6 +13,7 @@ enum CardMenuActionType cmTap, cmUntap, cmDoesntUntap, + cmDoesntUntapOnce, cmFlip, cmPeek, cmClone, diff --git a/cockatrice/src/game_graphics/player/menu/card_menu.cpp b/cockatrice/src/game_graphics/player/menu/card_menu.cpp index fdf4bb50f..d332fb296 100644 --- a/cockatrice/src/game_graphics/player/menu/card_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/card_menu.cpp @@ -493,7 +493,7 @@ void CardMenu::retranslateUi() //: Turn sideways or back again aTap->setText(tr("&Tap / Untap")); aDoesntUntap->setText(tr("Skip &untapping")); - aDoesntUntapOnce->setText(tr("Skip &untapping once")); + aDoesntUntapOnce->setText(tr("Skip &Untapping (One Turn)")); //: Turn face up/face down aFlip->setText(tr("T&urn Over")); // Only the user facing names in client got renamed to "turn over" // All code and proto bits are still unchanged (flip) for compatibility reasons 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 157fa6441..e8edd2ca6 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 @@ -1075,6 +1075,11 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo ges.enqueueGameEvent(event, playerId); } + if (card->getDoesntUntapOnce() != targetCard->getDoesntUntapOnce()) { + card->setAttribute(AttrDoesntUntapOnce, QVariant(targetCard->getDoesntUntapOnce()).toString(), &event); + ges.enqueueGameEvent(event, playerId); + } + // Copy counters QMapIterator i(targetCard->getCounters()); while (i.hasNext()) { @@ -1432,6 +1437,7 @@ Server_AbstractPlayer::cmdDumpZone(const Command_DumpZone &cmd, ResponseContaine cardInfo->set_annotation(card->getAnnotation().toStdString()); cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); cardInfo->set_doesnt_untap(card->getDoesntUntap()); + cardInfo->set_doesnt_untap_once(card->getDoesntUntapOnce()); QMapIterator cardCounterIterator(card->getCounters()); while (cardCounterIterator.hasNext()) { @@ -1544,6 +1550,7 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo cardInfo->set_annotation(card->getAnnotation().toStdString()); cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); cardInfo->set_doesnt_untap(card->getDoesntUntap()); + cardInfo->set_doesnt_untap_once(card->getDoesntUntapOnce()); QMapIterator cardCounterIterator(card->getCounters()); while (cardCounterIterator.hasNext()) { diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp index b858314c0..9a1e03e93 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp @@ -31,7 +31,7 @@ Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone) : zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), cardRef(cardRef), tapped(false), attacking(false), - facedown(false), destroyOnZoneChange(false), doesntUntap(false), parentCard(0), stashedCard(nullptr) + facedown(false), destroyOnZoneChange(false), doesntUntap(false), doesntUntapOnce(false), parentCard(0), stashedCard(nullptr) { } @@ -66,6 +66,11 @@ void Server_Card::resetState(bool keepAnnotations) QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, bool allCards) { + if (attribute == AttrTapped && avalue != "1" && allCards && doesntUntapOnce) { + setDoesntUntapOnce(false); + return QVariant(tapped).toString(); + } + if (attribute == AttrTapped && avalue != "1" && allCards && doesntUntap) { return QVariant(tapped).toString(); } @@ -105,6 +110,9 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue case AttrDoesntUntap: setDoesntUntap(avalue == "1"); break; + case AttrDoesntUntapOnce: + setDoesntUntapOnce(avalue == "1"); + break; } if (event) { event->set_attr_value(avalue.toStdString()); @@ -204,6 +212,9 @@ void Server_Card::getInfo(ServerInfo_Card *info) if (doesntUntap) { info->set_doesnt_untap(true); } + if (doesntUntapOnce) { + info->set_doesnt_untap_once(true); + } QMapIterator cardCounterIterator(counters); while (cardCounterIterator.hasNext()) { diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h index 3d7e649b9..8cdff0d2e 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.h @@ -49,6 +49,7 @@ private: QString annotation; bool destroyOnZoneChange; bool doesntUntap; + bool doesntUntapOnce; Server_Card *parentCard; QList attachedCards; @@ -127,6 +128,10 @@ public: { return doesntUntap; } + bool getDoesntUntapOnce() const + { + return doesntUntapOnce; + } bool getDestroyOnZoneChange() const { return destroyOnZoneChange; @@ -203,6 +208,10 @@ public: { doesntUntap = _doesntUntap; } + void setDoesntUntapOnce(bool _doesntUntapOnce) + { + doesntUntapOnce = _doesntUntapOnce; + } void setParentCard(Server_Card *_parentCard); void addAttachedCard(Server_Card *card) { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/card_attributes.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/card_attributes.proto index ac23ca0d3..e42de5612 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/card_attributes.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/card_attributes.proto @@ -7,4 +7,5 @@ enum CardAttribute { AttrPT = 5; AttrAnnotation = 6; AttrDoesntUntap = 7; + AttrDoesntUntapOnce = 8; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_card.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_card.proto index a9a8b5c9d..8264e189b 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_card.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_card.proto @@ -53,4 +53,7 @@ message ServerInfo_Card { // unique id of this kind of card, extends the name to specify a specific printing of a card optional string provider_id = 17; + + // whether the card should not be untapped during its controller's next turn + optional bool doesnt_untap_once = 18; } From 156349100687cdf931443dd6b31028c05560022f Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Wed, 12 Aug 2026 14:44:27 -0600 Subject: [PATCH 3/8] Fix checkmark and add red. --- cockatrice/src/game/player/player_actions.cpp | 9 +++++++++ cockatrice/src/game_graphics/board/card_item.cpp | 14 ++++++++++++++ .../network/server/remote/game/server_card.cpp | 1 + 3 files changed, 24 insertions(+) diff --git a/cockatrice/src/game/player/player_actions.cpp b/cockatrice/src/game/player/player_actions.cpp index de909ca5e..107daa139 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/cockatrice/src/game/player/player_actions.cpp @@ -1777,6 +1777,15 @@ void PlayerActions::cardMenuAction(QList selectedCards, CardMenuActi commandList.append(cmd); break; } + case cmDoesntUntapOnce: { + auto *cmd = new Command_SetCardAttr; + cmd->set_zone(card->getZone()->getName().toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrDoesntUntapOnce); + cmd->set_attr_value(card->getDoesntUntapOnce() ? "0" : "1"); + commandList.append(cmd); + break; + } case cmFlip: { auto *cmd = new Command_FlipCard; cmd->set_zone(card->getZone()->getName().toStdString()); diff --git a/cockatrice/src/game_graphics/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp index 78cab4985..b0850627d 100644 --- a/cockatrice/src/game_graphics/board/card_item.cpp +++ b/cockatrice/src/game_graphics/board/card_item.cpp @@ -146,6 +146,20 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, painter->restore(); } + if (state->getDoesntUntapOnce()) { + painter->save(); + + painter->setRenderHint(QPainter::Antialiasing, false); + + QPen pen; + pen.setColor(Qt::red); + pen.setWidth(0); // Cosmetic pen + painter->setPen(pen); + painter->drawPath(shape()); + + painter->restore(); + } + painter->restore(); } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp index 9a1e03e93..0dcb4a068 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp @@ -62,6 +62,7 @@ void Server_Card::resetState(bool keepAnnotations) setAnnotation(QString()); } setDoesntUntap(false); + setDoesntUntapOnce(false); } QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, bool allCards) From de421cf406e6007e9083b2fd23e8d72adaad45e3 Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Wed, 12 Aug 2026 15:03:26 -0600 Subject: [PATCH 4/8] Change color. --- cockatrice/src/game_graphics/board/card_item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/game_graphics/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp index b0850627d..e8c83ebcd 100644 --- a/cockatrice/src/game_graphics/board/card_item.cpp +++ b/cockatrice/src/game_graphics/board/card_item.cpp @@ -152,7 +152,7 @@ void CardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, painter->setRenderHint(QPainter::Antialiasing, false); QPen pen; - pen.setColor(Qt::red); + pen.setColor(Qt::cyan); pen.setWidth(0); // Cosmetic pen painter->setPen(pen); painter->drawPath(shape()); From 1380a8af99ad88ba3bb29784c4836f117fc33ebb Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Wed, 12 Aug 2026 16:18:48 -0600 Subject: [PATCH 5/8] Appears to work. --- cockatrice/src/game/player/player_event_handler.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index 701474299..a8f99a857 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -199,13 +199,15 @@ void PlayerEventHandler::setCardAttrHelper(const GameEventContext &context, switch (attribute) { case AttrTapped: { bool tapped = avalue == "1"; - if (!(!tapped && card->getDoesntUntap() && allCards)) { + if (!(!tapped && card->getDoesntUntap() && allCards) && !(!tapped && card->getDoesntUntapOnce() && allCards)) { if (!allCards) { emit logSetTapped(player, card, tapped); } bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext; card->setTapped(tapped, canAnimate); - } + } else if (!tapped && card->getDoesntUntapOnce() && allCards) { + card->setDoesntUntapOnce(false); + } break; } case AttrAttacking: { From c2926037fc33298740feb95d0d0907b5146248be Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Wed, 12 Aug 2026 16:35:51 -0600 Subject: [PATCH 6/8] Fix shortcut name. --- cockatrice/src/client/settings/shortcuts_settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 276184a8c..09c7aada8 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -504,7 +504,7 @@ private: {"Player/aDoesntUntap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping"), parseSequenceString("Alt+U"), ShortcutGroup::Playing_Area)}, - {"Player/aDoesntUntapOnce", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip (One Turn)"), + {"Player/aDoesntUntapOnce", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping (One Turn)"), parseSequenceString(""), ShortcutGroup::Playing_Area)}, {"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"), From 766f02149be133a49e0f5fb3e11d1d26cef2b2ed Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Wed, 12 Aug 2026 23:38:22 -0600 Subject: [PATCH 7/8] Formatting. --- cockatrice/src/client/settings/shortcuts_settings.h | 4 ++-- cockatrice/src/game/board/card_state.cpp | 12 ++++++------ cockatrice/src/game/player/player_event_handler.cpp | 7 ++++--- cockatrice/src/game_graphics/board/card_item.cpp | 2 +- .../src/game_graphics/log/message_log_widget.cpp | 3 ++- .../src/game_graphics/player/menu/card_menu.cpp | 3 ++- .../server/remote/game/server_abstract_player.cpp | 2 +- .../network/server/remote/game/server_card.cpp | 5 +++-- 8 files changed, 21 insertions(+), 17 deletions(-) diff --git a/cockatrice/src/client/settings/shortcuts_settings.h b/cockatrice/src/client/settings/shortcuts_settings.h index 09c7aada8..a3256b939 100644 --- a/cockatrice/src/client/settings/shortcuts_settings.h +++ b/cockatrice/src/client/settings/shortcuts_settings.h @@ -505,8 +505,8 @@ private: parseSequenceString("Alt+U"), ShortcutGroup::Playing_Area)}, {"Player/aDoesntUntapOnce", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Skip Untapping (One Turn)"), - parseSequenceString(""), - ShortcutGroup::Playing_Area)}, + parseSequenceString(""), + ShortcutGroup::Playing_Area)}, {"Player/aFlip", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Turn Card Over"), parseSequenceString("Alt+F"), ShortcutGroup::Playing_Area)}, diff --git a/cockatrice/src/game/board/card_state.cpp b/cockatrice/src/game/board/card_state.cpp index fda57111c..37fe7036f 100644 --- a/cockatrice/src/game/board/card_state.cpp +++ b/cockatrice/src/game/board/card_state.cpp @@ -91,12 +91,12 @@ void CardState::setDoesntUntap(bool _doesntUntap) void CardState::setDoesntUntapOnce(bool _doesntUntapOnce) { - if (doesntUntapOnce == _doesntUntapOnce) { - return; - } - doesntUntapOnce = _doesntUntapOnce; - emit doesntUntapOnceChanged(_doesntUntapOnce); - emit stateChanged(); + if (doesntUntapOnce == _doesntUntapOnce) { + return; + } + doesntUntapOnce = _doesntUntapOnce; + emit doesntUntapOnceChanged(_doesntUntapOnce); + emit stateChanged(); } void CardState::setDestroyOnZoneChange(bool _destroyOnZoneChange) diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/cockatrice/src/game/player/player_event_handler.cpp index a8f99a857..0191748a8 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/cockatrice/src/game/player/player_event_handler.cpp @@ -199,15 +199,16 @@ void PlayerEventHandler::setCardAttrHelper(const GameEventContext &context, switch (attribute) { case AttrTapped: { bool tapped = avalue == "1"; - if (!(!tapped && card->getDoesntUntap() && allCards) && !(!tapped && card->getDoesntUntapOnce() && allCards)) { + if (!(!tapped && card->getDoesntUntap() && allCards) && + !(!tapped && card->getDoesntUntapOnce() && allCards)) { if (!allCards) { emit logSetTapped(player, card, tapped); } bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext; card->setTapped(tapped, canAnimate); } else if (!tapped && card->getDoesntUntapOnce() && allCards) { - card->setDoesntUntapOnce(false); - } + card->setDoesntUntapOnce(false); + } break; } case AttrAttacking: { diff --git a/cockatrice/src/game_graphics/board/card_item.cpp b/cockatrice/src/game_graphics/board/card_item.cpp index e8c83ebcd..fe5a1cfe5 100644 --- a/cockatrice/src/game_graphics/board/card_item.cpp +++ b/cockatrice/src/game_graphics/board/card_item.cpp @@ -243,7 +243,7 @@ void CardItem::resetState(bool keepAnnotations) attachedCards.clear(); setTapped(false, false); setDoesntUntap(false); - setDoesntUntapOnce(false); + setDoesntUntapOnce(false); if (scene()) { static_cast(scene())->unregisterAnimationItem(this); } diff --git a/cockatrice/src/game_graphics/log/message_log_widget.cpp b/cockatrice/src/game_graphics/log/message_log_widget.cpp index c0b449b4a..b99114def 100644 --- a/cockatrice/src/game_graphics/log/message_log_widget.cpp +++ b/cockatrice/src/game_graphics/log/message_log_widget.cpp @@ -848,7 +848,8 @@ void MessageLogWidget::connectToPlayerEventHandler(PlayerEventHandler *playerEve connect(playerEventHandler, &PlayerEventHandler::logSetCardCounter, this, &MessageLogWidget::logSetCardCounter); connect(playerEventHandler, &PlayerEventHandler::logSetTapped, this, &MessageLogWidget::logSetTapped); connect(playerEventHandler, &PlayerEventHandler::logSetDoesntUntap, this, &MessageLogWidget::logSetDoesntUntap); - connect(playerEventHandler, &PlayerEventHandler::logSetDoesntUntapOnce, this, &MessageLogWidget::logSetDoesntUntapOnce); + connect(playerEventHandler, &PlayerEventHandler::logSetDoesntUntapOnce, this, + &MessageLogWidget::logSetDoesntUntapOnce); connect(playerEventHandler, &PlayerEventHandler::logSetPT, this, &MessageLogWidget::logSetPT); connect(playerEventHandler, &PlayerEventHandler::logSetAnnotation, this, &MessageLogWidget::logSetAnnotation); connect(playerEventHandler, &PlayerEventHandler::logMoveCard, this, &MessageLogWidget::logMoveCard); diff --git a/cockatrice/src/game_graphics/player/menu/card_menu.cpp b/cockatrice/src/game_graphics/player/menu/card_menu.cpp index d332fb296..ed078956d 100644 --- a/cockatrice/src/game_graphics/player/menu/card_menu.cpp +++ b/cockatrice/src/game_graphics/player/menu/card_menu.cpp @@ -73,7 +73,8 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho // Actions using invoke (type dispatch, need selection) aTap = makeAction(this, invoke(cmTap)); aDoesntUntap = makeAction(this, invoke(cmDoesntUntap), /*checkable=*/true, card && card->getDoesntUntap()); - aDoesntUntapOnce = makeAction(this, invoke(cmDoesntUntapOnce), /*checkable=*/true, card && card->getDoesntUntapOnce()); + aDoesntUntapOnce = + makeAction(this, invoke(cmDoesntUntapOnce), /*checkable=*/true, card && card->getDoesntUntapOnce()); aFlip = makeAction(this, invoke(cmFlip)); aPeek = makeAction(this, invoke(cmPeek)); aClone = makeAction(this, invoke(cmClone)); 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 e8edd2ca6..47467f542 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 @@ -1550,7 +1550,7 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo cardInfo->set_annotation(card->getAnnotation().toStdString()); cardInfo->set_destroy_on_zone_change(card->getDestroyOnZoneChange()); cardInfo->set_doesnt_untap(card->getDoesntUntap()); - cardInfo->set_doesnt_untap_once(card->getDoesntUntapOnce()); + cardInfo->set_doesnt_untap_once(card->getDoesntUntapOnce()); QMapIterator cardCounterIterator(card->getCounters()); while (cardCounterIterator.hasNext()) { diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp index 0dcb4a068..74345a08f 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_card.cpp @@ -31,7 +31,8 @@ Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone) : zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), cardRef(cardRef), tapped(false), attacking(false), - facedown(false), destroyOnZoneChange(false), doesntUntap(false), doesntUntapOnce(false), parentCard(0), stashedCard(nullptr) + facedown(false), destroyOnZoneChange(false), doesntUntap(false), doesntUntapOnce(false), parentCard(0), + stashedCard(nullptr) { } @@ -68,7 +69,7 @@ void Server_Card::resetState(bool keepAnnotations) QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue, bool allCards) { if (attribute == AttrTapped && avalue != "1" && allCards && doesntUntapOnce) { - setDoesntUntapOnce(false); + setDoesntUntapOnce(false); return QVariant(tapped).toString(); } From a07b2271d405f11ee7dd5ed233c54a6268ac749f Mon Sep 17 00:00:00 2001 From: Skagra42 Date: Thu, 13 Aug 2026 00:25:51 -0600 Subject: [PATCH 8/8] Fix message. --- cockatrice/src/game_graphics/log/message_log_widget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/game_graphics/log/message_log_widget.cpp b/cockatrice/src/game_graphics/log/message_log_widget.cpp index b99114def..f9dd39658 100644 --- a/cockatrice/src/game_graphics/log/message_log_widget.cpp +++ b/cockatrice/src/game_graphics/log/message_log_widget.cpp @@ -695,7 +695,7 @@ void MessageLogWidget::logSetDoesntUntapOnce(PlayerLogic *player, CardItem *card { QString str; if (doesntUntapOnce) { - str = tr("%1 sets %2 to not untap during the next untap step."); + str = tr("%1 sets %2 to not untap during its controller's next untap step."); } else { str = tr("%1 sets %2 to untap normally."); }