Ticket #3383: lobby_coding_convention_v4.patch
File lobby_coding_convention_v4.patch, 49.5 KB (added by , 9 years ago) |
---|
-
binaries/data/mods/public/gui/lobby/lobby.js
1 var g_MapSizes = {}; 2 const g_MapTypesText = [translateWithContext("map", "Skirmish"), translateWithContext("map", "Random"), translate("Scenario")]; 3 const g_MapTypes = ["skirmish", "random", "scenario"]; 4 1 5 var g_ChatMessages = []; 2 var g_Name = "unknown"; 6 const g_ShowTimestamp = Engine.ConfigDB_GetValue("user", "lobby.chattimestamp") == "true"; 7 8 /** This object looks like { "name": [numMessagesSinceReset, lastReset, timeBlocked] } when in use. */ 9 var g_SpamMonitor = {}; 10 const g_SpamBlockLength = 30; 11 12 var g_Username = "unknown"; 13 var g_UserRating = ""; 14 15 const g_ModPrefix = "@"; 16 /** The user roles are defined in XmppClient::GetRoleString */ 17 const g_UserRoles = { 18 "none": { 19 "color": "192 192 192", 20 "title": translateWithContext("user role", "None"), 21 }, 22 "visitor": { 23 "color": "192 192 192", 24 "title": translateWithContext("user role", "Visitor"), 25 }, 26 "participant": { 27 "color": "192 192 192", 28 "title": translateWithContext("user role", "Participant"), 29 }, 30 "moderator": { 31 "color": "0 125 0", 32 "title": translateWithContext("user role", "Moderator"), 33 }, 34 "invalid": { 35 "color": "192 192 192", 36 "title": translateWithContext("user role", "Invalid"), 37 } 38 }; 39 40 const g_PlayerColors = { 41 "system": "255 0 0", 42 "@WFGbot": "255 24 24" 43 }; 44 const g_SystemColor = "150 0 0"; 45 3 46 var g_GameList = {} 47 /** Name of the column to be used for sorting. */ 4 48 var g_GameListSortBy = "name"; 49 /** Sorting order: +1 ascending, -1 descending. */ 50 var g_GameListOrder = 1; 51 /** Games will be sorted according to the order given here. */ 52 const g_GameStatusColor = { 53 "init": "0 125 0", 54 "waiting": "255 127 0", 55 "running": "255 0 0", 56 "unknown": "192 192 192" 57 } 58 59 /** Name of the column to be used for sorting. */ 5 60 var g_PlayerListSortBy = "name"; 6 var g_GameListOrder = 1; // 1 for ascending sort, and -1 for descending 61 /** Sorting order: +1 ascending, -1 descending. */ 7 62 var g_PlayerListOrder = 1; 8 var g_specialKey = Math.random(); 9 // This object looks like {"name":[numMessagesSinceReset, lastReset, timeBlocked]} when in use. 10 var g_spamMonitor = {}; 11 var g_timestamp = Engine.ConfigDB_GetValue("user", "lobby.chattimestamp") == "true"; 12 var g_mapSizes = {}; 13 const g_mapTypesText = [translateWithContext("map", "Skirmish"), translateWithContext("map", "Random"), translate("Scenario")]; 14 const g_mapTypes = ["skirmish", "random", "scenario"]; 15 var g_userRating = ""; // Rating of user, defaults to Unrated 16 var g_modPrefix = "@"; 17 // Block spammers for 30 seconds. 18 var SPAM_BLOCK_LENGTH = 30; 63 /** 64 * If sorted by status, the playerlist will be ordered according to the order given here. 65 * The possible player statuses are defined in XmppClient::GetPresenceString. 66 */ 67 const g_PlayerStatuses = { 68 "available": { 69 "status": translate("Online"), 70 "color": "0 125 0" 71 }, 72 "away": { 73 "status": translate("Away"), 74 "color": "229 76 13" 75 }, 76 "gone": { 77 "status": translate("Gone"), 78 "color": "229 76 13" 79 }, 80 "playing": { 81 "status": translate("Busy"), 82 "color": "125 0 0" 83 }, 84 "offline": { 85 "status": translate("Offline"), 86 "color": "0 0 0" 87 }, 88 "unknown": { 89 "status": translateWithContext("lobby presence", "Unknown"), 90 "color": "178 178 178" 91 } 92 } 19 93 20 94 //////////////////////////////////////////////////////////////////////////////////////////////// 21 95 22 96 function init(attribs) 23 97 { 24 98 // Play menu music 25 99 initMusic(); 26 100 global.music.setState(global.music.states.MENU); 27 101 28 g_ Name = Engine.LobbyGetNick();102 g_Username = Engine.LobbyGetNick(); 29 103 30 g_ mapSizes = initMapSizes();31 g_ mapSizes.shortNames.splice(0, 0, translateWithContext("map size", "Any"));32 g_ mapSizes.tiles.splice(0, 0, "");104 g_MapSizes = initMapSizes(); 105 g_MapSizes.shortNames.splice(0, 0, translateWithContext("map size", "Any")); 106 g_MapSizes.tiles.splice(0, 0, ""); 33 107 34 108 var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 35 mapSizeFilter.list = g_ mapSizes.shortNames;36 mapSizeFilter.list_data = g_ mapSizes.tiles;109 mapSizeFilter.list = g_MapSizes.shortNames; 110 mapSizeFilter.list_data = g_MapSizes.tiles; 37 111 38 112 var playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter"); 39 113 playersNumberFilter.list = [translateWithContext("player number", "Any"),2,3,4,5,6,7,8]; 40 114 playersNumberFilter.list_data = ["",2,3,4,5,6,7,8]; 41 115 42 116 var mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter"); 43 mapTypeFilter.list = [translateWithContext("map", "Any")].concat(g_ mapTypesText);44 mapTypeFilter.list_data = [""].concat(g_ mapTypes);117 mapTypeFilter.list = [translateWithContext("map", "Any")].concat(g_MapTypesText); 118 mapTypeFilter.list_data = [""].concat(g_MapTypes); 45 119 46 120 Engine.LobbySetPlayerPresence("available"); 47 121 Engine.SendGetGameList(); 48 122 Engine.SendGetBoardList(); 49 123 updatePlayerList(); … … 103 177 applyFilters(); 104 178 } 105 179 106 180 function applyFilters() 107 181 { 108 // Update the list of games109 182 updateGameList(); 110 111 // Update info box about the game currently selected112 183 updateGameSelection(); 113 184 } 114 185 115 186 /** 116 187 * Filter a game based on the status of the filter dropdowns. … … 118 189 * @param game Game to be tested. 119 190 * @return True if game should not be displayed. 120 191 */ 121 192 function filterGame(game) 122 193 { 123 var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter");124 var playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter");125 var mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter");126 var showFullFilter = Engine.GetGUIObjectByName("showFullFilter");127 194 // We assume index 0 means display all for any given filter. 195 var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 128 196 if (mapSizeFilter.selected != 0 && game.mapSize != mapSizeFilter.list_data[mapSizeFilter.selected]) 129 197 return true; 198 199 var playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter"); 130 200 if (playersNumberFilter.selected != 0 && game.tnbp != playersNumberFilter.list_data[playersNumberFilter.selected]) 131 201 return true; 202 203 var mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter"); 132 204 if (mapTypeFilter.selected != 0 && game.mapType != mapTypeFilter.list_data[mapTypeFilter.selected]) 133 205 return true; 206 207 var showFullFilter = Engine.GetGUIObjectByName("showFullFilter"); 134 208 if (!showFullFilter.checked && game.tnbp <= game.nbp) 135 209 return true; 136 210 137 211 return false; 138 212 } … … 145 219 function updateSubject(newSubject) 146 220 { 147 221 var subject = Engine.GetGUIObjectByName("subject"); 148 222 var subjectBox = Engine.GetGUIObjectByName("subjectBox"); 149 223 var logo = Engine.GetGUIObjectByName("logo"); 224 150 225 // Load new subject and un-escape newlines. 151 226 subject.caption = newSubject; 227 152 228 // If the subject is only whitespace, hide it and reposition the logo. 153 229 if (subject.caption.match(/^([\s\t\r\n]*)$/g)) 154 230 { 155 231 subjectBox.hidden = true; 156 232 logo.size = "50%-110 50%-50 50%+110 50%+50"; … … 167 243 * 168 244 * @return Array containing the player, presence, nickname, and rating listings. 169 245 */ 170 246 function updatePlayerList() 171 247 { 172 var playersBox = Engine.GetGUIObjectByName("playersBox");173 var playerList = [];174 var presenceList = [];175 var nickList = [];176 var ratingList = [];177 var cleanPlayerList = Engine.GetPlayerList();178 248 // Sort the player list, ignoring case. 179 cleanPlayerList.sort(function(a,b)249 var cleanPlayerList = Engine.GetPlayerList().sort((a,b) => 180 250 { 251 let statusOrder = Object.keys(g_PlayerStatuses); 252 let sortA, sortB; 181 253 switch (g_PlayerListSortBy) 182 254 { 183 255 case 'rating': 184 if (a.rating < b.rating) 185 return -g_PlayerListOrder; 186 else if (a.rating > b.rating) 187 return g_PlayerListOrder; 188 return 0; 256 sortA = a.rating; 257 sortB = b.rating; 258 break; 189 259 case 'status': 190 let order = ["available", "away", "playing", "gone", "offline"]; 191 let presenceA = order.indexOf(a.presence); 192 let presenceB = order.indexOf(b.presence); 193 if (presenceA < presenceB) 194 return -g_PlayerListOrder; 195 else if (presenceA > presenceB) 196 return g_PlayerListOrder; 197 return 0; 260 sortA = statusOrder.indexOf(a.presence); 261 sortB = statusOrder.indexOf(b.presence); 262 break; 198 263 case 'name': 199 264 default: 200 var aName = a.name.toLowerCase(); 201 var bName = b.name.toLowerCase(); 202 if (aName < bName) 203 return -g_PlayerListOrder; 204 else if (aName > bName) 205 return g_PlayerListOrder; 206 return 0; 265 sortA = a.name.toLowerCase(); 266 sortB = b.name.toLowerCase(); 207 267 } 268 269 if (sortA < sortB) 270 return -g_PlayerListOrder; 271 else if (sortA > sortB) 272 return g_PlayerListOrder; 273 return 0; 208 274 }); 209 for (var i = 0; i < cleanPlayerList.length; i++) 275 276 // Create GUI entries 277 var playerList = []; 278 var presenceList = []; 279 var nickList = []; 280 var ratingList = []; 281 for (let player of cleanPlayerList) 210 282 { 211 283 // Identify current user's rating. 212 if (cleanPlayerList[i].name == g_Name && cleanPlayerList[i].rating) 213 g_userRating = cleanPlayerList[i].rating; 214 // Add a "-" for unrated players. 215 if (!cleanPlayerList[i].rating) 216 cleanPlayerList[i].rating = "-"; 217 // Colorize. 218 var [name, status, rating] = formatPlayerListEntry(cleanPlayerList[i].name, cleanPlayerList[i].presence, cleanPlayerList[i].rating, cleanPlayerList[i].role); 284 if (player.name == g_Username && player.rating) 285 g_UserRating = player.rating; 286 287 let [name, status, rating] = formatPlayerListEntry(player.name, player.presence, player.rating, player.role); 288 219 289 // Push to lists. 220 290 playerList.push(name); 221 291 presenceList.push(status); 222 nickList.push(cleanPlayerList[i].name); 223 var ratingSpaces = " "; 224 for (var index = 0; index < 4 - Math.ceil(Math.log(cleanPlayerList[i].rating) / Math.LN10); index++) 225 ratingSpaces += " "; 226 ratingList.push(String(ratingSpaces + rating)); 292 ratingList.push(rating); 293 nickList.push(player.name); 227 294 } 295 296 // Push to GUI 297 var playersBox = Engine.GetGUIObjectByName("playersBox"); 228 298 playersBox.list_name = playerList; 229 299 playersBox.list_status = presenceList; 230 300 playersBox.list_rating = ratingList; 231 301 playersBox.list = nickList; 232 302 if (playersBox.selected >= playersBox.list.length) 233 303 playersBox.selected = -1; 234 return [playerList, presenceList, nickList, ratingList];235 304 } 236 305 237 306 /** 238 307 * Display the profile of the selected player. 239 308 * Displays N/A for all stats until updateProfile is called when the stats 240 309 * are actually received from the bot. 241 * 310 * 242 311 * @param caller From which screen is the user requesting data from? 243 312 */ 244 313 function displayProfile(caller) 245 314 { 246 var playerList , rating;315 var playerList; 247 316 if (caller == "leaderboard") 248 317 playerList = Engine.GetGUIObjectByName("leaderboardBox"); 249 318 else if (caller == "lobbylist") 250 319 playerList = Engine.GetGUIObjectByName("playersBox"); 251 320 else if (caller == "fetch") … … 261 330 Engine.GetGUIObjectByName("profileArea").hidden = true; 262 331 return; 263 332 } 264 333 Engine.GetGUIObjectByName("profileArea").hidden = false; 265 334 266 Engine.SendGetProfile(playerList.list[playerList.selected]); 335 Engine.SendGetProfile(playerList.list[playerList.selected]); 267 336 268 var user = playerList.list_name[playerList.selected];269 337 var role = Engine.LobbyGetPlayerRole(playerList.list[playerList.selected]); 270 var userList = Engine.GetGUIObjectByName("playersBox");271 338 if (role && caller == "lobbylist") 272 339 { 273 // Make the role uppercase. 274 role = role.charAt(0).toUpperCase() + role.slice(1); 275 if (role == "Moderator") 276 role = '[color="0 125 0"]' + translate(role) + '[/color]'; 340 if (!g_UserRoles[role]) 341 { 342 error("Unknown user role '" + role + "'"); 343 role = "invalid"; 344 } 345 role = colorize(g_UserRoles[role].color, g_UserRoles[role].title); 277 346 } 278 347 else 279 348 role = ""; 280 349 281 Engine.GetGUIObjectByName("usernameText").caption = user;282 Engine.GetGUIObjectByName("roleText").caption = translate(role);350 Engine.GetGUIObjectByName("usernameText").caption = playerList.list_name[playerList.selected]; 351 Engine.GetGUIObjectByName("roleText").caption = role; 283 352 Engine.GetGUIObjectByName("rankText").caption = translate("N/A"); 284 353 Engine.GetGUIObjectByName("highestRatingText").caption = translate("N/A"); 285 354 Engine.GetGUIObjectByName("totalGamesText").caption = translate("N/A"); 286 355 Engine.GetGUIObjectByName("winsText").caption = translate("N/A"); 287 356 Engine.GetGUIObjectByName("lossesText").caption = translate("N/A"); 288 357 Engine.GetGUIObjectByName("ratioText").caption = translate("N/A"); 289 358 } 290 359 291 360 /** 292 361 * Update the profile of the selected player with data from the bot. 293 *294 362 */ 295 363 function updateProfile() 296 364 { 297 365 var playerList, user; 298 366 var attributes = Engine.GetProfile(); … … 306 374 Engine.GetGUIObjectByName("profileErrorText").hidden = false; 307 375 return; 308 376 } 309 377 Engine.GetGUIObjectByName("profileWindowArea").hidden = false; 310 378 Engine.GetGUIObjectByName("profileErrorText").hidden = true; 311 379 312 380 if (attributes[0].rating != "") 313 user = sprintf(translate("%(nick)s (%(rating)s)"), { nick: user, rating: attributes[0].rating });381 user = sprintf(translate("%(nick)s (%(rating)s)"), { "nick": user, "rating": attributes[0].rating }); 314 382 315 383 Engine.GetGUIObjectByName("profileUsernameText").caption = user; 316 384 Engine.GetGUIObjectByName("profileRankText").caption = attributes[0].rank; 317 385 Engine.GetGUIObjectByName("profileHighestRatingText").caption = attributes[0].highestRating; 318 386 Engine.GetGUIObjectByName("profileTotalGamesText").caption = attributes[0].totalGamesPlayed; 319 387 Engine.GetGUIObjectByName("profileWinsText").caption = attributes[0].wins; 320 388 Engine.GetGUIObjectByName("profileLossesText").caption = attributes[0].losses; 321 389 322 390 var winRate = (attributes[0].wins / attributes[0].totalGamesPlayed * 100).toFixed(2); 323 391 if (attributes[0].totalGamesPlayed != 0) 324 Engine.GetGUIObjectByName("profileRatioText").caption = sprintf(translate("%(percentage)s%%"), { percentage: winRate });392 Engine.GetGUIObjectByName("profileRatioText").caption = sprintf(translate("%(percentage)s%%"), { "percentage": winRate }); 325 393 else 326 394 Engine.GetGUIObjectByName("profileRatioText").caption = translateWithContext("Used for an undefined winning rate", "-"); 327 395 return; 328 396 } 329 397 else if (!Engine.GetGUIObjectByName("leaderboard").hidden) 330 398 playerList = Engine.GetGUIObjectByName("leaderboardBox"); 331 399 else 332 400 playerList = Engine.GetGUIObjectByName("playersBox"); 333 401 334 402 if (attributes[0].rating == "-2") 335 403 return; 404 336 405 // Make sure the stats we have received coincide with the selected player. 337 406 if (attributes[0].player != playerList.list[playerList.selected]) 338 407 return; 408 339 409 user = playerList.list_name[playerList.selected]; 340 410 if (attributes[0].rating != "") 341 user = sprintf(translate("%(nick)s (%(rating)s)"), { nick: user, rating: attributes[0].rating });411 user = sprintf(translate("%(nick)s (%(rating)s)"), { "nick": user, "rating": attributes[0].rating }); 342 412 343 413 Engine.GetGUIObjectByName("usernameText").caption = user; 344 414 Engine.GetGUIObjectByName("rankText").caption = attributes[0].rank; 345 415 Engine.GetGUIObjectByName("highestRatingText").caption = attributes[0].highestRating; 346 416 Engine.GetGUIObjectByName("totalGamesText").caption = attributes[0].totalGamesPlayed; 347 417 Engine.GetGUIObjectByName("winsText").caption = attributes[0].wins; 348 418 Engine.GetGUIObjectByName("lossesText").caption = attributes[0].losses; 349 419 350 420 var winRate = (attributes[0].wins / attributes[0].totalGamesPlayed * 100).toFixed(2); 351 421 if (attributes[0].totalGamesPlayed != 0) 352 Engine.GetGUIObjectByName("ratioText").caption = sprintf(translate("%(percentage)s%%"), { percentage: winRate });422 Engine.GetGUIObjectByName("ratioText").caption = sprintf(translate("%(percentage)s%%"), { "percentage": winRate }); 353 423 else 354 424 Engine.GetGUIObjectByName("ratioText").caption = translateWithContext("Used for an undefined winning rate", "-"); 355 425 } 356 426 357 427 /** 358 428 * Update the leaderboard from data cached in C++. 359 429 */ 360 430 function updateLeaderboard() 361 431 { 362 // Get list from C++ 363 var boardList = Engine.GetBoardList(); 364 // Get GUI leaderboard object 365 var leaderboard = Engine.GetGUIObjectByName("leaderboardBox"); 366 // Sort list in acending order by rating 367 boardList.sort(function(a, b) b.rating - a.rating); 432 // Sort users by rating 433 var boardList = Engine.GetBoardList().sort((a, b) => b.rating - a.rating); 368 434 435 // Create GUI entries 369 436 var list = []; 370 437 var list_name = []; 371 438 var list_rank = []; 372 439 var list_rating = []; 373 440 374 // Push changes 375 for (var i = 0; i < boardList.length; i++) 441 for (let i in boardList) 376 442 { 377 443 list_name.push(boardList[i].name); 378 444 list_rating.push(boardList[i].rating); 379 445 list_rank.push(i+1); 380 446 list.push(boardList[i].name); 381 447 } 382 448 449 // Push to GUI 450 var leaderboard = Engine.GetGUIObjectByName("leaderboardBox"); 383 451 leaderboard.list_name = list_name; 384 452 leaderboard.list_rating = list_rating; 385 453 leaderboard.list_rank = list_rank; 386 454 leaderboard.list = list; 387 455 … … 392 460 /** 393 461 * Update the game listing from data cached in C++. 394 462 */ 395 463 function updateGameList() 396 464 { 397 var gamesBox = Engine.GetGUIObjectByName("gamesBox"); 398 var gameList = Engine.GetGameList(); 399 // Store the game whole game list data so that we can access it later 400 // to update the game info panel. 401 g_GameList = gameList; 402 403 // Sort the list of games to that games 'waiting' are displayed at the top, followed by 'init', followed by 'running'. 404 var gameStatuses = ['waiting', 'init', 'running']; 405 g_GameList.sort(function (a,b) { 465 var gameStatusOrder = Object.keys(g_GameStatusColor); 466 467 g_GameList = Engine.GetGameList().sort((a,b) => 468 { 406 469 switch (g_GameListSortBy) 407 470 { 408 471 case 'name': 409 472 case 'mapSize': 410 473 // mapSize contains the number of tiles for random maps 411 474 // scenario maps always display default size 475 412 476 case 'mapType': 413 477 if (a[g_GameListSortBy] < b[g_GameListSortBy]) 414 478 return -g_GameListOrder; 415 479 else if (a[g_GameListSortBy] > b[g_GameListSortBy]) 416 480 return g_GameListOrder; 417 481 return 0; 482 418 483 case 'mapName': 419 484 if (translate(a.niceMapName) < translate(b.niceMapName)) 420 485 return -g_GameListOrder; 421 486 else if (translate(a.niceMapName) > translate(b.niceMapName)) 422 487 return g_GameListOrder; 423 488 return 0; 489 424 490 case 'nPlayers': 425 491 // Numerical comparison of player count ratio. 426 492 if (a.nbp * b.tnbp < b.nbp * a.tnbp) // ratio a = a.nbp / a.tnbp, ratio b = b.nbp / b.tnbp 427 493 return -g_GameListOrder; 428 494 else if (a.nbp * b.tnbp > b.nbp * a.tnbp) 429 495 return g_GameListOrder; 430 496 return 0; 497 431 498 default: 432 if (gameStatus es.indexOf(a.state) < gameStatuses.indexOf(b.state))499 if (gameStatusOrder.indexOf(a.state) < gameStatusOrder.indexOf(b.state)) 433 500 return -1; 434 else if (gameStatus es.indexOf(a.state) > gameStatuses.indexOf(b.state))501 else if (gameStatusOrder.indexOf(a.state) > gameStatusOrder.indexOf(b.state)) 435 502 return 1; 436 503 437 504 // Alphabetical comparison of names as tiebreaker. 438 505 if (a.name < b.name) 439 506 return -1; … … 441 508 return 1; 442 509 return 0; 443 510 } 444 511 }); 445 512 513 // Create GUI entries 446 514 var list_name = []; 447 515 var list_ip = []; 448 516 var list_mapName = []; 449 517 var list_mapSize = []; 450 518 var list_mapType = []; 451 519 var list_nPlayers = []; 452 520 var list = []; 453 521 var list_data = []; 454 522 455 var c = 0; 456 for (var g of gameList) 523 for (let i in g_GameList) 457 524 { 458 if (!filterGame(g)) 525 let g = g_GameList[i]; 526 527 if (filterGame(g)) 528 continue; 529 530 if (!g_GameStatusColor[g.state]) 459 531 { 460 // 'waiting' games are highlighted in orange, 'running' in red, and 'init' in green. 461 let name = escapeText(g.name); 462 if (g.state == 'init') 463 name = '[color="0 125 0"]' + name + '[/color]'; 464 else if (g.state == 'waiting') 465 name = '[color="255 127 0"]' + name + '[/color]'; 466 else 467 name = '[color="255 0 0"]' + name + '[/color]'; 468 list_name.push(name); 469 list_ip.push(g.ip); 470 list_mapName.push(translate(g.niceMapName)); 471 list_mapSize.push(translatedMapSize(g.mapSize)); 472 let idx = g_mapTypes.indexOf(g.mapType); 473 list_mapType.push(idx != -1 ? g_mapTypesText[idx] : ""); 474 list_nPlayers.push(g.nbp + "/" +g.tnbp); 475 list.push(name); 476 list_data.push(c); 532 error("Unknown game status '" + g.state + "'"); 533 g.state = "unknown"; 477 534 } 478 c++; 535 536 let name = colorize(g_GameStatusColor[g.state], escapeText(g.name)); 537 538 list_name.push(name); 539 list_ip.push(g.ip); 540 list_mapName.push(translate(g.niceMapName)); 541 list_mapSize.push(translatedMapSize(g.mapSize)); 542 let idx = g_MapTypes.indexOf(g.mapType); 543 list_mapType.push(idx != -1 ? g_MapTypesText[idx] : ""); 544 list_nPlayers.push(g.nbp + "/" + g.tnbp); 545 list.push(name); 546 list_data.push(i); 479 547 } 480 548 549 // Push to GUI 550 var gamesBox = Engine.GetGUIObjectByName("gamesBox"); 481 551 gamesBox.list_name = list_name; 482 552 gamesBox.list_mapName = list_mapName; 483 553 gamesBox.list_mapSize = list_mapSize; 484 554 gamesBox.list_mapType = list_mapType; 485 555 gamesBox.list_nPlayers = list_nPlayers; … … 494 564 } 495 565 496 566 /** 497 567 * Colorize and format the entries in the player list. 498 568 * 499 * @param nickname Name of player. 500 * @param presence Presence of player. 501 * @param rating Rating of player. 502 * @return Colorized versions of name, status, and rating. 569 * @return {Array} [name, status, rating] with each item being colorized 503 570 */ 504 571 function formatPlayerListEntry(nickname, presence, rating) 505 572 { 506 // Set colors based on player status 507 var color; 508 var status; 509 switch (presence) 510 { 511 case "playing": 512 color = "125 0 0"; 513 status = translate("Busy"); 514 break; 515 case "gone": 516 case "away": 517 color = "229 76 13"; 518 status = translate("Away"); 519 break; 520 case "available": 521 color = "0 125 0"; 522 status = translate("Online"); 523 break; 524 case "offline": 525 color = "0 0 0"; 526 status = translate("Offline"); 527 break; 528 default: 529 warn(sprintf("Unknown presence '%(presence)s'", { presence: presence })); 530 color = "178 178 178"; 531 status = translateWithContext("lobby presence", "Unknown"); 532 break; 573 if (!g_PlayerStatuses[presence]) 574 { 575 error("Unknown presence '" + presence + "'"); 576 presence = "unknown"; 533 577 } 534 // Center the unrated symbol.535 if (rating == "-")536 rating = " -";537 var formattedStatus = '[color="' + color + '"]' + status + "[/color]";538 var formattedRating = '[color="' + color + '"]' + rating + "[/color]";539 var role = Engine.LobbyGetPlayerRole(nickname);540 if (role == "moderator")541 nickname = g_modPrefix + nickname;542 var formattedName = colorPlayerName(nickname);543 578 544 // Push this player's name and status onto the list 545 return [formattedName, formattedStatus, formattedRating]; 579 // Center the rating (optional placeholder) 580 if (!rating) 581 rating = "-"; 582 rating = (" " + rating).substr(-5); 583 584 // Add moderator prefix 585 if (Engine.LobbyGetPlayerRole(nickname) == "moderator") 586 nickname = g_ModPrefix + nickname; 587 588 // Push colorized name, status and rating to the list 589 var color = g_PlayerStatuses[presence].color; 590 return [ 591 colorizePlayername(nickname), 592 colorize(color, g_PlayerStatuses[presence].status), 593 colorize(color, rating) 594 ]; 546 595 } 547 596 548 597 /** 549 598 * Given a map size, returns that map size translated into the current 550 599 * language. … … 552 601 function translatedMapSize(mapSize) 553 602 { 554 603 if (+mapSize !== +mapSize) // NaN 555 604 return translate(mapSize); 556 605 else 557 return g_ mapSizes.shortNames[g_mapSizes.tiles.indexOf(+mapSize)];606 return g_MapSizes.shortNames[g_MapSizes.tiles.indexOf(+mapSize)]; 558 607 } 559 608 560 609 /** 561 610 * Populate the game info area with information on the current game selection. 562 611 */ … … 575 624 var mapData; 576 625 var g = Engine.GetGUIObjectByName("gamesBox").list_data[selected]; 577 626 578 627 // Load map data 579 628 if (g_GameList[g].mapType == "random" && g_GameList[g].mapName == "random") 580 mapData = { "settings": {"Description": translate("A randomly selected map.")}};629 mapData = { "settings": { "Description": translate("A randomly selected map.") } }; 581 630 else if (g_GameList[g].mapType == "random" && Engine.FileExists(g_GameList[g].mapName + ".json")) 582 631 mapData = Engine.ReadJSONFile(g_GameList[g].mapName + ".json"); 583 632 else if (Engine.FileExists(g_GameList[g].mapName + ".xml")) 584 633 mapData = Engine.LoadMapSettings(g_GameList[g].mapName + ".xml"); 585 634 else 586 // Warn the player if we can't find the map. 587 warn(sprintf("Map '%(mapName)s' not found locally.", { mapName: g_GameList[g].mapName })); 635 warn("Map '" + g_GameList[g].mapName + "' not found locally."); 588 636 589 637 // Show the game info panel and join button. 590 638 Engine.GetGUIObjectByName("gameInfo").hidden = false; 591 639 Engine.GetGUIObjectByName("joinGameButton").hidden = false; 592 640 Engine.GetGUIObjectByName("gameInfoEmpty").hidden = true; … … 594 642 // Display the map name, number of players, the names of the players, the map size and the map type. 595 643 Engine.GetGUIObjectByName("sgMapName").caption = translate(g_GameList[g].niceMapName); 596 644 Engine.GetGUIObjectByName("sgNbPlayers").caption = g_GameList[g].nbp + "/" + g_GameList[g].tnbp; 597 645 Engine.GetGUIObjectByName("sgPlayersNames").caption = g_GameList[g].players; 598 646 Engine.GetGUIObjectByName("sgMapSize").caption = translatedMapSize(g_GameList[g].mapSize); 599 let idx = g_ mapTypes.indexOf(g_GameList[g].mapType);600 Engine.GetGUIObjectByName("sgMapType").caption = idx != -1 ? g_ mapTypesText[idx] : "";647 let idx = g_MapTypes.indexOf(g_GameList[g].mapType); 648 Engine.GetGUIObjectByName("sgMapType").caption = idx != -1 ? g_MapTypesText[idx] : ""; 601 649 602 650 // Display map description if it exists, otherwise display a placeholder. 603 651 if (mapData && mapData.settings.Description) 604 652 var mapDescription = translate(mapData.settings.Description); 605 653 else … … 619 667 * Start the joining process on the currectly selected game. 620 668 */ 621 669 function joinSelectedGame() 622 670 { 623 671 var gamesBox = Engine.GetGUIObjectByName("gamesBox"); 624 if (gamesBox.selected >= 0) 625 { 626 var g = gamesBox.list_data[gamesBox.selected]; 627 var sname = g_Name; 628 var sip = g_GameList[g].ip; 629 630 // TODO: What about valid host names? 631 // Check if it looks like an ip address 632 if (sip.split('.').length != 4) 633 { 634 addChatMessage({ "from": "system", "text": sprintf(translate("This game's address '%(ip)s' does not appear to be valid."), { ip: sip }) }); 635 return; 636 } 672 if (gamesBox.selected < 0) 673 return; 637 674 638 // Open Multiplayer connection window with join option. 639 Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "join", name: sname, ip: sip, rating: g_userRating }); 675 // TODO: What about valid host names? 676 // Check if it looks like an ip address 677 var ip = g_GameList[gamesBox.list_data[gamesBox.selected]].ip; 678 if (ip.split('.').length != 4) 679 { 680 addChatMessage({ "from": "system", "text": sprintf(translate("This game's address '%(ip)s' does not appear to be valid."), { "ip": ip }) }); 681 return; 640 682 } 683 684 // Open Multiplayer connection window with join option. 685 Engine.PushGuiPage("page_gamesetup_mp.xml", { 686 "multiplayerGameType": "join", 687 "ip": ip, 688 "name": g_Username, 689 "rating": g_UserRating 690 }); 641 691 } 642 692 643 693 /** 644 694 * Start the hosting process. 645 695 */ 646 696 function hostGame() 647 697 { 648 698 // Open Multiplayer connection window with host option. 649 Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "host", name: g_Name, rating: g_userRating }); 699 Engine.PushGuiPage("page_gamesetup_mp.xml", { 700 "multiplayerGameType": "host", 701 "name": g_Username, 702 "rating": g_UserRating 703 }); 650 704 } 651 705 652 706 //////////////////////////////////////////////////////////////////////////////////////////////// 653 707 // Utils 654 708 //////////////////////////////////////////////////////////////////////////////////////////////// … … 695 749 var ratingList = playersBox.list_rating; 696 750 var nickIndex = nickList.indexOf(nick); 697 751 switch(message.level) 698 752 { 699 753 case "join": 700 var [name, status, rating] = formatPlayerListEntry(nick, presence, "-");754 var [name, status, rating] = formatPlayerListEntry(nick, presence, null); 701 755 playerList.push(name); 702 756 presenceList.push(status); 703 757 nickList.push(nick); 704 758 ratingList.push(String(rating)); 705 addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has joined."), { nick: nick }), "key": g_specialKey});759 addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has joined."), { "nick": nick }), "isSpecial": true }); 706 760 break; 707 761 case "leave": 708 762 if (nickIndex == -1) // Left, but not present (TODO: warn about this?) 709 763 break; 710 764 playerList.splice(nickIndex, 1); 711 765 presenceList.splice(nickIndex, 1); 712 766 nickList.splice(nickIndex, 1); 713 767 ratingList.splice(nickIndex, 1); 714 addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has left."), { nick: nick }), "key": g_specialKey});768 addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has left."), { "nick": nick }), "isSpecial": true }); 715 769 break; 716 770 case "nick": 717 771 if (nickIndex == -1) // Changed nick, but not present (shouldn't ever happen) 718 772 break; 719 if (!isValidNick(message.data))720 {721 addChatMessage({ "from": "system", "text": sprintf(translate("Invalid nickname: %(nick)s"), { nick: message.data })});722 break;723 }724 773 var [name, status, rating] = formatPlayerListEntry(message.data, presence, stripColorCodes(ratingList[nickIndex])); // TODO: actually we don't want to change the presence here, so use what was used before 725 774 playerList[nickIndex] = name; 726 775 // presence stays the same 727 776 nickList[nickIndex] = message.data; 728 addChatMessage({ "text": "/special " + sprintf(translate("%(oldnick)s is now known as %(newnick)s."), { oldnick: nick, newnick: message.data }), "key": g_specialKey});777 addChatMessage({ "text": "/special " + sprintf(translate("%(oldnick)s is now known as %(newnick)s."), { "oldnick": nick, "newnick": message.data }), "isSpecial": true }); 729 778 break; 730 779 case "presence": 731 780 if (nickIndex == -1) // Changed presence, but not online (shouldn't ever happen) 732 781 break; 733 782 var [name, status, rating] = formatPlayerListEntry(nick, presence, stripColorCodes(ratingList[nickIndex])); … … 737 786 break; 738 787 case "subject": 739 788 updateSubject(message.text); 740 789 break; 741 790 default: 742 warn(sprintf("Unknown message.level '%(msglvl)s'", { msglvl: message.level })); 743 break; 791 warn("Unknown message.level '" + message.level + "'"); 744 792 } 745 793 // Push new data to GUI 746 794 playersBox.list_name = playerList; 747 795 playersBox.list_status = presenceList; 748 796 playersBox.list_rating = ratingList; 749 playersBox.list = nickList; 797 playersBox.list = nickList; 750 798 if (playersBox.selected >= playersBox.list.length) 751 799 playersBox.selected = -1; 752 800 break; 753 801 case "system": 754 802 switch (message.level) 755 803 { 756 804 case "standard": 757 addChatMessage({ "from": "system", "text": text, "color": "150 0 0"});805 addChatMessage({ "from": "system", "text": text, "color": g_SystemColor }); 758 806 if (message.text == "disconnected") 759 807 { 760 808 // Clear the list of games and the list of players 761 809 updateGameList(); 762 810 updateLeaderboard(); 763 811 updatePlayerList(); 764 812 // Disable the 'host' button 765 813 Engine.GetGUIObjectByName("hostButton").enabled = false; 766 814 } 767 815 else if (message.text == "connected") 768 {769 816 Engine.GetGUIObjectByName("hostButton").enabled = true; 770 } 817 771 818 break; 772 819 case "error": 773 addChatMessage({ "from": "system", "text": text, "color": "150 0 0"});820 addChatMessage({ "from": "system", "text": text, "color": g_SystemColor }); 774 821 break; 775 822 case "internal": 776 823 switch (message.text) 777 824 { 778 825 case "gamelist updated": … … 790 837 } 791 838 break; 792 839 } 793 840 break; 794 841 default: 795 error( sprintf("Unrecognised message type %(msgtype)s", { msgtype: message.type }));842 error("Unrecognised message type '" + message.type + "'"); 796 843 } 797 844 } 798 845 } 799 846 800 /* Messages */ 847 function returnToMainMenu() 848 { 849 lobbyStop(); 850 Engine.SwitchGuiPage("page_pregame.xml"); 851 } 852 801 853 function submitChatInput() 802 854 { 803 855 var input = Engine.GetGUIObjectByName("chatInput"); 804 856 var text = input.caption; 805 if (text.length) 806 { 807 if (!handleSpecialCommand(text) && !isSpam(text, g_Name)) 808 Engine.LobbySendMessage(text); 809 input.caption = ""; 810 } 811 } 857 if (!text.length) 858 return; 812 859 813 function isValidNick(nick)814 { 815 var prohibitedNicks = ["system"]; 816 return prohibitedNicks.indexOf(nick) == -1;860 if (!handleSpecialCommand(text) && !isSpam(text, g_Username)) 861 Engine.LobbySendMessage(text); 862 863 input.caption = ""; 817 864 } 818 865 819 866 /** 820 867 * Handle all '/' commands. 821 868 * … … 825 872 function handleSpecialCommand(text) 826 873 { 827 874 if (text[0] != '/') 828 875 return false; 829 876 830 var [cmd, nick] = ircSplit(text);877 var [cmd, nick] = splitCommand(text); 831 878 832 879 switch (cmd) 833 880 { 834 881 case "away": 835 882 Engine.LobbySetPlayerPresence("away"); … … 843 890 break; 844 891 case "ban": // TODO: Split reason from nick and pass it too, for now just support "/ban nick" 845 892 Engine.LobbyBan(nick, ""); 846 893 break; 847 894 case "quit": 848 lobbyStop(); 849 Engine.SwitchGuiPage("page_pregame.xml"); 895 returnToMainMenu(); 850 896 break; 851 897 case "say": 852 898 case "me": 853 899 return false; 854 900 default: 855 addChatMessage({ "from":"system", "text": sprintf(translate("We're sorry, the '%(cmd)s' command is not supported."), { cmd: cmd})});901 addChatMessage({ "from":"system", "text": sprintf(translate("We're sorry, the '%(cmd)s' command is not supported."), { "cmd": cmd }) }); 856 902 } 903 857 904 return true; 858 905 } 859 906 860 907 /** 861 908 * Process and, if appropriate, display a formatted message. … … 863 910 * @param msg The message to be processed. 864 911 */ 865 912 function addChatMessage(msg) 866 913 { 867 914 // Some calls of this function will leave some msg parameters empty. Text is required though. 868 if (msg.from) 869 { 870 // Display the moderator symbol in the chatbox. 871 var playerRole = Engine.LobbyGetPlayerRole(msg.from); 872 if (playerRole == "moderator") 873 msg.from = g_modPrefix + msg.from; 874 } 875 else 876 msg.from = null; 877 if (!msg.color) 878 msg.color = null; 879 if (!msg.key) 880 msg.key = null; 881 if (!msg.datetime) 882 msg.datetime = null; 915 msg.from = msg.from || ""; 916 msg.color = msg.color || ""; 917 msg.isSpecial = msg.isSpecial || false; 918 msg.datetime = msg.datetime || ""; 919 920 // Add moderator prefix 921 if (Engine.LobbyGetPlayerRole(msg.from) == "moderator") 922 msg.from = g_ModPrefix + msg.from; 883 923 884 924 // Highlight local user's nick 885 if (msg.text.indexOf(g_ Name) != -1 && g_Name != msg.from)886 msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_ Name + '\\b', "g"), colorPlayerName(g_Name));925 if (msg.text.indexOf(g_Username) != -1 && g_Username != msg.from) 926 msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Username + '\\b', "g"), colorizePlayername(g_Username)); 887 927 888 928 // Run spam test if it's not a historical message 889 929 if (!msg.datetime) 890 930 updateSpamMonitor(msg.from); 931 891 932 if (isSpam(msg.text, msg.from)) 892 933 return; 893 934 894 935 // Format Text 895 var formatted = ircFormat(msg.text, msg.from, msg.color, msg.key, msg.datetime);936 var formatted = formatChatMessage(msg.text, msg.from, msg.color, msg.isSpecial, msg.datetime); 896 937 897 938 // If there is text, add it to the chat box. 898 939 if (formatted) 899 940 { 900 941 g_ChatMessages.push(formatted); 901 942 Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n"); 902 943 } 903 944 } 904 945 905 function ircSplit(string) 946 /** 947 * Given user input, return the command without the slash and the argument (everything after the first space) 948 * @returns {Array} [command, nick] 949 */ 950 function splitCommand(string) 906 951 { 907 952 var idx = string.indexOf(' '); 908 953 if (idx != -1) 909 954 return [string.substr(1,idx-1), string.substr(idx+1)]; 910 955 return [string.substr(1), ""]; 911 956 } 912 957 913 958 /** 914 959 * Format text in an IRC-like way. 915 960 * 916 * @param text Body of the message.917 * @param from Sender of the message.918 * @param color Optional color of sender.919 * @param key Key to verify join/leave messages with. TODO: Remove this, it only provides synthetic security.920 * @param datetime Current date and time of message, only used for historical messages961 * @param text {string} Body of the message. 962 * @param from {string} Sender of the message. 963 * @param color {string} Optional color of sender. 964 * @param isSpecial {Boolean} Whether or not this message is a system message. 965 * @param datetime {string} Current date and time of message, only used for historical messages 921 966 * @return Formatted text. 922 967 */ 923 function ircFormat(text, from, color, key, datetime)968 function formatChatMessage(text, from, color, isSpecial, datetime) 924 969 { 925 // Generate and apply color to uncolored names, 926 if (!color && from) 927 var coloredFrom = colorPlayerName(from); 928 else if (color && from) 929 var coloredFrom = '[color="' + color + '"]' + from + "[/color]"; 970 if (from) 971 var coloredFrom = color ? colorize(color, from) : colorizePlayername(from); 930 972 931 973 // Handle commands allowed past handleSpecialCommand. 932 974 if (text[0] == '/') 933 975 { 934 var [command, message] = ircSplit(text);976 var [command, message] = splitCommand(text); 935 977 switch (command) 936 978 { 937 979 case "me": 938 980 // Translation: IRC message prefix when the sender uses the /me command. 939 var senderString = '[font="sans-bold-13"]' + sprintf(translate("* %(sender)s"), { sender: coloredFrom }) + '[/font]';981 var senderString = '[font="sans-bold-13"]' + sprintf(translate("* %(sender)s"), { "sender": coloredFrom }) + '[/font]'; 940 982 // Translation: IRC message issued using the ‘/me’ command. 941 var formattedMessage = sprintf(translate("%(sender)s %(action)s"), { sender: senderString, action: message });983 var formattedMessage = sprintf(translate("%(sender)s %(action)s"), { "sender": senderString, "action": message }); 942 984 break; 985 943 986 case "say": 944 987 // Translation: IRC message prefix. 945 var senderString = '[font="sans-bold-13"]' + sprintf(translate("<%(sender)s>"), { sender: coloredFrom }) + '[/font]';988 var senderString = '[font="sans-bold-13"]' + sprintf(translate("<%(sender)s>"), { "sender": coloredFrom }) + '[/font]'; 946 989 // Translation: IRC message. 947 var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { sender: senderString, message: message });990 var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { "sender": senderString, "message": message }); 948 991 break 992 949 993 case "special": 950 if ( key === g_specialKey)994 if (isSpecial) 951 995 // Translation: IRC system message. 952 var formattedMessage = '[font="sans-bold-13"]' + sprintf(translate("== %(message)s"), { message: message }) + '[/font]';996 var formattedMessage = '[font="sans-bold-13"]' + sprintf(translate("== %(message)s"), { "message": message }) + '[/font]'; 953 997 else 954 998 { 955 999 // Translation: IRC message prefix. 956 var senderString = '[font="sans-bold-13"]' + sprintf(translate("<%(sender)s>"), { sender: coloredFrom }) + '[/font]';1000 var senderString = '[font="sans-bold-13"]' + sprintf(translate("<%(sender)s>"), { "sender": coloredFrom }) + '[/font]'; 957 1001 // Translation: IRC message. 958 var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { sender: senderString, message: message });1002 var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { "sender": senderString, "message": message }); 959 1003 } 960 1004 break; 1005 961 1006 default: 962 1007 // This should never happen. 963 1008 var formattedMessage = ""; 1009 break; 964 1010 } 965 1011 } 966 1012 else 967 1013 { 968 1014 // Translation: IRC message prefix. 969 var senderString = '[font="sans-bold-13"]' + sprintf(translate("<%(sender)s>"), { sender: coloredFrom }) + '[/font]';1015 var senderString = '[font="sans-bold-13"]' + sprintf(translate("<%(sender)s>"), { "sender": coloredFrom }) + '[/font]'; 970 1016 // Translation: IRC message. 971 var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { sender: senderString, message: text });1017 var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { "sender": senderString, "message": text }); 972 1018 } 973 1019 974 1020 // Build time header if enabled 975 if (g_timestamp) 976 { 977 978 var time; 979 if (datetime) 980 { 981 var parserDate = datetime.split("T")[0].split("-"); 982 var parserTime = datetime.split("T")[1].split(":"); 983 // See http://xmpp.org/extensions/xep-0082.html#sect-idp285136 for format of datetime 984 // Date takes Year, Month, Day, Hour, Minute, Second 985 time = new Date(Date.UTC(parserDate[0], parserDate[1], parserDate[2], parserTime[0], parserTime[1], parserTime[2].split("Z")[0])); 986 } 987 else 988 time = new Date(Date.now()); 989 990 // Translation: Time as shown in the multiplayer lobby (when you enable it in the options page). 991 // For a list of symbols that you can use, see: 992 // https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table 993 var timeString = Engine.FormatMillisecondsIntoDateString(time.getTime(), translate("HH:mm")); 1021 if (g_ShowTimestamp) 1022 return prefixTimestamp(formattedMessage, datetime); 994 1023 995 // Translation: Time prefix as shown in the multiplayer lobby (when you enable it in the options page).996 var timePrefixString = '[font="sans-bold-13"]' + sprintf(translate("\\[%(time)s]"), { time: timeString }) + '[/font]'; 1024 return formattedMessage; 1025 } 997 1026 998 // Translation: IRC message format when there is a time prefix. 999 return sprintf(translate("%(time)s %(message)s"), { time: timePrefixString, message: formattedMessage }); 1027 function prefixTimestamp(formattedMessage, datetime) 1028 { 1029 var time; 1030 if (datetime) 1031 { 1032 var parserDate = datetime.split("T")[0].split("-"); 1033 var parserTime = datetime.split("T")[1].split(":"); 1034 // See http://xmpp.org/extensions/xep-0082.html#sect-idp285136 for format of datetime 1035 // Date takes Year, Month, Day, Hour, Minute, Second 1036 time = new Date(Date.UTC(parserDate[0], parserDate[1], parserDate[2], parserTime[0], parserTime[1], parserTime[2].split("Z")[0])); 1000 1037 } 1001 1038 else 1002 return formattedMessage; 1039 time = new Date(Date.now()); 1040 1041 // Translation: Time as shown in the multiplayer lobby (when you enable it in the options page). 1042 // For a list of symbols that you can use, see: 1043 // https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table 1044 var timeString = Engine.FormatMillisecondsIntoDateString(time.getTime(), translate("HH:mm")); 1045 1046 // Translation: Time prefix as shown in the multiplayer lobby (when you enable it in the options page). 1047 var timePrefixString = '[font="sans-bold-13"]' + sprintf(translate("\\[%(time)s]"), { "time": timeString }) + '[/font]'; 1048 1049 // Translation: IRC message format when there is a time prefix. 1050 return sprintf(translate("%(time)s %(message)s"), { "time": timePrefixString, "message": formattedMessage }); 1003 1051 } 1004 1052 1005 1053 /** 1006 1054 * Update the spam monitor. 1007 1055 * … … 1011 1059 { 1012 1060 // Integer time in seconds. 1013 1061 var time = Math.floor(Date.now() / 1000); 1014 1062 1015 1063 // Update or initialize the user in the spam monitor. 1016 if (g_ spamMonitor[from])1017 g_spamMonitor[from][0]++;1064 if (g_SpamMonitor[from]) 1065 ++g_SpamMonitor[from][0]; 1018 1066 else 1019 g_ spamMonitor[from] = [1, time, 0];1067 g_SpamMonitor[from] = [1, time, 0]; 1020 1068 } 1021 1069 1022 1070 /** 1023 1071 * Check if a message is spam. 1024 1072 * … … 1030 1078 { 1031 1079 // Integer time in seconds. 1032 1080 var time = Math.floor(Date.now() / 1000); 1033 1081 1034 1082 // Initialize if not already in the database. 1035 if (!g_ spamMonitor[from])1036 g_ spamMonitor[from] = [1, time, 0];1083 if (!g_SpamMonitor[from]) 1084 g_SpamMonitor[from] = [1, time, 0]; 1037 1085 1038 1086 // Block blank lines. 1039 1087 if (text == " ") 1040 1088 return true; 1089 1041 1090 // Block users who are still within their spam block period. 1042 else if (g_spamMonitor[from][2] + SPAM_BLOCK_LENGTH>= time)1091 if (g_SpamMonitor[from][2] + g_SpamBlockLength >= time) 1043 1092 return true; 1093 1044 1094 // Block users who exceed the rate of 1 message per second for five seconds and are not already blocked. TODO: Make this smarter and block profanity. 1045 else if (g_spamMonitor[from][0] == 6)1095 if (g_SpamMonitor[from][0] == 6) 1046 1096 { 1047 g_ spamMonitor[from][2] = time;1048 if (from == g_ Name)1097 g_SpamMonitor[from][2] = time; 1098 if (from == g_Username) 1049 1099 addChatMessage({ "from": "system", "text": translate("Please do not spam. You have been blocked for thirty seconds.") }); 1050 1100 return true; 1051 1101 } 1102 1052 1103 // Return false if everything is clear. 1053 else 1054 return false; 1104 return false; 1055 1105 } 1056 1106 1057 1107 /** 1058 1108 * Reset timer used to measure message send speed. 1059 1109 */ … … 1061 1111 { 1062 1112 // Integer time in seconds. 1063 1113 var time = Math.floor(Date.now() / 1000); 1064 1114 1065 1115 // Clear message count every 5 seconds. 1066 for each (var stats in g_spamMonitor)1116 for (let i in g_SpamMonitor) 1067 1117 { 1068 if ( stats[1] + 5 <= time)1118 if (g_SpamMonitor[i][1] + 5 <= time) 1069 1119 { 1070 stats[1] = time;1071 stats[0] = 0;1120 g_SpamMonitor[i][1] = time; 1121 g_SpamMonitor[i][0] = 0; 1072 1122 } 1073 1123 } 1124 } 1074 1125 1126 function colorize(color, text) 1127 { 1128 return '[color="' + color + '"]' + text + "[/color]"; 1129 } 1130 1131 function colorizePlayername(playername) 1132 { 1133 return colorize(g_PlayerColors[playername] ? g_PlayerColors[playername] : getPlayerColor(playername.replace(g_ModPrefix, "")), playername); 1075 1134 } 1076 1135 1077 /* Utilities */ 1078 // Generate a (mostly) unique color for this player based on their name. 1079 // See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript 1136 /** 1137 * Generate a (mostly) unique color for this player based on their name. 1138 * See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript 1139 */ 1080 1140 function getPlayerColor(playername) 1081 1141 { 1082 1142 // Generate a probably-unique hash for the player name and use that to create a color. 1083 1143 var hash = 0; 1084 for ( var i = 0; i < playername.length; i++)1144 for (let i in playername) 1085 1145 hash = playername.charCodeAt(i) + ((hash << 5) - hash); 1086 1146 1087 1147 // 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. 1088 1148 // 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 1089 1149 // us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so 1090 1150 // we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back. 1091 1151 var [h, s, l] = rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF); 1092 1152 return hslToRgb(h, s, Math.max(0.4, l)).join(" "); 1093 1153 } 1094 1154 1095 function repeatString(times, string) { 1096 return Array(times + 1).join(string); 1097 } 1098 1099 // Some names are special and should always appear in certain colors. 1100 var fixedColors = { "system": repeatString(7, "255.0.0."), "@WFGbot": repeatString(7, "255.24.24."), 1101 "pyrogenesis": repeatString(2, "97.0.0.") + repeatString(2, "124.0.0.") + "138.0.0." + 1102 repeatString(2, "174.0.0.") + repeatString(2, "229.40.0.") + repeatString(2, "243.125.15.") }; 1103 function colorPlayerName(playername) 1104 { 1105 var color = fixedColors[playername]; 1106 if (color) { 1107 color = color.split("."); 1108 return ('[color="' + playername.split("").map(function (c, i) color.slice(i * 3, i * 3 + 3).join(" ") + '"]' + c + '[/color][color="') 1109 .join("") + '"]').slice(0, -10); 1110 } 1111 return '[color="' + getPlayerColor(playername.replace(g_modPrefix, "")) + '"]' + playername + '[/color]'; 1112 } 1113 1114 // Ensure `value` is between 0 and 1. 1155 /** 1156 * Ensure `value` is between 0 and 1. 1157 */ 1115 1158 function clampColorValue(value) 1116 1159 { 1117 return Math. abs(1 - Math.abs(value -1));1160 return Math.max(0, Math.min(value, 1)); 1118 1161 } 1119 1162 1120 // See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion 1163 /** 1164 * See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion 1165 */ 1121 1166 function rgbToHsl(r, g, b) 1122 1167 { 1123 1168 r /= 255; 1124 1169 g /= 255; 1125 1170 b /= 255; … … 1167 1212 r = hue2rgb(p, q, h + 1/3); 1168 1213 g = hue2rgb(p, q, h); 1169 1214 b = hue2rgb(p, q, h - 1/3); 1170 1215 } 1171 1216 1172 return [r, g, b].map( function (n)Math.round(n * 255));1217 return [r, g, b].map(n => Math.round(n * 255)); 1173 1218 } 1174 1175 (function () {1176 function hexToRgb(hex) {1177 return parseInt(hex.slice(0, 2), 16) + "." + parseInt(hex.slice(2, 4), 16) + "." + parseInt(hex.slice(4, 6), 16) + ".";1178 }1179 function r(times, hex) {1180 return repeatString(times, hexToRgb(hex));1181 }1182 1183 fixedColors["Twilight_Sparkle"] = r(2, "d19fe3") + r(2, "b689c8") + r(2, "a76bc2") +1184 r(4, "263773") + r(2, "131f46") + r(2, "662d8a") + r(2, "ed438a");1185 fixedColors["Applejack"] = r(3, "ffc261") + r(3, "efb05d") + r(3, "f26f31");1186 fixedColors["Rarity"] = r(1, "ebeff1") + r(1, "dee3e4") + r(1, "bec2c3") +1187 r(1, "83509f") + r(1, "4b2568") + r(1, "4917d6");1188 fixedColors["Rainbow_Dash"] = r(2, "ee4144") + r(1, "f37033") + r(1, "fdf6af") +1189 r(1, "62bc4d") + r(1, "1e98d3") + r(2, "672f89") + r(1, "9edbf9") +1190 r(1, "88c4eb") + r(1, "77b0e0") + r(1, "1e98d3");1191 fixedColors["Pinkie_Pie"] = r(2, "f3b6cf") + r(2, "ec9dc4") + r(4, "eb81b4") +1192 r(1, "ed458b") + r(1, "be1d77");1193 fixedColors["Fluttershy"] = r(2, "fdf6af") + r(2, "fee78f") + r(2, "ead463") +1194 r(2, "f3b6cf") + r(2, "eb81b4");1195 fixedColors["Sweetie_Belle"] = r(2, "efedee") + r(3, "e2dee3") + r(3, "cfc8d1") +1196 r(2, "b28dc0") + r(2, "f6b8d2") + r(1, "795b8a");1197 fixedColors["Apple_Bloom"] = r(2, "f4f49b") + r(2, "e7e793") + r(2, "dac582") +1198 r(2, "f46091") + r(2, "f8415f") + r(1, "c52451");1199 fixedColors["Scootaloo"] = r(2, "fbba64") + r(2, "f2ab56") + r(2, "f37003") +1200 r(2, "bf5d95") + r(1, "bf1f79");1201 fixedColors["Luna"] = r(1, "7ca7fa") + r(1, "5d6fc1") + r(1, "656cb9") + r(1, "393993");1202 fixedColors["Celestia"] = r(1, "fdfafc") + r(1, "f7eaf2") + r(1, "d99ec5") +1203 r(1, "00aec5") + r(1, "f7c6dc") + r(1, "98d9ef") + r(1, "ced7ed") + r(1, "fed17b");1204 })(); -
binaries/data/mods/public/gui/lobby/lobby.xml
161 161 </action> 162 162 </object> 163 163 164 164 <object type="button" style="ModernButtonRed" size="0 100%-25 100% 100%"> 165 165 <translatableAttribute id="caption">Main Menu</translatableAttribute> 166 <action on="Press"> 167 lobbyStop(); 168 Engine.SwitchGuiPage("page_pregame.xml"); 169 </action> 166 <action on="Press">returnToMainMenu();</action> 170 167 </object> 171 168 </object> 172 169 173 170 <!-- Middle panel: Filters, game list, chat box. --> 174 171 <object name="middlePanel" size="20%+5 5% 100%-255 97.2%"> -
binaries/data/mods/public/gui/session/messages.js
366 366 return; 367 367 368 368 if (Engine.GetGUIObjectByName("toggleTeamChat").checked) 369 369 text = "/team " + text; 370 370 371 if (g_IsNetworked) 372 Engine.SendNetworkChat(text); 373 else 374 addChatMessage({ "type": "message", "guid": "local", "text": text }); 371 submitChatDirectly(text); 375 372 } 376 373 377 374 function addChatMessage(msg) 378 375 { 379 376 var playerColor, username;