2013-11-07 21:07:24 +01:00
var g _ChatMessages = [ ] ;
var g _Name = "unknown" ;
var g _GameList = { } ;
var g _specialKey = Math . random ( ) ;
2014-01-25 21:51:01 +01:00
// This object looks like {"name":[numMessagesSinceReset, lastReset, timeBlocked]} when in use.
2013-11-07 21:07:24 +01:00
var g _spamMonitor = { } ;
var g _timestamp = Engine . ConfigDB _GetValue ( "user" , "lobby.chattimestamp" ) == "true" ;
var g _mapSizes = { } ;
2014-11-13 02:26:36 +01:00
const g _mapTypesText = [ translateWithContext ( "map" , "Skirmish" ) , translateWithContext ( "map" , "Random" ) , translate ( "Scenario" ) ] ;
const g _mapTypes = [ "skirmish" , "random" , "scenario" ] ;
2014-04-28 07:39:18 +02:00
var g _userRating = "" ; // Rating of user, defaults to Unrated
2014-02-21 00:26:36 +01:00
var g _modPrefix = "@" ;
2014-01-25 21:51:01 +01:00
// Block spammers for 30 seconds.
var SPAM _BLOCK _LENGTH = 30 ;
2013-11-07 21:07:24 +01:00
////////////////////////////////////////////////////////////////////////////////////////////////
function init ( attribs )
{
// Play menu music
2013-11-15 19:35:19 +01:00
initMusic ( ) ;
2013-11-07 21:07:24 +01:00
global . music . setState ( global . music . states . MENU ) ;
g _Name = Engine . LobbyGetNick ( ) ;
g _mapSizes = initMapSizes ( ) ;
2014-04-20 22:51:48 +02:00
g _mapSizes . shortNames . splice ( 0 , 0 , translateWithContext ( "map size" , "Any" ) ) ;
2013-12-31 05:54:16 +01:00
g _mapSizes . tiles . splice ( 0 , 0 , "" ) ;
2013-11-07 21:07:24 +01:00
2014-01-04 11:14:53 +01:00
var mapSizeFilter = Engine . GetGUIObjectByName ( "mapSizeFilter" ) ;
2013-11-07 21:07:24 +01:00
mapSizeFilter . list = g _mapSizes . shortNames ;
mapSizeFilter . list _data = g _mapSizes . tiles ;
2014-01-04 11:14:53 +01:00
var playersNumberFilter = Engine . GetGUIObjectByName ( "playersNumberFilter" ) ;
2014-04-20 22:51:48 +02:00
playersNumberFilter . list = [ translateWithContext ( "player number" , "Any" ) , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ;
2013-12-31 05:54:16 +01:00
playersNumberFilter . list _data = [ "" , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ;
2013-11-07 21:07:24 +01:00
2014-01-04 11:14:53 +01:00
var mapTypeFilter = Engine . GetGUIObjectByName ( "mapTypeFilter" ) ;
2014-11-13 02:26:36 +01:00
mapTypeFilter . list = [ translateWithContext ( "map" , "Any" ) ] . concat ( g _mapTypesText ) ;
mapTypeFilter . list _data = [ "" ] . concat ( g _mapTypes ) ;
2013-11-07 21:07:24 +01:00
Engine . LobbySetPlayerPresence ( "available" ) ;
Engine . SendGetGameList ( ) ;
Engine . SendGetBoardList ( ) ;
2014-01-24 00:13:13 +01:00
Engine . SendGetRatingList ( ) ;
2013-11-07 21:07:24 +01:00
updatePlayerList ( ) ;
2014-01-24 19:20:15 +01:00
updateSubject ( Engine . LobbyGetRoomSubject ( ) ) ;
2013-11-07 21:07:24 +01:00
resetFilters ( ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Xmpp client connection management
////////////////////////////////////////////////////////////////////////////////////////////////
function lobbyStop ( )
{
Engine . StopXmppClient ( ) ;
}
function lobbyConnect ( )
{
Engine . ConnectXmppClient ( ) ;
}
function lobbyDisconnect ( )
{
Engine . DisconnectXmppClient ( ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Update functions
////////////////////////////////////////////////////////////////////////////////////////////////
function resetFilters ( )
{
// Reset states of gui objects
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "mapSizeFilter" ) . selected = 0
Engine . GetGUIObjectByName ( "playersNumberFilter" ) . selected = 0 ;
Engine . GetGUIObjectByName ( "mapTypeFilter" ) . selected = 0 ;
2014-11-16 23:40:00 +01:00
Engine . GetGUIObjectByName ( "showFullFilter" ) . checked = false ;
2013-11-07 21:07:24 +01:00
// Update the list of games
updateGameList ( ) ;
// Update info box about the game currently selected
2014-01-24 19:20:15 +01:00
updateGameSelection ( ) ;
2013-11-07 21:07:24 +01:00
}
function applyFilters ( )
{
// Update the list of games
updateGameList ( ) ;
// Update info box about the game currently selected
2014-01-24 19:20:15 +01:00
updateGameSelection ( ) ;
2013-11-07 21:07:24 +01:00
}
2014-01-24 19:20:15 +01:00
/ * *
* Filter a game based on the status of the filter dropdowns .
*
* @ param game Game to be tested .
* @ return True if game should not be displayed .
* /
function filterGame ( game )
2013-11-07 21:07:24 +01:00
{
2014-01-24 19:20:15 +01:00
var mapSizeFilter = Engine . GetGUIObjectByName ( "mapSizeFilter" ) ;
var playersNumberFilter = Engine . GetGUIObjectByName ( "playersNumberFilter" ) ;
var mapTypeFilter = Engine . GetGUIObjectByName ( "mapTypeFilter" ) ;
var showFullFilter = Engine . GetGUIObjectByName ( "showFullFilter" ) ;
// We assume index 0 means display all for any given filter.
if ( mapSizeFilter . selected != 0 && game . mapSize != mapSizeFilter . list _data [ mapSizeFilter . selected ] )
return true ;
if ( playersNumberFilter . selected != 0 && game . tnbp != playersNumberFilter . list _data [ playersNumberFilter . selected ] )
return true ;
if ( mapTypeFilter . selected != 0 && game . mapType != mapTypeFilter . list _data [ mapTypeFilter . selected ] )
return true ;
if ( ! showFullFilter . checked && game . tnbp <= game . nbp )
return true ;
2013-11-07 21:07:24 +01:00
2014-01-24 19:20:15 +01:00
return false ;
}
/ * *
* Update the subject GUI object .
*
* @ param newSubject New room subject .
* /
function updateSubject ( newSubject )
{
var subject = Engine . GetGUIObjectByName ( "subject" ) ;
var subjectBox = Engine . GetGUIObjectByName ( "subjectBox" ) ;
var logo = Engine . GetGUIObjectByName ( "logo" ) ;
// Load new subject and un-escape newlines.
2014-12-13 02:08:29 +01:00
subject . caption = newSubject ;
2014-01-24 19:20:15 +01:00
// If the subject is only whitespace, hide it and reposition the logo.
if ( subject . caption . match ( /^([\s\t\r\n]*)$/g ) )
{
subjectBox . hidden = true ;
logo . size = "50%-110 50%-50 50%+110 50%+50" ;
}
else
{
subjectBox . hidden = false ;
logo . size = "50%-110 40 50%+110 140" ;
}
2013-11-07 21:07:24 +01:00
}
2014-01-24 19:20:15 +01:00
/ * *
2014-01-26 05:25:35 +01:00
* Do a full update of the player listing , including ratings from C ++ .
2014-01-24 19:20:15 +01:00
*
* @ return Array containing the player , presence , nickname , and rating listings .
* /
2013-11-07 21:07:24 +01:00
function updatePlayerList ( )
{
2014-01-04 11:14:53 +01:00
var playersBox = Engine . GetGUIObjectByName ( "playersBox" ) ;
2015-01-24 15:46:52 +01:00
var playerList = [ ] ;
var presenceList = [ ] ;
var nickList = [ ] ;
var ratingList = [ ] ;
2014-01-26 05:25:35 +01:00
var cleanPlayerList = Engine . GetPlayerList ( ) ;
2014-04-13 20:29:32 +02:00
// Sort the player list, ignoring case.
cleanPlayerList . sort ( function ( a , b )
{
var aName = a . name . toLowerCase ( ) ;
var bName = b . name . toLowerCase ( ) ;
return ( ( aName > bName ) ? 1 : ( bName > aName ) ? - 1 : 0 ) ;
} ) ;
2014-01-26 05:25:35 +01:00
for ( var i = 0 ; i < cleanPlayerList . length ; i ++ )
2013-11-07 21:07:24 +01:00
{
2014-02-19 01:40:35 +01:00
// Identify current user's rating.
if ( cleanPlayerList [ i ] . name == g _Name && cleanPlayerList [ i ] . rating )
g _userRating = cleanPlayerList [ i ] . rating ;
2014-01-26 05:25:35 +01:00
// Add a "-" for unrated players.
if ( ! cleanPlayerList [ i ] . rating )
2014-01-26 06:27:00 +01:00
cleanPlayerList [ i ] . rating = "-" ;
2014-01-26 05:25:35 +01:00
// Colorize.
2014-01-30 16:46:19 +01:00
var [ name , status , rating ] = formatPlayerListEntry ( cleanPlayerList [ i ] . name , cleanPlayerList [ i ] . presence , cleanPlayerList [ i ] . rating , cleanPlayerList [ i ] . role ) ;
2014-01-26 05:25:35 +01:00
// Push to lists.
2013-11-07 21:07:24 +01:00
playerList . push ( name ) ;
presenceList . push ( status ) ;
2014-01-26 05:25:35 +01:00
nickList . push ( cleanPlayerList [ i ] . name ) ;
2014-02-19 01:40:35 +01:00
var ratingSpaces = " " ;
for ( var index = 0 ; index < 4 - Math . ceil ( Math . log ( cleanPlayerList [ i ] . rating ) / Math . LN10 ) ; index ++ )
ratingSpaces += " " ;
ratingList . push ( String ( ratingSpaces + rating ) ) ;
2013-11-07 21:07:24 +01:00
}
playersBox . list _name = playerList ;
playersBox . list _status = presenceList ;
2014-01-24 00:13:13 +01:00
playersBox . list _rating = ratingList ;
2014-01-26 05:25:35 +01:00
playersBox . list = nickList ;
2013-11-07 21:07:24 +01:00
if ( playersBox . selected >= playersBox . list . length )
playersBox . selected = - 1 ;
2014-01-24 00:13:13 +01:00
return [ playerList , presenceList , nickList , ratingList ] ;
2013-11-07 21:07:24 +01:00
}
2014-09-20 17:35:26 +02:00
/ * *
* Display the profile of the selected player .
* Displays N / A for all stats until updateProfile is called when the stats
* are actually received from the bot .
*
* @ param caller From which screen is the user requesting data from ?
* /
function displayProfile ( caller )
{
var playerList , rating ;
if ( caller == "leaderboard" )
playerList = Engine . GetGUIObjectByName ( "leaderboardBox" ) ;
else if ( caller == "lobbylist" )
playerList = Engine . GetGUIObjectByName ( "playersBox" ) ;
else if ( caller == "fetch" )
{
Engine . SendGetProfile ( Engine . GetGUIObjectByName ( "fetchInput" ) . caption ) ;
return ;
}
else
return ;
if ( ! playerList . list [ playerList . selected ] )
{
Engine . GetGUIObjectByName ( "profileArea" ) . hidden = true ;
return ;
}
Engine . GetGUIObjectByName ( "profileArea" ) . hidden = false ;
Engine . SendGetProfile ( playerList . list [ playerList . selected ] ) ;
var user = playerList . list _name [ playerList . selected ] ;
var role = Engine . LobbyGetPlayerRole ( playerList . list [ playerList . selected ] ) ;
var userList = Engine . GetGUIObjectByName ( "playersBox" ) ;
if ( role && caller == "lobbylist" )
{
// Make the role uppercase.
role = role . charAt ( 0 ) . toUpperCase ( ) + role . slice ( 1 ) ;
if ( role == "Moderator" )
role = '[color="0 125 0"]' + translate ( role ) + '[/color]' ;
}
else
role = "" ;
Engine . GetGUIObjectByName ( "usernameText" ) . caption = user ;
Engine . GetGUIObjectByName ( "roleText" ) . caption = translate ( role ) ;
Engine . GetGUIObjectByName ( "rankText" ) . caption = translate ( "N/A" ) ;
Engine . GetGUIObjectByName ( "highestRatingText" ) . caption = translate ( "N/A" ) ;
Engine . GetGUIObjectByName ( "totalGamesText" ) . caption = translate ( "N/A" ) ;
Engine . GetGUIObjectByName ( "winsText" ) . caption = translate ( "N/A" ) ;
Engine . GetGUIObjectByName ( "lossesText" ) . caption = translate ( "N/A" ) ;
Engine . GetGUIObjectByName ( "ratioText" ) . caption = translate ( "N/A" ) ;
}
/ * *
* Update the profile of the selected player with data from the bot .
*
* /
function updateProfile ( )
{
var playerList , user ;
var attributes = Engine . GetProfile ( ) ;
if ( ! Engine . GetGUIObjectByName ( "profileFetch" ) . hidden )
{
user = attributes [ 0 ] . player ;
if ( attributes [ 0 ] . rating == "-2" ) // Profile not found code
{
Engine . GetGUIObjectByName ( "profileWindowArea" ) . hidden = true ;
Engine . GetGUIObjectByName ( "profileErrorText" ) . hidden = false ;
return ;
}
Engine . GetGUIObjectByName ( "profileWindowArea" ) . hidden = false ;
Engine . GetGUIObjectByName ( "profileErrorText" ) . hidden = true ;
if ( attributes [ 0 ] . rating != "" )
user = sprintf ( translate ( "%(nick)s (%(rating)s)" ) , { nick : user , rating : attributes [ 0 ] . rating } ) ;
Engine . GetGUIObjectByName ( "profileUsernameText" ) . caption = user ;
Engine . GetGUIObjectByName ( "profileRankText" ) . caption = attributes [ 0 ] . rank ;
Engine . GetGUIObjectByName ( "profileHighestRatingText" ) . caption = attributes [ 0 ] . highestRating ;
Engine . GetGUIObjectByName ( "profileTotalGamesText" ) . caption = attributes [ 0 ] . totalGamesPlayed ;
Engine . GetGUIObjectByName ( "profileWinsText" ) . caption = attributes [ 0 ] . wins ;
Engine . GetGUIObjectByName ( "profileLossesText" ) . caption = attributes [ 0 ] . losses ;
var winRate = ( attributes [ 0 ] . wins / attributes [ 0 ] . totalGamesPlayed * 100 ) . toFixed ( 2 ) ;
if ( attributes [ 0 ] . totalGamesPlayed != 0 )
Engine . GetGUIObjectByName ( "profileRatioText" ) . caption = sprintf ( translate ( "%(percentage)s%%" ) , { percentage : winRate } ) ;
else
Engine . GetGUIObjectByName ( "profileRatioText" ) . caption = translate ( "-" ) ;
return ;
}
else if ( ! Engine . GetGUIObjectByName ( "leaderboard" ) . hidden )
playerList = Engine . GetGUIObjectByName ( "leaderboardBox" ) ;
else
playerList = Engine . GetGUIObjectByName ( "playersBox" ) ;
if ( attributes [ 0 ] . rating == "-2" )
return ;
// Make sure the stats we have received coincide with the selected player.
if ( attributes [ 0 ] . player != playerList . list [ playerList . selected ] )
return ;
user = playerList . list _name [ playerList . selected ] ;
if ( attributes [ 0 ] . rating != "" )
user = sprintf ( translate ( "%(nick)s (%(rating)s)" ) , { nick : user , rating : attributes [ 0 ] . rating } ) ;
Engine . GetGUIObjectByName ( "usernameText" ) . caption = user ;
Engine . GetGUIObjectByName ( "rankText" ) . caption = attributes [ 0 ] . rank ;
Engine . GetGUIObjectByName ( "highestRatingText" ) . caption = attributes [ 0 ] . highestRating ;
Engine . GetGUIObjectByName ( "totalGamesText" ) . caption = attributes [ 0 ] . totalGamesPlayed ;
Engine . GetGUIObjectByName ( "winsText" ) . caption = attributes [ 0 ] . wins ;
Engine . GetGUIObjectByName ( "lossesText" ) . caption = attributes [ 0 ] . losses ;
var winRate = ( attributes [ 0 ] . wins / attributes [ 0 ] . totalGamesPlayed * 100 ) . toFixed ( 2 ) ;
if ( attributes [ 0 ] . totalGamesPlayed != 0 )
Engine . GetGUIObjectByName ( "ratioText" ) . caption = sprintf ( translate ( "%(percentage)s%%" ) , { percentage : winRate } ) ;
else
Engine . GetGUIObjectByName ( "ratioText" ) . caption = translate ( "-" ) ;
}
2014-01-24 19:20:15 +01:00
/ * *
* Update the leaderboard from data cached in C ++ .
* /
function updateLeaderboard ( )
2013-11-07 21:07:24 +01:00
{
// Get list from C++
var boardList = Engine . GetBoardList ( ) ;
// Get GUI leaderboard object
2014-01-04 11:14:53 +01:00
var leaderboard = Engine . GetGUIObjectByName ( "leaderboardBox" ) ;
2013-11-07 21:07:24 +01:00
// Sort list in acending order by rating
boardList . sort ( function ( a , b ) b . rating - a . rating ) ;
var list = [ ] ;
var list _name = [ ] ;
var list _rank = [ ] ;
var list _rating = [ ] ;
// Push changes
for ( var i = 0 ; i < boardList . length ; i ++ )
{
list _name . push ( boardList [ i ] . name ) ;
list _rating . push ( boardList [ i ] . rating ) ;
list _rank . push ( i + 1 ) ;
list . push ( boardList [ i ] . name ) ;
}
leaderboard . list _name = list _name ;
leaderboard . list _rating = list _rating ;
leaderboard . list _rank = list _rank ;
leaderboard . list = list ;
if ( leaderboard . selected >= leaderboard . list . length )
leaderboard . selected = - 1 ;
}
2014-01-24 19:20:15 +01:00
/ * *
* Update the game listing from data cached in C ++ .
* /
2013-11-07 21:07:24 +01:00
function updateGameList ( )
{
2014-01-04 11:14:53 +01:00
var gamesBox = Engine . GetGUIObjectByName ( "gamesBox" ) ;
2013-11-07 21:07:24 +01:00
var gameList = Engine . GetGameList ( ) ;
// Store the game whole game list data so that we can access it later
// to update the game info panel.
g _GameList = gameList ;
2014-09-15 00:11:03 +02:00
// Sort the list of games to that games 'waiting' are displayed at the top, followed by 'init', followed by 'running'.
var gameStatuses = [ 'waiting' , 'init' , 'running' ] ;
2013-11-07 21:07:24 +01:00
g _GameList . sort ( function ( a , b ) {
2014-09-15 00:11:03 +02:00
if ( gameStatuses . indexOf ( a . state ) < gameStatuses . indexOf ( b . state ) )
return - 1 ;
else if ( gameStatuses . indexOf ( a . state ) > gameStatuses . indexOf ( b . state ) )
return 1 ;
// Alphabetical comparison of names as tiebreaker.
if ( a . name < b . name )
return - 1 ;
else if ( a . name > b . name )
return 1 ;
return 0 ;
2013-11-07 21:07:24 +01:00
} ) ;
var list _name = [ ] ;
var list _ip = [ ] ;
var list _mapName = [ ] ;
var list _mapSize = [ ] ;
var list _mapType = [ ] ;
var list _nPlayers = [ ] ;
var list = [ ] ;
var list _data = [ ] ;
var c = 0 ;
2014-01-06 21:55:22 +01:00
for each ( var g in gameList )
2013-11-07 21:07:24 +01:00
{
2014-01-24 19:20:15 +01:00
if ( ! filterGame ( g ) )
2013-11-07 21:07:24 +01:00
{
2014-09-14 23:23:49 +02:00
// 'waiting' games are highlighted in orange, 'running' in red, and 'init' in green.
2014-11-13 02:26:36 +01:00
let name ;
2014-09-14 22:59:35 +02:00
if ( g . state == 'init' )
name = '[color="0 125 0"]' + g . name + '[/color]' ;
else if ( g . state == 'waiting' )
name = '[color="255 127 0"]' + g . name + '[/color]' ;
else
name = '[color="255 0 0"]' + g . name + '[/color]' ;
2013-11-07 21:07:24 +01:00
list _name . push ( name ) ;
list _ip . push ( g . ip ) ;
2014-09-14 23:10:49 +02:00
list _mapName . push ( translate ( g . niceMapName ) ) ;
2014-12-16 22:05:24 +01:00
list _mapSize . push ( translatedMapSize ( g . mapSize ) ) ;
2014-11-13 02:26:36 +01:00
let idx = g _mapTypes . indexOf ( g . mapType ) ;
list _mapType . push ( idx != - 1 ? g _mapTypesText [ idx ] : "" ) ;
2013-11-07 21:07:24 +01:00
list _nPlayers . push ( g . nbp + "/" + g . tnbp ) ;
list . push ( g . name ) ;
list _data . push ( c ) ;
}
c ++ ;
}
gamesBox . list _name = list _name ;
gamesBox . list _mapName = list _mapName ;
gamesBox . list _mapSize = list _mapSize ;
gamesBox . list _mapType = list _mapType ;
gamesBox . list _nPlayers = list _nPlayers ;
gamesBox . list = list ;
gamesBox . list _data = list _data ;
if ( gamesBox . selected >= gamesBox . list _name . length )
gamesBox . selected = - 1 ;
2014-01-24 19:20:15 +01:00
// Update info box about the game currently selected
updateGameSelection ( ) ;
2013-11-07 21:07:24 +01:00
}
2014-01-24 19:20:15 +01:00
/ * *
* Colorize and format the entries in the player list .
*
* @ param nickname Name of player .
* @ param presence Presence of player .
* @ param rating Rating of player .
2014-01-25 21:51:01 +01:00
* @ return Colorized versions of name , status , and rating .
2014-01-24 19:20:15 +01:00
* /
2014-04-27 21:26:12 +02:00
function formatPlayerListEntry ( nickname , presence , rating )
2013-11-07 21:07:24 +01:00
{
// Set colors based on player status
2014-04-20 22:51:48 +02:00
var color ;
var status ;
2013-11-07 21:07:24 +01:00
switch ( presence )
{
case "playing" :
2014-01-25 21:51:01 +01:00
color = "125 0 0" ;
2014-04-20 22:51:48 +02:00
status = translate ( "Busy" ) ;
2013-11-07 21:07:24 +01:00
break ;
2014-01-11 04:53:41 +01:00
case "gone" :
2013-11-07 21:07:24 +01:00
case "away" :
2014-01-25 21:51:01 +01:00
color = "229 76 13" ;
2014-04-20 22:51:48 +02:00
status = translate ( "Away" ) ;
2013-11-07 21:07:24 +01:00
break ;
case "available" :
2014-01-25 21:51:01 +01:00
color = "0 125 0" ;
2014-04-20 22:51:48 +02:00
status = translate ( "Online" ) ;
2013-11-07 21:07:24 +01:00
break ;
case "offline" :
2014-01-25 21:51:01 +01:00
color = "0 0 0" ;
2014-04-20 22:51:48 +02:00
status = translate ( "Offline" ) ;
2013-11-07 21:07:24 +01:00
break ;
default :
2014-04-20 22:51:48 +02:00
warn ( sprintf ( "Unknown presence '%(presence)s'" , { presence : presence } ) ) ;
2014-01-25 21:51:01 +01:00
color = "178 178 178" ;
2014-04-20 22:51:48 +02:00
status = translateWithContext ( "lobby presence" , "Unknown" ) ;
2013-11-07 21:07:24 +01:00
break ;
}
2014-01-26 06:27:00 +01:00
// Center the unrated symbol.
if ( rating == "-" )
rating = " -" ;
2014-01-25 21:51:01 +01:00
var formattedStatus = '[color="' + color + '"]' + status + "[/color]" ;
2014-02-21 00:26:36 +01:00
var formattedRating = '[color="' + color + '"]' + rating + "[/color]" ;
2014-04-27 21:26:12 +02:00
var role = Engine . LobbyGetPlayerRole ( nickname ) ;
2014-02-21 00:26:36 +01:00
if ( role == "moderator" )
nickname = g _modPrefix + nickname ;
2014-01-25 21:51:01 +01:00
var formattedName = colorPlayerName ( nickname ) ;
2013-11-07 21:07:24 +01:00
// Push this player's name and status onto the list
2014-01-25 21:51:01 +01:00
return [ formattedName , formattedStatus , formattedRating ] ;
2013-11-07 21:07:24 +01:00
}
2014-12-09 21:11:16 +01:00
/ * *
* Given a map size , returns that map size translated into the current
* language .
* /
function translatedMapSize ( mapSize )
{
if ( + mapSize !== + mapSize ) // NaN
return translate ( mapSize ) ;
else
return g _mapSizes . shortNames [ g _mapSizes . tiles . indexOf ( + mapSize ) ] ;
}
2014-01-24 19:20:15 +01:00
/ * *
* Populate the game info area with information on the current game selection .
* /
function updateGameSelection ( )
2013-11-07 21:07:24 +01:00
{
2014-01-24 19:20:15 +01:00
var selected = Engine . GetGUIObjectByName ( "gamesBox" ) . selected ;
// If a game is not selected, hide the game information area.
2013-11-07 21:07:24 +01:00
if ( selected == - 1 )
{
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "gameInfo" ) . hidden = true ;
Engine . GetGUIObjectByName ( "joinGameButton" ) . hidden = true ;
2014-01-25 21:51:01 +01:00
Engine . GetGUIObjectByName ( "gameInfoEmpty" ) . hidden = false ;
2013-11-07 21:07:24 +01:00
return ;
}
var mapData ;
2014-01-04 11:14:53 +01:00
var g = Engine . GetGUIObjectByName ( "gamesBox" ) . list _data [ selected ] ;
2013-11-07 21:07:24 +01:00
// Load map data
2013-11-10 01:45:19 +01:00
if ( g _GameList [ g ] . mapType == "random" && g _GameList [ g ] . mapName == "random" )
2014-04-20 22:51:48 +02:00
mapData = { "settings" : { "Description" : translate ( "A randomly selected map." ) } } ;
2014-01-04 11:14:53 +01:00
else if ( g _GameList [ g ] . mapType == "random" && Engine . FileExists ( g _GameList [ g ] . mapName + ".json" ) )
2014-11-13 02:26:22 +01:00
mapData = Engine . ReadJSONFile ( g _GameList [ g ] . mapName + ".json" ) ;
2014-01-04 11:14:53 +01:00
else if ( Engine . FileExists ( g _GameList [ g ] . mapName + ".xml" ) )
2013-11-07 21:07:24 +01:00
mapData = Engine . LoadMapSettings ( g _GameList [ g ] . mapName + ".xml" ) ;
else
// Warn the player if we can't find the map.
2014-04-20 22:51:48 +02:00
warn ( sprintf ( "Map '%(mapName)s' not found locally." , { mapName : g _GameList [ g ] . mapName } ) ) ;
2013-11-07 21:07:24 +01:00
2014-01-25 21:51:01 +01:00
// Show the game info panel and join button.
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "gameInfo" ) . hidden = false ;
Engine . GetGUIObjectByName ( "joinGameButton" ) . hidden = false ;
2014-01-25 21:51:01 +01:00
Engine . GetGUIObjectByName ( "gameInfoEmpty" ) . hidden = true ;
2013-11-07 21:07:24 +01:00
// Display the map name, number of players, the names of the players, the map size and the map type.
2014-09-14 23:10:49 +02:00
Engine . GetGUIObjectByName ( "sgMapName" ) . caption = translate ( g _GameList [ g ] . niceMapName ) ;
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "sgNbPlayers" ) . caption = g _GameList [ g ] . nbp + "/" + g _GameList [ g ] . tnbp ;
Engine . GetGUIObjectByName ( "sgPlayersNames" ) . caption = g _GameList [ g ] . players ;
2014-12-09 21:11:16 +01:00
Engine . GetGUIObjectByName ( "sgMapSize" ) . caption = translatedMapSize ( g _GameList [ g ] . mapSize ) ;
2014-11-13 02:26:36 +01:00
let idx = g _mapTypes . indexOf ( g _GameList [ g ] . mapType ) ;
Engine . GetGUIObjectByName ( "sgMapType" ) . caption = idx != - 1 ? g _mapTypesText [ idx ] : "" ;
2013-11-07 21:07:24 +01:00
// Display map description if it exists, otherwise display a placeholder.
if ( mapData && mapData . settings . Description )
2014-09-14 23:10:49 +02:00
var mapDescription = translate ( mapData . settings . Description ) ;
2013-11-07 21:07:24 +01:00
else
2014-04-20 22:51:48 +02:00
var mapDescription = translate ( "Sorry, no description available." ) ;
2014-01-25 21:51:01 +01:00
2013-11-07 21:07:24 +01:00
// Display map preview if it exists, otherwise display a placeholder.
if ( mapData && mapData . settings . Preview )
var mapPreview = mapData . settings . Preview ;
else
var mapPreview = "nopreview.png" ;
2014-01-25 21:51:01 +01:00
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "sgMapDescription" ) . caption = mapDescription ;
Engine . GetGUIObjectByName ( "sgMapPreview" ) . sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapPreview ;
2013-11-07 21:07:24 +01:00
}
2014-01-24 19:20:15 +01:00
/ * *
* Start the joining process on the currectly selected game .
* /
2013-11-07 21:07:24 +01:00
function joinSelectedGame ( )
{
2014-01-04 11:14:53 +01:00
var gamesBox = Engine . GetGUIObjectByName ( "gamesBox" ) ;
2013-11-07 21:07:24 +01:00
if ( gamesBox . selected >= 0 )
{
var g = gamesBox . list _data [ gamesBox . selected ] ;
var sname = g _Name ;
var sip = g _GameList [ g ] . ip ;
// TODO: What about valid host names?
// Check if it looks like an ip address
if ( sip . split ( '.' ) . length != 4 )
{
2014-04-20 22:51:48 +02:00
addChatMessage ( { "from" : "system" , "text" : sprintf ( translate ( "This game's address '%(ip)s' does not appear to be valid." ) , { ip : sip } ) } ) ;
2013-11-07 21:07:24 +01:00
return ;
}
// Open Multiplayer connection window with join option.
2014-06-21 02:25:12 +02:00
Engine . PushGuiPage ( "page_gamesetup_mp.xml" , { multiplayerGameType : "join" , name : sname , ip : sip , rating : g _userRating } ) ;
2013-11-07 21:07:24 +01:00
}
}
2014-02-19 01:40:35 +01:00
/ * *
* Start the hosting process .
* /
function hostGame ( )
{
// Open Multiplayer connection window with host option.
Engine . PushGuiPage ( "page_gamesetup_mp.xml" , { multiplayerGameType : "host" , name : g _Name , rating : g _userRating } ) ;
}
2013-11-07 21:07:24 +01:00
////////////////////////////////////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////////////////////////////////////
2014-01-24 00:13:13 +01:00
function stripColorCodes ( input )
{
return input . replace ( /\[(\w+)[^w]*?](.*?)\[\/\1]/g , '$2' ) ;
}
2013-11-07 21:07:24 +01:00
////////////////////////////////////////////////////////////////////////////////////////////////
// GUI event handlers
////////////////////////////////////////////////////////////////////////////////////////////////
function onTick ( )
{
updateTimers ( ) ;
2014-01-25 21:51:01 +01:00
checkSpamMonitor ( ) ;
2013-11-07 21:07:24 +01:00
// Receive messages
while ( true )
{
var message = Engine . LobbyGuiPollMessage ( ) ;
// Clean Message
if ( ! message )
break ;
2014-01-24 19:20:15 +01:00
var text = escapeText ( message . text ) ;
2013-11-07 21:07:24 +01:00
switch ( message . type )
{
case "mucmessage" : // For room messages
2014-04-13 03:48:24 +02:00
var from = escapeText ( message . from ) ;
2014-01-24 19:20:15 +01:00
addChatMessage ( { "from" : from , "text" : text } ) ;
2013-11-07 21:07:24 +01:00
break ;
case "message" : // For private messages
2014-04-13 03:48:24 +02:00
var from = escapeText ( message . from ) ;
2014-01-24 19:20:15 +01:00
addChatMessage ( { "from" : from , "text" : text } ) ;
2013-11-07 21:07:24 +01:00
break ;
case "muc" :
var nick = message . text ;
var presence = Engine . LobbyGetPlayerPresence ( nick ) ;
2014-01-04 11:14:53 +01:00
var playersBox = Engine . GetGUIObjectByName ( "playersBox" ) ;
2013-11-07 21:07:24 +01:00
var playerList = playersBox . list _name ;
var presenceList = playersBox . list _status ;
var nickList = playersBox . list ;
2014-01-24 00:13:13 +01:00
var ratingList = playersBox . list _rating ;
2013-11-07 21:07:24 +01:00
var nickIndex = nickList . indexOf ( nick ) ;
switch ( message . level )
{
case "join" :
if ( nick == g _Name )
{
// We just joined, we need to get the full player list
2014-01-24 00:13:13 +01:00
[ playerList , presenceList , nickList , ratingList ] = updatePlayerList ( ) ;
2013-11-07 21:07:24 +01:00
break ;
}
2014-01-26 06:27:00 +01:00
var [ name , status , rating ] = formatPlayerListEntry ( nick , presence , "-" ) ;
2013-11-07 21:07:24 +01:00
playerList . push ( name ) ;
presenceList . push ( status ) ;
nickList . push ( nick ) ;
2014-01-24 00:13:13 +01:00
ratingList . push ( String ( rating ) ) ;
Engine . SendGetRatingList ( ) ;
2014-04-20 22:51:48 +02:00
addChatMessage ( { "text" : "/special " + sprintf ( translate ( "%(nick)s has joined." ) , { nick : nick } ) , "key" : g _specialKey } ) ;
2013-11-07 21:07:24 +01:00
break ;
case "leave" :
if ( nickIndex == - 1 ) // Left, but not present (TODO: warn about this?)
break ;
playerList . splice ( nickIndex , 1 ) ;
presenceList . splice ( nickIndex , 1 ) ;
nickList . splice ( nickIndex , 1 ) ;
2014-01-24 00:13:13 +01:00
ratingList . splice ( nickIndex , 1 ) ;
2014-04-20 22:51:48 +02:00
addChatMessage ( { "text" : "/special " + sprintf ( translate ( "%(nick)s has left." ) , { nick : nick } ) , "key" : g _specialKey } ) ;
2013-11-07 21:07:24 +01:00
break ;
case "nick" :
if ( nickIndex == - 1 ) // This shouldn't ever happen
break ;
2013-11-10 22:52:38 +01:00
if ( ! isValidNick ( message . data ) )
{
2014-04-20 22:51:48 +02:00
addChatMessage ( { "from" : "system" , "text" : sprintf ( translate ( "Invalid nickname: %(nick)s" ) , { nick : message . data } ) } ) ;
2013-11-10 22:52:38 +01:00
break ;
}
2014-01-24 00:13:13 +01:00
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
2013-11-07 21:07:24 +01:00
playerList [ nickIndex ] = name ;
// presence stays the same
nickList [ nickIndex ] = message . data ;
2014-04-20 22:51:48 +02:00
addChatMessage ( { "text" : "/special " + sprintf ( translate ( "%(oldnick)s is now known as %(newnick)s." ) , { oldnick : nick , newnick : message . data } ) , "key" : g _specialKey } ) ;
2014-01-24 00:13:13 +01:00
Engine . SendGetRatingList ( ) ;
2013-11-07 21:07:24 +01:00
break ;
case "presence" :
if ( nickIndex == - 1 ) // This shouldn't ever happen
break ;
2014-01-24 00:13:13 +01:00
var [ name , status , rating ] = formatPlayerListEntry ( nick , presence , stripColorCodes ( ratingList [ nickIndex ] ) ) ;
2013-11-07 21:07:24 +01:00
presenceList [ nickIndex ] = status ;
playerList [ nickIndex ] = name ;
2014-01-24 00:13:13 +01:00
ratingList [ nickIndex ] = rating ;
2013-11-07 21:07:24 +01:00
break ;
2014-01-24 19:20:15 +01:00
case "subject" :
updateSubject ( message . text ) ;
break ;
2013-11-07 21:07:24 +01:00
default :
2014-04-20 22:51:48 +02:00
warn ( sprintf ( "Unknown message.level '%(msglvl)s'" , { msglvl : message . level } ) ) ;
2013-11-07 21:07:24 +01:00
break ;
}
// Push new data to GUI
playersBox . list _name = playerList ;
playersBox . list _status = presenceList ;
2014-01-24 00:13:13 +01:00
playersBox . list _rating = ratingList ;
playersBox . list = nickList ;
2013-11-07 21:07:24 +01:00
if ( playersBox . selected >= playersBox . list . length )
playersBox . selected = - 1 ;
break ;
case "system" :
switch ( message . level )
{
case "standard" :
2014-01-24 19:20:15 +01:00
addChatMessage ( { "from" : "system" , "text" : text , "color" : "150 0 0" } ) ;
2013-11-07 21:07:24 +01:00
if ( message . text == "disconnected" )
{
// Clear the list of games and the list of players
updateGameList ( ) ;
2014-01-24 19:20:15 +01:00
updateLeaderboard ( ) ;
2013-11-07 21:07:24 +01:00
updatePlayerList ( ) ;
// Disable the 'host' button
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "hostButton" ) . enabled = false ;
2013-11-07 21:07:24 +01:00
}
else if ( message . text == "connected" )
{
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "hostButton" ) . enabled = true ;
2013-11-07 21:07:24 +01:00
}
break ;
case "error" :
2014-01-24 19:20:15 +01:00
addChatMessage ( { "from" : "system" , "text" : text , "color" : "150 0 0" } ) ;
2013-11-07 21:07:24 +01:00
break ;
case "internal" :
switch ( message . text )
{
case "gamelist updated" :
updateGameList ( ) ;
break ;
case "boardlist updated" :
2014-01-24 19:20:15 +01:00
updateLeaderboard ( ) ;
2013-11-07 21:07:24 +01:00
break ;
2014-01-24 00:13:13 +01:00
case "ratinglist updated" :
updatePlayerList ( ) ;
break ;
2014-09-20 17:35:26 +02:00
case "profile updated" :
updateProfile ( ) ;
break ;
2013-11-07 21:07:24 +01:00
}
2014-09-20 17:35:26 +02:00
break ;
2013-11-07 21:07:24 +01:00
}
break ;
default :
2014-04-20 22:51:48 +02:00
error ( sprintf ( "Unrecognised message type %(msgtype)s" , { msgtype : message . type } ) ) ;
2013-11-07 21:07:24 +01:00
}
}
}
/* Messages */
function submitChatInput ( )
{
2014-01-04 11:14:53 +01:00
var input = Engine . GetGUIObjectByName ( "chatInput" ) ;
2013-11-07 21:07:24 +01:00
var text = escapeText ( input . caption ) ;
if ( text . length )
{
2014-01-25 22:58:22 +01:00
if ( ! handleSpecialCommand ( text ) && ! isSpam ( text , g _Name ) )
2013-11-07 21:07:24 +01:00
Engine . LobbySendMessage ( text ) ;
input . caption = "" ;
}
}
2013-11-10 22:52:38 +01:00
function isValidNick ( nick )
{
var prohibitedNicks = [ "system" ] ;
return prohibitedNicks . indexOf ( nick ) == - 1 ;
}
2014-01-24 19:20:15 +01:00
/ * *
* Handle all '/' commands .
*
* @ param text Text to be checked for commands .
* @ return true if more text processing is needed , false otherwise .
* /
2013-11-07 21:07:24 +01:00
function handleSpecialCommand ( text )
{
if ( text [ 0 ] != '/' )
return false ;
var [ cmd , nick ] = ircSplit ( text ) ;
switch ( cmd )
{
case "away" :
Engine . LobbySetPlayerPresence ( "away" ) ;
break ;
case "back" :
Engine . LobbySetPlayerPresence ( "available" ) ;
break ;
case "kick" : // TODO: Split reason from nick and pass it too, for now just support "/kick nick"
// also allow quoting nicks (and/or prevent users from changing it here, but that doesn't help if the spammer uses a different client)
Engine . LobbyKick ( nick , "" ) ;
break ;
case "ban" : // TODO: Split reason from nick and pass it too, for now just support "/ban nick"
Engine . LobbyBan ( nick , "" ) ;
break ;
case "quit" :
lobbyStop ( ) ;
Engine . SwitchGuiPage ( "page_pregame.xml" ) ;
break ;
2013-12-25 20:28:53 +01:00
case "say" :
case "me" :
2013-11-07 21:07:24 +01:00
return false ;
2013-12-25 20:28:53 +01:00
default :
2014-04-20 22:51:48 +02:00
addChatMessage ( { "from" : "system" , "text" : sprintf ( translate ( "We're sorry, the '%(cmd)s' command is not supported." ) , { cmd : cmd } ) } ) ;
2013-11-07 21:07:24 +01:00
}
return true ;
}
2014-01-24 19:20:15 +01:00
/ * *
* Process and , if appropriate , display a formatted message .
*
* @ param msg The message to be processed .
* /
2013-11-07 21:07:24 +01:00
function addChatMessage ( msg )
{
2014-04-13 03:48:24 +02:00
// Some calls of this function will leave some msg parameters empty. Text is required though.
if ( msg . from )
{
// Display the moderator symbol in the chatbox.
var playerRole = Engine . LobbyGetPlayerRole ( msg . from ) ;
if ( playerRole == "moderator" )
msg . from = g _modPrefix + msg . from ;
}
else
msg . from = null ;
if ( ! msg . color )
msg . color = null ;
if ( ! msg . key )
msg . key = null ;
2014-04-13 01:40:36 +02:00
2013-11-07 21:07:24 +01:00
// Highlight local user's nick
if ( msg . text . indexOf ( g _Name ) != - 1 && g _Name != msg . from )
msg . text = msg . text . replace ( new RegExp ( '\\b' + '\\' + g _Name + '\\b' , "g" ) , colorPlayerName ( g _Name ) ) ;
// Run spam test
2014-01-25 22:58:22 +01:00
updateSpamMonitor ( msg . from ) ;
if ( isSpam ( msg . text , msg . from ) )
2013-11-07 21:07:24 +01:00
return ;
// Format Text
2013-11-08 01:57:58 +01:00
var formatted = ircFormat ( msg . text , msg . from , msg . color , msg . key ) ;
2013-11-07 21:07:24 +01:00
// If there is text, add it to the chat box.
if ( formatted )
{
g _ChatMessages . push ( formatted ) ;
2014-01-04 11:14:53 +01:00
Engine . GetGUIObjectByName ( "chatText" ) . caption = g _ChatMessages . join ( "\n" ) ;
2013-11-07 21:07:24 +01:00
}
}
function ircSplit ( string )
{
var idx = string . indexOf ( ' ' ) ;
if ( idx != - 1 )
return [ string . substr ( 1 , idx - 1 ) , string . substr ( idx + 1 ) ] ;
return [ string . substr ( 1 ) , "" ] ;
}
2014-01-24 19:20:15 +01:00
/ * *
* Format text in an IRC - like way .
*
* @ param text Body of the message .
* @ param from Sender of the message .
* @ param color Optional color of sender .
* @ param key Key to verify join / leave messages with . TODO : Remove this , it only provides synthetic security .
* @ return Formatted text .
* /
2013-11-08 01:57:58 +01:00
function ircFormat ( text , from , color , key )
2013-11-07 21:07:24 +01:00
{
2013-11-08 01:57:58 +01:00
// Generate and apply color to uncolored names,
if ( ! color && from )
2013-11-15 19:35:19 +01:00
var coloredFrom = colorPlayerName ( from ) ;
2013-12-25 20:28:53 +01:00
else if ( color && from )
var coloredFrom = '[color="' + color + '"]' + from + "[/color]" ;
2013-11-08 01:57:58 +01:00
2013-12-25 20:28:53 +01:00
// Handle commands allowed past handleSpecialCommand.
2013-11-07 21:07:24 +01:00
if ( text [ 0 ] == '/' )
{
var [ command , message ] = ircSplit ( text ) ;
switch ( command )
{
case "me" :
2014-04-20 22:51:48 +02:00
// Translation: IRC message prefix when the sender uses the /me command.
2014-04-24 15:33:15 +02:00
var senderString = '[font="sans-bold-13"]' + sprintf ( translate ( "* %(sender)s" ) , { sender : coloredFrom } ) + '[/font]' ;
2014-04-20 22:51:48 +02:00
// Translation: IRC message issued using the ‘/me’ command.
var formattedMessage = sprintf ( translate ( "%(sender)s %(action)s" ) , { sender : senderString , action : message } ) ;
break ;
2013-11-07 21:07:24 +01:00
case "say" :
2014-04-20 22:51:48 +02:00
// Translation: IRC message prefix.
2014-04-24 15:33:15 +02:00
var senderString = '[font="sans-bold-13"]' + sprintf ( translate ( "<%(sender)s>" ) , { sender : coloredFrom } ) + '[/font]' ;
2014-04-20 22:51:48 +02:00
// Translation: IRC message.
var formattedMessage = sprintf ( translate ( "%(sender)s %(message)s" ) , { sender : senderString , message : message } ) ;
break
2013-11-07 21:07:24 +01:00
case "special" :
if ( key === g _specialKey )
2014-04-20 22:51:48 +02:00
// Translation: IRC system message.
2014-04-24 15:33:15 +02:00
var formattedMessage = '[font="sans-bold-13"]' + sprintf ( translate ( "== %(message)s" ) , { message : message } ) + '[/font]' ;
2014-04-20 22:51:48 +02:00
else
{
// Translation: IRC message prefix.
2014-04-24 15:33:15 +02:00
var senderString = '[font="sans-bold-13"]' + sprintf ( translate ( "<%(sender)s>" ) , { sender : coloredFrom } ) + '[/font]' ;
2014-04-20 22:51:48 +02:00
// Translation: IRC message.
var formattedMessage = sprintf ( translate ( "%(sender)s %(message)s" ) , { sender : senderString , message : message } ) ;
}
2013-11-07 21:07:24 +01:00
break ;
default :
2013-12-25 20:28:53 +01:00
// This should never happen.
2014-04-20 22:51:48 +02:00
var formattedMessage = "" ;
2013-11-07 21:07:24 +01:00
}
}
2014-04-20 22:51:48 +02:00
else
{
// Translation: IRC message prefix.
2014-04-24 15:33:15 +02:00
var senderString = '[font="sans-bold-13"]' + sprintf ( translate ( "<%(sender)s>" ) , { sender : coloredFrom } ) + '[/font]' ;
2014-04-20 22:51:48 +02:00
// Translation: IRC message.
var formattedMessage = sprintf ( translate ( "%(sender)s %(message)s" ) , { sender : senderString , message : text } ) ;
}
// Build time header if enabled
if ( g _timestamp )
{
// Time for optional time header
var time = new Date ( Date . now ( ) ) ;
// Translation: Time as shown in the multiplayer lobby (when you enable it in the options page).
// For a list of symbols that you can use, see:
// https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table
var timeString = Engine . FormatMillisecondsIntoDateString ( time . getTime ( ) , translate ( "HH:mm" ) ) ;
// Translation: Time prefix as shown in the multiplayer lobby (when you enable it in the options page).
2014-11-20 23:19:35 +01:00
var timePrefixString = '[font="sans-bold-13"]' + sprintf ( translate ( "\\[%(time)s]" ) , { time : timeString } ) + '[/font]' ;
2014-04-20 22:51:48 +02:00
// Translation: IRC message format when there is a time prefix.
return sprintf ( translate ( "%(time)s %(message)s" ) , { time : timePrefixString , message : formattedMessage } ) ;
}
else
return formattedMessage ;
2013-11-07 21:07:24 +01:00
}
2014-01-24 19:20:15 +01:00
/ * *
2014-01-25 22:58:22 +01:00
* Update the spam monitor .
2014-01-24 19:20:15 +01:00
*
2014-01-25 22:58:22 +01:00
* @ param from User to update .
2014-01-24 19:20:15 +01:00
* /
2014-01-25 22:58:22 +01:00
function updateSpamMonitor ( from )
2013-11-07 21:07:24 +01:00
{
2014-01-25 21:51:01 +01:00
// Integer time in seconds.
var time = Math . floor ( Date . now ( ) / 1000 ) ;
2014-01-25 22:58:22 +01:00
// Update or initialize the user in the spam monitor.
2013-11-07 21:07:24 +01:00
if ( g _spamMonitor [ from ] )
2014-01-25 21:51:01 +01:00
g _spamMonitor [ from ] [ 0 ] ++ ;
2013-11-07 21:07:24 +01:00
else
2014-01-25 21:51:01 +01:00
g _spamMonitor [ from ] = [ 1 , time , 0 ] ;
2014-01-25 22:58:22 +01:00
}
/ * *
* Check if a message is spam .
*
* @ param text Body of message .
* @ param from Sender of message .
* @ return True if message should be blocked .
* /
function isSpam ( text , from )
{
// Integer time in seconds.
var time = Math . floor ( Date . now ( ) / 1000 ) ;
// Initialize if not already in the database.
if ( ! g _spamMonitor [ from ] )
g _spamMonitor [ from ] = [ 1 , time , 0 ] ;
2014-01-25 21:51:01 +01:00
// Block blank lines.
if ( text == " " )
return true ;
// Block users who are still within their spam block period.
else if ( g _spamMonitor [ from ] [ 2 ] + SPAM _BLOCK _LENGTH >= time )
return true ;
// 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.
2014-01-25 22:58:22 +01:00
else if ( g _spamMonitor [ from ] [ 0 ] == 6 )
2013-11-07 21:07:24 +01:00
{
2014-01-25 21:51:01 +01:00
g _spamMonitor [ from ] [ 2 ] = time ;
2013-11-07 21:07:24 +01:00
if ( from == g _Name )
2014-04-20 22:51:48 +02:00
addChatMessage ( { "from" : "system" , "text" : translate ( "Please do not spam. You have been blocked for thirty seconds." ) } ) ;
2013-11-07 21:07:24 +01:00
return true ;
}
// Return false if everything is clear.
2014-01-25 21:51:01 +01:00
else
return false ;
2013-11-07 21:07:24 +01:00
}
2014-01-24 19:20:15 +01:00
/ * *
* Reset timer used to measure message send speed .
* /
2014-01-25 21:51:01 +01:00
function checkSpamMonitor ( )
2013-11-07 21:07:24 +01:00
{
2014-01-25 21:51:01 +01:00
// Integer time in seconds.
var time = Math . floor ( Date . now ( ) / 1000 ) ;
// Clear message count every 5 seconds.
for each ( var stats in g _spamMonitor )
{
if ( stats [ 1 ] + 5 <= time )
{
stats [ 1 ] = time ;
stats [ 0 ] = 0 ;
}
}
2013-11-07 21:07:24 +01:00
}
/* Utilities */
// Generate a (mostly) unique color for this player based on their name.
// See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
function getPlayerColor ( playername )
{
// Generate a probably-unique hash for the player name and use that to create a color.
var hash = 0 ;
for ( var i = 0 ; i < playername . length ; i ++ )
hash = playername . charCodeAt ( i ) + ( ( hash << 5 ) - hash ) ;
// First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
// The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
// us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
// we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
var [ h , s , l ] = rgbToHsl ( hash >> 24 & 0xFF , hash >> 16 & 0xFF , hash >> 8 & 0xFF ) ;
return hslToRgb ( h , s , Math . max ( 0.4 , l ) ) . join ( " " ) ;
}
function repeatString ( times , string ) {
return Array ( times + 1 ) . join ( string ) ;
}
// Some names are special and should always appear in certain colors.
2014-02-21 00:26:36 +01:00
var fixedColors = { "system" : repeatString ( 7 , "255.0.0." ) , "@WFGbot" : repeatString ( 7 , "255.24.24." ) ,
2013-11-07 21:07:24 +01:00
"pyrogenesis" : repeatString ( 2 , "97.0.0." ) + repeatString ( 2 , "124.0.0." ) + "138.0.0." +
repeatString ( 2 , "174.0.0." ) + repeatString ( 2 , "229.40.0." ) + repeatString ( 2 , "243.125.15." ) } ;
function colorPlayerName ( playername )
{
var color = fixedColors [ playername ] ;
if ( color ) {
color = color . split ( "." ) ;
return ( '[color="' + playername . split ( "" ) . map ( function ( c , i ) color . slice ( i * 3 , i * 3 + 3 ) . join ( " " ) + '"]' + c + '[/color][color="' )
. join ( "" ) + '"]' ) . slice ( 0 , - 10 ) ;
}
2014-02-21 00:26:36 +01:00
return '[color="' + getPlayerColor ( playername . replace ( g _modPrefix , "" ) ) + '"]' + playername + '[/color]' ;
2013-11-07 21:07:24 +01:00
}
// Ensure `value` is between 0 and 1.
function clampColorValue ( value )
{
return Math . abs ( 1 - Math . abs ( value - 1 ) ) ;
}
// See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
function rgbToHsl ( r , g , b )
{
r /= 255 ;
g /= 255 ;
b /= 255 ;
var max = Math . max ( r , g , b ) , min = Math . min ( r , g , b ) ;
var h , s , l = ( max + min ) / 2 ;
if ( max == min )
h = s = 0 ; // achromatic
else
{
var d = max - min ;
s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ) ;
switch ( max )
{
case r : h = ( g - b ) / d + ( g < b ? 6 : 0 ) ; break ;
case g : h = ( b - r ) / d + 2 ; break ;
case b : h = ( r - g ) / d + 4 ; break ;
}
h /= 6 ;
}
return [ h , s , l ] ;
}
function hslToRgb ( h , s , l )
{
2013-11-10 05:09:03 +01:00
function hue2rgb ( p , q , t )
{
if ( t < 0 ) t += 1 ;
if ( t > 1 ) t -= 1 ;
if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t ;
if ( t < 1 / 2 ) return q ;
if ( t < 2 / 3 ) return p + ( q - p ) * ( 2 / 3 - t ) * 6 ;
return p ;
}
2013-11-07 21:07:24 +01:00
[ h , s , l ] = [ h , s , l ] . map ( clampColorValue ) ;
var r , g , b ;
if ( s == 0 )
r = g = b = l ; // achromatic
else {
var q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s ;
var p = 2 * l - q ;
r = hue2rgb ( p , q , h + 1 / 3 ) ;
g = hue2rgb ( p , q , h ) ;
b = hue2rgb ( p , q , h - 1 / 3 ) ;
}
return [ r , g , b ] . map ( function ( n ) Math . round ( n * 255 ) ) ;
}
( function ( ) {
function hexToRgb ( hex ) {
return parseInt ( hex . slice ( 0 , 2 ) , 16 ) + "." + parseInt ( hex . slice ( 2 , 4 ) , 16 ) + "." + parseInt ( hex . slice ( 4 , 6 ) , 16 ) + "." ;
}
function r ( times , hex ) {
return repeatString ( times , hexToRgb ( hex ) ) ;
}
2013-11-10 22:52:38 +01:00
fixedColors [ "Twilight_Sparkle" ] = r ( 2 , "d19fe3" ) + r ( 2 , "b689c8" ) + r ( 2 , "a76bc2" ) +
2013-11-07 21:07:24 +01:00
r ( 4 , "263773" ) + r ( 2 , "131f46" ) + r ( 2 , "662d8a" ) + r ( 2 , "ed438a" ) ;
fixedColors [ "Applejack" ] = r ( 3 , "ffc261" ) + r ( 3 , "efb05d" ) + r ( 3 , "f26f31" ) ;
fixedColors [ "Rarity" ] = r ( 1 , "ebeff1" ) + r ( 1 , "dee3e4" ) + r ( 1 , "bec2c3" ) +
r ( 1 , "83509f" ) + r ( 1 , "4b2568" ) + r ( 1 , "4917d6" ) ;
2013-11-10 22:52:38 +01:00
fixedColors [ "Rainbow_Dash" ] = r ( 2 , "ee4144" ) + r ( 1 , "f37033" ) + r ( 1 , "fdf6af" ) +
2013-11-07 21:07:24 +01:00
r ( 1 , "62bc4d" ) + r ( 1 , "1e98d3" ) + r ( 2 , "672f89" ) + r ( 1 , "9edbf9" ) +
r ( 1 , "88c4eb" ) + r ( 1 , "77b0e0" ) + r ( 1 , "1e98d3" ) ;
2013-11-10 22:52:38 +01:00
fixedColors [ "Pinkie_Pie" ] = r ( 2 , "f3b6cf" ) + r ( 2 , "ec9dc4" ) + r ( 4 , "eb81b4" ) +
2013-11-07 21:07:24 +01:00
r ( 1 , "ed458b" ) + r ( 1 , "be1d77" ) ;
fixedColors [ "Fluttershy" ] = r ( 2 , "fdf6af" ) + r ( 2 , "fee78f" ) + r ( 2 , "ead463" ) +
r ( 2 , "f3b6cf" ) + r ( 2 , "eb81b4" ) ;
2013-11-10 22:52:38 +01:00
fixedColors [ "Sweetie_Belle" ] = r ( 2 , "efedee" ) + r ( 3 , "e2dee3" ) + r ( 3 , "cfc8d1" ) +
2013-11-07 21:07:24 +01:00
r ( 2 , "b28dc0" ) + r ( 2 , "f6b8d2" ) + r ( 1 , "795b8a" ) ;
2013-11-10 22:52:38 +01:00
fixedColors [ "Apple_Bloom" ] = r ( 2 , "f4f49b" ) + r ( 2 , "e7e793" ) + r ( 2 , "dac582" ) +
2013-11-07 21:07:24 +01:00
r ( 2 , "f46091" ) + r ( 2 , "f8415f" ) + r ( 1 , "c52451" ) ;
fixedColors [ "Scootaloo" ] = r ( 2 , "fbba64" ) + r ( 2 , "f2ab56" ) + r ( 2 , "f37003" ) +
r ( 2 , "bf5d95" ) + r ( 1 , "bf1f79" ) ;
fixedColors [ "Luna" ] = r ( 1 , "7ca7fa" ) + r ( 1 , "5d6fc1" ) + r ( 1 , "656cb9" ) + r ( 1 , "393993" ) ;
fixedColors [ "Celestia" ] = r ( 1 , "fdfafc" ) + r ( 1 , "f7eaf2" ) + r ( 1 , "d99ec5" ) +
r ( 1 , "00aec5" ) + r ( 1 , "f7c6dc" ) + r ( 1 , "98d9ef" ) + r ( 1 , "ced7ed" ) + r ( 1 , "fed17b" ) ;
} ) ( ) ;