pokemon-showdown-client/js/sim.js
Guangcong Luo 7e7afd3b96 Change up the link separators
The links in the top right are now separated by whitespace.

The previous brackets were visually pretty noisy, and we no longer
have a need to draw attention to them.
2013-04-01 00:23:50 -07:00

3615 lines
134 KiB
JavaScript

// some setting-like stuff
Config.server = Config.server || 'sim.smogon.com';
Config.serverport = Config.serverport || 8000;
Config.serverprotocol = Config.serverprotocol || 'ws';
var socket;
var locPrefix = '/';
if (Config.urlPrefix) locPrefix += Config.urlPrefix;
var actionphp = (function() {
var ret = '/~~' + Config.serverid + '/action.php';
if (Config.testclient) {
ret = 'http://play.pokemonshowdown.com' + ret;
}
return ret;
})();
var _gaq = _gaq || [];
// initialize sockets
var socket = null;
var me = {
name: '',
named: false,
registered: false,
userid: '',
token: '',
challengekeyid: -1,
challenge: '',
renameQueued: false,
users: {},
rooms: {},
ignore: {},
mute: (function(c) {
if (c !== null) {
Tools.prefs.set('mute', !!c, true);
$.cookie('showdown_mute', null);
return c;
}
return Tools.prefs.get('mute');
})($.cookie('showdown_mute')),
lastChallengeNotification: '',
pm: {},
curPopup: '',
popups: []
};
var rooms = {};
var curRoom = null;
var curTitle = 'Showdown!';
var battles = {};
var formats = [''];
var teams = null;
var isAndroid = navigator.userAgent.toLowerCase().indexOf("android") > -1 && navigator.userAgent.toLowerCase().indexOf("firefox") <= -1;
//
function selectTab(tab, e) {
if (e && e.preventDefault) e.preventDefault();
if (!tab) tab = 'lobby';
if (!rooms[tab]) {
joinTab(tab);
return false;
}
if (curRoom && tab !== curRoom.id && curRoom.battle) {
curRoom.battle.pause();
}
curRoom = rooms[tab];
$('#main').children().hide();
$('#leftbar a').removeClass('cur');
$('#tab-' + tab).show();
if (!$('#tabtab-' + tab).length) {
updateRoomList();
}
$('#tabtab-' + tab).addClass('cur');
if (curRoom && curRoom.battle) {
curRoom.battle.setMute(me.mute);
curRoom.battle.play();
}
curRoom.focus();
$(window).scrollTop(51);
if (tab === 'lobby') $('#backbutton').addClass('lobby');
else $('#backbutton').removeClass('lobby');
changeState(tab);
updateLobbyChat(tab);
return false;
}
function joinTab(tab) {
if (!tab || tab === 'lobby') return;
if (rooms.lobby) rooms.lobby.send('/join '+tab);
}
function leaveTab(tab, confirm) {
if (rooms[tab]) {
if (rooms[tab].me.side && rooms[tab].battle && rooms[tab].battle.rated && !rooms[tab].battleEnded && !confirm) {
overlay('forfeit', tab);
return;
}
rooms[tab].send('/leave');
rooms[tab].dealloc();
delete rooms[tab];
}
$('#tab-' + tab).remove();
if (curRoom.id === tab) {
curRoom = null;
selectTab('lobby');
} else {
updateRoomList();
}
}
function addTab(tab, type) {
if (rooms[tab] && rooms[tab].elem) {
if (tab !== 'lobby') selectTab(tab);
return;
}
var elem;
switch (type) {
case 'lobby':
$('#main').append('<div id="tab-' + tab + '" class="battle-tab"></div>');
elem = $('#main').children().last();
rooms[tab] = new Lobby(tab, elem);
break;
case 'teambuilder':
$('#main').append('<div id="tab-' + tab + '" class="battle-tab"></div>');
elem = $('#main').children().last();
rooms[tab] = new Teambuilder(tab, elem);
break;
case 'ladder':
$('#main').append('<div id="tab-' + tab + '" class="battle-tab"></div>');
elem = $('#main').children().last();
rooms[tab] = new Ladder(tab, elem);
break;
case 'battle':
$('#main').append('<div id="tab-' + tab + '" class="battle-tab"></div>');
elem = $('#main').children().last();
rooms[tab] = new BattleRoom(tab, elem);
break;
default:
$('#main').append('<div id="tab-' + tab + '" class="battle-tab">error</div>');
var room = {
id: tab,
type: 'error'
};
room.elem = $('#main').children().last();
rooms[tab] = room;
break;
}
if (tab === loc || (tab !== 'lobby' && tab !== 'teambuilder' && tab !== 'ladder')) {
selectTab(tab);
} else {
rooms[tab].elem.hide();
}
}
function emit(socket, type, data) {
if (Config.serverprotocol === 'io') {
socket.emit(type, data);
} else {
if (typeof data === 'object') data.type = type;
else data = {type: type, message: data};
if (data.type === 'chat') {
// if (window.console && console.log) console.log('>> '+data.room+'|'+data.message);
socket.send(''+data.room+'|'+data.message);
} else {
socket.send($.toJSON(data));
}
}
}
function BattleRoom(id, elem) {
var selfR = this;
this.id = id;
this.elem = elem;
this.meIdent = {
name: me.name,
named: 'init'
};
this.notifying = false;
me.rooms[id] = {};
selfR.me = me.rooms[id];
elem.html('<div class="battlewrapper"><div class="battle">Battle is here</div><div class="foehint"></div><div class="battle-log"></div><div class="battle-log-add">Connecting...</div><div class="replay-controls"></div></div>');
this.battleElem = elem.find('.battle');
this.controlsElem = elem.find('.replay-controls');
this.chatFrameElem = elem.find('.battle-log');
this.chatElem = null;
this.chatAddElem = elem.find('.battle-log-add');
this.chatboxElem = null;
this.joinElem = null;
this.foeHintElem = elem.find('.foehint');
this.battleEnded = false;
this.dealloc = function () {
if (selfR.battle) selfR.battle.dealloc();
};
this.focus = function () {
selfR.updateMe();
if (selfR.chatElem) selfR.chatFrameElem.scrollTop(selfR.chatElem.height());
if (selfR.chatboxElem) selfR.chatboxElem.focus();
};
this.updateJoinButton = function () {
if (selfR.battle.done) selfR.battleEnded = true;
if (selfR.battleEnded) {
selfR.controlsElem.html('<div class="controls"><em><button onclick="return rooms[\'' + selfR.id + '\'].formRestart()">Instant Replay</button><!--button onclick="return rooms[\'' + selfR.id + '\'].formSaveReplay()">Share replay</button--></em></div>');
if (selfR.me.side) {
selfR.controlsElem.html('<div class="controls"><em><button onclick="return rooms[\'' + selfR.id + '\'].formRestart()">Instant Replay</button> <button onclick="return rooms[\'' + selfR.id + '\'].formSaveReplay()">Share replay</button> <button onclick="return rooms[\'' + selfR.id + '\'].formLeaveBattle()">Leave this battle</button></em></div>');
}
if (selfR.joinElem) {
selfR.joinElem.remove();
}
//selfR.battleElem.append('<div class="playbutton"><button onclick="return rooms[\'' + selfR.id + '\'].formRestart()">Start new game<small style="font-size:12pt"><br />(Unranked)</small></button></div>');
//selfR.joinElem = selfR.battleElem.children().last();
} else if (selfR.battle.mySide.initialized && selfR.battle.yourSide.initialized) {
if (selfR.joinElem) {
selfR.joinElem.remove();
}
selfR.joinElem = null;
} else if (selfR.me.side) {
if (selfR.joinElem) {
selfR.joinElem.remove();
}
selfR.joinElem = null;
if (selfR.battle.kickingInactive) {
selfR.controlsElem.html('<div class="controls"><button onclick="return rooms[\'' + selfR.id + '\'].formLeaveBattle()">Leave this battle</button> <p><button onclick="rooms[\'' + selfR.id + '\'].formStopBattleTimer();return false"><small>Stop timer</small></button> <small>&larr; Your opponent has disconnected. Click this to delay your victory.</small></p></div>');
} else {
selfR.controlsElem.html('<div class="controls"><button onclick="return rooms[\'' + selfR.id + '\'].formLeaveBattle()">Leave this battle</button> <p><button onclick="rooms[\'' + selfR.id + '\'].formKickInactive();return false"><small>Claim victory</small></button> <small>&larr; Your opponent has disconnected. Click this if they don\'t reconnect.</small></p></div>');
}
} else {
if (selfR.joinElem) {
selfR.joinElem.remove();
}
selfR.battleElem.append('<div class="playbutton"><button onclick="return rooms[\'' + selfR.id + '\'].formJoinBattle()">Join Battle</button></div>');
selfR.joinElem = selfR.battleElem.children().last();
}
};
this.init = function (data) {
this.version = (data.version !== undefined) ? data.version : 0;
if (selfR.battle.activityQueue) {
// re-initialize
selfR.battleEnded = false;
selfR.battle = new Battle(selfR.battleElem, selfR.chatFrameElem);
if (widthClass !== 'tiny-layout') {
selfR.battle.messageSpeed = 80;
}
selfR.battle.setMute(me.mute);
selfR.battle.customCallback = selfR.callback;
selfR.battle.startCallback = selfR.updateJoinButton;
selfR.battle.stagnateCallback = selfR.updateJoinButton;
selfR.battle.endCallback = selfR.updateJoinButton;
selfR.chatFrameElem.find('.inner').html('');
selfR.controlsElem.html('');
}
selfR.battle.play();
if (data.battlelog) {
for (var i = 0; i < data.battlelog.length; i++) {
selfR.battle.add(data.battlelog[i]);
}
selfR.battle.fastForwardTo(-1);
}
selfR.updateMe();
if (selfR.chatElem) {
selfR.chatFrameElem.scrollTop(selfR.chatElem.height());
}
};
this.rawMessage = function(message) {
this.message({rawMessage: message});
};
this.message = function (message, andLobby) {
if (message.pm) {
var pmuserid = (toUserid(message.name) === me.userid ? toUserid(message.pm) : toUserid(message.name))
if (me.ignore[toUserid(message.name)] && message.name.substr(0, 1) === ' ') return;
selfR.add('|pm|' + message.name.substr(1) + '|' + pmuserid + '|' + message.pm + '|' + message.message);
} else if (message.rawMessage) {
// This is sanitised in battle.js.
selfR.add('|chatmsg-raw|' + message.rawMessage);
} else if (message.evalRulesRedirect || message.evalRawMessage) {
// TODO: This will be removed in due course.
window.location.href = 'http://pokemonshowdown.com/rules';
} else if (message.name) {
selfR.add('|chat|' + message.name.substr(1) + '|' + message.message);
} else if (message.message) {
selfR.add('|chatmsg|' + message.message);
} else {
selfR.add('|chatmsg|' + message);
}
if (andLobby && rooms.lobby) {
rooms.lobby.message(message);
}
};
this.send = function (message) {
emit(socket, 'chat', {room:this.id,message:message});
};
// Same as send, but appends the rqid to the message so that the server
// can verify that the decision is sent in response to the correct request.
this.sendDecision = function (message) {
this.send(message + '|' + this.me.request.rqid);
};
this.add = function (log) {
if (typeof log === 'string') log = log.split('\n');
selfR.update({updates:log});
};
this.update = function (update) {
if (update.updates) {
var updated = false;
for (var i = 0; i < update.updates.length; i++) {
if (!updated && (update.updates[i] === '')) {
selfR.me.callbackWaiting = false;
updated = true;
selfR.controlsElem.html('');
}
if (update.updates[i] === 'RESET') {
selfR.foeHintElem.html('');
var blog = selfR.chatFrameElem.find('.inner').html();
delete selfR.me.side;
selfR.battleEnded = false;
selfR.battle = new Battle(selfR.battleElem, selfR.chatFrameElem);
if (widthClass !== 'tiny-layout') {
selfR.battle.messageSpeed = 80;
}
selfR.battle.setMute(me.mute);
selfR.battle.customCallback = selfR.callback;
selfR.battle.startCallback = selfR.updateJoinButton;
selfR.battle.stagnateCallback = selfR.updateJoinButton;
selfR.battle.endCallback = selfR.updateJoinButton;
selfR.chatFrameElem.find('.inner').html(blog + '<h2>NEW GAME</h2>');
selfR.chatFrameElem.scrollTop(selfR.chatFrameElem.find('.inner').height());
selfR.controlsElem.html('');
selfR.battle.play();
selfR.updateJoinButton();
break;
}
if (update.updates[i].substr(0, 6) === '|chat|' || update.updates[i].substr(0, 9) === '|chatmsg|' || update.updates[i].substr(0, 10) === '|inactive|') {
selfR.battle.instantAdd(update.updates[i]);
} else {
if (update.updates[i].substr(0,10) === '|callback|') selfR.controlsElem.html('');
if (update.updates[i].substr(0,12) === '| callback | ') selfR.controlsElem.html('');
selfR.battle.add(update.updates[i]);
}
}
}
if (update.request) {
selfR.me.request = update.request; // currently unused
selfR.me.request.requestType = 'move';
var notifyObject = null;
if (selfR.me.request.forceSwitch) {
selfR.me.request.requestType = 'switch';
notifyObject = {
type: 'yourSwitch',
room: selfR.id
};
} else if (selfR.me.request.teamPreview) {
selfR.me.request.requestType = 'team';
notifyObject = {
type: 'yourSwitch',
room: selfR.id
};
} else if (selfR.me.request.wait) {
selfR.me.request.requestType = 'wait';
} else {
notifyObject = {
type: 'yourMove',
room: selfR.id
};
}
if (notifyObject) {
var doNotify = function() {
notify(notifyObject);
selfR.notifying = true;
updateRoomList();
};
if (selfR.battle.yourSide.initialized) {
// The opponent's name is already known.
notifyObject.user = selfR.battle.yourSide.name;
doNotify();
} else {
// The opponent's name isn't known yet, so wait until it is
// known before sending the notification, so that it can include
// the opponent's name.
var callback = selfR.battle.stagnateCallback;
selfR.battle.stagnateCallback = function(battle) {
notifyObject.user = battle.yourSide.name;
doNotify();
battle.stagnateCallback = callback;
if (callback) callback(battle);
};
}
}
//if (selfR.me.callbackWaiting) selfR.callback();
}
if (typeof update.active !== 'undefined') {
if (!update.active && selfR.me.side) {
selfR.controlsElem.html('<div class="controls"><button onclick="return rooms[\'' + selfR.id + '\'].formLeaveBattle()">Leave this battle</button></div>');
}
}
if (update.side) {
if (update.side === 'none') {
$('#controls').html('');
delete selfR.me.side;
} else {
selfR.me.side = update.side;
}
}
if (update.sideData) {
selfR.updateSide(update.sideData, update.midBattle);
}
selfR.updateMe();
};
this.updateSide = function(sideData, midBattle) {
var sidesSwitched = false;
selfR.me.sideData = sideData; // just for easy debugging
if (selfR.battle.sidesSwitched !== !!(selfR.me.side === 'p2')) {
sidesSwitched = true;
selfR.battle.reset();
selfR.battle.switchSides();
}
for (var i = 0; i < sideData.pokemon.length; i++) {
var pokemonData = sideData.pokemon[i];
var pokemon;
if (i == 0) {
pokemon = selfR.battle.getPokemon(''+pokemonData.ident, pokemonData.details);
pokemon.slot = 0;
pokemon.side.pokemon = [pokemon];
// if (pokemon.side.active[0] && pokemon.side.active[0].ident == pokemon.ident) pokemon.side.active[0] = pokemon;
} else if (i < selfR.battle.mySide.active.length) {
pokemon = selfR.battle.getPokemon('new: '+pokemonData.ident, pokemonData.details);
pokemon.slot = i;
// if (pokemon.side.active[i] && pokemon.side.active[i].ident == pokemon.ident) pokemon.side.active[i] = pokemon;
if (pokemon.side.active[i] && pokemon.side.active[i].ident == pokemon.ident) {
pokemon.side.active[i].item = pokemon.item;
pokemon.side.active[i].ability = pokemon.ability;
pokemon.side.active[i].baseAbility = pokemon.baseAbility;
}
} else {
pokemon = selfR.battle.getPokemon('new: '+pokemonData.ident, pokemonData.details);
}
pokemon.healthParse(pokemonData.condition);
if (pokemonData.baseAbility) {
pokemon.baseAbility = pokemonData.baseAbility;
if (!pokemon.ability || pokemon.ability.substr(0,2) === '??') pokemon.ability = pokemon.baseAbility;
}
pokemon.item = pokemonData.item;
pokemon.moves = pokemonData.moves;
}
selfR.battle.mySide.updateSidebar();
if (sidesSwitched) {
if (midBattle) {
selfR.battle.fastForwardTo(-1);
} else {
selfR.battle.play();
}
}
};
this.updateMe = function () {
if (selfR.meIdent.name !== me.name || selfR.meIdent.named !== me.named) {
if (me.named) {
selfR.chatAddElem.html('<form onsubmit="return false" class="chatbox"><label style="' + hashColor(me.userid) + '">' + sanitize(me.name) + ':</label> <textarea class="textbox" type="text" size="70" autocomplete="off" onkeypress="return rooms[\'' + selfR.id + '\'].formKeyPress(event)"></textarea></form>');
selfR.chatboxElem = selfR.chatAddElem.find('textarea');
// The keypress event does not capture tab, so use keydown.
selfR.chatboxElem.keydown(rooms['lobby'].formKeyDown);
selfR.chatboxElem.autoResize({
animateDuration: 100,
extraSpace: 0
});
selfR.chatboxElem.focus();
} else {
selfR.chatAddElem.html('<form><button onclick="return rooms[\'' + selfR.id + '\'].formRename()">Join chat</button></form>');
}
selfR.meIdent.name = me.name;
selfR.meIdent.named = me.named;
}
var inner = selfR.chatFrameElem.find('.inner');
if (inner.length) selfR.chatElem = inner;
else selfR.chatElem = null;
selfR.updateJoinButton();
};
this.callback = function (battle, type, moveTarget) {
if (!battle) battle = selfR.battle;
selfR.notifying = false;
if (type === 'restart') {
selfR.me.callbackWaiting = false;
selfR.battleEnded = true;
updateRoomList();
return;
}
var myActive = selfR.battle.mySide.active;
var yourActive = selfR.battle.yourSide.active;
var text = '';
if (yourActive[1]) {
text += '<div style="position:absolute;top:85px;left:320px;width:90px;height:100px;"' + tooltipAttrs(yourActive[1].getIdent(), 'pokemon', true, 'foe') + '></div>';
}
if (yourActive[0]) {
text += '<div style="position:absolute;top:90px;left:390px;width:100px;height:100px;"' + tooltipAttrs(yourActive[0].getIdent(), 'pokemon', true, 'foe') + '></div>';
}
if (myActive[0]) {
text += '<div style="position:absolute;top:210px;left:130px;width:180px;height:160px;"' + tooltipAttrs(myActive[0].getIdent(), 'pokemon', true, true) + '></div>';
}
if (myActive[1]) {
text += '<div style="position:absolute;top:210px;left:270px;width:160px;height:160px;"' + tooltipAttrs(myActive[1].getIdent(), 'pokemon', true, true) + '></div>';
}
selfR.foeHintElem.html(text);
if (!selfR.me.request) {
selfR.controlsElem.html('<div class="controls"><em>Waiting for players...</em></div>');
selfR.updateJoinButton();
updateRoomList();
return;
}
if (selfR.me.request.side) {
selfR.updateSide(selfR.me.request.side, true);
}
selfR.me.callbackWaiting = true;
var active = selfR.battle.mySide.active[0];
if (!active) active = {};
if (selfR.battle.kickingInactive) {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em></div> <button onclick="rooms[\'' + selfR.id + '\'].formStopBattleTimer();return false"><small>Stop timer</small></button>');
} else {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em></div> <button onclick="rooms[\'' + selfR.id + '\'].formKickInactive();return false"><small>Kick inactive player</small></button>');
}
var act = '';
var switchables = [];
if (selfR.me.request) {
act = selfR.me.request.requestType;
if (selfR.me.request.side) {
switchables = selfR.battle.mySide.pokemon;
}
}
switch (act) {
case 'move':
{
if (type !== 'move2' && type !== 'movetarget') {
selfR.choices = [];
selfR.choiceSwitchFlags = {};
while (switchables[selfR.choices.length] && switchables[selfR.choices.length].fainted) selfR.choices.push('pass');
}
var pos = selfR.choices.length - (type === 'movetarget'?1:0);
var hpbar = '';
{
if (switchables[pos].hp * 5 / switchables[pos].maxhp < 1) {
hpbar = '<small class="critical">';
} else if (switchables[pos].hp * 2 / switchables[pos].maxhp < 1) {
hpbar = '<small class="weak">';
} else {
hpbar = '<small class="healthy">';
}
hpbar += ''+switchables[pos].hp+'/'+switchables[pos].maxhp+'</small>';
}
var active = selfR.me.request;
if (active.active) active = active.active[pos];
var moves = active.moves;
var trapped = active.trapped;
var controls = '<div class="controls"><div class="whatdo">';
if (type === 'move2' || type === 'movetarget') {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].callback(null,\'move\')">Back</button> ';
}
// Target selector
if (type === 'movetarget') {
controls += 'At who? '+hpbar+'</div>';
controls += '<div class="switchmenu" style="display:block">';
var myActive = selfR.battle.mySide.active;
var yourActive = selfR.battle.yourSide.active;
var yourSlot = yourActive.length-1-pos;
for (var i = yourActive.length-1; i >= 0; i--) {
var pokemon = yourActive[i];
var disabled = false;
if (moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf') {
disabled = true;
} else if (moveTarget === 'normal' || moveTarget === 'adjacentFoe') {
if (Math.abs(yourSlot-i) > 1) disabled = true;
}
if (!pokemon) {
controls += '<button disabled="disabled"></button> ';
} else if (disabled || pokemon.zerohp) {
controls += '<button disabled="disabled"' + tooltipAttrs(pokemon.getIdent(), 'pokemon', true, 'foe') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + (!pokemon.zerohp?'<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':''):'') +'</button> ';
} else {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].formSelectTarget(' + i + ', false)"' + tooltipAttrs(pokemon.getIdent(), 'pokemon', true, 'foe') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':'')+'</button> ';
}
}
controls += '<div style="clear:both"></div> </div><div class="switchmenu" style="display:block">';
for (var i = 0; i < myActive.length; i++) {
var pokemon = myActive[i];
var disabled = false;
if (moveTarget === 'adjacentFoe') {
disabled = true;
} else if (moveTarget === 'normal' || moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf') {
if (Math.abs(pos-i) > 1) disabled = true;
}
if (moveTarget !== 'adjacentAllyOrSelf' && pos == i) disabled = true;
if (!pokemon) {
controls += '<button disabled="disabled"></button> ';
} else if (disabled || pokemon.zerohp) {
controls += '<button disabled="disabled"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + (!pokemon.zerohp?'<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':''):'') +'</button> ';
} else {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].formSelectTarget(' + i + ', true)"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':'')+'</button> ';
}
}
controls += '</div>';
controls += '</div>';
selfR.controlsElem.html(controls);
break;
}
// Move chooser
controls += 'What will <strong>' + sanitize(switchables[pos].name) + '</strong> do? '+hpbar+'</div>';
var hasMoves = false;
var hasDisabled = false;
controls += '<div class="movecontrols"><div class="moveselect"><button onclick="rooms[\'' + selfR.id + '\'].formSelectMove()">Attack</button></div><div class="movemenu">';
var movebuttons = '';
for (var i = 0; i < moves.length; i++) {
var moveData = moves[i];
var move = Tools.getMove(moves[i].move);
if (!move) {
move = {
name: moves[i].move,
id: moves[i].move,
type: ''
};
}
var name = move.name;
var pp = moveData.pp + '/' + moveData.maxpp;
if (!moveData.maxpp) pp = '&ndash;';
if (move.id === 'Struggle' || move.id === 'Recharge') pp = '&ndash;';
if (move.id === 'Recharge') move.type = '&ndash;';
if (name.substr(0, 12) === 'Hidden Power') name = 'Hidden Power';
if (moveData.disabled) {
movebuttons += '<button disabled="disabled"' + tooltipAttrs(moveData.move, 'move') + '>';
hasDisabled = true;
} else {
movebuttons += '<button class="type-' + move.type + '" onclick="rooms[\'' + selfR.id + '\'].formUseMove(\'' + moveData.move.replace(/'/g, '\\\'') + '\', \''+moveData.target+'\')"' + tooltipAttrs(moveData.move, 'move') + '>';
hasMoves = true;
}
movebuttons += name + '<br /><small class="type">' + move.type + '</small> <small class="pp">' + pp + '</small>&nbsp;</button> ';
}
if (!hasMoves) {
controls += '<button class="movebutton" onclick="rooms[\'' + selfR.id + '\'].formUseMove(\'Struggle\')">Struggle<br /><small class="type">Normal</small> <small class="pp">&ndash;</small>&nbsp;</button> ';
} else {
controls += movebuttons;
}
controls += '<div style="clear:left"></div>';
if (hasDisabled) {
// controls += '<small>(grayed out moves have been disabled by Disable, Encore, or something like that)</small>';
}
controls += '</div></div><div class="switchcontrols"><div class="switchselect"><button onclick="rooms[\'' + selfR.id + '\'].formSelectSwitch()">Switch</button></div><div class="switchmenu">';
if (trapped) {
controls += '<em>You are trapped and cannot switch!</em>';
} else {
controls += '';
for (var i = 0; i < switchables.length; i++) {
var pokemon = switchables[i];
pokemon.name = pokemon.ident.substr(4);
if (pokemon.zerohp || i < selfR.battle.mySide.active.length || selfR.choiceSwitchFlags[i]) {
controls += '<button disabled="disabled"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + (!pokemon.zerohp?'<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':''):'') +'</button> ';
} else {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].formSwitchTo(' + i + ')"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':'')+'</button> ';
}
}
if (selfR.battle.mySide.pokemon.length > 6) {
//controls += '<small>Pokeball data corrupt. Please copy the text from this button: <button onclick="prompt(\'copy this text\', curRoom.battle.activityQueue.join(\' :: \'));return false">[click here]</button> and tell aesoft.</small>';
}
}
controls += '</div></div></div>';
selfR.controlsElem.html(controls);
}
selfR.notifying = true;
break;
case 'switch':
if (type !== 'switch2') {
selfR.choices = [];
selfR.choiceSwitchFlags = {};
if (selfR.me.request.forceSwitch !== true) {
while (!selfR.me.request.forceSwitch[selfR.choices.length] && selfR.choices.length < 6) selfR.choices.push('pass');
}
}
var pos = selfR.choices.length;
var controls = '<div class="controls"><div class="whatdo">';
if (type === 'switch2') {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].callback(null,\'switch\')">Back</button> ';
}
controls += 'Switch <strong>'+sanitize(switchables[pos].name)+'</strong> to:</div>';
controls += '<div class="switchcontrols"><div class="switchselect"><button onclick="rooms[\'' + selfR.id + '\'].formSelectSwitch()">Switch</button></div><div class="switchmenu">';
for (var i = 0; i < switchables.length; i++) {
var pokemon = switchables[i];
if (i >= 6) {
//controls += '<small>Pokeball data corrupt. Please copy the text from this button: <button onclick="prompt(\'copy this text\', curRoom.battle.activityQueue.join(\' :: \'));return false">[click here]</button> and tell aesoft.</small>';
break;
}
if (pokemon.zerohp || i < selfR.battle.mySide.active.length || selfR.choiceSwitchFlags[i]) {
controls += '<button disabled="disabled"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + (!pokemon.zerohp?'<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':''):'') +'</button> ';
} else {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].formSwitchTo(' + i + ')"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + '<span class="hpbar' + pokemon.getHPColorClass() + '"><span style="width:'+(Math.round(pokemon.hp*92/pokemon.maxhp)||1)+'px"></span></span>'+(pokemon.status?'<span class="status '+pokemon.status+'"></span>':'')+'</button> ';
}
}
controls += '</div></div></div>';
selfR.controlsElem.html(controls);
selfR.formSelectSwitch();
selfR.notifying = true;
break;
case 'team':
var controls = '<div class="controls"><div class="whatdo">';
if (type !== 'team2') {
selfR.teamPreviewChoice = [1,2,3,4,5,6].slice(0,switchables.length);
selfR.teamPreviewDone = 0;
selfR.teamPreviewCount = 0;
if (selfR.battle.gameType === 'doubles') {
selfR.teamPreviewCount = 2;
}
controls += 'How will you start the battle?</div>';
controls += '<div class="switchcontrols"><div class="switchselect"><button onclick="rooms[\'' + selfR.id + '\'].formSelectSwitch()">Choose Lead</button></div><div class="switchmenu">';
for (var i = 0; i < switchables.length; i++) {
var pokemon = switchables[i];
if (i >= 6) {
break;
}
if (toId(pokemon.baseAbility) === 'illusion') {
selfR.teamPreviewCount = 6;
}
controls += '<button onclick="rooms[\'' + selfR.id + '\'].formTeamPreviewSelect(' + i + ')"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + '</button> ';
}
if (selfR.battle.teamPreviewCount) selfR.teamPreviewCount = parseInt(selfR.battle.teamPreviewCount,10);
controls += '</div>';
} else {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].callback(null,\'team\')">Back</button> What about the rest of your team?</div>';
controls += '<div class="switchcontrols"><div class="switchselect"><button onclick="rooms[\'' + selfR.id + '\'].formSelectSwitch()">Choose a pokemon for slot '+(selfR.teamPreviewDone+1)+'</button></div><div class="switchmenu">';
for (var i = 0; i < switchables.length; i++) {
var pokemon = switchables[selfR.teamPreviewChoice[i]-1];
if (i >= 6) {
break;
}
if (i < selfR.teamPreviewDone) {
controls += '<button disabled="disabled"' + tooltipAttrs(i, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + '</button> ';
} else {
controls += '<button onclick="rooms[\'' + selfR.id + '\'].formTeamPreviewSelect(' + i + ')"' + tooltipAttrs(selfR.teamPreviewChoice[i]-1, 'sidepokemon') + '><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon(pokemon)+'"></span>' + sanitize(pokemon.name) + '</button> ';
}
}
controls += '</div>';
}
controls += '</div></div>';
selfR.controlsElem.html(controls);
selfR.formSelectSwitch();
selfR.notifying = true;
break;
}
updateRoomList();
};
this.formJoinBattle = function () {
selfR.send('/joinbattle');
return false;
};
this.formKickInactive = function () {
selfR.send('/kickinactive');
return false;
};
this.formStopBattleTimer = function () {
selfR.send('/timer off');
return false;
};
this.formForfeit = function () {
selfR.send('/forfeit');
return false;
};
this.formSaveReplay = function () {
selfR.send('/savereplay');
return false;
};
this.formRestart = function () {
/* hideTooltip();
selfR.send('/restart'); */
selfR.me.request = null;
selfR.battle.reset();
selfR.battle.play();
return false;
};
this.formUseMove = function (move, target) {
var myActive = selfR.battle.mySide.active;
hideTooltip();
if (move !== undefined) {
var choosableTargets = {normal:1, any:1, adjacentAlly:1, adjacentAllyOrSelf:1, adjacentFoe:1};
selfR.choices.push('move '+move);
if (myActive.length > 1 && target in choosableTargets) {
selfR.callback(selfR.battle, 'movetarget', target);
return false;
}
}
while (myActive.length > selfR.choices.length && !myActive[selfR.choices.length]) {
selfR.choices.push('pass');
}
if (myActive.length > selfR.choices.length) {
selfR.callback(selfR.battle, 'move2');
return false;
}
if (selfR.battle.kickingInactive) {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em> <button onclick="rooms[\'' + selfR.id + '\'].formUndoDecision(); return false">Cancel</button></div> <br /><button onclick="rooms[\'' + selfR.id + '\'].formStopBattleTimer();return false"><small>Stop timer</small></button>');
} else {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em> <button onclick="rooms[\'' + selfR.id + '\'].formUndoDecision(); return false">Cancel</button></div> <br /><button onclick="rooms[\'' + selfR.id + '\'].formKickInactive();return false"><small>Kick inactive player</small></button>');
}
selfR.sendDecision('/choose '+selfR.choices.join(','));
selfR.notifying = false;
updateRoomList();
return false;
};
this.formSelectTarget = function (pos, isMySide) {
var posString;
if (isMySide) {
posString = ''+(-(pos+1));
} else {
posString = ''+(pos+1);
}
selfR.choices[selfR.choices.length-1] += ' '+posString;
selfR.formUseMove();
return false;
};
this.formSwitchTo = function (pos) {
hideTooltip();
selfR.choices.push('switch '+(parseInt(pos,10)+1));
selfR.choiceSwitchFlags[pos] = true;
if (selfR.me.request && selfR.me.request.requestType === 'move' && selfR.battle.mySide.active.length > selfR.choices.length) {
selfR.callback(selfR.battle, 'move2');
return false;
}
if (selfR.me.request && selfR.me.request.requestType === 'switch') {
if (selfR.me.request.forceSwitch !== true) {
while (selfR.battle.mySide.active.length > selfR.choices.length && !selfR.me.request.forceSwitch[selfR.choices.length]) selfR.choices.push('pass');
}
if (selfR.battle.mySide.active.length > selfR.choices.length) {
selfR.callback(selfR.battle, 'switch2');
return false;
}
}
if (selfR.battle.kickingInactive) {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em> <button onclick="rooms[\'' + selfR.id + '\'].formUndoDecision(); return false">Cancel</button></div> <br /><button onclick="rooms[\'' + selfR.id + '\'].formStopBattleTimer();return false"><small>Stop timer</small></button>');
} else {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em> <button onclick="rooms[\'' + selfR.id + '\'].formUndoDecision(); return false">Cancel</button></div> <br /><button onclick="rooms[\'' + selfR.id + '\'].formKickInactive();return false"><small>Kick inactive player</small></button>');
}
selfR.sendDecision('/choose '+selfR.choices.join(','));
selfR.notifying = false;
updateRoomList();
return false;
};
this.formTeamPreviewSelect = function (pos) {
pos = parseInt(pos,10);
hideTooltip();
if (selfR.teamPreviewCount) {
var temp = selfR.teamPreviewChoice[pos];
selfR.teamPreviewChoice[pos] = selfR.teamPreviewChoice[selfR.teamPreviewDone];
selfR.teamPreviewChoice[selfR.teamPreviewDone] = temp;
selfR.teamPreviewDone++;
if (selfR.teamPreviewDone < Math.min(selfR.teamPreviewChoice.length, selfR.teamPreviewCount)) {
selfR.callback(selfR.battle, 'team2');
return false;
}
pos = selfR.teamPreviewChoice.join('');
} else {
pos = pos+1;
}
if (selfR.battle.kickingInactive) {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em> <button onclick="rooms[\'' + selfR.id + '\'].formUndoDecision(); return false">Cancel</button></div> <br /><button onclick="rooms[\'' + selfR.id + '\'].formStopBattleTimer();return false"><small>Stop timer</small></button>');
} else {
selfR.controlsElem.html('<div class="controls"><em>Waiting for opponent...</em> <button onclick="rooms[\'' + selfR.id + '\'].formUndoDecision(); return false">Cancel</button></div> <br /><button onclick="rooms[\'' + selfR.id + '\'].formKickInactive();return false"><small>Kick inactive player</small></button>');
}
selfR.sendDecision('/team '+(pos));
selfR.notifying = false;
updateRoomList();
return false;
};
this.formUndoDecision = function (pos) {
selfR.send('/undo');
selfR.notifying = true;
selfR.callback(selfR.battle, 'decision');
return false;
};
// Key press in the battle chat textbox.
this.formKeyPress = function (e) {
hideTooltip();
if (e.keyCode === 13) {
if (selfR.chatboxElem.val()) {
var text = selfR.chatboxElem.val();
rooms.lobby.tabComplete.reset();
rooms.lobby.chatHistory.push(text);
text = rooms.lobby.parseCommand(text);
if (text) {
selfR.send(text);
}
selfR.chatboxElem.val('');
}
return false;
}
return true;
};
this.formRename = function () {
overlay('rename');
return false;
};
this.formLeaveBattle = function () {
hideTooltip();
selfR.send('/leavebattle');
selfR.notifying = false;
updateRoomList();
return false;
};
this.formSelectSwitch = function () {
hideTooltip();
selfR.controlsElem.find('.controls').attr('class', 'controls switch-controls');
return false;
};
this.formSelectMove = function () {
hideTooltip();
selfR.controlsElem.find('.controls').attr('class', 'controls move-controls');
return false;
};
this.battle = new Battle(this.battleElem, this.chatFrameElem);
if (widthClass !== 'tiny-layout') {
this.battle.messageSpeed = 80;
}
this.battle.setMute(me.mute);
this.battle.customCallback = this.callback;
this.battle.endCallback = this.endCallback;
this.battle.startCallback = this.updateMe;
this.battle.stagnateCallback = this.updateMe;
}
var lobbyChatElem = null;
function updateLobbyChat(tab) {
if (!tab && curRoom) tab = curRoom.id;
if (tab === 'lobby') {
$('#lobbychat').prop('class', 'lobbychat mainlobbychat');
$('#lobbychat').show();
if (rooms.lobby && rooms.lobby.chatElem) rooms.lobby.chatFrameElem.scrollTop(rooms.lobby.chatElem.height());
if (rooms.lobby && rooms.lobby.chatboxElem) rooms.lobby.chatboxElem.focus();
} else if (tab === 'teambuilder' || tab === 'ladder') {
$('#lobbychat').prop('class', 'lobbychat sidelobbychat');
$('#lobbychat').show();
if (rooms.lobby && rooms.lobby.chatElem) rooms.lobby.chatFrameElem.scrollTop(rooms.lobby.chatElem.height());
if (rooms.lobby && rooms.lobby.chatboxElem) rooms.lobby.chatboxElem.focus();
} else if (widthClass === 'huge-layout') {
$('#lobbychat').prop('class', 'lobbychat secondarylobbychat');
$('#lobbychat').show();
} else $('#lobbychat').hide();
}
function Lobby(id, elem) {
var selfR = this;
this.id = id;
this.elem = elem;
this.meIdent = {
name: me.name,
named: 'init'
};
me.rooms[id] = {};
this.me = me.rooms[id];
this.joinLeave = {
'join': [],
'leave': []
};
this.joinLeaveElem = null;
this.userCount = {};
this.userList = {};
this.userActivity = [];
this.tabComplete = {
candidates: null,
index: 0,
prefix: null,
cursor: -1,
reset: function() {
this.cursor = -1;
}
};
this.chatHistory = (function() {
var self = {
lines: [],
index: 0,
push: function(line) {
if (self.lines.length > 100) {
self.lines.splice(0, 20);
}
self.lines.push(line);
self.index = self.lines.length;
}
};
return self;
})();
this.highlightRegExp = null;
this.searcher = null;
this.selectedTeam = 0;
this.selectedFormat = '';
elem.html('<div class="mainsection"><div class="maintop"></div><div class="mainbottom"></div><div class="mainpopup" style="display:none"></div></div><div id="inline-nav"></div>');
$('#lobbychat').html('<div class="battle-log"><div class="inner"></div><div class="inner-after"></div></div><div class="battle-log-add">Connecting...</div>');
this.mainElem = elem.find('.mainsection');
this.mainTopElem = elem.find('.maintop');
this.mainBottomElem = elem.find('.mainbottom');
this.popupElem = elem.find('.mainpopup');
this.chatFrameElem = $('#lobbychat').find('.battle-log');
this.chatElem = $('#lobbychat').find('.battle-log .inner');
this.chatAddElem = $('#lobbychat').find('.battle-log-add');
this.chatboxElem = null;
this.dealloc = function () {};
this.focus = function () {
selfR.updateMe();
selfR.updateMainTop(true);
selfR.chatFrameElem.scrollTop(selfR.chatElem.height());
if (selfR.chatboxElem) selfR.chatboxElem.focus();
};
this.rawMessage = function(message) {
this.message({rawMessage: message});
};
this.message = function (message) {
if (typeof message !== 'string') {
selfR.add([message]);
} else {
selfR.add([{
message: message
}]);
}
};
this.send = function (message) {
emit(socket, 'chat', {room:'',message:message});
};
this.clear = function () {
selfR.chatElem.html('');
};
this.popupClose = function (i) {
if (!me.popups.length) return;
if (typeof i === 'undefined') i = me.popups.length - 1;
me.popups.splice(i, 1);
selfR.updatePopup();
selfR.popupChatboxElem.val('');
if (selfR.chatboxElem) {
selfR.chatboxElem.focus();
}
};
this.popupKeyUp = function (e) {
if (e.keyCode === 27) {
selfR.popupClose();
return false;
}
};
this.popupKeyPress = function (e) {
hideTooltip();
if (e.keyCode === 13) {
var text;
if ((text = selfR.popupChatboxElem.val())) {
selfR.tabComplete.reset();
selfR.chatHistory.push(text);
text = selfR.parseCommand(text);
if (text) {
var splitText = text.split('\n');
for (var i=0, len=splitText.length; i<len; i++) if (splitText[i]) splitText[i] = '/msg ' + me.curPopup + ', ' + splitText[i];
selfR.send(splitText.join('\n'));
}
selfR.popupChatboxElem.val('');
}
return false;
}
return true;
};
this.parseCommand = function(text) {
var cmd = '';
var target = '';
if (text.substr(0,2) !== '//' && text.substr(0,1) === '/') {
var spaceIndex = text.indexOf(' ');
if (spaceIndex > 0) {
cmd = text.substr(1, spaceIndex-1);
target = text.substr(spaceIndex+1);
} else {
cmd = text.substr(1);
target = '';
}
}
switch (cmd.toLowerCase()) {
case 'challenge':
case 'user':
case 'open':
if (!target) target = prompt('Who?');
if (target) rooms.lobby.formChallenge(target);
return false;
case 'ignore':
if (me.ignore[toUserid(target)]) {
this.message('User ' + target + ' is already on your ignore list. (Moderator messages will not be ignored.)');
} else {
me.ignore[toUserid(target)] = 1;
this.message('User ' + target + ' ignored. (Moderator messages will not be ignored.)');
}
return false;
case 'unignore':
if (!me.ignore[toUserid(target)]) {
this.message('User ' + target + ' isn\'t on your ignore list.');
} else {
delete me.ignore[toUserid(target)];
this.message('User ' + target + ' no longer ignored.');
}
return false;
case 'clear':
if (this.clear) this.clear();
return false;
case 'nick':
if (target) {
renameMe(target);
} else {
rooms.lobby.formRename();
}
return false;
case 'showjoins':
rooms.lobby.add('Join/leave messages: ON');
Tools.prefs.set('showjoins', true, true);
return false;
case 'hidejoins':
rooms.lobby.add('Join/leave messages: HIDDEN');
Tools.prefs.set('showjoins', false, true);
return false;
case 'showbattles':
rooms.lobby.add('Battle messages: ON');
Tools.prefs.set('showbattles', true, true);
return false;
case 'hidebattles':
rooms.lobby.add('Battle messages: HIDDEN');
Tools.prefs.set('showbattles', false, true);
return false;
case 'timestamps':
var targets = target.split(',');
if ((['all', 'lobby', 'pms'].indexOf(targets[0]) === -1)
|| (targets.length < 2)
|| (['off', 'minutes', 'seconds'].indexOf(
targets[1] = targets[1].trim()) === -1)) {
rooms.lobby.add('Error: Invalid /timestamps command');
return '/help timestamps'; // show help
}
var timestamps = Tools.prefs.get('timestamps') || {};
if (typeof timestamps === 'string') {
// The previous has a timestamps preference from the previous
// regime. We can't set properties of a string, so set it to
// an empty object.
timestamps = {};
}
switch (targets[0]) {
case 'all':
timestamps.lobby = targets[1];
timestamps.pms = targets[1];
break;
case 'lobby':
timestamps.lobby = targets[1];
break;
case 'pms':
timestamps.pms = targets[1];
break;
}
rooms.lobby.add('Timestamps preference set to: `' + targets[1] + '` for `' + targets[0] + '`.');
Tools.prefs.set('timestamps', timestamps, true);
return false;
case 'highlight':
var highlights = Tools.prefs.get('highlights') || [];
if (target.indexOf(',') > -1) {
var targets = target.split(',');
// trim the targets to be safe
for (var i=0, len=targets.length; i<len; i++) {
targets[i] = targets[i].trim();
}
switch (targets[0]) {
case 'add':
for (var i=1, len=targets.length; i<len; i++) {
highlights.push(targets[i].trim());
}
rooms.lobby.add("Now highlighting on: " + highlights.join(', '));
// We update the regex
this.highlightRegExp = new RegExp('\\b('+highlights.join('|')+')\\b', 'i');
break;
case 'delete':
var newHls = [];
for (var i=0, len=highlights.length; i<len; i++) {
if (targets.indexOf(highlights[i]) === -1) {
newHls.push(highlights[i]);
}
}
highlights = newHls;
rooms.lobby.add("Now highlighting on: " + highlights.join(', '));
// We update the regex
this.highlightRegExp = new RegExp('\\b('+highlights.join('|')+')\\b', 'i');
break;
}
Tools.prefs.set('highlights', highlights, true);
} else {
if (target === 'delete') {
Tools.prefs.set('highlights', false, true);
rooms.lobby.add("All highlights cleared");
} else if (target === 'show' || target === 'list') {
// Shows a list of the current highlighting words
if (highlights.length > 0) {
var hls = highlights.join(', ');
rooms.lobby.add('Current highlight list: ' + hls);
} else {
rooms.lobby.add('Your highlight list is empty.');
}
} else {
// Wrong command
rooms.lobby.add('Error: Invalid /highlight command.');
return '/help highlight'; // show help
}
}
return false;
case 'rank':
case 'ranking':
case 'rating':
case 'ladder':
if (!target) target = me.userid;
var self = this;
$.get(actionphp + '?act=ladderget&user='+target, Tools.safeJson(function(data) {
try {
var buffer = '<div class="ladder"><table>';
buffer += '<tr><td colspan="7">User: <strong>'+target+'</strong></td></tr>';
if (!data.length) {
buffer += '<tr><td colspan="7"><em>This user has not played any ladder games yet.</em></td></tr>';
} else {
buffer += '<tr><th>Format</th><th>ACRE</th><th>GXE</th><th>Glicko2</th><th>W</th><th>L</th><th>T</th></tr>';
for (var i=0; i<data.length; i++) {
var row = data[i];
buffer += '<tr><td>'+row.formatid+'</td><td><strong>'+Math.round(row.acre)+'</strong></td><td>'+Math.round(row.gxe,1)+'</td><td>';
if (row.rprd > 50) {
buffer += '<span><em>'+Math.round(row.rpr)+'<small> &#177; '+Math.round(row.rprd)+'</small></em> <small>(provisional)</small></span>';
} else {
buffer += '<em>'+Math.round(row.rpr)+'<small> &#177; '+Math.round(row.rprd)+'</small></em>';
}
buffer += '</td><td>'+row.w+'</td><td>'+row.l+'</td><td>'+row.t+'</td></tr>';
}
}
buffer += '</table></div>';
self.rawMessage(buffer);
} catch(e) {
}
}), 'text');
return false;
case 'buttonban':
var reason = prompt('Why do you wish to ban this user?');
if (reason === null) return false;
if (reason === false) reason = '';
rooms.lobby.send('/ban ' + target + ', ' + reason);
return false;
case 'buttonmute':
var reason = prompt('Why do you wish to mute this user?');
if (reason === null) return false;
if (reason === false) reason = '';
rooms.lobby.send('/mute ' + target + ', ' + reason);
return false;
case 'buttonunmute':
rooms.lobby.send('/unmute ' + target);
return false;
case 'buttonkick':
var reason = prompt('Why do you wish to kick this user?');
if (reason === null) return false;
if (reason === false) reason = '';
rooms.lobby.send('/kick ' + target + ', ' + reason);
return false;
case 'avatar':
var parts = target.split(',');
var avatar = parseInt(parts[0], 10);
if (avatar) {
Tools.prefs.set('avatar', avatar, true);
}
return text; // Send the /avatar command through to the server.
}
return text;
};
this.popupOpen = function (userid) {
userid = toUserid(userid);
for (var i = 0; i < me.popups.length; i++) {
if (me.popups[i] === userid) return selfR.popupFocus(i);
}
me.popups.push(userid);
selfR.updatePopup();
selfR.popupChatboxElem.focus();
};
this.popupFocus = function (i) {
if (!me.popups.length) return;
if (i == me.popups.length - 1) return;
me.popups = me.popups.concat(me.popups.splice(i, 1));
selfR.updatePopup();
selfR.popupChatboxElem.focus();
};
this.updatePopup = function (data) {
if (selfR.popupState && !me.popups.length) {
selfR.popupElem.html('');
selfR.popupElem.hide();
selfR.popupState = '';
} else if (selfR.popupState !== 'pm-' + me.popups.join(',')) {
var code = '<div id="' + selfR.id + '-pmlog-list">';
var popupListCode = '';
var name;
for (var i = 0; i < me.popups.length - 1; i++) {
name = sanitize(me.users[me.popups[i]] || me.popups[i]);
popupListCode += '<h3><button class="closebutton" onclick="rooms[\'' + selfR.id + '\'].popupClose(' + i + ');return false;"><i class="icon-remove-sign"></i></button><a onclick="rooms[\'' + selfR.id + '\'].popupFocus(' + i + ');return false;">' + name + '</a></h3>';
}
if (me.curPopup === me.popups[i]) {
$('#' + selfR.id + '-pmlog-list').html(popupListCode);
}
me.curPopup = me.popups[i];
code += popupListCode;
code += '</div>';
var clickableName = '<a onclick="return rooms.lobby.formChallenge(\'' + me.curPopup + '\');">' + sanitize(me.users[me.curPopup] || me.curPopup) + '</a>';
code += '<h3><button class="closebutton" onclick="rooms[\'' + selfR.id + '\'].popupClose(' + i + ');return false"><i class="icon-remove-sign"></i></button>' + clickableName + '</h3>';
code += '<div id="' + selfR.id + '-pmlog-frame" class="battle-log" onclick="rooms[\'' + selfR.id + '\'].popupChatboxElem.focus()"><div id="' + selfR.id + '-pmlog" class="inner">' + (me.pm[me.curPopup] || '') + '</div><div class="inner-after"></div></div>';
if (!selfR.popupElem.children('.battle-log-add').length) {
code += '<div class="battle-log-add"><form onsubmit="return false" class="chatbox"><textarea class="textbox" type="text" size="70" autocomplete="off" onkeypress="return rooms[\'' + selfR.id + '\'].popupKeyPress(event)" onkeyup="return rooms[\'' + selfR.id + '\'].popupKeyUp(event)"></textarea></form></div>';
selfR.popupElem.html(code);
} else {
selfR.popupElem.contents().not('.battle-log-add').remove();
selfR.popupElem.prepend(code);
}
selfR.popupChatboxElem = selfR.popupElem.find('textarea').last();
selfR.popupChatboxElem.keydown(rooms['lobby'].formKeyDown);
selfR.popupElem.show();
$('#' + selfR.id + '-pmlog-frame').scrollTop($('#' + selfR.id + '-pmlog').height());
selfR.popupChatboxElem.autoResize({
animateDuration: 100,
extraSpace: 0
});
selfR.popupState = 'pm-' + me.popups.join(',');
} else if (me.popups.length) {
if ($('#' + selfR.id + '-pmlog-frame').scrollTop() + 60 >= $('#' + selfR.id + '-pmlog').height() - $('#' + selfR.id + '-pmlog-frame').height()) {
autoscroll = true;
}
$('#' + selfR.id + '-pmlog').append(data);
if (autoscroll) {
$('#' + selfR.id + '-pmlog-frame').scrollTop($('#' + selfR.id + '-pmlog').height());
}
}
};
// Mark a user as active for the purpose of tab complete.
this.markUserActive = function (userid) {
var idx = selfR.userActivity.indexOf(userid);
if (idx !== -1) {
selfR.userActivity.splice(idx, 1);
}
selfR.userActivity.push(userid);
if (selfR.userActivity.length > 100) {
// Prune the list.
selfR.userActivity.splice(0, 20);
}
};
this.getTimestamp = function (section) {
var pref = Tools.prefs.get('timestamps') || {};
var sectionPref = ((section === 'pms') ? pref.pms : pref.lobby) || 'off';
if ((sectionPref === 'off') || (sectionPref === undefined)) return '';
var date = new Date();
var components = [ date.getHours(), date.getMinutes() ];
if (sectionPref === 'seconds') {
components.push(date.getSeconds());
}
return '[' + components.map(
function(x) { return (x < 10) ? '0' + x : x; }
).join(':') + '] ';
};
this.getHighlight = function (message) {
var highlights = Tools.prefs.get('highlights') || [];
if (!this.highlightRegExp) {
try {
this.highlightRegExp = new RegExp('\\b('+highlights.join('|')+')\\b', 'i');
} catch (e) {
// If the expression above is not a regexp, we'll get here.
// Don't throw an exception because that would prevent the chat
// message from showing up, or, when the lobby is initialising,
// it will prevent the initialisation from completing.
return false;
}
}
return ((highlights.length > 0) && this.highlightRegExp.test(message));
};
this.add = function (log) {
if (typeof log === 'string') log = log.split('\n');
var autoscroll = false;
if (selfR.chatFrameElem.scrollTop() + 60 >= selfR.chatElem.height() - selfR.chatFrameElem.height()) {
autoscroll = true;
}
selfR.lastUpdate = log;
for (var i = 0; i < log.length; i++) {
if (typeof log[i] === 'string') {
if (log[i].substr(0,1) !== '|') log[i] = '||'+log[i];
var row = log[i].substr(1).split('|');
switch (row[0]) {
case 'c':
case 'chat':
log[i] = {
name: row[1],
message: row.slice(2).join('|')
};
break;
case 'b':
case 'B':
log[i] = {
action: 'battle',
room: row[1],
name: row[2],
name2: row[3],
silent: (row[0] === 'B')
};
break;
case 'j':
case 'J':
log[i] = {
action: 'join',
name: row[1],
silent: (row[0] === 'J')
};
break;
case 'l':
case 'L':
log[i] = {
action: 'leave',
name: row[1],
silent: (row[0] === 'L')
};
break;
case 'n':
case 'N':
log[i] = {
action: 'rename',
name: row[1],
oldid: row[2],
silent: true
};
break;
case 'raw':
log[i] = {
rawMessage: row.slice(1).join('|')
};
break;
case 'refresh':
// refresh the page
document.location.reload(true);
break;
case 'formats':
var isSection = false;
var section = '';
BattleFormats = {};
for (var j=1; j<row.length; j++) {
if (isSection) {
section = row[j];
isSection = false;
} else if (row[j] === '') {
isSection = true;
} else {
var searchShow = true;
var challengeShow = true;
var team = null;
var name = row[j];
if (name.substr(name.length-2) === ',#') { // preset teams
team = 'preset';
name = name.substr(0,name.length-2);
}
if (name.substr(name.length-2) === ',,') { // search-only
challengeShow = false;
name = name.substr(0,name.length-2);
} else if (name.substr(name.length-1) === ',') { // challenge-only
searchShow = false;
name = name.substr(0,name.length-1);
}
BattleFormats[toId(name)] = {
id: toId(name),
name: name,
team: team,
section: section,
searchShow: searchShow,
challengeShow: challengeShow,
rated: challengeShow && searchShow,
isTeambuilderFormat: challengeShow && searchShow && !team,
effectType: 'Format'
};
}
}
selfR.updateMainTop(true);
break;
case '':
default:
log[i] = {
message: row.slice(1).join('|')
};
break;
}
}
if (log[i].name && log[i].message) {
var userid = toUserid(log[i].name);
var color = hashColor(userid);
if (me.ignore[userid] && log[i].name.substr(0, 1) === ' ') continue;
// Add this user to the list of people who have spoken recently.
selfR.markUserActive(userid);
selfR.joinLeaveElem = null;
selfR.joinLeave = {
'join': [],
'leave': []
};
var clickableName = '<span style="cursor:pointer" onclick="return rooms.lobby.formChallenge(\'' + userid + '\');">' + sanitize(log[i].name.substr(1)) + '</span>';
var message = log[i].message;
var isHighlighted = selfR.getHighlight(message);
if (isHighlighted) {
notify({
type: 'highlight',
user: log[i].name
});
}
var highlight = isHighlighted ? ' style="background-color:#FDA;"' : '';
var chatDiv = '<div class="chat"' + highlight + '>';
var timestamp = selfR.getTimestamp(log[i].pm ? 'pms' : 'lobby');
if (log[i].name.substr(0, 1) !== ' ') clickableName = '<small>' + sanitize(log[i].name.substr(0, 1)) + '</small>'+clickableName;
if (log[i].pm) {
var pmuserid = (userid === me.userid ? toUserid(log[i].pm) : userid);
if (!me.pm[pmuserid]) me.pm[pmuserid] = '';
var pmcode = '<div class="chat">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em> ' + messageSanitize(message) + '</em></div>';
for (var j = 0; j < me.popups.length; j++) {
if (pmuserid === me.popups[j]) break;
}
if (j == me.popups.length) {
// This is a new PM.
me.popups.unshift(pmuserid);
notify({
type: 'pm',
user: log[i].name
});
}
me.pm[pmuserid] += pmcode;
if (me.popups.length && me.popups[me.popups.length - 1] === pmuserid) {
selfR.updatePopup(pmcode);
} else {
selfR.updatePopup();
}
selfR.chatElem.append('<div class="chat">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <span class="message-pm"><i style="cursor:pointer" onclick="selectTab(\'lobby\');rooms.lobby.popupOpen(\'' + pmuserid + '\')">(Private to ' + sanitize(log[i].pm) + ')</i> ' + messageSanitize(message) + '</span></div>');
//} else if (log[i].act) {
// selfR.chatElem.append('<div class="chat"><strong style="' + color + '">&bull;</strong> <em' + (log[i].name.substr(1) === me.name ? ' class="mine"' : '') + '>' + clickableName + ' <i>' + message + '</i></em></div>');
} else if (message.substr(0,2) === '//') {
selfR.chatElem.append(chatDiv + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em' + (log[i].name.substr(1) === me.name ? ' class="mine"' : '') + '>' + messageSanitize(message.substr(1)) + '</em></div>');
} else if (message.substr(0,4).toLowerCase() === '/me ') {
selfR.chatElem.append(chatDiv + timestamp + '<strong style="' + color + '">&bull;</strong> <em' + (log[i].name.substr(1) === me.name ? ' class="mine"' : '') + '>' + clickableName + ' <i>' + messageSanitize(message.substr(4)) + '</i></em></div>');
} else if (message.substr(0,5).toLowerCase() === '/mee ') {
selfR.chatElem.append(chatDiv + timestamp + '<strong style="' + color + '">&bull;</strong> <em' + (log[i].name.substr(1) === me.name ? ' class="mine"' : '') + '>' + clickableName + '<i>' + messageSanitize(message.substr(5)) + '</i></em></div>');
} else if (message.substr(0,10).toLowerCase() === '/announce ') {
selfR.chatElem.append(chatDiv + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <span class="message-announce">' + messageSanitize(message.substr(10)) + '</span></div>');
} else if (message.substr(0,14).toLowerCase() === '/data-pokemon ') {
selfR.chatElem.append('<div class="message"><ul class=\"utilichart\">'+Chart.pokemonRow(Tools.getTemplate(message.substr(14)),'',{})+'<li style=\"clear:both\"></li></ul></div>');
} else if (message.substr(0,11).toLowerCase() === '/data-item ') {
selfR.chatElem.append('<div class="message"><ul class=\"utilichart\">'+Chart.itemRow(Tools.getItem(message.substr(11)),'',{})+'<li style=\"clear:both\"></li></ul></div>');
} else if (message.substr(0,14).toLowerCase() === '/data-ability ') {
selfR.chatElem.append('<div class="message"><ul class=\"utilichart\">'+Chart.abilityRow(Tools.getAbility(message.substr(14)),'',{})+'<li style=\"clear:both\"></li></ul></div>');
} else if (message.substr(0,11).toLowerCase() === '/data-move ') {
selfR.chatElem.append('<div class="message"><ul class=\"utilichart\">'+Chart.moveRow(Tools.getMove(message.substr(11)),'',{})+'<li style=\"clear:both\"></li></ul></div>');
} else {
// Normal chat message.
selfR.chatElem.append(chatDiv + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em' + (log[i].name.substr(1) === me.name ? ' class="mine"' : '') + '>' + messageSanitize(message) + '</em></div>');
}
} else if (log[i].name && log[i].action === 'battle') {
var id = log[i].room;
var matches = id.match(/^battle\-([a-z0-9]*[a-z])[0-9]*$/);
var format = (matches ? matches[1] : '');
selfR.rooms.push({
id: id,
format: format,
p1: log[i].name,
p2: log[i].name2
});
if (selfR.rooms.length > 8) selfR.rooms.shift();
selfR.debounceUpdate();
if (log[i].silent && !Tools.prefs.get('showbattles')) continue;
selfR.joinLeaveElem = null;
selfR.joinLeave = {
'join': [],
'leave': []
};
var id = log[i].room;
var battletype = 'Battle';
if (log[i].format) {
battletype = log[i].format + ' battle';
if (log[i].format === 'Random Battle') battletype = 'Random Battle';
}
selfR.chatElem.append('<div class="message"><a href="' + locPrefix+id + '" onclick="selectTab(\'' + id + '\'); return false" class="battle-start">' + battletype + ' started between <strong style="' + hashColor(toUserid(log[i].name)) + '">' + sanitize(log[i].name) + '</strong> and <strong style="' + hashColor(toUserid(log[i].name2)) + '">' + sanitize(log[i].name2) + '</strong>.</a></div>');
} else if (log[i].message) {
selfR.chatElem.append('<div class="message">' + sanitize(log[i].message) + '</div>');
} else if (log[i].rawMessage) {
// This is so that the register link continues to work on old servers.
// It can be removed later.
if (log[i].rawMessage === '<div style="background-color:#6688AA;color:white;padding:2px 4px"><b>Register an account to protect your ladder rating!</b><br /><button onclick="overlay(\'register\',{ifuserid:\''+me.userid+'\'});return false"><b>Register</b></button></div>') {
selfR.chatElem.append('<div class="message">' + log[i].rawMessage + '</div>');
} else {
selfR.chatElem.append('<div class="message">' + Tools.htmlSanitize(log[i].rawMessage) + '</div>');
}
} else if (log[i].evalRulesRedirect || log[i].evalRawMessage) {
// TODO: This will be removed in due course.
window.location.href = 'http://pokemonshowdown.com/rules';
} else if (log[i].name && (log[i].action === 'join' || log[i].action === 'leave' || log[i].action === 'rename')) {
var userid = toUserid(log[i].name);
if (log[i].action === 'join') {
if (log[i].oldid) delete me.users[toUserid(log[i].oldid)];
if (!me.users[userid]) selfR.userCount.users++;
me.users[userid] = log[i].name;
selfR.userList.add(userid);
selfR.userList.updateUserCount();
selfR.userList.updateNoUsersOnline();
} else if (log[i].action === 'leave') {
if (me.users[userid]) selfR.userCount.users--;
delete me.users[userid];
selfR.userList.remove(userid);
selfR.userList.updateUserCount();
selfR.userList.updateNoUsersOnline();
} else if (log[i].action === 'rename') {
if (log[i].oldid) delete me.users[toUserid(log[i].oldid)];
me.users[userid] = log[i].name;
selfR.userList.remove(log[i].oldid);
selfR.userList.add(userid);
continue;
}
if (log[i].silent && !Tools.prefs.get('showjoins')) continue;
if (!selfR.joinLeaveElem) {
selfR.chatElem.append('<div class="message"><small>Loading...</small></div>');
selfR.joinLeaveElem = selfR.chatElem.children().last();
}
selfR.joinLeave[log[i].action].push(log[i].name);
var message = '';
if (selfR.joinLeave['join'].length) {
var preList = selfR.joinLeave['join'];
var list = [];
var named = {};
for (var j = 0; j < preList.length; j++) {
if (!named[preList[j]]) list.push(preList[j]);
named[preList[j]] = true;
}
for (var j = 0; j < list.length; j++) {
if (j >= 5) {
message += ', and ' + (list.length - 5) + ' others';
break;
}
if (j > 0) {
if (j == 1 && list.length == 2) {
message += ' and ';
} else if (j == list.length - 1) {
message += ', and ';
} else {
message += ', ';
}
}
message += sanitize(list[j]);
}
message += ' joined';
}
if (selfR.joinLeave['leave'].length) {
if (selfR.joinLeave['join'].length) {
message += '; ';
}
var preList = selfR.joinLeave['leave'];
var list = [];
var named = {};
for (var j = 0; j < preList.length; j++) {
if (!named[preList[j]]) list.push(preList[j]);
named[preList[j]] = true;
}
for (var j = 0; j < list.length; j++) {
if (j >= 5) {
message += ', and ' + (list.length - 5) + ' others';
break;
}
if (j > 0) {
if (j == 1 && list.length == 2) {
message += ' and ';
} else if (j == list.length - 1) {
message += ', and ';
} else {
message += ', ';
}
}
message += sanitize(list[j]);
}
message += ' left<br />';
}
selfR.joinLeaveElem.html('<small style="color: #555555">' + message + '</small>');
}
}
if (autoscroll) {
selfR.chatFrameElem.scrollTop(selfR.chatElem.height());
}
var $children = selfR.chatElem.children();
if ($children.length > 900) {
$children.slice(0,100).remove();
}
};
// Lobby init
this.init = function (data) {
if (data.log) {
selfR.chatElem.html('');
// Disable timestamps for the past log because the server doesn't
// tell us what time the messages were sent at.
var timestamps = Tools.prefs.get('timestamps');
Tools.prefs.set('timestamps', 'off');
selfR.add(data.log); // Add past log.
Tools.prefs.set('timestamps', timestamps);
}
selfR.update(data);
selfR.chatFrameElem.scrollTop(selfR.chatElem.height());
selfR.updateMe();
if (me.named) {
// Preferred avatar feature
var avatar = Tools.prefs.get('avatar');
if (avatar) {
// This will be compatible even with servers that don't support
// the second argument for /avatar yet.
selfR.send('/avatar ' + avatar + ',1');
}
}
if (me.renameQueued) {
renameMe(me.renameQueued);
me.renameQueued = false;
}
};
this.update = function (data) {
if (data.logUpdate) {
selfR.add(data.logUpdate);
}
if (typeof data.searching !== 'undefined') {
selfR.me.searching = data.searching;
selfR.updateMainTop();
}
if (typeof data.searcher !== 'undefined') {
selfR.searcher = data.searcher;
}
if (typeof data.u !== 'undefined') {
selfR.userCount = {};
me.users = {};
var commaIndex = data.u.indexOf(',');
if (commaIndex >= 0) {
selfR.userCount.users = parseInt(data.u.substr(0,commaIndex),10);
var users = data.u.substr(commaIndex+1).split(',');
for (var i=0,len=users.length; i<len; i++) {
if (users[i]) me.users[toId(users[i])] = users[i];
}
} else {
selfR.userCount.users = parseInt(data.u);
selfR.userCount.guests = selfR.userCount.users;
}
selfR.userList.construct();
}
if (data.rooms) {
selfR.rooms = [];
for (var id in data.rooms) {
var room = data.rooms[id];
var matches = id.match(/^battle\-([a-z0-9]*[a-z])[0-9]*$/);
room.format = (matches ? matches[1] : '');
room.id = id;
selfR.rooms.unshift(room);
}
}
//selfR.updateMainTop();
selfR.updateMe();
};
this.mainTopState = '';
this.command = function (data) {
if (data.command === 'userdetails') {
var userid = data.userid;
if (!$('#' + selfR.id + '-userdetails-' + userid).length) return;
var roomListCode = '';
for (var id in data.rooms) {
var roomData = data.rooms[id];
var matches = id.match(/^battle\-([a-z0-9]*[a-z])[0-9]*$/);
var format = (matches ? '<small>[' + matches[1] + ']</small><br />' : '');
var roomDesc = format + '<em class="p1">' + sanitize(roomData.p1) + '</em> <small class="vs">vs.</small> <em class="p2">' + sanitize(roomData.p2) + '</em>';
if (!roomData.p1) {
matches = id.match(/[^0-9]([0-9]*)$/);
roomDesc = format + 'empty room ' + matches[1];
} else if (!roomData.p2) {
roomDesc = format + '<em class="p1">' + sanitize(roomData.p1) + '</em>';
}
roomListCode += '<div><a href="' + locPrefix + '' + id + '" onclick="selectTab(\'' + id + '\');return false">' + roomDesc + '</a></div>';
}
var code = '<img src="' + Tools.resourcePrefix + 'sprites/trainers/' + data.avatar + '.png" />';
if (roomListCode) {
roomListCode = '<div class="action-form">In rooms:<br /><div class="roomlist">' + roomListCode + '</div></div>';
}
if (data.ip || data.ips) {
var ips = data.ips || [data.ip];
// Mute and Ban buttons for auths
var banMuteBuffer = '';
var mygroup = me.users[me.userid].substr(0, 1);
if ([' ', '!', '#', '+'].indexOf(mygroup) === -1) {
banMuteBuffer += '<br /><br />';
if (me.users[userid].substr(0, 1) === '!') {
banMuteBuffer += '<button onclick="rooms[\'' + selfR.id + '\'].parseCommand(\'/buttonunmute ' + userid + '\');">Unmute</button>';
} else {
banMuteBuffer += '<button onclick="rooms[\'' + selfR.id + '\'].parseCommand(\'/buttonmute ' + userid + '\');">Mute</button>';
}
if (mygroup !== '%') {
banMuteBuffer += ' <button onclick="rooms[\'' + selfR.id + '\'].parseCommand(\'/buttonban ' + userid + '\');">Ban</button>';
banMuteBuffer += ' <button onclick="rooms[\'' + selfR.id + '\'].parseCommand(\'/buttonkick ' + userid + '\');">Kick</button>';
}
}
var ipbits = [];
for (var i = 0; i < ips.length; ++i) {
ipbits.push('<a href="http://www.geoiptool.com/en/?IP=' + ips[i] + '" target="iplookup">' + ips[i] + '</a>');
}
roomListCode = '<div class="action-form"><small>IP' + ((ips.length > 1) ? 's' : '') + ': ' + ipbits.join(', ') + '</small>' + banMuteBuffer + '</div>' + roomListCode;
}
$('#' + selfR.id + '-userrooms-' + userid).html(roomListCode);
$('#' + selfR.id + '-userdetails-' + userid).html(code);
} else if (data.command === 'roomlist') {
if (!$('#' + selfR.id + '-roomlist').length) return;
var roomListCode = '';
var i = 0;
for (var id in data.rooms) {
var roomData = data.rooms[id];
var matches = id.match(/^battle\-([a-z0-9]*[a-z])[0-9]*$/);
var format = (matches ? '<small>[' + matches[1] + ']</small><br />' : '');
var roomDesc = format + '<em class="p1">' + sanitize(roomData.p1) + '</em> <small class="vs">vs.</small> <em class="p2">' + sanitize(roomData.p2) + '</em>';
if (!roomData.p1) {
matches = id.match(/[^0-9]([0-9]*)$/);
roomDesc = format + 'empty room ' + matches[1];
} else if (!roomData.p2) {
roomDesc = format + '<em class="p1">' + sanitize(roomData.p1) + '</em>';
}
roomListCode += '<div><a href="' + locPrefix+id + '" onclick="selectTab(\'' + id + '\');return false">' + roomDesc + '</a></div>';
i++;
}
if (!roomListCode) {
roomListCode = 'No battles are going on right now.';
}
$('#' + selfR.id + '-roomlist').html('<div class="roomlist"><div><small>(' + i + ' battle' + (i == 1 ? '' : 's') + ')</small></div>' + roomListCode + '</div>');
} else if (data.command === 'savereplay') {
var id = data.id;
$.post(actionphp + '?act=uploadreplay', {
log: data.log,
id: data.id
}, function(data) {
if (data === 'success') {
overlay('replayuploaded', id);
} else {
overlay('message', "Error while uploading replay: "+data);
}
});
}
};
this.rooms = [];
this.updateMainTop = function (force) {
var text = '';
var challenge = null;
if (me.challengesFrom) {
for (var i in me.challengesFrom) {
challenge = me.challengesFrom[i];
break;
}
}
if (force) selfR.mainTopState = '';
selfR.notifying = !! challenge;
updateRoomList();
if (challenge) {
if (selfR.mainTopState === 'challenge-' + challenge.from) return;
selfR.mainTopState = 'challenge-' + challenge.from;
if (me.lastChallengeNotification !== challenge.from) {
notify({
type: 'challenge',
room: selfR.id,
user: (me.users[challenge.from] || challenge.from),
userid: challenge.from
});
me.lastChallengeNotification = challenge.from;
}
selfR.selectedFormat = toId(challenge.format);
text = '<div class="action-notify"><button class="closebutton" style="float:right;margin:-6px -10px 0 0" onclick="return rooms[\'' + selfR.id + '\'].formRejectChallenge(\'' + sanitize(challenge.from) + '\')"><i class="icon-remove-sign"></i></button>';
text += 'Challenge from: ' + (me.users[challenge.from] || challenge.from) + '<br /><label class="label">Format:</label> ' + sanitize(challenge.format) + '</br >';
text += '' + selfR.getTeamSelect(challenge.format) + '<br />';
text += '<button onclick="return rooms[\'' + selfR.id + '\'].formAcceptChallenge(\'' + sanitize(challenge.from) + '\')" id="' + selfR.id + '-gobutton"' + (selfR.goDisabled ? ' disabled="disabled"' : '') + '>Accept</button> <button onclick="return rooms[\'' + selfR.id + '\'].formRejectChallenge(\'' + sanitize(challenge.from) + '\')"><small>Reject</small></button></div>';
} else if (me.userForm) {
var userid = toUserid(me.userForm);
var name = (me.users[userid] || me.userForm);
var groupDetails = {
'~': "Administrator (~)",
'&': "Leader (&amp;)",
'@': "Moderator (@)",
'%': "Driver (%)",
'+': "Voiced (+)",
'!': "<span style='color:#777777'>Muted (!)</span>"
};
var group = groupDetails[name.substr(0, 1)];
if (group) name = name.substr(1);
if (selfR.mainTopState === 'userform-' + userid) return;
selfR.mainTopState = 'userform-' + userid;
if (me.userForm === '#lobby-rooms') {
text = '<div><button onclick="rooms[\'' + selfR.id + '\'].formCloseUserForm();return false"><i class="icon-chevron-left"></i> Back to lobby</button> <button onclick="rooms[\'' + selfR.id + '\'].send(\'/cmd roomlist\');return false"><i class="icon-refresh"></i> Refresh</button></div><div id="' + selfR.id + '-roomlist"><em>Loading...</em></div>';
selfR.send('/cmd roomlist');
} else {
text = '<div class="action-form"><button style="float:right;margin:-6px -10px 0 0" class="closebutton" onclick="return rooms[\'' + selfR.id + '\'].formCloseUserForm()"><i class="icon-remove-sign"></i></button>';
text += '<strong>' + sanitize(name) + '</strong><br />';
text += '<small>' + (group || '') + '</small><br />';
text += '<div id="' + selfR.id + '-userdetails-' + userid + '" style="height:85px"></div>';
if (userid === me.userid) {
text += '<button onclick="return rooms[\'' + selfR.id + '\'].formCloseUserForm()">Close</button></div>';
} else {
text += '<button onclick="$(\'#' + selfR.id + '-challengeform\').toggle();return false"><strong>Challenge</strong></button> <button onclick="rooms[\'' + selfR.id + '\'].popupOpen(\'' + userid + '\');rooms[\'' + selfR.id + '\'].formCloseUserForm();return false"><strong>PM</strong></button> <button onclick="return rooms[\'' + selfR.id + '\'].formCloseUserForm()">Close</button>';
text += '</div><div class="action-form" style="display:none" id="' + selfR.id + '-challengeform">';
text += selfR.getFormatSelect('challenge') + '<br />';
text += '' + selfR.getTeamSelect(selfR.selectedFormat) + '<br />';
text += '<button onclick="return rooms[\'' + selfR.id + '\'].formMakeChallenge(\'' + sanitize(userid) + '\')" id="' + selfR.id + '-gobutton"' + (selfR.goDisabled ? ' disabled="disabled"' : '') + '><strong>Make challenge</strong></button> <button onclick="$(\'#' + selfR.id + '-challengeform\').hide();return false">Cancel</button></div>';
}
text += '<div id="' + selfR.id + '-userrooms-' + userid + '"></div>';
selfR.send('/cmd userdetails '+userid);
}
} else if (me.challengeTo) {
if (selfR.mainTopState === 'challenging') return;
selfR.mainTopState = 'challengeto';
var teamname = 'Random team';
if (selectedTeam >= 0) teamname = teams[selectedTeam].name;
text = '<div class="action-waiting">Challenging: ' + (me.users[me.challengeTo.to] || me.challengeTo.to) + '<br />Format: ' + me.challengeTo.format + '<br />Team: ' + teamname + '<br /><button onclick="return rooms[\'' + selfR.id + '\'].formCloseUserForm(\'' + sanitize(me.challengeTo.to) + '\')"><small>Cancel</small></button></div>';
} else if (selfR.me.searching) {
if (selfR.mainTopState === 'searching') return;
selfR.mainTopState = 'searching';
text = '<div class="action-waiting">Format: ' + selfR.me.searching.format + '<br />Searching...<br /><button onclick="return rooms[\'' + selfR.id + '\'].formSearchBattle(false)"><small>Cancel</small></button></div>';
} else {
var roomListCode = '';
for (var i=selfR.rooms.length-1; i>=0; i--) {
var roomData = selfR.rooms[i];
if (!roomListCode) roomListCode += '<h3>Watch battles</h3>';
var roomDesc = '<small>[' + Tools.getEffect(roomData.format).name + ']</small><br /><em class="p1">' + sanitize(roomData.p1) + '</em> <small class="vs">vs.</small> <em class="p2">' + sanitize(roomData.p2) + '</em>';
roomListCode += '<div><a href="' + locPrefix + '' + roomData.id + '" onclick="selectTab(\'' + roomData.id + '\');return false">' + roomDesc + '</a></div>';
}
if (roomListCode) roomListCode += '<button onclick="rooms[\'' + selfR.id + '\'].formChallenge(\'#lobby-rooms\');return false">All battles &rarr;</button>';
var searcherText = '';
if (selfR.searcher) {
searcherText = '<small>There ' + (selfR.searcher === 1 ? 'is' : 'are') + ' ' + selfR.searcher + ' other ' + (selfR.searcher === 1 ? 'person' : 'people') + ' searching.</small>';
}
if (selfR.mainTopState === 'search-'+selfR.selectedFormat+(!selfR.goDisabled?'-nogo':'')) {
$('#' + selfR.id + '-searcher').html(searcherText);
$('#' + selfR.id + '-roomlist').html(roomListCode);
return;
}
selfR.mainTopState = 'search-'+selfR.selectedFormat+(!selfR.goDisabled?'-nogo':'');
text = '<div class="action-default">';
text += '' + selfR.getFormatSelect('search') + '<br />';
text += '' + selfR.getTeamSelect(selfR.selectedFormat) + '<br />';
if (selfR.goDisabled)
{
text += '</select><br /><button class="mainbutton disabled" onclick="overlay(\'message\',\'You need to make a team in the teambuilder.\');return false" id="'+selfR.id+'-gobutton"><strong><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon('Meloetta-Pirouette')+'"></span>Look for a battle</strong></button></div>';
}
else
{
text += '</select><br /><button class="mainbutton" onclick="return rooms[\''+selfR.id+'\'].formSearchBattle(true)" id="'+selfR.id+'-gobutton"><strong><span class="pokemonicon" style="display:inline-block;vertical-align:middle;'+Tools.getIcon('Meloetta-Pirouette')+'"></span>Look for a battle</strong></button></div>';
}
text += '<span id="' + selfR.id + '-searcher">' + searcherText + '</span><br />';
text += '<div id="' + selfR.id + '-roomlist" class="roomlist">' + roomListCode + '</div>';
}
selfR.mainTopElem.html(text);
if (!challenge) {
me.lastChallengeNotification = '';
}
};
this.debounceUpdateTimeout = null;
this.debounceUpdateQueued = false;
this.debounceUpdate = function() {
if (!selfR.debounceUpdateTimeout) {
selfR.updateMainTop();
selfR.debounceUpdateQueued = false;
selfR.debounceUpdateTimeout = setTimeout(selfR.debounceUpdateEnd, 3000);
} else {
selfR.debounceUpdateQueued = true;
}
};
this.debounceUpdateEnd = function() {
selfR.debounceUpdateTimeout = null;
if (selfR.debounceUpdateQueued) {
selfR.debounceUpdate();
}
};
this.timeEvent = (function() {
var data = [];
var last;
var current;
var starts = [];
return {
start: function() {
last = +new Date();
starts.push(last);
data.push(current = [0]);
},
end: function() {
},
checkpoint: function() {
var now = +new Date();
current.push(now - last);
last = now;
},
getResults: function() {
var average = [];
var total = [];
var trials = data.length;
for (var i = 0; i < data[0].length; ++i) {
var sum = 0;
for (var j = 0; j < trials; ++j) {
sum += data[j][i];
}
average[i] = Math.round(sum / trials * 10) / 10;
total[i] = sum;
}
var intervals = [];
for (var i = 0; i < starts.length - 1; ++i) {
intervals[i] = starts[i + 1] - starts[i];
}
return {average: average, total: total, intervals: intervals};
}
};
})();
this.userList = {
ranks: {
'~': 2,
'&': 2,
'@': 1,
'%': 1,
'+': 1,
' ': 0,
'!': 0,
'#': 0
},
rankOrder: {
'~': 1,
'&': 2,
'@': 3,
'%': 4,
'+': 5,
' ': 6,
'!': 7,
'#': 8
},
updateUserCount: function() {
$('#usercount-users').html(selfR.userCount.users || '0');
},
updateCurrentUser: function() {
$('.userlist > .cur').attr('class', '');
$('#userlist-user-' + me.userForm).attr('class', 'cur');
},
add: function(userid) {
var users = $('.userlist').children();
// Determine where to insert the user using a binary search.
var left = 0;
var right = users.length - 1;
while (right >= left) {
var mid = Math.floor((right - left) / 2 + left);
var cmp = this.elemComparator(users[mid], userid);
if (cmp < 0) {
left = mid + 1;
} else if (cmp > 0) {
right = mid - 1;
} else {
// The user is already in the list.
return;
}
}
$(this.constructItem(userid)).insertAfter($(users[right]));
},
remove: function(userid) {
$('#userlist-user-' + userid).remove();
},
buttonOnClick: function(userid) {
if (me.named) {
return selfR.formChallenge(userid);
}
return selfR.formRename();
},
constructItem: function(userid) {
var group = me.users[userid].substr(0, 1);
var text = '';
// Sanitising the `userid` here is probably unnecessary, because
// IDs can't contain anything dangerous.
text += '<li' + (me.userForm === userid ? ' class="cur"' : '') + ' id="userlist-user-' + sanitize(userid) + '">';
text += '<button class="userbutton" onclick="return rooms.lobby.userList.buttonOnClick(\'' + sanitize(userid) + '\')">';
text += '<em class="group' + (this.ranks[group]===2 ? ' staffgroup' : '') + '">' + sanitize(group) + '</em>';
if (group === '~' || group === '&') {
text += '<strong><em style="' + hashColor(userid) + '">' + sanitize(me.users[userid].substr(1)) + '</em></strong>';
} else if (group === '%' || group === '@') {
text += '<strong style="' + hashColor(userid) + '">' + sanitize(me.users[userid].substr(1)) + '</strong>';
} else {
text += '<span style="' + hashColor(userid) + '">' + sanitize(me.users[userid].substr(1)) + '</span>';
}
text += '</button>';
text += '</li>';
return text;
},
elemComparator: function(elem, userid) {
var id = elem.id;
switch (id) {
case 'userlist-users':
return -1; // `elem` comes first
case 'userlist-empty':
case 'userlist-unregistered':
case 'userlist-guests':
return 1; // `userid` comes first
}
// extract the portion of the `id` after 'userlist-user-'
var elemuserid = id.substr(14);
return this.comparator(elemuserid, userid);
},
comparator: function(a, b) {
if (a === b) return 0;
var aRank = this.rankOrder[me.users[a] ? me.users[a].substr(0, 1) : ' '];
var bRank = this.rankOrder[me.users[b] ? me.users[b].substr(0, 1) : ' '];
if (aRank !== bRank) return aRank - bRank;
return (a > b ? 1 : -1);
},
noNamedUsersOnline: '<li id="userlist-empty">No named users online</li>',
updateNoUsersOnline: function() {
var elem = $('#userlist-empty');
if ($("[id^=userlist-user-]").length === 0) {
if (elem.length === 0) {
var guests = $('#userlist-guests');
if (guests.length === 0) {
$('.userlist').append($(this.noNamedUsersOnline));
} else {
guests.before($(this.noNamedUsersOnline));
}
}
} else {
elem.remove();
}
},
construct: function() {
var text = '';
text += '<ul class="userlist">';
text += '<li id="userlist-users" style="text-align:center;padding:2px 0"><small><span id="usercount-users">' + (selfR.userCount.users || '0') + '</span> users online:</small></li>';
var users = [];
if (me.users) {
var self = this;
users = Object.keys(me.users).sort(function(a, b) {
return self.comparator(a, b);
});
}
for (var i=0, len=users.length; i<users.length; i++) {
var userid = users[i];
text += this.constructItem(userid);
}
if (!users.length) {
text += this.noNamedUsersOnline;
}
if (selfR.userCount.unregistered) {
text += '<li id="userlist-unregistered" style="height:auto;padding-top:5px;padding-bottom:5px">';
text += '<span style="font-size:10pt;display:block;text-align:center;padding-bottom:5px;font-style:italic">Due to lag, <span id="usercount-unregistered">' + selfR.userCount.unregistered + '</span> unregistered users are hidden.</span>';
text += ' <button' + (me.challengeTo ? ' disabled="disabled"' : ' onclick="var gname=prompt(\'Challenge who?\');if (gname) rooms[\'' + selfR.id + '\'].formChallenge(gname);return false"') + '>Challenge an unregistered user</button>';
text += '<div style="clear:both"></div>';
text += '</li>';
}
if (selfR.userCount.guests) {
text += '<li id="userlist-guests" style="text-align:center;padding:2px 0"><small>(<span id="usercount-guests">' + selfR.userCount.guests + '</span> guest' + (selfR.userCount.guests == 1 ? '' : 's') + ')</small></li>';
}
text += '</ul>';
selfR.mainBottomElem.html(text);
}
};
this.updateMe = function () {
if (selfR.meIdent.name !== me.name || selfR.meIdent.named !== me.named) {
if (me.named) {
selfR.chatAddElem.html('<form onsubmit="return false" class="chatbox"><label style="' + hashColor(me.userid) + '">' + sanitize(me.name) + ':</label> <textarea class="textbox" type="text" size="70" autocomplete="off" onkeypress="return rooms[\'' + selfR.id + '\'].formKeyPress(event)"></textarea></form>');
selfR.chatboxElem = selfR.chatAddElem.find('textarea');
// The keypress event does not capture tab, so use keydown.
selfR.chatboxElem.keydown(this.formKeyDown);
selfR.chatboxElem.autoResize({
animateDuration: 100,
extraSpace: 0
});
selfR.chatboxElem.focus();
} else {
selfR.chatAddElem.html('<form><button onclick="return rooms[\'' + selfR.id + '\'].formRename()">Join chat</button></form>');
}
selfR.meIdent.name = me.name;
selfR.meIdent.named = me.named;
}
};
// Key press in the chat textbox.
this.formKeyPress = function (e) {
hideTooltip();
if (e.keyCode === 13) { // Enter
var text;
if ((text = selfR.chatboxElem.val())) {
selfR.tabComplete.reset();
selfR.chatHistory.push(text);
text = selfR.parseCommand(text);
if (text) {
selfR.send(text);
}
selfR.chatboxElem.val('');
}
return false;
}
return true;
};
this.formKeyDown = function (e) {
hideTooltip();
var chatbox = $(e.delegateTarget);
switch (e.keyCode) {
case 9: // Tab key
// don't do anything if a modifier is pressed
if (e.shiftKey) return true;
if (e.ctrlKey) return true;
// We don't want to tab away from this box.
e.preventDefault();
// Don't tab complete at the start of the text box.
var idx = chatbox.prop('selectionStart');
if (idx === 0) return true;
var text = chatbox.val();
if (idx === selfR.tabComplete.cursor) {
// The user is cycling through the candidate names.
if (++selfR.tabComplete.index >= selfR.tabComplete.candidates.length) {
selfR.tabComplete.index = 0;
}
} else {
// This is a new tab completion.
// There needs to be non-whitespace to the left of the cursor.
var m = /^(.*?)([^ ]*)$/.exec(text.substr(0, idx));
if (!m) return true;
selfR.tabComplete.prefix = m[1];
var idprefix = toId(m[2]);
var candidates = [];
for (var i in me.users) {
if (!me.users.hasOwnProperty(i)) continue;
if (!(typeof i === 'string')) continue;
if (i.substr(0, idprefix.length) !== idprefix) continue;
candidates.push(i);
}
// Sort by most recent to speak in the chat, or, in the case of a tie,
// in alphabetical order.
candidates.sort(function(a, b) {
var aidx = selfR.userActivity.indexOf(a);
var bidx = selfR.userActivity.indexOf(b);
if (aidx !== -1) {
if (bidx !== -1) {
return bidx - aidx;
}
return -1; // a comes first
} else if (bidx != -1) {
return 1; // b comes first
}
return (a < b) ? -1 : 1; // alphabetical order
});
selfR.tabComplete.candidates = candidates;
selfR.tabComplete.index = 0;
}
// Substitute in the tab-completed name.
var substituteUserId = selfR.tabComplete.candidates[selfR.tabComplete.index];
var name = me.users[substituteUserId].substr(1);
chatbox.val(selfR.tabComplete.prefix + name + text.substr(idx));
var pos = selfR.tabComplete.prefix.length + name.length;
chatbox[0].setSelectionRange(pos, pos);
selfR.tabComplete.cursor = pos;
return true;
case 38: // Up key
// don't do anything if a modifier is pressed
if (e.shiftKey) return true;
if (e.ctrlKey) return true;
e.preventDefault();
if (selfR.chatHistory.index > 0) {
var line = chatbox.val();
if (selfR.chatHistory.index === selfR.chatHistory.lines.length) {
if (line !== '') {
selfR.chatHistory.push(line);
--selfR.chatHistory.index;
}
} else {
selfR.chatHistory.lines[selfR.chatHistory.index] = line;
}
chatbox.val(selfR.chatHistory.lines[--selfR.chatHistory.index]);
}
return true;
case 40: // Down key
// don't do anything if a modifier is pressed
if (e.shiftKey) return true;
if (e.ctrlKey) return true;
e.preventDefault();
if (selfR.chatHistory.index === selfR.chatHistory.lines.length) {
var line = chatbox.val();
if (line !== '') {
selfR.chatHistory.push(line);
chatbox.val('');
}
} else if (selfR.chatHistory.index === selfR.chatHistory.lines.length - 1) {
selfR.chatHistory.lines[selfR.chatHistory.index] = chatbox.val();
chatbox.val('');
++selfR.chatHistory.index;
} else {
selfR.chatHistory.lines[selfR.chatHistory.index] = chatbox.val();
chatbox.val(selfR.chatHistory.lines[++selfR.chatHistory.index]);
}
return true;
}
return true;
};
this.formRename = function () {
overlay('rename');
return false;
};
this.formSearchBattle = function (search, name) {
requestNotify();
if (!search) {
selfR.send('/search');
} else {
if (!me.named) {
overlay('rename');
return false;
}
var format = $('#' + selfR.id + '-format').val();
selectTeam($('#' + selfR.id + '-team').val());
selfR.send('/search '+toId(format));
}
return false;
};
this.formChallenge = function (user) {
me.userForm = user;
selfR.userList.updateCurrentUser();
selfR.updateMainTop();
$(window).scrollTop(51);
return false;
};
this.getFormatSelect = function (selectType) {
var text = '';
text += '<label class="label">Format:</label> <select id="' + selfR.id + '-format" onchange="return rooms[\'' + selfR.id + '\'].formSelectFormat()">';
var curSection = '';
for (var i in exports.BattleFormats) {
var format = exports.BattleFormats[i];
var selected = false;
if (format.effectType !== 'Format') continue;
if (selectType && !format[selectType + 'Show']) continue;
if (!selfR.selectedFormat) {
if (selectType) selected = format[selectType + 'Default'];
if (selected && !format.team && !teams.length) selected = false;
if (selected) {
selfR.selectedFormat = i;
}
} else {
selected = (selfR.selectedFormat === i);
}
var details = '';
if (format.rated && selectType === 'search') {
//details = ' (rated)';
}
if (format.section && format.section !== curSection) {
if (curSection) text += '</optgroup>';
text += '<optgroup label="'+sanitize(format.section)+'">';
curSection = format.section;
}
if (!format.section && curSection) text += '</optgroup>';
text += '<option value="' + sanitize(i) + '"' + (selected ? ' selected="selected"' : '') + '>' + sanitize(format.name) + details + '</option>';
}
if (curSection) text += '</optgroup>';
text += '</select>';
return text;
};
this.getTeamSelect = function (format) {
if (!format) format = selfR.selectedFormat;
var formatid = '';
if (!format.name) {
formatid = format;
format = exports.BattleFormats[toId(format)];
if (!format) format = {id:formatid, name:formatid};
}
selfR.goDisabled = false;
if (format.team) {
var gobutton = $('#' + selfR.id + '-gobutton');
if (gobutton.length) gobutton[0].disabled = false;
return '<span id="' + selfR.id + '-teamselect"><label class="label">Team:</label> Random Team</span>';
} else {
var text = '<span id="' + selfR.id + '-teamselect"><label class="label">Team:</label> <select id="' + selfR.id + '-team" onchange="return rooms[\'' + selfR.id + '\'].formSelectTeam()">';
if (!teams.length) {
text += '<option value="0">You have no teams</option>';
selfR.goDisabled = true;
} else {
var teamFormat = (format.teambuilderFormat || (format.isTeambuilderFormat ? formatid : false));
for (var i = 0; i < teams.length; i++) {
var selected = (i === selfR.selectedTeam);
if ((!teams[i].format && !teamFormat) || teams[i].format === teamFormat) {
text += '<option value="' + i + '"' + (selected ? ' selected="selected"' : '') + '>' + sanitize(teams[i].name) + '</option>';
}
}
text += '<optgroup label="Other teams">';
for (var i = 0; i < teams.length; i++) {
if ((!teams[i].format && !teamFormat) || teams[i].format === teamFormat) continue;
text += '<option value="' + i + '">' + sanitize(teams[i].name) + '</option>';
}
text += '</optgroup>';
}
if (format.canUseRandomTeam) {
text += '<option value="-1">Random Team</option>';
}
text += '</select></span>';
var gobutton = $('#' + selfR.id + '-gobutton');
if (gobutton.length) gobutton[0].disabled = selfR.goDisabled;
return text;
}
};
this.formSelectTeam = function () {
var i = parseInt($('#' + selfR.id + '-team').val());
if (i === 0 && !teams.length) selfR.goDisabled = true;
else selfR.goDisabled = false;
selfR.selectedTeam = i;
selfR.updateMainTop();
};
this.formSelectFormat = function (format) {
selfR.selectedFormat = $('#' + selfR.id + '-format').val();
$('#' + selfR.id + '-teamselect').replaceWith(selfR.getTeamSelect());
selfR.updateMainTop();
};
this.formMakeChallenge = function (userid) {
requestNotify();
var format = $('#' + selfR.id + '-format').val();
me.userForm = '';
selfR.userList.updateCurrentUser();
selectTeam($('#' + selfR.id + '-team').val());
selfR.send('/challenge '+userid+', '+format);
return false;
};
this.formCloseUserForm = function (userid) {
if (me.userForm) {
me.userForm = '';
selfR.userList.updateCurrentUser();
selfR.updateMainTop();
return false;
}
selfR.updateMainTop();
selfR.send('/cancelchallenge '+userid);
return false;
};
this.formAcceptChallenge = function (userid) {
requestNotify();
selectTeam($('#' + selfR.id + '-team').val());
selfR.send('/accept '+userid);
return false;
};
this.formRejectChallenge = function (userid) {
selfR.send('/reject '+userid);
return false;
};
}
function updateMe() {
var notifybutton = '';
/* if (needEnableNotify())
{
notifybutton = '<button onclick="return requestNotify()"><strong style="color:red">ENABLE NOTIFICATIONS</strong></button> ';
} */
//var mutebutton = ' <button onclick="return formMute()" style="height:20px;vertical-align:middle;">' + (me.mute ? '<img src="/fx/mute.png" width="18" height="18" alt="Unmute" />' : '<img src="/fx/sound.png" width="18" height="18" alt="Mute" />') + '</button>';
var mutebutton = ' <button onclick="return formMute()" style="width:30px;font-size:9pt">' + (me.mute ? '<i class="icon-volume-off" title="Unmute"></i>' : '<i class="icon-volume-up" title="Mute"></i>') + '</button>';
if (me.named) {
$('#userbar').html(notifybutton + '<i class="icon-user" style="color:#779EC5"></i> ' + sanitize(me.name) + mutebutton + ' <button onclick="return rooms[\'lobby\'].formRename()" style="font-size:9pt">Change name</button>');
$.cookie('showdown_username', me.name, {
expires: 14
});
Tools.postCrossDomainMessage({username: me.name});
} else {
$('#userbar').html(notifybutton + '<i class="icon-user" style="color:#999"></i> ' + sanitize(me.name) + mutebutton + ' <button onclick="return rooms[\'lobby\'].formRename()" style="font-size:9pt">Choose name</button>');
}
$('#userbar').prepend('<small><a href="http://pokemonshowdown.com/" target="_blank">Website</a> &nbsp; <a href="http://pokemonshowdown.com/rules" target="_blank">Rules</a> &nbsp; <a href="http://www.smogon.com/forums/showthread.php?t=3469932" target="_blank" onclick="_gaq.push([\'_trackEvent\', \'Report bug link\', Config.serverid]); return true;">Report bug</a> &nbsp; </small> ');
if (rooms.lobby) {
rooms.lobby.updateMe();
rooms.lobby.debounceUpdate();
}
}
function formMute() {
me.mute = !me.mute;
Tools.prefs.set('mute', !!me.mute, true);
if (curRoom.battle) {
curRoom.battle.setMute(me.mute);
}
updateMe();
}
function updateRoomList() {
var code = '';
if (!curRoom) curRoom = rooms.lobby;
code += '<div><a id="tabtab-lobby" class="tab' + (curRoom.id === 'lobby' ? ' cur' : '') + (rooms.lobby && rooms.lobby.notifying ? ' notifying' : '') + '" href="' + locPrefix + '" onclick="selectTab(\'lobby\'); return false"><i class="icon-comments-alt"></i> Lobby</a>';
code += '<a id="tabtab-teambuilder"' + (curRoom.id === 'teambuilder' ? ' class="cur"' : '') + ' href="' + locPrefix + 'teambuilder" onclick="selectTab(\'teambuilder\', event);return false"><i class="icon-edit"></i> Teambuilder</a>';
code += '<a id="tabtab-ladder"' + (curRoom.id === 'ladder' ? ' class="cur"' : '') + ' href="' + locPrefix + 'ladder" onclick="selectTab(\'ladder\');return false"><i class="icon-list-ol"></i> Ladder</a></div>';
var shownRooms = {
lobby: true,
teambuilder: true,
ladder: true
};
var yourRooms = false;
for (var id in rooms) {
if (shownRooms[id]) continue;
shownRooms[id] = true;
if (!yourRooms) code += '<div><small>Your rooms</small></div>';
yourRooms = true;
var roomDesc = id;
var roomName = (id.substr(0, 7) === 'battle-' ? id.substr(7) : id);
closesize = 'close2';
if (roomName) {
roomDesc = '' + roomName + '<small>(inactive)</small>';
closesize = 'close0';
}
if (rooms[id].battle) {
var p1 = '';
var p2 = '';
if (rooms[id].battle.p1 && rooms[id].battle.p1.initialized) p1 = rooms[id].battle.p1.name;
if (rooms[id].battle.p2 && rooms[id].battle.p2.initialized) p2 = rooms[id].battle.p2.name;
if (p1 && p2) {
roomDesc = '<em class="p1">' + sanitize(p1) + '</em> <small class="vs">vs.</small> <em class="p2">' + sanitize(p2) + '</em>';
closesize = 'close3';
} else if (p1) {
roomDesc = '<em class="p1">' + sanitize(p1) + '</em> <small>(inactive)</small>';
rooms[id].notifying = false;
closesize = 'close0';
} else if (p2) {
roomDesc = '<em class="p1">' + sanitize(p2) + '</em> <small>(inactive)</small>';
rooms[id].notifying = false;
closesize = 'close0';
} else {
roomDesc = '' + roomName + '<small>(empty)</small>';
rooms[id].notifying = false;
closesize = 'close0';
}
}
code += '<div><a id="tabtab-' + id + '" class="tab battletab' + (curRoom.id === id ? ' cur' : '') + (rooms[id] && rooms[id].notifying ? ' notifying' : '') + '" href="' + locPrefix + '' + id + '" onclick="selectTab(\'' + id + '\');return false">' + roomDesc + '</a><span onclick="leaveTab(\'' + id + '\')" class="close ' + closesize + '"></span></div>';
}
$('#leftbar').html(code);
$('#inline-nav').html('<h3>Your tabs</h3>' + code.replace(/ id="[^"]*"/g, ''));
}
var widthClass = 'normal-layout';
var heightClass = 'normal-height';
var fixedWidth = true;
function updateResize() {
if (window.screen && screen.width && screen.width >= 640) {
if (fixedWidth) {
document.getElementById('viewport').setAttribute('content','width=device-width');
fixedWidth = false;
}
} else {
if (!fixedWidth) {
document.getElementById('viewport').setAttribute('content','width=640');
fixedWidth = true;
}
}
if ($(window).width() < 740) {
$('body').prop('class', 'tiny-layout');
widthClass = 'tiny-layout';
} else if ($(window).width() < 870) {
$('body').prop('class', 'small-layout');
widthClass = 'small-layout';
} else if ($(window).width() < 1420) {
$('body').prop('class', 'normal-layout');
widthClass = 'normal-layout';
} else {
$('body').prop('class', 'huge-layout');
widthClass = 'huge-layout';
}
if ($(window).height() < 575) {
$('body').addClass('tiny-height');
heightClass = 'tiny-height';
} else {
$('body').addClass('normal-height');
heightClass = 'normal-height';
}
updateLobbyChat();
}
function tooltipAttrs(thing, type, ownHeight, isActive) {
return ' onmouseover="return showTooltip(\'' + sanitize(''+thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ', ' + (isActive ? 'true' : 'false') + ')" onmouseout="return hideTooltip()" onmouseup="hideTooltip()"';
}
function showTooltip(thing, type, elem, ownHeight, isActive) {
var offset = {
left: 150,
top: 500
};
if (elem) offset = $(elem).offset();
var x = offset.left - 25;
if (elem) {
if (ownHeight) offset = $(elem).offset();
else offset = $(elem).parent().offset();
}
var y = offset.top - 15;
if (widthClass === 'tiny-layout') {
if (x > 360) x = 360;
}
if (y < 140) y = 140;
$('#tooltipwrapper').css({
left: x,
top: y
});
var text = '';
switch (type) {
case 'move':
var move = Tools.getMove(thing);
if (!move) return;
var basePower = move.basePower;
if (!basePower) basePower = '&mdash;';
var accuracy = move.accuracy;
if (!accuracy || accuracy === true) accuracy = '&mdash;';
else accuracy = '' + accuracy + '%';
text = '<div class="tooltipinner"><div class="tooltip">';
text += '<h2>' + move.name + '<br />'+Tools.getTypeIcon(move.type)+' <img src="' + Tools.resourcePrefix + 'sprites/categories/' + move.category + '.png" alt="' + move.category + '" /></h2>';
text += '<p>Base power: ' + basePower + '</p>';
text += '<p>Accuracy: ' + accuracy + '</p>';
if (move.desc) {
text += '<p class="section">' + move.desc + '</p>';
}
text += '</div></div>';
break;
case 'pokemon':
var pokemon = curRoom.battle.getPokemon(thing);
if (!pokemon) return;
//fallthrough
case 'sidepokemon':
if (!pokemon) pokemon = curRoom.battle.mySide.pokemon[parseInt(thing)];
text = '<div class="tooltipinner"><div class="tooltip">';
text += '<h2>' + pokemon.getFullName() + (pokemon.level !== 100 ? ' <small>L' + pokemon.level + '</small>' : '') + '<br />';
var types = pokemon.types;
var template = pokemon;
if (pokemon.volatiles.transform && pokemon.volatiles.formechange) {
template = Tools.getTemplate(pokemon.volatiles.formechange[2]);
types = template.types;
text += '<small>(Transformed into '+pokemon.volatiles.formechange[2]+')</small><br />';
} else if (pokemon.volatiles.formechange) {
template = Tools.getTemplate(pokemon.volatiles.formechange[2]);
types = template.types;
text += '<small>(Forme: '+pokemon.volatiles.formechange[2]+')</small><br />';
}
if (pokemon.volatiles.typechange) {
text += '<small>(Type changed)</small><br />';
types = [pokemon.volatiles.typechange[2]];
}
text += Tools.getTypeIcon(types[0]);
if (types[1]) {
text += ' '+Tools.getTypeIcon(types[1]);
}
text += '</h2>';
var exacthp = '';
if (pokemon.maxhp != 100 && pokemon.maxhp != 1000 && pokemon.maxhp != 48) exacthp = ' ('+pokemon.hp+'/'+pokemon.maxhp+')';
if (pokemon.maxhp == 48 && isActive) exacthp = ' <small>('+pokemon.hp+'/'+pokemon.maxhp+' pixels)</small>';
text += '<p>HP: ' + Math.round(100 * pokemon.hp / pokemon.maxhp) + '%'+exacthp+(pokemon.status?' <span class="status '+pokemon.status+'">'+pokemon.status.toUpperCase()+'</span>':'')+'</p>';
if (!pokemon.baseAbility && (!pokemon.ability || pokemon.ability.substr(0, 2) === '??')) {
text += '<p>Possible abilities: ' + Tools.getAbility(template.abilities['0']).name;
if (template.abilities['1']) text += ', ' + Tools.getAbility(template.abilities['1']).name;
if (template.abilities['DW']) text += ', ' + Tools.getAbility(template.abilities['DW']).name;
text += '</p>';
} else if (pokemon.ability) {
text += '<p>Ability: ' + Tools.getAbility(pokemon.ability).name + '</p>';
} else if (pokemon.baseAbility) {
text += '<p>Ability: ' + Tools.getAbility(pokemon.baseAbility).name + '</p>';
}
if (pokemon.item) {
text += '<p>Item: ' + Tools.getItem(pokemon.item).name + '</p>';
}
if (pokemon.moves && pokemon.moves.length && (!isActive || isActive === 'foe')) {
text += '<p class="section">';
for (var i = 0; i < pokemon.moves.length; i++) {
var name = Tools.getMove(pokemon.moves[i]).name;
text += '&#8901; ' + name + '<br />';
}
text += '</p>';
}
text += '</div></div>';
break;
}
$('#tooltipwrapper').html(text);
return true;
}
function hideTooltip() {
$('#tooltipwrapper').html('');
return true;
}
var initialized = false;
var socketInit = null;
$(window).resize(updateResize);
var changeState = function () {};
var loc = 'lobby';
if (document.location.pathname.substr(0, locPrefix.length) === locPrefix) {
loc = document.location.pathname.substr(locPrefix.length);
if (!loc || loc === 'test.html' || loc === 'temp.html' || loc.substr(loc.length-15) === 'testclient.html') loc = 'lobby';
}
if (window.history && history.pushState) {
// HTML5 history
changeState = function (newLoc) {
if (!initialized) return;
var urlLoc = newLoc;
if (urlLoc === 'lobby') urlLoc = '';
if (document.location.pathname !== locPrefix + urlLoc) {
try {
history.pushState(null, null, locPrefix + urlLoc);
} catch (e) {
// Throws insecure operation when running on local filesystem.
}
}
loc = newLoc;
};
window.onpopstate = function (e) {
if (document.location.pathname.substr(0, locPrefix.length) === locPrefix) {
loc = document.location.pathname.substr(locPrefix.length);
if (!loc || loc === 'test.html' || loc === 'temp.html' || loc.substr(loc.length-15) === 'testclient.html') loc = 'lobby';
if (!socket) {
return; // haven't even initted yet
}
selectTab(loc);
}
};
}
var notify = function () {};
var requestNotify = function () {};
var dismissNotify = function () {};
var needEnableNotify = function () {
return false;
};
var activeNotification = null;
var activeNotificationData = null;
window.focused = true;
$(window).focus(function () {
window.focused = true;
dismissNotify();
});
$(window).click(function () {
window.focused = true;
dismissNotify();
});
$(window).blur(function () {
window.focused = false;
});
var favicon = {
// -- "PUBLIC" ----------------------------------------------------------------
defaultPause: 500,
change: function (iconURL, optionalDocTitle) {
clearTimeout(this.loopTimer);
if (optionalDocTitle) {
document.title = optionalDocTitle;
}
this.addLink(iconURL, true);
},
animate: function (iconSequence, optionalDelay) {
this.preloadIcons(iconSequence);
this.iconSequence = iconSequence;
this.sequencePause = (optionalDelay) ? optionalDelay : this.defaultPause;
favicon.index = 0;
favicon.change(iconSequence[0]);
this.loopTimer = setInterval(function () {
favicon.index = (favicon.index + 1) % favicon.iconSequence.length;
favicon.addLink(favicon.iconSequence[favicon.index], false);
}, favicon.sequencePause);
},
// -- "PRIVATE" ---------------------------------------------------------------
loopTimer: null,
preloadIcons: function (iconSequence) {
var dummyImageForPreloading = document.createElement("img");
for (var i = 0; i < iconSequence.length; i++) {
dummyImageForPreloading.src = iconSequence[i];
}
},
addLink: function (iconURL) {
var link = document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
link.href = iconURL;
this.removeLinkIfExists();
this.docHead.appendChild(link);
},
removeLinkIfExists: function () {
var links = this.docHead.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.type == "image/x-icon" && link.rel == "shortcut icon") {
this.docHead.removeChild(link);
return; // Assuming only one match at most.
}
}
},
docHead: document.getElementsByTagName("head")[0]
}
{
// HTML5 notifications
if (window.Notification) {
needEnableNotify = function () {
if (Notification.permissionLevel) return (Notification.permissionLevel() !== 'granted');
if (window.webkitNotifications) return (window.webkitNotifications.checkPermission() != 0);
return false;
};
requestNotify = function () {
/* if (Notification.permissionLevel && Notification.requestPermission) {
if (Notification.permissionLevel() !== 'granted') {
try {
Notification.requestPermission();
} catch (e) {};
}
return false;
} */
/* if (window.webkitNotifications && window.webkitNotifications.requestPermission && window.webkitNotifications.checkPermission() != 0) {
webkitNotifications.requestPermission();
} */
return false;
};
dismissNotify = function () {
favicon.change(Tools.resourcePrefix + 'favicon.ico');
if (activeNotification) {
activeNotification.cancel();
activeNotification = null;
activeNotificationData = null;
}
};
notify = function (data) {
if (window.focused) return;
favicon.animate([Tools.resourcePrefix + 'favicon-notify.ico', Tools.resourcePrefix + 'favicon-notify2.ico']);
if (needEnableNotify()) {
requestNotify();
} else {
var message = 'Something has happened!';
switch (data.type) {
case 'challenge':
message = ""+data.user+" has challenged you to a battle!";
break;
case 'highlight':
message = 'You have been highlighted by ' + data.user + '!';
break;
case 'pm':
message = 'You have received a PM from ' + data.user + '!';
break;
case 'yourMove':
case 'yourSwitch':
message = "It's your move in your battle against "+data.user+".";
break;
}
//var notification = window.webkitNotifications.createHTMLNotification('http://play.pokemonshowdown.com/notification.php?type=' + data.type + '&person=' + encodeURIComponent(data.user) + '&personid=' + data.userid + '&room=' + data.room)
var notification = new Notification("Pokemon Showdown", {
iconUrl: "/favicon-notify.gif",
body: message,
tag: data.type+'-'+data.room+'-'+data.user,
onclose: function (event) {
window.focus();
}
});
notification.show();
dismissNotify();
activeNotification = notification;
activeNotificationData = data;
}
};
} else if (window.macgap) {
// MacGap notifications! :o
notify = function (data) {
if (window.focused) return;
var message = '';
switch (data.type) {
case 'challenge':
macgap.growl.notify({
title: "Challenged!",
content: ""+data.user+" has challenged you to a battle!"
});
break;
case 'highlight':
macgap.growl.notify({
title: 'Highlighted!',
content: 'You have been highlighted by ' + data.user + '!'
});
break;
case 'pm':
macgap.growl.notify({
title: 'PM!',
content: 'You have received a PM from ' + data.user + '!'
});
break;
case 'yourMove':
case 'yourSwitch':
macgap.growl.notify({
title: "Your move!",
content: "It's your move in your battle against "+data.user+"."
});
break;
default:
macgap.growl.notify({
title: "Pokemon Showdown",
content: "Something has happened!"
});
break;
}
macgap.dock.badge = "1";
}
dismissNotify = function () {
macgap.dock.badge = "";
}
} else {
var activeNotificationData = null;
notify = function (data) {
if (window.focused) return;
favicon.animate(['/favicon-notify.ico', '/favicon-notify2.ico']);
activeNotificationData = data;
activeNotification = setInterval(updateNotifyTitle, 1500);
};
dismissNotify = function () {
favicon.change('/favicon.ico');
if (activeNotification) {
clearTimeout(activeNotification);
document.title = curTitle;
activeNotification = null;
activeNotificationData = null;
}
};
updateNotifyTitle = function () {
if (!activeNotification) return false;
if (!activeNotificationData) return false;
window.notifying = !window.notifying;
if (window.notifying) {
switch (activeNotificationData.type) {
case 'challenge':
document.title = 'CHALLENGED';
break;
case 'highlight':
document.title = 'HIGHLIGHTED';
break;
case 'pm':
document.title = 'PM';
break;
case 'yourMove':
case 'yourSwitch':
document.title = 'YOUR MOVE';
break;
default:
document.title = 'ACTIVITY';
break;
}
} else {
document.title = curTitle;
}
};
}
}
function notificationClick(button, data) {
switch (button) {
case 'accept':
rooms[data.room].formAcceptChallenge(data.userid);
break;
case 'reject':
rooms[data.room].formRejectChallenge(data.userid);
break;
}
};
// overlay
function overlay(overlayType, data) {
var contents = '';
var focusElem = '';
var selectElem = '';
switch (overlayType) {
case 'message':
contents = '<p>' + data + '</p>';
contents += '<p><button onclick="overlayClose();return false" id="overlay_ok">OK</button></p>';
focusElem = '#overlay_ok';
break;
case 'replayuploaded':
contents = '<p>Your replay has been uploaded! It\'s available at:</p>';
contents += '<p><a href="http://pokemonshowdown.com/replay/'+data+'" target="_blank" onclick="overlayClose()">http://pokemonshowdown.com/replay/'+data+'</a></p>';
contents += '<p><button onclick="window.open(\'/replay/battle-'+data+'\',\'_blank\');overlayClose();return false" id="overlay_ok"><strong>Open</strong></button> <button onclick="overlayClose();return false" id="overlay_cancel">Cancel</button></p>';
focusElem = '#overlay_ok';
break;
case 'init':
if (data) return;
contents = '<p><strong>Pokemon Showdown is BETA</strong> and unfinished. If you are looking for something that isn\'t frequently down for maintenance and bug fixes, please check back in several weeks.</p>';
contents += '<p>There is a link to report bugs in the top right. If you find a bug, please report it.</p>';
contents += '<p><button onclick="overlayClose();return false" id="overlay_ok"><strong>I understand</strong></button> <button onclick="document.location.href = \'http://www.zombo.com/\';return false">I don\'t understand</button></p>';
focusElem = '#overlay_ok';
break;
case 'register':
if (!data) data = {};
if (me.registered && me.registered.userid === me.userid) return;
if (data.ifuserid !== me.userid) return;
if (data.error) {
contents += '<p class="error">' + data.error + '</p>';
} else if (data.reason) {
contents += '<p>' + data.reason + '</p>';
} else {
contents += '<p>Register an account:</p>';
}
contents += '<p><label class="label">Username:</label> ' + (data.name || me.name) + '<input type="hidden" id="overlay_username" value="' + sanitize(data.name || me.name) + '" /></p>';
contents += '<p><label class="label">Password:</label> <input class="textbox" type="password" id="overlay_password" /></p>';
contents += '<p><label class="label">Password (confirm):</label> <input class="textbox" type="password" id="overlay_cpassword" /></p>';
contents += '<p><img src="' + Tools.resourcePrefix + 'sprites/bwani/pikachu.gif" /></p>';
contents += '<p><label class="label">What is this pokemon?</label> <input class="textbox" type="text" id="overlay_captcha" value="' + sanitize(data.captcha) + '" /></p>';
contents += '<p><button type="submit"><strong>Register</strong></button> <button onclick="overlayClose();return false">Cancel</button></p>';
selectElem = '#overlay_password';
break;
case 'login':
if (!data) data = {};
if (data.error) {
contents += '<p class="error">' + data.error + '</p>';
} else if (data.reason) {
contents += '<p>' + data.reason + '</p>';
} else {
contents += '<p>The name you chose is registered.</p>';
}
contents += '<p><label class="label">Username:</label> ' + data.name + '<input type="hidden" id="overlay_username" value="' + sanitize(data.name) + '" /></p>';
contents += '<p><label class="label">Password:</label> <input class="textbox" type="password" id="overlay_password" /></p>';
contents += '<p><button type="submit"><strong>Log in</strong></button> <button onclick="overlayClose();return false">Cancel</button></p>';
selectElem = '#overlay_password';
break;
case 'testclientgetassertion':
contents += '<p>Because of the <a href="https://en.wikipedia.org/wiki/Same-origin_policy" target="_blank">same-origin policy</a>, some manual work is required to log in using <code>testclient.html</code>.</p>';
contents += '<iframe id="overlay_iframe" src="' + data.query + '" style="width: 100%; height: 50px;" class="textbox"></iframe>';
contents += '<p>Please copy <strong>all the text</strong> from the box above and paste it in the box below. If the box above just shows a semi-colon (;), log in using the <a href="http://play.pokemonshowdown.com" target="_blank">official client</a> and then refresh this page.</p>';
if (data.error) {
contents += '<p><strong>' + data.error + '</strong></p>';
}
contents += '<input class="textbox" type="hidden" id="overlay_username" value="' + data.name + '" />';
contents += '<p><label class="label">Data from the box above:</label> <input style="width: 100%;" class="textbox" type="text" id="overlay_assertion" /></p>';
contents += '<p><button type="submit"><strong>Log in</strong></button> <button onclick="overlayClose();return false">Cancel</button></p>';
selectElem = '#overlay_assertion';
break;
case 'betalogin':
if (!data) data = {};
contents += '<p><strong>Pokemon Showdown is in private beta testing.</strong></p>';
contents += '<p><a href="http://www.smogon.com/forums/showthread.php?p=3948327#post3948327">Request a beta account</a></p>';
if (data.error) {
contents += '<p class="error">' + data.error + '</p>';
} else if (data.reason) {
contents += '<p>' + data.reason + '</p>';
} else {}
contents += '<p><label class="label">Username:</label> <input class="textbox" type="text" id="overlay_username" value="' + sanitize(data.name || '') + '" /></p>';
contents += '<p><label class="label">Password:</label> <input class="textbox" type="password" id="overlay_password" /></p>';
contents += '<p><button type="submit"><strong>Log in</strong></button> <button onclick="overlayClose();return false">Cancel</button></p>';
selectElem = '#overlay_username';
break;
case 'down':
contents += '<p style="font-size:14pt"><strong>Pokemon Showdown is under heavy load.<br />:(</strong></p>';
contents += '<p>Bear with us as we freak out.</p>';
// contents += '<p><a href="http://www.smogon.com/forums/showthread.php?p=4319109#post4319109">We\'re currently on a backup server, and it\'s full.</a></p>';
contents += '<p>(Alternatively, reload this page in an hour or two; it should be back up by then.)</p>';
// contents += '<p><strong>We WILL fix these performance issues ASAP.</strong></p>';
// contents += '<p>Please try back in an hour or so; the server will open and close intermittently until we sort out the remaining performance issues.</p>';
// contents += '<p style="font-size:14pt"><strong>Pokemon Showdown is confirmed to be under a DDoS attack. :(</strong></p>';
// contents += '<p>Bear with us as we freak out.</p>';
// contents += '<p><a href="http://www.smogon.com/forums/showthread.php?t=3469851">There\'s slightly more information in this Smogon thread.</a></p>';
// contents += '<p>Please try back later today.</p>';
break;
case 'unsupported':
contents += '<p><strong>You have an old version of your browser.</strong></p>';
contents += '<p>Please upgrade to one of:</p>';
contents += '<p>Internet Explorer 9+, Firefox 4+, Chrome 11+, Safari 4+</p>';
break;
case 'rename':
if (!data) data = {};
if (data.error) {
contents += '<p class="error">' + data.error + '</p>';
if (data.error.indexOf(' forced you to change ') >= 0) {
contents += '<p>Keep in mind these rules:</p>';
contents += '<ol>';
contents += '<li>Usernames may not be derogatory or insulting in nature, to an individual or group (insulting yourself is okay as long as it\'s not too serious).</li>';
contents += '<li>Usernames may not reference sexual activity, directly or indirectly.</li>';
contents += '<li>Usernames may not impersonate a recognized user (a user with %, @, &, or ~ next to their name).</li>';
contents += '</ol>';
}
}
contents += '<p><label class="label">Username:</label> <input class="textbox" type="text" id="overlay_name" value="' + (me.named ? sanitize(me.name) : '') + '" /></p>';
contents += '<p><button type="submit"><strong>Choose name</strong></button> <button onclick="overlayClose();return false">Cancel</button></p>';
selectElem = '#overlay_name';
break;
case 'forfeit':
contents += '<p>Are you sure you want to forfeit? <input type="hidden" id="overlay_room" value="' + data + '" /></p>';
contents += '<p><button type="submit"><strong>Forfeit</strong></button> <button onclick="overlayClose();return false" id="overlay_cancel">Cancel</button></p>';
selectElem = '#overlay_cancel';
break;
case 'disconnect':
if (rooms.teambuilder && rooms.teambuilder.formSave) {
rooms.teambuilder.formSave();
}
contents += '<p>You have been disconnected - possibly because the server was restarted.</p>'
contents += '<p><button onclick="document.location.reload();return false" id="overlay_refresh"><strong>Reconnect</strong></button> <button onclick="overlayClose();return false">Cancel</button></p>';
focusElem = '#overlay_refresh';
}
$('#overlay').html('<form id="messagebox" onsubmit="overlaySubmit(event, \'' + overlayType + '\'); return false">' + contents + '</form>');
$('#overlay').show();
if (selectElem) $(selectElem).select();
else if (focusElem) $(focusElem).focus();
};
function overlayClose() {
$('#overlay').html('');
$('#overlay').hide();
};
function renameMe(name) {
if (me.userid !== toId(name)) {
var query = actionphp + '?act=getassertion&userid=' + toId(name) +
'&challengekeyid=' + me.challengekeyid +
'&challenge=' + me.challenge;
if (Config.testclient) {
overlay('testclientgetassertion', { name: name, query: query });
return;
}
if (name === '') {
return;
}
$.get(query, function(data) {
if (data === ';') {
overlay('login', {name: name});
} else if (data.substr(0, 2) === ';;') {
overlay('rename', {error: data.substr(2)});
} else if (data.indexOf('\n') >= 0) {
alert("The login server is overloaded. Please try again later.");
} else {
rooms.lobby.send('/trn '+name+',0,'+data);
}
});
} else {
rooms.lobby.send('/trn '+name);
}
}
function overlaySubmit(e, overlayType) {
switch (overlayType) {
case 'rename':
var name = $('#overlay_name').val();
overlayClose();
renameMe(name);
break;
case 'login':
case 'betalogin':
var name = $('#overlay_username').val();
$.post(actionphp, {
act: 'login',
name: name,
pass: $('#overlay_password').val(),
challengekeyid: me.challengekeyid,
challenge: me.challenge
}, Tools.safeJson(function (data) {
if (!data) data = {};
if (data.sid !== undefined) {
Tools.postCrossDomainMessage({sid: data.sid});
}
var token = data.assertion;
if (data.curuser && data.curuser.loggedin) {
me.registered = data.curuser;
name = data.curuser.username;
if (!socket) {
document.location.reload();
return;
}
rooms.lobby.send('/trn '+name+',0,'+token);
} else {
overlay(overlayType, {
name: name,
error: 'Wrong password.'
});
}
}), 'text');
overlayClose();
break;
case 'testclientgetassertion':
var assertion = $('#overlay_assertion').val();
var query = $('#overlay_iframe').attr('src');
var name = $('#overlay_username').val();
overlayClose();
if (!assertion.split(';')[1]) {
// The user only selected part of the textbox.
overlay('testclientgetassertion', {
name: name,
query: query,
error: 'You didn\'t select all the text last time. Try again.' });
} else {
if (name === '') {
// Get the userid from the assertion (assume challenge-response).
name = assertion.split(',')[1];
}
rooms.lobby.send('/trn ' + name + ',0,' + assertion);
}
break;
case 'register':
var name = $('#overlay_username').val();
var captcha = $('#overlay_captcha').val();
$.post(actionphp, {
act: 'register',
username: name,
password: $('#overlay_password').val(),
cpassword: $('#overlay_cpassword').val(),
captcha: captcha,
challengekeyid: me.challengekeyid,
challenge: me.challenge
}, Tools.safeJson(function (data) {
if (!data) data = {};
if (data.sid !== undefined) {
Tools.postCrossDomainMessage({sid: data.sid});
}
var token = data.assertion;
if (data.curuser && data.curuser.loggedin) {
me.registered = data.curuser;
name = data.curuser.username;
if (!socket) {
document.location.reload();
return;
}
rooms.lobby.send('/trn '+name+',1,'+token);
overlay('message', "You have been successfully registered.");
} else {
overlay('register', {
ifuserid: me.userid,
name: name,
captcha: captcha,
error: data.actionerror
});
}
}), 'text').error(function (e) {
alert('error: ' + e);
});
overlayClose();
break;
case 'forfeit':
var room = rooms[$('#overlay_room').val()];
if (room) {
room.formForfeit();
leaveTab(room.id, true);
}
overlayClose();
break;
}
}
function init() {
addTab('teambuilder', 'teambuilder');
addTab('ladder', 'ladder');
if (socketInit) socketInit();
initialized = true;
}
var cookieTeams = true;
teams = (function() {
var savedTeam = $.parseJSON($.cookie('showdown_team1'));
var teams = [];
if (savedTeam) {
teams.push(savedTeam);
}
savedTeam = $.parseJSON($.cookie('showdown_team2'));
if (savedTeam) {
teams.push(savedTeam);
}
savedTeam = $.parseJSON($.cookie('showdown_team3'));
if (savedTeam) {
teams.push(savedTeam);
}
if (window.localStorage) {
cookieTeams = false;
var teamString = localStorage.getItem('showdown_teams');
if (teamString) teams = JSON.parse(teamString);
}
return teams;
})();
// time to connect
(function() {
var connect = function(data, name) {
if (Config.down) return;
if (!data) data = {};
var token = data.assertion || '';
if (data.curuser && data.curuser.loggedin) {
me.registered = data.curuser;
name = data.curuser.username;
} else if (Config.oldie) {
overlay('unsupported');
return;
} else if (Config.requirelogin) {
document.getElementById('loading-message').innerHTML = '';
overlay('betalogin');
return;
}
var constructSocket = function(host, port) {
if (Config.serverprotocol === 'io') return io.connect('http://' + host + ':' + port);
else if (Config.serverprotocol === 'eio') return new eio.Socket({ host: host, port: port });
else return new SockJS('http://' + host + ':' + port);
};
socket = constructSocket(Config.server, Config.serverport);
var events = {
init: function (data) {
if (data.name) {
me.name = data.name;
me.named = data.named;
me.userid = data.userid;
me.renamePending = !! data.renamePending;
if (data.token) me.token = data.token;
}
if (data.notFound) {
selectTab('lobby');
return;
}
var tempInitialize = function () {
addTab(data.room, data.roomType);
var room = rooms[data.room];
room.init(data);
updateMe(data);
$('#loading-message').remove();
if (loc && loc !== 'lobby') {
selectTab(loc);
}
};
if (!initialized) {
socketInit = tempInitialize;
} else {
tempInitialize();
}
},
update: function (data) {
if (typeof data.name !== 'undefined') {
me.name = data.name;
me.named = data.named;
me.userid = data.userid;
me.renamePending = !! data.renamePending;
if (data.token) me.token = data.token;
}
if (typeof data.challengesFrom !== 'undefined') {
me.challengesFrom = data.challengesFrom;
rooms.lobby.notifying = false;
for (var i in me.challengesFrom) {
rooms.lobby.notifying = true;
break;
}
updateRoomList();
rooms.lobby.updateMainTop();
}
if (typeof data.challengeTo !== 'undefined') {
me.challengeTo = data.challengeTo;
rooms.lobby.updateMainTop();
}
updateMe(data);
if (data.room && rooms[data.room]) {
rooms[data.room].update(data);
} else if (curRoom) {
//curRoom.update(data);
}
},
disconnect: function () {
$('#userbar').prepend('<strong style="color:#BB0000;border:1px solid #BB0000;padding:0px 2px;font-size:10pt;">disconnect detected</strong> ');
overlay('disconnect');
},
nameTaken: function (data) {
if (data && data.permanent) {
overlay('message', data.reason);
} else if (data && data.name) {
overlay('login', data);
} else if (data) {
overlay('rename', {
error: data.reason
});
} else {
alert('nameTaken signal');
$('#userbar').prepend('<strong style="color:#BB0000;border:1px solid #BB0000;padding:0px 2px;font-size:10pt;">nameTaken signal</strong> ');
}
},
message: function (message) {
if (message.html) {
overlay('message', message.html);
return;
}
if (message.message) message = message.message;
overlay('message', '<div style="white-space:pre-wrap">' + message + '</div>');
},
command: function (message) {
if (message.room && rooms[message.room]) {
rooms[message.room].command(message);
}
},
console: function (message) {
var room = null;
if (message.room && rooms[message.room]) {
room = rooms[message.room];
if (room) room.add(message);
//if (room.id === 'lobby' && message.silent) room.updateMainTop();
} else {
if (curRoom) curRoom.message(message, true);
}
}
};
function parseSpecialData(text) {
var parts = text.split('|');
if (parts.length < 2) return false;
switch (parts[1]) {
case 'challenge-string':
case 'challstr':
me.challengekeyid = parseInt(parts[2], 10);
me.challenge = parts[3];
if (rooms.lobby !== undefined) {
renameMe(name);
} else {
me.renameQueued = name;
}
return true;
}
return false;
}
if (Config.serverprotocol === 'io') {
for (var e in events) {
socket.on(e, (function(type) {
return function(data) {
events[type](data);
};
})(e));
}
socket.on('data', function(text) {
var roomid = 'lobby';
if (text.substr(0,1) === '>') {
var nlIndex = text.indexOf('\n');
if (nlIndex < 0) return;
roomid = text.substr(1,nlIndex-1);
text = text.substr(nlIndex+1);
}
if (!parseSpecialData(text) && (rooms[roomid] !== undefined)) {
rooms[roomid].add(text);
}
});
if (!name) token = '';
document.getElementById('loading-message').innerHTML += ' DONE<br />Connecting to Showdown server...';
emit(socket, 'join', {
name: name,
room: 'lobby',
token: token
});
} else {
var socketopened = false;
var altport = (Config.serverport === Config.serveraltport);
document.getElementById('loading-message').innerHTML += ' DONE<br />Connecting to Showdown server...';
socket.onopen = function() {
socketopened = true;
if (altport) {
_gaq.push(['_trackEvent', 'Alt port connection', Config.serverid]);
}
if (!name) token = '';
document.getElementById('loading-message').innerHTML += ' DONE<br />Joining Showdown server...';
emit(socket, 'join', {
name: name,
room: 'lobby',
token: token
});
};
socket.onmessage = function(msg) {
if (msg.data.substr(0,1) !== '{') {
var text = msg.data;
var roomid = 'lobby';
if (text.substr(0,1) === '>') {
var nlIndex = text.indexOf('\n');
if (nlIndex < 0) return;
roomid = text.substr(1,nlIndex-1);
text = text.substr(nlIndex+1);
}
if (!parseSpecialData(text) && (rooms[roomid] !== undefined)) {
rooms[roomid].add(text);
}
return;
}
var data = $.parseJSON(msg.data);
if (!data) return;
if (events[data.type]) events[data.type](data);
};
socket.onclose = function () {
if (!socketopened && Config.serveraltport && !altport) {
altport = true;
Config.serverport = Config.serveraltport;
socket = (function() {
var s = constructSocket(Config.server, Config.serverport);
s.onopen = socket.onopen;
s.onmessage = socket.onmessage;
s.onclose = socket.onclose;
return s;
})();
return;
}
$('#userbar').prepend('<strong style="color:#BB0000;border:1px solid #BB0000;padding:0px 2px;font-size:10pt;">disconnect detected</strong> ');
overlay('disconnect');
};
}
};
if ((Config.crossdomain === undefined) || (window.postMessage === undefined)) {
connect(Config.upkeep, $.cookie('showdown_username') || '');
} else {
var origin = 'http://play.pokemonshowdown.com';
$(window).on('message', function($e) {
var e = $e.originalEvent;
if (e.origin !== origin) return;
Tools.postCrossDomainMessage = function(data) {
return e.source.postMessage(data, origin);
};
// sid
$.cookie('sid', e.data.sid);
// teams
if (e.data.teams) {
cookieTeams = false;
teams = $.parseJSON(e.data.teams);
}
Teambuilder.writeTeams = function() {
Tools.postCrossDomainMessage({teams: $.toJSON(teams)});
};
if (rooms.teambuilder) {
rooms.teambuilder.init();
}
// prefs
if (e.data.prefs) {
Tools.prefs.data = $.parseJSON(e.data.prefs);
}
Tools.prefs.save = function() {
Tools.postCrossDomainMessage({prefs: $.toJSON(this.data)});
};
// connect
connect(e.data.upkeep, e.data.username);
});
var $iframe = $(
'<iframe src="//play.pokemonshowdown.com/crossdomain.php?prefix=' +
encodeURIComponent(Config.crossdomain.prefix) +
'&amp;challengeresponse=' +
encodeURIComponent(Config.crossdomain.challengeresponse) +
'&amp;server=' +
encodeURIComponent(Config.server) +
'" style="display: none;"></iframe>'
);
$('body').append($iframe);
}
})();