2
0
Эх сурвалжийг харах

Implemented matches history in lobby

Ivan Savenko 1 жил өмнө
parent
commit
829b754091

+ 5 - 0
Mods/vcmi/config/vcmi/english.json

@@ -78,6 +78,11 @@
 	"vcmi.lobby.login.error" : "Connection error: %s",
 	"vcmi.lobby.login.create" : "New Account",
 	"vcmi.lobby.login.login" : "Login",
+	"vcmi.lobby.header.rooms" : "Game Rooms",
+	"vcmi.lobby.header.channels" : "Chat Channels",
+	"vcmi.lobby.header.chat" : "Game Chat",
+	"vcmi.lobby.header.history" : "Your Games",
+	"vcmi.lobby.header.players" : "Players Online",
 	"vcmi.lobby.room.create" : "Create New Room",
 	"vcmi.lobby.room.players.limit" : "Players Limit",
 	"vcmi.lobby.room.description.public" : "Any player can join public room.",

+ 6 - 1
Mods/vcmi/config/vcmi/ukrainian.json

@@ -66,7 +66,7 @@
 	
 	"vcmi.lobby.filepath" : "Назва файлу",
 	"vcmi.lobby.creationDate" : "Дата створення",
-	"vcmi.lobby.scenarioName" : "Scenario name",
+	"vcmi.lobby.scenarioName" : "Назва сценарію",
 	"vcmi.lobby.mapPreview" : "Огляд мапи",
 	"vcmi.lobby.noPreview" : "огляд недоступний",
 	"vcmi.lobby.noUnderground" : "немає підземелля",
@@ -78,6 +78,11 @@
 	"vcmi.lobby.login.error" : "Помилка з'єднання: %s",
 	"vcmi.lobby.login.create" : "Створити акаунт",
 	"vcmi.lobby.login.login" : "Увійти",
+	"vcmi.lobby.header.rooms" : "Кімнати",
+	"vcmi.lobby.header.channels" : "Канали чату",
+	"vcmi.lobby.header.chat" : "Чат гри",
+	"vcmi.lobby.header.history" : "Ваші попередні ігри",
+	"vcmi.lobby.header.players" : "Гравці в мережі",
 	"vcmi.lobby.room.create" : "Створити нову кімнату",
 	"vcmi.lobby.room.players.limit" : "Максимум гравців",
 	"vcmi.lobby.room.description.public" : "Будь-хто з гравців може приєднатися до публічної кімнати.",

+ 1 - 2
client/CServerHandler.cpp

@@ -172,8 +172,7 @@ void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen
 		localPlayerNames.push_back(settings["general"]["playerName"].String());
 
 	gameChat->resetMatchState();
-	if (lobbyClient)
-		lobbyClient->resetMatchState();
+	lobbyClient->resetMatchState();
 }
 
 GameChatHandler & CServerHandler::getGameChat()

+ 15 - 2
client/GameChatHandler.cpp

@@ -13,6 +13,7 @@
 #include "CServerHandler.h"
 #include "CPlayerInterface.h"
 #include "PlayerLocalState.h"
+#include "globalLobby/GlobalLobbyClient.h"
 #include "lobby/CLobbyScreen.h"
 
 #include "adventureMap/CInGameConsole.h"
@@ -34,7 +35,7 @@ static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0)
 	return TextOperations::getFormattedTimeLocal(std::chrono::system_clock::to_time_t(timeNowChrono));
 }
 
-const std::vector<GameChatMessage> GameChatHandler::getChatHistory()
+const std::vector<GameChatMessage> & GameChatHandler::getChatHistory()
 {
 	return chatHistory;
 }
@@ -47,6 +48,7 @@ void GameChatHandler::resetMatchState()
 void GameChatHandler::sendMessageGameplay(const std::string & messageText)
 {
 	LOCPLINT->cb->sendMessage(messageText, LOCPLINT->localState->getCurrentArmy());
+	CSH->getGlobalLobby().sendMatchChatMessage(messageText);
 }
 
 void GameChatHandler::sendMessageLobby(const std::string & senderName, const std::string & messageText)
@@ -55,11 +57,22 @@ void GameChatHandler::sendMessageLobby(const std::string & senderName, const std
 	lcm.message = messageText;
 	lcm.playerName = senderName;
 	CSH->sendLobbyPack(lcm);
+	CSH->getGlobalLobby().sendMatchChatMessage(messageText);
 }
 
 void GameChatHandler::onNewLobbyMessageReceived(const std::string & senderName, const std::string & messageText)
 {
-	auto lobby = dynamic_cast<CLobbyScreen*>(SEL);
+	if (!SEL)
+	{
+		logGlobal->debug("Received chat message for lobby but lobby not yet exists!");
+		return;
+	}
+
+	auto * lobby = dynamic_cast<CLobbyScreen*>(SEL);
+
+	// FIXME: when can this happen?
+	assert(lobby);
+	assert(lobby->card);
 
 	if(lobby && lobby->card)
 	{

+ 1 - 1
client/GameChatHandler.h

@@ -25,7 +25,7 @@ class GameChatHandler : boost::noncopyable
 	std::vector<GameChatMessage> chatHistory;
 public:
 	/// Returns all message history for current match
-	const std::vector<GameChatMessage> getChatHistory();
+	const std::vector<GameChatMessage> & getChatHistory();
 
 	/// Erases any local state, must be called when client disconnects from match server
 	void resetMatchState();

+ 58 - 2
client/globalLobby/GlobalLobbyClient.cpp

@@ -78,6 +78,9 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptr<INetworkConnectio
 	if(json["type"].String() == "inviteReceived")
 		return receiveInviteReceived(json);
 
+	if(json["type"].String() == "matchesHistory")
+		return receiveMatchesHistory(json);
+
 	logGlobal->error("Received unexpected message from lobby server: %s", json["type"].String());
 }
 
@@ -211,7 +214,13 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
 		room.hostAccountDisplayName = jsonEntry["hostAccountDisplayName"].String();
 		room.description = jsonEntry["description"].String();
 		room.statusID = jsonEntry["status"].String();
-		room.playersCount = jsonEntry["playersCount"].Integer();
+		for (auto const & jsonParticipant : jsonEntry["participants"].Vector())
+		{
+			GlobalLobbyAccount account;
+			account.accountID =  jsonParticipant["accountID"].String();
+			account.displayName =  jsonParticipant["displayName"].String();
+			room.participants.push_back(account);
+		}
 		room.playerLimit = jsonEntry["playerLimit"].Integer();
 
 		activeRooms.push_back(room);
@@ -222,6 +231,36 @@ void GlobalLobbyClient::receiveActiveGameRooms(const JsonNode & json)
 		lobbyWindowPtr->onActiveRooms(activeRooms);
 }
 
+void GlobalLobbyClient::receiveMatchesHistory(const JsonNode & json)
+{
+	matchesHistory.clear();
+
+	for (auto const & jsonEntry : json["matchesHistory"].Vector())
+	{
+		GlobalLobbyRoom room;
+
+		room.gameRoomID = jsonEntry["gameRoomID"].String();
+		room.hostAccountID = jsonEntry["hostAccountID"].String();
+		room.hostAccountDisplayName = jsonEntry["hostAccountDisplayName"].String();
+		room.description = jsonEntry["description"].String();
+		room.statusID = jsonEntry["status"].String();
+		for (auto const & jsonParticipant : jsonEntry["participants"].Vector())
+		{
+			GlobalLobbyAccount account;
+			account.accountID =  jsonParticipant["accountID"].String();
+			account.displayName =  jsonParticipant["displayName"].String();
+			room.participants.push_back(account);
+		}
+		room.playerLimit = jsonEntry["playerLimit"].Integer();
+
+		matchesHistory.push_back(room);
+	}
+
+	auto lobbyWindowPtr = lobbyWindow.lock();
+	if(lobbyWindowPtr)
+		lobbyWindowPtr->onMatchesHistory(activeRooms);
+}
+
 void GlobalLobbyClient::receiveInviteReceived(const JsonNode & json)
 {
 	assert(0); //TODO
@@ -372,7 +411,7 @@ const std::vector<std::string> & GlobalLobbyClient::getActiveChannels() const
 	return activeChannels;
 }
 
-const std::vector<GlobalLobbyHistoryMatch> & GlobalLobbyClient::getMatchesHistory() const
+const std::vector<GlobalLobbyRoom> & GlobalLobbyClient::getMatchesHistory() const
 {
 	return matchesHistory;
 }
@@ -435,3 +474,20 @@ void GlobalLobbyClient::resetMatchState()
 {
 	currentGameRoomUUID.clear();
 }
+
+void GlobalLobbyClient::sendMatchChatMessage(const std::string & messageText)
+{
+	if (!isConnected())
+		return; // we are not playing with lobby
+
+	if (currentGameRoomUUID.empty())
+		return; // we are not playing through lobby
+
+	JsonNode toSend;
+	toSend["type"].String() = "sendChatMessage";
+	toSend["channelType"].String() = "match";
+	toSend["channelName"].String() = currentGameRoomUUID;
+	toSend["messageText"].String() = messageText;
+
+	CSH->getGlobalLobby().sendMessage(toSend);
+}

+ 4 - 2
client/globalLobby/GlobalLobbyClient.h

@@ -24,7 +24,7 @@ class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyabl
 	std::vector<GlobalLobbyAccount> activeAccounts;
 	std::vector<GlobalLobbyRoom> activeRooms;
 	std::vector<std::string> activeChannels;
-	std::vector<GlobalLobbyHistoryMatch> matchesHistory;
+	std::vector<GlobalLobbyRoom> matchesHistory;
 
 	/// Contains known history of each channel
 	/// Key: concatenated channel type and channel name
@@ -50,6 +50,7 @@ class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyabl
 	void receiveChatMessage(const JsonNode & json);
 	void receiveActiveAccounts(const JsonNode & json);
 	void receiveActiveGameRooms(const JsonNode & json);
+	void receiveMatchesHistory(const JsonNode & json);
 	void receiveJoinRoomSuccess(const JsonNode & json);
 	void receiveInviteReceived(const JsonNode & json);
 
@@ -63,12 +64,13 @@ public:
 	const std::vector<GlobalLobbyAccount> & getActiveAccounts() const;
 	const std::vector<GlobalLobbyRoom> & getActiveRooms() const;
 	const std::vector<std::string> & getActiveChannels() const;
-	const std::vector<GlobalLobbyHistoryMatch> & getMatchesHistory() const;
+	const std::vector<GlobalLobbyRoom> & getMatchesHistory() const;
 	const std::vector<GlobalLobbyChannelMessage> & getChannelHistory(const std::string & channelType, const std::string & channelName) const;
 
 	/// Activate interface and pushes lobby UI as top window
 	void activateInterface();
 
+	void sendMatchChatMessage(const std::string & messageText);
 	void sendMessage(const JsonNode & data);
 	void sendClientRegister(const std::string & accountName);
 	void sendClientLogin();

+ 2 - 8
client/globalLobby/GlobalLobbyDefines.h

@@ -23,7 +23,8 @@ struct GlobalLobbyRoom
 	std::string hostAccountDisplayName;
 	std::string description;
 	std::string statusID;
-	int playersCount;
+	std::string startDateFormatted;
+	std::vector<GlobalLobbyAccount> participants;
 	int playerLimit;
 };
 
@@ -34,10 +35,3 @@ struct GlobalLobbyChannelMessage
 	std::string displayName;
 	std::string messageText;
 };
-
-struct GlobalLobbyHistoryMatch
-{
-	std::string gameRoomID;
-	std::string startDateFormatted;
-	std::vector<std::string> opponentDisplayNames;
-};

+ 12 - 7
client/globalLobby/GlobalLobbyWidget.cpp

@@ -133,6 +133,11 @@ std::shared_ptr<CListBox> GlobalLobbyWidget::getRoomList()
 	return widget<CListBox>("roomList");
 }
 
+std::shared_ptr<CListBox> GlobalLobbyWidget::getMatchList()
+{
+	return widget<CListBox>("matchList");
+}
+
 GlobalLobbyChannelCardBase::GlobalLobbyChannelCardBase(GlobalLobbyWindow * window, const std::string & channelType, const std::string & channelName)
 	: window(window)
 	, channelType(channelType)
@@ -169,7 +174,7 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
 	};
 
 	auto roomSizeText = MetaString::createFromRawString("%d/%d");
-	roomSizeText.replaceNumber(roomDescription.playersCount);
+	roomSizeText.replaceNumber(roomDescription.participants.size());
 	roomSizeText.replaceNumber(roomDescription.playerLimit);
 
 	auto roomStatusText = MetaString::createFromTextID("vcmi.lobby.room.state." + roomDescription.statusID);
@@ -203,7 +208,7 @@ GlobalLobbyChannelCard::GlobalLobbyChannelCard(GlobalLobbyWindow * window, const
 	labelName = std::make_shared<CLabel>(5, 20, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, Languages::getLanguageOptions(channelName).nameNative);
 }
 
-GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const GlobalLobbyHistoryMatch & matchDescription)
+GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & matchDescription)
 	: GlobalLobbyChannelCardBase(window, "match", matchDescription.gameRoomID)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@@ -216,16 +221,16 @@ GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const Glo
 
 	MetaString opponentDescription;
 
-	if (matchDescription.opponentDisplayNames.empty())
+	if (matchDescription.participants.size() == 1)
 		opponentDescription.appendRawString("Solo game"); // or "Singleplayer game" is better?
 
-	if (matchDescription.opponentDisplayNames.size() == 1)
-		opponentDescription.appendRawString(matchDescription.opponentDisplayNames[0]);
+	if (matchDescription.participants.size() == 2)
+		opponentDescription.appendRawString(matchDescription.participants[0].displayName);//FIXME: find opponent - not our player
 
-	if (matchDescription.opponentDisplayNames.size() > 1)
+	if (matchDescription.participants.size() > 2)
 	{
 		opponentDescription.appendRawString("%d players");
-		opponentDescription.replaceNumber(matchDescription.opponentDisplayNames.size());
+		opponentDescription.replaceNumber(matchDescription.participants.size());
 	}
 
 	labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString());

+ 1 - 2
client/globalLobby/GlobalLobbyWidget.h

@@ -14,7 +14,6 @@
 class GlobalLobbyWindow;
 struct GlobalLobbyAccount;
 struct GlobalLobbyRoom;
-struct GlobalLobbyHistoryMatch;
 class CListBox;
 
 class GlobalLobbyWidget : public InterfaceObjectConfigurable
@@ -90,5 +89,5 @@ class GlobalLobbyMatchCard : public GlobalLobbyChannelCardBase
 	std::shared_ptr<CLabel> labelMatchOpponent;
 
 public:
-	GlobalLobbyMatchCard(GlobalLobbyWindow * window, const GlobalLobbyHistoryMatch & matchDescription);
+	GlobalLobbyMatchCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & matchDescription);
 };

+ 9 - 1
client/globalLobby/GlobalLobbyWindow.cpp

@@ -113,12 +113,20 @@ void GlobalLobbyWindow::onActiveAccounts(const std::vector<GlobalLobbyAccount> &
 
 void GlobalLobbyWindow::onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms)
 {
-	if (rooms.size() == widget->getAccountList()->size())
+	if (rooms.size() == widget->getRoomList()->size())
 		widget->getRoomList()->reset();
 	else
 		widget->getRoomList()->resize(rooms.size());
 }
 
+void GlobalLobbyWindow::onMatchesHistory(const std::vector<GlobalLobbyRoom> & history)
+{
+	if (history.size() == widget->getMatchList()->size())
+		widget->getMatchList()->reset();
+	else
+		widget->getMatchList()->resize(history.size());
+}
+
 void GlobalLobbyWindow::onJoinedRoom()
 {
 	widget->getAccountList()->reset();

+ 1 - 0
client/globalLobby/GlobalLobbyWindow.h

@@ -39,6 +39,7 @@ public:
 	void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when, const std::string & channelType, const std::string & channelName);
 	void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts);
 	void onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms);
+	void onMatchesHistory(const std::vector<GlobalLobbyRoom> & history);
 	void onJoinedRoom();
 	void onLeftRoom();
 };

+ 22 - 4
config/schemas/lobbyProtocol/activeGameRooms.json

@@ -20,7 +20,7 @@
 			{
 				"type" : "object",
 				"additionalProperties" : false,
-				"required" : [ "gameRoomID", "hostAccountID", "hostAccountDisplayName", "description", "playersCount", "playerLimit", "status" ],
+				"required" : [ "gameRoomID", "hostAccountID", "hostAccountDisplayName", "description", "participants", "playerLimit", "status" ],
 				"properties" : {
 					"gameRoomID" :
 					{
@@ -42,10 +42,28 @@
 						"type" : "string",
 						"description" : "Auto-generated description of this room"
 					},
-					"playersCount" :
+					"participants" :
 					{
-						"type" : "number",
-						"description" : "Current number of players in this room, including host"
+						"type" : "array",
+						"description" : "List of accounts in the room, including host",
+						"items" :
+						{
+							"type" : "object",
+							"additionalProperties" : false,
+							"required" : [ "accountID", "displayName" ],
+							"properties" : {
+								"accountID" :
+								{
+									"type" : "string",
+									"description" : "Unique ID of an account"
+								},
+								"displayName" :
+								{
+									"type" : "string",
+									"description" : "Display name of an account"
+								}
+							}
+						}
 					},
 					"status" :
 					{

+ 5 - 5
config/widgets/lobbyWindow.json

@@ -53,7 +53,7 @@
 		{
 			"type": "labelTitle",
 			"position": {"x": 15, "y": 53},
-			"text" : "Room List"
+			"text" : "vcmi.lobby.header.rooms"
 		},
 		{
 			"type" : "lobbyItemList",
@@ -73,7 +73,7 @@
 		{
 			"type": "labelTitle",
 			"position": {"x": 280, "y": 53},
-			"text" : "Channel List"
+			"text" : "vcmi.lobby.header.channels"
 		},
 		{
 			"type" : "lobbyItemList",
@@ -91,7 +91,7 @@
 		{
 			"type": "labelTitle",
 			"position": {"x": 280, "y": 213},
-			"text" : "Games History"
+			"text" : "vcmi.lobby.header.history"
 		},
 		{
 			"type" : "lobbyItemList",
@@ -111,7 +111,7 @@
 		{
 			"type": "labelTitle",
 			"position": {"x": 440, "y": 53},
-			"text" : "Game Chat"
+			"text" : "vcmi.lobby.header.chat"
 		},
 		{
 			"type": "textBox",
@@ -141,7 +141,7 @@
 		{
 			"type": "labelTitle",
 			"position": {"x": 880, "y": 53},
-			"text" : "Account List"
+			"text" : "vcmi.lobby.header.players"
 		},
 		{
 			"type" : "lobbyItemList",

+ 46 - 4
lobby/LobbyDatabase.cpp

@@ -89,6 +89,7 @@ void LobbyDatabase::clearOldData()
 		WHERE online <> 0
 	)";
 
+	//FIXME: set different status for rooms that never reached in game state
 	static const std::string removeActiveRooms = R"(
 		UPDATE gameRooms
 		SET status = 5
@@ -201,6 +202,14 @@ void LobbyDatabase::prepareStatements()
 		WHERE roomID = ?
 	)";
 
+	static const std::string getAccountGameHistoryText = R"(
+		SELECT gr.roomID, hostAccountID, displayName, description, status, playerLimit
+		FROM gameRoomPlayers grp
+		LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID
+		LEFT JOIN accounts a ON gr.hostAccountID = a.accountID
+		WHERE grp.accountID = ? AND status IN (4,5)
+	)";
+
 	static const std::string getAccountGameRoomText = R"(
 		SELECT grp.roomID
 		FROM gameRoomPlayers grp
@@ -223,8 +232,9 @@ void LobbyDatabase::prepareStatements()
 	)";
 
 	static const std::string countRoomUsedSlotsText = R"(
-		SELECT COUNT(accountID)
-		FROM gameRoomPlayers
+		SELECT a.accountID, a.displayName
+		FROM gameRoomPlayers grp
+		LEFT JOIN accounts a ON a.accountID = grp.accountID
 		WHERE roomID = ?
 	)";
 
@@ -298,6 +308,7 @@ void LobbyDatabase::prepareStatements()
 	getFullMessageHistoryStatement = database->prepare(getFullMessageHistoryText);
 	getIdleGameRoomStatement = database->prepare(getIdleGameRoomText);
 	getGameRoomStatusStatement = database->prepare(getGameRoomStatusText);
+	getAccountGameHistoryStatement = database->prepare(getAccountGameHistoryText);
 	getAccountGameRoomStatement = database->prepare(getAccountGameRoomText);
 	getActiveAccountsStatement = database->prepare(getActiveAccountsText);
 	getActiveGameRoomsStatement = database->prepare(getActiveGameRoomsText);
@@ -545,8 +556,39 @@ std::vector<LobbyGameRoom> LobbyDatabase::getActiveGameRooms()
 	for (auto & room : result)
 	{
 		countRoomUsedSlotsStatement->setBinds(room.roomID);
-		if(countRoomUsedSlotsStatement->execute())
-			countRoomUsedSlotsStatement->getColumns(room.playersCount);
+		while(countRoomUsedSlotsStatement->execute())
+		{
+			LobbyAccount account;
+			countRoomUsedSlotsStatement->getColumns(account.accountID, account.displayName);
+			room.participants.push_back(account);
+		}
+		countRoomUsedSlotsStatement->reset();
+	}
+	return result;
+}
+
+std::vector<LobbyGameRoom> LobbyDatabase::getAccountGameHistory(const std::string & accountID)
+{
+	std::vector<LobbyGameRoom> result;
+
+	getAccountGameHistoryStatement->setBinds(accountID);
+	while(getAccountGameHistoryStatement->execute())
+	{
+		LobbyGameRoom entry;
+		getAccountGameHistoryStatement->getColumns(entry.roomID, entry.hostAccountID, entry.hostAccountDisplayName, entry.description, entry.roomState, entry.playerLimit);
+		result.push_back(entry);
+	}
+	getAccountGameHistoryStatement->reset();
+
+	for (auto & room : result)
+	{
+		countRoomUsedSlotsStatement->setBinds(room.roomID);
+		while(countRoomUsedSlotsStatement->execute())
+		{
+			LobbyAccount account;
+			countRoomUsedSlotsStatement->getColumns(account.accountID, account.displayName);
+			room.participants.push_back(account);
+		}
 		countRoomUsedSlotsStatement->reset();
 	}
 	return result;

+ 2 - 0
lobby/LobbyDatabase.h

@@ -41,6 +41,7 @@ class LobbyDatabase
 	SQLiteStatementPtr getFullMessageHistoryStatement;
 	SQLiteStatementPtr getIdleGameRoomStatement;
 	SQLiteStatementPtr getGameRoomStatusStatement;
+	SQLiteStatementPtr getAccountGameHistoryStatement;
 	SQLiteStatementPtr getActiveGameRoomsStatement;
 	SQLiteStatementPtr getActiveAccountsStatement;
 	SQLiteStatementPtr getAccountGameRoomStatement;
@@ -81,6 +82,7 @@ public:
 	void updateRoomPlayerLimit(const std::string & gameRoomID, int playerLimit);
 	void updateRoomDescription(const std::string & gameRoomID, const std::string & description);
 
+	std::vector<LobbyGameRoom> getAccountGameHistory(const std::string & accountID);
 	std::vector<LobbyGameRoom> getActiveGameRooms();
 	std::vector<LobbyAccount> getActiveAccounts();
 	std::vector<LobbyChatMessage> getRecentMessageHistory(const std::string & channelType, const std::string & channelName);

+ 1 - 1
lobby/LobbyDefines.h

@@ -44,8 +44,8 @@ struct LobbyGameRoom
 	std::string hostAccountID;
 	std::string hostAccountDisplayName;
 	std::string description;
+	std::vector<LobbyAccount> participants;
 	LobbyRoomState roomState;
-	uint32_t playersCount;
 	uint32_t playerLimit;
 };
 

+ 57 - 21
lobby/LobbyServer.cpp

@@ -167,14 +167,53 @@ void LobbyServer::broadcastActiveAccounts()
 		sendMessage(connection.first, reply);
 }
 
-static constexpr std::array LOBBY_ROOM_STATE_NAMES = {
-	"idle",
-	"public",
-	"private",
-	"busy",
-	"cancelled",
-	"closed"
-};
+static JsonNode loadLobbyAccountToJson(const LobbyAccount & account)
+{
+	JsonNode jsonEntry;
+	jsonEntry["accountID"].String() = account.accountID;
+	jsonEntry["displayName"].String() = account.displayName;
+	return jsonEntry;
+}
+
+static JsonNode loadLobbyGameRoomToJson(const LobbyGameRoom & gameRoom)
+{
+	static constexpr std::array LOBBY_ROOM_STATE_NAMES = {
+		"idle",
+		"public",
+		"private",
+		"busy",
+		"cancelled",
+		"closed"
+	};
+
+	JsonNode jsonEntry;
+	jsonEntry["gameRoomID"].String() = gameRoom.roomID;
+	jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
+	jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
+	jsonEntry["description"].String() = gameRoom.description;
+	jsonEntry["status"].String() = LOBBY_ROOM_STATE_NAMES[vstd::to_underlying(gameRoom.roomState)];
+	jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit;
+
+	for (auto const & account : gameRoom.participants)
+		jsonEntry["participants"].Vector().push_back(loadLobbyAccountToJson(account));
+
+	return jsonEntry;
+}
+
+void LobbyServer::sendMatchesHistory(const NetworkConnectionPtr & target)
+{
+	std::string accountID = activeAccounts.at(target);
+
+	auto matchesHistory = database->getAccountGameHistory(accountID);
+	JsonNode reply;
+	reply["type"].String() = "matchesHistory";
+	reply["matchesHistory"].Vector(); // force creation of empty vector
+
+	for(const auto & gameRoom : matchesHistory)
+		reply["matchesHistory"].Vector().push_back(loadLobbyGameRoomToJson(gameRoom));
+
+	sendMessage(target, reply);
+}
 
 JsonNode LobbyServer::prepareActiveGameRooms()
 {
@@ -184,17 +223,7 @@ JsonNode LobbyServer::prepareActiveGameRooms()
 	reply["gameRooms"].Vector(); // force creation of empty vector
 
 	for(const auto & gameRoom : activeGameRoomStats)
-	{
-		JsonNode jsonEntry;
-		jsonEntry["gameRoomID"].String() = gameRoom.roomID;
-		jsonEntry["hostAccountID"].String() = gameRoom.hostAccountID;
-		jsonEntry["hostAccountDisplayName"].String() = gameRoom.hostAccountDisplayName;
-		jsonEntry["description"].String() = gameRoom.description;
-		jsonEntry["status"].String() = LOBBY_ROOM_STATE_NAMES[vstd::to_underlying(gameRoom.roomState)];
-		jsonEntry["playersCount"].Integer() = gameRoom.playersCount;
-		jsonEntry["playerLimit"].Integer() = gameRoom.playerLimit;
-		reply["gameRooms"].Vector().push_back(jsonEntry);
-	}
+		reply["gameRooms"].Vector().push_back(loadLobbyGameRoomToJson(gameRoom));
 
 	return reply;
 }
@@ -252,7 +281,12 @@ void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const
 
 	if(activeGameRooms.count(connection))
 	{
-		database->setGameRoomStatus(activeGameRooms.at(connection), LobbyRoomState::CLOSED);
+		std::string gameRoomID = activeGameRooms.at(connection);
+		database->setGameRoomStatus(gameRoomID, LobbyRoomState::CLOSED);
+
+		for(const auto & connection : activeAccounts)
+			if (database->isPlayerInGameRoom(connection.second, gameRoomID))
+				sendMatchesHistory(connection.first);
 		activeGameRooms.erase(connection);
 	}
 
@@ -458,9 +492,10 @@ void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection
 
 		database->insertChatMessage(senderAccountID, channelType, channelName, messageText);
 
+		// TODO: Don't report match messages if room is still active - players in room will receive these messages via match server
 		for(const auto & otherConnection : activeAccounts)
 		{
-			if (database->isPlayerInGameRoom(senderAccountID, otherConnection.second))
+			if (database->isPlayerInGameRoom(otherConnection.second, channelName))
 				sendChatMessage(otherConnection.first, channelType, channelName, senderAccountID, displayName, messageText);
 		}
 	}
@@ -536,6 +571,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co
 	// and update acount list to everybody else including new account
 	broadcastActiveAccounts();
 	sendMessage(connection, prepareActiveGameRooms());
+	sendMatchesHistory(connection);
 }
 
 void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json)

+ 1 - 0
lobby/LobbyServer.h

@@ -76,6 +76,7 @@ class LobbyServer final : public INetworkServerListener
 	void sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID);
 	void sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID, bool proxyMode);
 	void sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID);
+	void sendMatchesHistory(const NetworkConnectionPtr & target);
 
 	void receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json);
 	void receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json);