Refine command zone counters and unify counter clamp arithmetic

- Route value updates through the virtual setValue() so
      CommanderTaxCounter's tooltip tracks the live value instead of
      freezing at its initial value
    - Reject deletion of reserved tax counters in cmdDelCounter; they are
      server-managed and must persist for the game
    - Register aMoveToCommandZone and aViewCommandZone as bindable
      shortcuts, matching their sibling move/view zone actions
    - Extract overflow-safe clamped addition into addClamped(), shared by
      Server_Card and Server_Counter instead of duplicated
    - Update stale comments: the clip-container note (now used by
      CommandZone) and CommandZone's layout docs (defer to
      SelectZone::layoutCardsVertically)
This commit is contained in:
DawnFire42 2026-06-16 12:49:10 -04:00
parent 754b31cc29
commit 240ca7029f
No known key found for this signature in database
GPG key ID: 24BB855EE2911B33
8 changed files with 37 additions and 22 deletions

View file

@ -577,6 +577,9 @@ private:
{"Player/aMoveToTable", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Battlefield"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aMoveToCommandZone", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Command Zone"),
parseSequenceString(""),
ShortcutGroup::Move_selected)},
{"Player/aViewHand",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hand"), parseSequenceString(""), ShortcutGroup::View)},
{"Player/aViewGraveyard",
@ -588,6 +591,8 @@ private:
{"Player/aViewSideboard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Sideboard"),
parseSequenceString("Ctrl+F3"),
ShortcutGroup::View)},
{"Player/aViewCommandZone",
ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Command Zone"), parseSequenceString(""), ShortcutGroup::View)},
{"Player/aViewTopCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top Cards of Library"),
parseSequenceString("Ctrl+W"),
ShortcutGroup::View)},

View file

@ -28,10 +28,7 @@ AbstractCounter::AbstractCounter(CounterState *state,
{
setAcceptHoverEvents(true);
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) {
value = newValue;
update();
});
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) { setValue(newValue); });
connect(state, &CounterState::activeChanged, this, [this](bool newActive) {
setActive(newActive);

View file

@ -36,11 +36,11 @@ constexpr qreal COMMAND_ZONE_WIDTH = CardDimensions::WIDTH_F * 1.5;
* @class CommandZone
* @brief Graphics layer for the command zone in Commander format games.
*
* Always visible when enabled. Supports multiple cards using a zigzag
* horizontal stacking pattern: single cards display centered, multiple
* cards alternate left-right with vertical overlap compression.
* Can be minimized to 25% height via double-click.
* Always visible when enabled. Cards are laid out by the shared
* SelectZone::layoutCardsVertically. Can be minimized to 25% height via
* double-click.
*
* @see SelectZone::layoutCardsVertically for the card layout
* @see CommandZoneLogic for card data management
* @see CommanderTaxCounter for the tax counter overlay
*/
@ -79,7 +79,7 @@ public:
[[nodiscard]] QRectF boundingRect() const override;
/** @brief Paints the zone background using the Commander theme brush. */
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
/** @brief Repositions cards using zigzag horizontal stacking with overlap compression. */
/** @brief Repositions cards via the shared SelectZone vertical layout (allows bottom overflow). */
void reorganizeCards() override;
/** @brief Toggles between full and 25% minimized height. */

View file

@ -119,9 +119,9 @@ protected:
void layoutCardsVertically(const StackLayoutParams &params);
// -- Clip container --
// The clip container mechanism is available for future zones that need visual clipping
// (e.g., zones too short to fit a full card). To enable: call setupClipContainer() in the
// zone's constructor, and set allowBottomOverflow=true in layout params.
// Provides visual clipping for zones too short to fit a full card (e.g. when minimized);
// used by CommandZone. To enable: call setupClipContainer() in the zone's constructor and
// set allowBottomOverflow=true in layout params.
/**
* @brief Restores any cards that were hover-escaped but whose hover state was not properly cleaned up.

View file

@ -139,10 +139,8 @@ bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
bool Server_Card::incrementCounter(int counterId, int delta, Event_SetCardCounter *event)
{
const int oldValue = counters.value(counterId, 0);
const auto result = static_cast<int64_t>(oldValue) + static_cast<int64_t>(delta);
// Clamp to [0, MAX_COUNTER_VALUE] for card counters
const int newValue =
static_cast<int>(qBound(static_cast<int64_t>(0), result, static_cast<int64_t>(MAX_COUNTER_VALUE)));
const int newValue = addClamped(oldValue, delta, 0, MAX_COUNTER_VALUE);
if (newValue == oldValue) {
return false;

View file

@ -22,6 +22,7 @@
#include <QString>
#include <libcockatrice/protocol/pb/color.pb.h>
#include <libcockatrice/utility/trice_limits.h>
#include <limits>
class ServerInfo_Counter;
@ -128,11 +129,8 @@ public:
*/
[[nodiscard]] bool incrementCount(int delta)
{
const auto result = static_cast<int64_t>(count) + static_cast<int64_t>(delta);
const int clamped =
static_cast<int>(qBound(static_cast<int64_t>(minValue), result, static_cast<int64_t>(maxValue)));
int oldCount = count;
count = clamped;
const int oldCount = count;
count = addClamped(count, delta, minValue, maxValue);
return count != oldCount;
}

View file

@ -549,8 +549,11 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &
const int counterId = cmd.counter_id();
if (isCommandZoneCounterBlocked(counterId)) {
return Response::RespContextError;
// Reserved tax counters are server-managed system counters and must never be
// deleted by a client. When the command zone is disabled they don't exist, so
// a lookup would fail anyway; when it's enabled they must persist for the game.
if (CounterIds::isTaxCounter(counterId)) {
return Response::RespFunctionNotAllowed;
}
Server_Counter *counter = counters.value(counterId, nullptr);

View file

@ -2,6 +2,8 @@
#define TRICE_LIMITS_H
#include <QString>
#include <QtGlobal>
#include <cstdint>
// max size for short strings, like names and things that are generally a single phrase
constexpr int MAX_NAME_LENGTH = 0xff;
@ -21,6 +23,18 @@ constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
// Server enforces these bounds; client may also check for UX optimization.
constexpr int MAX_COUNTER_VALUE = 999;
/**
* @brief Overflow-safe clamped addition: returns value + delta bounded to [minValue, maxValue].
*
* Uses a 64-bit intermediate so the addition itself cannot overflow int. Shared by the
* counter arithmetic in Server_Card and Server_Counter so both stay in sync.
*/
inline int addClamped(int value, int delta, int minValue, int maxValue)
{
const auto result = static_cast<int64_t>(value) + static_cast<int64_t>(delta);
return static_cast<int>(qBound(static_cast<int64_t>(minValue), result, static_cast<int64_t>(maxValue)));
}
// optimized functions to get qstrings that are at most that long
static inline QString nameFromStdString(const std::string &_string)
{