1
1
forked from 0ad/0ad

Mark players as buddies in the lobby and add a symbol to identify them in the lobby playerlist, gamelist and in the game description panel of the lobby, replay and load/save screen.

Differential Revision: https://code.wildfiregames.com/D209
Patch By: fpre aka. ffffffff
Reviewed By: causative
This was SVN commit r19433.
This commit is contained in:
elexis 2017-04-19 11:51:29 +00:00
parent 89c413031f
commit dcf12abe8c
5 changed files with 108 additions and 16 deletions

View File

@ -372,6 +372,7 @@ history = 0 ; Number of past messages to display on join
room = "arena22" ; Default MUC room to join room = "arena22" ; Default MUC room to join
server = "lobby.wildfiregames.com" ; Address of lobby server server = "lobby.wildfiregames.com" ; Address of lobby server
xpartamupp = "wfgbot22" ; Name of the server-side xmpp client that manage games xpartamupp = "wfgbot22" ; Name of the server-side xmpp client that manage games
buddies = "," ; Comma separated list of playernames that the current user has marked as buddies
[mod] [mod]
enabledmods = "mod public" enabledmods = "mod public"

View File

@ -158,6 +158,15 @@ function multiplayerName()
return Engine.ConfigDB_GetValue("user", "playername.multiplayer") || Engine.GetSystemUsername(); return Engine.ConfigDB_GetValue("user", "playername.multiplayer") || Engine.GetSystemUsername();
} }
/**
* Returns the nickname without the lobby rating.
*/
function removeRatingFromNick(playerName)
{
let result = /^(\S+)\ \(\d+\)$/g.exec(playerName);
return result ? result[1] : playerName;
}
function tryAutoComplete(text, autoCompleteList) function tryAutoComplete(text, autoCompleteList)
{ {
if (!text.length) if (!text.length)

View File

@ -3,6 +3,22 @@
*/ */
var g_DescriptionHighlight = "orange"; var g_DescriptionHighlight = "orange";
/**
* XEP-0172 doesn't restrict nicknames, but our lobby policy does.
* So use this human readable delimiter to separate buddy names in the config file.
*/
var g_BuddyListDelimiter = ",";
/**
* Array of playernames that the current user has marked as buddies.
*/
var g_Buddies = Engine.ConfigDB_GetValue("user", "lobby.buddies").split(g_BuddyListDelimiter);
/**
* Denotes which players are a lobby buddy of the current user.
*/
var g_BuddySymbol = '•';
/** /**
* Returns map description and preview image or placeholder. * Returns map description and preview image or placeholder.
*/ */
@ -123,7 +139,10 @@ function formatPlayerInfo(playerDataArray, playerStates)
(typeof getPlayerColor == 'function' ? (typeof getPlayerColor == 'function' ?
(isAI ? "white" : getPlayerColor(playerData.Name)) : (isAI ? "white" : getPlayerColor(playerData.Name)) :
rgbToGuiColor(playerData.Color || g_Settings.PlayerDefaults[playerIdx].Color)) + rgbToGuiColor(playerData.Color || g_Settings.PlayerDefaults[playerIdx].Color)) +
'"]' + escapeText(playerData.Name) + "[/color]", '"]' +
(g_Buddies.indexOf(removeRatingFromNick(playerData.Name)) != -1 ? g_BuddySymbol + " " : "") +
escapeText(playerData.Name) +
"[/color]",
"civ": "civ":
!playerData.Civ ? !playerData.Civ ?

View File

@ -382,23 +382,34 @@ function updatePlayerList()
if (playersBox.selected > -1) if (playersBox.selected > -1)
g_SelectedPlayer = playersBox.list[playersBox.selected]; g_SelectedPlayer = playersBox.list[playersBox.selected];
let buddyStatusList = [];
let playerList = []; let playerList = [];
let presenceList = []; let presenceList = [];
let nickList = []; let nickList = [];
let ratingList = []; let ratingList = [];
let cleanPlayerList = Engine.GetPlayerList().sort((a, b) => { let cleanPlayerList = Engine.GetPlayerList().map(player => {
player.isBuddy = g_Buddies.indexOf(player.name) != -1;
return player;
}).sort((a, b) => {
let sortA, sortB; let sortA, sortB;
let statusOrder = Object.keys(g_PlayerStatuses);
let statusA = statusOrder.indexOf(a.presence) + a.name.toLowerCase();
let statusB = statusOrder.indexOf(b.presence) + b.name.toLowerCase();
switch (sortBy) switch (sortBy)
{ {
case 'buddy':
sortA = (a.isBuddy ? 1 : 2) + statusA;
sortB = (b.isBuddy ? 1 : 2) + statusB;
break;
case 'rating': case 'rating':
sortA = +a.rating; sortA = +a.rating;
sortB = +b.rating; sortB = +b.rating;
break; break;
case 'status': case 'status':
let statusOrder = Object.keys(g_PlayerStatuses); sortA = statusA;
sortA = statusOrder.indexOf(a.presence); sortB = statusB;
sortB = statusOrder.indexOf(b.presence);
break; break;
case 'name': case 'name':
default: default:
@ -427,12 +438,14 @@ function updatePlayerList()
let coloredPresence = '[color="' + statusColor + '"]' + g_PlayerStatuses[presence].status + "[/color]"; let coloredPresence = '[color="' + statusColor + '"]' + g_PlayerStatuses[presence].status + "[/color]";
let coloredRating = '[color="' + statusColor + '"]' + rating + "[/color]"; let coloredRating = '[color="' + statusColor + '"]' + rating + "[/color]";
buddyStatusList.push(player.isBuddy ? '[color="' + statusColor + '"]' + g_BuddySymbol + '[/color]' : "");
playerList.push(coloredName); playerList.push(coloredName);
presenceList.push(coloredPresence); presenceList.push(coloredPresence);
ratingList.push(coloredRating); ratingList.push(coloredRating);
nickList.push(player.name); nickList.push(player.name);
} }
playersBox.list_buddy = buddyStatusList;
playersBox.list_name = playerList; playersBox.list_name = playerList;
playersBox.list_status = presenceList; playersBox.list_status = presenceList;
playersBox.list_rating = ratingList; playersBox.list_rating = ratingList;
@ -443,6 +456,31 @@ function updatePlayerList()
playersBox.selected = playersBox.list.indexOf(g_SelectedPlayer); playersBox.selected = playersBox.list.indexOf(g_SelectedPlayer);
} }
/**
* Toggle buddy state for a player in playerlist within the user config
*/
function toggleBuddy()
{
let playerList = Engine.GetGUIObjectByName("playersBox");
let name = playerList.list[playerList.selected];
if (!name || name == g_Username || name.indexOf(g_BuddyListDelimiter) != -1)
return;
let index = g_Buddies.indexOf(name);
if (index != -1)
g_Buddies.splice(index, 1);
else
g_Buddies.push(name);
let buddies = g_Buddies.filter(nick => nick).join(g_BuddyListDelimiter);
Engine.ConfigDB_CreateValue("user", "lobby.buddies", buddies);
Engine.ConfigDB_WriteValueToFile("user", "lobby.buddies", buddies, "config/user.cfg");
updatePlayerList();
updateGameList();
}
/** /**
* Select the game listing the selected player when toggling the full games filter. * Select the game listing the selected player when toggling the full games filter.
*/ */
@ -465,9 +503,7 @@ function selectGameFromPlayername(playerName)
for (let i = 0; i < g_GameList.length; ++i) for (let i = 0; i < g_GameList.length; ++i)
for (let player of stringifiedTeamListToPlayerData(g_GameList[i].players)) for (let player of stringifiedTeamListToPlayerData(g_GameList[i].players))
{ {
let result = /^(\S+)\ \(\d+\)$/g.exec(player.Name); let nick = removeRatingFromNick(player.Name);
let nick = result ? result[1] : player.Name;
if (playerName != nick) if (playerName != nick)
continue; continue;
@ -632,7 +668,18 @@ function updateGameList()
g_SelectedGamePort = g_GameList[gamesBox.selected].port; g_SelectedGamePort = g_GameList[gamesBox.selected].port;
} }
g_GameList = Engine.GetGameList().filter(game => !filterGame(game)).sort((a, b) => { g_GameList = Engine.GetGameList().map(game => {
game.hasBuddies = 0;
for (let player of stringifiedTeamListToPlayerData(game.players))
{
let nick = removeRatingFromNick(player.Name);
// Sort games with playing buddies above games with spectating buddies
if (game.hasBuddies < 2 && g_Buddies.indexOf(nick) != -1)
game.hasBuddies = player.Team == "observer" ? 1 : 2;
}
return game;
}).filter(game => !filterGame(game)).sort((a, b) => {
let sortA, sortB; let sortA, sortB;
switch (sortBy) switch (sortBy)
{ {
@ -642,6 +689,10 @@ function updateGameList()
sortA = a[sortBy]; sortA = a[sortBy];
sortB = b[sortBy]; sortB = b[sortBy];
break; break;
case 'buddy':
sortA = String(b.hasBuddies) + g_GameStatusOrder.indexOf(a.state) + a.name.toLowerCase();
sortB = String(a.hasBuddies) + g_GameStatusOrder.indexOf(b.state) + b.name.toLowerCase();
break;
case 'mapName': case 'mapName':
sortA = translate(a.niceMapName); sortA = translate(a.niceMapName);
sortB = translate(b.niceMapName); sortB = translate(b.niceMapName);
@ -661,6 +712,7 @@ function updateGameList()
return 0; return 0;
}); });
let list_buddy = [];
let list_name = []; let list_name = [];
let list_mapName = []; let list_mapName = [];
let list_mapSize = []; let list_mapSize = [];
@ -679,6 +731,7 @@ function updateGameList()
if (game.ip == g_SelectedGameIP && game.port == g_SelectedGamePort) if (game.ip == g_SelectedGameIP && game.port == g_SelectedGamePort)
selectedGameIndex = +i; selectedGameIndex = +i;
list_buddy.push(game.hasBuddies ? '[color="' + g_GameColors[game.state] + '"]' + g_BuddySymbol + '[/color]' : "");
list_name.push('[color="' + g_GameColors[game.state] + '"]' + gameName); list_name.push('[color="' + g_GameColors[game.state] + '"]' + gameName);
list_mapName.push(translateMapTitle(game.niceMapName)); list_mapName.push(translateMapTitle(game.niceMapName));
list_mapSize.push(translateMapSize(game.mapSize)); list_mapSize.push(translateMapSize(game.mapSize));
@ -688,6 +741,7 @@ function updateGameList()
list_data.push(i); list_data.push(i);
} }
gamesBox.list_buddy = list_buddy;
gamesBox.list_name = list_name; gamesBox.list_name = list_name;
gamesBox.list_mapName = list_mapName; gamesBox.list_mapName = list_mapName;
gamesBox.list_mapSize = list_mapSize; gamesBox.list_mapSize = list_mapSize;

View File

@ -23,7 +23,7 @@
</action> </action>
<!-- Left panel: Player list. --> <!-- Left panel: Player list. -->
<object name="leftPanel" size="20 30 20% 100%-280"> <object name="leftPanel" size="20 30 20% 100%-310">
<object name="playersBox" <object name="playersBox"
style="ModernList" style="ModernList"
sprite_asc="ModernArrowDown" sprite_asc="ModernArrowDown"
@ -36,10 +36,11 @@
size="0 0 100% 100%" size="0 0 100% 100%"
font="sans-bold-stroke-13" font="sans-bold-stroke-13"
> >
<column id="buddy" width="12"/>
<column id="status" width="26%"> <column id="status" width="26%">
<translatableAttribute id="heading">Status</translatableAttribute> <translatableAttribute id="heading">Status</translatableAttribute>
</column> </column>
<column id="name" width="48%"> <column id="name" width="45%-12">
<translatableAttribute id="heading">Name</translatableAttribute> <translatableAttribute id="heading">Name</translatableAttribute>
</column> </column>
<column id="rating" width="26%"> <column id="rating" width="26%">
@ -51,10 +52,11 @@
<action on="SelectionColumnChange"> <action on="SelectionColumnChange">
updatePlayerList(); updatePlayerList();
</action> </action>
<action on="mouseleftdoubleclickitem">toggleBuddy();</action>
</object> </object>
</object> </object>
<object name="profilePanel" size="20 100%-275 20% 100%-80"> <object name="profilePanel" size="20 100%-305 20% 100%-110">
<object name="profileBox" type="image" sprite="ModernDarkBoxGold" size="0 0 100% 100%"> <object name="profileBox" type="image" sprite="ModernDarkBoxGold" size="0 0 100% 100%">
<object name="profileArea" size="0 0 100% 100%" hidden="true"> <object name="profileArea" size="0 0 100% 100%" hidden="true">
<object name="usernameText" size="0 0 100% 45" type="text" style="ModernLabelText" text_align="center" font="sans-bold-16" /> <object name="usernameText" size="0 0 100% 45" type="text" style="ModernLabelText" text_align="center" font="sans-bold-16" />
@ -87,8 +89,14 @@
</object> </object>
</object> </object>
<object name="leftButtonPanel" size="20 100%-75 20% 100%-20"> <object name="leftButtonPanel" size="20 100%-105 20% 100%-20">
<object name="leaderboardButton" type="button" style="ModernButtonRed" size="0 0 100% 25"> <object name="toggleBuddy" type="button" style="ModernButtonRed" size="0 100%-85 100% 100%-60">
<translatableAttribute id="caption" comment="Toggle Buddy">Toggle Buddy</translatableAttribute>
<action on="Press">
toggleBuddy();
</action>
</object>
<object name="leaderboardButton" type="button" style="ModernButtonRed" size="0 100%-55 100% 100%-30">
<translatableAttribute id="caption">Leaderboard</translatableAttribute> <translatableAttribute id="caption">Leaderboard</translatableAttribute>
<action on="Press"> <action on="Press">
Engine.SendGetBoardList(); Engine.SendGetBoardList();
@ -97,7 +105,7 @@
displayProfile("leaderboard"); displayProfile("leaderboard");
</action> </action>
</object> </object>
<object name="userprofileButton" type="button" style="ModernButtonRed" size="0 30 100% 100%"> <object name="userprofileButton" type="button" style="ModernButtonRed" size="0 100%-25 100% 100%">
<translatableAttribute id="caption">User Profile Lookup</translatableAttribute> <translatableAttribute id="caption">User Profile Lookup</translatableAttribute>
<action on="Press"> <action on="Press">
Engine.GetGUIObjectByName("profileFetch").hidden = false; Engine.GetGUIObjectByName("profileFetch").hidden = false;
@ -199,7 +207,8 @@
<action on="SelectionChange">updateGameSelection();</action> <action on="SelectionChange">updateGameSelection();</action>
<action on="SelectionColumnChange">applyFilters();</action> <action on="SelectionColumnChange">applyFilters();</action>
<action on="mouseleftdoubleclickitem">joinButton();</action> <action on="mouseleftdoubleclickitem">joinButton();</action>
<column id="name" color="0 60 0" width="27%"> <column id="buddy" width="12"/>
<column id="name" color="0 60 0" width="27%-12">
<translatableAttribute id="heading">Name</translatableAttribute> <translatableAttribute id="heading">Name</translatableAttribute>
</column> </column>
<column id="mapName" color="128 128 128" width="25%"> <column id="mapName" color="128 128 128" width="25%">