Wire command zone menu, harden counter command handling, and expand tests

- Connect aMoveToCommandZone to cmMoveToCommandZone; the menu item was
    added, shown, and shortcut-bound but never wired, so it did nothing
  - Extract evaluateModifyCounter() and route cmdIncCounter/cmdSetCounter
    through it, replacing isCommandZoneCounterBlocked; reject inc/set on
    an inactive tax counter so a hidden counter cannot accumulate value
  - Reject reserved tax counter names in cmdCreateCounter to prevent a
    client from spoofing a system tax counter via a user-created counter
  - Route AbstractCounter::valueChanged through the virtual setValue() so
    CommanderTaxCounter clamps and refreshes its tooltip on value changes
  - Deduplicate actPlayAndIncreaseTax/actPlayAndIncreasePartnerTax into a
    single playAndIncreaseTax(counterId) helper
  - Move MINIMUM_STACKING_HEIGHT from CommandZone to the PlayerGraphicsItem
    layout code, its only consumer
  - Remove unused AbstractCounter forward declaration from player_logic.h
  - Add EvaluateModifyCounter tests and PartnerTax cases for
    evaluateSetCounterActive
  - Pin the FirstUserId floor in new_counter_id_test using ids 3 and 5 so
    a naive "highest id + 1" regression fails the test
This commit is contained in:
DawnFire42 2026-06-28 21:43:42 -04:00
parent 0bfb0f4131
commit bc09a19b50
No known key found for this signature in database
GPG key ID: 24BB855EE2911B33
11 changed files with 172 additions and 47 deletions

View file

@ -441,10 +441,33 @@ Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer &
return retVal;
}
bool Server_Player::isCommandZoneCounterBlocked(int counterId) const
Response::ResponseCode Server_Player::evaluateModifyCounter(bool gameStarted,
bool playerConceded,
bool commandZoneEnabled,
int counterId,
const Server_Counter *counter)
{
return (counterId == CounterIds::CommanderTax || counterId == CounterIds::PartnerTax) &&
!game->getEnableCommandZone();
if (!gameStarted) {
return Response::RespGameNotStarted;
}
if (playerConceded) {
return Response::RespContextError;
}
if (CounterIds::isTaxCounter(counterId)) {
// Tax counters are server-managed: they only exist in Commander games, and an
// inactive (hidden) tax counter must stay at zero. Block modification in either
// case so the value can never diverge from what players can see.
if (!commandZoneEnabled) {
return Response::RespContextError;
}
if (counter && !counter->isActive()) {
return Response::RespContextError;
}
}
if (!counter) {
return Response::RespNameNotFound;
}
return Response::RespOk;
}
Response::ResponseCode
@ -458,14 +481,12 @@ Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer &
}
const int counterId = cmd.counter_id();
if (isCommandZoneCounterBlocked(counterId)) {
return Response::RespContextError;
}
Server_Counter *c = counters.value(counterId, nullptr);
if (!c) {
return Response::RespNameNotFound;
const Response::ResponseCode authResult =
evaluateModifyCounter(game->getGameStarted(), conceded, game->getEnableCommandZone(), counterId, c);
if (authResult != Response::RespOk) {
return authResult;
}
bool didChange = c->incrementCount(cmd.delta());
@ -489,8 +510,15 @@ Server_Player::cmdCreateCounter(const Command_CreateCounter &cmd, ResponseContai
return Response::RespContextError;
}
auto *c = new Server_Counter(newCounterId(), nameFromStdString(cmd.counter_name()), cmd.counter_color(),
cmd.radius(), cmd.value());
const QString counterName = nameFromStdString(cmd.counter_name());
// Reserved system counter names (commander/partner tax) are how clients identify
// server-managed tax counters for rendering and logging; a client must not be able
// to spoof one via a user-created counter.
if (CounterNames::isTaxCounter(counterName)) {
return Response::RespFunctionNotAllowed;
}
auto *c = new Server_Counter(newCounterId(), counterName, cmd.counter_color(), cmd.radius(), cmd.value());
addCounter(c);
Event_CreateCounter event;
@ -516,14 +544,12 @@ Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer &
}
const int counterId = cmd.counter_id();
if (isCommandZoneCounterBlocked(counterId)) {
return Response::RespContextError;
}
Server_Counter *c = counters.value(counterId, nullptr);
if (!c) {
return Response::RespNameNotFound;
const Response::ResponseCode authResult =
evaluateModifyCounter(game->getGameStarted(), conceded, game->getEnableCommandZone(), counterId, c);
if (authResult != Response::RespOk) {
return authResult;
}
bool didChange = c->setCount(cmd.value());

View file

@ -9,7 +9,6 @@ class Server_Player : public Server_AbstractPlayer
private:
QMap<int, Server_Counter *> counters;
QList<int> lastDrawList;
bool isCommandZoneCounterBlocked(int counterId) const;
public:
Server_Player(Server_Game *_game,
@ -37,6 +36,14 @@ public:
int counterId,
const Server_Counter *counter,
bool requestedActive);
// Authorization shared by cmdIncCounter and cmdSetCounter. Reserved tax counters
// may only be modified inside a Commander game and only while active, so that an
// inactive (hidden) tax counter can never accumulate a value behind the scenes.
static Response::ResponseCode evaluateModifyCounter(bool gameStarted,
bool playerConceded,
bool commandZoneEnabled,
int counterId,
const Server_Counter *counter);
void setupZones() override;
void clearZones() override;