Create game as spectator (#4281)

* refactoring

* allow for creation of games as spectator

allow setting the amount of games per user to none
remove limit on amount of games when creating a game as judge as
spectator

* refactor common/server_player.cpp

* do not close games with spectating host automatically

* remove check that filters out 0 player games

this check didn't really do anything, deleted games are removed before
it would be reached

* don't transfer host to spectators

this seems to cause a bug, also present on master
This commit is contained in:
ebbit1q 2021-03-13 20:39:25 +01:00 committed by GitHub
parent b722864caf
commit 06bfc0291a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 153 additions and 140 deletions

View file

@ -83,11 +83,13 @@ void DlgCreateGame::sharedCtor()
spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch")); spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch"));
spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat"));
spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands")); spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands"));
createGameAsSpectatorCheckBox = new QCheckBox(tr("Create game as spectator"));
QVBoxLayout *spectatorsLayout = new QVBoxLayout; QVBoxLayout *spectatorsLayout = new QVBoxLayout;
spectatorsLayout->addWidget(spectatorsAllowedCheckBox); spectatorsLayout->addWidget(spectatorsAllowedCheckBox);
spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox); spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox);
spectatorsLayout->addWidget(spectatorsCanTalkCheckBox); spectatorsLayout->addWidget(spectatorsCanTalkCheckBox);
spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox); spectatorsLayout->addWidget(spectatorsSeeEverythingCheckBox);
spectatorsLayout->addWidget(createGameAsSpectatorCheckBox);
spectatorsGroupBox = new QGroupBox(tr("Spectators")); spectatorsGroupBox = new QGroupBox(tr("Spectators"));
spectatorsGroupBox->setLayout(spectatorsLayout); spectatorsGroupBox->setLayout(spectatorsLayout);
@ -129,6 +131,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameType
spectatorsNeedPasswordCheckBox->setChecked(SettingsCache::instance().getSpectatorsNeedPassword()); spectatorsNeedPasswordCheckBox->setChecked(SettingsCache::instance().getSpectatorsNeedPassword());
spectatorsCanTalkCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanTalk()); spectatorsCanTalkCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanTalk());
spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanSeeEverything()); spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanSeeEverything());
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator());
if (!rememberGameSettings->isChecked()) { if (!rememberGameSettings->isChecked()) {
actReset(); actReset();
@ -159,6 +162,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMap<int, QS
spectatorsNeedPasswordCheckBox->setEnabled(false); spectatorsNeedPasswordCheckBox->setEnabled(false);
spectatorsCanTalkCheckBox->setEnabled(false); spectatorsCanTalkCheckBox->setEnabled(false);
spectatorsSeeEverythingCheckBox->setEnabled(false); spectatorsSeeEverythingCheckBox->setEnabled(false);
createGameAsSpectatorCheckBox->setEnabled(false);
descriptionEdit->setText(QString::fromStdString(gameInfo.description())); descriptionEdit->setText(QString::fromStdString(gameInfo.description()));
maxPlayersEdit->setValue(gameInfo.max_players()); maxPlayersEdit->setValue(gameInfo.max_players());
@ -200,6 +204,7 @@ void DlgCreateGame::actReset()
spectatorsNeedPasswordCheckBox->setChecked(false); spectatorsNeedPasswordCheckBox->setChecked(false);
spectatorsCanTalkCheckBox->setChecked(false); spectatorsCanTalkCheckBox->setChecked(false);
spectatorsSeeEverythingCheckBox->setChecked(false); spectatorsSeeEverythingCheckBox->setChecked(false);
createGameAsSpectatorCheckBox->setChecked(false);
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes); QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
while (gameTypeCheckBoxIterator.hasNext()) { while (gameTypeCheckBoxIterator.hasNext()) {
@ -226,6 +231,7 @@ void DlgCreateGame::actOK()
cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked()); cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked());
cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked()); cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked());
cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier); cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier);
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
QString gameTypes = QString(); QString gameTypes = QString();
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes); QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
@ -247,6 +253,7 @@ void DlgCreateGame::actOK()
SettingsCache::instance().setSpectatorsNeedPassword(spectatorsNeedPasswordCheckBox->isChecked()); SettingsCache::instance().setSpectatorsNeedPassword(spectatorsNeedPasswordCheckBox->isChecked());
SettingsCache::instance().setSpectatorsCanTalk(spectatorsCanTalkCheckBox->isChecked()); SettingsCache::instance().setSpectatorsCanTalk(spectatorsCanTalkCheckBox->isChecked());
SettingsCache::instance().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked()); SettingsCache::instance().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked());
SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
SettingsCache::instance().setGameTypes(gameTypes); SettingsCache::instance().setGameTypes(gameTypes);
} }
PendingCommand *pend = room->prepareRoomCommand(cmd); PendingCommand *pend = room->prepareRoomCommand(cmd);

View file

@ -39,7 +39,7 @@ private:
QSpinBox *maxPlayersEdit; QSpinBox *maxPlayersEdit;
QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox;
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
*spectatorsSeeEverythingCheckBox; *spectatorsSeeEverythingCheckBox, *createGameAsSpectatorCheckBox;
QDialogButtonBox *buttonBox; QDialogButtonBox *buttonBox;
QPushButton *clearButton; QPushButton *clearButton;
QCheckBox *rememberGameSettings; QCheckBox *rememberGameSettings;

View file

@ -278,8 +278,6 @@ void GamesModel::updateGameList(const ServerInfo_Game &game)
return; return;
} }
} }
if (game.player_count() <= 0)
return;
beginInsertRows(QModelIndex(), gameList.size(), gameList.size()); beginInsertRows(QModelIndex(), gameList.size(), gameList.size());
gameList.append(game); gameList.append(game);
endInsertRows(); endInsertRows();

View file

@ -284,6 +284,7 @@ SettingsCache::SettingsCache()
spectatorsNeedPassword = settings->value("game/spectatorsneedpassword", false).toBool(); spectatorsNeedPassword = settings->value("game/spectatorsneedpassword", false).toBool();
spectatorsCanTalk = settings->value("game/spectatorscantalk", false).toBool(); spectatorsCanTalk = settings->value("game/spectatorscantalk", false).toBool();
spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool(); spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool();
createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool();
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString(); clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString(); clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
@ -940,6 +941,12 @@ void SettingsCache::setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEv
settings->setValue("game/spectatorscanseeeverything", spectatorsCanSeeEverything); settings->setValue("game/spectatorscanseeeverything", spectatorsCanSeeEverything);
} }
void SettingsCache::setCreateGameAsSpectator(const bool _createGameAsSpectator)
{
createGameAsSpectator = _createGameAsSpectator;
settings->setValue("game/creategameasspectator", createGameAsSpectator);
}
void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
{ {
rememberGameSettings = _rememberGameSettings; rememberGameSettings = _rememberGameSettings;

View file

@ -124,6 +124,7 @@ private:
bool spectatorsNeedPassword; bool spectatorsNeedPassword;
bool spectatorsCanTalk; bool spectatorsCanTalk;
bool spectatorsCanSeeEverything; bool spectatorsCanSeeEverything;
bool createGameAsSpectator;
int keepalive; int keepalive;
void translateLegacySettings(); void translateLegacySettings();
QString getSafeConfigPath(QString configEntry, QString defaultPath) const; QString getSafeConfigPath(QString configEntry, QString defaultPath) const;
@ -394,6 +395,10 @@ public:
{ {
return spectatorsCanSeeEverything; return spectatorsCanSeeEverything;
} }
bool getCreateGameAsSpectator() const
{
return createGameAsSpectator;
}
bool getRememberGameSettings() const bool getRememberGameSettings() const
{ {
return rememberGameSettings; return rememberGameSettings;
@ -525,6 +530,7 @@ public slots:
void setSpectatorsNeedPassword(const bool _spectatorsNeedPassword); void setSpectatorsNeedPassword(const bool _spectatorsNeedPassword);
void setSpectatorsCanTalk(const bool _spectatorsCanTalk); void setSpectatorsCanTalk(const bool _spectatorsCanTalk);
void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything); void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything);
void setCreateGameAsSpectator(const bool _createGameAsSpectator);
void setRememberGameSettings(const bool _rememberGameSettings); void setRememberGameSettings(const bool _rememberGameSettings);
void setNotifyAboutUpdate(int _notifyaboutupdate); void setNotifyAboutUpdate(int _notifyaboutupdate);
void setNotifyAboutNewVersion(int _notifyaboutnewversion); void setNotifyAboutNewVersion(int _notifyaboutnewversion);

View file

@ -66,7 +66,7 @@ void UserContextMenu::retranslateUi()
aBanHistory->setText(tr("View user's &ban history")); aBanHistory->setText(tr("View user's &ban history"));
aPromoteToMod->setText(tr("&Promote user to moderator")); aPromoteToMod->setText(tr("&Promote user to moderator"));
aDemoteFromMod->setText(tr("Dem&ote user from moderator")); aDemoteFromMod->setText(tr("Dem&ote user from moderator"));
aPromoteToJudge->setText(tr("Promote user to &juge")); aPromoteToJudge->setText(tr("Promote user to &judge"));
aDemoteFromJudge->setText(tr("Demote user from judge")); aDemoteFromJudge->setText(tr("Demote user from judge"));
} }

View file

@ -37,6 +37,7 @@ message Command_CreateGame {
optional bool spectators_see_everything = 9; optional bool spectators_see_everything = 9;
repeated uint32 game_type_ids = 10; repeated uint32 game_type_ids = 10;
optional bool join_as_judge = 11; optional bool join_as_judge = 11;
optional bool join_as_spectator = 12;
} }
message Command_JoinGame { message Command_JoinGame {

View file

@ -93,9 +93,9 @@ Server_Game::~Server_Game()
gameClosed = true; gameClosed = true;
sendGameEventContainer(prepareGameEvent(Event_GameClosed(), -1)); sendGameEventContainer(prepareGameEvent(Event_GameClosed(), -1));
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) player->prepareDestroy();
playerIterator.next().value()->prepareDestroy(); }
players.clear(); players.clear();
room->removeGame(this); room->removeGame(this);
@ -131,9 +131,9 @@ void Server_Game::storeGameInformation()
for (int i = gameInfo.game_types_size() - 1; i >= 0; --i) for (int i = gameInfo.game_types_size() - 1; i >= 0; --i)
gameTypes.append(allGameTypes[gameInfo.game_types(i)]); gameTypes.append(allGameTypes[gameInfo.game_types(i)]);
QSetIterator<QString> playerIterator(allPlayersEver); for (auto playerName : allPlayersEver) {
while (playerIterator.hasNext()) replayMatchInfo->add_player_names(playerName.toStdString());
replayMatchInfo->add_player_names(playerIterator.next().toStdString()); }
for (int i = 0; i < replayList.size(); ++i) { for (int i = 0; i < replayList.size(); ++i) {
ServerInfo_Replay *replayInfo = replayMatchInfo->add_replay_list(); ServerInfo_Replay *replayInfo = replayMatchInfo->add_replay_list();
@ -142,23 +142,21 @@ void Server_Game::storeGameInformation()
replayInfo->set_duration(replayList[i]->duration_seconds()); replayInfo->set_duration(replayList[i]->duration_seconds());
} }
QSet<QString> allUsersInGame = allPlayersEver + allSpectatorsEver;
QSetIterator<QString> allUsersIterator(allUsersInGame);
SessionEvent *sessionEvent = Server_ProtocolHandler::prepareSessionEvent(replayEvent); SessionEvent *sessionEvent = Server_ProtocolHandler::prepareSessionEvent(replayEvent);
Server *server = room->getServer(); Server *server = room->getServer();
server->clientsLock.lockForRead(); server->clientsLock.lockForRead();
while (allUsersIterator.hasNext()) { for (auto userName : allPlayersEver + allSpectatorsEver) {
Server_AbstractUserInterface *userHandler = server->findUser(allUsersIterator.next()); Server_AbstractUserInterface *userHandler = server->findUser(userName);
if (userHandler && server->getStoreReplaysEnabled()) if (userHandler && server->getStoreReplaysEnabled())
userHandler->sendProtocolItem(*sessionEvent); userHandler->sendProtocolItem(*sessionEvent);
} }
server->clientsLock.unlock(); server->clientsLock.unlock();
delete sessionEvent; delete sessionEvent;
if (server->getStoreReplaysEnabled()) if (server->getStoreReplaysEnabled()) {
server->getDatabaseInterface()->storeGameInformation(room->getName(), gameTypes, gameInfo, allPlayersEver, server->getDatabaseInterface()->storeGameInformation(room->getName(), gameTypes, gameInfo, allPlayersEver,
allSpectatorsEver, replayList); allSpectatorsEver, replayList);
}
} }
void Server_Game::pingClockTimeout() void Server_Game::pingClockTimeout()
@ -175,20 +173,22 @@ void Server_Game::pingClockTimeout()
if (player == nullptr) if (player == nullptr)
continue; continue;
if (!player->getSpectator()) if (!player->getSpectator()) {
++playerCount; ++playerCount;
}
int oldPingTime = player->getPingTime(); int oldPingTime = player->getPingTime();
int newPingTime; int newPingTime;
player->playerMutex.lock(); {
if (player->getUserInterface()) { QMutexLocker playerMutexLocker(&player->playerMutex);
newPingTime = player->getUserInterface()->getLastCommandTime(); if (player->getUserInterface()) {
} else { newPingTime = player->getUserInterface()->getLastCommandTime();
newPingTime = -1; } else {
newPingTime = -1;
}
} }
player->playerMutex.unlock();
if ((newPingTime != -1) && !player->getSpectator()) { if ((newPingTime != -1) && (!player->getSpectator() || player->getPlayerId() == hostId)) {
allPlayersInactive = false; allPlayersInactive = false;
} }
@ -217,11 +217,11 @@ int Server_Game::getPlayerCount() const
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
QMapIterator<int, Server_Player *> playerIterator(players);
int result = 0; int result = 0;
while (playerIterator.hasNext()) for (Server_Player *player : players.values()) {
if (!playerIterator.next().value()->getSpectator()) if (!player->getSpectator())
++result; ++result;
}
return result; return result;
} }
@ -229,11 +229,11 @@ int Server_Game::getSpectatorCount() const
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
QMapIterator<int, Server_Player *> playerIterator(players);
int result = 0; int result = 0;
while (playerIterator.hasNext()) for (Server_Player *player : players.values()) {
if (playerIterator.next().value()->getSpectator()) if (player->getSpectator())
++result; ++result;
}
return result; return result;
} }
@ -250,9 +250,9 @@ void Server_Game::createGameStateChangedEvent(Event_GameStateChanged *event,
} else } else
event->set_game_started(false); event->set_game_started(false);
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *otherPlayer : players.values()) {
while (playerIterator.hasNext()) otherPlayer->getInfo(event->add_player_list(), playerWhosAsking, omniscient, withUserInfo);
playerIterator.next().value()->getInfo(event->add_player_list(), playerWhosAsking, omniscient, withUserInfo); }
} }
void Server_Game::sendGameStateToPlayers() void Server_Game::sendGameStateToPlayers()
@ -277,9 +277,7 @@ void Server_Game::sendGameStateToPlayers()
createGameStateChangedEvent(&spectatorEvent, 0, false, false); createGameStateChangedEvent(&spectatorEvent, 0, false, false);
// send game state info to clients according to their role in the game // send game state info to clients according to their role in the game
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) {
Server_Player *player = playerIterator.next().value();
GameEventContainer *gec; GameEventContainer *gec;
if (player->getSpectator()) if (player->getSpectator())
gec = prepareGameEvent(spectatorEvent, -1); gec = prepareGameEvent(spectatorEvent, -1);
@ -301,23 +299,17 @@ void Server_Game::doStartGameIfReady()
if (getPlayerCount() < maxPlayers) if (getPlayerCount() < maxPlayers)
return; return;
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) { if (!player->getReadyStart() && !player->getSpectator())
Server_Player *p = playerIterator.next().value();
if (!p->getReadyStart() && !p->getSpectator())
return; return;
} }
playerIterator.toFront(); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) { if (!player->getSpectator())
Server_Player *p = playerIterator.next().value(); player->setupZones();
if (!p->getSpectator())
p->setupZones();
} }
gameStarted = true; gameStarted = true;
playerIterator.toFront(); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) {
Server_Player *player = playerIterator.next().value();
player->setConceded(false); player->setConceded(false);
player->setReadyStart(false); player->setReadyStart(false);
} }
@ -367,11 +359,9 @@ void Server_Game::stopGameIfFinished()
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
QMapIterator<int, Server_Player *> playerIterator(players);
int playing = 0; int playing = 0;
while (playerIterator.hasNext()) { for (Server_Player *player : players.values()) {
Server_Player *p = playerIterator.next().value(); if (!player->getConceded() && !player->getSpectator())
if (!p->getConceded() && !p->getSpectator())
++playing; ++playing;
} }
if (playing > 1) if (playing > 1)
@ -379,11 +369,9 @@ void Server_Game::stopGameIfFinished()
gameStarted = false; gameStarted = false;
playerIterator.toFront(); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) { player->clearZones();
Server_Player *p = playerIterator.next().value(); player->setConceded(false);
p->clearZones();
p->setConceded(false);
} }
sendGameStateToPlayers(); sendGameStateToPlayers();
@ -404,11 +392,9 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user,
bool asJudge) bool asJudge)
{ {
Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface(); Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface();
{ for (Server_Player *player : players.values()) {
QMapIterator<int, Server_Player *> playerIterator(players); if (player->getUserInfo()->name() == user->name())
while (playerIterator.hasNext()) return Response::RespContextError;
if (playerIterator.next().value()->getUserInfo()->name() == user->name())
return Response::RespContextError;
} }
if (asJudge && !(user->user_level() & ServerInfo_User::IsJudge)) { if (asJudge && !(user->user_level() & ServerInfo_User::IsJudge)) {
@ -441,10 +427,10 @@ bool Server_Game::containsUser(const QString &userName) const
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) if (player->getUserInfo()->name() == userName.toStdString())
if (playerIterator.next().value()->getUserInfo()->name() == userName.toStdString())
return true; return true;
}
return false; return false;
} }
@ -466,14 +452,18 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface,
sendGameEventContainer(prepareGameEvent(joinEvent, -1)); sendGameEventContainer(prepareGameEvent(joinEvent, -1));
const QString playerName = QString::fromStdString(newPlayer->getUserInfo()->name()); const QString playerName = QString::fromStdString(newPlayer->getUserInfo()->name());
if (spectator)
allSpectatorsEver.insert(playerName);
else
allPlayersEver.insert(playerName);
players.insert(newPlayer->getPlayerId(), newPlayer); players.insert(newPlayer->getPlayerId(), newPlayer);
if (newPlayer->getUserInfo()->name() == creatorInfo->name()) { if (spectator) {
hostId = newPlayer->getPlayerId(); allSpectatorsEver.insert(playerName);
sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId)); } else {
allPlayersEver.insert(playerName);
// if the original creator of the game joins, give them host status back
// FIXME: transferring host to spectators has side effects
if (newPlayer->getUserInfo()->name() == creatorInfo->name()) {
hostId = newPlayer->getPlayerId();
sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId));
}
} }
if (broadcastUpdate) { if (broadcastUpdate) {
@ -513,26 +503,24 @@ void Server_Game::removePlayer(Server_Player *player, Event_Leave::LeaveReason r
bool spectator = player->getSpectator(); bool spectator = player->getSpectator();
player->prepareDestroy(); player->prepareDestroy();
if (!getPlayerCount()) { if (playerHost) {
gameClosed = true; int newHostId = -1;
deleteLater(); for (Server_Player *otherPlayer : players.values()) {
return; if (!otherPlayer->getSpectator()) {
} else if (!spectator) { newHostId = otherPlayer->getPlayerId();
if (playerHost) { break;
int newHostId = -1;
QMapIterator<int, Server_Player *> playerIterator(players);
while (playerIterator.hasNext()) {
Server_Player *p = playerIterator.next().value();
if (!p->getSpectator()) {
newHostId = p->getPlayerId();
break;
}
}
if (newHostId != -1) {
hostId = newHostId;
sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId));
} }
} }
if (newHostId != -1) {
hostId = newHostId;
sendGameEventContainer(prepareGameEvent(Event_GameHostChanged(), hostId));
} else {
gameClosed = true;
deleteLater();
return;
}
}
if (!spectator) {
stopGameIfFinished(); stopGameIfFinished();
if (gameStarted && playerActive) if (gameStarted && playerActive)
nextTurn(); nextTurn();
@ -553,10 +541,8 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Play
// Remove all arrows of other players pointing to the player being removed or to one of his cards. // Remove all arrows of other players pointing to the player being removed or to one of his cards.
// Also remove all arrows starting at one of his cards. This is necessary since players can create // Also remove all arrows starting at one of his cards. This is necessary since players can create
// arrows that start at another person's cards. // arrows that start at another person's cards.
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *otherPlayer : players.values()) {
while (playerIterator.hasNext()) { QList<Server_Arrow *> arrows = otherPlayer->getArrows().values();
Server_Player *p = playerIterator.next().value();
QList<Server_Arrow *> arrows = p->getArrows().values();
QList<Server_Arrow *> toDelete; QList<Server_Arrow *> toDelete;
for (int i = 0; i < arrows.size(); ++i) { for (int i = 0; i < arrows.size(); ++i) {
Server_Arrow *a = arrows[i]; Server_Arrow *a = arrows[i];
@ -574,9 +560,9 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Play
for (int i = 0; i < toDelete.size(); ++i) { for (int i = 0; i < toDelete.size(); ++i) {
Event_DeleteArrow event; Event_DeleteArrow event;
event.set_arrow_id(toDelete[i]->getId()); event.set_arrow_id(toDelete[i]->getId());
ges.enqueueGameEvent(event, p->getPlayerId()); ges.enqueueGameEvent(event, otherPlayer->getPlayerId());
p->deleteArrow(toDelete[i]->getId()); otherPlayer->deleteArrow(toDelete[i]->getId());
} }
} }
} }
@ -586,15 +572,13 @@ void Server_Game::unattachCards(GameEventStorage &ges, Server_Player *player)
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
QMapIterator<QString, Server_CardZone *> zoneIterator(player->getZones()); QMapIterator<QString, Server_CardZone *> zoneIterator(player->getZones());
while (zoneIterator.hasNext()) { for (Server_CardZone *zone : player->getZones()) {
Server_CardZone *zone = zoneIterator.next().value(); for (Server_Card *card : zone->getCards()) {
for (int i = 0; i < zone->getCards().size(); ++i) {
Server_Card *card = zone->getCards().at(i);
// Make a copy of the list because the original one gets modified during the loop // Make a copy of the list because the original one gets modified during the loop
QList<Server_Card *> attachedCards = card->getAttachedCards(); QList<Server_Card *> attachedCards = card->getAttachedCards();
for (int i = 0; i < attachedCards.size(); ++i) for (Server_Card *attachedCard : attachedCards) {
attachedCards[i]->getZone()->getPlayer()->unattachCard(ges, attachedCards[i]); attachedCard->getZone()->getPlayer()->unattachCard(ges, attachedCard);
}
} }
} }
} }
@ -633,9 +617,7 @@ void Server_Game::setActivePhase(int _activePhase)
{ {
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) {
Server_Player *player = playerIterator.next().value();
QList<Server_Arrow *> toDelete = player->getArrows().values(); QList<Server_Arrow *> toDelete = player->getArrows().values();
for (int i = 0; i < toDelete.size(); ++i) { for (int i = 0; i < toDelete.size(); ++i) {
Server_Arrow *a = toDelete[i]; Server_Arrow *a = toDelete[i];
@ -705,10 +687,9 @@ void Server_Game::createGameJoinedEvent(Server_Player *player, ResponseContainer
event2.set_active_player_id(activePlayer); event2.set_active_player_id(activePlayer);
event2.set_active_phase(activePhase); event2.set_active_phase(activePhase);
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) player->getInfo(event2.add_player_list(), player, player->getSpectator() && spectatorsSeeEverything, true);
playerIterator.next().value()->getInfo(event2.add_player_list(), player, }
player->getSpectator() && spectatorsSeeEverything, true);
rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, prepareGameEvent(event2, -1)); rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, prepareGameEvent(event2, -1));
} }
@ -720,14 +701,12 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont,
QMutexLocker locker(&gameMutex); QMutexLocker locker(&gameMutex);
cont->set_game_id(gameId); cont->set_game_id(gameId);
QMapIterator<int, Server_Player *> playerIterator(players); for (Server_Player *player : players.values()) {
while (playerIterator.hasNext()) {
Server_Player *p = playerIterator.next().value();
const bool playerPrivate = const bool playerPrivate =
(p->getPlayerId() == privatePlayerId) || (p->getSpectator() && spectatorsSeeEverything); (player->getPlayerId() == privatePlayerId) || (player->getSpectator() && spectatorsSeeEverything);
if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) || if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) ||
(recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) (recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate))
p->sendGameEvent(*cont); player->sendGameEvent(*cont);
} }
if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) { if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) {
cont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame); cont->set_seconds_elapsed(secondsElapsed - startTimeOfThisGame);
@ -760,11 +739,12 @@ void Server_Game::getInfo(ServerInfo_Game &result) const
result.set_room_id(room->getId()); result.set_room_id(room->getId());
result.set_game_id(gameId); result.set_game_id(gameId);
if (gameClosed) if (gameClosed) {
result.set_closed(true); result.set_closed(true);
else { } else {
for (int i = 0; i < gameTypes.size(); ++i) for (auto type : gameTypes) {
result.add_game_types(gameTypes[i]); result.add_game_types(type);
}
result.set_max_players(getMaxPlayers()); result.set_max_players(getMaxPlayers());
result.set_description(getDescription().toStdString()); result.set_description(getDescription().toStdString());

View file

@ -766,22 +766,28 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
if (gameId == -1) if (gameId == -1)
return Response::RespInternalError; return Response::RespInternalError;
if (server->getMaxGamesPerUser() > 0) auto level = userInfo->user_level();
if (room->getGamesCreatedByUser(QString::fromStdString(userInfo->name())) >= server->getMaxGamesPerUser()) bool isJudge = level & ServerInfo_User::IsJudge;
return Response::RespContextError; int maxGames = server->getMaxGamesPerUser();
bool asJudge = cmd.join_as_judge();
if (cmd.join_as_judge() && !server->permitCreateGameAsJudge() && bool asSpectator = cmd.join_as_spectator();
!(userInfo->user_level() & ServerInfo_User::IsJudge)) { // allow judges to open games as spectator without limit to facilitate bots etc, -1 means no limit
if (!(isJudge && asJudge && asSpectator) && maxGames >= 0 &&
room->getGamesCreatedByUser(QString::fromStdString(userInfo->name())) >= maxGames) {
return Response::RespContextError; return Response::RespContextError;
} }
QList<int> gameTypes; // if a non judge user tries to create a game as judge while not permitted, instead create a normal game
for (int i = cmd.game_type_ids_size() - 1; i >= 0; --i) if (asJudge && !(server->permitCreateGameAsJudge() || isJudge)) {
gameTypes.append(cmd.game_type_ids(i)); asJudge = false;
}
QString description = QString::fromStdString(cmd.description()); QList<int> gameTypes;
if (description.size() > 60) for (int i = cmd.game_type_ids_size() - 1; i >= 0; --i) { // FIXME: why are these iterated in reverse?
description = description.left(60); gameTypes.append(cmd.game_type_ids(i));
}
QString description = QString::fromStdString(cmd.description()).left(60);
// When server doesn't permit registered users to exist, do not honor only-reg setting // When server doesn't permit registered users to exist, do not honor only-reg setting
bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers()); bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers());
@ -790,7 +796,7 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(), cmd.spectators_need_password(), cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(), cmd.spectators_need_password(),
cmd.spectators_can_talk(), cmd.spectators_see_everything(), room); cmd.spectators_can_talk(), cmd.spectators_see_everything(), room);
game->addPlayer(this, rc, false, cmd.join_as_judge(), false); game->addPlayer(this, rc, asSpectator, asJudge, false);
room->addGame(game); room->addGame(game);
return Response::RespOk; return Response::RespOk;

View file

@ -243,8 +243,8 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam
// server->roomsMutex is always locked. // server->roomsMutex is always locked.
QReadLocker roomGamesLocker(&gamesLock); QReadLocker roomGamesLocker(&gamesLock);
Server_Game *g = games.value(cmd.game_id()); Server_Game *game = games.value(cmd.game_id());
if (!g) { if (!game) {
if (externalGames.contains(cmd.game_id())) { if (externalGames.contains(cmd.game_id())) {
CommandContainer cont; CommandContainer cont;
cont.set_cmd_id(rc.getCmdId()); cont.set_cmd_id(rc.getCmdId());
@ -256,16 +256,18 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam
userInterface->getUserInfo()->session_id(), id); userInterface->getUserInfo()->session_id(), id);
return Response::RespNothing; return Response::RespNothing;
} else } else {
return Response::RespNameNotFound; return Response::RespNameNotFound;
}
} }
QMutexLocker gameLocker(&g->gameMutex); QMutexLocker gameLocker(&game->gameMutex);
Response::ResponseCode result = g->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()), Response::ResponseCode result =
cmd.spectator(), cmd.override_restrictions(), cmd.join_as_judge()); game->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()), cmd.spectator(),
cmd.override_restrictions(), cmd.join_as_judge());
if (result == Response::RespOk) if (result == Response::RespOk)
g->addPlayer(userInterface, rc, cmd.spectator(), cmd.join_as_judge()); game->addPlayer(userInterface, rc, cmd.spectator(), cmd.join_as_judge());
return result; return result;
} }

View file

@ -242,6 +242,9 @@ void SettingsCache::setSpectatorsCanTalk(const bool /* _spectatorsCanTalk */)
void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSeeEverything */) void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSeeEverything */)
{ {
} }
void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectator */)
{
}
void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */) void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */)
{ {
} }

View file

@ -344,7 +344,7 @@ max_message_size_per_interval=1000
; Maximum number of messages in an interval before new messages gets dropped; default is 10 ; Maximum number of messages in an interval before new messages gets dropped; default is 10
max_message_count_per_interval=10 max_message_count_per_interval=10
; Maximum number of games a single user can create; default is 5 ; Maximum number of games a single user can create; default is 5; set to -1 to disable; 0 disallows game creation
max_games_per_user=5 max_games_per_user=5
; Servatrice can avoid users from flooding games with large number of game commands in an interval of time. ; Servatrice can avoid users from flooding games with large number of game commands in an interval of time.

View file

@ -246,6 +246,9 @@ void SettingsCache::setSpectatorsCanTalk(const bool /* _spectatorsCanTalk */)
void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSeeEverything */) void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSeeEverything */)
{ {
} }
void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectator */)
{
}
void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */) void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */)
{ {
} }