Index: ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/private.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/private.png
===================================================================
--- ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/private.png (nonexistent)
+++ ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/private.png (revision 24795)
Property changes on: ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/private.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/public.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/public.png
===================================================================
--- ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/public.png (nonexistent)
+++ ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/public.png (revision 24795)
Property changes on: ps/trunk/binaries/data/mods/public/art/textures/ui/lobby/public.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js (revision 24794)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/Game.js (revision 24795)
@@ -1,338 +1,340 @@
/**
* This class represents a multiplayer match hosted by a player in the lobby.
* Having this represented as a class allows to leverage significant performance
* gains by caching computed, escaped, translated strings and sorting keys.
*
* Additionally class representation allows implementation of events such as
* a new match being hosted, a match having ended, or a buddy having joined a match.
*
* Ensure that escapeText is applied to player controlled data for display.
*
* Users of the properties of this class:
* GameList, GameDetails, MapFilters, JoinButton, any user of GameList.selectedGame()
*/
class Game
{
constructor(mapCache)
{
this.mapCache = mapCache;
// Stanza data, object with exclusively string values
// Used to compare which part of the stanza data changed,
// perform partial updates and trigger event notifications.
this.stanza = {};
for (let name of this.StanzaKeys)
this.stanza[name] = "";
// This will be displayed in the GameList and GameDetails
// Important: Player input must be processed with escapeText
this.displayData = {
"tags": {}
};
// Cache the values used for sorting
this.sortValues = {
"state": "",
"compatibility": "",
"hasBuddyString": ""
};
// Array of objects, result of stringifiedTeamListToPlayerData
this.players = [];
// Whether the current player has the same mods launched as the host of this game
this.isCompatible = undefined;
// Used to display which mods are missing if the player attempts a join
this.mods = undefined;
// Used by the rating column and rating filer
this.gameRating = undefined;
// 'Persistent temporary' sprintf arguments object to avoid repeated object construction
this.playerCountArgs = {};
}
/**
* Called from GameList to ensure call order.
*/
onBuddyChange()
{
this.updatePlayers(this.stanza);
}
/**
* This function computes values that will either certainly or
* most likely be used later (i.e. by filtering, sorting and gamelist display).
*
* The performance benefit arises from the fact that for a new gamelist stanza
* many if not most games and game properties did not change.
*/
update(newStanza, sortKey)
{
let oldStanza = this.stanza;
let displayData = this.displayData;
let sortValues = this.sortValues;
if (oldStanza.name != newStanza.name)
{
Engine.ProfileStart("gameName");
sortValues.gameName = newStanza.name.toLowerCase();
this.updateGameName(newStanza);
Engine.ProfileStop();
}
if (oldStanza.state != newStanza.state)
{
Engine.ProfileStart("gameState");
this.updateGameTags(newStanza);
sortValues.state = this.GameStatusOrder.indexOf(newStanza.state);
Engine.ProfileStop();
}
if (oldStanza.niceMapName != newStanza.niceMapName)
{
Engine.ProfileStart("niceMapName");
displayData.mapName = escapeText(this.mapCache.translateMapName(newStanza.niceMapName));
displayData.mapDescription = this.mapCache.getTranslatedMapDescription(newStanza.mapType, newStanza.mapName);
Engine.ProfileStop();
}
if (oldStanza.mapName != newStanza.mapName)
{
Engine.ProfileStart("mapName");
sortValues.mapName = displayData.mapName;
Engine.ProfileStop();
}
if (oldStanza.mapType != newStanza.mapType)
{
Engine.ProfileStart("mapType");
displayData.mapType = g_MapTypes.Title[g_MapTypes.Name.indexOf(newStanza.mapType)] || "";
sortValues.mapType = newStanza.mapType;
Engine.ProfileStop();
}
if (oldStanza.mapSize != newStanza.mapSize)
{
Engine.ProfileStart("mapSize");
displayData.mapSize = translateMapSize(newStanza.mapSize);
sortValues.mapSize = newStanza.mapSize;
Engine.ProfileStop();
}
let playersChanged = oldStanza.players != newStanza.players;
if (playersChanged)
{
Engine.ProfileStart("playerData");
this.updatePlayers(newStanza);
Engine.ProfileStop();
}
if (oldStanza.nbp != newStanza.nbp ||
oldStanza.maxnbp != newStanza.maxnbp ||
playersChanged)
{
Engine.ProfileStart("playerCount");
displayData.playerCount = this.getTranslatedPlayerCount(newStanza);
sortValues.maxnbp = newStanza.maxnbp;
Engine.ProfileStop();
}
if (oldStanza.mods != newStanza.mods)
{
Engine.ProfileStart("mods");
this.updateMods(newStanza);
Engine.ProfileStop();
}
+ displayData.private = newStanza.hasPassword ? '[icon="icon_private"]' : '[icon="icon_public"]';
+
this.stanza = newStanza;
this.sortValue = this.sortValues[sortKey];
}
updatePlayers(newStanza)
{
let players;
{
Engine.ProfileStart("stringifiedTeamListToPlayerData");
players = stringifiedTeamListToPlayerData(newStanza.players);
this.players = players;
Engine.ProfileStop();
}
{
Engine.ProfileStart("parsePlayers");
let observerCount = 0;
let hasBuddies = 0;
let playerRatingTotal = 0;
for (let player of players)
{
let playerNickRating = splitRatingFromNick(player.Name);
if (player.Team == "observer")
++observerCount;
else
playerRatingTotal += playerNickRating.rating || g_DefaultLobbyRating;
// Sort games with playing buddies above games with spectating buddies
if (hasBuddies < 2 && g_Buddies.indexOf(playerNickRating.nick) != -1)
hasBuddies = player.Team == "observer" ? 1 : 2;
}
this.observerCount = observerCount;
this.hasBuddies = hasBuddies;
let displayData = this.displayData;
let sortValues = this.sortValues;
displayData.buddy = this.hasBuddies ? setStringTags(g_BuddySymbol, displayData.tags) : "";
sortValues.hasBuddyString = String(hasBuddies);
sortValues.buddy = sortValues.hasBuddyString + sortValues.gameName;
let playerCount = players.length - observerCount;
let gameRating =
playerCount ?
Math.round(playerRatingTotal / playerCount) :
g_DefaultLobbyRating;
this.gameRating = gameRating;
sortValues.gameRating = gameRating;
Engine.ProfileStop();
}
}
updateMods(newStanza)
{
{
Engine.ProfileStart("JSON.parse");
try
{
this.mods = JSON.parse(newStanza.mods);
}
catch (e)
{
this.mods = [];
}
Engine.ProfileStop();
}
{
Engine.ProfileStart("hasSameMods");
let isCompatible = this.mods && hasSameMods(this.mods, Engine.GetEngineInfo().mods);
if (this.isCompatible != isCompatible)
{
this.isCompatible = isCompatible;
this.updateGameTags(newStanza);
this.sortValues.compatibility = String(isCompatible);
}
Engine.ProfileStop();
}
}
updateGameTags(newStanza)
{
let displayData = this.displayData;
displayData.tags = this.isCompatible ? this.StateTags[newStanza.state] : this.IncompatibleTags;
displayData.buddy = this.hasBuddies ? setStringTags(g_BuddySymbol, displayData.tags) : "";
this.updateGameName(newStanza);
}
updateGameName(newStanza)
{
let displayData = this.displayData;
displayData.gameName = setStringTags(escapeText(newStanza.name), displayData.tags);
let sortValues = this.sortValues;
sortValues.gameName = sortValues.compatibility + sortValues.state + sortValues.gameName;
sortValues.buddy = sortValues.hasBuddyString + sortValues.gameName;
}
getTranslatedPlayerCount(newStanza)
{
let playerCountArgs = this.playerCountArgs;
playerCountArgs.current = setStringTags(escapeText(newStanza.nbp), this.PlayerCountTags.CurrentPlayers);
playerCountArgs.max = setStringTags(escapeText(newStanza.maxnbp), this.PlayerCountTags.MaxPlayers);
let txt;
if (this.observerCount)
{
playerCountArgs.observercount = setStringTags(this.observerCount, this.PlayerCountTags.Observers);
txt = this.PlayerCountObservers;
}
else
txt = this.PlayerCountNoObservers;
return sprintf(txt, playerCountArgs);
}
}
/**
* These are all keys that occur in a gamelist stanza sent by XPartaMupp.
*/
Game.prototype.StanzaKeys = [
"name",
"hasPassword",
"hostUsername",
"state",
"nbp",
"maxnbp",
"players",
"mapName",
"niceMapName",
"mapSize",
"mapType",
"victoryConditions",
"startTime",
"mods"
];
/**
* Initial sorting order of the gamelist.
*/
Game.prototype.GameStatusOrder = [
"init",
"waiting",
"running"
];
// Translation: The number of players and observers in this game
Game.prototype.PlayerCountObservers = translate("%(current)s/%(max)s +%(observercount)s");
// Translation: The number of players in this game
Game.prototype.PlayerCountNoObservers = translate("%(current)s/%(max)s");
/**
* Compatible games will be listed in these colors.
*/
Game.prototype.StateTags = {
"init": {
"color": "0 219 0"
},
"waiting": {
"color": "255 127 0"
},
"running": {
"color": "219 0 0"
}
};
/**
* Games that require different mods than the ones launched by the current player are grayed out.
*/
Game.prototype.IncompatibleTags = {
"color": "gray"
};
/**
* Color for the player count number in the games list.
*/
Game.prototype.PlayerCountTags = {
"CurrentPlayers": {
"color": "0 160 160"
},
"MaxPlayers": {
"color": "0 160 160"
},
"Observers": {
"color": "0 128 128"
}
};
Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js (revision 24794)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.js (revision 24795)
@@ -1,222 +1,226 @@
/**
* Each property of this class handles one specific map filter and is defined in external files.
*/
class GameListFilters
{
}
/**
* This class displays the list of multiplayer matches that are currently being set up or running,
* filtered and sorted depending on player selection.
*/
class GameList
{
constructor(xmppMessages, buddyButton, mapCache)
{
this.mapCache = mapCache;
// Array of Game class instances, where the keys are ip+port strings, used for quick lookups
this.games = {};
// Array of Game class instances sorted by display order
this.gameList = [];
this.selectionChangeHandlers = new Set();
this.gamesBox = Engine.GetGUIObjectByName("gamesBox");
this.gamesBox.onSelectionChange = this.onSelectionChange.bind(this);
this.gamesBox.onSelectionColumnChange = this.onFilterChange.bind(this);
let ratingColumn = Engine.ConfigDB_GetValue("user", "lobby.columns.gamerating") == "true";
this.gamesBox.hidden_mapType = ratingColumn;
this.gamesBox.hidden_gameRating = !ratingColumn;
// Avoid repeated array construction
this.list_buddy = [];
+ this.list_private = [];
this.list_gameName = [];
this.list_mapName = [];
this.list_mapSize = [];
this.list_mapType = [];
this.list_maxnbp = [];
this.list_gameRating = [];
this.list = [];
this.filters = [];
for (let name in GameListFilters)
this.filters.push(new GameListFilters[name](this.onFilterChange.bind(this)));
xmppMessages.registerXmppMessageHandler("game", "gamelist", this.rebuildGameList.bind(this));
xmppMessages.registerXmppMessageHandler("system", "disconnected", this.rebuildGameList.bind(this));
buddyButton.registerBuddyChangeHandler(this.onBuddyChange.bind(this));
this.rebuildGameList();
}
registerSelectionChangeHandler(handler)
{
this.selectionChangeHandlers.add(handler);
}
onFilterChange()
{
this.rebuildGameList();
}
onBuddyChange()
{
for (let name in this.games)
this.games[name].onBuddyChange();
this.rebuildGameList();
}
onSelectionChange()
{
let game = this.selectedGame();
for (let handler of this.selectionChangeHandlers)
handler(game);
}
selectedGame()
{
return this.gameList[this.gamesBox.selected] || undefined;
}
rebuildGameList()
{
Engine.ProfileStart("rebuildGameList");
Engine.ProfileStart("getGameList");
let selectedGame = this.selectedGame();
let gameListData = Engine.GetGameList();
Engine.ProfileStop();
{
Engine.ProfileStart("updateGames");
let selectedColumn = this.gamesBox.selected_column;
let newGames = {};
for (let stanza of gameListData)
{
let game = this.games[stanza.hostUsername] || undefined;
let exists = !!game;
if (!exists)
game = new Game(this.mapCache);
game.update(stanza, selectedColumn);
newGames[stanza.hostUsername] = game;
}
this.games = newGames;
Engine.ProfileStop();
}
{
Engine.ProfileStart("filterGameList");
this.gameList.length = 0;
for (let ip in this.games)
{
let game = this.games[ip];
if (this.filters.every(filter => filter.filter(game)))
this.gameList.push(game);
}
Engine.ProfileStop();
}
{
Engine.ProfileStart("sortGameList");
let sortOrder = this.gamesBox.selected_column_order;
this.gameList.sort((game1, game2) => {
if (game1.sortValue < game2.sortValue) return -sortOrder;
if (game1.sortValue > game2.sortValue) return +sortOrder;
return 0;
});
Engine.ProfileStop();
}
let selectedGameIndex = -1;
{
Engine.ProfileStart("setupGameList");
let length = this.gameList.length;
this.list_buddy.length = length;
+ this.list_private.length = length;
this.list_gameName.length = length;
this.list_mapName.length = length;
this.list_mapSize.length = length;
this.list_mapType.length = length;
this.list_maxnbp.length = length;
this.list_gameRating.length = length;
this.list.length = length;
this.gameList.forEach((game, i) => {
let displayData = game.displayData;
this.list_buddy[i] = displayData.buddy;
+ this.list_private[i] = displayData.private;
this.list_gameName[i] = displayData.gameName;
this.list_mapName[i] = displayData.mapName;
this.list_mapSize[i] = displayData.mapSize;
this.list_mapType[i] = displayData.mapType;
this.list_maxnbp[i] = displayData.playerCount;
this.list_gameRating[i] = game.gameRating;
this.list[i] = "";
if (selectedGame && game.hostUsername == selectedGame.hostUsername && game.stanza.gameName == selectedGame.stanza.gameName)
selectedGameIndex = i;
});
Engine.ProfileStop();
}
{
Engine.ProfileStart("copyToGUI");
let gamesBox = this.gamesBox;
+ gamesBox.list_private = this.list_private;
gamesBox.list_buddy = this.list_buddy;
gamesBox.list_gameName = this.list_gameName;
gamesBox.list_mapName = this.list_mapName;
gamesBox.list_mapSize = this.list_mapSize;
gamesBox.list_mapType = this.list_mapType;
gamesBox.list_maxnbp = this.list_maxnbp;
gamesBox.list_gameRating = this.list_gameRating;
// Change these last, otherwise crash
gamesBox.list = this.list;
gamesBox.list_data = this.list;
gamesBox.auto_scroll = false;
Engine.ProfileStop();
gamesBox.selected = selectedGameIndex;
}
Engine.ProfileStop();
}
/**
* Select the game where the selected player is currently playing, observing or offline.
* Selects in that order to account for players that occur in multiple games.
*/
selectGameFromPlayername(playerName)
{
if (!playerName)
return;
let foundAsObserver = false;
for (let i = 0; i < this.gameList.length; ++i)
for (let player of this.gameList[i].players)
{
if (playerName != splitRatingFromNick(player.Name).nick)
continue;
this.gamesBox.auto_scroll = true;
if (player.Team == "observer")
{
foundAsObserver = true;
this.gamesBox.selected = i;
}
else if (!player.Offline)
{
this.gamesBox.selected = i;
return;
}
if (!foundAsObserver)
this.gamesBox.selected = i;
}
}
}
Index: ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.xml (revision 24794)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/LobbyPage/GameList.xml (revision 24795)
@@ -1,29 +1,30 @@
Index: ps/trunk/binaries/data/mods/public/gui/lobby/icons/private.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/icons/private.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/icons/private.xml (revision 24795)
@@ -0,0 +1,8 @@
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/lobby/icons/public.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/icons/public.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/icons/public.xml (revision 24795)
@@ -0,0 +1,8 @@
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/page_lobby.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/page_lobby.xml (revision 24794)
+++ ps/trunk/binaries/data/mods/public/gui/page_lobby.xml (revision 24795)
@@ -1,15 +1,16 @@
common/modern/setup.xmlcommon/modern/styles.xmlcommon/modern/sprites.xmlcommon/styles.xmlcommon/sprites.xml
+ lobby/icons/lobby/lobby.xmlcommon/global.xml