Просмотр исходного кода

Implemented per-language, player-player and (untested) match channels.

Ivan Savenko 1 год назад
Родитель
Сommit
df5af589ae

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

@@ -78,11 +78,8 @@
 	"vcmi.lobby.login.error" : "Connection error: %s",
 	"vcmi.lobby.login.create" : "New Account",
 	"vcmi.lobby.login.login" : "Login",
-
 	"vcmi.lobby.room.create" : "Create New Room",
 	"vcmi.lobby.room.players.limit" : "Players Limit",
-	"vcmi.lobby.room.public" : "Public",
-	"vcmi.lobby.room.private" : "Private",
 	"vcmi.lobby.room.description.public" : "Any player can join public room.",
 	"vcmi.lobby.room.description.private" : "Only invited players can join private room.",
 	"vcmi.lobby.room.description.new" : "To start the game, select a scenario or set up a random map.",

+ 71 - 12
client/globalLobby/GlobalLobbyClient.cpp

@@ -27,7 +27,13 @@
 #include "../../lib/TextOperations.h"
 #include "../../lib/CGeneralTextHandler.h"
 
-GlobalLobbyClient::GlobalLobbyClient() = default;
+GlobalLobbyClient::GlobalLobbyClient()
+{
+	activeChannels.emplace_back("english");
+	if (CGI->generaltexth->getPreferredLanguage() != "english")
+		activeChannels.emplace_back(CGI->generaltexth->getPreferredLanguage());
+}
+
 GlobalLobbyClient::~GlobalLobbyClient() = default;
 
 static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0)
@@ -126,29 +132,50 @@ void GlobalLobbyClient::receiveClientLoginSuccess(const JsonNode & json)
 
 void GlobalLobbyClient::receiveChatHistory(const JsonNode & json)
 {
+	std::string channelType = json["channelType"].String();
+	std::string channelName = json["channelName"].String();
+	std::string channelKey = channelType + '_' + channelName;
+
+	// create empty entry, potentially replacing any pre-existing data
+	chatHistory[channelKey] = {};
+
+	auto lobbyWindowPtr = lobbyWindow.lock();
+
 	for(const auto & entry : json["messages"].Vector())
 	{
-		std::string accountID = entry["accountID"].String();
-		std::string displayName = entry["displayName"].String();
-		std::string messageText = entry["messageText"].String();
+		GlobalLobbyChannelMessage message;
+
+		message.accountID = entry["accountID"].String();
+		message.displayName = entry["displayName"].String();
+		message.messageText = entry["messageText"].String();
 		int ageSeconds = entry["ageSeconds"].Integer();
-		std::string timeFormatted = getCurrentTimeFormatted(-ageSeconds);
+		message.timeFormatted = getCurrentTimeFormatted(-ageSeconds);
+
+		chatHistory[channelKey].push_back(message);
 
-		auto lobbyWindowPtr = lobbyWindow.lock();
 		if(lobbyWindowPtr)
-			lobbyWindowPtr->onGameChatMessage(displayName, messageText, timeFormatted);
+			lobbyWindowPtr->onGameChatMessage(message.displayName, message.messageText, message.timeFormatted, channelType, channelName);
 	}
 }
 
 void GlobalLobbyClient::receiveChatMessage(const JsonNode & json)
 {
-	std::string accountID = json["accountID"].String();
-	std::string displayName = json["displayName"].String();
-	std::string messageText = json["messageText"].String();
-	std::string timeFormatted = getCurrentTimeFormatted();
+	GlobalLobbyChannelMessage message;
+
+	message.accountID = json["accountID"].String();
+	message.displayName = json["displayName"].String();
+	message.messageText = json["messageText"].String();
+	message.timeFormatted = getCurrentTimeFormatted();
+
+	std::string channelType = json["channelType"].String();
+	std::string channelName = json["channelName"].String();
+	std::string channelKey = channelType + '_' + channelName;
+
+	chatHistory[channelKey].push_back(message);
+
 	auto lobbyWindowPtr = lobbyWindow.lock();
 	if(lobbyWindowPtr)
-		lobbyWindowPtr->onGameChatMessage(displayName, messageText, timeFormatted);
+		lobbyWindowPtr->onGameChatMessage(message.displayName, message.messageText, message.timeFormatted, channelType, channelName);
 }
 
 void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json)
@@ -341,6 +368,38 @@ const std::vector<GlobalLobbyRoom> & GlobalLobbyClient::getActiveRooms() const
 	return activeRooms;
 }
 
+const std::vector<std::string> & GlobalLobbyClient::getActiveChannels() const
+{
+	return activeChannels;
+}
+
+const std::vector<GlobalLobbyHistoryMatch> & GlobalLobbyClient::getMatchesHistory() const
+{
+	return matchesHistory;
+}
+
+const std::vector<GlobalLobbyChannelMessage> & GlobalLobbyClient::getChannelHistory(const std::string & channelType, const std::string & channelName) const
+{
+	static const std::vector<GlobalLobbyChannelMessage> emptyVector;
+
+	std::string keyToTest = channelType + '_' + channelName;
+
+	if (chatHistory.count(keyToTest) == 0)
+	{
+		if (channelType != "global")
+		{
+			JsonNode toSend;
+			toSend["type"].String() = "requestChatHistory";
+			toSend["channelType"].String() = channelType;
+			toSend["channelName"].String() = channelName;
+			CSH->getGlobalLobby().sendMessage(toSend);
+		}
+		return emptyVector;
+	}
+	else
+		return chatHistory.at(keyToTest);
+}
+
 void GlobalLobbyClient::activateInterface()
 {
 	if (GH.windows().topWindow<GlobalLobbyWindow>() != nullptr)

+ 10 - 0
client/globalLobby/GlobalLobbyClient.h

@@ -23,6 +23,13 @@ class GlobalLobbyClient final : public INetworkClientListener, boost::noncopyabl
 {
 	std::vector<GlobalLobbyAccount> activeAccounts;
 	std::vector<GlobalLobbyRoom> activeRooms;
+	std::vector<std::string> activeChannels;
+	std::vector<GlobalLobbyHistoryMatch> matchesHistory;
+
+	/// Contains known history of each channel
+	/// Key: concatenated channel type and channel name
+	/// Value: list of known chat messages
+	std::map<std::string, std::vector<GlobalLobbyChannelMessage>> chatHistory;
 
 	std::shared_ptr<INetworkConnection> networkConnection;
 
@@ -54,6 +61,9 @@ 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<GlobalLobbyChannelMessage> & getChannelHistory(const std::string & channelType, const std::string & channelName) const;
 
 	/// Activate interface and pushes lobby UI as top window
 	void activateInterface();

+ 16 - 1
client/globalLobby/GlobalLobbyDefines.h

@@ -1,5 +1,5 @@
 /*
- * GlobalLobbyClient.h, part of VCMI engine
+ * GlobalLobbyDefines.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -26,3 +26,18 @@ struct GlobalLobbyRoom
 	int playersCount;
 	int playerLimit;
 };
+
+struct GlobalLobbyChannelMessage
+{
+	std::string timeFormatted;
+	std::string accountID;
+	std::string displayName;
+	std::string messageText;
+};
+
+struct GlobalLobbyHistoryMatch
+{
+	std::string gameRoomID;
+	std::string startDateFormatted;
+	std::vector<std::string> opponentDisplayNames;
+};

+ 2 - 2
client/globalLobby/GlobalLobbyServerSetup.cpp

@@ -50,8 +50,8 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup()
 
 	auto buttonPublic  = std::make_shared<CToggleButton>(Point(10, 120),  AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
 	auto buttonPrivate = std::make_shared<CToggleButton>(Point(146, 120), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
-	buttonPublic->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.public"), EFonts::FONT_SMALL, Colors::YELLOW);
-	buttonPrivate->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.private"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonPublic->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.state.public"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonPrivate->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.state.private"), EFonts::FONT_SMALL, Colors::YELLOW);
 
 	toggleRoomType = std::make_shared<CToggleGroup>(nullptr);
 	toggleRoomType->addToggle(0, buttonPublic);

+ 104 - 28
client/globalLobby/GlobalLobbyWidget.cpp

@@ -24,6 +24,7 @@
 #include "../widgets/ObjectLists.h"
 #include "../widgets/TextControls.h"
 
+#include "../../lib/Languages.h"
 #include "../../lib/MetaString.h"
 
 GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window)
@@ -31,18 +32,17 @@ GlobalLobbyWidget::GlobalLobbyWidget(GlobalLobbyWindow * window)
 {
 	addCallback("closeWindow", [](int) { GH.windows().popWindows(1); });
 	addCallback("sendMessage", [this](int) { this->window->doSendChatMessage(); });
-	addCallback("createGameRoom", [this](int) { if (!CSH->inGame()) this->window->doCreateGameRoom(); else GH.windows().popWindows(1); });
+	addCallback("createGameRoom", [this](int) { if (!CSH->inGame()) this->window->doCreateGameRoom(); });//TODO: button should be blocked instead
 
-	REGISTER_BUILDER("accountList", &GlobalLobbyWidget::buildAccountList);
-	REGISTER_BUILDER("roomList", &GlobalLobbyWidget::buildRoomList);
+	REGISTER_BUILDER("lobbyItemList", &GlobalLobbyWidget::buildItemList);
 
 	const JsonNode config(JsonPath::builtin("config/widgets/lobbyWindow.json"));
 	build(config);
 }
 
-std::shared_ptr<CIntObject> GlobalLobbyWidget::buildAccountList(const JsonNode & config) const
+GlobalLobbyWidget::CreateFunc GlobalLobbyWidget::getItemListConstructorFunc(const std::string & callbackName) const
 {
-	const auto & createCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
+	const auto & createAccountCardCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
 	{
 		const auto & accounts = CSH->getGlobalLobby().getActiveAccounts();
 
@@ -51,21 +51,7 @@ std::shared_ptr<CIntObject> GlobalLobbyWidget::buildAccountList(const JsonNode &
 		return std::make_shared<CIntObject>();
 	};
 
-	auto position = readPosition(config["position"]);
-	auto itemOffset = readPosition(config["itemOffset"]);
-	auto sliderPosition = readPosition(config["sliderPosition"]);
-	auto sliderSize = readPosition(config["sliderSize"]);
-	size_t visibleAmount = config["visibleAmount"].Integer();
-	size_t totalAmount = 0; // Will be set later, on server netpack
-	int sliderMode = 1 | 4; //  present, vertical, blue
-	int initialPos = 0;
-
-	return std::make_shared<CListBox>(createCallback, position, itemOffset, visibleAmount, totalAmount, initialPos, sliderMode, Rect(sliderPosition, sliderSize));
-}
-
-std::shared_ptr<CIntObject> GlobalLobbyWidget::buildRoomList(const JsonNode & config) const
-{
-	const auto & createCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
+	const auto & createRoomCardCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
 	{
 		const auto & rooms = CSH->getGlobalLobby().getActiveRooms();
 
@@ -74,16 +60,52 @@ std::shared_ptr<CIntObject> GlobalLobbyWidget::buildRoomList(const JsonNode & co
 		return std::make_shared<CIntObject>();
 	};
 
+	const auto & createChannelCardCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
+	{
+		const auto & channels = CSH->getGlobalLobby().getActiveChannels();
+
+		if(index < channels.size())
+			return std::make_shared<GlobalLobbyChannelCard>(this->window, channels[index]);
+		return std::make_shared<CIntObject>();
+	};
+
+	const auto & createMatchCardCallback = [this](size_t index) -> std::shared_ptr<CIntObject>
+	{
+		const auto & matches = CSH->getGlobalLobby().getMatchesHistory();
+
+		if(index < matches.size())
+			return std::make_shared<GlobalLobbyMatchCard>(this->window, matches[index]);
+		return std::make_shared<CIntObject>();
+	};
+
+	if (callbackName == "room")
+		return createRoomCardCallback;
+
+	if (callbackName == "account")
+		return createAccountCardCallback;
+
+	if (callbackName == "channel")
+		return createChannelCardCallback;
+
+	if (callbackName == "match")
+		return createMatchCardCallback;
+
+	throw std::runtime_error("Unknown item type in lobby widget constructor: " + callbackName);
+}
+
+std::shared_ptr<CIntObject> GlobalLobbyWidget::buildItemList(const JsonNode & config) const
+{
+	auto callback = getItemListConstructorFunc(config["itemType"].String());
 	auto position = readPosition(config["position"]);
 	auto itemOffset = readPosition(config["itemOffset"]);
 	auto sliderPosition = readPosition(config["sliderPosition"]);
 	auto sliderSize = readPosition(config["sliderSize"]);
 	size_t visibleAmount = config["visibleAmount"].Integer();
 	size_t totalAmount = 0; // Will be set later, on server netpack
-	int sliderMode = 1 | 4; //  present, vertical, blue
+	int sliderMode = config["sliderSize"].isNull() ? 0 : (1 | 4); //  present, vertical, blue
 	int initialPos = 0;
 
-	return std::make_shared<CListBox>(createCallback, position, itemOffset, visibleAmount, totalAmount, initialPos, sliderMode, Rect(sliderPosition, sliderSize));
+	return std::make_shared<CListBox>(callback, position, itemOffset, visibleAmount, totalAmount, initialPos, sliderMode, Rect(sliderPosition, sliderSize));
 }
 
 std::shared_ptr<CLabel> GlobalLobbyWidget::getAccountNameLabel()
@@ -111,7 +133,21 @@ std::shared_ptr<CListBox> GlobalLobbyWidget::getRoomList()
 	return widget<CListBox>("roomList");
 }
 
+GlobalLobbyChannelCardBase::GlobalLobbyChannelCardBase(GlobalLobbyWindow * window, const std::string & channelType, const std::string & channelName)
+	: window(window)
+	, channelType(channelType)
+	, channelName(channelName)
+{
+	addUsedEvents(LCLICK);
+}
+
+void GlobalLobbyChannelCardBase::clickPressed(const Point & cursorPosition)
+{
+	window->doOpenChannel(channelType, channelName);
+}
+
 GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription)
+	: GlobalLobbyChannelCardBase(window, "player", accountDescription.accountID)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
@@ -119,8 +155,8 @@ GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const
 	pos.h = 40;
 
 	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64));
-	labelName = std::make_shared<CLabel>(5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, accountDescription.displayName);
-	labelStatus = std::make_shared<CLabel>(5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, accountDescription.status);
+	labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
+	labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
 }
 
 GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription)
@@ -142,10 +178,10 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
 	pos.h = 40;
 
 	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64));
-	labelName = std::make_shared<CLabel>(5, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName);
-	labelDescription = std::make_shared<CLabel>(5, 20, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, roomDescription.description);
-	labelRoomSize = std::make_shared<CLabel>(178, 2, FONT_SMALL, ETextAlignment::TOPRIGHT, Colors::YELLOW, roomSizeText.toString());
-	labelRoomStatus = std::make_shared<CLabel>(190, 20, FONT_SMALL, ETextAlignment::TOPRIGHT, Colors::YELLOW, roomStatusText.toString());
+	labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, roomDescription.hostAccountDisplayName);
+	labelDescription = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, roomDescription.description);
+	labelRoomSize = std::make_shared<CLabel>(178, 10, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomSizeText.toString());
+	labelRoomStatus = std::make_shared<CLabel>(190, 30, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::YELLOW, roomStatusText.toString());
 	iconRoomSize = std::make_shared<CPicture>(ImagePath::builtin("lobby/iconPlayer"), Point(180, 5));
 
 	if(!CSH->inGame() && roomDescription.statusID == "public")
@@ -154,3 +190,43 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
 		buttonJoin->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/iconEnter")));
 	}
 }
+
+GlobalLobbyChannelCard::GlobalLobbyChannelCard(GlobalLobbyWindow * window, const std::string & channelName)
+	: GlobalLobbyChannelCardBase(window, "global", channelName)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	pos.w = 146;
+	pos.h = 40;
+
+	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64));
+	labelName = std::make_shared<CLabel>(5, 20, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, Languages::getLanguageOptions(channelName).nameNative);
+}
+
+GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const GlobalLobbyHistoryMatch & matchDescription)
+	: GlobalLobbyChannelCardBase(window, "match", matchDescription.gameRoomID)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	pos.w = 130;
+	pos.h = 40;
+
+	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64));
+	labelMatchDate = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, matchDescription.startDateFormatted);
+
+	MetaString opponentDescription;
+
+	if (matchDescription.opponentDisplayNames.empty())
+		opponentDescription.appendRawString("Solo game"); // or "Singleplayer game" is better?
+
+	if (matchDescription.opponentDisplayNames.size() == 1)
+		opponentDescription.appendRawString(matchDescription.opponentDisplayNames[0]);
+
+	if (matchDescription.opponentDisplayNames.size() > 1)
+	{
+		opponentDescription.appendRawString("%d players");
+		opponentDescription.replaceNumber(matchDescription.opponentDisplayNames.size());
+	}
+
+	labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString());
+}

+ 42 - 7
client/globalLobby/GlobalLobbyWidget.h

@@ -14,14 +14,17 @@
 class GlobalLobbyWindow;
 struct GlobalLobbyAccount;
 struct GlobalLobbyRoom;
+struct GlobalLobbyHistoryMatch;
 class CListBox;
 
 class GlobalLobbyWidget : public InterfaceObjectConfigurable
 {
 	GlobalLobbyWindow * window;
 
-	std::shared_ptr<CIntObject> buildAccountList(const JsonNode &) const;
-	std::shared_ptr<CIntObject> buildRoomList(const JsonNode &) const;
+	using CreateFunc = std::function<std::shared_ptr<CIntObject>(size_t)>;
+
+	std::shared_ptr<CIntObject> buildItemList(const JsonNode &) const;
+	CreateFunc getItemListConstructorFunc(const std::string & callbackName) const;
 
 public:
 	explicit GlobalLobbyWidget(GlobalLobbyWindow * window);
@@ -31,24 +34,34 @@ public:
 	std::shared_ptr<CTextBox> getGameChat();
 	std::shared_ptr<CListBox> getAccountList();
 	std::shared_ptr<CListBox> getRoomList();
+	std::shared_ptr<CListBox> getChannelList();
+	std::shared_ptr<CListBox> getMatchList();
 };
 
-class GlobalLobbyAccountCard : public CIntObject
+class GlobalLobbyChannelCardBase : public CIntObject
 {
+	GlobalLobbyWindow * window;
+	std::string channelType;
+	std::string channelName;
+
+	void clickPressed(const Point & cursorPosition) override;
 public:
-	GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription);
+	GlobalLobbyChannelCardBase(GlobalLobbyWindow * window, const std::string & channelType, const std::string & channelName);
+};
 
+class GlobalLobbyAccountCard : public GlobalLobbyChannelCardBase
+{
 	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
 	std::shared_ptr<CLabel> labelName;
 	std::shared_ptr<CLabel> labelStatus;
 	std::shared_ptr<CButton> buttonInvite;
+
+public:
+	GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription);
 };
 
 class GlobalLobbyRoomCard : public CIntObject
 {
-public:
-	GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription);
-
 	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
 	std::shared_ptr<CLabel> labelName;
 	std::shared_ptr<CLabel> labelRoomSize;
@@ -56,4 +69,26 @@ public:
 	std::shared_ptr<CLabel> labelDescription;
 	std::shared_ptr<CButton> buttonJoin;
 	std::shared_ptr<CPicture> iconRoomSize;
+
+public:
+	GlobalLobbyRoomCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & roomDescription);
+};
+
+class GlobalLobbyChannelCard : public GlobalLobbyChannelCardBase
+{
+	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
+	std::shared_ptr<CLabel> labelName;
+
+public:
+	GlobalLobbyChannelCard(GlobalLobbyWindow * window, const std::string & channelName);
+};
+
+class GlobalLobbyMatchCard : public GlobalLobbyChannelCardBase
+{
+	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
+	std::shared_ptr<CLabel> labelMatchDate;
+	std::shared_ptr<CLabel> labelMatchOpponent;
+
+public:
+	GlobalLobbyMatchCard(GlobalLobbyWindow * window, const GlobalLobbyHistoryMatch & matchDescription);
 };

+ 20 - 1
client/globalLobby/GlobalLobbyWindow.cpp

@@ -33,6 +33,20 @@ GlobalLobbyWindow::GlobalLobbyWindow()
 	center();
 
 	widget->getAccountNameLabel()->setText(settings["lobby"]["displayName"].String());
+	doOpenChannel("global", "english");
+}
+
+void GlobalLobbyWindow::doOpenChannel(const std::string & channelType, const std::string & channelName)
+{
+	currentChannelType = channelType;
+	currentChannelName = channelName;
+	chatHistory.clear();
+	widget->getGameChat()->setText("");
+
+	auto history = CSH->getGlobalLobby().getChannelHistory(channelType, channelName);
+
+	for (auto const & entry : history)
+		onGameChatMessage(entry.displayName, entry.messageText, entry.timeFormatted, channelType, channelName);
 }
 
 void GlobalLobbyWindow::doSendChatMessage()
@@ -41,6 +55,8 @@ void GlobalLobbyWindow::doSendChatMessage()
 
 	JsonNode toSend;
 	toSend["type"].String() = "sendChatMessage";
+	toSend["channelType"].String() = currentChannelType;
+	toSend["channelName"].String() = currentChannelName;
 	toSend["messageText"].String() = messageText;
 
 	CSH->getGlobalLobby().sendMessage(toSend);
@@ -71,8 +87,11 @@ void GlobalLobbyWindow::doJoinRoom(const std::string & roomID)
 	CSH->getGlobalLobby().sendMessage(toSend);
 }
 
-void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when)
+void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when, const std::string & channelType, const std::string & channelName)
 {
+	if (channelType != currentChannelType || channelName != currentChannelName)
+		return; // TODO: send ping to player that another channel got a new message
+
 	MetaString chatMessageFormatted;
 	chatMessageFormatted.appendRawString("[%s] {%s}: %s\n");
 	chatMessageFormatted.replaceRawString(when);

+ 8 - 2
client/globalLobby/GlobalLobbyWindow.h

@@ -18,19 +18,25 @@ struct GlobalLobbyRoom;
 class GlobalLobbyWindow : public CWindowObject
 {
 	std::string chatHistory;
+	std::string currentChannelType;
+	std::string currentChannelName;
 
 	std::shared_ptr<GlobalLobbyWidget> widget;
 
 public:
 	GlobalLobbyWindow();
 
+	// Callbacks for UI
+
 	void doSendChatMessage();
 	void doCreateGameRoom();
-
+	void doOpenChannel(const std::string & channelType, const std::string & channelName);
 	void doInviteAccount(const std::string & accountID);
 	void doJoinRoom(const std::string & roomID);
 
-	void onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when);
+	// Callbacks for network packs
+
+	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 onJoinedRoom();

+ 14 - 1
config/schemas/lobbyProtocol/chatHistory.json

@@ -3,7 +3,7 @@
 	"$schema" : "http://json-schema.org/draft-06/schema",
 	"title" : "Lobby protocol: chatHistory",
 	"description" : "Sent by server immediately after login to fill initial chat history",
-	"required" : [ "type", "messages" ],
+	"required" : [ "type", "messages", "channelType", "channelName" ],
 	"additionalProperties" : false,
 
 	"properties" : {
@@ -12,6 +12,19 @@
 			"type" : "string",
 			"const" : "chatHistory"
 		},
+
+		"channelType" :
+		{
+			"type" : "string",
+			"enum" : [ "global", "match", "player" ],
+			"description" : "Type of room to which these messages have been set."
+		},
+		"channelName" :
+		{
+			"type" : "string",
+			"description" : "Name of room to which these messages have been set. For 'global' this is language, for 'match' and 'player' this is receiver UUID"
+		},
+
 		"messages" :
 		{
 			"type" : "array",

+ 6 - 7
config/schemas/lobbyProtocol/chatMessage.json

@@ -3,7 +3,7 @@
 	"$schema" : "http://json-schema.org/draft-06/schema",
 	"title" : "Lobby protocol: chatMessage",
 	"description" : "Sent by server to all players in lobby whenever new message is sent to game chat",
-	"required" : [ "type", "messageText", "accountID", "displayName", "roomMode", "roomName" ],
+	"required" : [ "type", "messageText", "accountID", "displayName", "channelType", "channelName" ],
 	"additionalProperties" : false,
 
 	"properties" : {
@@ -27,17 +27,16 @@
 			"type" : "string",
 			"description" : "Display name of account that have sent this message"
 		},
-		"roomMode" :
+		"channelType" :
 		{
 			"type" : "string",
-			"const" : "global",
-			"description" : "Type of room to which this message has been set. Right now can only be 'general'"
+			"enum" : [ "global", "match", "player" ],
+			"description" : "Type of room to which this message has been set."
 		},
-		"roomName" :
+		"channelName" :
 		{
 			"type" : "string",
-			"const" : "english",
-			"description" : "Name of room to which this message has been set. Right now only 'english' is used"
+			"description" : "Name of room to which this message has been set. For 'global' this is language, for 'match' and 'player' this is receiver UUID"
 		}
 	}
 }

+ 0 - 21
config/schemas/lobbyProtocol/declineInvite.json

@@ -1,21 +0,0 @@
-{
-	"type" : "object",
-	"$schema" : "http://json-schema.org/draft-06/schema",
-	"title" : "Lobby protocol: declineInvite",
-	"description" : "Sent by client when player declines invite to join a room",
-	"required" : [ "type", "gameRoomID" ],
-	"additionalProperties" : false,
-
-	"properties" : {
-		"type" :
-		{
-			"type" : "string",
-			"const" : "declineInvite"
-		},
-		"gameRoomID" :
-		{
-			"type" : "string",
-			"description" : "ID of game room to decline invite"
-		}
-	}
-}

+ 13 - 2
config/schemas/lobbyProtocol/sendChatMessage.json

@@ -2,8 +2,8 @@
 	"type" : "object",
 	"$schema" : "http://json-schema.org/draft-06/schema",
 	"title" : "Lobby protocol: sendChatMessage",
-	"description" : "Sent by client when player requests lobby login",
-	"required" : [ "type", "messageText" ],
+	"description" : "Sent by client when player enters a chat message",
+	"required" : [ "type", "messageText", "channelType", "channelName" ],
 	"additionalProperties" : false,
 
 	"properties" : {
@@ -16,6 +16,17 @@
 		{
 			"type" : "string",
 			"description" : "Text of sent message"
+		},
+		"channelType" :
+		{
+			"type" : "string",
+			"enum" : [ "global", "match", "player" ],
+			"description" : "Type of room to which this message has been set."
+		},
+		"channelName" :
+		{
+			"type" : "string",
+			"description" : "Name of room to which this message has been set. For 'global' this is language, for 'match' and 'player' this is receiver UUID"
 		}
 	}
 }

+ 26 - 24
config/widgets/lobbyWindow.json

@@ -56,8 +56,9 @@
 			"text" : "Room List"
 		},
 		{
-			"type" : "roomList",
+			"type" : "lobbyItemList",
 			"name" : "roomList",
+			"itemType" : "room",
 			"position" : { "x" : 7, "y" : 68 },
 			"itemOffset" : { "x" : 0, "y" : 40 },
 			"sliderPosition" : { "x" : 230, "y" : 0 },
@@ -74,17 +75,35 @@
 			"position": {"x": 280, "y": 53},
 			"text" : "Channel List"
 		},
+		{
+			"type" : "lobbyItemList",
+			"name" : "channelList",
+			"itemType" : "channel",
+			"position" : { "x" : 272, "y" : 68 },
+			"itemOffset" : { "x" : 0, "y" : 40 },
+			"visibleAmount" : 3
+		},
 		
 		{
 			"type": "areaFilled",
-			"rect": {"x": 270, "y": 200, "w": 150, "h": 390}
+			"rect": {"x": 270, "y": 210, "w": 150, "h": 380}
 		},
 		{
 			"type": "labelTitle",
-			"position": {"x": 280, "y": 203},
+			"position": {"x": 280, "y": 213},
 			"text" : "Games History"
 		},
-		
+		{
+			"type" : "lobbyItemList",
+			"name" : "matchList",
+			"itemType" : "match",
+			"position" : { "x" : 272, "y" : 228 },
+			"itemOffset" : { "x" : 0, "y" : 40 },
+			"sliderPosition" : { "x" : 130, "y" : 0 },
+			"sliderSize" : { "x" : 360, "y" : 360 },
+			"visibleAmount" : 9
+		},
+
 		{
 			"type": "areaFilled",
 			"rect": {"x": 430, "y": 50, "w": 430, "h": 515}
@@ -125,33 +144,16 @@
 			"text" : "Account List"
 		},
 		{
-			"type" : "accountList",
+			"type" : "lobbyItemList",
 			"name" : "accountList",
+			"itemType" : "account",
 			"position" : { "x" : 872, "y" : 68 },
 			"itemOffset" : { "x" : 0, "y" : 40 },
 			"sliderPosition" : { "x" : 130, "y" : 0 },
 			"sliderSize" : { "x" : 520, "y" : 520 },
-			"visibleAmount" : 12
+			"visibleAmount" : 13
 		},
 
-//		{
-//			"type": "button",
-//			"position": {"x": 840, "y": 10},
-//			"image": "settingsWindow/button80",
-//			"help": "core.help.288",
-//			"callback": "closeWindow",
-//			"items":
-//			[
-//				{
-//					"type": "label",
-//					"font": "medium",
-//					"alignment": "center",
-//					"color": "yellow",
-//					"text": "Leave"
-//				}
-//			]
-//		},
-		
 		{
 			"type": "button",
 			"position": {"x": 870, "y": 10},

+ 2 - 5
lib/Languages.h

@@ -111,8 +111,7 @@ inline const auto & getLanguageList()
 
 inline const Options & getLanguageOptions(ELanguages language)
 {
-	assert(language < ELanguages::COUNT);
-	return getLanguageList()[static_cast<size_t>(language)];
+	return getLanguageList().at(static_cast<size_t>(language));
 }
 
 inline const Options & getLanguageOptions(const std::string & language)
@@ -121,9 +120,7 @@ inline const Options & getLanguageOptions(const std::string & language)
 		if(entry.identifier == language)
 			return entry;
 
-	static const Options emptyValue;
-	assert(0);
-	return emptyValue;
+	throw std::runtime_error("Language " + language + " does not exists!");
 }
 
 template<typename Numeric>

+ 34 - 8
lobby/LobbyDatabase.cpp

@@ -18,7 +18,8 @@ void LobbyDatabase::createTables()
 		CREATE TABLE IF NOT EXISTS chatMessages (
 			id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
 			senderName TEXT,
-			roomType TEXT,
+			channelType TEXT,
+			channelName TEXT,
 			messageText TEXT,
 			creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
 		);
@@ -103,7 +104,7 @@ void LobbyDatabase::prepareStatements()
 	// INSERT INTO
 
 	static const std::string insertChatMessageText = R"(
-		INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?);
+		INSERT INTO chatMessages(senderName, messageText, channelType, channelName) VALUES( ?, ?, ?, ?);
 	)";
 
 	static const std::string insertAccountText = R"(
@@ -174,11 +175,19 @@ void LobbyDatabase::prepareStatements()
 		SELECT senderName, displayName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',cm.creationTime)  AS secondsElapsed
 		FROM chatMessages cm
 		LEFT JOIN accounts on accountID = senderName
-		WHERE secondsElapsed < 60*60*18
+		WHERE secondsElapsed < 60*60*18 AND channelType = ? AND channelName = ?
 		ORDER BY cm.creationTime DESC
 		LIMIT 100
 	)";
 
+	static const std::string getFullMessageHistoryText = R"(
+		SELECT senderName, displayName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',cm.creationTime) AS secondsElapsed
+		FROM chatMessages cm
+		LEFT JOIN accounts on accountID = senderName
+		WHERE channelType = ? AND channelName = ?
+		ORDER BY cm.creationTime DESC
+	)";
+
 	static const std::string getIdleGameRoomText = R"(
 		SELECT roomID
 		FROM gameRooms
@@ -248,7 +257,7 @@ void LobbyDatabase::prepareStatements()
 		SELECT COUNT(accountID)
 		FROM gameRoomPlayers grp
 		LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID
-		WHERE accountID = ? AND grp.roomID = ? AND status IN (1, 2, 3)
+		WHERE accountID = ? AND grp.roomID = ?
 	)";
 
 	static const std::string isPlayerInAnyGameRoomText = R"(
@@ -278,7 +287,6 @@ void LobbyDatabase::prepareStatements()
 	insertGameRoomInvitesStatement = database->prepare(insertGameRoomInvitesText);
 
 	deleteGameRoomPlayersStatement = database->prepare(deleteGameRoomPlayersText);
-	deleteGameRoomInvitesStatement = database->prepare(deleteGameRoomInvitesText);
 
 	setAccountOnlineStatement = database->prepare(setAccountOnlineText);
 	setGameRoomStatusStatement = database->prepare(setGameRoomStatusText);
@@ -287,6 +295,7 @@ void LobbyDatabase::prepareStatements()
 	updateRoomPlayerLimitStatement = database->prepare(updateRoomPlayerLimitText);
 
 	getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText);
+	getFullMessageHistoryStatement = database->prepare(getFullMessageHistoryText);
 	getIdleGameRoomStatement = database->prepare(getIdleGameRoomText);
 	getGameRoomStatusStatement = database->prepare(getGameRoomStatusText);
 	getAccountGameRoomStatement = database->prepare(getAccountGameRoomText);
@@ -313,9 +322,9 @@ LobbyDatabase::LobbyDatabase(const boost::filesystem::path & databasePath)
 	prepareStatements();
 }
 
-void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText)
+void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & channelType, const std::string & channelName, const std::string & messageText)
 {
-	insertChatMessageStatement->executeOnce(sender, messageText);
+	insertChatMessageStatement->executeOnce(sender, messageText, channelType, channelName);
 }
 
 bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID)
@@ -342,10 +351,11 @@ bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID, const std:
 	return result;
 }
 
-std::vector<LobbyChatMessage> LobbyDatabase::getRecentMessageHistory()
+std::vector<LobbyChatMessage> LobbyDatabase::getRecentMessageHistory(const std::string & channelType, const std::string & channelName)
 {
 	std::vector<LobbyChatMessage> result;
 
+	getRecentMessageHistoryStatement->setBinds(channelType, channelName);
 	while(getRecentMessageHistoryStatement->execute())
 	{
 		LobbyChatMessage message;
@@ -357,6 +367,22 @@ std::vector<LobbyChatMessage> LobbyDatabase::getRecentMessageHistory()
 	return result;
 }
 
+std::vector<LobbyChatMessage> LobbyDatabase::getFullMessageHistory(const std::string & channelType, const std::string & channelName)
+{
+	std::vector<LobbyChatMessage> result;
+
+	getFullMessageHistoryStatement->setBinds(channelType, channelName);
+	while(getFullMessageHistoryStatement->execute())
+	{
+		LobbyChatMessage message;
+		getFullMessageHistoryStatement->getColumns(message.accountID, message.displayName, message.messageText, message.age);
+		result.push_back(message);
+	}
+	getFullMessageHistoryStatement->reset();
+
+	return result;
+}
+
 void LobbyDatabase::setAccountOnline(const std::string & accountID, bool isOnline)
 {
 	setAccountOnlineStatement->executeOnce(isOnline ? 1 : 0, accountID);

+ 4 - 2
lobby/LobbyDatabase.h

@@ -38,6 +38,7 @@ class LobbyDatabase
 	SQLiteStatementPtr updateRoomPlayerLimitStatement;
 
 	SQLiteStatementPtr getRecentMessageHistoryStatement;
+	SQLiteStatementPtr getFullMessageHistoryStatement;
 	SQLiteStatementPtr getIdleGameRoomStatement;
 	SQLiteStatementPtr getGameRoomStatusStatement;
 	SQLiteStatementPtr getActiveGameRoomsStatement;
@@ -74,7 +75,7 @@ public:
 	void insertGameRoom(const std::string & roomID, const std::string & hostAccountID);
 	void insertAccount(const std::string & accountID, const std::string & displayName);
 	void insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID);
-	void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomID, const std::string & messageText);
+	void insertChatMessage(const std::string & sender, const std::string & channelType, const std::string & roomID, const std::string & messageText);
 
 	void updateAccountLoginTime(const std::string & accountID);
 	void updateRoomPlayerLimit(const std::string & gameRoomID, int playerLimit);
@@ -82,7 +83,8 @@ public:
 
 	std::vector<LobbyGameRoom> getActiveGameRooms();
 	std::vector<LobbyAccount> getActiveAccounts();
-	std::vector<LobbyChatMessage> getRecentMessageHistory();
+	std::vector<LobbyChatMessage> getRecentMessageHistory(const std::string & channelType, const std::string & channelName);
+	std::vector<LobbyChatMessage> getFullMessageHistory(const std::string & channelType, const std::string & channelName);
 
 	std::string getIdleGameRoom(const std::string & hostAccountID);
 	std::string getAccountGameRoom(const std::string & accountID);

+ 103 - 24
lobby/LobbyServer.cpp

@@ -15,6 +15,7 @@
 #include "../lib/json/JsonFormatException.h"
 #include "../lib/json/JsonNode.h"
 #include "../lib/json/JsonUtils.h"
+#include "../lib/Languages.h"
 
 #include <boost/uuid/uuid_generators.hpp>
 #include <boost/uuid/uuid_io.hpp>
@@ -109,10 +110,22 @@ void LobbyServer::sendServerLoginSuccess(const NetworkConnectionPtr & target, co
 	sendMessage(target, reply);
 }
 
-void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> & history)
+void LobbyServer::sendFullChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::string & channelNameForClient)
+{
+	sendChatHistory(target, channelType, channelNameForClient, database->getFullMessageHistory(channelType, channelName));
+}
+
+void LobbyServer::sendRecentChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName)
+{
+	sendChatHistory(target, channelType, channelName, database->getRecentMessageHistory(channelType, channelName));
+}
+
+void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::vector<LobbyChatMessage> & history)
 {
 	JsonNode reply;
 	reply["type"].String() = "chatHistory";
+	reply["channelType"].String() = channelType;
+	reply["channelName"].String() = channelName;
 	reply["messages"].Vector(); // force creation of empty vector
 
 	for(const auto & message : boost::adaptors::reverse(history))
@@ -208,15 +221,15 @@ void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const
 	sendMessage(target, reply);
 }
 
-void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, const std::string & displayName, const std::string & messageText)
+void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::string & accountID, const std::string & displayName, const std::string & messageText)
 {
 	JsonNode reply;
 	reply["type"].String() = "chatMessage";
 	reply["messageText"].String() = messageText;
 	reply["accountID"].String() = accountID;
 	reply["displayName"].String() = displayName;
-	reply["roomMode"].String() = roomMode;
-	reply["roomName"].String() = roomName;
+	reply["channelType"].String() = channelType;
+	reply["channelName"].String() = channelName;
 
 	sendMessage(target, reply);
 }
@@ -319,6 +332,9 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons
 		if(messageType == "sendChatMessage")
 			return receiveSendChatMessage(connection, json);
 
+		if(messageType == "requestChatHistory")
+			return receiveRequestChatHistory(connection, json);
+
 		if(messageType == "activateGameRoom")
 			return receiveActivateGameRoom(connection, json);
 
@@ -328,9 +344,6 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons
 		if(messageType == "sendInvite")
 			return receiveSendInvite(connection, json);
 
-		if(messageType == "declineInvite")
-			return receiveDeclineInvite(connection, json);
-
 		logGlobal->warn("%s: Unknown message type: %s", accountName, messageType);
 		return;
 	}
@@ -376,20 +389,95 @@ void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, cons
 	logGlobal->info("(unauthorised): Unknown message type %s", messageType);
 }
 
-void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
+void LobbyServer::receiveRequestChatHistory(const NetworkConnectionPtr & connection, const JsonNode & json)
 {
 	std::string accountID = activeAccounts[connection];
+	std::string channelType = json["channelType"].String();
+	std::string channelName = json["channelName"].String();
+
+	if (channelType == "global")
+	{
+		// can only be sent on connection, initiated by server
+		sendOperationFailed(connection, "Operation not supported!");
+	}
+
+	if (channelType == "match")
+	{
+		if (!database->isPlayerInGameRoom(accountID, channelName))
+			return sendOperationFailed(connection, "Can not access room you are not part of!");
+
+		sendFullChatHistory(connection, channelType, channelName, channelName);
+	}
+
+	if (channelType == "player")
+	{
+		if (!database->isAccountIDExists(channelName))
+			return sendOperationFailed(connection, "Such player does not exists!");
+
+		// room ID for private messages is actually <player 1 ID>_<player 2 ID>, with player ID's sorted alphabetically (to generate unique room ID)
+		std::string roomID = std::min(accountID, channelName) + "_" + std::max(accountID, channelName);
+		sendFullChatHistory(connection, channelType, roomID, channelName);
+	}
+}
+
+void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
+{
+	std::string senderAccountID = activeAccounts[connection];
 	std::string messageText = json["messageText"].String();
+	std::string channelType = json["channelType"].String();
+	std::string channelName = json["channelName"].String();
 	std::string messageTextClean = sanitizeChatMessage(messageText);
-	std::string displayName = database->getAccountDisplayName(accountID);
+	std::string displayName = database->getAccountDisplayName(senderAccountID);
 
 	if(messageTextClean.empty())
 		return sendOperationFailed(connection, "No printable characters in sent message!");
 
-	database->insertChatMessage(accountID, "global", "english", messageText);
+	if (channelType == "global")
+	{
+		try
+		{
+			Languages::getLanguageOptions(channelName);
+		}
+		catch (const std::runtime_error &)
+		{
+			return sendOperationFailed(connection, "Unknown language!");
+		}
+		database->insertChatMessage(senderAccountID, channelType, channelName, messageText);
+
+		for(const auto & otherConnection : activeAccounts)
+			sendChatMessage(otherConnection.first, channelType, channelName, senderAccountID, displayName, messageText);
+	}
+
+	if (channelType == "match")
+	{
+		if (!database->isPlayerInGameRoom(senderAccountID, channelName))
+			return sendOperationFailed(connection, "Can not access room you are not part of!");
+
+		database->insertChatMessage(senderAccountID, channelType, channelName, messageText);
+
+		for(const auto & otherConnection : activeAccounts)
+		{
+			if (database->isPlayerInGameRoom(senderAccountID, otherConnection.second))
+				sendChatMessage(otherConnection.first, channelType, channelName, senderAccountID, displayName, messageText);
+		}
+	}
+
+	if (channelType == "player")
+	{
+		const std::string & receiverAccountID = channelName;
+		std::string roomID = std::min(senderAccountID, receiverAccountID) + "_" + std::max(senderAccountID, receiverAccountID);
+
+		if (!database->isAccountIDExists(receiverAccountID))
+			return sendOperationFailed(connection, "Such player does not exists!");
 
-	for(const auto & otherConnection : activeAccounts)
-		sendChatMessage(otherConnection.first, "global", "english", accountID, displayName, messageText);
+		database->insertChatMessage(senderAccountID, channelType, roomID, messageText);
+
+		for(const auto & otherConnection : activeAccounts)
+		{
+			if (otherConnection.second == receiverAccountID)
+				sendChatMessage(otherConnection.first, channelType, senderAccountID, senderAccountID, displayName, messageText);
+		}
+	}
 }
 
 void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json)
@@ -435,7 +523,9 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co
 	activeAccounts[connection] = accountID;
 
 	sendClientLoginSuccess(connection, accountCookie, displayName);
-	sendChatHistory(connection, database->getRecentMessageHistory());
+	sendRecentChatHistory(connection, "global", "english");
+	if (language != "english")
+		sendRecentChatHistory(connection, "global", language);
 
 	// send active game rooms list to new account
 	// and update acount list to everybody else including new account
@@ -647,17 +737,6 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con
 	sendInviteReceived(targetAccount, senderName, gameRoomID);
 }
 
-void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
-{
-	std::string accountID = activeAccounts[connection];
-	std::string gameRoomID = json["gameRoomID"].String();
-
-	if(database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
-		return sendOperationFailed(connection, "No active invite found!");
-
-	database->deleteGameRoomInvite(accountID, gameRoomID);
-}
-
 LobbyServer::~LobbyServer() = default;
 
 LobbyServer::LobbyServer(const boost::filesystem::path & databasePath)

+ 5 - 3
lobby/LobbyServer.h

@@ -65,12 +65,14 @@ class LobbyServer final : public INetworkServerListener
 	/// Returns parsed json on success or empty json node on failure
 	JsonNode parseAndValidateMessage(const std::vector<std::byte> & message) const;
 
-	void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & accountID, const std::string & displayName, const std::string & messageText);
+	void sendChatMessage(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::string & accountID, const std::string & displayName, const std::string & messageText);
 	void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie);
 	void sendOperationFailed(const NetworkConnectionPtr & target, const std::string & reason);
 	void sendServerLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie);
 	void sendClientLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie, const std::string & displayName);
-	void sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> &);
+	void sendFullChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::string & channelNameForClient);
+	void sendRecentChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName);
+	void sendChatHistory(const NetworkConnectionPtr & target, const std::string & channelType, const std::string & channelName, const std::vector<LobbyChatMessage> & history);
 	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);
@@ -82,13 +84,13 @@ class LobbyServer final : public INetworkServerListener
 	void receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
 
 	void receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json);
+	void receiveRequestChatHistory(const NetworkConnectionPtr & connection, const JsonNode & json);
 	void receiveActivateGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
 	void receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
 	void receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
 	void receiveChangeRoomDescription(const NetworkConnectionPtr & connection, const JsonNode & json);
 	void receiveGameStarted(const NetworkConnectionPtr & connection, const JsonNode & json);
 	void receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json);
-	void receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json);
 
 public:
 	explicit LobbyServer(const boost::filesystem::path & databasePath);