Browse Source

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

Ivan Savenko 10 months ago
parent
commit
9d7c4a60e0
32 changed files with 540 additions and 48 deletions
  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