[ServerGame] Refactor out spectator/judge bools into RoleInfo (#7761)

This commit is contained in:
RickyRister 2026-11-02 00:37:05 -07:00 • committed by GitHub
parent a1a8a59f4a
commit 62e590d300
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 89 additions and 68 deletions

View file

@ -8,6 +8,7 @@ set(HEADERS
game/server_cardzone.h
game/server_counter.h
game/game_config.h
game/role_info.h
game/server_deck_validation_strategy.h
game/server_game.h
game/server_game_lifecycle_strategy.h

View file

@ -0,0 +1,13 @@
#ifndef COCKATRICE_ROLE_INFO_H
#define COCKATRICE_ROLE_INFO_H
/**
* @brief Represents information about a participant's roles.
*/
struct RoleInfo
{
bool isJudge = false; ///< Whether the participant joined as a judge
bool isSpectator = false; ///< Whether the participant joined as a spectator
};
#endif // COCKATRICE_ROLE_INFO_H

View file

@ -61,10 +61,10 @@
Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game,
int _playerId,
const ServerInfo_User &_userInfo,
bool _judge,
const RoleInfo &_roleInfo,
Server_AbstractUserInterface *_userInterface)
: ServerInfo_User_Container(_userInfo), game(_game), userInterface(_userInterface), pingTime(0),
playerId(_playerId), judge(_judge)
playerId(_playerId), roleInfo(_roleInfo)
{
}
@ -98,8 +98,8 @@ void Server_AbstractParticipant::getProperties(ServerInfo_PlayerProperties &resu
if (withUserInfo) {
copyUserInfo(*(result.mutable_user_info()), true);
}
result.set_spectator(spectator);
result.set_judge(judge);
result.set_spectator(isSpectator());
result.set_judge(isJudge());
result.set_ping_seconds(pingTime);
getPlayerProperties(result);
}
@ -176,7 +176,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdUnconcede(const Command_Un
Response::ResponseCode
Server_AbstractParticipant::cmdJudge(const Command_Judge &cmd, ResponseContainer &rc, GameEventStorage &ges)
{
if (!judge) {
if (!isJudge()) {
return Response::RespFunctionNotAllowed;
}
@ -204,7 +204,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdReadyStart(const Command_R
Response::ResponseCode
Server_AbstractParticipant::cmdGameSay(const Command_GameSay &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
if (spectator) {
if (isSpectator()) {
/* Spectators can only talk if:
* (a) the game creator allows it
* (b) the spectator is a moderator/administrator
@ -365,7 +365,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdNextTurn(const Command_Nex
return Response::RespGameNotStarted;
}
if (!judge) {
if (!isJudge()) {
return Response::RespFunctionNotAllowed;
}
@ -381,7 +381,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdSetActivePhase(const Comma
return Response::RespGameNotStarted;
}
if (!judge) {
if (!isJudge()) {
return Response::RespFunctionNotAllowed;
}
@ -415,8 +415,8 @@ Response::ResponseCode Server_AbstractParticipant::cmdReverseTurn(const Command_
ResponseContainer & /*rc*/,
GameEventStorage &ges)
{
if (!judge) {
if (spectator) {
if (!isJudge()) {
if (isSpectator()) {
return Response::RespFunctionNotAllowed;
}
@ -577,7 +577,7 @@ void Server_AbstractParticipant::setUserInterface(Server_AbstractUserInterface *
void Server_AbstractParticipant::disconnectClient()
{
bool isRegistered = userInfo->user_level() & ServerInfo_User::IsRegistered;
if (!isRegistered || spectator) {
if (!isRegistered || isSpectator()) {
game->removeParticipant(this, Event_Leave::USER_DISCONNECTED);
} else {
setUserInterface(nullptr);

View file

@ -2,6 +2,7 @@
#define ABSTRACT_PARTICIPANT_H
#include "../serverinfo_user_container.h"
#include "role_info.h"
#include "server_arrowtarget.h"
#include <QMutex>
@ -61,8 +62,7 @@ protected:
Server_AbstractUserInterface *userInterface;
int pingTime;
int playerId;
bool spectator;
bool judge;
RoleInfo roleInfo;
virtual void getPlayerProperties(ServerInfo_PlayerProperties &result);
mutable QMutex playerMutex;
@ -70,7 +70,7 @@ public:
Server_AbstractParticipant(Server_Game *_game,
int _playerId,
const ServerInfo_User &_userInfo,
bool _judge,
const RoleInfo &_roleInfo,
Server_AbstractUserInterface *_handler);
~Server_AbstractParticipant() override;
virtual void prepareDestroy()
@ -91,11 +91,11 @@ public:
}
bool isSpectator() const
{
return spectator;
return roleInfo.isSpectator;
}
bool isJudge() const
{
return judge;
return roleInfo.isJudge;
}
Server_Game *getGame() const
{

View file

@ -81,10 +81,9 @@ Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
const ServerInfo_User &_userInfo,
bool _judge,
Server_AbstractUserInterface *_userInterface)
: Server_AbstractParticipant(_game, _playerId, _userInfo, _judge, _userInterface), conceded(false), deck(nullptr),
sideboardLocked(true), readyStart(false), nextCardId(0)
: Server_AbstractParticipant(_game, _playerId, _userInfo, {.isJudge = _judge}, _userInterface), conceded(false),
deck(nullptr), sideboardLocked(true), readyStart(false), nextCardId(0)
{
spectator = false;
}
Server_AbstractPlayer::~Server_AbstractPlayer() = default;
@ -295,7 +294,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges,
{
// Disallow controller change to other zones than the table.
if (((targetzone->getType() != ServerInfo_Zone::PublicZone) || !targetzone->hasCoords()) &&
(startzone->getPlayer() != targetzone->getPlayer()) && !judge) {
(startzone->getPlayer() != targetzone->getPlayer()) && !isJudge()) {
return Response::RespContextError;
}
@ -808,7 +807,7 @@ Server_AbstractPlayer::cmdMoveCard(const Command_MoveCard &cmd, ResponseContaine
return Response::RespNameNotFound;
}
if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)) && !judge) {
if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)) && !isJudge()) {
return Response::RespContextError;
}
@ -821,7 +820,7 @@ Server_AbstractPlayer::cmdMoveCard(const Command_MoveCard &cmd, ResponseContaine
return Response::RespNameNotFound;
}
if ((startPlayer != this) && (targetPlayer != this) && !judge) {
if ((startPlayer != this) && (targetPlayer != this) && !isJudge()) {
return Response::RespContextError;
}
@ -1598,7 +1597,7 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo
zone->addWritePermission(cmd.player_id());
}
if (judge) {
if (isJudge()) {
ges.setOverwriteOwnership(true);
}

View file

@ -41,6 +41,7 @@
#include "libcockatrice/protocol/pb/serverinfo_replay_match.pb.h"
#include "libcockatrice/protocol/pb/serverinfo_user.pb.h"
#include "libcockatrice/protocol/pb/session_event.pb.h"
#include "role_info.h"
#include "server_abstract_player.h"
#include "server_abstractuserinterface.h"
#include "server_arrow.h"
@ -479,9 +480,8 @@ void Server_Game::stopGameIfFinished()
Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user,
const QString &_password,
bool spectator,
bool overrideRestrictions,
bool asJudge)
const RoleInfo &roleInfo,
bool overrideRestrictions)
{
Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface();
for (auto *participant : participants.values()) {
@ -490,11 +490,11 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user,
}
}
if (asJudge && !(user->user_level() & ServerInfo_User::IsJudge)) {
if (roleInfo.isJudge && !(user->user_level() & ServerInfo_User::IsJudge)) {
return Response::RespUserLevelTooLow;
}
if (!(overrideRestrictions && (user->user_level() & (ServerInfo_User::IsModerator | ServerInfo_User::IsJudge)))) {
if ((_password != password) && !(spectator && !spectatorsNeedPassword)) {
if ((_password != password) && !(roleInfo.isSpectator && !spectatorsNeedPassword)) {
return Response::RespWrongPassword;
}
if (!(user->user_level() & ServerInfo_User::IsRegistered) && onlyRegistered) {
@ -510,13 +510,13 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user,
QString::fromStdString(user->name()))) {
return Response::RespInIgnoreList;
}
if (spectator) {
if (roleInfo.isSpectator) {
if (!spectatorsAllowed) {
return Response::RespSpectatorsNotAllowed;
}
}
}
if (!spectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) {
if (!roleInfo.isSpectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) {
return Response::RespGameFull;
}
@ -537,19 +537,18 @@ bool Server_Game::containsUser(const QString &userName) const
void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface,
ResponseContainer &rc,
bool spectator,
bool judge,
const RoleInfo &roleInfo,
bool broadcastUpdate)
{
QMutexLocker locker(&gameMutex);
Server_AbstractParticipant *newParticipant;
if (spectator) {
if (roleInfo.isSpectator) {
newParticipant = new Server_Spectator(this, nextPlayerId++, userInterface->copyUserInfo(true, true, true),
judge, userInterface);
roleInfo.isJudge, userInterface);
} else {
newParticipant = new Server_Player(this, nextPlayerId++, userInterface->copyUserInfo(true, true, true), judge,
userInterface);
newParticipant = new Server_Player(this, nextPlayerId++, userInterface->copyUserInfo(true, true, true),
roleInfo.isJudge, userInterface);
}
newParticipant->moveToThread(thread());
@ -560,7 +559,7 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface,
const QString playerName = QString::fromStdString(newParticipant->getUserInfo()->name());
participants.insert(newParticipant->getPlayerId(), newParticipant);
if (spectator) {
if (roleInfo.isSpectator) {
allSpectatorsEver.insert(playerName);
} else {
allPlayersEver.insert(playerName);
@ -579,7 +578,7 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface,
emit gameInfoChanged(gameInfo);
}
if ((newParticipant->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !spectator) {
if ((newParticipant->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !roleInfo.isSpectator) {
room->getServer()->addPersistentPlayer(playerName, room->getId(), gameId, newParticipant->getPlayerId());
}
@ -871,6 +870,13 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti
}
}
bool Server_Game::canSeePrivateEventsFor(const Server_AbstractParticipant *participant, int privatePlayerId) const
{
return participant->getPlayerId() == privatePlayerId // same player
|| participant->isJudge() // judges see everything
|| (participant->isSpectator() && spectatorsSeeEverything); // spectators see everything
}
void Server_Game::sendGameEventContainer(GameEventContainer *cont,
GameEventStorageItem::EventRecipients recipients,
int privatePlayerId)
@ -879,8 +885,7 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont,
cont->set_game_id(gameId);
for (auto *participant : participants.values()) {
const bool playerPrivate = (participant->getPlayerId() == privatePlayerId) || participant->isJudge() ||
(participant->isSpectator() && spectatorsSeeEverything);
const bool playerPrivate = canSeePrivateEventsFor(participant, privatePlayerId);
if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) ||
(recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) {
participant->sendGameEvent(*cont);

View file

@ -62,6 +62,7 @@ class Message;
} // namespace protobuf
} // namespace google
struct GameConfig;
struct RoleInfo;
class Server_Game : public QObject
{
@ -109,6 +110,7 @@ private:
bool omniscient,
bool withUserInfo);
void storeGameInformation();
bool canSeePrivateEventsFor(const Server_AbstractParticipant *participant, int privatePlayerId) const;
signals:
void sigStartGameIfReady(bool override);
void gameInfoChanged(ServerInfo_Game gameInfo);
@ -188,12 +190,11 @@ public:
return shareDecklistsOnLoad;
}
Response::ResponseCode
checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions, bool asJudge);
checkJoin(ServerInfo_User *user, const QString &_password, const RoleInfo &roleInfo, bool overrideRestrictions);
bool containsUser(const QString &userName) const;
void addPlayer(Server_AbstractUserInterface *userInterface,
ResponseContainer &rc,
bool spectator,
bool judge,
const RoleInfo &roleInfo,
bool broadcastUpdate = true);
void removeParticipant(Server_AbstractParticipant *participant, Event_Leave::LeaveReason reason);
void removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_AbstractPlayer *player);

View file

@ -573,7 +573,7 @@ Server_Player::cmdNextTurn(const Command_NextTurn & /*cmd*/, ResponseContainer &
return Response::RespGameNotStarted;
}
if (conceded && !judge) {
if (conceded && !isJudge()) {
return Response::RespContextError;
}
@ -589,7 +589,7 @@ Response::ResponseCode Server_Player::cmdSetActivePhase(const Command_SetActiveP
return Response::RespGameNotStarted;
}
if (!judge) {
if (!isJudge()) {
if (conceded) {
return Response::RespContextError;
}
@ -623,7 +623,7 @@ Response::ResponseCode
Server_Player::cmdReverseTurn(const Command_ReverseTurn &cmd, ResponseContainer &rc, GameEventStorage &ges)
{
if (!judge && conceded) {
if (!isJudge() && conceded) {
return Response::RespContextError;
}
return Server_AbstractParticipant::cmdReverseTurn(cmd, rc, ges);

View file

@ -11,7 +11,6 @@ Server_Spectator::Server_Spectator(Server_Game *_game,
const ServerInfo_User &_userInfo,
bool _judge,
Server_AbstractUserInterface *_userInterface)
: Server_AbstractParticipant(_game, _playerId, _userInfo, _judge, _userInterface)
: Server_AbstractParticipant(_game, _playerId, _userInfo, {.isJudge = _judge, .isSpectator = true}, _userInterface)
{
spectator = true;
}

View file

@ -1141,7 +1141,8 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
auto *game = new Server_Game(config, room);
game->addPlayer(this, rc, asSpectator, asJudge, false);
RoleInfo roleInfo{.isJudge = asJudge, .isSpectator = asSpectator};
game->addPlayer(this, rc, roleInfo, false);
room->addGame(game);
return Response::RespOk;

View file

@ -1,5 +1,6 @@
#include "server_room.h"
#include "game/role_info.h"
#include "game/server_game.h"
#include "libcockatrice/protocol/pb/response.pb.h"
#include "libcockatrice/protocol/pb/room_event.pb.h"
@ -372,11 +373,11 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam
QMutexLocker gameLocker(&game->gameMutex);
Response::ResponseCode result =
game->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()), cmd.spectator(),
cmd.override_restrictions(), cmd.join_as_judge());
RoleInfo roleInfo{.isJudge = cmd.join_as_judge(), .isSpectator = cmd.spectator()};
Response::ResponseCode result = game->checkJoin(
userInterface->getUserInfo(), QString::fromStdString(cmd.password()), roleInfo, cmd.override_restrictions());
if (result == Response::RespOk) {
game->addPlayer(userInterface, rc, cmd.spectator(), cmd.join_as_judge());
game->addPlayer(userInterface, rc, roleInfo);
}
return result;

View file

@ -4,6 +4,7 @@
*/
#include "game/game_config.h"
#include "game/role_info.h"
#include "game/server_game.h"
#include "libcockatrice/protocol/pb/response.pb.h"
#include "server.h"
@ -119,13 +120,13 @@ TEST_F(GameJoinOverrideTest, StaffBypassPasswordRestriction)
game = makeGame(true, false, false, true);
// A plain user cannot override the password even with the override flag set.
EXPECT_EQ(game->checkJoin(&plainUser, "wrong", false, true, false), Response::RespWrongPassword);
EXPECT_EQ(game->checkJoin(&plainUser, "wrong", {}, true), Response::RespWrongPassword);
// Moderators and judges may enter any game regardless of the password.
EXPECT_EQ(game->checkJoin(&moderator, "wrong", false, true, false), Response::RespOk);
EXPECT_EQ(game->checkJoin(&judge, "wrong", false, true, false), Response::RespOk);
EXPECT_EQ(game->checkJoin(&moderator, "wrong", {}, true), Response::RespOk);
EXPECT_EQ(game->checkJoin(&judge, "wrong", {}, true), Response::RespOk);
// Without the override flag judges are still subject to the password.
EXPECT_EQ(game->checkJoin(&judge, "wrong", false, false, true), Response::RespWrongPassword);
EXPECT_EQ(game->checkJoin(&judge, "secret", false, false, true), Response::RespOk);
EXPECT_EQ(game->checkJoin(&judge, "wrong", {.isJudge = true}, false), Response::RespWrongPassword);
EXPECT_EQ(game->checkJoin(&judge, "secret", {.isJudge = true}, false), Response::RespOk);
}
TEST_F(GameJoinOverrideTest, StaffBypassRegisteredOnlyRestriction)
@ -133,9 +134,9 @@ TEST_F(GameJoinOverrideTest, StaffBypassRegisteredOnlyRestriction)
game = makeGame(false, true, false, true);
// Without the override flag the only-registered restriction still applies.
EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, false, false), Response::RespUserLevelTooLow);
EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), {}, false), Response::RespUserLevelTooLow);
// An unregistered judge may enter when overriding restrictions.
EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, true, false), Response::RespOk);
EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), {}, true), Response::RespOk);
}
TEST_F(GameJoinOverrideTest, StaffBypassBuddiesOnlyRestriction)
@ -143,10 +144,10 @@ TEST_F(GameJoinOverrideTest, StaffBypassBuddiesOnlyRestriction)
game = makeGame(false, false, true, true);
// A plain user who is not on the creator's buddy list gets rejected.
EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, false), Response::RespOnlyBuddies);
EXPECT_EQ(game->checkJoin(&plainUser, QString(), {}, true), Response::RespOnlyBuddies);
// Moderators and judges bypass the buddies-only restriction.
EXPECT_EQ(game->checkJoin(&moderator, QString(), false, true, false), Response::RespOk);
EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, false), Response::RespOk);
EXPECT_EQ(game->checkJoin(&moderator, QString(), {}, true), Response::RespOk);
EXPECT_EQ(game->checkJoin(&judge, QString(), {}, true), Response::RespOk);
}
TEST_F(GameJoinOverrideTest, StaffBypassSpectatorsNotAllowedRestriction)
@ -154,11 +155,11 @@ TEST_F(GameJoinOverrideTest, StaffBypassSpectatorsNotAllowedRestriction)
game = makeGame(false, false, false, false);
// A plain user cannot spectate when the game disallows spectators.
EXPECT_EQ(game->checkJoin(&plainUser, QString(), true, false, false), Response::RespSpectatorsNotAllowed);
EXPECT_EQ(game->checkJoin(&plainUser, QString(), {.isSpectator = true}, false), Response::RespSpectatorsNotAllowed);
// Moderators and judges may spectate any game regardless of the password
// and the spectator restriction.
EXPECT_EQ(game->checkJoin(&moderator, "wrong", true, true, false), Response::RespOk);
EXPECT_EQ(game->checkJoin(&judge, "wrong", true, true, false), Response::RespOk);
EXPECT_EQ(game->checkJoin(&moderator, "wrong", {.isSpectator = true}, true), Response::RespOk);
EXPECT_EQ(game->checkJoin(&judge, "wrong", {.isSpectator = true}, true), Response::RespOk);
}
TEST_F(GameJoinOverrideTest, JudgeOverrideDoesNotGrantJudgeJoinToPlainUser)
@ -166,8 +167,8 @@ TEST_F(GameJoinOverrideTest, JudgeOverrideDoesNotGrantJudgeJoinToPlainUser)
game = makeGame(false, false, false, true);
// joining with join_as_judge still requires the judge flag even when overriding.
EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, true), Response::RespUserLevelTooLow);
EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, true), Response::RespOk);
EXPECT_EQ(game->checkJoin(&plainUser, QString(), {.isJudge = true}, true), Response::RespUserLevelTooLow);
EXPECT_EQ(game->checkJoin(&judge, QString(), {.isJudge = true}, true), Response::RespOk);
}
} // namespace