From cbbe5e1a765f4bb27347715309ac83ac3fcb5f33 Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Fri, 3 May 2013 15:55:46 -0700 Subject: [PATCH] Support playing in battles --- js/client-battle.js | 916 +++++++++++++++++++++++++------------------- js/client.js | 39 +- style/client.css | 260 ++++++++++++- 3 files changed, 805 insertions(+), 410 deletions(-) diff --git a/js/client-battle.js b/js/client-battle.js index b97e68d91..ecb23f41d 100644 --- a/js/client-battle.js +++ b/js/client-battle.js @@ -80,16 +80,17 @@ var logLine = log[i]; if (logLine === '') { - this.me.callbackWaiting = false; + 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.add(logLine, Tools.prefs('noanim')); + this.battle.activityQueue.push(logLine); } } + this.battle.add('', Tools.prefs('noanim')); this.updateControls(); }, updateUser: function() { @@ -149,246 +150,164 @@ *********************************************************/ updateControls: function() { - if (this.battle.playbackState === 2) { - this.$controls.html('Playing.'); - } else { - this.$controls.html('Not playing.'); + if (this.$join) { + this.$join.remove(); + this.$join = null; } - }, - updateJoinButton: function() { - if (this.battle.done) this.battleEnded = true; - if (selfR.battleEnded) { - selfR.controlsElem.html('
'); - if (selfR.me.side) { - selfR.controlsElem.html('
'); - } - if (selfR.joinElem) { - selfR.joinElem.remove(); - } - } 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('

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

'); - } else { - selfR.controlsElem.html('

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

'); - } - } else { - if (selfR.joinElem) { - selfR.joinElem.remove(); - } - selfR.battleElem.append('
'); - selfR.joinElem = selfR.battleElem.children().last(); - } - }, - // 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.me.request.rqid); - }, - request: null, - receiveRequest: function(request) { - this.request = request; // currently unused - request.requestType = 'move'; - var notifyObject = null; - if (request.forceSwitch) { - request.requestType = 'switch'; - notifyObject = { - type: 'yourSwitch', - room: selfR.id - }; - } else if (request.teamPreview) { - request.requestType = 'team'; - notifyObject = { - type: 'yourSwitch', - room: selfR.id - }; - } else if (request.wait) { - 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); - // }; - // } - // } - }, - 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 = 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(); - } - } - }, - callback: function (battle, type, moveTarget) { - if (!battle) battle = selfR.battle; - selfR.notifying = false; - if (type === 'restart') { - selfR.me.callbackWaiting = false; - selfR.battleEnded = true; - updateRoomList(); + + if (this.battle.playbackState === 5) { + + // battle is seeking + this.$controls.html(''); return; - } else if (type === 'trapped') { + + } 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); + + } + }, + 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 (selfR.me.request && selfR.me.request.active && - selfR.me.request.active[idx]) { + if (this.request && this.request.active && + this.request.active[idx]) { // This pokemon is now known to be trapped. - selfR.me.request.active[idx].trapped = true; + 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. - selfR.battle.add(selfR.battle.mySide.active[idx].getName() + ' is trapped!'); + this.battle.add(this.battle.mySide.active[idx].getName() + ' is trapped!'); } } - var myActive = selfR.battle.mySide.active; - var yourActive = selfR.battle.yourSide.active; - var text = ''; - if (yourActive[1]) { - text += '
'; - } - if (yourActive[0]) { - text += '
'; - } - if (myActive[0]) { - text += '
'; - } - if (myActive[1]) { - text += '
'; - } - selfR.foeHintElem.html(text); - - if (!selfR.me.request) { - selfR.controlsElem.html('
Waiting for players...
'); - 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]; + this.callbackWaiting = true; + var active = this.battle.mySide.active[0]; if (!active) active = {}; - if (selfR.battle.kickingInactive) { - selfR.controlsElem.html('
Waiting for opponent...
'); - } else { - selfR.controlsElem.html('
Waiting for opponent...
'); - } + var act = ''; var switchables = []; - - if (selfR.me.request) { - act = selfR.me.request.requestType; - if (selfR.me.request.side) { - switchables = selfR.battle.mySide.pokemon; + 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 (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 = ''; - } else if (switchables[pos].hp * 2 / switchables[pos].maxhp < 1) { - hpbar = ''; - } else { - hpbar = ''; + if (!this.choice) { + this.choice = { + choices: [], + switchFlags: {} + } + while (switchables[this.choice.choices.length] && switchables[this.choice.choices.length].fainted) { + this.choice.choices.push('pass'); } - hpbar += ''+switchables[pos].hp+'/'+switchables[pos].maxhp+''; } - var active = selfR.me.request; + 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; - selfR.me.finalDecision = active.maybeTrapped || false; - if (selfR.me.finalDecision) { - for (var i = pos + 1; i < selfR.battle.mySide.active.length; ++i) { - var p = selfR.battle.mySide.active[i]; + 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) { - selfR.me.finalDecision = false; + this.finalDecision = false; } } } var controls = '
'; if (type === 'move2' || type === 'movetarget') { - controls += ' '; + controls += ' '; } // Target selector @@ -397,8 +316,8 @@ controls += 'At who? '+hpbar+'
'; controls += '
'; - var myActive = selfR.battle.mySide.active; - var yourActive = selfR.battle.yourSide.active; + 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]; @@ -411,11 +330,12 @@ } if (!pokemon) { - controls += ' '; + controls += ' '; } else if (disabled || pokemon.zerohp) { - controls += ' '; + controls += ' '; } else { - controls += ' '; + var posString = ''; + controls += ' '; } } controls += '
'; @@ -433,14 +353,14 @@ if (!pokemon) { controls += ' '; } else if (disabled || pokemon.zerohp) { - controls += ' '; + controls += ' '; } else { - controls += ' '; + controls += ' '; } } controls += '
'; controls += '
'; - selfR.controlsElem.html(controls); + this.$controls.html(controls); break; } @@ -449,7 +369,7 @@ controls += 'What will ' + Tools.escapeHTML(switchables[pos].name) + ' do? '+hpbar+''; var hasMoves = false; var hasDisabled = false; - controls += '
'; + controls += '
'; var movebuttons = ''; for (var i = 0; i < moves.length; i++) { var moveData = moves[i]; @@ -468,16 +388,16 @@ if (move.id === 'Recharge') move.type = '–'; if (name.substr(0, 12) === 'Hidden Power') name = 'Hidden Power'; if (moveData.disabled) { - movebuttons += ' '; } if (!hasMoves) { - controls += ' '; + controls += ' '; } else { controls += movebuttons; } @@ -485,279 +405,471 @@ if (hasDisabled) { // controls += '(grayed out moves have been disabled by Disable, Encore, or something like that)'; } - controls += '
'; + controls += '
'; if (trapped) { controls += 'You are trapped and cannot switch!'; } else { controls += ''; - if (selfR.me.finalDecision) { + 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 < selfR.battle.mySide.active.length || selfR.choiceSwitchFlags[i]) { - controls += ' '; + if (pokemon.zerohp || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) { + controls += ' '; } else { - controls += ' '; + controls += ' '; } } - if (selfR.battle.mySide.pokemon.length > 6) { - //controls += 'Pokeball data corrupt. Please copy the text from this button: and tell aesoft.'; - } } controls += '
'; - selfR.controlsElem.html(controls); + this.$controls.html(controls); } - selfR.notifying = true; + this.notifying = true; break; + case 'switch': - selfR.me.finalDecision = false; - 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'); + 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 = selfR.choices.length; + var pos = this.choice.choices.length; var controls = '
'; if (type === 'switch2') { - controls += ' '; + controls += ' '; } controls += 'Switch '+Tools.escapeHTML(switchables[pos].name)+' to:
'; - controls += '
'; + controls += '
'; for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[i]; - if (i >= 6) { - //controls += 'Pokeball data corrupt. Please copy the text from this button: and tell aesoft.'; - break; - } - if (pokemon.zerohp || i < selfR.battle.mySide.active.length || selfR.choiceSwitchFlags[i]) { - controls += ' '; + if (pokemon.zerohp || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) { + controls += ' '; + controls += ' '; } controls += '
'; - selfR.controlsElem.html(controls); - selfR.formSelectSwitch(); - selfR.notifying = true; + this.$controls.html(controls); + this.selectSwitch(); + this.notifying = true; break; + case 'team': var controls = '
'; - 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; + if (!this.choice) { + 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 += '
'; + controls += '
'; for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[i]; if (i >= 6) { break; } if (toId(pokemon.baseAbility) === 'illusion') { - selfR.teamPreviewCount = 6; + this.choice.count = 6; } - controls += ' '; + controls += ' '; } - if (selfR.battle.teamPreviewCount) selfR.teamPreviewCount = parseInt(selfR.battle.teamPreviewCount,10); + if (this.battle.teamPreviewCount) this.choice.count = parseInt(this.battle.teamPreviewCount,10); controls += '
'; } else { - controls += ' What about the rest of your team?
'; - controls += '
'; + controls += ' What about the rest of your team?
'; + controls += '
'; for (var i = 0; i < switchables.length; i++) { - var pokemon = switchables[selfR.teamPreviewChoice[i]-1]; + var pokemon = switchables[this.choice.teamPreview[i]-1]; if (i >= 6) { break; } - if (i < selfR.teamPreviewDone) { - controls += ' '; + if (i < this.choice.done) { + controls += ' '; } else { - controls += ' '; + controls += ' '; } } controls += '
'; } controls += '
'; - selfR.controlsElem.html(controls); - selfR.formSelectSwitch(); - selfR.notifying = true; + 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; } - updateRoomList(); }, - formJoinBattle: function () { - selfR.send('/joinbattle'); - return false; + // 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); }, - formKickInactive: function () { - selfR.send('/kickinactive'); - return false; + 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); + // }; + // } + // } }, - formStopBattleTimer: function () { - selfR.send('/timer off'); - return false; + 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(); + } + } }, - formForfeit: function () { - selfR.send('/forfeit'); - return false; + 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(); }, - formSaveReplay: function () { - selfR.send('/savereplay'); - return false; + + // buttons + joinBattle: function() { + this.send('/joinbattle'); }, - formRestart: function () { - /* hideTooltip(); - selfR.send('/restart'); */ - selfR.me.request = null; - selfR.battle.reset(); - selfR.battle.play(); - return false; + setTimer: function(setting) { + this.send('/timer '+setting); }, - formUseMove: function (move, target) { - var myActive = selfR.battle.mySide.active; - hideTooltip(); + 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}; - selfR.choices.push('move '+move); + this.choice.choices.push('move '+move); if (myActive.length > 1 && target in choosableTargets) { - selfR.callback(selfR.battle, 'movetarget', target); + this.choice.type = 'movetarget'; + this.choice.moveTarget = target; + this.updateControlsForPlayer(); return false; } } - while (myActive.length > selfR.choices.length && !myActive[selfR.choices.length]) { - selfR.choices.push('pass'); + while (myActive.length > this.choice.choices.length && !myActive[this.choice.choices.length]) { + this.choice.choices.push('pass'); } - if (myActive.length > selfR.choices.length) { - selfR.callback(selfR.battle, 'move2'); + if (myActive.length > this.choice.choices.length) { + this.choice.type = 'move2'; + this.updateControlsForPlayer(); return false; } - selfR.me.finalDecision = false; - if (selfR.battle.kickingInactive) { - selfR.controlsElem.html('
Waiting for opponent... ' + (selfR.me.finalDecision ? '' : '') + '

'); - } else { - selfR.controlsElem.html('
Waiting for opponent... ' + (selfR.me.finalDecision ? '' : '') + '

'); - } - selfR.sendDecision('/choose '+selfR.choices.join(',')); - selfR.notifying = false; - updateRoomList(); - return false; + + this.sendDecision('/choose '+this.choice.choices.join(',')); + this.notifying = false; + + this.finalDecision = false; + this.choice = {waiting: true}; + this.updateControlsForPlayer(); }, - 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; + chooseMoveTarget: function(posString) { + this.choices.choices[this.choices.choices.length-1] += ' '+posString; + this.chooseMove(); }, - 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'); + 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 (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 (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 (selfR.battle.mySide.active.length > selfR.choices.length) { - selfR.callback(selfR.battle, 'switch2'); + if (this.battle.mySide.active.length > this.choice.choices.length) { + this.choice.type = 'switch2'; + this.updateControlsForPlayer(); return false; } } - if (selfR.battle.kickingInactive) { - selfR.controlsElem.html('
Waiting for opponent... ' + (selfR.me.finalDecision ? '' : '') + '

'); - } else { - selfR.controlsElem.html('
Waiting for opponent... ' + (selfR.me.finalDecision ? '' : '') + '

'); - } - selfR.sendDecision('/choose '+selfR.choices.join(',')); - selfR.notifying = false; - updateRoomList(); - return false; + + this.sendDecision('/choose '+this.choice.choices.join(',')); + this.notifying = false; + + this.choice = {waiting: true}; + this.updateControlsForPlayer(); }, - formTeamPreviewSelect: function (pos) { + chooseTeamPreview: 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; + 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; - selfR.teamPreviewDone++; + this.choice.done++; - if (selfR.teamPreviewDone < Math.min(selfR.teamPreviewChoice.length, selfR.teamPreviewCount)) { - selfR.callback(selfR.battle, 'team2'); + if (this.choice.done < Math.min(this.choice.teamPreview.length, this.choice.count)) { + this.choice.type = 'team2'; + this.updateControlsForPlayer(); return false; } - pos = selfR.teamPreviewChoice.join(''); + pos = this.choice.teamPreview.join(''); } else { pos = pos+1; } - if (selfR.battle.kickingInactive) { - selfR.controlsElem.html('
Waiting for opponent... ' + (selfR.me.finalDecision ? '' : '') + '

'); - } else { - selfR.controlsElem.html('
Waiting for opponent... ' + (selfR.me.finalDecision ? '' : '') + '

'); + + 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) { + if (!$('#tooltipwrapper')) $(body).append('
'); + return false; + 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(); } - selfR.sendDecision('/team '+(pos)); - selfR.notifying = false; - updateRoomList(); - return false; - }, - formUndoDecision: function (pos) { - selfR.send('/undo'); - selfR.notifying = true; - selfR.callback(selfR.battle, 'decision'); - return false; - }, - // Key press in the battle chat textbox. - 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(''); + 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 = '—'; + 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 + '

'; } - return false; + text += '
'; + 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 = '
'; + 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; } + $('#tooltipwrapper').html(text); return true; }, - formRename: function () { - overlay('rename'); - return false; - }, - formLeaveBattle: function () { - hideTooltip(); - selfR.send('/leavebattle'); - selfR.notifying = false; - updateRoomList(); - return false; - }, - formSelectSwitch: function () { - hideTooltip(); - selfR.controlsElem.find('.controls').attr('class', 'controls switch-controls'); - return false; - }, - formSelectMove: function () { - hideTooltip(); - selfR.controlsElem.find('.controls').attr('class', 'controls move-controls'); - return false; + hideTooltip: function() { + $('#tooltipwrapper').html(''); } }); diff --git a/js/client.js b/js/client.js index 79e9a5748..abff9483d 100644 --- a/js/client.js +++ b/js/client.js @@ -350,8 +350,12 @@ * This object defines event handles for JSON-style messages. */ var events = { + /** + * These are all deprecated. Stop using them. :| + */ init: function (data) { if (data.name !== undefined) { + // Legacy self.user.set({ name: data.name, userid: toUserid(data.name), @@ -359,6 +363,10 @@ }); } if (data.room) { + // Correct way to initialize rooms: + // >ROOMID + // |init|ROOMTYPE + // LOG if (data.room === 'lobby') { self.addRoom('lobby'); } else { @@ -370,10 +378,10 @@ self.rooms[data.room].init(data.battlelog.join('\n')); } } - // TODO: All other handling of `init` messages. }, update: function (data) { if (data.name !== undefined) { + // Legacy self.user.set({ name: data.name, userid: toUserid(data.name), @@ -384,34 +392,51 @@ } } if (data.updates) { + // Correct way to send battlelog updates: + // >ROOMID + // BATTLELOG var room = self.rooms[data.room]; if (room) room.receive(data.updates.join('\n')); } if (data.challengesFrom) { + // Legacy if (self.rooms['']) self.rooms[''].updateChallenges(data); } - // TODO: All other handling of `update` messages. + if (data.request) { + // Legacy + var room = self.rooms[data.room]; + if (room && room.receiveRequest) { + if (data.request.side) data.request.side.id = data.side; + room.receiveRequest(data.request); + } + } + }, + message: function (message) { + // Correct way to send popups: (unimplemented) + // |popup|MESSAGE }, - /** - * These are all deprecated. Stop using them. :| - */ - message: function (message) {}, console: function (message) { if (message.pm) { - // the only case we're going to handle + // Correct way to send PMs: (unimplemented) + // |pm|SOURCE|TARGET|MESSAGE self.rooms[''].addPM(message.name, message.message, message.pm); if (self.rooms['lobby']) { self.rooms['lobby'].addPM(message.name, message.message, message.pm); } } else if (message.rawMessage) { + // Correct way to send raw console messages: + // |raw|RAWMESSAGE self.receive('|raw|'+message.rawMessage); } else { + // Correct way to send console messages: + // MESSAGE self.receive(message.message); } }, disconnect: function () {}, nameTaken: function (data) {}, command: function (message) { + // Legacy self.trigger('response:'+message.command, message); } }; diff --git a/style/client.css b/style/client.css index fc4142ea8..2e5aa4e99 100644 --- a/style/client.css +++ b/style/client.css @@ -831,11 +831,269 @@ a.ilink:hover { } .ps-room .battle-controls { position: absolute; - top: 360px; + top: 370px; left: 0; width: 640px; } +.battle-controls .whatdo { + margin-top: -2px; + padding: 0 8px; + font-size: 9pt; + color: #555555; +} +.battle-controls .whatdo small { + float: right; + padding: 1px 2px; + border: 1px solid #999999; + border-radius: 4px; +} +.battle-controls .whatdo small.weak { + color: #AAAA22; + border-color: #AAAA22; +} +.battle-controls .whatdo small.critical { + color: #EE4433; + border-color: #EE4433; +} +.tiny-layout .battlewrapper .battle-log, +.tiny-layout .battlewrapper .battle-log-add, +.small-layout .battlewrapper .battle-log, +.small-layout .battlewrapper .battle-log-add { + display: none; +} + +.tiny-height .controls { + position: absolute; + bottom: 10px; + left: 0; + right: 0; + background: #444444; + background: rgba(40,40,40,.85); + color: #FFFFFF; + padding: 4px 8px; +} +.tiny-height .battle-controls .whatdo { + color: #FFFFFF; +} +.tiny-height .battle-controls .whatdo small.weak { + color: #DDDD55; + border-color: #DDDD55; +} +.tiny-height .battle-controls .whatdo small.critical { + color: #FF7766; + border-color: #FF7766; +} +.tiny-height .movemenu { + display: none; + padding: 0 100px 0 90px; +} +.tiny-height .switchmenu { + display: none; + padding: 0 70px 0 180px; +} +.tiny-height .moveselect { + position: absolute; + left: 20px; + bottom: 20px; +} +.tiny-height .switchselect { + position: absolute; + right: 20px; + bottom: 20px; +} +.moveselect button, +.switchselect button { + background: transparent; + border: 0; + font-weight: bold; + font-style: italic; + color: #555555; + font-size: 12pt; + display: block; + margin: 0; + padding: 9px 7px 1px 7px; +} +.moveselect button { + color: #884422; +} +.switchselect button { + color: #445588; +} +.switchmenu button { + position: relative; + display: block; + float: left; + width: 100px; + margin-right: 6px; + font: 9pt Verdana, sans-serif; + padding: 5px 5px 5px 0; + white-space: pre; + overflow: hidden; +} +.switchmenu button .pokemonicon { + float: left; + margin: -4px 0; + opacity: 0.8; +} +.switchmenu button .hpbar { + position: absolute; + display: block; + border: 1px solid #AAA; + background: #EEE; + height: 2px; + bottom: 2px; left: 2px; right: 2px; + border-radius: 2px; + opacity: .6; +} + +/* The declaration order of these three hpbar* classes is significant. */ + +.switchmenu button .hpbar span { + display: block; + height: 1px; + background: #0A6; + border-top: 1px solid #3C0; + border-radius: 1px; +} +.switchmenu button .hpbar-yellow span { + border-top-color: #a5aa53; + background-color: #a2a822; +} +.switchmenu button .hpbar-red span { + border-top-color: #faa; + background-color: #f55; +} + +/****************/ + +.switchmenu button .status { + position: absolute; + display: block; + right: 2px; + bottom: 1px; + width: 5px; + height: 5px; + padding: 0; + border-radius: 2px; + border: 1px solid #C2C2C2; + opacity: 1.0; +} +.status { + color: white; + border-radius: 3px; + padding: 0 2px; +} +.status.brn { + background: #EE5533; +} +.status.psn, .status.tox { + background: #A4009A; +} +.status.par { + background: #9AA400; +} +.status.slp { + background: #AA77AA; +} +.status.frz { + background: #009AA4; +} + +.tiny-height .battle-controls .whatdo { + padding-bottom: 50px; +} +.tiny-height .battle-controls .move-controls .whatdo, +.tiny-height .battle-controls .switch-controls .whatdo { + padding-bottom: 5px; +} +.tiny-height .move-controls .movemenu, +.tiny-height .switch-controls .switchmenu { + display: block; + margin-right: 0; +} + +.movemenu button { + float: left; + display: block; + width: 153px; + margin-right: 6px; + height: 40px; + font: 10pt/100% Verdana, sans-serif; + position: relative; + padding: 6px 4px 0 4px; +} +.movemenu button small { + color: #777777; +} +.movemenu button small.type { + padding-top: 3px; + float: left; + font-size: 7pt; +} +.movemenu button small.pp { + padding-top: 3px; + float: right; + font-size: 7pt; +} + +.switchmenu, +.movemenu { + display: block; + margin-right: -10px; + padding-left: 5px; +} +.switchmenu button, +.movemenu button { + position: relative; + outline: none; + cursor: pointer; + text-align: center; + text-decoration: none; + text-shadow: 0 1px 0 rgba(255,255,255,.4); + border-radius: 5px; + margin-top: 2px; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + box-shadow: 0 1px 2px rgba(0,0,0,.2), inset 0 -1px 2px rgba(255,255,255,1); + + /* default colors */ + color: #111111; + border: solid 1px #AAAAAA; + background: #e3e3e3; + background: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); + background: -moz-linear-gradient(top, #f6f6f6, #e3e3e3); + background: linear-gradient(top, #f6f6f6, #e3e3e3); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#e3e3e3'); +} +.switchmenu button:hover, +.movemenu button:hover { + background: #cfcfcf; + background: -webkit-gradient(linear, left top, left bottom, from(#f2f2f2), to(#cfcfcf)); + background: -moz-linear-gradient(top, #f2f2f2, #cfcfcf); + background: linear-gradient(top, #f2f2f2, #cfcfcf); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f2f2f2', endColorstr='#cfcfcf'); + border-color: #606060; +} +.switchmenu button:active, +.movemenu button:active { + background: -webkit-gradient(linear, left top, left bottom, from(#cfcfcf), to(#f2f2f2)); + background: -moz-linear-gradient(top, #cfcfcf, #f2f2f2); + background: linear-gradient(top, #cfcfcf, #f2f2f2); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#cfcfcf', endColorstr='#f2f2f2'); +} + +.switchmenu button:disabled, +.movemenu button:disabled { + cursor: default; + background: #F3F3F3 !important; + border-color: #CCCCCC !important; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; + color: #777777 !important; +} + /********************************************************* * Teambuilder *********************************************************/