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