mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-28 00:42:19 -07:00
Refactor tax counters into cast counts with separate ID space and user activated create/delete
This commit is contained in:
parent
4354191918
commit
f5e49a571e
50 changed files with 900 additions and 828 deletions
1
Testing/Temporary/CTestCostData.txt
Normal file
1
Testing/Temporary/CTestCostData.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
---
|
||||||
3
Testing/Temporary/LastTest.log
Normal file
3
Testing/Temporary/LastTest.log
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Start testing: Aug 10 22:25 EDT
|
||||||
|
----------------------------------------------------------
|
||||||
|
End testing: Aug 10 22:25 EDT
|
||||||
|
|
@ -67,7 +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/cast_count_widget.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
|
||||||
|
|
|
||||||
|
|
@ -597,18 +597,18 @@ private:
|
||||||
{"Player/aViewBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom Cards of Library"),
|
{"Player/aViewBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom Cards of Library"),
|
||||||
parseSequenceString("Ctrl+Shift+W"),
|
parseSequenceString("Ctrl+Shift+W"),
|
||||||
ShortcutGroup::View)},
|
ShortcutGroup::View)},
|
||||||
{"Player/aAddCommanderTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 1st Tax"),
|
{"Player/aIncrease1stCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 1st Cast Count"),
|
||||||
parseSequenceString(""),
|
parseSequenceString(""),
|
||||||
ShortcutGroup::Player_Counters)},
|
ShortcutGroup::Player_Counters)},
|
||||||
{"Player/aRemoveCommanderTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 1st Tax"),
|
{"Player/aDecrease1stCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 1st Cast Count"),
|
||||||
parseSequenceString(""),
|
parseSequenceString(""),
|
||||||
ShortcutGroup::Player_Counters)},
|
ShortcutGroup::Player_Counters)},
|
||||||
{"Player/aAddPartnerTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 2nd Tax"),
|
{"Player/aIncrease2ndCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Increase 2nd Cast Count"),
|
||||||
parseSequenceString(""),
|
parseSequenceString(""),
|
||||||
ShortcutGroup::Player_Counters)},
|
ShortcutGroup::Player_Counters)},
|
||||||
{"Player/aRemovePartnerTax", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 2nd Tax"),
|
{"Player/aDecrease2ndCastCount", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Decrease 2nd Cast Count"),
|
||||||
parseSequenceString(""),
|
parseSequenceString(""),
|
||||||
ShortcutGroup::Player_Counters)},
|
ShortcutGroup::Player_Counters)},
|
||||||
{"Player/aCloseMostRecentZoneView", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Close Recent View"),
|
{"Player/aCloseMostRecentZoneView", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Close Recent View"),
|
||||||
parseSequenceString("Esc"),
|
parseSequenceString("Esc"),
|
||||||
ShortcutGroup::View)},
|
ShortcutGroup::View)},
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,13 @@
|
||||||
#include <libcockatrice/card/relation/card_relation.h>
|
#include <libcockatrice/card/relation/card_relation.h>
|
||||||
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_create_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_create_token.pb.h>
|
#include <libcockatrice/protocol/pb/command_create_token.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_delete_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
|
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_flip_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_flip_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
|
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_inc_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_mulligan.pb.h>
|
#include <libcockatrice/protocol/pb/command_mulligan.pb.h>
|
||||||
|
|
@ -25,7 +28,6 @@
|
||||||
#include <libcockatrice/protocol/pb/command_roll_die.pb.h>
|
#include <libcockatrice/protocol/pb/command_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>
|
||||||
|
|
@ -1674,32 +1676,34 @@ void PlayerActions::playSelectedCardsImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actPlayAndIncrease1stTax(QList<CardItem *> selectedCards)
|
void PlayerActions::actPlayAndIncrease1stCastCount(QList<CardItem *> selectedCards)
|
||||||
{
|
{
|
||||||
playAndIncreaseTax(selectedCards, CounterIds::TaxCounter1);
|
playAndIncreaseCastCount(selectedCards, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actPlayAndIncrease2ndTax(QList<CardItem *> selectedCards)
|
void PlayerActions::actPlayAndIncrease2ndCastCount(QList<CardItem *> selectedCards)
|
||||||
{
|
{
|
||||||
playAndIncreaseTax(selectedCards, CounterIds::TaxCounter2);
|
playAndIncreaseCastCount(selectedCards, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::playAndIncreaseTax(QList<CardItem *> selectedCards, int counterId)
|
void PlayerActions::playAndIncreaseCastCount(QList<CardItem *> selectedCards, int index)
|
||||||
{
|
{
|
||||||
playSelectedCardsImpl(selectedCards, false, [this, counterId](PendingCommand *pend, const QString &originalZone) {
|
playSelectedCardsImpl(selectedCards, false, [this, index](PendingCommand *pend, const QString &originalZone) {
|
||||||
if (originalZone != ZoneNames::COMMAND || pend == nullptr) {
|
if (originalZone != ZoneNames::COMMAND || pend == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Gate the tax increment on the server accepting the move, so a rejected move
|
// Gate the increment on the server accepting the move
|
||||||
// (the card couldn't legally leave the command zone) never inflates the tax.
|
|
||||||
connect(pend, &PendingCommand::finished, this,
|
connect(pend, &PendingCommand::finished, this,
|
||||||
[this, counterId](const Response &response, const CommandContainer &, const QVariant &) {
|
[this, index](const Response &response, const CommandContainer &, const QVariant &) {
|
||||||
if (response.response_code() != Response::RespOk) {
|
if (response.response_code() != Response::RespOk) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CounterState *state = player->getCounters().value(counterId, nullptr);
|
CounterState *state = player->getCastCount(index);
|
||||||
if (state && state->isActive()) {
|
if (state) {
|
||||||
sendIncCounter(counterId, 1);
|
Command_IncCastCount cmd;
|
||||||
|
cmd.set_index(index);
|
||||||
|
cmd.set_delta(1);
|
||||||
|
sendGameCommand(cmd);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -1713,29 +1717,40 @@ void PlayerActions::sendIncCounter(int counterId, int delta)
|
||||||
sendGameCommand(cmd);
|
sendGameCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actModifyTaxCounter(int counterId, int delta)
|
void PlayerActions::actModifyCastCount(int index, int delta)
|
||||||
{
|
{
|
||||||
CounterState *state = player->getCounters().value(counterId, nullptr);
|
if (!CastCountIds::isValidIndex(index)) {
|
||||||
if (!state || !state->isActive() || !CounterIds::isTaxCounter(counterId)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendIncCounter(counterId, delta);
|
CounterState *state = player->getCastCount(index);
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Command_IncCastCount cmd;
|
||||||
|
cmd.set_index(index);
|
||||||
|
cmd.set_delta(delta);
|
||||||
|
sendGameCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actToggleTaxCounter(int counterId)
|
void PlayerActions::actToggleCastCount(int index)
|
||||||
{
|
{
|
||||||
CounterState *state = player->getCounters().value(counterId, nullptr);
|
if (!CastCountIds::isValidIndex(index)) {
|
||||||
if (!state || !CounterIds::isTaxCounter(counterId)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Prevent disabling a counter with tax accumulated; player must reset to 0 first
|
CounterState *state = player->getCastCount(index);
|
||||||
if (state->isActive() && state->getValue() != 0) {
|
if (state) {
|
||||||
return;
|
// Delete only allowed when value is 0
|
||||||
|
if (state->getValue() != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Command_DeleteCastCount cmd;
|
||||||
|
cmd.set_index(index);
|
||||||
|
sendGameCommand(cmd);
|
||||||
|
} else {
|
||||||
|
Command_CreateCastCount cmd;
|
||||||
|
cmd.set_index(index);
|
||||||
|
sendGameCommand(cmd);
|
||||||
}
|
}
|
||||||
Command_SetCounterActive cmd;
|
|
||||||
cmd.set_counter_id(counterId);
|
|
||||||
cmd.set_active(!state->isActive());
|
|
||||||
sendGameCommand(cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerActions::actPlay(QList<CardItem *> selectedCards)
|
void PlayerActions::actPlay(QList<CardItem *> selectedCards)
|
||||||
|
|
|
||||||
|
|
@ -127,14 +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 1st tax counter. */
|
/** @brief Plays the selected card and increments the 1st cast count. */
|
||||||
void actPlayAndIncrease1stTax(QList<CardItem *> selectedCards);
|
void actPlayAndIncrease1stCastCount(QList<CardItem *> selectedCards);
|
||||||
/** @brief Plays the selected card and increments the 2nd tax counter. */
|
/** @brief Plays the selected card and increments the 2nd cast count. */
|
||||||
void actPlayAndIncrease2ndTax(QList<CardItem *> selectedCards);
|
void actPlayAndIncrease2ndCastCount(QList<CardItem *> selectedCards);
|
||||||
/** @brief Modifies a tax counter by delta if it is active. */
|
/** @brief Modifies a cast count by delta. */
|
||||||
void actModifyTaxCounter(int counterId, int delta);
|
void actModifyCastCount(int index, int delta);
|
||||||
/** @brief Toggles a tax counter's active state (only if inactive or value is 0). */
|
/** @brief Toggles a cast count's existence (create if missing, delete if value is 0). */
|
||||||
void actToggleTaxCounter(int counterId);
|
void actToggleCastCount(int index);
|
||||||
void actHide(QList<CardItem *> selectedCards);
|
void actHide(QList<CardItem *> selectedCards);
|
||||||
|
|
||||||
void actMoveTopCardToPlay();
|
void actMoveTopCardToPlay();
|
||||||
|
|
@ -280,11 +280,11 @@ private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Plays the selected cards and, for each that came from the command zone and whose move
|
* @brief Plays the selected cards and, for each that came from the command zone and whose move
|
||||||
* the server accepts, increments the given (active) tax counter by one.
|
* the server accepts, increments the given cast count by one.
|
||||||
* @param selectedCards Cards to play
|
* @param selectedCards Cards to play
|
||||||
* @param counterId The tax counter to increment (CounterIds::TaxCounter1 through TaxCounter5)
|
* @param index The cast count index (1-5)
|
||||||
*/
|
*/
|
||||||
void playAndIncreaseTax(QList<CardItem *> selectedCards, int counterId);
|
void playAndIncreaseCastCount(QList<CardItem *> selectedCards, int index);
|
||||||
|
|
||||||
void cmdSetTopCard(Command_MoveCard &cmd);
|
void cmdSetTopCard(Command_MoveCard &cmd);
|
||||||
void cmdSetBottomCard(Command_MoveCard &cmd);
|
void cmdSetBottomCard(Command_MoveCard &cmd);
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,12 @@
|
||||||
#include <libcockatrice/protocol/pb/event_attach_card.pb.h>
|
#include <libcockatrice/protocol/pb/event_attach_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_change_zone_properties.pb.h>
|
#include <libcockatrice/protocol/pb/event_change_zone_properties.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_create_arrow.pb.h>
|
#include <libcockatrice/protocol/pb/event_create_arrow.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_create_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_create_counter.pb.h>
|
#include <libcockatrice/protocol/pb/event_create_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_create_token.pb.h>
|
#include <libcockatrice/protocol/pb/event_create_token.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_del_counter.pb.h>
|
#include <libcockatrice/protocol/pb/event_del_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_delete_arrow.pb.h>
|
#include <libcockatrice/protocol/pb/event_delete_arrow.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_delete_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_destroy_card.pb.h>
|
#include <libcockatrice/protocol/pb/event_destroy_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_draw_cards.pb.h>
|
#include <libcockatrice/protocol/pb/event_draw_cards.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_dump_zone.pb.h>
|
#include <libcockatrice/protocol/pb/event_dump_zone.pb.h>
|
||||||
|
|
@ -30,8 +32,8 @@
|
||||||
#include <libcockatrice/protocol/pb/event_roll_die.pb.h>
|
#include <libcockatrice/protocol/pb/event_roll_die.pb.h>
|
||||||
#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_cast_count.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/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
|
@ -277,20 +279,26 @@ void PlayerEventHandler::eventSetCounter(const Event_SetCounter &event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerEventHandler::eventSetCounterActive(const Event_SetCounterActive &event)
|
|
||||||
{
|
|
||||||
CounterState *state = player->getCounters().value(event.counter_id(), nullptr);
|
|
||||||
if (!state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state->setActive(event.active());
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayerEventHandler::eventDelCounter(const Event_DelCounter &event)
|
void PlayerEventHandler::eventDelCounter(const Event_DelCounter &event)
|
||||||
{
|
{
|
||||||
player->delCounter(event.counter_id());
|
player->delCounter(event.counter_id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerEventHandler::eventCreateCastCount(const Event_CreateCastCount &event)
|
||||||
|
{
|
||||||
|
player->addCastCount(event.index());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerEventHandler::eventDeleteCastCount(const Event_DeleteCastCount &event)
|
||||||
|
{
|
||||||
|
player->delCastCount(event.index());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerEventHandler::eventSetCastCount(const Event_SetCastCount &event)
|
||||||
|
{
|
||||||
|
player->setCastCountValue(event.index(), event.value());
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerEventHandler::eventDumpZone(const Event_DumpZone &event)
|
void PlayerEventHandler::eventDumpZone(const Event_DumpZone &event)
|
||||||
{
|
{
|
||||||
PlayerLogic *zoneOwner = player->getGame()->getPlayerManager()->getPlayers().value(event.zone_owner_id(), 0);
|
PlayerLogic *zoneOwner = player->getGame()->getPlayerManager()->getPlayers().value(event.zone_owner_id(), 0);
|
||||||
|
|
@ -640,12 +648,18 @@ void PlayerEventHandler::processGameEvent(GameEvent::GameEventType type,
|
||||||
case GameEvent::SET_COUNTER:
|
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;
|
||||||
|
case GameEvent::CREATE_CAST_COUNT:
|
||||||
|
eventCreateCastCount(event.GetExtension(Event_CreateCastCount::ext));
|
||||||
|
break;
|
||||||
|
case GameEvent::DELETE_CAST_COUNT:
|
||||||
|
eventDeleteCastCount(event.GetExtension(Event_DeleteCastCount::ext));
|
||||||
|
break;
|
||||||
|
case GameEvent::SET_CAST_COUNT:
|
||||||
|
eventSetCastCount(event.GetExtension(Event_SetCastCount::ext));
|
||||||
|
break;
|
||||||
case GameEvent::DUMP_ZONE:
|
case GameEvent::DUMP_ZONE:
|
||||||
eventDumpZone(event.GetExtension(Event_DumpZone::ext));
|
eventDumpZone(event.GetExtension(Event_DumpZone::ext));
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,9 @@ 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_CreateCastCount;
|
||||||
|
class Event_DeleteCastCount;
|
||||||
|
class Event_SetCastCount;
|
||||||
class Event_Shuffle;
|
class Event_Shuffle;
|
||||||
class Event_GameLogNotice;
|
class Event_GameLogNotice;
|
||||||
|
|
||||||
|
|
@ -156,12 +158,18 @@ public:
|
||||||
/// Set a player-level counter value.
|
/// Set a player-level counter value.
|
||||||
void eventSetCounter(const Event_SetCounter &event);
|
void eventSetCounter(const Event_SetCounter &event);
|
||||||
|
|
||||||
/** @brief Show or hide a player-level counter without deleting it. */
|
|
||||||
void eventSetCounterActive(const Event_SetCounterActive &event);
|
|
||||||
|
|
||||||
/// Delete a player-level counter.
|
/// Delete a player-level counter.
|
||||||
void eventDelCounter(const Event_DelCounter &event);
|
void eventDelCounter(const Event_DelCounter &event);
|
||||||
|
|
||||||
|
/// Create a cast count.
|
||||||
|
void eventCreateCastCount(const Event_CreateCastCount &event);
|
||||||
|
|
||||||
|
/// Delete a cast count.
|
||||||
|
void eventDeleteCastCount(const Event_DeleteCastCount &event);
|
||||||
|
|
||||||
|
/// Set a cast count value.
|
||||||
|
void eventSetCastCount(const Event_SetCastCount &event);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
/** @name Zone-level operations
|
/** @name Zone-level operations
|
||||||
|
|
|
||||||
|
|
@ -292,6 +292,49 @@ void PlayerLogic::clearCounters()
|
||||||
}
|
}
|
||||||
qDeleteAll(counters);
|
qDeleteAll(counters);
|
||||||
counters.clear();
|
counters.clear();
|
||||||
|
|
||||||
|
for (int index : castCounts.keys()) {
|
||||||
|
emit castCountRemoved(index);
|
||||||
|
}
|
||||||
|
qDeleteAll(castCounts);
|
||||||
|
castCounts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
CounterState *PlayerLogic::addCastCount(int index)
|
||||||
|
{
|
||||||
|
if (castCounts.contains(index)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
QString name = CastCountIds::nameForIndex(index);
|
||||||
|
auto *state = new CounterState(index, name, QColor(128, 128, 128), 20, 0, true, this);
|
||||||
|
castCounts.insert(index, state);
|
||||||
|
emit castCountAdded(index, state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerLogic::delCastCount(int index)
|
||||||
|
{
|
||||||
|
auto *state = castCounts.take(index);
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit castCountRemoved(index);
|
||||||
|
state->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerLogic::setCastCountValue(int index, int value)
|
||||||
|
{
|
||||||
|
auto *state = castCounts.value(index, nullptr);
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state->setValue(value);
|
||||||
|
emit castCountValueChanged(index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
CounterState *PlayerLogic::getCastCount(int index) const
|
||||||
|
{
|
||||||
|
return castCounts.value(index, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
CounterState *PlayerLogic::getLifeCounter() const
|
CounterState *PlayerLogic::getLifeCounter() const
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,9 @@ signals:
|
||||||
void counterAdded(CounterState *state);
|
void counterAdded(CounterState *state);
|
||||||
void counterRemoved(int counterId);
|
void counterRemoved(int counterId);
|
||||||
void rearrangeCounters();
|
void rearrangeCounters();
|
||||||
|
void castCountAdded(int index, CounterState *state);
|
||||||
|
void castCountRemoved(int index);
|
||||||
|
void castCountValueChanged(int index, int value);
|
||||||
void activeChanged(bool active);
|
void activeChanged(bool active);
|
||||||
void zoneIdChanged(int zoneId);
|
void zoneIdChanged(int zoneId);
|
||||||
void concededChanged(int playerId, bool conceded);
|
void concededChanged(int playerId, bool conceded);
|
||||||
|
|
@ -225,6 +228,16 @@ public:
|
||||||
*/
|
*/
|
||||||
CounterState *getLifeCounter() const;
|
CounterState *getLifeCounter() const;
|
||||||
|
|
||||||
|
// Cast count management
|
||||||
|
CounterState *addCastCount(int index);
|
||||||
|
void delCastCount(int index);
|
||||||
|
void setCastCountValue(int index, int value);
|
||||||
|
CounterState *getCastCount(int index) const;
|
||||||
|
const QMap<int, CounterState *> &getCastCounts() const
|
||||||
|
{
|
||||||
|
return castCounts;
|
||||||
|
}
|
||||||
|
|
||||||
void setConceded(bool _conceded);
|
void setConceded(bool _conceded);
|
||||||
bool getConceded() const
|
bool getConceded() const
|
||||||
{
|
{
|
||||||
|
|
@ -259,6 +272,7 @@ private:
|
||||||
int zoneId;
|
int zoneId;
|
||||||
QMap<QString, CardZoneLogic *> zones;
|
QMap<QString, CardZoneLogic *> zones;
|
||||||
QMap<int, CounterState *> counters;
|
QMap<int, CounterState *> counters;
|
||||||
|
QMap<int, CounterState *> castCounts; // index (1-5) -> state
|
||||||
|
|
||||||
bool dialogSemaphore;
|
bool dialogSemaphore;
|
||||||
QList<CardItem *> cardsToDelete;
|
QList<CardItem *> cardsToDelete;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ AbstractCounter::AbstractCounter(CounterState *state,
|
||||||
setAcceptHoverEvents(true);
|
setAcceptHoverEvents(true);
|
||||||
|
|
||||||
// Route through the (possibly overridden) virtual setValue so subclasses such as
|
// Route through the (possibly overridden) virtual setValue so subclasses such as
|
||||||
// CommanderTaxCounter can clamp and refresh their tooltip on every value change.
|
// CastCountWidget can clamp and refresh their tooltip on every value change.
|
||||||
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) { setValue(newValue); });
|
connect(state, &CounterState::valueChanged, this, [this](int, int newValue) { setValue(newValue); });
|
||||||
|
|
||||||
connect(state, &CounterState::activeChanged, this, [this](bool newActive) {
|
connect(state, &CounterState::activeChanged, this, [this](bool newActive) {
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Sets the counter value and triggers a visual update.
|
* @brief Sets the counter value and triggers a visual update.
|
||||||
*
|
*
|
||||||
* Virtual to allow subclass display customization (e.g., CommanderTaxCounter tooltip updates).
|
* Virtual to allow subclass display customization (e.g., CastCountWidget tooltip updates).
|
||||||
* Overflow protection is handled server-side, not in client counter classes.
|
* Overflow protection is handled server-side, not in client counter classes.
|
||||||
*
|
*
|
||||||
* @param _value The new counter value
|
* @param _value The new counter value
|
||||||
|
|
@ -122,9 +122,9 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Shows or hides the counter and enables or disables its menu.
|
* @brief Shows or hides the counter and enables or disables its menu.
|
||||||
*
|
*
|
||||||
* Sole owner of both, so container layout code only positions counters and may read
|
* Sole owner of both visibility and menu state, so container layout code only positions
|
||||||
* isActive(). Qt AND-s visibility with the parent item's, so an active counter nested in a
|
* counters and may read isActive(). Note that Qt AND-s visibility with the parent item's,
|
||||||
* hidden zone (e.g. a tax counter in the command zone) still does not render.
|
* so an active counter nested in a hidden parent still does not render.
|
||||||
*
|
*
|
||||||
* @param _active True to show and enable the counter, false to hide and disable it
|
* @param _active True to show and enable the counter, false to hide and disable it
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,30 @@
|
||||||
#include "commander_tax_counter.h"
|
#include "cast_count_widget.h"
|
||||||
|
|
||||||
#include "../../game/board/counter_state.h"
|
#include "../../game/board/counter_state.h"
|
||||||
#include "translate_counter_name.h"
|
#include "translate_counter_name.h"
|
||||||
|
|
||||||
#include <QColor>
|
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
static constexpr qreal CORNER_RADIUS = 4.0;
|
static constexpr qreal CORNER_RADIUS = 4.0;
|
||||||
static constexpr qreal FONT_SIZE_RATIO = 0.6;
|
static constexpr qreal FONT_SIZE_RATIO = 0.6;
|
||||||
|
|
||||||
CommanderTaxCounter::CommanderTaxCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent)
|
CastCountWidget::CastCountWidget(CounterState *state, PlayerLogic *player, QGraphicsItem *parent)
|
||||||
: AbstractCounter(state, player, false, false, parent), size(state->getRadius())
|
: AbstractCounter(state, player, false, false, parent), size(state->getRadius())
|
||||||
{
|
{
|
||||||
setCacheMode(DeviceCoordinateCache);
|
setCacheMode(DeviceCoordinateCache);
|
||||||
setAcceptHoverEvents(true);
|
setAcceptHoverEvents(true);
|
||||||
|
|
||||||
setToolTip(tr("%1: %2").arg(TranslateCounterName::getDisplayName(getName())).arg(getValue()));
|
setToolTip(tr("%1: %2").arg(TranslateCounterName::getDisplayName(getName())).arg(getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
QRectF CommanderTaxCounter::boundingRect() const
|
QRectF CastCountWidget::boundingRect() const
|
||||||
{
|
{
|
||||||
return QRectF(0, 0, size, size);
|
return QRectF(0, 0, size, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommanderTaxCounter::paint(QPainter *painter,
|
void CastCountWidget::paint(QPainter *painter,
|
||||||
[[maybe_unused]] const QStyleOptionGraphicsItem *option,
|
[[maybe_unused]] const QStyleOptionGraphicsItem *option,
|
||||||
[[maybe_unused]] QWidget *widget)
|
[[maybe_unused]] QWidget *widget)
|
||||||
{
|
{
|
||||||
painter->save();
|
painter->save();
|
||||||
|
|
||||||
|
|
@ -46,7 +44,7 @@ void CommanderTaxCounter::paint(QPainter *painter,
|
||||||
painter->restore();
|
painter->restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommanderTaxCounter::setValue(int _value)
|
void CastCountWidget::setValue(int _value)
|
||||||
{
|
{
|
||||||
int clampedValue = qMax(0, _value);
|
int clampedValue = qMax(0, _value);
|
||||||
AbstractCounter::setValue(clampedValue);
|
AbstractCounter::setValue(clampedValue);
|
||||||
35
cockatrice/src/game_graphics/board/cast_count_widget.h
Normal file
35
cockatrice/src/game_graphics/board/cast_count_widget.h
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* @file cast_count_widget.h
|
||||||
|
* @ingroup GameGraphicsBoard
|
||||||
|
* @brief Displays a single cast count overlay counter in the command zone.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COCKATRICE_CAST_COUNT_WIDGET_H
|
||||||
|
#define COCKATRICE_CAST_COUNT_WIDGET_H
|
||||||
|
|
||||||
|
#include "abstract_counter.h"
|
||||||
|
|
||||||
|
namespace CastCountSizes
|
||||||
|
{
|
||||||
|
constexpr int MARGIN = 2;
|
||||||
|
} // namespace CastCountSizes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class CastCountWidget
|
||||||
|
* @brief A compact counter widget for tracking cast counts in the command zone.
|
||||||
|
*/
|
||||||
|
class CastCountWidget : public AbstractCounter
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private:
|
||||||
|
int size;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CastCountWidget(CounterState *state, PlayerLogic *player, QGraphicsItem *parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] QRectF boundingRect() const override;
|
||||||
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||||
|
void setValue(int _value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COCKATRICE_CAST_COUNT_WIDGET_H
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
/**
|
|
||||||
* @file commander_tax_counter.h
|
|
||||||
* @ingroup GameGraphicsPlayers
|
|
||||||
* @brief Square counter for commander tax, clamped to non-negative values.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COCKATRICE_COMMANDER_TAX_COUNTER_H
|
|
||||||
#define COCKATRICE_COMMANDER_TAX_COUNTER_H
|
|
||||||
|
|
||||||
#include "abstract_counter.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @namespace TaxCounterSizes
|
|
||||||
* @brief Layout constants for commander tax counters.
|
|
||||||
*/
|
|
||||||
namespace TaxCounterSizes
|
|
||||||
{
|
|
||||||
|
|
||||||
/** @brief Margin around and between tax counter icons */
|
|
||||||
constexpr int TAX_COUNTER_MARGIN = 2;
|
|
||||||
|
|
||||||
} // namespace TaxCounterSizes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class CommanderTaxCounter
|
|
||||||
* @brief Counter for tracking commander tax in Commander format.
|
|
||||||
*
|
|
||||||
* Displays the number of times the commander has been cast from the command
|
|
||||||
* zone. Can be adjusted manually via +1/-1 menu actions, or automatically
|
|
||||||
* incremented when using "Play and Increase Tax" on an accepted cast from
|
|
||||||
* the command zone. Values are clamped to >= 0.
|
|
||||||
*
|
|
||||||
* Appearance: square with rounded corners, semi-transparent background,
|
|
||||||
* positioned at top-left of command zone.
|
|
||||||
*
|
|
||||||
* Two instances per player: CounterIds::CommanderTax and CounterIds::PartnerTax.
|
|
||||||
* Each counter supports an active/inactive state (inherited from AbstractCounter):
|
|
||||||
* commander tax starts active; partner tax starts inactive until explicitly
|
|
||||||
* enabled by the player via the context menu.
|
|
||||||
*
|
|
||||||
* @see AbstractCounter
|
|
||||||
* @see AbstractCounter::setActive()
|
|
||||||
* @see CounterIds
|
|
||||||
*/
|
|
||||||
class CommanderTaxCounter : public AbstractCounter
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
private:
|
|
||||||
int size; ///< Width and height of the counter in pixels
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Constructs a CommanderTaxCounter.
|
|
||||||
* @param state Counter state containing id, name, value, etc.
|
|
||||||
* @param player The player who owns this counter
|
|
||||||
* @param parent Parent graphics item (typically the command zone)
|
|
||||||
*/
|
|
||||||
CommanderTaxCounter(CounterState *state, PlayerLogic *player, QGraphicsItem *parent = nullptr);
|
|
||||||
|
|
||||||
[[nodiscard]] QRectF boundingRect() const override;
|
|
||||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Overrides AbstractCounter::setValue to clamp values to >= 0 and update the tooltip.
|
|
||||||
* @param _value New value (clamped if negative)
|
|
||||||
*/
|
|
||||||
void setValue(int _value) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // COCKATRICE_COMMANDER_TAX_COUNTER_H
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#include "translate_counter_name.h"
|
#include "translate_counter_name.h"
|
||||||
|
|
||||||
#include <libcockatrice/utility/counter_ids.h>
|
|
||||||
|
|
||||||
const QMap<QString, QString> TranslateCounterName::translated = {
|
const QMap<QString, QString> TranslateCounterName::translated = {
|
||||||
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},
|
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},
|
||||||
{"w", QT_TRANSLATE_NOOP("TranslateCounterName", "White")},
|
{"w", QT_TRANSLATE_NOOP("TranslateCounterName", "White")},
|
||||||
|
|
@ -11,8 +9,8 @@ const QMap<QString, QString> TranslateCounterName::translated = {
|
||||||
{"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")},
|
||||||
{CounterNames::TaxCounter1, QT_TRANSLATE_NOOP("TranslateCounterName", "1st Tax")},
|
{"cast_count_1", QT_TRANSLATE_NOOP("TranslateCounterName", "1st Cast Count")},
|
||||||
{CounterNames::TaxCounter2, QT_TRANSLATE_NOOP("TranslateCounterName", "2nd Tax")},
|
{"cast_count_2", QT_TRANSLATE_NOOP("TranslateCounterName", "2nd Cast Count")},
|
||||||
{CounterNames::TaxCounter3, QT_TRANSLATE_NOOP("TranslateCounterName", "3rd Tax")},
|
{"cast_count_3", QT_TRANSLATE_NOOP("TranslateCounterName", "3rd Cast Count")},
|
||||||
{CounterNames::TaxCounter4, QT_TRANSLATE_NOOP("TranslateCounterName", "4th Tax")},
|
{"cast_count_4", QT_TRANSLATE_NOOP("TranslateCounterName", "4th Cast Count")},
|
||||||
{CounterNames::TaxCounter5, QT_TRANSLATE_NOOP("TranslateCounterName", "5th Tax")}};
|
{"cast_count_5", QT_TRANSLATE_NOOP("TranslateCounterName", "5th Cast Count")}};
|
||||||
|
|
|
||||||
|
|
@ -672,27 +672,11 @@ void MessageLogWidget::logSetCardCounter(PlayerLogic *player, QString cardName,
|
||||||
|
|
||||||
void MessageLogWidget::logSetCounter(PlayerLogic *player, int counterId, QString counterName, int value, int oldValue)
|
void MessageLogWidget::logSetCounter(PlayerLogic *player, int counterId, QString counterName, int value, int oldValue)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(counterId);
|
||||||
if (counterName == "life") {
|
if (counterName == "life") {
|
||||||
soundEngine->playSound("life_change");
|
soundEngine->playSound("life_change");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CounterIds::isTaxCounter(counterId)) {
|
|
||||||
QString playerName = sanitizeHtml(player->getPlayerInfo()->getName());
|
|
||||||
QString valueStr = QString("<font class=\"blue\">%1</font>").arg(value);
|
|
||||||
int delta = value - oldValue;
|
|
||||||
QString deltaStr = QString::number(delta);
|
|
||||||
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
|
|
||||||
QString taxLabel = QString("<font class=\"blue\">%1</font>").arg(sanitizeHtml(counterDisplayName));
|
|
||||||
if (value > oldValue) {
|
|
||||||
// delta > 0 here; the format string supplies the leading '+'
|
|
||||||
appendHtmlServerMessage(tr("%1 increases %2 to %3 (+%4).").arg(playerName, taxLabel, valueStr, deltaStr));
|
|
||||||
} else {
|
|
||||||
// delta < 0 here; deltaStr already carries the '-' sign
|
|
||||||
appendHtmlServerMessage(tr("%1 decreases %2 to %3 (%4).").arg(playerName, taxLabel, valueStr, deltaStr));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString counterDisplayName = TranslateCounterName::getDisplayName(counterName);
|
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()))
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,10 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho
|
||||||
aUnattach = makeAction(this, [actions, sel]() { actions->actUnattach(sel()); });
|
aUnattach = makeAction(this, [actions, sel]() { actions->actUnattach(sel()); });
|
||||||
aSetAnnotation = makeAction(this, [actions, sel]() { actions->actRequestSetAnnotationDialog(sel()); });
|
aSetAnnotation = makeAction(this, [actions, sel]() { actions->actRequestSetAnnotationDialog(sel()); });
|
||||||
aPlay = makeAction(this, [actions, sel]() { actions->actPlay(sel()); });
|
aPlay = makeAction(this, [actions, sel]() { actions->actPlay(sel()); });
|
||||||
aPlayAndIncrease1stTax = makeAction(this, [actions, sel]() { actions->actPlayAndIncrease1stTax(sel()); });
|
aPlayAndIncrease1stCastCount =
|
||||||
aPlayAndIncrease2ndTax = makeAction(this, [actions, sel]() { actions->actPlayAndIncrease2ndTax(sel()); });
|
makeAction(this, [actions, sel]() { actions->actPlayAndIncrease1stCastCount(sel()); });
|
||||||
|
aPlayAndIncrease2ndCastCount =
|
||||||
|
makeAction(this, [actions, sel]() { actions->actPlayAndIncrease2ndCastCount(sel()); });
|
||||||
aPlayFacedown = makeAction(this, [actions, sel]() { actions->actPlayFacedown(sel()); });
|
aPlayFacedown = makeAction(this, [actions, sel]() { actions->actPlayFacedown(sel()); });
|
||||||
aHide = makeAction(this, [actions, sel]() { actions->actHide(sel()); });
|
aHide = makeAction(this, [actions, sel]() { actions->actHide(sel()); });
|
||||||
aReduceLifeByPower = makeAction(this, [actions, sel]() { actions->actReduceLifeByPower(sel()); });
|
aReduceLifeByPower = makeAction(this, [actions, sel]() { actions->actReduceLifeByPower(sel()); });
|
||||||
|
|
@ -166,16 +168,15 @@ CardMenu::CardMenu(PlayerGraphicsItem *_player, const CardItem *_card, bool _sho
|
||||||
if (writeableCard) {
|
if (writeableCard) {
|
||||||
addAction(aPlay);
|
addAction(aPlay);
|
||||||
|
|
||||||
// Only offer for single selection: a multi-select would over-count casts by
|
// Only offer for single selection: a multi-select would over-count casts
|
||||||
// bumping one commander's tax counter once per command-zone card.
|
|
||||||
const bool singleSelection = gameScene->selectedCards().size() <= 1;
|
const bool singleSelection = gameScene->selectedCards().size() <= 1;
|
||||||
|
|
||||||
if (singleSelection && player->getTaxCounterIfActive(CounterIds::TaxCounter1)) {
|
if (singleSelection && player->getCastCountWidget(1)) {
|
||||||
addAction(aPlayAndIncrease1stTax);
|
addAction(aPlayAndIncrease1stCastCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (singleSelection && player->getTaxCounterIfActive(CounterIds::TaxCounter2)) {
|
if (singleSelection && player->getCastCountWidget(2)) {
|
||||||
addAction(aPlayAndIncrease2ndTax);
|
addAction(aPlayAndIncrease2ndCastCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No reveal submenu - command zone is public
|
// No reveal submenu - command zone is public
|
||||||
|
|
@ -524,8 +525,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"));
|
||||||
aPlayAndIncrease1stTax->setText(tr("Play and &Increase 1st Tax"));
|
aPlayAndIncrease1stCastCount->setText(tr("Play and &Increase 1st Cast Count"));
|
||||||
aPlayAndIncrease2ndTax->setText(tr("Play and Increase &2nd Tax"));
|
aPlayAndIncrease2ndCastCount->setText(tr("Play and Increase &2nd Cast Count"));
|
||||||
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,8 +32,8 @@ public:
|
||||||
QMenu *mCardCounters;
|
QMenu *mCardCounters;
|
||||||
|
|
||||||
QAction *aPlay, *aPlayFacedown;
|
QAction *aPlay, *aPlayFacedown;
|
||||||
/** @brief Play actions that also increment the corresponding tax counter. */
|
/** @brief Play actions that also increment the corresponding cast count. */
|
||||||
QAction *aPlayAndIncrease1stTax, *aPlayAndIncrease2ndTax;
|
QAction *aPlayAndIncrease1stCastCount, *aPlayAndIncrease2ndCastCount;
|
||||||
QAction *aRevealToAll;
|
QAction *aRevealToAll;
|
||||||
QAction *aHide;
|
QAction *aHide;
|
||||||
QAction *aClone;
|
QAction *aClone;
|
||||||
|
|
|
||||||
|
|
@ -14,42 +14,41 @@
|
||||||
|
|
||||||
CommandZoneMenu::CommandZoneMenu(PlayerGraphicsItem *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player)
|
CommandZoneMenu::CommandZoneMenu(PlayerGraphicsItem *_player, QMenu *playerMenu) : QMenu(playerMenu), player(_player)
|
||||||
{
|
{
|
||||||
// Shortcuts only for first two tax counters (matching legacy behavior)
|
inc1ShortcutKey = QStringLiteral("Player/aIncrease1stCastCount");
|
||||||
incTax1ShortcutKey = QStringLiteral("Player/aAddCommanderTax");
|
dec1ShortcutKey = QStringLiteral("Player/aDecrease1stCastCount");
|
||||||
decTax1ShortcutKey = QStringLiteral("Player/aRemoveCommanderTax");
|
inc2ShortcutKey = QStringLiteral("Player/aIncrease2ndCastCount");
|
||||||
incTax2ShortcutKey = QStringLiteral("Player/aAddPartnerTax");
|
dec2ShortcutKey = QStringLiteral("Player/aDecrease2ndCastCount");
|
||||||
decTax2ShortcutKey = QStringLiteral("Player/aRemovePartnerTax");
|
|
||||||
|
|
||||||
PlayerLogic *logic = player->getLogic();
|
PlayerLogic *logic = player->getLogic();
|
||||||
if (logic && logic->getPlayerInfo()->getLocalOrJudge()) {
|
if (logic && logic->getPlayerInfo()->getLocalOrJudge()) {
|
||||||
for (int i = 0; i < TaxCounterCount; ++i) {
|
for (int i = 0; i < CastCountCount; ++i) {
|
||||||
int counterId = CounterIds::taxCounterIdFromIndex(i);
|
int index = i + 1; // 1-based index
|
||||||
|
|
||||||
aIncreaseTax[i] = new QAction(this);
|
aIncrease[i] = new QAction(this);
|
||||||
connect(aIncreaseTax[i], &QAction::triggered, this, [this, counterId]() {
|
connect(aIncrease[i], &QAction::triggered, this, [this, index]() {
|
||||||
if (auto *l = player->getLogic()) {
|
if (auto *l = player->getLogic()) {
|
||||||
l->getPlayerActions()->actModifyTaxCounter(counterId, 1);
|
l->getPlayerActions()->actModifyCastCount(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addAction(aIncreaseTax[i]);
|
addAction(aIncrease[i]);
|
||||||
|
|
||||||
aDecreaseTax[i] = new QAction(this);
|
aDecrease[i] = new QAction(this);
|
||||||
connect(aDecreaseTax[i], &QAction::triggered, this, [this, counterId]() {
|
connect(aDecrease[i], &QAction::triggered, this, [this, index]() {
|
||||||
if (auto *l = player->getLogic()) {
|
if (auto *l = player->getLogic()) {
|
||||||
l->getPlayerActions()->actModifyTaxCounter(counterId, -1);
|
l->getPlayerActions()->actModifyCastCount(index, -1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addAction(aDecreaseTax[i]);
|
addAction(aDecrease[i]);
|
||||||
|
|
||||||
addSeparator();
|
addSeparator();
|
||||||
|
|
||||||
aToggleTax[i] = new QAction(this);
|
aToggle[i] = new QAction(this);
|
||||||
connect(aToggleTax[i], &QAction::triggered, this, [this, counterId]() {
|
connect(aToggle[i], &QAction::triggered, this, [this, index]() {
|
||||||
if (auto *l = player->getLogic()) {
|
if (auto *l = player->getLogic()) {
|
||||||
l->getPlayerActions()->actToggleTaxCounter(counterId);
|
l->getPlayerActions()->actToggleCastCount(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addAction(aToggleTax[i]);
|
addAction(aToggle[i]);
|
||||||
|
|
||||||
addSeparator();
|
addSeparator();
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +57,7 @@ CommandZoneMenu::CommandZoneMenu(PlayerGraphicsItem *_player, QMenu *playerMenu)
|
||||||
connect(aToggleMinimized, &QAction::triggered, this, &CommandZoneMenu::actToggleMinimized);
|
connect(aToggleMinimized, &QAction::triggered, this, &CommandZoneMenu::actToggleMinimized);
|
||||||
addAction(aToggleMinimized);
|
addAction(aToggleMinimized);
|
||||||
|
|
||||||
connect(this, &QMenu::aboutToShow, this, &CommandZoneMenu::updateTaxCounterActionStates);
|
connect(this, &QMenu::aboutToShow, this, &CommandZoneMenu::updateCastCountActionStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
|
|
@ -70,14 +69,13 @@ void CommandZoneMenu::retranslateUi()
|
||||||
|
|
||||||
static const char *ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
|
static const char *ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
|
||||||
|
|
||||||
for (int i = 0; i < TaxCounterCount; ++i) {
|
for (int i = 0; i < CastCountCount; ++i) {
|
||||||
if (aIncreaseTax[i]) {
|
if (aIncrease[i]) {
|
||||||
aIncreaseTax[i]->setText(tr("&Increase %1 Tax (+1)").arg(ordinals[i]));
|
aIncrease[i]->setText(tr("&Increase %1 Cast Count (+1)").arg(ordinals[i]));
|
||||||
}
|
}
|
||||||
if (aDecreaseTax[i]) {
|
if (aDecrease[i]) {
|
||||||
aDecreaseTax[i]->setText(tr("&Decrease %1 Tax (-1)").arg(ordinals[i]));
|
aDecrease[i]->setText(tr("&Decrease %1 Cast Count (-1)").arg(ordinals[i]));
|
||||||
}
|
}
|
||||||
// Toggle action labels are derived dynamically in updateTaxCounterActionStates()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aToggleMinimized) {
|
if (aToggleMinimized) {
|
||||||
|
|
@ -93,44 +91,39 @@ void CommandZoneMenu::actToggleMinimized()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandZoneMenu::updateTaxCounterActionStates()
|
void CommandZoneMenu::updateCastCountActionStates()
|
||||||
{
|
{
|
||||||
static const char *ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
|
static const char *ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
|
||||||
|
|
||||||
// Collect all tax counter states
|
std::array<AbstractCounter *, CastCountCount> castCounts{};
|
||||||
std::array<AbstractCounter *, TaxCounterCount> taxCounters{};
|
for (int i = 0; i < CastCountCount; ++i) {
|
||||||
for (int i = 0; i < TaxCounterCount; ++i) {
|
castCounts[i] = player->getCastCountWidget(i + 1);
|
||||||
taxCounters[i] = player->getTaxCounterIfActive(CounterIds::taxCounterIdFromIndex(i));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find highest active tax counter index
|
|
||||||
int highestActive = -1;
|
int highestActive = -1;
|
||||||
for (int i = TaxCounterCount - 1; i >= 0; --i) {
|
for (int i = CastCountCount - 1; i >= 0; --i) {
|
||||||
if (taxCounters[i]) {
|
if (castCounts[i]) {
|
||||||
highestActive = i;
|
highestActive = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < TaxCounterCount; ++i) {
|
for (int i = 0; i < CastCountCount; ++i) {
|
||||||
AbstractCounter *counter = taxCounters[i];
|
AbstractCounter *counter = castCounts[i];
|
||||||
|
|
||||||
if (aIncreaseTax[i]) {
|
if (aIncrease[i]) {
|
||||||
aIncreaseTax[i]->setVisible(counter && counter->getValue() < MAX_COUNTER_VALUE);
|
aIncrease[i]->setVisible(counter && counter->getValue() < MAX_COUNTER_VALUE);
|
||||||
}
|
}
|
||||||
if (aDecreaseTax[i]) {
|
if (aDecrease[i]) {
|
||||||
aDecreaseTax[i]->setVisible(counter && counter->getValue() > 0);
|
aDecrease[i]->setVisible(counter && counter->getValue() > 0);
|
||||||
}
|
}
|
||||||
if (aToggleTax[i]) {
|
if (aToggle[i]) {
|
||||||
aToggleTax[i]->setText(counter ? tr("&Remove %1 Tax").arg(ordinals[i])
|
aToggle[i]->setText(counter ? tr("&Remove %1 Cast Count").arg(ordinals[i])
|
||||||
: tr("&Add %1 Tax").arg(ordinals[i]));
|
: tr("&Add %1 Cast Count").arg(ordinals[i]));
|
||||||
|
|
||||||
// Toggle visible if:
|
bool canAdd = !counter && (i == 0 || castCounts[i - 1]);
|
||||||
// - Counter doesn't exist and previous counter is active (can add next in sequence)
|
|
||||||
// - Counter exists with value 0 and is the highest active (can remove last in sequence)
|
|
||||||
bool canAdd = !counter && (i == 0 || taxCounters[i - 1]);
|
|
||||||
bool canRemove = counter && counter->getValue() == 0 && i == highestActive;
|
bool canRemove = counter && counter->getValue() == 0 && i == highestActive;
|
||||||
aToggleTax[i]->setVisible(canAdd || canRemove);
|
aToggle[i]->setVisible(canAdd || canRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,34 +137,32 @@ void CommandZoneMenu::setShortcutsActive()
|
||||||
{
|
{
|
||||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||||
|
|
||||||
// Only first two tax counters have shortcuts
|
if (aIncrease[0]) {
|
||||||
if (aIncreaseTax[0]) {
|
aIncrease[0]->setShortcuts(shortcuts.getShortcut(inc1ShortcutKey));
|
||||||
aIncreaseTax[0]->setShortcuts(shortcuts.getShortcut(incTax1ShortcutKey));
|
|
||||||
}
|
}
|
||||||
if (aDecreaseTax[0]) {
|
if (aDecrease[0]) {
|
||||||
aDecreaseTax[0]->setShortcuts(shortcuts.getShortcut(decTax1ShortcutKey));
|
aDecrease[0]->setShortcuts(shortcuts.getShortcut(dec1ShortcutKey));
|
||||||
}
|
}
|
||||||
if (aIncreaseTax[1]) {
|
if (aIncrease[1]) {
|
||||||
aIncreaseTax[1]->setShortcuts(shortcuts.getShortcut(incTax2ShortcutKey));
|
aIncrease[1]->setShortcuts(shortcuts.getShortcut(inc2ShortcutKey));
|
||||||
}
|
}
|
||||||
if (aDecreaseTax[1]) {
|
if (aDecrease[1]) {
|
||||||
aDecreaseTax[1]->setShortcuts(shortcuts.getShortcut(decTax2ShortcutKey));
|
aDecrease[1]->setShortcuts(shortcuts.getShortcut(dec2ShortcutKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandZoneMenu::setShortcutsInactive()
|
void CommandZoneMenu::setShortcutsInactive()
|
||||||
{
|
{
|
||||||
// Only first two tax counters have shortcuts
|
if (aIncrease[0]) {
|
||||||
if (aIncreaseTax[0]) {
|
aIncrease[0]->setShortcut(QKeySequence());
|
||||||
aIncreaseTax[0]->setShortcut(QKeySequence());
|
|
||||||
}
|
}
|
||||||
if (aDecreaseTax[0]) {
|
if (aDecrease[0]) {
|
||||||
aDecreaseTax[0]->setShortcut(QKeySequence());
|
aDecrease[0]->setShortcut(QKeySequence());
|
||||||
}
|
}
|
||||||
if (aIncreaseTax[1]) {
|
if (aIncrease[1]) {
|
||||||
aIncreaseTax[1]->setShortcut(QKeySequence());
|
aIncrease[1]->setShortcut(QKeySequence());
|
||||||
}
|
}
|
||||||
if (aDecreaseTax[1]) {
|
if (aDecrease[1]) {
|
||||||
aDecreaseTax[1]->setShortcut(QKeySequence());
|
aDecrease[1]->setShortcut(QKeySequence());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* @file command_zone_menu.h
|
* @file command_zone_menu.h
|
||||||
* @ingroup GameMenusZones
|
* @ingroup GameGraphicsPlayers
|
||||||
* @brief Context menu for command zone right-click actions.
|
* @brief Submenu for command zone actions (cast counts, minimize/restore).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef COCKATRICE_COMMAND_ZONE_MENU_H
|
#ifndef COCKATRICE_COMMAND_ZONE_MENU_H
|
||||||
|
|
@ -17,10 +17,10 @@ class PlayerGraphicsItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class CommandZoneMenu
|
* @class CommandZoneMenu
|
||||||
* @brief Context menu for the command zone.
|
* @brief Player submenu for managing cast counts and command zone display.
|
||||||
*
|
*
|
||||||
* Appears when right-clicking on the command zone. Provides actions for
|
* Provides actions to increment/decrement each cast count, toggle cast counts
|
||||||
* adjusting tax counters (up to 5) and toggling minimized state.
|
* on/off, and minimize/restore the command zone display.
|
||||||
*
|
*
|
||||||
* @see PlayerMenu
|
* @see PlayerMenu
|
||||||
* @see CommandZone
|
* @see CommandZone
|
||||||
|
|
@ -36,15 +36,15 @@ public:
|
||||||
void setShortcutsInactive() override;
|
void setShortcutsInactive() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr int TaxCounterCount = CounterIds::TaxCounterCount;
|
static constexpr int CastCountCount = CastCountIds::Count;
|
||||||
|
|
||||||
std::array<QAction *, TaxCounterCount> aIncreaseTax{};
|
std::array<QAction *, CastCountCount> aIncrease{};
|
||||||
std::array<QAction *, TaxCounterCount> aDecreaseTax{};
|
std::array<QAction *, CastCountCount> aDecrease{};
|
||||||
std::array<QAction *, TaxCounterCount> aToggleTax{};
|
std::array<QAction *, CastCountCount> aToggle{};
|
||||||
QAction *aToggleMinimized = nullptr;
|
QAction *aToggleMinimized = nullptr;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateTaxCounterActionStates();
|
void updateCastCountActionStates();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void actToggleMinimized();
|
void actToggleMinimized();
|
||||||
|
|
@ -52,11 +52,10 @@ private slots:
|
||||||
private:
|
private:
|
||||||
PlayerGraphicsItem *player;
|
PlayerGraphicsItem *player;
|
||||||
|
|
||||||
// Shortcuts only for first two tax counters
|
QString inc1ShortcutKey;
|
||||||
QString incTax1ShortcutKey;
|
QString dec1ShortcutKey;
|
||||||
QString decTax1ShortcutKey;
|
QString inc2ShortcutKey;
|
||||||
QString incTax2ShortcutKey;
|
QString dec2ShortcutKey;
|
||||||
QString decTax2ShortcutKey;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_COMMAND_ZONE_MENU_H
|
#endif // COCKATRICE_COMMAND_ZONE_MENU_H
|
||||||
|
|
|
||||||
|
|
@ -3,7 +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/cast_count_widget.h"
|
||||||
#include "../board/counter_general.h"
|
#include "../board/counter_general.h"
|
||||||
#include "../hand_counter.h"
|
#include "../hand_counter.h"
|
||||||
#include "../z_values.h"
|
#include "../z_values.h"
|
||||||
|
|
@ -32,6 +32,8 @@ PlayerGraphicsItem::PlayerGraphicsItem(PlayerLogic *_player) : player(_player)
|
||||||
|
|
||||||
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
|
connect(player, &PlayerLogic::counterAdded, this, &PlayerGraphicsItem::onCounterAdded);
|
||||||
connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved);
|
connect(player, &PlayerLogic::counterRemoved, this, &PlayerGraphicsItem::onCounterRemoved);
|
||||||
|
connect(player, &PlayerLogic::castCountAdded, this, &PlayerGraphicsItem::onCastCountAdded);
|
||||||
|
connect(player, &PlayerLogic::castCountRemoved, this, &PlayerGraphicsItem::onCastCountRemoved);
|
||||||
|
|
||||||
playerMenu = new PlayerMenu(this);
|
playerMenu = new PlayerMenu(this);
|
||||||
|
|
||||||
|
|
@ -231,34 +233,12 @@ 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 (CounterIds::isTaxCounter(state->getId())) {
|
|
||||||
if (!commandZoneGraphicsItem) {
|
|
||||||
qWarning() << "Cannot create tax counter" << state->getName() << "- command zone not available";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Qt parent (commandZoneGraphicsItem) owns widget; counterWidgets map holds reference
|
|
||||||
// for lookup; CommandZone::registerTaxCounter connects QObject::destroyed for cleanup
|
|
||||||
widget = new CommanderTaxCounter(state, player, commandZoneGraphicsItem);
|
|
||||||
widget->setActive(state->isActive());
|
|
||||||
commandZoneGraphicsItem->registerTaxCounter(widget);
|
|
||||||
|
|
||||||
if (auto *menu = playerMenu->getCommandZoneMenu()) {
|
|
||||||
connect(state, &CounterState::activeChanged, menu, &CommandZoneMenu::updateTaxCounterActionStates);
|
|
||||||
connect(state, &CounterState::valueChanged, menu, &CommandZoneMenu::updateTaxCounterActionStates);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
widget = new GeneralCounter(state, player, true, this);
|
widget = new GeneralCounter(state, player, true, this);
|
||||||
}
|
}
|
||||||
counterWidgets.insert(state->getId(), widget);
|
counterWidgets.insert(state->getId(), widget);
|
||||||
|
|
||||||
// A counter's submenu follows its isActive() state: while inactive the counter is hidden and
|
|
||||||
// the server rejects every modification. Only tax counters go inactive today.
|
|
||||||
setCounterMenuRegistered(widget, state->isActive());
|
setCounterMenuRegistered(widget, state->isActive());
|
||||||
connect(state, &CounterState::activeChanged, this, [this, counterId = state->getId()](bool newActive) {
|
|
||||||
if (AbstractCounter *counter = getCounterWidget(counterId)) {
|
|
||||||
setCounterMenuRegistered(counter, newActive);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (playerMenu->getShortcutsActive()) {
|
if (playerMenu->getShortcutsActive()) {
|
||||||
widget->setShortcutsActive();
|
widget->setShortcutsActive();
|
||||||
|
|
@ -267,6 +247,25 @@ void PlayerGraphicsItem::onCounterAdded(CounterState *state)
|
||||||
rearrangeCounters();
|
rearrangeCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlayerGraphicsItem::onCastCountAdded(int index, CounterState *state)
|
||||||
|
{
|
||||||
|
if (!commandZoneGraphicsItem) {
|
||||||
|
qWarning() << "Cannot create cast count" << index << "- command zone not available";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *widget = new CastCountWidget(state, player, commandZoneGraphicsItem);
|
||||||
|
commandZoneGraphicsItem->registerCastCount(widget);
|
||||||
|
castCountWidgets.insert(index, widget);
|
||||||
|
|
||||||
|
if (auto *menu = playerMenu->getCommandZoneMenu()) {
|
||||||
|
menu->updateCastCountActionStates();
|
||||||
|
connect(state, &CounterState::valueChanged, menu, &CommandZoneMenu::updateCastCountActionStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
rearrangeCounters();
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerGraphicsItem::onCounterRemoved(int counterId)
|
void PlayerGraphicsItem::onCounterRemoved(int counterId)
|
||||||
{
|
{
|
||||||
auto *widget = counterWidgets.take(counterId);
|
auto *widget = counterWidgets.take(counterId);
|
||||||
|
|
@ -274,25 +273,37 @@ void PlayerGraphicsItem::onCounterRemoved(int counterId)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCounterMenuRegistered(widget, false);
|
setCounterMenuRegistered(widget, false);
|
||||||
if (commandZoneGraphicsItem && CounterIds::isTaxCounter(widget->getId())) {
|
widget->delCounter();
|
||||||
commandZoneGraphicsItem->unregisterTaxCounter(widget);
|
rearrangeCounters();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerGraphicsItem::onCastCountRemoved(int index)
|
||||||
|
{
|
||||||
|
auto *widget = castCountWidgets.take(index);
|
||||||
|
if (!widget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (commandZoneGraphicsItem) {
|
||||||
|
commandZoneGraphicsItem->unregisterCastCount(widget);
|
||||||
}
|
}
|
||||||
widget->delCounter();
|
widget->delCounter();
|
||||||
|
|
||||||
|
if (auto *menu = playerMenu->getCommandZoneMenu()) {
|
||||||
|
menu->updateCastCountActionStates();
|
||||||
|
}
|
||||||
|
|
||||||
rearrangeCounters();
|
rearrangeCounters();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerGraphicsItem::rearrangeCounters()
|
void PlayerGraphicsItem::rearrangeCounters()
|
||||||
{
|
{
|
||||||
if (commandZoneGraphicsItem) {
|
if (commandZoneGraphicsItem) {
|
||||||
commandZoneGraphicsItem->rearrangeTaxCounters();
|
commandZoneGraphicsItem->rearrangeCastCounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (CounterIds::isTaxCounter(ctr->getId())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!ctr->getShownInCounterArea()) {
|
if (!ctr->getShownInCounterArea()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -302,10 +313,9 @@ void PlayerGraphicsItem::rearrangeCounters()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractCounter *PlayerGraphicsItem::getTaxCounterIfActive(int counterId) const
|
AbstractCounter *PlayerGraphicsItem::getCastCountWidget(int index) const
|
||||||
{
|
{
|
||||||
AbstractCounter *counter = getCounterWidget(counterId);
|
return castCountWidgets.value(index, nullptr);
|
||||||
return (counter && counter->isActive()) ? counter : nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerGraphicsItem::rearrangeZones()
|
void PlayerGraphicsItem::rearrangeZones()
|
||||||
|
|
|
||||||
|
|
@ -123,14 +123,16 @@ public:
|
||||||
{
|
{
|
||||||
return counterWidgets.value(counterId, nullptr);
|
return counterWidgets.value(counterId, nullptr);
|
||||||
}
|
}
|
||||||
/** @brief Returns the tax counter if it exists and is active, or nullptr otherwise. */
|
/** @brief Returns the cast count widget for the given index (1-5), or nullptr if not found. */
|
||||||
[[nodiscard]] AbstractCounter *getTaxCounterIfActive(int counterId) const;
|
[[nodiscard]] AbstractCounter *getCastCountWidget(int index) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onPlayerActiveChanged(bool _active);
|
void onPlayerActiveChanged(bool _active);
|
||||||
void onCustomZoneAdded(QString customZoneName);
|
void onCustomZoneAdded(QString customZoneName);
|
||||||
void onCounterAdded(CounterState *state);
|
void onCounterAdded(CounterState *state);
|
||||||
void onCounterRemoved(int counterId);
|
void onCounterRemoved(int counterId);
|
||||||
|
void onCastCountAdded(int index, CounterState *state);
|
||||||
|
void onCastCountRemoved(int index);
|
||||||
void rearrangeCounters();
|
void rearrangeCounters();
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
|
|
||||||
|
|
@ -147,6 +149,7 @@ private:
|
||||||
PlayerArea *playerArea;
|
PlayerArea *playerArea;
|
||||||
PlayerTarget *playerTarget;
|
PlayerTarget *playerTarget;
|
||||||
QMap<int, AbstractCounter *> counterWidgets;
|
QMap<int, AbstractCounter *> counterWidgets;
|
||||||
|
QMap<int, AbstractCounter *> castCountWidgets;
|
||||||
QMap<QString, CardZone *> zoneGraphicsItems;
|
QMap<QString, CardZone *> zoneGraphicsItems;
|
||||||
PileZone *deckZoneGraphicsItem;
|
PileZone *deckZoneGraphicsItem;
|
||||||
PileZone *sideboardGraphicsItem;
|
PileZone *sideboardGraphicsItem;
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ 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 */
|
/** @brief Cast count overlay */
|
||||||
constexpr qreal TAX_COUNTERS = ZValueLayerManager::overlayZValue(2.0);
|
constexpr qreal CAST_COUNTS = 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);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include "../board/abstract_counter.h"
|
#include "../board/abstract_counter.h"
|
||||||
#include "../board/card_drag_item.h"
|
#include "../board/card_drag_item.h"
|
||||||
#include "../board/card_item.h"
|
#include "../board/card_item.h"
|
||||||
#include "../board/commander_tax_counter.h"
|
#include "../board/cast_count_widget.h"
|
||||||
#include "../z_values.h"
|
#include "../z_values.h"
|
||||||
|
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
|
|
@ -26,7 +26,7 @@ CommandZone::CommandZone(CommandZoneLogic *_logic, int _zoneHeight, QGraphicsIte
|
||||||
|
|
||||||
CommandZone::~CommandZone()
|
CommandZone::~CommandZone()
|
||||||
{
|
{
|
||||||
for (AbstractCounter *ctr : taxCounters) {
|
for (AbstractCounter *ctr : castCounts) {
|
||||||
disconnect(ctr, &QObject::destroyed, this, nullptr);
|
disconnect(ctr, &QObject::destroyed, this, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ void CommandZone::setMinimumHeight(int height)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The floor only affects height while minimized; skip relayout if the displayed
|
// The floor only affects height while minimized; skip relayout if the displayed
|
||||||
// height is unchanged (e.g. a tax counter toggled while expanded).
|
// height is unchanged (e.g. a cast count toggled while expanded).
|
||||||
const qreal oldEffectiveHeight = currentHeight();
|
const qreal oldEffectiveHeight = currentHeight();
|
||||||
minimumHeight = height;
|
minimumHeight = height;
|
||||||
if (qFuzzyCompare(1.0 + currentHeight(), 1.0 + oldEffectiveHeight)) {
|
if (qFuzzyCompare(1.0 + currentHeight(), 1.0 + oldEffectiveHeight)) {
|
||||||
|
|
@ -145,38 +145,32 @@ void CommandZone::reorganizeCards()
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandZone::registerTaxCounter(AbstractCounter *counter)
|
void CommandZone::registerCastCount(AbstractCounter *counter)
|
||||||
{
|
{
|
||||||
if (!counter || taxCounters.contains(counter)) {
|
if (!counter || castCounts.contains(counter)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
taxCounters.append(counter);
|
castCounts.append(counter);
|
||||||
connect(counter, &QObject::destroyed, this, [this, counter]() { taxCounters.removeOne(counter); });
|
connect(counter, &QObject::destroyed, this, [this, counter]() { castCounts.removeOne(counter); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandZone::unregisterTaxCounter(AbstractCounter *counter)
|
void CommandZone::unregisterCastCount(AbstractCounter *counter)
|
||||||
{
|
{
|
||||||
if (!counter) {
|
if (!counter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
disconnect(counter, &QObject::destroyed, this, nullptr);
|
disconnect(counter, &QObject::destroyed, this, nullptr);
|
||||||
taxCounters.removeOne(counter);
|
castCounts.removeOne(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandZone::rearrangeTaxCounters()
|
void CommandZone::rearrangeCastCounts()
|
||||||
{
|
{
|
||||||
qreal y = TaxCounterSizes::TAX_COUNTER_MARGIN;
|
qreal y = CastCountSizes::MARGIN;
|
||||||
|
|
||||||
for (AbstractCounter *ctr : taxCounters) {
|
for (AbstractCounter *ctr : castCounts) {
|
||||||
ctr->setPos(TaxCounterSizes::TAX_COUNTER_MARGIN, y);
|
ctr->setPos(CastCountSizes::MARGIN, y);
|
||||||
ctr->setZValue(ZValues::TAX_COUNTERS);
|
ctr->setZValue(ZValues::CAST_COUNTS);
|
||||||
// Visibility is owned solely by AbstractCounter::setActive() (the counter's own flag),
|
y += ctr->getRadius() + CastCountSizes::MARGIN;
|
||||||
// which Qt AND-s with this CommandZone's visibility via child-visibility propagation
|
|
||||||
// (tax counters are graphics children of the zone). This function only handles layout,
|
|
||||||
// so it stacks and measures by isActive() alone.
|
|
||||||
if (ctr->isActive()) {
|
|
||||||
y += ctr->getRadius() + TaxCounterSizes::TAX_COUNTER_MARGIN;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setMinimumHeight(static_cast<int>(y));
|
setMinimumHeight(static_cast<int>(y));
|
||||||
|
|
|
||||||
|
|
@ -38,21 +38,21 @@ constexpr qreal COMMAND_ZONE_WIDTH = CardDimensions::WIDTH_F * 1.5;
|
||||||
*
|
*
|
||||||
* Always visible when enabled. Uses the generic vertical stacking layout
|
* Always visible when enabled. Uses the generic vertical stacking layout
|
||||||
* with bottom overflow enabled. Can be minimized via double-click (25% height,
|
* with bottom overflow enabled. Can be minimized via double-click (25% height,
|
||||||
* or the tax-counter floor if higher).
|
* or the cast count floor if higher).
|
||||||
*
|
*
|
||||||
* @see SelectZone::layoutCardsVertically for the stacking algorithm
|
* @see SelectZone::layoutCardsVertically for the stacking algorithm
|
||||||
* @see CommandZoneLogic for card data management
|
* @see CommandZoneLogic for card data management
|
||||||
* @see CommanderTaxCounter for the tax counter overlay
|
* @see CastCountWidget for the cast count overlay
|
||||||
*/
|
*/
|
||||||
class CommandZone : public SelectZone
|
class CommandZone : public SelectZone
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
static constexpr double MINIMIZED_HEIGHT_RATIO = 0.25;
|
static constexpr double MINIMIZED_HEIGHT_RATIO = 0.25;
|
||||||
int zoneHeight; ///< Full height in pixels when expanded
|
int zoneHeight; ///< Full height in pixels when expanded
|
||||||
bool minimized = false; ///< Whether zone is collapsed (25% height, or the tax-counter floor)
|
bool minimized = false; ///< Whether zone is collapsed (25% height, or the cast count floor)
|
||||||
int minimumHeight = 0; ///< Floor for minimized height (e.g. to fit tax counters)
|
int minimumHeight = 0; ///< Floor for minimized height (e.g. to fit cast counts)
|
||||||
QList<AbstractCounter *> taxCounters; ///< Registered tax counter widgets
|
QList<AbstractCounter *> castCounts; ///< Registered cast count widgets
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|
@ -86,24 +86,24 @@ public:
|
||||||
[[nodiscard]] bool isMinimized() const;
|
[[nodiscard]] bool isMinimized() const;
|
||||||
/** @brief Returns the current display height (full or minimized). */
|
/** @brief Returns the current display height (full or minimized). */
|
||||||
[[nodiscard]] qreal currentHeight() const;
|
[[nodiscard]] qreal currentHeight() const;
|
||||||
/** @brief Sets the minimum height floor, e.g. to ensure tax counters remain visible. */
|
/** @brief Sets the minimum height floor, e.g. to ensure cast counts remain visible. */
|
||||||
void setMinimumHeight(int height);
|
void setMinimumHeight(int height);
|
||||||
/** @brief Registers a tax counter widget for layout management. */
|
/** @brief Registers a cast count widget for layout management. */
|
||||||
void registerTaxCounter(AbstractCounter *counter);
|
void registerCastCount(AbstractCounter *counter);
|
||||||
/**
|
/**
|
||||||
* @brief Unregisters a tax counter widget from layout management.
|
* @brief Unregisters a cast count widget from layout management.
|
||||||
*
|
*
|
||||||
* Call on removal: the widget outlives it via deleteLater(), so unregister synchronously
|
* Call on removal: the widget outlives it via deleteLater(), so unregister synchronously
|
||||||
* to keep it out of the next layout pass.
|
* to keep it out of the next layout pass.
|
||||||
*/
|
*/
|
||||||
void unregisterTaxCounter(AbstractCounter *counter);
|
void unregisterCastCount(AbstractCounter *counter);
|
||||||
/** @brief Lays out visible tax counters vertically in the top-left corner of the command zone. */
|
/** @brief Lays out visible cast counts vertically in the top-left corner of the command zone. */
|
||||||
void rearrangeTaxCounters();
|
void rearrangeCastCounts();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
/** @brief Emitted when the zone's minimized state changes. */
|
/** @brief Emitted when the zone's minimized state changes. */
|
||||||
void minimizedChanged(bool isMinimized);
|
void minimizedChanged(bool isMinimized);
|
||||||
/** @brief Emitted when display height changes without a minimized-state change (e.g. tax counter toggled while
|
/** @brief Emitted when display height changes without a minimized-state change (e.g. cast count toggled while
|
||||||
* minimized). */
|
* minimized). */
|
||||||
void effectiveHeightChanged();
|
void effectiveHeightChanged();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,19 @@
|
||||||
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_concede.pb.h>
|
#include <libcockatrice/protocol/pb/command_concede.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
|
#include <libcockatrice/protocol/pb/command_create_arrow.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_create_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_create_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_create_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_create_token.pb.h>
|
#include <libcockatrice/protocol/pb/command_create_token.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
|
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_del_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_del_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
|
#include <libcockatrice/protocol/pb/command_delete_arrow.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_delete_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
|
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_dump_zone.pb.h>
|
#include <libcockatrice/protocol/pb/command_dump_zone.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_flip_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_flip_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
|
#include <libcockatrice/protocol/pb/command_game_say.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_inc_card_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_inc_card_counter.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_inc_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_kick_from_game.pb.h>
|
#include <libcockatrice/protocol/pb/command_kick_from_game.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_leave_game.pb.h>
|
#include <libcockatrice/protocol/pb/command_leave_game.pb.h>
|
||||||
|
|
@ -36,8 +39,8 @@
|
||||||
#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_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_cast_count.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>
|
||||||
|
|
@ -343,9 +346,30 @@ Response::ResponseCode Server_AbstractParticipant::cmdDelCounter(const Command_D
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_AbstractParticipant::cmdSetCounterActive(const Command_SetCounterActive & /*cmd*/,
|
Response::ResponseCode Server_AbstractParticipant::cmdCreateCastCount(const Command_CreateCastCount & /*cmd*/,
|
||||||
ResponseContainer & /*rc*/,
|
ResponseContainer & /*rc*/,
|
||||||
GameEventStorage & /*ges*/)
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractParticipant::cmdDeleteCastCount(const Command_DeleteCastCount & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractParticipant::cmdIncCastCount(const Command_IncCastCount & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
|
{
|
||||||
|
return Response::RespFunctionNotAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode Server_AbstractParticipant::cmdSetCastCount(const Command_SetCastCount & /*cmd*/,
|
||||||
|
ResponseContainer & /*rc*/,
|
||||||
|
GameEventStorage & /*ges*/)
|
||||||
{
|
{
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
@ -533,8 +557,17 @@ 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:
|
case GameCommand::CREATE_CAST_COUNT:
|
||||||
return cmdSetCounterActive(command.GetExtension(Command_SetCounterActive::ext), rc, ges);
|
return cmdCreateCastCount(command.GetExtension(Command_CreateCastCount::ext), rc, ges);
|
||||||
|
break;
|
||||||
|
case GameCommand::DELETE_CAST_COUNT:
|
||||||
|
return cmdDeleteCastCount(command.GetExtension(Command_DeleteCastCount::ext), rc, ges);
|
||||||
|
break;
|
||||||
|
case GameCommand::INC_CAST_COUNT:
|
||||||
|
return cmdIncCastCount(command.GetExtension(Command_IncCastCount::ext), rc, ges);
|
||||||
|
break;
|
||||||
|
case GameCommand::SET_CAST_COUNT:
|
||||||
|
return cmdSetCastCount(command.GetExtension(Command_SetCastCount::ext), rc, ges);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return Response::RespInvalidCommand;
|
return Response::RespInvalidCommand;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ 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;
|
||||||
|
|
@ -53,6 +52,10 @@ class Command_SetSideboardPlan;
|
||||||
class Command_DeckSelect;
|
class Command_DeckSelect;
|
||||||
class Command_SetSideboardLock;
|
class Command_SetSideboardLock;
|
||||||
class Command_ChangeZoneProperties;
|
class Command_ChangeZoneProperties;
|
||||||
|
class Command_CreateCastCount;
|
||||||
|
class Command_DeleteCastCount;
|
||||||
|
class Command_IncCastCount;
|
||||||
|
class Command_SetCastCount;
|
||||||
|
|
||||||
class Server_AbstractParticipant : public Server_ArrowTarget, public ServerInfo_User_Container
|
class Server_AbstractParticipant : public Server_ArrowTarget, public ServerInfo_User_Container
|
||||||
{
|
{
|
||||||
|
|
@ -162,8 +165,6 @@ 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);
|
||||||
|
|
@ -175,6 +176,14 @@ public:
|
||||||
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
||||||
virtual Response::ResponseCode
|
virtual Response::ResponseCode
|
||||||
cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
|
cmdCreateCastCount(const Command_CreateCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
|
cmdDeleteCastCount(const Command_DeleteCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
|
cmdIncCastCount(const Command_IncCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
virtual Response::ResponseCode
|
||||||
|
cmdSetCastCount(const Command_SetCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
|
|
||||||
Response::ResponseCode processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges);
|
Response::ResponseCode processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges);
|
||||||
void sendGameEvent(const GameEventContainer &event);
|
void sendGameEvent(const GameEventContainer &event);
|
||||||
|
|
|
||||||
|
|
@ -18,33 +18,39 @@
|
||||||
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_attach_card.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
#include <libcockatrice/protocol/pb/command_change_zone_properties.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_concede.pb.h>
|
#include <libcockatrice/protocol/pb/command_concede.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_create_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_create_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_create_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
|
#include <libcockatrice/protocol/pb/command_deck_select.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_del_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_del_counter.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_delete_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
|
#include <libcockatrice/protocol/pb/command_draw_cards.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/command_inc_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
|
#include <libcockatrice/protocol/pb/command_inc_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||||
#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_cast_count.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>
|
||||||
#include <libcockatrice/protocol/pb/context_deck_select.pb.h>
|
#include <libcockatrice/protocol/pb/context_deck_select.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
|
#include <libcockatrice/protocol/pb/context_mulligan.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/context_set_sideboard_lock.pb.h>
|
#include <libcockatrice/protocol/pb/context_set_sideboard_lock.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_create_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_create_counter.pb.h>
|
#include <libcockatrice/protocol/pb/event_create_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_del_counter.pb.h>
|
#include <libcockatrice/protocol/pb/event_del_counter.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_delete_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_draw_cards.pb.h>
|
#include <libcockatrice/protocol/pb/event_draw_cards.pb.h>
|
||||||
#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_cast_count.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>
|
||||||
#include <libcockatrice/protocol/pb/response_dump_zone.pb.h>
|
#include <libcockatrice/protocol/pb/response_dump_zone.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/serverinfo_cast_count.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_player.pb.h>
|
||||||
#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>
|
||||||
|
|
@ -75,7 +81,6 @@ int Server_Player::newCounterId() const
|
||||||
id = c->getId();
|
id = c->getId();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Skip reserved IDs 0-9 even in non-Commander games to prevent collision
|
|
||||||
return std::max(id + 1, CounterIds::FirstUserId);
|
return std::max(id + 1, CounterIds::FirstUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,15 +114,7 @@ void Server_Player::setupZones()
|
||||||
// Command zone for Commander format
|
// Command zone for Commander format
|
||||||
if (game->getEnableCommandZone()) {
|
if (game->getEnableCommandZone()) {
|
||||||
addZone(new Server_CardZone(this, ZoneNames::COMMAND, false, ServerInfo_Zone::PublicZone));
|
addZone(new Server_CardZone(this, ZoneNames::COMMAND, false, ServerInfo_Zone::PublicZone));
|
||||||
for (int i = 0; i < CounterIds::TaxCounterCount; ++i) {
|
castCounts.insert(1, 0);
|
||||||
int id = CounterIds::taxCounterIdFromIndex(i);
|
|
||||||
const char *name = CounterNames::forId(id);
|
|
||||||
auto *counter = new Server_Counter(id, name, makeColor(128, 128, 128), 20, 0, 0, MAX_COUNTER_VALUE);
|
|
||||||
if (i > 0) {
|
|
||||||
(void)counter->setActive(false);
|
|
||||||
}
|
|
||||||
addCounter(counter);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
@ -174,6 +171,7 @@ void Server_Player::clearZones()
|
||||||
delete counter;
|
delete counter;
|
||||||
}
|
}
|
||||||
counters.clear();
|
counters.clear();
|
||||||
|
castCounts.clear();
|
||||||
|
|
||||||
lastDrawList.clear();
|
lastDrawList.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -445,11 +443,8 @@ Server_Player::cmdUndoDraw(const Command_UndoDraw & /*cmd*/, ResponseContainer &
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_Player::evaluateModifyCounter(bool gameStarted,
|
Response::ResponseCode
|
||||||
bool playerConceded,
|
Server_Player::evaluateModifyCounter(bool gameStarted, bool playerConceded, const Server_Counter *counter)
|
||||||
bool commandZoneEnabled,
|
|
||||||
int counterId,
|
|
||||||
const Server_Counter *counter)
|
|
||||||
{
|
{
|
||||||
if (!gameStarted) {
|
if (!gameStarted) {
|
||||||
return Response::RespGameNotStarted;
|
return Response::RespGameNotStarted;
|
||||||
|
|
@ -457,17 +452,6 @@ Response::ResponseCode Server_Player::evaluateModifyCounter(bool gameStarted,
|
||||||
if (playerConceded) {
|
if (playerConceded) {
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
if (CounterIds::isTaxCounter(counterId)) {
|
|
||||||
// Tax counters are server-managed: they only exist in Commander games, and an
|
|
||||||
// inactive (hidden) tax counter must stay at zero. Block modification in either
|
|
||||||
// case so the value can never diverge from what players can see.
|
|
||||||
if (!commandZoneEnabled) {
|
|
||||||
return Response::RespContextError;
|
|
||||||
}
|
|
||||||
if (counter && !counter->isActive()) {
|
|
||||||
return Response::RespContextError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!counter) {
|
if (!counter) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
}
|
}
|
||||||
|
|
@ -480,8 +464,7 @@ Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer &
|
||||||
const int counterId = cmd.counter_id();
|
const int counterId = cmd.counter_id();
|
||||||
Server_Counter *c = counters.value(counterId, nullptr);
|
Server_Counter *c = counters.value(counterId, nullptr);
|
||||||
|
|
||||||
const Response::ResponseCode authResult =
|
const Response::ResponseCode authResult = evaluateModifyCounter(game->getGameStarted(), conceded, c);
|
||||||
evaluateModifyCounter(game->getGameStarted(), conceded, game->getEnableCommandZone(), counterId, c);
|
|
||||||
if (authResult != Response::RespOk) {
|
if (authResult != Response::RespOk) {
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +481,7 @@ Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer &
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
Server_Player::evaluateCreateCounter(bool gameStarted, bool playerConceded, const QString &counterName)
|
Server_Player::evaluateCreateCounter(bool gameStarted, bool playerConceded, const QString & /*counterName*/)
|
||||||
{
|
{
|
||||||
if (!gameStarted) {
|
if (!gameStarted) {
|
||||||
return Response::RespGameNotStarted;
|
return Response::RespGameNotStarted;
|
||||||
|
|
@ -506,12 +489,6 @@ Server_Player::evaluateCreateCounter(bool gameStarted, bool playerConceded, cons
|
||||||
if (playerConceded) {
|
if (playerConceded) {
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
// Reserved system counter names (commander/partner tax) are how clients identify
|
|
||||||
// server-managed tax counters for rendering and logging; a client must not be able
|
|
||||||
// to spoof one via a user-created counter.
|
|
||||||
if (CounterNames::isTaxCounter(counterName)) {
|
|
||||||
return Response::RespFunctionNotAllowed;
|
|
||||||
}
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -546,8 +523,7 @@ Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer &
|
||||||
const int counterId = cmd.counter_id();
|
const int counterId = cmd.counter_id();
|
||||||
Server_Counter *c = counters.value(counterId, nullptr);
|
Server_Counter *c = counters.value(counterId, nullptr);
|
||||||
|
|
||||||
const Response::ResponseCode authResult =
|
const Response::ResponseCode authResult = evaluateModifyCounter(game->getGameStarted(), conceded, c);
|
||||||
evaluateModifyCounter(game->getGameStarted(), conceded, game->getEnableCommandZone(), counterId, c);
|
|
||||||
if (authResult != Response::RespOk) {
|
if (authResult != Response::RespOk) {
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
@ -564,7 +540,7 @@ Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer &
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::ResponseCode
|
Response::ResponseCode
|
||||||
Server_Player::evaluateDelCounter(bool gameStarted, bool playerConceded, int counterId, const Server_Counter *counter)
|
Server_Player::evaluateDelCounter(bool gameStarted, bool playerConceded, const Server_Counter *counter)
|
||||||
{
|
{
|
||||||
if (!gameStarted) {
|
if (!gameStarted) {
|
||||||
return Response::RespGameNotStarted;
|
return Response::RespGameNotStarted;
|
||||||
|
|
@ -572,12 +548,6 @@ Server_Player::evaluateDelCounter(bool gameStarted, bool playerConceded, int cou
|
||||||
if (playerConceded) {
|
if (playerConceded) {
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
// Reserved tax counters are server-managed system counters and must never be
|
|
||||||
// deleted by a client. When the command zone is disabled they don't exist, so
|
|
||||||
// a lookup would fail anyway; when it's enabled they must persist for the game.
|
|
||||||
if (CounterIds::isTaxCounter(counterId)) {
|
|
||||||
return Response::RespFunctionNotAllowed;
|
|
||||||
}
|
|
||||||
if (!counter) {
|
if (!counter) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
}
|
}
|
||||||
|
|
@ -590,7 +560,7 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &
|
||||||
const int counterId = cmd.counter_id();
|
const int counterId = cmd.counter_id();
|
||||||
Server_Counter *counter = counters.value(counterId, nullptr);
|
Server_Counter *counter = counters.value(counterId, nullptr);
|
||||||
|
|
||||||
const Response::ResponseCode authResult = evaluateDelCounter(game->getGameStarted(), conceded, counterId, counter);
|
const Response::ResponseCode authResult = evaluateDelCounter(game->getGameStarted(), conceded, counter);
|
||||||
if (authResult != Response::RespOk) {
|
if (authResult != Response::RespOk) {
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
@ -605,14 +575,14 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_Player::evaluateSetCounterActive(bool gameStarted,
|
// Cast count validation and command handlers
|
||||||
bool playerConceded,
|
|
||||||
bool commandZoneEnabled,
|
Response::ResponseCode Server_Player::evaluateCreateCastCount(bool gameStarted,
|
||||||
int counterId,
|
bool playerConceded,
|
||||||
const Server_Counter *counter,
|
bool commandZoneEnabled,
|
||||||
bool requestedActive,
|
int index,
|
||||||
const Server_Counter *predecessorCounter,
|
bool exists,
|
||||||
const Server_Counter *successorCounter)
|
bool predecessorExists)
|
||||||
{
|
{
|
||||||
if (!gameStarted) {
|
if (!gameStarted) {
|
||||||
return Response::RespGameNotStarted;
|
return Response::RespGameNotStarted;
|
||||||
|
|
@ -620,53 +590,166 @@ Response::ResponseCode Server_Player::evaluateSetCounterActive(bool gameStarted,
|
||||||
if (playerConceded) {
|
if (playerConceded) {
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
if (!CounterIds::isTaxCounter(counterId)) {
|
|
||||||
return Response::RespFunctionNotAllowed;
|
|
||||||
}
|
|
||||||
if (!commandZoneEnabled) {
|
if (!commandZoneEnabled) {
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
if (!counter) {
|
if (!CastCountIds::isValidIndex(index)) {
|
||||||
return Response::RespNameNotFound;
|
|
||||||
}
|
|
||||||
// Prevent disabling a counter with tax accumulated; player must reset to 0 first
|
|
||||||
if (!requestedActive && counter->getCount() != 0) {
|
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
// Enforce ordering: can only activate if predecessor is active
|
if (exists) {
|
||||||
if (requestedActive && predecessorCounter && !predecessorCounter->isActive()) {
|
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
// Enforce ordering: can only deactivate if successor is inactive
|
if (index > 1 && !predecessorExists) {
|
||||||
if (!requestedActive && successorCounter && successorCounter->isActive()) {
|
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::ResponseCode Server_Player::cmdSetCounterActive(const Command_SetCounterActive &cmd,
|
Response::ResponseCode Server_Player::evaluateDeleteCastCount(bool gameStarted,
|
||||||
ResponseContainer & /*rc*/,
|
bool playerConceded,
|
||||||
GameEventStorage &ges)
|
bool commandZoneEnabled,
|
||||||
|
int index,
|
||||||
|
bool exists,
|
||||||
|
int value,
|
||||||
|
bool successorExists)
|
||||||
{
|
{
|
||||||
const int counterId = cmd.counter_id();
|
if (!gameStarted) {
|
||||||
Server_Counter *c = counters.value(counterId, nullptr);
|
return Response::RespGameNotStarted;
|
||||||
|
}
|
||||||
|
if (playerConceded) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
if (!commandZoneEnabled) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
if (!CastCountIds::isValidIndex(index)) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
return Response::RespNameNotFound;
|
||||||
|
}
|
||||||
|
if (value != 0) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
if (successorExists) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
int predecessorId = CounterIds::taxCounterIdFromIndex(CounterIds::taxCounterIndex(counterId) - 1);
|
Response::ResponseCode Server_Player::evaluateModifyCastCount(bool gameStarted,
|
||||||
int successorId = CounterIds::taxCounterIdFromIndex(CounterIds::taxCounterIndex(counterId) + 1);
|
bool playerConceded,
|
||||||
Server_Counter *predecessor = predecessorId >= 0 ? counters.value(predecessorId, nullptr) : nullptr;
|
bool commandZoneEnabled,
|
||||||
Server_Counter *successor = successorId >= 0 ? counters.value(successorId, nullptr) : nullptr;
|
int index,
|
||||||
|
bool exists)
|
||||||
|
{
|
||||||
|
if (!gameStarted) {
|
||||||
|
return Response::RespGameNotStarted;
|
||||||
|
}
|
||||||
|
if (playerConceded) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
if (!commandZoneEnabled) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
if (!CastCountIds::isValidIndex(index)) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
return Response::RespNameNotFound;
|
||||||
|
}
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
const Response::ResponseCode authResult =
|
Response::ResponseCode
|
||||||
evaluateSetCounterActive(game->getGameStarted(), conceded, game->getEnableCommandZone(), counterId, c,
|
Server_Player::cmdCreateCastCount(const Command_CreateCastCount &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
||||||
cmd.active(), predecessor, successor);
|
{
|
||||||
|
const int index = cmd.index();
|
||||||
|
bool exists = castCounts.contains(index);
|
||||||
|
bool predecessorExists = (index == 1) || castCounts.contains(index - 1);
|
||||||
|
|
||||||
|
const Response::ResponseCode authResult = evaluateCreateCastCount(
|
||||||
|
game->getGameStarted(), conceded, game->getEnableCommandZone(), index, exists, predecessorExists);
|
||||||
if (authResult != Response::RespOk) {
|
if (authResult != Response::RespOk) {
|
||||||
return authResult;
|
return authResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->setActive(cmd.active())) {
|
castCounts.insert(index, 0);
|
||||||
Event_SetCounterActive event;
|
|
||||||
event.set_counter_id(c->getId());
|
Event_CreateCastCount event;
|
||||||
event.set_active(c->isActive());
|
event.set_index(index);
|
||||||
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode
|
||||||
|
Server_Player::cmdDeleteCastCount(const Command_DeleteCastCount &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
const int index = cmd.index();
|
||||||
|
bool exists = castCounts.contains(index);
|
||||||
|
int value = exists ? castCounts.value(index) : 0;
|
||||||
|
bool successorExists = castCounts.contains(index + 1);
|
||||||
|
|
||||||
|
const Response::ResponseCode authResult = evaluateDeleteCastCount(
|
||||||
|
game->getGameStarted(), conceded, game->getEnableCommandZone(), index, exists, value, successorExists);
|
||||||
|
if (authResult != Response::RespOk) {
|
||||||
|
return authResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
castCounts.remove(index);
|
||||||
|
|
||||||
|
Event_DeleteCastCount event;
|
||||||
|
event.set_index(index);
|
||||||
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode
|
||||||
|
Server_Player::cmdIncCastCount(const Command_IncCastCount &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
const int index = cmd.index();
|
||||||
|
bool exists = castCounts.contains(index);
|
||||||
|
|
||||||
|
const Response::ResponseCode authResult =
|
||||||
|
evaluateModifyCastCount(game->getGameStarted(), conceded, game->getEnableCommandZone(), index, exists);
|
||||||
|
if (authResult != Response::RespOk) {
|
||||||
|
return authResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldValue = castCounts.value(index);
|
||||||
|
int newValue = qBound(0, oldValue + cmd.delta(), MAX_COUNTER_VALUE);
|
||||||
|
if (newValue != oldValue) {
|
||||||
|
castCounts.insert(index, newValue);
|
||||||
|
Event_SetCastCount event;
|
||||||
|
event.set_index(index);
|
||||||
|
event.set_value(newValue);
|
||||||
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode
|
||||||
|
Server_Player::cmdSetCastCount(const Command_SetCastCount &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
|
||||||
|
{
|
||||||
|
const int index = cmd.index();
|
||||||
|
bool exists = castCounts.contains(index);
|
||||||
|
|
||||||
|
const Response::ResponseCode authResult =
|
||||||
|
evaluateModifyCastCount(game->getGameStarted(), conceded, game->getEnableCommandZone(), index, exists);
|
||||||
|
if (authResult != Response::RespOk) {
|
||||||
|
return authResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
int oldValue = castCounts.value(index);
|
||||||
|
int newValue = qBound(0, cmd.value(), MAX_COUNTER_VALUE);
|
||||||
|
if (newValue != oldValue) {
|
||||||
|
castCounts.insert(index, newValue);
|
||||||
|
Event_SetCastCount event;
|
||||||
|
event.set_index(index);
|
||||||
|
event.set_value(newValue);
|
||||||
ges.enqueueGameEvent(event, playerId);
|
ges.enqueueGameEvent(event, playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -746,4 +829,10 @@ void Server_Player::getInfo(ServerInfo_Player *info,
|
||||||
for (Server_Counter *counter : counters) {
|
for (Server_Counter *counter : counters) {
|
||||||
counter->getInfo(info->add_counter_list());
|
counter->getInfo(info->add_counter_list());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto it = castCounts.constBegin(); it != castCounts.constEnd(); ++it) {
|
||||||
|
auto *castCount = info->add_cast_count_list();
|
||||||
|
castCount->set_index(it.key());
|
||||||
|
castCount->set_value(it.value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class Server_Player : public Server_AbstractPlayer
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
QMap<int, Server_Counter *> counters;
|
QMap<int, Server_Counter *> counters;
|
||||||
|
QMap<int, int> castCounts; // index (1-5) -> value
|
||||||
QList<int> lastDrawList;
|
QList<int> lastDrawList;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -21,90 +22,40 @@ public:
|
||||||
{
|
{
|
||||||
return counters;
|
return counters;
|
||||||
}
|
}
|
||||||
|
const QMap<int, int> &getCastCounts() const
|
||||||
|
{
|
||||||
|
return castCounts;
|
||||||
|
}
|
||||||
int newCounterId() const;
|
int newCounterId() const;
|
||||||
void addCounter(Server_Counter *counter);
|
void addCounter(Server_Counter *counter);
|
||||||
|
|
||||||
/** @name Counter command authorization
|
|
||||||
* Decision logic extracted from the corresponding cmd* handlers so it can be unit-tested
|
|
||||||
* in isolation. Each function takes all relevant state as parameters and touches no
|
|
||||||
* instance members, hence static.
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Decide whether a client may delete a counter.
|
|
||||||
*
|
|
||||||
* Reserved tax counters are server-managed and may never be deleted by a client.
|
|
||||||
*
|
|
||||||
* @param gameStarted Whether the game has started
|
|
||||||
* @param playerConceded Whether the player has conceded
|
|
||||||
* @param counterId ID of the counter to delete
|
|
||||||
* @param counter Counter with id counterId, or nullptr if the player has no such counter.
|
|
||||||
* @return Response::RespOk if permitted, otherwise the error response for the client.
|
|
||||||
*/
|
|
||||||
static Response::ResponseCode
|
static Response::ResponseCode
|
||||||
evaluateDelCounter(bool gameStarted, bool playerConceded, int counterId, const Server_Counter *counter);
|
evaluateDelCounter(bool gameStarted, bool playerConceded, const Server_Counter *counter);
|
||||||
|
|
||||||
/**
|
static Response::ResponseCode
|
||||||
* @brief Decide whether a client may change a counter's active (visible) state.
|
evaluateModifyCounter(bool gameStarted, bool playerConceded, const Server_Counter *counter);
|
||||||
*
|
|
||||||
* Only reserved tax counters can be toggled, and one holding a non-zero value must be reset
|
|
||||||
* to zero before it can be deactivated.
|
|
||||||
*
|
|
||||||
* @param gameStarted Whether the game has started
|
|
||||||
* @param playerConceded Whether the player has conceded
|
|
||||||
* @param commandZoneEnabled Whether command zone is enabled for this game
|
|
||||||
* @param counterId ID of the counter to toggle
|
|
||||||
* @param counter Counter with id counterId, or nullptr if the player has no such counter.
|
|
||||||
* @param requestedActive Active state the client asked for.
|
|
||||||
* @param predecessorCounter Tax counter that must be active before this one can be activated (nullptr if none).
|
|
||||||
* @param successorCounter Tax counter that must be inactive before this one can be deactivated (nullptr if none).
|
|
||||||
* @return Response::RespOk if permitted, otherwise the error response for the client.
|
|
||||||
*/
|
|
||||||
static Response::ResponseCode evaluateSetCounterActive(bool gameStarted,
|
|
||||||
bool playerConceded,
|
|
||||||
bool commandZoneEnabled,
|
|
||||||
int counterId,
|
|
||||||
const Server_Counter *counter,
|
|
||||||
bool requestedActive,
|
|
||||||
const Server_Counter *predecessorCounter,
|
|
||||||
const Server_Counter *successorCounter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Decide whether a client may change a counter's value.
|
|
||||||
*
|
|
||||||
* Shared by cmdIncCounter and cmdSetCounter. Reserved tax counters may only be modified
|
|
||||||
* inside a Commander game and only while active, so an inactive (hidden) tax counter can
|
|
||||||
* never accumulate a value behind the scenes.
|
|
||||||
*
|
|
||||||
* @param gameStarted Whether the game has started
|
|
||||||
* @param playerConceded Whether the player has conceded
|
|
||||||
* @param commandZoneEnabled Whether command zone is enabled for this game
|
|
||||||
* @param counterId ID of the counter to modify
|
|
||||||
* @param counter Counter with id counterId, or nullptr if the player has no such counter.
|
|
||||||
* @return Response::RespOk if permitted, otherwise the error response for the client.
|
|
||||||
*/
|
|
||||||
static Response::ResponseCode evaluateModifyCounter(bool gameStarted,
|
|
||||||
bool playerConceded,
|
|
||||||
bool commandZoneEnabled,
|
|
||||||
int counterId,
|
|
||||||
const Server_Counter *counter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Decide whether a client may create a counter with the given name.
|
|
||||||
*
|
|
||||||
* Reserved system counter names (commander/partner tax) are rejected to prevent
|
|
||||||
* clients from spoofing server-managed tax counters.
|
|
||||||
*
|
|
||||||
* @param gameStarted Whether the game has started
|
|
||||||
* @param playerConceded Whether the player has conceded
|
|
||||||
* @param counterName Name requested for the new counter
|
|
||||||
* @return Response::RespOk if permitted, otherwise the error response for the client.
|
|
||||||
*/
|
|
||||||
static Response::ResponseCode
|
static Response::ResponseCode
|
||||||
evaluateCreateCounter(bool gameStarted, bool playerConceded, const QString &counterName);
|
evaluateCreateCounter(bool gameStarted, bool playerConceded, const QString &counterName);
|
||||||
|
|
||||||
/** @} */
|
// Cast count validation
|
||||||
|
static Response::ResponseCode evaluateCreateCastCount(bool gameStarted,
|
||||||
|
bool playerConceded,
|
||||||
|
bool commandZoneEnabled,
|
||||||
|
int index,
|
||||||
|
bool exists,
|
||||||
|
bool predecessorExists);
|
||||||
|
|
||||||
|
static Response::ResponseCode evaluateDeleteCastCount(bool gameStarted,
|
||||||
|
bool playerConceded,
|
||||||
|
bool commandZoneEnabled,
|
||||||
|
int index,
|
||||||
|
bool exists,
|
||||||
|
int value,
|
||||||
|
bool successorExists);
|
||||||
|
|
||||||
|
static Response::ResponseCode
|
||||||
|
evaluateModifyCastCount(bool gameStarted, bool playerConceded, bool commandZoneEnabled, int index, bool exists);
|
||||||
|
|
||||||
void setupZones() override;
|
void setupZones() override;
|
||||||
void clearZones() override;
|
void clearZones() override;
|
||||||
|
|
@ -139,8 +90,6 @@ 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;
|
||||||
|
|
@ -150,6 +99,16 @@ public:
|
||||||
ResponseContainer &rc,
|
ResponseContainer &rc,
|
||||||
GameEventStorage &ges) override;
|
GameEventStorage &ges) override;
|
||||||
|
|
||||||
|
// Cast count commands
|
||||||
|
Response::ResponseCode
|
||||||
|
cmdCreateCastCount(const Command_CreateCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
|
Response::ResponseCode
|
||||||
|
cmdDeleteCastCount(const Command_DeleteCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
|
Response::ResponseCode
|
||||||
|
cmdIncCastCount(const Command_IncCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
|
Response::ResponseCode
|
||||||
|
cmdSetCastCount(const Command_SetCastCount &cmd, ResponseContainer &rc, GameEventStorage &ges) override;
|
||||||
|
|
||||||
void getInfo(ServerInfo_Player *info,
|
void getInfo(ServerInfo_Player *info,
|
||||||
Server_AbstractParticipant *playerWhosAsking,
|
Server_AbstractParticipant *playerWhosAsking,
|
||||||
bool omniscient,
|
bool omniscient,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ set(PROTO_FILES
|
||||||
command_change_zone_properties.proto
|
command_change_zone_properties.proto
|
||||||
command_concede.proto
|
command_concede.proto
|
||||||
command_create_arrow.proto
|
command_create_arrow.proto
|
||||||
|
command_create_cast_count.proto
|
||||||
command_create_counter.proto
|
command_create_counter.proto
|
||||||
command_create_token.proto
|
command_create_token.proto
|
||||||
command_deck_del.proto
|
command_deck_del.proto
|
||||||
|
|
@ -21,11 +22,13 @@ set(PROTO_FILES
|
||||||
command_deck_upload.proto
|
command_deck_upload.proto
|
||||||
command_del_counter.proto
|
command_del_counter.proto
|
||||||
command_delete_arrow.proto
|
command_delete_arrow.proto
|
||||||
|
command_delete_cast_count.proto
|
||||||
command_draw_cards.proto
|
command_draw_cards.proto
|
||||||
command_dump_zone.proto
|
command_dump_zone.proto
|
||||||
command_flip_card.proto
|
command_flip_card.proto
|
||||||
command_game_say.proto
|
command_game_say.proto
|
||||||
command_inc_card_counter.proto
|
command_inc_card_counter.proto
|
||||||
|
command_inc_cast_count.proto
|
||||||
command_inc_counter.proto
|
command_inc_counter.proto
|
||||||
command_kick_from_game.proto
|
command_kick_from_game.proto
|
||||||
command_leave_game.proto
|
command_leave_game.proto
|
||||||
|
|
@ -45,8 +48,8 @@ set(PROTO_FILES
|
||||||
command_set_active_phase.proto
|
command_set_active_phase.proto
|
||||||
command_set_card_attr.proto
|
command_set_card_attr.proto
|
||||||
command_set_card_counter.proto
|
command_set_card_counter.proto
|
||||||
|
command_set_cast_count.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
|
||||||
|
|
@ -66,10 +69,12 @@ set(PROTO_FILES
|
||||||
event_change_zone_properties.proto
|
event_change_zone_properties.proto
|
||||||
event_connection_closed.proto
|
event_connection_closed.proto
|
||||||
event_create_arrow.proto
|
event_create_arrow.proto
|
||||||
|
event_create_cast_count.proto
|
||||||
event_create_counter.proto
|
event_create_counter.proto
|
||||||
event_create_token.proto
|
event_create_token.proto
|
||||||
event_del_counter.proto
|
event_del_counter.proto
|
||||||
event_delete_arrow.proto
|
event_delete_arrow.proto
|
||||||
|
event_delete_cast_count.proto
|
||||||
event_destroy_card.proto
|
event_destroy_card.proto
|
||||||
event_draw_cards.proto
|
event_draw_cards.proto
|
||||||
event_dump_zone.proto
|
event_dump_zone.proto
|
||||||
|
|
@ -106,8 +111,8 @@ set(PROTO_FILES
|
||||||
event_set_active_player.proto
|
event_set_active_player.proto
|
||||||
event_set_card_attr.proto
|
event_set_card_attr.proto
|
||||||
event_set_card_counter.proto
|
event_set_card_counter.proto
|
||||||
|
event_set_cast_count.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
|
||||||
|
|
@ -149,6 +154,7 @@ set(PROTO_FILES
|
||||||
server_message.proto
|
server_message.proto
|
||||||
serverinfo_arrow.proto
|
serverinfo_arrow.proto
|
||||||
serverinfo_ban.proto
|
serverinfo_ban.proto
|
||||||
|
serverinfo_cast_count.proto
|
||||||
serverinfo_card.proto
|
serverinfo_card.proto
|
||||||
serverinfo_cardcounter.proto
|
serverinfo_cardcounter.proto
|
||||||
serverinfo_chat_message.proto
|
serverinfo_chat_message.proto
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_commands.proto";
|
||||||
|
|
||||||
|
message Command_CreateCastCount {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_CreateCastCount ext = 1036;
|
||||||
|
}
|
||||||
|
optional sint32 index = 1 [default = 1];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_commands.proto";
|
||||||
|
|
||||||
|
message Command_DeleteCastCount {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_DeleteCastCount ext = 1037;
|
||||||
|
}
|
||||||
|
optional sint32 index = 1 [default = 1];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_commands.proto";
|
||||||
|
|
||||||
|
message Command_IncCastCount {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_IncCastCount ext = 1038;
|
||||||
|
}
|
||||||
|
optional sint32 index = 1 [default = 1];
|
||||||
|
optional sint32 delta = 2 [default = 1];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_commands.proto";
|
||||||
|
|
||||||
|
message Command_SetCastCount {
|
||||||
|
extend GameCommand {
|
||||||
|
optional Command_SetCastCount ext = 1039;
|
||||||
|
}
|
||||||
|
optional sint32 index = 1 [default = 1];
|
||||||
|
optional sint32 value = 2 [default = 0];
|
||||||
|
}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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,9 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_event.proto";
|
||||||
|
|
||||||
|
message Event_CreateCastCount {
|
||||||
|
extend GameEvent {
|
||||||
|
optional Event_CreateCastCount ext = 2024;
|
||||||
|
}
|
||||||
|
optional sint32 index = 1 [default = 1];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_event.proto";
|
||||||
|
|
||||||
|
message Event_DeleteCastCount {
|
||||||
|
extend GameEvent {
|
||||||
|
optional Event_DeleteCastCount ext = 2025;
|
||||||
|
}
|
||||||
|
optional sint32 index = 1 [default = 1];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "game_event.proto";
|
||||||
|
|
||||||
|
message Event_SetCastCount {
|
||||||
|
extend GameEvent {
|
||||||
|
optional Event_SetCastCount ext = 2026;
|
||||||
|
}
|
||||||
|
optional sint32 index = 1 [default = 1];
|
||||||
|
optional sint32 value = 2 [default = 0];
|
||||||
|
}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
syntax = "proto2";
|
|
||||||
import "game_event.proto";
|
|
||||||
|
|
||||||
message Event_SetCounterActive {
|
|
||||||
extend GameEvent {
|
|
||||||
optional Event_SetCounterActive ext = 2023;
|
|
||||||
}
|
|
||||||
optional sint32 counter_id = 1 [default = -1];
|
|
||||||
optional bool active = 2 [default = true];
|
|
||||||
}
|
|
||||||
|
|
@ -176,10 +176,25 @@ message GameCommand {
|
||||||
/// Client: reflected via subsequent turn events
|
/// Client: reflected via subsequent turn events
|
||||||
REVERSE_TURN = 1034;
|
REVERSE_TURN = 1034;
|
||||||
|
|
||||||
/// Show or hide a reserved tax counter without deleting it.
|
/// Create a cast count at the specified index.
|
||||||
/// Server: Server_Player::cmdSetCounterActive
|
/// Server: Server_Player::cmdCreateCastCount
|
||||||
/// Client: PlayerEventHandler::eventSetCounterActive
|
/// Client: PlayerEventHandler::eventCreateCastCount
|
||||||
SET_COUNTER_ACTIVE = 1035;
|
CREATE_CAST_COUNT = 1036;
|
||||||
|
|
||||||
|
/// Delete a cast count at the specified index.
|
||||||
|
/// Server: Server_Player::cmdDeleteCastCount
|
||||||
|
/// Client: PlayerEventHandler::eventDeleteCastCount
|
||||||
|
DELETE_CAST_COUNT = 1037;
|
||||||
|
|
||||||
|
/// Increment or decrement a cast count.
|
||||||
|
/// Server: Server_Player::cmdIncCastCount
|
||||||
|
/// Client: PlayerEventHandler::eventSetCastCount
|
||||||
|
INC_CAST_COUNT = 1038;
|
||||||
|
|
||||||
|
/// Set a cast count to a specific value.
|
||||||
|
/// Server: Server_Player::cmdSetCastCount
|
||||||
|
/// Client: PlayerEventHandler::eventSetCastCount
|
||||||
|
SET_CAST_COUNT = 1039;
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ 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;
|
CREATE_CAST_COUNT = 2024;
|
||||||
|
DELETE_CAST_COUNT = 2025;
|
||||||
|
SET_CAST_COUNT = 2026;
|
||||||
}
|
}
|
||||||
optional sint32 player_id = 1 [default = -1];
|
optional sint32 player_id = 1 [default = -1];
|
||||||
extensions 100 to max;
|
extensions 100 to max;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
message ServerInfo_CastCount {
|
||||||
|
optional sint32 index = 1;
|
||||||
|
optional sint32 value = 2;
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import "serverinfo_zone.proto";
|
||||||
import "serverinfo_counter.proto";
|
import "serverinfo_counter.proto";
|
||||||
import "serverinfo_arrow.proto";
|
import "serverinfo_arrow.proto";
|
||||||
import "serverinfo_playerproperties.proto";
|
import "serverinfo_playerproperties.proto";
|
||||||
|
import "serverinfo_cast_count.proto";
|
||||||
|
|
||||||
message ServerInfo_Player {
|
message ServerInfo_Player {
|
||||||
optional ServerInfo_PlayerProperties properties = 1;
|
optional ServerInfo_PlayerProperties properties = 1;
|
||||||
|
|
@ -10,4 +11,5 @@ message ServerInfo_Player {
|
||||||
repeated ServerInfo_Zone zone_list = 3;
|
repeated ServerInfo_Zone zone_list = 3;
|
||||||
repeated ServerInfo_Counter counter_list = 4;
|
repeated ServerInfo_Counter counter_list = 4;
|
||||||
repeated ServerInfo_Arrow arrow_list = 5;
|
repeated ServerInfo_Arrow arrow_list = 5;
|
||||||
|
repeated ServerInfo_CastCount cast_count_list = 6;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* @file counter_ids.h
|
* @file counter_ids.h
|
||||||
* @ingroup Core
|
* @ingroup Utility
|
||||||
* @brief Shared counter IDs and names for system counters (e.g. tax counters).
|
* @brief Counter ID ranges and cast count index utilities.
|
||||||
|
*
|
||||||
|
* Defines the boundary between built-in counters (life, mana colors, storm) and
|
||||||
|
* user-created counters, plus helpers for the cast count system used in Commander.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef COCKATRICE_COUNTER_IDS_H
|
#ifndef COCKATRICE_COUNTER_IDS_H
|
||||||
|
|
@ -11,95 +14,36 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @namespace CounterIds
|
* @namespace CounterIds
|
||||||
* @brief Shared counter IDs used by both client and server.
|
* @brief Reserved counter ID ranges.
|
||||||
*
|
|
||||||
* Single source of truth: included directly by both sides, so they cannot drift.
|
|
||||||
*
|
|
||||||
* Reserved counter IDs for system counters:
|
|
||||||
* IDs 0-7: Standard player counters (life, mana colors, storm)
|
|
||||||
* IDs 8-12: Tax counters (1st through 5th)
|
|
||||||
* IDs 13+: Available for user-created counters (FirstUserId)
|
|
||||||
*
|
|
||||||
* newCounterId() always returns >= FirstUserId to prevent user counters
|
|
||||||
* from colliding with reserved IDs, even in non-Commander games.
|
|
||||||
*/
|
*/
|
||||||
namespace CounterIds
|
namespace CounterIds
|
||||||
{
|
{
|
||||||
constexpr int TaxCounter1 = 8; ///< 1st tax counter
|
constexpr int FirstUserId = 8; ///< First ID available for user-created counters (0-7 are built-in)
|
||||||
constexpr int TaxCounter2 = 9; ///< 2nd tax counter
|
|
||||||
constexpr int TaxCounter3 = 10; ///< 3rd tax counter
|
|
||||||
constexpr int TaxCounter4 = 11; ///< 4th tax counter
|
|
||||||
constexpr int TaxCounter5 = 12; ///< 5th tax counter
|
|
||||||
constexpr int FirstUserId = 13; ///< First ID available for user-created counters
|
|
||||||
|
|
||||||
constexpr int FirstTaxCounterId = TaxCounter1;
|
|
||||||
constexpr int LastTaxCounterId = TaxCounter5;
|
|
||||||
constexpr int TaxCounterCount = LastTaxCounterId - FirstTaxCounterId + 1;
|
|
||||||
|
|
||||||
/** @brief Returns true if the given ID is a reserved tax counter. */
|
|
||||||
inline bool isTaxCounter(int id)
|
|
||||||
{
|
|
||||||
return id >= FirstTaxCounterId && id <= LastTaxCounterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @brief Returns the tax counter index (0-based) for display, or -1 if not a tax counter. */
|
|
||||||
inline int taxCounterIndex(int id)
|
|
||||||
{
|
|
||||||
if (!isTaxCounter(id)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return id - FirstTaxCounterId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @brief Returns the tax counter ID for the given 0-based index, or -1 if out of range. */
|
|
||||||
inline int taxCounterIdFromIndex(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= TaxCounterCount) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return FirstTaxCounterId + index;
|
|
||||||
}
|
|
||||||
} // namespace CounterIds
|
} // namespace CounterIds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @namespace CounterNames
|
* @namespace CastCountIds
|
||||||
* @brief Reserved counter names for server-managed tax counters.
|
* @brief Cast count index range and utilities.
|
||||||
*
|
*
|
||||||
* Used to reject user-created counters that would spoof system counters.
|
* Cast counts use a separate 1-based index space (1-5), not sharing IDs with regular counters.
|
||||||
*/
|
*/
|
||||||
namespace CounterNames
|
namespace CastCountIds
|
||||||
{
|
{
|
||||||
constexpr const char *TaxCounter1 = "1st_tax_counter";
|
constexpr int MinIndex = 1; ///< Minimum valid cast count index
|
||||||
constexpr const char *TaxCounter2 = "2nd_tax_counter";
|
constexpr int MaxIndex = 5; ///< Maximum valid cast count index
|
||||||
constexpr const char *TaxCounter3 = "3rd_tax_counter";
|
constexpr int Count = MaxIndex; ///< Total number of cast count slots
|
||||||
constexpr const char *TaxCounter4 = "4th_tax_counter";
|
|
||||||
constexpr const char *TaxCounter5 = "5th_tax_counter";
|
|
||||||
|
|
||||||
/** @brief Returns the name for the given tax counter ID, or nullptr if not a tax counter. */
|
/** @brief Returns true if the index is in the valid cast count range [1, 5]. */
|
||||||
inline const char *forId(int id)
|
inline bool isValidIndex(int index)
|
||||||
{
|
{
|
||||||
switch (id) {
|
return index >= MinIndex && index <= MaxIndex;
|
||||||
case CounterIds::TaxCounter1:
|
|
||||||
return TaxCounter1;
|
|
||||||
case CounterIds::TaxCounter2:
|
|
||||||
return TaxCounter2;
|
|
||||||
case CounterIds::TaxCounter3:
|
|
||||||
return TaxCounter3;
|
|
||||||
case CounterIds::TaxCounter4:
|
|
||||||
return TaxCounter4;
|
|
||||||
case CounterIds::TaxCounter5:
|
|
||||||
return TaxCounter5;
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Returns true if the given name is a reserved tax counter name. */
|
/** @brief Returns the internal name for a cast count at the given index (e.g. "cast_count_1"). */
|
||||||
inline bool isTaxCounter(const QString &name)
|
inline QString nameForIndex(int index)
|
||||||
{
|
{
|
||||||
return name == TaxCounter1 || name == TaxCounter2 || name == TaxCounter3 || name == TaxCounter4 ||
|
return QString("cast_count_%1").arg(index);
|
||||||
name == TaxCounter5;
|
|
||||||
}
|
}
|
||||||
} // namespace CounterNames
|
} // namespace CastCountIds
|
||||||
|
|
||||||
#endif // COCKATRICE_COUNTER_IDS_H
|
#endif // COCKATRICE_COUNTER_IDS_H
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
*
|
*
|
||||||
* Caps an individual counter's VALUE (e.g. a +1/+1 counter at 999), not how many counters
|
* Caps an individual counter's VALUE (e.g. a +1/+1 counter at 999), not how many counters
|
||||||
* something holds. Applies to counters that are constrained to a non-negative display range,
|
* something holds. Applies to counters that are constrained to a non-negative display range,
|
||||||
* such as card counters and commander tax. Unbounded counters (e.g. a player's life total)
|
* such as card counters and cast counts. Unbounded counters (e.g. a player's life total)
|
||||||
* do not use this limit and may go negative, saturating only at the int range.
|
* do not use this limit and may go negative, saturating only at the int range.
|
||||||
*
|
*
|
||||||
* The max of 999 is a display constraint (3-digit rendering) and a reasonable gameplay limit.
|
* The max of 999 is a display constraint (3-digit rendering) and a reasonable gameplay limit.
|
||||||
|
|
|
||||||
|
|
@ -18,156 +18,35 @@ namespace
|
||||||
{
|
{
|
||||||
constexpr int UserCounterId = CounterIds::FirstUserId;
|
constexpr int UserCounterId = CounterIds::FirstUserId;
|
||||||
|
|
||||||
Server_Counter makeCounter(int id, int count, bool active = true)
|
Server_Counter makeCounter(int id, int count)
|
||||||
{
|
{
|
||||||
Server_Counter c(id, "c", color(), 20, count);
|
return Server_Counter(id, "c", color(), 20, count);
|
||||||
(void)c.setActive(active);
|
|
||||||
return c;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(EvaluateDelCounter, RejectsWhenGameNotStarted)
|
TEST(EvaluateDelCounter, RejectsWhenGameNotStarted)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(UserCounterId, 0);
|
Server_Counter counter = makeCounter(UserCounterId, 0);
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(Server_Player::evaluateDelCounter(/*gameStarted=*/false, /*playerConceded=*/false, &counter),
|
||||||
Server_Player::evaluateDelCounter(/*gameStarted=*/false, /*playerConceded=*/false, UserCounterId, &counter),
|
Response::RespGameNotStarted);
|
||||||
Response::RespGameNotStarted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateDelCounter, RejectsWhenPlayerConceded)
|
TEST(EvaluateDelCounter, RejectsWhenPlayerConceded)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(UserCounterId, 0);
|
Server_Counter counter = makeCounter(UserCounterId, 0);
|
||||||
EXPECT_EQ(Server_Player::evaluateDelCounter(/*gameStarted=*/true, /*playerConceded=*/true, UserCounterId, &counter),
|
EXPECT_EQ(Server_Player::evaluateDelCounter(/*gameStarted=*/true, /*playerConceded=*/true, &counter),
|
||||||
Response::RespContextError);
|
Response::RespContextError);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateDelCounter, RejectsTaxCounters)
|
|
||||||
{
|
|
||||||
Server_Counter tax1 = makeCounter(CounterIds::TaxCounter1, 0);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateDelCounter(true, false, CounterIds::TaxCounter1, &tax1),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
|
|
||||||
Server_Counter tax2 = makeCounter(CounterIds::TaxCounter2, 0);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateDelCounter(true, false, CounterIds::TaxCounter2, &tax2),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateDelCounter, RejectsMissingCounter)
|
TEST(EvaluateDelCounter, RejectsMissingCounter)
|
||||||
{
|
{
|
||||||
EXPECT_EQ(Server_Player::evaluateDelCounter(true, false, UserCounterId, nullptr), Response::RespNameNotFound);
|
EXPECT_EQ(Server_Player::evaluateDelCounter(true, false, nullptr), Response::RespNameNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateDelCounter, AllowsDeletingUserCounter)
|
TEST(EvaluateDelCounter, AllowsDeletingUserCounter)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(UserCounterId, 7);
|
Server_Counter counter = makeCounter(UserCounterId, 7);
|
||||||
EXPECT_EQ(Server_Player::evaluateDelCounter(true, false, UserCounterId, &counter), Response::RespOk);
|
EXPECT_EQ(Server_Player::evaluateDelCounter(true, false, &counter), Response::RespOk);
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateDelCounter, GameNotStartedTakesPrecedenceOverTaxGuard)
|
|
||||||
{
|
|
||||||
Server_Counter tax1 = makeCounter(CounterIds::TaxCounter1, 0);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateDelCounter(false, false, CounterIds::TaxCounter1, &tax1),
|
|
||||||
Response::RespGameNotStarted);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsWhenGameNotStarted)
|
|
||||||
{
|
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 0);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(/*gameStarted=*/false, /*playerConceded=*/false,
|
|
||||||
/*commandZoneEnabled=*/true, CounterIds::TaxCounter1, &counter,
|
|
||||||
/*requestedActive=*/true, nullptr, nullptr),
|
|
||||||
Response::RespGameNotStarted);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsWhenPlayerConceded)
|
|
||||||
{
|
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 0);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, /*playerConceded=*/true, true, CounterIds::TaxCounter1,
|
|
||||||
&counter, true, nullptr, nullptr),
|
|
||||||
Response::RespContextError);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsNonTaxCounter)
|
|
||||||
{
|
|
||||||
Server_Counter counter = makeCounter(UserCounterId, 0);
|
|
||||||
EXPECT_EQ(
|
|
||||||
Server_Player::evaluateSetCounterActive(true, false, true, UserCounterId, &counter, true, nullptr, nullptr),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsWhenCommandZoneDisabled)
|
|
||||||
{
|
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 0);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, /*commandZoneEnabled=*/false,
|
|
||||||
CounterIds::TaxCounter1, &counter, true, nullptr, nullptr),
|
|
||||||
Response::RespContextError);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsMissingCounter)
|
|
||||||
{
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter1, nullptr, true,
|
|
||||||
nullptr, nullptr),
|
|
||||||
Response::RespNameNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsDisablingWhenTaxAccumulated)
|
|
||||||
{
|
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 3);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter1, &counter,
|
|
||||||
/*requestedActive=*/false, nullptr, nullptr),
|
|
||||||
Response::RespContextError);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, AllowsEnablingWithAccumulatedTax)
|
|
||||||
{
|
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 3);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter1, &counter,
|
|
||||||
/*requestedActive=*/true, nullptr, nullptr),
|
|
||||||
Response::RespOk);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, AllowsDisablingWhenCounterIsZero)
|
|
||||||
{
|
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 0);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter1, &counter,
|
|
||||||
/*requestedActive=*/false, nullptr, nullptr),
|
|
||||||
Response::RespOk);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, AllowsEnabling2ndTaxWhen1stIsActive)
|
|
||||||
{
|
|
||||||
Server_Counter tax1 = makeCounter(CounterIds::TaxCounter1, 0, true);
|
|
||||||
Server_Counter tax2 = makeCounter(CounterIds::TaxCounter2, 0, false);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter2, &tax2,
|
|
||||||
/*requestedActive=*/true, &tax1, nullptr),
|
|
||||||
Response::RespOk);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsEnabling2ndTaxWhen1stIsInactive)
|
|
||||||
{
|
|
||||||
Server_Counter tax1 = makeCounter(CounterIds::TaxCounter1, 0, false);
|
|
||||||
Server_Counter tax2 = makeCounter(CounterIds::TaxCounter2, 0, false);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter2, &tax2,
|
|
||||||
/*requestedActive=*/true, &tax1, nullptr),
|
|
||||||
Response::RespContextError);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, AllowsDisabling1stTaxWhen2ndIsInactive)
|
|
||||||
{
|
|
||||||
Server_Counter tax1 = makeCounter(CounterIds::TaxCounter1, 0, true);
|
|
||||||
Server_Counter tax2 = makeCounter(CounterIds::TaxCounter2, 0, false);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter1, &tax1,
|
|
||||||
/*requestedActive=*/false, nullptr, &tax2),
|
|
||||||
Response::RespOk);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateSetCounterActive, RejectsDisabling1stTaxWhen2ndIsActive)
|
|
||||||
{
|
|
||||||
Server_Counter tax1 = makeCounter(CounterIds::TaxCounter1, 0, true);
|
|
||||||
Server_Counter tax2 = makeCounter(CounterIds::TaxCounter2, 0, true);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::TaxCounter1, &tax1,
|
|
||||||
/*requestedActive=*/false, nullptr, &tax2),
|
|
||||||
Response::RespContextError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateCreateCounter, RejectsWhenGameNotStarted)
|
TEST(EvaluateCreateCounter, RejectsWhenGameNotStarted)
|
||||||
|
|
@ -181,20 +60,6 @@ TEST(EvaluateCreateCounter, RejectsWhenPlayerConceded)
|
||||||
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, /*playerConceded=*/true, "test"), Response::RespContextError);
|
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, /*playerConceded=*/true, "test"), Response::RespContextError);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateCreateCounter, RejectsTaxCounterNames)
|
|
||||||
{
|
|
||||||
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, false, CounterNames::TaxCounter1),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, false, CounterNames::TaxCounter2),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, false, CounterNames::TaxCounter3),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, false, CounterNames::TaxCounter4),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, false, CounterNames::TaxCounter5),
|
|
||||||
Response::RespFunctionNotAllowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(EvaluateCreateCounter, AllowsUserCounterName)
|
TEST(EvaluateCreateCounter, AllowsUserCounterName)
|
||||||
{
|
{
|
||||||
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, false, "poison"), Response::RespOk);
|
EXPECT_EQ(Server_Player::evaluateCreateCounter(true, false, "poison"), Response::RespOk);
|
||||||
|
|
@ -203,61 +68,151 @@ TEST(EvaluateCreateCounter, AllowsUserCounterName)
|
||||||
TEST(EvaluateModifyCounter, RejectsWhenGameNotStarted)
|
TEST(EvaluateModifyCounter, RejectsWhenGameNotStarted)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(UserCounterId, 0);
|
Server_Counter counter = makeCounter(UserCounterId, 0);
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(/*gameStarted=*/false, /*playerConceded=*/false, true, UserCounterId,
|
EXPECT_EQ(Server_Player::evaluateModifyCounter(/*gameStarted=*/false, /*playerConceded=*/false, &counter),
|
||||||
&counter),
|
|
||||||
Response::RespGameNotStarted);
|
Response::RespGameNotStarted);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateModifyCounter, RejectsWhenPlayerConceded)
|
TEST(EvaluateModifyCounter, RejectsWhenPlayerConceded)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(UserCounterId, 0);
|
Server_Counter counter = makeCounter(UserCounterId, 0);
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, /*playerConceded=*/true, true, UserCounterId, &counter),
|
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, /*playerConceded=*/true, &counter),
|
||||||
Response::RespContextError);
|
Response::RespContextError);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateModifyCounter, RejectsMissingCounter)
|
TEST(EvaluateModifyCounter, RejectsMissingCounter)
|
||||||
{
|
{
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, true, UserCounterId, nullptr),
|
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, nullptr), Response::RespNameNotFound);
|
||||||
Response::RespNameNotFound);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateModifyCounter, AllowsUserCounter)
|
TEST(EvaluateModifyCounter, AllowsUserCounter)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(UserCounterId, 5);
|
Server_Counter counter = makeCounter(UserCounterId, 5);
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, true, UserCounterId, &counter), Response::RespOk);
|
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, &counter), Response::RespOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateModifyCounter, RejectsTaxCounterWhenCommandZoneDisabled)
|
// Cast count evaluation tests
|
||||||
|
|
||||||
|
TEST(EvaluateCreateCastCount, RejectsWhenGameNotStarted)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 0);
|
EXPECT_EQ(Server_Player::evaluateCreateCastCount(/*gameStarted=*/false, /*playerConceded=*/false,
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/false, CounterIds::TaxCounter1,
|
/*commandZoneEnabled=*/true, 1, /*exists=*/false,
|
||||||
&counter),
|
/*predecessorExists=*/true),
|
||||||
|
Response::RespGameNotStarted);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateCreateCastCount, RejectsWhenPlayerConceded)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateCreateCastCount(true, /*playerConceded=*/true, true, 1, false, true),
|
||||||
Response::RespContextError);
|
Response::RespContextError);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateModifyCounter, RejectsInactiveTaxCounter)
|
TEST(EvaluateCreateCastCount, RejectsWhenCommandZoneDisabled)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter2, 0, false);
|
EXPECT_EQ(Server_Player::evaluateCreateCastCount(true, false, /*commandZoneEnabled=*/false, 1, false, true),
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/true, CounterIds::TaxCounter2,
|
|
||||||
&counter),
|
|
||||||
Response::RespContextError);
|
Response::RespContextError);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateModifyCounter, AllowsActiveTaxCounter)
|
TEST(EvaluateCreateCastCount, RejectsInvalidIndex)
|
||||||
{
|
{
|
||||||
Server_Counter counter = makeCounter(CounterIds::TaxCounter1, 0, true);
|
EXPECT_EQ(Server_Player::evaluateCreateCastCount(true, false, true, 0, false, false), Response::RespContextError);
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/true, CounterIds::TaxCounter1,
|
EXPECT_EQ(Server_Player::evaluateCreateCastCount(true, false, true, 6, false, false), Response::RespContextError);
|
||||||
&counter),
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateCreateCastCount, RejectsExistingIndex)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateCreateCastCount(true, false, true, 1, /*exists=*/true, true),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateCreateCastCount, RejectsGapInSequence)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(
|
||||||
|
Server_Player::evaluateCreateCastCount(true, false, true, 3, /*exists=*/false, /*predecessorExists=*/false),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateCreateCastCount, AllowsNextInSequence)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(
|
||||||
|
Server_Player::evaluateCreateCastCount(true, false, true, 2, /*exists=*/false, /*predecessorExists=*/true),
|
||||||
|
Response::RespOk);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateDeleteCastCount, RejectsWhenGameNotStarted)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateDeleteCastCount(/*gameStarted=*/false, /*playerConceded=*/false,
|
||||||
|
/*commandZoneEnabled=*/true, 1, /*exists=*/true, /*value=*/0,
|
||||||
|
/*successorExists=*/false),
|
||||||
|
Response::RespGameNotStarted);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateDeleteCastCount, RejectsWhenPlayerConceded)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateDeleteCastCount(true, /*playerConceded=*/true, true, 1, true, 0, false),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateDeleteCastCount, RejectsWhenCommandZoneDisabled)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateDeleteCastCount(true, false, /*commandZoneEnabled=*/false, 1, true, 0, false),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateDeleteCastCount, RejectsMissingIndex)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateDeleteCastCount(true, false, true, 1, /*exists=*/false, 0, false),
|
||||||
|
Response::RespNameNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateDeleteCastCount, RejectsNonZeroValue)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateDeleteCastCount(true, false, true, 1, true, /*value=*/5, false),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateDeleteCastCount, RejectsNonHighestIndex)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateDeleteCastCount(true, false, true, 1, true, 0, /*successorExists=*/true),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateDeleteCastCount, AllowsHighestWithZeroValue)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateDeleteCastCount(true, false, true, 2, /*exists=*/true, /*value=*/0,
|
||||||
|
/*successorExists=*/false),
|
||||||
Response::RespOk);
|
Response::RespOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EvaluateModifyCounter, RejectsMissingTaxCounter)
|
TEST(EvaluateModifyCastCount, RejectsWhenGameNotStarted)
|
||||||
{
|
{
|
||||||
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/true, CounterIds::TaxCounter1,
|
EXPECT_EQ(Server_Player::evaluateModifyCastCount(/*gameStarted=*/false, /*playerConceded=*/false,
|
||||||
nullptr),
|
/*commandZoneEnabled=*/true, 1, /*exists=*/true),
|
||||||
|
Response::RespGameNotStarted);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateModifyCastCount, RejectsWhenPlayerConceded)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateModifyCastCount(true, /*playerConceded=*/true, true, 1, true),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateModifyCastCount, RejectsWhenCommandZoneDisabled)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateModifyCastCount(true, false, /*commandZoneEnabled=*/false, 1, true),
|
||||||
|
Response::RespContextError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateModifyCastCount, RejectsMissingIndex)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateModifyCastCount(true, false, true, 1, /*exists=*/false),
|
||||||
Response::RespNameNotFound);
|
Response::RespNameNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(EvaluateModifyCastCount, AllowsExistingIndex)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(Server_Player::evaluateModifyCastCount(true, false, true, 1, /*exists=*/true), Response::RespOk);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
|
|
||||||
|
|
@ -49,14 +49,6 @@ TEST(NewCounterId, SkipsReservedRangeWhenOnlyReservedCountersExist)
|
||||||
EXPECT_EQ(f.player.newCounterId(), CounterIds::FirstUserId);
|
EXPECT_EQ(f.player.newCounterId(), CounterIds::FirstUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(NewCounterId, SkipsTaxCounterIds)
|
|
||||||
{
|
|
||||||
PlayerFixture f;
|
|
||||||
f.player.addCounter(new Server_Counter(CounterIds::TaxCounter1, "tax1", color(), 0, 0));
|
|
||||||
f.player.addCounter(new Server_Counter(CounterIds::TaxCounter2, "tax2", color(), 0, 0));
|
|
||||||
EXPECT_EQ(f.player.newCounterId(), CounterIds::FirstUserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NewCounterId, ReturnsNextIdAboveHighestUserCounter)
|
TEST(NewCounterId, ReturnsNextIdAboveHighestUserCounter)
|
||||||
{
|
{
|
||||||
PlayerFixture f;
|
PlayerFixture f;
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ struct CommandZoneTestPlayer : Server_Player
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Builds a Server_Player on a minimal fake game whose command zone is toggled by the
|
* @brief Builds a Server_Player on a minimal fake game whose command zone is toggled by the
|
||||||
* constructor argument, then runs setupZones() so getCounters() reflects the result.
|
* constructor argument, then runs setupZones() so getCastCounts() reflects the result.
|
||||||
*/
|
*/
|
||||||
struct SetupFixture
|
struct SetupFixture
|
||||||
{
|
{
|
||||||
|
|
@ -86,55 +86,27 @@ struct SetupFixture
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(SetupZonesCommandZone, CreatesAllTaxCountersWhenEnabled)
|
TEST(SetupZonesCommandZone, CreatesFirstCastCountWhenEnabled)
|
||||||
{
|
{
|
||||||
SetupFixture f(true);
|
SetupFixture f(true);
|
||||||
const QMap<int, Server_Counter *> &counters = f.player.getCounters();
|
const QMap<int, int> &castCounts = f.player.getCastCounts();
|
||||||
|
|
||||||
EXPECT_TRUE(f.player.getZones().contains(ZoneNames::COMMAND));
|
EXPECT_TRUE(f.player.getZones().contains(ZoneNames::COMMAND));
|
||||||
|
|
||||||
for (int i = 0; i < CounterIds::TaxCounterCount; ++i) {
|
EXPECT_EQ(castCounts.size(), 1);
|
||||||
int id = CounterIds::taxCounterIdFromIndex(i);
|
EXPECT_TRUE(castCounts.contains(1));
|
||||||
ASSERT_TRUE(counters.contains(id));
|
EXPECT_EQ(castCounts.value(1), 0);
|
||||||
}
|
|
||||||
|
|
||||||
const Server_Counter *tax1 = counters.value(CounterIds::TaxCounter1);
|
|
||||||
EXPECT_TRUE(tax1->isActive());
|
|
||||||
EXPECT_EQ(tax1->getCount(), 0);
|
|
||||||
|
|
||||||
for (int i = 1; i < CounterIds::TaxCounterCount; ++i) {
|
|
||||||
int id = CounterIds::taxCounterIdFromIndex(i);
|
|
||||||
const Server_Counter *counter = counters.value(id);
|
|
||||||
EXPECT_FALSE(counter->isActive());
|
|
||||||
EXPECT_EQ(counter->getCount(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SetupZonesCommandZone, TaxCountersUseBounds)
|
TEST(SetupZonesCommandZone, NoCastCountsWhenDisabled)
|
||||||
{
|
|
||||||
SetupFixture f(true);
|
|
||||||
Server_Counter *tax1 = f.player.getCounters().value(CounterIds::TaxCounter1);
|
|
||||||
ASSERT_NE(tax1, nullptr);
|
|
||||||
|
|
||||||
EXPECT_TRUE(tax1->setCount(MAX_COUNTER_VALUE + 1000));
|
|
||||||
EXPECT_EQ(tax1->getCount(), MAX_COUNTER_VALUE);
|
|
||||||
EXPECT_TRUE(tax1->setCount(-1));
|
|
||||||
EXPECT_EQ(tax1->getCount(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SetupZonesCommandZone, NoTaxCountersWhenDisabled)
|
|
||||||
{
|
{
|
||||||
SetupFixture f(false);
|
SetupFixture f(false);
|
||||||
const QMap<int, Server_Counter *> &counters = f.player.getCounters();
|
const QMap<int, int> &castCounts = f.player.getCastCounts();
|
||||||
|
|
||||||
EXPECT_FALSE(f.player.getZones().contains(ZoneNames::COMMAND));
|
EXPECT_FALSE(f.player.getZones().contains(ZoneNames::COMMAND));
|
||||||
|
EXPECT_TRUE(castCounts.isEmpty());
|
||||||
|
|
||||||
for (int i = 0; i < CounterIds::TaxCounterCount; ++i) {
|
EXPECT_TRUE(f.player.getCounters().contains(0));
|
||||||
int id = CounterIds::taxCounterIdFromIndex(i);
|
|
||||||
EXPECT_FALSE(counters.contains(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_TRUE(counters.contains(0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,13 @@ TEST(ServerCounter, IncrementClampsToCustomBounds)
|
||||||
EXPECT_EQ(c.getCount(), 0);
|
EXPECT_EQ(c.getCount(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ServerCounter, CustomBoundsForCommanderTax)
|
TEST(ServerCounter, CustomBoundsForCastCount)
|
||||||
{
|
{
|
||||||
Server_Counter taxCounter(1, "tax", color(), 20, 0, 0, MAX_COUNTER_VALUE);
|
Server_Counter castCount(1, "cast_count", color(), 20, 0, 0, MAX_COUNTER_VALUE);
|
||||||
EXPECT_TRUE(taxCounter.setCount(1000));
|
EXPECT_TRUE(castCount.setCount(1000));
|
||||||
EXPECT_EQ(taxCounter.getCount(), MAX_COUNTER_VALUE);
|
EXPECT_EQ(castCount.getCount(), MAX_COUNTER_VALUE);
|
||||||
EXPECT_TRUE(taxCounter.setCount(-5));
|
EXPECT_TRUE(castCount.setCount(-5));
|
||||||
EXPECT_EQ(taxCounter.getCount(), 0);
|
EXPECT_EQ(castCount.getCount(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ServerCounter, SetActiveReportsChangeOnce)
|
TEST(ServerCounter, SetActiveReportsChangeOnce)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue