diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index aff2f002..a5cded2c 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -24,6 +24,7 @@
resources/icons/player.svg
resources/icons/ready_start.svg
resources/icons/remove_row.svg
+ resources/icons/scales.svg
resources/icons/search.svg
resources/icons/settings.svg
resources/icons/spectator.svg
diff --git a/cockatrice/resources/icons/scales.svg b/cockatrice/resources/icons/scales.svg
new file mode 100644
index 00000000..a410921d
--- /dev/null
+++ b/cockatrice/resources/icons/scales.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/cockatrice/src/abstractcounter.cpp b/cockatrice/src/abstractcounter.cpp
index d9095138..247f5b91 100644
--- a/cockatrice/src/abstractcounter.cpp
+++ b/cockatrice/src/abstractcounter.cpp
@@ -26,7 +26,7 @@ AbstractCounter::AbstractCounter(Player *_player,
shortcutActive = false;
- if (player->getLocal()) {
+ if (player->getLocalOrJudge()) {
menu = new QMenu(name);
aSet = new QAction(this);
connect(aSet, SIGNAL(triggered()), this, SLOT(setCounter()));
@@ -115,7 +115,7 @@ void AbstractCounter::setValue(int _value)
void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
- if (isUnderMouse() && player->getLocal()) {
+ if (isUnderMouse() && player->getLocalOrJudge()) {
if (event->button() == Qt::MidButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) {
if (menu)
menu->exec(event->screenPos());
diff --git a/cockatrice/src/carditem.cpp b/cockatrice/src/carditem.cpp
index 06b4b362..00b42bd9 100644
--- a/cockatrice/src/carditem.cpp
+++ b/cockatrice/src/carditem.cpp
@@ -321,7 +321,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
const ZoneViewZone *view = static_cast(zone);
if (view->getRevealZone() && !view->getWriteableRevealZone())
return;
- } else if (!owner->getLocal())
+ } else if (!owner->getLocalOrJudge())
return;
bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
@@ -352,7 +352,7 @@ void CardItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void CardItem::playCard(bool faceDown)
{
// Do nothing if the card belongs to another player
- if (!owner->getLocal())
+ if (!owner->getLocalOrJudge())
return;
TableZone *tz = qobject_cast(zone);
diff --git a/cockatrice/src/chatview/chatview.h b/cockatrice/src/chatview/chatview.h
index 9fdceb15..38b560b1 100644
--- a/cockatrice/src/chatview/chatview.h
+++ b/cockatrice/src/chatview/chatview.h
@@ -71,8 +71,9 @@ public:
QWidget *parent = 0);
void retranslateUi();
void appendHtml(const QString &html);
- void
- appendHtmlServerMessage(const QString &html, bool optionalIsBold = false, QString optionalFontColor = QString());
+ void virtual appendHtmlServerMessage(const QString &html,
+ bool optionalIsBold = false,
+ QString optionalFontColor = QString());
void appendMessage(QString message,
RoomMessageTypeFlags messageType = 0,
QString sender = QString(),
diff --git a/cockatrice/src/dlg_creategame.cpp b/cockatrice/src/dlg_creategame.cpp
index 2173eb74..df0280f1 100644
--- a/cockatrice/src/dlg_creategame.cpp
+++ b/cockatrice/src/dlg_creategame.cpp
@@ -1,6 +1,7 @@
#include "dlg_creategame.h"
#include "settingscache.h"
#include "tab_room.h"
+#include
#include
#include
#include
@@ -223,6 +224,7 @@ void DlgCreateGame::actOK()
cmd.set_spectators_need_password(spectatorsNeedPasswordCheckBox->isChecked());
cmd.set_spectators_can_talk(spectatorsCanTalkCheckBox->isChecked());
cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked());
+ cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier);
QString gameTypes = QString();
QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes);
diff --git a/cockatrice/src/gameselector.cpp b/cockatrice/src/gameselector.cpp
index 1651f271..1777830a 100644
--- a/cockatrice/src/gameselector.cpp
+++ b/cockatrice/src/gameselector.cpp
@@ -8,6 +8,7 @@
#include "pending_command.h"
#include "tab_room.h"
#include "tab_supervisor.h"
+#include
#include
#include
#include
@@ -205,6 +206,7 @@ void GameSelector::actJoin()
cmd.set_password(password.toStdString());
cmd.set_spectator(spectator);
cmd.set_override_restrictions(overrideRestrictions);
+ cmd.set_join_as_judge((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0);
TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id());
if (!r) {
diff --git a/cockatrice/src/messagelogwidget.cpp b/cockatrice/src/messagelogwidget.cpp
index 2d257bb8..59799ae0 100644
--- a/cockatrice/src/messagelogwidget.cpp
+++ b/cockatrice/src/messagelogwidget.cpp
@@ -121,9 +121,11 @@ MessageLogWidget::getFromStr(CardZone *zone, QString cardName, int position, boo
void MessageLogWidget::containerProcessingDone()
{
+
if (currentContext == MessageContext_MoveCard) {
for (auto &i : moveCardQueue)
logDoMoveCard(i);
+
moveCardQueue.clear();
moveCardTapped.clear();
moveCardExtras.clear();
@@ -134,6 +136,7 @@ void MessageLogWidget::containerProcessingDone()
}
currentContext = MessageContext_None;
+ messageSuffix = messagePrefix = QString();
}
void MessageLogWidget::containerProcessingStarted(const GameEventContext &context)
@@ -815,6 +818,18 @@ void MessageLogWidget::logUndoDraw(Player *player, QString cardName)
.arg(QString("%2").arg(sanitizeHtml(cardName)).arg(sanitizeHtml(cardName))));
}
+void MessageLogWidget::setContextJudgeName(QString name)
+{
+ messagePrefix = QString("");
+ messageSuffix = QString(" [
%1]").arg(sanitizeHtml(name));
+}
+
+void MessageLogWidget::appendHtmlServerMessage(const QString &html, bool optionalIsBold, QString optionalFontColor)
+{
+
+ ChatView::appendHtmlServerMessage(messagePrefix + html + messageSuffix, optionalIsBold, optionalFontColor);
+}
+
void MessageLogWidget::connectToPlayer(Player *player)
{
connect(player, SIGNAL(logSay(Player *, QString)), this, SLOT(logSay(Player *, QString)));
diff --git a/cockatrice/src/messagelogwidget.h b/cockatrice/src/messagelogwidget.h
index 7bbccb24..61a93bf4 100644
--- a/cockatrice/src/messagelogwidget.h
+++ b/cockatrice/src/messagelogwidget.h
@@ -38,6 +38,7 @@ private:
QList moveCardQueue;
QMap moveCardTapped;
QList moveCardExtras;
+ QString messagePrefix, messageSuffix;
const QString tableConstant() const;
const QString graveyardConstant() const;
@@ -108,6 +109,10 @@ public slots:
void logStopDumpZone(Player *player, CardZone *zone);
void logUnattachCard(Player *player, QString cardName);
void logUndoDraw(Player *player, QString cardName);
+ void setContextJudgeName(QString player);
+ void appendHtmlServerMessage(const QString &html,
+ bool optionalIsBold = false,
+ QString optionalFontColor = QString()) override;
public:
void connectToPlayer(Player *player);
diff --git a/cockatrice/src/pixmapgenerator.cpp b/cockatrice/src/pixmapgenerator.cpp
index c65ce12c..77a0bca0 100644
--- a/cockatrice/src/pixmapgenerator.cpp
+++ b/cockatrice/src/pixmapgenerator.cpp
@@ -143,6 +143,7 @@ QPixmap UserLevelPixmapGenerator::generatePixmap(int height, UserLevelFlags user
QPixmap pixmap = QPixmap("theme:userlevels/" + levelString)
.scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+
pmCache.insert(key, pixmap);
return pixmap;
}
diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp
index b69ac9a1..69115706 100644
--- a/cockatrice/src/player.cpp
+++ b/cockatrice/src/player.cpp
@@ -30,6 +30,7 @@
#include "pb/command_attach_card.pb.h"
#include "pb/command_change_zone_properties.pb.h"
+#include "pb/command_concede.pb.h"
#include "pb/command_create_token.pb.h"
#include "pb/command_draw_cards.pb.h"
#include "pb/command_flip_card.pb.h"
@@ -91,10 +92,11 @@ void PlayerArea::setSize(qreal width, qreal height)
bRect = QRectF(0, 0, width, height);
}
-Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent)
+Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, TabGame *_parent)
: QObject(_parent), game(_parent), shortcutsActive(false), defaultNumberTopCards(1),
defaultNumberTopCardsToPlaceBelow(1), lastTokenDestroy(true), lastTokenTableRow(0), id(_id), active(false),
- local(_local), mirrored(false), handVisible(false), conceded(false), dialogSemaphore(false), deck(nullptr)
+ local(_local), judge(_judge), mirrored(false), handVisible(false), conceded(false), dialogSemaphore(false),
+ deck(nullptr)
{
userInfo = new ServerInfo_User;
userInfo->CopyFrom(info);
@@ -140,10 +142,12 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare
updateBoundingRect();
- if (local) {
+ if (local || judge) {
connect(_parent, SIGNAL(playerAdded(Player *)), this, SLOT(addPlayer(Player *)));
connect(_parent, SIGNAL(playerRemoved(Player *)), this, SLOT(removePlayer(Player *)));
+ }
+ if (local || judge) {
aMoveHandToTopLibrary = new QAction(this);
aMoveHandToTopLibrary->setData(QList() << "deck" << 0);
aMoveHandToBottomLibrary = new QAction(this);
@@ -204,7 +208,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare
aViewRfg = new QAction(this);
connect(aViewRfg, SIGNAL(triggered()), this, SLOT(actViewRfg()));
- if (local) {
+ if (local || judge) {
aViewSideboard = new QAction(this);
connect(aViewSideboard, SIGNAL(triggered()), this, SLOT(actViewSideboard()));
@@ -237,7 +241,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare
playerMenu = new QMenu(QString());
table->setMenu(playerMenu);
- if (local) {
+ if (local || judge) {
handMenu = playerMenu->addMenu(QString());
playerLists.append(mRevealHand = handMenu->addMenu(QString()));
playerLists.append(mRevealRandomHandCard = handMenu->addMenu(QString()));
@@ -286,7 +290,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare
graveMenu = playerMenu->addMenu(QString());
graveMenu->addAction(aViewGraveyard);
- if (local) {
+ if (local || judge) {
mRevealRandomGraveyardCard = graveMenu->addMenu(QString());
QAction *newAction = mRevealRandomGraveyardCard->addAction(QString());
newAction->setData(-1);
@@ -300,7 +304,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare
rfgMenu->addAction(aViewRfg);
rfg->setMenu(rfgMenu, aViewRfg);
- if (local) {
+ if (local || judge) {
graveMenu->addSeparator();
moveGraveMenu = graveMenu->addMenu(QString());
moveGraveMenu->addAction(aMoveGraveToTopLibrary);
@@ -349,12 +353,22 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare
playerMenu->addAction(aCreateAnotherToken);
playerMenu->addMenu(createPredefinedTokenMenu);
playerMenu->addSeparator();
+ }
+
+ if (local) {
sayMenu = playerMenu->addMenu(QString());
initSayMenu();
+ }
+ if (local || judge) {
aCardMenu = new QAction(this);
playerMenu->addSeparator();
playerMenu->addAction(aCardMenu);
+ } else {
+ aCardMenu = nullptr;
+ }
+
+ if (local || judge) {
for (auto &playerList : playerLists) {
QAction *newAction = playerList->addAction(QString());
@@ -363,12 +377,13 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_pare
allPlayersActions.append(newAction);
playerList->addSeparator();
}
- } else {
+ }
+
+ if (!local && !judge) {
countersMenu = nullptr;
sbMenu = nullptr;
aCreateAnotherToken = nullptr;
createPredefinedTokenMenu = nullptr;
- aCardMenu = nullptr;
}
aTap = new QAction(this);
@@ -632,7 +647,7 @@ void Player::retranslateUi()
graveMenu->setTitle(tr("&Graveyard"));
rfgMenu->setTitle(tr("&Exile"));
- if (local) {
+ if (local || judge) {
moveHandMenu->setTitle(tr("&Move hand to..."));
aMoveHandToTopLibrary->setText(tr("&Top of library"));
aMoveHandToBottomLibrary->setText(tr("&Bottom of library"));
@@ -684,7 +699,6 @@ void Player::retranslateUi()
aCreateToken->setText(tr("&Create token..."));
aCreateAnotherToken->setText(tr("C&reate another token"));
createPredefinedTokenMenu->setTitle(tr("Cr&eate predefined token"));
- sayMenu->setTitle(tr("S&ay"));
QMapIterator counterIterator(counters);
while (counterIterator.hasNext())
@@ -696,6 +710,10 @@ void Player::retranslateUi()
allPlayersAction->setText(tr("&All players"));
}
+ if (local) {
+ sayMenu->setTitle(tr("S&ay"));
+ }
+
aPlay->setText(tr("&Play"));
aHide->setText(tr("&Hide"));
aPlayFacedown->setText(tr("Play &Face Down"));
@@ -2122,7 +2140,7 @@ AbstractCounter *Player::addCounter(int counterId, const QString &name, QColor c
ctr = new GeneralCounter(this, counterId, name, color, radius, value, true, this);
}
counters.insert(counterId, ctr);
- if (countersMenu) {
+ if (countersMenu && ctr->getMenu()) {
countersMenu->addMenu(ctr->getMenu());
}
if (shortcutsActive) {
@@ -2254,17 +2272,47 @@ void Player::rearrangeCounters()
PendingCommand *Player::prepareGameCommand(const google::protobuf::Message &cmd)
{
- return game->prepareGameCommand(cmd);
+
+ if (judge && !local) {
+ Command_Judge base;
+ GameCommand *c = base.add_game_command();
+ base.set_target_id(id);
+ c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
+ return game->prepareGameCommand(base);
+ } else {
+ return game->prepareGameCommand(cmd);
+ }
}
PendingCommand *Player::prepareGameCommand(const QList &cmdList)
{
- return game->prepareGameCommand(cmdList);
+ if (judge && !local) {
+ Command_Judge base;
+ base.set_target_id(id);
+ for (int i = 0; i < cmdList.size(); ++i) {
+ GameCommand *c = base.add_game_command();
+ c->GetReflection()
+ ->MutableMessage(c, cmdList[i]->GetDescriptor()->FindExtensionByName("ext"))
+ ->CopyFrom(*cmdList[i]);
+ delete cmdList[i];
+ }
+ return game->prepareGameCommand(base);
+ } else {
+ return game->prepareGameCommand(cmdList);
+ }
}
void Player::sendGameCommand(const google::protobuf::Message &command)
{
- game->sendGameCommand(command, id);
+ if (judge && !local) {
+ Command_Judge base;
+ GameCommand *c = base.add_game_command();
+ base.set_target_id(id);
+ c->GetReflection()->MutableMessage(c, command.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(command);
+ game->sendGameCommand(base, id);
+ } else {
+ game->sendGameCommand(command, id);
+ }
}
void Player::sendGameCommand(PendingCommand *pend)
@@ -2806,7 +2854,7 @@ void Player::refreshShortcuts()
void Player::updateCardMenu(const CardItem *card)
{
// If bad card OR is a spectator (as spectators don't need card menus), return
- if (card == nullptr || game->isSpectator()) {
+ if (card == nullptr || (game->isSpectator() && !judge)) {
return;
}
@@ -2817,7 +2865,7 @@ void Player::updateCardMenu(const CardItem *card)
cardMenu->clear();
bool revealedCard = false;
- bool writeableCard = getLocal();
+ bool writeableCard = getLocalOrJudge();
if (card->getZone() && card->getZone()->getIsView()) {
auto *view = dynamic_cast(card->getZone());
if (view->getRevealZone()) {
diff --git a/cockatrice/src/player.h b/cockatrice/src/player.h
index b88338b1..f8a5b2fa 100644
--- a/cockatrice/src/player.h
+++ b/cockatrice/src/player.h
@@ -228,6 +228,7 @@ private:
int id;
bool active;
bool local;
+ bool judge;
bool mirrored;
bool handVisible;
bool conceded;
@@ -339,8 +340,9 @@ public:
return playerTarget;
}
- Player(const ServerInfo_User &info, int _id, bool _local, TabGame *_parent);
+ Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, TabGame *_parent);
~Player() override;
+
void retranslateUi();
void clear();
TabGame *getGame() const
@@ -365,6 +367,14 @@ public:
{
return local;
}
+ bool getLocalOrJudge() const
+ {
+ return local || judge;
+ }
+ bool getJudge() const
+ {
+ return judge;
+ }
bool getMirrored() const
{
return mirrored;
diff --git a/cockatrice/src/playerlistwidget.cpp b/cockatrice/src/playerlistwidget.cpp
index 657c1ca3..177d238b 100644
--- a/cockatrice/src/playerlistwidget.cpp
+++ b/cockatrice/src/playerlistwidget.cpp
@@ -58,6 +58,7 @@ PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor,
notReadyIcon = QPixmap("theme:icons/not_ready_start");
concededIcon = QPixmap("theme:icons/conceded");
playerIcon = QPixmap("theme:icons/player");
+ judgeIcon = QPixmap("theme:icons/scales");
spectatorIcon = QPixmap("theme:icons/spectator");
lockIcon = QPixmap("theme:icons/lock");
@@ -111,8 +112,16 @@ void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties
return;
bool isSpectator = prop.has_spectator() && prop.spectator();
- player->setIcon(1, isSpectator ? spectatorIcon : playerIcon);
- player->setData(1, Qt::UserRole, !isSpectator);
+ if (prop.has_judge() || prop.has_spectator()) {
+ if (prop.has_judge() && prop.judge()) {
+ player->setIcon(1, judgeIcon);
+ } else if (isSpectator) {
+ player->setIcon(1, spectatorIcon);
+ } else {
+ player->setIcon(1, playerIcon);
+ }
+ player->setData(1, Qt::UserRole, !isSpectator);
+ }
if (!isSpectator) {
if (prop.has_conceded())
diff --git a/cockatrice/src/playerlistwidget.h b/cockatrice/src/playerlistwidget.h
index 278bb3c8..a9c37c63 100644
--- a/cockatrice/src/playerlistwidget.h
+++ b/cockatrice/src/playerlistwidget.h
@@ -37,7 +37,7 @@ private:
AbstractClient *client;
TabGame *game;
UserContextMenu *userContextMenu;
- QIcon readyIcon, notReadyIcon, concededIcon, playerIcon, spectatorIcon, lockIcon;
+ QIcon readyIcon, notReadyIcon, concededIcon, playerIcon, judgeIcon, spectatorIcon, lockIcon;
bool gameStarted;
signals:
void openMessageDialog(const QString &userName, bool focus);
diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp
index 735c74d1..fec4beb3 100644
--- a/cockatrice/src/tab_game.cpp
+++ b/cockatrice/src/tab_game.cpp
@@ -332,8 +332,8 @@ void DeckViewContainer::setDeck(const DeckLoader &deck)
TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
: Tab(_tabSupervisor), secondsElapsed(0), hostId(-1), localPlayerId(-1),
- isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(true), gameStateKnown(false), resuming(false),
- currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(_replay), currentReplayStep(0),
+ isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(true), judge(false), gameStateKnown(false),
+ resuming(false), currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(_replay), currentReplayStep(0),
sayLabel(nullptr), sayEdit(nullptr)
{
// THIS CTOR IS USED ON REPLAY
@@ -393,8 +393,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
const QMap &_roomGameTypes)
: Tab(_tabSupervisor), clients(_clients), gameInfo(event.game_info()), roomGameTypes(_roomGameTypes),
hostId(event.host_id()), localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()),
- spectator(event.spectator()), gameStateKnown(false), resuming(event.resuming()), currentPhase(-1),
- activeCard(nullptr), gameClosed(false), replay(nullptr), replayDock(nullptr)
+ spectator(event.spectator()), judge(event.judge()), gameStateKnown(false), resuming(event.resuming()),
+ currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(nullptr), replayDock(nullptr)
{
// THIS CTOR IS USED ON GAMES
gameInfo.set_started(false);
@@ -751,7 +751,7 @@ void TabGame::actCompleterChanged()
Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info)
{
bool local = ((clients.size() > 1) || (playerId == localPlayerId));
- auto *newPlayer = new Player(info, playerId, local, this);
+ auto *newPlayer = new Player(info, playerId, local, judge, this);
connect(newPlayer, SIGNAL(openDeckEditor(const DeckLoader *)), this, SIGNAL(openDeckEditor(const DeckLoader *)));
QString newPlayerName = "@" + newPlayer->getName();
if (sayEdit && !autocompleteUserList.contains(newPlayerName)) {
@@ -789,6 +789,17 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont, Abstract
const GameEvent &event = cont.event_list(i);
const int playerId = event.player_id();
const auto eventType = static_cast(getPbExtension(event));
+
+ if (cont.has_forced_by_judge()) {
+ auto id = cont.forced_by_judge();
+ Player *judgep = players.value(id, nullptr);
+ if (judgep) {
+ messageLog->setContextJudgeName(judgep->getName());
+ } else if (spectators.contains(id)) {
+ messageLog->setContextJudgeName(QString::fromStdString(spectators.value(id).name()));
+ }
+ }
+
if (spectators.contains(playerId)) {
switch (eventType) {
case GameEvent::GAME_SAY:
diff --git a/cockatrice/src/tab_game.h b/cockatrice/src/tab_game.h
index 41f56ddc..36c7c856 100644
--- a/cockatrice/src/tab_game.h
+++ b/cockatrice/src/tab_game.h
@@ -124,6 +124,7 @@ private:
int localPlayerId;
const bool isLocalGame;
bool spectator;
+ bool judge;
QMap players;
QMap spectators;
bool gameStateKnown;
diff --git a/cockatrice/src/user_context_menu.cpp b/cockatrice/src/user_context_menu.cpp
index 7968c1b2..c5a45e17 100644
--- a/cockatrice/src/user_context_menu.cpp
+++ b/cockatrice/src/user_context_menu.cpp
@@ -44,6 +44,8 @@ UserContextMenu::UserContextMenu(const TabSupervisor *_tabSupervisor, QWidget *p
aBanHistory = new QAction(QString(), this);
aPromoteToMod = new QAction(QString(), this);
aDemoteFromMod = new QAction(QString(), this);
+ aPromoteToJudge = new QAction(QString(), this);
+ aDemoteFromJudge = new QAction(QString(), this);
retranslateUi();
}
@@ -64,6 +66,8 @@ void UserContextMenu::retranslateUi()
aBanHistory->setText(tr("View user's &ban history"));
aPromoteToMod->setText(tr("&Promote user to moderator"));
aDemoteFromMod->setText(tr("Dem&ote user from moderator"));
+ aPromoteToJudge->setText(tr("Promote user to &juge"));
+ aDemoteFromJudge->setText(tr("Demote user from judge"));
}
void UserContextMenu::gamesOfUserReceived(const Response &resp, const CommandContainer &commandContainer)
@@ -220,7 +224,7 @@ void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const
const Command_AdjustMod &cmd = commandContainer.admin_command(0).GetExtension(Command_AdjustMod::ext);
if (resp.response_code() == Response::RespOk) {
- if (cmd.should_be_mod()) {
+ if (cmd.should_be_mod() || cmd.should_be_judge()) {
QMessageBox::information(static_cast(parent()), tr("Success"),
tr("Successfully promoted user."));
} else {
@@ -228,7 +232,7 @@ void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const
}
} else {
- if (cmd.should_be_mod()) {
+ if (cmd.should_be_mod() || cmd.should_be_judge()) {
QMessageBox::information(static_cast(parent()), tr("Failed"), tr("Failed to promote user."));
} else {
QMessageBox::information(static_cast(parent()), tr("Failed"), tr("Failed to demote user."));
@@ -312,6 +316,15 @@ void UserContextMenu::showContextMenu(const QPoint &pos,
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
menu->addAction(aPromoteToMod);
}
+
+ if (userLevel.testFlag(ServerInfo_User::IsJudge) &&
+ (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
+ menu->addAction(aDemoteFromJudge);
+
+ } else if (userLevel.testFlag(ServerInfo_User::IsRegistered) &&
+ (tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
+ menu->addAction(aPromoteToJudge);
+ }
}
bool anotherUser = userName != tabSupervisor->getOwnUsername();
aDetails->setEnabled(true);
@@ -389,6 +402,15 @@ void UserContextMenu::showContextMenu(const QPoint &pos,
cmd.set_user_name(userName.toStdString());
cmd.set_should_be_mod(actionClicked == aPromoteToMod);
+ PendingCommand *pend = client->prepareAdminCommand(cmd);
+ connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
+ SLOT(adjustMod_processUserResponse(Response, CommandContainer)));
+ client->sendCommand(pend);
+ } else if (actionClicked == aPromoteToJudge || actionClicked == aDemoteFromJudge) {
+ Command_AdjustMod cmd;
+ cmd.set_user_name(userName.toStdString());
+ cmd.set_should_be_judge(actionClicked == aPromoteToJudge);
+
PendingCommand *pend = client->prepareAdminCommand(cmd);
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(adjustMod_processUserResponse(Response, CommandContainer)));
diff --git a/cockatrice/src/user_context_menu.h b/cockatrice/src/user_context_menu.h
index 894ba617..884d67ba 100644
--- a/cockatrice/src/user_context_menu.h
+++ b/cockatrice/src/user_context_menu.h
@@ -30,6 +30,7 @@ private:
QAction *aKick;
QAction *aBan, *aBanHistory;
QAction *aPromoteToMod, *aDemoteFromMod;
+ QAction *aPromoteToJudge, *aDemoteFromJudge;
QAction *aWarnUser, *aWarnHistory;
signals:
void openMessageDialog(const QString &userName, bool focus);
diff --git a/cockatrice/src/userinfobox.cpp b/cockatrice/src/userinfobox.cpp
index d4d22e76..ebb063b2 100644
--- a/cockatrice/src/userinfobox.cpp
+++ b/cockatrice/src/userinfobox.cpp
@@ -109,6 +109,9 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user)
else
userLevelText = tr("Unregistered user");
+ if (userLevel.testFlag(ServerInfo_User::IsJudge))
+ userLevelText += " | " + tr("Judge");
+
if (user.has_privlevel() && user.privlevel() != "NONE") {
userLevelText += " | " + QString("%1").arg(user.privlevel().c_str());
}
diff --git a/cockatrice/src/userlist.cpp b/cockatrice/src/userlist.cpp
index 4e5600bc..0af5cb3d 100644
--- a/cockatrice/src/userlist.cpp
+++ b/cockatrice/src/userlist.cpp
@@ -307,7 +307,7 @@ bool UserListTWI::operator<(const QTreeWidgetItem &other) const
// Sort by user level
if (data(0, Qt::UserRole) != other.data(0, Qt::UserRole))
- return data(0, Qt::UserRole).toInt() > other.data(0, Qt::UserRole).toInt();
+ return (data(0, Qt::UserRole).toInt() & 15) > (other.data(0, Qt::UserRole).toInt() & 15);
// Sort by name
return QString::localeAwareCompare(data(2, Qt::UserRole).toString(), other.data(2, Qt::UserRole).toString()) < 0;
diff --git a/common/pb/admin_commands.proto b/common/pb/admin_commands.proto
index 9d5e5414..6f974d92 100644
--- a/common/pb/admin_commands.proto
+++ b/common/pb/admin_commands.proto
@@ -34,6 +34,7 @@ message Command_AdjustMod {
optional Command_AdjustMod ext = 1003;
}
required string user_name = 1;
- required bool should_be_mod = 2;
+ optional bool should_be_mod = 2;
+ optional bool should_be_judge = 3;
}
diff --git a/common/pb/event_game_joined.proto b/common/pb/event_game_joined.proto
index eaf2894c..5d90b0b7 100644
--- a/common/pb/event_game_joined.proto
+++ b/common/pb/event_game_joined.proto
@@ -13,4 +13,5 @@ message Event_GameJoined {
optional sint32 player_id = 4;
optional bool spectator = 5;
optional bool resuming = 6;
+ optional bool judge = 7;
}
diff --git a/common/pb/game_commands.proto b/common/pb/game_commands.proto
index 266c218f..1544f00c 100644
--- a/common/pb/game_commands.proto
+++ b/common/pb/game_commands.proto
@@ -34,7 +34,15 @@ message GameCommand {
SET_SIDEBOARD_LOCK = 1030;
CHANGE_ZONE_PROPERTIES = 1031;
UNCONCEDE = 1032;
-
+ JUDGE = 1033;
}
extensions 100 to max;
}
+
+message Command_Judge {
+ extend GameCommand {
+ optional Command_Judge ext = 1033;
+ }
+ optional sint32 target_id = 1 [default = -1];
+ repeated GameCommand game_command = 2;
+}
diff --git a/common/pb/game_event_container.proto b/common/pb/game_event_container.proto
index fd4ca430..239da4fd 100644
--- a/common/pb/game_event_container.proto
+++ b/common/pb/game_event_container.proto
@@ -7,4 +7,5 @@ message GameEventContainer {
repeated GameEvent event_list = 2;
optional GameEventContext context = 3;
optional uint32 seconds_elapsed = 4;
+ optional uint32 forced_by_judge = 5;
}
diff --git a/common/pb/room_commands.proto b/common/pb/room_commands.proto
index d6872810..d1dfde9f 100644
--- a/common/pb/room_commands.proto
+++ b/common/pb/room_commands.proto
@@ -36,6 +36,7 @@ message Command_CreateGame {
optional bool spectators_can_talk = 8;
optional bool spectators_see_everything = 9;
repeated uint32 game_type_ids = 10;
+ optional bool join_as_judge = 11;
}
message Command_JoinGame {
@@ -46,4 +47,5 @@ message Command_JoinGame {
optional string password = 2;
optional bool spectator = 3;
optional bool override_restrictions = 4;
+ optional bool join_as_judge = 5;
}
diff --git a/common/pb/serverinfo_playerproperties.proto b/common/pb/serverinfo_playerproperties.proto
index 96f3644a..cdd0ee42 100644
--- a/common/pb/serverinfo_playerproperties.proto
+++ b/common/pb/serverinfo_playerproperties.proto
@@ -10,4 +10,5 @@ message ServerInfo_PlayerProperties {
optional string deck_hash = 6;
optional sint32 ping_seconds = 7;
optional bool sideboard_locked = 8;
+ optional bool judge = 9;
}
diff --git a/common/pb/serverinfo_user.proto b/common/pb/serverinfo_user.proto
index 5baf091c..908d2032 100644
--- a/common/pb/serverinfo_user.proto
+++ b/common/pb/serverinfo_user.proto
@@ -6,6 +6,7 @@ message ServerInfo_User {
IsRegistered = 2;
IsModerator = 4;
IsAdmin = 8;
+ IsJudge = 16;
};
enum Gender {
GenderUnknown = -1;
diff --git a/common/server.h b/common/server.h
index 6b299279..b7f5e85f 100644
--- a/common/server.h
+++ b/common/server.h
@@ -172,6 +172,10 @@ public:
{
return 0;
}
+ virtual bool permitCreateGameAsJudge() const
+ {
+ return false;
+ }
Server_DatabaseInterface *getDatabaseInterface() const;
int getNextLocalGameId()
diff --git a/common/server_game.cpp b/common/server_game.cpp
index 859ae81f..2fff28a4 100644
--- a/common/server_game.cpp
+++ b/common/server_game.cpp
@@ -390,8 +390,11 @@ void Server_Game::stopGameIfFinished()
emit gameInfoChanged(gameInfo);
}
-Response::ResponseCode
-Server_Game::checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions)
+Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user,
+ const QString &_password,
+ bool spectator,
+ bool overrideRestrictions,
+ bool asJudge)
{
Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface();
{
@@ -400,6 +403,10 @@ Server_Game::checkJoin(ServerInfo_User *user, const QString &_password, bool spe
if (playerIterator.next().value()->getUserInfo()->name() == user->name())
return Response::RespContextError;
}
+
+ if (asJudge && !(user->user_level() & ServerInfo_User::IsJudge)) {
+ return Response::RespUserLevelTooLow;
+ }
if (!(overrideRestrictions && (user->user_level() & ServerInfo_User::IsModerator))) {
if ((_password != password) && !(spectator && !spectatorsNeedPassword))
return Response::RespWrongPassword;
@@ -437,12 +444,14 @@ bool Server_Game::containsUser(const QString &userName) const
void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface,
ResponseContainer &rc,
bool spectator,
+ bool judge,
bool broadcastUpdate)
{
QMutexLocker locker(&gameMutex);
Server_Player *newPlayer = new Server_Player(this, nextPlayerId++, userInterface->copyUserInfo(true, true, true),
- spectator, userInterface);
+ spectator, judge, userInterface);
+
newPlayer->moveToThread(thread());
Event_Join joinEvent;
@@ -663,6 +672,7 @@ void Server_Game::createGameJoinedEvent(Server_Player *player, ResponseContainer
event1.set_host_id(hostId);
event1.set_player_id(player->getPlayerId());
event1.set_spectator(player->getSpectator());
+ event1.set_judge(player->getJudge());
event1.set_resuming(resuming);
if (resuming) {
const QStringList &allGameTypes = room->getGameTypes();
diff --git a/common/server_game.h b/common/server_game.h
index 89ad6e31..39470b52 100644
--- a/common/server_game.h
+++ b/common/server_game.h
@@ -157,11 +157,12 @@ public:
return spectatorsSeeEverything;
}
Response::ResponseCode
- checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions);
+ checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions, bool asJudge);
bool containsUser(const QString &userName) const;
void addPlayer(Server_AbstractUserInterface *userInterface,
ResponseContainer &rc,
bool spectator,
+ bool judge,
bool broadcastUpdate = true);
void removePlayer(Server_Player *player, Event_Leave::LeaveReason reason);
void removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Player *player);
diff --git a/common/server_player.cpp b/common/server_player.cpp
index 8ccfc62b..8289c640 100644
--- a/common/server_player.cpp
+++ b/common/server_player.cpp
@@ -87,10 +87,11 @@ Server_Player::Server_Player(Server_Game *_game,
int _playerId,
const ServerInfo_User &_userInfo,
bool _spectator,
+ bool _judge,
Server_AbstractUserInterface *_userInterface)
: ServerInfo_User_Container(_userInfo), game(_game), userInterface(_userInterface), deck(nullptr), pingTime(0),
- playerId(_playerId), spectator(_spectator), initialCards(0), nextCardId(0), readyStart(false), conceded(false),
- sideboardLocked(true)
+ playerId(_playerId), spectator(_spectator), judge(_judge), initialCards(0), nextCardId(0), readyStart(false),
+ conceded(false), sideboardLocked(true)
{
}
@@ -251,6 +252,7 @@ void Server_Player::getProperties(ServerInfo_PlayerProperties &result, bool with
result.set_sideboard_locked(sideboardLocked);
result.set_ready_start(readyStart);
}
+ result.set_judge(judge);
if (deck)
result.set_deck_hash(deck->getDeckHash().toStdString());
result.set_ping_seconds(pingTime);
@@ -353,7 +355,7 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
{
// Disallow controller change to other zones than the table.
if (((targetzone->getType() != ServerInfo_Zone::PublicZone) || !targetzone->hasCoords()) &&
- (startzone->getPlayer() != targetzone->getPlayer()))
+ (startzone->getPlayer() != targetzone->getPlayer()) && !judge)
return Response::RespContextError;
if (!targetzone->hasCoords() && (x <= -1))
@@ -797,6 +799,24 @@ Server_Player::cmdUnconcede(const Command_Unconcede & /*cmd*/, ResponseContainer
return Response::RespOk;
}
+Response::ResponseCode Server_Player::cmdJudge(const Command_Judge &cmd, ResponseContainer &rc, GameEventStorage &ges)
+{
+ if (!judge)
+ return Response::RespFunctionNotAllowed;
+
+ Server_Player *player = this->game->getPlayers().value(cmd.target_id());
+
+ ges.setForcedByJudge(playerId);
+ if (player == nullptr)
+ return Response::RespContextError;
+
+ for (int i = 0; i < cmd.game_command_size(); ++i) {
+ player->processGameCommand(cmd.game_command(i), rc, ges);
+ }
+
+ return Response::RespOk;
+}
+
Response::ResponseCode
Server_Player::cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges)
{
@@ -988,7 +1008,7 @@ Server_Player::cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer & /*rc
if (!startZone)
return Response::RespNameNotFound;
- if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)))
+ if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)) && !judge)
return Response::RespContextError;
Server_Player *targetPlayer = game->getPlayers().value(cmd.target_player_id());
@@ -998,7 +1018,7 @@ Server_Player::cmdMoveCard(const Command_MoveCard &cmd, ResponseContainer & /*rc
if (!targetZone)
return Response::RespNameNotFound;
- if ((startPlayer != this) && (targetPlayer != this))
+ if ((startPlayer != this) && (targetPlayer != this) && !judge)
return Response::RespContextError;
QList cardsToMove;
@@ -1491,13 +1511,16 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &
Response::ResponseCode
Server_Player::cmdNextTurn(const Command_NextTurn & /*cmd*/, ResponseContainer & /*rc*/, GameEventStorage & /*ges*/)
{
- if (spectator)
- return Response::RespFunctionNotAllowed;
-
if (!game->getGameStarted())
return Response::RespGameNotStarted;
- if (conceded)
- return Response::RespContextError;
+
+ if (!judge) {
+ if (spectator)
+ return Response::RespFunctionNotAllowed;
+
+ if (conceded)
+ return Response::RespContextError;
+ }
game->nextTurn();
return Response::RespOk;
@@ -1507,16 +1530,20 @@ Response::ResponseCode Server_Player::cmdSetActivePhase(const Command_SetActiveP
ResponseContainer & /*rc*/,
GameEventStorage & /*ges*/)
{
- if (spectator)
- return Response::RespFunctionNotAllowed;
-
if (!game->getGameStarted())
return Response::RespGameNotStarted;
- if (conceded)
- return Response::RespContextError;
- if (game->getActivePlayer() != playerId)
- return Response::RespContextError;
+ if (!judge) {
+ if (spectator)
+ return Response::RespFunctionNotAllowed;
+
+ if (conceded)
+ return Response::RespContextError;
+
+ if (game->getActivePlayer() != playerId)
+ return Response::RespContextError;
+ }
+
game->setActivePhase(cmd.phase());
return Response::RespOk;
@@ -1858,6 +1885,9 @@ Server_Player::processGameCommand(const GameCommand &command, ResponseContainer
case GameCommand::UNCONCEDE:
return cmdUnconcede(command.GetExtension(Command_Unconcede::ext), rc, ges);
break;
+ case GameCommand::JUDGE:
+ return cmdJudge(command.GetExtension(Command_Judge::ext), rc, ges);
+ break;
default:
return Response::RespInvalidCommand;
diff --git a/common/server_player.h b/common/server_player.h
index 9ff4ebde..79b8c9ee 100644
--- a/common/server_player.h
+++ b/common/server_player.h
@@ -47,6 +47,7 @@ class Command_IncCardCounter;
class Command_ReadyStart;
class Command_Concede;
class Command_Unconcede;
+class Command_Judge;
class Command_IncCounter;
class Command_CreateCounter;
class Command_SetCounter;
@@ -77,6 +78,7 @@ private:
int pingTime;
int playerId;
bool spectator;
+ bool judge;
int initialCards;
int nextCardId;
bool readyStart;
@@ -89,6 +91,7 @@ public:
int _playerId,
const ServerInfo_User &_userInfo,
bool _spectator,
+ bool _judge,
Server_AbstractUserInterface *_handler);
~Server_Player() override;
void prepareDestroy();
@@ -115,6 +118,10 @@ public:
{
return spectator;
}
+ bool getJudge() const
+ {
+ return judge;
+ }
bool getConceded() const
{
return conceded;
@@ -185,6 +192,7 @@ public:
cmdKickFromGame(const Command_KickFromGame &cmd, ResponseContainer &rc, GameEventStorage &ges);
Response::ResponseCode cmdConcede(const Command_Concede &cmd, ResponseContainer &rc, GameEventStorage &ges);
Response::ResponseCode cmdUnconcede(const Command_Unconcede &cmd, ResponseContainer &rc, GameEventStorage &ges);
+ Response::ResponseCode cmdJudge(const Command_Judge &cmd, ResponseContainer &rc, GameEventStorage &ges);
Response::ResponseCode cmdReadyStart(const Command_ReadyStart &cmd, ResponseContainer &rc, GameEventStorage &ges);
Response::ResponseCode cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &rc, GameEventStorage &ges);
Response::ResponseCode
diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp
index e27be317..e49c5b0a 100644
--- a/common/server_protocolhandler.cpp
+++ b/common/server_protocolhandler.cpp
@@ -762,6 +762,11 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
if (room->getGamesCreatedByUser(QString::fromStdString(userInfo->name())) >= server->getMaxGamesPerUser())
return Response::RespContextError;
+ if (cmd.join_as_judge() && !server->permitCreateGameAsJudge() &&
+ !(userInfo->user_level() & ServerInfo_User::IsJudge)) {
+ return Response::RespContextError;
+ }
+
QList gameTypes;
for (int i = cmd.game_type_ids_size() - 1; i >= 0; --i)
gameTypes.append(cmd.game_type_ids(i));
@@ -776,7 +781,8 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), cmd.max_players(), gameTypes,
cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(), cmd.spectators_need_password(),
cmd.spectators_can_talk(), cmd.spectators_see_everything(), room);
- game->addPlayer(this, rc, false, false);
+
+ game->addPlayer(this, rc, false, cmd.join_as_judge(), false);
room->addGame(game);
return Response::RespOk;
diff --git a/common/server_response_containers.cpp b/common/server_response_containers.cpp
index a9b38a01..c487e7f2 100644
--- a/common/server_response_containers.cpp
+++ b/common/server_response_containers.cpp
@@ -53,6 +53,10 @@ void GameEventStorage::sendToGame(Server_Game *game)
GameEventContainer *contPrivate = new GameEventContainer;
GameEventContainer *contOthers = new GameEventContainer;
+ if (forcedByJudge != -1) {
+ contPrivate->set_forced_by_judge(forcedByJudge);
+ contOthers->set_forced_by_judge(forcedByJudge);
+ }
for (int i = 0; i < gameEventList.size(); ++i) {
const GameEvent &event = gameEventList[i]->getGameEvent();
const GameEventStorageItem::EventRecipients recipients = gameEventList[i]->getRecipients();
diff --git a/common/server_response_containers.h b/common/server_response_containers.h
index b2e77755..404e8773 100644
--- a/common/server_response_containers.h
+++ b/common/server_response_containers.h
@@ -48,6 +48,7 @@ private:
::google::protobuf::Message *gameEventContext;
QList gameEventList;
int privatePlayerId;
+ int forcedByJudge = -1;
public:
GameEventStorage();
@@ -66,6 +67,10 @@ public:
{
return privatePlayerId;
}
+ void setForcedByJudge(int playerId)
+ {
+ forcedByJudge = playerId;
+ }
void enqueueGameEvent(const ::google::protobuf::Message &event,
int playerId,
diff --git a/common/server_room.cpp b/common/server_room.cpp
index 5f1ccd3c..a95f4eb6 100644
--- a/common/server_room.cpp
+++ b/common/server_room.cpp
@@ -262,9 +262,9 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam
QMutexLocker gameLocker(&g->gameMutex);
Response::ResponseCode result = g->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()),
- cmd.spectator(), cmd.override_restrictions());
+ cmd.spectator(), cmd.override_restrictions(), cmd.join_as_judge());
if (result == Response::RespOk)
- g->addPlayer(userInterface, rc, cmd.spectator());
+ g->addPlayer(userInterface, rc, cmd.spectator(), cmd.join_as_judge());
return result;
}
diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example
index c2aee7c0..b35208c7 100644
--- a/servatrice/servatrice.ini.example
+++ b/servatrice/servatrice.ini.example
@@ -304,6 +304,11 @@ max_game_inactivity_time=120
; the database. Default value is true.
store_replays=true
+; Allow users to create a new game and join it as a judge. The host will be able to execute any action on
+; the cards of every player. This is needed in order to support some games (eg. Werewolf).
+; Default off to prevent abuse on servers that are mostly running other games.
+allow_create_as_judge=false
+
[security]
; You may want to restrict the number of users that can connect to your server at any given time.
enable_max_user_limit=false
diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp
index d9d08880..1991aa7c 100644
--- a/servatrice/src/servatrice.cpp
+++ b/servatrice/src/servatrice.cpp
@@ -950,6 +950,11 @@ int Servatrice::getNumberOfTCPPools() const
return settingsCache->value("server/number_pools", 1).toInt();
}
+bool Servatrice::permitCreateGameAsJudge() const
+{
+ return settingsCache->value("game/allow_create_as_judge", false).toBool();
+}
+
QHostAddress Servatrice::getServerTCPHost() const
{
QString host = settingsCache->value("server/host", "any").toString();
diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h
index 6d2788c0..9b06329c 100644
--- a/servatrice/src/servatrice.h
+++ b/servatrice/src/servatrice.h
@@ -268,6 +268,7 @@ public:
int getCommandCountingInterval() const override;
int getMaxCommandCountPerInterval() const override;
int getMaxUserTotal() const override;
+ bool permitCreateGameAsJudge() const override;
int getMaxTcpUserLimit() const;
int getMaxWebSocketUserLimit() const;
int getUsersWithAddress(const QHostAddress &address) const;
diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp
index 31f74339..c7498667 100644
--- a/servatrice/src/servatrice_database_interface.cpp
+++ b/servatrice/src/servatrice_database_interface.cpp
@@ -549,10 +549,14 @@ ServerInfo_User Servatrice_DatabaseInterface::evalUserQueryResult(const QSqlQuer
const int is_admin = query->value(2).toInt();
int userLevel = ServerInfo_User::IsUser | ServerInfo_User::IsRegistered;
- if (is_admin == 1)
+ if (is_admin & 1)
userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator;
- else if (is_admin == 2)
+ else if (is_admin & 2)
userLevel |= ServerInfo_User::IsModerator;
+
+ if (is_admin & 4)
+ userLevel |= ServerInfo_User::IsJudge;
+
result.set_user_level(userLevel);
const QString country = query->value(3).toString();
diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp
index 7ea74ba0..51c8a6d0 100644
--- a/servatrice/src/serversocketinterface.cpp
+++ b/servatrice/src/serversocketinterface.cpp
@@ -1415,57 +1415,88 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReloadConfig(const Comm
return Response::RespOk;
}
+bool AbstractServerSocketInterface::addAdminFlagToUser(const QString &userName, int flag)
+{
+ QSqlQuery *query =
+ sqlInterface->prepareQuery("update {prefix}_users set admin = (admin | :adminlevel) where name = :username");
+ query->bindValue(":adminlevel", flag);
+ query->bindValue(":username", userName);
+ if (!sqlInterface->execSqlQuery(query)) {
+ logger->logMessage(
+ QString::fromStdString("Failed to promote user %1: %2").arg(userName).arg(query->lastError().text()));
+ return false;
+ }
+
+ AbstractServerSocketInterface *user =
+ static_cast(server->getUsers().value(userName));
+ if (user) {
+ Event_NotifyUser event;
+ event.set_type(Event_NotifyUser::PROMOTED);
+ SessionEvent *se = user->prepareSessionEvent(event);
+ user->sendProtocolItem(*se);
+ delete se;
+ }
+
+ return true;
+}
+
+bool AbstractServerSocketInterface::removeAdminFlagFromUser(const QString &userName, int flag)
+{
+ QSqlQuery *query =
+ sqlInterface->prepareQuery("update {prefix}_users set admin = (admin & ~ :adminlevel) where name = :username");
+ query->bindValue(":adminlevel", flag);
+ query->bindValue(":username", userName);
+ if (!sqlInterface->execSqlQuery(query)) {
+ logger->logMessage(
+ QString::fromStdString("Failed to demote user %1: %2").arg(userName).arg(query->lastError().text()));
+ return false;
+ }
+
+ AbstractServerSocketInterface *user =
+ static_cast(server->getUsers().value(userName));
+ if (user) {
+ Event_ConnectionClosed event;
+ event.set_reason(Event_ConnectionClosed::DEMOTED);
+ event.set_reason_str("Your moderator and/or judge status has been revoked.");
+ event.set_end_time(QDateTime::currentDateTime().toTime_t());
+
+ SessionEvent *se = user->prepareSessionEvent(event);
+ user->sendProtocolItem(*se);
+ delete se;
+ }
+
+ QMetaObject::invokeMethod(user, "prepareDestroy", Qt::QueuedConnection);
+ return true;
+}
+
Response::ResponseCode AbstractServerSocketInterface::cmdAdjustMod(const Command_AdjustMod &cmd,
ResponseContainer & /*rc*/)
{
QString userName = QString::fromStdString(cmd.user_name());
- if (cmd.should_be_mod()) {
- QSqlQuery *query =
- sqlInterface->prepareQuery("update {prefix}_users set admin = :adminlevel where name = :username");
- query->bindValue(":adminlevel", 2);
- query->bindValue(":username", userName);
- if (!sqlInterface->execSqlQuery(query)) {
- logger->logMessage(
- QString::fromStdString("Failed to promote user %1: %2").arg(userName).arg(query->lastError().text()));
- return Response::RespInternalError;
+ if (cmd.has_should_be_mod()) {
+ if (cmd.should_be_mod()) {
+ if (!addAdminFlagToUser(userName, 2)) {
+ return Response::RespInternalError;
+ }
+ } else {
+ if (!removeAdminFlagFromUser(userName, 2)) {
+ return Response::RespInternalError;
+ }
}
+ }
- AbstractServerSocketInterface *user =
- static_cast(server->getUsers().value(userName));
- if (user) {
- Event_NotifyUser event;
- event.set_type(Event_NotifyUser::PROMOTED);
- SessionEvent *se = user->prepareSessionEvent(event);
- user->sendProtocolItem(*se);
- delete se;
+ if (cmd.has_should_be_judge()) {
+ if (cmd.should_be_judge()) {
+ if (!addAdminFlagToUser(userName, 4)) {
+ return Response::RespInternalError;
+ }
+ } else {
+ if (!removeAdminFlagFromUser(userName, 4)) {
+ return Response::RespInternalError;
+ }
}
- } else {
- QSqlQuery *query =
- sqlInterface->prepareQuery("update {prefix}_users set admin = :adminlevel where name = :username");
- query->bindValue(":adminlevel", 0);
- query->bindValue(":username", userName);
- if (!sqlInterface->execSqlQuery(query)) {
- logger->logMessage(
- QString::fromStdString("Failed to demote user %1: %2").arg(userName).arg(query->lastError().text()));
- return Response::RespInternalError;
- }
-
- AbstractServerSocketInterface *user =
- static_cast(server->getUsers().value(userName));
- if (user) {
- Event_ConnectionClosed event;
- event.set_reason(Event_ConnectionClosed::DEMOTED);
- event.set_reason_str("Your moderator status has been revoked.");
- event.set_end_time(QDateTime::currentDateTime().toTime_t());
-
- SessionEvent *se = user->prepareSessionEvent(event);
- user->sendProtocolItem(*se);
- delete se;
- }
-
- QMetaObject::invokeMethod(user, "prepareDestroy", Qt::QueuedConnection);
}
return Response::RespOk;
diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h
index 6a78cfa1..69b239c3 100644
--- a/servatrice/src/serversocketinterface.h
+++ b/servatrice/src/serversocketinterface.h
@@ -119,6 +119,9 @@ private:
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
Response::ResponseCode cmdAccountPassword(const Command_AccountPassword &cmd, ResponseContainer &rc);
+ bool addAdminFlagToUser(const QString &user, int flag);
+ bool removeAdminFlagFromUser(const QString &user, int flag);
+
public:
AbstractServerSocketInterface(Servatrice *_server,
Servatrice_DatabaseInterface *_databaseInterface,