mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-17 04:27:45 -07:00
[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:
parent
da4ba222c0
commit
b4057a865d
73 changed files with 1540 additions and 86 deletions
|
|
@ -67,6 +67,7 @@ set(cockatrice_SOURCES
|
||||||
src/game_graphics/board/card_item.cpp
|
src/game_graphics/board/card_item.cpp
|
||||||
src/game/board/card_list.cpp
|
src/game/board/card_list.cpp
|
||||||
src/game/board/card_state.cpp
|
src/game/board/card_state.cpp
|
||||||
|
src/game_graphics/board/commander_tax_counter.cpp
|
||||||
src/game_graphics/board/counter_general.cpp
|
src/game_graphics/board/counter_general.cpp
|
||||||
src/game/board/counter_state.cpp
|
src/game/board/counter_state.cpp
|
||||||
src/game_graphics/board/translate_counter_name.cpp
|
src/game_graphics/board/translate_counter_name.cpp
|
||||||
|
|
@ -86,7 +87,8 @@ set(cockatrice_SOURCES
|
||||||
src/game_graphics/log/message_log_widget.cpp
|
src/game_graphics/log/message_log_widget.cpp
|
||||||
src/game/phase.cpp
|
src/game/phase.cpp
|
||||||
src/game_graphics/phases_toolbar.cpp
|
src/game_graphics/phases_toolbar.cpp
|
||||||
src/game_graphics/player/menu/card_menu.cpp
|
src/game_graphics/player/menu/card_menu.
|
||||||
|
src/game_graphics/player/menu/command_zone_menu.cpp
|
||||||
src/game_graphics/player/menu/custom_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/grave_menu.cpp
|
||||||
src/game_graphics/player/menu/hand_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_graphics/player/player_target.cpp
|
||||||
src/game/replay.cpp
|
src/game/replay.cpp
|
||||||
src/game/zones/card_zone_logic.cpp
|
src/game/zones/card_zone_logic.cpp
|
||||||
|
src/game/zones/command_zone.cpp
|
||||||
|
src/game/zones/command_zone_logic.cpp
|
||||||
src/game/zones/hand_zone_logic.cpp
|
src/game/zones/hand_zone_logic.cpp
|
||||||
src/game/zones/pile_zone_logic.cpp
|
src/game/zones/pile_zone_logic.cpp
|
||||||
src/game/zones/stack_zone_logic.cpp
|
src/game/zones/stack_zone_logic.cpp
|
||||||
|
|
|
||||||
|
|
@ -413,6 +413,7 @@ SettingsCache::SettingsCache()
|
||||||
createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool();
|
createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool();
|
||||||
defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
|
defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
|
||||||
shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool();
|
shareDecklistsOnLoad = settings->value("game/sharedecklistsonload", false).toBool();
|
||||||
|
enableCommandZone = settings->value("game/enablecommandzone", false).toBool();
|
||||||
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
|
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
|
||||||
|
|
||||||
// Local game settings use "localgameoptions/" prefix to keep them separate
|
// Local game settings use "localgameoptions/" prefix to keep them separate
|
||||||
|
|
@ -420,6 +421,7 @@ SettingsCache::SettingsCache()
|
||||||
localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool();
|
localGameRememberSettings = settings->value("localgameoptions/remembersettings", false).toBool();
|
||||||
localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt();
|
localGameMaxPlayers = settings->value("localgameoptions/maxplayers", 1).toInt();
|
||||||
localGameStartingLifeTotal = settings->value("localgameoptions/startinglifetotal", 20).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();
|
clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
|
||||||
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
|
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
|
||||||
|
|
@ -1258,6 +1260,12 @@ void SettingsCache::setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad)
|
||||||
settings->setValue("game/sharedecklistsonload", 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)
|
void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value)
|
||||||
{
|
{
|
||||||
checkUpdatesOnStartup = static_cast<bool>(value);
|
checkUpdatesOnStartup = static_cast<bool>(value);
|
||||||
|
|
@ -1318,6 +1326,12 @@ void SettingsCache::setLocalGameStartingLifeTotal(int value)
|
||||||
settings->setValue("localgameoptions/startinglifetotal", 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)
|
void SettingsCache::setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate)
|
||||||
{
|
{
|
||||||
notifyAboutUpdates = static_cast<bool>(_notifyaboutupdate);
|
notifyAboutUpdates = static_cast<bool>(_notifyaboutupdate);
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,7 @@ private:
|
||||||
bool createGameAsSpectator;
|
bool createGameAsSpectator;
|
||||||
int defaultStartingLifeTotal;
|
int defaultStartingLifeTotal;
|
||||||
bool shareDecklistsOnLoad;
|
bool shareDecklistsOnLoad;
|
||||||
|
bool enableCommandZone;
|
||||||
int keepalive;
|
int keepalive;
|
||||||
int timeout;
|
int timeout;
|
||||||
void translateLegacySettings();
|
void translateLegacySettings();
|
||||||
|
|
@ -342,6 +343,7 @@ private:
|
||||||
bool localGameRememberSettings;
|
bool localGameRememberSettings;
|
||||||
int localGameMaxPlayers;
|
int localGameMaxPlayers;
|
||||||
int localGameStartingLifeTotal;
|
int localGameStartingLifeTotal;
|
||||||
|
bool localGameEnableCommandZone;
|
||||||
|
|
||||||
QList<ReleaseChannel *> releaseChannels;
|
QList<ReleaseChannel *> releaseChannels;
|
||||||
bool isPortableBuild;
|
bool isPortableBuild;
|
||||||
|
|
@ -889,6 +891,10 @@ public:
|
||||||
{
|
{
|
||||||
return shareDecklistsOnLoad;
|
return shareDecklistsOnLoad;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool getEnableCommandZone() const
|
||||||
|
{
|
||||||
|
return enableCommandZone;
|
||||||
|
}
|
||||||
[[nodiscard]] bool getCreateGameAsSpectator() const
|
[[nodiscard]] bool getCreateGameAsSpectator() const
|
||||||
{
|
{
|
||||||
return createGameAsSpectator;
|
return createGameAsSpectator;
|
||||||
|
|
@ -909,6 +915,10 @@ public:
|
||||||
{
|
{
|
||||||
return localGameStartingLifeTotal;
|
return localGameStartingLifeTotal;
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] bool getLocalGameEnableCommandZone() const
|
||||||
|
{
|
||||||
|
return localGameEnableCommandZone;
|
||||||
|
}
|
||||||
[[nodiscard]] int getKeepAlive() const override
|
[[nodiscard]] int getKeepAlive() const override
|
||||||
{
|
{
|
||||||
return keepalive;
|
return keepalive;
|
||||||
|
|
@ -1138,10 +1148,12 @@ public slots:
|
||||||
void setCreateGameAsSpectator(const bool _createGameAsSpectator);
|
void setCreateGameAsSpectator(const bool _createGameAsSpectator);
|
||||||
void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal);
|
void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal);
|
||||||
void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad);
|
void setShareDecklistsOnLoad(const bool _shareDecklistsOnLoad);
|
||||||
|
void setEnableCommandZone(const bool _enableCommandZone);
|
||||||
void setRememberGameSettings(const bool _rememberGameSettings);
|
void setRememberGameSettings(const bool _rememberGameSettings);
|
||||||
void setLocalGameRememberSettings(bool value);
|
void setLocalGameRememberSettings(bool value);
|
||||||
void setLocalGameMaxPlayers(int value);
|
void setLocalGameMaxPlayers(int value);
|
||||||
void setLocalGameStartingLifeTotal(int value);
|
void setLocalGameStartingLifeTotal(int value);
|
||||||
|
void setLocalGameEnableCommandZone(bool value);
|
||||||
void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value);
|
void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value);
|
||||||
void setStartupCardUpdateCheckPromptForUpdate(bool value);
|
void setStartupCardUpdateCheckPromptForUpdate(bool value);
|
||||||
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
|
void setStartupCardUpdateCheckAlwaysUpdate(bool value);
|
||||||
|
|
|
||||||
60
cockatrice/src/game/board/commander_tax_counter.cpp
Normal file
60
cockatrice/src/game/board/commander_tax_counter.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#include "commander_tax_counter.h"
|
||||||
|
|
||||||
|
#include "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/board/commander_tax_counter.h
Normal file
72
cockatrice/src/game/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
|
||||||
|
|
@ -2,15 +2,22 @@
|
||||||
|
|
||||||
#include <libcockatrice/utility/color.h>
|
#include <libcockatrice/utility/color.h>
|
||||||
|
|
||||||
CounterState::CounterState(int id, const QString &name, const QColor &color, int radius, int value, QObject *parent)
|
CounterState::CounterState(int id,
|
||||||
: QObject(parent), id(id), name(name), color(color), radius(radius), value(value)
|
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)
|
CounterState *CounterState::fromProto(const ServerInfo_Counter &counter, QObject *parent)
|
||||||
{
|
{
|
||||||
return new CounterState(counter.id(), QString::fromStdString(counter.name()),
|
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)
|
void CounterState::setValue(int newValue)
|
||||||
|
|
@ -22,3 +29,12 @@ void CounterState::setValue(int newValue)
|
||||||
value = newValue;
|
value = newValue;
|
||||||
emit valueChanged(old, 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
|
Q_OBJECT
|
||||||
public:
|
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);
|
static CounterState *fromProto(const ServerInfo_Counter &counter, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
|
@ -34,11 +40,17 @@ public:
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
bool isActive() const
|
||||||
|
{
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
void setValue(int newValue);
|
void setValue(int newValue);
|
||||||
|
void setActive(bool newActive);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valueChanged(int oldValue, int newValue);
|
void valueChanged(int oldValue, int newValue);
|
||||||
|
void activeChanged(bool newActive);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int id;
|
int id;
|
||||||
|
|
@ -46,6 +58,7 @@ private:
|
||||||
QColor color;
|
QColor color;
|
||||||
int radius;
|
int radius;
|
||||||
int value;
|
int value;
|
||||||
|
bool active;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_COUNTER_STATE_H
|
#endif // COCKATRICE_COUNTER_STATE_H
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "../../game_graphics/zones/table_zone.h"
|
#include "../../game_graphics/zones/table_zone.h"
|
||||||
#include "../../interface/widgets/tabs/tab_game.h"
|
#include "../../interface/widgets/tabs/tab_game.h"
|
||||||
#include "../../interface/widgets/utility/get_text_with_max.h"
|
#include "../../interface/widgets/utility/get_text_with_max.h"
|
||||||
|
|
||||||
#include "../zones/view_zone_logic.h"
|
#include "../zones/view_zone_logic.h"
|
||||||
|
|
||||||
#include <libcockatrice/card/database/card_database_manager.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_roll_die.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_card_attr.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_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_shuffle.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
|
#include <libcockatrice/protocol/pb/command_undo_draw.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/context_move_card.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/expression.h>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
@ -1526,9 +1529,9 @@ void PlayerActions::offsetCardCounter(QList<CardItem *> selectedCards, int count
|
||||||
int oldValue = card->getCounters().value(counterId, 0);
|
int oldValue = card->getCounters().value(counterId, 0);
|
||||||
int newValue = oldValue + offset;
|
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.
|
// 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) {
|
if (clampedValue != oldValue) {
|
||||||
auto *cmd = new Command_SetCardCounter;
|
auto *cmd = new Command_SetCardCounter;
|
||||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||||
|
|
@ -1562,7 +1565,7 @@ void PlayerActions::actSetCardCounter(QList<CardItem *> selectedCards, int count
|
||||||
Expression exp(oldValue);
|
Expression exp(oldValue);
|
||||||
double parsed = exp.parse(counterValue);
|
double parsed = exp.parse(counterValue);
|
||||||
// Clamp in double precision first to avoid UB, then cast
|
// 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;
|
auto *cmd = new Command_SetCardCounter;
|
||||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||||
|
|
@ -1592,7 +1595,7 @@ void PlayerActions::actIncrementAllCardCounters(QList<CardItem *> cardsToUpdate)
|
||||||
counterIterator.next();
|
counterIterator.next();
|
||||||
int counterId = counterIterator.key();
|
int counterId = counterIterator.key();
|
||||||
int currentValue = counterIterator.value();
|
int currentValue = counterIterator.value();
|
||||||
if (currentValue >= MAX_COUNTERS_ON_CARD) {
|
if (currentValue >= MAX_COUNTER_VALUE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1624,6 +1627,14 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone)
|
||||||
|
|
||||||
void PlayerActions::playSelectedCards(QList<CardItem *> selectedCards, const bool faceDown)
|
void PlayerActions::playSelectedCards(QList<CardItem *> selectedCards, const bool faceDown)
|
||||||
{
|
{
|
||||||
|
playSelectedCardsImpl(faceDown, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerActions::playSelectedCardsImpl(bool faceDown,
|
||||||
|
const std::function<void(CardItem *, const QString &)> &postPlayCallback)
|
||||||
|
{
|
||||||
|
QList<CardItem *> selectedCards = player->getGameScene()->selectedCards();
|
||||||
|
|
||||||
// CardIds will get shuffled downwards when cards leave the deck.
|
// 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
|
// We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play
|
||||||
// out the cards one-by-one.
|
// out the cards one-by-one.
|
||||||
|
|
@ -1632,9 +1643,66 @@ void PlayerActions::playSelectedCards(QList<CardItem *> selectedCards, const boo
|
||||||
|
|
||||||
for (auto &card : selectedCards) {
|
for (auto &card : selectedCards) {
|
||||||
if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != ZoneNames::TABLE) {
|
if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != ZoneNames::TABLE) {
|
||||||
|
const QString originalZone = card->getZone()->getName();
|
||||||
playCard(card, faceDown);
|
playCard(card, faceDown);
|
||||||
|
if (postPlayCallback) {
|
||||||
|
postPlayCallback(card, originalZone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerActions::actPlayAndIncreaseTax()
|
||||||
|
{
|
||||||
|
playSelectedCardsImpl(false, [this](CardItem * /*card*/, const QString &originalZone) {
|
||||||
|
if (originalZone == ZoneNames::COMMAND) {
|
||||||
|
AbstractCounter *ctr = player->getCounterWidget(CounterIds::CommanderTax);
|
||||||
|
if (ctr && ctr->isActive()) {
|
||||||
|
sendIncCounter(CounterIds::CommanderTax, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerActions::actPlayAndIncreasePartnerTax()
|
||||||
|
{
|
||||||
|
playSelectedCardsImpl(false, [this](CardItem * /*card*/, const QString &originalZone) {
|
||||||
|
if (originalZone == ZoneNames::COMMAND) {
|
||||||
|
AbstractCounter *ctr = player->getCounterWidget(CounterIds::PartnerTax);
|
||||||
|
if (ctr && ctr->isActive()) {
|
||||||
|
sendIncCounter(CounterIds::PartnerTax, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
AbstractCounter *ctr = player->getCounterWidget(counterId);
|
||||||
|
if (!ctr || !ctr->isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendIncCounter(counterId, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerActions::actToggleTaxCounter(int counterId)
|
||||||
|
{
|
||||||
|
AbstractCounter *ctr = player->getCounterWidget(counterId);
|
||||||
|
if (!ctr || (ctr->isActive() && ctr->getValue() != 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Command_SetCounterActive cmd;
|
||||||
|
cmd.set_counter_id(counterId);
|
||||||
|
cmd.set_active(!ctr->isActive());
|
||||||
|
sendGameCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actPlay(QList<CardItem *> selectedCards)
|
void PlayerActions::actPlay(QList<CardItem *> selectedCards)
|
||||||
|
|
@ -1917,6 +1985,18 @@ void PlayerActions::cardMenuAction(QList<CardItem *> selectedCards, CardMenuActi
|
||||||
commandList.append(cmd);
|
commandList.append(cmd);
|
||||||
break;
|
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: {
|
case cmMoveToTable: {
|
||||||
// Each card needs its own command because table row, pt, and cipt vary per card
|
// Each card needs its own command because table row, pt, and cipt vary per card
|
||||||
for (const auto &card : cardList) {
|
for (const auto &card : cardList) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <functional>
|
||||||
#include <libcockatrice/card/relation/card_relation_type.h>
|
#include <libcockatrice/card/relation/card_relation_type.h>
|
||||||
#include <libcockatrice/filters/filter_string.h>
|
#include <libcockatrice/filters/filter_string.h>
|
||||||
|
|
||||||
|
|
@ -126,6 +127,14 @@ public slots:
|
||||||
|
|
||||||
void actPlay(QList<CardItem *> selectedCards);
|
void actPlay(QList<CardItem *> selectedCards);
|
||||||
void actPlayFacedown(QList<CardItem *> selectedCards);
|
void actPlayFacedown(QList<CardItem *> selectedCards);
|
||||||
|
/** @brief Plays the selected card and increments the primary commander tax counter. */
|
||||||
|
void actPlayAndIncreaseTax();
|
||||||
|
/** @brief Plays the selected card and increments the partner commander tax counter. */
|
||||||
|
void actPlayAndIncreasePartnerTax();
|
||||||
|
/** @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 actHide(QList<CardItem *> selectedCards);
|
||||||
|
|
||||||
void actMoveTopCardToPlay();
|
void actMoveTopCardToPlay();
|
||||||
|
|
@ -219,6 +228,8 @@ public slots:
|
||||||
void cardMenuAction(QList<CardItem *> selectedCards, CardMenuActionType type);
|
void cardMenuAction(QList<CardItem *> selectedCards, CardMenuActionType type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void sendIncCounter(int counterId, int delta);
|
||||||
|
|
||||||
PlayerLogic *player;
|
PlayerLogic *player;
|
||||||
|
|
||||||
int defaultNumberTopCards = 1;
|
int defaultNumberTopCards = 1;
|
||||||
|
|
@ -244,6 +255,14 @@ private:
|
||||||
|
|
||||||
void playSelectedCards(QList<CardItem *> selectedCards, bool faceDown = false);
|
void playSelectedCards(QList<CardItem *> selectedCards, bool faceDown = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shared implementation for playing selected cards with an optional post-play callback.
|
||||||
|
* @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(bool faceDown,
|
||||||
|
const std::function<void(CardItem *, const QString &)> &postPlayCallback = nullptr);
|
||||||
|
|
||||||
void cmdSetTopCard(Command_MoveCard &cmd);
|
void cmdSetTopCard(Command_MoveCard &cmd);
|
||||||
void cmdSetBottomCard(Command_MoveCard &cmd);
|
void cmdSetBottomCard(Command_MoveCard &cmd);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "../../game_graphics/board/card_item.h"
|
#include "../../game_graphics/board/card_item.h"
|
||||||
#include "../../game_graphics/zones/view_zone.h"
|
#include "../../game_graphics/zones/view_zone.h"
|
||||||
#include "../../interface/widgets/tabs/tab_game.h"
|
#include "../../interface/widgets/tabs/tab_game.h"
|
||||||
|
#include "../board/abstract_counter.h"
|
||||||
#include "../board/arrow_data.h"
|
#include "../board/arrow_data.h"
|
||||||
#include "../board/card_list.h"
|
#include "../board/card_list.h"
|
||||||
#include "player_actions.h"
|
#include "player_actions.h"
|
||||||
|
|
@ -31,8 +32,10 @@
|
||||||
#include <libcockatrice/protocol/pb/event_set_card_attr.pb.h>
|
#include <libcockatrice/protocol/pb/event_set_card_attr.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
|
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_set_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/protocol/pb/event_shuffle.pb.h>
|
||||||
#include <libcockatrice/utility/color.h>
|
#include <libcockatrice/utility/color.h>
|
||||||
|
#include <libcockatrice/utility/counter_ids.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
||||||
PlayerEventHandler::PlayerEventHandler(PlayerLogic *_player) : QObject(_player), player(_player)
|
PlayerEventHandler::PlayerEventHandler(PlayerLogic *_player) : QObject(_player), player(_player)
|
||||||
|
|
@ -264,13 +267,31 @@ void PlayerEventHandler::eventCreateCounter(const Event_CreateCounter &event)
|
||||||
|
|
||||||
void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
|
void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
|
||||||
{
|
{
|
||||||
CounterState *ctr = player->getCounters().value(event.counter_id(), nullptr);
|
CounterState *state = player->getCounters().value(event.counter_id(), nullptr);
|
||||||
if (!ctr) {
|
if (!state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int oldValue = ctr->getValue();
|
int oldValue = state->getValue();
|
||||||
ctr->setValue(event.value());
|
state->setValue(event.value());
|
||||||
emit logSetCounter(player, ctr->getName(), event.value(), oldValue);
|
|
||||||
|
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());
|
||||||
|
|
||||||
|
AbstractCounter *widget = player->getGraphicsItem()->getCounterWidget(event.counter_id());
|
||||||
|
if (widget) {
|
||||||
|
widget->setActive(event.active());
|
||||||
|
emit player->rearrangeCounters();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerEventHandler::eventDelCounter(const Event_DelCounter &event)
|
void PlayerEventHandler::eventDelCounter(const Event_DelCounter &event)
|
||||||
|
|
@ -627,6 +648,9 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
|
||||||
case GameEvent::SET_COUNTER:
|
case GameEvent::SET_COUNTER:
|
||||||
eventSetCounter(event.GetExtension(Event_SetCounter::ext));
|
eventSetCounter(event.GetExtension(Event_SetCounter::ext));
|
||||||
break;
|
break;
|
||||||
|
case GameEvent::SET_COUNTER_ACTIVE:
|
||||||
|
eventSetCounterActive(event.GetExtension(Event_SetCounterActive::ext));
|
||||||
|
break;
|
||||||
case GameEvent::DEL_COUNTER:
|
case GameEvent::DEL_COUNTER:
|
||||||
eventDelCounter(event.GetExtension(Event_DelCounter::ext));
|
eventDelCounter(event.GetExtension(Event_DelCounter::ext));
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ class Event_RollDie;
|
||||||
class Event_SetCardAttr;
|
class Event_SetCardAttr;
|
||||||
class Event_SetCardCounter;
|
class Event_SetCardCounter;
|
||||||
class Event_SetCounter;
|
class Event_SetCounter;
|
||||||
|
class Event_SetCounterActive;
|
||||||
class Event_Shuffle;
|
class Event_Shuffle;
|
||||||
class Event_GameLogNotice;
|
class Event_GameLogNotice;
|
||||||
|
|
||||||
|
|
@ -104,6 +105,7 @@ public:
|
||||||
void eventSetCardCounter(const Event_SetCardCounter &event);
|
void eventSetCardCounter(const Event_SetCardCounter &event);
|
||||||
void eventCreateCounter(const Event_CreateCounter &event);
|
void eventCreateCounter(const Event_CreateCounter &event);
|
||||||
void eventSetCounter(const Event_SetCounter &event);
|
void eventSetCounter(const Event_SetCounter &event);
|
||||||
|
void eventSetCounterActive(const Event_SetCounterActive &event);
|
||||||
void eventDelCounter(const Event_DelCounter &event);
|
void eventDelCounter(const Event_DelCounter &event);
|
||||||
void eventDumpZone(const Event_DumpZone &event);
|
void eventDumpZone(const Event_DumpZone &event);
|
||||||
void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
|
void eventMoveCard(const Event_MoveCard &event, const GameEventContext &context);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@
|
||||||
#include "../../interface/theme_manager.h"
|
#include "../../interface/theme_manager.h"
|
||||||
#include "../../interface/widgets/tabs/tab_game.h"
|
#include "../../interface/widgets/tabs/tab_game.h"
|
||||||
#include "../board/card_list.h"
|
#include "../board/card_list.h"
|
||||||
|
#include "../board/commander_tax_counter.h"
|
||||||
|
#include "../board/counter_general.h"
|
||||||
|
#include "../game_scene.h"
|
||||||
|
#include "../zones/command_zone.h"
|
||||||
#include "player_actions.h"
|
#include "player_actions.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
@ -28,11 +32,12 @@
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_zone.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_zone.pb.h>
|
||||||
#include <libcockatrice/utility/color.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)
|
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)),
|
: QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)),
|
||||||
playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false),
|
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();
|
initializeZones();
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +53,7 @@ void PlayerLogic::initializeZones()
|
||||||
bool visibleHand = playerInfo->getLocalOrJudge() ||
|
bool visibleHand = playerInfo->getLocalOrJudge() ||
|
||||||
(game->getPlayerManager()->isSpectator() && game->getGameMetaInfo()->spectatorsOmniscient());
|
(game->getPlayerManager()->isSpectator() && game->getGameMetaInfo()->spectatorsOmniscient());
|
||||||
addZone(new HandZoneLogic(this, ZoneNames::HAND, false, false, visibleHand, this));
|
addZone(new HandZoneLogic(this, ZoneNames::HAND, false, false, visibleHand, this));
|
||||||
|
addZone(new CommandZoneLogic(this, ZoneNames::COMMAND, true, false, true, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerLogic::~PlayerLogic()
|
PlayerLogic::~PlayerLogic()
|
||||||
|
|
@ -104,7 +110,9 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
|
||||||
/* StackZone */
|
/* StackZone */
|
||||||
ZoneNames::STACK,
|
ZoneNames::STACK,
|
||||||
/* HandZone */
|
/* HandZone */
|
||||||
ZoneNames::HAND};
|
ZoneNames::HAND,
|
||||||
|
/* CommandZone */
|
||||||
|
ZoneNames::COMMAND};
|
||||||
clearCounters();
|
clearCounters();
|
||||||
emit arrowsClearedLocally();
|
emit arrowsClearedLocally();
|
||||||
|
|
||||||
|
|
@ -119,7 +127,19 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info)
|
||||||
|
|
||||||
emit clearCustomZonesMenu();
|
emit clearCustomZonesMenu();
|
||||||
|
|
||||||
|
// Check if server has command zone by scanning the zone list
|
||||||
const int zoneListSize = info.zone_list_size();
|
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) {
|
for (int i = 0; i < zoneListSize; ++i) {
|
||||||
const ServerInfo_Zone &zoneInfo = info.zone_list(i);
|
const ServerInfo_Zone &zoneInfo = info.zone_list(i);
|
||||||
|
|
||||||
|
|
@ -253,15 +273,17 @@ void PlayerLogic::setDeck(const DeckList &_deck)
|
||||||
CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter)
|
CounterState *PlayerLogic::addCounter(const ServerInfo_Counter &counter)
|
||||||
{
|
{
|
||||||
return addCounter(counter.id(), QString::fromStdString(counter.name()),
|
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)) {
|
if (counters.contains(id)) {
|
||||||
return nullptr;
|
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);
|
counters.insert(id, state);
|
||||||
emit counterAdded(state);
|
emit counterAdded(state);
|
||||||
return state;
|
return state;
|
||||||
|
|
@ -296,6 +318,11 @@ CounterState *PlayerLogic::getLifeCounter() const
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AbstractCounter *PlayerLogic::getCounterWidget(int counterId) const
|
||||||
|
{
|
||||||
|
return graphicsItem->getCounterWidget(counterId);
|
||||||
|
}
|
||||||
|
|
||||||
bool PlayerLogic::clearCardsToDelete()
|
bool PlayerLogic::clearCardsToDelete()
|
||||||
{
|
{
|
||||||
if (cardsToDelete.isEmpty()) {
|
if (cardsToDelete.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "../../interface/widgets/menus/tearoff_menu.h"
|
#include "../../interface/widgets/menus/tearoff_menu.h"
|
||||||
#include "../board/arrow_data.h"
|
#include "../board/arrow_data.h"
|
||||||
#include "../interface/deck_loader/loaded_deck.h"
|
#include "../interface/deck_loader/loaded_deck.h"
|
||||||
|
#include "../zones/command_zone_logic.h"
|
||||||
#include "../zones/hand_zone_logic.h"
|
#include "../zones/hand_zone_logic.h"
|
||||||
#include "../zones/pile_zone_logic.h"
|
#include "../zones/pile_zone_logic.h"
|
||||||
#include "../zones/stack_zone_logic.h"
|
#include "../zones/stack_zone_logic.h"
|
||||||
|
|
@ -57,6 +58,7 @@ class ServerInfo_Counter;
|
||||||
class ServerInfo_Player;
|
class ServerInfo_Player;
|
||||||
class ServerInfo_User;
|
class ServerInfo_User;
|
||||||
class TabGame;
|
class TabGame;
|
||||||
|
class AbstractCounter;
|
||||||
|
|
||||||
const int MAX_TOKENS_PER_DIALOG = 99;
|
const int MAX_TOKENS_PER_DIALOG = 99;
|
||||||
|
|
||||||
|
|
@ -87,6 +89,7 @@ signals:
|
||||||
void arrowDeleteRequested(int creatorId, int arrowId);
|
void arrowDeleteRequested(int creatorId, int arrowId);
|
||||||
void arrowDeleted(int creatorId, int arrowId);
|
void arrowDeleted(int creatorId, int arrowId);
|
||||||
void arrowsClearedLocally(); // fires on clear() and processPlayerInfo
|
void arrowsClearedLocally(); // fires on clear() and processPlayerInfo
|
||||||
|
void commandZoneSupportChanged(bool hasCommandZone);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setActive(bool _active);
|
void setActive(bool _active);
|
||||||
|
|
@ -191,8 +194,21 @@ public:
|
||||||
return qobject_cast<HandZoneLogic *>(zones.value(ZoneNames::HAND));
|
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(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 delCounter(int counterId);
|
||||||
void clearCounters();
|
void clearCounters();
|
||||||
|
|
||||||
|
|
@ -206,6 +222,9 @@ public:
|
||||||
*/
|
*/
|
||||||
CounterState *getLifeCounter() const;
|
CounterState *getLifeCounter() const;
|
||||||
|
|
||||||
|
/** @brief Returns the counter widget for the given ID, or nullptr if not found. */
|
||||||
|
AbstractCounter *getCounterWidget(int counterId) const;
|
||||||
|
|
||||||
void setConceded(bool _conceded);
|
void setConceded(bool _conceded);
|
||||||
bool getConceded() const
|
bool getConceded() const
|
||||||
{
|
{
|
||||||
|
|
@ -242,6 +261,7 @@ private:
|
||||||
QMap<int, CounterState *> counters;
|
QMap<int, CounterState *> counters;
|
||||||
|
|
||||||
bool dialogSemaphore;
|
bool dialogSemaphore;
|
||||||
|
bool serverHasCommandZone;
|
||||||
QList<CardItem *> cardsToDelete;
|
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));
|
return (theirOwn ? tr("their graveyard", "nominative") : tr("%1's graveyard", "nominative").arg(ownerName));
|
||||||
} else if (name == ZoneNames::EXILE) {
|
} else if (name == ZoneNames::EXILE) {
|
||||||
return (theirOwn ? tr("their exile", "nominative") : tr("%1's exile", "nominative").arg(ownerName));
|
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) {
|
} else if (name == ZoneNames::SIDEBOARD) {
|
||||||
switch (gc) {
|
switch (gc) {
|
||||||
case CaseLookAtZone:
|
case CaseLookAtZone:
|
||||||
|
|
|
||||||
176
cockatrice/src/game/zones/command_zone.cpp
Normal file
176
cockatrice/src/game/zones/command_zone.cpp
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
#include "command_zone.h"
|
||||||
|
|
||||||
|
#include "../../client/settings/cache_settings.h"
|
||||||
|
#include "../../game_graphics/zones/select_zone.h"
|
||||||
|
#include "../../interface/theme_manager.h"
|
||||||
|
#include "../board/card_drag_item.h"
|
||||||
|
#include "../board/card_item.h"
|
||||||
|
#include "../board/commander_tax_counter.h"
|
||||||
|
#include "../player/player_actions.h"
|
||||||
|
#include "../player/player_logic.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() && 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::rearrangeTaxCounters()
|
||||||
|
{
|
||||||
|
bool commandZoneVisible = isVisible();
|
||||||
|
int activeTaxCounterCount = 0;
|
||||||
|
|
||||||
|
auto *graphicsItem = getLogic()->getPlayer()->getGraphicsItem();
|
||||||
|
if (!graphicsItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AbstractCounter *ctr : graphicsItem->getTaxCounterWidgets()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
103
cockatrice/src/game/zones/command_zone.h
Normal file
103
cockatrice/src/game/zones/command_zone.h
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
/**
|
||||||
|
* @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_graphics/zones/select_zone.h"
|
||||||
|
#include "../card_dimensions.h"
|
||||||
|
#include "command_zone_logic.h"
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 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
|
||||||
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 "../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
|
||||||
|
|
@ -79,6 +79,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()
|
void AbstractCounter::retranslateUi()
|
||||||
{
|
{
|
||||||
if (aSet) {
|
if (aSet) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @file abstract_counter.h
|
* @file abstract_counter.h
|
||||||
* @ingroup GameGraphicsPlayers
|
* @ingroup GameGraphicsPlayers
|
||||||
|
* @brief Abstract base for player counters displayed on the game board.
|
||||||
*/
|
*/
|
||||||
//! \todo Document this file.
|
//! \todo Document this file.
|
||||||
|
|
||||||
|
|
@ -61,6 +62,13 @@ public:
|
||||||
~AbstractCounter() override;
|
~AbstractCounter() override;
|
||||||
|
|
||||||
void retranslateUi() 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 setShortcutsActive() override;
|
||||||
void setShortcutsInactive() override;
|
void setShortcutsInactive() override;
|
||||||
void delCounter();
|
void delCounter();
|
||||||
|
|
@ -93,6 +101,25 @@ public:
|
||||||
{
|
{
|
||||||
return shownInCounterArea;
|
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
|
class AbstractCounterDialog : public QInputDialog
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,6 @@ const QMap<QString, QString> TranslateCounterName::translated = {
|
||||||
{"r", QT_TRANSLATE_NOOP("TranslateCounterName", "Red")},
|
{"r", QT_TRANSLATE_NOOP("TranslateCounterName", "Red")},
|
||||||
{"g", QT_TRANSLATE_NOOP("TranslateCounterName", "Green")},
|
{"g", QT_TRANSLATE_NOOP("TranslateCounterName", "Green")},
|
||||||
{"x", QT_TRANSLATE_NOOP("TranslateCounterName", "Colorless")},
|
{"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_move_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
|
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
|
||||||
|
#include <libcockatrice/utility/counter_ids.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|
@ -80,6 +81,8 @@ MessageLogWidget::getFromStr(CardZoneLogic *zone, QString cardName, int position
|
||||||
fromStr = tr(" from sideboard");
|
fromStr = tr(" from sideboard");
|
||||||
} else if (zoneName == ZoneNames::STACK) {
|
} else if (zoneName == ZoneNames::STACK) {
|
||||||
fromStr = tr(" from the stack");
|
fromStr = tr(" from the stack");
|
||||||
|
} else if (zoneName == ZoneNames::COMMAND) {
|
||||||
|
fromStr = tr(" from the command zone");
|
||||||
} else {
|
} else {
|
||||||
fromStr = tr(" from custom zone '%1'").arg(zoneName);
|
fromStr = tr(" from custom zone '%1'").arg(zoneName);
|
||||||
}
|
}
|
||||||
|
|
@ -344,6 +347,8 @@ void MessageLogWidget::logMoveCard(PlayerLogic *player,
|
||||||
} else {
|
} else {
|
||||||
finalStr = tr("%1 plays %2%3.");
|
finalStr = tr("%1 plays %2%3.");
|
||||||
}
|
}
|
||||||
|
} else if (targetZoneName == ZoneNames::COMMAND) {
|
||||||
|
finalStr = tr("%1 moves %2%3 to the command zone.");
|
||||||
} else {
|
} else {
|
||||||
fourthArg = targetZoneName;
|
fourthArg = targetZoneName;
|
||||||
if (card->getFaceDown()) {
|
if (card->getFaceDown()) {
|
||||||
|
|
@ -671,6 +676,20 @@ void MessageLogWidget::logSetCounter(PlayerLogic *player, QString counterName, i
|
||||||
soundEngine->playSound("life_change");
|
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);
|
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
|
||||||
appendHtmlServerMessage(tr("%1 sets counter %2 to %3 (%4%5).")
|
appendHtmlServerMessage(tr("%1 sets counter %2 to %3 (%4%5).")
|
||||||
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
|
.arg(sanitizeHtml(player->getPlayerInfo()->getName()))
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ enum CardMenuActionType
|
||||||
cmMoveToHand,
|
cmMoveToHand,
|
||||||
cmMoveToGraveyard,
|
cmMoveToGraveyard,
|
||||||
cmMoveToExile,
|
cmMoveToExile,
|
||||||
cmMoveToTable
|
cmMoveToTable,
|
||||||
|
cmMoveToCommandZone
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H
|
#endif // COCKATRICE_CARD_MENU_ACTION_TYPE_H
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "../../../client/settings/card_counter_settings.h"
|
#include "../../../client/settings/card_counter_settings.h"
|
||||||
#include "../../../interface/widgets/tabs/tab_game.h"
|
#include "../../../interface/widgets/tabs/tab_game.h"
|
||||||
|
#include "../../board/abstract_counter.h"
|
||||||
#include "../../board/card_item.h"
|
#include "../../board/card_item.h"
|
||||||
#include "../../game/player/player_actions.h"
|
#include "../../game/player/player_actions.h"
|
||||||
#include "../../game/player/player_logic.h"
|
#include "../../game/player/player_logic.h"
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <libcockatrice/card/database/card_database_manager.h>
|
#include <libcockatrice/card/database/card_database_manager.h>
|
||||||
#include <libcockatrice/card/relation/card_relation.h>
|
#include <libcockatrice/card/relation/card_relation.h>
|
||||||
|
#include <libcockatrice/utility/counter_ids.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,6 +94,12 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho
|
||||||
aSelectRow = new QAction(this);
|
aSelectRow = new QAction(this);
|
||||||
aSelectColumn = 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(aAttach, &QAction::triggered, actions, &PlayerActions::actAttach);
|
||||||
connect(aDrawArrow, &QAction::triggered, actions, &PlayerActions::actDrawArrow);
|
connect(aDrawArrow, &QAction::triggered, actions, &PlayerActions::actDrawArrow);
|
||||||
connect(aSelectAll, &QAction::triggered, actions, &PlayerActions::actSelectAll);
|
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 ||
|
} else if (card->getZone()->getName() == ZoneNames::EXILE ||
|
||||||
card->getZone()->getName() == ZoneNames::GRAVE) {
|
card->getZone()->getName() == ZoneNames::GRAVE) {
|
||||||
createGraveyardOrExileMenu(writeableCard);
|
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 {
|
} else {
|
||||||
createHandOrCustomZoneMenu(writeableCard);
|
createHandOrCustomZoneMenu(writeableCard);
|
||||||
}
|
}
|
||||||
|
|
@ -487,6 +522,8 @@ void CardMenu::retranslateUi()
|
||||||
aPlay->setText(tr("&Play"));
|
aPlay->setText(tr("&Play"));
|
||||||
aHide->setText(tr("&Hide"));
|
aHide->setText(tr("&Hide"));
|
||||||
aPlayFacedown->setText(tr("Play &Face Down"));
|
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"));
|
aRevealToAll->setText(tr("&All players"));
|
||||||
//: Turn sideways or back again
|
//: Turn sideways or back again
|
||||||
aTap->setText(tr("&Tap / Untap"));
|
aTap->setText(tr("&Tap / Untap"));
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ public:
|
||||||
QMenu *mCardCounters;
|
QMenu *mCardCounters;
|
||||||
|
|
||||||
QAction *aPlay, *aPlayFacedown;
|
QAction *aPlay, *aPlayFacedown;
|
||||||
|
QAction *
|
||||||
|
aPlayAndIncreaseTax; ///< Plays card and increments the primary commander tax counter (CounterIds::CommanderTax)
|
||||||
|
QAction *aPlayAndIncreasePartnerTax;
|
||||||
QAction *aRevealToAll;
|
QAction *aRevealToAll;
|
||||||
QAction *aHide;
|
QAction *aHide;
|
||||||
QAction *aClone;
|
QAction *aClone;
|
||||||
|
|
|
||||||
184
cockatrice/src/game_graphics/player/menu/command_zone_menu.cpp
Normal file
184
cockatrice/src/game_graphics/player/menu/command_zone_menu.cpp
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
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 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
|
||||||
|
|
@ -20,6 +20,8 @@ MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
|
||||||
aMoveToGraveyard->setData(cmMoveToGraveyard);
|
aMoveToGraveyard->setData(cmMoveToGraveyard);
|
||||||
aMoveToExile = new QAction(this);
|
aMoveToExile = new QAction(this);
|
||||||
aMoveToExile->setData(cmMoveToExile);
|
aMoveToExile->setData(cmMoveToExile);
|
||||||
|
aMoveToCommandZone = new QAction(this);
|
||||||
|
aMoveToCommandZone->setData(cmMoveToCommandZone);
|
||||||
|
|
||||||
auto *actions = player->getLogic()->getPlayerActions();
|
auto *actions = player->getLogic()->getPlayerActions();
|
||||||
|
|
||||||
|
|
@ -49,6 +51,8 @@ MoveMenu::MoveMenu(PlayerGraphicsItem *player) : QMenu(tr("Move to"))
|
||||||
addAction(aMoveToGraveyard);
|
addAction(aMoveToGraveyard);
|
||||||
addSeparator();
|
addSeparator();
|
||||||
addAction(aMoveToExile);
|
addAction(aMoveToExile);
|
||||||
|
addSeparator();
|
||||||
|
addAction(aMoveToCommandZone);
|
||||||
|
|
||||||
setShortcutsActive();
|
setShortcutsActive();
|
||||||
|
|
||||||
|
|
@ -65,6 +69,7 @@ void MoveMenu::setShortcutsActive()
|
||||||
aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand"));
|
aMoveToHand->setShortcuts(shortcuts.getShortcut("Player/aMoveToHand"));
|
||||||
aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard"));
|
aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard"));
|
||||||
aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile"));
|
aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile"));
|
||||||
|
aMoveToCommandZone->setShortcuts(shortcuts.getShortcut("Player/aMoveToCommandZone"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoveMenu::retranslateUi()
|
void MoveMenu::retranslateUi()
|
||||||
|
|
@ -76,4 +81,5 @@ void MoveMenu::retranslateUi()
|
||||||
aMoveToHand->setText(tr("&Hand"));
|
aMoveToHand->setText(tr("&Hand"));
|
||||||
aMoveToGraveyard->setText(tr("&Graveyard"));
|
aMoveToGraveyard->setText(tr("&Graveyard"));
|
||||||
aMoveToExile->setText(tr("&Exile"));
|
aMoveToExile->setText(tr("&Exile"));
|
||||||
|
aMoveToCommandZone->setText(tr("&Command Zone"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ public:
|
||||||
QAction *aMoveToTable = nullptr;
|
QAction *aMoveToTable = nullptr;
|
||||||
QAction *aMoveToGraveyard = nullptr;
|
QAction *aMoveToGraveyard = nullptr;
|
||||||
QAction *aMoveToExile = nullptr;
|
QAction *aMoveToExile = nullptr;
|
||||||
|
QAction *aMoveToCommandZone = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_MOVE_MENU_H
|
#endif // COCKATRICE_MOVE_MENU_H
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "../../../game_graphics/zones/table_zone.h"
|
#include "../../../game_graphics/zones/table_zone.h"
|
||||||
#include "../../../interface/widgets/tabs/tab_game.h"
|
#include "../../../interface/widgets/tabs/tab_game.h"
|
||||||
#include "../../board/card_item.h"
|
#include "../../board/card_item.h"
|
||||||
|
#include "../../zones/command_zone.h"
|
||||||
#include "../player_graphics_item.h"
|
#include "../player_graphics_item.h"
|
||||||
#include "card_menu.h"
|
#include "card_menu.h"
|
||||||
#include "hand_menu.h"
|
#include "hand_menu.h"
|
||||||
|
|
@ -31,6 +32,16 @@ PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_
|
||||||
|
|
||||||
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
|
if (player->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
|
||||||
sideboardMenu = addManagedMenu<SideboardMenu>(player, playerMenu);
|
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);
|
customZonesMenu = addManagedMenu<CustomZoneMenu>(player);
|
||||||
playerMenu->addSeparator();
|
playerMenu->addSeparator();
|
||||||
|
|
||||||
|
|
@ -39,6 +50,7 @@ PlayerMenu::PlayerMenu(PlayerGraphicsItem *_player) : QObject(_player), player(_
|
||||||
utilityMenu = createManagedComponent<UtilityMenu>(player, playerMenu);
|
utilityMenu = createManagedComponent<UtilityMenu>(player, playerMenu);
|
||||||
} else {
|
} else {
|
||||||
sideboardMenu = nullptr;
|
sideboardMenu = nullptr;
|
||||||
|
commandZoneMenu = nullptr;
|
||||||
customZonesMenu = nullptr;
|
customZonesMenu = nullptr;
|
||||||
countersMenu = nullptr;
|
countersMenu = nullptr;
|
||||||
utilityMenu = nullptr;
|
utilityMenu = nullptr;
|
||||||
|
|
@ -66,6 +78,10 @@ void PlayerMenu::setMenusForGraphicItems()
|
||||||
player->getHandZoneGraphicsItem()->setMenu(handMenu);
|
player->getHandZoneGraphicsItem()->setMenu(handMenu);
|
||||||
player->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard);
|
player->getDeckZoneGraphicsItem()->setMenu(libraryMenu, libraryMenu->aDrawCard);
|
||||||
player->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu);
|
player->getSideboardZoneGraphicsItem()->setMenu(sideboardMenu);
|
||||||
|
|
||||||
|
if (auto *commandZone = player->getCommandZoneGraphicsItem()) {
|
||||||
|
commandZone->setMenu(commandZoneMenu, commandZoneMenu->aViewZone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
#define COCKATRICE_PLAYER_MENU_H
|
#define COCKATRICE_PLAYER_MENU_H
|
||||||
|
|
||||||
#include "../../../interface/widgets/menus/tearoff_menu.h"
|
#include "../../../interface/widgets/menus/tearoff_menu.h"
|
||||||
|
#include "../player_logic.h"
|
||||||
|
#include "command_zone_menu.h"
|
||||||
#include "custom_zone_menu.h"
|
#include "custom_zone_menu.h"
|
||||||
#include "grave_menu.h"
|
#include "grave_menu.h"
|
||||||
#include "hand_menu.h"
|
#include "hand_menu.h"
|
||||||
|
|
@ -88,6 +90,7 @@ private:
|
||||||
RfgMenu *rfgMenu;
|
RfgMenu *rfgMenu;
|
||||||
UtilityMenu *utilityMenu;
|
UtilityMenu *utilityMenu;
|
||||||
SayMenu *sayMenu;
|
SayMenu *sayMenu;
|
||||||
|
CommandZoneMenu *commandZoneMenu;
|
||||||
CustomZoneMenu *customZonesMenu;
|
CustomZoneMenu *customZonesMenu;
|
||||||
|
|
||||||
/** @brief Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via
|
/** @brief Drives AbstractPlayerComponent lifecycle delegation. Counters are iterated separately via
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "../../game/player/player_actions.h"
|
#include "../../game/player/player_actions.h"
|
||||||
#include "../../interface/widgets/tabs/tab_game.h"
|
#include "../../interface/widgets/tabs/tab_game.h"
|
||||||
#include "../board/abstract_card_item.h"
|
#include "../board/abstract_card_item.h"
|
||||||
|
#include "../board/commander_tax_counter.h"
|
||||||
#include "../board/counter_general.h"
|
#include "../board/counter_general.h"
|
||||||
#include "../hand_counter.h"
|
#include "../hand_counter.h"
|
||||||
#include "../zones/hand_zone.h"
|
#include "../zones/hand_zone.h"
|
||||||
|
|
@ -13,6 +14,10 @@
|
||||||
#include "player_dialogs.h"
|
#include "player_dialogs.h"
|
||||||
|
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
|
#include "../z_values.h"
|
||||||
|
#include "../zones/command_zone.h"
|
||||||
|
|
||||||
|
#include <libcockatrice/utility/counter_ids.h>
|
||||||
|
|
||||||
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
||||||
{
|
{
|
||||||
|
|
@ -118,6 +123,12 @@ void PlayerGraphicsItem::initializeZones()
|
||||||
new HandZone(player->getHandZone(), static_cast<int>(tableZoneGraphicsItem->boundingRect().height()), this);
|
new HandZone(player->getHandZone(), static_cast<int>(tableZoneGraphicsItem->boundingRect().height()), this);
|
||||||
connect(player->getPlayerActions(), &PlayerActions::requestSortHand, handZoneGraphicsItem, &HandZone::sortHand);
|
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,
|
connect(handZoneGraphicsItem->getLogic(), &HandZoneLogic::cardCountChanged, handCounter,
|
||||||
&HandCounter::updateNumber);
|
&HandCounter::updateNumber);
|
||||||
connect(handCounter, &HandCounter::showContextMenu, handZoneGraphicsItem, &HandZone::showContextMenu);
|
connect(handCounter, &HandCounter::showContextMenu, handZoneGraphicsItem, &HandZone::showContextMenu);
|
||||||
|
|
@ -171,6 +182,13 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
|
||||||
AbstractCounter *widget;
|
AbstractCounter *widget;
|
||||||
if (state->getName() == "life") {
|
if (state->getName() == "life") {
|
||||||
widget = playerTarget->addCounter(state);
|
widget = playerTarget->addCounter(state);
|
||||||
|
} else if (CounterNames::isTaxCounter(state->getName())) {
|
||||||
|
if (!commandZoneGraphicsItem) {
|
||||||
|
qWarning() << "Cannot create tax counter" << state->getName() << "- command zone not available";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widget = new CommanderTaxCounter(state, player, commandZoneGraphicsItem);
|
||||||
|
widget->setActive(state->isActive());
|
||||||
} else {
|
} else {
|
||||||
widget = new GeneralCounter(state, player, true, this);
|
widget = new GeneralCounter(state, player, true, this);
|
||||||
}
|
}
|
||||||
|
|
@ -202,9 +220,16 @@ void PlayerGraphicsItem::onCounterRemoved(int counterId)
|
||||||
|
|
||||||
void PlayerGraphicsItem::rearrangeCounters()
|
void PlayerGraphicsItem::rearrangeCounters()
|
||||||
{
|
{
|
||||||
|
if (commandZoneGraphicsItem) {
|
||||||
|
commandZoneGraphicsItem->rearrangeTaxCounters();
|
||||||
|
}
|
||||||
|
|
||||||
qreal ySize = boundingRect().y() + 80;
|
qreal ySize = boundingRect().y() + 80;
|
||||||
constexpr qreal padding = 5;
|
constexpr qreal padding = 5;
|
||||||
for (auto *ctr : counterWidgets.values()) {
|
for (auto *ctr : counterWidgets.values()) {
|
||||||
|
if (CounterNames::isTaxCounter(ctr->getName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!ctr->getShownInCounterArea()) {
|
if (!ctr->getShownInCounterArea()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -214,9 +239,33 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerGraphicsItem::rearrangeZones()
|
void PlayerGraphicsItem::rearrangeZones()
|
||||||
{
|
{
|
||||||
auto base = QPointF(CardDimensions::HEIGHT_F + counterAreaWidth + 15, 0);
|
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 (SettingsCache::instance().getHorizontalHand()) {
|
||||||
if (mirrored) {
|
if (mirrored) {
|
||||||
if (player->getHandZone()->contentsKnown()) {
|
if (player->getHandZone()->contentsKnown()) {
|
||||||
|
|
@ -227,12 +276,12 @@ void PlayerGraphicsItem::rearrangeZones()
|
||||||
handVisible = false;
|
handVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stackZoneGraphicsItem->setPos(base);
|
positionCommandAndStackZones(base);
|
||||||
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||||
|
|
||||||
tableZoneGraphicsItem->setPos(base);
|
tableZoneGraphicsItem->setPos(base);
|
||||||
} else {
|
} else {
|
||||||
stackZoneGraphicsItem->setPos(base);
|
positionCommandAndStackZones(base);
|
||||||
|
|
||||||
tableZoneGraphicsItem->setPos(base.x() + stackZoneGraphicsItem->boundingRect().width(), 0);
|
tableZoneGraphicsItem->setPos(base.x() + stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||||
base += QPointF(0, tableZoneGraphicsItem->boundingRect().height());
|
base += QPointF(0, tableZoneGraphicsItem->boundingRect().height());
|
||||||
|
|
@ -252,7 +301,7 @@ void PlayerGraphicsItem::rearrangeZones()
|
||||||
handZoneGraphicsItem->setPos(base);
|
handZoneGraphicsItem->setPos(base);
|
||||||
base += QPointF(handZoneGraphicsItem->boundingRect().width(), 0);
|
base += QPointF(handZoneGraphicsItem->boundingRect().width(), 0);
|
||||||
|
|
||||||
stackZoneGraphicsItem->setPos(base);
|
positionCommandAndStackZones(base);
|
||||||
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
base += QPointF(stackZoneGraphicsItem->boundingRect().width(), 0);
|
||||||
|
|
||||||
tableZoneGraphicsItem->setPos(base);
|
tableZoneGraphicsItem->setPos(base);
|
||||||
|
|
@ -281,3 +330,28 @@ void PlayerGraphicsItem::updateBoundingRect()
|
||||||
|
|
||||||
emit sizeChanged();
|
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>
|
#include <QGraphicsObject>
|
||||||
|
|
||||||
|
class CommandZone;
|
||||||
class HandZone;
|
class HandZone;
|
||||||
class PileZone;
|
class PileZone;
|
||||||
class PlayerDialogs;
|
class PlayerDialogs;
|
||||||
|
|
@ -107,6 +108,18 @@ public:
|
||||||
{
|
{
|
||||||
return handZoneGraphicsItem;
|
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;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onPlayerActiveChanged(bool _active);
|
void onPlayerActiveChanged(bool _active);
|
||||||
|
|
@ -114,6 +127,8 @@ public slots:
|
||||||
void onCounterRemoved(int counterId);
|
void onCounterRemoved(int counterId);
|
||||||
void rearrangeCounters();
|
void rearrangeCounters();
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
|
/** @brief Shows or hides the command zone and rearranges dependent zones. */
|
||||||
|
void setCommandZoneVisible(bool visible);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sizeChanged();
|
void sizeChanged();
|
||||||
|
|
@ -135,10 +150,15 @@ private:
|
||||||
TableZone *tableZoneGraphicsItem;
|
TableZone *tableZoneGraphicsItem;
|
||||||
StackZone *stackZoneGraphicsItem;
|
StackZone *stackZoneGraphicsItem;
|
||||||
HandZone *handZoneGraphicsItem;
|
HandZone *handZoneGraphicsItem;
|
||||||
|
CommandZone *commandZoneGraphicsItem;
|
||||||
QRectF bRect;
|
QRectF bRect;
|
||||||
bool mirrored;
|
bool mirrored;
|
||||||
bool handVisible = false;
|
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:
|
private slots:
|
||||||
void updateBoundingRect();
|
void updateBoundingRect();
|
||||||
void rearrangeZones();
|
void rearrangeZones();
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,16 @@
|
||||||
namespace ZValues
|
namespace ZValues
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @brief Command zone sits at standard zone level */
|
||||||
|
constexpr qreal COMMAND_ZONE = 1.0;
|
||||||
|
|
||||||
// Expose base for callers that need it
|
// Expose base for callers that need it
|
||||||
constexpr qreal OVERLAY_BASE = ZValueLayerManager::OVERLAY_BASE;
|
constexpr qreal OVERLAY_BASE = ZValueLayerManager::OVERLAY_BASE;
|
||||||
|
|
||||||
// Overlay layer Z-values for items that should appear above normal cards
|
// Overlay layer Z-values for items that should appear above normal cards
|
||||||
constexpr qreal HOVERED_CARD = ZValueLayerManager::overlayZValue(1.0);
|
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 ARROWS = ZValueLayerManager::overlayZValue(3.0);
|
||||||
constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0);
|
constexpr qreal ZONE_VIEW_WIDGET = ZValueLayerManager::overlayZValue(4.0);
|
||||||
constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0);
|
constexpr qreal DRAG_ITEM = ZValueLayerManager::overlayZValue(5.0);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ QRectF StackZone::boundingRect() const
|
||||||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||||
{
|
{
|
||||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
||||||
|
|
||||||
|
QPointF scenePos = mapToScene(QPointF(0, 0));
|
||||||
|
painter->setBrushOrigin(-scenePos);
|
||||||
|
|
||||||
painter->fillRect(boundingRect(), brush);
|
painter->fillRect(boundingRect(), brush);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,12 @@
|
||||||
#define PLAYERZONE_BG_NAME "playerzone"
|
#define PLAYERZONE_BG_NAME "playerzone"
|
||||||
#define STACKZONE_BG_NAME "stackzone"
|
#define STACKZONE_BG_NAME "stackzone"
|
||||||
#define TABLEZONE_BG_NAME "tablezone"
|
#define TABLEZONE_BG_NAME "tablezone"
|
||||||
|
#define COMMANDZONE_BG_NAME "commandzone"
|
||||||
static const QColor HANDZONE_BG_DEFAULT = QColor(80, 100, 50);
|
static const QColor HANDZONE_BG_DEFAULT = QColor(80, 100, 50);
|
||||||
static const QColor TABLEZONE_BG_DEFAULT = QColor(70, 50, 100);
|
static const QColor TABLEZONE_BG_DEFAULT = QColor(70, 50, 100);
|
||||||
static const QColor PLAYERZONE_BG_DEFAULT = QColor(200, 200, 200);
|
static const QColor PLAYERZONE_BG_DEFAULT = QColor(200, 200, 200);
|
||||||
static const QColor STACKZONE_BG_DEFAULT = QColor(113, 43, 43);
|
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"};
|
static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"};
|
||||||
|
|
||||||
struct PaletteColorInfo
|
struct PaletteColorInfo
|
||||||
|
|
@ -271,6 +273,9 @@ void ThemeManager::applyStyleAndPalette(const QString &themeName,
|
||||||
const PaletteConfig &palCfg,
|
const PaletteConfig &palCfg,
|
||||||
const QString &activeScheme)
|
const QString &activeScheme)
|
||||||
{
|
{
|
||||||
|
#if (QT_VERSION < QT_VERSION_CHECK(6, 5, 0))
|
||||||
|
Q_UNUSED(activeScheme)
|
||||||
|
#endif
|
||||||
QString styleName = themeCfg.styleName;
|
QString styleName = themeCfg.styleName;
|
||||||
if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) {
|
if (styleName.isEmpty() || styleName.compare("Default", Qt::CaseInsensitive) == 0) {
|
||||||
if (themeName == FUSION_THEME_NAME) {
|
if (themeName == FUSION_THEME_NAME) {
|
||||||
|
|
@ -370,6 +375,8 @@ void ThemeManager::themeChangedSlot()
|
||||||
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||||
|
|
||||||
brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_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) {
|
for (auto &brushCache : brushesCache) {
|
||||||
brushCache.clear();
|
brushCache.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -394,6 +401,9 @@ static QString roleBgName(ThemeManager::Role role)
|
||||||
case ThemeManager::Table:
|
case ThemeManager::Table:
|
||||||
return TABLEZONE_BG_NAME;
|
return TABLEZONE_BG_NAME;
|
||||||
|
|
||||||
|
case ThemeManager::Command:
|
||||||
|
return COMMANDZONE_BG_NAME;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Q_ASSERT(false);
|
Q_ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ public:
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
Player,
|
Player,
|
||||||
MaxRole = Player,
|
Command,
|
||||||
|
MaxRole = Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ void DlgCreateGame::sharedCtor()
|
||||||
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
|
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
|
||||||
|
|
||||||
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
|
shareDecklistsOnLoadCheckBox = new QCheckBox(tr("Open decklists in lobby"));
|
||||||
|
enableCommandZoneCheckBox = new QCheckBox(tr("Enable command zone"));
|
||||||
|
|
||||||
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
|
createGameAsJudgeCheckBox = new QCheckBox(tr("Create game as judge"));
|
||||||
|
|
||||||
|
|
@ -109,6 +110,7 @@ void DlgCreateGame::sharedCtor()
|
||||||
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
|
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
|
||||||
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
|
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
|
||||||
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
|
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadCheckBox, 1, 0);
|
||||||
|
gameSetupOptionsLayout->addWidget(enableCommandZoneCheckBox, 1, 1);
|
||||||
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
|
if (room && room->getUserInfo()->user_level() & ServerInfo_User::IsJudge) {
|
||||||
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
|
gameSetupOptionsLayout->addWidget(createGameAsJudgeCheckBox, 2, 0);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -171,6 +173,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameType
|
||||||
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator());
|
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator());
|
||||||
startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal());
|
startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal());
|
||||||
shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().getShareDecklistsOnLoad());
|
shareDecklistsOnLoadCheckBox->setChecked(SettingsCache::instance().getShareDecklistsOnLoad());
|
||||||
|
enableCommandZoneCheckBox->setChecked(SettingsCache::instance().getEnableCommandZone());
|
||||||
|
|
||||||
if (!rememberGameSettings->isChecked()) {
|
if (!rememberGameSettings->isChecked()) {
|
||||||
actReset();
|
actReset();
|
||||||
|
|
@ -204,6 +207,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
|
||||||
createGameAsSpectatorCheckBox->setEnabled(false);
|
createGameAsSpectatorCheckBox->setEnabled(false);
|
||||||
startingLifeTotalEdit->setEnabled(false);
|
startingLifeTotalEdit->setEnabled(false);
|
||||||
shareDecklistsOnLoadCheckBox->setEnabled(false);
|
shareDecklistsOnLoadCheckBox->setEnabled(false);
|
||||||
|
enableCommandZoneCheckBox->setEnabled(false);
|
||||||
|
|
||||||
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
|
descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
|
||||||
maxPlayersEdit->setValue(gameInfo.max_players());
|
maxPlayersEdit->setValue(gameInfo.max_players());
|
||||||
|
|
@ -250,6 +254,7 @@ void DlgCreateGame::actReset()
|
||||||
|
|
||||||
startingLifeTotalEdit->setValue(20);
|
startingLifeTotalEdit->setValue(20);
|
||||||
shareDecklistsOnLoadCheckBox->setChecked(false);
|
shareDecklistsOnLoadCheckBox->setChecked(false);
|
||||||
|
enableCommandZoneCheckBox->setChecked(false);
|
||||||
createGameAsJudgeCheckBox->setChecked(false);
|
createGameAsJudgeCheckBox->setChecked(false);
|
||||||
|
|
||||||
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
||||||
|
|
@ -280,6 +285,7 @@ void DlgCreateGame::actOK()
|
||||||
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
|
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
|
||||||
cmd.set_starting_life_total(startingLifeTotalEdit->value());
|
cmd.set_starting_life_total(startingLifeTotalEdit->value());
|
||||||
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
|
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
|
||||||
|
cmd.set_enable_command_zone(enableCommandZoneCheckBox->isChecked());
|
||||||
|
|
||||||
auto _gameTypes = QString();
|
auto _gameTypes = QString();
|
||||||
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
|
||||||
|
|
@ -304,6 +310,7 @@ void DlgCreateGame::actOK()
|
||||||
SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
|
SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
|
||||||
SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
|
SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
|
||||||
SettingsCache::instance().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked());
|
SettingsCache::instance().setShareDecklistsOnLoad(shareDecklistsOnLoadCheckBox->isChecked());
|
||||||
|
SettingsCache::instance().setEnableCommandZone(enableCommandZoneCheckBox->isChecked());
|
||||||
SettingsCache::instance().setGameTypes(_gameTypes);
|
SettingsCache::instance().setGameTypes(_gameTypes);
|
||||||
}
|
}
|
||||||
PendingCommand *pend = room->prepareRoomCommand(cmd);
|
PendingCommand *pend = room->prepareRoomCommand(cmd);
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ private:
|
||||||
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
|
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
|
||||||
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
|
*spectatorsSeeEverythingCheckBox, *createGameAsJudgeCheckBox, *createGameAsSpectatorCheckBox;
|
||||||
QCheckBox *shareDecklistsOnLoadCheckBox;
|
QCheckBox *shareDecklistsOnLoadCheckBox;
|
||||||
|
QCheckBox *enableCommandZoneCheckBox;
|
||||||
QDialogButtonBox *buttonBox;
|
QDialogButtonBox *buttonBox;
|
||||||
QPushButton *clearButton;
|
QPushButton *clearButton;
|
||||||
QCheckBox *rememberGameSettings;
|
QCheckBox *rememberGameSettings;
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,13 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent)
|
||||||
startingLifeTotalEdit->setValue(20);
|
startingLifeTotalEdit->setValue(20);
|
||||||
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
|
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
|
||||||
|
|
||||||
|
enableCommandZoneCheckBox = new QCheckBox(tr("Enable command zone"), this);
|
||||||
|
|
||||||
auto *gameSetupGrid = new QGridLayout;
|
auto *gameSetupGrid = new QGridLayout;
|
||||||
gameSetupGrid->setContentsMargins(5, 5, 5, 5);
|
gameSetupGrid->setContentsMargins(5, 5, 5, 5);
|
||||||
gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0);
|
gameSetupGrid->addWidget(startingLifeTotalLabel, 0, 0);
|
||||||
gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1);
|
gameSetupGrid->addWidget(startingLifeTotalEdit, 0, 1);
|
||||||
|
gameSetupGrid->addWidget(enableCommandZoneCheckBox, 1, 0, 1, 2);
|
||||||
gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this);
|
gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"), this);
|
||||||
gameSetupOptionsGroupBox->setLayout(gameSetupGrid);
|
gameSetupOptionsGroupBox->setLayout(gameSetupGrid);
|
||||||
|
|
||||||
|
|
@ -57,6 +60,7 @@ DlgLocalGameOptions::DlgLocalGameOptions(QWidget *parent) : QDialog(parent)
|
||||||
if (rememberSettingsCheckBox->isChecked()) {
|
if (rememberSettingsCheckBox->isChecked()) {
|
||||||
numberPlayersEdit->setValue(SettingsCache::instance().getLocalGameMaxPlayers());
|
numberPlayersEdit->setValue(SettingsCache::instance().getLocalGameMaxPlayers());
|
||||||
startingLifeTotalEdit->setValue(SettingsCache::instance().getLocalGameStartingLifeTotal());
|
startingLifeTotalEdit->setValue(SettingsCache::instance().getLocalGameStartingLifeTotal());
|
||||||
|
enableCommandZoneCheckBox->setChecked(SettingsCache::instance().getLocalGameEnableCommandZone());
|
||||||
}
|
}
|
||||||
|
|
||||||
setWindowTitle(tr("Local game options"));
|
setWindowTitle(tr("Local game options"));
|
||||||
|
|
@ -71,6 +75,7 @@ void DlgLocalGameOptions::actOK()
|
||||||
if (rememberSettingsCheckBox->isChecked()) {
|
if (rememberSettingsCheckBox->isChecked()) {
|
||||||
SettingsCache::instance().setLocalGameMaxPlayers(numberPlayersEdit->value());
|
SettingsCache::instance().setLocalGameMaxPlayers(numberPlayersEdit->value());
|
||||||
SettingsCache::instance().setLocalGameStartingLifeTotal(startingLifeTotalEdit->value());
|
SettingsCache::instance().setLocalGameStartingLifeTotal(startingLifeTotalEdit->value());
|
||||||
|
SettingsCache::instance().setLocalGameEnableCommandZone(enableCommandZoneCheckBox->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
accept();
|
accept();
|
||||||
|
|
@ -81,5 +86,6 @@ LocalGameOptions DlgLocalGameOptions::getOptions() const
|
||||||
return LocalGameOptions{
|
return LocalGameOptions{
|
||||||
.numberPlayers = numberPlayersEdit->value(),
|
.numberPlayers = numberPlayersEdit->value(),
|
||||||
.startingLifeTotal = startingLifeTotalEdit->value(),
|
.startingLifeTotal = startingLifeTotalEdit->value(),
|
||||||
|
.enableCommandZone = enableCommandZoneCheckBox->isChecked(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ struct LocalGameOptions
|
||||||
{
|
{
|
||||||
int numberPlayers = 1;
|
int numberPlayers = 1;
|
||||||
int startingLifeTotal = 20;
|
int startingLifeTotal = 20;
|
||||||
|
bool enableCommandZone = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QCheckBox;
|
class QCheckBox;
|
||||||
|
|
@ -45,6 +46,7 @@ private:
|
||||||
QLabel *startingLifeTotalLabel;
|
QLabel *startingLifeTotalLabel;
|
||||||
QSpinBox *startingLifeTotalEdit;
|
QSpinBox *startingLifeTotalEdit;
|
||||||
|
|
||||||
|
QCheckBox *enableCommandZoneCheckBox;
|
||||||
QCheckBox *rememberSettingsCheckBox;
|
QCheckBox *rememberSettingsCheckBox;
|
||||||
|
|
||||||
QDialogButtonBox *buttonBox;
|
QDialogButtonBox *buttonBox;
|
||||||
|
|
|
||||||
|
|
@ -690,7 +690,7 @@ void TabGame::addLocalPlayer(PlayerLogic *newPlayer, int playerId)
|
||||||
auto *deckView = new TabbedDeckViewContainer(playerId, this);
|
auto *deckView = new TabbedDeckViewContainer(playerId, this);
|
||||||
connect(deckView->playerDeckView, &DeckViewContainer::newCardAdded, this, &TabGame::newCardAdded);
|
connect(deckView->playerDeckView, &DeckViewContainer::newCardAdded, this, &TabGame::newCardAdded);
|
||||||
deckViewContainers.insert(playerId, deckView);
|
deckViewContainers.insert(playerId, deckView);
|
||||||
deckViewContainerLayout->addWidget(deckView);
|
deckViewContainerLayout->insertWidget(0, deckView, 1);
|
||||||
|
|
||||||
// auto load deck for player if that debug setting is enabled
|
// auto load deck for player if that debug setting is enabled
|
||||||
QString deckPath = SettingsCache::instance().debug().getDeckPathForPlayer(newPlayer->getPlayerInfo()->getName());
|
QString deckPath = SettingsCache::instance().debug().getDeckPathForPlayer(newPlayer->getPlayerInfo()->getName());
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ void MainWindow::startLocalGame(const LocalGameOptions &options)
|
||||||
Command_CreateGame createCommand;
|
Command_CreateGame createCommand;
|
||||||
createCommand.set_max_players(static_cast<google::protobuf::uint32>(options.numberPlayers));
|
createCommand.set_max_players(static_cast<google::protobuf::uint32>(options.numberPlayers));
|
||||||
createCommand.set_starting_life_total(options.startingLifeTotal);
|
createCommand.set_starting_life_total(options.startingLifeTotal);
|
||||||
|
createCommand.set_enable_command_zone(options.enableCommandZone);
|
||||||
mainClient->sendCommand(LocalClient::prepareRoomCommand(createCommand, 0));
|
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 |
|
|
@ -476,8 +476,7 @@
|
||||||
* @ingroup GameMenus
|
* @ingroup GameMenus
|
||||||
* @brief Menus for interacting with zones.
|
* @brief Menus for interacting with zones.
|
||||||
*
|
*
|
||||||
* Provides contextual options for a CardZone such as the hand,
|
* Provides contextual options for card zones.
|
||||||
* library, graveyard, and battlefield.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -512,8 +511,7 @@
|
||||||
* @ingroup GameGraphics
|
* @ingroup GameGraphics
|
||||||
* @brief Graphical representations of zones.
|
* @brief Graphical representations of zones.
|
||||||
*
|
*
|
||||||
* Provides layout, visuals, and animations for a CardZone like the hand,
|
* Provides layout, visuals, and animations for card zones.
|
||||||
* library, battlefield, and graveyard.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -548,8 +546,7 @@
|
||||||
* @ingroup GameLogic
|
* @ingroup GameLogic
|
||||||
* @brief Logical handling of CardZones during a Game.
|
* @brief Logical handling of CardZones during a Game.
|
||||||
*
|
*
|
||||||
* Defines the rules and behaviors of zones such as the hand,
|
* Defines the rules and behaviors of card zones.
|
||||||
* battlefield, library, and graveyard.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
#include <libcockatrice/protocol/pb/command_set_card_attr.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_card_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_set_counter_active.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||||
|
|
@ -342,6 +343,13 @@ Response::ResponseCode Server_AbstractParticipant::cmdDelCounter(const Command_D
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractParticipant::cmdSetCounterActive(const Command_SetCounterActive & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_AbstractParticipant::cmdNextTurn(const Command_NextTurn & /*cmd*/,
|
Response::ResponseCode Server_AbstractParticipant::cmdNextTurn(const Command_NextTurn & /*cmd*/,
|
||||||
ResponseContainer & /*rc*/,
|
ResponseContainer & /*rc*/,
|
||||||
GameEventStorage & /*ges*/)
|
GameEventStorage & /*ges*/)
|
||||||
|
|
@ -525,6 +533,9 @@ Server_AbstractParticipant::processGameCommand(const GameCommand &command, Respo
|
||||||
case GameCommand::REVERSE_TURN:
|
case GameCommand::REVERSE_TURN:
|
||||||
return cmdReverseTurn(command.GetExtension(Command_ReverseTurn::ext), rc, ges);
|
return cmdReverseTurn(command.GetExtension(Command_ReverseTurn::ext), rc, ges);
|
||||||
break;
|
break;
|
||||||
|
case GameCommand::SET_COUNTER_ACTIVE:
|
||||||
|
return cmdSetCounterActive(command.GetExtension(Command_SetCounterActive::ext), rc, ges);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return Response::RespInvalidCommand;
|
return Response::RespInvalidCommand;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ class Command_Judge;
|
||||||
class Command_IncCounter;
|
class Command_IncCounter;
|
||||||
class Command_CreateCounter;
|
class Command_CreateCounter;
|
||||||
class Command_SetCounter;
|
class Command_SetCounter;
|
||||||
|
class Command_SetCounterActive;
|
||||||
class Command_DelCounter;
|
class Command_DelCounter;
|
||||||
class Command_NextTurn;
|
class Command_NextTurn;
|
||||||
class Command_SetActivePhase;
|
class Command_SetActivePhase;
|
||||||
|
|
@ -161,6 +162,8 @@ public:
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
|
cmdSetCounterActive(const Command_SetCounterActive &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
cmdNextTurn(const Command_NextTurn &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
cmdNextTurn(const Command_NextTurn &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
cmdSetActivePhase(const Command_SetActivePhase &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
cmdSetActivePhase(const Command_SetActivePhase &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,8 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue
|
||||||
|
|
||||||
bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
|
bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
|
||||||
{
|
{
|
||||||
// Clamp to valid card counter range [0, MAX_COUNTERS_ON_CARD]
|
// Clamp to valid card counter range [0, MAX_COUNTER_VALUE]
|
||||||
value = qBound(0, value, MAX_COUNTERS_ON_CARD);
|
value = qBound(0, value, MAX_COUNTER_VALUE);
|
||||||
|
|
||||||
const int oldValue = counters.value(_id, 0);
|
const int oldValue = counters.value(_id, 0);
|
||||||
if (value == oldValue) {
|
if (value == oldValue) {
|
||||||
|
|
@ -140,9 +140,9 @@ bool Server_Card::incrementCounter(int counterId, int delta, Event_SetCardCounte
|
||||||
{
|
{
|
||||||
const int oldValue = counters.value(counterId, 0);
|
const int oldValue = counters.value(counterId, 0);
|
||||||
const auto result = static_cast<int64_t>(oldValue) + static_cast<int64_t>(delta);
|
const auto result = static_cast<int64_t>(oldValue) + static_cast<int64_t>(delta);
|
||||||
// Clamp to [0, MAX_COUNTERS_ON_CARD] for card counters
|
// Clamp to [0, MAX_COUNTER_VALUE] for card counters
|
||||||
const int newValue =
|
const int newValue =
|
||||||
static_cast<int>(qBound(static_cast<int64_t>(0), result, static_cast<int64_t>(MAX_COUNTERS_ON_CARD)));
|
static_cast<int>(qBound(static_cast<int64_t>(0), result, static_cast<int64_t>(MAX_COUNTER_VALUE)));
|
||||||
|
|
||||||
if (newValue == oldValue) {
|
if (newValue == oldValue) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Sets a card counter to an exact value with clamping.
|
* @brief Sets a card counter to an exact value with clamping.
|
||||||
* @param _id The counter ID.
|
* @param _id The counter ID.
|
||||||
* @param value The desired value (clamped to [0, MAX_COUNTERS_ON_CARD]; 0 removes the counter).
|
* @param value The desired value (clamped to [0, MAX_COUNTER_VALUE]; 0 removes the counter).
|
||||||
* @param event Optional event to populate with counter state.
|
* @param event Optional event to populate with counter state.
|
||||||
* @return true if the value changed, false otherwise.
|
* @return true if the value changed, false otherwise.
|
||||||
*/
|
*/
|
||||||
|
|
@ -168,7 +168,7 @@ public:
|
||||||
* @param event Optional event to populate with counter state.
|
* @param event Optional event to populate with counter state.
|
||||||
* @return true if the value changed, false otherwise.
|
* @return true if the value changed, false otherwise.
|
||||||
* @note If counter does not exist, starts from 0. Counter is removed if result is 0.
|
* @note If counter does not exist, starts from 0. Counter is removed if result is 0.
|
||||||
* @note Clamps result to [0, MAX_COUNTERS_ON_CARD].
|
* @note Clamps result to [0, MAX_COUNTER_VALUE].
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool incrementCounter(int counterId, int delta, Event_SetCardCounter *event = nullptr);
|
[[nodiscard]] bool incrementCounter(int counterId, int delta, Event_SetCardCounter *event = nullptr);
|
||||||
void setTapped(bool _tapped)
|
void setTapped(bool _tapped)
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,19 @@
|
||||||
#include "server_counter.h"
|
#include "server_counter.h"
|
||||||
|
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_counter.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_counter.pb.h>
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
Server_Counter::Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count)
|
Server_Counter::Server_Counter(int _id,
|
||||||
: id(_id), name(_name), counterColor(_counterColor), radius(_radius), count(_count)
|
const QString &_name,
|
||||||
|
const color &_counterColor,
|
||||||
|
int _radius,
|
||||||
|
int _count,
|
||||||
|
int _minValue,
|
||||||
|
int _maxValue)
|
||||||
|
: id(_id), name(_name), counterColor(_counterColor), radius(_radius), count(_count), minValue(_minValue),
|
||||||
|
maxValue(_maxValue)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
//! \todo Extract overflow-safe arithmetic into shared helper.
|
|
||||||
//! Duplicated in Server_Card::incrementCounter() - keep in sync if modified.
|
|
||||||
bool Server_Counter::incrementCount(int delta)
|
|
||||||
{
|
|
||||||
const int oldCount = count;
|
|
||||||
const auto result = static_cast<int64_t>(count) + static_cast<int64_t>(delta);
|
|
||||||
count = static_cast<int>(qBound(static_cast<int64_t>(std::numeric_limits<int>::min()), result,
|
|
||||||
static_cast<int64_t>(std::numeric_limits<int>::max())));
|
|
||||||
return count != oldCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server_Counter::getInfo(ServerInfo_Counter *info)
|
void Server_Counter::getInfo(ServerInfo_Counter *info)
|
||||||
{
|
{
|
||||||
info->set_id(id);
|
info->set_id(id);
|
||||||
|
|
@ -26,4 +21,5 @@ void Server_Counter::getInfo(ServerInfo_Counter *info)
|
||||||
info->mutable_counter_color()->CopyFrom(counterColor);
|
info->mutable_counter_color()->CopyFrom(counterColor);
|
||||||
info->set_radius(radius);
|
info->set_radius(radius);
|
||||||
info->set_count(count);
|
info->set_count(count);
|
||||||
|
info->set_active(active);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,18 @@
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <libcockatrice/protocol/pb/color.pb.h>
|
#include <libcockatrice/protocol/pb/color.pb.h>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
class ServerInfo_Counter;
|
class ServerInfo_Counter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class Server_Counter
|
* @class Server_Counter
|
||||||
* @brief Represents a player counter with overflow-safe increment arithmetic.
|
* @brief Represents a player counter with overflow-safe increment arithmetic and optional bounds.
|
||||||
*
|
*
|
||||||
* All value modifications return whether the value actually changed,
|
* All value modifications return whether the value actually changed,
|
||||||
* enabling callers to skip unnecessary network events.
|
* enabling callers to skip unnecessary network events.
|
||||||
*
|
*
|
||||||
* @note Direct assignment via setCount() does not clamp; only
|
* @note Values are clamped to [minValue, maxValue] on both setCount() and incrementCount().
|
||||||
* incrementCount() enforces int boundary saturation.
|
|
||||||
* @note Unlike card counters, player counters are never auto-removed
|
* @note Unlike card counters, player counters are never auto-removed
|
||||||
* when they reach zero - they persist with value 0.
|
* when they reach zero - they persist with value 0.
|
||||||
*/
|
*/
|
||||||
|
|
@ -45,9 +45,30 @@ protected:
|
||||||
color counterColor;
|
color counterColor;
|
||||||
int radius;
|
int radius;
|
||||||
int count;
|
int count;
|
||||||
|
int minValue; ///< Minimum allowed value (default: INT_MIN, i.e. unbounded)
|
||||||
|
int maxValue; ///< Maximum allowed value (default: INT_MAX, i.e. unbounded)
|
||||||
|
bool active = true; ///< Whether this counter is visible/active (default: true)
|
||||||
|
|
||||||
|
static constexpr int DEFAULT_MAX_VALUE = std::numeric_limits<int>::max();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count = 0);
|
/**
|
||||||
|
* @brief Constructs a counter.
|
||||||
|
* @param _id Unique counter identifier
|
||||||
|
* @param _name Display name
|
||||||
|
* @param _counterColor Counter color
|
||||||
|
* @param _radius Display radius
|
||||||
|
* @param _count Initial value (default 0)
|
||||||
|
* @param _minValue Minimum allowed value (default INT_MIN)
|
||||||
|
* @param _maxValue Maximum allowed value (default INT_MAX)
|
||||||
|
*/
|
||||||
|
Server_Counter(int _id,
|
||||||
|
const QString &_name,
|
||||||
|
const color &_counterColor,
|
||||||
|
int _radius,
|
||||||
|
int _count = 0,
|
||||||
|
int _minValue = std::numeric_limits<int>::min(),
|
||||||
|
int _maxValue = DEFAULT_MAX_VALUE);
|
||||||
~Server_Counter()
|
~Server_Counter()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
@ -71,18 +92,31 @@ public:
|
||||||
{
|
{
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
bool isActive() const
|
||||||
|
{
|
||||||
|
return active;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @brief Sets the counter to an exact value.
|
* @brief Sets the active (visible) state of this counter.
|
||||||
* @param _count The new value (assigned directly without clamping).
|
* @param _active True to show the counter, false to hide it
|
||||||
* @return true if the value changed, false otherwise.
|
* @return true if the state changed
|
||||||
* @warning This performs raw assignment. For overflow-safe incrementing,
|
*/
|
||||||
* use incrementCount().
|
[[nodiscard]] bool setActive(bool _active)
|
||||||
|
{
|
||||||
|
bool oldActive = active;
|
||||||
|
active = _active;
|
||||||
|
return active != oldActive;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Sets the counter value, clamping to [minValue, maxValue].
|
||||||
|
* @param _count The desired new value
|
||||||
|
* @return true if the clamped value differs from the previous value
|
||||||
|
* @note For increment operations, prefer incrementCount() which handles overflow safely.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool setCount(int _count)
|
[[nodiscard]] bool setCount(int _count)
|
||||||
{
|
{
|
||||||
const int oldCount = count;
|
int oldCount = count;
|
||||||
count = _count;
|
count = qBound(minValue, _count, maxValue);
|
||||||
return count != oldCount;
|
return count != oldCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,9 +124,17 @@ public:
|
||||||
* @brief Increments the counter by delta with overflow-safe arithmetic.
|
* @brief Increments the counter by delta with overflow-safe arithmetic.
|
||||||
* @param delta The amount to add (may be negative for decrement).
|
* @param delta The amount to add (may be negative for decrement).
|
||||||
* @return true if the value changed, false otherwise.
|
* @return true if the value changed, false otherwise.
|
||||||
* @note Clamps result to [INT_MIN, INT_MAX] to prevent overflow.
|
* @note Clamps result to [minValue, maxValue] to prevent overflow.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] bool incrementCount(int delta);
|
[[nodiscard]] bool incrementCount(int delta)
|
||||||
|
{
|
||||||
|
const auto result = static_cast<int64_t>(count) + static_cast<int64_t>(delta);
|
||||||
|
const int clamped =
|
||||||
|
static_cast<int>(qBound(static_cast<int64_t>(minValue), result, static_cast<int64_t>(maxValue)));
|
||||||
|
int oldCount = count;
|
||||||
|
count = clamped;
|
||||||
|
return count != oldCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Populates info with this counter's current state for network serialization.
|
* @brief Populates info with this counter's current state for network serialization.
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo,
|
||||||
bool _spectatorsSeeEverything,
|
bool _spectatorsSeeEverything,
|
||||||
int _startingLifeTotal,
|
int _startingLifeTotal,
|
||||||
bool _shareDecklistsOnLoad,
|
bool _shareDecklistsOnLoad,
|
||||||
|
bool _enableCommandZone,
|
||||||
Server_Room *_room)
|
Server_Room *_room)
|
||||||
: QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(_creatorInfo)),
|
: QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(_creatorInfo)),
|
||||||
gameStarted(false), gameClosed(false), gameId(_gameId), password(_password), maxPlayers(_maxPlayers),
|
gameStarted(false), gameClosed(false), gameId(_gameId), password(_password), maxPlayers(_maxPlayers),
|
||||||
|
|
@ -74,9 +75,9 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo,
|
||||||
onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed),
|
onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed),
|
||||||
spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk),
|
spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk),
|
||||||
spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal),
|
spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal),
|
||||||
shareDecklistsOnLoad(_shareDecklistsOnLoad), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0),
|
shareDecklistsOnLoad(_shareDecklistsOnLoad), enableCommandZone(_enableCommandZone), inactivityCounter(0),
|
||||||
firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr),
|
startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), turnOrderReversed(false),
|
||||||
gameMutex()
|
startTime(QDateTime::currentDateTime()), pingClock(nullptr), gameMutex()
|
||||||
{
|
{
|
||||||
currentReplay = new GameReplay;
|
currentReplay = new GameReplay;
|
||||||
currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId());
|
currentReplay->set_replay_id(room->getServer()->getDatabaseInterface()->getNextReplayId());
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ private:
|
||||||
bool spectatorsSeeEverything;
|
bool spectatorsSeeEverything;
|
||||||
int startingLifeTotal;
|
int startingLifeTotal;
|
||||||
bool shareDecklistsOnLoad;
|
bool shareDecklistsOnLoad;
|
||||||
|
bool enableCommandZone;
|
||||||
int inactivityCounter;
|
int inactivityCounter;
|
||||||
int startTimeOfThisGame, secondsElapsed;
|
int startTimeOfThisGame, secondsElapsed;
|
||||||
bool firstGameStarted;
|
bool firstGameStarted;
|
||||||
|
|
@ -106,6 +107,7 @@ public:
|
||||||
bool _spectatorsSeeEverything,
|
bool _spectatorsSeeEverything,
|
||||||
int _startingLifeTotal,
|
int _startingLifeTotal,
|
||||||
bool _shareDecklistsOnLoad,
|
bool _shareDecklistsOnLoad,
|
||||||
|
bool _enableCommandZone,
|
||||||
Server_Room *parent);
|
Server_Room *parent);
|
||||||
~Server_Game() override;
|
~Server_Game() override;
|
||||||
Server_Room *getRoom() const
|
Server_Room *getRoom() const
|
||||||
|
|
@ -173,6 +175,10 @@ public:
|
||||||
{
|
{
|
||||||
return shareDecklistsOnLoad;
|
return shareDecklistsOnLoad;
|
||||||
}
|
}
|
||||||
|
bool getEnableCommandZone() const
|
||||||
|
{
|
||||||
|
return enableCommandZone;
|
||||||
|
}
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions, bool asJudge);
|
checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions, bool asJudge);
|
||||||
bool containsUser(const QString &userName) const;
|
bool containsUser(const QString &userName) const;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
#include <libcockatrice/protocol/pb/command_mulligan.pb.h>
|
#include <libcockatrice/protocol/pb/command_mulligan.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_active_phase.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_active_phase.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_set_counter_active.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||||
|
|
@ -39,6 +40,7 @@
|
||||||
#include <libcockatrice/protocol/pb/event_game_log_notice.pb.h>
|
#include <libcockatrice/protocol/pb/event_game_log_notice.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_player_properties_changed.pb.h>
|
#include <libcockatrice/protocol/pb/event_player_properties_changed.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_set_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/protocol/pb/event_shuffle.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
|
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
|
||||||
|
|
@ -47,6 +49,7 @@
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||||
#include <libcockatrice/rng/rng_abstract.h>
|
#include <libcockatrice/rng/rng_abstract.h>
|
||||||
#include <libcockatrice/utility/color.h>
|
#include <libcockatrice/utility/color.h>
|
||||||
|
#include <libcockatrice/utility/counter_ids.h>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
||||||
|
|
@ -101,6 +104,17 @@ void Server_Player::setupZones()
|
||||||
addCounter(new Server_Counter(6, "x", makeColor(255, 255, 255), 20, 0));
|
addCounter(new Server_Counter(6, "x", makeColor(255, 255, 255), 20, 0));
|
||||||
addCounter(new Server_Counter(7, "storm", makeColor(255, 150, 30), 20, 0));
|
addCounter(new Server_Counter(7, "storm", makeColor(255, 150, 30), 20, 0));
|
||||||
|
|
||||||
|
// Command zone for Commander format
|
||||||
|
if (game->getEnableCommandZone()) {
|
||||||
|
addZone(new Server_CardZone(this, ZoneNames::COMMAND, false, ServerInfo_Zone::PublicZone));
|
||||||
|
addCounter(new Server_Counter(CounterIds::CommanderTax, CounterNames::CommanderTax, makeColor(128, 128, 128),
|
||||||
|
20, 0, 0, MAX_COUNTER_VALUE));
|
||||||
|
auto *partnerTax = new Server_Counter(CounterIds::PartnerTax, CounterNames::PartnerTax,
|
||||||
|
makeColor(128, 128, 128), 20, 0, 0, MAX_COUNTER_VALUE);
|
||||||
|
(void)partnerTax->setActive(false);
|
||||||
|
addCounter(partnerTax);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
// Assign card ids and create deck from deck list
|
// Assign card ids and create deck from deck list
|
||||||
|
|
@ -426,6 +440,12 @@ Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer &
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Server_Player::isCommandZoneCounterBlocked(int counterId) const
|
||||||
|
{
|
||||||
|
return (counterId == CounterIds::CommanderTax || counterId == CounterIds::PartnerTax) &&
|
||||||
|
!game->getEnableCommandZone();
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
||||||
{
|
{
|
||||||
|
|
@ -437,6 +457,11 @@ Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer &
|
||||||
}
|
}
|
||||||
|
|
||||||
const int counterId = cmd.counter_id();
|
const int counterId = cmd.counter_id();
|
||||||
|
|
||||||
|
if (isCommandZoneCounterBlocked(counterId)) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
||||||
Server_Counter *c = counters.value(counterId, nullptr);
|
Server_Counter *c = counters.value(counterId, nullptr);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
|
|
@ -490,6 +515,11 @@ Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer &
|
||||||
}
|
}
|
||||||
|
|
||||||
const int counterId = cmd.counter_id();
|
const int counterId = cmd.counter_id();
|
||||||
|
|
||||||
|
if (isCommandZoneCounterBlocked(counterId)) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
||||||
Server_Counter *c = counters.value(counterId, nullptr);
|
Server_Counter *c = counters.value(counterId, nullptr);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
|
|
@ -517,6 +547,11 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &
|
||||||
}
|
}
|
||||||
|
|
||||||
const int counterId = cmd.counter_id();
|
const int counterId = cmd.counter_id();
|
||||||
|
|
||||||
|
if (isCommandZoneCounterBlocked(counterId)) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
||||||
Server_Counter *counter = counters.value(counterId, nullptr);
|
Server_Counter *counter = counters.value(counterId, nullptr);
|
||||||
if (!counter) {
|
if (!counter) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
|
|
@ -531,6 +566,35 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_Player::cmdSetCounterActive(const Command_SetCounterActive &cmd,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
if (!game->getGameStarted()) {
|
||||||
|
return Response::RespGameNotStarted;
|
||||||
|
}
|
||||||
|
if (conceded) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int counterId = cmd.counter_id();
|
||||||
|
Server_Counter *c = counters.value(counterId, nullptr);
|
||||||
|
if (!c) {
|
||||||
|
return Response::RespNameNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool didChange = c->setActive(cmd.active());
|
||||||
|
|
||||||
|
if (didChange) {
|
||||||
|
Event_SetCounterActive event;
|
||||||
|
event.set_counter_id(c->getId());
|
||||||
|
event.set_active(c->isActive());
|
||||||
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
Server_Player::cmdNextTurn(const Command_NextTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/)
|
Server_Player::cmdNextTurn(const Command_NextTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class Server_Player : public Server_AbstractPlayer
|
||||||
private:
|
private:
|
||||||
QMap<int, Server_Counter *> counters;
|
QMap<int, Server_Counter *> counters;
|
||||||
QList<int> lastDrawList;
|
QList<int> lastDrawList;
|
||||||
|
bool isCommandZoneCounterBlocked(int counterId) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Server_Player(Server_Game *_game,
|
Server_Player(Server_Game *_game,
|
||||||
|
|
@ -57,6 +58,8 @@ public:
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
|
cmdSetCounterActive(const Command_SetCounterActive &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
|
Response::ResponseCode
|
||||||
cmdNextTurn(const Command_NextTurn &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
cmdNextTurn(const Command_NextTurn &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
cmdSetActivePhase(const Command_SetActivePhase &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
cmdSetActivePhase(const Command_SetActivePhase &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
|
|
|
||||||
|
|
@ -885,10 +885,11 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
|
||||||
|
|
||||||
// When server doesn't permit registered users to exist, do not honor only-reg setting
|
// When server doesn't permit registered users to exist, do not honor only-reg setting
|
||||||
bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers());
|
bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers());
|
||||||
auto *game = new Server_Game(copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()),
|
auto *game =
|
||||||
cmd.max_players(), gameTypes, cmd.only_buddies(), onlyRegisteredUsers,
|
new Server_Game(copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()),
|
||||||
cmd.spectators_allowed(), cmd.spectators_need_password(), cmd.spectators_can_talk(),
|
cmd.max_players(), gameTypes, cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(),
|
||||||
cmd.spectators_see_everything(), startingLifeTotal, shareDecklistsOnLoad, room);
|
cmd.spectators_need_password(), cmd.spectators_can_talk(), cmd.spectators_see_everything(),
|
||||||
|
startingLifeTotal, shareDecklistsOnLoad, cmd.enable_command_zone(), room);
|
||||||
|
|
||||||
game->addPlayer(this, rc, asSpectator, asJudge, false);
|
game->addPlayer(this, rc, asSpectator, asJudge, false);
|
||||||
room->addGame(game);
|
room->addGame(game);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ set(PROTO_FILES
|
||||||
command_set_card_attr.proto
|
command_set_card_attr.proto
|
||||||
command_set_card_counter.proto
|
command_set_card_counter.proto
|
||||||
command_set_counter.proto
|
command_set_counter.proto
|
||||||
|
command_set_counter_active.proto
|
||||||
command_set_sideboard_lock.proto
|
command_set_sideboard_lock.proto
|
||||||
command_set_sideboard_plan.proto
|
command_set_sideboard_plan.proto
|
||||||
command_shuffle.proto
|
command_shuffle.proto
|
||||||
|
|
@ -106,6 +107,7 @@ set(PROTO_FILES
|
||||||
event_set_card_attr.proto
|
event_set_card_attr.proto
|
||||||
event_set_card_counter.proto
|
event_set_card_counter.proto
|
||||||
event_set_counter.proto
|
event_set_counter.proto
|
||||||
|
event_set_counter_active.proto
|
||||||
event_shuffle.proto
|
event_shuffle.proto
|
||||||
event_user_joined.proto
|
event_user_joined.proto
|
||||||
event_user_left.proto
|
event_user_left.proto
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_commands.proto";
|
||||||
|
message Command_SetCounterActive {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_SetCounterActive ext = 1035;
|
||||||
|
}
|
||||||
|
optional sint32 counter_id = 1 [default = -1];
|
||||||
|
optional bool active = 2 [default = true];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_event.proto";
|
||||||
|
|
||||||
|
message Event_SetCounterActive {
|
||||||
|
extend GameEvent {
|
||||||
|
optional Event_SetCounterActive ext = 2023;
|
||||||
|
}
|
||||||
|
optional sint32 counter_id = 1;
|
||||||
|
optional bool active = 2;
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ message GameCommand {
|
||||||
UNCONCEDE = 1032;
|
UNCONCEDE = 1032;
|
||||||
JUDGE = 1033;
|
JUDGE = 1033;
|
||||||
REVERSE_TURN = 1034;
|
REVERSE_TURN = 1034;
|
||||||
|
SET_COUNTER_ACTIVE = 1035;
|
||||||
}
|
}
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ message GameEvent {
|
||||||
CHANGE_ZONE_PROPERTIES = 2020;
|
CHANGE_ZONE_PROPERTIES = 2020;
|
||||||
REVERSE_TURN = 2021;
|
REVERSE_TURN = 2021;
|
||||||
GAME_LOG_NOTICE = 2022;
|
GAME_LOG_NOTICE = 2022;
|
||||||
|
SET_COUNTER_ACTIVE = 2023;
|
||||||
}
|
}
|
||||||
optional sint32 player_id = 1 [default = -1];
|
optional sint32 player_id = 1 [default = -1];
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,9 @@ message Command_CreateGame {
|
||||||
|
|
||||||
// share decklists with all players when selected
|
// share decklists with all players when selected
|
||||||
optional bool share_decklists_on_load = 14;
|
optional bool share_decklists_on_load = 14;
|
||||||
|
|
||||||
|
// enable command zone for Commander format
|
||||||
|
optional bool enable_command_zone = 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Command_JoinGame {
|
message Command_JoinGame {
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ message ServerInfo_Counter {
|
||||||
optional color counter_color = 3;
|
optional color counter_color = 3;
|
||||||
optional sint32 radius = 4;
|
optional sint32 radius = 4;
|
||||||
optional sint32 count = 5;
|
optional sint32 count = 5;
|
||||||
|
optional bool active = 6 [default = true];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ set(UTILITY_HEADERS
|
||||||
libcockatrice/utility/levenshtein.h
|
libcockatrice/utility/levenshtein.h
|
||||||
libcockatrice/utility/macros.h
|
libcockatrice/utility/macros.h
|
||||||
libcockatrice/utility/passwordhasher.h
|
libcockatrice/utility/passwordhasher.h
|
||||||
|
libcockatrice/utility/counter_ids.h
|
||||||
libcockatrice/utility/trice_limits.h
|
libcockatrice/utility/trice_limits.h
|
||||||
libcockatrice/utility/zone_names.h
|
libcockatrice/utility/zone_names.h
|
||||||
)
|
)
|
||||||
|
|
|
||||||
46
libcockatrice_utility/libcockatrice/utility/counter_ids.h
Normal file
46
libcockatrice_utility/libcockatrice/utility/counter_ids.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* @file counter_ids.h
|
||||||
|
* @ingroup GameLogic
|
||||||
|
* @brief Shared counter IDs and names for system counters (e.g. commander tax).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COCKATRICE_COUNTER_IDS_H
|
||||||
|
#define COCKATRICE_COUNTER_IDS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared counter IDs used by both client and server.
|
||||||
|
* These must match between server_player.cpp and player_event_handler.cpp.
|
||||||
|
*
|
||||||
|
* Reserved counter IDs for system counters:
|
||||||
|
* IDs 0-7: Standard player counters (life, mana colors, storm)
|
||||||
|
* IDs 8-9: Commander tax counters
|
||||||
|
* IDs 10+: Available for user-created counters
|
||||||
|
*
|
||||||
|
* The server's newCounterId() starts from the highest existing ID + 1,
|
||||||
|
* so these reserved IDs won't conflict as long as they're created first
|
||||||
|
* during setupZones(). See server_player.cpp::setupZones() for the
|
||||||
|
* authoritative list of reserved IDs.
|
||||||
|
*
|
||||||
|
* To find all files referencing these IDs, grep for CounterIds::CommanderTax
|
||||||
|
* and CounterIds::PartnerTax across the codebase.
|
||||||
|
*/
|
||||||
|
namespace CounterIds
|
||||||
|
{
|
||||||
|
constexpr int CommanderTax = 8;
|
||||||
|
constexpr int PartnerTax = 9;
|
||||||
|
} // namespace CounterIds
|
||||||
|
|
||||||
|
namespace CounterNames
|
||||||
|
{
|
||||||
|
constexpr const char *CommanderTax = "commander_tax_counter";
|
||||||
|
constexpr const char *PartnerTax = "partner_tax_counter";
|
||||||
|
|
||||||
|
inline bool isTaxCounter(const QString &name)
|
||||||
|
{
|
||||||
|
return name == CommanderTax || name == PartnerTax;
|
||||||
|
}
|
||||||
|
} // namespace CounterNames
|
||||||
|
|
||||||
|
#endif // COCKATRICE_COUNTER_IDS_H
|
||||||
|
|
@ -15,11 +15,11 @@ constexpr uint MAXIMUM_DIE_SIDES = 1000000;
|
||||||
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
|
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
|
||||||
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
|
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
|
||||||
|
|
||||||
// Card counter value bounds [0, MAX_COUNTERS_ON_CARD].
|
// Counter value bounds [0, MAX_COUNTER_VALUE].
|
||||||
// Counters on cards (e.g., +1/+1 counters, charge counters) are non-negative physical game objects.
|
// Counters (on cards or players) are non-negative values.
|
||||||
// The max of 999 is a display constraint (3-digit rendering) and reasonable gameplay limit.
|
// The max of 999 is a display constraint (3-digit rendering) and reasonable gameplay limit.
|
||||||
// Server enforces these bounds; client may also check for UX optimization.
|
// Server enforces these bounds; client may also check for UX optimization.
|
||||||
constexpr int MAX_COUNTERS_ON_CARD = 999;
|
constexpr int MAX_COUNTER_VALUE = 999;
|
||||||
|
|
||||||
// optimized functions to get qstrings that are at most that long
|
// optimized functions to get qstrings that are at most that long
|
||||||
static inline QString nameFromStdString(const std::string &_string)
|
static inline QString nameFromStdString(const std::string &_string)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ constexpr const char *DECK = "deck";
|
||||||
constexpr const char *SIDEBOARD = "sb";
|
constexpr const char *SIDEBOARD = "sb";
|
||||||
constexpr const char *STACK = "stack";
|
constexpr const char *STACK = "stack";
|
||||||
|
|
||||||
|
// Command zone (Commander format)
|
||||||
|
constexpr const char *COMMAND = "command";
|
||||||
|
|
||||||
} // namespace ZoneNames
|
} // namespace ZoneNames
|
||||||
|
|
||||||
#endif // ZONE_NAMES_H
|
#endif // ZONE_NAMES_H
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ TEST(ReverseCardMoveTest, MoveCardFromBottomTest)
|
||||||
// instantiate a fake server instance
|
// instantiate a fake server instance
|
||||||
FakeServer server;
|
FakeServer server;
|
||||||
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
Server_Room room(0, 0, "", "", "", "", false, "", {}, &server);
|
||||||
Server_Game game(user, 1, "", "", 2, QList<int>(), false, false, false, false, false, false, 20, false, &room);
|
Server_Game game(user, 1, "", "", 2, QList<int>(), false, false, false, false, false, false, 20, false, false,
|
||||||
|
&room);
|
||||||
Server_AbstractPlayer player(&game, 1, user, false, nullptr);
|
Server_AbstractPlayer player(&game, 1, user, false, nullptr);
|
||||||
Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone);
|
Server_CardZone deckZone(&player, ZoneNames::DECK, true, ServerInfo_Zone::PublicZone);
|
||||||
Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone);
|
Server_CardZone exileZone(&player, ZoneNames::EXILE, true, ServerInfo_Zone::PublicZone);
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ TEST(ServerCardCounter, IncrementExistingCounter)
|
||||||
TEST(ServerCardCounter, IncrementOverflowProtection)
|
TEST(ServerCardCounter, IncrementOverflowProtection)
|
||||||
{
|
{
|
||||||
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE));
|
||||||
EXPECT_FALSE(card.incrementCounter(1, 1));
|
EXPECT_FALSE(card.incrementCounter(1, 1));
|
||||||
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ServerCardCounter, DecrementUnderflowProtection)
|
TEST(ServerCardCounter, DecrementUnderflowProtection)
|
||||||
|
|
@ -113,13 +113,13 @@ TEST(ServerCardCounter, IncrementCounterPopulatesEvent)
|
||||||
TEST(ServerCardCounter, IncrementCounterEventReflectsClampedValue)
|
TEST(ServerCardCounter, IncrementCounterEventReflectsClampedValue)
|
||||||
{
|
{
|
||||||
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE - 5));
|
||||||
|
|
||||||
Event_SetCardCounter event;
|
Event_SetCardCounter event;
|
||||||
EXPECT_TRUE(card.incrementCounter(1, 10, &event));
|
EXPECT_TRUE(card.incrementCounter(1, 10, &event));
|
||||||
|
|
||||||
EXPECT_EQ(event.counter_id(), 1);
|
EXPECT_EQ(event.counter_id(), 1);
|
||||||
EXPECT_EQ(event.counter_value(), MAX_COUNTERS_ON_CARD);
|
EXPECT_EQ(event.counter_value(), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr)
|
TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr)
|
||||||
|
|
@ -133,7 +133,7 @@ TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr)
|
||||||
TEST(ServerCardCounter, IncrementCounterEventNotPopulatedWhenUnchanged)
|
TEST(ServerCardCounter, IncrementCounterEventNotPopulatedWhenUnchanged)
|
||||||
{
|
{
|
||||||
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE));
|
||||||
|
|
||||||
Event_SetCardCounter event;
|
Event_SetCardCounter event;
|
||||||
event.set_counter_id(999);
|
event.set_counter_id(999);
|
||||||
|
|
@ -156,7 +156,7 @@ TEST(ServerCardCounter, SetCounterClampsAboveMaxToMax)
|
||||||
{
|
{
|
||||||
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
EXPECT_TRUE(card.setCounter(1, 1500));
|
EXPECT_TRUE(card.setCounter(1, 1500));
|
||||||
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ServerCardCounter, IncrementDoesNotGoBelowZero)
|
TEST(ServerCardCounter, IncrementDoesNotGoBelowZero)
|
||||||
|
|
@ -171,9 +171,9 @@ TEST(ServerCardCounter, IncrementDoesNotGoBelowZero)
|
||||||
TEST(ServerCardCounter, IncrementDoesNotExceedMax)
|
TEST(ServerCardCounter, IncrementDoesNotExceedMax)
|
||||||
{
|
{
|
||||||
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5));
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTER_VALUE - 5));
|
||||||
EXPECT_TRUE(card.incrementCounter(1, 10));
|
EXPECT_TRUE(card.incrementCounter(1, 10));
|
||||||
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <libcockatrice/network/server/remote/game/server_counter.h>
|
#include <libcockatrice/network/server/remote/game/server_counter.h>
|
||||||
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
TEST(ServerCounter, IncrementDoesNotOverflow)
|
TEST(ServerCounter, IncrementDoesNotOverflow)
|
||||||
|
|
@ -79,6 +80,37 @@ TEST(ServerCounter, MixedExtremesDoNotClamp)
|
||||||
EXPECT_EQ(c.getCount(), -1);
|
EXPECT_EQ(c.getCount(), -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, SetCountClampsToCustomBounds)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, 50, 0, 100);
|
||||||
|
EXPECT_TRUE(c.setCount(150));
|
||||||
|
EXPECT_EQ(c.getCount(), 100);
|
||||||
|
EXPECT_TRUE(c.setCount(-10));
|
||||||
|
EXPECT_EQ(c.getCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, IncrementClampsToCustomBounds)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, 50, 0, 100);
|
||||||
|
EXPECT_TRUE(c.incrementCount(100));
|
||||||
|
EXPECT_EQ(c.getCount(), 100);
|
||||||
|
EXPECT_FALSE(c.incrementCount(1));
|
||||||
|
EXPECT_EQ(c.getCount(), 100);
|
||||||
|
EXPECT_TRUE(c.incrementCount(-200));
|
||||||
|
EXPECT_EQ(c.getCount(), 0);
|
||||||
|
EXPECT_FALSE(c.incrementCount(-1));
|
||||||
|
EXPECT_EQ(c.getCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, CustomBoundsForCommanderTax)
|
||||||
|
{
|
||||||
|
Server_Counter taxCounter(1, "tax", color(), 20, 0, 0, MAX_COUNTER_VALUE);
|
||||||
|
EXPECT_TRUE(taxCounter.setCount(1000));
|
||||||
|
EXPECT_EQ(taxCounter.getCount(), MAX_COUNTER_VALUE);
|
||||||
|
EXPECT_TRUE(taxCounter.setCount(-5));
|
||||||
|
EXPECT_EQ(taxCounter.getCount(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue