Created
March 24, 2020 06:06
-
-
Save badosu/4a4ddcbd7d3d1aab328e1fee508a8ec3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
/** | |
* Used for civ settings display of the selected game. | |
*/ | |
const g_CivData = loadCivData(false, false); | |
/** | |
* A symbol which is prepended to the username of moderators. | |
*/ | |
var g_ModeratorPrefix = "@"; | |
const g_EngineInfo = Engine.GetEngineInfo(); | |
/** | |
* 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. | |
*/ | |
var g_GameColors = { | |
"init": { "style": {}, "buddyStyle": {} }, | |
"waiting": { "style": {}, "buddyStyle": {} }, | |
"running": { "style": {}, "buddyStyle": {} }, | |
"incompatible": { "style": { "color": "128 128 128" }, "buddyStyle": { "color": "160 160 160" } } | |
}; | |
/** | |
* Initial sorting order of the gamelist. | |
*/ | |
var g_GameStatusOrder = Object.keys(g_GameColors); | |
/** | |
* The playerlist will be assembled using these values. | |
*/ | |
var g_PlayerStatuses = { | |
"available": { "style": {}, "buddyStyle": {} , "status": translate("Online") }, | |
"away": { "style": {}, "buddyStyle": {} , "status": translate("Away") }, | |
"playing": { "style": {}, "buddyStyle": {} , "status": translate("Busy") }, | |
"offline": { "style": {}, "buddyStyle": {} , "status": translate("Offline") }, | |
"unknown": { "style": {}, "buddyStyle": {} , "status": translateWithContext("lobby presence", "Unknown") } | |
}; | |
/** | |
* Style for indicating the user in the playerlist and the game where he is listed. | |
*/ | |
var g_UserStyle; | |
/** | |
* Configuration auto away. | |
*/ | |
var g_AutoAway = { | |
"timeMinutes": +Engine.ConfigDB_GetValue("user", "lobby.autoawaytime"), | |
"time": false, | |
"background": false, | |
"timeInBackground": false, | |
"timerHandler": 0, | |
"applied": false | |
}; | |
/** | |
* Different states for presence and auto-away feature. | |
* Called as function due to configurable g_AutoAway.timeMinutes. | |
*/ | |
var g_AutoAwayStates = [ | |
{ | |
"name": "available", | |
"desc": () => translate("Available"), | |
"func": () => setPlayerPresence("available", false, false, false) | |
}, | |
{ | |
"name": "away", | |
"desc": () => translate("Away"), | |
"func": () => setPlayerPresence("away", false, false, false) | |
}, | |
{ | |
"name": "available_awaytime", | |
"desc": () => sprintf(translate("Away after %(minutes)s %(minute)s"), | |
{ | |
"minutes": g_AutoAway.timeMinutes, | |
"minute": translatePlural("minute", "minutes", g_AutoAway.timeMinutes) | |
}), | |
"func": () => setPlayerPresence("available", true, false, false) | |
} | |
]; | |
/** | |
* Indicator for window focus state. | |
*/ | |
var g_WindowFocus = true; | |
/** | |
* Presence change, after timeout apply latest. | |
*/ | |
var g_Presence = { | |
"timer": 0, | |
"setNew": "", | |
"timeoutMilliSeconds": 500 | |
}; | |
var g_RoleNames = { | |
"moderator": translate("Moderator"), | |
"participant": translate("Player"), | |
"visitor": translate("Muted Player") | |
}; | |
/** | |
* Color for error messages in the chat. | |
*/ | |
var g_SystemColor = "150 0 0"; | |
/** | |
* Color for private messages in the chat. | |
*/ | |
var g_PrivateMessageColor = "0 150 0"; | |
/** | |
* Used for highlighting the sender of chat messages. | |
*/ | |
var g_SenderFont = "sans-bold-13"; | |
/** | |
* Color to highlight chat commands in the explanation. | |
*/ | |
var g_ChatCommandColor = "200 200 255"; | |
/** | |
* Indicates if the lobby is opened as a dialog or window. | |
*/ | |
var g_Dialog = false; | |
/** | |
* 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 = []; | |
/** | |
* All players in the lobby. | |
*/ | |
var g_PlayerList = []; | |
/** | |
* Set gamelist column sort. | |
*/ | |
var g_GamesSort = []; | |
/** | |
* Set playerlist column sort. | |
*/ | |
var g_PlayersSort = []; | |
/** | |
* 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 = ""; | |
var g_SelectedGameName = ""; | |
/** | |
* Whether the current user has been kicked or banned. | |
*/ | |
var g_Kicked = false; | |
/** | |
* Whether the player was already asked to reconnect to the lobby. | |
* Ensures that no more than one message box is opened at a time. | |
*/ | |
var g_AskedReconnect = false; | |
var g_CallbackSet = false; | |
var g_InGame = false; | |
/** | |
* Manage stacked cancel hotkey. Call first true condition onPress function. | |
*/ | |
let g_CancelHotkey = [ | |
() => setLeaderboardVisibility(false), | |
() => setUserProfileVisibility(false) | |
]; | |
var g_OptionsPage = "Lobby"; | |
/** | |
* Store civilization code and page (structree or history) opened in civilization info. | |
*/ | |
var g_CivInfo = { | |
"code": "", | |
"page": "page_structree.xml" | |
}; | |
var g_SendMsgOnReconnect = []; | |
var g_ReconnectXmpp = false; | |
/** | |
* List of more buttons bar below the chat input. | |
*/ | |
var g_MoreButtonsBarFuncs = { | |
"Options": { | |
"name": "Options", | |
"color": "210 210 210", | |
"func": openGameOptions, | |
"tooltip": colorizeHotkey("%(hotkey)s - Open " + setStringTags("options", { "color": "white" }) + ".", "options") }, | |
"Civilizations": { | |
"name": "Civilizations", | |
"color": "170 255 170", | |
"func": openCivInfo, | |
"tooltip": colorizeHotkey("%(hotkey)s - Open " + setStringTags("structure tree", { "color": "white" }) + "/" + setStringTags("history", { "color": "gray" }) + ".", "structree") }, | |
"Forum": { | |
"name": "Forum", | |
"func": () => Engine.OpenURL("https://wildfiregames.com/forum/"), | |
"tooltip": "Open the wildfiregames "+setStringTags("forum", { "color" :"white" })+" in the browser." }, | |
"Replays": { | |
"name": "Replays", | |
"color": "210 255 255", | |
"func": () => Engine.PushGuiPage("page_replaymenu.xml", { | |
"ingame": g_InGame, "dialog": true, "callback": "startReplay" }) }, | |
"Scores": { | |
"name": "Scores", | |
"color": "255 187 170", | |
"func": showLastGameSummary, | |
"tooltip": "Show last " + setStringTags("summary", { "color": "white" }) + "." }, | |
"Chat": { | |
"name": "Chat", | |
"color": "210 210 210", | |
"func": () => | |
//g_ChatCommands["pinggamebuddies"].handler(), | |
g_ChatCommands["help"].handler(), | |
"tooltip": "Show chat commands." | |
}, | |
"Donate": { | |
"name": "Support", | |
"color": "225 170 170", | |
"func": () => Engine.OpenURL("https://www.paypal.com"), | |
"tooltip": "If you feel well with the mod, support the current and following work with some $ or € for Coffee and Fanta in PayPal to [email protected]." } | |
// "tooltip": "If you feel well with the project and everything works, think about Support the work for 0ad with coffee and fanta. Paypal: [email protected]" }, | |
}; | |
function openCivInfo() | |
{ | |
Engine.PushGuiPage(g_CivInfo.page, { "civ": g_CivInfo.code, "callback": "storeCivInfoPage" }); | |
} | |
function openGameOptions() | |
{ | |
Engine.PushGuiPage("page_options.xml", { | |
"selectedCategory": g_OptionsPage || "Lobby", | |
"callback": "initUserConfigurables" | |
}) | |
} | |
var g_Muted = false; | |
/** | |
* 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 => false, | |
"connected": msg => { | |
let text = ""; | |
while ((text = g_SendMsgOnReconnect.shift())) | |
Engine.LobbySendMessage(text); | |
g_AskedReconnect = false; | |
updateConnectedState(); | |
return false; | |
}, | |
"disconnected": msg => { | |
if (g_ReconnectXmpp) | |
{ | |
Engine.ConnectXmppClient(); | |
g_ReconnectXmpp = false; | |
} | |
else | |
{ | |
updateGameList(); | |
updateLeaderboard(); | |
updateConnectedState(); | |
if (!g_Kicked) | |
{ | |
addChatMessage({ | |
"from": "system", | |
"time": msg.time, | |
"text": translate("Disconnected.") + " " + msg.reason | |
}); | |
reconnectMessageBox(); | |
} | |
} | |
return !msg.historic; | |
}, | |
"error": msg => { | |
addChatMessage({ | |
"from": "system", | |
"time": msg.time, | |
"text": msg.text | |
}); | |
return false; | |
} | |
}, | |
"chat": { | |
"subject": msg => { | |
updateSubject(msg.subject); | |
if (msg.nick) | |
addChatMessage({ | |
"text": "/special " + sprintf(translate("%(nick)s changed the lobby subject to %(subject)s"), { | |
"nick": msg.nick, | |
"subject": msg.subject | |
}), | |
"time": msg.time, | |
"isSpecial": true, | |
"subject": true, | |
"historic": msg.historic | |
}); | |
return false; | |
}, | |
"join": msg => { | |
addChatMessage({ | |
"text": "/special " + sprintf(translate("%(nick)s has joined."), { | |
"nick": msg.nick | |
}), | |
"time": msg.time, | |
"isSpecial": true, | |
"historic": msg.historic | |
}); | |
return !msg.historic; | |
}, | |
"leave": msg => { | |
addChatMessage({ | |
"text": "/special " + sprintf(translate("%(nick)s has left."), { | |
"nick": msg.nick | |
}), | |
"time": msg.time, | |
"isSpecial": true, | |
"historic": msg.historic | |
}); | |
if (msg.nick == g_Username) | |
Engine.DisconnectXmppClient(); | |
return !msg.historic; | |
}, | |
"presence": msg => !msg.historic, | |
"role": msg => { | |
Engine.GetGUIObjectByName("chatInput").hidden = Engine.LobbyGetPlayerRole(g_Username) == "visitor"; | |
let me = g_Username == msg.nick; | |
let newrole = Engine.LobbyGetPlayerRole(msg.nick); | |
g_Muted = me && newrole == "visitor"; | |
let txt = | |
newrole == "visitor" ? | |
me ? | |
translate("You have been muted.") : | |
translate("%(nick)s has been muted.") : | |
newrole == "moderator" ? | |
me ? | |
translate("You are now a moderator.") : | |
translate("%(nick)s is now a moderator.") : | |
msg.oldrole == "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.nick }), | |
"time": msg.time, | |
"isSpecial": true, | |
"historic": msg.historic | |
}); | |
if (g_SelectedPlayer == msg.nick) | |
updateUserRoleText(g_SelectedPlayer); | |
return false; | |
}, | |
"nick": msg => { | |
addChatMessage({ | |
"text": "/special " + sprintf(translate("%(oldnick)s is now known as %(newnick)s."), { | |
"oldnick": msg.oldnick, | |
"newnick": msg.newnick | |
}), | |
"time": msg.time, | |
"isSpecial": true, | |
"historic": msg.historic | |
}); | |
return !msg.historic; | |
}, | |
"kicked": msg => { | |
handleKick(false, msg.nick, msg.reason, msg.time, msg.historic); | |
return !msg.historic; | |
}, | |
"banned": msg => { | |
handleKick(true, msg.nick, msg.reason, msg.time, msg.historic); | |
return !msg.historic; | |
}, | |
"room-message": msg => { | |
// warn("hi2") | |
// warn("room msg lobby: " + uneval(msg)); | |
addChatMessage({ | |
"from": escapeText(msg.from), | |
"text": escapeText(msg.text), | |
"time": msg.time, | |
"historic": msg.historic | |
}); | |
return false; | |
}, | |
"private-message": msg => { | |
// warn("hi") | |
// warn("private msg lobby: " + uneval(msg)); | |
msg.private = true; | |
if (!msg.text) | |
return true; | |
// Announcements and the Message of the Day are sent by the server directly | |
if (!msg.from) | |
messageBox( | |
400, 250, | |
msg.text.trim(), | |
translate("Notice") | |
); | |
// warn("private msg lobby: " + uneval(msg)); | |
// 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, | |
"historic": msg.historic, | |
"private": true | |
}); | |
return false; | |
} | |
}, | |
"game": { | |
"gamelist": msg => { | |
updateGameList(g_AutoScrollGameListFromSelection); | |
g_InitGameList = false; | |
g_AutoScrollGameListFromSelection = false; | |
return false; | |
}, | |
"profile": msg => { | |
updateProfile(); | |
return false; | |
}, | |
"leaderboard": msg => { | |
updateLeaderboard(); | |
return false; | |
}, | |
"ratinglist": msg => { | |
return !msg.historic; | |
} | |
} | |
}; | |
function joinChatCommandsArgs(args) | |
{ | |
let a = args.filter(a => a); | |
return a.length ? a.join(" ") + " " : ""; | |
} | |
/** | |
* Remembering Away Option when go "/away" and "/back" | |
*/ | |
var g_SavedAwayOption = 0; | |
const g_DefaultChatSendPingExclude = player => | |
![ "WFGbot", g_Username, "Ratings" ].some(name => player.name == name) && (player.presence == "available" || player.presence == "away" || !g_PlayerListInGame[player.name] || !!g_PlayerListInGameObserver[player.name]) && player.role != "moderator" | |
const defaultChatSendPing = (filter, msg) => | |
{ | |
let players = Engine.GetPlayerList().filter(player => g_DefaultChatSendPingExclude(player) && filter(player)).map(player => player.name); | |
if (msg.search("%players") != -1) | |
msg = msg.replace("%players", players.join(", ")); | |
else | |
msg = msg + players.join(", "); | |
let msgObj = { name: "playerPing", text: msg }; //, "text": "Please get ready " + names.join(", ") + "" } | |
spamFilter(msgObj, msg => submitChatMessageOverMax(msg.text), 5000, (msg, timeLeft) => | |
addChatMessage({ "type": "system", "from": "system", "text": "Spam "+ Math.ceil(timeLeft/1000) + " <= 5 secs"} ) ); | |
// submitChatMessageOverMax(msg) | |
return false; | |
} | |
const g_ChatMaxChars = 255; | |
function submitChatMessageOverMax(msg) | |
{ | |
let i = 0; | |
while (i < msg.length) | |
{ | |
Engine.LobbySendMessage(msg.substr(i,g_ChatMaxChars)); | |
i += g_ChatMaxChars; | |
} | |
} | |
/** | |
* 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 = { | |
"link": { | |
"description": translate("Open Link [0-9]*. Ex: /link, /link 1"), | |
"handler": args => true }, | |
"linklist": { | |
"description": translate("Shows link list"), | |
"handler": args => addChatMessage({ | |
"from": "Link List", | |
"text": "\n"+g_ChatLinkList.map((a,i) => i+": "+a).join("\n") | |
}) }, | |
"away": { | |
"description": translate("Set your state to 'Away'."), | |
"handler": args => { | |
Engine.LobbySetPlayerPresence("away"); | |
let presenceDropdown = Engine.GetGUIObjectByName("presenceDropdown"); | |
g_SavedAwayOption = presenceDropdown.selected; | |
presenceDropdown.selected = g_AutoAwayStates.findIndex(opt => opt.name == "away"); | |
return false; | |
} | |
}, | |
"back": { | |
"description": translate("Set your state to 'Online'."), | |
"handler": args => { | |
Engine.LobbySetPlayerPresence("available"); | |
Engine.GetGUIObjectByName("presenceDropdown").selected = g_SavedAwayOption; | |
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": coloredText(command, g_ChatCommandColor), | |
"description": escapeText(g_ChatCommands[command].description) | |
}); | |
addChatMessage({ | |
"from": "system", | |
"text": text | |
}); | |
return false; | |
} | |
}, | |
"showip": { | |
"description": translate("Show ip and port of the selected game from the gamelist."), | |
"handler": args => { | |
let gamesBox = Engine.GetGUIObjectByName("gameList"); | |
if (gamesBox.selected > -1) | |
{ | |
let game = g_GameList[gamesBox.selected]; | |
let ip = game.stunIP ? game.stunIP : game.ip; | |
let port = game.stunPort ? game.stunPort : game.port; | |
print("IP from " + escapeText(game.name) + ": " + ip + ":" + port) | |
addChatMessage({ | |
"from": "system", | |
"text": "IP from " + escapeText(game.name) + ": " + ip + ":" + port | |
}); | |
} | |
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 | |
}, | |
"private": { | |
"description": translate("Send private text as a chat message (even if it starts with slash). Example: /private playername message."), | |
"handler": args => { return true; } | |
}, | |
"clear": { | |
"description": translate("Clear all chat scrollback."), | |
"handler": args => { | |
clearChatMessages(); | |
return false; | |
} | |
}, | |
"quit": { | |
"description": translate("Return to the main menu."), | |
"handler": args => { | |
leaveLobby(); | |
return false; | |
} | |
}, | |
"pingbuddies": { | |
"description": translate("Ping buddies. Arguments can be used to write a text to buddies. %players can be used as placeholder for buddies. Ex: /pingbuddies, /pingbuddies Hi, /pingbuddies %players nubs"), | |
"handler": args => { | |
defaultChatSendPing(player => g_Buddies.some(name => name == player.name), joinChatCommandsArgs(args)); | |
return false; | |
} | |
}, | |
"pinggamebuddies": { | |
"description": translate("Ping online/away buddies that are not in a game as players for a game."), | |
"handler": args => { | |
// warn(g_SelectedGameIP) | |
// let gameName = g_SelectedGameIP && Engine.GetGameList().find(game => game.stunIP == g_SelectedGameIP || game.ip == g_SelectedGameIP); | |
// warn(uneval(gameName == undefined)); | |
// defaultChatSendPing(player => g_Buddies.some(name => name == player.name), "Go for a game? " + args + (gameName == undefined ? "" : " (join: '"+gameName+"')")); | |
defaultChatSendPing(player => g_Buddies.some(name => name == player.name), "Go for a game? " + joinChatCommandsArgs(args)); | |
return false; | |
} | |
}, | |
"pinggamenobuddies": { | |
"description": translate("Ping online/away players that are not buddies that are not in a game as players for a game."), | |
"handler": args => { | |
defaultChatSendPing(player => !g_Buddies.some(name => name == player.name), "Go for a game? " + joinChatCommandsArgs(args)); | |
return false; | |
} | |
}, | |
"pinggameall": { | |
"description": translate("Ping online/away players that are not in a game as players for a game."), | |
"handler": args => { | |
defaultChatSendPing(player => true, "Go for a game? " + joinChatCommandsArgs(args)); | |
return false; | |
} | |
}, | |
"discord": { | |
"description": translate("Open discord forum thread in browser."), | |
"handler": args => { | |
Engine.OpenURL("https://wildfiregames.com/forum/index.php?/topic/22225-0ad-discord-server"); | |
return false; | |
} | |
}, | |
"discordchannel": { | |
"description": translate("Open discord channel in browser."), | |
"handler": args => { | |
Engine.OpenURL("https://discord.gg/cwp99Wj"); | |
return false; | |
} | |
}, | |
"forumlatest": { | |
"description": translate("Open Forum lastest posts in browser."), | |
"handler": args => { | |
Engine.OpenURL("https://wildfiregames.com/forum/index.php?/discover/unread/&stream_read=all"); | |
return false; | |
} | |
}, | |
"forumunread": { | |
"description": translate("Open Forum unread posts in browser."), | |
"handler": args => { | |
Engine.OpenURL("https://wildfiregames.com/forum/index.php?/discover/unread/#"); | |
return false; | |
} | |
}, | |
"forumreplays": { | |
"description": translate("Open Forum replays in browser."), | |
"handler": args => { | |
Engine.OpenURL("https://wildfiregames.com/forum/index.php?/forum/468-alpha-23/"); | |
return false; | |
} | |
} | |
}; | |
function clearPlayerSelectionTooltip(show) | |
{ | |
Engine.GetGUIObjectByName("playerList").tooltip = | |
show ? '' + | |
setStringTags(escapeText("[Double Click]"), { "color": "yellow" }) + " - Show Profile.\n" + | |
setStringTags(escapeText("[Escape]"), { "color": "yellow" }) + " - Clear selection.\n" | |
: ""; | |
} | |
function clearGameSelectionTooltip(show) | |
{ | |
Engine.GetGUIObjectByName("gameList").tooltip = | |
show ? '' + setStringTags(escapeText("[Escape]"), { "color": "yellow" }) + " - Clear selection." | |
: ""; | |
} | |
var g_AutoScrollGameListFromSelection = false; | |
var g_FocusObj = { | |
list: [], | |
switchToNext: [], | |
localTabHotkey: [], | |
ptr: -1 | |
} | |
/** | |
* list: | |
obj | |
switchToNext: (), | |
localTabHotkey: (), | |
focus: () | |
*/ | |
var g_FocusObj_ = { | |
list: [], | |
ptr: -1 | |
} | |
/** | |
* Cycle through focus objects | |
*/ | |
function focusSwitch() | |
{ | |
// warn(g_FocusObj.ptr + " " + g_FocusObj.list.length) | |
if (g_FocusObj.ptr >= 0) | |
{ | |
// warn("hi" + g_FocusObj.ptr); | |
if (!g_FocusObj.switchToNext[g_FocusObj.ptr](g_FocusObj.list[g_FocusObj.ptr])) | |
{ | |
g_FocusObj.localTabHotkey[g_FocusObj.ptr](); | |
return; | |
} | |
} | |
++g_FocusObj.ptr; | |
// warn("go " +g_FocusObj.ptr) | |
g_FocusObj.ptr = g_FocusObj.ptr >= g_FocusObj.list.length ? 0 : g_FocusObj.ptr; | |
// warn(g_FocusObj.list[g_FocusObj.ptr]) | |
g_FocusObj.focus[g_FocusObj.ptr](g_FocusObj.list[g_FocusObj.ptr]); | |
} | |
function initFocusSwitcher() | |
{ | |
return | |
for (let i of [ "chatInput", "playerList", "gameList" ]) | |
{ | |
g_FocusObj.list.push(Engine.GetGUIObjectByName(i)); | |
} | |
g_FocusObj.switchToNext.push(obj => { return !obj.caption.length; }); | |
g_FocusObj.focus.push(obj => obj.focus()); | |
g_FocusObj.localTabHotkey.push(obj => { autoCompleteNick(this, g_PlayerList.map(player => player.name).concat( | |
Object.keys(g_ChatCommands).sort().map(com => { return "/" + com; }))) }); | |
g_FocusObj.switchToNext.push(() => true); | |
g_FocusObj.focus.push(obj => { if (obj.selected == -1) obj.selected = 0; obj.focus(); }); | |
g_FocusObj.switchToNext.push(() => true); | |
g_FocusObj.focus.push(obj => { if (obj.selected == -1) obj.selected = 0; obj.focus(); }); | |
} | |
function updateTime() | |
{ | |
let time = Engine.FormatMillisecondsIntoDateStringLocal(Date.now(), translate("HH:mm")); | |
//(new Date()).toLocaleTimeString() | |
Engine.GetGUIObjectByName("chatInputTime").caption = | |
setStringTags(escapeText("[") + time + escapeText("]"), | |
{ "color": "255 255 255" }); | |
// { "color": "102 226 255" }); | |
setTimeout(updateTime, 1000); | |
} | |
var g_ChatLinkList = []; | |
function unev(e) | |
{ | |
warn(uneval(e)); | |
} | |
function fa(callbackResult, checkPositions, finalPositions = []) | |
{ | |
let pos = 0; | |
// warn(uneval(checkPositions)) | |
// return; | |
if (checkPositions.length) | |
for (let i of checkPositions) | |
fa(callbackResult, checkPositions.slice().filter(a => a != i), finalPositions.concat([i])); | |
else | |
callbackResult(finalPositions); | |
// callbackResult(finalPositions); | |
// print(b.join(",")+"\n"); | |
} | |
let g_PlayersRated = [ | |
{ "name": "f", "rate": 17 }, | |
{ "name": "f1", "rate": 25 }, | |
{ "name": "f2", "rate": 23 }, | |
{ "name": "f3", "rate": 20 }, | |
{ "name": "f4", "rate": 19 }, | |
{ "name": "f5", "rate": 26 }, | |
{ "name": "f6", "rate": 22 }, | |
{ "name": "f7", "rate": 29 }, | |
]; | |
let g_PlayersRatedRateRatio = g_PlayersRated.map(a => a.rate).reduce((a, b) => a + b) / g_PlayersRated.length; | |
function teamsRate(pl, teams) | |
{ | |
let playerPerTeam = Math.floor(pl.length / teams); | |
let teamIndex = index => Math.floor(index / playerPerTeam); | |
return pl.map(a => a.rate).reduce((a, b, i) => { | |
a[teamIndex(i)] += b; return a; | |
}, Array(teams).fill(0)).map(a => a / playerPerTeam); | |
} | |
let g_PlayersRateBestConfig = []; | |
let g_PlayersRateBestConfigError = Number.MAX_SAFE_INTEGER; | |
var c= 0; | |
function checkPlayerRateConfig(playerRatePositions) | |
{ | |
let playerRateConfig = playerRatePositions.map(a => g_PlayersRated[a]); | |
let playersRateConfigError = teamsRate(playerRateConfig, 2).map(rate => Math.abs(rate - g_PlayersRatedRateRatio)).reduce((a, b) => a + b); | |
if (playersRateConfigError < g_PlayersRateBestConfigError) | |
{ | |
g_PlayersRateBestConfig = playerRateConfig; | |
g_PlayersRateBestConfigError = playersRateConfigError; | |
} | |
} | |
const g_PlayerListFilterPlayerDefaultValue = "Filter player, games, attributes"; | |
/** | |
* Called after the XmppConnection succeeded and when returning from a game. | |
* | |
* @param {Object} attribs | |
*/ | |
function init(attribs = {}) | |
{ | |
// loadReplays(null, false); | |
// g_Replays = g_Replays.filter(replay => replay.isMultiplayer); | |
// let games = 0; | |
// for (let replay of g_Replays) | |
// { | |
// let metadata = Engine.GetReplayMetadata(replay.directory); | |
// if (!metadata) | |
// continue; | |
// if (!replayEqualsGame(metadata.mapSettings)) | |
// continue; | |
// ++games; | |
// // unev(metadata.mapSettings, 1); | |
// // break; | |
// // unev(metadata.mapSettings.AISeed); | |
// // unev(metadata.mapSettings.Seed); | |
// // unev(metadata.mapSettings.mapType); | |
// // unev(metadata.mapSettings.TriggerScripts); | |
// // unev(metadata.mapSettings.VictoryScripts); | |
// // unev(metadata.mapSettings.RatingEnabled); | |
// // unev(metadata.mapSettings.Preview); | |
// // unev(metadata.mapSettings.Keywords); | |
// // unev(metadata.mapSettings.Keywords); | |
// // unev(metadata.mapSettings.Keywords); | |
// // unev(metadata.mapSettings.Keywords); | |
// // break; | |
// // for (let playerData of replay.attribs.settings.PlayerData) | |
// // { | |
// // if (!playerData || playerData.AI) | |
// // continue; | |
// // unev(playerData) | |
// // break; | |
// // players.push() | |
// // if (!g_PlayersRated.some(player => player.name == playername)) | |
// // continue; | |
// // } | |
// } | |
// // unev(Array(g_PlayersRated.length).fill(0).map((a, i) => i)); | |
// fa(checkPlayerRateConfig, Array(g_PlayersRated.length).fill(0).map((a, i) => i)); // [1,2,3,4,5,6,7,8]); | |
// print(g_PlayersRateBestConfig.map(a => a.name + " " + a.rate).join('\n')+"\n") | |
// warn("Average Player Ratio: " +g_PlayersRatedRateRatio); | |
// warn("Teams Rate: " +uneval(teamsRate(g_PlayersRateBestConfig, 2))); | |
// warn("Rate Difference: " + g_PlayersRateBestConfigError) | |
// for (let i in g_ReplaysPlayersRate) | |
// g_ReplaysPlayersRate[i].scorePerTime /= g_ReplaysPlayersRate[i].games; | |
// // unev(Object.keys(g_ReplaysPlayersRate).sort((a, b) => g_ReplaysPlayersRate[a]-g_ReplaysPlayersRate[b]).map(name => [name, g_ReplaysPlayersRate[name]])) | |
// unev(Object.keys(g_ReplaysPlayersRate).sort((a, b) => g_ReplaysPlayersRate[a].scorePerTime - g_ReplaysPlayersRate[b].scorePerTime).map(name => [name, g_ReplaysPlayersRate[name].scorePerTime, g_ReplaysPlayersRate[name].games])) | |
// warn("games: " + games) | |
// unev(teamsRate(g_PlayersRated, 2)); | |
// let fPlayerRate = g_RatePlayers.reduce((a, b, i, arr) => a + b.rate, arr[0].rate); | |
// let msg = " ok ok oko http://ok.de www.ok.de" | |
// warn(uneval(msg.split(" "))); | |
// msg.split(" ").forEach(m => { | |
// if (m.search("http://") == 0) | |
// g_ChatLinkList.unshift(m) | |
// else if (m.search("www.") == 0) | |
// g_ChatLinkList.unshift(m) | |
// }); | |
// // msg.txt.search("http://") | |
// warn(uneval(g_ChatLinkList)) | |
g_Dialog = !!attribs.dialog; | |
g_InGame = !!attribs.ingame; | |
g_SelectedGameIP = !!attribs.game_ip ? attribs.game_ip : ""; | |
g_SelectedGamePort = !!attribs.game_port ? attribs.game_port : ""; | |
g_SelectedGameName = !!attribs.game_name ? attribs.game_name : ""; | |
g_AutoScrollGameListFromSelection = !!attribs.game_ip || !!attribs.game_name; | |
if (g_SelectedGameIP || g_SelectedGameName) | |
g_CancelHotkey.splice(2, 0, setGameListBoxUnselected) | |
if (!g_Settings) | |
{ | |
leaveLobby(); | |
return; | |
} | |
let playerList = Engine.GetGUIObjectByName("playerList"); | |
playerList.tooltip = translate("Click: Show the game of the player\nDouble Click: Show profile of the player\nEscape: Clear Selection/Profile show."); | |
let filterPlayerList = Engine.GetGUIObjectByName("playerSearchInput"); | |
filterPlayerList.caption = g_PlayerListFilterPlayerDefaultValue; | |
g_CancelHotkey.push(function() | |
{ | |
if (filterPlayerList.caption == g_PlayerListFilterPlayerDefaultValue) | |
return false; | |
filterPlayerList.caption = g_PlayerListFilterPlayerDefaultValue; | |
filterPlayer(); | |
return true; | |
}); | |
filterPlayerList.tooltip = translate("Filter here player and gamelist by attributes playername, mapname, players amount, time, etc.\n" + | |
"Enter to filter.\n" + | |
"Escape to clear."); | |
g_CallbackSet = !!attribs.callback; | |
readConfigStatusColors(); | |
initMusic(); | |
global.music.setState(global.music.states.MENU); | |
initDialogStyle(); | |
g_GamesSort = initGUIListSort("gameList", "lobby.sort.games"); | |
initGameFilters(); | |
updateConnectedState(); | |
initAutoAway(); | |
updateTime(); | |
// When rejoining the lobby after a game, we don't need to process presence changes | |
Engine.LobbyClearPresenceUpdates(); | |
g_PlayersSort = initGUIListSort("playerList", "lobby.sort.players"); | |
updatePlayerList(); | |
updateSubject(Engine.LobbyGetRoomSubject()); | |
initUserConfigurables(); | |
updateToggleBuddy(); | |
Engine.GetGUIObjectByName("chatInput").tooltip = | |
colorizeAutocompleteHotkey("Press %(hotkey)s to focus chat input and keep pressing %(hotkey)s to cycle through all autocompleting playernames or /-commands.\nEnter " + setStringTags("/help", { "color": "yellow"}) + " for all /-commands."); | |
// if (!!attribs.chatMessages) | |
// { | |
// g_ChatMessages = attribs.chatMessages; | |
// // warn(uneval(attribs.chatMessages.length)) | |
// updateChatTextGui(); | |
// } | |
// else | |
// Get all messages since the login | |
for (let msg of Engine.LobbyGuiPollHistoricMessages()) | |
g_NetMessageTypes[msg.type][msg.level](msg); | |
if (!Engine.IsXmppClientConnected()) | |
reconnectMessageBox(); | |
if (attribs && attribs.joinGame) | |
Engine.PushGuiPage("page_gamesetup_mp.xml", attribs.joinGame); | |
else if (attribs && attribs.startReplay) | |
startReplay(attribs.startReplay); | |
initFocusSwitcher(); | |
Engine.GetGUIObjectByName("leaderboardButton").caption = "Leaderboard"; | |
Engine.GetGUIObjectByName("hostButton").caption = "Create Game"; | |
Engine.GetGUIObjectByName("hostButton").tooltip = colorizeHotkey("%(hotkey)s - New Game.", "lobby.newgame"); | |
Engine.GetGUIObjectByName("leaveButton").tooltip = colorizeHotkey("%(hotkey)s - Go back.", "close"); | |
// g_TextInputs["playerSearchInput"] = new textInputCaptionHint("Search Player", Engine.GetGUIObjectByName("playerSearchInput"), function() { | |
// g_PlayerListFilterPlayer = this.obj.caption; | |
// updatePlayerList(true); | |
// }) | |
// g_ChatCommands["pinggamebuddies"].handler(""); | |
// g_ChatCommands["pingbuddies"].handler("Hi"); | |
// g_ChatCommands["pingbuddies"].handler(); | |
// Engine.GetGUIObjectByName("followHostFilter").selected = 1; | |
} | |
function startReplay(data) | |
{ | |
if (data && g_Dialog) | |
Engine.PopGuiPageCB({ "goGUI": [ "page_lobby.xml", { "startReplay": data } ] }); | |
else if (data && !!data.replayDirectory && data.page) | |
{ | |
if (!Engine.StartVisualReplay(data.replayDirectory)) | |
{ | |
warn('Replay "' + escapeText(Engine.GetReplayDirectoryName(replayDirectory)) + '" not found! Please click on reload cache.'); | |
return; | |
} | |
Engine.SwitchGuiPage(...data.page); | |
} | |
} | |
function reconnectMessageBox() | |
{ | |
if (g_AskedReconnect) | |
return; | |
g_AskedReconnect = true; | |
messageBox( | |
400, 200, | |
translate("You have been disconnected from the lobby. Do you want to reconnect?"), | |
translate("Confirmation"), | |
[translate("No"), translate("Yes")], | |
[null, Engine.ConnectXmppClient]); | |
} | |
/** | |
* Set style of GUI elements and the window style. | |
*/ | |
function initDialogStyle() | |
{ | |
let lobbyWindow = Engine.GetGUIObjectByName("lobbyWindow"); | |
lobbyWindow.sprite = g_Dialog ? "ModernDialog" : "ModernWindow"; | |
lobbyWindow.size = g_Dialog ? "42 42 100%-42 100%-42" : "0 0 100% 100%"; | |
Engine.GetGUIObjectByName("lobbyWindowTitle").size = g_Dialog ? "50%-128 -16 50%+128 16" : "50%-128 4 50%+128 36"; | |
Engine.GetGUIObjectByName("leaveButton").caption = g_Dialog ? | |
translateWithContext("previous page", "Back") : | |
translateWithContext("previous page", "Main Menu"); | |
Engine.GetGUIObjectByName("hostButton").hidden = g_Dialog; | |
Engine.GetGUIObjectByName("joinGameButton").hidden = g_Dialog; | |
Engine.GetGUIObjectByName("gameInfoEmpty").size = "0 0 100% 100%-24" + (g_Dialog ? "" : "-30"); | |
Engine.GetGUIObjectByName("gameInfo").size = "0 0 100% 100%-24" + (g_Dialog ? "" : "-60"); | |
Engine.GetGUIObjectByName("middlePanel").size = "20%+5 " + (g_Dialog ? "18" : "40") + " 100%-255 100%-20"; | |
Engine.GetGUIObjectByName("rightPanel").size = "100%-250 " + (g_Dialog ? "18" : "40") + " 100%-20 100%-20"; | |
setLeftPanelExpanded(true); | |
if (g_Dialog) | |
{ | |
Engine.GetGUIObjectByName("lobbyDialogToggle").onPress = leaveLobby; | |
g_CancelHotkey.push(leaveLobby); | |
Engine.GetGUIObjectByName("chatInput").focus(); | |
} | |
} | |
/** | |
* Set style of GUI elements according to the connection state of the lobby. | |
*/ | |
function updateConnectedState() | |
{ | |
Engine.GetGUIObjectByName("chatInput").hidden = !Engine.IsXmppClientConnected(); | |
Engine.GetGUIObjectByName("presenceDropdown").enabled = Engine.IsXmppClientConnected(); | |
for (let button of ["host", "leaderboard", "userprofile", "toggleBuddy"]) | |
Engine.GetGUIObjectByName(button + "Button").enabled = Engine.IsXmppClientConnected(); | |
} | |
function readConfigStatusColors() | |
{ | |
for (let i in g_GameColors) | |
{ | |
g_GameColors[i].style = { "color": isValidColor(Engine.ConfigDB_GetValue("user", "lobby.statuscolors.games." + i)) }; | |
g_GameColors[i].buddyStyle = { "color": isValidColor(Engine.ConfigDB_GetValue("user", "lobby.statuscolors.games.buddy." + i)) }; | |
} | |
for (let i in g_PlayerStatuses) | |
{ | |
g_PlayerStatuses[i].style = { "color": isValidColor(Engine.ConfigDB_GetValue("user", "lobby.statuscolors.players." + i)) }; | |
g_PlayerStatuses[i].buddyStyle = { "color": isValidColor(Engine.ConfigDB_GetValue("user", "lobby.statuscolors.players.buddy." + i)) }; | |
} | |
g_UserStyle = { "color": isValidColor(Engine.ConfigDB_GetValue("user", "lobby.userplayer.color")) }; | |
} | |
/** | |
* Initiate presences dropdown and configurable auto away time minutes. | |
*/ | |
function initAutoAway() | |
{ | |
let presenceDropdown = Engine.GetGUIObjectByName("presenceDropdown"); | |
presenceDropdown.list = g_AutoAwayStates.map(state => state.desc()); | |
presenceDropdown.list_data = g_AutoAwayStates.map(state => state.name); | |
presenceDropdown.selected = (presenceDropdown.list_data.findIndex(data => data == Engine.ConfigDB_GetValue("user", "lobby.presenceselection")) | |
+ 1 || 1) - 1; | |
} | |
function initUserConfigurables(data) | |
{ | |
g_OptionsPage = data && data.page; | |
updateLobbyColumns(); | |
initGUIMoreButtonsBar(); | |
g_AutoAway.timeMinutes = +Engine.ConfigDB_GetValue("user", "lobby.autoawaytime"); | |
initAutoAway(); | |
} | |
function initGUIMoreButtonsBar() | |
{ | |
let showConfig = Engine.ConfigDB_GetValue("user", "gui.lobby.morebuttonsbar"); | |
Engine.GetGUIObjectByName("moreOptionsBarActionHide").onmouseenter = | |
showConfig == "hiding" ? () => setMoreButtonsBarVisibility(false, true) : () => true; | |
Engine.GetGUIObjectByName("moreOptionsBarActionShow").onmouseenter = | |
showConfig == "hiding" ? () => setMoreButtonsBarVisibility(true, true) : () => true; | |
setMoreButtonsBarVisibility(showConfig == "visible", showConfig == "hiding"); | |
if (showConfig == "disabled") | |
return; | |
let buttonWidthPercentton = (1 / Object.keys(g_MoreButtonsBarFuncs).length) * 100; | |
let j = -1; | |
for (let i in g_MoreButtonsBarFuncs) | |
{ | |
let button = Engine.GetGUIObjectByName("moreButtons[" + ++j + "]"); | |
button.hidden = false; | |
// Let gap "+2" between buttons and calculate size to fit space | |
button.size = j * buttonWidthPercentton + "%" + (j > 0 ? "+2" : "") + " 100%-25 " + | |
((j + 1) * buttonWidthPercentton) + "%" + " 100%"; | |
button.caption = g_MoreButtonsBarFuncs[i].name || i; | |
button.onpress = g_MoreButtonsBarFuncs[i].func || g_MoreButtonsBarFuncs[i]; | |
button.tooltip = g_MoreButtonsBarFuncs[i].tooltip || ""; | |
if (!!g_MoreButtonsBarFuncs[i].color) | |
button.textcolor_over = g_MoreButtonsBarFuncs[i].color; | |
} | |
} | |
function setMoreButtonsBarVisibility(show, autoHideAble) | |
{ | |
Engine.GetGUIObjectByName("moreOptionsBarActionHide").hidden = !(autoHideAble && show); | |
Engine.GetGUIObjectByName("chatPanel").size = show ? "0 49% 100% 100%-29" : "0 49% 100% 100%" | |
Engine.GetGUIObjectByName("moreButtons").hidden = !show; | |
} | |
function updateLobbyColumns() | |
{ | |
let gameRating = Engine.ConfigDB_GetValue("user", "lobby.columns.gamerating") == "true"; | |
// Only show the selected columns | |
let gamesBox = Engine.GetGUIObjectByName("gameList"); | |
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 leaveLobby() | |
{ | |
if (g_AutoAway.timerHandler) | |
clearTimeout(g_AutoAway.timerHandler); | |
if (g_Dialog) | |
{ | |
Engine.LobbySetPlayerPresence("playing"); | |
if (g_CallbackSet) | |
Engine.PopGuiPageCB({ "chatMessages": g_ChatMessages }); | |
else | |
Engine.PopGuiPage(); | |
} | |
else | |
{ | |
Engine.StopXmppClient(); | |
saveSettingAndWriteToUserConfig("lobby.loggedin", "false"); | |
Engine.SwitchGuiPage("page_pregame.xml"); //, { showPreLobby: Engine.ConfigDB_GetValue("user", "lobby.autologin") == "true" }); | |
} | |
} | |
/** | |
* Apply lobby player presence and save it in user config. | |
*/ | |
function applyPlayerPresence(presence) | |
{ | |
Engine.LobbySetPlayerPresence(presence); | |
let dropdown = Engine.GetGUIObjectByName("presenceDropdown"); | |
Engine.ConfigDB_CreateValue("user", "lobby.presenceselection", dropdown.list_data[dropdown.selected]); | |
Engine.ConfigDB_WriteValueToFile("user", "lobby.presenceselection", dropdown.list_data[dropdown.selected], "config/user.cfg"); | |
} | |
/** | |
* Apply lobby presence for player. | |
* 500ms timeouts between multiple presence changes. After timeout applies latest presence change. | |
* | |
* @param {string} presence - Presence string for user. ("available", "away", "busy") | |
* @param {bool} awayTime - Optional auto away when time g_AutoAway.timeMinutes inactive. | |
* @param {bool} awayBackground - Optional auto away when window looses focus. | |
* @param {bool} awayTimeInBackground - Optional auto away when time inactive while window is unfocused. | |
*/ | |
function setPlayerPresence(presence, awayTime, awayBackground, awayTimeInBackground) | |
{ | |
g_AutoAway.time = awayTime; | |
g_AutoAway.background = awayBackground; | |
g_AutoAway.timeInBackground = awayTimeInBackground; | |
resetAutoAway(); | |
// When timeout, store presence. | |
if (g_Presence.timer) | |
g_Presence.setNew = presence; | |
else | |
{ | |
applyPlayerPresence(presence); | |
g_Presence.setNew = ""; | |
// Start timeout for next presence apply. | |
g_Presence.timer = setTimeout(() => { | |
if (g_Presence.setNew) | |
applyPlayerPresence(g_Presence.setNew); | |
g_Presence.timer = 0; | |
}, g_Presence.timeoutMilliSeconds); | |
} | |
} | |
var g_HandleInputAfterGui = []; | |
function handleInputAfterGui(ev) | |
{ | |
// Wait for window focus to reset auto away. | |
if (g_WindowFocus) // ev.type != "mousemotion" && | |
resetAutoAway(); | |
return false; | |
} | |
function setAutoAway() | |
{ | |
Engine.LobbySetPlayerPresence("away"); | |
g_AutoAway.applied = true; | |
} | |
function resetAutoAway() | |
{ | |
if (g_AutoAway.applied) | |
{ | |
Engine.LobbySetPlayerPresence("available"); | |
g_AutoAway.applied = false; | |
} | |
if (g_AutoAway.timerHandler) | |
clearTimeout(g_AutoAway.timerHandler); | |
if (g_AutoAway.time && (!g_AutoAway.timeInBackground || !g_WindowFocus)) | |
g_AutoAway.timerHandler = setTimeout(setAutoAway, 1000 * 60 * g_AutoAway.timeMinutes); | |
if (g_AutoAway.background && !g_AutoAway.timeInBackground && !g_WindowFocus) | |
setAutoAway(); | |
} | |
function initGameFilters() | |
{ | |
let followHostFilter = Engine.GetGUIObjectByName("followHostFilter"); | |
followHostFilter.list = ["Keep in lobby", "Auto follow new host", "Auto follow new buddy host", "Auto follow new 2-3 buddies host", "Message me about new host", "Message me about new buddy host", "Message me about 2-3 buddies in new host"]; | |
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 = [">1500", ">1400", ">1300", ">1200", "<1200", "<1100", "<1000"]; | |
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("followHostFilter").selected = 0; | |
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, historic) | |
{ | |
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, | |
"historic": historic, | |
"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. | |
*/ | |
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("playerList"); | |
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; | |
// warn("dd"+toggleBuddyButton.sprite); | |
toggleBuddyButton.textcolor= g_Buddies.indexOf(playerName) != -1 ? "200 200 200" : "188 188 245"; | |
} | |
const g_FilteredListString = escapeText("filtered"); | |
/** | |
* Do a full update of the player listing, including ratings from cached C++ information. | |
*/ | |
function updatePlayerList(autoScroll = false) | |
{ | |
let playersBox = Engine.GetGUIObjectByName("playerList"); | |
let highlightedBuddy = Engine.ConfigDB_GetValue("user", "lobby.highlightbuddies") == "true"; | |
let buddyStatusList = []; | |
let playerList = []; | |
let presenceList = []; | |
let nickList = []; | |
let ratingList = []; | |
let playerlist =g_PlayerList = Engine.GetPlayerList().map(player => { | |
player.isBuddy = g_Buddies.indexOf(player.name) != -1; | |
return player; | |
}); | |
// Prefer players with info about in-game status when comparing player with same presence status in lobby. | |
let statusSort = obj => { let r="" + Object.keys(g_PlayerStatuses).indexOf(obj.presence) + | |
(!PlayerGameStatus[obj.name] ? "z" : "a" + Object.keys(PlayerGameStatuses).map(key => | |
PlayerGameStatuses[key]).indexOf(PlayerGameStatus[obj.name])); | |
//print(r+"\n"); | |
return r; | |
} | |
g_PlayerList = playerlist.filter(player => { | |
const searchStr = g_PlayerListFilterPlayer.toLowerCase(); | |
const search = s => s.toLowerCase().indexOf(searchStr) != -1; | |
if (search(player.name)) | |
return true; | |
if (player.presence && g_PlayerStatuses[player.presence] && g_PlayerStatuses[player.presence].status && | |
search(g_PlayerStatuses[player.presence].status)) | |
return true; | |
if (player.rating && search(player.rating)) | |
return true; | |
if (g_GameList.some(game => stringifiedTeamListToPlayerData(game.players).some(playerGame => playerGame.Name.toLowerCase().indexOf(player.name.toLowerCase()) != -1))) | |
return true; | |
return false; | |
} | |
).sort((a, b) => { | |
for (let sort of g_PlayersSort) | |
{ | |
let ret = cmpObjs(a, b, sort.name, { | |
'buddy': obj => (obj.name == g_Username ? 3 : obj.isBuddy ? 2 : 1), | |
'rating': obj => +obj.rating, | |
'status': obj => statusSort(obj), | |
'name': obj => obj.name.toLowerCase() | |
}, sort.order); | |
if (ret) | |
return ret; ; | |
// Keep user player on same sort data priored. | |
if (a.name == g_Username) | |
return -sort.order; | |
if (b.name == g_Username) | |
return +sort.order; | |
} | |
return 0; | |
}); | |
// Colorize list entries | |
for (let player of g_PlayerList) | |
{ | |
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 status = g_PlayerStatuses[presence].status; | |
if (Engine.ConfigDB_GetValue("user", "lobby.playerlistgamestatus") == "true" && | |
!!PlayerGameStatus[player.name]) status = PlayerGameStatus[player.name] + "/" + status; //.slice(0, 3) + ; | |
//if (!!SpecPlayer[splitRatingFromNick(player.name).nick]) status = status.slice(0, 3) + "/Spec"; | |
let statusStyle = highlightedBuddy && player.name == g_Username ? g_UserStyle : | |
highlightedBuddy && player.isBuddy ? g_PlayerStatuses[presence].buddyStyle : | |
g_PlayerStatuses[presence].style; | |
buddyStatusList.push(player.name == g_Username ? setStringTags(g_UserSymbol, statusStyle) : player.isBuddy ? setStringTags(g_BuddySymbol, statusStyle) : ""); | |
playerList.push(colorPlayerName((player.role == "moderator" ? g_ModeratorPrefix : "") + player.name)); | |
presenceList.push(setStringTags(status, statusStyle)); | |
ratingList.push(setStringTags(rating, statusStyle)); | |
nickList.push(player.name); | |
} | |
// if (g_PlayerList.length == 1) | |
// g_SelectedPlayer = nickList[0]; | |
if (playerlist.length != g_PlayerList.length) | |
{ | |
let form = str => setStringTags(str, { "color": "244 244 155" }); | |
const filtered = "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " | |
buddyStatusList.push(form("")); | |
playerList.push(form(g_FilteredListString)); | |
presenceList.push(form("")); | |
ratingList.push(form("")); | |
nickList.push("filtered"); | |
} | |
playersBox.list_buddy = buddyStatusList; | |
playersBox.list_name = playerList; | |
playersBox.list_status = presenceList; | |
playersBox.list_rating = ratingList; | |
playersBox.list = nickList; | |
playersBox.auto_scroll = autoScroll; | |
playersBox.selected = playersBox.list.indexOf(g_SelectedPlayer); | |
updatePlayerGamesNumber(); | |
} | |
/** | |
* Toggle buddy state for a player in playerlist within the user config | |
*/ | |
function toggleBuddy() | |
{ | |
let playerList = Engine.GetGUIObjectByName("playerList"); | |
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(); | |
saveSettingAndWriteToUserConfig("lobby.buddies", g_Buddies.filter(nick => nick).join(g_BuddyListDelimiter) || g_BuddyListDelimiter); | |
if (index == -1 && Engine.ConfigDB_GetValue("user", "lobby.sendprivatemsgonbuddy") == "true" && !g_Muted) | |
Engine.LobbySendMessage("/private " + name + " I "+ (index == -1 ? "buddied" : "unbuddied") + " you. <3"); | |
// Engine.LobbySendMessage("/private " + playername + " I "+ (g_Buddies.indexOf(playerName) != -1 ? "buddied" : "unbuddied") + "you."); | |
updatePlayerList(true); | |
updateGameList(true); | |
} | |
/** | |
* 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("gameList"); | |
let foundAsObserver = false; | |
let selected = -1; | |
for (let i = 0; i < g_GameList.length; ++i) | |
for (let player of stringifiedTeamListToPlayerData(g_GameList[i].players)) | |
{ | |
if (g_SelectedPlayer != splitRatingFromNick(player.Name).nick) | |
continue; | |
gameList.auto_scroll = true; | |
if (player.Team == "observer") | |
{ | |
foundAsObserver = true; | |
selected = i; | |
} | |
else if (!player.Offline) | |
{ | |
selected = i; | |
break; | |
} | |
else if (!foundAsObserver) | |
selected = i; | |
} | |
gameList.selected = selected; | |
// clearGameSelectionTooltip(true); | |
} | |
function filterPlayer() | |
{ | |
g_PlayerListFilterPlayer = Engine.GetGUIObjectByName("playerSearchInput").caption; | |
if (g_PlayerListFilterPlayer == g_PlayerListFilterPlayerDefaultValue) | |
{ | |
g_PlayerListFilterPlayer = ""; | |
Engine.GetGUIObjectByName("playerSearchInput").textcolor = "255 255 255"; | |
} | |
else if (g_PlayerListFilterPlayer) | |
Engine.GetGUIObjectByName("playerSearchInput").textcolor = "244 244 155"; | |
updateGameList(true); | |
updatePlayerList(true); | |
} | |
function onPlayerListSelection() | |
{ | |
let playerList = Engine.GetGUIObjectByName("playerList"); | |
if (playerList.selected == playerList.list.indexOf(g_SelectedPlayer)) | |
return; | |
if (!g_SelectedPlayer) | |
g_CancelHotkey.splice(2, 0, setPlayerListBoxUnselected); | |
g_SelectedPlayer = playerList.list[playerList.selected]; | |
setLeftPanelExpanded(true); | |
// Keep selected player in view when profile panel get expanded firstly. | |
updatePlayerList(true); | |
// if (!g_SelectedPlayer) | |
// return; | |
// lookupSelectedUserProfile("playersBox"); | |
updateToggleBuddy(); | |
selectGameFromPlayername(); | |
// clearPlayerSelectionTooltip(true); | |
} | |
function setLeaderboardVisibility(visible) | |
{ | |
Engine.GetGUIObjectByName("profilePanel").z = visible ? "101" : "99"; | |
if (visible == !Engine.GetGUIObjectByName("leaderboard").hidden) | |
return false; | |
if (visible) | |
Engine.SendGetBoardList(); | |
lookupSelectedUserProfile(visible ? "leaderboardBox" : "playerList"); | |
let playerlist = Engine.GetGUIObjectByName("playerList"); | |
let leaderboard = Engine.GetGUIObjectByName("leaderboardBox"); | |
if (visible && playerlist && playerlist.selected != -1) | |
{ | |
let name = playerlist.list[playerlist.selected].toLowerCase(); | |
leaderboard.list_name.some((name2, i) => { if (name2.toLowerCase() != name) return false; leaderboard.auto_scroll = true; leaderboard.selected = i; leaderboard.auto_scroll = false; return true; }); | |
} | |
Engine.GetGUIObjectByName("leaderboard").hidden = !visible; | |
Engine.GetGUIObjectByName("fade").hidden = !visible; | |
return true; | |
} | |
function setUserProfileVisibility(visible) | |
{ | |
if (visible == !Engine.GetGUIObjectByName("profileFetch").hidden) | |
return false; | |
Engine.GetGUIObjectByName("profileFetch").hidden = !visible; | |
Engine.GetGUIObjectByName("fade").hidden = !visible; | |
return true; | |
} | |
/** | |
* Display the profile of the player in the user profile window. | |
*/ | |
function lookupUserProfile() | |
{ | |
Engine.SendGetProfile(Engine.GetGUIObjectByName("fetchInput").caption); | |
} | |
function setLeftPanelExpanded(expanded) | |
{ | |
Engine.GetGUIObjectByName("profilePanel").hidden = expanded; | |
Engine.GetGUIObjectByName("leftPanel").size = "20 " +(g_Dialog ? "18" : "40") + " 20% 100%-101" + (expanded ? "" : "-204"); | |
} | |
function textInputFocus(obj, focus) | |
{ | |
if (focus) | |
{ | |
if (obj.caption == "Search Player") | |
obj.caption = ""; | |
} | |
else | |
if (obj.caption == "") | |
obj.caption = "Search Player"; | |
} | |
var g_PlayerListFilterPlayer = "" | |
function setGameListBoxUnselected(cancelHotkeyFunctionIndex) | |
{ | |
if (Engine.GetGUIObjectByName("gameList").selected == -1) | |
return false; | |
Engine.GetGUIObjectByName("gameList").selected = -1; | |
g_SelectedGameIP = ""; | |
g_SelectedGamePort = ""; | |
// clearGameSelectionTooltip(false); | |
g_CancelHotkey.splice(cancelHotkeyFunctionIndex, 1); | |
return true; | |
} | |
function setPlayerListBoxUnselected(cancelHotkeyFunctionIndex) | |
{ | |
if (Engine.GetGUIObjectByName("playerList").selected == -1) | |
return false; | |
Engine.GetGUIObjectByName("playerList").selected = -1; | |
g_SelectedPlayer = null; | |
setLeftPanelExpanded(true); | |
// clearPlayerSelectionTooltip(false); | |
g_CancelHotkey.splice(cancelHotkeyFunctionIndex, 1); | |
return true; | |
} | |
/** | |
* 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]; | |
if (!playerName) | |
return; | |
setLeftPanelExpanded(false); | |
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("playerList"); | |
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; | |
} | |
function updatePlayerGamesNumber() | |
{ | |
let guiObj = Engine.GetGUIObjectByName("playerGamesNumber"); | |
let info = []; | |
let tooltip = []; | |
let formatInfo = (count, availableCount, name) => { | |
if (count == 0) | |
return ""; | |
let numbers = [ count ]; | |
if (availableCount > 0 ) | |
numbers.push('[color="0 255 0"]' + availableCount + '[/color]'); | |
return sprintf(translate("%(number)s %(info)s"), { | |
"number": numbers.join(translateWithContext("value separator", "/")), | |
"info": name | |
}); | |
}; | |
info.push(formatInfo( | |
g_PlayerList.length, | |
g_PlayerList.filter(player => player.presence == "available").length, | |
translatePlural("Player", "Players", g_PlayerList.length) | |
)); | |
let buddyPlayerInfo = ""; | |
let buddiesList = g_PlayerList.filter(player => player.isBuddy); | |
buddyPlayerInfo = formatInfo( | |
buddiesList.length, | |
buddiesList.filter(player => player.presence == "available").length, | |
translatePlural("Buddy", "Buddies", buddiesList.length) | |
); | |
info.push(formatInfo( | |
g_GameList.length, | |
g_GameList.filter(game => game.state == "init").length, | |
translatePlural("Game", "Games", g_GameList.length) | |
)); | |
let buddyGamesInfo = ""; | |
let buddiesGamesList = g_GameList.filter(game => game.hasBuddies); | |
buddyGamesInfo = formatInfo( | |
buddiesGamesList.length, | |
buddiesGamesList.filter(game => game.state == "init").length, | |
translatePlural("Buddy Game", "Buddy Games", buddiesGamesList.length) | |
); | |
let caption = arr => arr.filter(str => str).join(translateWithContext("info separator", " · ")); | |
let removeFormationCode = string => string.replace(/\[.*?\]/g, ""); | |
for (let [ position, buddyInfo ] of [ [ 1, buddyPlayerInfo ], [ 3, buddyGamesInfo ] ]) | |
{ | |
if (buddyInfo == "") | |
continue; | |
if (Engine.GetTextWidth(guiObj.font, removeFormationCode(caption(info.concat(buddyInfo))) + " ") | |
< | |
guiObj.getComputedSize().right - guiObj.getComputedSize().left) | |
info.splice(position, 0, buddyInfo); | |
else | |
tooltip.push(buddyInfo); | |
} | |
guiObj.caption = caption(info); | |
guiObj.tooltip = '[font="' + guiObj.font + '"]' + caption(tooltip) + '[/font]'; | |
} | |
var g_PlayerListInGame = {}; | |
var g_PlayerListInGameObserver = {}; | |
var g_LastGameListUpdate = 0; | |
var g_GameListUpdateCache = []; | |
var g_InitGameList = true; | |
var g_ShowingNewGameNotice = false; | |
var notChecked = true; | |
const PlayerGameStatuses = {undefined: "", spec: "Spec", defeated: "Lost", won: "Won", playing: "Play"}; | |
var PlayerGameStatus = {}; | |
/** | |
* Update the game listing from data cached in C++. | |
*/ | |
function updateGameList(autoScroll = false) | |
{ | |
let gamesBox = Engine.GetGUIObjectByName("gameList"); | |
let highlightedBuddy = Engine.ConfigDB_GetValue("user", "lobby.highlightbuddies") == "true"; | |
let compTrans = (compTrans, obj, att, defAtt) => compTrans[att] && compTrans[att](obj) || obj[att] || obj[defAtt]; | |
// always empty spectating playerlist at updategamelist | |
PlayerGameStatus = {}; | |
if (gamesBox.selected > -1 && g_GameList[gamesBox.selected]) | |
{ | |
let game = g_GameList[gamesBox.selected]; | |
g_SelectedGameIP = game.stunIP ? game.stunIP : game.ip; | |
g_SelectedGamePort = game.stunPort ? game.stunPort : game.port; | |
// if (g_AutoScrollGameListFromSelection && g_GameList.length > 0) | |
// warn(g_SelectedGameIP + ":" + g_SelectedGamePort); | |
} | |
// Engine.GetGameList().filter(game => { | |
// for (let player of stringifiedTeamListToPlayerData(game.players)) | |
// { | |
// } | |
let gameTimeHighest = 0; | |
let found1 = false; | |
let gamelist = []; | |
gamelist = Engine.GetGameList().map(game => { | |
game.hasBuddies = 0; | |
game.observeNum = 0; | |
game.buddies = 0; | |
// Compute average rating of participating players | |
let playerRatings = []; | |
if (notChecked && game.name == "fpre") | |
{ | |
g_SelectedGameName = game.name; | |
found1 = true; | |
notChecked = false; | |
} | |
for (let player of stringifiedTeamListToPlayerData(game.players)) | |
{ | |
let playerNickRating = splitRatingFromNick(player.Name); | |
if (player.Team != "observer") | |
g_PlayerListInGame[playerNickRating.nick] = game.name; | |
else if (player.Team == "observer" && game.state != "init") | |
g_PlayerListInGameObserver[playerNickRating.nick] = game.name; | |
if (player.Team != "observer") | |
playerRatings.push(playerNickRating.rating || g_DefaultLobbyRating); | |
game.hasUser = game.hasUser || multiplayerName(g_Username) == playerNickRating.nick || playerNickRating.nick == g_Username; | |
// if (game.hostUsername == "fpre") { | |
// warn(multiplayerName(g_Username)) | |
// } | |
if (game.hasUser) | |
game.hasBuddies = 3; | |
// Sort games with playing buddies above games with spectating buddies | |
if (game.hasBuddies < 2 && g_Buddies.indexOf(playerNickRating.nick) != -1) | |
game.hasBuddies = player.Team == "observer" ? 1 : 2; | |
if (!player.Offline && g_Buddies.indexOf(playerNickRating.nick) != -1) | |
{ | |
++game.buddies; | |
} | |
let nick = playerNickRating.nick; | |
if (player.Team == "observer") | |
{ | |
++game.observeNum; | |
PlayerGameStatus[nick] = PlayerGameStatuses.spec; | |
} | |
else if (player.Team != "observer") | |
{ | |
if (!player.State || player.State == "active") | |
{ | |
PlayerGameStatus[nick] = PlayerGameStatuses.playing; | |
} | |
else if (!player.Offline) | |
switch (player.State) | |
{ | |
case "defeated": | |
PlayerGameStatus[nick] = PlayerGameStatuses.defeated; | |
break; | |
case "won": | |
PlayerGameStatus[nick] = PlayerGameStatuses.won; | |
break; | |
} | |
} | |
} | |
game.time = 0; | |
if (game.startTime) | |
game.time = Math.round((Date.now() - game.startTime*1000)/(1000*60)); | |
if (game.time > gameTimeHighest) | |
gameTimeHighest = game.time; | |
game.gameRating = | |
playerRatings.length ? | |
Math.round(playerRatings.reduce((sum, current) => sum + current) / playerRatings.length) : | |
g_DefaultLobbyRating; | |
if (game.mods === '') | |
return game; | |
if (!game.mods || !JSON.parse(game.mods)) | |
warn(uneval(game.mods)) | |
else | |
if (!hasSameMods(JSON.parse(game.mods), g_EngineInfo.mods)) | |
game.state = "incompatible"; | |
return game; | |
}); | |
g_GameList = gamelist.filter(game => !filterGame(game)).sort((a, b) => { | |
for (let sort of g_GamesSort) | |
{ | |
if (gamesBox["hidden_" + sort.name]) | |
continue; | |
// (obj.hasBuddies || obj.hasUser == g_Username ? 1 : 2), | |
let ret = cmpObjs(a, b, sort.name, { | |
'buddy': obj => obj.hasBuddies + " " + obj.buddies, | |
'name': obj => g_GameStatusOrder.indexOf(obj.state) + obj.name.toLowerCase(), | |
'mapName': obj => translate(obj.niceMapName), | |
'nPlayers': obj => obj.maxnbp | |
}, sort.order); | |
if (ret) | |
return ret; | |
} | |
return 0; | |
} | |
).filter(game => { | |
const searchStr = g_PlayerListFilterPlayer.toLowerCase(); | |
const search = s => s && s.toLowerCase().indexOf(searchStr) != -1; | |
if (search(translateMapTitle(game.niceMapName))) | |
return true; | |
if (search(translateMapSize(game.mapSize))) | |
return true; | |
if (stringifiedTeamListToPlayerData(game.players).some(player => search(player.Name))) | |
return true; | |
if (search(game.name)) | |
return true; | |
let mapTypeIdx = g_MapTypes.Name.indexOf(game.mapType); | |
if (search(g_MapTypes.Title[mapTypeIdx] || "")) | |
return true; | |
if (search(game.nbp + "")) | |
return true; | |
if (search(game.maxnbp + "")) | |
return true; | |
if (search(game.gameRating + "")) | |
return true; | |
if (search(game.time + "")) | |
return true; | |
if ([ "buddy", translate("buddy"), translate("Buddy") ].some(s => search(s + "")) && game.hasBuddies) | |
return true; | |
if (game.mods && JSON.parse(game.mods).some(s => search(s + ""))) | |
return true; | |
return false; | |
}); | |
updatePlayerList(false); | |
let list_buddy = []; | |
let list_buddies = []; | |
let list_name = []; | |
let list_mapName = []; | |
let list_mods = []; | |
let list_mapSize = []; | |
let list_mapType = []; | |
let list_nPlayers = []; | |
let list_gameRating = []; | |
let list_time = []; | |
let list = []; | |
let list_data = []; | |
let selectedGameIndex = -1; | |
// let i = ""; | |
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 ((g_SelectedGameName && game.name == g_SelectedGameName) || (game.stunIP && (game.stunIP == g_SelectedGameIP && game.stunPort == g_SelectedGamePort)) || (!game.stunIP && (game.ip == g_SelectedGameIP && game.port == g_SelectedGamePort))) | |
{ | |
selectedGameIndex = +i; | |
g_SelectedGameName = ""; | |
g_SelectedGameIP = ""; | |
g_SelectedGamePort = ""; | |
} | |
list_buddy.push((game.hasBuddies || game.hasUser ? setStringTags( | |
game.hasUser ? g_UserSymbol : g_BuddySymbol, | |
highlightedBuddy && game.hasUser ? g_UserStyle : | |
highlightedBuddy && game.hasBuddies ? g_GameColors[game.state].buddyStyle : | |
g_GameColors[game.state].style) | |
: "")+setStringTags(game.buddies ? " " + game.buddies : "", { "color": "255 215 0" })); | |
let fgod = game.mods ? JSON.parse(game.mods).some(mod => mod[0].startsWith("fgod")) : false; | |
list_mods.push(game.mods ? JSON.parse(game.mods).filter(m=>m[0]).map(mod => mod[0] == "public" ? mod[1] : mod[0]).join(",") : ""); | |
list_name.push(setStringTags(gameName, fgod ? { "color": "yellow" } : highlightedBuddy && game.hasUser ? g_UserStyle : | |
highlightedBuddy && game.hasBuddies ? g_GameColors[game.state].buddyStyle : g_GameColors[game.state].style)); | |
list_mapName.push(translateMapTitle(game.niceMapName)); | |
list_mapSize.push(translateMapSize(game.mapSize)); | |
list_mapType.push(g_MapTypes.Title[mapTypeIdx] || ""); | |
let ob = game.observeNum ? setStringTags(" +" + game.observeNum + "", { "color": "255 215 0" }) : ""; | |
list_nPlayers.push(game.nbp + "/" + game.maxnbp + ob); | |
list_gameRating.push(game.gameRating); | |
list.push(gameName); | |
list_data.push(i); | |
let t = 128 + Math.floor((game.time/20)*80); | |
t = t >128+80 ? 128+80 : t; | |
list_time.push(setStringTags(game.time + "m", { color: [t, t, t].join(" ") })); | |
// if (game.hasUser) | |
// warn("user" + game.hasBuddies) | |
// if (game.hasBuddies) | |
// warn("buddies" + game.hasBuddies) | |
} | |
if (gamelist.length != g_GameList.length) | |
{ | |
let form = str => setStringTags(str, { "color": "244 244 155" }); | |
const filtered = "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " | |
list_buddy.push(form("")); | |
list_name.push(form(g_FilteredListString)); | |
list_mods.push(form("")); | |
list_mapName.push(form("")); | |
list_mapType.push(form("")); | |
list_mapSize.push(form("")); | |
list_nPlayers.push(form("")); | |
list_gameRating.push(form("")); | |
list.push("filtered"); | |
list_data.push(222); | |
list_time.push(""); | |
} | |
// if (g_GameList.length == 1) | |
// selectedGameIndex = 0; | |
gamesBox.list_buddy = list_buddy; | |
// gamesBox.list_buddies = list_buddies; | |
gamesBox.list_name = list_name; | |
gamesBox.list_mods = list_mods; | |
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; | |
gamesBox.list_time = list_time; | |
// Change these last, otherwise crash | |
gamesBox.list = list; | |
gamesBox.list_data = list_data; | |
gamesBox.auto_scroll = autoScroll; | |
gamesBox.selected = selectedGameIndex; | |
updateGameSelection(); | |
updatePlayerGamesNumber(); | |
checkNewGames(); | |
if (false && found1) | |
{ | |
messageBox( | |
400, 200, | |
translate("yaaa"), | |
translate("Message"), | |
[translate("Close"), translate("Enter")], | |
[ | |
() => { g_ShowingNewGameNotice = false; }, | |
joinSelectedGame | |
// () => { | |
// warn("join") | |
// joinSelectedGame(); | |
// } | |
], | |
null, | |
1, | |
4 | |
); | |
} | |
} | |
function tabChat(obj) | |
{ | |
autoCompleteNick(obj, uniq(g_PlayerList.map(player => player.name).concat( | |
Object.keys(g_ChatCommands).sort().map(com => { return "/" + com; })).concat(g_GameList.map(game => game.name)).concat(g_Buddies))); | |
} | |
function followIntoNewHost(newGame, msg, follow) | |
{ | |
// return; | |
g_ShowingNewGameNotice = true; | |
// let i = g_GameList.findIndex(game => game.name == newGame.name); | |
// if (i == -1) | |
// return; | |
// Engine.GetGUIObjectByName("gameList").selected = i; | |
g_SelectedGameName = newGame.name; | |
// warn(g_SelectedGameName) | |
updateGameList(); | |
// warn("select game "+ i) | |
messageBox( | |
400, 200, | |
translate(msg), | |
translate("Message"), | |
[translate("No"), translate("Enter")], | |
[ | |
() => { g_ShowingNewGameNotice = false; }, | |
joinSelectedGame | |
// () => { | |
// warn("join") | |
// joinSelectedGame(); | |
// } | |
], | |
null, | |
follow ? 1 : 0, | |
follow ? 4 : 10 | |
); | |
} | |
function checkNewGames() | |
{ | |
// let gameList = [{state: "init", name: "JJ", hostUsername: "wing", players: [{ Name: "wing (120)" } ]}]; | |
// g_GameList = g_GameList.concat(gameList); | |
let followHostFilter = Engine.GetGUIObjectByName("followHostFilter"); | |
let checkNewGame = (follow, msg, cond = game => true) => { | |
let newGame = undefined; | |
newGame = g_GameList.filter(game => game.state == "init" && cond(game)).find(game => !g_GameListUpdateCache.some(oldGame => oldGame.name == game.name)); | |
if (newGame != undefined) | |
followIntoNewHost(newGame, msg(newGame), follow); | |
}; | |
if (!g_ShowingNewGameNotice) //Date.now() - g_LastGameListUpdate > 1000 && | |
{ | |
if (!g_InitGameList) | |
{ | |
switch (followHostFilter.selected) | |
{ | |
case 1: | |
checkNewGame(true, newGame => "Follow new host '" + newGame.name +"' of "+ newGame.hostUsername +"?"); | |
break; | |
case 2: | |
checkNewGame(true, newGame => "Follow new buddy host '" + newGame.name +"' of "+ newGame.hostUsername +"?", game => g_Buddies.indexOf(splitRatingFromNick(game.hostUsername).nick) != -1); | |
break; | |
case 3: | |
checkNewGame(true, newGame => "Follow new buddies host '" + newGame.name +"' of "+ newGame.hostUsername +"?", game => stringifiedTeamListToPlayerData(game.players).some(player => g_Buddies.indexOf(splitRatingFromNick(player.Name).nick) != -1)); | |
// game.players.some(player => g_Buddies.indexOf(splitRatingFromNick(player.Name).nick) != -1)); | |
break; | |
case 4: | |
checkNewGame(false, newGame => "New host '" + newGame.name +"' open of "+ newGame.hostUsername +"."); | |
break; | |
case 5: | |
checkNewGame(false, newGame => "New buddy host '" + newGame.name +"' of "+ newGame.hostUsername +"?", game => g_Buddies.indexOf(splitRatingFromNick(game.hostUsername).nick) != -1); | |
break; | |
case 6: | |
checkNewGame(false, newGame => "New buddies host '" + newGame.name +"' of "+ newGame.hostUsername +"?", game => game.players.some(player => g_Buddies.indexOf(splitRatingFromNick(player.Name).nick) != -1)); | |
break; | |
} | |
g_LastGameListUpdate = Date.now(); | |
g_GameListUpdateCache = g_GameList; | |
} | |
} | |
} | |
/** | |
* 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 = g_Dialog || !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), null, game.hostUsername); | |
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); | |
// clearGameSelectionTooltip(true); | |
if (!g_SelectedGameIP) | |
g_CancelHotkey.splice(2, 0, setGameListBoxUnselected) | |
g_SelectedGameIP = "1"; | |
} | |
function selectedGame() | |
{ | |
let gamesBox = Engine.GetGUIObjectByName("gameList"); | |
if (!gamesBox || gamesBox.selected < 0 || !g_GameList[gamesBox.list_data[gamesBox.selected]]) | |
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 || (g_Dialog && !g_InGame)) | |
return; | |
let rating = getRejoinRating(game); | |
let username = rating ? multiplayerName(g_Username) + " (" + rating + ")" : multiplayerName(g_Username); | |
if (game.state == "incompatible") | |
messageBox( | |
400, 200, | |
translate("Your active mods do not match the mods of this game.") + "\n\n" + | |
(game.mods ? comparedModsString(JSON.parse(game.mods), g_EngineInfo.mods) : "") + "\n\n" + | |
translate("Do you want to switch to the mod selection page?"), | |
translate("Incompatible mods"), | |
[translate("No"), translate("Yes")], | |
[ | |
null, | |
() => { | |
Engine.StopXmppClient(); | |
Engine.SwitchGuiPage("page_modmod.xml", { | |
"cancelbutton": true | |
}); | |
} | |
] | |
); | |
else if (game.state == "init" || stringifiedTeamListToPlayerData(game.players).some(player => player.Name == username)) | |
joinSelectedGame(); | |
else | |
joinSelectedGame(); | |
// messageBox( | |
// 400, 200, | |
// translate("The game has already started. Do you want to join as observer?"), | |
// translate("Confirmation"), | |
// [translate("No"), translate("Yes")], | |
// [null, joinSelectedGame] | |
// ); | |
} | |
function joinSelectedGame() | |
{ | |
if (g_InGame) | |
messageBox( | |
400, 200, | |
translate("Do you want to quit the current game and join selected game?"), | |
translate("Confirmation"), | |
[translate("No"), translate("Yes")], | |
[null, joinSelectedGameReally] | |
); | |
else | |
joinSelectedGameReally(); | |
} | |
/** | |
* Attempt to join the selected game without asking for confirmation. | |
*/ | |
function joinSelectedGameReally() | |
{ | |
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; | |
} | |
// let nameToConnect = multiplayerName(g_Username); | |
// for (let player of stringifiedTeamListToPlayerData(game.players)) | |
// { | |
// let playerNickRating = splitRatingFromNick(player.Name); | |
// if (playerNickRating.nick == nameToConnect && !player.Offline) | |
// { | |
// nameToConnect += "2"; | |
// break; | |
// } | |
// } | |
let settings = { | |
"multiplayerGameType": "join", | |
"ip": ip, | |
"port": port, | |
"name": g_Username, | |
"rating": getRejoinRating(game), | |
"useSTUN": !!game.stunIP, | |
"hostJID": game.hostUsername + "@" + g_LobbyServer + "/0ad", | |
"passingArguments": { "hostUsername": game.hostUsername }, | |
"serverName": game.name | |
}; | |
if (g_Dialog) | |
Engine.PopGuiPageCB({ "goGUI": [ "page_lobby.xml", { "joinGame": settings } ] }); | |
else | |
Engine.PushGuiPage("page_gamesetup_mp.xml", settings); | |
} | |
/** | |
* Rejoin games with the original playername, even if the rating changed meanwhile. | |
*/ | |
function getRejoinRating(game) | |
{ | |
for (let player of stringifiedTeamListToPlayerData(game.players)) | |
{ | |
let playerNickRating = splitRatingFromNick(player.Name); | |
if (playerNickRating.nick == multiplayerName(g_Username)) | |
return playerNickRating.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.LobbyGuiPollNewMessage(); | |
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; | |
} | |
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": coloredText(cmd, g_ChatCommandColor) | |
}) | |
}); | |
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": coloredText(cmd, g_ChatCommandColor) | |
}) | |
}); | |
return false; | |
} | |
return g_ChatCommands[cmd].handler(args); | |
} | |
function showLastGameSummary() | |
{ | |
let replays = Engine.GetReplays(false).filter(replay => | |
replay.attribs.settings.PlayerData.filter(player => player && !player.AI).length > 1 ).sort((a, b) => | |
b.attribs.timestamp - a.attribs.timestamp | |
); | |
let simData = {}; | |
if (replays) | |
for (let i in replays) | |
{ | |
simData = Engine.GetReplayMetadata(replays[i].directory); | |
if (simData) | |
break; | |
} | |
if (!replays[0] || !replays || !simData) | |
{ | |
messageBox(500, 200, translate("No summary data available."), translate("Error")); | |
return; | |
} | |
Engine.PushGuiPage("page_summary.xml", { | |
"sim": simData, | |
"gui": { | |
"replayDirectory": replays[0].directory, | |
"isInLobby": true, | |
"ingame": g_InGame, | |
"dialog": true }, | |
"callback": "startReplay" | |
}); | |
} | |
function storeCivInfoPage(data) | |
{ | |
g_CivInfo.code = data.civ; | |
g_CivInfo.page = data.page; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment