[Game] Add Command Zone support with commander tax tracking

- Add CommandZone and CommandZoneLogic for commander
  - Add CommanderTaxCounter
  - Add counter active state protocol (show/hide tax counters)
  - Add "Enable Command Zone" option in game creation dialogs
  - Add context menu actions for command zone operations

Took 9 minutes

Took 11 minutes
This commit is contained in:
DawnFire42 2026-05-21 21:30:40 -04:00
parent 687e6644bc
commit 75d59e2d82
No known key found for this signature in database
GPG key ID: 24BB855EE2911B33
73 changed files with 1540 additions and 86 deletions

View file

@ -2,6 +2,7 @@
#include "../../../client/settings/card_counter_settings.h"
#include "../../../interface/widgets/tabs/tab_game.h"
#include "../../board/abstract_counter.h"
#include "../../board/card_item.h"
#include "../../game/player/player_actions.h"
#include "../../game/player/player_logic.h"
@ -14,6 +15,7 @@
#include <QPainter>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/card/relation/card_relation.h>
#include <libcockatrice/utility/counter_ids.h>
#include <libcockatrice/utility/zone_names.h>
/**
@ -92,6 +94,12 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho
aSelectRow = new QAction(this);
aSelectColumn = new QAction(this);
aPlayAndIncreaseTax = new QAction(this);
connect(aPlayAndIncreaseTax, &QAction::triggered, playerActions, &PlayerActions::actPlayAndIncreaseTax);
aPlayAndIncreasePartnerTax = new QAction(this);
connect(aPlayAndIncreasePartnerTax, &QAction::triggered, playerActions,
&PlayerActions::actPlayAndIncreasePartnerTax);
connect(aAttach, &QAction::triggered, actions, &PlayerActions::actAttach);
connect(aDrawArrow, &QAction::triggered, actions, &PlayerActions::actDrawArrow);
connect(aSelectAll, &QAction::triggered, actions, &PlayerActions::actSelectAll);
@ -157,6 +165,33 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho
} else if (card->getZone()->getName() == ZoneNames::EXILE ||
card->getZone()->getName() == ZoneNames::GRAVE) {
createGraveyardOrExileMenu(writeableCard);
} else if (card->getZone()->getName() == ZoneNames::COMMAND) {
if (writeableCard) {
addAction(aPlay);
AbstractCounter *cmdTax = player->getCounterWidget(CounterIds::CommanderTax);
if (cmdTax && cmdTax->isActive()) {
addAction(aPlayAndIncreaseTax);
}
AbstractCounter *partnerTax = player->getCounterWidget(CounterIds::PartnerTax);
if (partnerTax && partnerTax->isActive()) {
addAction(aPlayAndIncreasePartnerTax);
}
// No reveal submenu - command zone is public
addSeparator();
addAction(aClone);
addMenu(new MoveMenu(player));
} else {
addAction(aDrawArrow);
addSeparator();
addAction(aClone);
}
addSeparator();
addAction(aSelectAll);
addRelatedCardView();
addRelatedCardActions();
} else {
createHandOrCustomZoneMenu(writeableCard);
}
@ -487,6 +522,8 @@ void CardMenu::retranslateUi()
aPlay->setText(tr("&Play"));
aHide->setText(tr("&Hide"));
aPlayFacedown->setText(tr("Play &Face Down"));
aPlayAndIncreaseTax->setText(tr("Play and &Increase Commander Tax"));
aPlayAndIncreasePartnerTax->setText(tr("Play and Increase &Partner Tax"));
aRevealToAll->setText(tr("&All players"));
//: Turn sideways or back again
aTap->setText(tr("&Tap / Untap"));

View file

@ -32,6 +32,9 @@ public:
QMenu *mCardCounters;
QAction *aPlay, *aPlayFacedown;
QAction *
aPlayAndIncreaseTax; ///< Plays card and increments the primary commander tax counter (CounterIds::CommanderTax)
QAction *aPlayAndIncreasePartnerTax;
QAction *aRevealToAll;
QAction *aHide;
QAction *aClone;

View file

@ -0,0 +1,184 @@
#include "command_zone_menu.h"
#include "../../../client/settings/cache_settings.h"
#include "../../board/abstract_counter.h"
#include "../../game_scene.h"
#include "../../zones/command_zone.h"
#include "../player_actions.h"
#include "../player_graphics_item.h"
#include "../player_logic.h"
#include <libcockatrice/utility/counter_ids.h>
#include <libcockatrice/utility/zone_names.h>
CommandZoneMenu::CommandZoneMenu(PlayerLogic *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player)
{
viewZoneShortcutKey = QStringLiteral("Player/aViewCommandZone");
incTaxShortcutKey = QStringLiteral("Player/aAddCommanderTax");
decTaxShortcutKey = QStringLiteral("Player/aRemoveCommanderTax");
incPartnerTaxShortcutKey = QStringLiteral("Player/aAddPartnerTax");
decPartnerTaxShortcutKey = QStringLiteral("Player/aRemovePartnerTax");
aViewZone = new QAction(this);
connect(aViewZone, &QAction::triggered, this,
[this]() { player->getGameScene()->toggleZoneView(player, ZoneNames::COMMAND, -1); });
if (player->getPlayerInfo()->getLocalOrJudge()) {
addAction(aViewZone);
addSeparator();
PlayerActions *playerActions = player->getPlayerActions();
aIncreaseCommanderTax = new QAction(this);
connect(aIncreaseCommanderTax, &QAction::triggered, this,
[playerActions]() { playerActions->actModifyTaxCounter(CounterIds::CommanderTax, 1); });
addAction(aIncreaseCommanderTax);
aDecreaseCommanderTax = new QAction(this);
connect(aDecreaseCommanderTax, &QAction::triggered, this,
[playerActions]() { playerActions->actModifyTaxCounter(CounterIds::CommanderTax, -1); });
addAction(aDecreaseCommanderTax);
addSeparator();
aIncreasePartnerTax = new QAction(this);
connect(aIncreasePartnerTax, &QAction::triggered, this,
[playerActions]() { playerActions->actModifyTaxCounter(CounterIds::PartnerTax, 1); });
addAction(aIncreasePartnerTax);
aDecreasePartnerTax = new QAction(this);
connect(aDecreasePartnerTax, &QAction::triggered, this,
[playerActions]() { playerActions->actModifyTaxCounter(CounterIds::PartnerTax, -1); });
addAction(aDecreasePartnerTax);
addSeparator();
aToggleCommanderTaxCounter = new QAction(this);
connect(aToggleCommanderTaxCounter, &QAction::triggered, this,
[playerActions]() { playerActions->actToggleTaxCounter(CounterIds::CommanderTax); });
addAction(aToggleCommanderTaxCounter);
aTogglePartnerTaxCounter = new QAction(this);
connect(aTogglePartnerTaxCounter, &QAction::triggered, this,
[playerActions]() { playerActions->actToggleTaxCounter(CounterIds::PartnerTax); });
addAction(aTogglePartnerTaxCounter);
addSeparator();
aToggleMinimized = new QAction(this);
connect(aToggleMinimized, &QAction::triggered, this, &CommandZoneMenu::actToggleMinimized);
addAction(aToggleMinimized);
connect(this, &QMenu::aboutToShow, this, &CommandZoneMenu::updateTaxCounterActionStates);
}
retranslateUi();
}
void CommandZoneMenu::retranslateUi()
{
setTitle(tr("Co&mmander"));
if (aViewZone) {
aViewZone->setText(tr("&View command zone"));
}
if (aIncreaseCommanderTax) {
aIncreaseCommanderTax->setText(tr("&Increase Commander Tax (+1)"));
}
if (aDecreaseCommanderTax) {
aDecreaseCommanderTax->setText(tr("&Decrease Commander Tax (-1)"));
}
if (aToggleCommanderTaxCounter) {
aToggleCommanderTaxCounter->setText(tr("&Remove Commander Tax"));
}
if (aIncreasePartnerTax) {
aIncreasePartnerTax->setText(tr("Increase &Partner Tax (+1)"));
}
if (aDecreasePartnerTax) {
aDecreasePartnerTax->setText(tr("Decrease P&artner Tax (-1)"));
}
if (aTogglePartnerTaxCounter) {
aTogglePartnerTaxCounter->setText(tr("&Add Partner Tax"));
}
if (aToggleMinimized) {
aToggleMinimized->setText(tr("&Minimize"));
}
}
void CommandZoneMenu::actToggleMinimized()
{
CommandZone *zone = player->getGraphicsItem()->getCommandZoneGraphicsItem();
if (zone) {
zone->toggleMinimized();
}
}
void CommandZoneMenu::updateTaxCounterActionStates()
{
AbstractCounter *cmdTax = player->getCounterWidget(CounterIds::CommanderTax);
bool cmdActive = cmdTax && cmdTax->isActive();
AbstractCounter *partnerTax = player->getCounterWidget(CounterIds::PartnerTax);
bool partnerActive = partnerTax && partnerTax->isActive();
if (aIncreaseCommanderTax) {
aIncreaseCommanderTax->setVisible(cmdActive);
}
if (aDecreaseCommanderTax) {
aDecreaseCommanderTax->setVisible(cmdActive);
}
if (aToggleCommanderTaxCounter) {
aToggleCommanderTaxCounter->setText(cmdActive ? tr("&Remove Commander Tax") : tr("&Add Commander Tax"));
aToggleCommanderTaxCounter->setVisible(!cmdActive || (cmdTax && cmdTax->getValue() == 0));
}
if (aIncreasePartnerTax) {
aIncreasePartnerTax->setVisible(partnerActive);
}
if (aDecreasePartnerTax) {
aDecreasePartnerTax->setVisible(partnerActive);
}
if (aTogglePartnerTaxCounter) {
aTogglePartnerTaxCounter->setText(partnerActive ? tr("R&emove Partner Tax") : tr("&Add Partner Tax"));
aTogglePartnerTaxCounter->setVisible(!partnerActive || (partnerTax && partnerTax->getValue() == 0));
}
}
void CommandZoneMenu::setShortcutsActive()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
if (aViewZone) {
aViewZone->setShortcuts(shortcuts.getShortcut(viewZoneShortcutKey));
}
if (aIncreaseCommanderTax) {
aIncreaseCommanderTax->setShortcuts(shortcuts.getShortcut(incTaxShortcutKey));
}
if (aDecreaseCommanderTax) {
aDecreaseCommanderTax->setShortcuts(shortcuts.getShortcut(decTaxShortcutKey));
}
if (aIncreasePartnerTax) {
aIncreasePartnerTax->setShortcuts(shortcuts.getShortcut(incPartnerTaxShortcutKey));
}
if (aDecreasePartnerTax) {
aDecreasePartnerTax->setShortcuts(shortcuts.getShortcut(decPartnerTaxShortcutKey));
}
}
void CommandZoneMenu::setShortcutsInactive()
{
if (aViewZone) {
aViewZone->setShortcut(QKeySequence());
}
if (aIncreaseCommanderTax) {
aIncreaseCommanderTax->setShortcut(QKeySequence());
}
if (aDecreaseCommanderTax) {
aDecreaseCommanderTax->setShortcut(QKeySequence());
}
if (aIncreasePartnerTax) {
aIncreasePartnerTax->setShortcut(QKeySequence());
}
if (aDecreasePartnerTax) {
aDecreasePartnerTax->setShortcut(QKeySequence());
}
}

View file

@ -0,0 +1,62 @@
/**
* @file command_zone_menu.h
* @ingroup GameMenusZones
* @brief Context menu for command zone right-click actions.
*/
#ifndef COCKATRICE_COMMAND_ZONE_MENU_H
#define COCKATRICE_COMMAND_ZONE_MENU_H
#include "abstract_player_component.h"
#include <QMenu>
class PlayerLogic;
/**
* @class CommandZoneMenu
* @brief Context menu for the command zone.
*
* Appears when right-clicking on the command zone. Provides actions for
* viewing zone contents, adjusting the commander tax counter, and
* toggling minimized state.
*
* @see PlayerMenu
* @see CommandZone
*/
class CommandZoneMenu : public QMenu, public AbstractPlayerComponent
{
Q_OBJECT
public:
explicit CommandZoneMenu(PlayerLogic *player, QMenu *playerMenu);
void retranslateUi() override;
void setShortcutsActive() override;
void setShortcutsInactive() override;
QAction *aViewZone = nullptr; ///< Opens a zone viewer for the command zone
private:
QAction *aIncreaseCommanderTax = nullptr; ///< Increments the primary commander tax counter
QAction *aDecreaseCommanderTax = nullptr; ///< Decrements the primary commander tax counter
QAction *aToggleCommanderTaxCounter = nullptr; ///< Toggles primary commander tax counter visibility
QAction *aIncreasePartnerTax = nullptr; ///< Increments the partner commander tax counter
QAction *aDecreasePartnerTax = nullptr; ///< Decrements the partner commander tax counter
QAction *aTogglePartnerTaxCounter = nullptr; ///< Toggles partner commander tax counter visibility
QAction *aToggleMinimized = nullptr; ///< Toggles command zone minimized state
private slots:
void actToggleMinimized();
private:
void updateTaxCounterActionStates();
PlayerLogic *player;
QString viewZoneShortcutKey;
QString incTaxShortcutKey;
QString decTaxShortcutKey;
QString incPartnerTaxShortcutKey;
QString decPartnerTaxShortcutKey;
};
#endif // COCKATRICE_COMMAND_ZONE_MENU_H

View file

@ -20,6 +20,8 @@ MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
aMoveToGraveyard->setData(cmMoveToGraveyard);
aMoveToExile = new QAction(this);
aMoveToExile->setData(cmMoveToExile);
aMoveToCommandZone = new QAction(this);
aMoveToCommandZone->setData(cmMoveToCommandZone);
auto *actions = player->getLogic()->getPlayerActions();
@ -49,6 +51,8 @@ MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
addAction(aMoveToGraveyard);
addSeparator();
addAction(aMoveToExile);
addSeparator();
addAction(aMoveToCommandZone);
setShortcutsActive();
@ -65,6 +69,7 @@ void MoveMenu::setShortcutsActive()
aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand"));
aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard"));
aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile"));
aMoveToCommandZone->setShortcuts(shortcuts.getShortcut("Player/aMoveToCommandZone"));
}
void MoveMenu::retranslateUi()
@ -76,4 +81,5 @@ void MoveMenu::retranslateUi()
aMoveToHand->setText(tr("&Hand"));
aMoveToGraveyard->setText(tr("&Graveyard"));
aMoveToExile->setText(tr("&Exile"));
aMoveToCommandZone->setText(tr("&Command Zone"));
}

View file

@ -26,6 +26,7 @@ public:
QAction *aMoveToTable = nullptr;
QAction *aMoveToGraveyard = nullptr;
QAction *aMoveToExile = nullptr;
QAction *aMoveToCommandZone = nullptr;
};
#endif // COCKATRICE_MOVE_MENU_H

View file

@ -5,6 +5,7 @@
#include "../../../game_graphics/zones/table_zone.h"
#include "../../../interface/widgets/tabs/tab_game.h"
#include "../../board/card_item.h"
#include "../../zones/command_zone.h"
#include "../player_graphics_item.h"
#include "card_menu.h"
#include "hand_menu.h"
@ -31,6 +32,16 @@ PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
sideboardMenu = addManagedMenu<SideboardMenu>(player, playerMenu);
commandZoneMenu = addManagedMenu<CommandZoneMenu>(player, playerMenu);
auto updateCommandZoneMenuVisibility = [this](bool has) {
if (commandZoneMenu) {
commandZoneMenu->menuAction()->setVisible(has);
}
};
connect(player, &PlayerLogic::commandZoneSupportChanged, this, updateCommandZoneMenuVisibility);
updateCommandZoneMenuVisibility(player->hasServerCommandZone());
customZonesMenu = addManagedMenu<CustomZoneMenu>(player);
playerMenu->addSeparator();
@ -39,6 +50,7 @@ PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_
utilityMenu = createManagedComponent<UtilityMenu>(player, playerMenu);
} else {
sideboardMenu = nullptr;
commandZoneMenu = nullptr;
customZonesMenu = nullptr;
countersMenu = nullptr;
utilityMenu = nullptr;
@ -66,6 +78,10 @@ void PlayerMenu::setMenusForGraphicItems()
player->getHandZoneGraphicsItem()->setMenu(handMenu);
player->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard);
player->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu);
if (auto *commandZone = player->getCommandZoneGraphicsItem()) {
commandZone->setMenu(commandZoneMenu, commandZoneMenu->aViewZone);
}
}
}

View file

@ -8,6 +8,8 @@
#define COCKATRICE_PLAYER_MENU_H
#include "../../../interface/widgets/menus/tearoff_menu.h"
#include "../player_logic.h"
#include "command_zone_menu.h"
#include "custom_zone_menu.h"
#include "grave_menu.h"
#include "hand_menu.h"
@ -88,6 +90,7 @@ private:
RfgMenu *rfgMenu;
UtilityMenu *utilityMenu;
SayMenu *sayMenu;
CommandZoneMenu *commandZoneMenu;
CustomZoneMenu *customZonesMenu;
/** @brief Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via