Refactor tax counters into cast counts with separate ID space and user activated create/delete

This commit is contained in:
DawnFire42 2026-08-10 22:28:00 -04:00
parent 4354191918
commit f5e49a571e
No known key found for this signature in database
GPG key ID: 24BB855EE2911B33
50 changed files with 900 additions and 828 deletions

View file

@ -67,7 +67,7 @@ set(cockatrice_SOURCES
src/game_graphics/board/card_item.cpp
src/game/board/card_list.cpp
src/game/board/card_state.cpp
src/game_graphics/board/commander_tax_counter.cpp
src/game_graphics/board/cast_count_widget.cpp
src/game_graphics/board/counter_general.cpp
src/game/board/counter_state.cpp
src/game_graphics/board/translate_counter_name.cpp

View file

@ -597,18 +597,18 @@ private:
{"Player/aViewBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom Cards of Library"),
parseSequenceString("Ctrl+Shift+W"),
ShortcutGroup::View)},
{"Player/aAddCommanderTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 1st Tax"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aRemoveCommanderTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 1st Tax"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aAddPartnerTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 2nd Tax"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aRemovePartnerTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 2nd Tax"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncrease1stCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 1st Cast Count"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecrease1stCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 1st Cast Count"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aIncrease2ndCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 2nd Cast Count"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aDecrease2ndCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 2nd Cast Count"),
parseSequenceString(""),
ShortcutGroup::Player_Counters)},
{"Player/aCloseMostRecentZoneView", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Close Recent View"),
parseSequenceString("Esc"),
ShortcutGroup::View)},

View file

@ -14,10 +14,13 @@
#include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
#include <libcockatrice/protocol/pb/command_create_cast_count.pb.h>
#include <libcockatrice/protocol/pb/command_create_token.pb.h>
#include <libcockatrice/protocol/pb/command_delete_cast_count.pb.h>
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
#include <libcockatrice/protocol/pb/command_flip_card.pb.h>
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
#include <libcockatrice/protocol/pb/command_inc_cast_count.pb.h>
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
#include <libcockatrice/protocol/pb/command_mulligan.pb.h>
@ -25,7 +28,6 @@
#include <libcockatrice/protocol/pb/command_roll_die.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
#include <libcockatrice/protocol/pb/command_set_card_counter.pb.h>
#include <libcockatrice/protocol/pb/command_set_counter_active.pb.h>
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
@ -1674,32 +1676,34 @@ void PlayerActions::playSelectedCardsImpl(
}
}
void PlayerActions::actPlayAndIncrease1stTax(QList<CardItem *> selectedCards)
void PlayerActions::actPlayAndIncrease1stCastCount(QList<CardItem *> selectedCards)
{
playAndIncreaseTax(selectedCards, CounterIds::TaxCounter1);
playAndIncreaseCastCount(selectedCards, 1);
}
void PlayerActions::actPlayAndIncrease2ndTax(QList<CardItem *> selectedCards)
void PlayerActions::actPlayAndIncrease2ndCastCount(QList<CardItem *> selectedCards)
{
playAndIncreaseTax(selectedCards, CounterIds::TaxCounter2);
playAndIncreaseCastCount(selectedCards, 2);
}
void PlayerActions::playAndIncreaseTax(QList<CardItem *> selectedCards, int counterId)
void PlayerActions::playAndIncreaseCastCount(QList<CardItem *> selectedCards, int index)
{
playSelectedCardsImpl(selectedCards, false, [this, counterId](PendingCommand *pend, const QString &originalZone) {
playSelectedCardsImpl(selectedCards, false, [this, index](PendingCommand *pend, const QString &originalZone) {
if (originalZone != ZoneNames::COMMAND || pend == nullptr) {
return;
}
// Gate the tax increment on the server accepting the move, so a rejected move
// (the card couldn't legally leave the command zone) never inflates the tax.
// Gate the increment on the server accepting the move
connect(pend, &PendingCommand::finished, this,
[this, counterId](const Response &response, const CommandContainer &, const QVariant &) {
[this, index](const Response &response, const CommandContainer &, const QVariant &) {
if (response.response_code() != Response::RespOk) {
return;
}
CounterState *state = player->getCounters().value(counterId, nullptr);
if (state && state->isActive()) {
sendIncCounter(counterId, 1);
CounterState *state = player->getCastCount(index);
if (state) {
Command_IncCastCount cmd;
cmd.set_index(index);
cmd.set_delta(1);
sendGameCommand(cmd);
}
});
});
@ -1713,29 +1717,40 @@ void PlayerActions::sendIncCounter(int counterId, int delta)
sendGameCommand(cmd);
}
void PlayerActions::actModifyTaxCounter(int counterId, int delta)
void PlayerActions::actModifyCastCount(int index, int delta)
{
CounterState *state = player->getCounters().value(counterId, nullptr);
if (!state || !state->isActive() || !CounterIds::isTaxCounter(counterId)) {
if (!CastCountIds::isValidIndex(index)) {
return;
}
sendIncCounter(counterId, delta);
CounterState *state = player->getCastCount(index);
if (!state) {
return;
}
Command_IncCastCount cmd;
cmd.set_index(index);
cmd.set_delta(delta);
sendGameCommand(cmd);
}
void PlayerActions::actToggleTaxCounter(int counterId)
void PlayerActions::actToggleCastCount(int index)
{
CounterState *state = player->getCounters().value(counterId, nullptr);
if (!state || !CounterIds::isTaxCounter(counterId)) {
if (!CastCountIds::isValidIndex(index)) {
return;
}
// Prevent disabling a counter with tax accumulated; player must reset to 0 first
if (state->isActive() && state->getValue() != 0) {
return;
CounterState *state = player->getCastCount(index);
if (state) {
// Delete only allowed when value is 0
if (state->getValue() != 0) {
return;
}
Command_DeleteCastCount cmd;
cmd.set_index(index);
sendGameCommand(cmd);
} else {
Command_CreateCastCount cmd;
cmd.set_index(index);
sendGameCommand(cmd);
}
Command_SetCounterActive cmd;
cmd.set_counter_id(counterId);
cmd.set_active(!state->isActive());
sendGameCommand(cmd);
}
void PlayerActions::actPlay(QList<CardItem *> selectedCards)

View file

@ -127,14 +127,14 @@ public slots:
void actPlay(QList<CardItem *> selectedCards);
void actPlayFacedown(QList<CardItem *> selectedCards);
/** @brief Plays the selected card and increments the 1st tax counter. */
void actPlayAndIncrease1stTax(QList<CardItem *> selectedCards);
/** @brief Plays the selected card and increments the 2nd tax counter. */
void actPlayAndIncrease2ndTax(QList<CardItem *> selectedCards);
/** @brief Modifies a tax counter by delta if it is active. */
void actModifyTaxCounter(int counterId, int delta);
/** @brief Toggles a tax counter's active state (only if inactive or value is 0). */
void actToggleTaxCounter(int counterId);
/** @brief Plays the selected card and increments the 1st cast count. */
void actPlayAndIncrease1stCastCount(QList<CardItem *> selectedCards);
/** @brief Plays the selected card and increments the 2nd cast count. */
void actPlayAndIncrease2ndCastCount(QList<CardItem *> selectedCards);
/** @brief Modifies a cast count by delta. */
void actModifyCastCount(int index, int delta);
/** @brief Toggles a cast count's existence (create if missing, delete if value is 0). */
void actToggleCastCount(int index);
void actHide(QList<CardItem *> selectedCards);
void actMoveTopCardToPlay();
@ -280,11 +280,11 @@ private:
/**
* @brief Plays the selected cards and, for each that came from the command zone and whose move
* the server accepts, increments the given (active) tax counter by one.
* the server accepts, increments the given cast count by one.
* @param selectedCards Cards to play
* @param counterId The tax counter to increment (CounterIds::TaxCounter1 through TaxCounter5)
* @param index The cast count index (1-5)
*/
void playAndIncreaseTax(QList<CardItem *> selectedCards, int counterId);
void playAndIncreaseCastCount(QList<CardItem *> selectedCards, int index);
void cmdSetTopCard(Command_MoveCard &cmd);
void cmdSetBottomCard(Command_MoveCard &cmd);

View file

@ -15,10 +15,12 @@
#include <libcockatrice/protocol/pb/event_attach_card.pb.h>
#include <libcockatrice/protocol/pb/event_change_zone_properties.pb.h>
#include <libcockatrice/protocol/pb/event_create_arrow.pb.h>
#include <libcockatrice/protocol/pb/event_create_cast_count.pb.h>
#include <libcockatrice/protocol/pb/event_create_counter.pb.h>
#include <libcockatrice/protocol/pb/event_create_token.pb.h>
#include <libcockatrice/protocol/pb/event_del_counter.pb.h>
#include <libcockatrice/protocol/pb/event_delete_arrow.pb.h>
#include <libcockatrice/protocol/pb/event_delete_cast_count.pb.h>
#include <libcockatrice/protocol/pb/event_destroy_card.pb.h>
#include <libcockatrice/protocol/pb/event_draw_cards.pb.h>
#include <libcockatrice/protocol/pb/event_dump_zone.pb.h>
@ -30,8 +32,8 @@
#include <libcockatrice/protocol/pb/event_roll_die.pb.h>
#include <libcockatrice/protocol/pb/event_set_card_attr.pb.h>
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
#include <libcockatrice/protocol/pb/event_set_cast_count.pb.h>
#include <libcockatrice/protocol/pb/event_set_counter.pb.h>
#include <libcockatrice/protocol/pb/event_set_counter_active.pb.h>
#include <libcockatrice/protocol/pb/event_shuffle.pb.h>
#include <libcockatrice/utility/color.h>
#include <libcockatrice/utility/zone_names.h>
@ -277,20 +279,26 @@ void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
}
}
void PlayerEventHandler::eventSetCounterActive(const Event_SetCounterActive &event)
{
CounterState *state = player->getCounters().value(event.counter_id(), nullptr);
if (!state) {
return;
}
state->setActive(event.active());
}
void PlayerEventHandler::eventDelCounter(const Event_DelCounter &event)
{
player->delCounter(event.counter_id());
}
void PlayerEventHandler::eventCreateCastCount(const Event_CreateCastCount &event)
{
player->addCastCount(event.index());
}
void PlayerEventHandler::eventDeleteCastCount(const Event_DeleteCastCount &event)
{
player->delCastCount(event.index());
}
void PlayerEventHandler::eventSetCastCount(const Event_SetCastCount &event)
{
player->setCastCountValue(event.index(), event.value());
}
void PlayerEventHandler::eventDumpZone(const Event_DumpZone &event)
{
PlayerLogic *zoneOwner = player->getGame()->getPlayerManager()->getPlayers().value(event.zone_owner_id(), 0);
@ -640,12 +648,18 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
case GameEvent::SET_COUNTER:
eventSetCounter(event.GetExtension(Event_SetCounter::ext));
break;
case GameEvent::SET_COUNTER_ACTIVE:
eventSetCounterActive(event.GetExtension(Event_SetCounterActive::ext));
break;
case GameEvent::DEL_COUNTER:
eventDelCounter(event.GetExtension(Event_DelCounter::ext));
break;
case GameEvent::CREATE_CAST_COUNT:
eventCreateCastCount(event.GetExtension(Event_CreateCastCount::ext));
break;
case GameEvent::DELETE_CAST_COUNT:
eventDeleteCastCount(event.GetExtension(Event_DeleteCastCount::ext));
break;
case GameEvent::SET_CAST_COUNT:
eventSetCastCount(event.GetExtension(Event_SetCastCount::ext));
break;
case GameEvent::DUMP_ZONE:
eventDumpZone(event.GetExtension(Event_DumpZone::ext));
break;

View file

@ -48,7 +48,9 @@ class Event_RollDie;
class Event_SetCardAttr;
class Event_SetCardCounter;
class Event_SetCounter;
class Event_SetCounterActive;
class Event_CreateCastCount;
class Event_DeleteCastCount;
class Event_SetCastCount;
class Event_Shuffle;
class Event_GameLogNotice;
@ -156,12 +158,18 @@ public:
/// Set a player-level counter value.
void eventSetCounter(const Event_SetCounter &event);
/** @brief Show or hide a player-level counter without deleting it. */
void eventSetCounterActive(const Event_SetCounterActive &event);
/// Delete a player-level counter.
void eventDelCounter(const Event_DelCounter &event);
/// Create a cast count.
void eventCreateCastCount(const Event_CreateCastCount &event);
/// Delete a cast count.
void eventDeleteCastCount(const Event_DeleteCastCount &event);
/// Set a cast count value.
void eventSetCastCount(const Event_SetCastCount &event);
/** @} */
/** @name Zone-level operations

View file

@ -292,6 +292,49 @@ void PlayerLogic::clearCounters()
}
qDeleteAll(counters);
counters.clear();
for (int index : castCounts.keys()) {
emit castCountRemoved(index);
}
qDeleteAll(castCounts);
castCounts.clear();
}
CounterState *PlayerLogic::addCastCount(int index)
{
if (castCounts.contains(index)) {
return nullptr;
}
QString name = CastCountIds::nameForIndex(index);
auto *state = new CounterState(index, name, QColor(128, 128, 128), 20, 0, true, this);
castCounts.insert(index, state);
emit castCountAdded(index, state);
return state;
}
void PlayerLogic::delCastCount(int index)
{
auto *state = castCounts.take(index);
if (!state) {
return;
}
emit castCountRemoved(index);
state->deleteLater();
}
void PlayerLogic::setCastCountValue(int index, int value)
{
auto *state = castCounts.value(index, nullptr);
if (!state) {
return;
}
state->setValue(value);
emit castCountValueChanged(index, value);
}
CounterState *PlayerLogic::getCastCount(int index) const
{
return castCounts.value(index, nullptr);
}
CounterState *PlayerLogic::getLifeCounter() const

View file

@ -79,6 +79,9 @@ signals:
void counterAdded(CounterState *state);
void counterRemoved(int counterId);
void rearrangeCounters();
void castCountAdded(int index, CounterState *state);
void castCountRemoved(int index);
void castCountValueChanged(int index, int value);
void activeChanged(bool active);
void zoneIdChanged(int zoneId);
void concededChanged(int playerId, bool conceded);
@ -225,6 +228,16 @@ public:
*/
CounterState *getLifeCounter() const;
// Cast count management
CounterState *addCastCount(int index);
void delCastCount(int index);
void setCastCountValue(int index, int value);
CounterState *getCastCount(int index) const;
const QMap<int, CounterState *> &getCastCounts() const
{
return castCounts;
}
void setConceded(bool _conceded);
bool getConceded() const
{
@ -259,6 +272,7 @@ private:
int zoneId;
QMap<QString, CardZoneLogic *> zones;
QMap<int, CounterState *> counters;
QMap<int, CounterState *> castCounts; // index (1-5) -> state
bool dialogSemaphore;
QList<CardItem *> cardsToDelete;

View file

@ -30,7 +30,7 @@ AbstractCounter::AbstractCounter(CounterState *state,
setAcceptHoverEvents(true);
// Route through the (possibly overridden) virtual setValue so subclasses such as
// CommanderTaxCounter can clamp and refresh their tooltip on every value change.
// CastCountWidget can clamp and refresh their tooltip on every value change.
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) { setValue(newValue); });
connect(state, &CounterState::activeChanged, this, [this](bool newActive) {

View file

@ -74,7 +74,7 @@ public:
/**
* @brief Sets the counter value and triggers a visual update.
*
* Virtual to allow subclass display customization (e.g., CommanderTaxCounter tooltip updates).
* Virtual to allow subclass display customization (e.g., CastCountWidget tooltip updates).
* Overflow protection is handled server-side, not in client counter classes.
*
* @param _value The new counter value
@ -122,9 +122,9 @@ public:
/**
* @brief Shows or hides the counter and enables or disables its menu.
*
* Sole owner of both, so container layout code only positions counters and may read
* isActive(). Qt AND-s visibility with the parent item's, so an active counter nested in a
* hidden zone (e.g. a tax counter in the command zone) still does not render.
* Sole owner of both visibility and menu state, so container layout code only positions
* counters and may read isActive(). Note that Qt AND-s visibility with the parent item's,
* so an active counter nested in a hidden parent still does not render.
*
* @param _active True to show and enable the counter, false to hide and disable it
*/

View file

@ -1,32 +1,30 @@
#include "commander_tax_counter.h"
#include "cast_count_widget.h"
#include "../../game/board/counter_state.h"
#include "translate_counter_name.h"
#include <QColor>
#include <QFontDatabase>
#include <QPainter>
static constexpr qreal CORNER_RADIUS = 4.0;
static constexpr qreal FONT_SIZE_RATIO = 0.6;
CommanderTaxCounter::CommanderTaxCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent)
CastCountWidget::CastCountWidget(CounterState *state, PlayerLogic *player, QGraphicsItem *parent)
: AbstractCounter(state, player, false, false, parent), size(state->getRadius())
{
setCacheMode(DeviceCoordinateCache);
setAcceptHoverEvents(true);
setToolTip(tr("%1: %2").arg(TranslateCounterName::getDisplayName(getName())).arg(getValue()));
}
QRectF CommanderTaxCounter::boundingRect() const
QRectF CastCountWidget::boundingRect() const
{
return QRectF(0, 0, size, size);
}
void CommanderTaxCounter::paint(QPainter *painter,
[[maybe_unused]] const QStyleOptionGraphicsItem *option,
[[maybe_unused]] QWidget *widget)
void CastCountWidget::paint(QPainter *painter,
[[maybe_unused]] const QStyleOptionGraphicsItem *option,
[[maybe_unused]] QWidget *widget)
{
painter->save();
@ -46,7 +44,7 @@ void CommanderTaxCounter::paint(QPainter *painter,
painter->restore();
}
void CommanderTaxCounter::setValue(int _value)
void CastCountWidget::setValue(int _value)
{
int clampedValue = qMax(0, _value);
AbstractCounter::setValue(clampedValue);

View file

@ -0,0 +1,35 @@
/**
* @file cast_count_widget.h
* @ingroup GameGraphicsBoard
* @brief Displays a single cast count overlay counter in the command zone.
*/
#ifndef COCKATRICE_CAST_COUNT_WIDGET_H
#define COCKATRICE_CAST_COUNT_WIDGET_H
#include "abstract_counter.h"
namespace CastCountSizes
{
constexpr int MARGIN = 2;
} // namespace CastCountSizes
/**
* @class CastCountWidget
* @brief A compact counter widget for tracking cast counts in the command zone.
*/
class CastCountWidget : public AbstractCounter
{
Q_OBJECT
private:
int size;
public:
CastCountWidget(CounterState *state, PlayerLogic *player, QGraphicsItem *parent = nullptr);
[[nodiscard]] QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void setValue(int _value) override;
};
#endif // COCKATRICE_CAST_COUNT_WIDGET_H

View file

@ -1,70 +0,0 @@
/**
* @file commander_tax_counter.h
* @ingroup GameGraphicsPlayers
* @brief Square counter for commander tax, clamped to non-negative values.
*/
#ifndef COCKATRICE_COMMANDER_TAX_COUNTER_H
#define COCKATRICE_COMMANDER_TAX_COUNTER_H
#include "abstract_counter.h"
/**
* @namespace TaxCounterSizes
* @brief Layout constants for commander tax counters.
*/
namespace TaxCounterSizes
{
/** @brief Margin around and between tax counter icons */
constexpr int TAX_COUNTER_MARGIN = 2;
} // namespace TaxCounterSizes
/**
* @class CommanderTaxCounter
* @brief Counter for tracking commander tax in Commander format.
*
* Displays the number of times the commander has been cast from the command
* zone. Can be adjusted manually via +1/-1 menu actions, or automatically
* incremented when using "Play and Increase Tax" on an accepted cast from
* the command zone. Values are clamped to >= 0.
*
* Appearance: square with rounded corners, semi-transparent background,
* positioned at top-left of command zone.
*
* Two instances per player: CounterIds::CommanderTax and CounterIds::PartnerTax.
* Each counter supports an active/inactive state (inherited from AbstractCounter):
* commander tax starts active; partner tax starts inactive until explicitly
* enabled by the player via the context menu.
*
* @see AbstractCounter
* @see AbstractCounter::setActive()
* @see CounterIds
*/
class CommanderTaxCounter : public AbstractCounter
{
Q_OBJECT
private:
int size; ///< Width and height of the counter in pixels
public:
/**
* @brief Constructs a CommanderTaxCounter.
* @param state Counter state containing id, name, value, etc.
* @param player The player who owns this counter
* @param parent Parent graphics item (typically the command zone)
*/
CommanderTaxCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent = nullptr);
[[nodiscard]] QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
/**
* @brief Overrides AbstractCounter::setValue to clamp values to >= 0 and update the tooltip.
* @param _value New value (clamped if negative)
*/
void setValue(int _value) override;
};
#endif // COCKATRICE_COMMANDER_TAX_COUNTER_H

View file

@ -1,7 +1,5 @@
#include "translate_counter_name.h"
#include <libcockatrice/utility/counter_ids.h>
const QMap<QString, QString> TranslateCounterName::translated = {
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},
{"w", QT_TRANSLATE_NOOP("TranslateCounterName", "White")},
@ -11,8 +9,8 @@ const QMap<QString, QString> TranslateCounterName::translated = {
{"g", QT_TRANSLATE_NOOP("TranslateCounterName", "Green")},
{"x", QT_TRANSLATE_NOOP("TranslateCounterName", "Colorless")},
{"storm", QT_TRANSLATE_NOOP("TranslateCounterName", "Other")},
{CounterNames::TaxCounter1, QT_TRANSLATE_NOOP("TranslateCounterName", "1st Tax")},
{CounterNames::TaxCounter2, QT_TRANSLATE_NOOP("TranslateCounterName", "2nd Tax")},
{CounterNames::TaxCounter3, QT_TRANSLATE_NOOP("TranslateCounterName", "3rd Tax")},
{CounterNames::TaxCounter4, QT_TRANSLATE_NOOP("TranslateCounterName", "4th Tax")},
{CounterNames::TaxCounter5, QT_TRANSLATE_NOOP("TranslateCounterName", "5th Tax")}};
{"cast_count_1", QT_TRANSLATE_NOOP("TranslateCounterName", "1st Cast Count")},
{"cast_count_2", QT_TRANSLATE_NOOP("TranslateCounterName", "2nd Cast Count")},
{"cast_count_3", QT_TRANSLATE_NOOP("TranslateCounterName", "3rd Cast Count")},
{"cast_count_4", QT_TRANSLATE_NOOP("TranslateCounterName", "4th Cast Count")},
{"cast_count_5", QT_TRANSLATE_NOOP("TranslateCounterName", "5th Cast Count")}};

View file

@ -672,27 +672,11 @@ void MessageLogWidget::logSetCardCounter(PlayerLogic *player, QString cardName,
void MessageLogWidget::logSetCounter(PlayerLogic *player, int counterId, QString counterName, int value, int oldValue)
{
Q_UNUSED(counterId);
if (counterName == "life") {
soundEngine->playSound("life_change");
}
if (CounterIds::isTaxCounter(counterId)) {
QString playerName = sanitizeHtml(player->getPlayerInfo()->getName());
QString valueStr = QString("<font class=\"blue\">%1</font>").arg(value);
int delta = value - oldValue;
QString deltaStr = QString::number(delta);
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
QString taxLabel = QString("<font class=\"blue\">%1</font>").arg(sanitizeHtml(counterDisplayName));
if (value > oldValue) {
// delta > 0 here; the format string supplies the leading '+'
appendHtmlServerMessage(tr("%1 increases %2 to %3 (+%4).").arg(playerName, taxLabel, valueStr, deltaStr));
} else {
// delta < 0 here; deltaStr already carries the '-' sign
appendHtmlServerMessage(tr("%1 decreases %2 to %3 (%4).").arg(playerName, taxLabel, valueStr, deltaStr));
}
return;
}
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
appendHtmlServerMessage(tr("%1 sets counter %2 to %3 (%4%5).")
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))

View file

@ -84,8 +84,10 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho
aUnattach = makeAction(this, [actions, sel]() { actions->actUnattach(sel()); });
aSetAnnotation = makeAction(this, [actions, sel]() { actions->actRequestSetAnnotationDialog(sel()); });
aPlay = makeAction(this, [actions, sel]() { actions->actPlay(sel()); });
aPlayAndIncrease1stTax = makeAction(this, [actions, sel]() { actions->actPlayAndIncrease1stTax(sel()); });
aPlayAndIncrease2ndTax = makeAction(this, [actions, sel]() { actions->actPlayAndIncrease2ndTax(sel()); });
aPlayAndIncrease1stCastCount =
makeAction(this, [actions, sel]() { actions->actPlayAndIncrease1stCastCount(sel()); });
aPlayAndIncrease2ndCastCount =
makeAction(this, [actions, sel]() { actions->actPlayAndIncrease2ndCastCount(sel()); });
aPlayFacedown = makeAction(this, [actions, sel]() { actions->actPlayFacedown(sel()); });
aHide = makeAction(this, [actions, sel]() { actions->actHide(sel()); });
aReduceLifeByPower = makeAction(this, [actions, sel]() { actions->actReduceLifeByPower(sel()); });
@ -166,16 +168,15 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho
if (writeableCard) {
addAction(aPlay);
// Only offer for single selection: a multi-select would over-count casts by
// bumping one commander's tax counter once per command-zone card.
// Only offer for single selection: a multi-select would over-count casts
const bool singleSelection = gameScene->selectedCards().size() <= 1;
if (singleSelection && player->getTaxCounterIfActive(CounterIds::TaxCounter1)) {
addAction(aPlayAndIncrease1stTax);
if (singleSelection && player->getCastCountWidget(1)) {
addAction(aPlayAndIncrease1stCastCount);
}
if (singleSelection && player->getTaxCounterIfActive(CounterIds::TaxCounter2)) {
addAction(aPlayAndIncrease2ndTax);
if (singleSelection && player->getCastCountWidget(2)) {
addAction(aPlayAndIncrease2ndCastCount);
}
// No reveal submenu - command zone is public
@ -524,8 +525,8 @@ void CardMenu::retranslateUi()
aPlay->setText(tr("&Play"));
aHide->setText(tr("&Hide"));
aPlayFacedown->setText(tr("Play &Face Down"));
aPlayAndIncrease1stTax->setText(tr("Play and &Increase 1st Tax"));
aPlayAndIncrease2ndTax->setText(tr("Play and Increase &2nd Tax"));
aPlayAndIncrease1stCastCount->setText(tr("Play and &Increase 1st Cast Count"));
aPlayAndIncrease2ndCastCount->setText(tr("Play and Increase &2nd Cast Count"));
aRevealToAll->setText(tr("&All players"));
//: Turn sideways or back again
aTap->setText(tr("&Tap / Untap"));

View file

@ -32,8 +32,8 @@ public:
QMenu *mCardCounters;
QAction *aPlay, *aPlayFacedown;
/** @brief Play actions that also increment the corresponding tax counter. */
QAction *aPlayAndIncrease1stTax, *aPlayAndIncrease2ndTax;
/** @brief Play actions that also increment the corresponding cast count. */
QAction *aPlayAndIncrease1stCastCount, *aPlayAndIncrease2ndCastCount;
QAction *aRevealToAll;
QAction *aHide;
QAction *aClone;

View file

@ -14,42 +14,41 @@
CommandZoneMenu::CommandZoneMenu(PlayerGraphicsItem *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player)
{
// Shortcuts only for first two tax counters (matching legacy behavior)
incTax1ShortcutKey = QStringLiteral("Player/aAddCommanderTax");
decTax1ShortcutKey = QStringLiteral("Player/aRemoveCommanderTax");
incTax2ShortcutKey = QStringLiteral("Player/aAddPartnerTax");
decTax2ShortcutKey = QStringLiteral("Player/aRemovePartnerTax");
inc1ShortcutKey = QStringLiteral("Player/aIncrease1stCastCount");
dec1ShortcutKey = QStringLiteral("Player/aDecrease1stCastCount");
inc2ShortcutKey = QStringLiteral("Player/aIncrease2ndCastCount");
dec2ShortcutKey = QStringLiteral("Player/aDecrease2ndCastCount");
PlayerLogic *logic = player->getLogic();
if (logic && logic->getPlayerInfo()->getLocalOrJudge()) {
for (int i = 0; i < TaxCounterCount; ++i) {
int counterId = CounterIds::taxCounterIdFromIndex(i);
for (int i = 0; i < CastCountCount; ++i) {
int index = i + 1; // 1-based index
aIncreaseTax[i] = new QAction(this);
connect(aIncreaseTax[i], &QAction::triggered, this, [this, counterId]() {
aIncrease[i] = new QAction(this);
connect(aIncrease[i], &QAction::triggered, this, [this, index]() {
if (auto *l = player->getLogic()) {
l->getPlayerActions()->actModifyTaxCounter(counterId, 1);
l->getPlayerActions()->actModifyCastCount(index, 1);
}
});
addAction(aIncreaseTax[i]);
addAction(aIncrease[i]);
aDecreaseTax[i] = new QAction(this);
connect(aDecreaseTax[i], &QAction::triggered, this, [this, counterId]() {
aDecrease[i] = new QAction(this);
connect(aDecrease[i], &QAction::triggered, this, [this, index]() {
if (auto *l = player->getLogic()) {
l->getPlayerActions()->actModifyTaxCounter(counterId, -1);
l->getPlayerActions()->actModifyCastCount(index, -1);
}
});
addAction(aDecreaseTax[i]);
addAction(aDecrease[i]);
addSeparator();
aToggleTax[i] = new QAction(this);
connect(aToggleTax[i], &QAction::triggered, this, [this, counterId]() {
aToggle[i] = new QAction(this);
connect(aToggle[i], &QAction::triggered, this, [this, index]() {
if (auto *l = player->getLogic()) {
l->getPlayerActions()->actToggleTaxCounter(counterId);
l->getPlayerActions()->actToggleCastCount(index);
}
});
addAction(aToggleTax[i]);
addAction(aToggle[i]);
addSeparator();
}
@ -58,7 +57,7 @@ CommandZoneMenu::CommandZoneMenu(PlayerGraphicsItem *_player, QMenu *playerMenu)
connect(aToggleMinimized, &QAction::triggered, this, &CommandZoneMenu::actToggleMinimized);
addAction(aToggleMinimized);
connect(this, &QMenu::aboutToShow, this, &CommandZoneMenu::updateTaxCounterActionStates);
connect(this, &QMenu::aboutToShow, this, &CommandZoneMenu::updateCastCountActionStates);
}
retranslateUi();
@ -70,14 +69,13 @@ void CommandZoneMenu::retranslateUi()
static const char *ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
for (int i = 0; i < TaxCounterCount; ++i) {
if (aIncreaseTax[i]) {
aIncreaseTax[i]->setText(tr("&Increase %1 Tax (+1)").arg(ordinals[i]));
for (int i = 0; i < CastCountCount; ++i) {
if (aIncrease[i]) {
aIncrease[i]->setText(tr("&Increase %1 Cast Count (+1)").arg(ordinals[i]));
}
if (aDecreaseTax[i]) {
aDecreaseTax[i]->setText(tr("&Decrease %1 Tax (-1)").arg(ordinals[i]));
if (aDecrease[i]) {
aDecrease[i]->setText(tr("&Decrease %1 Cast Count (-1)").arg(ordinals[i]));
}
// Toggle action labels are derived dynamically in updateTaxCounterActionStates()
}
if (aToggleMinimized) {
@ -93,44 +91,39 @@ void CommandZoneMenu::actToggleMinimized()
}
}
void CommandZoneMenu::updateTaxCounterActionStates()
void CommandZoneMenu::updateCastCountActionStates()
{
static const char *ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
// Collect all tax counter states
std::array<AbstractCounter *, TaxCounterCount> taxCounters{};
for (int i = 0; i < TaxCounterCount; ++i) {
taxCounters[i] = player->getTaxCounterIfActive(CounterIds::taxCounterIdFromIndex(i));
std::array<AbstractCounter *, CastCountCount> castCounts{};
for (int i = 0; i < CastCountCount; ++i) {
castCounts[i] = player->getCastCountWidget(i + 1);
}
// Find highest active tax counter index
int highestActive = -1;
for (int i = TaxCounterCount - 1; i >= 0; --i) {
if (taxCounters[i]) {
for (int i = CastCountCount - 1; i >= 0; --i) {
if (castCounts[i]) {
highestActive = i;
break;
}
}
for (int i = 0; i < TaxCounterCount; ++i) {
AbstractCounter *counter = taxCounters[i];
for (int i = 0; i < CastCountCount; ++i) {
AbstractCounter *counter = castCounts[i];
if (aIncreaseTax[i]) {
aIncreaseTax[i]->setVisible(counter && counter->getValue() < MAX_COUNTER_VALUE);
if (aIncrease[i]) {
aIncrease[i]->setVisible(counter && counter->getValue() < MAX_COUNTER_VALUE);
}
if (aDecreaseTax[i]) {
aDecreaseTax[i]->setVisible(counter && counter->getValue() > 0);
if (aDecrease[i]) {
aDecrease[i]->setVisible(counter && counter->getValue() > 0);
}
if (aToggleTax[i]) {
aToggleTax[i]->setText(counter ? tr("&Remove %1 Tax").arg(ordinals[i])
: tr("&Add %1 Tax").arg(ordinals[i]));
if (aToggle[i]) {
aToggle[i]->setText(counter ? tr("&Remove %1 Cast Count").arg(ordinals[i])
: tr("&Add %1 Cast Count").arg(ordinals[i]));
// Toggle visible if:
// - Counter doesn't exist and previous counter is active (can add next in sequence)
// - Counter exists with value 0 and is the highest active (can remove last in sequence)
bool canAdd = !counter && (i == 0 || taxCounters[i - 1]);
bool canAdd = !counter && (i == 0 || castCounts[i - 1]);
bool canRemove = counter && counter->getValue() == 0 && i == highestActive;
aToggleTax[i]->setVisible(canAdd || canRemove);
aToggle[i]->setVisible(canAdd || canRemove);
}
}
@ -144,34 +137,32 @@ void CommandZoneMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
// Only first two tax counters have shortcuts
if (aIncreaseTax[0]) {
aIncreaseTax[0]->setShortcuts(shortcuts.getShortcut(incTax1ShortcutKey));
if (aIncrease[0]) {
aIncrease[0]->setShortcuts(shortcuts.getShortcut(inc1ShortcutKey));
}
if (aDecreaseTax[0]) {
aDecreaseTax[0]->setShortcuts(shortcuts.getShortcut(decTax1ShortcutKey));
if (aDecrease[0]) {
aDecrease[0]->setShortcuts(shortcuts.getShortcut(dec1ShortcutKey));
}
if (aIncreaseTax[1]) {
aIncreaseTax[1]->setShortcuts(shortcuts.getShortcut(incTax2ShortcutKey));
if (aIncrease[1]) {
aIncrease[1]->setShortcuts(shortcuts.getShortcut(inc2ShortcutKey));
}
if (aDecreaseTax[1]) {
aDecreaseTax[1]->setShortcuts(shortcuts.getShortcut(decTax2ShortcutKey));
if (aDecrease[1]) {
aDecrease[1]->setShortcuts(shortcuts.getShortcut(dec2ShortcutKey));
}
}
void CommandZoneMenu::setShortcutsInactive()
{
// Only first two tax counters have shortcuts
if (aIncreaseTax[0]) {
aIncreaseTax[0]->setShortcut(QKeySequence());
if (aIncrease[0]) {
aIncrease[0]->setShortcut(QKeySequence());
}
if (aDecreaseTax[0]) {
aDecreaseTax[0]->setShortcut(QKeySequence());
if (aDecrease[0]) {
aDecrease[0]->setShortcut(QKeySequence());
}
if (aIncreaseTax[1]) {
aIncreaseTax[1]->setShortcut(QKeySequence());
if (aIncrease[1]) {
aIncrease[1]->setShortcut(QKeySequence());
}
if (aDecreaseTax[1]) {
aDecreaseTax[1]->setShortcut(QKeySequence());
if (aDecrease[1]) {
aDecrease[1]->setShortcut(QKeySequence());
}
}

View file

@ -1,7 +1,7 @@
/**
* @file command_zone_menu.h
* @ingroup GameMenusZones
* @brief Context menu for command zone right-click actions.
* @ingroup GameGraphicsPlayers
* @brief Submenu for command zone actions (cast counts, minimize/restore).
*/
#ifndef COCKATRICE_COMMAND_ZONE_MENU_H
@ -17,10 +17,10 @@ class PlayerGraphicsItem;
/**
* @class CommandZoneMenu
* @brief Context menu for the command zone.
* @brief Player submenu for managing cast counts and command zone display.
*
* Appears when right-clicking on the command zone. Provides actions for
* adjusting tax counters (up to 5) and toggling minimized state.
* Provides actions to increment/decrement each cast count, toggle cast counts
* on/off, and minimize/restore the command zone display.
*
* @see PlayerMenu
* @see CommandZone
@ -36,15 +36,15 @@ public:
void setShortcutsInactive() override;
private:
static constexpr int TaxCounterCount = CounterIds::TaxCounterCount;
static constexpr int CastCountCount = CastCountIds::Count;
std::array<QAction *, TaxCounterCount> aIncreaseTax{};
std::array<QAction *, TaxCounterCount> aDecreaseTax{};
std::array<QAction *, TaxCounterCount> aToggleTax{};
std::array<QAction *, CastCountCount> aIncrease{};
std::array<QAction *, CastCountCount> aDecrease{};
std::array<QAction *, CastCountCount> aToggle{};
QAction *aToggleMinimized = nullptr;
public slots:
void updateTaxCounterActionStates();
void updateCastCountActionStates();
private slots:
void actToggleMinimized();
@ -52,11 +52,10 @@ private slots:
private:
PlayerGraphicsItem *player;
// Shortcuts only for first two tax counters
QString incTax1ShortcutKey;
QString decTax1ShortcutKey;
QString incTax2ShortcutKey;
QString decTax2ShortcutKey;
QString inc1ShortcutKey;
QString dec1ShortcutKey;
QString inc2ShortcutKey;
QString dec2ShortcutKey;
};
#endif // COCKATRICE_COMMAND_ZONE_MENU_H

View file

@ -3,7 +3,7 @@
#include "../../game/player/player_actions.h"
#include "../../interface/widgets/tabs/tab_game.h"
#include "../board/abstract_card_item.h"
#include "../board/commander_tax_counter.h"
#include "../board/cast_count_widget.h"
#include "../board/counter_general.h"
#include "../hand_counter.h"
#include "../z_values.h"
@ -32,6 +32,8 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved);
connect(player, &PlayerLogic::castCountAdded, this, &PlayerGraphicsItem::onCastCountAdded);
connect(player, &PlayerLogic::castCountRemoved, this, &PlayerGraphicsItem::onCastCountRemoved);
playerMenu = new PlayerMenu(this);
@ -231,34 +233,12 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
AbstractCounter *widget;
if (state->getName() == "life") {
widget = playerTarget->addCounter(state);
} else if (CounterIds::isTaxCounter(state->getId())) {
if (!commandZoneGraphicsItem) {
qWarning() << "Cannot create tax counter" << state->getName() << "- command zone not available";
return;
}
// Qt parent (commandZoneGraphicsItem) owns widget; counterWidgets map holds reference
// for lookup; CommandZone::registerTaxCounter connects QObject::destroyed for cleanup
widget = new CommanderTaxCounter(state, player, commandZoneGraphicsItem);
widget->setActive(state->isActive());
commandZoneGraphicsItem->registerTaxCounter(widget);
if (auto *menu = playerMenu->getCommandZoneMenu()) {
connect(state, &CounterState::activeChanged, menu, &CommandZoneMenu::updateTaxCounterActionStates);
connect(state, &CounterState::valueChanged, menu, &CommandZoneMenu::updateTaxCounterActionStates);
}
} else {
widget = new GeneralCounter(state, player, true, this);
}
counterWidgets.insert(state->getId(), widget);
// A counter's submenu follows its isActive() state: while inactive the counter is hidden and
// the server rejects every modification. Only tax counters go inactive today.
setCounterMenuRegistered(widget, state->isActive());
connect(state, &CounterState::activeChanged, this, [this, counterId = state->getId()](bool newActive) {
if (AbstractCounter *counter = getCounterWidget(counterId)) {
setCounterMenuRegistered(counter, newActive);
}
});
if (playerMenu->getShortcutsActive()) {
widget->setShortcutsActive();
@ -267,6 +247,25 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
rearrangeCounters();
}
void PlayerGraphicsItem::onCastCountAdded(int index, CounterState *state)
{
if (!commandZoneGraphicsItem) {
qWarning() << "Cannot create cast count" << index << "- command zone not available";
return;
}
auto *widget = new CastCountWidget(state, player, commandZoneGraphicsItem);
commandZoneGraphicsItem->registerCastCount(widget);
castCountWidgets.insert(index, widget);
if (auto *menu = playerMenu->getCommandZoneMenu()) {
menu->updateCastCountActionStates();
connect(state, &CounterState::valueChanged, menu, &CommandZoneMenu::updateCastCountActionStates);
}
rearrangeCounters();
}
void PlayerGraphicsItem::onCounterRemoved(int counterId)
{
auto *widget = counterWidgets.take(counterId);
@ -274,25 +273,37 @@ void PlayerGraphicsItem::onCounterRemoved(int counterId)
return;
}
setCounterMenuRegistered(widget, false);
if (commandZoneGraphicsItem && CounterIds::isTaxCounter(widget->getId())) {
commandZoneGraphicsItem->unregisterTaxCounter(widget);
widget->delCounter();
rearrangeCounters();
}
void PlayerGraphicsItem::onCastCountRemoved(int index)
{
auto *widget = castCountWidgets.take(index);
if (!widget) {
return;
}
if (commandZoneGraphicsItem) {
commandZoneGraphicsItem->unregisterCastCount(widget);
}
widget->delCounter();
if (auto *menu = playerMenu->getCommandZoneMenu()) {
menu->updateCastCountActionStates();
}
rearrangeCounters();
}
void PlayerGraphicsItem::rearrangeCounters()
{
if (commandZoneGraphicsItem) {
commandZoneGraphicsItem->rearrangeTaxCounters();
commandZoneGraphicsItem->rearrangeCastCounts();
}
qreal ySize = boundingRect().y() + 80;
constexpr qreal padding = 5;
for (auto *ctr : counterWidgets.values()) {
if (CounterIds::isTaxCounter(ctr->getId())) {
continue;
}
if (!ctr->getShownInCounterArea()) {
continue;
}
@ -302,10 +313,9 @@ void PlayerGraphicsItem::rearrangeCounters()
}
}
AbstractCounter *PlayerGraphicsItem::getTaxCounterIfActive(int counterId) const
AbstractCounter *PlayerGraphicsItem::getCastCountWidget(int index) const
{
AbstractCounter *counter = getCounterWidget(counterId);
return (counter && counter->isActive()) ? counter : nullptr;
return castCountWidgets.value(index, nullptr);
}
void PlayerGraphicsItem::rearrangeZones()

View file

@ -123,14 +123,16 @@ public:
{
return counterWidgets.value(counterId, nullptr);
}
/** @brief Returns the tax counter if it exists and is active, or nullptr otherwise. */
[[nodiscard]] AbstractCounter *getTaxCounterIfActive(int counterId) const;
/** @brief Returns the cast count widget for the given index (1-5), or nullptr if not found. */
[[nodiscard]] AbstractCounter *getCastCountWidget(int index) const;
public slots:
void onPlayerActiveChanged(bool _active);
void onCustomZoneAdded(QString customZoneName);
void onCounterAdded(CounterState *state);
void onCounterRemoved(int counterId);
void onCastCountAdded(int index, CounterState *state);
void onCastCountRemoved(int index);
void rearrangeCounters();
void retranslateUi();
@ -147,6 +149,7 @@ private:
PlayerArea *playerArea;
PlayerTarget *playerTarget;
QMap<int, AbstractCounter *> counterWidgets;
QMap<int, AbstractCounter *> castCountWidgets;
QMap<QString, CardZone *> zoneGraphicsItems;
PileZone *deckZoneGraphicsItem;
PileZone *sideboardGraphicsItem;

View file

@ -34,8 +34,8 @@ constexpr qreal OVERLAY_BASE = ZValueLayerManager::OVERLAY_BASE;
// Overlay layer Z-values for items that should appear above normal cards
constexpr qreal HOVERED_CARD = ZValueLayerManager::overlayZValue(1.0);
/** @brief Commander tax counter overlay */
constexpr qreal TAX_COUNTERS = ZValueLayerManager::overlayZValue(2.0);
/** @brief Cast count overlay */
constexpr qreal CAST_COUNTS = ZValueLayerManager::overlayZValue(2.0);
constexpr qreal ARROWS = ZValueLayerManager::overlayZValue(3.0);
constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0);
constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0);

View file

@ -7,7 +7,7 @@
#include "../board/abstract_counter.h"
#include "../board/card_drag_item.h"
#include "../board/card_item.h"
#include "../board/commander_tax_counter.h"
#include "../board/cast_count_widget.h"
#include "../z_values.h"
#include <QGraphicsSceneMouseEvent>
@ -26,7 +26,7 @@ CommandZone::CommandZone(CommandZoneLogic *_logic, int _zoneHeight, QGraphicsIte
CommandZone::~CommandZone()
{
for (AbstractCounter *ctr : taxCounters) {
for (AbstractCounter *ctr : castCounts) {
disconnect(ctr, &QObject::destroyed, this, nullptr);
}
}
@ -52,7 +52,7 @@ void CommandZone::setMinimumHeight(int height)
return;
}
// The floor only affects height while minimized; skip relayout if the displayed
// height is unchanged (e.g. a tax counter toggled while expanded).
// height is unchanged (e.g. a cast count toggled while expanded).
const qreal oldEffectiveHeight = currentHeight();
minimumHeight = height;
if (qFuzzyCompare(1.0 + currentHeight(), 1.0 + oldEffectiveHeight)) {
@ -145,38 +145,32 @@ void CommandZone::reorganizeCards()
update();
}
void CommandZone::registerTaxCounter(AbstractCounter *counter)
void CommandZone::registerCastCount(AbstractCounter *counter)
{
if (!counter || taxCounters.contains(counter)) {
if (!counter || castCounts.contains(counter)) {
return;
}
taxCounters.append(counter);
connect(counter, &QObject::destroyed, this, [this, counter]() { taxCounters.removeOne(counter); });
castCounts.append(counter);
connect(counter, &QObject::destroyed, this, [this, counter]() { castCounts.removeOne(counter); });
}
void CommandZone::unregisterTaxCounter(AbstractCounter *counter)
void CommandZone::unregisterCastCount(AbstractCounter *counter)
{
if (!counter) {
return;
}
disconnect(counter, &QObject::destroyed, this, nullptr);
taxCounters.removeOne(counter);
castCounts.removeOne(counter);
}
void CommandZone::rearrangeTaxCounters()
void CommandZone::rearrangeCastCounts()
{
qreal y = TaxCounterSizes::TAX_COUNTER_MARGIN;
qreal y = CastCountSizes::MARGIN;
for (AbstractCounter *ctr : taxCounters) {
ctr->setPos(TaxCounterSizes::TAX_COUNTER_MARGIN, y);
ctr->setZValue(ZValues::TAX_COUNTERS);
// Visibility is owned solely by AbstractCounter::setActive() (the counter's own flag),
// which Qt AND-s with this CommandZone's visibility via child-visibility propagation
// (tax counters are graphics children of the zone). This function only handles layout,
// so it stacks and measures by isActive() alone.
if (ctr->isActive()) {
y += ctr->getRadius() + TaxCounterSizes::TAX_COUNTER_MARGIN;
}
for (AbstractCounter *ctr : castCounts) {
ctr->setPos(CastCountSizes::MARGIN, y);
ctr->setZValue(ZValues::CAST_COUNTS);
y += ctr->getRadius() + CastCountSizes::MARGIN;
}
setMinimumHeight(static_cast<int>(y));

View file

@ -38,21 +38,21 @@ constexpr qreal COMMAND_ZONE_WIDTH = CardDimensions::WIDTH_F * 1.5;
*
* Always visible when enabled. Uses the generic vertical stacking layout
* with bottom overflow enabled. Can be minimized via double-click (25% height,
* or the tax-counter floor if higher).
* or the cast count floor if higher).
*
* @see SelectZone::layoutCardsVertically for the stacking algorithm
* @see CommandZoneLogic for card data management
* @see CommanderTaxCounter for the tax counter overlay
* @see CastCountWidget for the cast count overlay
*/
class CommandZone : public SelectZone
{
Q_OBJECT
private:
static constexpr double MINIMIZED_HEIGHT_RATIO = 0.25;
int zoneHeight; ///< Full height in pixels when expanded
bool minimized = false; ///< Whether zone is collapsed (25% height, or the tax-counter floor)
int minimumHeight = 0; ///< Floor for minimized height (e.g. to fit tax counters)
QList<AbstractCounter *> taxCounters; ///< Registered tax counter widgets
int zoneHeight; ///< Full height in pixels when expanded
bool minimized = false; ///< Whether zone is collapsed (25% height, or the cast count floor)
int minimumHeight = 0; ///< Floor for minimized height (e.g. to fit cast counts)
QList<AbstractCounter *> castCounts; ///< Registered cast count widgets
public:
/**
@ -86,24 +86,24 @@ public:
[[nodiscard]] bool isMinimized() const;
/** @brief Returns the current display height (full or minimized). */
[[nodiscard]] qreal currentHeight() const;
/** @brief Sets the minimum height floor, e.g. to ensure tax counters remain visible. */
/** @brief Sets the minimum height floor, e.g. to ensure cast counts remain visible. */
void setMinimumHeight(int height);
/** @brief Registers a tax counter widget for layout management. */
void registerTaxCounter(AbstractCounter *counter);
/** @brief Registers a cast count widget for layout management. */
void registerCastCount(AbstractCounter *counter);
/**
* @brief Unregisters a tax counter widget from layout management.
* @brief Unregisters a cast count widget from layout management.
*
* Call on removal: the widget outlives it via deleteLater(), so unregister synchronously
* to keep it out of the next layout pass.
*/
void unregisterTaxCounter(AbstractCounter *counter);
/** @brief Lays out visible tax counters vertically in the top-left corner of the command zone. */
void rearrangeTaxCounters();
void unregisterCastCount(AbstractCounter *counter);
/** @brief Lays out visible cast counts vertically in the top-left corner of the command zone. */
void rearrangeCastCounts();
signals:
/** @brief Emitted when the zone's minimized state changes. */
void minimizedChanged(bool isMinimized);
/** @brief Emitted when display height changes without a minimized-state change (e.g. tax counter toggled while
/** @brief Emitted when display height changes without a minimized-state change (e.g. cast count toggled while
* minimized). */
void effectiveHeightChanged();