Ticket #3143: 3143_lobby_data_v3.patch

File 3143_lobby_data_v3.patch, 20.4 KB (added by elexis, 8 years ago)

Fixes what was encountered and can be fixed without rewriting or extending a lot of c++ lobby / XML code. Didn't test yet and didn't review some parts of the patch yet.

  • binaries/data/mods/public/gui/common/functions_utility.js

    function sortNameIgnoreCase(x, y)  
    5757
    5858/**
    5959 * Escape tag start and escape characters, so users cannot use special formatting.
    6060 * Also limit string length to 256 characters (not counting escape characters).
    6161 */
    62 function escapeText(text)
     62function escapeText(text, limitLength = true)
    6363{
    6464    if (!text)
    6565        return text;
    6666
    67     return text.substr(0, 255).replace(/\\/g, "\\\\").replace(/\[/g, "\\[");
     67    if (limitLength)
     68        text = text.substr(0, 255);
     69
     70    return text.replace(/\\/g, "\\\\").replace(/\[/g, "\\[");
     71}
     72
     73function unescapeText(text)
     74{
     75    if (!text)
     76        return text;
     77    return text.replace(/\\\\/g, "\\").replace(/\\\[/g, "\[");
     78}
     79
     80/**
     81 * Merge players by team to remove duplicate Team entries, thus reducing the packet size of the lobby report.
     82 */
     83function playerDataToStringifiedTeamList(playerData)
     84{
     85    let teamList = {};
     86
     87    for (let pData of playerData)
     88    {
     89        let team = pData.Team === undefined ? -1 : pData.Team;
     90        if (!teamList[team])
     91            teamList[team] = [];
     92        teamList[team].push(pData);
     93        delete teamList[team].Team;
     94    }
     95
     96    return escapeText(JSON.stringify(teamList), false);
     97}
     98
     99function stringifiedTeamListToPlayerData(stringifiedTeamList)
     100{
     101    let teamList = JSON.parse(unescapeText(stringifiedTeamList));
     102    let playerData = [];
     103
     104    for (let team in teamList)
     105        for (let pData of teamList[team])
     106        {
     107            pData.Team = +team;
     108            playerData.push(pData);
     109        }
     110
     111    return playerData;
    68112}
    69113
    70114function translateMapTitle(mapTitle)
    71115{
    72116    return mapTitle == "random" ? translateWithContext("map selection", "Random") : translate(mapTitle);
    function formatPlayerInfo(playerDataArra  
    224268    let playerDescriptions = {};
    225269    let playerIdx = 0;
    226270
    227271    for (let playerData of playerDataArray)
    228272    {
    229         if (playerData == null || playerData.Civ == "gaia")
     273        if (playerData == null || playerData.Civ && playerData.Civ == "gaia")
    230274            continue;
    231275
    232276        ++playerIdx;
    233277        let teamIdx = playerData.Team;
    234278        let isAI = playerData.AI && playerData.AI != "";
    235         let playerState = playerStates && playerStates[playerIdx];
     279        let playerState = playerStates && playerStates[playerIdx] || playerData.State;
    236280        let isActive = !playerState || playerState == "active";
     281        let isOffline = playerData.Offline;
    237282
    238283        let playerDescription;
    239284        if (isAI)
    240285        {
    241             if (isActive)
    242                 // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
    243                 playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)");
     286            if (playerData.Civ)
     287            {
     288                if (isActive)
     289                    // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     290                    playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)");
     291                else
     292                    // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     293                    playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, %(state)s)");
     294            }
    244295            else
    245                 // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
    246                 playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, %(state)s)");
     296            {
     297                if (isActive)
     298                    // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     299                    playerDescription = translate("%(playerName)s (%(AIdifficulty)s %(AIname)s)");
     300                else
     301                    // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     302                    playerDescription = translate("%(playerName)s (%(AIdifficulty)s %(AIname)s, %(state)s)");
     303            }
    247304        }
    248305        else
    249306        {
    250             if (isActive)
    251                 // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
    252                 playerDescription = translate("%(playerName)s (%(civ)s)");
     307            // Can only occur in the lobby for now, so no strings with civ needed
     308            if (isOffline)
     309            {
     310                if (isActive)
     311                    // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     312                    playerDescription = translate("%(playerName)s (OFFLINE)");
     313                else
     314                    // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     315                    playerDescription = translate("%(playerName)s (OFFLINE, %(state)s)");
     316            }
    253317            else
    254                 // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
    255                 playerDescription = translate("%(playerName)s (%(civ)s, %(state)s)");
     318            {
     319                if (playerData.Civ)
     320                    if (isActive)
     321                        // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     322                        playerDescription = translate("%(playerName)s (%(civ)s)");
     323                    else
     324                        // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     325                        playerDescription = translate("%(playerName)s (%(civ)s, %(state)s)");
     326                else
     327                    if (isActive)
     328                        // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     329                        playerDescription = translate("%(playerName)s");
     330                    else
     331                        // Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
     332                        playerDescription = translate("%(playerName)s (%(state)s)");
     333            }
    256334        }
    257335
    258336        // Sort player descriptions by team
    259337        if (!playerDescriptions[teamIdx])
    260338            playerDescriptions[teamIdx] = [];
    261339
    262340        playerDescriptions[teamIdx].push(sprintf(playerDescription, {
    263341            "playerName":
    264342                '[color="' +
    265                 rgbToGuiColor(playerData.Color || g_Settings.PlayerDefaults[playerIdx].Color) +
     343                (typeof getPlayerColor == 'function' ?
     344                    (isAI ?
     345                        "255 255 255" :
     346                        getPlayerColor(playerData.Name)) :
     347                    rgbToGuiColor(playerData.Color || g_Settings.PlayerDefaults[playerIdx].Color)) +
    266348                '"]' + escapeText(playerData.Name) + "[/color]",
    267349
    268350            "civ":
    269351                !playerData.Civ ?
    270352                    translate("Unknown Civilization") :
    function formatPlayerInfo(playerDataArra  
    281363            "AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : ""
    282364        }));
    283365    }
    284366
    285367    let teams = Object.keys(playerDescriptions);
     368    if (teams.indexOf("observer") > -1)
     369        teams.splice(teams.indexOf("observer"), 1);
     370
     371    let teamDescription = [];
    286372
    287373    // If there are no teams, merge all playersDescriptions
    288374    if (teams.length == 1)
    289         return playerDescriptions[teams[0]].join("\n") + "\n";
     375        teamDescription.push(playerDescriptions[teams[0]].join("\n"));
    290376
    291377    // If there are teams, merge "Team N:" + playerDescriptions
    292     return teams.map(team => {
     378    else
     379        teamDescription = teamDescription.concat(teams.map(team => {
     380
     381            let teamCaption = team == -1 ?
     382                translate("No Team") :
     383                sprintf(translate("Team %(team)s"), { "team": +team + 1 });
     384
     385            // Translation: Describe players of one team in a selected game, f.e. in the replay- or savegame menu or lobby
     386            return sprintf(translate("%(team)s:\n%(playerDescriptions)s"), {
     387                "team": '[font="sans-bold-14"]' + teamCaption + "[/font]",
     388                "playerDescriptions": playerDescriptions[team].join("\n")
     389            });
     390        }));
     391
     392    if (playerDescriptions.observer)
     393        teamDescription.push(sprintf(translate("%(team)s:\n%(playerDescriptions)s"), {
     394            "team": '[font="sans-bold-14"]' + translatePlural("Observer", "Observers", playerDescriptions.observer.length) + "[/font]",
     395            "playerDescriptions": playerDescriptions.observer.join("\n")
     396        }));
    293397
    294         let teamCaption = team == -1 ?
    295             translate("No Team") :
    296             sprintf(translate("Team %(team)s"), { "team": +team + 1 });
    297 
    298         // Translation: Describe players of one team in a selected game, f.e. in the replay- or savegame menu or lobby
    299         return sprintf(translate("%(team)s:\n%(playerDescriptions)s"), {
    300             "team": '[font="sans-bold-14"]' + teamCaption + "[/font]",
    301             "playerDescriptions": playerDescriptions[team].join("\n")
    302         });
    303     }).join("\n\n");
     398    return teamDescription.join("\n\n");
    304399}
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

    function handleReadyMessage(message)  
    676676 */
    677677function handleGamestartMessage(message)
    678678{
    679679    if (g_IsController && Engine.HasXmppClient())
    680680    {
    681         let playerNames = Object.keys(g_PlayerAssignments).map(guid => g_PlayerAssignments[guid].name);
    682         Engine.SendChangeStateGame(playerNames.length, playerNames.join(", "));
     681        let clients = formatClientsForStanza();
     682        Engine.SendChangeStateGame(clients.connectedPlayers, clients.list);
    683683    }
    684684
    685685    Engine.SwitchGuiPage("page_loading.xml", {
    686686        "attribs": g_GameAttributes,
    687687        "isNetworked" : g_IsNetworked,
    function resetReadyData()  
    19371937    else
    19381938        setReady(false, false);
    19391939}
    19401940
    19411941/**
     1942 * Don't send teams until the game was started.
     1943 * The playerData format from g_GameAttributes is kept to reuse the GUI function presenting the data.
     1944 */
     1945function formatClientsForStanza()
     1946{
     1947    let connectedPlayers = 0;
     1948    let list = [];
     1949
     1950    for (let guid in g_PlayerAssignments)
     1951    {
     1952        if (g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1])
     1953        {
     1954            ++connectedPlayers;
     1955            list.push({
     1956                "Name": g_PlayerAssignments[guid].name
     1957            });
     1958        }
     1959        else
     1960            list.push({
     1961                "Name": g_PlayerAssignments[guid].name,
     1962                "Team": "observer"
     1963            });
     1964    }
     1965
     1966    return {
     1967        "list": playerDataToStringifiedTeamList(list),
     1968        "connectedPlayers": connectedPlayers
     1969    };
     1970}
     1971
     1972/**
    19421973 * Send the relevant gamesettings to the lobbybot.
    19431974 */
    19441975function sendRegisterGameStanza()
    19451976{
    19461977    if (!g_IsController || !Engine.HasXmppClient())
    function sendRegisterGameStanza()  
    19491980    let selectedMapSize = Engine.GetGUIObjectByName("mapSize").selected;
    19501981    let selectedVictoryCondition = Engine.GetGUIObjectByName("victoryCondition").selected;
    19511982
    19521983    let mapSize = g_GameAttributes.mapType == "random" ? Engine.GetGUIObjectByName("mapSize").list_data[selectedMapSize] : "Default";
    19531984    let victoryCondition = Engine.GetGUIObjectByName("victoryCondition").list[selectedVictoryCondition];
    1954     let playerNames = Object.keys(g_PlayerAssignments).map(guid => g_PlayerAssignments[guid].name).sort();
     1985    let clients = formatClientsForStanza();
    19551986
    19561987    let stanza = {
    19571988        "name": g_ServerName,
    19581989        "port": g_ServerPort,
    19591990        "mapName": g_GameAttributes.map,
    19601991        "niceMapName": getMapDisplayName(g_GameAttributes.map),
    19611992        "mapSize": mapSize,
    19621993        "mapType": g_GameAttributes.mapType,
    19631994        "victoryCondition": victoryCondition,
    1964         "nbp": Object.keys(g_PlayerAssignments).length || 1,
    1965         "tnbp": g_GameAttributes.settings.PlayerData.length,
    1966         "players": playerNames.join(", ")
     1995        "nbp": clients.connectedPlayers,
     1996        "maxnbp": g_GameAttributes.settings.PlayerData.length,
     1997        "players": clients.list,
    19671998    };
    19681999
    19692000    // Only send the stanza if the relevant settings actually changed
    19702001    if (g_LastGameStanza && Object.keys(stanza).every(prop => g_LastGameStanza[prop] == stanza[prop]))
    19712002        return;
  • binaries/data/mods/public/gui/lobby/lobby.js

    function filterGame(game)  
    283283    if (mapSizeFilter.selected != 0 &&
    284284        game.mapSize != mapSizeFilter.list_data[mapSizeFilter.selected])
    285285        return true;
    286286
    287287    if (playersNumberFilter.selected != 0 &&
    288         game.tnbp != playersNumberFilter.list_data[playersNumberFilter.selected])
     288        game.maxnbp != playersNumberFilter.list_data[playersNumberFilter.selected])
    289289        return true;
    290290
    291291    if (mapTypeFilter.selected != 0 &&
    292292        game.mapType != mapTypeFilter.list_data[mapTypeFilter.selected])
    293293        return true;
    294294
    295     if (!showFullFilter.checked && game.tnbp <= game.nbp)
     295    if (!showFullFilter.checked && game.maxnbp <= game.nbp)
    296296        return true;
    297297
    298298    return false;
    299299}
    300300
    function updateGameList()  
    544544            sortA = translate(a.niceMapName);
    545545            sortB = translate(b.niceMapName);
    546546            break;
    547547        case 'nPlayers':
    548548            // Compare playercount ratio
    549             sortA = a.nbp * b.tnbp;
    550             sortB = b.nbp * a.tnbp;
     549            sortA = a.nbp * b.maxnbp;
     550            sortB = b.nbp * a.maxnbp;
    551551            break;
    552552        case 'status':
    553553        default:
    554554            sortA = g_GameStatusOrder.indexOf(a.state);
    555555            sortB = g_GameStatusOrder.indexOf(b.state);
    function updateGameList()  
    580580
    581581        list_name.push('[color="' + g_GameColors[game.state] + '"]' + gameName);
    582582        list_mapName.push(translateMapTitle(game.niceMapName));
    583583        list_mapSize.push(translateMapSize(game.mapSize));
    584584        list_mapType.push(g_MapTypes.Title[mapTypeIdx] || "");
    585         list_nPlayers.push(game.nbp + "/" + game.tnbp);
     585        list_nPlayers.push(game.nbp + "/" + game.maxnbp);
    586586        list.push(gameName);
    587587        list_data.push(i);
    588588    }
    589589
    590590    gamesBox.list_name = list_name;
    function updateGameSelection()  
    614614    if (!game)
    615615        return;
    616616
    617617    Engine.GetGUIObjectByName("sgMapName").caption = translateMapTitle(game.niceMapName);
    618618    Engine.GetGUIObjectByName("sgNbPlayers").caption = sprintf(
    619         translate("Players: %(current)s/%(total)s"), {
     619        translate("Players: %(current)s/%(max)s"), {
    620620            "current": game.nbp,
    621             "total": game.tnbp
     621            "max": game.maxnbp
    622622        });
    623623
    624     Engine.GetGUIObjectByName("sgPlayersNames").caption = game.players;
     624    Engine.GetGUIObjectByName("sgPlayersNames").caption = formatPlayerInfo(stringifiedTeamListToPlayerData(game.players));
    625625    Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(game.mapSize);
    626626
    627627    let mapTypeIdx = g_MapTypes.Name.indexOf(game.mapType);
    628628    Engine.GetGUIObjectByName("sgMapType").caption = g_MapTypes.Title[mapTypeIdx] || "";
    629629
    function joinButton()  
    651651    if (!game)
    652652        return;
    653653
    654654    let username = g_UserRating ? g_Username + " (" + g_UserRating + ")" : g_Username;
    655655
    656     if (game.state == "init" || game.players.split(", ").indexOf(username) > -1)
     656    if (game.state == "init" || stringifiedTeamListToPlayerData(game.players).some(player => player.Name == username))
    657657        joinSelectedGame();
    658658    else
    659659        messageBox(
    660660            400, 200,
    661661            translate("The game has already started.") + "\n" +
  • binaries/data/mods/public/gui/session/messages.js

    var g_NotificationsTypes =  
    248248            "guid": findGuidForPlayerID(player),
    249249            "player": player,
    250250            "resign": !!notification.resign
    251251        });
    252252        playerFinished(player, false);
     253        sendLobbyPlayerlistUpdate();
    253254    },
    254255    "won": function(notification, player)
    255256    {
    256257        addChatMessage({
    257258            "type": "won",
    258259            "guid": findGuidForPlayerID(player),
    259260            "player": player
    260261        });
    261262        playerFinished(player, true);
     263        sendLobbyPlayerlistUpdate();
    262264    },
    263265    "diplomacy": function(notification, player)
    264266    {
    265267        addChatMessage({
    266268            "type": "diplomacy",
    function handlePlayerAssignmentsMessage(  
    539541    joins.forEach(guid => {
    540542        onClientJoin(guid);
    541543    });
    542544
    543545    updateChatAddressees();
    544 
    545     // Update lobby gamestatus
    546     if (g_IsController && Engine.HasXmppClient())
    547     {
    548         let players = Object.keys(g_PlayerAssignments).map(guid => g_PlayerAssignments[guid].name);
    549         Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", "));
    550     }
     546    sendLobbyPlayerlistUpdate();
    551547}
    552548
    553549function onClientJoin(guid)
    554550{
    555551    let playerID = g_PlayerAssignments[guid].player;
  • binaries/data/mods/public/gui/session/session.js

    const g_PopulationAlertColor = "orange";  
    1313const g_Ambient = [ "audio/ambient/dayscape/day_temperate_gen_03.ogg" ];
    1414
    1515/**
    1616 * Map, player and match settings set in gamesetup.
    1717 */
    18 var g_GameAttributes;
     18const g_GameAttributes = Object.freeze(Engine.GetInitAttributes());
    1919
    2020/**
    2121 * Is this user in control of game settings (i.e. is a network server, or offline player).
    2222 */
    2323var g_IsController;
    function init(initData, hotloadData)  
    223223        Engine.EndGame();
    224224        Engine.SwitchGuiPage("page_pregame.xml");
    225225        return;
    226226    }
    227227
    228     g_GameAttributes = Engine.GetInitAttributes();
    229 
    230228    if (initData)
    231229    {
    232230        g_IsNetworked = initData.isNetworked;
    233231        g_IsController = initData.isController;
    234232        g_PlayerAssignments = initData.playerAssignments;
    function init(initData, hotloadData)  
    287285    initHotkeyTooltips();
    288286
    289287    if (hotloadData)
    290288        g_Selection.selected = hotloadData.selection;
    291289
     290    sendLobbyPlayerlistUpdate();
    292291    onSimulationUpdate();
    293 
    294292    setTimeout(displayGamestateNotifications, 1000);
    295293
    296294    // Report the performance after 5 seconds (when we're still near
    297295    // the initial camera view) and a minute (when the profiler will
    298296    // have settled down if framerates as very low), to give some
  • binaries/data/mods/public/gui/session/utility_functions.js

    function resourcesToAlphaMask(neededReso  
    129129        totalCost += +neededResources[resource];
    130130    var alpha = 50 + Math.round(+totalCost/10.0);
    131131    alpha = alpha > 125 ? 125 : alpha;
    132132    return "color:255 0 0 " + alpha;
    133133}
     134
     135/**
     136 * The playerData format from g_GameAttributes is kept to reuse the GUI function presenting the data.
     137 */
     138function sendLobbyPlayerlistUpdate()
     139{
     140    if (!g_IsController || !Engine.HasXmppClient())
     141        return;
     142
     143    // Extract the relevant player data and minimize packet load
     144    let output = [];
     145    for (let playerID in g_GameAttributes.settings.PlayerData)
     146    {
     147        let pData = g_GameAttributes.settings.PlayerData;
     148
     149        let outData = { "Name": pData.Name };
     150
     151        if (g_GameAttributes.settings.LockTeams)
     152            outData.Team = pData.Team;
     153
     154        if (pData.AI)
     155        {
     156            outData.AI = pData.AI;
     157            outData.AIDiff = pData.AIDiff;
     158        }
     159
     160        if (g_Players[playerID + 1].Offline)
     161            outData.Offline = true;
     162
     163        // Whether the player has won or was defeated
     164        let state = g_Players[playerID + 1].State;
     165        if (state != "active")
     166            outData.State = state;
     167
     168        output.push(outData);
     169    }
     170
     171    // Add observers
     172    let connectedPlayers = 0;
     173    for (let guid in g_PlayerAssignments)
     174    {
     175        let pData = g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1];
     176
     177        if (pData)
     178            ++connectedPlayers;
     179        else
     180            playerData.push({
     181                "Name": g_PlayerAssignments[guid].name,
     182                "Team": "observer"
     183            });
     184    }
     185
     186    Engine.SendChangeStateGame(connectedPlayers, playerDataToStringifiedTeamList(output));
     187}
  • source/lobby/XmppClient.cpp

    void XmppClient::GUIGetGameList(ScriptIn  
    500500{
    501501    JSContext* cx = scriptInterface.GetContext();
    502502    JSAutoRequest rq(cx);
    503503
    504504    scriptInterface.Eval("([])", ret);
    505     const char* stats[] = { "name", "ip", "port", "state", "nbp", "tnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition" };
     505    const char* stats[] = { "name", "ip", "port", "state", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition" };
    506506    for(const glooxwrapper::Tag* const& t : m_GameList)
    507507    {
    508508        JS::RootedValue game(cx);
    509509        scriptInterface.Eval("({})", &game);
    510510
  • source/tools/XpartaMuPP/XpartaMuPP.py

    class GameList():  
    299299    """
    300300    JID = str(JID)
    301301    if JID in self.gameList:
    302302      if self.gameList[JID]['nbp-init'] > data['nbp']:
    303303        logging.debug("change game (%s) state from %s to %s", JID, self.gameList[JID]['state'], 'waiting')
    304         self.gameList[JID]['nbp'] = data['nbp']
    305304        self.gameList[JID]['state'] = 'waiting'
    306305      else:
    307306        logging.debug("change game (%s) state from %s to %s", JID, self.gameList[JID]['state'], 'running')
    308         self.gameList[JID]['nbp'] = data['nbp']
    309307        self.gameList[JID]['state'] = 'running'
     308      self.gameList[JID]['nbp'] = data['nbp']
     309      self.gameList[JID]['players'] = data['players']
    310310
    311311## Class which manages different game reports from clients ##
    312312##   and calls leaderboard functions as appropriate.       ##
    313313class ReportManager():
    314314  def __init__(self, leaderboard):