function floor(num) { return Math.floor(num); } function clampIntRange(num, min, max) { num = Math.floor(num); if (num < min) num = min; if (typeof max !== 'undefined' && num > max) num = max; return num; } function toId(text) { text = text || ''; if (typeof text === 'number') text = ''+text; if (typeof text !== 'string') return ''; //??? return text.toLowerCase().replace(/[^a-z0-9]+/g, ''); } function shuffle(array) { var tmp, current, top = array.length; if(top) while(--top) { current = Math.floor(Math.random() * (top + 1)); tmp = array[current]; array[current] = array[top]; array[top] = tmp; } return array; } function objectKeys(object) { var keys = []; for (var prop in object) { if (object.hasOwnProperty(prop)) { keys.push(prop); } } return keys; } function BattlePokemon(set, side) { var selfB = side.battle; var selfS = side; var selfP = this; this.side = side; if (typeof set === 'string') set = {name: set}; this.baseSet = set; this.set = this.baseSet; this.name = set.name || set.species || 'Bulbasaur'; this.species = set.species || this.name; if (!BattlePokedex[toId(this.species)]) { selfB.debug('Unidentified species: '+this.species); this.species = 'Bulbasaur'; } this.speciesid = toId(this.species); this.baseTemplate = selfB.getTemplate(this.species); this.template = this.baseTemplate; this.moves = []; this.baseMoves = this.moves; this.movepp = {}; this.moveset = []; this.baseMoveset = []; this.trapped = false; this.level = clampIntRange(set.level || 100, 1, 100); this.hp = 0; this.maxhp = 100; var genders = {M:'M',F:'F'}; this.gender = this.template.gender || genders[set.gender] || (Math.random()*2<1?'M':'F'); if (this.gender === 'N') this.gender = ''; this.fullname = this.side.id + ': ' + this.name; this.details = this.species + (this.level==100?'':', L'+this.level) + (this.gender===''?'':', '+this.gender); this.baseFullid = this.fullid; this.id = toId(this.fullid); this.baseId = this.id; this.fainted = false; this.lastItem = ''; this.status = ''; this.statusData = {}; this.volatiles = {}; this.position = 0; this.lastMove = ''; this.lastDamage = 0; this.lastAttackedBy = null; this.movedThisTurn = false; this.usedItemThisTurn = false; this.newlySwitched = false; this.beingCalledBack = false; this.isActive = false; this.transformed = false; this.negateImmunity = {}; this.height = this.template.height; this.heightm = this.template.heightm; this.weight = this.template.weight; this.weightkg = this.template.weightkg; this.ignore = {}; this.duringMove = false; this.baseAbility = toId(set.ability); this.ability = this.baseAbility; this.item = toId(set.item); this.abilityData = {id: this.ability}; this.itemData = {id: this.item}; this.hpType = 'Dark'; this.hpPower = 70; if (this.set.moves) { for (var i=0; i 520 * 1.06) { stat *= 520 / selfP.bst * 1.06; // stat scaling in progress } if (selfP.set.bstscale && selfP.bst < 520 * 0.94) { stat *= 520 / selfP.bst * 0.94; // stat scaling in progress } */ if (i==='hp') { continue; } else { selfP.unboostedStats[i] = floor(floor(2*stat+selfP.set.ivs[i]+floor(selfP.set.evs[i]/4))*selfP.level / 100 + 5); } selfP.stats[i] = selfP.unboostedStats[i]; } for (var i in selfP.baseBoosts) { selfP.boosts[i] = selfP.baseBoosts[i]; } BattleScripts.natureModify(selfP.stats, selfP.set.nature); for (var i in selfP.stats) { selfP.stats[i] = floor(selfP.stats[i]); } if (init) return; selfB.runEvent('ModifyStats', selfP, null, null, selfP.stats); for (var i in selfP.stats) { selfP.stats[i] = floor(selfP.stats[i]); selfP.unboostedStats[i] = selfP.stats[i]; } var boostTable = [1,1.5,2,2.5,3,3.5,4]; for (i in selfP.boosts) { if (selfP.boosts[i] > 6) selfP.boosts[i] = 6; if (selfP.boosts[i] < -6) selfP.boosts[i] = -6; if (i === 'accuracy' || i === 'evasion' || i === 'hp') continue; // hp should never happen if (selfP.boosts[i] >= 0) { selfP.stats[i] = floor(selfP.unboostedStats[i] * boostTable[selfP.boosts[i]]); } else { selfP.stats[i] = floor(selfP.unboostedStats[i] / boostTable[-selfP.boosts[i]]); } } selfB.runEvent('ModifyPokemon', selfP); }; this.getMoveData = function(move) { move = selfB.getMove(move); for (var i=0; i 0) boosts += selfP.boosts[i]; } return boosts; }; this.boostBy = function(boost, source, effect) { var changed = false; for (var i in boost) { var delta = boost[i]; selfP.baseBoosts[i] += delta; if (selfP.baseBoosts[i] > 6) { delta -= selfP.baseBoosts[i] - 6; selfP.baseBoosts[i] = 6; } if (selfP.baseBoosts[i] < -6) { delta -= selfP.baseBoosts[i] - (-6); selfP.baseBoosts[i] = -6; } if (delta) changed = true; } selfP.update(); return changed; }; this.clearBoosts = function() { for (var i in selfP.baseBoosts) { selfP.baseBoosts[i] = 0; } selfP.update(); }; this.setBoost = function(boost) { for (var i in boost) { selfP.baseBoosts[i] = boost[i]; } selfP.update(); }; this.copyVolatileFrom = function(pokemon) { selfP.clearVolatile(); selfP.baseBoosts = pokemon.baseBoosts; selfP.volatiles = pokemon.volatiles; selfP.update(); pokemon.clearVolatile(); for (var i in selfP.volatiles) { var status = selfP.getVolatile(i); if (status.noCopy) { delete selfP.volatiles[i]; } } }; this.transformInto = function(baseTemplate) { var pokemon = null; if (baseTemplate.template) { pokemon = baseTemplate; baseTemplate = pokemon.template; if (pokemon.fainted || pokemon.id !== pokemon.baseId || pokemon.volatiles['Substitute']) { return false; } } else if (!baseTemplate.abilities) { baseTemplate = selfB.getTemplate(baseTemplate); } if (pokemon && pokemon.transformed) { return false; } selfP.transformed = true; selfP.template = baseTemplate; selfP.baseStats = selfP.template.baseStats; selfP.types = baseTemplate.types; if (pokemon) { selfP.ability = pokemon.ability; selfP.set = pokemon.set; selfP.moveset = []; for (var i=0; i 0) d = 1; d = floor(d); if (isNaN(d)) return 0; if (d <= 0) return 0; selfP.hp -= d; if (selfP.hp <= 0) { d += selfP.hp; selfP.faint(source, effect); } return d; }; this.hasMove = function(moveid) { if (moveid.id) moveid = moveid.id; moveid = toId(moveid); for (var i=0; i= selfP.maxhp) return 0; selfP.hp += d; if (selfP.hp > selfP.maxhp) { d -= selfP.hp - selfP.maxhp; selfP.hp = selfP.maxhp; } return d; }; // sets HP, returns delta this.sethp = function(d) { if (!selfP.hp) return 0; d = floor(d); if (isNaN(d)) return; if (d < 1) d = 1; d = d-selfP.hp; selfP.hp += d; if (selfP.hp > selfP.maxhp) { d -= selfP.hp - selfP.maxhp; selfP.hp = selfP.maxhp; } return d; }; this.trySetStatus = function(status, source, sourceEffect) { if (!selfP.hp) return false; if (selfP.status) return false; return selfP.setStatus(status, source, sourceEffect); }; this.cureStatus = function() { if (!selfP.hp) return false; // unlike clearStatus, gives cure message if (selfP.status) { selfB.add('-curestatus',selfP.fullname,selfP.status); selfP.setStatus(''); } }; this.setStatus = function(status, source, sourceEffect, ignoreImmunities) { if (!selfP.hp) return false; status = selfB.getEffect(status); if (selfB.event) { if (!source) source = selfB.event.source; if (!sourceEffect) sourceEffect = selfB.effect; } if (!ignoreImmunities && status.id) { // the game currently never ignores immunities if (!selfP.runImmunity(status.id==='tox'?'psn':status.id)) { selfB.debug('immune to status'); return false; } } if (selfP.status === status.id) return false; var prevStatus = selfP.status; var prevStatusData = selfP.statusData; if (status.id && !selfB.runEvent('SetStatus', selfP, source, sourceEffect, status)) { selfB.debug('set status ['+status.id+'] interrupted'); return false; } selfP.status = status.id; selfP.statusData = {id: status.id, target: selfP}; if (source) selfP.statusData.source = source; if (status.id && !selfB.singleEvent('Start', status, selfP.statusData, selfP, source, sourceEffect)) { selfB.debug('status start ['+status.id+'] interrupted'); // cancel the setstatus selfP.status = prevStatus; selfP.statusData = prevStatusData; return false; } selfP.update(); if (status.id && !selfB.runEvent('AfterSetStatus', selfP, source, sourceEffect, status)) { return false; } return true; }; this.clearStatus = function() { // unlike cureStatus, does not give cure message return selfP.setStatus(''); }; this.getStatus = function() { return selfB.getEffect(selfP.status); }; this.eatItem = function(source, sourceEffect) { if (!selfP.hp || !selfP.isActive) return false; if (!selfP.item) return false; if (!sourceEffect && selfB.effect) sourceEffect = selfB.effect; if (!source && selfB.event && selfB.event.target) source = selfB.event.target; var item = selfP.getItem(); if (selfB.runEvent('UseItem', selfP, null, null, item) && selfB.runEvent('EatItem', selfP, null, null, item)) { selfB.add('-enditem',selfP.fullname,item.name,'[eat]'); selfB.singleEvent('Eat', item, selfP.itemData, selfP, source, sourceEffect); selfP.lastItem = selfP.item; selfP.item = ''; selfP.itemData = {id: '', target: selfP}; selfP.usedItemThisTurn = true; return true; } return false; }; this.useItem = function(source, sourceEffect) { if (!selfP.hp || !selfP.isActive) return false; if (!selfP.item) return false; if (!sourceEffect && selfB.effect) sourceEffect = selfB.effect; if (!source && selfB.event && selfB.event.target) source = selfB.event.target; var item = selfP.getItem(); if (selfB.runEvent('UseItem', selfP, null, null, item)) { var effect = ''; if (item.isGem) effect = ' | [from] gem'; switch (item.id) { default: selfB.add('-enditem',selfP.fullname,item.name+effect); break; } selfB.singleEvent('Use', item, selfP.itemData, selfP, source, sourceEffect); selfP.lastItem = selfP.item; selfP.item = ''; selfP.itemData = {id: '', target: selfP}; selfP.usedItemThisTurn = true; return true; } return false; }; this.takeItem = function(source) { if (!selfP.hp || !selfP.isActive) return false; if (!selfP.item) return false; if (!source) source = selfP; var item = selfP.getItem(); if (selfB.runEvent('TakeItem', selfP, source, null, item)) { selfP.lastItem = ''; selfP.item = ''; selfP.itemData = {id: '', target: selfP}; return item; } return false; }; this.setItem = function(item, source, effect) { if (!selfP.hp || !selfP.isActive) return false; item = selfB.getItem(item); selfP.lastItem = selfP.item; selfP.item = item.id; selfP.itemData = {id: item.id, target: selfP}; if (item.id) { selfB.singleEvent('Start', item, selfP.itemData, selfP, source, effect); } if (selfP.lastItem) selfP.usedItemThisTurn = true; return true; }; this.getItem = function() { return selfB.getItem(selfP.item); }; this.clearItem = function() { return selfP.setItem(''); }; this.setAbility = function(ability, source, effect) { if (!selfP.hp) return false; ability = selfB.getAbility(ability); if (selfP.ability === ability.id) { return false; } if (ability.id === 'Multitype' || ability.id === 'Illusion' || selfP.ability === 'Multitype') { return false; } selfP.ability = ability.id; selfP.abilityData = {id: ability.id, target: selfP}; if (ability.id) { selfB.singleEvent('Start', ability, selfP.abilityData, selfP, source, effect); } return true; }; this.getAbility = function() { return selfB.getAbility(selfP.ability); }; this.clearAbility = function() { return selfP.setAbility(''); }; this.getNature = function() { return selfB.getNature(selfP.set.nature); }; this.addVolatile = function(status, source, sourceEffect) { if (!selfP.hp) return false; status = selfB.getEffect(status); if (selfB.event) { if (!source) source = selfB.event.source; if (!sourceEffect) sourceEffect = selfB.effect; } if (selfP.volatiles[status.id]) { selfB.singleEvent('Restart', status, selfP.volatiles[status.id], selfP, source, sourceEffect); return false; } selfP.volatiles[status.id] = {id: status.id}; selfP.volatiles[status.id].target = selfP; if (source) { selfP.volatiles[status.id].source = source; selfP.volatiles[status.id].sourcePosition = source.position; } if (sourceEffect) { selfP.volatiles[status.id].sourceEffect = sourceEffect; } if (status.duration) { selfP.volatiles[status.id].duration = status.duration; } if (status.durationCallback) { selfP.volatiles[status.id].duration = status.durationCallback.call(selfB, selfP, source, sourceEffect); } if (!selfB.singleEvent('Start', status, selfP.volatiles[status.id], selfP, source, sourceEffect)) { // cancel delete selfP.volatiles[status.id]; return false; } selfP.update(); return true; }; this.getVolatile = function(status) { status = selfB.getEffect(status); if (!selfP.volatiles[status.id]) return null; return status; }; this.removeVolatile = function(status) { if (!selfP.hp) return false; status = selfB.getEffect(status); if (!selfP.volatiles[status.id]) return false; selfB.singleEvent('End', status, selfP.volatiles[status.id], selfP); delete selfP.volatiles[status.id]; selfP.update(); return true; }; this.hpPercent = function(d) { //return floor(floor(d*48/selfP.maxhp + 0.5)*100/48); return floor(d*100/selfP.maxhp + 0.5); }; this.getHealth = function(health) { if (selfP.fainted) return ' (0)'; //var hpp = floor(48*selfP.hp/selfP.maxhp) || 1; var hpp = floor(selfP.hp*100/selfP.maxhp + 0.5) || 1; if (!selfP.hp) hpp = 0; var status = ''; if (selfP.status) status = ' '+selfP.status; return ' ('+hpp+'/100'+status+')'; }; this.lockMove = function(moveid) { // shortcut function for locking a pokemon into a move // not really necessary, btw: you can do this all in effect script // actually, you can do nearly everything in effect script if (!moveid || (!selfP.hasMove(moveid) && moveid !== 'recharge')) return; if (moveid === 'recharge') selfP.disabledMoves['recharge'] = false; var moves = selfP.moveset; for (var i=0; i= 5) { // oh fuck this.add('message STACK LIMIT EXCEEDED'); this.add('message PLEASE TELL AESOFT'); this.add('message Event: '+eventid); this.add('message Parent event: '+selfB.event.id); return false; } //this.add('Event: '+eventid+' (depth '+selfB.eventDepth+')'); effect = selfB.getEffect(effect); if (target.fainted) { return false; } if (effect.effectType === 'Status' && target.status !== effect.id) { // it's changed; call it off return true; } if (target.ignore && target.ignore[effect.effectType]) { selfB.debug(eventid+' handler suppressed by Klutz or Magic Room'); return true; } if (target.ignore && target.ignore[effect.effectType+'Target']) { selfB.debug(eventid+' handler suppressed by Air Lock'); return true; } if (typeof effect['on'+eventid] === 'undefined') return true; var parentEffect = selfB.effect; var parentEffectData = selfB.effectData; var parentEvent = selfB.event; selfB.effect = effect; selfB.effectData = effectData; selfB.event = {id: eventid, target: target, source: source, effect: sourceEffect}; selfB.eventDepth++; var args = [target, source, sourceEffect]; if (typeof relayVar !== 'undefined') args.unshift(relayVar); var returnVal = true; if (typeof effect['on'+eventid] === 'function') { returnVal = effect['on'+eventid].apply(selfB, args); } else { returnVal = effect['on'+eventid]; } selfB.eventDepth--; selfB.effect = parentEffect; selfB.effectData = parentEffectData; selfB.event = parentEvent; if (typeof returnVal === 'undefined') return true; return returnVal; }; this.runEvent = function(eventid, target, source, effect, relayVar) { if (selfB.eventDepth >= 5) { // oh fuck this.add('message STACK LIMIT EXCEEDED'); this.add('message PLEASE REPORT IN BUG THREAD'); this.add('message Event: '+eventid); this.add('message Parent event: '+selfB.event.id); return false; } if (!target) target = selfB; var statuses = selfB.getRelevantEffects(target, 'on'+eventid, 'onSource'+eventid, source); var hasRelayVar = true; effect = selfB.getEffect(effect); var args = [target, source, effect]; //console.log('Event: '+eventid+' (depth '+selfB.eventDepth+') t:'+target.id+' s:'+(!source||source.id)+' e:'+effect.id); if (typeof relayVar === 'undefined' || relayVar === null) { relayVar = true; hasRelayVar = false; } else { args.unshift(relayVar); } for (var i=0; i 0 && target.boostBy(currentBoost)) { switch (effect.id) { default: if (effect.effectType === 'Move') { selfB.add('-boost',target.fullname,i,boost[i]); } else { selfB.add('-boost',target.fullname,i,boost[i],'[from] '+effect.name); } break; } selfB.runEvent('AfterEachBoost', target, source, effect, currentBoost); } } for (var i in boost) { var currentBoost = {}; currentBoost[i] = boost[i]; if (boost[i] < 0 && target.boostBy(currentBoost)) { switch (effect.id) { case 'Intimidate': selfB.add('-unboost',target.fullname,i,(-boost[i])); break; default: if (effect.effectType === 'Move') { selfB.add('-unboost',target.fullname,i,(-boost[i])); } else { selfB.add('-unboost',target.fullname,i,(-boost[i]),'[from] '+effect.name); } break; } selfB.runEvent('AfterEachBoost', target, source, effect, currentBoost); } } selfB.runEvent('AfterBoost', target, source, effect, boost); }; this.damage = function(damage, target, source, effect) { if (selfB.event) { if (!target) target = selfB.event.target; if (!source) source = selfB.event.source; if (!effect) effect = selfB.effect; } if (!target || !target.hp) return 0; effect = selfB.getEffect(effect); if (!damage) return 0; damage = clampIntRange(damage, 1); if (effect.id !== 'struggle-recoil') // Struggle recoil is not affected by effects { if (effect.effectType === 'Weather' && !target.runImmunity(effect.id)) { this.debug('weather immunity'); return 0; } damage = selfB.runEvent('Damage', target, source, effect, damage); if (!damage) { this.debug('damage event said zero'); return 0; } } damage = clampIntRange(damage, 1); damage = target.damage(damage, source, effect); if (!damage) { this.debug('pokemon.damage said zero'); return 0; } var name = effect.name; if (name === 'tox') name = 'psn'; switch (name) { case 'partiallytrapped': selfB.add('-damage',target.fullname,target.hpPercent(damage)+target.getHealth(),'[from] '+selfB.getEffect(selfB.effectData.sourceEffect).name, '[partiallytrapped]'); break; default: if (effect.effectType === 'Move') { selfB.add('-damage',target.fullname,target.hpPercent(damage)+target.getHealth()); } else { selfB.add('-damage',target.fullname,target.hpPercent(damage)+target.getHealth(),'[from] '+name); } break; } if (target.fainted) selfB.faint(target); else { damage = selfB.runEvent('AfterDamage', target, source, effect, damage); if (effect && !effect.negateSecondary) { selfB.runEvent('Secondary', target, source, effect); } } return damage; }; this.heal = function(damage, target, source, effect) { if (selfB.event) { if (!target) target = selfB.event.target; if (!source) source = selfB.event.source; if (!effect) effect = selfB.effect; } effect = selfB.getEffect(effect); damage = Math.ceil(damage); // for things like Liquid Ooze, the Heal event still happens when nothing is healed. damage = selfB.runEvent('Heal', target, source, effect, damage); if (!damage) return 0; damage = Math.ceil(damage); if (!target || !target.hp) return 0; if (target.hp >= target.maxhp) return 0; damage = target.heal(damage, source, effect); switch (effect.id) { case 'LeechSeed': break; default: if (effect.effectType === 'Move') { selfB.add('-heal',target.fullname,target.hpPercent(damage)+target.getHealth()); } else { selfB.add('-heal',target.fullname,target.hpPercent(damage)+target.getHealth(),'[from] '+effect.id); } break; } return damage; }; this.getDamage = function(pokemon, target, move, suppressMessages) { if (typeof move === 'string') move = selfB.getMove(move); if (typeof move === 'number') move = { basePower: move, type: '???', category: 'Physical' }; if (move.affectedByImmunities) { if (!target.runImmunity(move.type, true)) { return false; } } if (move.isSoundBased) { if (!target.runImmunity('sound', true)) { return false; } } if (move.ohko) { if (target.level > pokemon.level) { this.add('-failed', target.fullname); return false; } return target.maxhp; } if (move.damageCallback) { return move.damageCallback.call(selfB, pokemon, target); } if (move.damage === 'level') { return pokemon.level; } if (move.damage) { return move.damage; } if (!move) { move = {}; } if (!move.category) move.category = 'Physical'; if (!move.defensiveCategory) move.defensiveCategory = move.category; if (!move.type) move.type = '???'; var type = move.type; // '???' is typeless damage: used for Struggle and Confusion etc var basePower = move.basePower; if (move.basePowerCallback) { basePower = move.basePowerCallback.call(selfB, pokemon, target); } if (!basePower) return 0; move.critRatio = clampIntRange(move.critRatio, 0, 5); var critMult = [0, 16, 8, 4, 3, 2]; move.crit = move.willCrit || false; if (typeof move.willCrit === 'undefined') { if (move.critRatio) { move.crit = (Math.random()*critMult[move.critRatio] < 1); } } if (move.crit) { move.crit = selfB.runEvent('CriticalHit', target, null, move); } // happens after crit calculation if (basePower) { basePower = selfB.runEvent('BasePower', pokemon, target, move, basePower); if (move.basePowerModifier) { basePower *= move.basePowerModifier; } } if (!basePower) return 0; var attack = move.category==='Physical'?pokemon.stats.atk:pokemon.stats.spa; var defense = move.defensiveCategory==='Physical'?target.stats.def:target.stats.spd; var level = pokemon.level; if (move.crit) { move.ignoreNegativeOffensive = true; move.ignorePositiveDefensive = true; } if (move.ignoreNegativeOffensive && attack < (move.category==='Physical'?pokemon.unboostedStats.atk:pokemon.unboostedStats.spa)) { move.ignoreOffensive = true; } if (move.ignoreOffensive) { selfB.debug('Negating (sp)atk boost/penalty.'); attack = (move.category==='Physical'?pokemon.unboostedStats.atk:pokemon.unboostedStats.spa); } if (move.ignorePositiveDefensive && defense > (move.defensiveCategory==='Physical'?target.unboostedStats.def:target.unboostedStats.spd)) { move.ignoreDefensive = true; } if (move.ignoreDefensive) { selfB.debug('Negating (sp)def boost/penalty.'); defense = move.defensiveCategory==='Physical'?target.unboostedStats.def:target.unboostedStats.spd; } //int(int(int(2*L/5+2)*A*P/D)/50); var baseDamage = floor(floor(floor(2*level/5+2) * basePower * attack/defense)/50); // STAB if (type !== '???' && pokemon.hasType(type)) { // The "???" type never gets STAB // Not even if you Roost in Gen 4 and somehow manage to use // Struggle in the same turn. // (On second thought, it might be easier to get a Missingno.) baseDamage *= (move.stab || 1.5); } // types var totalTypeMod = selfB.getEffectiveness(type, target); if (totalTypeMod > 0) { if (!suppressMessages) selfB.add('-supereffective', target.fullname); baseDamage *= 2; if (totalTypeMod >= 2) { baseDamage *= 2; } } if (totalTypeMod < 0) { if (!suppressMessages) selfB.add('-resisted', target.fullname); baseDamage /= 2; if (totalTypeMod <= -2) { baseDamage /= 2; } } // crit if (move.crit) { if (!suppressMessages) selfB.add('-crit', target.fullname); baseDamage *= (move.critModifier || 2); } // randomizer // gen 1-2 //var randFactor = floor(Math.random()*39)+217; //baseDamage *= floor(randFactor * 100 / 255) / 100; baseDamage *= (85 + floor(Math.random() * 16)) / 100; if (basePower && !floor(baseDamage)) { return 1; } return floor(baseDamage); }; this.getTarget = function(decision) { return decision.targetSide.active[decision.targetPosition]; }; this.resolveTarget = function(pokemon, move) { move = selfB.getMove(move); if (move.target === 'self' || move.target === 'all' || move.target === 'allies' || move.target === 'allySide' || move.target === 'ally') { return pokemon; } return pokemon.side.foe.active[0]; }; this.runMove = function(move, pokemon, target) { move = selfB.getMove(move); if (!target) { target = selfB.resolveTarget(pokemon, move); } BattleScripts.runMove.call(selfB, move, pokemon, target); }; this.useMove = function(move, pokemon, target, flags) { move = selfB.getMove(move); if (!target) target = selfB.resolveTarget(pokemon, move); if (move.target === 'self' || move.target === 'allies') { target = pokemon; } BattleScripts.useMove.call(selfB, move, pokemon, target, flags); }; this.moveHit = function(target, source, move, a, b) { BattleScripts.moveHit.call(selfB, target, source, move, a, b); }; this.checkFainted = function() { if (selfB.allySide.active[0].fainted || selfB.foeSide.active[0].fainted) { selfB.callback('switch'); selfB.decisionWaiting = true; return true; } return false; }; this.queue = []; this.faintQueue = []; this.decisionWaiting = false; this.faintMessages = function() { while (selfB.faintQueue.length) { var faintData = selfB.faintQueue.shift(); if (!faintData.target.fainted) { selfB.add('faint',faintData.target.fullname); selfB.runEvent('Faint', faintData.target, faintData.source, faintData.effect); faintData.target.fainted = true; faintData.target.side.pokemonLeft--; } } if (!selfB.allySide.pokemonLeft && !selfB.foeSide.pokemonLeft) { selfB.win(); return true; } if (!selfB.allySide.pokemonLeft) { selfB.win(selfB.foeSide); return true; } if (!selfB.foeSide.pokemonLeft) { selfB.win(selfB.allySide); return true; } return false; }; this.addQueue = function(decision, noSort) { if (decision) { if (!decision.priority) { var priorities = { 'beforeTurn': 100, 'beforeTurnMove': 99, 'switch': 6, 'runSwitch': 5.9, 'residual': -100, 'team': 102, 'start': 101 }; if (priorities[decision.choice]) { decision.priority = priorities[decision.choice]; } } if (decision.choice === 'move') { if (selfB.getMove(decision.move).beforeTurnCallback) { selfB.addQueue({choice: 'beforeTurnMove', pokemon: decision.pokemon, move: decision.move}, true); } } if (decision.move) { var target; if (!decision.targetPosition) { target = selfB.resolveTarget(decision.pokemon, decision.move); decision.targetSide = target.side; decision.targetPosition = target.position; } decision.move = selfB.getMove(decision.move); if (!decision.priority) { var priority = decision.move.priority; priority = selfB.runEvent('ModifyPriority', decision.pokemon, target, decision.move, priority); decision.priority = priority; } } if (!decision.side) decision.side = decision.pokemon; if (!decision.choice && decision.move) decision.choice = 'move'; if (!decision.pokemon && !decision.speed) decision.speed = 1; if (!decision.speed && decision.newPokemon) decision.speed = decision.newPokemon.stats.spe; if (!decision.speed) decision.speed = decision.pokemon.stats.spe; selfB.queue.push(decision); } if (!noSort) { selfB.queue.sort(selfB.comparePriority); } }; this.willAct = function() { for (var i=0; i= 6 || i < 0) return; if (i == 0) return; var pokemon = decision.side.pokemon[i]; if (!pokemon) return; decision.side.pokemon[i] = decision.side.pokemon[0]; decision.side.pokemon[0] = pokemon; decision.side.pokemon[i].position = i; decision.side.pokemon[0].position = 0; return; // we return here because the update event would crash since there are no active pokemon yet break; case 'switch': if (decision.pokemon) { decision.pokemon.beingCalledBack = true; var lastMove = selfB.getMove(decision.pokemon.lastMove); if (!(lastMove.batonPass || (lastMove.self && lastMove.self.batonPass))) { // Don't run any event handlers if Baton Pass was used. if (!selfB.runEvent('SwitchOut', decision.pokemon)) { // Warning: DO NOT interrupt a switch-out // if you just want to trap a pokemon. // To trap a pokemon and prevent it from switching out, // (e.g. Mean Look, Magnet Pull) use the 'trapped' flag // instead. // Note: Nothing in BW or earlier interrupts // a switch-out. break; } } } if (decision.pokemon && !decision.pokemon.hp && !decision.pokemon.fainted) { break; } selfB.switchIn(decision.newPokemon); //decision.pokemon.runSwitchIn(); break; case 'runSwitch': selfB.singleEvent('Start', decision.pokemon.getAbility(), decision.pokemon.abilityData, decision.pokemon); selfB.singleEvent('Start', decision.pokemon.getItem(), decision.pokemon.itemData, decision.pokemon); break; case 'beforeTurn': selfB.eachEvent('BeforeTurn'); break; case 'residual': selfB.add(''); selfB.clearActiveMove(true); selfB.residualEvent('Residual'); break; } selfB.clearActiveMove(); if (selfB.faintMessages()) return true; selfB.eachEvent('Update'); if (selfB.allySide.active[0].switchFlag) { selfB.allySide.active[0].switchFlag = false; if (selfB.canSwitch(selfB.allySide)) { selfB.callback('switch-ally'); selfB.decisionWaiting = true; return true; } } if (selfB.foeSide.active[0].switchFlag) { selfB.foeSide.active[0].switchFlag = false; if (selfB.canSwitch(selfB.foeSide)) { selfB.callback('switch-foe'); selfB.decisionWaiting = true; return true; } } return false; }; this.go = function() { selfB.add(''); if (selfB.curCallback) { if (selfB.curCallback === 'team-preview') { selfB.add('team-preview-end'); } selfB.curCallback = ''; } if (selfB.allySide.decision && selfB.allySide.decision !== true) { selfB.addQueue(selfB.allySide.decision, true); selfB.allySide.decision = true; } if (selfB.foeSide.decision && selfB.foeSide.decision !== true) { selfB.addQueue(selfB.foeSide.decision, true); selfB.foeSide.decision = true; } if (!selfB.midTurn) { selfB.queue.push({choice:'residual', priority: -100}); selfB.queue.push({choice:'beforeTurn', priority: 100}); selfB.midTurn = true; } selfB.addQueue(null); var currentPriority = 6; while (selfB.queue.length) { var decision = selfB.queue.shift(); /* while (decision.priority < currentPriority && currentPriority > -6) { selfB.eachEvent('Priority', null, currentPriority); currentPriority--; } */ selfB.runDecision(decision); if (selfB.decisionWaiting) { return; } if (!selfB.queue.length || selfB.queue[0].choice === 'runSwitch') { if (selfB.faintMessages()) return; } if (selfB.ended) return; } if (selfB.checkFainted()) return; selfB.nextTurn(); selfB.midTurn = false; selfB.queue = []; }; this.changeDecision = function(pokemon, decision) { selfB.cancelDecision(pokemon); if (!decision.pokemon) decision.pokemon = pokemon; selfB.addQueue(decision); }; this.decision = function(user, choice, data, recurse) { if (!recurse) recurse = 0; if (recurse > 2) { console.log('infinite recursion; breaking'); selfB.add('message Stack overflow detected.'); selfB.add('message BATTLE CRASHED.'); selfB.win(); return false; // infinite recursion } if (!user) return; // wtf if (!user.sides[selfB.roomid]) return; // wtf var side = user.sides[selfB.roomid]; var decision = {side: side, choice: choice, priority: 0, speed: 0}; selfB.cancelDecision(side.active[0]); if (choice === 'undo') { if (side.decision !== true) { // Don't undo a decision if it's not your turn side.decision = null; } return; } else if (choice === 'team') { if (selfB.curCallback !== 'team-preview') { return; // hacker! } if (!data) { return; // hacker! } decision.team = data; side.decision = decision; } else if (choice === 'switch') { console.log('switching to '+data); data = floor(data); if (data < 0) data = 0; if (data > side.pokemon.length-1) data = side.pokemon.length-1; var pokemon = side.pokemon[data]; if (pokemon && !pokemon.fainted) { decision.priority = 6; decision.newPokemon = pokemon; // todo: wtf if (!side.active[0]) { selfB.debug("The game hasn't started yet"); selfB.decision(user, 'move', 'no clue', recurse+1); return; } decision.pokemon = side.active[0]; if (side.active[0].trapped && selfB.curCallback === 'move') { // hacker! selfB.debug("Can't switch: The active pokemon is trapped"); selfB.decision(user, 'move', 'switch trapped', recurse+1); return; } if (decision.pokemon === pokemon) { // no selfB.debug("Can't switch: You can't switch to an active pokemon"); selfB.decision(user, 'move', 'null switch', recurse+1); return; } side.decision = decision; } else { selfB.debug("Can't switch: This pokemon doesn't exist"); // hacker! selfB.decision(user, 'move', 'pokemon doesnt exist', recurse+1); return; } } if (choice === 'move') { if (selfB.curCallback !== 'move') { return; // hacker! } var move = selfB.getMove(data); if (move) { if (side.active[0].fainted) { // hacker! var pokemon = selfB.getRandomSwitchable(side); if (!pokemon) { // double wtf; the game should be ended // by this point selfB.add('message A player has no usable pokemon, but the battle didn\'t end.'); selfB.add('message BATTLE CRASHED.'); selfB.win(); return; } selfB.debug('Hacking detected, using fainted pokemon\'s move; forcing switch to '+pokemon.id); selfB.decision(user, 'switch', pokemon.id, recurse+1); } if (!side.active[0].canUseMove(move)) { var badmove = move; // hacker! move = side.active[0].getValidMoves()[0]; if (move !== 'Struggle' && move !== 'Recharge') { selfB.debug('Hacking detected, '+badmove.id+' not allowed; forcing '+move); } } decision.pokemon = side.active[0]; decision.move = move; side.decision = decision; } else { // hacker! selfB.debug('Move not found: '+data); selfB.decision(user, 'move', 'switch trapped', recurse+1); return; } } if (selfB.allySide.decision && selfB.foeSide.decision) { selfB.decisionWaiting = false; selfB.go(); } }; this.add = function() { selfB.log.push('| '+Array.prototype.slice.call(arguments).join(' | ')); }; this.debug = function(activity) { selfB.add('debug', activity); }; this.join = function(user, slot) { if (selfB.allySide && selfB.allySide.user && selfB.foeSide && selfB.foeSide.user) return false; if (!user) return false; // !!! if (user.sides[selfB.roomid]) return false; if (selfB.allySide && selfB.allySide.user || slot === 2) { if (selfB.started) { user.sides[selfB.roomid] = selfB.foeSide; selfB.foeSide.user = user; user.sides[selfB.roomid].name = user.name; } else { console.log("NEW SIDE: "+user.name); selfB.foeSide = new BattleSide(user, selfB, 1); selfB.sides[1] = selfB.foeSide; user.sides[selfB.roomid] = selfB.foeSide; } selfB.add('player', 'p2', selfB.foeSide.name, selfB.foeSide.user.avatar); } else { if (selfB.started) { user.sides[selfB.roomid] = selfB.allySide; selfB.allySide.user = user; selfB.allySide.name = user.name; } else { console.log("NEW SIDE: "+user.name); selfB.allySide = new BattleSide(user, selfB, 0); selfB.sides[0] = selfB.allySide; user.sides[selfB.roomid] = selfB.allySide; } selfB.add('player', 'p1', selfB.allySide.name, selfB.allySide.user.avatar); } selfB.start(); return true; }; this.rename = function(user) { if (!user || !user.sides[selfB.roomid]) return; if (user.sides[selfB.roomid] === selfB.allySide) { user.sides[selfB.roomid].name = user.name; selfB.add('player', 'p1', selfB.allySide.name, user.avatar); } if (user.sides[selfB.roomid] === selfB.foeSide) { user.sides[selfB.roomid].name = user.name; selfB.add('player', 'p2', selfB.foeSide.name, user.avatar); } }; this.leave = function(user) { if (!user) return false; if (!user.sides[selfB.roomid]) return false; user.sides[selfB.roomid].emitUpdate({side: 'none'}); if (selfB.foeSide === user.sides[selfB.roomid]) { delete user.sides[selfB.roomid]; selfB.foeSide.user = null; selfB.add('foe-player '); selfB.active = false; } else if (selfB.allySide === user.sides[selfB.roomid]) { delete user.sides[selfB.roomid]; selfB.allySide.user = null; selfB.add('player '); selfB.active = false; } return true; }; this.getUpdates = function() { var prevUpdate = selfB.lastUpdate; if (selfB.lastUpdate === selfB.log.length) return null; selfB.lastUpdate = selfB.log.length; return { prevUpdate: prevUpdate, updates: selfB.log.slice(prevUpdate), active: selfB.active }; }; this.getUpdatesFrom = function(prevUpdate) { if (!prevUpdate) prevUpdate = 0; return { prevUpdate: prevUpdate, updates: selfB.log.slice(prevUpdate) }; }; // merge in scripts and tools for (var i in Tools) { if (!this[i]) { this[i] = Tools[i]; } } this.destroy = function() { // deallocate ourself // deallocate children and get rid of references to them for (var i=0; i