mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 20:13:24 -08:00
Merge current upstream into random-reveal review continuation
Preserve SET_PLAYER_ORDER alongside REVEAL_RANDOM_CARDS in the shared command enum; retain current upstream source and the reviewed PlayerActions routing.
This commit is contained in:
commit
e32bd364bd
443 changed files with 15635 additions and 11767 deletions
|
|
@ -1,7 +1,3 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS
|
||||
game/dump_zone_range.h
|
||||
game/server_abstract_participant.h
|
||||
|
|
@ -12,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
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ struct GameConfig
|
|||
bool spectatorsSeeEverything = true;
|
||||
int startingLifeTotal = 20;
|
||||
bool shareDecklistsOnLoad = true;
|
||||
bool shufflePlayers = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_card_counter.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_counter.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_player_order.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_playmat.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_sideboard_lock.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_sideboard_plan.pb.h>
|
||||
|
|
@ -61,10 +62,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 +99,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 +177,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 +205,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 +366,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdNextTurn(const Command_Nex
|
|||
return Response::RespGameNotStarted;
|
||||
}
|
||||
|
||||
if (!judge) {
|
||||
if (!isJudge()) {
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
|
|
@ -381,7 +382,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdSetActivePhase(const Comma
|
|||
return Response::RespGameNotStarted;
|
||||
}
|
||||
|
||||
if (!judge) {
|
||||
if (!isJudge()) {
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
|
|
@ -416,8 +417,8 @@ Response::ResponseCode Server_AbstractParticipant::cmdReverseTurn(const Command_
|
|||
ResponseContainer & /*rc*/,
|
||||
GameEventStorage &ges)
|
||||
{
|
||||
if (!judge) {
|
||||
if (spectator) {
|
||||
if (!isJudge()) {
|
||||
if (isSpectator()) {
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
|
|
@ -435,6 +436,33 @@ Response::ResponseCode Server_AbstractParticipant::cmdReverseTurn(const Command_
|
|||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode Server_AbstractParticipant::cmdSetPlayerOrder(const Command_SetPlayerOrder &cmd,
|
||||
ResponseContainer & /*rc*/,
|
||||
GameEventStorage & /*ges*/)
|
||||
{
|
||||
if ((game->getHostId() != playerId) && !(userInfo->user_level() & ServerInfo_User::IsModerator)) {
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
|
||||
if (game->getGameStarted()) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
if (cmd.randomize()) {
|
||||
game->shufflePlayerSeats();
|
||||
} else {
|
||||
QList<QString> playerNames;
|
||||
for (int i = 0; i < cmd.player_names_size(); ++i) {
|
||||
playerNames.append(QString::fromStdString(cmd.player_names(i)));
|
||||
}
|
||||
game->reorderPlayerSeats(playerNames);
|
||||
}
|
||||
|
||||
// Clients learn the new seating from the Event_PlayerOrderChanged that
|
||||
// reseatPlayers broadcasts; no full game state resend is needed for it.
|
||||
return Response::RespOk;
|
||||
}
|
||||
|
||||
Response::ResponseCode
|
||||
Server_AbstractParticipant::processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges)
|
||||
{
|
||||
|
|
@ -558,6 +586,9 @@ Server_AbstractParticipant::processGameCommand(const GameCommand &command, Respo
|
|||
case GameCommand::SET_PLAYMAT:
|
||||
return cmdSetPlaymat(command.GetExtension(Command_SetPlaymat::ext), rc, ges);
|
||||
break;
|
||||
case GameCommand::SET_PLAYER_ORDER:
|
||||
return cmdSetPlayerOrder(command.GetExtension(Command_SetPlayerOrder::ext), rc, ges);
|
||||
break;
|
||||
default:
|
||||
return Response::RespInvalidCommand;
|
||||
}
|
||||
|
|
@ -592,7 +623,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);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define ABSTRACT_PARTICIPANT_H
|
||||
|
||||
#include "../serverinfo_user_container.h"
|
||||
#include "role_info.h"
|
||||
#include "server_arrowtarget.h"
|
||||
|
||||
#include <QMutex>
|
||||
|
|
@ -52,6 +53,7 @@ class Command_DeckSelect;
|
|||
class Command_SetSideboardLock;
|
||||
class Command_ChangeZoneProperties;
|
||||
class Command_SetPlaymat;
|
||||
class Command_SetPlayerOrder;
|
||||
|
||||
class Server_AbstractParticipant : public Server_ArrowTarget, public ServerInfo_User_Container
|
||||
{
|
||||
|
|
@ -61,8 +63,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 +71,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()
|
||||
|
|
@ -89,13 +90,17 @@ public:
|
|||
{
|
||||
return playerId;
|
||||
}
|
||||
void setPlayerId(int _playerId)
|
||||
{
|
||||
playerId = _playerId;
|
||||
}
|
||||
bool isSpectator() const
|
||||
{
|
||||
return spectator;
|
||||
return roleInfo.isSpectator;
|
||||
}
|
||||
bool isJudge() const
|
||||
{
|
||||
return judge;
|
||||
return roleInfo.isJudge;
|
||||
}
|
||||
Server_Game *getGame() const
|
||||
{
|
||||
|
|
@ -173,6 +178,8 @@ public:
|
|||
virtual Response::ResponseCode
|
||||
cmdReverseTurn(const Command_ReverseTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage &ges);
|
||||
virtual Response::ResponseCode
|
||||
cmdSetPlayerOrder(const Command_SetPlayerOrder &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||
virtual Response::ResponseCode
|
||||
cmdChangeZoneProperties(const Command_ChangeZoneProperties &cmd, ResponseContainer &rc, GameEventStorage &ges);
|
||||
|
||||
Response::ResponseCode processGameCommand(const GameCommand &command, ResponseContainer &rc, GameEventStorage &ges);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -1607,7 +1606,7 @@ Response::ResponseCode Server_AbstractPlayer::cmdRevealCards(const Command_Revea
|
|||
zone->addWritePermission(cmd.player_id());
|
||||
}
|
||||
|
||||
if (judge) {
|
||||
if (isJudge()) {
|
||||
ges.setOverwriteOwnership(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -72,11 +73,13 @@
|
|||
#include <libcockatrice/protocol/pb/event_join.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_kicked.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_leave.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_player_order_changed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_player_properties_changed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_replay_added.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_set_active_phase.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_set_active_player.pb.h>
|
||||
#include <libcockatrice/protocol/pb/game_replay.pb.h>
|
||||
#include <libcockatrice/rng/rng_abstract.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
#include <qlogging.h>
|
||||
#include <qnamespace.h>
|
||||
|
|
@ -90,8 +93,8 @@ Server_Game::Server_Game(const GameConfig &config, Server_Room *_room)
|
|||
spectatorsAllowed(config.spectatorsAllowed), spectatorsNeedPassword(config.spectatorsNeedPassword),
|
||||
spectatorsCanTalk(config.spectatorsCanTalk), spectatorsSeeEverything(config.spectatorsSeeEverything),
|
||||
startingLifeTotal(config.startingLifeTotal), shareDecklistsOnLoad(config.shareDecklistsOnLoad),
|
||||
inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false),
|
||||
turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr),
|
||||
shufflePlayers(config.shufflePlayers), inactivityCounter(0), startTimeOfThisGame(0), secondsElapsed(0),
|
||||
firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()), pingClock(nullptr),
|
||||
deckValidationStrategy(new Server_DefaultDeckValidationStrategy),
|
||||
lifecycleStrategy(new Server_DefaultLifecycleStrategy), matchResultStrategy(new Server_NullMatchResultStrategy),
|
||||
gameMutex()
|
||||
|
|
@ -291,6 +294,32 @@ int Server_Game::getSpectatorCount() const
|
|||
return result;
|
||||
}
|
||||
|
||||
QList<Server_AbstractParticipant *> Server_Game::getParticipantsInSeatOrder() const
|
||||
{
|
||||
// Players in seat order first (fall back to id order for seats predating
|
||||
// seatOrder), then spectators. Player ids are stable; only the ordering
|
||||
// changes on shuffle/reorder.
|
||||
QList<Server_AbstractParticipant *> ordered;
|
||||
ordered.reserve(participants.size());
|
||||
for (int id : seatOrder) {
|
||||
auto *participant = participants.value(id, nullptr);
|
||||
if (participant && !participant->isSpectator() && !ordered.contains(participant)) {
|
||||
ordered.append(participant);
|
||||
}
|
||||
}
|
||||
for (auto *participant : participants.values()) {
|
||||
if (!participant->isSpectator() && !ordered.contains(participant)) {
|
||||
ordered.append(participant);
|
||||
}
|
||||
}
|
||||
for (auto *participant : participants.values()) {
|
||||
if (participant->isSpectator()) {
|
||||
ordered.append(participant);
|
||||
}
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
void Server_Game::createGameStateChangedEvent(Event_GameStateChanged *event,
|
||||
Server_AbstractParticipant *recipient,
|
||||
bool omniscient,
|
||||
|
|
@ -305,16 +334,24 @@ void Server_Game::createGameStateChangedEvent(Event_GameStateChanged *event,
|
|||
event->set_game_started(false);
|
||||
}
|
||||
|
||||
for (Server_AbstractParticipant *participant : participants.values()) {
|
||||
// Emit the player list in seat order so every client (including old ones
|
||||
// that ignore Event_PlayerOrderChanged) sees the current seating. Player
|
||||
// ids are stable; only this ordering changes on shuffle/reorder.
|
||||
QList<Server_AbstractParticipant *> ordered = getParticipantsInSeatOrder();
|
||||
for (Server_AbstractParticipant *participant : ordered) {
|
||||
participant->getInfo(event->add_player_list(), recipient, omniscient, withUserInfo);
|
||||
}
|
||||
|
||||
if (recipient != nullptr) {
|
||||
event->set_local_player_id(recipient->getPlayerId());
|
||||
}
|
||||
}
|
||||
|
||||
void Server_Game::sendGameStateToPlayers()
|
||||
{
|
||||
// game state information for replay and omniscient spectators
|
||||
Event_GameStateChanged omniscientEvent;
|
||||
createGameStateChangedEvent(&omniscientEvent, nullptr, true, false);
|
||||
createGameStateChangedEvent(&omniscientEvent, nullptr, true, true);
|
||||
|
||||
GameEventContainer *replayCont = prepareGameEvent(omniscientEvent, -1);
|
||||
replayCont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame);
|
||||
|
|
@ -326,7 +363,7 @@ void Server_Game::sendGameStateToPlayers()
|
|||
// the data we used for the replay. All spectators are equal, so we don't need to make a createGameStateChangedEvent
|
||||
// call for each one.
|
||||
Event_GameStateChanged spectatorNormalEvent;
|
||||
createGameStateChangedEvent(&spectatorNormalEvent, nullptr, false, false);
|
||||
createGameStateChangedEvent(&spectatorNormalEvent, nullptr, false, true);
|
||||
|
||||
// send game state info to clients according to their role in the game
|
||||
for (auto *participant : participants.values()) {
|
||||
|
|
@ -339,7 +376,7 @@ void Server_Game::sendGameStateToPlayers()
|
|||
}
|
||||
} else {
|
||||
Event_GameStateChanged event;
|
||||
createGameStateChangedEvent(&event, participant, participant->isJudge(), false);
|
||||
createGameStateChangedEvent(&event, participant, participant->isJudge(), true);
|
||||
|
||||
gec = prepareGameEvent(event, -1);
|
||||
}
|
||||
|
|
@ -379,6 +416,11 @@ void Server_Game::doStartGameIfReady(bool forceStartGame)
|
|||
return;
|
||||
}
|
||||
|
||||
if (shufflePlayers && players.size() > 1) {
|
||||
shufflePlayerSeats();
|
||||
players = getPlayers();
|
||||
}
|
||||
|
||||
for (Server_AbstractPlayer *player : players.values()) {
|
||||
player->setupZones();
|
||||
}
|
||||
|
|
@ -432,6 +474,116 @@ void Server_Game::startGameIfReady(bool forceStartGame)
|
|||
emit sigStartGameIfReady(forceStartGame);
|
||||
}
|
||||
|
||||
void Server_Game::shufflePlayerSeats()
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
||||
// Collect players in current seat order (fall back to id order for seats
|
||||
// predating seatOrder). Spectators keep their seats.
|
||||
QList<Server_AbstractParticipant *> players;
|
||||
for (int id : seatOrder) {
|
||||
auto *participant = participants.value(id, nullptr);
|
||||
if (participant && !participant->isSpectator()) {
|
||||
players.append(participant);
|
||||
}
|
||||
}
|
||||
for (auto *participant : participants.values()) {
|
||||
if (!participant->isSpectator() && !players.contains(participant)) {
|
||||
players.append(participant);
|
||||
}
|
||||
}
|
||||
QList<Server_AbstractParticipant *> spectators;
|
||||
for (auto *participant : participants.values()) {
|
||||
if (participant->isSpectator()) {
|
||||
spectators.append(participant);
|
||||
}
|
||||
}
|
||||
|
||||
// Fisher-Yates shuffle of the players.
|
||||
QList<Server_AbstractParticipant *> shuffledPlayers;
|
||||
while (!players.isEmpty()) {
|
||||
const int index = rng->rand(0, players.size() - 1);
|
||||
shuffledPlayers.append(players.takeAt(index));
|
||||
}
|
||||
|
||||
reseatPlayers(shuffledPlayers + spectators, true);
|
||||
}
|
||||
|
||||
void Server_Game::reorderPlayerSeats(const QList<QString> &orderedNames)
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
||||
// Collect players in current seat order (fall back to id order for seats
|
||||
// predating seatOrder). Spectators keep their seats.
|
||||
QList<Server_AbstractParticipant *> players;
|
||||
for (int id : seatOrder) {
|
||||
auto *participant = participants.value(id, nullptr);
|
||||
if (participant && !participant->isSpectator()) {
|
||||
players.append(participant);
|
||||
}
|
||||
}
|
||||
for (auto *participant : participants.values()) {
|
||||
if (!participant->isSpectator() && !players.contains(participant)) {
|
||||
players.append(participant);
|
||||
}
|
||||
}
|
||||
QList<Server_AbstractParticipant *> spectators;
|
||||
for (auto *participant : participants.values()) {
|
||||
if (participant->isSpectator()) {
|
||||
spectators.append(participant);
|
||||
}
|
||||
}
|
||||
|
||||
// Order the players according to the given names, appending any player that
|
||||
// was not listed (should not happen) in their current seat order.
|
||||
QList<Server_AbstractParticipant *> orderedPlayers;
|
||||
QSet<Server_AbstractParticipant *> placed;
|
||||
for (const QString &name : orderedNames) {
|
||||
for (auto *participant : players) {
|
||||
if (!placed.contains(participant) && (QString::fromStdString(participant->getUserInfo()->name()) == name)) {
|
||||
orderedPlayers.append(participant);
|
||||
placed.insert(participant);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto *participant : players) {
|
||||
if (!placed.contains(participant)) {
|
||||
orderedPlayers.append(participant);
|
||||
}
|
||||
}
|
||||
|
||||
reseatPlayers(orderedPlayers + spectators, false);
|
||||
}
|
||||
|
||||
void Server_Game::reseatPlayers(const QList<Server_AbstractParticipant *> &orderedParticipants, bool randomized)
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
||||
// Keep player ids stable and only reorder the seating/turn order. Changing
|
||||
// ids would break old clients (stale localPlayerId from Event_GameJoined
|
||||
// duplicates their own seat as an opponent), invalidate the home-server
|
||||
// per-user game/seat mapping used for ISL command routing, and orphan the
|
||||
// host id. All of those maps stay valid because ids never change here.
|
||||
// The current host therefore keeps its id and needs no re-key tracking.
|
||||
seatOrder.clear();
|
||||
for (auto *participant : orderedParticipants) {
|
||||
if (!participant->isSpectator()) {
|
||||
seatOrder.append(participant->getPlayerId());
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast the new seating order so that every client can display it.
|
||||
Event_PlayerOrderChanged event;
|
||||
for (auto *participant : orderedParticipants) {
|
||||
if (!participant->isSpectator()) {
|
||||
event.add_player_names(QString::fromStdString(participant->getUserInfo()->name()).toStdString());
|
||||
}
|
||||
}
|
||||
event.set_randomized(randomized);
|
||||
sendGameEventContainer(prepareGameEvent(event, -1));
|
||||
}
|
||||
|
||||
void Server_Game::stopGameIfFinished()
|
||||
{
|
||||
QMutexLocker locker(&gameMutex);
|
||||
|
|
@ -479,9 +631,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 +641,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 +661,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 +688,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,10 +710,11 @@ 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);
|
||||
seatOrder.append(newParticipant->getPlayerId());
|
||||
|
||||
// if the original creator of the game joins, give them host status back
|
||||
//! \todo Transferring host to spectators has side effects.
|
||||
|
|
@ -579,7 +730,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());
|
||||
}
|
||||
|
||||
|
|
@ -593,6 +744,7 @@ void Server_Game::removeParticipant(Server_AbstractParticipant *participant, Eve
|
|||
room->getServer()->removePersistentPlayer(QString::fromStdString(participant->getUserInfo()->name()), room->getId(),
|
||||
gameId, participant->getPlayerId());
|
||||
participants.remove(participant->getPlayerId());
|
||||
seatOrder.removeAll(participant->getPlayerId());
|
||||
|
||||
bool spectator = participant->isSpectator();
|
||||
GameEventStorage ges;
|
||||
|
|
@ -612,10 +764,20 @@ void Server_Game::removeParticipant(Server_AbstractParticipant *participant, Eve
|
|||
participant->prepareDestroy();
|
||||
|
||||
if (playerHost) {
|
||||
// Pass host to the next player in seat order so the game always keeps
|
||||
// a host while players remain.
|
||||
int newHostId = -1;
|
||||
for (auto *otherPlayer : getPlayers().values()) {
|
||||
newHostId = otherPlayer->getPlayerId();
|
||||
break;
|
||||
for (int id : seatOrder) {
|
||||
if (participants.contains(id)) {
|
||||
newHostId = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newHostId == -1) {
|
||||
for (auto *otherPlayer : getPlayers().values()) {
|
||||
newHostId = otherPlayer->getPlayerId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (newHostId != -1) {
|
||||
hostId = newHostId;
|
||||
|
|
@ -772,7 +934,20 @@ void Server_Game::nextTurn()
|
|||
}
|
||||
|
||||
auto players = getPlayers();
|
||||
const QList<int> keys = players.keys();
|
||||
// Advance in seat order, not id order, so shuffles/reorders change the
|
||||
// turn sequence while ids stay stable.
|
||||
QList<int> keys;
|
||||
keys.reserve(players.size());
|
||||
for (int id : seatOrder) {
|
||||
if (players.contains(id)) {
|
||||
keys.append(id);
|
||||
}
|
||||
}
|
||||
for (int id : players.keys()) {
|
||||
if (!keys.contains(id)) {
|
||||
keys.append(id);
|
||||
}
|
||||
}
|
||||
int listPos = -1;
|
||||
if (activePlayer != -1) {
|
||||
listPos = keys.indexOf(activePlayer);
|
||||
|
|
@ -822,7 +997,7 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti
|
|||
event2.set_active_phase(activePhase);
|
||||
|
||||
bool omniscient = (joiningParticipant->isSpectator() && spectatorsSeeEverything) || joiningParticipant->isJudge();
|
||||
for (auto *participant : participants.values()) {
|
||||
for (auto *participant : getParticipantsInSeatOrder()) {
|
||||
participant->getInfo(event2.add_player_list(), joiningParticipant, omniscient, true);
|
||||
}
|
||||
|
||||
|
|
@ -871,6 +1046,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 +1061,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);
|
||||
|
|
@ -945,6 +1126,7 @@ void Server_Game::getInfo(ServerInfo_Game &result) const
|
|||
result.set_spectators_can_chat(spectatorsCanTalk);
|
||||
result.set_spectators_omniscient(spectatorsSeeEverything);
|
||||
result.set_share_decklists_on_load(shareDecklistsOnLoad);
|
||||
result.set_shuffle_players(shufflePlayers);
|
||||
result.set_spectators_count(getSpectatorCount());
|
||||
result.set_start_time(startTime.toSecsSinceEpoch());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ class Message;
|
|||
} // namespace protobuf
|
||||
} // namespace google
|
||||
struct GameConfig;
|
||||
struct RoleInfo;
|
||||
|
||||
class Server_Game : public QObject
|
||||
{
|
||||
|
|
@ -73,6 +74,11 @@ private:
|
|||
int hostId;
|
||||
ServerInfo_User *creatorInfo;
|
||||
QMap<int, Server_AbstractParticipant *> participants;
|
||||
// Seating/turn order as an ordered list of player ids. Player ids themselves
|
||||
// are stable for the lifetime of the participant; shuffling or reordering
|
||||
// only permutes this list so old clients, ISL routes and persistent
|
||||
// references (all keyed by player id) keep working.
|
||||
QList<int> seatOrder;
|
||||
QSet<QString> allPlayersEver, allSpectatorsEver;
|
||||
bool gameStarted;
|
||||
bool gameClosed;
|
||||
|
|
@ -89,6 +95,7 @@ private:
|
|||
bool spectatorsSeeEverything;
|
||||
int startingLifeTotal;
|
||||
bool shareDecklistsOnLoad;
|
||||
bool shufflePlayers;
|
||||
int inactivityCounter;
|
||||
int startTimeOfThisGame, secondsElapsed;
|
||||
bool firstGameStarted;
|
||||
|
|
@ -108,7 +115,10 @@ private:
|
|||
Server_AbstractParticipant *recipient,
|
||||
bool omniscient,
|
||||
bool withUserInfo);
|
||||
QList<Server_AbstractParticipant *> getParticipantsInSeatOrder() const;
|
||||
void reseatPlayers(const QList<Server_AbstractParticipant *> &orderedParticipants, bool randomized);
|
||||
void storeGameInformation();
|
||||
bool canSeePrivateEventsFor(const Server_AbstractParticipant *participant, int privatePlayerId) const;
|
||||
signals:
|
||||
void sigStartGameIfReady(bool override);
|
||||
void gameInfoChanged(ServerInfo_Game gameInfo);
|
||||
|
|
@ -147,6 +157,10 @@ public:
|
|||
{
|
||||
return participants;
|
||||
}
|
||||
QList<int> getSeatOrder() const
|
||||
{
|
||||
return seatOrder;
|
||||
}
|
||||
int getGameId() const
|
||||
{
|
||||
return gameId;
|
||||
|
|
@ -187,13 +201,16 @@ public:
|
|||
{
|
||||
return shareDecklistsOnLoad;
|
||||
}
|
||||
bool getShufflePlayers() const
|
||||
{
|
||||
return shufflePlayers;
|
||||
}
|
||||
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);
|
||||
|
|
@ -201,6 +218,8 @@ public:
|
|||
bool kickParticipant(int playerId);
|
||||
void startGameIfReady(bool forceStartGame);
|
||||
void stopGameIfFinished();
|
||||
void shufflePlayerSeats();
|
||||
void reorderPlayerSeats(const QList<QString> &orderedNames);
|
||||
int getActivePlayer() const
|
||||
{
|
||||
return activePlayer;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,6 +131,16 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session,
|
|||
return authState;
|
||||
}
|
||||
|
||||
if (authState == PasswordChangeRequired) {
|
||||
// The credentials were right, but the account must choose a new password before it may
|
||||
// use the server. Give the session an identity so it can complete that change, without
|
||||
// registering a user session or announcing the user as logged in.
|
||||
ServerInfo_User pendingData = databaseInterface->getUserData(name, true);
|
||||
pendingData.set_address(session->getAddress().toStdString());
|
||||
session->setUserInfo(pendingData);
|
||||
return authState;
|
||||
}
|
||||
|
||||
ServerInfo_User data = databaseInterface->getUserData(name, true);
|
||||
data.set_address(session->getAddress().toStdString());
|
||||
name = QString::fromStdString(data.name()); // Compensate for case indifference
|
||||
|
|
@ -296,7 +306,9 @@ void Server::removeClient(Server_ProtocolHandler *client)
|
|||
QWriteLocker locker(&clientsLock);
|
||||
clients.removeAt(clientIndex);
|
||||
ServerInfo_User *data = client->getUserInfo();
|
||||
if (data) {
|
||||
// A session that never completed login (for example one that still owes a password change)
|
||||
// has no session id: it was never announced as joined, so it must not be announced as leaving.
|
||||
if (data && data->has_session_id()) {
|
||||
Event_UserLeft event;
|
||||
event.set_name(data->name());
|
||||
SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event);
|
||||
|
|
@ -311,12 +323,10 @@ void Server::removeClient(Server_ProtocolHandler *client)
|
|||
users.remove(QString::fromStdString(data->name()));
|
||||
qDebug() << "Server::removeClient: name=" << QString::fromStdString(data->name());
|
||||
|
||||
if (data->has_session_id()) {
|
||||
const qint64 sessionId = data->session_id();
|
||||
usersBySessionId.remove(sessionId);
|
||||
emit endSession(sessionId);
|
||||
qDebug() << "closed session id:" << sessionId;
|
||||
}
|
||||
const qint64 sessionId = data->session_id();
|
||||
usersBySessionId.remove(sessionId);
|
||||
emit endSession(sessionId);
|
||||
qDebug() << "closed session id:" << sessionId;
|
||||
}
|
||||
qDebug() << "Server::removeClient: removed" << (void *)client << ";" << clients.size() << "clients; "
|
||||
<< users.size() << "users left";
|
||||
|
|
@ -330,7 +340,9 @@ QList<QString> Server::getOnlineModeratorList() const
|
|||
ServerInfo_User *data = client->getUserInfo();
|
||||
|
||||
//! \todo This line should be updated in the event there is any type of new user level created.
|
||||
if (data &&
|
||||
// A session that still owes a forced password change has user info but no session id:
|
||||
// it never joined, so it must not be reported as an online moderator either.
|
||||
if (data && data->has_session_id() &&
|
||||
(data->user_level() & ServerInfo_User::IsModerator || data->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
results << QString::fromStdString(data->name()).simplified();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,8 +64,9 @@ Server_ProtocolHandler::Server_ProtocolHandler(Server *_server,
|
|||
Server_DatabaseInterface *_databaseInterface,
|
||||
QObject *parent)
|
||||
: QObject(parent), Server_AbstractUserInterface(_server), deleted(false), databaseInterface(_databaseInterface),
|
||||
authState(NotLoggedIn), usingRealPassword(false), acceptsUserListChanges(false), acceptsRoomListChanges(false),
|
||||
idleClientWarningSent(false), timeRunning(0), lastDataReceived(0), lastActionReceived(0)
|
||||
authState(NotLoggedIn), usingRealPassword(false), passwordChangePending(false), acceptsUserListChanges(false),
|
||||
acceptsRoomListChanges(false), idleClientWarningSent(false), timeRunning(0), lastDataReceived(0),
|
||||
lastActionReceived(0)
|
||||
|
||||
{
|
||||
connect(server, &Server::pingClockTimeout, this, &Server_ProtocolHandler::pingClockTimeout);
|
||||
|
|
@ -212,7 +213,10 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co
|
|||
logDebugMessage(getSafeDebugString(sc));
|
||||
}
|
||||
const auto commandType = static_cast<SessionCommand::SessionCommandType>(num);
|
||||
if (authState == NotLoggedIn && !isPreAuthSessionCommand(commandType)) {
|
||||
if (passwordChangePending && commandType != SessionCommand::PING &&
|
||||
commandType != SessionCommand::ACCOUNT_PASSWORD) {
|
||||
resp = Response::RespFunctionNotAllowed;
|
||||
} else if (authState == NotLoggedIn && !isPreAuthSessionCommand(commandType)) {
|
||||
resp = Response::RespLoginNeeded;
|
||||
} else {
|
||||
switch (commandType) {
|
||||
|
|
@ -489,6 +493,19 @@ void Server_ProtocolHandler::processCommandContainer(const CommandContainer &con
|
|||
|
||||
lastDataReceived = timeRunning;
|
||||
|
||||
// A session that still owes a forced password change may only reach the session family,
|
||||
// where processSessionCommandContainer narrows it further to ping and the change itself.
|
||||
// Every other family is rejected here, including a container that also carries a session
|
||||
// command: the dispatch below prefers game/room over session, so testing only for the
|
||||
// absence of a session command would let such a container through.
|
||||
if (passwordChangePending &&
|
||||
(cont.game_command_size() || cont.room_command_size() || cont.moderator_command_size() ||
|
||||
cont.admin_command_size() || cont.developer_command_size())) {
|
||||
sendResponseContainer(ResponseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1),
|
||||
Response::RespFunctionNotAllowed);
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseContainer responseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1);
|
||||
Response::ResponseCode finalResponseCode;
|
||||
|
||||
|
|
@ -696,6 +713,11 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
|
|||
server->recordFailedLogin(getAddress(), userName);
|
||||
return Response::RespAccountNotActivated;
|
||||
case PasswordChangeRequired:
|
||||
// The credentials were right, but the account must choose a new password first.
|
||||
// Keep the session authenticated just far enough for the client to do that:
|
||||
// processCommandContainer refuses everything except the change itself.
|
||||
authState = PasswordRight;
|
||||
passwordChangePending = true;
|
||||
return Response::RespPasswordChangeRequired;
|
||||
default:
|
||||
server->clearFailedLogins(getAddress(), userName);
|
||||
|
|
@ -1094,6 +1116,7 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
|
|||
int startingLifeTotal = cmd.has_starting_life_total() ? cmd.starting_life_total() : 20;
|
||||
|
||||
bool shareDecklistsOnLoad = cmd.has_share_decklists_on_load() ? cmd.share_decklists_on_load() : false;
|
||||
bool shufflePlayers = cmd.has_shuffle_players() ? cmd.shuffle_players() : false;
|
||||
|
||||
const int gameId = databaseInterface->getNextGameId();
|
||||
if (gameId == -1) {
|
||||
|
|
@ -1115,11 +1138,13 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
|
|||
.spectatorsCanTalk = cmd.spectators_can_talk(),
|
||||
.spectatorsSeeEverything = cmd.spectators_see_everything(),
|
||||
.startingLifeTotal = startingLifeTotal,
|
||||
.shareDecklistsOnLoad = shareDecklistsOnLoad};
|
||||
.shareDecklistsOnLoad = shareDecklistsOnLoad,
|
||||
.shufflePlayers = shufflePlayers};
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,12 @@ protected:
|
|||
Server_DatabaseInterface *databaseInterface;
|
||||
AuthenticationResult authState;
|
||||
bool usingRealPassword;
|
||||
/**
|
||||
* @brief True when the credentials were right but the account must choose a new password
|
||||
* before it may use the server. Only ping, the password change and disconnecting
|
||||
* are served while this is set.
|
||||
*/
|
||||
bool passwordChangePending;
|
||||
bool acceptsUserListChanges;
|
||||
bool acceptsRoomListChanges;
|
||||
bool idleClientWarningSent;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue