mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-26 00:23:55 -07:00
Merge beea819b89 into 5ffe344779
This commit is contained in:
commit
bbaa351e6a
72 changed files with 1557 additions and 86 deletions
|
|
@ -67,6 +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/counter_general.cpp
|
||||
src/game/board/counter_state.cpp
|
||||
src/game_graphics/board/translate_counter_name.cpp
|
||||
|
|
@ -87,6 +88,7 @@ set(cockatrice_SOURCES
|
|||
src/game/phase.cpp
|
||||
src/game_graphics/phases_toolbar.cpp
|
||||
src/game_graphics/player/menu/card_menu.cpp
|
||||
src/game_graphics/player/menu/command_zone_menu.cpp
|
||||
src/game_graphics/player/menu/custom_zone_menu.cpp
|
||||
src/game_graphics/player/menu/grave_menu.cpp
|
||||
src/game_graphics/player/menu/hand_menu.cpp
|
||||
|
|
@ -110,6 +112,8 @@ set(cockatrice_SOURCES
|
|||
src/game_graphics/player/player_target.cpp
|
||||
src/game/replay.cpp
|
||||
src/game/zones/card_zone_logic.cpp
|
||||
src/game_graphics/zones/command_zone.cpp
|
||||
src/game/zones/command_zone_logic.cpp
|
||||
src/game/zones/hand_zone_logic.cpp
|
||||
src/game/zones/pile_zone_logic.cpp
|
||||
src/game/zones/stack_zone_logic.cpp
|
||||
|
|
|
|||
|
|
@ -414,6 +414,7 @@ SettingsCache::SettingsCache()
|
|||
createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool();
|
||||
defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
|
||||
shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool();
|
||||
enableCommandZone = settings->value("game/enablecommandzone", false).toBool();
|
||||
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
|
||||
|
||||
// Local game settings use "localgameoptions/" prefix to keep them separate
|
||||
|
|
@ -421,6 +422,7 @@ SettingsCache::SettingsCache()
|
|||
localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool();
|
||||
localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt();
|
||||
localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).toInt();
|
||||
localGameEnableCommandZone = settings->value("localgameoptions/enablecommandzone", false).toBool();
|
||||
|
||||
clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
|
||||
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
|
||||
|
|
@ -1265,6 +1267,12 @@ void SettingsCache::setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad)
|
|||
settings->setValue("game/sharedecklistsonload", shareDecklistsOnLoad);
|
||||
}
|
||||
|
||||
void SettingsCache::setEnableCommandZone(const bool _enableCommandZone)
|
||||
{
|
||||
enableCommandZone = _enableCommandZone;
|
||||
settings->setValue("game/enablecommandzone", enableCommandZone);
|
||||
}
|
||||
|
||||
void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value)
|
||||
{
|
||||
checkUpdatesOnStartup = static_cast<bool>(value);
|
||||
|
|
@ -1325,6 +1333,12 @@ void SettingsCache::setLocalGameStartingLifeTotal(int value)
|
|||
settings->setValue("localgameoptions/startinglifetotal", value);
|
||||
}
|
||||
|
||||
void SettingsCache::setLocalGameEnableCommandZone(bool value)
|
||||
{
|
||||
localGameEnableCommandZone = value;
|
||||
settings->setValue("localgameoptions/enablecommandzone", value);
|
||||
}
|
||||
|
||||
void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate)
|
||||
{
|
||||
notifyAboutUpdates = static_cast<bool>(_notifyaboutupdate);
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ private:
|
|||
bool createGameAsSpectator;
|
||||
int defaultStartingLifeTotal;
|
||||
bool shareDecklistsOnLoad;
|
||||
bool enableCommandZone;
|
||||
int keepalive;
|
||||
int timeout;
|
||||
void translateLegacySettings();
|
||||
|
|
@ -344,6 +345,7 @@ private:
|
|||
bool localGameRememberSettings;
|
||||
int localGameMaxPlayers;
|
||||
int localGameStartingLifeTotal;
|
||||
bool localGameEnableCommandZone;
|
||||
|
||||
QList<ReleaseChannel *> releaseChannels;
|
||||
bool isPortableBuild;
|
||||
|
|
@ -895,6 +897,10 @@ public:
|
|||
{
|
||||
return shareDecklistsOnLoad;
|
||||
}
|
||||
[[nodiscard]] bool getEnableCommandZone() const
|
||||
{
|
||||
return enableCommandZone;
|
||||
}
|
||||
[[nodiscard]] bool getCreateGameAsSpectator() const
|
||||
{
|
||||
return createGameAsSpectator;
|
||||
|
|
@ -915,6 +921,10 @@ public:
|
|||
{
|
||||
return localGameStartingLifeTotal;
|
||||
}
|
||||
[[nodiscard]] bool getLocalGameEnableCommandZone() const
|
||||
{
|
||||
return localGameEnableCommandZone;
|
||||
}
|
||||
[[nodiscard]] int getKeepAlive() const override
|
||||
{
|
||||
return keepalive;
|
||||
|
|
@ -1145,10 +1155,12 @@ public slots:
|
|||
void setCreateGameAsSpectator(const bool _createGameAsSpectator);
|
||||
void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal);
|
||||
void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad);
|
||||
void setEnableCommandZone(const bool _enableCommandZone);
|
||||
void setRememberGameSettings(const bool _rememberGameSettings);
|
||||
void setLocalGameRememberSettings(bool value);
|
||||
void setLocalGameMaxPlayers(int value);
|
||||
void setLocalGameStartingLifeTotal(int value);
|
||||
void setLocalGameEnableCommandZone(bool value);
|
||||
void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value);
|
||||
void setStartupCardUpdateCheckPromptForUpdate(bool value);
|
||||
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
|
||||
|
|
|
|||
|
|
@ -2,15 +2,22 @@
|
|||
|
||||
#include <libcockatrice/utility/color.h>
|
||||
|
||||
CounterState::CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent)
|
||||
: QObject(parent), id(id), name(name), color(color), radius(radius), value(value)
|
||||
CounterState::CounterState(int id,
|
||||
const QString &name,
|
||||
const QColor &color,
|
||||
int radius,
|
||||
int value,
|
||||
bool active,
|
||||
QObject *parent)
|
||||
: QObject(parent), id(id), name(name), color(color), radius(radius), value(value), active(active)
|
||||
{
|
||||
}
|
||||
|
||||
CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject *parent)
|
||||
{
|
||||
return new CounterState(counter.id(), QString::fromStdString(counter.name()),
|
||||
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(), parent);
|
||||
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(),
|
||||
counter.active(), parent);
|
||||
}
|
||||
|
||||
void CounterState::setValue(int newValue)
|
||||
|
|
@ -21,4 +28,13 @@ void CounterState::setValue(int newValue)
|
|||
int old = value;
|
||||
value = newValue;
|
||||
emit valueChanged(old, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
void CounterState::setActive(bool newActive)
|
||||
{
|
||||
if (newActive == active) {
|
||||
return;
|
||||
}
|
||||
active = newActive;
|
||||
emit activeChanged(newActive);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,13 @@ class CounterState : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent = nullptr);
|
||||
CounterState(int id,
|
||||
const QString &name,
|
||||
const QColor &color,
|
||||
int radius,
|
||||
int value,
|
||||
bool active = true,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
static CounterState *fromProto(const ServerInfo_Counter &counter, QObject *parent = nullptr);
|
||||
|
||||
|
|
@ -34,11 +40,17 @@ public:
|
|||
{
|
||||
return value;
|
||||
}
|
||||
bool isActive() const
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
void setValue(int newValue);
|
||||
void setActive(bool newActive);
|
||||
|
||||
signals:
|
||||
void valueChanged(int oldValue, int newValue);
|
||||
void activeChanged(bool newActive);
|
||||
|
||||
private:
|
||||
int id;
|
||||
|
|
@ -46,6 +58,7 @@ private:
|
|||
QColor color;
|
||||
int radius;
|
||||
int value;
|
||||
bool active;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COUNTER_STATE_H
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "../../game_graphics/zones/table_zone.h"
|
||||
#include "../../interface/widgets/tabs/tab_game.h"
|
||||
#include "../../interface/widgets/utility/get_text_with_max.h"
|
||||
#include "../board/counter_state.h"
|
||||
#include "../zones/view_zone_logic.h"
|
||||
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
|
@ -24,9 +25,11 @@
|
|||
#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>
|
||||
#include <libcockatrice/utility/counter_ids.h>
|
||||
#include <libcockatrice/utility/expression.h>
|
||||
#include <libcockatrice/utility/trice_limits.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
|
|
@ -1527,9 +1530,9 @@ void PlayerActions::offsetCardCounter(QList<CardItem *> selectedCards, int count
|
|||
int oldValue = card->getCounters().value(counterId, 0);
|
||||
int newValue = oldValue + offset;
|
||||
|
||||
// Early exit optimization: server enforces [0, MAX_COUNTERS_ON_CARD].
|
||||
// Early exit optimization: server enforces [0, MAX_COUNTER_VALUE].
|
||||
// Compare clamped value to allow recovery from invalid states.
|
||||
int clampedValue = qBound(0, newValue, MAX_COUNTERS_ON_CARD);
|
||||
int clampedValue = qBound(0, newValue, MAX_COUNTER_VALUE);
|
||||
if (clampedValue != oldValue) {
|
||||
auto *cmd = new Command_SetCardCounter;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
|
|
@ -1563,7 +1566,7 @@ void PlayerActions::actSetCardCounter(QList<CardItem *> selectedCards, int count
|
|||
Expression exp(oldValue);
|
||||
double parsed = exp.parse(counterValue);
|
||||
// Clamp in double precision first to avoid UB, then cast
|
||||
int number = static_cast<int>(qBound(0.0, parsed, static_cast<double>(MAX_COUNTERS_ON_CARD)));
|
||||
int number = static_cast<int>(qBound(0.0, parsed, static_cast<double>(MAX_COUNTER_VALUE)));
|
||||
|
||||
auto *cmd = new Command_SetCardCounter;
|
||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||
|
|
@ -1593,7 +1596,7 @@ void PlayerActions::actIncrementAllCardCounters(QList<CardItem *> cardsToUpdate)
|
|||
counterIterator.next();
|
||||
int counterId = counterIterator.key();
|
||||
int currentValue = counterIterator.value();
|
||||
if (currentValue >= MAX_COUNTERS_ON_CARD) {
|
||||
if (currentValue >= MAX_COUNTER_VALUE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1624,6 +1627,13 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
|
|||
}
|
||||
|
||||
void PlayerActions::playSelectedCards(QList<CardItem *> selectedCards, const bool faceDown)
|
||||
{
|
||||
playSelectedCardsImpl(selectedCards, faceDown, nullptr);
|
||||
}
|
||||
|
||||
void PlayerActions::playSelectedCardsImpl(QList<CardItem *> selectedCards,
|
||||
bool faceDown,
|
||||
const std::function<void(CardItem *, const QString &)> &postPlayCallback)
|
||||
{
|
||||
// CardIds will get shuffled downwards when cards leave the deck.
|
||||
// We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play
|
||||
|
|
@ -1633,11 +1643,69 @@ void PlayerActions::playSelectedCards(QList<CardItem *> selectedCards, const boo
|
|||
|
||||
for (auto &card : selectedCards) {
|
||||
if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != ZoneNames::TABLE) {
|
||||
const QString originalZone = card->getZone()->getName();
|
||||
playCard(card, faceDown);
|
||||
if (postPlayCallback) {
|
||||
postPlayCallback(card, originalZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerActions::actPlayAndIncreaseTax(QList<CardItem *> selectedCards)
|
||||
{
|
||||
playSelectedCardsImpl(selectedCards, false, [this](CardItem * /*card*/, const QString &originalZone) {
|
||||
if (originalZone == ZoneNames::COMMAND) {
|
||||
CounterState *state = player->getCounters().value(CounterIds::CommanderTax, nullptr);
|
||||
if (state && state->isActive()) {
|
||||
sendIncCounter(CounterIds::CommanderTax, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PlayerActions::actPlayAndIncreasePartnerTax(QList<CardItem *> selectedCards)
|
||||
{
|
||||
playSelectedCardsImpl(selectedCards, false, [this](CardItem * /*card*/, const QString &originalZone) {
|
||||
if (originalZone == ZoneNames::COMMAND) {
|
||||
CounterState *state = player->getCounters().value(CounterIds::PartnerTax, nullptr);
|
||||
if (state && state->isActive()) {
|
||||
sendIncCounter(CounterIds::PartnerTax, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PlayerActions::sendIncCounter(int counterId, int delta)
|
||||
{
|
||||
Command_IncCounter cmd;
|
||||
cmd.set_counter_id(counterId);
|
||||
cmd.set_delta(delta);
|
||||
sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void PlayerActions::actModifyTaxCounter(int counterId, int delta)
|
||||
{
|
||||
CounterState *state = player->getCounters().value(counterId, nullptr);
|
||||
if (!state || !state->isActive()) {
|
||||
return;
|
||||
}
|
||||
sendIncCounter(counterId, delta);
|
||||
}
|
||||
|
||||
void PlayerActions::actToggleTaxCounter(int counterId)
|
||||
{
|
||||
CounterState *state = player->getCounters().value(counterId, nullptr);
|
||||
// Prevent disabling a counter with tax accumulated; player must reset to 0 first
|
||||
if (!state || (state->isActive() && state->getValue() != 0)) {
|
||||
return;
|
||||
}
|
||||
Command_SetCounterActive cmd;
|
||||
cmd.set_counter_id(counterId);
|
||||
cmd.set_active(!state->isActive());
|
||||
sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void PlayerActions::actPlay(QList<CardItem *> selectedCards)
|
||||
{
|
||||
playSelectedCards(selectedCards, false);
|
||||
|
|
@ -1918,6 +1986,18 @@ void PlayerActions::cardMenuAction(QList<CardItem *> selectedCards, CardMenuActi
|
|||
commandList.append(cmd);
|
||||
break;
|
||||
}
|
||||
case cmMoveToCommandZone: {
|
||||
auto *cmd = new Command_MoveCard;
|
||||
cmd->set_start_player_id(startPlayerId);
|
||||
cmd->set_start_zone(startZone.toStdString());
|
||||
cmd->mutable_cards_to_move()->CopyFrom(idList);
|
||||
cmd->set_target_player_id(player->getPlayerInfo()->getId());
|
||||
cmd->set_target_zone(ZoneNames::COMMAND);
|
||||
cmd->set_x(0);
|
||||
cmd->set_y(0);
|
||||
commandList.append(cmd);
|
||||
break;
|
||||
}
|
||||
case cmMoveToTable: {
|
||||
// Each card needs its own command because table row, pt, and cipt vary per card
|
||||
for (const auto &card : cardList) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <QMenu>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
#include <libcockatrice/card/relation/card_relation_type.h>
|
||||
#include <libcockatrice/filters/filter_string.h>
|
||||
|
||||
|
|
@ -126,6 +127,14 @@ public slots:
|
|||
|
||||
void actPlay(QList<CardItem *> selectedCards);
|
||||
void actPlayFacedown(QList<CardItem *> selectedCards);
|
||||
/** @brief Plays the selected card and increments the primary commander tax counter. */
|
||||
void actPlayAndIncreaseTax(QList<CardItem *> selectedCards);
|
||||
/** @brief Plays the selected card and increments the partner commander tax counter. */
|
||||
void actPlayAndIncreasePartnerTax(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);
|
||||
void actHide(QList<CardItem *> selectedCards);
|
||||
|
||||
void actMoveTopCardToPlay();
|
||||
|
|
@ -219,6 +228,8 @@ public slots:
|
|||
void cardMenuAction(QList<CardItem *> selectedCards, CardMenuActionType type);
|
||||
|
||||
private:
|
||||
void sendIncCounter(int counterId, int delta);
|
||||
|
||||
PlayerLogic *player;
|
||||
|
||||
int defaultNumberTopCards = 1;
|
||||
|
|
@ -244,6 +255,17 @@ private:
|
|||
|
||||
void playSelectedCards(QList<CardItem *> selectedCards, bool faceDown = false);
|
||||
|
||||
/**
|
||||
* @brief Shared implementation for playing selected cards with an optional post-play callback.
|
||||
* @param selectedCards Cards to play
|
||||
* @param faceDown Whether to play cards face-down
|
||||
* @param postPlayCallback Called after each card is played, receiving the card and its *original* zone name
|
||||
* (captured before playCard, since playCard sends a move command that may change the card's zone).
|
||||
*/
|
||||
void playSelectedCardsImpl(QList<CardItem *> selectedCards,
|
||||
bool faceDown,
|
||||
const std::function<void(CardItem *, const QString &)> &postPlayCallback = nullptr);
|
||||
|
||||
void cmdSetTopCard(Command_MoveCard &cmd);
|
||||
void cmdSetBottomCard(Command_MoveCard &cmd);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,10 @@
|
|||
#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_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/counter_ids.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
|
||||
PlayerEventHandler::PlayerEventHandler(PlayerLogic *_player) : QObject(_player), player(_player)
|
||||
|
|
@ -264,13 +266,25 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event)
|
|||
|
||||
void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
|
||||
{
|
||||
CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr);
|
||||
if (!ctr) {
|
||||
CounterState *state = player->getCounters().value(event.counter_id(), nullptr);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
int oldValue = ctr->getValue();
|
||||
ctr->setValue(event.value());
|
||||
emit logSetCounter(player, ctr->getName(), event.value(), oldValue);
|
||||
int oldValue = state->getValue();
|
||||
state->setValue(event.value());
|
||||
|
||||
if (event.value() != oldValue) {
|
||||
emit logSetCounter(player, state->getName(), event.value(), oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -627,6 +641,9 @@ 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;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class Event_RollDie;
|
|||
class Event_SetCardAttr;
|
||||
class Event_SetCardCounter;
|
||||
class Event_SetCounter;
|
||||
class Event_SetCounterActive;
|
||||
class Event_Shuffle;
|
||||
class Event_GameLogNotice;
|
||||
|
||||
|
|
@ -104,6 +105,7 @@ public:
|
|||
void eventSetCardCounter(const Event_SetCardCounter &event);
|
||||
void eventCreateCounter(const Event_CreateCounter &event);
|
||||
void eventSetCounter(const Event_SetCounter &event);
|
||||
void eventSetCounterActive(const Event_SetCounterActive &event);
|
||||
void eventDelCounter(const Event_DelCounter &event);
|
||||
void eventDumpZone(const Event_DumpZone &event);
|
||||
void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@
|
|||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_zone.pb.h>
|
||||
#include <libcockatrice/utility/color.h>
|
||||
#include <libcockatrice/utility/counter_ids.h>
|
||||
|
||||
PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent)
|
||||
: QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)),
|
||||
playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false),
|
||||
conceded(false), zoneId(0), dialogSemaphore(false)
|
||||
conceded(false), zoneId(0), dialogSemaphore(false), serverHasCommandZone(false)
|
||||
{
|
||||
initializeZones();
|
||||
}
|
||||
|
|
@ -48,6 +49,7 @@ void PlayerLogic::initializeZones()
|
|||
bool visibleHand = playerInfo->getLocalOrJudge() ||
|
||||
(game->getPlayerManager()->isSpectator() && game->getGameMetaInfo()->spectatorsOmniscient());
|
||||
addZone(new HandZoneLogic(this, ZoneNames::HAND, false, false, visibleHand, this));
|
||||
addZone(new CommandZoneLogic(this, ZoneNames::COMMAND, true, false, true, this));
|
||||
}
|
||||
|
||||
PlayerLogic::~PlayerLogic()
|
||||
|
|
@ -104,7 +106,9 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
|
|||
/* StackZone */
|
||||
ZoneNames::STACK,
|
||||
/* HandZone */
|
||||
ZoneNames::HAND};
|
||||
ZoneNames::HAND,
|
||||
/* CommandZone */
|
||||
ZoneNames::COMMAND};
|
||||
clearCounters();
|
||||
emit arrowsClearedLocally();
|
||||
|
||||
|
|
@ -119,7 +123,19 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
|
|||
|
||||
emit clearCustomZonesMenu();
|
||||
|
||||
// Check if server has command zone by scanning the zone list
|
||||
const int zoneListSize = info.zone_list_size();
|
||||
bool foundCommandZone = false;
|
||||
for (int i = 0; i < zoneListSize; ++i) {
|
||||
if (QString::fromStdString(info.zone_list(i).name()) == ZoneNames::COMMAND) {
|
||||
foundCommandZone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (serverHasCommandZone != foundCommandZone) {
|
||||
serverHasCommandZone = foundCommandZone;
|
||||
emit commandZoneSupportChanged(foundCommandZone);
|
||||
}
|
||||
for (int i = 0; i < zoneListSize; ++i) {
|
||||
const ServerInfo_Zone &zoneInfo = info.zone_list(i);
|
||||
|
||||
|
|
@ -253,15 +269,17 @@ void PlayerLogic::setDeck(const DeckList &_deck)
|
|||
CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter)
|
||||
{
|
||||
return addCounter(counter.id(), QString::fromStdString(counter.name()),
|
||||
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count());
|
||||
convertColorToQColor(counter.counter_color()), counter.radius(), counter.count(),
|
||||
counter.active());
|
||||
}
|
||||
|
||||
CounterState *PlayerLogic::addCounter(int id, const QString &name, const QColor &color, int radius, int value)
|
||||
CounterState *
|
||||
PlayerLogic::addCounter(int id, const QString &name, const QColor &color, int radius, int value, bool active)
|
||||
{
|
||||
if (counters.contains(id)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto *state = new CounterState(id, name, color, radius, value, this);
|
||||
auto *state = new CounterState(id, name, color, radius, value, active, this);
|
||||
counters.insert(id, state);
|
||||
emit counterAdded(state);
|
||||
return state;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include "../../interface/widgets/menus/tearoff_menu.h"
|
||||
#include "../board/arrow_data.h"
|
||||
#include "../interface/deck_loader/loaded_deck.h"
|
||||
#include "../zones/command_zone_logic.h"
|
||||
#include "../zones/hand_zone_logic.h"
|
||||
#include "../zones/pile_zone_logic.h"
|
||||
#include "../zones/stack_zone_logic.h"
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
#include <libcockatrice/filters/filter_string.h>
|
||||
#include <libcockatrice/protocol/pb/card_attributes.pb.h>
|
||||
#include <libcockatrice/protocol/pb/game_event.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(PlayerLog, "player");
|
||||
|
|
@ -57,6 +59,7 @@ class ServerInfo_Counter;
|
|||
class ServerInfo_Player;
|
||||
class ServerInfo_User;
|
||||
class TabGame;
|
||||
class AbstractCounter;
|
||||
|
||||
const int MAX_TOKENS_PER_DIALOG = 99;
|
||||
|
||||
|
|
@ -87,6 +90,7 @@ signals:
|
|||
void arrowDeleteRequested(int creatorId, int arrowId);
|
||||
void arrowDeleted(int creatorId, int arrowId);
|
||||
void arrowsClearedLocally(); // fires on clear() and processPlayerInfo
|
||||
void commandZoneSupportChanged(bool hasCommandZone);
|
||||
|
||||
public slots:
|
||||
void setActive(bool _active);
|
||||
|
|
@ -191,8 +195,21 @@ public:
|
|||
return qobject_cast<HandZoneLogic *>(zones.value(ZoneNames::HAND));
|
||||
}
|
||||
|
||||
/** @brief Returns the command zone logic, or nullptr if not present. */
|
||||
CommandZoneLogic *getCommandZone()
|
||||
{
|
||||
return qobject_cast<CommandZoneLogic *>(zones.value(ZoneNames::COMMAND));
|
||||
}
|
||||
|
||||
/** @brief Whether the server confirmed command zone support for this game. */
|
||||
bool hasServerCommandZone() const
|
||||
{
|
||||
return serverHasCommandZone;
|
||||
}
|
||||
|
||||
CounterState *addCounter(const ServerInfo_Counter &counter);
|
||||
CounterState *addCounter(int id, const QString &name, const QColor &color, int radius, int value);
|
||||
CounterState *
|
||||
addCounter(int id, const QString &name, const QColor &color, int radius, int value, bool active = true);
|
||||
void delCounter(int counterId);
|
||||
void clearCounters();
|
||||
|
||||
|
|
@ -242,6 +259,7 @@ private:
|
|||
QMap<int, CounterState *> counters;
|
||||
|
||||
bool dialogSemaphore;
|
||||
bool serverHasCommandZone;
|
||||
QList<CardItem *> cardsToDelete;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,9 @@ QString CardZoneLogic::getTranslatedName(bool theirOwn, GrammaticalCase gc) cons
|
|||
return (theirOwn ? tr("their graveyard", "nominative") : tr("%1's graveyard", "nominative").arg(ownerName));
|
||||
} else if (name == ZoneNames::EXILE) {
|
||||
return (theirOwn ? tr("their exile", "nominative") : tr("%1's exile", "nominative").arg(ownerName));
|
||||
} else if (name == ZoneNames::COMMAND) {
|
||||
return (theirOwn ? tr("their command zone", "nominative")
|
||||
: tr("%1's command zone", "nominative").arg(ownerName));
|
||||
} else if (name == ZoneNames::SIDEBOARD) {
|
||||
switch (gc) {
|
||||
case CaseLookAtZone:
|
||||
|
|
|
|||
19
cockatrice/src/game/zones/command_zone_logic.cpp
Normal file
19
cockatrice/src/game/zones/command_zone_logic.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#include "command_zone_logic.h"
|
||||
|
||||
#include "../../game_graphics/board/card_item.h"
|
||||
#include "card_zone_algorithms.h"
|
||||
|
||||
CommandZoneLogic::CommandZoneLogic(PlayerLogic *_player,
|
||||
const QString &_name,
|
||||
bool _hasCardAttr,
|
||||
bool _isShufflable,
|
||||
bool _contentsKnown,
|
||||
QObject *parent)
|
||||
: CardZoneLogic(_player, _name, _hasCardAttr, _isShufflable, _contentsKnown, parent)
|
||||
{
|
||||
}
|
||||
|
||||
void CommandZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/)
|
||||
{
|
||||
CardZoneAlgorithms::addCardToList(cards, card, x, false);
|
||||
}
|
||||
51
cockatrice/src/game/zones/command_zone_logic.h
Normal file
51
cockatrice/src/game/zones/command_zone_logic.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @file command_zone_logic.h
|
||||
* @ingroup GameLogicZones
|
||||
* @brief Logic layer for the command zone, used for Commander format.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_COMMAND_ZONE_LOGIC_H
|
||||
#define COCKATRICE_COMMAND_ZONE_LOGIC_H
|
||||
#include "card_zone_logic.h"
|
||||
|
||||
/**
|
||||
* @class CommandZoneLogic
|
||||
* @brief Logic layer for managing cards in the command zone.
|
||||
*
|
||||
* Handles data storage and card management for the command zone in Commander format.
|
||||
* Supports ordered card insertion for drag-and-drop operations.
|
||||
*
|
||||
* @see CommandZone for the graphics layer
|
||||
* @see CardZoneLogic
|
||||
*/
|
||||
class CommandZoneLogic : public CardZoneLogic
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a CommandZoneLogic instance.
|
||||
* @param _player The player who owns this zone
|
||||
* @param _name Zone name (ZoneNames::COMMAND)
|
||||
* @param _hasCardAttr Whether cards in this zone have attributes
|
||||
* @param _isShufflable Whether the zone can be shuffled
|
||||
* @param _contentsKnown Whether the zone contents are public knowledge
|
||||
* @param parent Parent QObject
|
||||
*/
|
||||
CommandZoneLogic(PlayerLogic *_player,
|
||||
const QString &_name,
|
||||
bool _hasCardAttr,
|
||||
bool _isShufflable,
|
||||
bool _contentsKnown,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Adds a card at position x (y ignored). Appends if x is -1 or out of range.
|
||||
* @param card Card to add
|
||||
* @param x Insertion index, or -1 to append
|
||||
* @param y Unused
|
||||
*/
|
||||
void addCardImpl(CardItem *card, int x, int y) override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMAND_ZONE_LOGIC_H
|
||||
|
|
@ -33,6 +33,11 @@ AbstractCounter::AbstractCounter(CounterState *state,
|
|||
update();
|
||||
});
|
||||
|
||||
connect(state, &CounterState::activeChanged, this, [this](bool newActive) {
|
||||
setActive(newActive);
|
||||
emit player->rearrangeCounters();
|
||||
});
|
||||
|
||||
if (player->getPlayerInfo()->getLocalOrJudge()) {
|
||||
menu = new TearOffMenu(TranslateCounterName::getDisplayName(state->getName()));
|
||||
aSet = new QAction(this);
|
||||
|
|
@ -79,6 +84,19 @@ void AbstractCounter::delCounter()
|
|||
}
|
||||
}
|
||||
|
||||
void AbstractCounter::setValue(int _value)
|
||||
{
|
||||
value = _value;
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCounter::setActive(bool _active)
|
||||
{
|
||||
active = _active;
|
||||
setVisible(_active);
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCounter::retranslateUi()
|
||||
{
|
||||
if (aSet) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* @file abstract_counter.h
|
||||
* @ingroup GameGraphicsPlayers
|
||||
* @brief Abstract base for player counters displayed on the game board.
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
|
|
@ -61,6 +62,13 @@ public:
|
|||
~AbstractCounter() override;
|
||||
|
||||
void retranslateUi() override;
|
||||
|
||||
/**
|
||||
* @brief Sets the counter value and triggers a visual update.
|
||||
* Virtual to allow subclass display customization (e.g., CommanderTaxCounter tooltip updates).
|
||||
* Overflow protection is handled server-side, not in client counter classes.
|
||||
*/
|
||||
virtual void setValue(int _value);
|
||||
void setShortcutsActive() override;
|
||||
void setShortcutsInactive() override;
|
||||
void delCounter();
|
||||
|
|
@ -93,6 +101,25 @@ public:
|
|||
{
|
||||
return shownInCounterArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether this counter is active (visible and interactable).
|
||||
* Inactive counters are hidden and their menu actions should be disabled.
|
||||
*/
|
||||
[[nodiscard]] bool isActive() const
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the active state of this counter.
|
||||
* When inactive, the counter is hidden via setVisible(false).
|
||||
* @param _active True to show and enable the counter, false to hide it
|
||||
*/
|
||||
virtual void setActive(bool _active);
|
||||
|
||||
private:
|
||||
bool active = true;
|
||||
};
|
||||
|
||||
class AbstractCounterDialog : public QInputDialog
|
||||
|
|
|
|||
60
cockatrice/src/game_graphics/board/commander_tax_counter.cpp
Normal file
60
cockatrice/src/game_graphics/board/commander_tax_counter.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#include "commander_tax_counter.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;
|
||||
static constexpr int OVERLAY_ALPHA = 191;
|
||||
static const QColor OVERLAY_BG_NORMAL{40, 40, 40, OVERLAY_ALPHA};
|
||||
static const QColor OVERLAY_BG_HOVERED{70, 70, 70, OVERLAY_ALPHA};
|
||||
|
||||
CommanderTaxCounter::CommanderTaxCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent)
|
||||
: AbstractCounter(state, player, false, false, parent), size(TaxCounterSizes::TAX_COUNTER_SIZE)
|
||||
{
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
setAcceptHoverEvents(true);
|
||||
setCursor(Qt::ArrowCursor);
|
||||
|
||||
setToolTip(tr("%1: %2").arg(TranslateCounterName::getDisplayName(getName())).arg(getValue()));
|
||||
}
|
||||
|
||||
QRectF CommanderTaxCounter::boundingRect() const
|
||||
{
|
||||
return QRectF(0, 0, size, size);
|
||||
}
|
||||
|
||||
void CommanderTaxCounter::paint(QPainter *painter,
|
||||
[[maybe_unused]] const QStyleOptionGraphicsItem *option,
|
||||
[[maybe_unused]] QWidget *widget)
|
||||
{
|
||||
painter->save();
|
||||
|
||||
QRectF rect = boundingRect().adjusted(1, 1, -1, -1);
|
||||
|
||||
QColor bgColor = hovered ? OVERLAY_BG_HOVERED : OVERLAY_BG_NORMAL;
|
||||
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(bgColor);
|
||||
painter->drawRoundedRect(rect, CORNER_RADIUS, CORNER_RADIUS);
|
||||
|
||||
QFont f = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
|
||||
f.setPixelSize(static_cast<int>(size * FONT_SIZE_RATIO));
|
||||
f.setWeight(QFont::Bold);
|
||||
painter->setFont(f);
|
||||
painter->setPen(Qt::white);
|
||||
painter->drawText(rect, Qt::AlignCenter, QString::number(value));
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void CommanderTaxCounter::setValue(int _value)
|
||||
{
|
||||
int clampedValue = qMax(0, _value);
|
||||
AbstractCounter::setValue(clampedValue);
|
||||
setToolTip(tr("%1: %2").arg(TranslateCounterName::getDisplayName(getName())).arg(clampedValue));
|
||||
}
|
||||
72
cockatrice/src/game_graphics/board/commander_tax_counter.h
Normal file
72
cockatrice/src/game_graphics/board/commander_tax_counter.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @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 Size constants for commander tax counter layout.
|
||||
*/
|
||||
namespace TaxCounterSizes
|
||||
{
|
||||
|
||||
/** @brief Size of commander tax counter icons (width and height) */
|
||||
constexpr int TAX_COUNTER_SIZE = 24;
|
||||
|
||||
/** @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 cumulative cost increase for casting a commander. The counter
|
||||
* is manually adjusted by the player to track their commander tax. 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;
|
||||
|
||||
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
|
||||
|
|
@ -8,4 +8,6 @@ const QMap<QString, QString> TranslateCounterName::translated = {
|
|||
{"r", QT_TRANSLATE_NOOP("TranslateCounterName", "Red")},
|
||||
{"g", QT_TRANSLATE_NOOP("TranslateCounterName", "Green")},
|
||||
{"x", QT_TRANSLATE_NOOP("TranslateCounterName", "Colorless")},
|
||||
{"storm", QT_TRANSLATE_NOOP("TranslateCounterName", "Other")}};
|
||||
{"storm", QT_TRANSLATE_NOOP("TranslateCounterName", "Other")},
|
||||
{"commander_tax_counter", QT_TRANSLATE_NOOP("TranslateCounterName", "Commander Tax")},
|
||||
{"partner_tax_counter", QT_TRANSLATE_NOOP("TranslateCounterName", "Partner Tax")}};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
#include <libcockatrice/protocol/pb/context_move_card.pb.h>
|
||||
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
|
||||
#include <libcockatrice/utility/counter_ids.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
#include <utility>
|
||||
|
||||
|
|
@ -80,6 +81,8 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
|
|||
fromStr = tr(" from sideboard");
|
||||
} else if (zoneName == ZoneNames::STACK) {
|
||||
fromStr = tr(" from the stack");
|
||||
} else if (zoneName == ZoneNames::COMMAND) {
|
||||
fromStr = tr(" from the command zone");
|
||||
} else {
|
||||
fromStr = tr(" from custom zone '%1'").arg(zoneName);
|
||||
}
|
||||
|
|
@ -344,6 +347,8 @@ void MessageLogWidget::logMoveCard(PlayerLogic *player,
|
|||
} else {
|
||||
finalStr = tr("%1 plays %2%3.");
|
||||
}
|
||||
} else if (targetZoneName == ZoneNames::COMMAND) {
|
||||
finalStr = tr("%1 moves %2%3 to the command zone.");
|
||||
} else {
|
||||
fourthArg = targetZoneName;
|
||||
if (card->getFaceDown()) {
|
||||
|
|
@ -671,6 +676,20 @@ void MessageLogWidget::logSetCounter(PlayerLogic *player, QString counterName, i
|
|||
soundEngine->playSound("life_change");
|
||||
}
|
||||
|
||||
if (counterName == CounterNames::CommanderTax || counterName == CounterNames::PartnerTax) {
|
||||
QString playerName = sanitizeHtml(player->getPlayerInfo()->getName());
|
||||
QString valueStr = QString("<font class=\"blue\">%1</font>").arg(value);
|
||||
int delta = value - oldValue;
|
||||
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
|
||||
QString taxLabel = QString("<font class=\"blue\">%1</font>").arg(sanitizeHtml(counterDisplayName));
|
||||
if (value > oldValue) {
|
||||
appendHtmlServerMessage(tr("%1 increases %2 to %3 (+%4).").arg(playerName, taxLabel, valueStr).arg(delta));
|
||||
} else {
|
||||
appendHtmlServerMessage(tr("%1 decreases %2 to %3 (%4).").arg(playerName, taxLabel, valueStr).arg(delta));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
|
||||
appendHtmlServerMessage(tr("%1 sets counter %2 to %3 (%4%5).")
|
||||
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ enum CardMenuActionType
|
|||
cmMoveToHand,
|
||||
cmMoveToGraveyard,
|
||||
cmMoveToExile,
|
||||
cmMoveToTable
|
||||
cmMoveToTable,
|
||||
cmMoveToCommandZone
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
/**
|
||||
|
|
@ -81,6 +83,8 @@ 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()); });
|
||||
aPlayAndIncreaseTax = makeAction(this, [actions, sel]() { actions->actPlayAndIncreaseTax(sel()); });
|
||||
aPlayAndIncreasePartnerTax = makeAction(this, [actions, sel]() { actions->actPlayAndIncreasePartnerTax(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()); });
|
||||
|
|
@ -157,6 +161,31 @@ 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);
|
||||
|
||||
if (player->getTaxCounterIfActive(CounterIds::CommanderTax)) {
|
||||
addAction(aPlayAndIncreaseTax);
|
||||
}
|
||||
|
||||
if (player->getTaxCounterIfActive(CounterIds::PartnerTax)) {
|
||||
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 +516,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"));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
185
cockatrice/src/game_graphics/player/menu/command_zone_menu.cpp
Normal file
185
cockatrice/src/game_graphics/player/menu/command_zone_menu.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "command_zone_menu.h"
|
||||
|
||||
#include "../../../client/settings/cache_settings.h"
|
||||
#include "../../board/abstract_counter.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../game/player/player_logic.h"
|
||||
#include "../../game_scene.h"
|
||||
#include "../../zones/command_zone.h"
|
||||
#include "../player_graphics_item.h"
|
||||
|
||||
#include <libcockatrice/utility/counter_ids.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
|
||||
CommandZoneMenu::CommandZoneMenu(PlayerGraphicsItem *_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]() {
|
||||
if (PlayerLogic *logic = player->getLogic()) {
|
||||
emit logic->requestZoneViewToggle(logic, ZoneNames::COMMAND, -1, false);
|
||||
}
|
||||
});
|
||||
|
||||
PlayerLogic *logic = player->getLogic();
|
||||
if (logic && logic->getPlayerInfo()->getLocalOrJudge()) {
|
||||
addAction(aViewZone);
|
||||
addSeparator();
|
||||
|
||||
PlayerActions *playerActions = player->getLogic()->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->getCommandZoneGraphicsItem();
|
||||
if (zone) {
|
||||
zone->toggleMinimized();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandZoneMenu::updateTaxCounterActionStates()
|
||||
{
|
||||
AbstractCounter *cmdTax = player->getTaxCounterIfActive(CounterIds::CommanderTax);
|
||||
AbstractCounter *partnerTax = player->getTaxCounterIfActive(CounterIds::PartnerTax);
|
||||
|
||||
if (aIncreaseCommanderTax) {
|
||||
aIncreaseCommanderTax->setVisible(cmdTax != nullptr);
|
||||
}
|
||||
if (aDecreaseCommanderTax) {
|
||||
aDecreaseCommanderTax->setVisible(cmdTax != nullptr);
|
||||
}
|
||||
if (aToggleCommanderTaxCounter) {
|
||||
aToggleCommanderTaxCounter->setText(cmdTax ? tr("&Remove Commander Tax") : tr("&Add Commander Tax"));
|
||||
aToggleCommanderTaxCounter->setVisible(!cmdTax || cmdTax->getValue() == 0);
|
||||
}
|
||||
|
||||
if (aIncreasePartnerTax) {
|
||||
aIncreasePartnerTax->setVisible(partnerTax != nullptr);
|
||||
}
|
||||
if (aDecreasePartnerTax) {
|
||||
aDecreasePartnerTax->setVisible(partnerTax != nullptr);
|
||||
}
|
||||
if (aTogglePartnerTaxCounter) {
|
||||
aTogglePartnerTaxCounter->setText(partnerTax ? tr("R&emove Partner Tax") : tr("&Add Partner Tax"));
|
||||
aTogglePartnerTaxCounter->setVisible(!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());
|
||||
}
|
||||
}
|
||||
62
cockatrice/src/game_graphics/player/menu/command_zone_menu.h
Normal file
62
cockatrice/src/game_graphics/player/menu/command_zone_menu.h
Normal 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 PlayerGraphicsItem;
|
||||
|
||||
/**
|
||||
* @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(PlayerGraphicsItem *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();
|
||||
PlayerGraphicsItem *player;
|
||||
|
||||
QString viewZoneShortcutKey;
|
||||
QString incTaxShortcutKey;
|
||||
QString decTaxShortcutKey;
|
||||
QString incPartnerTaxShortcutKey;
|
||||
QString decPartnerTaxShortcutKey;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMAND_ZONE_MENU_H
|
||||
|
|
@ -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,13 @@ MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
|
|||
addAction(aMoveToGraveyard);
|
||||
addSeparator();
|
||||
addAction(aMoveToExile);
|
||||
addSeparator();
|
||||
addAction(aMoveToCommandZone);
|
||||
|
||||
auto *playerLogic = player->getLogic();
|
||||
auto updateCommandZoneVisibility = [this](bool has) { aMoveToCommandZone->setVisible(has); };
|
||||
connect(playerLogic, &PlayerLogic::commandZoneSupportChanged, this, updateCommandZoneVisibility);
|
||||
updateCommandZoneVisibility(playerLogic->hasServerCommandZone());
|
||||
|
||||
setShortcutsActive();
|
||||
|
||||
|
|
@ -65,6 +74,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 +86,5 @@ void MoveMenu::retranslateUi()
|
|||
aMoveToHand->setText(tr("&Hand"));
|
||||
aMoveToGraveyard->setText(tr("&Graveyard"));
|
||||
aMoveToExile->setText(tr("&Exile"));
|
||||
aMoveToCommandZone->setText(tr("&Command Zone"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public:
|
|||
QAction *aMoveToTable = nullptr;
|
||||
QAction *aMoveToGraveyard = nullptr;
|
||||
QAction *aMoveToExile = nullptr;
|
||||
QAction *aMoveToCommandZone = nullptr;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_MOVE_MENU_H
|
||||
|
|
|
|||
|
|
@ -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->getLogic(), &PlayerLogic::commandZoneSupportChanged, this, updateCommandZoneMenuVisibility);
|
||||
updateCommandZoneMenuVisibility(player->getLogic()->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#define COCKATRICE_PLAYER_MENU_H
|
||||
|
||||
#include "../../../interface/widgets/menus/tearoff_menu.h"
|
||||
#include "../../game/player/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
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
#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/counter_general.h"
|
||||
#include "../hand_counter.h"
|
||||
#include "../z_values.h"
|
||||
#include "../zones/command_zone.h"
|
||||
#include "../zones/hand_zone.h"
|
||||
#include "../zones/pile_zone.h"
|
||||
#include "../zones/stack_zone.h"
|
||||
|
|
@ -13,6 +16,7 @@
|
|||
#include "player_dialogs.h"
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <libcockatrice/utility/counter_ids.h>
|
||||
|
||||
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
||||
{
|
||||
|
|
@ -61,6 +65,7 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
|||
|
||||
connect(player, &PlayerLogic::addViewCustomZoneActionToCustomZoneMenu, this,
|
||||
&PlayerGraphicsItem::onCustomZoneAdded);
|
||||
connect(player, &PlayerLogic::commandZoneSupportChanged, this, &PlayerGraphicsItem::setCommandZoneVisible);
|
||||
|
||||
playerMenu->setMenusForGraphicItems();
|
||||
|
||||
|
|
@ -121,6 +126,12 @@ void PlayerGraphicsItem::initializeZones()
|
|||
new HandZone(player->getHandZone(), static_cast<int>(tableZoneGraphicsItem->boundingRect().height()), this);
|
||||
connect(player->getPlayerActions(), &PlayerActions::requestSortHand, handZoneGraphicsItem, &HandZone::sortHand);
|
||||
|
||||
// Command zone
|
||||
commandZoneGraphicsItem = new CommandZone(player->getCommandZone(), ZoneSizes::COMMAND_ZONE_HEIGHT, this);
|
||||
commandZoneGraphicsItem->setZValue(ZValues::COMMAND_ZONE);
|
||||
commandZoneGraphicsItem->setVisible(false);
|
||||
connect(commandZoneGraphicsItem, &CommandZone::minimizedChanged, this, &PlayerGraphicsItem::rearrangeZones);
|
||||
|
||||
connect(handZoneGraphicsItem->getLogic(), &HandZoneLogic::cardCountChanged, handCounter,
|
||||
&HandCounter::updateNumber);
|
||||
connect(handCounter, &HandCounter::showContextMenu, handZoneGraphicsItem, &HandZone::showContextMenu);
|
||||
|
|
@ -132,6 +143,7 @@ void PlayerGraphicsItem::initializeZones()
|
|||
zoneGraphicsItems.insert(player->getTableZone()->getName(), tableZoneGraphicsItem);
|
||||
zoneGraphicsItems.insert(player->getStackZone()->getName(), stackZoneGraphicsItem);
|
||||
zoneGraphicsItems.insert(player->getHandZone()->getName(), handZoneGraphicsItem);
|
||||
zoneGraphicsItems.insert(player->getCommandZone()->getName(), commandZoneGraphicsItem);
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::onCustomZoneAdded(QString customZoneName)
|
||||
|
|
@ -187,6 +199,16 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
|
|||
AbstractCounter *widget;
|
||||
if (state->getName() == "life") {
|
||||
widget = playerTarget->addCounter(state);
|
||||
} else if (CounterNames::isTaxCounter(state->getName())) {
|
||||
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);
|
||||
} else {
|
||||
widget = new GeneralCounter(state, player, true, this);
|
||||
}
|
||||
|
|
@ -218,9 +240,16 @@ void PlayerGraphicsItem::onCounterRemoved(int counterId)
|
|||
|
||||
void PlayerGraphicsItem::rearrangeCounters()
|
||||
{
|
||||
if (commandZoneGraphicsItem) {
|
||||
commandZoneGraphicsItem->rearrangeTaxCounters();
|
||||
}
|
||||
|
||||
qreal ySize = boundingRect().y() + 80;
|
||||
constexpr qreal padding = 5;
|
||||
for (auto *ctr : counterWidgets.values()) {
|
||||
if (CounterNames::isTaxCounter(ctr->getName())) {
|
||||
continue;
|
||||
}
|
||||
if (!ctr->getShownInCounterArea()) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -230,9 +259,39 @@ void PlayerGraphicsItem::rearrangeCounters()
|
|||
}
|
||||
}
|
||||
|
||||
QList<AbstractCounter *> PlayerGraphicsItem::getTaxCounterWidgets() const
|
||||
{
|
||||
QList<AbstractCounter *> result;
|
||||
for (AbstractCounter *ctr : counterWidgets.values()) {
|
||||
if (CounterNames::isTaxCounter(ctr->getName())) {
|
||||
result.append(ctr);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AbstractCounter *PlayerGraphicsItem::getTaxCounterIfActive(int counterId) const
|
||||
{
|
||||
AbstractCounter *counter = getCounterWidget(counterId);
|
||||
return (counter && counter->isActive()) ? counter : nullptr;
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::rearrangeZones()
|
||||
{
|
||||
auto base = QPointF(CardDimensions::HEIGHT_F + counterAreaWidth + 15, 0);
|
||||
|
||||
// Calculate stack height, accounting for command zone if visible
|
||||
bool commandZoneVisible = commandZoneGraphicsItem && commandZoneGraphicsItem->isVisible();
|
||||
qreal tableHeight = tableZoneGraphicsItem->boundingRect().height();
|
||||
qreal stackHeight = tableHeight;
|
||||
if (commandZoneVisible) {
|
||||
stackHeight = tableHeight - totalCommandZoneHeight();
|
||||
if (stackHeight < CommandZone::MINIMUM_STACKING_HEIGHT) {
|
||||
stackHeight = CommandZone::MINIMUM_STACKING_HEIGHT;
|
||||
}
|
||||
}
|
||||
stackZoneGraphicsItem->setHeight(stackHeight);
|
||||
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
if (mirrored) {
|
||||
if (player->getHandZone()->contentsKnown()) {
|
||||
|
|
@ -243,12 +302,12 @@ void PlayerGraphicsItem::rearrangeZones()
|
|||
handVisible = false;
|
||||
}
|
||||
|
||||
stackZoneGraphicsItem->setPos(base);
|
||||
positionCommandAndStackZones(base);
|
||||
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||
|
||||
tableZoneGraphicsItem->setPos(base);
|
||||
} else {
|
||||
stackZoneGraphicsItem->setPos(base);
|
||||
positionCommandAndStackZones(base);
|
||||
|
||||
tableZoneGraphicsItem->setPos(base.x() + stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||
base += QPointF(0, tableZoneGraphicsItem->boundingRect().height());
|
||||
|
|
@ -268,7 +327,7 @@ void PlayerGraphicsItem::rearrangeZones()
|
|||
handZoneGraphicsItem->setPos(base);
|
||||
base += QPointF(handZoneGraphicsItem->boundingRect().width(), 0);
|
||||
|
||||
stackZoneGraphicsItem->setPos(base);
|
||||
positionCommandAndStackZones(base);
|
||||
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||
|
||||
tableZoneGraphicsItem->setPos(base);
|
||||
|
|
@ -297,3 +356,28 @@ void PlayerGraphicsItem::updateBoundingRect()
|
|||
|
||||
emit sizeChanged();
|
||||
}
|
||||
|
||||
qreal PlayerGraphicsItem::totalCommandZoneHeight() const
|
||||
{
|
||||
if (commandZoneGraphicsItem && commandZoneGraphicsItem->isVisible()) {
|
||||
return commandZoneGraphicsItem->currentHeight();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::positionCommandAndStackZones(const QPointF &base)
|
||||
{
|
||||
bool commandZoneVisible = commandZoneGraphicsItem && commandZoneGraphicsItem->isVisible();
|
||||
if (commandZoneVisible) {
|
||||
commandZoneGraphicsItem->setPos(base);
|
||||
}
|
||||
stackZoneGraphicsItem->setPos(base.x(), base.y() + (commandZoneVisible ? totalCommandZoneHeight() : 0));
|
||||
}
|
||||
|
||||
void PlayerGraphicsItem::setCommandZoneVisible(bool visible)
|
||||
{
|
||||
if (commandZoneGraphicsItem) {
|
||||
commandZoneGraphicsItem->setVisible(visible);
|
||||
}
|
||||
rearrangeZones();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <QGraphicsObject>
|
||||
|
||||
class CommandZone;
|
||||
class HandZone;
|
||||
class PileZone;
|
||||
class PlayerDialogs;
|
||||
|
|
@ -112,6 +113,20 @@ public:
|
|||
{
|
||||
return handZoneGraphicsItem;
|
||||
}
|
||||
/** @brief Returns the command zone graphics item. */
|
||||
[[nodiscard]] CommandZone *getCommandZoneGraphicsItem() const
|
||||
{
|
||||
return commandZoneGraphicsItem;
|
||||
}
|
||||
/** @brief Returns the counter widget for the given counter ID, or nullptr if not found. */
|
||||
[[nodiscard]] AbstractCounter *getCounterWidget(int counterId) const
|
||||
{
|
||||
return counterWidgets.value(counterId, nullptr);
|
||||
}
|
||||
/** @brief Returns all tax counter widgets (commander tax and partner tax). */
|
||||
[[nodiscard]] QList<AbstractCounter *> getTaxCounterWidgets() const;
|
||||
/** @brief Returns the tax counter if it exists and is active, or nullptr otherwise. */
|
||||
[[nodiscard]] AbstractCounter *getTaxCounterIfActive(int counterId) const;
|
||||
|
||||
public slots:
|
||||
void onPlayerActiveChanged(bool _active);
|
||||
|
|
@ -120,6 +135,8 @@ public slots:
|
|||
void onCounterRemoved(int counterId);
|
||||
void rearrangeCounters();
|
||||
void retranslateUi();
|
||||
/** @brief Shows or hides the command zone and rearranges dependent zones. */
|
||||
void setCommandZoneVisible(bool visible);
|
||||
|
||||
signals:
|
||||
void sizeChanged();
|
||||
|
|
@ -142,10 +159,15 @@ private:
|
|||
TableZone *tableZoneGraphicsItem;
|
||||
StackZone *stackZoneGraphicsItem;
|
||||
HandZone *handZoneGraphicsItem;
|
||||
CommandZone *commandZoneGraphicsItem;
|
||||
QRectF bRect;
|
||||
bool mirrored;
|
||||
bool handVisible = false;
|
||||
|
||||
/** @brief Returns the command zone's display height, or 0 if hidden. */
|
||||
[[nodiscard]] qreal totalCommandZoneHeight() const;
|
||||
/** @brief Positions the command and stack zones vertically starting from base, updating base.y. */
|
||||
void positionCommandAndStackZones(const QPointF &base);
|
||||
private slots:
|
||||
void updateBoundingRect();
|
||||
void rearrangeZones();
|
||||
|
|
|
|||
|
|
@ -29,11 +29,16 @@
|
|||
namespace ZValues
|
||||
{
|
||||
|
||||
/** @brief Command zone sits at standard zone level */
|
||||
constexpr qreal COMMAND_ZONE = 1.0;
|
||||
|
||||
// Expose base for callers that need it
|
||||
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);
|
||||
constexpr qreal ARROWS = ZValueLayerManager::overlayZValue(3.0);
|
||||
constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0);
|
||||
constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0);
|
||||
|
|
|
|||
181
cockatrice/src/game_graphics/zones/command_zone.cpp
Normal file
181
cockatrice/src/game_graphics/zones/command_zone.cpp
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
#include "command_zone.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../game/player/player_logic.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
#include "../board/abstract_counter.h"
|
||||
#include "../board/card_drag_item.h"
|
||||
#include "../board/card_item.h"
|
||||
#include "../board/commander_tax_counter.h"
|
||||
#include "../z_values.h"
|
||||
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
#include <libcockatrice/utility/counter_ids.h>
|
||||
|
||||
CommandZone::CommandZone(CommandZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent)
|
||||
: SelectZone(_logic, parent), zoneHeight(_zoneHeight)
|
||||
{
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, &CommandZone::updateBg);
|
||||
updateBg();
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
setupClipContainer(ZValues::CARD_BASE);
|
||||
}
|
||||
|
||||
void CommandZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF CommandZone::boundingRect() const
|
||||
{
|
||||
return {0, 0, ZoneSizes::COMMAND_ZONE_WIDTH, currentHeight()};
|
||||
}
|
||||
|
||||
qreal CommandZone::currentHeight() const
|
||||
{
|
||||
return minimized ? qMax(zoneHeight * MINIMIZED_HEIGHT_RATIO, static_cast<double>(minimumHeight)) : zoneHeight;
|
||||
}
|
||||
|
||||
void CommandZone::setMinimumHeight(int height)
|
||||
{
|
||||
if (minimumHeight == height) {
|
||||
return;
|
||||
}
|
||||
minimumHeight = height;
|
||||
prepareGeometryChange();
|
||||
updateClipRect();
|
||||
reorganizeCards();
|
||||
update();
|
||||
// NOTE: Do NOT emit minimizedChanged here. The minimized STATE has not changed,
|
||||
// only the minimum height constraint. Emitting here causes an infinite loop:
|
||||
// rearrangeZones -> rearrangeCounters -> rearrangeTaxCounters -> setMinimumHeight
|
||||
// -> minimizedChanged -> rearrangeZones (loop!)
|
||||
}
|
||||
|
||||
bool CommandZone::isMinimized() const
|
||||
{
|
||||
return minimized;
|
||||
}
|
||||
|
||||
void CommandZone::toggleMinimized()
|
||||
{
|
||||
minimized = !minimized;
|
||||
|
||||
prepareGeometryChange();
|
||||
updateClipRect();
|
||||
reorganizeCards();
|
||||
update();
|
||||
|
||||
emit minimizedChanged(minimized);
|
||||
}
|
||||
|
||||
void CommandZone::paint(QPainter *painter,
|
||||
[[maybe_unused]] const QStyleOptionGraphicsItem *option,
|
||||
[[maybe_unused]] QWidget *widget)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Command, getLogic()->getPlayer()->getZoneId());
|
||||
|
||||
QPointF scenePos = mapToScene(QPointF(0, 0));
|
||||
painter->setBrushOrigin(-scenePos);
|
||||
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
void CommandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr || dragItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE);
|
||||
|
||||
// Same-zone no-op: don't move a card onto itself
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (!cards.isEmpty() && index < cards.size() && startZone == getLogic() &&
|
||||
cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
cmd.set_x(index);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (const CardDragItem *item : dragItems) {
|
||||
if (item) {
|
||||
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
cardToMove->set_card_id(item->getId());
|
||||
if (item->isForceFaceDown()) {
|
||||
cardToMove->set_face_down(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void CommandZone::reorganizeCards()
|
||||
{
|
||||
restoreStaleEscapedCards();
|
||||
updateClipRect();
|
||||
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (cards.isEmpty()) {
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
auto params = buildStackParams(MIN_CARD_VISIBLE);
|
||||
params.allowBottomOverflow = true;
|
||||
layoutCardsVertically(params);
|
||||
update();
|
||||
}
|
||||
|
||||
void CommandZone::registerTaxCounter(AbstractCounter *counter)
|
||||
{
|
||||
if (!counter || taxCounters.contains(counter)) {
|
||||
return;
|
||||
}
|
||||
taxCounters.append(counter);
|
||||
connect(counter, &QObject::destroyed, this, [this, counter]() { taxCounters.removeOne(counter); });
|
||||
}
|
||||
|
||||
void CommandZone::rearrangeTaxCounters()
|
||||
{
|
||||
bool commandZoneVisible = isVisible();
|
||||
int activeTaxCounterCount = 0;
|
||||
|
||||
for (AbstractCounter *ctr : taxCounters) {
|
||||
qreal y = TaxCounterSizes::TAX_COUNTER_MARGIN +
|
||||
activeTaxCounterCount * (TaxCounterSizes::TAX_COUNTER_SIZE + TaxCounterSizes::TAX_COUNTER_MARGIN);
|
||||
ctr->setPos(TaxCounterSizes::TAX_COUNTER_MARGIN, y);
|
||||
ctr->setZValue(ZValues::TAX_COUNTERS);
|
||||
bool visible = commandZoneVisible && ctr->isActive();
|
||||
ctr->setVisible(visible);
|
||||
if (visible) {
|
||||
++activeTaxCounterCount;
|
||||
}
|
||||
}
|
||||
|
||||
int minHeight = activeTaxCounterCount * (TaxCounterSizes::TAX_COUNTER_SIZE + TaxCounterSizes::TAX_COUNTER_MARGIN) +
|
||||
TaxCounterSizes::TAX_COUNTER_MARGIN;
|
||||
setMinimumHeight(minHeight);
|
||||
}
|
||||
|
||||
void CommandZone::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
toggleMinimized();
|
||||
event->accept();
|
||||
} else {
|
||||
SelectZone::mouseDoubleClickEvent(event);
|
||||
}
|
||||
}
|
||||
108
cockatrice/src/game_graphics/zones/command_zone.h
Normal file
108
cockatrice/src/game_graphics/zones/command_zone.h
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* @file command_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief Graphics layer for the command zone, used for Commander format.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_COMMAND_ZONE_H
|
||||
#define COCKATRICE_COMMAND_ZONE_H
|
||||
|
||||
#include "../../game/zones/command_zone_logic.h"
|
||||
#include "../card_dimensions.h"
|
||||
#include "select_zone.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
class AbstractCounter;
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CommandZoneLog, "command_zone");
|
||||
|
||||
/**
|
||||
* @namespace ZoneSizes
|
||||
* @brief Size constants for the command zone and its sub-elements.
|
||||
*/
|
||||
namespace ZoneSizes
|
||||
{
|
||||
|
||||
/** @brief Height of the command zone (accommodates a card plus padding) */
|
||||
constexpr qreal COMMAND_ZONE_HEIGHT = CardDimensions::HEIGHT + 8;
|
||||
|
||||
/** @brief Width of the command zone (matches stack zone) */
|
||||
constexpr qreal COMMAND_ZONE_WIDTH = CardDimensions::WIDTH_F * 1.5;
|
||||
|
||||
} // namespace ZoneSizes
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @see CommandZoneLogic for card data management
|
||||
* @see CommanderTaxCounter for the tax counter overlay
|
||||
*/
|
||||
class CommandZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static constexpr qreal MINIMUM_STACKING_HEIGHT = 50.0;
|
||||
|
||||
private:
|
||||
static constexpr double MINIMIZED_HEIGHT_RATIO = 0.25;
|
||||
int zoneHeight; ///< Full height in pixels when expanded
|
||||
bool minimized = false; ///< Whether zone is at 25% height
|
||||
int minimumHeight = 0; ///< Floor for minimized height (e.g. to fit tax counters)
|
||||
QList<AbstractCounter *> taxCounters; ///< Registered tax counter widgets
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a CommandZone graphics item.
|
||||
* @param _logic Logic layer managing card data
|
||||
* @param _zoneHeight Zone height in pixels
|
||||
* @param parent Parent graphics item
|
||||
*/
|
||||
CommandZone(CommandZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent);
|
||||
|
||||
/**
|
||||
* @brief Handles card drops, calculating insertion position from drop point.
|
||||
* @param dragItems Cards being dragged
|
||||
* @param startZone Source zone
|
||||
* @param dropPoint Drop position in local coordinates
|
||||
*/
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
|
||||
/** @brief Returns the bounding rectangle, accounting for minimized state. */
|
||||
[[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. */
|
||||
void reorganizeCards() override;
|
||||
|
||||
/** @brief Toggles between full and 25% minimized height. */
|
||||
void toggleMinimized();
|
||||
[[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. */
|
||||
void setMinimumHeight(int height);
|
||||
/** @brief Registers a tax counter widget for layout management. */
|
||||
void registerTaxCounter(AbstractCounter *counter);
|
||||
/** @brief Lays out visible tax counters vertically in the top-left corner of the command zone. */
|
||||
void rearrangeTaxCounters();
|
||||
|
||||
signals:
|
||||
/** @brief Emitted when the zone toggles between minimized and expanded states. */
|
||||
void minimizedChanged(bool isMinimized);
|
||||
|
||||
protected:
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void updateBg();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMAND_ZONE_H
|
||||
|
|
@ -32,6 +32,10 @@ QRectF StackZone::boundingRect() const
|
|||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
||||
|
||||
QPointF scenePos = mapToScene(QPointF(0, 0));
|
||||
painter->setBrushOrigin(-scenePos);
|
||||
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +51,8 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
|
||||
// Same-zone no-op: don't move a card onto itself
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (!cards.isEmpty() && startZone == getLogic() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
||||
if (!cards.isEmpty() && index < cards.size() && startZone == getLogic() &&
|
||||
cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@
|
|||
#define PLAYERZONE_BG_NAME "playerzone"
|
||||
#define STACKZONE_BG_NAME "stackzone"
|
||||
#define TABLEZONE_BG_NAME "tablezone"
|
||||
#define COMMANDZONE_BG_NAME "commandzone"
|
||||
static const QColor HANDZONE_BG_DEFAULT = QColor(80, 100, 50);
|
||||
static const QColor TABLEZONE_BG_DEFAULT = QColor(70, 50, 100);
|
||||
static const QColor PLAYERZONE_BG_DEFAULT = QColor(200, 200, 200);
|
||||
static const QColor STACKZONE_BG_DEFAULT = QColor(113, 43, 43);
|
||||
static const QColor COMMANDZONE_BG_DEFAULT = QColor(50, 60, 80);
|
||||
static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"};
|
||||
|
||||
struct PaletteColorInfo
|
||||
|
|
@ -271,6 +273,9 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
|
|||
const PaletteConfig &palCfg,
|
||||
const QString &activeScheme)
|
||||
{
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 0))
|
||||
Q_UNUSED(activeScheme)
|
||||
#endif
|
||||
QString styleName = themeCfg.styleName;
|
||||
if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) {
|
||||
if (themeName == FUSION_THEME_NAME) {
|
||||
|
|
@ -370,6 +375,8 @@ void ThemeManager::themeChangedSlot()
|
|||
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
|
||||
|
||||
brushes[Role::Command] = loadBrush(COMMANDZONE_BG_NAME, COMMANDZONE_BG_DEFAULT);
|
||||
for (auto &brushCache : brushesCache) {
|
||||
brushCache.clear();
|
||||
}
|
||||
|
|
@ -394,6 +401,9 @@ static QString roleBgName(ThemeManager::Role role)
|
|||
case ThemeManager::Table:
|
||||
return TABLEZONE_BG_NAME;
|
||||
|
||||
case ThemeManager::Command:
|
||||
return COMMANDZONE_BG_NAME;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ public:
|
|||
Stack,
|
||||
Table,
|
||||
Player,
|
||||
MaxRole = Player,
|
||||
Command,
|
||||
MaxRole = Command,
|
||||
};
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ void DlgCreateGame::sharedCtor()
|
|||
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
|
||||
|
||||
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
|
||||
enableCommandZoneCheckBox = new QCheckBox(tr("Enable command zone"));
|
||||
|
||||
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
|
||||
|
||||
|
|
@ -109,6 +110,7 @@ void DlgCreateGame::sharedCtor()
|
|||
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
|
||||
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
|
||||
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
|
||||
gameSetupOptionsLayout->addWidget(enableCommandZoneCheckBox, 1, 1);
|
||||
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
|
||||
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
|
||||
} else {
|
||||
|
|
@ -171,6 +173,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameType
|
|||
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator());
|
||||
startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal());
|
||||
shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().getShareDecklistsOnLoad());
|
||||
enableCommandZoneCheckBox->setChecked(SettingsCache::instance().getEnableCommandZone());
|
||||
|
||||
if (!rememberGameSettings->isChecked()) {
|
||||
actReset();
|
||||
|
|
@ -204,6 +207,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
|
|||
createGameAsSpectatorCheckBox->setEnabled(false);
|
||||
startingLifeTotalEdit->setEnabled(false);
|
||||
shareDecklistsOnLoadCheckBox->setEnabled(false);
|
||||
enableCommandZoneCheckBox->setEnabled(false);
|
||||
|
||||
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
|
||||
maxPlayersEdit->setValue(gameInfo.max_players());
|
||||
|
|
@ -250,6 +254,7 @@ void DlgCreateGame::actReset()
|
|||
|
||||
startingLifeTotalEdit->setValue(20);
|
||||
shareDecklistsOnLoadCheckBox->setChecked(false);
|
||||
enableCommandZoneCheckBox->setChecked(false);
|
||||
createGameAsJudgeCheckBox->setChecked(false);
|
||||
|
||||
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
||||
|
|
@ -280,6 +285,7 @@ void DlgCreateGame::actOK()
|
|||
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
|
||||
cmd.set_starting_life_total(startingLifeTotalEdit->value());
|
||||
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
|
||||
cmd.set_enable_command_zone(enableCommandZoneCheckBox->isChecked());
|
||||
|
||||
auto _gameTypes = QString();
|
||||
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
||||
|
|
@ -304,6 +310,7 @@ void DlgCreateGame::actOK()
|
|||
SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
|
||||
SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
|
||||
SettingsCache::instance().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked());
|
||||
SettingsCache::instance().setEnableCommandZone(enableCommandZoneCheckBox->isChecked());
|
||||
SettingsCache::instance().setGameTypes(_gameTypes);
|
||||
}
|
||||
PendingCommand *pend = room->prepareRoomCommand(cmd);
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ private:
|
|||
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
|
||||
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
|
||||
QCheckBox *shareDecklistsOnLoadCheckBox;
|
||||
QCheckBox *enableCommandZoneCheckBox;
|
||||
QDialogButtonBox *buttonBox;
|
||||
QPushButton *clearButton;
|
||||
QCheckBox *rememberGameSettings;
|
||||
|
|
|
|||
|
|
@ -33,10 +33,13 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent)
|
|||
startingLifeTotalEdit->setValue(20);
|
||||
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
|
||||
|
||||
enableCommandZoneCheckBox = new QCheckBox(tr("Enable command zone"), this);
|
||||
|
||||
auto *gameSetupGrid = new QGridLayout;
|
||||
gameSetupGrid->setContentsMargins(5, 5, 5, 5);
|
||||
gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0);
|
||||
gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1);
|
||||
gameSetupGrid->addWidget(enableCommandZoneCheckBox, 1, 0, 1, 2);
|
||||
gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this);
|
||||
gameSetupOptionsGroupBox->setLayout(gameSetupGrid);
|
||||
|
||||
|
|
@ -57,6 +60,7 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent)
|
|||
if (rememberSettingsCheckBox->isChecked()) {
|
||||
numberPlayersEdit->setValue(SettingsCache::instance().getLocalGameMaxPlayers());
|
||||
startingLifeTotalEdit->setValue(SettingsCache::instance().getLocalGameStartingLifeTotal());
|
||||
enableCommandZoneCheckBox->setChecked(SettingsCache::instance().getLocalGameEnableCommandZone());
|
||||
}
|
||||
|
||||
setWindowTitle(tr("Local game options"));
|
||||
|
|
@ -71,6 +75,7 @@ void DlgLocalGameOptions::actOK()
|
|||
if (rememberSettingsCheckBox->isChecked()) {
|
||||
SettingsCache::instance().setLocalGameMaxPlayers(numberPlayersEdit->value());
|
||||
SettingsCache::instance().setLocalGameStartingLifeTotal(startingLifeTotalEdit->value());
|
||||
SettingsCache::instance().setLocalGameEnableCommandZone(enableCommandZoneCheckBox->isChecked());
|
||||
}
|
||||
|
||||
accept();
|
||||
|
|
@ -81,5 +86,6 @@ LocalGameOptions DlgLocalGameOptions::getOptions() const
|
|||
return LocalGameOptions{
|
||||
.numberPlayers = numberPlayersEdit->value(),
|
||||
.startingLifeTotal = startingLifeTotalEdit->value(),
|
||||
.enableCommandZone = enableCommandZoneCheckBox->isChecked(),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ struct LocalGameOptions
|
|||
{
|
||||
int numberPlayers = 1;
|
||||
int startingLifeTotal = 20;
|
||||
bool enableCommandZone = false;
|
||||
};
|
||||
|
||||
class QCheckBox;
|
||||
|
|
@ -45,6 +46,7 @@ private:
|
|||
QLabel *startingLifeTotalLabel;
|
||||
QSpinBox *startingLifeTotalEdit;
|
||||
|
||||
QCheckBox *enableCommandZoneCheckBox;
|
||||
QCheckBox *rememberSettingsCheckBox;
|
||||
|
||||
QDialogButtonBox *buttonBox;
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ void MainWindow::startLocalGame(const LocalGameOptions &options)
|
|||
Command_CreateGame createCommand;
|
||||
createCommand.set_max_players(static_cast<google::protobuf::uint32>(options.numberPlayers));
|
||||
createCommand.set_starting_life_total(options.startingLifeTotal);
|
||||
createCommand.set_enable_command_zone(options.enableCommandZone);
|
||||
mainClient->sendCommand(LocalClient::prepareRoomCommand(createCommand, 0));
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
cockatrice/themes/Fabric/zones/commandzone.png
Normal file
BIN
cockatrice/themes/Fabric/zones/commandzone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 220 KiB |
BIN
cockatrice/themes/Leather/zones/commandzone.png
Normal file
BIN
cockatrice/themes/Leather/zones/commandzone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
BIN
cockatrice/themes/Plasma/zones/commandzone.png
Normal file
BIN
cockatrice/themes/Plasma/zones/commandzone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
cockatrice/themes/VelvetMarble/zones/commandzone.png
Normal file
BIN
cockatrice/themes/VelvetMarble/zones/commandzone.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Loading…
Add table
Add a link
Reference in a new issue