소스 검색

Added support for configuring which language channels are visible to
player in lobby

Ivan Savenko 8 달 전
부모
커밋
9d7c4a60e0
32개의 변경된 파일540개의 추가작업 그리고 48개의 파일을 삭제
  1. BIN
      Mods/vcmi/Content/Sprites/lobby/addChannel.png
  2. BIN
      Mods/vcmi/Content/Sprites/lobby/closeChannel.png
  3. BIN
      Mods/vcmi/Content/Sprites2x/lobby/addChannel.png
  4. BIN
      Mods/vcmi/Content/Sprites2x/lobby/closeChannel.png
  5. BIN
      Mods/vcmi/Content/Sprites2x/lobby/iconPlayer.png
  6. BIN
      Mods/vcmi/Content/Sprites3x/lobby/addChannel.png
  7. BIN
      Mods/vcmi/Content/Sprites3x/lobby/closeChannel.png
  8. BIN
      Mods/vcmi/Content/Sprites3x/lobby/iconPlayer.png
  9. BIN
      Mods/vcmi/Content/Sprites4x/lobby/addChannel.png
  10. BIN
      Mods/vcmi/Content/Sprites4x/lobby/closeChannel.png
  11. BIN
      Mods/vcmi/Content/Sprites4x/lobby/iconPlayer.png
  12. 1 0
      Mods/vcmi/Content/config/english.json
  13. 1 0
      Mods/vcmi/Content/config/ukrainian.json
  14. 2 0
      client/CMakeLists.txt
  15. 86 0
      client/globalLobby/GlobalLobbyAddChannelWindow.cpp
  16. 46 0
      client/globalLobby/GlobalLobbyAddChannelWindow.h
  17. 52 3
      client/globalLobby/GlobalLobbyClient.cpp
  18. 2 0
      client/globalLobby/GlobalLobbyClient.h
  19. 21 1
      client/globalLobby/GlobalLobbyWidget.cpp
  20. 1 1
      client/globalLobby/GlobalLobbyWidget.h
  21. 16 0
      client/globalLobby/GlobalLobbyWindow.cpp
  22. 1 0
      client/globalLobby/GlobalLobbyWindow.h
  23. 20 18
      client/renderSDL/CTrueTypeFont.cpp
  24. 1 0
      client/renderSDL/CTrueTypeFont.h
  25. 18 10
      client/renderSDL/FontChain.cpp
  26. 6 0
      config/schemas/lobbyProtocol/clientLogin.json
  27. 5 1
      config/schemas/settings.json
  28. 118 0
      config/widgets/buttons/lobbyAddChannel.json
  29. 118 0
      config/widgets/buttons/lobbyCloseChannel.json
  30. 7 7
      config/widgets/lobbyWindow.json
  31. 4 2
      include/vstd/RNG.h
  32. 14 5
      lobby/LobbyServer.cpp

BIN
Mods/vcmi/Content/Sprites/lobby/addChannel.png


BIN
Mods/vcmi/Content/Sprites/lobby/closeChannel.png


BIN
Mods/vcmi/Content/Sprites2x/lobby/addChannel.png


BIN
Mods/vcmi/Content/Sprites2x/lobby/closeChannel.png


BIN
Mods/vcmi/Content/Sprites2x/lobby/iconPlayer.png


BIN
Mods/vcmi/Content/Sprites3x/lobby/addChannel.png


BIN
Mods/vcmi/Content/Sprites3x/lobby/closeChannel.png


BIN
Mods/vcmi/Content/Sprites3x/lobby/iconPlayer.png


BIN
Mods/vcmi/Content/Sprites4x/lobby/addChannel.png


BIN
Mods/vcmi/Content/Sprites4x/lobby/closeChannel.png


BIN
Mods/vcmi/Content/Sprites4x/lobby/iconPlayer.png


+ 1 - 0
Mods/vcmi/Content/config/english.json

@@ -199,6 +199,7 @@
 	"vcmi.lobby.preview.error.invite" : "You were not invited to this room.",
 	"vcmi.lobby.preview.error.mods" : "You are using different set of mods.",
 	"vcmi.lobby.preview.error.version" : "You are using different version of VCMI.",
+	"vcmi.lobby.channel.add" : "Add Channel",
 	"vcmi.lobby.room.new" : "New Game",
 	"vcmi.lobby.room.load" : "Load Game",
 	"vcmi.lobby.room.type" : "Room Type",

+ 1 - 0
Mods/vcmi/Content/config/ukrainian.json

@@ -199,6 +199,7 @@
 	"vcmi.lobby.preview.error.invite" : "Ви не були запрошені до цієї кімнати.",
 	"vcmi.lobby.preview.error.mods" : "Ви використовуєте інший набір модифікацій.",
 	"vcmi.lobby.preview.error.version" : "Ви використовуєте іншу версію VCMI.",
+	"vcmi.lobby.channel.add" : "Додати канал чату",
 	"vcmi.lobby.room.new" : "Нова гра",
 	"vcmi.lobby.room.load" : "Завантажити гру",
 	"vcmi.lobby.room.type" : "Тип кімнати",

+ 2 - 0
client/CMakeLists.txt

@@ -109,6 +109,7 @@ set(vcmiclientcommon_SRCS
 	renderSDL/ScreenHandler.cpp
 	renderSDL/SDL_Extensions.cpp
 
+	globalLobby/GlobalLobbyAddChannelWindow.cpp
 	globalLobby/GlobalLobbyClient.cpp
 	globalLobby/GlobalLobbyInviteWindow.cpp
 	globalLobby/GlobalLobbyLoginWindow.cpp
@@ -322,6 +323,7 @@ set(vcmiclientcommon_HEADERS
 
 	globalLobby/GlobalLobbyClient.h
 	globalLobby/GlobalLobbyDefines.h
+	globalLobby/GlobalLobbyAddChannelWindow.h
 	globalLobby/GlobalLobbyInviteWindow.h
 	globalLobby/GlobalLobbyLoginWindow.h
 	globalLobby/GlobalLobbyRoomWindow.h

+ 86 - 0
client/globalLobby/GlobalLobbyAddChannelWindow.cpp

@@ -0,0 +1,86 @@
+/*
+ * GlobalLobbyAddChannelWindow.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#include "StdInc.h"
+#include "GlobalLobbyAddChannelWindow.h"
+
+#include "GlobalLobbyClient.h"
+
+#include "../CServerHandler.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/Shortcut.h"
+#include "../gui/WindowHandler.h"
+#include "../widgets/Buttons.h"
+#include "../widgets/GraphicalPrimitiveCanvas.h"
+#include "../widgets/Images.h"
+#include "../widgets/ObjectLists.h"
+#include "../widgets/TextControls.h"
+
+#include "../../lib/texts/MetaString.h"
+#include "../../lib/texts/Languages.h"
+
+GlobalLobbyAddChannelWindowCard::GlobalLobbyAddChannelWindowCard(const std::string & languageID)
+	: languageID(languageID)
+{
+	pos.w = 200;
+	pos.h = 40;
+	addUsedEvents(LCLICK);
+
+	OBJECT_CONSTRUCTION;
+	const auto & language = Languages::getLanguageOptions(languageID);
+
+	backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
+	labelNameNative = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, language.nameNative);
+
+	if (language.nameNative != language.nameEnglish)
+		labelNameTranslated = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, language.nameEnglish);
+}
+
+void GlobalLobbyAddChannelWindowCard::clickPressed(const Point & cursorPosition)
+{
+	CSH->getGlobalLobby().addChannel(languageID);
+	GH.windows().popWindows(1);
+}
+
+GlobalLobbyAddChannelWindow::GlobalLobbyAddChannelWindow()
+	: CWindowObject(BORDERED)
+{
+	OBJECT_CONSTRUCTION;
+
+	pos.w = 236;
+	pos.h = 420;
+
+	filledBackground = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
+	filledBackground->setPlayerColor(PlayerColor(1));
+	labelTitle = std::make_shared<CLabel>(
+		pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.channel.add").toString()
+		);
+
+	const auto & allLanguages = Languages::getLanguageList();
+	std::vector<std::string> newLanguages;
+	for (const auto & language : allLanguages)
+		if (!vstd::contains(CSH->getGlobalLobby().getActiveChannels(), language.identifier))
+			newLanguages.push_back(language.identifier);
+
+	const auto & createChannelCardCallback = [newLanguages](size_t index) -> std::shared_ptr<CIntObject>
+	{
+		if(index < newLanguages.size())
+			return std::make_shared<GlobalLobbyAddChannelWindowCard>(newLanguages[index]);
+		return std::make_shared<CIntObject>();
+	};
+
+	listBackground = std::make_shared<TransparentFilledRectangle>(Rect(8, 48, 220, 324), ColorRGBA(0, 0, 0, 64), ColorRGBA(64, 80, 128, 255), 1);
+	languageList = std::make_shared<CListBox>(createChannelCardCallback, Point(10, 50), Point(0, 40), 8, newLanguages.size(), 0, 1 | 4, Rect(200, 0, 320, 320));
+	languageList->setRedrawParent(true);
+
+	buttonClose = std::make_shared<CButton>(Point(86, 384), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this]() { close(); }, EShortcut::GLOBAL_RETURN );
+
+	center();
+}

+ 46 - 0
client/globalLobby/GlobalLobbyAddChannelWindow.h

@@ -0,0 +1,46 @@
+/*
+ * GlobalLobbyInviteWindow.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "GlobalLobbyObserver.h"
+
+#include "../windows/CWindowObject.h"
+
+class CLabel;
+class FilledTexturePlayerColored;
+class TransparentFilledRectangle;
+class CListBox;
+class CButton;
+struct GlobalLobbyAccount;
+
+class GlobalLobbyAddChannelWindow final : public CWindowObject
+{
+	std::shared_ptr<FilledTexturePlayerColored> filledBackground;
+	std::shared_ptr<CLabel> labelTitle;
+	std::shared_ptr<CListBox> languageList;
+	std::shared_ptr<TransparentFilledRectangle> listBackground;
+	std::shared_ptr<CButton> buttonClose;
+
+public:
+	GlobalLobbyAddChannelWindow();
+};
+
+class GlobalLobbyAddChannelWindowCard : public CIntObject
+{
+	std::string languageID;
+
+	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
+	std::shared_ptr<CLabel> labelNameNative;
+	std::shared_ptr<CLabel> labelNameTranslated;
+
+	void clickPressed(const Point & cursorPosition) override;
+public:
+	GlobalLobbyAddChannelWindowCard(const std::string & languageID);
+};

+ 52 - 3
client/globalLobby/GlobalLobbyClient.cpp

@@ -32,9 +32,54 @@
 
 GlobalLobbyClient::GlobalLobbyClient()
 {
-	activeChannels.emplace_back("english");
-	if (CGI->generaltexth->getPreferredLanguage() != "english")
-		activeChannels.emplace_back(CGI->generaltexth->getPreferredLanguage());
+	auto customChannels = settings["lobby"]["languageRooms"].convertTo<std::vector<std::string>>();
+
+	if (customChannels.empty())
+	{
+		activeChannels.emplace_back("english");
+		if (CGI->generaltexth->getPreferredLanguage() != "english")
+			activeChannels.emplace_back(CGI->generaltexth->getPreferredLanguage());
+	}
+	else
+	{
+		activeChannels = customChannels;
+	}
+}
+
+void GlobalLobbyClient::addChannel(const std::string & channel)
+{
+	activeChannels.emplace_back(channel);
+
+	auto lobbyWindowPtr = lobbyWindow.lock();
+	if(lobbyWindowPtr)
+		lobbyWindowPtr->refreshActiveChannels();
+
+	JsonNode toSend;
+	toSend["type"].String() = "requestChatHistory";
+	toSend["channelType"].String() = "global";
+	toSend["channelName"].String() = channel;
+	CSH->getGlobalLobby().sendMessage(toSend);
+
+	Settings languageRooms = settings.write["lobby"]["languageRooms"];
+
+	languageRooms->Vector().clear();
+	for (const auto & lang : activeChannels)
+		languageRooms->Vector().push_back(JsonNode(lang));
+}
+
+void GlobalLobbyClient::closeChannel(const std::string & channel)
+{
+	vstd::erase(activeChannels, channel);
+
+	auto lobbyWindowPtr = lobbyWindow.lock();
+	if(lobbyWindowPtr)
+		lobbyWindowPtr->refreshActiveChannels();
+
+	Settings languageRooms = settings.write["lobby"]["languageRooms"];
+
+	languageRooms->Vector().clear();
+	for (const auto & lang : activeChannels)
+		languageRooms->Vector().push_back(JsonNode(lang));
 }
 
 GlobalLobbyClient::~GlobalLobbyClient() = default;
@@ -347,6 +392,10 @@ void GlobalLobbyClient::sendClientLogin()
 	toSend["accountCookie"].String() = getAccountCookie();
 	toSend["language"].String() = CGI->generaltexth->getPreferredLanguage();
 	toSend["version"].String() = VCMI_VERSION_STRING;
+
+	for (const auto & language : activeChannels)
+		toSend["languageRooms"].Vector().push_back(JsonNode(language));
+
 	sendMessage(toSend);
 }
 

+ 2 - 0
client/globalLobby/GlobalLobbyClient.h

@@ -92,6 +92,8 @@ public:
 	void sendClientRegister(const std::string & accountName);
 	void sendClientLogin();
 	void sendOpenRoom(const std::string & mode, int playerLimit);
+	void addChannel(const std::string & channel);
+	void closeChannel(const std::string & channel);
 
 	void sendProxyConnectionLogin(const NetworkConnectionPtr & netConnection);
 	void resetMatchState();

+ 21 - 1
client/globalLobby/GlobalLobbyWidget.cpp

@@ -11,9 +11,10 @@
 #include "StdInc.h"
 #include "GlobalLobbyWidget.h"
 
+#include "GlobalLobbyAddChannelWindow.h"
 #include "GlobalLobbyClient.h"
-#include "GlobalLobbyWindow.h"
 #include "GlobalLobbyRoomWindow.h"
+#include "GlobalLobbyWindow.h"
 
 #include "../CGameInfo.h"
 #include "../CServerHandler.h"
@@ -72,6 +73,18 @@ GlobalLobbyWidget::CreateFunc GlobalLobbyWidget::getItemListConstructorFunc(cons
 
 		if(index < channels.size())
 			return std::make_shared<GlobalLobbyChannelCard>(this->window, channels[index]);
+
+		if(index == channels.size())
+		{
+			const auto buttonCallback = [](){
+				GH.windows().createAndPushWindow<GlobalLobbyAddChannelWindow>();
+			};
+
+			auto result = std::make_shared<CButton>(Point(0,0), AnimationPath::builtin("lobbyAddChannel"), CButton::tooltip(), buttonCallback);
+			result->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/addChannel")));
+			return result;
+		}
+
 		return std::make_shared<CIntObject>();
 	};
 
@@ -255,6 +268,13 @@ GlobalLobbyChannelCard::GlobalLobbyChannelCard(GlobalLobbyWindow * window, const
 {
 	OBJECT_CONSTRUCTION;
 	labelName = std::make_shared<CLabel>(5, 20, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, Languages::getLanguageOptions(channelName).nameNative);
+
+	if (CSH->getGlobalLobby().getActiveChannels().size() > 1)
+	{
+		pos.w = 110;
+		buttonClose = std::make_shared<CButton>(Point(113, 7), AnimationPath::builtin("lobbyCloseChannel"), CButton::tooltip(), [channelName](){CSH->getGlobalLobby().closeChannel(channelName);});
+		buttonClose->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/closeChannel")));
+	}
 }
 
 GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const GlobalLobbyRoom & matchDescription)

+ 1 - 1
client/globalLobby/GlobalLobbyWidget.h

@@ -77,7 +77,6 @@ class GlobalLobbyRoomCard : public CIntObject
 	std::shared_ptr<CLabel> labelRoomSize;
 	std::shared_ptr<CLabel> labelRoomStatus;
 	std::shared_ptr<CLabel> labelDescription;
-	std::shared_ptr<CButton> buttonJoin;
 	std::shared_ptr<CPicture> iconRoomSize;
 
 	void clickPressed(const Point & cursorPosition) override;
@@ -88,6 +87,7 @@ public:
 class GlobalLobbyChannelCard : public GlobalLobbyChannelCardBase
 {
 	std::shared_ptr<CLabel> labelName;
+	std::shared_ptr<CButton> buttonClose;
 
 public:
 	GlobalLobbyChannelCard(GlobalLobbyWindow * window, const std::string & channelName);

+ 16 - 0
client/globalLobby/GlobalLobbyWindow.cpp

@@ -39,6 +39,7 @@ GlobalLobbyWindow::GlobalLobbyWindow()
 	doOpenChannel("global", "english", Languages::getLanguageOptions("english").nameNative);
 
 	widget->getChannelListHeader()->setText(MetaString::createFromTextID("vcmi.lobby.header.channels").toString());
+	widget->getChannelList()->resize(CSH->getGlobalLobby().getActiveChannels().size()+1);
 }
 
 bool GlobalLobbyWindow::isChannelOpen(const std::string & testChannelType, const std::string & testChannelName) const
@@ -182,6 +183,21 @@ void GlobalLobbyWindow::onMatchesHistory(const std::vector<GlobalLobbyRoom> & hi
 	widget->getMatchListHeader()->setText(text.toString());
 }
 
+void GlobalLobbyWindow::refreshActiveChannels()
+{
+	const auto & activeChannels = CSH->getGlobalLobby().getActiveChannels();
+
+	if (activeChannels.size()+1 == widget->getChannelList()->size())
+		widget->getChannelList()->reset();
+	else
+		widget->getChannelList()->resize(activeChannels.size()+1);
+
+	if (currentChannelType == "global" && !vstd::contains(activeChannels, currentChannelName) && !activeChannels.empty())
+	{
+		doOpenChannel("global", activeChannels.front(), Languages::getLanguageOptions(activeChannels.front()).nameNative);
+	}
+}
+
 void GlobalLobbyWindow::onInviteReceived(const std::string & invitedRoomID)
 {
 	widget->getRoomList()->reset();

+ 1 - 0
client/globalLobby/GlobalLobbyWindow.h

@@ -45,6 +45,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 refreshChatText();
+	void refreshActiveChannels();
 	void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts) override;
 	void onActiveGameRooms(const std::vector<GlobalLobbyRoom> & rooms) override;
 	void onMatchesHistory(const std::vector<GlobalLobbyRoom> & history);

+ 20 - 18
client/renderSDL/CTrueTypeFont.cpp

@@ -127,27 +127,29 @@ size_t CTrueTypeFont::getStringWidthScaled(const std::string & text) const
 
 void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const
 {
-	if (color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow
-	{
-		if (outline)
-			renderText(surface, data, Colors::BLACK, pos - Point(1,1) * getScalingFactor());
+	if (data.empty())
+		return;
 
-		if (dropShadow || outline)
-			renderText(surface, data, Colors::BLACK, pos + Point(1,1) * getScalingFactor());
-	}
+	if (outline)
+		renderTextImpl(surface, data, Colors::BLACK, pos - Point(1,1) * getScalingFactor());
 
-	if (!data.empty())
-	{
-		SDL_Surface * rendered;
-		if (blended)
-			rendered = TTF_RenderUTF8_Blended(font.get(), data.c_str(), CSDL_Ext::toSDL(color));
-		else
-			rendered = TTF_RenderUTF8_Solid(font.get(), data.c_str(), CSDL_Ext::toSDL(color));
+	if (dropShadow || outline)
+		renderTextImpl(surface, data, Colors::BLACK, pos + Point(1,1) * getScalingFactor());
 
-		assert(rendered);
+	renderTextImpl(surface, data, color, pos);
+}
 
-		CSDL_Ext::blitSurface(rendered, surface, pos);
-		SDL_FreeSurface(rendered);
-	}
+void CTrueTypeFont::renderTextImpl(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const
+{
+	SDL_Surface * rendered;
+	if (blended)
+		rendered = TTF_RenderUTF8_Blended(font.get(), data.c_str(), CSDL_Ext::toSDL(color));
+	else
+		rendered = TTF_RenderUTF8_Solid(font.get(), data.c_str(), CSDL_Ext::toSDL(color));
+
+	assert(rendered);
+
+	CSDL_Ext::blitSurface(rendered, surface, pos);
+	SDL_FreeSurface(rendered);
 }
 

+ 1 - 0
client/renderSDL/CTrueTypeFont.h

@@ -34,6 +34,7 @@ class CTrueTypeFont final : public IFont
 	int getFontStyle(const JsonNode & config) const;
 
 	void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
+	void renderTextImpl(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const;
 	size_t getFontAscentScaled() const override;
 public:
 	CTrueTypeFont(const JsonNode & fontConfig);

+ 18 - 10
client/renderSDL/FontChain.cpp

@@ -113,24 +113,32 @@ size_t FontChain::getGlyphWidthScaled(const char * data) const
 
 std::vector<FontChain::TextChunk> FontChain::splitTextToChunks(const std::string & data) const
 {
+	// U+FFFD - replacement character (question mark in rhombus)
+	static const std::string replacementCharacter = u8"�";
+
 	std::vector<TextChunk> chunks;
 
-	for (size_t i = 0; i < data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))
+	const auto & selectFont = [this](const char * characterPtr) -> const IFont *
 	{
-		const IFont * currentFont = nullptr;
 		for(const auto & font : chain)
+			if (font->canRepresentCharacter(characterPtr))
+				return font.get();
+		return nullptr;
+	};
+
+	for (size_t i = 0; i < data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))
+	{
+		std::string symbol = data.substr(i, TextOperations::getUnicodeCharacterSize(data[i]));
+		const IFont * currentFont = selectFont(symbol.data());
+
+		if (currentFont == nullptr)
 		{
-			if (font->canRepresentCharacter(data.data() + i))
-			{
-				currentFont = font.get();
-				break;
-			}
+			symbol = replacementCharacter;
+			currentFont = selectFont(symbol.data());
 		}
 
 		if (currentFont == nullptr)
-			continue; // not representable
-
-		std::string symbol = data.substr(i, TextOperations::getUnicodeCharacterSize(data[i]));
+			continue; // Still nothing - neither desired character nor fallback can be rendered
 
 		if (chunks.empty() || chunks.back().font != currentFont)
 			chunks.push_back({currentFont, symbol});

+ 6 - 0
config/schemas/lobbyProtocol/clientLogin.json

@@ -32,6 +32,12 @@
 		{
 			"type" : "string",
 			"description" : "Version of client, e.g. 1.5.0"
+		},
+		"languageRooms" :
+		{
+			"type" : "array",
+			"items" : { "type" : "string" },
+			"description" : "(since 1.6.4) List of language rooms for which player wants to receive chat history"
 		}
 	}
 }

+ 5 - 1
config/schemas/settings.json

@@ -673,7 +673,7 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default" : {},
-			"required" : [ "mapPreview", "hostname", "port", "roomPlayerLimit", "roomType", "roomMode" ],
+			"required" : [ "mapPreview", "hostname", "port", "roomPlayerLimit", "roomType", "roomMode", "languageRooms" ],
 			"properties" : {
 				"mapPreview" : {
 					"type" : "boolean",
@@ -698,6 +698,10 @@
 				"roomMode" : {
 					"type" : "number",
 					"default" : 0
+				},
+				"languageRooms" : {
+					"type" : "array",
+					"default" : []
 				}
 			}
 		},

+ 118 - 0
config/widgets/buttons/lobbyAddChannel.json

@@ -0,0 +1,118 @@
+{
+	"normal" : {
+		"width": 146,
+		"height": 40,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 0, "y": 0, "w": 146, "h": 40}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 146, "h": 40},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 128 ] },
+				
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					
+					{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+					{ "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+				]
+			}
+		]
+	},
+	"pressed" : {
+		"width": 146,
+		"height": 40,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 1, "y": 1, "w": 145, "h": 39}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 146, "h": 40},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 160 ] },
+				
+					{ "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+					
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] },
+
+					{ "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] },
+					{ "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] },
+				]
+			}
+		]
+	},
+	"blocked" : {
+		"width": 146,
+		"height": 40,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 0, "y": 0, "w": 146, "h": 40}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 146, "h": 40},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 128 ] },
+				
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					
+					{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+					{ "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+				]
+			}
+		]
+	},
+	"highlighted" : {
+		"width": 146,
+		"height": 40,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 0, "y": 0, "w": 146, "h": 40}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 146, "h": 40},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 128 ] },
+				
+					{ "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] },
+					
+					{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] },
+					{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] },
+					
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
+				]
+			}
+		]
+	},
+}

+ 118 - 0
config/widgets/buttons/lobbyCloseChannel.json

@@ -0,0 +1,118 @@
+{
+	"normal" : {
+		"width": 25,
+		"height": 25,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 0, "y": 0, "w": 25, "h": 25}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 25, "h": 25},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 128 ] },
+				
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					
+					{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+					{ "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+				]
+			}
+		]
+	},
+	"pressed" : {
+		"width": 25,
+		"height": 25,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 1, "y": 1, "w": 24, "h": 24}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 25, "h": 25},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 3, "y" : 3}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 160 ] },
+				
+					{ "type" : "rectangle", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+					
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 48 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 96 ] },
+
+					{ "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : 2, "y" : -3}, "color" : [ 255, 255, 255, 64 ] },
+					{ "type" : "line", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : 2}, "color" : [ 255, 255, 255, 128 ] },
+				]
+			}
+		]
+	},
+	"blocked" : {
+		"width": 25,
+		"height": 25,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 0, "y": 0, "w": 25, "h": 25}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 25, "h": 25},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 128 ] },
+				
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : 0, "y" : -1}, "color" : [ 255, 255, 255, 64 ] },
+					{ "type" : "line", "a" : { "x" : 0, "y" : 0}, "b" : { "x" : -1, "y" : 0}, "color" : [ 255, 255, 255, 128 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 80 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
+
+					{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 0, 0, 0, 192 ] },
+					
+					{ "type" : "line", "a" : { "x" : 0, "y" : -1}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+					{ "type" : "line", "a" : { "x" : -1, "y" : 0}, "b" : { "x" : -1, "y" : -1}, "color" : [ 0, 0, 0, 255 ] },
+				]
+			}
+		]
+	},
+	"highlighted" : {
+		"width": 25,
+		"height": 25,
+		"items" : [
+			{
+				"type": "texture",
+				"image": "DiBoxBck",
+				"color" : "blue",
+				"rect": {"x": 0, "y": 0, "w": 25, "h": 25}
+			},
+			{
+				"type": "graphicalPrimitive",
+				"rect": {"x": 0, "y": 0, "w": 25, "h": 25},
+				"primitives" : [
+					{ "type" : "filledBox", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 0, 0, 0, 128 ] },
+				
+					{ "type" : "rectangle", "a" : { "x" : 2, "y" : 2}, "b" : { "x" : -3, "y" : -3}, "color" : [ 255, 255, 255, 255 ] },
+					
+					{ "type" : "line", "a" : { "x" : 1, "y" : -2}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] },
+					{ "type" : "line", "a" : { "x" : -2, "y" : 1}, "b" : { "x" : -2, "y" : -2}, "color" : [ 255, 255, 255, 255 ] },
+					
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : 1, "y" : -2}, "color" : [ 255, 255, 255, 160 ] },
+					{ "type" : "line", "a" : { "x" : 1, "y" : 1}, "b" : { "x" : -2, "y" : 1}, "color" : [ 255, 255, 255, 160 ] },
+				]
+			}
+		]
+	},
+}

+ 7 - 7
config/widgets/lobbyWindow.json

@@ -69,7 +69,7 @@
 		
 		{
 			"type": "areaFilled",
-			"rect": {"x": 270, "y": 50, "w": 150, "h": 140}
+			"rect": {"x": 270, "y": 50, "w": 150, "h": 180}
 		},
 		{
 			"name" : "headerChannelList",
@@ -82,27 +82,27 @@
 			"itemType" : "channel",
 			"position" : { "x" : 272, "y" : 68 },
 			"itemOffset" : { "x" : 0, "y" : 40 },
-			"visibleAmount" : 3
+			"visibleAmount" : 4
 		},
 		
 		{
 			"type": "areaFilled",
-			"rect": {"x": 270, "y": 210, "w": 150, "h": 380}
+			"rect": {"x": 270, "y": 250, "w": 150, "h": 340}
 		},
 		{
 			"name" : "headerMatchList",
 			"type": "labelTitle",
-			"position": {"x": 280, "y": 213}
+			"position": {"x": 280, "y": 253}
 		},
 		{
 			"type" : "lobbyItemList",
 			"name" : "matchList",
 			"itemType" : "match",
-			"position" : { "x" : 272, "y" : 228 },
+			"position" : { "x" : 272, "y" : 268 },
 			"itemOffset" : { "x" : 0, "y" : 40 },
 			"sliderPosition" : { "x" : 130, "y" : 0 },
-			"sliderSize" : { "x" : 360, "y" : 360 },
-			"visibleAmount" : 9
+			"sliderSize" : { "x" : 320, "y" : 320 },
+			"visibleAmount" : 8
 		},
 
 		{

+ 4 - 2
include/vstd/RNG.h

@@ -81,9 +81,11 @@ namespace RandomGeneratorUtil
 
 		for (size_t i = 0; i < container.size(); ++i)
 		{
-			roll -= container[i];
-			if(roll < 0)
+			int chance = container[i];
+			if(roll < chance)
 				return i;
+
+			roll -= chance;
 		}
 		return container.size() - 1;
 	}

+ 14 - 5
lobby/LobbyServer.cpp

@@ -467,8 +467,7 @@ void LobbyServer::receiveRequestChatHistory(const NetworkConnectionPtr & connect
 
 	if (channelType == "global")
 	{
-		// can only be sent on connection, initiated by server
-		sendOperationFailed(connection, "Operation not supported!");
+		sendRecentChatHistory(connection, channelType, channelName);
 	}
 
 	if (channelType == "match")
@@ -588,6 +587,7 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co
 	std::string accountCookie = json["accountCookie"].String();
 	std::string language = json["language"].String();
 	std::string version = json["version"].String();
+	const auto & languageRooms = json["languageRooms"].Vector();
 
 	if(!database->isAccountIDExists(accountID))
 		return sendOperationFailed(connection, "Account not found");
@@ -606,9 +606,18 @@ void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, co
 
 	logGlobal->info("%s: Logged in as %s", accountID, displayName);
 	sendClientLoginSuccess(connection, accountCookie, displayName);
-	sendRecentChatHistory(connection, "global", "english");
-	if (language != "english")
-		sendRecentChatHistory(connection, "global", language);
+
+	if (!languageRooms.empty())
+	{
+		for (const auto & entry : languageRooms)
+			sendRecentChatHistory(connection, "global", entry.String());
+	}
+	else
+	{
+		sendRecentChatHistory(connection, "global", "english");
+		if (language != "english")
+			sendRecentChatHistory(connection, "global", language);
+	}
 
 	// send active game rooms list to new account
 	// and update account list to everybody else including new account