Quellcode durchsuchen

UI improvements for lobby screen:

- Show description of current chat room in header
- Show count of online accounts / active rooms
- Highlight currently selected chat room
Ivan Savenko vor 1 Jahr
Ursprung
Commit
4dce0479ba

+ 6 - 4
Mods/vcmi/config/vcmi/english.json

@@ -72,17 +72,19 @@
 	"vcmi.lobby.noUnderground" : "no underground",
 	"vcmi.lobby.sortDate" : "Sorts maps by change date",
 	
-	"vcmi.lobby.login.title" : "VCMI Lobby",
+	"vcmi.lobby.login.title" : "VCMI Online Lobby",
 	"vcmi.lobby.login.username" : "Username:",
 	"vcmi.lobby.login.connecting" : "Connecting...",
 	"vcmi.lobby.login.error" : "Connection error: %s",
 	"vcmi.lobby.login.create" : "New Account",
 	"vcmi.lobby.login.login" : "Login",
-	"vcmi.lobby.header.rooms" : "Game Rooms",
+	"vcmi.lobby.header.rooms" : "Game Rooms - %d",
 	"vcmi.lobby.header.channels" : "Chat Channels",
-	"vcmi.lobby.header.chat" : "Game Chat",
+	"vcmi.lobby.header.chat.global" : "Global Game Chat - %s", // %s -> language name
+	"vcmi.lobby.header.chat.match" : "Previous game chat from %s", // %s -> game start date & time
+	"vcmi.lobby.header.chat.private" : "Private chat with %s", // %s -> nickname of another player
 	"vcmi.lobby.header.history" : "Your Previous Games",
-	"vcmi.lobby.header.players" : "Players Online",
+	"vcmi.lobby.header.players" : "Players Online - %d",
 	"vcmi.lobby.match.solo" : "Solo Game",
 	"vcmi.lobby.match.duel" : "Game with %s",
 	"vcmi.lobby.match.multi" : "%d players",

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

@@ -72,17 +72,19 @@
 	"vcmi.lobby.noUnderground" : "немає підземелля",
 	"vcmi.lobby.sortDate" : "Сортувати мапи за датою зміни",
 
-	"vcmi.lobby.login.title" : "VCMI Лобі",
+	"vcmi.lobby.login.title" : "Онлайн лобі VCMI",
 	"vcmi.lobby.login.username" : "Логін:",
 	"vcmi.lobby.login.connecting" : "Підключення...",
 	"vcmi.lobby.login.error" : "Помилка з'єднання: %s",
 	"vcmi.lobby.login.create" : "Створити акаунт",
 	"vcmi.lobby.login.login" : "Увійти",
-	"vcmi.lobby.header.rooms" : "Кімнати",
+	"vcmi.lobby.header.rooms" : "Активні кімнати - %d",
 	"vcmi.lobby.header.channels" : "Канали чату",
-	"vcmi.lobby.header.chat" : "Чат гри",
+	"vcmi.lobby.header.chat.global" : "Глобальний ігровий чат - %s", // %s -> language name
+	"vcmi.lobby.header.chat.match" : "Чат минулої гри від %s", // %s -> game start date & time
+	"vcmi.lobby.header.chat.private" : "Приватний чат з %s", // %s -> nickname of another player
 	"vcmi.lobby.header.history" : "Ваші попередні ігри",
-	"vcmi.lobby.header.players" : "Гравці в мережі",
+	"vcmi.lobby.header.players" : "Гравці в мережі - %d",
 	"vcmi.lobby.match.solo" : "Одиночна гра",
 	"vcmi.lobby.match.duel" : "Гра з %s",
 	"vcmi.lobby.match.multi" : "%d гравців",

+ 45 - 21
client/globalLobby/GlobalLobbyWidget.cpp

@@ -134,33 +134,67 @@ std::shared_ptr<CListBox> GlobalLobbyWidget::getRoomList()
 	return widget<CListBox>("roomList");
 }
 
+std::shared_ptr<CListBox> GlobalLobbyWidget::getChannelList()
+{
+	return widget<CListBox>("channelList");
+}
+
 std::shared_ptr<CListBox> GlobalLobbyWidget::getMatchList()
 {
 	return widget<CListBox>("matchList");
 }
 
-GlobalLobbyChannelCardBase::GlobalLobbyChannelCardBase(GlobalLobbyWindow * window, const std::string & channelType, const std::string & channelName)
+std::shared_ptr<CLabel> GlobalLobbyWidget::getGameChatHeader()
+{
+	return widget<CLabel>("headerGameChat");
+}
+
+std::shared_ptr<CLabel> GlobalLobbyWidget::getAccountListHeader()
+{
+	return widget<CLabel>("headerAccountList");
+}
+
+std::shared_ptr<CLabel> GlobalLobbyWidget::getRoomListHeader()
+{
+	return widget<CLabel>("headerRoomList");
+}
+
+std::shared_ptr<CLabel> GlobalLobbyWidget::getChannelListHeader()
+{
+	return widget<CLabel>("headerChannelList");
+}
+
+std::shared_ptr<CLabel> GlobalLobbyWidget::getMatchListHeader()
+{
+	return widget<CLabel>("headerMatchList");
+}
+
+GlobalLobbyChannelCardBase::GlobalLobbyChannelCardBase(GlobalLobbyWindow * window, const Point & dimensions, const std::string & channelType, const std::string & channelName, const std::string & channelDescription)
 	: window(window)
 	, channelType(channelType)
 	, channelName(channelName)
+	, channelDescription(channelDescription)
 {
+	pos.w = dimensions.x;
+	pos.h = dimensions.y;
 	addUsedEvents(LCLICK);
+
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	if (window->isChannelOpen(channelType, channelName))
+		backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), Colors::YELLOW, 2);
+	else
+		backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
 }
 
 void GlobalLobbyChannelCardBase::clickPressed(const Point & cursorPosition)
 {
-	window->doOpenChannel(channelType, channelName);
+	window->doOpenChannel(channelType, channelName, channelDescription);
 }
 
 GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription)
-	: GlobalLobbyChannelCardBase(window, "player", accountDescription.accountID)
+	: GlobalLobbyChannelCardBase(window, Point(130, 40), "player", accountDescription.accountID, accountDescription.displayName)
 {
-	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));
 	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);
 }
@@ -198,26 +232,16 @@ GlobalLobbyRoomCard::GlobalLobbyRoomCard(GlobalLobbyWindow * window, const Globa
 }
 
 GlobalLobbyChannelCard::GlobalLobbyChannelCard(GlobalLobbyWindow * window, const std::string & channelName)
-	: GlobalLobbyChannelCardBase(window, "global", channelName)
+	: GlobalLobbyChannelCardBase(window, Point(146, 40), "global", channelName, Languages::getLanguageOptions(channelName).nameNative)
 {
 	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 GlobalLobbyRoom & matchDescription)
-	: GlobalLobbyChannelCardBase(window, "match", matchDescription.gameRoomID)
+	: GlobalLobbyChannelCardBase(window, Point(130, 40), "match", matchDescription.gameRoomID, matchDescription.startDateFormatted)
 {
 	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;

+ 10 - 3
client/globalLobby/GlobalLobbyWidget.h

@@ -35,6 +35,12 @@ public:
 	std::shared_ptr<CListBox> getRoomList();
 	std::shared_ptr<CListBox> getChannelList();
 	std::shared_ptr<CListBox> getMatchList();
+
+	std::shared_ptr<CLabel> getGameChatHeader();
+	std::shared_ptr<CLabel> getAccountListHeader();
+	std::shared_ptr<CLabel> getRoomListHeader();
+	std::shared_ptr<CLabel> getChannelListHeader();
+	std::shared_ptr<CLabel> getMatchListHeader();
 };
 
 class GlobalLobbyChannelCardBase : public CIntObject
@@ -42,10 +48,13 @@ class GlobalLobbyChannelCardBase : public CIntObject
 	GlobalLobbyWindow * window;
 	std::string channelType;
 	std::string channelName;
+	std::string channelDescription;
 
 	void clickPressed(const Point & cursorPosition) override;
+
+	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
 public:
-	GlobalLobbyChannelCardBase(GlobalLobbyWindow * window, const std::string & channelType, const std::string & channelName);
+	GlobalLobbyChannelCardBase(GlobalLobbyWindow * window, const Point & dimensions, const std::string & channelType, const std::string & channelName, const std::string & channelDescription);
 };
 
 class GlobalLobbyAccountCard : public GlobalLobbyChannelCardBase
@@ -75,7 +84,6 @@ public:
 
 class GlobalLobbyChannelCard : public GlobalLobbyChannelCardBase
 {
-	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
 	std::shared_ptr<CLabel> labelName;
 
 public:
@@ -84,7 +92,6 @@ public:
 
 class GlobalLobbyMatchCard : public GlobalLobbyChannelCardBase
 {
-	std::shared_ptr<TransparentFilledRectangle> backgroundOverlay;
 	std::shared_ptr<CLabel> labelMatchDate;
 	std::shared_ptr<CLabel> labelMatchOpponent;
 

+ 34 - 2
client/globalLobby/GlobalLobbyWindow.cpp

@@ -22,6 +22,7 @@
 #include "../widgets/ObjectLists.h"
 
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/Languages.h"
 #include "../../lib/MetaString.h"
 
 GlobalLobbyWindow::GlobalLobbyWindow()
@@ -33,10 +34,17 @@ GlobalLobbyWindow::GlobalLobbyWindow()
 	center();
 
 	widget->getAccountNameLabel()->setText(settings["lobby"]["displayName"].String());
-	doOpenChannel("global", "english");
+	doOpenChannel("global", "english", Languages::getLanguageOptions("english").nameNative);
+
+	widget->getChannelListHeader()->setText(MetaString::createFromTextID("vcmi.lobby.header.channels").toString());
+}
+
+bool GlobalLobbyWindow::isChannelOpen(const std::string & testChannelType, const std::string & testChannelName)
+{
+	return testChannelType == currentChannelType && testChannelName == currentChannelName;
 }
 
-void GlobalLobbyWindow::doOpenChannel(const std::string & channelType, const std::string & channelName)
+void GlobalLobbyWindow::doOpenChannel(const std::string & channelType, const std::string & channelName, const std::string & roomDescription)
 {
 	currentChannelType = channelType;
 	currentChannelName = channelName;
@@ -47,6 +55,18 @@ void GlobalLobbyWindow::doOpenChannel(const std::string & channelType, const std
 
 	for (auto const & entry : history)
 		onGameChatMessage(entry.displayName, entry.messageText, entry.timeFormatted, channelType, channelName);
+
+	MetaString text;
+	text.appendTextID("vcmi.lobby.header.chat." + channelType);
+	text.replaceRawString(roomDescription);
+	widget->getGameChatHeader()->setText(text.toString());
+
+	// Update currently selected item in UI
+	widget->getAccountList()->reset();
+	widget->getChannelList()->reset();
+	widget->getMatchList()->reset();
+
+	redraw();
 }
 
 void GlobalLobbyWindow::doSendChatMessage()
@@ -109,6 +129,10 @@ void GlobalLobbyWindow::onActiveAccounts(const std::vector<GlobalLobbyAccount> &
 		widget->getAccountList()->reset();
 	else
 		widget->getAccountList()->resize(accounts.size());
+
+	MetaString text = MetaString::createFromTextID("vcmi.lobby.header.players");
+	text.replaceNumber(accounts.size());
+	widget->getAccountListHeader()->setText(text.toString());
 }
 
 void GlobalLobbyWindow::onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms)
@@ -117,6 +141,10 @@ void GlobalLobbyWindow::onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms
 		widget->getRoomList()->reset();
 	else
 		widget->getRoomList()->resize(rooms.size());
+
+	MetaString text = MetaString::createFromTextID("vcmi.lobby.header.rooms");
+	text.replaceNumber(rooms.size());
+	widget->getRoomListHeader()->setText(text.toString());
 }
 
 void GlobalLobbyWindow::onMatchesHistory(const std::vector<GlobalLobbyRoom> & history)
@@ -125,6 +153,10 @@ void GlobalLobbyWindow::onMatchesHistory(const std::vector<GlobalLobbyRoom> & hi
 		widget->getMatchList()->reset();
 	else
 		widget->getMatchList()->resize(history.size());
+
+	MetaString text = MetaString::createFromTextID("vcmi.lobby.header.history");
+	text.replaceNumber(history.size());
+	widget->getMatchListHeader()->setText(text.toString());
 }
 
 void GlobalLobbyWindow::onJoinedRoom()

+ 4 - 1
client/globalLobby/GlobalLobbyWindow.h

@@ -30,10 +30,13 @@ public:
 
 	void doSendChatMessage();
 	void doCreateGameRoom();
-	void doOpenChannel(const std::string & channelType, const std::string & channelName);
+	void doOpenChannel(const std::string & channelType, const std::string & channelName, const std::string & roomDescription);
 	void doInviteAccount(const std::string & accountID);
 	void doJoinRoom(const std::string & roomID);
 
+	/// Returns true if provided chat channel is the one that is currently open in UI
+	bool isChannelOpen(const std::string & channelType, const std::string & channelName);
+
 	// 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);

+ 2 - 0
client/widgets/TextControls.cpp

@@ -82,6 +82,8 @@ void CLabel::setAutoRedraw(bool value)
 
 void CLabel::setText(const std::string & Txt)
 {
+	assert(TextOperations::isValidUnicodeString(Txt));
+
 	text = Txt;
 	
 	trimText();

+ 90 - 0
config/schemas/lobbyProtocol/matchesHistory.json

@@ -0,0 +1,90 @@
+{
+	"type" : "object",
+	"$schema" : "http://json-schema.org/draft-06/schema",
+	"title" : "Lobby protocol: matchesHistory",
+	"description" : "Sent by server to initialized or update list of previous matches by player",
+	"required" : [ "type", "matchesHistory" ],
+	"additionalProperties" : false,
+
+	"properties" : {
+		"type" :
+		{
+			"type" : "string",
+			"const" : "matchesHistory"
+		},
+		"matchesHistory" :
+		{
+			"type" : "array",
+			"description" : "List of previously played matches",
+			"items" :
+			{
+				"type" : "object",
+				"additionalProperties" : false,
+				"required" : [ "gameRoomID", "hostAccountID", "hostAccountDisplayName", "description", "participants", "status", "playerLimit", "ageSeconds" ],
+				"properties" : {
+					"gameRoomID" :
+					{
+						"type" : "string",
+						"description" : "Unique ID of game room"
+					},
+					"hostAccountID" :
+					{
+						"type" : "string",
+						"description" : "ID of account that created and hosts this game room"
+					},
+					"hostAccountDisplayName" :
+					{
+						"type" : "string",
+						"description" : "Display name of account that created and hosts this game room"
+					},
+					"description" :
+					{
+						"type" : "string",
+						"description" : "Auto-generated description of this room"
+					},
+					"participants" :
+					{
+						"type" : "array",
+						"description" : "List of accounts in the room, including host",
+						"items" :
+						{
+							"type" : "object",
+							"additionalProperties" : false,
+							"required" : [ "accountID", "displayName" ],
+							"properties" : {
+								"accountID" :
+								{
+									"type" : "string",
+									"description" : "Unique ID of an account"
+								},
+								"displayName" :
+								{
+									"type" : "string",
+									"description" : "Display name of an account"
+								}
+							}
+						}
+					},
+					"status" :
+					{
+						"type" : "string",
+						"enum" : [ "idle", "public", "private", "busy", "cancelled", "closed" ],
+						"description" : "Current status of game room"
+					},
+					"playerLimit" :
+					{
+						"type" : "number",
+						"minimum" : 1,
+						"maximum" : 8,
+						"description" : "Maximum number of players that can join this room, including host"
+					},
+					"ageSeconds" :
+					{
+						"type" : "number",
+						"description" : "Age of this room in seconds. For example, 10 means that this room was created 10 seconds ago"
+					}
+				}
+			}
+		}
+	}
+}

+ 10 - 10
config/widgets/lobbyWindow.json

@@ -51,9 +51,9 @@
 			"rect": {"x": 5, "y": 50, "w": 250, "h": 500}
 		},
 		{
+			"name" : "headerRoomList",
 			"type": "labelTitle",
-			"position": {"x": 15, "y": 53},
-			"text" : "vcmi.lobby.header.rooms"
+			"position": {"x": 15, "y": 53}
 		},
 		{
 			"type" : "lobbyItemList",
@@ -71,9 +71,9 @@
 			"rect": {"x": 270, "y": 50, "w": 150, "h": 140}
 		},
 		{
+			"name" : "headerChannelList",
 			"type": "labelTitle",
-			"position": {"x": 280, "y": 53},
-			"text" : "vcmi.lobby.header.channels"
+			"position": {"x": 280, "y": 53}
 		},
 		{
 			"type" : "lobbyItemList",
@@ -89,9 +89,9 @@
 			"rect": {"x": 270, "y": 210, "w": 150, "h": 380}
 		},
 		{
+			"name" : "headerMatchList",
 			"type": "labelTitle",
-			"position": {"x": 280, "y": 213},
-			"text" : "vcmi.lobby.header.history"
+			"position": {"x": 280, "y": 213}
 		},
 		{
 			"type" : "lobbyItemList",
@@ -109,9 +109,9 @@
 			"rect": {"x": 430, "y": 50, "w": 430, "h": 515}
 		},
 		{
+			"name" : "headerGameChat",
 			"type": "labelTitle",
-			"position": {"x": 440, "y": 53},
-			"text" : "vcmi.lobby.header.chat"
+			"position": {"x": 440, "y": 53}
 		},
 		{
 			"type": "textBox",
@@ -139,9 +139,9 @@
 			"rect": {"x": 870, "y": 50, "w": 150, "h": 540}
 		},
 		{
+			"name": "headerAccountList",
 			"type": "labelTitle",
-			"position": {"x": 880, "y": 53},
-			"text" : "vcmi.lobby.header.players"
+			"position": {"x": 880, "y": 53}
 		},
 		{
 			"type" : "lobbyItemList",

+ 4 - 4
lobby/LobbyServer.cpp

@@ -41,7 +41,7 @@ std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) co
 	// - control characters ('\0' ... ' ')
 	// - '{' and '}' symbols to avoid formatting
 	// - other non-printable characters?
-	return inputString;
+	return boost::trim_copy(inputString);
 }
 
 NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID) const
@@ -285,9 +285,9 @@ void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection, const
 		std::string gameRoomID = activeGameRooms.at(connection);
 		database->setGameRoomStatus(gameRoomID, LobbyRoomState::CLOSED);
 
-		for(const auto & connection : activeAccounts)
-			if (database->isPlayerInGameRoom(connection.second, gameRoomID))
-				sendMatchesHistory(connection.first);
+		for(const auto & accountConnection : activeAccounts)
+			if (database->isPlayerInGameRoom(accountConnection.second, gameRoomID))
+				sendMatchesHistory(accountConnection.first);
 		activeGameRooms.erase(connection);
 	}