diff --git a/js/client-battle-tooltips.js b/js/client-battle-tooltips.js index 74942fca9..21eb945b7 100644 --- a/js/client-battle-tooltips.js +++ b/js/client-battle-tooltips.js @@ -1,4 +1,605 @@ var BattleTooltips = (function () { + + // In addition to some static methods: + // BattleTooltips.tooltipAttrs() + // BattleTooltips.showTooltip() + // BattleTooltips.hideTooltip() + // Most tooltips tend to involve a battle and/or room object + function BattleTooltips(battle, room) { + this.battle = battle; + this.room = room; + + // tooltips + var buf = ''; + var tooltips = { + your2: {top: 70, left: 250, width: 80, height: 100}, + your1: {top: 85, left: 320, width: 90, height: 100}, + your0: {top: 90, left: 390, width: 100, height: 100}, + my0: {top: 200, left: 130, width: 120, height: 160}, + my1: {top: 200, left: 250, width: 150, height: 160}, + my2: {top: 200, left: 350, width: 150, height: 160} + }; + for (var active in tooltips) { + buf += '
'; + } + room.$foeHint.html(buf); + } + + BattleTooltips.showTooltipFor = function (roomid, thing, type, elem, ownHeight) { + app.rooms[roomid].tooltips.showTooltip(thing, type, elem, ownHeight); + }; + BattleTooltips.hideTooltip = function () { + $('#tooltipwrapper').html(''); + }; + + // tooltips + BattleTooltips.prototype.tooltipAttrs = function (thing, type, ownHeight) { + var roomid = this.room.id; + return ' onmouseover="BattleTooltips.showTooltipFor(\'' + roomid + '\', \'' + Tools.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')" ontouchstart="BattleTooltips.showTooltipFor(\'' + roomid + '\', \'' + Tools.escapeHTML('' + thing, true) + '\',\'' + type + '\', this, ' + (ownHeight ? 'true' : 'false') + ')" onmouseout="BattleTooltips.hideTooltip()" onmouseup="BattleTooltips.hideTooltip()"'; + }; + + BattleTooltips.prototype.showTooltip = function (thing, type, elem, ownHeight) { + var room = this.room; + + var text = ''; + switch (type) { + case 'move': + var move = Tools.getMove(thing); + if (!move) return; + text = this.showMoveTooltip(move); + break; + + case 'pokemon': + var side = room.battle[thing.slice(0, -1) + "Side"]; + var pokemon = side.active[thing.slice(-1)]; + if (!pokemon) return; + /* falls through */ + case 'sidepokemon': + var myPokemon; + var isActive = (type === 'pokemon'); + if (room.myPokemon) { + if (!pokemon) { + myPokemon = room.myPokemon[parseInt(thing, 10)]; + pokemon = myPokemon; + } else if (room.controlsShown && pokemon.side === room.battle.mySide) { + // battlePokemon = pokemon; + myPokemon = room.myPokemon[pokemon.slot]; + } + } + text = this.showPokemonTooltip(pokemon, myPokemon, isActive); + break; + } + + 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 > room.leftWidth + 335) x = room.leftWidth + 335; + if (y < 140) y = 140; + if (x > $(window).width() - 303) x = Math.max($(window).width() - 303, 0); + + if (!$('#tooltipwrapper').length) $(document.body).append('
'); + $('#tooltipwrapper').css({ + left: x, + top: y + }); + $('#tooltipwrapper').html(text).appendTo(document.body); + if (elem) { + var height = $('#tooltipwrapper .tooltip').height(); + if (height > y) { + y += height + 10; + if (ownHeight) y += $(elem).height(); + else y += $(elem).parent().height(); + $('#tooltipwrapper').css('top', y); + } + } + }; + + BattleTooltips.prototype.hideTooltip = function () { + BattleTooltips.hideTooltip(); + }; + + BattleTooltips.prototype.showMoveTooltip = function (move) { + var text = ''; + var basePower = move.basePower; + var basePowerText = ''; + var additionalInfo = ''; + var yourActive = this.battle.yourSide.active; + var pokemon = this.battle.mySide.active[this.room.choice.choices.length]; + + // Check if there are more than one active Pokémon to check for multiple possible BPs. + if (yourActive.length > 1) { + // We check if there is a difference in base powers to note it. + // Otherwise, it is just shown as in singles. + // The trick is that we need to calculate it first for each Pokémon to see if it changes. + var previousBasepower = false; + var difference = false; + var basePowers = []; + for (var i = 0; i < yourActive.length; i++) { + if (!yourActive[i]) continue; + basePower = this.getMoveBasePower(move, pokemon, yourActive[i]); + if (previousBasepower === false) previousBasepower = basePower; + if (previousBasepower !== basePower) difference = true; + if (!basePower) basePower = '—'; + basePowers.push('Base power for ' + yourActive[i].name + ': ' + basePower); + } + if (difference) { + basePowerText = '

' + basePowers.join('
') + '

'; + } + // Falls through to not to repeat code on showing the base power. + } + if (!basePowerText) { + var activeTarget = yourActive[0] || yourActive[1] || yourActive[2]; + basePower = this.getMoveBasePower(move, pokemon, activeTarget) || basePower; + if (!basePower) basePower = '—'; + basePowerText = '

Base power: ' + basePower + '

'; + } + var accuracy = move.accuracy; + if (!accuracy || accuracy === true) accuracy = '—'; + else accuracy = '' + accuracy + '%'; + + // Handle move type for moves that vary their type. + var moveType = this.getMoveType(move, pokemon); + + // Deal with Nature Power special case, indicating which move it calls. + if (move.id === 'naturepower') { + if (this.battle.gen === 6) { + additionalInfo = 'Calls '; + if (this.battle.hasPseudoWeather('Electric Terrain')) { + additionalInfo += Tools.getTypeIcon('Electric') + ' Thunderbolt'; + } else if (this.battle.hasPseudoWeather('Grassy Terrain')) { + additionalInfo += Tools.getTypeIcon('Grass') + ' Energy Ball'; + } else if (this.battle.hasPseudoWeather('Misty Terrain')) { + additionalInfo += Tools.getTypeIcon('Fairy') + ' Moonblast'; + } else { + additionalInfo += Tools.getTypeIcon('Normal') + ' Tri Attack'; + } + } else if (this.battle.gen > 3) { + // In gens 4 and 5 it calls Earthquake. + additionalInfo = 'Calls ' + Tools.getTypeIcon('Ground') + ' Earthquake'; + } else { + // In gen 3 it calls Swift, so it retains its normal typing. + additionalInfo = 'Calls ' + Tools.getTypeIcon('Normal') + ' Swift'; + } + } + + text = '
'; + text += '

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

'; + text += basePowerText; + text += '

' + additionalInfo + '

'; + text += '

Accuracy: ' + accuracy + '

'; + var flags = { + "authentic": "Ignores a target's substitute.", + "bite": "Power is multiplied by 1.5 when used by a Pokemon with the Ability Strong Jaw.", + "bullet": "Has no effect on Pokemon with the Ability Bulletproof.", + "charge": "The user is unable to make a move between turns.", + "contact": "Makes contact.", + "defrost": "Thaws the user if executed successfully while the user is frozen.", + "distance": "Can target a Pokemon positioned anywhere in a Triple Battle.", + "gravity": "Prevented from being executed or selected during Gravity's effect.", + "heal": "Prevented from being executed or selected during Heal Block's effect.", + "mirror": "Can be copied by Mirror Move.", + "nonsky": "Prevented from being executed or selected in a Sky Battle.", + "powder": "Has no effect on Grass-type Pokemon, Pokemon with the Ability Overcoat, and Pokemon holding Safety Goggles.", + "protect": "Blocked by Detect, Protect, ", + "pulse": "Power is multiplied by 1.5 when used by a Pokemon with the Ability Mega Launcher.", + "punch": "Power is multiplied by 1.2 when used by a Pokemon with the Ability Iron Fist.", + "recharge": "If this move is successful, the user must recharge on the following turn and cannot make a move.", + "reflectable": "Bounced back to the original user by Magic Coat or the Ability Magic Bounce.", + "snatch": "Can be stolen from the original user and instead used by another Pokemon using Snatch.", + "sound": "Has no effect on Pokemon with the Ability Soundproof." + }; + if (move.desc) { + text += '

' + move.desc; + for (var i in move.flags) { + if (i === 'distance' && move.target !== 'any') continue; + text += " " + flags[i]; + if (i === 'protect') { + if (move.category !== 'Status') text += "King's Shield, "; + text += "and Spiky Shield."; + } + } + var priority = move.priority; + if (priority) { + text += " Priority "; + if (priority > 0) text += " + "; + text += priority + "."; + } + text += '

'; + } + text += '
'; + return text; + }; + + BattleTooltips.prototype.showPokemonTooltip = function (pokemon, myPokemon, isActive) { + var text = ''; + var gender = ''; + if (pokemon.gender === 'F') gender = ' ♀'; + if (pokemon.gender === 'M') gender = ' ♂'; + text = '
'; + text += '

' + pokemon.getFullName() + gender + (pokemon.level !== 100 ? ' L' + pokemon.level + '' : '') + '
'; + + var template = pokemon; + if (!pokemon.types) template = Tools.getTemplate(pokemon.species); + if (pokemon.volatiles && pokemon.volatiles.transform && pokemon.volatiles.formechange) { + template = Tools.getTemplate(pokemon.volatiles.formechange[2]); + text += '(Transformed into ' + pokemon.volatiles.formechange[2] + ')
'; + } else if (pokemon.volatiles && pokemon.volatiles.formechange) { + template = Tools.getTemplate(pokemon.volatiles.formechange[2]); + text += '(Forme: ' + pokemon.volatiles.formechange[2] + ')
'; + } + var types = template.types; + var isTypeChanged = false; + if (pokemon.volatiles && pokemon.volatiles.typechange) { + isTypeChanged = true; + types = pokemon.volatiles.typechange[2].split('/'); + } + if (pokemon.volatiles && pokemon.volatiles.typeadd) { + isTypeChanged = true; + if (types && types.indexOf(pokemon.volatiles.typeadd[2]) === -1) { + types = types.concat(pokemon.volatiles.typeadd[2]); + } + } + if (isTypeChanged) text += '(Type changed)
'; + if (types) { + text += types.map(Tools.getTypeIcon).join(' '); + } else { + text += 'Types unknown'; + } + text += '

'; + if (pokemon.fainted) { + text += '

HP: (fainted)

'; + } else { + var exacthp = ''; + if (myPokemon) exacthp = ' (' + myPokemon.hp + '/' + myPokemon.maxhp + ')'; + else if (pokemon.maxhp == 48) exacthp = ' (' + pokemon.hp + '/' + pokemon.maxhp + ' pixels)'; + text += '

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

'; + } + var showOtherSees = isActive; + if (myPokemon) { + if (this.battle.gen > 2) { + text += '

Ability: ' + Tools.getAbility(myPokemon.baseAbility).name; + if (myPokemon.item) { + text += ' / Item: ' + Tools.getItem(myPokemon.item).name; + } + text += '

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

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

'; + } + text += '

' + myPokemon.stats['atk'] + ' Atk / ' + myPokemon.stats['def'] + ' Def / ' + myPokemon.stats['spa']; + if (this.battle.gen === 1) { + text += ' Spc / '; + } else { + text += ' SpA / ' + myPokemon.stats['spd'] + ' SpD / '; + } + text += myPokemon.stats['spe'] + ' Spe

'; + if (isActive) text += '

Opponent sees:

'; + } else { + showOtherSees = true; + } + if (this.battle.gen > 2 && showOtherSees) { + if (!pokemon.baseAbility && !pokemon.ability) { + if (template.abilities) { + text += '

Possible abilities: ' + Tools.getAbility(template.abilities['0']).name; + if (template.abilities['1']) text += ', ' + Tools.getAbility(template.abilities['1']).name; + if (this.battle.gen > 4 && template.abilities['H']) text += ', ' + Tools.getAbility(template.abilities['H']).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 (showOtherSees) { + var item = ''; + var itemEffect = pokemon.itemEffect || ''; + if (pokemon.prevItem) { + item = 'None'; + if (itemEffect) itemEffect += '; '; + var prevItem = Tools.getItem(pokemon.prevItem).name; + itemEffect += pokemon.prevItemEffect ? prevItem + ' was ' + pokemon.prevItemEffect : 'was ' + prevItem; + } + if (pokemon.item) item = Tools.getItem(pokemon.item).name; + if (itemEffect) itemEffect = ' (' + itemEffect + ')'; + if (item) text += '

Item: ' + item + itemEffect + '

'; + + if (template.baseStats) { + text += '

' + this.getTemplateMinSpeed(template, pokemon.level) + ' to ' + this.getTemplateMaxSpeed(template, pokemon.level) + ' Spe (before items/abilities/modifiers)

'; + } + } + + if (myPokemon && !isActive) { + text += '

'; + var battlePokemon = this.battle.getPokemon('old: ' + pokemon.ident, pokemon.details); + for (var i = 0; i < myPokemon.moves.length; i++) { + var move = Tools.getMove(myPokemon.moves[i]); + var name = move.name; + var pp = 0, maxpp = 0; + if (battlePokemon && battlePokemon.moveTrack) { + for (var j = 0; j < battlePokemon.moveTrack.length; j++) { + if (name === battlePokemon.moveTrack[j][0]) { + maxpp = Math.floor(move.pp * 8 / 5); + pp = maxpp - battlePokemon.moveTrack[j][1]; + break; + } + } + } + text += '• ' + name + (maxpp ? ' (' + pp + '/' + maxpp + ')' : '') + '
'; + } + text += '

'; + } else if (pokemon.moveTrack && pokemon.moveTrack.length) { + text += '

'; + for (var i = 0; i < pokemon.moveTrack.length; i++) { + var moveName = pokemon.moveTrack[i][0]; + var move, maxpp; + if (moveName.charAt(0) === '*') { + moveName = moveName.substr(1); + move = Tools.getMove(moveName); + maxpp = 5; + } else { + move = Tools.getMove(moveName); + maxpp = Math.floor(move.pp * 8 / 5); + } + var pp = maxpp - pokemon.moveTrack[i][1]; + text += '• ' + move.name + ' (' + pp + '/' + maxpp + ')
'; + } + text += '

'; + } + text += '
'; + return text; + }; + + // Functions to calculate speed ranges of an opponent. + BattleTooltips.prototype.getTemplateMinSpeed = function (template, level) { + var nature = (this.battle.tier === 'Random Battle' || this.battle.gen < 3) ? 1 : 0.9; + return Math.floor(Math.floor(2 * template.baseStats['spe'] * level / 100 + 5) * nature); + }; + BattleTooltips.prototype.getTemplateMaxSpeed = function (template, level) { + var iv = (this.battle.gen < 3) ? 30 : 31; + var value = iv + (this.battle.tier === 'Random Battle' ? 21 : 63); + var nature = (this.battle.tier === 'Random Battle' || this.battle.gen < 3) ? 1 : 1.1; + return Math.floor(Math.floor(Math.floor(2 * template.baseStats['spe'] + value) * level / 100 + 5) * nature); + }; + + // Gets the proper current type for moves with a variable type. + BattleTooltips.prototype.getMoveType = function (move, pokemon) { + var myPokemon = this.room.myPokemon[pokemon.slot]; + var ability = Tools.getAbility(myPokemon.baseAbility).name; + var moveType = move.type; + // Normalize is the first move type changing effect. + if (ability === 'Normalize') { + moveType = 'Normal'; + } + // Moves that require an item to change their type. + if (!this.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) { + if (move.id === 'judgment') { + var item = Tools.getItem(myPokemon.item); + if (item.onPlate) moveType = item.onPlate; + } + if (move.id === 'technoblast') { + var item = Tools.getItem(myPokemon.item); + if (item.onDrive) moveType = item.onDrive; + } + if (move.id === 'naturalgift') { + var item = Tools.getItem(myPokemon.item); + if (item.naturalGift) moveType = item.naturalGift.type; + } + } + // Weather and pseudo-weather type changes. + if (move.id === 'weatherball' && this.battle.weather) { + // Check if you have an anti weather ability to skip this. + var noWeatherAbility = !!(ability in {'Air Lock': 1, 'Cloud Nine': 1}); + // If you don't, check if the opponent has it afterwards. + if (!noWeatherAbility) { + for (var i = 0; i < this.battle.yourSide.active.length; i++) { + if (this.battle.yourSide.active[i] && this.battle.yourSide.active[i].ability in {'Air Lock': 1, 'Cloud Nine': 1}) { + noWeatherAbility = true; + break; + } + } + } + + // If the weather is indeed active, check it to see what move type weatherball gets. + if (!noWeatherAbility) { + if (this.battle.weather === 'sunnyday' || this.battle.weather === 'desolateland') moveType = 'Fire'; + if (this.battle.weather === 'raindance' || this.battle.weather === 'primordialsea') moveType = 'Water'; + if (this.battle.weather === 'sandstorm') moveType = 'Rock'; + if (this.battle.weather === 'hail') moveType = 'Ice'; + } + } + // Other abilities that change the move type. + if (moveType === 'Normal' && move.category && move.category !== 'Status' && !(move.id in {'naturalgift': 1, 'struggle': 1})) { + if (ability === 'Aerilate') moveType = 'Flying'; + if (ability === 'Pixilate') moveType = 'Fairy'; + if (ability === 'Refrigerate') moveType = 'Ice'; + } + return moveType; + }; + // Gets the proper current base power for moves which have a variable base power. + // Takes into account the target for some moves. + // If it is unsure of the actual base power, it gives an estimate. + BattleTooltips.prototype.getMoveBasePower = function (move, pokemon, target) { + var myPokemon = this.room.myPokemon[pokemon.slot]; + var ability = Tools.getAbility(myPokemon.baseAbility).name; + var basePower = move.basePower; + var basePowerComment = ''; + var thereIsWeather = (this.battle.weather in {'sunnyday': 1, 'desolateland': 1, 'raindance': 1, 'primordialsea': 1, 'sandstorm': 1, 'hail':1}); + if (move.id === 'acrobatics') { + if (!myPokemon.item) { + basePower *= 2; + basePowerComment = ' (Boosted by lack of item)'; + } + } + if (move.id === 'crushgrip' || move.id === 'wringout') { + basePower = Math.floor(Math.floor((120 * (100 * Math.floor(target.hp * 4096 / target.maxhp)) + 2048 - 1) / 4096) / 100) || 1; + basePowerComment = ' (Approximation)'; + } + if (move.id === 'eruption' || move.id === 'waterspout') { + basePower = Math.floor(150 * pokemon.hp / pokemon.maxhp) || 1; + } + if (move.id === 'flail' || move.id === 'reversal') { + if (this.battle.gen > 4) { + var multiplier = 48; + var ratios = [2, 5, 10, 17, 33]; + } else { + var multiplier = 64; + var ratios = [2, 6, 13, 22, 43]; + } + var ratio = pokemon.hp * multiplier / pokemon.maxhp; + if (ratio < ratios[0]) basePower = 200; + else if (ratio < ratios[1]) basePower = 150; + else if (ratio < ratios[2]) basePower = 100; + else if (ratio < ratios[3]) basePower = 80; + else if (ratio < ratios[4]) basePower = 40; + else basePower = 20; + } + if (move.id === 'hex' && target.status) { + basePower *= 2; + basePowerComment = ' (Boosted by status)'; + } + if (move.id === 'punishment') { + var boosts = Object.keys(target.boosts); + var multiply = 0; + for (var i = 0; i < boosts.length; i++) { + if (target.boosts[boosts[i]] > 0) multiply += target.boosts[boosts[i]]; + } + basePower = 60 + 20 * multiply; + if (basePower > 200) basePower = 200; + } + if (move.id === 'smellingsalts') { + if (target.status === 'par') { + basePower *= 2; + basePowerComment = ' (Boosted by status)'; + } + } + if (move.id === 'storedpower') { + var boosts = Object.keys(pokemon.boosts); + var multiply = 0; + for (var i = 0; i < boosts.length; i++) { + if (pokemon.boosts[boosts[i]] > 0) multiply += pokemon.boosts[boosts[i]]; + } + basePower = 20 + 20 * multiply; + } + if (move.id === 'trumpcard') { + basePower = 40; + if (move.pp === 1) basePower = 200; + else if (move.pp === 2) basePower = 80; + else if (move.pp === 3) basePower = 60; + else if (move.pp === 4) basePower = 50; + } + if (move.id === 'venoshock') { + if (target.status === 'psn' || target.status === 'tox') { + basePower *= 2; + basePowerComment = ' (Boosted by status)'; + } + } + if (move.id === 'wakeupslap') { + if (target.status === 'slp') { + basePower *= 2; + basePowerComment = ' (Boosted by status)'; + } + } + if (move.id === 'weatherball' && thereIsWeather) { + basePower = 100; + } + // Moves that check opponent speed. + if (move.id === 'electroball') { + var template = target; + var min = 0; + var max = 0; + if (target.volatiles && target.volatiles.formechange) template = Tools.getTemplate(target.volatiles.formechange[2]); + var minRatio = (myPokemon.stats['spe'] / this.getTemplateMaxSpeed(template, target.level)); + var maxRatio = (myPokemon.stats['spe'] / this.getTemplateMinSpeed(template, target.level)); + if (minRatio >= 4) min = 150; + else if (minRatio >= 3) min = 120; + else if (minRatio >= 2) min = 80; + else if (minRatio >= 1) min = 60; + else min = 40; + if (maxRatio >= 4) max = 150; + else if (maxRatio >= 3) max = 120; + else if (maxRatio >= 2) max = 80; + else if (maxRatio >= 1) max = 60; + else max = 40; + // Special case due to being a range. + return this.boostBasePowerRange(move, pokemon, min, max); + } + if (move.id === 'gyroball') { + var template = target; + if (target.volatiles && target.volatiles.formechange) template = Tools.getTemplate(target.volatiles.formechange[2]); + var min = (Math.floor(25 * this.getTemplateMinSpeed(template, target.level) / myPokemon.stats['spe']) || 1); + var max = (Math.floor(25 * this.getTemplateMaxSpeed(template, target.level) / myPokemon.stats['spe']) || 1); + if (min > 150) min = 150; + if (max > 150) max = 150; + // Special case due to range as well. + return this.boostBasePowerRange(move, pokemon, min, max); + } + // Movements which have base power changed due to items. + if (myPokemon.item && !this.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) { + if (move.id === 'fling') { + var item = Tools.getItem(myPokemon.item); + if (item.fling) basePower = item.fling.basePower; + } + if (move.id === 'naturalgift') { + var item = Tools.getItem(myPokemon.item); + if (item.naturalGift) basePower = item.naturalGift.basePower; + } + } + // Movements which have base power changed according to weight. + if (target && target.weightkg) { + var targetWeight = target.weightkg; + var pokemonWeight = pokemon.weightkg; + // Autotomize cannot be really known on client, so we calculate it's one charge. + if (target.volatiles && target.volatiles.autotomize) targetWeight -= 100; + if (targetWeight < 0.1) targetWeight = 0.1; + if (move.id === 'lowkick' || move.id === 'grassknot') { + basePower = 20; + if (targetWeight >= 200) basePower = 120; + else if (targetWeight >= 100) basePower = 100; + else if (targetWeight >= 50) basePower = 80; + else if (targetWeight >= 25) basePower = 60; + else if (targetWeight >= 10) basePower = 40; + if (target.volatiles && target.volatiles.autotomize) basePowerComment = ' (Approximation)'; + } + if (move.id === 'heavyslam' || move.id === 'heatcrash') { + basePower = 40; + if (pokemonWeight > targetWeight * 5) basePower = 120; + else if (pokemonWeight > targetWeight * 4) basePower = 100; + else if (pokemonWeight > targetWeight * 3) basePower = 80; + else if (pokemonWeight > targetWeight * 2) basePower = 60; + if (target.volatiles && target.volatiles.autotomize) basePowerComment = ' (Approximation)'; + } + } + if (!basePower) return basePowerComment; + + // Other ability boosts. + if (ability === 'Technician' && basePower <= 60) { + basePower *= 1.5; + basePowerComment = ' (Technician boosted)'; + } + if (move.type === 'Normal' && move.category !== 'Status' && !(move.id in {'naturalgift': 1, 'struggle': 1}) && (!thereIsWeather || thereIsWeather && move.id !== 'weatherball')) { + if (ability in {'Aerilate': 1, 'Pixilate': 1, 'Refrigerate': 1}) { + basePower = Math.floor(basePower * 1.3); + basePowerComment = ' (' + ability + ' boosted)'; + } + } + return this.boostBasePower(move, pokemon, basePower, basePowerComment); + }; + var incenseTypes = { 'Odd Incense': 'Psychic', 'Rock Incense': 'Rock', @@ -32,13 +633,12 @@ var BattleTooltips = (function () { 'Struggle': 1, 'Water Pledge': 1 }; - function BattleTooltips() {} - BattleTooltips.prototype.getItemBoost = function (BattleRoom, move, pokemon) { - var myPokemon = BattleRoom.myPokemon[pokemon.slot]; - if (!myPokemon.item || BattleRoom.battle.hasPseudoWeather('Magic Room') || pokemon.volatiles && pokemon.volatiles['embargo']) return 0; + BattleTooltips.prototype.getItemBoost = function (move, pokemon) { + var myPokemon = this.room.myPokemon[pokemon.slot]; + if (!myPokemon.item || this.battle.hasPseudoWeather('Magic Room') || pokemon.volatiles && pokemon.volatiles['embargo']) return 0; var item = Tools.getItem(myPokemon.item); - var moveType = BattleRoom.getMoveType(move, pokemon); + var moveType = this.getMoveType(move, pokemon); var itemName = item.name; var moveName = move.name; @@ -49,31 +649,31 @@ var BattleTooltips = (function () { if (incenseTypes[item.name] === moveType) return 1.2; // Type-enhancing items - if (itemTypes[item.name] === moveType) return BattleRoom.battle.gen < 4 ? 1.1 : 1.2; + if (itemTypes[item.name] === moveType) return this.battle.gen < 4 ? 1.1 : 1.2; // Gems if (moveName in noGemMoves) return 0; - if (itemName === moveType + ' Gem') return BattleRoom.battle.gen < 6 ? 1.5 : 1.3; + if (itemName === moveType + ' Gem') return this.battle.gen < 6 ? 1.5 : 1.3; return 0; }; - BattleTooltips.prototype.boostBasePower = function (BattleRoom, move, pokemon, basePower, basePowerComment) { - var itemBoost = this.getItemBoost(BattleRoom, move, pokemon); + BattleTooltips.prototype.boostBasePower = function (move, pokemon, basePower, basePowerComment) { + var itemBoost = this.getItemBoost(move, pokemon); if (itemBoost) { basePower = Math.floor(basePower * itemBoost); - var myPokemon = BattleRoom.myPokemon[pokemon.slot]; + var myPokemon = this.room.myPokemon[pokemon.slot]; basePowerComment += ' (Boosted by ' + Tools.getItem(myPokemon.item).name + ')'; } return basePower + basePowerComment; }; - BattleTooltips.prototype.boostBasePower_Ball = function (BattleRoom, move, pokemon, min, max) { - var myPokemon = BattleRoom.myPokemon[pokemon.slot]; + BattleTooltips.prototype.boostBasePowerRange = function (move, pokemon, min, max) { + var myPokemon = this.room.myPokemon[pokemon.slot]; var technician = Tools.getAbility(myPokemon.baseAbility).name === 'Technician'; if (technician) { if (min <= 60) min *= 1.5; if (max <= 60) max *= 1.5; } - var itemBoost = this.getItemBoost(BattleRoom, move, pokemon); + var itemBoost = this.getItemBoost(move, pokemon); if (itemBoost) { min *= itemBoost; max *= itemBoost; diff --git a/js/client-battle.js b/js/client-battle.js index b36b74e0d..ac17d82c3 100644 --- a/js/client-battle.js +++ b/js/client-battle.js @@ -15,29 +15,12 @@ this.$chatFrame = this.$el.find('.battle-log'); this.$chatAdd = this.$el.find('.battle-log-add'); this.$join = null; - - // tooltips - var buf = ''; - var tooltips = { - your2: {top: 70, left: 250, width: 80, height: 100}, - your1: {top: 85, left: 320, width: 90, height: 100}, - your0: {top: 90, left: 390, width: 100, height: 100}, - my0: {top: 200, left: 130, width: 120, height: 160}, - my1: {top: 200, left: 250, width: 150, height: 160}, - my2: {top: 200, left: 350, width: 150, height: 160} - }; - for (var active in tooltips) { - buf += '
'; - } this.$foeHint = this.$el.find('.foehint'); - this.$foeHint.html(buf); BattleSound.setMute(Tools.prefs('mute')); this.battle = new Battle(this.$battle, this.$chatFrame); + this.tooltips = new BattleTooltips(this.battle, this); + this.battle.roomid = this.id; this.users = {}; @@ -107,7 +90,7 @@ this.add(data); }, focus: function () { - this.hideTooltip(); + this.tooltips.hideTooltip(); if (this.battle.playbackState === 3) this.battle.play(); ConsoleRoom.prototype.focus.call(this); }, @@ -389,7 +372,7 @@ } else if (!pokemon || pokemon.zerohp) { controls += ' '; } else { - controls += ' '; + controls += ' '; } } controls += '
'; @@ -409,7 +392,7 @@ } else if (!pokemon || pokemon.zerohp) { controls += ' '; } else { - controls += ' '; + controls += ' '; } } controls += '
'; @@ -441,12 +424,12 @@ if (move.id === 'Struggle' || move.id === 'Recharge') pp = '–'; if (move.id === 'Recharge') move.type = '–'; if (name.substr(0, 12) === 'Hidden Power') name = 'Hidden Power'; - var moveType = this.getMoveType(move, this.battle.mySide.active[pos]); + var moveType = this.tooltips.getMoveType(move, this.battle.mySide.active[pos]); if (moveData.disabled) { - movebuttons += ' '; @@ -476,9 +459,9 @@ var pokemon = switchables[i]; pokemon.name = pokemon.ident.substr(4); if (pokemon.zerohp || i < this.battle.mySide.active.length || this.choice.switchFlags[i]) { - controls += ' '; + controls += ' '; } else { - controls += ' '; + controls += ' '; } } if (this.finalDecisionSwitch && this.battle.gen > 2) { @@ -528,11 +511,11 @@ pokemon = this.myPokemon[i]; if (pokemon && !pokemon.zerohp || this.choice.switchOutFlags[i]) { - controls += ' '; + controls += ' '; } else if (!pokemon) { controls += ' '; } else { - controls += ' '; + controls += ' '; } } controls += ''; @@ -549,9 +532,9 @@ 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 += ' '; } @@ -584,7 +567,7 @@ if (toId(pokemon.baseAbility) === 'illusion') { this.choice.count = 6; } - controls += ' '; + controls += ' '; } if (this.battle.teamPreviewCount) this.choice.count = parseInt(this.battle.teamPreviewCount, 10); controls += ''; @@ -597,9 +580,9 @@ break; } if (i < this.choice.done) { - controls += ' '; + controls += ' '; } else { - controls += ' '; + controls += ' '; } } controls += ''; @@ -735,7 +718,7 @@ this.battle.switchSides(); }, instantReplay: function () { - this.hideTooltip(); + this.tooltips.hideTooltip(); this.request = null; this.battle.reset(); this.battle.play(); @@ -767,7 +750,7 @@ // choice buttons chooseMove: function (pos, e) { var myActive = this.battle.mySide.active; - this.hideTooltip(); + this.tooltips.hideTooltip(); var isMega = !!(this.$('input[name=megaevo]')[0] || '').checked; if (pos !== undefined) { var move = e.getAttribute('data-move'); @@ -803,7 +786,7 @@ }, chooseShift: function () { var myActive = this.battle.mySide.active; - this.hideTooltip(); + this.tooltips.hideTooltip(); this.choice.choices.push('shift'); while (myActive.length > this.choice.choices.length && !myActive[this.choice.choices.length]) { @@ -822,7 +805,7 @@ this.updateControlsForPlayer(); }, chooseSwitch: function (pos) { - this.hideTooltip(); + this.tooltips.hideTooltip(); if (pos !== undefined) { this.choice.switchFlags[pos] = true; // Just chose the current position of the pokemon that will switch in. @@ -890,7 +873,7 @@ }, chooseTeamPreview: function (pos) { pos = parseInt(pos, 10); - this.hideTooltip(); + this.tooltips.hideTooltip(); if (this.choice.count) { var temp = this.choice.teamPreview[pos]; this.choice.teamPreview[pos] = this.choice.teamPreview[this.choice.done]; @@ -915,7 +898,7 @@ this.updateControlsForPlayer(); }, chooseDisabled: function (data) { - this.hideTooltip(); + this.tooltips.hideTooltip(); data = data.split(','); if (data[1] === 'fainted') { app.addPopupMessage(data[0] + " has no energy left to battle!"); @@ -937,561 +920,18 @@ this.updateControlsForPlayer(); }, leaveBattle: function () { - this.hideTooltip(); + this.tooltips.hideTooltip(); this.send('/leavebattle'); this.side = ''; this.closeNotification('choice'); }, selectSwitch: function () { - this.hideTooltip(); + this.tooltips.hideTooltip(); this.$controls.find('.controls').attr('class', 'controls switch-controls'); }, selectMove: function () { - this.hideTooltip(); + this.tooltips.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') + ')" ontouchstart="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 > this.leftWidth + 335) x = this.leftWidth + 335; - if (y < 140) y = 140; - if (x > $(window).width() - 303) x = Math.max($(window).width() - 303, 0); - if (!$('#tooltipwrapper').length) $(document.body).append('
'); - $('#tooltipwrapper').css({ - left: x, - top: y - }); - - var text = ''; - switch (type) { - case 'move': - var move = Tools.getMove(thing); - if (!move) return; - var basePower = move.basePower; - var basePowerText = ''; - var additionalInfo = ''; - var yourActive = this.battle.yourSide.active; - var pokemon = this.battle.mySide.active[this.choice.choices.length]; - - // Check if there are more than one active Pokémon to check for multiple possible BPs. - if (yourActive.length > 1) { - // We check if there is a difference in base powers to note it. - // Otherwise, it is just shown as in singles. - // The trick is that we need to calculate it first for each Pokémon to see if it changes. - var previousBasepower = false; - var difference = false; - var basePowers = []; - for (var i = 0; i < yourActive.length; i++) { - if (!yourActive[i]) continue; - basePower = this.getMoveBasePower(move, pokemon, yourActive[i]); - if (previousBasepower === false) previousBasepower = basePower; - if (previousBasepower !== basePower) difference = true; - if (!basePower) basePower = '—'; - basePowers.push('Base power for ' + yourActive[i].name + ': ' + basePower); - } - if (difference) { - basePowerText = '

' + basePowers.join('
') + '

'; - } - // Falls through to not to repeat code on showing the base power. - } - if (!basePowerText) { - var activeTarget = yourActive[0] || yourActive[1] || yourActive[2]; - basePower = this.getMoveBasePower(move, pokemon, activeTarget) || basePower; - if (!basePower) basePower = '—'; - basePowerText = '

Base power: ' + basePower + '

'; - } - var accuracy = move.accuracy; - if (!accuracy || accuracy === true) accuracy = '—'; - else accuracy = '' + accuracy + '%'; - - // Handle move type for moves that vary their type. - var moveType = this.getMoveType(move, pokemon); - - // Deal with Nature Power special case, indicating which move it calls. - if (move.id === 'naturepower') { - if (this.battle.gen === 6) { - additionalInfo = 'Calls '; - if (this.battle.hasPseudoWeather('Electric Terrain')) { - additionalInfo += Tools.getTypeIcon('Electric') + ' Thunderbolt'; - } else if (this.battle.hasPseudoWeather('Grassy Terrain')) { - additionalInfo += Tools.getTypeIcon('Grass') + ' Energy Ball'; - } else if (this.battle.hasPseudoWeather('Misty Terrain')) { - additionalInfo += Tools.getTypeIcon('Fairy') + ' Moonblast'; - } else { - additionalInfo += Tools.getTypeIcon('Normal') + ' Tri Attack'; - } - } else if (this.battle.gen > 3) { - // In gens 4 and 5 it calls Earthquake. - additionalInfo = 'Calls ' + Tools.getTypeIcon('Ground') + ' Earthquake'; - } else { - // In gen 3 it calls Swift, so it retains its normal typing. - additionalInfo = 'Calls ' + Tools.getTypeIcon('Normal') + ' Swift'; - } - } - - text = '
'; - text += '

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

'; - text += basePowerText; - text += '

' + additionalInfo + '

'; - text += '

Accuracy: ' + accuracy + '

'; - var flags = { - "authentic": "Ignores a target's substitute.", - "bite": "Power is multiplied by 1.5 when used by a Pokemon with the Ability Strong Jaw.", - "bullet": "Has no effect on Pokemon with the Ability Bulletproof.", - "charge": "The user is unable to make a move between turns.", - "contact": "Makes contact.", - "defrost": "Thaws the user if executed successfully while the user is frozen.", - "distance": "Can target a Pokemon positioned anywhere in a Triple Battle.", - "gravity": "Prevented from being executed or selected during Gravity's effect.", - "heal": "Prevented from being executed or selected during Heal Block's effect.", - "mirror": "Can be copied by Mirror Move.", - "nonsky": "Prevented from being executed or selected in a Sky Battle.", - "powder": "Has no effect on Grass-type Pokemon, Pokemon with the Ability Overcoat, and Pokemon holding Safety Goggles.", - "protect": "Blocked by Detect, Protect, ", - "pulse": "Power is multiplied by 1.5 when used by a Pokemon with the Ability Mega Launcher.", - "punch": "Power is multiplied by 1.2 when used by a Pokemon with the Ability Iron Fist.", - "recharge": "If this move is successful, the user must recharge on the following turn and cannot make a move.", - "reflectable": "Bounced back to the original user by Magic Coat or the Ability Magic Bounce.", - "snatch": "Can be stolen from the original user and instead used by another Pokemon using Snatch.", - "sound": "Has no effect on Pokemon with the Ability Soundproof." - }; - if (move.desc) { - text += '

' + move.desc; - for (var i in move.flags) { - if (i === 'distance' && move.target !== 'any') continue; - text += " " + flags[i]; - if (i === 'protect') { - if (move.category !== 'Status') text += "King's Shield, "; - text += "and Spiky Shield."; - } - } - var priority = move.priority; - if (priority) { - text += " Priority "; - if (priority > 0) text += " + "; - text += priority + "."; - } - text += '

'; - } - text += '
'; - break; - - case 'pokemon': - var side = this.battle[thing.slice(0, -1) + "Side"]; - var pokemon = side.active[thing.slice(-1)]; - if (!pokemon) return; - /* falls through */ - case 'sidepokemon': - var battlePokemon; - var myPokemon; - if (this.myPokemon) { - if (!pokemon) { - pokemon = this.myPokemon[parseInt(thing, 10)]; - battlePokemon = this.battle.getPokemon('other: old: ' + pokemon.ident, pokemon.details); - } else if (this.controlsShown && pokemon.side === this.battle.mySide) { - myPokemon = this.myPokemon[pokemon.slot]; - } - } - var gender = ''; - if (pokemon.gender === 'F') gender = ' ♀'; - if (pokemon.gender === 'M') gender = ' ♂'; - text = '
'; - text += '

' + pokemon.getFullName() + gender + (pokemon.level !== 100 ? ' L' + pokemon.level + '' : '') + '
'; - - var template = pokemon; - if (!pokemon.types) template = Tools.getTemplate(pokemon.species); - if (pokemon.volatiles && pokemon.volatiles.transform && pokemon.volatiles.formechange) { - template = Tools.getTemplate(pokemon.volatiles.formechange[2]); - text += '(Transformed into ' + pokemon.volatiles.formechange[2] + ')
'; - } else if (pokemon.volatiles && pokemon.volatiles.formechange) { - template = Tools.getTemplate(pokemon.volatiles.formechange[2]); - text += '(Forme: ' + pokemon.volatiles.formechange[2] + ')
'; - } - var types = template.types; - var isTypeChanged = false; - if (pokemon.volatiles && pokemon.volatiles.typechange) { - isTypeChanged = true; - types = pokemon.volatiles.typechange[2].split('/'); - } - if (pokemon.volatiles && pokemon.volatiles.typeadd) { - isTypeChanged = true; - if (types && types.indexOf(pokemon.volatiles.typeadd[2]) === -1) { - types = types.concat(pokemon.volatiles.typeadd[2]); - } - } - if (isTypeChanged) text += '(Type changed)
'; - if (types) { - text += types.map(Tools.getTypeIcon).join(' '); - } else { - text += 'Types unknown'; - } - text += '

'; - if (pokemon.fainted) { - text += '

HP: (fainted)

'; - } else { - 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 (myPokemon) { - if (this.battle.gen > 2) { - text += '

Ability: ' + Tools.getAbility(myPokemon.baseAbility).name; - if (myPokemon.item) { - text += ' / Item: ' + Tools.getItem(myPokemon.item).name; - } - text += '

'; - } else if (myPokemon.item) { - text += '

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

'; - } - text += '

' + myPokemon.stats['atk'] + ' Atk / ' + myPokemon.stats['def'] + ' Def / ' + myPokemon.stats['spa']; - if (this.battle.gen === 1) { - text += ' Spc / '; - } else { - text += ' SpA / ' + myPokemon.stats['spd'] + ' SpD / '; - } - text += myPokemon.stats['spe'] + ' Spe

'; - text += '

Opponent sees:

'; - } - if (this.battle.gen > 2) { - if (!pokemon.baseAbility && !pokemon.ability) { - if (template.abilities) { - text += '

Possible abilities: ' + Tools.getAbility(template.abilities['0']).name; - if (template.abilities['1']) text += ', ' + Tools.getAbility(template.abilities['1']).name; - if (this.battle.gen > 4 && template.abilities['H']) text += ', ' + Tools.getAbility(template.abilities['H']).name; - text += '

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

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

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

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

'; - } - } - var item = ''; - var itemEffect = pokemon.itemEffect || ''; - if (pokemon.prevItem) { - item = 'None'; - if (itemEffect) itemEffect += '; '; - var prevItem = Tools.getItem(pokemon.prevItem).name; - itemEffect += pokemon.prevItemEffect ? prevItem + ' was ' + pokemon.prevItemEffect : 'was ' + prevItem; - } - if (pokemon.item) item = Tools.getItem(pokemon.item).name; - if (itemEffect) itemEffect = ' (' + itemEffect + ')'; - if (item) text += '

Item: ' + item + itemEffect + '

'; - if (pokemon.stats) { - text += '

' + pokemon.stats['atk'] + ' Atk / ' + pokemon.stats['def'] + ' Def / ' + pokemon.stats['spa']; - if (this.battle.gen === 1) { - text += ' Spc / '; - } else { - text += ' SpA / ' + pokemon.stats['spd'] + ' SpD / '; - } - text += pokemon.stats['spe'] + ' Spe

'; - } else if (template.baseStats) { - text += '

' + this.getTemplateMinSpeed(template, pokemon.level) + ' to ' + this.getTemplateMaxSpeed(template, pokemon.level) + ' Spe (before items/abilities/modifiers)

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

'; - for (var i = 0; i < pokemon.moves.length; i++) { - var move = Tools.getMove(pokemon.moves[i]); - var name = move.name; - var pp = 0, maxpp = 0; - if (battlePokemon) { - for (var j = 0; j < battlePokemon.moveTrack.length; j++) { - if (name === battlePokemon.moveTrack[j][0]) { - maxpp = Math.floor(move.pp * 8 / 5); - pp = maxpp - battlePokemon.moveTrack[j][1]; - break; - } - } - } - text += '⋅ ' + name + (maxpp ? ' (' + pp + '/' + maxpp + ')' : '') + '
'; - } - text += '

'; - } else if (pokemon.moveTrack && pokemon.moveTrack.length) { - text += '

'; - for (var i = 0; i < pokemon.moveTrack.length; i++) { - var moveName = pokemon.moveTrack[i][0]; - var move, maxpp; - if (moveName.charAt(0) === '*') { - moveName = moveName.substr(1); - move = Tools.getMove(moveName); - maxpp = 5; - } else { - move = Tools.getMove(moveName); - maxpp = Math.floor(move.pp * 8 / 5); - } - var pp = maxpp - pokemon.moveTrack[i][1]; - text += '⋅ ' + move.name + ' (' + pp + '/' + maxpp + ')
'; - } - text += '

'; - } - text += '
'; - break; - } - $('#tooltipwrapper').html(text).appendTo(document.body); - if (elem) { - var height = $('#tooltipwrapper .tooltip').height(); - if (height > y) { - y += height + 10; - if (ownHeight) y += $(elem).height(); - else y += $(elem).parent().height(); - $('#tooltipwrapper').css('top', y); - } - } - }, - hideTooltip: function () { - $('#tooltipwrapper').html(''); - }, - // Functions to calculate speed ranges of an opponent. - getTemplateMinSpeed: function (template, level) { - var nature = (this.battle.tier === 'Random Battle' || this.battle.gen < 3) ? 1 : 0.9; - return Math.floor(Math.floor(2 * template.baseStats['spe'] * level / 100 + 5) * nature); - }, - getTemplateMaxSpeed: function (template, level) { - var iv = (this.battle.gen < 3) ? 30 : 31; - var value = iv + (this.battle.tier === 'Random Battle' ? 21 : 63); - var nature = (this.battle.tier === 'Random Battle' || this.battle.gen < 3) ? 1 : 1.1; - return Math.floor(Math.floor(Math.floor(2 * template.baseStats['spe'] + value) * level / 100 + 5) * nature); - }, - // Gets the proper current type for moves with a variable type. - getMoveType: function (move, pokemon) { - var myPokemon = this.myPokemon[pokemon.slot]; - var ability = Tools.getAbility(myPokemon.baseAbility).name; - var moveType = move.type; - // Normalize is the first move type changing effect. - if (ability === 'Normalize') { - moveType = 'Normal'; - } - // Moves that require an item to change their type. - if (!this.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) { - if (move.id === 'judgment') { - var item = Tools.getItem(myPokemon.item); - if (item.onPlate) moveType = item.onPlate; - } - if (move.id === 'technoblast') { - var item = Tools.getItem(myPokemon.item); - if (item.onDrive) moveType = item.onDrive; - } - if (move.id === 'naturalgift') { - var item = Tools.getItem(myPokemon.item); - if (item.naturalGift) moveType = item.naturalGift.type; - } - } - // Weather and pseudo-weather type changes. - if (move.id === 'weatherball' && this.battle.weather) { - // Check if you have an anti weather ability to skip this. - var noWeatherAbility = !!(ability in {'Air Lock': 1, 'Cloud Nine': 1}); - // If you don't, check if the opponent has it afterwards. - if (!noWeatherAbility) { - for (var i = 0; i < this.battle.yourSide.active.length; i++) { - if (this.battle.yourSide.active[i] && this.battle.yourSide.active[i].ability in {'Air Lock': 1, 'Cloud Nine': 1}) { - noWeatherAbility = true; - break; - } - } - } - - // If the weather is indeed active, check it to see what move type weatherball gets. - if (!noWeatherAbility) { - if (this.battle.weather === 'sunnyday' || this.battle.weather === 'desolateland') moveType = 'Fire'; - if (this.battle.weather === 'raindance' || this.battle.weather === 'primordialsea') moveType = 'Water'; - if (this.battle.weather === 'sandstorm') moveType = 'Rock'; - if (this.battle.weather === 'hail') moveType = 'Ice'; - } - } - // Other abilities that change the move type. - if (moveType === 'Normal' && move.category && move.category !== 'Status' && !(move.id in {'naturalgift': 1, 'struggle': 1})) { - if (ability === 'Aerilate') moveType = 'Flying'; - if (ability === 'Pixilate') moveType = 'Fairy'; - if (ability === 'Refrigerate') moveType = 'Ice'; - } - return moveType; - }, - // Gets the proper current base power for moves which have a variable base power. - // Takes into account the target for some moves. - // If it is unsure of the actual base power, it gives an estimate. - getMoveBasePower: function (move, pokemon, target) { - var myPokemon = this.myPokemon[pokemon.slot]; - var ability = Tools.getAbility(myPokemon.baseAbility).name; - var basePower = move.basePower; - var basePowerComment = ''; - var Tooltips = new BattleTooltips(); - var thereIsWeather = (this.battle.weather in {'sunnyday': 1, 'desolateland': 1, 'raindance': 1, 'primordialsea': 1, 'sandstorm': 1, 'hail':1}); - if (move.id === 'acrobatics') { - if (!myPokemon.item) { - basePower *= 2; - basePowerComment = ' (Boosted by lack of item)'; - } - } - if (move.id === 'crushgrip' || move.id === 'wringout') { - basePower = Math.floor(Math.floor((120 * (100 * Math.floor(target.hp * 4096 / target.maxhp)) + 2048 - 1) / 4096) / 100) || 1; - basePowerComment = ' (Approximation)'; - } - if (move.id === 'eruption' || move.id === 'waterspout') { - basePower = Math.floor(150 * pokemon.hp / pokemon.maxhp) || 1; - } - if (move.id === 'flail' || move.id === 'reversal') { - if (this.battle.gen > 4) { - var multiplier = 48; - var ratios = [2, 5, 10, 17, 33]; - } else { - var multiplier = 64; - var ratios = [2, 6, 13, 22, 43]; - } - var ratio = pokemon.hp * multiplier / pokemon.maxhp; - if (ratio < ratios[0]) basePower = 200; - else if (ratio < ratios[1]) basePower = 150; - else if (ratio < ratios[2]) basePower = 100; - else if (ratio < ratios[3]) basePower = 80; - else if (ratio < ratios[4]) basePower = 40; - else basePower = 20; - } - if (move.id === 'hex' && target.status) { - basePower *= 2; - basePowerComment = ' (Boosted by status)'; - } - if (move.id === 'punishment') { - var boosts = Object.keys(target.boosts); - var multiply = 0; - for (var i = 0; i < boosts.length; i++) { - if (target.boosts[boosts[i]] > 0) multiply += target.boosts[boosts[i]]; - } - basePower = 60 + 20 * multiply; - if (basePower > 200) basePower = 200; - } - if (move.id === 'smellingsalts') { - if (target.status === 'par') { - basePower *= 2; - basePowerComment = ' (Boosted by status)'; - } - } - if (move.id === 'storedpower') { - var boosts = Object.keys(pokemon.boosts); - var multiply = 0; - for (var i = 0; i < boosts.length; i++) { - if (pokemon.boosts[boosts[i]] > 0) multiply += pokemon.boosts[boosts[i]]; - } - basePower = 20 + 20 * multiply; - } - if (move.id === 'trumpcard') { - basePower = 40; - if (move.pp === 1) basePower = 200; - else if (move.pp === 2) basePower = 80; - else if (move.pp === 3) basePower = 60; - else if (move.pp === 4) basePower = 50; - } - if (move.id === 'venoshock') { - if (target.status === 'psn' || target.status === 'tox') { - basePower *= 2; - basePowerComment = ' (Boosted by status)'; - } - } - if (move.id === 'wakeupslap') { - if (target.status === 'slp') { - basePower *= 2; - basePowerComment = ' (Boosted by status)'; - } - } - if (move.id === 'weatherball' && thereIsWeather) { - basePower = 100; - } - // Moves that check opponent speed. - if (move.id === 'electroball') { - var template = target; - var min = 0; - var max = 0; - if (target.volatiles && target.volatiles.formechange) template = Tools.getTemplate(target.volatiles.formechange[2]); - var minRatio = (myPokemon.stats['spe'] / this.getTemplateMaxSpeed(template, target.level)); - var maxRatio = (myPokemon.stats['spe'] / this.getTemplateMinSpeed(template, target.level)); - if (minRatio >= 4) min = 150; - else if (minRatio >= 3) min = 120; - else if (minRatio >= 2) min = 80; - else if (minRatio >= 1) min = 60; - else min = 40; - if (maxRatio >= 4) max = 150; - else if (maxRatio >= 3) max = 120; - else if (maxRatio >= 2) max = 80; - else if (maxRatio >= 1) max = 60; - else max = 40; - // Special case due to being a range. - return Tooltips.boostBasePower_Ball(this, move, pokemon, min, max); - } - if (move.id === 'gyroball') { - var template = target; - if (target.volatiles && target.volatiles.formechange) template = Tools.getTemplate(target.volatiles.formechange[2]); - var min = (Math.floor(25 * this.getTemplateMinSpeed(template, target.level) / myPokemon.stats['spe']) || 1); - var max = (Math.floor(25 * this.getTemplateMaxSpeed(template, target.level) / myPokemon.stats['spe']) || 1); - if (min > 150) min = 150; - if (max > 150) max = 150; - // Special case due to range as well. - return Tooltips.boostBasePower_Ball(this, move, pokemon, min, max); - } - // Movements which have base power changed due to items. - if (myPokemon.item && !this.battle.hasPseudoWeather('Magic Room') && (!pokemon.volatiles || !pokemon.volatiles['embargo'])) { - if (move.id === 'fling') { - var item = Tools.getItem(myPokemon.item); - if (item.fling) basePower = item.fling.basePower; - } - if (move.id === 'naturalgift') { - var item = Tools.getItem(myPokemon.item); - if (item.naturalGift) basePower = item.naturalGift.basePower; - } - } - // Movements which have base power changed according to weight. - if (target && target.weightkg) { - var targetWeight = target.weightkg; - var pokemonWeight = pokemon.weightkg; - // Autotomize cannot be really known on client, so we calculate it's one charge. - if (target.volatiles && target.volatiles.autotomize) targetWeight -= 100; - if (targetWeight < 0.1) targetWeight = 0.1; - if (move.id === 'lowkick' || move.id === 'grassknot') { - basePower = 20; - if (targetWeight >= 200) basePower = 120; - else if (targetWeight >= 100) basePower = 100; - else if (targetWeight >= 50) basePower = 80; - else if (targetWeight >= 25) basePower = 60; - else if (targetWeight >= 10) basePower = 40; - if (target.volatiles && target.volatiles.autotomize) basePowerComment = ' (Approximation)'; - } - if (move.id === 'heavyslam' || move.id === 'heatcrash') { - basePower = 40; - if (pokemonWeight > targetWeight * 5) basePower = 120; - else if (pokemonWeight > targetWeight * 4) basePower = 100; - else if (pokemonWeight > targetWeight * 3) basePower = 80; - else if (pokemonWeight > targetWeight * 2) basePower = 60; - if (target.volatiles && target.volatiles.autotomize) basePowerComment = ' (Approximation)'; - } - } - if (!basePower) return basePowerComment; - - // Other ability boosts. - if (ability === 'Technician' && basePower <= 60) { - basePower *= 1.5; - basePowerComment = ' (Technician boosted)'; - } - if (move.type === 'Normal' && move.category !== 'Status' && !(move.id in {'naturalgift': 1, 'struggle': 1}) && (!thereIsWeather || thereIsWeather && move.id !== 'weatherball')) { - if (ability in {'Aerilate': 1, 'Pixilate': 1, 'Refrigerate': 1}) { - basePower = Math.floor(basePower * 1.3); - basePowerComment = ' (' + ability + ' boosted)'; - } - } - return Tooltips.boostBasePower(this, move, pokemon, basePower, basePowerComment); } });