(function($) { var BattleRoom = this.BattleRoom = this.Room.extend({ minWidth: 955, maxWidth: 1180, initialize: function(data) { this.me = {}; this.$el.addClass('ps-room-opaque').html('
Battle is here
Connecting...
'); this.$battle = this.$el.find('.battle'); this.$controls = this.$el.find('.battle-controls'); this.$chatFrame = this.$el.find('.battle-log'); this.$chatAdd = this.$el.find('.battle-log-add'); this.$join = null; this.$foeHint = this.$el.find('.foehint'); this.battle = new Battle(this.$battle, this.$chatFrame); this.$chat = this.$chatFrame.find('.inner'); // this.battle.setMute(me.isMuted()); this.battle.customCallback = _.bind(this.updateControls, this); this.battle.endCallback = _.bind(this.updateControls, this); this.battle.startCallback = _.bind(this.updateControls, this); this.battle.stagnateCallback = _.bind(this.updateControls, this); this.battle.play(); app.user.on('change', this.updateUser, this); this.updateUser(); }, events: { 'keydown textarea': 'keyPress', 'submit form': 'submit', 'click .username': 'clickUsername' }, battleEnded: false, focus: function() { if (this.$chatbox) this.$chatbox.focus(); }, join: function() { app.send('/join '+this.id); }, leave: function() { app.send('/leave '+this.id); if (this.battle) this.battle.dealloc(); }, updateLayout: function() { if (this.$el.width() < 950) { this.battle.messageDelay = 800; } else { this.battle.messageDelay = 8; } if (this.$chat) this.$chatFrame.scrollTop(this.$chat.height()); }, show: function() { Room.prototype.show.apply(this, arguments); this.updateLayout(); }, receive: function(data) { this.add(data); }, init: function(data) { var log = data.split('\n'); if (data.substr(0,6) === '|init|') log.shift(); if (this.battle.activityQueue.length) return; this.battle.activityQueue = log; this.battle.fastForwardTo(-1); this.updateLayout(); this.updateControls(); }, add: function(data) { if (data.substr(0,6) === '|init|') { return this.init(data); } var log = data.split('\n'); for (var i = 0; i < log.length; i++) { var logLine = log[i]; if (logLine === '') { this.callbackWaiting = false; this.$controls.html(''); } if (logLine.substr(0, 6) === '|chat|' || logLine.substr(0, 3) === '|c|' || logLine.substr(0, 9) === '|chatmsg|' || logLine.substr(0, 10) === '|inactive|') { this.battle.instantAdd(logLine); } else { this.battle.activityQueue.push(logLine); } } this.battle.add('', Tools.prefs('noanim')); this.updateControls(); }, updateUser: function() { var name = app.user.get('name'); var userid = app.user.get('userid'); if (!name) { this.$chatAdd.html('Connecting...'); this.$chatbox = null; } else if (!app.user.get('named')) { this.$chatAdd.html('
'); this.$chatbox = null; } else { this.$chatAdd.html('
'); this.$chatbox = this.$chatAdd.find('textarea'); this.$chatbox.autoResize({ animate: false, extraSpace: 0 }); if (this === app.curSideRoom || this === app.curRoom) { this.$chatbox.focus(); } } }, submit: function(e) { e.preventDefault(); e.stopPropagation(); var text; if ((text = this.$chatbox.val())) { // this.tabComplete.reset(); // this.chatHistory.push(text); // text = this.parseCommand(text); if (text) { this.send(text); } this.$chatbox.val(''); } }, keyPress: function(e) { if (e.keyCode === 13 && !e.shiftKey) { // Enter this.submit(e); } else if (e.keyCode === 9 && !e.shiftKey && !e.ctrlKey) { // Tab // if (this.handleTabComplete(this.$chatbox)) { // e.preventDefault(); // e.stopPropagation(); // } } }, clickUsername: function(e) { e.stopPropagation(); e.preventDefault(); var name = $(e.currentTarget).data('name'); app.addPopup('user', UserPopup, {name: name, sourceEl: e.currentTarget}); }, /********************************************************* * Battle stuff *********************************************************/ updateControls: function() { if (this.$join) { this.$join.remove(); this.$join = null; } if (this.battle.playbackState === 5) { // battle is seeking this.$controls.html(''); return; } else if (this.battle.playbackState === 2) { // battle is playing this.$controls.html(''); return; } // tooltips var myActive = this.battle.mySide.active; var yourActive = this.battle.yourSide.active; var buf = ''; if (yourActive[1]) { buf += '
'; } if (yourActive[0]) { buf += '
'; } if (myActive[0]) { buf += '
'; } if (myActive[1]) { buf += '
'; } this.$foeHint.html(buf); if (this.battle.done) { // battle has ended this.$controls.html('
'); } else if (this.side) { // player this.updateControlsForPlayer() } else if (this.battle.mySide.initialized && this.battle.yourSide.initialized) { // full battle this.$controls.html('Waiting for players...'); } else { // empty battle this.$join = $('
'); this.$battle.append(this.$join); } // This intentionally doesn't happen if the battle is still playing, // since those early-return. app.topbar.updateTabbar(); }, updateControlsForPlayer: function() { if (!this.request) { if (this.battle.kickingInactive) { this.$controls.html('

← Your opponent has disconnected. Click this to delay your victory.

'); } else { this.$controls.html('

← Your opponent has disconnected. Click this if they don\'t reconnect.

'); } } var battle = this.battle; // this.notifying = false; if (!this.choice) { this.updateSide(this.request.side); } // updated trappedness if (false && 'trapped') { var idx = parseInt(moveTarget[1], 10); // moveTarget is a poor name now... if (this.request && this.request.active && this.request.active[idx]) { // This pokemon is now known to be trapped. this.request.active[idx].trapped = true; // TODO: Maybe a more sophisticated UI for this. // In singles, this isn't really necessary because the switch UI will be // immediately disabled. However, in doubles it might not be obvious why // the player is being asked to make a new decision without this message. this.battle.add(this.battle.mySide.active[idx].getName() + ' is trapped!'); } } this.callbackWaiting = true; var active = this.battle.mySide.active[0]; if (!active) active = {}; var act = ''; var switchables = []; if (this.request) { act = this.request.requestType; if (this.request.side) { switchables = this.battle.mySide.pokemon; } } var type = ''; var moveTarget = ''; if (this.choice) { type = this.choice.type; moveTarget = this.choice.moveTarget; if (this.choice.waiting) act = ''; } // The choice object: // !this.choice = nothing has been chosen // this.choice.choices = array of choice strings // this.choice.switchFlags = dict of pokemon indexes that have a switch pending switch (act) { case 'move': { if (!this.choice) { this.choice = { choices: [], switchFlags: {} } while (switchables[this.choice.choices.length] && switchables[this.choice.choices.length].fainted) { this.choice.choices.push('pass'); } } var pos = this.choice.choices.length - (type === 'movetarget'?1:0); // hp bar var hpbar = ''; if (switchables[pos].hp * 5 / switchables[pos].maxhp < 1) { hpbar = ''; } else if (switchables[pos].hp * 2 / switchables[pos].maxhp < 1) { hpbar = ''; } else { hpbar = ''; } hpbar += ''+switchables[pos].hp+'/'+switchables[pos].maxhp+''; var active = this.request; if (active.active) active = active.active[pos]; var moves = active.moves; var trapped = active.trapped; this.finalDecision = active.maybeTrapped || false; if (this.finalDecision) { for (var i = pos + 1; i < this.battle.mySide.active.length; ++i) { var p = this.battle.mySide.active[i]; if (p && !p.fainted) { this.finalDecision = false; } } } var controls = '
'; if (type === 'move2' || type === 'movetarget') { controls += ' '; } // Target selector if (type === 'movetarget') { controls += 'At who? '+hpbar+'
'; controls += '
'; var myActive = this.battle.mySide.active; var yourActive = this.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 += ' '; } else if (disabled || pokemon.zerohp) { controls += ' '; } else { var posString = ''; controls += ' '; } } controls += '
'; 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 += ' '; } else if (disabled || pokemon.zerohp) { controls += ' '; } else { controls += ' '; } } controls += '
'; controls += '
'; this.$controls.html(controls); break; } // Move chooser controls += 'What will ' + Tools.escapeHTML(switchables[pos].name) + ' do? '+hpbar+''; var hasMoves = false; var hasDisabled = false; controls += '
'; 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 = '–'; if (move.id === 'Struggle' || move.id === 'Recharge') pp = '–'; if (move.id === 'Recharge') move.type = '–'; if (name.substr(0, 12) === 'Hidden Power') name = 'Hidden Power'; if (moveData.disabled) { movebuttons += ' '; } if (!hasMoves) { controls += ' '; } else { controls += movebuttons; } controls += '
'; if (hasDisabled) { // controls += '(grayed out moves have been disabled by Disable, Encore, or something like that)'; } controls += '
'; if (trapped) { controls += 'You are trapped and cannot switch!'; } else { controls += ''; if (this.finalDecision) { controls += 'You might be trapped, so you won\'t be able to cancel a switch!
'; } for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[i]; pokemon.name = pokemon.ident.substr(4); if (pokemon.zerohp || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) { controls += ' '; } else { controls += ' '; } } } controls += '
'; this.$controls.html(controls); } this.notifying = true; break; case 'switch': this.finalDecision = false; if (!this.choice) { this.choice = { choices: [], switchFlags: {} }; if (this.request.forceSwitch !== true) { while (!this.request.forceSwitch[this.choice.choices.length] && this.choice.choices.length < 6) this.choice.choices.push('pass'); } } var pos = this.choice.choices.length; var controls = '
'; if (type === 'switch2') { controls += ' '; } controls += 'Switch '+Tools.escapeHTML(switchables[pos].name)+' to:
'; controls += '
'; for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[i]; if (pokemon.zerohp || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) { controls += ' '; } controls += '
'; this.$controls.html(controls); this.selectSwitch(); this.notifying = true; break; case 'team': var controls = '
'; if (!this.choice || !this.choice.done) { this.choice = { teamPreview: [1,2,3,4,5,6].slice(0,switchables.length), done: 0, count: 0 } if (this.battle.gameType === 'doubles') { this.choice.count = 2; } controls += 'How will you start the battle?
'; controls += '
'; for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[i]; if (i >= 6) { break; } if (toId(pokemon.baseAbility) === 'illusion') { this.choice.count = 6; } controls += ' '; } if (this.battle.teamPreviewCount) this.choice.count = parseInt(this.battle.teamPreviewCount,10); controls += '
'; } else { controls += ' What about the rest of your team?
'; controls += '
'; for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[this.choice.teamPreview[i]-1]; if (i >= 6) { break; } if (i < this.choice.done) { controls += ' '; } else { controls += ' '; } } controls += '
'; } controls += '
'; this.$controls.html(controls); this.selectSwitch(); this.notifying = true; break; default: var buf = '
Waiting for opponent... '; if (this.choice && this.choice.waiting && !this.finalDecision) { buf += ''; } buf += '
'; if (this.battle.kickingInactive) { buf += '
'; } else { buf += '
'; } this.$controls.html(buf); break; } }, // 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. sendDecision: function(message) { this.send(message + '|' + this.request.rqid); }, request: null, receiveRequest: function(request) { request.requestType = 'move'; var notifyObject = null; if (request.forceSwitch) { request.requestType = 'switch'; notifyObject = { type: 'yourSwitch', room: this.id }; } else if (request.teamPreview) { request.requestType = 'team'; notifyObject = { type: 'yourSwitch', room: this.id }; } else if (request.wait) { request.requestType = 'wait'; } else { notifyObject = { type: 'yourMove', room: this.id }; } this.choice = null; this.request = request; if (request.side) { this.updateSideLocation(request.side, true); } // if (notifyObject) { // var doNotify = function() { // notify(notifyObject); // this.notifying = true; // updateRoomList(); // }; // if (this.battle.yourSide.initialized) { // // The opponent's name is already known. // notifyObject.user = this.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 = this.battle.stagnateCallback; // this.battle.stagnateCallback = function(battle) { // notifyObject.user = battle.yourSide.name; // doNotify(); // battle.stagnateCallback = callback; // if (callback) callback(battle); // }; // } // } }, updateSideLocation: function(sideData, midBattle) { if (!sideData.id) return; this.side = sideData.id; if (this.battle.sidesSwitched !== !!(this.side === 'p2')) { sidesSwitched = true; this.battle.reset(); this.battle.switchSides(); if (midBattle) { this.battle.fastForwardTo(-1); } else { this.battle.play(); } } }, updateSide: function(sideData) { for (var i = 0; i < sideData.pokemon.length; i++) { var pokemonData = sideData.pokemon[i]; var pokemon; if (i == 0) { pokemon = this.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 < this.battle.mySide.active.length) { pokemon = this.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 = this.battle.getPokemon('new: '+pokemonData.ident, pokemonData.details); } pokemon.healthParse(pokemonData.condition); if (pokemonData.baseAbility) { pokemon.baseAbility = pokemonData.baseAbility; if (!pokemon.ability) pokemon.ability = pokemon.baseAbility; } pokemon.item = pokemonData.item; pokemon.moves = pokemonData.moves; } this.battle.mySide.updateSidebar(); }, // buttons joinBattle: function() { this.send('/joinbattle'); }, setTimer: function(setting) { this.send('/timer '+setting); }, forfeit: function() { this.send('/forfeit'); }, saveReplay: function() { this.send('/savereplay'); }, instantReplay: function() { this.hideTooltip(); this.request = null; this.battle.reset(); this.battle.play(); }, skipTurn: function() { this.battle.skipTurn(); }, // choice buttons chooseMove: function(move) { var myActive = this.battle.mySide.active; var target = Tools.getMove(move).target; this.hideTooltip(); if (move !== undefined) { var choosableTargets = {normal:1, any:1, adjacentAlly:1, adjacentAllyOrSelf:1, adjacentFoe:1}; this.choice.choices.push('move '+move); if (myActive.length > 1 && target in choosableTargets) { this.choice.type = 'movetarget'; this.choice.moveTarget = target; this.updateControlsForPlayer(); return false; } } while (myActive.length > this.choice.choices.length && !myActive[this.choice.choices.length]) { this.choice.choices.push('pass'); } if (myActive.length > this.choice.choices.length) { this.choice.type = 'move2'; this.updateControlsForPlayer(); return false; } this.sendDecision('/choose '+this.choice.choices.join(',')); this.notifying = false; this.finalDecision = false; this.choice = {waiting: true}; this.updateControlsForPlayer(); }, chooseMoveTarget: function(posString) { this.choice.choices[this.choice.choices.length-1] += ' '+posString; this.chooseMove(); }, chooseSwitch: function(pos) { this.hideTooltip(); this.choice.choices.push('switch '+(parseInt(pos,10)+1)); this.choice.switchFlags[pos] = true; if (this.request && this.request.requestType === 'move' && this.battle.mySide.active.length > this.choice.choices.length) { this.choice.type = 'move2'; this.updateControlsForPlayer(); return false; } if (this.request && this.request.requestType === 'switch') { if (this.request.forceSwitch !== true) { while (this.battle.mySide.active.length > this.choice.choices.length && !this.request.forceSwitch[this.choice.choices.length]) this.choice.choices.push('pass'); } if (this.battle.mySide.active.length > this.choice.choices.length) { this.choice.type = 'switch2'; this.updateControlsForPlayer(); return false; } } this.sendDecision('/choose '+this.choice.choices.join(',')); this.notifying = false; this.choice = {waiting: true}; this.updateControlsForPlayer(); }, chooseTeamPreview: function(pos) { pos = parseInt(pos,10); this.hideTooltip(); if (this.choice.count) { var temp = this.choice.teamPreview[pos]; this.choice.teamPreview[pos] = this.choice.teamPreview[this.choice.done]; this.choice.teamPreview[this.choice.done] = temp; this.choice.done++; if (this.choice.done < Math.min(this.choice.teamPreview.length, this.choice.count)) { this.choice.type = 'team2'; this.updateControlsForPlayer(); return false; } pos = this.choice.teamPreview.join(''); } else { pos = pos+1; } this.sendDecision('/team '+(pos)); this.notifying = false; this.choice = {waiting: true}; this.updateControlsForPlayer(); }, undoChoice: function(pos) { this.send('/undo'); this.notifying = true; this.choice = null; this.updateControlsForPlayer(); }, leaveBattle: function() { this.hideTooltip(); this.send('/leavebattle'); this.notifying = false; }, selectSwitch: function() { this.hideTooltip(); this.$controls.find('.controls').attr('class', 'controls switch-controls'); }, selectMove: function() { this.hideTooltip(); this.$controls.find('.controls').attr('class', 'controls move-controls'); }, // tooltips tooltipAttrs: function(thing, type, ownHeight, isActive) { return ' onmouseover="room.showTooltip(\'' + Tools.escapeHTML(''+thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ', ' + (isActive ? 'true' : 'false') + ')" onmouseout="room.hideTooltip()" onmouseup="room.hideTooltip()"'; }, showTooltip: function(thing, type, elem, ownHeight, isActive) { var offset = { left: 150, top: 500 }; if (elem) offset = $(elem).offset(); var x = offset.left - 2; if (elem) { if (ownHeight) offset = $(elem).offset(); else offset = $(elem).parent().offset(); } var y = offset.top - 5; if (x > 335) x = 335; 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 = '—'; var accuracy = move.accuracy; if (!accuracy || accuracy === true) accuracy = '—'; else accuracy = '' + accuracy + '%'; text = '
'; text += '

' + move.name + '
'+Tools.getTypeIcon(move.type)+' ' + move.category + '

'; text += '

Base power: ' + basePower + '

'; text += '

Accuracy: ' + accuracy + '

'; if (move.desc) { text += '

' + move.desc + '

'; } text += '
'; break; case 'pokemon': var pokemon = this.battle.getPokemon(thing); if (!pokemon) return; //fallthrough case 'sidepokemon': if (!pokemon) pokemon = this.battle.mySide.pokemon[parseInt(thing)]; text = '
'; text += '

' + pokemon.getFullName() + (pokemon.level !== 100 ? ' L' + pokemon.level + '' : '') + '
'; 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 += '(Transformed into '+pokemon.volatiles.formechange[2]+')
'; } else if (pokemon.volatiles.formechange) { template = Tools.getTemplate(pokemon.volatiles.formechange[2]); types = template.types; text += '(Forme: '+pokemon.volatiles.formechange[2]+')
'; } if (pokemon.volatiles.typechange) { text += '(Type changed)
'; types = [pokemon.volatiles.typechange[2]]; } text += Tools.getTypeIcon(types[0]); if (types[1]) { text += ' '+Tools.getTypeIcon(types[1]); } text += '

'; var exacthp = ''; if (pokemon.maxhp != 100 && pokemon.maxhp != 1000 && pokemon.maxhp != 48) exacthp = ' ('+pokemon.hp+'/'+pokemon.maxhp+')'; if (pokemon.maxhp == 48 && isActive) exacthp = ' ('+pokemon.hp+'/'+pokemon.maxhp+' pixels)'; text += '

HP: ' + pokemon.hpDisplay() +exacthp+(pokemon.status?' '+pokemon.status.toUpperCase()+'':'')+'

'; if (!pokemon.baseAbility && !pokemon.ability) { text += '

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 += '

'; } else if (pokemon.ability) { text += '

Ability: ' + Tools.getAbility(pokemon.ability).name + '

'; } else if (pokemon.baseAbility) { text += '

Ability: ' + Tools.getAbility(pokemon.baseAbility).name + '

'; } if (pokemon.item) { text += '

Item: ' + Tools.getItem(pokemon.item).name + '

'; } if (pokemon.moves && pokemon.moves.length && (!isActive || isActive === 'foe')) { text += '

'; for (var i = 0; i < pokemon.moves.length; i++) { var name = Tools.getMove(pokemon.moves[i]).name; text += '⋅ ' + name + '
'; } text += '

'; } text += '
'; break; } if (!$('#tooltipwrapper').length) $(document.body).append('
'); $('#tooltipwrapper').html(text).appendTo(document.body); }, hideTooltip: function() { $('#tooltipwrapper').html(''); } }); }).call(this, jQuery);