Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -365,6 +365,9 @@ server = "lobby.wildfiregames.com" ; Address of lobby server xpartamupp = "wfgbot22" ; Name of the server-side xmpp client that manage games +[lobby.columns] +averagerating = false ; Show the average rating of the participating players in a column of the gamelist + [mod] enabledmods = "mod public" 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 @@ -219,10 +219,35 @@ Engine.LobbyClearPresenceUpdates(); updatePlayerList(); updateSubject(Engine.LobbyGetRoomSubject()); + updateLobbyColumns(); Engine.GetGUIObjectByName("chatInput").tooltip = colorizeAutocompleteHotkey(); } +function updateLobbyColumns() +{ + let averageRating = Engine.ConfigDB_GetValue("user", "lobby.columns.averagerating") == "true"; + + // Only show the selected columns + let gamesBox = Engine.GetGUIObjectByName("gamesBox"); + gamesBox.hidden_mapType = averageRating; + gamesBox.hidden_averageRating = !averageRating; + + // Only show the filters of selected columns + let mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter"); + mapTypeFilter.hidden = averageRating; + let averageRatingFilter = Engine.GetGUIObjectByName("averageRatingFilter"); + averageRatingFilter.hidden = !averageRating; + + // Keep filters right above the according column + // TODO: Ideally we could read the size of the columns of the COList instead + let playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter"); + let size = playersNumberFilter.size; + size.rleft = averageRating ? 74: 90; + size.rright = averageRating ? 84: 100; + playersNumberFilter.size = size; +} + function returnToMainMenu() { Engine.StopXmppClient(); @@ -244,6 +269,16 @@ mapTypeFilter.list = [translateWithContext("map", "Any")].concat(g_MapTypes.Title); mapTypeFilter.list_data = [""].concat(g_MapTypes.Name); + let averageRatingOptions = ["<1000", "<1100","<1200",">1200",">1300",">1400",">1500"].reverse(); + averageRatingOptions = prepareForDropdown(averageRatingOptions.map(r => ({ + "value": r, + "label": sprintf(r[0] == ">" ? translate("> %(rating)s") : translate("< %(rating)s"), { "rating": r.substr(1) }) + }))) + + let averageRatingFilter = Engine.GetGUIObjectByName("averageRatingFilter"); + averageRatingFilter.list = [translateWithContext("map", "Any")].concat(averageRatingOptions.label); + averageRatingFilter.list_data = [""].concat(averageRatingOptions.value); + resetFilters(); } @@ -252,6 +287,7 @@ Engine.GetGUIObjectByName("mapSizeFilter").selected = 0; Engine.GetGUIObjectByName("playersNumberFilter").selected = 0; Engine.GetGUIObjectByName("mapTypeFilter").selected = g_MapTypes.Default; + Engine.GetGUIObjectByName("averageRatingFilter").selected = 0; Engine.GetGUIObjectByName("showFullFilter").checked = false; applyFilters(); @@ -274,6 +310,7 @@ let mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); let playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter"); let mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter"); + let averageRatingFilter = Engine.GetGUIObjectByName("averageRatingFilter"); let showFullFilter = Engine.GetGUIObjectByName("showFullFilter"); // We assume index 0 means display all for any given filter. @@ -292,6 +329,14 @@ if (!showFullFilter.checked && game.maxnbp <= game.nbp) return true; + if (averageRatingFilter.selected > 0) + { + let selected = averageRatingFilter.list_data[averageRatingFilter.selected]; + if (selected.startsWith(">") && +selected.substr(1) >= game.averageRating || + selected.startsWith("<") && +selected.substr(1) <= game.averageRating) + return true; + } + return false; } @@ -530,19 +575,44 @@ g_SelectedGamePort = g_GameList[gamesBox.selected].port; } - g_GameList = Engine.GetGameList().filter(game => !filterGame(game)).sort((a, b) => { + let testList = [{name:"Partida de pablek2",ip:"bla",port:"20595",state:"running",nbp:"2",maxnbp:"2",players:'{"0":\\[{"Name":"pablek2 (1269)","Team":0}],"1":\\[{"Name":"Raulyo (875)","Team":1}]}',mapName:"maps/random/marmara",niceMapName:"Marmara",mapSize:"320",mapType:"random",victoryCondition:"Conquista",startTime:"1487082783"},{name:"Reaperisonline's game",ip:"bla",port:"20595",state:"waiting",nbp:"5",maxnbp:"6",players:'{"0":\\[{"Name":"Reaperisonline (1259)","Team":0},{"Name":"Sh4d0w3rPL (1387)","Team":0}],"1":\\[{"Name":"jtggamer (1103)","Team":1},{"Name":"temik5 (1033)","Team":1},{"Name":"WGold (1180)","Team":1},{"Name":"siliboss (1143)","Team":1,"Offline":true,"State":"defeated"}]}',mapName:"maps/random/fortress",niceMapName:"Fortress",mapSize:"320",mapType:"random",victoryCondition:"Conquest",startTime:"1487081792"},{name:"siole's game",ip:"blabla",port:"20595",state:"running",nbp:"8",maxnbp:"8",players:'{"-1":\\[{"Name":"siole","Team":-1},{"Name":"Grugnas (1417)","Team":-1},{"Name":"merlin1 (1564)","Team":-1},{"Name":"snelius (1365)","Team":-1},{"Name":"imrobbyg (1622)","Team":-1},{"Name":"PrincipalityOfZeon (1652)","Team":-1},{"Name":"jaxtrx2 (1030)","Team":-1},{"Name":"nigel87 (1530)","Team":-1}],"observer":\\[{"Name":"fpre (1545)","Team":"observer"},{"Name":"Achelao (1418)","Team":"observer"},{"Name":"elexis3 (1156)","Team":"observer"}]}',mapName:"maps/random/survivalofthefittest",niceMapName:"Survival of the Fittest",mapSize:"384",mapType:"random",victoryCondition:"Regicide",startTime:"1486938980"},{name:"1v1",ip:"blabla",port:"20595",state:"running",nbp:"2",maxnbp:"2",players:'{"-1":\\[{"Name":"borg- (2114)","Team":-1},{"Name":"Feldfeld (1638)","Team":-1}]}',mapName:"maps/random/mainland",niceMapName:"Mainland",mapSize:"384",mapType:"random",victoryCondition:"Conquest",startTime:"1486532180"},{name:"Partida de JeanClaude",ip:"blabla",port:"20595",state:"running",nbp:"2",maxnbp:"2",players:'{"-1":\\[{"Name":"JeanClaude (1653)","Team":-1},{"Name":"Liberty (1569)","Team":-1}]}',mapName:"maps/random/mainland",niceMapName:"Mainland",mapSize:"384",mapType:"random",victoryCondition:"Conquest",startTime:"1486523148"},{name:"Komenoss spel",ip:"blabla",port:"20595",state:"running",nbp:"4",maxnbp:"4",players:'{"-1":\\[{"Name":"Komenos","Team":-1},{"Name":"Jordi_Iranzo93 (1278)","Team":-1},{"Name":"META-BARONS (1393)","Team":-1},{"Name":"King_Jerusalem (1265)","Team":-1}]}',mapName:"maps/random/bahrain",niceMapName:"Bahrain",mapSize:"384",mapType:"random",victoryCondition:"Erövring",startTime:"1487084910"},{name:"Partida de freedomm",ip:"blabla",port:"20595",state:"running",nbp:"2",maxnbp:"2",players:'{"0":\\[{"Name":"freedomm (1298)","Team":0}],"1":\\[{"Name":"bill13986 (1028)","Team":1}]}',mapName:"maps/skirmishes/Lorraine Plain (2)",niceMapName:"Lorraine Plain (2)",mapSize:"Default",mapType:"skirmish",victoryCondition:"Conquista",startTime:"1487085137"},{name:"Partida de comandantelobo",ip:"blabla",port:"20595",state:"running",nbp:"4",maxnbp:"4",players:'{"0":\\[{"Name":"comandantelobo (1047)","Team":0},{"Name":"perro (1153)","Team":0}],"1":\\[{"Name":"akshayhalasangi","Team":1},{"Name":"sckt (1263)","Team":1}]}',mapName:"maps/random/mainland",niceMapName:"Mainland",mapSize:"256",mapType:"random",victoryCondition:"Conquista",startTime:"1487083419"},{name:"Partie de mazert",ip:"blabla",port:"20595",state:"init",nbp:"4",maxnbp:"4",players:'{"-1":\\[{"Name":"mazert (1224)"},{"Name":"HirnWolf (1149)"},{"Name":"siliboss (1143)"},{"Name":"Ivaylo_Uzunov (1085)"}]}',mapName:"maps/random/frontier",niceMapName:"Frontier",mapSize:"256",mapType:"random",victoryCondition:"Conquête",startTime:""},{name:"mord's game",ip:"blabla",port:"20595",state:"running",nbp:"2",maxnbp:"2",players:'{"0":\\[{"Name":"mord (1165)","Team":0}],"1":\\[{"Name":"Chrisf1 (1222)","Team":1}]}',mapName:"maps/random/unknown",niceMapName:"Unknown",mapSize:"256",mapType:"random",victoryCondition:"Conquest",startTime:"1487082144"},{name:"craqqy's game",ip:"blabla",port:"20595",state:"running",nbp:"2",maxnbp:"2",players:'{"-1":\\[{"Name":"craqqy","Team":-1},{"Name":"KeaneY","Team":-1}]}',mapName:"maps/skirmishes/Acropolis Bay (2)",niceMapName:"Acropolis Bay (2)",mapSize:"Default",mapType:"skirmish",victoryCondition:"Conquest",startTime:"1487084573"},{name:"GummionkelchenIsBack's Spiel",ip:"blabla",port:"20595",state:"waiting",nbp:"5",maxnbp:"6",players:'{"0":\\[{"Name":"GummionkelchenIsBack (1396)","Team":0},{"Name":"Jordi_Iranzo93 (1278)","Team":0,"Offline":true},{"Name":"zDiegoAC","Team":0}],"1":\\[{"Name":"yashbanka123 (1141)","Team":1},{"Name":"TheRolle","Team":1},{"Name":"Rexosts (1342)","Team":1}]}',mapName:"maps/random/unknown_land",niceMapName:"Unknown Land",mapSize:"256",mapType:"random",victoryCondition:"Eroberung",startTime:"1487084209"},{name:"noobiel's game",ip:"blabla",port:"20595",state:"running",nbp:"1",maxnbp:"2",players:'{"-1":\\[{"Name":"noobiel","Team":-1},{"Name":"Ptolemy Epigone","Team":-1,"AI":"petra","AIDiff":3}]}',mapName:"maps/skirmishes/Greek Acropolis (2)",niceMapName:"Greek Acropolis (2)",mapSize:"Default",mapType:"skirmish",victoryCondition:"Conquest",startTime:"1486821890"}]; + + g_GameList = /*Engine.GetGameList()*/testList.map(game => { + + game.niceMapName = translateMapTitle(game.niceMapName); + + // Compute average rating of participating clients + let playerRatings = []; + for (let player of stringifiedTeamListToPlayerData(game.players)) + { + if (player.Team == "observer") + continue; + + let result = /^\S+\ \((\d+)\)$/g.exec(player.Name); + playerRatings.push(result ? +result[1] : 1200); + } + + game.averageRating = + playerRatings.length ? + Math.round(playerRatings.reduce((sum, current) => sum + current) / playerRatings.length) : + 1200; + + return game; + + }).filter(game => !filterGame(game)).sort((a, b) => { let sortA, sortB; switch (sortBy) { case 'name': case 'mapSize': case 'mapType': + case 'averageRating': sortA = a[sortBy]; sortB = b[sortBy]; break; case 'mapName': - sortA = translate(a.niceMapName); - sortB = translate(b.niceMapName); + sortA = a.niceMapName; + sortB = b.niceMapName; break; case 'nPlayers': sortA = a.maxnbp; @@ -564,6 +634,7 @@ let list_mapSize = []; let list_mapType = []; let list_nPlayers = []; + let list_averageRating = []; let list = []; let list_data = []; let selectedGameIndex = -1; @@ -578,10 +649,11 @@ selectedGameIndex = +i; list_name.push('[color="' + g_GameColors[game.state] + '"]' + gameName); - list_mapName.push(translateMapTitle(game.niceMapName)); + list_mapName.push(game.niceMapName); list_mapSize.push(translateMapSize(game.mapSize)); list_mapType.push(g_MapTypes.Title[mapTypeIdx] || ""); list_nPlayers.push(game.nbp + "/" + game.maxnbp); + list_averageRating.push(game.averageRating); list.push(gameName); list_data.push(i); } @@ -591,6 +663,8 @@ gamesBox.list_mapSize = list_mapSize; gamesBox.list_mapType = list_mapType; gamesBox.list_nPlayers = list_nPlayers; + gamesBox.list_averageRating = list_averageRating; + // Change these last, otherwise crash gamesBox.list = list; gamesBox.list_data = list_data; @@ -613,7 +687,7 @@ if (!game) return; - Engine.GetGUIObjectByName("sgMapName").caption = translateMapTitle(game.niceMapName); + Engine.GetGUIObjectByName("sgMapName").caption = game.niceMapName; let sgGameStartTime = Engine.GetGUIObjectByName("sgGameStartTime"); let sgNbPlayers = Engine.GetGUIObjectByName("sgNbPlayers"); Index: binaries/data/mods/public/gui/lobby/lobby.xml =================================================================== --- binaries/data/mods/public/gui/lobby/lobby.xml +++ binaries/data/mods/public/gui/lobby/lobby.xml @@ -199,7 +199,7 @@ updateGameSelection(); applyFilters(); joinButton(); - + Name @@ -211,16 +211,20 @@ Type - + Players + + Rating + + applyFilters(); @@ -228,7 +232,7 @@ applyFilters(); @@ -236,7 +240,15 @@ + applyFilters(); + + + applyFilters(); @@ -252,6 +264,7 @@ font="sans-bold-13"> applyFilters(); + 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 @@ -257,6 +257,12 @@ "label": "Chat Backlog", "tooltip": "Number of backlogged messages to load when joining the lobby", "parameters": { "config": "lobby.history", "min": "0" } + }, + { + "type": "boolean", + "label": "Average Player Rating Column", + "tooltip": "Show the average rating of the participating players in a column of the gamelist.", + "parameters": { "config": "lobby.columns.averagerating" } } ] } Index: source/gui/COList.cpp =================================================================== --- source/gui/COList.cpp +++ source/gui/COList.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -148,6 +148,11 @@ float xpos = 0; for (COListColumn column : m_Columns) { + bool hidden = false; + GUI::GetSetting(this, "hidden_" + column.m_Id, hidden); + if (hidden) + continue; + float width = column.m_Width; // Check if it's a decimal value, and if so, assume relative positioning. if (column.m_Width < 1 && column.m_Width > 0) @@ -271,6 +276,8 @@ m_Columns.push_back(column); AddSetting(GUIST_CGUIList, "list_" + column.m_Id); + AddSetting(GUIST_bool, "hidden_" + column.m_Id); + SetupText(); return true; @@ -372,6 +379,11 @@ float xpos = 0; for (size_t col = 0; col < m_Columns.size(); ++col) { + bool hidden = false; + GUI::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden); + if (hidden) + continue; + // Check if it's a decimal value, and if so, assume relative positioning. float width = m_Columns[col].m_Width; if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0) @@ -432,6 +444,11 @@ xpos = 0; for (size_t col = 0; col < objectsCount; ++col) { + bool hidden = false; + GUI::GetSetting(this, "hidden_" + m_Columns[col].m_Id, hidden); + if (hidden) + continue; + // Determine text position and width const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]);