From d50d179b2f1fb580aac6775755d0e3ca0d349636 Mon Sep 17 00:00:00 2001 From: Max-Wilhelm Bruker Date: Mon, 20 Feb 2012 22:13:48 +0100 Subject: [PATCH] server-side replay support --- cockatrice/src/localserver.h | 13 --- common/pb/CMakeLists.txt | 1 + common/pb/game_event_container.proto | 1 + common/pb/game_replay.proto | 7 ++ common/pb/serverinfo_game.proto | 1 + common/server.h | 29 +++--- common/server_game.cpp | 134 +++++++++++++++++---------- common/server_game.h | 9 +- common/server_player.cpp | 9 +- common/server_player.h | 4 +- common/server_protocolhandler.cpp | 14 +-- servatrice/src/servatrice.cpp | 42 +++++++++ servatrice/src/servatrice.h | 2 + 13 files changed, 177 insertions(+), 89 deletions(-) create mode 100644 common/pb/game_replay.proto diff --git a/cockatrice/src/localserver.h b/cockatrice/src/localserver.h index 23f3f68b..8935c12c 100644 --- a/cockatrice/src/localserver.h +++ b/cockatrice/src/localserver.h @@ -11,23 +11,10 @@ class LocalServer : public Server public: LocalServer(QObject *parent = 0); ~LocalServer(); - AuthenticationResult checkUserPassword(Server_ProtocolHandler * /*handler*/, const QString & /*user*/, const QString & /*password*/, QString & /*reasonStr*/) { return UnknownUser; } - QString getLoginMessage() const { return QString(); } - bool getGameShouldPing() const { return false; } - int getMaxGameInactivityTime() const { return 9999999; } - int getMaxPlayerInactivityTime() const { return 9999999; } - bool getThreaded() const { return false; } LocalServerInterface *newConnection(); protected: - int startSession(const QString & /*userName*/, const QString & /*address*/) { return -1; } - void endSession(int /*sessionId*/) { } - bool userExists(const QString & /*name*/) { return false; } ServerInfo_User getUserData(const QString &name); - QMap getBuddyList(const QString & /*name*/) { return QMap(); } - QMap getIgnoreList(const QString & /*name*/) { return QMap(); } - bool isInBuddyList(const QString & /*whoseList*/, const QString & /*who*/) { return false; } - bool isInIgnoreList(const QString & /*whoseList*/, const QString & /*who*/) { return false; } }; #endif \ No newline at end of file diff --git a/common/pb/CMakeLists.txt b/common/pb/CMakeLists.txt index f490f6db..9e5503a9 100644 --- a/common/pb/CMakeLists.txt +++ b/common/pb/CMakeLists.txt @@ -95,6 +95,7 @@ SET(PROTO_FILES game_event_container.proto game_event_context.proto game_event.proto + game_replay.proto moderator_commands.proto move_card_to_zone.proto response_deck_download.proto diff --git a/common/pb/game_event_container.proto b/common/pb/game_event_container.proto index 7a04cb37..69b92d23 100644 --- a/common/pb/game_event_container.proto +++ b/common/pb/game_event_container.proto @@ -5,4 +5,5 @@ message GameEventContainer { optional uint32 game_id = 1; repeated GameEvent event_list = 2; optional GameEventContext context = 3; + optional uint32 seconds_elapsed = 4; } diff --git a/common/pb/game_replay.proto b/common/pb/game_replay.proto new file mode 100644 index 00000000..10bb233d --- /dev/null +++ b/common/pb/game_replay.proto @@ -0,0 +1,7 @@ +import "serverinfo_game.proto"; +import "game_event_container.proto"; + +message GameReplay { + optional ServerInfo_Game game_info = 1; + repeated GameEventContainer event_list = 2; +} diff --git a/common/pb/serverinfo_game.proto b/common/pb/serverinfo_game.proto index 12e5b3bf..6f0e6ed3 100644 --- a/common/pb/serverinfo_game.proto +++ b/common/pb/serverinfo_game.proto @@ -15,4 +15,5 @@ message ServerInfo_Game { optional bool spectators_allowed = 12; optional bool spectators_need_password = 13; optional uint32 spectators_count = 14; + optional uint32 start_time = 15; } diff --git a/common/server.h b/common/server.h index 45d76481..7f0a75c3 100644 --- a/common/server.h +++ b/common/server.h @@ -10,6 +10,7 @@ class Server_Game; class Server_Room; class Server_ProtocolHandler; +class GameReplay; enum AuthenticationResult { NotLoggedIn = 0, PasswordRight = 1, UnknownUser = 2, WouldOverwriteOldSession = 3, UserIsBanned = 4 }; @@ -31,31 +32,33 @@ public: const QMap &getUsers() const { return users; } void addClient(Server_ProtocolHandler *player); void removeClient(Server_ProtocolHandler *player); - virtual QString getLoginMessage() const = 0; + virtual QString getLoginMessage() const { return QString(); } - virtual bool getGameShouldPing() const = 0; - virtual int getMaxGameInactivityTime() const = 0; - virtual int getMaxPlayerInactivityTime() const = 0; + virtual bool getGameShouldPing() const { return false; } + virtual int getMaxGameInactivityTime() const { return 9999999; } + virtual int getMaxPlayerInactivityTime() const { return 9999999; } virtual int getMessageCountingInterval() const { return 0; } virtual int getMaxMessageCountPerInterval() const { return 0; } virtual int getMaxMessageSizePerInterval() const { return 0; } virtual int getMaxGamesPerUser() const { return 0; } - virtual bool getThreaded() const = 0; + virtual bool getThreaded() const { return false; } - virtual QMap getBuddyList(const QString &name) = 0; - virtual QMap getIgnoreList(const QString &name) = 0; - virtual bool isInBuddyList(const QString &whoseList, const QString &who) = 0; - virtual bool isInIgnoreList(const QString &whoseList, const QString &who) = 0; + virtual QMap getBuddyList(const QString &name) { return QMap(); } + virtual QMap getIgnoreList(const QString &name) { return QMap(); } + virtual bool isInBuddyList(const QString &whoseList, const QString &who) { return false; } + virtual bool isInIgnoreList(const QString &whoseList, const QString &who) { return false; } + + virtual void storeGameInformation(int secondsElapsed, const QStringList &allPlayersEver, const GameReplay &replay) { } protected: void prepareDestroy(); QList clients; QMap users; QMap rooms; - virtual int startSession(const QString &userName, const QString &address) = 0; - virtual void endSession(int sessionId) = 0; - virtual bool userExists(const QString &user) = 0; - virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reason) = 0; + virtual int startSession(const QString &userName, const QString &address) { return -1; } + virtual void endSession(int sessionId) { } + virtual bool userExists(const QString &user) { return false; } + virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reason) { return UnknownUser; } virtual ServerInfo_User getUserData(const QString &name) = 0; int getUsersCount() const; int getGamesCount() const; diff --git a/common/server_game.cpp b/common/server_game.cpp index faacc9bf..99180d1a 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -39,13 +39,17 @@ #include "pb/event_set_active_player.pb.h" #include "pb/event_set_active_phase.pb.h" #include "pb/serverinfo_playerping.pb.h" +#include "pb/game_replay.pb.h" #include #include #include Server_Game::Server_Game(Server_ProtocolHandler *_creator, int _gameId, const QString &_description, const QString &_password, int _maxPlayers, const QList &_gameTypes, bool _onlyBuddies, bool _onlyRegistered, bool _spectatorsAllowed, bool _spectatorsNeedPassword, bool _spectatorsCanTalk, bool _spectatorsSeeEverything, Server_Room *_room) - : QObject(), room(_room), hostId(0), creatorInfo(new ServerInfo_User(_creator->copyUserInfo(false))), gameStarted(false), gameId(_gameId), description(_description), password(_password), maxPlayers(_maxPlayers), gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies), onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed), spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk), spectatorsSeeEverything(_spectatorsSeeEverything), inactivityCounter(0), secondsElapsed(0), gameMutex(QMutex::Recursive) + : QObject(), room(_room), hostId(0), creatorInfo(new ServerInfo_User(_creator->copyUserInfo(false))), gameStarted(false), gameId(_gameId), description(_description), password(_password), maxPlayers(_maxPlayers), gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies), onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed), spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk), spectatorsSeeEverything(_spectatorsSeeEverything), inactivityCounter(0), secondsElapsed(0), startTime(QDateTime::currentDateTime()), gameMutex(QMutex::Recursive) { + replay = new GameReplay; + replay->mutable_game_info()->CopyFrom(getInfo()); + connect(this, SIGNAL(sigStartGameIfReady()), this, SLOT(doStartGameIfReady()), Qt::QueuedConnection); addPlayer(_creator, false, false); @@ -59,8 +63,8 @@ Server_Game::Server_Game(Server_ProtocolHandler *_creator, int _gameId, const QS Server_Game::~Server_Game() { - QMutexLocker roomLocker(&room->roomMutex); - QMutexLocker locker(&gameMutex); + room->roomMutex.lock(); + gameMutex.lock(); sendGameEventContainer(prepareGameEvent(Event_GameClosed(), -1)); @@ -72,6 +76,13 @@ Server_Game::~Server_Game() room->removeGame(this); delete creatorInfo; creatorInfo = 0; + + gameMutex.unlock(); + room->roomMutex.unlock(); + + room->getServer()->storeGameInformation(secondsElapsed, allPlayersEver.toList(), *replay); + delete replay; + qDebug() << "Server_Game destructor: gameId=" << gameId; } @@ -146,6 +157,59 @@ int Server_Game::getSpectatorCount() const return result; } +void Server_Game::sendGameStateToPlayers() +{ + // Prepare game state information that everyone can see + Event_GameStateChanged gameStateChangedEvent; + gameStateChangedEvent.set_seconds_elapsed(secondsElapsed); + if (gameStarted) { + gameStateChangedEvent.set_game_started(true); + gameStateChangedEvent.set_active_player_id(0); + gameStateChangedEvent.set_active_phase(0); + } else + gameStateChangedEvent.set_game_started(false); + + // game state information for replay and omniscient spectators + Event_GameStateChanged omniscientEvent(gameStateChangedEvent); + QListIterator omniscientGameStateIterator(getGameState(0, true)); + while (omniscientGameStateIterator.hasNext()) + omniscientEvent.add_player_list()->CopyFrom(omniscientGameStateIterator.next()); + + GameEventContainer *replayCont = prepareGameEvent(omniscientEvent, -1); + replayCont->set_seconds_elapsed(secondsElapsed); + replayCont->clear_game_id(); + replay->add_event_list()->CopyFrom(*replayCont); + delete replayCont; + + // If spectators are not omniscient, we need an additional getGameState call, otherwise we can use the data we used for the replay. + // All spectators are equal, so we don't need to make a getGameState call for each one. + Event_GameStateChanged spectatorEvent(spectatorsSeeEverything ? omniscientEvent : gameStateChangedEvent); + if (!spectatorsSeeEverything) { + QListIterator spectatorGameStateIterator(getGameState(0, false)); + while (spectatorGameStateIterator.hasNext()) + spectatorEvent.add_player_list()->CopyFrom(spectatorGameStateIterator.next()); + } + + // send game state info to clients according to their role in the game + QMapIterator playerIterator(players); + while (playerIterator.hasNext()) { + Server_Player *player = playerIterator.next().value(); + GameEventContainer *gec; + if (player->getSpectator()) + gec = prepareGameEvent(spectatorEvent, -1); + else { + Event_GameStateChanged event(gameStateChangedEvent); + QListIterator gameStateIterator(getGameState(player)); + while (gameStateIterator.hasNext()) + event.add_player_list()->CopyFrom(gameStateIterator.next()); + + gec = prepareGameEvent(event, -1); + } + player->sendGameEvent(*gec); + delete gec; + } +} + void Server_Game::doStartGameIfReady() { QMutexLocker locker(&gameMutex); @@ -172,37 +236,9 @@ void Server_Game::doStartGameIfReady() player->setConceded(false); player->setReadyStart(false); } - playerIterator.toFront(); - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - Event_GameStateChanged event; - event.set_seconds_elapsed(secondsElapsed); - event.set_game_started(true); - event.set_active_player_id(0); - event.set_active_phase(0); - QListIterator gameStateIterator(getGameState(player)); - while (gameStateIterator.hasNext()) - event.add_player_list()->CopyFrom(gameStateIterator.next()); - - player->sendGameEvent(prepareGameEvent(event, -1)); - } -/* QSqlQuery query; - query.prepare("insert into games (id, descr, password, time_started) values(:id, :descr, :password, now())"); - query.bindValue(":id", gameId); - query.bindValue(":descr", description); - query.bindValue(":password", !password.isEmpty()); - query.exec(); + sendGameStateToPlayers(); - QMapIterator playerIterator2(players); - while (playerIterator2.hasNext()) { - Server_Player *player = playerIterator2.next().value(); - query.prepare("insert into games_players (id_game, player) values(:id, :player)"); - query.bindValue(":id", gameId); - query.bindValue(":player", player->getPlayerName()); - query.exec(); - } -*/ activePlayer = -1; nextTurn(); @@ -237,18 +273,7 @@ void Server_Game::stopGameIfFinished() p->setConceded(false); } - playerIterator.toFront(); - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); - Event_GameStateChanged event; - event.set_seconds_elapsed(secondsElapsed); - event.set_game_started(false); - QListIterator gameStateIterator(getGameState(player)); - while (gameStateIterator.hasNext()) - event.add_player_list()->CopyFrom(gameStateIterator.next()); - - player->sendGameEvent(prepareGameEvent(event, -1)); - } + sendGameStateToPlayers(); } Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions) @@ -296,9 +321,10 @@ Server_Player *Server_Game::addPlayer(Server_ProtocolHandler *handler, bool spec newPlayer->moveToThread(thread()); Event_Join joinEvent; - joinEvent.mutable_player_properties()->CopyFrom(newPlayer->getProperties()); + joinEvent.mutable_player_properties()->CopyFrom(newPlayer->getProperties(true)); sendGameEventContainer(prepareGameEvent(joinEvent, -1)); + allPlayersEver.insert(QString::fromStdString(newPlayer->getUserInfo()->name())); players.insert(playerId, newPlayer); if (newPlayer->getUserInfo()->name() == creatorInfo->name()) { hostId = playerId; @@ -410,7 +436,9 @@ bool Server_Game::kickPlayer(int playerId) if (!playerToKick) return false; - playerToKick->sendGameEvent(prepareGameEvent(Event_Kicked(), -1)); + GameEventContainer *gec = prepareGameEvent(Event_Kicked(), -1); + playerToKick->sendGameEvent(*gec); + delete gec; removePlayer(playerToKick); @@ -473,7 +501,7 @@ void Server_Game::nextTurn() setActivePlayer(keys[listPos]); } -QList Server_Game::getGameState(Server_Player *playerWhosAsking) const +QList Server_Game::getGameState(Server_Player *playerWhosAsking, bool omniscient, bool withUserInfo) const { QMutexLocker locker(&gameMutex); @@ -483,7 +511,7 @@ QList Server_Game::getGameState(Server_Player *playerWhosAski Server_Player *player = playerIterator.next().value(); ServerInfo_Player playerInfo; - playerInfo.mutable_properties()->CopyFrom(player->getProperties()); + playerInfo.mutable_properties()->CopyFrom(player->getProperties(withUserInfo)); if (player == playerWhosAsking) if (player->getDeck()) playerInfo.set_deck_list(player->getDeck()->writeToString_Native().toStdString()); @@ -528,7 +556,7 @@ QList Server_Game::getGameState(Server_Player *playerWhosAski zoneInfo->set_with_coords(zone->hasCoords()); zoneInfo->set_card_count(zone->cards.size()); if ( - (((playerWhosAsking == player) || (playerWhosAsking->getSpectator() && spectatorsSeeEverything)) && (zone->getType() != ServerInfo_Zone::HiddenZone)) + (((playerWhosAsking == player) || omniscient) && (zone->getType() != ServerInfo_Zone::HiddenZone)) || ((playerWhosAsking != player) && (zone->getType() == ServerInfo_Zone::PublicZone)) ) { QListIterator cardIterator(zone->cards); @@ -590,7 +618,12 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont, GameEventStor Server_Player *p = playerIterator.next().value(); const bool playerPrivate = (p->getPlayerId() == privatePlayerId) || (p->getSpectator() && spectatorsSeeEverything); if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) || (recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) - p->sendGameEvent(cont); + p->sendGameEvent(*cont); + } + if (recipients.testFlag(GameEventStorageItem::SendToPrivate)) { + cont->set_seconds_elapsed(secondsElapsed); + cont->clear_game_id(); + replay->add_event_list()->CopyFrom(*cont); } delete cont; @@ -631,6 +664,7 @@ ServerInfo_Game Server_Game::getInfo() const result.set_spectators_allowed(getSpectatorsAllowed()); result.set_spectators_need_password(getSpectatorsNeedPassword()); result.set_spectators_count(getSpectatorCount()); + result.set_start_time(startTime.toTime_t()); } return result; } diff --git a/common/server_game.h b/common/server_game.h index 0fefe940..7348c457 100644 --- a/common/server_game.h +++ b/common/server_game.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "server_player.h" #include "server_response_containers.h" #include "pb/response.pb.h" @@ -32,6 +34,7 @@ class QTimer; class GameEventContainer; +class GameReplay; class Server_Room; class ServerInfo_User; @@ -42,6 +45,7 @@ private: int hostId; ServerInfo_User *creatorInfo; QMap players; + QSet allPlayersEver; bool gameStarted; int gameId; QString description; @@ -56,7 +60,9 @@ private: bool spectatorsSeeEverything; int inactivityCounter; int secondsElapsed; + QDateTime startTime; QTimer *pingClock; + GameReplay *replay; signals: void sigStartGameIfReady(); private slots: @@ -98,7 +104,8 @@ public: void nextTurn(); int getSecondsElapsed() const { return secondsElapsed; } - QList getGameState(Server_Player *playerWhosAsking) const; + void sendGameStateToPlayers(); + QList getGameState(Server_Player *playerWhosAsking, bool omniscient = false, bool withUserInfo = false) const; GameEventContainer *prepareGameEvent(const ::google::protobuf::Message &gameEvent, int playerId, GameEventContext *context = 0); GameEventContext prepareGameEventContext(const ::google::protobuf::Message &gameEventContext); diff --git a/common/server_player.cpp b/common/server_player.cpp index 4c87e339..6cbeb896 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -191,13 +191,14 @@ void Server_Player::clearZones() lastDrawList.clear(); } -ServerInfo_PlayerProperties Server_Player::getProperties() +ServerInfo_PlayerProperties Server_Player::getProperties(bool withUserInfo) { QMutexLocker locker(&game->gameMutex); ServerInfo_PlayerProperties result; result.set_player_id(playerId); - result.mutable_user_info()->CopyFrom(*userInfo); + if (withUserInfo) + result.mutable_user_info()->CopyFrom(*userInfo); result.set_spectator(spectator); result.set_conceded(conceded); result.set_ready_start(readyStart); @@ -581,12 +582,12 @@ Response::ResponseCode Server_Player::setCardAttrHelper(GameEventStorage &ges, c return Response::RespOk; } -void Server_Player::sendGameEvent(GameEventContainer *cont) +void Server_Player::sendGameEvent(const GameEventContainer &cont) { QMutexLocker locker(&playerMutex); if (handler) - handler->sendProtocolItem(*cont); + handler->sendProtocolItem(cont); } void Server_Player::setProtocolHandler(Server_ProtocolHandler *_handler) diff --git a/common/server_player.h b/common/server_player.h index ca9ed40d..5f4bcadf 100644 --- a/common/server_player.h +++ b/common/server_player.h @@ -70,7 +70,7 @@ public: int getPingTime() const { return pingTime; } void setPingTime(int _pingTime) { pingTime = _pingTime; } - ServerInfo_PlayerProperties getProperties(); + ServerInfo_PlayerProperties getProperties(bool withUserInfo); int newCardId(); int newCounterId() const; @@ -92,7 +92,7 @@ public: void unattachCard(GameEventStorage &ges, Server_Card *card); Response::ResponseCode setCardAttrHelper(GameEventStorage &ges, const QString &zone, int cardId, CardAttribute attribute, const QString &attrValue); - void sendGameEvent(GameEventContainer *event); + void sendGameEvent(const GameEventContainer &event); }; #endif diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index dd3cc82f..3bd383ea 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -760,7 +760,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdCreateGame(const Command_Creat rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event1)); Event_GameStateChanged event2; - QListIterator gameStateIterator(game->getGameState(creator)); + QListIterator gameStateIterator(game->getGameState(creator, false, true)); while (gameStateIterator.hasNext()) event2.add_player_list()->CopyFrom(gameStateIterator.next()); event2.set_seconds_elapsed(0); @@ -807,7 +807,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinGame(const Command_JoinGam rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event1)); Event_GameStateChanged event2; - QListIterator gameStateIterator(g->getGameState(player)); + QListIterator gameStateIterator(g->getGameState(player, false, true)); while (gameStateIterator.hasNext()) event2.add_player_list()->CopyFrom(gameStateIterator.next()); event2.set_seconds_elapsed(g->getSecondsElapsed()); @@ -854,7 +854,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdDeckSelect(const Command_DeckS player->setDeck(deck); Event_PlayerPropertiesChanged event; - event.mutable_player_properties()->CopyFrom(player->getProperties()); + event.mutable_player_properties()->set_deck_hash(deck->getDeckHash().toStdString()); ges.enqueueGameEvent(event, player->getPlayerId()); Context_DeckSelect context; @@ -902,7 +902,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdConcede(const Command_Concede player->clearZones(); Event_PlayerPropertiesChanged event; - event.mutable_player_properties()->CopyFrom(player->getProperties()); + event.mutable_player_properties()->set_conceded(true); ges.enqueueGameEvent(event, player->getPlayerId()); ges.setGameEventContext(Context_Concede()); @@ -927,11 +927,13 @@ Response::ResponseCode Server_ProtocolHandler::cmdReadyStart(const Command_Ready player->setReadyStart(cmd.ready()); Event_PlayerPropertiesChanged event; - event.mutable_player_properties()->CopyFrom(player->getProperties()); + event.mutable_player_properties()->set_ready_start(cmd.ready()); ges.enqueueGameEvent(event, player->getPlayerId()); ges.setGameEventContext(Context_ReadyStart()); - game->startGameIfReady(); + if (cmd.ready()) + game->startGameIfReady(); + return Response::RespOk; } diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 88941987..028bf1b3 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -28,6 +28,7 @@ #include "server_logger.h" #include "main.h" #include "passwordhasher.h" +#include "pb/game_replay.pb.h" #include "pb/event_server_message.pb.h" #include "pb/event_server_shutdown.pb.h" #include "pb/event_connection_closed.pb.h" @@ -535,6 +536,47 @@ void Servatrice::statusUpdate() execSqlQuery(query); } +void Servatrice::storeGameInformation(int secondsElapsed, const QStringList &allPlayersEver, const GameReplay &replay) +{ + QStringList gameTypes; + for (int i = replay.game_info().game_types_size() - 1; i >= 0; --i) + gameTypes.append(QString::number(replay.game_info().game_types(i))); + + QByteArray replayBlob; + const unsigned int size = replay.ByteSize(); + replayBlob.resize(size); + replay.SerializeToArray(replayBlob.data(), size); + + QMutexLocker locker(&dbMutex); + if (!checkSql()) + return; + + QSqlQuery query1; + query1.prepare("insert into " + dbPrefix + "_games (id_room, id, descr, creator_name, password, game_types, player_count, time_started, time_finished, replay) values (:id_room, :id_game, :descr, :creator_name, :password, :game_types, :player_count, date_sub(now(), interval :seconds second), now(), :replay)"); + query1.bindValue(":id_room", replay.game_info().room_id()); + query1.bindValue(":id_game", replay.game_info().game_id()); + query1.bindValue(":descr", QString::fromStdString(replay.game_info().description())); + query1.bindValue(":creator_name", QString::fromStdString(replay.game_info().creator_info().name())); + query1.bindValue(":password", replay.game_info().with_password() ? 1 : 0); + query1.bindValue(":game_types", gameTypes.isEmpty() ? QString("") : gameTypes.join(",")); + query1.bindValue(":player_count", replay.game_info().max_players()); + query1.bindValue(":seconds", secondsElapsed); + query1.bindValue(":replay", replayBlob); + if (!execSqlQuery(query1)) + return; + + QSqlQuery query2; + query2.prepare("insert into " + dbPrefix + "_games_players (id_game, player_name) values (:id_game, :player_name)"); + QVariantList gameIds, playerNames; + for (int i = allPlayersEver.size() - 1; i >= 0; --i) { + gameIds.append(replay.game_info().game_id()); + playerNames.append(allPlayersEver[i]); + } + query2.bindValue(":id_game", gameIds); + query2.bindValue(":player_name", playerNames); + query2.execBatch(); +} + void Servatrice::scheduleShutdown(const QString &reason, int minutes) { QMutexLocker locker(&serverMutex); diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index dc8b1dd1..3904947e 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -29,6 +29,7 @@ class QSettings; class QSqlQuery; class QTimer; +class GameReplay; class Servatrice; class ServerSocketInterface; @@ -82,6 +83,7 @@ public: void incTxBytes(quint64 num); void incRxBytes(quint64 num); int getUserIdInDB(const QString &name); + void storeGameInformation(int secondsElapsed, const QStringList &allPlayersEver, const GameReplay &replay); protected: int startSession(const QString &userName, const QString &address); void endSession(int sessionId);