Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -398,6 +398,7 @@ server = "lobby.wildfiregames.com" ; Address of lobby server xpartamupp = "wfgbot23" ; Name of the server-side xmpp client that manage games buddies = "," ; Comma separated list of playernames that the current user has marked as buddies +chatsenderpresence = false ; Dynamically changing brightness of messages by presence of sent player [lobby.columns] gamerating = false ; Show the average rating of the participating players in a column of the gamelist Index: binaries/data/mods/public/gui/common/color.js =================================================================== --- binaries/data/mods/public/gui/common/color.js +++ binaries/data/mods/public/gui/common/color.js @@ -141,6 +141,17 @@ return [r, g, b].map(n => Math.round(n * 255)); } +/** + * Set color to brightness. + * + * @param {string} color - RGB Color as string with three numbers as pattern "[r] [g] [b]". + * @param {float} brightness - Adjust color brightness from [0-1]. + */ +function setColorBrightness(color, brightness) +{ + return color.split(" ").map(number => Math.round(number * brightness)).join(" "); +} + function colorizeHotkey(text, hotkey) { let key = Engine.ConfigDB_GetValue("user", "hotkey." + hotkey); Index: binaries/data/mods/public/gui/lobby/lobby.js =================================================================== --- binaries/data/mods/public/gui/lobby/lobby.js +++ binaries/data/mods/public/gui/lobby/lobby.js @@ -41,11 +41,11 @@ * The playerlist will be assembled using these values. */ var g_PlayerStatuses = { - "available": { "color": "0 219 0", "status": translate("Online") }, - "away": { "color": "229 76 13", "status": translate("Away") }, - "playing": { "color": "200 0 0", "status": translate("Busy") }, - "offline": { "color": "0 0 0", "status": translate("Offline") }, - "unknown": { "color": "178 178 178", "status": translateWithContext("lobby presence", "Unknown") } + "available": { "color": "0 219 0", "brightness": 1, "status": translate("Online") }, + "away": { "color": "229 76 13", "brightness": 0.8, "status": translate("Away") }, + "playing": { "color": "200 0 0", "brightness": 0.6, "status": translate("Busy") }, + "offline": { "color": "0 0 0", "brightness": 0.4, "status": translate("Offline") }, + "unknown": { "color": "178 178 178", "brightness": 0.4, "status": translateWithContext("lobby presence", "Unknown") } }; var g_RoleNames = { @@ -111,6 +111,11 @@ var g_Kicked = false; /** + * List of online players, their ratings, presence state and messages in chat. + */ +var g_PlayerList; + +/** * Processing of notifications sent by XmppClient.cpp. * * @returns true if the playerlist GUI must be updated. @@ -597,10 +602,47 @@ } /** + * Restore previous attributes from object from previous list, that are not on the new object. + * Find object by name's attribute. Try firstly at counting index. Or search the list. If on different position, take + * next index for next lookup as return. Upcoming objects are expected there. The Engine gives lists in same order + * (only not, if entries gone or inserted). + * + * @param {object} newObject - New object from new list. + * @param {integer} previousIndex - Counting index to look at previous list. + * @param {array} previousList - List, where old objects are in. + * @param {array} previousAttributes - Attributes to restore from previous list. + * @return {(object|integer)} - previous object, index to look next. + */ +function restoreObjectFromPreviousList(newObject, previousIndex, previousList, previousAttributes) +{ + let nextPreviousIndex = previousIndex; + + // Lookup object + let previousObject = previousList[previousIndex] && + previousList[previousIndex].name == newObject.name ? previousList[nextPreviousIndex++] : + previousList.find((previousObject, indexFound) => previousObject.name == newObject.name && (nextPreviousIndex = ++indexFound)); + + // Restore attributes, if exist or set undefined + previousAttributes.forEach(att => newObject[att] = previousObject && previousObject[att] ? previousObject[att] : undefined); + + return [ previousObject, nextPreviousIndex ]; +} + +/** * Do a full update of the player listing, including ratings from cached C++ information. */ function updatePlayerList() { + let isInitialPlayerList = !Array.isArray(g_PlayerList); + let newPlayerList = Engine.GetPlayerList(); + + if (isInitialPlayerList) + { + if (newPlayerList.length == 0) + return; + g_PlayerList = []; + } + let playersBox = Engine.GetGUIObjectByName("playersBox"); let sortBy = playersBox.selected_column || "name"; let sortOrder = playersBox.selected_column_order || 1; @@ -611,10 +653,29 @@ let nickList = []; let ratingList = []; - let cleanPlayerList = Engine.GetPlayerList().map(player => { + let isUpdatedChat = false; + let indexPrevious = 0; + + g_PlayerList = newPlayerList.map(player => { + let playerPrevious = {}; + + [ playerPrevious, indexPrevious ] = restoreObjectFromPreviousList(player, indexPrevious, g_PlayerList, [ "messages" ]); + + if (player.messages && playerPrevious && (playerPrevious.rating != player.rating || playerPrevious.presence != player.presence)) + player.messages.forEach(message => { + message.playerPresence = player.presence; + isUpdatedChat = ircFormat(message) || isUpdatedChat; + }); + player.isBuddy = g_Buddies.indexOf(player.name) != -1; return player; - }).sort((a, b) => { + }); + + // Check if unassigned messages to players lie in the chat + if (isInitialPlayerList) + g_ChatMessages.forEach(msg => setPlayerChatMessages(msg, true) && (isUpdatedChat = ircFormat(msg) || isUpdatedChat)); + + let cleanPlayerList = g_PlayerList.sort((a, b) => { let sortA, sortB; let statusOrder = Object.keys(g_PlayerStatuses); let statusA = statusOrder.indexOf(a.presence) + a.name.toLowerCase(); @@ -671,6 +732,9 @@ playersBox.list = nickList; playersBox.selected = playersBox.list.indexOf(g_SelectedPlayer); + + if (isUpdatedChat) + updateChatWindow(); } /** @@ -1257,6 +1321,30 @@ } /** + * Find player to a message and add the message reference to him, so if some player attribute in message changes, + * the message can be reformatted. + * + * @param {object} message - Message object. + * @param {boolean} checkAlreadyAdded - Ensure on initial playerlist, that message not already added to player (when playerlist + * arrived before messages from history). + * @return {boolean} - True if added, false if not. + */ +function setPlayerChatMessages(message, checkAlreadyAdded) +{ + let playerFrom = g_PlayerList && g_PlayerList.find(player => player.name && player.name == message.from); + + if (playerFrom && + (playerFrom.messages || (playerFrom.messages = [])) && + (!checkAlreadyAdded || playerFrom.messages.indexOf(message) == -1)) + { + playerFrom.messages.push(message); + message.playerPresence = playerFrom.presence; + return true; + } + return false; +} + +/** * Process and if appropriate, display a formatted message. * * @param {Object} msg - The message to be processed. @@ -1265,6 +1353,8 @@ { if (msg.from) { + setPlayerChatMessages(msg); + if (Engine.LobbyGetPlayerRole(msg.from) == "moderator") msg.from = g_ModeratorPrefix + msg.from; @@ -1278,12 +1368,16 @@ } } - let formatted = ircFormat(msg); - if (!formatted) + if (!ircFormat(msg)) return; - g_ChatMessages.push(formatted); - Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n"); + g_ChatMessages.push(msg); + updateChatWindow(); +} + +function updateChatWindow() +{ + Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.map(msg => msg.formatted).join("\n"); } /** @@ -1300,15 +1394,25 @@ } /** - * Format text in an IRC-like way. + * Format text in an IRC-like way and add it to msg.formatted. * * @param {Object} msg - Received chat message. - * @returns {string} - Formatted text. + * @returns {boolean} - Text has been formatted. */ function ircFormat(msg) { let formattedMessage = ""; - let coloredFrom = msg.from && colorPlayerName(msg.from); + + let brightness = g_PlayerStatuses[ + Engine.ConfigDB_GetValue("user", "lobby.chatsenderpresence") == "false" || + !msg.from || + msg.from == "system" ? "available" : msg.playerPresence || "offline" + ].brightness; + + let coloredFrom = msg.from ? coloredText( + msg.from, + setColorBrightness(getPlayerColor(msg.from), brightness) + ) : ""; // Handle commands allowed past handleChatCommand. if (msg.text[0] == '/') @@ -1367,7 +1471,7 @@ break; } default: - return ""; + return false; } } else @@ -1392,9 +1496,11 @@ }); } + msg.formatted = coloredText(formattedMessage, setColorBrightness("255 255 255", brightness)); + // Add chat message timestamp if (Engine.ConfigDB_GetValue("user", "chat.timestamp") != "true") - return formattedMessage; + return true; // Translation: Time as shown in the multiplayer lobby (when you enable it in the options page). // For a list of symbols that you can use, see: @@ -1407,10 +1513,15 @@ }); // Translation: IRC message format when there is a time prefix. - return sprintf(translate("%(time)s %(message)s"), { - "time": senderFont(timePrefixString), - "message": formattedMessage - }); + msg.formatted = coloredText( + sprintf(translate("%(time)s %(message)s"), { + "time": senderFont(timePrefixString), + "message": formattedMessage + }), + setColorBrightness("255 255 255", brightness) + ); + + return true; } /** Index: binaries/data/mods/public/gui/options/options.json =================================================================== --- binaries/data/mods/public/gui/options/options.json +++ binaries/data/mods/public/gui/options/options.json @@ -423,6 +423,12 @@ "label": "Game Rating Column", "tooltip": "Show the average rating of the participating players in a column of the gamelist.", "config": "lobby.columns.gamerating" + }, + { + "type": "boolean", + "label": "Chat Sender Presence", + "tooltip": "Show brightness of messages in chat according to the presence of the sender.", + "config": "lobby.chatsenderpresence" } ] },