Index: ps/trunk/binaries/data/mods/public/gui/lobby/lobby.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/lobby.js (revision 20039)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/lobby.js (revision 20040)
@@ -1,1430 +1,1456 @@
/**
* Used for the gamelist-filtering.
*/
const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
/**
* Used for the gamelist-filtering.
*/
const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
/**
* A symbol which is prepended to the username of moderators.
*/
const g_ModeratorPrefix = "@";
/**
* Current username. Cannot contain whitespace.
*/
const g_Username = Engine.LobbyGetNick();
/**
* Lobby server address to construct host JID.
*/
const g_LobbyServer = Engine.ConfigDB_GetValue("user", "lobby.server");
/**
* Current games will be listed in these colors.
*/
const g_GameColors = {
"init": "0 219 0",
"waiting": "255 127 0",
"running": "219 0 0"
};
/**
* Initial sorting order of the gamelist.
*/
const g_GameStatusOrder = ["init", "waiting", "running"];
/**
* The playerlist will be assembled using these values.
*/
const 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") }
};
const g_RoleNames = {
"moderator": translate("Moderator"),
"participant": translate("Player"),
"visitor": translate("Muted Player")
};
/**
* Color for error messages in the chat.
*/
const g_SystemColor = "150 0 0";
/**
* Color for private messages in the chat.
*/
const g_PrivateMessageColor = "0 150 0";
/**
* Used for highlighting the sender of chat messages.
*/
const g_SenderFont = "sans-bold-13";
/**
* Color to highlight chat commands in the explanation.
*/
const g_ChatCommandColor = "200 200 255";
/**
* All chat messages received since init (i.e. after lobby join and after returning from a game).
*/
var g_ChatMessages = [];
/**
* Rating of the current user.
* Contains the number or an empty string in case the user has no rating.
*/
var g_UserRating = "";
/**
* All games currently running.
*/
var g_GameList = {};
/**
* Used to restore the selection after updating the playerlist.
*/
var g_SelectedPlayer = "";
/**
* Used to restore the selection after updating the gamelist.
*/
var g_SelectedGameIP = "";
/**
* Used to restore the selection after updating the gamelist.
*/
var g_SelectedGamePort = "";
/**
* Whether the current user has been kicked or banned.
*/
var g_Kicked = false;
/**
- * Notifications sent by XmppClient.cpp
+ * Processing of notifications sent by XmppClient.cpp.
+ *
+ * @returns true if the playerlist GUI must be updated.
*/
var g_NetMessageTypes = {
"system": {
// Three cases are handled in prelobby.js
- "registered": msg => {
- },
- "connected": msg => {
- },
+ "registered": msg => false,
+ "connected": msg => false,
"disconnected": msg => {
updateGameList();
updateLeaderboard();
- updatePlayerList();
Engine.GetGUIObjectByName("chatInput").hidden = true;
for (let button of ["host", "leaderboard", "userprofile", "toggleBuddy"])
Engine.GetGUIObjectByName(button + "Button").enabled = false;
Engine.GetGUIObjectByName("chatInput").hidden = true;
if (!g_Kicked)
addChatMessage({
"from": "system",
"time": msg.time,
"text": translate("Disconnected.") + " " + msg.text
});
+ return true;
},
"error": msg => {
addChatMessage({
"from": "system",
"time": msg.time,
"text": msg.text
});
+ return false;
}
},
"chat": {
"subject": msg => {
updateSubject(msg.text);
+ return false;
},
"join": msg => {
addChatMessage({
"text": "/special " + sprintf(translate("%(nick)s has joined."), {
"nick": msg.text
}),
"time": msg.time,
"isSpecial": true
});
+ return true;
},
"leave": msg => {
addChatMessage({
"text": "/special " + sprintf(translate("%(nick)s has left."), {
"nick": msg.text
}),
"time": msg.time,
"isSpecial": true
});
if (msg.text == g_Username)
Engine.DisconnectXmppClient();
+
+ return true;
},
- "presence": msg => {
- },
+ "presence": msg => true,
"role": msg => {
Engine.GetGUIObjectByName("chatInput").hidden = Engine.LobbyGetPlayerRole(g_Username) == "visitor";
let me = g_Username == msg.text;
let role = Engine.LobbyGetPlayerRole(msg.text);
let txt =
role == "visitor" ?
me ?
translate("You have been muted.") :
translate("%(nick)s has been muted.") :
role == "moderator" ?
me ?
translate("You are now a moderator.") :
translate("%(nick)s is now a moderator.") :
msg.data == "visitor" ?
me ?
translate("You have been unmuted.") :
translate("%(nick)s has been unmuted.") :
me ?
translate("You are not a moderator anymore.") :
translate("%(nick)s is not a moderator anymore.");
addChatMessage({
"text": "/special " + sprintf(txt, { "nick": msg.text }),
"time": msg.time,
"isSpecial": true
});
if (g_SelectedPlayer == msg.text)
updateUserRoleText(g_SelectedPlayer);
+
+ return false;
},
"nick": msg => {
addChatMessage({
"text": "/special " + sprintf(translate("%(oldnick)s is now known as %(newnick)s."), {
"oldnick": msg.text,
"newnick": msg.data
}),
"time": msg.time,
"isSpecial": true
});
+ return true;
},
"kicked": msg => {
handleKick(false, msg.text, msg.data || "", msg.time);
+ return true;
},
"banned": msg => {
handleKick(true, msg.text, msg.data || "", msg.time);
+ return true;
},
"room-message": msg => {
addChatMessage({
"from": escapeText(msg.from),
"text": escapeText(msg.text),
"time": msg.time
});
+ return false;
},
"private-message": msg => {
// Announcements and the Message of the Day are sent by the server directly
if (!msg.from)
messageBox(
400, 250,
msg.text.trim(),
translate("Notice")
);
// We intend to not support private messages between users
if (!msg.from || Engine.LobbyGetPlayerRole(msg.from) == "moderator")
// some XMPP clients send trailing whitespace
addChatMessage({
"from": escapeText(msg.from || "system"),
"text": escapeText(msg.text.trim()),
"time": msg.time,
"private" : true
});
+ return false;
}
},
"game": {
- "gamelist": msg => updateGameList(),
- "profile": msg => updateProfile(),
- "leaderboard": msg => updateLeaderboard(),
- "ratinglist": msg => updatePlayerList()
+ "gamelist": msg => {
+ updateGameList();
+ return false;
+ },
+ "profile": msg => {
+ updateProfile();
+ return false;
+ },
+ "leaderboard": msg => {
+ updateLeaderboard();
+ return false;
+ },
+ "ratinglist": msg => {
+ return true;
+ }
}
};
/**
* Commands that can be entered by clients via chat input.
* A handler returns true if the user input should be sent as a chat message.
*/
var g_ChatCommands = {
"away": {
"description": translate("Set your state to 'Away'."),
"handler": args => {
Engine.LobbySetPlayerPresence("away");
return false;
}
},
"back": {
"description": translate("Set your state to 'Online'."),
"handler": args => {
Engine.LobbySetPlayerPresence("available");
return false;
}
},
"kick": {
"description": translate("Kick a specified user from the lobby. Usage: /kick nick reason"),
"handler": args => {
Engine.LobbyKick(args[0] || "", args[1] || "");
return false;
},
"moderatorOnly": true
},
"ban": {
"description": translate("Ban a specified user from the lobby. Usage: /ban nick reason"),
"handler": args => {
Engine.LobbyBan(args[0] || "", args[1] || "");
return false;
},
"moderatorOnly": true
},
"help": {
"description": translate("Show this help."),
"handler": args => {
let isModerator = Engine.LobbyGetPlayerRole(g_Username) == "moderator";
let text = translate("Chat commands:");
for (let command in g_ChatCommands)
if (!g_ChatCommands[command].moderatorOnly || isModerator)
// Translation: Chat command help format
text += "\n" + sprintf(translate("%(command)s - %(description)s"), {
"command": '[color="' + g_ChatCommandColor + '"]' + command + '[/color]',
"description": g_ChatCommands[command].description
});
addChatMessage({
"from": "system",
"text": text
});
return false;
}
},
"me": {
"description": translate("Send a chat message about yourself. Example: /me goes swimming."),
"handler": args => true
},
"say": {
"description": translate("Send text as a chat message (even if it starts with slash). Example: /say /help is a great command."),
"handler": args => true
},
"clear": {
"description": translate("Clear all chat scrollback."),
"handler": args => {
clearChatMessages();
return false;
}
},
"quit": {
"description": translate("Return to the main menu."),
"handler": args => {
returnToMainMenu();
return false;
}
}
};
/**
* Called after the XmppConnection succeeded and when returning from a game.
*
* @param {Object} attribs
*/
function init(attribs)
{
if (!g_Settings)
{
returnToMainMenu();
return;
}
initMusic();
global.music.setState(global.music.states.MENU);
initGameFilters();
Engine.LobbySetPlayerPresence("available");
// When rejoining the lobby after a game, we don't need to process presence changes
Engine.LobbyClearPresenceUpdates();
updatePlayerList();
updateSubject(Engine.LobbyGetRoomSubject());
updateLobbyColumns();
updateToggleBuddy();
Engine.GetGUIObjectByName("chatInput").tooltip = colorizeAutocompleteHotkey();
}
function updateLobbyColumns()
{
let gameRating = Engine.ConfigDB_GetValue("user", "lobby.columns.gamerating") == "true";
// Only show the selected columns
let gamesBox = Engine.GetGUIObjectByName("gamesBox");
gamesBox.hidden_mapType = gameRating;
gamesBox.hidden_gameRating = !gameRating;
// Only show the filters of selected columns
let mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter");
mapTypeFilter.hidden = gameRating;
let gameRatingFilter = Engine.GetGUIObjectByName("gameRatingFilter");
gameRatingFilter.hidden = !gameRating;
// Keep filters right above the according column
let playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter");
let size = playersNumberFilter.size;
size.rleft = gameRating ? 74: 90;
size.rright = gameRating ? 84: 100;
playersNumberFilter.size = size;
}
function returnToMainMenu()
{
Engine.StopXmppClient();
Engine.SwitchGuiPage("page_pregame.xml");
}
function initGameFilters()
{
let mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter");
mapSizeFilter.list = [translateWithContext("map size", "Any")].concat(g_MapSizes.Name);
mapSizeFilter.list_data = [""].concat(g_MapSizes.Tiles);
let playersArray = Array(g_MaxPlayers).fill(0).map((v, i) => i + 1); // 1, 2, ... MaxPlayers
let playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter");
playersNumberFilter.list = [translateWithContext("player number", "Any")].concat(playersArray);
playersNumberFilter.list_data = [""].concat(playersArray);
let mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter");
mapTypeFilter.list = [translateWithContext("map", "Any")].concat(g_MapTypes.Title);
mapTypeFilter.list_data = [""].concat(g_MapTypes.Name);
let gameRatingOptions = ["<1000", "<1100","<1200",">1200",">1300",">1400",">1500"].reverse();
gameRatingOptions = prepareForDropdown(gameRatingOptions.map(r => ({
"value": r,
"label": sprintf(
r[0] == ">" ?
translateWithContext("gamelist filter", "> %(rating)s") :
translateWithContext("gamelist filter", "< %(rating)s"),
{ "rating": r.substr(1) })
})));
let gameRatingFilter = Engine.GetGUIObjectByName("gameRatingFilter");
gameRatingFilter.list = [translateWithContext("map", "Any")].concat(gameRatingOptions.label);
gameRatingFilter.list_data = [""].concat(gameRatingOptions.value);
resetFilters();
}
function resetFilters()
{
Engine.GetGUIObjectByName("mapSizeFilter").selected = 0;
Engine.GetGUIObjectByName("playersNumberFilter").selected = 0;
Engine.GetGUIObjectByName("mapTypeFilter").selected = g_MapTypes.Default;
Engine.GetGUIObjectByName("gameRatingFilter").selected = 0;
Engine.GetGUIObjectByName("filterOpenGames").checked = false;
applyFilters();
}
function applyFilters()
{
updateGameList();
updateGameSelection();
}
/**
* Filter a game based on the status of the filter dropdowns.
*
* @param {Object} game
* @returns {boolean} - True if game should not be displayed.
*/
function filterGame(game)
{
let mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter");
let playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter");
let mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter");
let gameRatingFilter = Engine.GetGUIObjectByName("gameRatingFilter");
let filterOpenGames = Engine.GetGUIObjectByName("filterOpenGames");
// We assume index 0 means display all for any given filter.
if (mapSizeFilter.selected != 0 &&
game.mapSize != mapSizeFilter.list_data[mapSizeFilter.selected])
return true;
if (playersNumberFilter.selected != 0 &&
game.maxnbp != playersNumberFilter.list_data[playersNumberFilter.selected])
return true;
if (mapTypeFilter.selected != 0 &&
game.mapType != mapTypeFilter.list_data[mapTypeFilter.selected])
return true;
if (filterOpenGames.checked && (game.nbp >= game.maxnbp || game.state != "init"))
return true;
if (gameRatingFilter.selected > 0)
{
let selected = gameRatingFilter.list_data[gameRatingFilter.selected];
if (selected.startsWith(">") && +selected.substr(1) >= game.gameRating ||
selected.startsWith("<") && +selected.substr(1) <= game.gameRating)
return true;
}
return false;
}
function handleKick(banned, nick, reason, time)
{
let kickString = nick == g_Username ?
banned ?
translate("You have been banned from the lobby!") :
translate("You have been kicked from the lobby!") :
banned ?
translate("%(nick)s has been banned from the lobby.") :
translate("%(nick)s has been kicked from the lobby.");
if (reason)
reason = sprintf(translateWithContext("lobby kick", "Reason: %(reason)s"), {
"reason": reason
});
if (nick != g_Username)
{
addChatMessage({
"text": "/special " + sprintf(kickString, { "nick": nick }) + " " + reason,
"time": time,
"isSpecial": true
});
return;
}
addChatMessage({
"from": "system",
"time": time,
"text": kickString + " " + reason,
});
g_Kicked = true;
Engine.DisconnectXmppClient();
messageBox(
400, 250,
kickString + "\n" + reason,
banned ? translate("BANNED") : translate("KICKED")
);
}
/**
* Update the subject GUI object.
*
* @param {string} newSubject
*/
function updateSubject(newSubject)
{
Engine.GetGUIObjectByName("subject").caption = newSubject;
// If the subject is only whitespace, hide it and reposition the logo.
let subjectBox = Engine.GetGUIObjectByName("subjectBox");
subjectBox.hidden = !newSubject.trim();
let logo = Engine.GetGUIObjectByName("logo");
if (subjectBox.hidden)
logo.size = "50%-110 50%-50 50%+110 50%+50";
else
logo.size = "50%-110 40 50%+110 140";
}
/**
* Update the caption of the toggle buddy button.
*/
function updateToggleBuddy()
{
let playerList = Engine.GetGUIObjectByName("playersBox");
let playerName = playerList.list[playerList.selected];
let toggleBuddyButton = Engine.GetGUIObjectByName("toggleBuddyButton");
toggleBuddyButton.caption = g_Buddies.indexOf(playerName) != -1 ? translate("Unmark as Buddy") : translate("Mark as Buddy");
toggleBuddyButton.enabled = playerName && playerName != g_Username;
}
/**
* Do a full update of the player listing, including ratings from cached C++ information.
*/
function updatePlayerList()
{
let playersBox = Engine.GetGUIObjectByName("playersBox");
let sortBy = playersBox.selected_column || "name";
let sortOrder = playersBox.selected_column_order || 1;
let buddyStatusList = [];
let playerList = [];
let presenceList = [];
let nickList = [];
let ratingList = [];
let cleanPlayerList = Engine.GetPlayerList().map(player => {
player.isBuddy = g_Buddies.indexOf(player.name) != -1;
return player;
}).sort((a, b) => {
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)
{
case 'buddy':
sortA = (a.isBuddy ? 1 : 2) + statusA;
sortB = (b.isBuddy ? 1 : 2) + statusB;
break;
case 'rating':
sortA = +a.rating;
sortB = +b.rating;
break;
case 'status':
sortA = statusA;
sortB = statusB;
break;
case 'name':
default:
sortA = a.name.toLowerCase();
sortB = b.name.toLowerCase();
break;
}
if (sortA < sortB) return -sortOrder;
if (sortA > sortB) return +sortOrder;
return 0;
});
// Colorize list entries
for (let player of cleanPlayerList)
{
if (player.rating && player.name == g_Username)
g_UserRating = player.rating;
let rating = player.rating ? (" " + player.rating).substr(-5) : " -";
let presence = g_PlayerStatuses[player.presence] ? player.presence : "unknown";
if (presence == "unknown")
warn("Unknown presence:" + player.presence);
let statusColor = g_PlayerStatuses[presence].color;
let coloredName = colorPlayerName((player.role == "moderator" ? g_ModeratorPrefix : "") + player.name);
let coloredPresence = '[color="' + statusColor + '"]' + g_PlayerStatuses[presence].status + "[/color]";
let coloredRating = '[color="' + statusColor + '"]' + rating + "[/color]";
buddyStatusList.push(player.isBuddy ? '[color="' + statusColor + '"]' + g_BuddySymbol + '[/color]' : "");
playerList.push(coloredName);
presenceList.push(coloredPresence);
ratingList.push(coloredRating);
nickList.push(player.name);
}
playersBox.list_buddy = buddyStatusList;
playersBox.list_name = playerList;
playersBox.list_status = presenceList;
playersBox.list_rating = ratingList;
playersBox.list = nickList;
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);
updateToggleBuddy();
// Don't save empty strings to the config file
let buddies = g_Buddies.filter(nick => nick).join(g_BuddyListDelimiter) || g_BuddyListDelimiter;
Engine.ConfigDB_CreateValue("user", "lobby.buddies", buddies);
Engine.ConfigDB_WriteValueToFile("user", "lobby.buddies", buddies, "config/user.cfg");
updatePlayerList();
updateGameList();
}
/**
* 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.
*/
function selectGameFromPlayername()
{
if (!g_SelectedPlayer)
return;
let gameList = Engine.GetGUIObjectByName("gamesBox");
let foundAsObserver = false;
for (let i = 0; i < g_GameList.length; ++i)
for (let player of stringifiedTeamListToPlayerData(g_GameList[i].players))
{
let nick = splitRatingFromNick(player.Name)[0];
if (g_SelectedPlayer != nick)
continue;
if (player.Team == "observer")
{
foundAsObserver = true;
gameList.selected = i;
}
else if (!player.Offline)
{
gameList.selected = i;
return;
}
else if (!foundAsObserver)
gameList.selected = i;
}
}
function onPlayerListSelection()
{
let playerList = Engine.GetGUIObjectByName("playersBox");
if (playerList.selected == playerList.list.indexOf(g_SelectedPlayer))
return;
g_SelectedPlayer = playerList.list[playerList.selected];
lookupSelectedUserProfile("playersBox");
updateToggleBuddy();
selectGameFromPlayername();
}
function setLeaderboardVisibility(visible)
{
if (visible)
Engine.SendGetBoardList();
lookupSelectedUserProfile(visible ? "leaderboardBox" : "playersBox");
Engine.GetGUIObjectByName("leaderboard").hidden = !visible;
Engine.GetGUIObjectByName("fade").hidden = !visible;
}
function setUserProfileVisibility(visible)
{
Engine.GetGUIObjectByName("profileFetch").hidden = !visible;
Engine.GetGUIObjectByName("fade").hidden = !visible;
}
/**
* Display the profile of the player in the user profile window.
*/
function lookupUserProfile()
{
Engine.SendGetProfile(Engine.GetGUIObjectByName("fetchInput").caption);
}
/**
* Display the profile of the selected player in the main window.
* Displays N/A for all stats until updateProfile is called when the stats
* are actually received from the bot.
*/
function lookupSelectedUserProfile(guiObjectName)
{
let playerList = Engine.GetGUIObjectByName(guiObjectName);
let playerName = playerList.list[playerList.selected];
Engine.GetGUIObjectByName("profileArea").hidden = !playerName && !Engine.GetGUIObjectByName("usernameText").caption;
if (!playerName)
return;
Engine.SendGetProfile(playerName);
Engine.GetGUIObjectByName("usernameText").caption = playerName;
Engine.GetGUIObjectByName("rankText").caption = translate("N/A");
Engine.GetGUIObjectByName("highestRatingText").caption = translate("N/A");
Engine.GetGUIObjectByName("totalGamesText").caption = translate("N/A");
Engine.GetGUIObjectByName("winsText").caption = translate("N/A");
Engine.GetGUIObjectByName("lossesText").caption = translate("N/A");
Engine.GetGUIObjectByName("ratioText").caption = translate("N/A");
updateUserRoleText(playerName);
}
function updateUserRoleText(playerName)
{
Engine.GetGUIObjectByName("roleText").caption = g_RoleNames[Engine.LobbyGetPlayerRole(playerName) || "participant"];
}
/**
* Update the profile of the selected player with data from the bot.
*/
function updateProfile()
{
let attributes = Engine.GetProfile()[0];
let user = colorPlayerName(attributes.player, attributes.rating);
if (!Engine.GetGUIObjectByName("profileFetch").hidden)
{
let profileFound = attributes.rating != "-2";
Engine.GetGUIObjectByName("profileWindowArea").hidden = !profileFound;
Engine.GetGUIObjectByName("profileErrorText").hidden = profileFound;
if (!profileFound)
{
Engine.GetGUIObjectByName("profileErrorText").caption = sprintf(
translate("Player \"%(nick)s\" not found."),
{ "nick": attributes.player }
);
return;
}
Engine.GetGUIObjectByName("profileUsernameText").caption = user;
Engine.GetGUIObjectByName("profileRankText").caption = attributes.rank;
Engine.GetGUIObjectByName("profileHighestRatingText").caption = attributes.highestRating;
Engine.GetGUIObjectByName("profileTotalGamesText").caption = attributes.totalGamesPlayed;
Engine.GetGUIObjectByName("profileWinsText").caption = attributes.wins;
Engine.GetGUIObjectByName("profileLossesText").caption = attributes.losses;
Engine.GetGUIObjectByName("profileRatioText").caption = formatWinRate(attributes);
return;
}
let playerList;
if (!Engine.GetGUIObjectByName("leaderboard").hidden)
playerList = Engine.GetGUIObjectByName("leaderboardBox");
else
playerList = Engine.GetGUIObjectByName("playersBox");
if (attributes.rating == "-2")
return;
// Make sure the stats we have received coincide with the selected player.
if (attributes.player != playerList.list[playerList.selected])
return;
Engine.GetGUIObjectByName("usernameText").caption = user;
Engine.GetGUIObjectByName("rankText").caption = attributes.rank;
Engine.GetGUIObjectByName("highestRatingText").caption = attributes.highestRating;
Engine.GetGUIObjectByName("totalGamesText").caption = attributes.totalGamesPlayed;
Engine.GetGUIObjectByName("winsText").caption = attributes.wins;
Engine.GetGUIObjectByName("lossesText").caption = attributes.losses;
Engine.GetGUIObjectByName("ratioText").caption = formatWinRate(attributes);
}
/**
* Update the leaderboard from data cached in C++.
*/
function updateLeaderboard()
{
let leaderboard = Engine.GetGUIObjectByName("leaderboardBox");
let boardList = Engine.GetBoardList().sort((a, b) => b.rating - a.rating);
let list = [];
let list_name = [];
let list_rank = [];
let list_rating = [];
for (let i in boardList)
{
list_name.push(boardList[i].name);
list_rating.push(boardList[i].rating);
list_rank.push(+i+1);
list.push(boardList[i].name);
}
leaderboard.list_name = list_name;
leaderboard.list_rating = list_rating;
leaderboard.list_rank = list_rank;
leaderboard.list = list;
if (leaderboard.selected >= leaderboard.list.length)
leaderboard.selected = -1;
}
/**
* Update the game listing from data cached in C++.
*/
function updateGameList()
{
let gamesBox = Engine.GetGUIObjectByName("gamesBox");
let sortBy = gamesBox.selected_column;
let sortOrder = gamesBox.selected_column_order;
if (gamesBox.selected > -1)
{
g_SelectedGameIP = g_GameList[gamesBox.selected].ip;
g_SelectedGamePort = g_GameList[gamesBox.selected].port;
}
g_GameList = Engine.GetGameList().map(game => {
game.hasBuddies = 0;
// Compute average rating of participating players
let playerRatings = [];
for (let player of stringifiedTeamListToPlayerData(game.players))
{
let [nick, rating] = splitRatingFromNick(player.Name);
if (player.Team != "observer")
playerRatings.push(rating || g_DefaultLobbyRating);
// 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;
}
game.gameRating =
playerRatings.length ?
Math.round(playerRatings.reduce((sum, current) => sum + current) / playerRatings.length) :
g_DefaultLobbyRating;
return game;
}).filter(game => !filterGame(game)).sort((a, b) => {
let sortA, sortB;
switch (sortBy)
{
case 'name':
sortA = g_GameStatusOrder.indexOf(a.state) + a.name.toLowerCase();
sortB = g_GameStatusOrder.indexOf(b.state) + b.name.toLowerCase();
break;
case 'gameRating':
case 'mapSize':
case 'mapType':
sortA = a[sortBy];
sortB = b[sortBy];
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':
sortA = translate(a.niceMapName);
sortB = translate(b.niceMapName);
break;
case 'nPlayers':
sortA = a.maxnbp;
sortB = b.maxnbp;
break;
}
if (sortA < sortB) return -sortOrder;
if (sortA > sortB) return +sortOrder;
return 0;
});
let list_buddy = [];
let list_name = [];
let list_mapName = [];
let list_mapSize = [];
let list_mapType = [];
let list_nPlayers = [];
let list_gameRating = [];
let list = [];
let list_data = [];
let selectedGameIndex = -1;
for (let i in g_GameList)
{
let game = g_GameList[i];
let gameName = escapeText(game.name);
let mapTypeIdx = g_MapTypes.Name.indexOf(game.mapType);
if (game.ip == g_SelectedGameIP && game.port == g_SelectedGamePort)
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_mapName.push(translateMapTitle(game.niceMapName));
list_mapSize.push(translateMapSize(game.mapSize));
list_mapType.push(g_MapTypes.Title[mapTypeIdx] || "");
list_nPlayers.push(game.nbp + "/" + game.maxnbp);
list_gameRating.push(game.gameRating);
list.push(gameName);
list_data.push(i);
}
gamesBox.list_buddy = list_buddy;
gamesBox.list_name = list_name;
gamesBox.list_mapName = list_mapName;
gamesBox.list_mapSize = list_mapSize;
gamesBox.list_mapType = list_mapType;
gamesBox.list_nPlayers = list_nPlayers;
gamesBox.list_gameRating = list_gameRating;
// Change these last, otherwise crash
gamesBox.list = list;
gamesBox.list_data = list_data;
gamesBox.selected = selectedGameIndex;
updateGameSelection();
}
/**
* Populate the game info area with information on the current game selection.
*/
function updateGameSelection()
{
let game = selectedGame();
Engine.GetGUIObjectByName("gameInfo").hidden = !game;
Engine.GetGUIObjectByName("joinGameButton").hidden = !game;
Engine.GetGUIObjectByName("gameInfoEmpty").hidden = game;
if (!game)
return;
Engine.GetGUIObjectByName("sgMapName").caption = translateMapTitle(game.niceMapName);
let sgGameStartTime = Engine.GetGUIObjectByName("sgGameStartTime");
let sgNbPlayers = Engine.GetGUIObjectByName("sgNbPlayers");
let sgPlayersNames = Engine.GetGUIObjectByName("sgPlayersNames");
let playersNamesSize = sgPlayersNames.size;
playersNamesSize.top = game.startTime ? sgGameStartTime.size.bottom : sgNbPlayers.size.bottom;
playersNamesSize.rtop = game.startTime ? sgGameStartTime.size.rbottom : sgNbPlayers.size.rbottom;
sgPlayersNames.size = playersNamesSize;
sgGameStartTime.hidden = !game.startTime;
if (game.startTime)
sgGameStartTime.caption = sprintf(
// Translation: %(time)s is the hour and minute here.
translate("Game started at %(time)s"), {
"time": Engine.FormatMillisecondsIntoDateStringLocal(+game.startTime*1000, translate("HH:mm"))
});
sgNbPlayers.caption = sprintf(
translate("Players: %(current)s/%(total)s"), {
"current": game.nbp,
"total": game.maxnbp
});
sgPlayersNames.caption = formatPlayerInfo(stringifiedTeamListToPlayerData(game.players));
Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(game.mapSize);
let mapTypeIdx = g_MapTypes.Name.indexOf(game.mapType);
Engine.GetGUIObjectByName("sgMapType").caption = g_MapTypes.Title[mapTypeIdx] || "";
let mapData = getMapDescriptionAndPreview(game.mapType, game.mapName);
Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description;
setMapPreviewImage("sgMapPreview", mapData.preview);
}
function selectedGame()
{
let gamesBox = Engine.GetGUIObjectByName("gamesBox");
if (gamesBox.selected < 0)
return undefined;
return g_GameList[gamesBox.list_data[gamesBox.selected]];
}
/**
* Immediately rejoin and join gamesetups. Otherwise confirm late-observer join attempt.
*/
function joinButton()
{
let game = selectedGame();
if (!game)
return;
let rating = getRejoinRating(game);
let username = rating ? g_Username + " (" + rating + ")" : g_Username;
if (game.state == "init" || stringifiedTeamListToPlayerData(game.players).some(player => player.Name == username))
joinSelectedGame();
else
messageBox(
400, 200,
translate("The game has already started. Do you want to join as observer?"),
translate("Confirmation"),
[translate("No"), translate("Yes")],
[null, joinSelectedGame]
);
}
/**
* Attempt to join the selected game without asking for confirmation.
*/
function joinSelectedGame()
{
let game = selectedGame();
if (!game)
return;
let ip;
let port;
if (game.stunIP)
{
ip = game.stunIP;
port = game.stunPort;
}
else
{
ip = game.ip;
port = game.port;
}
if (ip.split('.').length != 4)
{
addChatMessage({
"from": "system",
"text": sprintf(
translate("This game's address '%(ip)s' does not appear to be valid."),
{ "ip": game.ip }
)
});
return;
}
Engine.PushGuiPage("page_gamesetup_mp.xml", {
"multiplayerGameType": "join",
"ip": ip,
"port": port,
"name": g_Username,
"rating": getRejoinRating(game),
"useSTUN": !!game.stunIP,
"hostJID": game.hostUsername + "@" + g_LobbyServer + "/0ad"
});
}
/**
* Rejoin games with the original playername, even if the rating changed meanwhile.
*/
function getRejoinRating(game)
{
for (let player of stringifiedTeamListToPlayerData(game.players))
{
let [nick, rating] = splitRatingFromNick(player.Name);
if (nick == g_Username)
return rating;
}
return g_UserRating;
}
/**
* Open the dialog box to enter the game name.
*/
function hostGame()
{
Engine.PushGuiPage("page_gamesetup_mp.xml", {
"multiplayerGameType": "host",
"name": g_Username,
"rating": g_UserRating
});
}
/**
* Processes GUI messages sent by the XmppClient.
*/
function onTick()
{
updateTimers();
+ let updateList = false;
+
while (true)
{
let msg = Engine.LobbyGuiPollMessage();
if (!msg)
break;
if (!g_NetMessageTypes[msg.type])
{
warn("Unrecognised message type: " + msg.type);
continue;
}
if (!g_NetMessageTypes[msg.type][msg.level])
{
warn("Unrecognised message level: " + msg.level);
continue;
}
- g_NetMessageTypes[msg.type][msg.level](msg);
- // To improve performance, only update the playerlist GUI when
- // the last update in the current stack is processed
- if (msg.type == "chat" && Engine.LobbyGetMucMessageCount() == 0)
- updatePlayerList();
+ if (g_NetMessageTypes[msg.type][msg.level](msg))
+ updateList = true;
}
+
+ // To improve performance, only update the playerlist GUI when
+ // the last update in the current stack is processed
+ if (updateList)
+ updatePlayerList();
}
/**
* Executes a lobby command or sends GUI input directly as chat.
*/
function submitChatInput()
{
let input = Engine.GetGUIObjectByName("chatInput");
let text = input.caption;
if (!text.length)
return;
if (handleChatCommand(text))
Engine.LobbySendMessage(text);
input.caption = "";
}
/**
* Handle all '/' commands.
*
* @param {string} text - Text to be checked for commands.
* @returns {boolean} true if the text should be sent via chat.
*/
function handleChatCommand(text)
{
if (text[0] != '/')
return true;
let [cmd, args] = ircSplit(text);
args = ircSplit("/" + args);
if (!g_ChatCommands[cmd])
{
addChatMessage({
"from": "system",
"text": sprintf(
translate("The command '%(cmd)s' is not supported."), {
"cmd": '[color="' + g_ChatCommandColor + '"]' + cmd + '[/color]'
})
});
return false;
}
if (g_ChatCommands[cmd].moderatorOnly && Engine.LobbyGetPlayerRole(g_Username) != "moderator")
{
addChatMessage({
"from": "system",
"text": sprintf(
translate("The command '%(cmd)s' is restricted to moderators."), {
"cmd": '[color="' + g_ChatCommandColor + '"]' + cmd + '[/color]'
})
});
return false;
}
return g_ChatCommands[cmd].handler(args);
}
/**
* Process and if appropriate, display a formatted message.
*
* @param {Object} msg - The message to be processed.
*/
function addChatMessage(msg)
{
if (msg.from)
{
if (Engine.LobbyGetPlayerRole(msg.from) == "moderator")
msg.from = g_ModeratorPrefix + msg.from;
// Highlight local user's nick
if (g_Username != msg.from)
{
msg.text = msg.text.replace(g_Username, colorPlayerName(g_Username));
notifyUser(g_Username, msg.text);
}
}
let formatted = ircFormat(msg);
if (!formatted)
return;
g_ChatMessages.push(formatted);
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
/**
* Splits given input into command and argument.
*/
function ircSplit(string)
{
let idx = string.indexOf(' ');
if (idx != -1)
return [string.substr(1,idx-1), string.substr(idx+1)];
return [string.substr(1), ""];
}
/**
* Format text in an IRC-like way.
*
* @param {Object} msg - Received chat message.
* @returns {string} - Formatted text.
*/
function ircFormat(msg)
{
let formattedMessage = "";
let coloredFrom = msg.from && colorPlayerName(msg.from);
// Handle commands allowed past handleChatCommand.
if (msg.text[0] == '/')
{
let [command, message] = ircSplit(msg.text);
switch (command)
{
case "me":
{
// Translation: IRC message prefix when the sender uses the /me command.
let senderString = sprintf(translate("* %(sender)s"), {
"sender": coloredFrom
});
// Translation: IRC message issued using the ‘/me’ command.
formattedMessage = sprintf(translate("%(sender)s %(action)s"), {
"sender": senderFont(senderString),
"action": message
});
break;
}
case "say":
{
// Translation: IRC message prefix.
let senderString = sprintf(translate("<%(sender)s>"), {
"sender": coloredFrom
});
// Translation: IRC message.
formattedMessage = sprintf(translate("%(sender)s %(message)s"), {
"sender": senderFont(senderString),
"message": message
});
break;
}
case "special":
{
if (msg.isSpecial)
// Translation: IRC system message.
formattedMessage = senderFont(sprintf(translate("== %(message)s"), {
"message": message
}));
else
{
// Translation: IRC message prefix.
let senderString = sprintf(translate("<%(sender)s>"), {
"sender": coloredFrom
});
// Translation: IRC message.
formattedMessage = sprintf(translate("%(sender)s %(message)s"), {
"sender": senderFont(senderString),
"message": message
});
}
break;
}
}
}
else
{
let senderString;
// Translation: IRC message prefix.
if (msg.private)
senderString = sprintf(translateWithContext("lobby private message", "(%(private)s) <%(sender)s>"), {
"private": '[color="' + g_PrivateMessageColor + '"]' +
translate("Private") + '[/color]',
"sender": coloredFrom
});
else
senderString = sprintf(translate("<%(sender)s>"), {
"sender": coloredFrom
});
// Translation: IRC message.
formattedMessage = sprintf(translate("%(sender)s %(message)s"), {
"sender": senderFont(senderString),
"message": msg.text
});
}
// Add chat message timestamp
if (Engine.ConfigDB_GetValue("user", "chat.timestamp") != "true")
return formattedMessage;
// 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:
// https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table
let timeString = Engine.FormatMillisecondsIntoDateStringLocal(msg.time ? msg.time * 1000 : Date.now(), translate("HH:mm"));
// Translation: Time prefix as shown in the multiplayer lobby (when you enable it in the options page).
let timePrefixString = sprintf(translate("\\[%(time)s]"), {
"time": timeString
});
// Translation: IRC message format when there is a time prefix.
return sprintf(translate("%(time)s %(message)s"), {
"time": senderFont(timePrefixString),
"message": formattedMessage
});
}
/**
* Generate a (mostly) unique color for this player based on their name.
* @see http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
* @param {string} playername
*/
function getPlayerColor(playername)
{
if (playername == "system")
return g_SystemColor;
// Generate a probably-unique hash for the player name and use that to create a color.
let hash = 0;
for (let i in playername)
hash = playername.charCodeAt(i) + ((hash << 5) - hash);
// First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
// The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
// us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
// we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
let [h, s, l] = rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF);
return hslToRgb(h, s, Math.max(0.7, l)).join(" ");
}
/**
* Returns the given playername wrapped in an appropriate color-tag.
*
* @param {string} playername
* @param {string} rating
*/
function colorPlayerName(playername, rating)
{
return '[color="' + getPlayerColor(playername.replace(g_ModeratorPrefix, "")) + '"]' +
(rating ? sprintf(
translate("%(nick)s (%(rating)s)"), {
"nick": playername,
"rating": rating
}) :
playername) + '[/color]';
}
function senderFont(text)
{
return '[font="' + g_SenderFont + '"]' + text + "[/font]";
}
function formatWinRate(attr)
{
if (!attr.totalGamesPlayed)
return translateWithContext("Used for an undefined winning rate", "-");
return sprintf(translate("%(percentage)s%%"), {
"percentage": (attr.wins / attr.totalGamesPlayed * 100).toFixed(2)
});
}
Index: ps/trunk/source/lobby/IXmppClient.h
===================================================================
--- ps/trunk/source/lobby/IXmppClient.h (revision 20039)
+++ ps/trunk/source/lobby/IXmppClient.h (revision 20040)
@@ -1,67 +1,66 @@
/* 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
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef IXMPPCLIENT_H
#define IXMPPCLIENT_H
#include "scriptinterface/ScriptTypes.h"
class ScriptInterface;
namespace StunClient {
class StunEndpoint;
}
class IXmppClient
{
public:
static IXmppClient* create(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize = 0, bool regOpt = false);
virtual ~IXmppClient() {}
virtual void connect() = 0;
virtual void disconnect() = 0;
virtual void recv() = 0;
virtual void SendIqGetBoardList() = 0;
virtual void SendIqGetProfile(const std::string& player) = 0;
virtual void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0;
virtual void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data) = 0;
virtual void SendIqUnregisterGame() = 0;
virtual void SendIqChangeStateGame(const std::string& nbp, const std::string& players) = 0;
virtual void SetNick(const std::string& nick) = 0;
virtual void GetNick(std::string& nick) = 0;
virtual void kick(const std::string& nick, const std::string& reason) = 0;
virtual void ban(const std::string& nick, const std::string& reason) = 0;
virtual void SetPresence(const std::string& presence) = 0;
virtual void GetPresence(const std::string& nickname, std::string& presence) = 0;
virtual void GetRole(const std::string& nickname, std::string& role) = 0;
virtual void GetSubject(std::string& subject) = 0;
virtual void GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void ClearPresenceUpdates() = 0;
- virtual int GetMucMessageCount() = 0;
virtual void GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void GuiPollMessage(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret) = 0;
virtual void SendMUCMessage(const std::string& message) = 0;
virtual void SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJID) = 0;
};
extern IXmppClient *g_XmppClient;
extern bool g_rankedGame;
#endif // XMPPCLIENT_H
Index: ps/trunk/source/lobby/XmppClient.cpp
===================================================================
--- ps/trunk/source/lobby/XmppClient.cpp (revision 20039)
+++ ps/trunk/source/lobby/XmppClient.cpp (revision 20040)
@@ -1,1145 +1,1133 @@
/* 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
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "XmppClient.h"
#include "StanzaExtensions.h"
#ifdef WIN32
# include
#endif
#include "i18n/L10n.h"
#include "lib/external_libraries/enet.h"
#include "lib/utf8.h"
#include "network/NetServer.h"
#include "network/StunClient.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Pyrogenesis.h"
#include "scriptinterface/ScriptInterface.h"
//debug
#if 1
#define DbgXMPP(x)
#else
#define DbgXMPP(x) std::cout << x << std::endl;
static std::string tag_xml(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->xml().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
#endif
static std::string tag_name(const glooxwrapper::IQ& iq)
{
std::string ret;
glooxwrapper::Tag* tag = iq.tag();
ret = tag->name().to_string();
glooxwrapper::Tag::free(tag);
return ret;
}
IXmppClient* IXmppClient::create(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize,bool regOpt)
{
return new XmppClient(sUsername, sPassword, sRoom, sNick, historyRequestSize, regOpt);
}
/**
* Construct the XMPP client.
*
* @param sUsername Username to login with of register.
* @param sPassword Password to login with or register.
* @param sRoom MUC room to join.
* @param sNick Nick to join with.
* @param historyRequestSize Number of stanzas of room history to request.
* @param regOpt If we are just registering or not.
*/
XmppClient::XmppClient(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize, bool regOpt)
: m_client(NULL), m_mucRoom(NULL), m_registration(NULL), m_username(sUsername), m_password(sPassword), m_nick(sNick), m_initialLoadComplete(false), m_sessionManager()
{
// Read lobby configuration from default.cfg
std::string sServer;
std::string sXpartamupp;
CFG_GET_VAL("lobby.server", sServer);
CFG_GET_VAL("lobby.xpartamupp", sXpartamupp);
m_xpartamuppId = sXpartamupp + "@" + sServer + "/CC";
glooxwrapper::JID clientJid(sUsername + "@" + sServer + "/0ad");
glooxwrapper::JID roomJid(sRoom + "@conference." + sServer + "/" + sNick);
// If we are connecting, use the full jid and a password
// If we are registering, only use the server name
if (!regOpt)
m_client = new glooxwrapper::Client(clientJid, sPassword);
else
m_client = new glooxwrapper::Client(sServer);
// Disable TLS as we haven't set a certificate on the server yet
m_client->setTls(gloox::TLSDisabled);
// Disable use of the SASL PLAIN mechanism, to prevent leaking credentials
// if the server doesn't list any supported SASL mechanism or the response
// has been modified to exclude those.
const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
m_client->setSASLMechanisms(mechs);
m_client->registerConnectionListener(this);
m_client->setPresence(gloox::Presence::Available, -1);
m_client->disco()->setVersion("Pyrogenesis", engine_version);
m_client->disco()->setIdentity("client", "bot");
m_client->setCompression(false);
m_client->registerStanzaExtension(new GameListQuery());
m_client->registerIqHandler(this, EXTGAMELISTQUERY);
m_client->registerStanzaExtension(new BoardListQuery());
m_client->registerIqHandler(this, EXTBOARDLISTQUERY);
m_client->registerStanzaExtension(new ProfileQuery());
m_client->registerIqHandler(this, EXTPROFILEQUERY);
m_client->registerMessageHandler(this);
// Uncomment to see the raw stanzas
//m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
if (!regOpt)
{
// Create a Multi User Chat Room
m_mucRoom = new glooxwrapper::MUCRoom(m_client, roomJid, this, 0);
// Get room history.
m_mucRoom->setRequestHistory(historyRequestSize, gloox::MUCRoom::HistoryMaxStanzas);
}
else
{
// Registration
m_registration = new glooxwrapper::Registration(m_client);
m_registration->registerRegistrationHandler(this);
}
m_sessionManager = new glooxwrapper::SessionManager(m_client, this);
// Register plugins to allow gloox parse them in incoming sessions
m_sessionManager->registerPlugins();
}
/**
* Destroy the xmpp client
*/
XmppClient::~XmppClient()
{
DbgXMPP("XmppClient destroyed");
delete m_registration;
delete m_mucRoom;
// Workaround for memory leak in gloox 1.0/1.0.1
m_client->removePresenceExtension(gloox::ExtCaps);
delete m_client;
for (const glooxwrapper::Tag* const& t : m_GameList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_BoardList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_Profile)
glooxwrapper::Tag::free(t);
}
/// Network
void XmppClient::connect()
{
m_initialLoadComplete = false;
m_client->connect(false);
}
void XmppClient::disconnect()
{
m_client->disconnect();
}
void XmppClient::recv()
{
m_client->recv(1);
}
/**
* Log (debug) Handler
*/
void XmppClient::handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message)
{
std::cout << "log: level: " << level << ", area: " << area << ", message: " << message << std::endl;
}
/*****************************************************
* Connection handlers *
*****************************************************/
/**
* Handle connection
*/
void XmppClient::onConnect()
{
if (m_mucRoom)
{
CreateGUIMessage("system", "connected");
m_mucRoom->join();
}
if (m_registration)
m_registration->fetchRegistrationFields();
}
/**
* Handle disconnection
*/
void XmppClient::onDisconnect(gloox::ConnectionError error)
{
// Make sure we properly leave the room so that
// everything works if we decide to come back later
if (m_mucRoom)
m_mucRoom->leave();
// Clear game, board and player lists.
for (const glooxwrapper::Tag* const& t : m_GameList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_BoardList)
glooxwrapper::Tag::free(t);
for (const glooxwrapper::Tag* const& t : m_Profile)
glooxwrapper::Tag::free(t);
m_BoardList.clear();
m_GameList.clear();
m_PlayerMap.clear();
m_Profile.clear();
CreateGUIMessage("system", "disconnected", ConnectionErrorToString(error));
}
/**
* Handle TLS connection
*/
bool XmppClient::onTLSConnect(const glooxwrapper::CertInfo& info)
{
UNUSED2(info);
DbgXMPP("onTLSConnect");
DbgXMPP(
"status: " << info.status <<
"\nissuer: " << info.issuer <<
"\npeer: " << info.server <<
"\nprotocol: " << info.protocol <<
"\nmac: " << info.mac <<
"\ncipher: " << info.cipher <<
"\ncompression: " << info.compression );
return true;
}
/**
* Handle MUC room errors
*/
void XmppClient::handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError err)
{
CreateGUIMessage("system", "error", StanzaErrorToString(err));
}
/*****************************************************
* Requests to server *
*****************************************************/
/**
* Request the leaderboard data from the server.
*/
void XmppClient::SendIqGetBoardList()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
BoardListQuery* b = new BoardListQuery();
b->m_Command = "getleaderboard";
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(b);
DbgXMPP("SendIqGetBoardList [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Request the profile data from the server.
*/
void XmppClient::SendIqGetProfile(const std::string& player)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
ProfileQuery* b = new ProfileQuery();
b->m_Command = player;
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(b);
DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send game report containing numerous game properties to the server.
*
* @param data A JS array of game statistics
*/
void XmppClient::SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Setup some base stanza attributes
GameReport* game = new GameReport();
glooxwrapper::Tag* report = glooxwrapper::Tag::allocate("game");
// Iterate through all the properties reported and add them to the stanza.
std::vector properties;
scriptInterface.EnumeratePropertyNamesWithPrefix(data, "", properties);
for (const std::string& p : properties)
{
std::wstring value;
scriptInterface.GetProperty(data, p.c_str(), value);
report->addAttribute(p, utf8_from_wstring(value));
}
// Add stanza to IQ
game->m_GameReport.emplace_back(report);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension(game);
DbgXMPP("SendGameReport [" << tag_xml(iq) << "]");
m_client->send(iq);
};
/**
* Send a request to register a game to the server.
*
* @param data A JS array of game attributes
*/
void XmppClient::SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Setup some base stanza attributes
GameListQuery* g = new GameListQuery();
g->m_Command = "register";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
// Add a fake ip which will be overwritten by the ip stamp XMPP module on the server.
game->addAttribute("ip", "fake");
// Iterate through all the properties reported and add them to the stanza.
std::vector properties;
scriptInterface.EnumeratePropertyNamesWithPrefix(data, "", properties);
for (const std::string& p : properties)
{
std::wstring value;
scriptInterface.GetProperty(data, p.c_str(), value);
game->addAttribute(p, utf8_from_wstring(value));
}
// Push the stanza onto the IQ
g->m_GameList.emplace_back(game);
// Send IQ
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension(g);
DbgXMPP("SendIqRegisterGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send a request to unregister a game to the server.
*/
void XmppClient::SendIqUnregisterGame()
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "unregister";
g->m_GameList.emplace_back(glooxwrapper::Tag::allocate("game"));
glooxwrapper::IQ iq( gloox::IQ::Set, xpartamuppJid );
iq.addExtension(g);
DbgXMPP("SendIqUnregisterGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Send a request to change the state of a registered game on the server.
*
* A game can either be in the 'running' or 'waiting' state - the server
* decides which - but we need to update the current players that are
* in-game so the server can make the calculation.
*/
void XmppClient::SendIqChangeStateGame(const std::string& nbp, const std::string& players)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
GameListQuery* g = new GameListQuery();
g->m_Command = "changestate";
glooxwrapper::Tag* game = glooxwrapper::Tag::allocate("game");
game->addAttribute("nbp", nbp);
game->addAttribute("players", players);
g->m_GameList.emplace_back(game);
glooxwrapper::IQ iq(gloox::IQ::Set, xpartamuppJid);
iq.addExtension(g);
DbgXMPP("SendIqChangeStateGame [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/*****************************************************
* Account registration *
*****************************************************/
void XmppClient::handleRegistrationFields(const glooxwrapper::JID&, int fields, glooxwrapper::string)
{
glooxwrapper::RegistrationFields vals;
vals.username = m_username;
vals.password = m_password;
m_registration->createAccount(fields, vals);
}
void XmppClient::handleRegistrationResult(const glooxwrapper::JID&, gloox::RegistrationResult result)
{
if (result == gloox::RegistrationSuccess)
CreateGUIMessage("system", "registered");
else
CreateGUIMessage("system", "error", RegistrationResultToString(result));
disconnect();
}
void XmppClient::handleAlreadyRegistered(const glooxwrapper::JID&)
{
DbgXMPP("the account already exists");
}
void XmppClient::handleDataForm(const glooxwrapper::JID&, const glooxwrapper::DataForm&)
{
DbgXMPP("dataForm received");
}
void XmppClient::handleOOB(const glooxwrapper::JID&, const glooxwrapper::OOB&)
{
DbgXMPP("OOB registration requested");
}
/*****************************************************
* Requests from GUI *
*****************************************************/
/**
* Handle requests from the GUI for the list of players.
*
* @return A JS array containing all known players and their presences
*/
void XmppClient::GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
// Convert the internal data structure to a Javascript object.
for (const std::pair >& p : m_PlayerMap)
{
JS::RootedValue player(cx);
scriptInterface.Eval("({})", &player);
scriptInterface.SetProperty(player, "name", wstring_from_utf8(p.first));
scriptInterface.SetProperty(player, "presence", wstring_from_utf8(p.second[0]));
scriptInterface.SetProperty(player, "rating", wstring_from_utf8(p.second[1]));
scriptInterface.SetProperty(player, "role", wstring_from_utf8(p.second[2]));
scriptInterface.CallFunctionVoid(ret, "push", player);
}
}
/**
* Handle requests from the GUI for the list of all active games.
*
* @return A JS array containing all known games
*/
void XmppClient::GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
const char* stats[] = { "name", "ip", "port", "stunIP", "stunPort", "hostUsername", "state", "nbp", "maxnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition", "startTime" };
for(const glooxwrapper::Tag* const& t : m_GameList)
{
JS::RootedValue game(cx);
scriptInterface.Eval("({})", &game);
for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
scriptInterface.SetProperty(game, stats[i], wstring_from_utf8(t->findAttribute(stats[i]).to_string()));
scriptInterface.CallFunctionVoid(ret, "push", game);
}
}
/**
* Handle requests from the GUI for leaderboard data.
*
* @return A JS array containing all known leaderboard data
*/
void XmppClient::GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
const char* attributes[] = { "name", "rank", "rating" };
for(const glooxwrapper::Tag* const& t : m_BoardList)
{
JS::RootedValue board(cx);
scriptInterface.Eval("({})", &board);
for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i)
scriptInterface.SetProperty(board, attributes[i], wstring_from_utf8(t->findAttribute(attributes[i]).to_string()));
scriptInterface.CallFunctionVoid(ret, "push", board);
}
}
/**
* Handle requests from the GUI for profile data.
*
* @return A JS array containing the specific user's profile data
*/
void XmppClient::GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("([])", ret);
const char* stats[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" };
for (const glooxwrapper::Tag* const& t : m_Profile)
{
JS::RootedValue profile(cx);
scriptInterface.Eval("({})", &profile);
for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
scriptInterface.SetProperty(profile, stats[i], wstring_from_utf8(t->findAttribute(stats[i]).to_string()));
scriptInterface.CallFunctionVoid(ret, "push", profile);
}
}
/*****************************************************
* Message interfaces *
*****************************************************/
/**
* Send GUI message queue when queried.
*/
void XmppClient::GuiPollMessage(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
if (m_GuiMessageQueue.empty())
{
ret.setUndefined();
return;
}
GUIMessage message = m_GuiMessageQueue.front();
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
scriptInterface.Eval("({})", ret);
scriptInterface.SetProperty(ret, "type", message.type);
if (!message.from.empty())
scriptInterface.SetProperty(ret, "from", message.from);
if (!message.text.empty())
scriptInterface.SetProperty(ret, "text", message.text);
if (!message.level.empty())
scriptInterface.SetProperty(ret, "level", message.level);
if (!message.data.empty())
scriptInterface.SetProperty(ret, "data", message.data);
scriptInterface.SetProperty(ret, "time", (double)message.time);
m_GuiMessageQueue.pop_front();
}
/**
* Send a standard MUC textual message.
*/
void XmppClient::SendMUCMessage(const std::string& message)
{
m_mucRoom->send(message);
}
/**
* Push a message onto the GUI queue.
*
* @param message Message to add to the queue
*/
void XmppClient::PushGuiMessage(XmppClient::GUIMessage message)
{
m_GuiMessageQueue.push_back(std::move(message));
}
/**
* Clears all presence updates from the message queue.
* Used when rejoining the lobby, since we don't need to handle past presence changes.
*/
void XmppClient::ClearPresenceUpdates()
{
m_GuiMessageQueue.erase(
std::remove_if(m_GuiMessageQueue.begin(), m_GuiMessageQueue.end(),
[](XmppClient::GUIMessage& message)
{
return message.type == L"chat" && message.level == L"presence";
}
), m_GuiMessageQueue.end());
}
/**
- * Used in order to update the GUI only once when multiple updates are queued.
- */
-int XmppClient::GetMucMessageCount()
-{
- return std::count_if(m_GuiMessageQueue.begin(), m_GuiMessageQueue.end(),
- [](XmppClient::GUIMessage& message)
- {
- return message.type == L"chat";
- });
-}
-
-/**
* Handle a room message.
*/
void XmppClient::handleMUCMessage(glooxwrapper::MUCRoom*, const glooxwrapper::Message& msg, bool priv)
{
DbgXMPP(msg.from().resource() << " said " << msg.body());
GUIMessage message;
message.type = L"chat";
message.level = priv ? L"private-message" : L"room-message";
message.from = wstring_from_utf8(msg.from().resource().to_string());
message.text = wstring_from_utf8(msg.body().to_string());
message.time = ComputeTimestamp(msg);
PushGuiMessage(message);
}
/**
* Handle a private message.
*/
void XmppClient::handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession *)
{
DbgXMPP("type " << msg.subtype() << ", subject " << msg.subject()
<< ", message " << msg.body() << ", thread id " << msg.thread());
GUIMessage message;
message.type = L"chat";
message.level = L"private-message";
message.from = wstring_from_utf8(msg.from().username().to_string());
message.text = wstring_from_utf8(msg.body().to_string());
message.time = ComputeTimestamp(msg);
PushGuiMessage(message);
}
/**
* Handle portions of messages containing custom stanza extensions.
*/
bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
{
DbgXMPP("handleIq [" << tag_xml(iq) << "]");
if (iq.subtype() == gloox::IQ::Result)
{
const GameListQuery* gq = iq.findExtension(EXTGAMELISTQUERY);
const BoardListQuery* bq = iq.findExtension(EXTBOARDLISTQUERY);
const ProfileQuery* pq = iq.findExtension(EXTPROFILEQUERY);
if (gq)
{
for (const glooxwrapper::Tag* const& t : m_GameList)
glooxwrapper::Tag::free(t);
m_GameList.clear();
for (const glooxwrapper::Tag* const& t : gq->m_GameList)
m_GameList.emplace_back(t->clone());
CreateGUIMessage("game", "gamelist");
}
if (bq)
{
if (bq->m_Command == "boardlist")
{
for (const glooxwrapper::Tag* const& t : m_BoardList)
glooxwrapper::Tag::free(t);
m_BoardList.clear();
for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
m_BoardList.emplace_back(t->clone());
CreateGUIMessage("game", "leaderboard");
}
else if (bq->m_Command == "ratinglist")
{
for (const glooxwrapper::Tag* const& t : bq->m_StanzaBoardList)
{
std::string name = t->findAttribute("name").to_string();
if (m_PlayerMap.find(name) != m_PlayerMap.end())
m_PlayerMap[name][1] = t->findAttribute("rating").to_string();
}
CreateGUIMessage("game", "ratinglist");
}
}
if (pq)
{
for (const glooxwrapper::Tag* const& t : m_Profile)
glooxwrapper::Tag::free(t);
m_Profile.clear();
for (const glooxwrapper::Tag* const& t : pq->m_StanzaProfile)
m_Profile.emplace_back(t->clone());
CreateGUIMessage("game", "profile");
}
}
else if (iq.subtype() == gloox::IQ::Error)
{
gloox::StanzaError err = iq.error_error();
CreateGUIMessage("system", "error", StanzaErrorToString(err));
}
else
{
CreateGUIMessage("system", "error", g_L10n.Translate("unknown subtype (see logs)"));
std::string tag = tag_name(iq);
LOGMESSAGE("unknown subtype '%s'", tag.c_str());
}
return true;
}
/**
* Create a new detail message for the GUI.
*
* @param type General message type
* @param level Detailed message type
* @param text Body of the message
* @param data Optional field, used for auxiliary data
*/
void XmppClient::CreateGUIMessage(const std::string& type, const std::string& level, const std::string& text, const std::string& data)
{
GUIMessage message;
message.type = wstring_from_utf8(type);
message.level = wstring_from_utf8(level);
message.text = wstring_from_utf8(text);
message.data = wstring_from_utf8(data);
message.time = std::time(nullptr);
PushGuiMessage(message);
}
/*****************************************************
* Presence, nickname, and subject *
*****************************************************/
/**
* Update local data when a user changes presence.
*/
void XmppClient::handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant participant, const glooxwrapper::Presence& presence)
{
std::string nick = participant.nick->resource().to_string();
gloox::Presence::PresenceType presenceType = presence.presence();
std::string presenceString, roleString;
GetPresenceString(presenceType, presenceString);
GetRoleString(participant.role, roleString);
if (presenceType == gloox::Presence::Unavailable)
{
if (!participant.newNick.empty() && (participant.flags & (gloox::UserNickChanged | gloox::UserSelf)))
{
// we have a nick change
std::string newNick = participant.newNick.to_string();
m_PlayerMap[newNick].resize(3);
m_PlayerMap[newNick][0] = presenceString;
m_PlayerMap[newNick][2] = roleString;
CreateGUIMessage("chat", "nick", nick, participant.newNick.to_string());
DbgXMPP(nick << " is now known as " << participant.newNick.to_string());
}
else if (participant.flags & gloox::UserKicked)
{
DbgXMPP(nick << " was kicked. Reason: " << participant.reason.to_string());
CreateGUIMessage("chat", "kicked", nick, participant.reason.to_string());
}
else if (participant.flags & gloox::UserBanned)
{
DbgXMPP(nick << " was banned. Reason: " << participant.reason.to_string());
CreateGUIMessage("chat", "banned", nick, participant.reason.to_string());
}
else
{
DbgXMPP(nick << " left the room (flags " << participant.flags << ")");
CreateGUIMessage("chat", "leave", nick);
}
m_PlayerMap.erase(nick);
}
else
{
/* During the initialization process, we recieve join messages for everyone
* currently in the room. We don't want to display these, so we filter them
* out. We will always be the last to join during initialization.
*/
if (!m_initialLoadComplete)
{
if (m_mucRoom->nick().to_string() == nick)
m_initialLoadComplete = true;
}
else if (m_PlayerMap.find(nick) == m_PlayerMap.end())
CreateGUIMessage("chat", "join", nick);
else if (m_PlayerMap[nick][2] != roleString)
CreateGUIMessage("chat", "role", nick, m_PlayerMap[nick][2]);
else
CreateGUIMessage("chat", "presence", nick);
DbgXMPP(nick << " is in the room, presence : " << (int)presenceType);
m_PlayerMap[nick].resize(3);
m_PlayerMap[nick][0] = presenceString;
m_PlayerMap[nick][2] = roleString;
}
}
/**
* Update local cache when subject changes.
*/
void XmppClient::handleMUCSubject(glooxwrapper::MUCRoom*, const glooxwrapper::string& UNUSED(nick), const glooxwrapper::string& subject)
{
m_Subject = subject.c_str();
CreateGUIMessage("chat", "subject", m_Subject);
}
/**
* Get current subject.
*
* @param topic Variable to store subject in.
*/
void XmppClient::GetSubject(std::string& subject)
{
subject = m_Subject;
}
/**
* Request nick change, real change via mucRoomHandler.
*
* @param nick Desired nickname
*/
void XmppClient::SetNick(const std::string& nick)
{
m_mucRoom->setNick(nick);
}
/**
* Get current nickname.
*
* @param nick Variable to store the nickname in.
*/
void XmppClient::GetNick(std::string& nick)
{
nick = m_mucRoom->nick().to_string();
}
/**
* Kick a player from the current room.
*
* @param nick Nickname to be kicked
* @param reason Reason the player was kicked
*/
void XmppClient::kick(const std::string& nick, const std::string& reason)
{
m_mucRoom->kick(nick, reason);
}
/**
* Ban a player from the current room.
*
* @param nick Nickname to be banned
* @param reason Reason the player was banned
*/
void XmppClient::ban(const std::string& nick, const std::string& reason)
{
m_mucRoom->ban(nick, reason);
}
/**
* Change the xmpp presence of the client.
*
* @param presence A string containing the desired presence
*/
void XmppClient::SetPresence(const std::string& presence)
{
#define IF(x,y) if (presence == x) m_mucRoom->setPresence(gloox::Presence::y)
IF("available", Available);
else IF("chat", Chat);
else IF("away", Away);
else IF("playing", DND);
else IF("offline", Unavailable);
// The others are not to be set
#undef IF
else LOGERROR("Unknown presence '%s'", presence.c_str());
}
/**
* Get the current xmpp presence of the given nick.
*
* @param nick Nickname to look up presence for
* @param presence Variable to store the presence in
*/
void XmppClient::GetPresence(const std::string& nick, std::string& presence)
{
if (m_PlayerMap.find(nick) != m_PlayerMap.end())
presence = m_PlayerMap[nick][0];
else
presence = "offline";
}
/**
* Get the current xmpp role of the given nick.
*
* @param nick Nickname to look up presence for
* @param role Variable to store the role in
*/
void XmppClient::GetRole(const std::string& nick, std::string& role)
{
if (m_PlayerMap.find(nick) != m_PlayerMap.end())
role = m_PlayerMap[nick][2];
else
role = "";
}
/*****************************************************
* Utilities *
*****************************************************/
/**
* Parse and return the timestamp of a historic chat message and return the current time for new chat messages.
* Historic chat messages are implement as DelayedDelivers as specified in XEP-0203.
* Hence, their timestamp MUST be in UTC and conform to the DateTime format XEP-0082.
*
* @returns Seconds since the epoch.
*/
std::time_t XmppClient::ComputeTimestamp(const glooxwrapper::Message& msg) const
{
// Only historic messages contain a timestamp!
if (!msg.when())
return std::time(nullptr);
// The locale is irrelevant, because the XMPP date format doesn't contain written month names
return g_L10n.ParseDateTime(msg.when()->stamp().to_string(), "Y-M-d'T'H:m:sZ", Locale::getUS()) / 1000.0;
}
/**
* Convert a gloox presence type to string.
*
* @param p Presence to be converted
* @param presence Variable to store the converted presence string in
*/
void XmppClient::GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const
{
switch(p)
{
#define CASE(x,y) case gloox::Presence::x: presence = y; break
CASE(Available, "available");
CASE(Chat, "chat");
CASE(Away, "away");
CASE(DND, "playing");
CASE(XA, "away");
CASE(Unavailable, "offline");
CASE(Probe, "probe");
CASE(Error, "error");
CASE(Invalid, "invalid");
default:
LOGERROR("Unknown presence type '%d'", (int)p);
break;
#undef CASE
}
}
/**
* Convert a gloox role type to string.
*
* @param p Role to be converted
* @param presence Variable to store the converted role string in
*/
void XmppClient::GetRoleString(const gloox::MUCRoomRole r, std::string& role) const
{
switch(r)
{
#define CASE(X, Y) case gloox::X: role = Y; break
CASE(RoleNone, "none");
CASE(RoleVisitor, "visitor");
CASE(RoleParticipant, "participant");
CASE(RoleModerator, "moderator");
CASE(RoleInvalid, "invalid");
default:
LOGERROR("Unknown role type '%d'", (int)r);
break;
#undef CASE
}
}
/**
* Convert a gloox stanza error type to string.
* Keep in sync with Gloox documentation
*
* @param err Error to be converted
* @return Converted error string
*/
std::string XmppClient::StanzaErrorToString(gloox::StanzaError err) const
{
#define CASE(X, Y) case gloox::X: return Y
#define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
switch (err)
{
CASE(StanzaErrorUndefined, g_L10n.Translate("No error"));
DEBUG_CASE(StanzaErrorBadRequest, "Server recieved malformed XML");
CASE(StanzaErrorConflict, g_L10n.Translate("Player already logged in"));
DEBUG_CASE(StanzaErrorFeatureNotImplemented, "Server does not implement requested feature");
CASE(StanzaErrorForbidden, g_L10n.Translate("Forbidden"));
DEBUG_CASE(StanzaErrorGone, "Unable to find message receipiant");
CASE(StanzaErrorInternalServerError, g_L10n.Translate("Internal server error"));
DEBUG_CASE(StanzaErrorItemNotFound, "Message receipiant does not exist");
DEBUG_CASE(StanzaErrorJidMalformed, "JID (XMPP address) malformed");
DEBUG_CASE(StanzaErrorNotAcceptable, "Receipiant refused message. Possible policy issue");
CASE(StanzaErrorNotAllowed, g_L10n.Translate("Not allowed"));
CASE(StanzaErrorNotAuthorized, g_L10n.Translate("Not authorized"));
DEBUG_CASE(StanzaErrorNotModified, "Requested item has not changed since last request");
DEBUG_CASE(StanzaErrorPaymentRequired, "This server requires payment");
CASE(StanzaErrorRecipientUnavailable, g_L10n.Translate("Recipient temporarily unavailable"));
DEBUG_CASE(StanzaErrorRedirect, "Request redirected");
CASE(StanzaErrorRegistrationRequired, g_L10n.Translate("Registration required"));
DEBUG_CASE(StanzaErrorRemoteServerNotFound, "Remote server not found");
DEBUG_CASE(StanzaErrorRemoteServerTimeout, "Remote server timed out");
DEBUG_CASE(StanzaErrorResourceConstraint, "The recipient is unable to process the message due to resource constraints");
CASE(StanzaErrorServiceUnavailable, g_L10n.Translate("Service unavailable"));
DEBUG_CASE(StanzaErrorSubscribtionRequired, "Service requires subscription");
DEBUG_CASE(StanzaErrorUnexpectedRequest, "Attempt to send from invalid stanza address");
DEBUG_CASE(StanzaErrorUnknownSender, "Invalid 'from' address");
default:
return g_L10n.Translate("Unknown error");
}
#undef DEBUG_CASE
#undef CASE
}
/**
* Convert a gloox connection error enum to string
* Keep in sync with Gloox documentation
*
* @param err Error to be converted
* @return Converted error string
*/
std::string XmppClient::ConnectionErrorToString(gloox::ConnectionError err) const
{
#define CASE(X, Y) case gloox::X: return Y
#define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
switch (err)
{
CASE(ConnNoError, g_L10n.Translate("No error"));
CASE(ConnStreamError, g_L10n.Translate("Stream error"));
CASE(ConnStreamVersionError, g_L10n.Translate("The incoming stream version is unsupported"));
CASE(ConnStreamClosed, g_L10n.Translate("The stream has been closed by the server"));
DEBUG_CASE(ConnProxyAuthRequired, "The HTTP/SOCKS5 proxy requires authentication");
DEBUG_CASE(ConnProxyAuthFailed, "HTTP/SOCKS5 proxy authentication failed");
DEBUG_CASE(ConnProxyNoSupportedAuth, "The HTTP/SOCKS5 proxy requires an unsupported authentication mechanism");
CASE(ConnIoError, g_L10n.Translate("An I/O error occured"));
DEBUG_CASE(ConnParseError, "An XML parse error occured");
CASE(ConnConnectionRefused, g_L10n.Translate("The connection was refused by the server"));
CASE(ConnDnsError, g_L10n.Translate("Resolving the server's hostname failed"));
CASE(ConnOutOfMemory, g_L10n.Translate("This system is out of memory"));
DEBUG_CASE(ConnNoSupportedAuth, "The authentication mechanisms the server offered are not supported or no authentication mechanisms were available");
CASE(ConnTlsFailed, g_L10n.Translate("The server's certificate could not be verified or the TLS handshake did not complete successfully"));
CASE(ConnTlsNotAvailable, g_L10n.Translate("The server did not offer required TLS encryption"));
DEBUG_CASE(ConnCompressionFailed, "Negotiation/initializing compression failed");
CASE(ConnAuthenticationFailed, g_L10n.Translate("Authentication failed. Incorrect password or account does not exist"));
CASE(ConnUserDisconnected, g_L10n.Translate("The user or system requested a disconnect"));
CASE(ConnNotConnected, g_L10n.Translate("There is no active connection"));
default:
return g_L10n.Translate("Unknown error");
}
#undef DEBUG_CASE
#undef CASE
}
/**
* Convert a gloox registration result enum to string
* Keep in sync with Gloox documentation
*
* @param err Enum to be converted
* @return Converted string
*/
std::string XmppClient::RegistrationResultToString(gloox::RegistrationResult res) const
{
#define CASE(X, Y) case gloox::X: return Y
#define DEBUG_CASE(X, Y) case gloox::X: return g_L10n.Translate("Error") + " (" + Y + ")"
switch (res)
{
CASE(RegistrationSuccess, g_L10n.Translate("Success"));
CASE(RegistrationNotAcceptable, g_L10n.Translate("Not all necessary information provided"));
CASE(RegistrationConflict, g_L10n.Translate("Username already exists"));
DEBUG_CASE(RegistrationNotAuthorized, "Account removal timeout or insufficiently secure channel for password change");
DEBUG_CASE(RegistrationBadRequest, "Server recieved incomplete request");
DEBUG_CASE(RegistrationForbidden, "Registration forbidden");
DEBUG_CASE(RegistrationRequired, "Account cannot be removed as it does not exist");
DEBUG_CASE(RegistrationUnexpectedRequest, "This client is unregistered with the server");
DEBUG_CASE(RegistrationNotAllowed, "Server does not permit password changes");
default:
return "";
}
#undef DEBUG_CASE
#undef CASE
}
void XmppClient::SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJIDStr)
{
ENSURE(stunEndpoint);
char ipStr[256] = "(error)";
ENetAddress addr;
addr.host = ntohl(stunEndpoint->ip);
enet_address_get_host_ip(&addr, ipStr, ARRAY_SIZE(ipStr));
glooxwrapper::JID hostJID(hostJIDStr);
glooxwrapper::Jingle::Session session = m_sessionManager->createSession(hostJID);
session.sessionInitiate(ipStr, stunEndpoint->port);
}
void XmppClient::handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session* UNUSED(session), const glooxwrapper::Jingle::Session::Jingle* jingle)
{
if (action == gloox::Jingle::SessionInitiate)
handleSessionInitiation(jingle);
}
void XmppClient::handleSessionInitiation(const glooxwrapper::Jingle::Session::Jingle* jingle)
{
glooxwrapper::Jingle::ICEUDP::Candidate candidate = jingle->getCandidate();
if (candidate.ip.empty())
{
LOGERROR("Failed to retrieve Jingle candidate");
return;
}
g_NetServer->SendHolePunchingMessage(candidate.ip.to_string(), candidate.port);
}
Index: ps/trunk/source/lobby/XmppClient.h
===================================================================
--- ps/trunk/source/lobby/XmppClient.h (revision 20039)
+++ ps/trunk/source/lobby/XmppClient.h (revision 20040)
@@ -1,169 +1,168 @@
/* 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
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef XXXMPPCLIENT_H
#define XXXMPPCLIENT_H
#include "IXmppClient.h"
#include
#include "glooxwrapper/glooxwrapper.h"
#include "scriptinterface/ScriptVal.h"
class ScriptInterface;
namespace glooxwrapper
{
class Client;
struct CertInfo;
}
class XmppClient : public IXmppClient, public glooxwrapper::ConnectionListener, public glooxwrapper::MUCRoomHandler, public glooxwrapper::IqHandler, public glooxwrapper::RegistrationHandler, public glooxwrapper::MessageHandler, public glooxwrapper::Jingle::SessionHandler
{
NONCOPYABLE(XmppClient);
private:
// Components
glooxwrapper::Client* m_client;
glooxwrapper::MUCRoom* m_mucRoom;
glooxwrapper::Registration* m_registration;
glooxwrapper::SessionManager* m_sessionManager;
// Account infos
std::string m_username;
std::string m_password;
std::string m_nick;
std::string m_xpartamuppId;
// State
bool m_initialLoadComplete;
public:
// Basic
XmppClient(const std::string& sUsername, const std::string& sPassword, const std::string& sRoom, const std::string& sNick, const int historyRequestSize = 0, const bool regOpt = false);
virtual ~XmppClient();
// Network
void connect();
void disconnect();
void recv();
void SendIqGetBoardList();
void SendIqGetProfile(const std::string& player);
void SendIqGameReport(const ScriptInterface& scriptInterface, JS::HandleValue data);
void SendIqRegisterGame(const ScriptInterface& scriptInterface, JS::HandleValue data);
void SendIqUnregisterGame();
void SendIqChangeStateGame(const std::string& nbp, const std::string& players);
void SetNick(const std::string& nick);
void GetNick(std::string& nick);
void kick(const std::string& nick, const std::string& reason);
void ban(const std::string& nick, const std::string& reason);
void SetPresence(const std::string& presence);
void GetPresence(const std::string& nickname, std::string& presence);
void GetRole(const std::string& nickname, std::string& role);
void GetSubject(std::string& subject);
void GUIGetPlayerList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void GUIGetGameList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void GUIGetBoardList(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void GUIGetProfile(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void SendStunEndpointToHost(StunClient::StunEndpoint* stunEndpoint, const std::string& hostJID);
protected:
/* Xmpp handlers */
/* MUC handlers */
virtual void handleMUCParticipantPresence(glooxwrapper::MUCRoom*, const glooxwrapper::MUCRoomParticipant, const glooxwrapper::Presence&);
virtual void handleMUCError(glooxwrapper::MUCRoom*, gloox::StanzaError);
virtual void handleMUCMessage(glooxwrapper::MUCRoom* room, const glooxwrapper::Message& msg, bool priv);
virtual void handleMUCSubject(glooxwrapper::MUCRoom*, const glooxwrapper::string& nick, const glooxwrapper::string& subject);
/* MUC handlers not supported by glooxwrapper */
// virtual bool handleMUCRoomCreation(glooxwrapper::MUCRoom*) {return false;}
// virtual void handleMUCInviteDecline(glooxwrapper::MUCRoom*, const glooxwrapper::JID&, const std::string&) {}
// virtual void handleMUCInfo(glooxwrapper::MUCRoom*, int, const std::string&, const glooxwrapper::DataForm*) {}
// virtual void handleMUCItems(glooxwrapper::MUCRoom*, const std::list >&) {}
/* Log handler */
virtual void handleLog(gloox::LogLevel level, gloox::LogArea area, const std::string& message);
/* ConnectionListener handlers*/
virtual void onConnect();
virtual void onDisconnect(gloox::ConnectionError e);
virtual bool onTLSConnect(const glooxwrapper::CertInfo& info);
/* Iq Handlers */
virtual bool handleIq(const glooxwrapper::IQ& iq);
virtual void handleIqID(const glooxwrapper::IQ&, int) {}
/* Registration Handlers */
virtual void handleRegistrationFields(const glooxwrapper::JID& /*from*/, int fields, glooxwrapper::string instructions );
virtual void handleRegistrationResult(const glooxwrapper::JID& /*from*/, gloox::RegistrationResult result);
virtual void handleAlreadyRegistered(const glooxwrapper::JID& /*from*/);
virtual void handleDataForm(const glooxwrapper::JID& /*from*/, const glooxwrapper::DataForm& /*form*/);
virtual void handleOOB(const glooxwrapper::JID& /*from*/, const glooxwrapper::OOB& oob);
/* Message Handler */
virtual void handleMessage(const glooxwrapper::Message& msg, glooxwrapper::MessageSession* session);
/* Session Handler */
virtual void handleSessionAction(gloox::Jingle::Action action, glooxwrapper::Jingle::Session* UNUSED(session), const glooxwrapper::Jingle::Session::Jingle* jingle);
virtual void handleSessionInitiation(const glooxwrapper::Jingle::Session::Jingle* jingle);
// Helpers
void GetPresenceString(const gloox::Presence::PresenceType p, std::string& presence) const;
void GetRoleString(const gloox::MUCRoomRole r, std::string& role) const;
std::string StanzaErrorToString(gloox::StanzaError err) const;
std::string ConnectionErrorToString(gloox::ConnectionError err) const;
std::string RegistrationResultToString(gloox::RegistrationResult res) const;
std::time_t ComputeTimestamp(const glooxwrapper::Message& msg) const;
public:
/* Messages */
struct GUIMessage
{
std::wstring type;
std::wstring level;
std::wstring text;
std::wstring data;
std::wstring from;
std::wstring message;
std::time_t time;
};
void GuiPollMessage(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
void SendMUCMessage(const std::string& message);
void ClearPresenceUpdates();
- int GetMucMessageCount();
protected:
void PushGuiMessage(XmppClient::GUIMessage message);
void CreateGUIMessage(const std::string& type, const std::string& level, const std::string& text = "", const std::string& data = "");
private:
/// Map of players
std::map > m_PlayerMap;
/// List of games
std::vector m_GameList;
/// List of rankings
std::vector m_BoardList;
/// Profile data
std::vector m_Profile;
/// Queue of messages for the GUI
std::deque m_GuiMessageQueue;
/// Current room subject/topic.
std::string m_Subject;
};
#endif // XMPPCLIENT_H
Index: ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp
===================================================================
--- ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp (revision 20039)
+++ ps/trunk/source/lobby/scripting/JSInterface_Lobby.cpp (revision 20040)
@@ -1,364 +1,358 @@
/* 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
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "JSInterface_Lobby.h"
#include "gui/GUIManager.h"
#include "lib/utf8.h"
#include "lobby/IXmppClient.h"
#include "ps/Profile.h"
#include "scriptinterface/ScriptInterface.h"
#include "third_party/encryption/pkcs5_pbkdf2.h"
#include "third_party/encryption/sha.h"
void JSI_Lobby::RegisterScriptFunctions(const ScriptInterface& scriptInterface)
{
// Lobby functions
scriptInterface.RegisterFunction("HasXmppClient");
scriptInterface.RegisterFunction("IsRankedGame");
scriptInterface.RegisterFunction("SetRankedGame");
#if CONFIG2_LOBBY // Allow the lobby to be disabled
scriptInterface.RegisterFunction("StartXmppClient");
scriptInterface.RegisterFunction("StartRegisterXmppClient");
scriptInterface.RegisterFunction("StopXmppClient");
scriptInterface.RegisterFunction("ConnectXmppClient");
scriptInterface.RegisterFunction("DisconnectXmppClient");
scriptInterface.RegisterFunction("SendGetBoardList");
scriptInterface.RegisterFunction("SendGetProfile");
scriptInterface.RegisterFunction("SendRegisterGame");
scriptInterface.RegisterFunction("SendGameReport");
scriptInterface.RegisterFunction("SendUnregisterGame");
scriptInterface.RegisterFunction("SendChangeStateGame");
scriptInterface.RegisterFunction("GetPlayerList");
scriptInterface.RegisterFunction("LobbyClearPresenceUpdates");
- scriptInterface.RegisterFunction("LobbyGetMucMessageCount");
scriptInterface.RegisterFunction("GetGameList");
scriptInterface.RegisterFunction("GetBoardList");
scriptInterface.RegisterFunction("GetProfile");
scriptInterface.RegisterFunction("LobbyGuiPollMessage");
scriptInterface.RegisterFunction("LobbySendMessage");
scriptInterface.RegisterFunction("LobbySetPlayerPresence");
scriptInterface.RegisterFunction("LobbySetNick");
scriptInterface.RegisterFunction("LobbyGetNick");
scriptInterface.RegisterFunction("LobbyKick");
scriptInterface.RegisterFunction("LobbyBan");
scriptInterface.RegisterFunction("LobbyGetPlayerPresence");
scriptInterface.RegisterFunction("LobbyGetPlayerRole");
scriptInterface.RegisterFunction("EncryptPassword");
scriptInterface.RegisterFunction("LobbyGetRoomSubject");
#endif // CONFIG2_LOBBY
}
bool JSI_Lobby::HasXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_XmppClient;
}
bool JSI_Lobby::IsRankedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_rankedGame;
}
void JSI_Lobby::SetRankedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool isRanked)
{
g_rankedGame = isRanked;
}
#if CONFIG2_LOBBY
void JSI_Lobby::StartXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& username, const std::wstring& password, const std::wstring& room, const std::wstring& nick, int historyRequestSize)
{
ENSURE(!g_XmppClient);
g_XmppClient = IXmppClient::create(utf8_from_wstring(username), utf8_from_wstring(password),
utf8_from_wstring(room), utf8_from_wstring(nick), historyRequestSize);
g_rankedGame = true;
}
void JSI_Lobby::StartRegisterXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& username, const std::wstring& password)
{
ENSURE(!g_XmppClient);
g_XmppClient = IXmppClient::create(utf8_from_wstring(username), utf8_from_wstring(password),
"", "", 0, true);
}
void JSI_Lobby::StopXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_XmppClient);
SAFE_DELETE(g_XmppClient);
g_rankedGame = false;
}
void JSI_Lobby::ConnectXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_XmppClient);
g_XmppClient->connect();
}
void JSI_Lobby::DisconnectXmppClient(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_XmppClient);
g_XmppClient->disconnect();
}
void JSI_Lobby::SendGetBoardList(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqGetBoardList();
}
void JSI_Lobby::SendGetProfile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& player)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqGetProfile(utf8_from_wstring(player));
}
void JSI_Lobby::SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqGameReport(*(pCxPrivate->pScriptInterface), data);
}
void JSI_Lobby::SendRegisterGame(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqRegisterGame(*(pCxPrivate->pScriptInterface), data);
}
void JSI_Lobby::SendUnregisterGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqUnregisterGame();
}
void JSI_Lobby::SendChangeStateGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nbp, const std::wstring& players)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqChangeStateGame(utf8_from_wstring(nbp), utf8_from_wstring(players));
}
JS::Value JSI_Lobby::GetPlayerList(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_XmppClient)
return JS::UndefinedValue();
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue playerList(cx);
g_XmppClient->GUIGetPlayerList(*(pCxPrivate->pScriptInterface), &playerList);
return playerList;
}
void JSI_Lobby::LobbyClearPresenceUpdates(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_XmppClient)
return;
g_XmppClient->ClearPresenceUpdates();
}
-int JSI_Lobby::LobbyGetMucMessageCount(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
-{
- return g_XmppClient ? g_XmppClient->GetMucMessageCount() : 0;
-}
-
JS::Value JSI_Lobby::GetGameList(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_XmppClient)
return JS::UndefinedValue();
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue gameList(cx);
g_XmppClient->GUIGetGameList(*(pCxPrivate->pScriptInterface), &gameList);
return gameList;
}
JS::Value JSI_Lobby::GetBoardList(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_XmppClient)
return JS::UndefinedValue();
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue boardList(cx);
g_XmppClient->GUIGetBoardList(*(pCxPrivate->pScriptInterface), &boardList);
return boardList;
}
JS::Value JSI_Lobby::GetProfile(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_XmppClient)
return JS::UndefinedValue();
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue profileFetch(cx);
g_XmppClient->GUIGetProfile(*(pCxPrivate->pScriptInterface), &profileFetch);
return profileFetch;
}
JS::Value JSI_Lobby::LobbyGuiPollMessage(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_XmppClient)
return JS::UndefinedValue();
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
JSAutoRequest rq(cx);
JS::RootedValue poll(cx);
g_XmppClient->GuiPollMessage(*(pCxPrivate->pScriptInterface), &poll);
return poll;
}
void JSI_Lobby::LobbySendMessage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& message)
{
if (!g_XmppClient)
return;
g_XmppClient->SendMUCMessage(utf8_from_wstring(message));
}
void JSI_Lobby::LobbySetPlayerPresence(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& presence)
{
if (!g_XmppClient)
return;
g_XmppClient->SetPresence(utf8_from_wstring(presence));
}
void JSI_Lobby::LobbySetNick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick)
{
if (!g_XmppClient)
return;
g_XmppClient->SetNick(utf8_from_wstring(nick));
}
std::wstring JSI_Lobby::LobbyGetNick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_XmppClient)
return L"";
std::string nick;
g_XmppClient->GetNick(nick);
return wstring_from_utf8(nick);
}
void JSI_Lobby::LobbyKick(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick, const std::wstring& reason)
{
if (!g_XmppClient)
return;
g_XmppClient->kick(utf8_from_wstring(nick), utf8_from_wstring(reason));
}
void JSI_Lobby::LobbyBan(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nick, const std::wstring& reason)
{
if (!g_XmppClient)
return;
g_XmppClient->ban(utf8_from_wstring(nick), utf8_from_wstring(reason));
}
std::wstring JSI_Lobby::LobbyGetPlayerPresence(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname)
{
if (!g_XmppClient)
return L"";
std::string presence;
g_XmppClient->GetPresence(utf8_from_wstring(nickname), presence);
return wstring_from_utf8(presence);
}
std::wstring JSI_Lobby::LobbyGetPlayerRole(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& nickname)
{
if (!g_XmppClient)
return L"";
std::string role;
g_XmppClient->GetRole(utf8_from_wstring(nickname), role);
return wstring_from_utf8(role);
}
// Non-public secure PBKDF2 hash function with salting and 1,337 iterations
std::string JSI_Lobby::EncryptPassword(const std::string& password, const std::string& username)
{
const int DIGESTSIZE = SHA_DIGEST_SIZE;
const int ITERATIONS = 1337;
static const unsigned char salt_base[DIGESTSIZE] = {
244, 243, 249, 244, 32, 33, 34, 35, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 133, 123, 234, 123 };
// initialize the salt buffer
unsigned char salt_buffer[DIGESTSIZE] = {0};
SHA256 hash;
hash.update(salt_base, sizeof(salt_base));
hash.update(username.c_str(), username.length());
hash.finish(salt_buffer);
// PBKDF2 to create the buffer
unsigned char encrypted[DIGESTSIZE];
pbkdf2(encrypted, (unsigned char*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS);
static const char base16[] = "0123456789ABCDEF";
char hex[2 * DIGESTSIZE];
for (int i = 0; i < DIGESTSIZE; ++i)
{
hex[i*2] = base16[encrypted[i] >> 4]; // 4 high bits
hex[i*2 + 1] = base16[encrypted[i] & 0x0F]; // 4 low bits
}
return std::string(hex, sizeof(hex));
}
std::wstring JSI_Lobby::EncryptPassword(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::wstring& pass, const std::wstring& user)
{
return wstring_from_utf8(JSI_Lobby::EncryptPassword(utf8_from_wstring(pass), utf8_from_wstring(user)));
}
std::wstring JSI_Lobby::LobbyGetRoomSubject(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_XmppClient)
return L"";
std::string subject;
g_XmppClient->GetSubject(subject);
return wstring_from_utf8(subject);
}
#endif
Index: ps/trunk/source/lobby/scripting/JSInterface_Lobby.h
===================================================================
--- ps/trunk/source/lobby/scripting/JSInterface_Lobby.h (revision 20039)
+++ ps/trunk/source/lobby/scripting/JSInterface_Lobby.h (revision 20040)
@@ -1,70 +1,69 @@
/* 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
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_JSI_LOBBY
#define INCLUDED_JSI_LOBBY
#include "lib/config2.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptVal.h"
namespace JSI_Lobby
{
void RegisterScriptFunctions(const ScriptInterface& scriptInterface);
bool HasXmppClient(ScriptInterface::CxPrivate* pCxPrivate);
bool IsRankedGame(ScriptInterface::CxPrivate* pCxPrivate);
void SetRankedGame(ScriptInterface::CxPrivate* pCxPrivate, bool isRanked);
#if CONFIG2_LOBBY
void StartXmppClient(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& username, const std::wstring& password, const std::wstring& room, const std::wstring& nick, int historyRequestSize);
void StartRegisterXmppClient(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& username, const std::wstring& password);
void StopXmppClient(ScriptInterface::CxPrivate* pCxPrivate);
void ConnectXmppClient(ScriptInterface::CxPrivate* pCxPrivate);
void DisconnectXmppClient(ScriptInterface::CxPrivate* pCxPrivate);
void SendGetBoardList(ScriptInterface::CxPrivate* pCxPrivate);
void SendGetProfile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& player);
void SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data);
void SendRegisterGame(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data);
void SendUnregisterGame(ScriptInterface::CxPrivate* pCxPrivate);
void SendChangeStateGame(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nbp, const std::wstring& players);
JS::Value GetPlayerList(ScriptInterface::CxPrivate* pCxPrivate);
void LobbyClearPresenceUpdates(ScriptInterface::CxPrivate* pCxPrivate);
- int LobbyGetMucMessageCount(ScriptInterface::CxPrivate* pCxPrivate);
JS::Value GetGameList(ScriptInterface::CxPrivate* pCxPrivate);
JS::Value GetBoardList(ScriptInterface::CxPrivate* pCxPrivate);
JS::Value GetProfile(ScriptInterface::CxPrivate* pCxPrivate);
JS::Value LobbyGuiPollMessage(ScriptInterface::CxPrivate* pCxPrivate);
void LobbySendMessage(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& message);
void LobbySetPlayerPresence(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& presence);
void LobbySetNick(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nick);
std::wstring LobbyGetNick(ScriptInterface::CxPrivate* pCxPrivate);
void LobbyKick(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nick, const std::wstring& reason);
void LobbyBan(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nick, const std::wstring& reason);
std::wstring LobbyGetPlayerPresence(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nickname);
std::wstring LobbyGetPlayerRole(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& nickname);
std::wstring LobbyGetRoomSubject(ScriptInterface::CxPrivate* pCxPrivate);
// Non-public secure PBKDF2 hash function with salting and 1,337 iterations
std::string EncryptPassword(const std::string& password, const std::string& username);
// Public hash interface.
std::wstring EncryptPassword(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& pass, const std::wstring& user);
#endif // CONFIG2_LOBBY
}
#endif