pokemon-showdown/simulator.js
Bill Meltsner 783f70f83f OHKO moves gain accuracy when the user's level is higher than the target's.
Also, OHKO failure due to the user's level being lower than the target's is now reported to the client.
2012-03-25 15:37:01 -05:00

3044 lines
78 KiB
JavaScript

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 !== 'string') return ''; //???
return text.replace(/ /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.detailString = '['+(this.gender?this.gender+'|':'')+'L'+this.level+']';
this.fullid = side.id+'-'+this.name+this.detailString+(this.species===this.name?'':'('+this.species+')');
this.tpid = side.id+'-'+this.species+this.detailString;
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<this.set.moves.length; i++)
{
var move = selfB.getMove(this.set.moves[i]);
var moveid = move.id;
if (move.id === 'HiddenPower')
{
this.hpType = move.type;
moveid += move.type;
}
this.baseMoveset.push({
move: moveid,
id: move.id,
pp: (move.noPPBoosts ? move.pp : move.pp * 8/5),
maxpp: (move.noPPBoosts ? move.pp : move.pp * 8/5),
disabled: false,
used: false
});
this.moves.push(moveid);
}
}
if (!this.set.evs)
{
this.set.evs = {
hp: 84,
atk: 84,
def: 84,
spa: 84,
spd: 84,
spe: 84
};
}
if (!this.set.ivs)
{
this.set.ivs = {
hp: 31,
atk: 31,
def: 31,
spa: 31,
spd: 31,
spe: 31
};
}
var stats = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
for (var i in stats)
{
if (!this.set.evs[i]) this.set.evs[i] = 0;
if (!this.set.ivs[i] && this.set.ivs[i] !== 0) this.set.ivs[i] = 31;
}
for (var i in this.set.evs)
{
this.set.evs[i] = clampIntRange(this.set.evs[i], 0, 255);
}
for (var i in this.set.ivs)
{
this.set.ivs[i] = clampIntRange(this.set.ivs[i], 0, 31);
}
this.stats = {
hp: 0,
atk: 0,
def: 0,
spa: 0,
spd: 0,
spe: 0
};
this.boosts = {
atk: 0,
def: 0,
spa: 0,
spd: 0,
spe: 0,
accuracy: 0,
evasion: 0
};
this.baseBoosts = {
atk: 0,
def: 0,
spa: 0,
spd: 0,
spe: 0,
accuracy: 0,
evasion: 0
};
this.unboostedStats = {
atk: 0,
def: 0,
spa: 0,
spd: 0,
spe: 0,
accuracy: 0,
evasion: 0
};
this.baseStats = this.template.baseStats;
this.bst = 0;
for (var i in this.baseStats)
{
this.bst += this.baseStats[i];
}
this.bst = this.bst || 10;
this.types = this.baseTemplate.types;
this.stats['hp'] = floor(floor(2*selfP.baseStats['hp']+selfP.set.ivs['hp']+floor(selfP.set.evs['hp']/4)+100)*selfP.level / 100 + 10);
if (this.baseStats['hp'] === 1) this.stats['hp'] = 1; // shedinja
this.unboostedStats['hp'] = this.stats['hp'];
this.maxhp = this.stats['hp'];
this.hp = this.hp || this.maxhp;
this.toString = function() {
return 'Pokemon: '+selfP.id;
};
this.update = function(init) {
selfP.id = selfP.baseId;
selfP.fullid = selfP.baseFullid;
selfP.baseStats = selfP.template.baseStats;
// reset for Light Metal etc
selfP.weightkg = selfP.template.weightkg;
// reset for Forecast etc
selfP.types = selfP.template.types;
// reset for diabled moves
selfP.disabledMoves = {};
selfP.negateImmunity = {};
selfP.trapped = false;
// reset for ignore settings
selfP.ignore = {};
for (var i in selfP.moveset)
{
if (selfP.moveset[i]) selfP.moveset[i].disabled = false;
}
for (var i in selfP.stats)
{
var stat = selfP.baseStats[i];
/* if (selfP.set.bstscale && selfP.bst > 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<selfP.moveset.length; i++)
{
var moveData = selfP.moveset[i];
if (moveData.id === move.id)
{
return moveData;
}
}
return null;
};
this.deductPP = function(move, amount) {
move = selfB.getMove(move);
var ppData = selfP.getMoveData(move);
var success = false;
if (ppData)
{
ppData.used = true;
}
if (ppData && ppData.pp)
{
if (amount)
{
ppData.pp -= amount;
}
else
{
ppData.pp -= selfB.runEvent('DeductPP', selfP, selfP, move, 1);
}
if (ppData.pp <= 0)
{
ppData.pp = 0;
}
success = true;
}
selfP.lastMove = move.id;
if (!amount)
{
selfP.movedThisTurn = true;
}
return success;
};
this.gotAttacked = function(move, damage, source) {
if (!damage) damage = 0;
move = selfB.getMove(move);
source.lastDamage = damage;
selfP.lastAttackedBy = {
pokemon: source,
damage: damage,
move: move.id,
thisTurn: true
};
};
this.getMoves = function() {
var moves = [];
var hasValidMove = false;
for (var i=0; i<selfP.moveset.length; i++)
{
var move = selfP.moveset[i];
if (selfP.disabledMoves[move.move] || !move.pp)
{
move.disabled = true;
}
moves.push(move);
if (!move.disabled)
{
hasValidMove = true;
}
}
if (!hasValidMove)
{
moves = [{
move: 'Struggle',
id: 'Struggle',
pp: 1,
maxpp: 1,
disabled: false
}];
}
if (selfP.volatiles['mustRecharge'])
{
moves = [{
move: 'Recharge',
id: 'Recharge',
pp: 1,
maxpp: 1,
disabled: false
}];
}
return moves;
};
this.positiveBoosts = function() {
var boosts = 0;
for (var i in selfP.boosts)
{
if (selfP.boosts[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<pokemon.moveset.length; i++)
{
var moveData = pokemon.moveset[i];
selfP.moveset.push({
move: toId(moveData.move),
id: moveData.id,
pp: 5,
maxpp: moveData.maxpp,
disabled: false
});
}
for (var j in pokemon.baseBoosts)
{
selfP.baseBoosts[j] = pokemon.baseBoosts[j];
}
}
selfP.update();
return true;
};
this.clearVolatile = function(init) {
selfP.baseBoosts = {
atk: 0,
def: 0,
spa: 0,
spd: 0,
spe: 0,
accuracy: 0,
evasion: 0
};
this.moveset = [];
// we're copying array contents
// DO NOT "optimize" it to copy just the pointer
// if you don't know what a pointer is, please don't
// touch this code
for (var i=0; i<this.baseMoveset.length; i++)
{
this.moveset.push(this.baseMoveset[i]);
}
selfP.transformed = false;
selfP.ability = selfP.baseAbility;
selfP.template = selfP.baseTemplate;
selfP.baseStats = selfP.template.baseStats;
selfP.types = selfP.template.types;
for (var i in selfP.volatiles)
{
if (selfP.volatiles[i].linkedStatus)
{
selfP.volatiles[i].linkedPokemon.removeVolatile(selfP.volatiles[i].linkedStatus);
}
}
selfP.volatiles = {};
selfP.switchFlag = false;
selfP.lastMove = '';
selfP.lastDamage = 0;
selfP.lastAttackedBy = null;
selfP.movedThisTurn = false;
selfP.newlySwitched = true;
selfP.beingCalledBack = false;
selfP.update(init);
};
if (!this.id) this.id = (side.n?'foe':'ally')+'-'+toId(this.name);
this.hasType = function (type) {
if (!type) return false;
if (Array.isArray(type))
{
for (var i=0; i<type.length; i++)
{
if (selfP.hasType(type[i])) return true;
}
}
else
{
if (selfP.types[0] === type) return true;
if (selfP.types[1] === type) return true;
}
return false;
};
// returns the amount of damage actually dealt
this.faint = function(source, effect) {
if (selfP.fainted) return 0;
var d = selfP.hp;
selfP.hp = 0;
selfP.switchFlag = false;
selfP.status = 'fnt';
//selfP.fainted = true;
selfB.faintQueue.push({
target: selfP,
source: source,
effect: effect
});
return d;
};
this.damage = function(d, source, effect) {
if (!selfP.hp) return 0;
if (d < 1 && d > 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.moveset.length; i++)
{
if (moveid === selfB.getMove(selfP.moveset[i].move).id)
{
return moveid;
}
}
return false;
};
this.canUseMove = function(moveid) {
if (moveid.id) moveid = moveid.id;
if (!selfP.hasMove(moveid)) return false;
if (selfP.disabledMoves[moveid]) return false;
var moveData = selfP.getMoveData(moveid);
if (!moveData || !moveData.pp || moveData.disabled) return false;
return true;
};
this.getValidMoves = function() {
var pMoves = selfP.getMoves();
var moves = [];
for (var i=0; i<pMoves.length; i++)
{
if (!pMoves[i].disabled)
{
moves.push(pMoves[i].move);
}
}
if (!moves.length) return ['Struggle'];
return moves;
};
// returns the amount of damage actually healed
this.heal = function(d) {
if (!selfP.hp) return 0;
d = floor(d);
if (isNaN(d)) return 0;
if (d <= 0) return 0;
if (selfP.hp >= 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('r-cure-status '+selfP.id+' '+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('r-eat '+selfP.id+' '+item.id);
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))
{
switch (item.id)
{
default:
selfB.add('residual '+selfP.id+' item-activate '+item.id);
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 = selfP.status;
if (status==='tox') status = 'toxic';
if (!status) status = 'none';
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<moves.length; i++)
{
if (moves[i].id !== moveid)
{
moves[i].disabled = true;
}
}
selfP.trapped = true;
};
this.runImmunity = function(type, message) {
if (selfP.fainted)
{
return false;
}
if (!type || type === '???')
{
return true;
}
if (selfP.negateImmunity[type]) return true;
if (!selfB.getImmunity(type, selfP))
{
selfB.debug('natural immunity');
if (message)
{
selfB.add('r-immune '+selfP.id);
}
return false;
}
var immunity = selfB.runEvent('Immunity', selfP, null, null, type);
if (!immunity)
{
selfB.debug('artificial immunity');
if (message && immunity !== null)
{
selfB.add('r-immune '+selfP.id);
}
return false;
}
return true;
};
this.runBeforeMove = function(target, move) {
if (selfP.fainted) return true;
return !selfB.runEvent('BeforeMove', selfP, target, move);
};
this.destroy = function() {
// deallocate ourself
// get rid of some possibly-circular references
side = null;
selfB = null;
selfS = null;
selfP.side = null;
selfP = null;
};
selfP.clearVolatile(true);
}
function BattleSide(user, battle, n)
{
var selfB = battle;
var selfS = this;
this.battle = battle;
this.n = n;
this.user = user;
this.name = user.name;
this.pokemon = [];
this.pokemonLeft = 0;
this.active = [null];
this.decision = null;
this.foe = null;
this.sideConditions = {};
this.id = (n?'foe':'ally');
this.team = BattleScripts.getTeam.call(selfB, selfS);
for (var i=0; i<this.team.length && i<6; i++)
{
console.log("NEW POKEMON: "+(this.team[i]?this.team[i].name:'[unidentified]'));
this.pokemon.push(new BattlePokemon(this.team[i], this));
}
this.pokemonLeft = this.pokemon.length;
for (var i=0; i<this.pokemon.length; i++)
{
this.pokemon[i].position = i;
}
this.toString = function() {
return 'Side: '+selfS.name;
};
this.getData = function() {
var data = {
name: selfS.name,
pokemon: []
};
for (var i=0; i<selfS.pokemon.length; i++)
{
var pokemon = selfS.pokemon[i];
data.pokemon.push({
name: pokemon.name,
species: pokemon.species,
id: pokemon.baseId,
status: pokemon.status,
level: pokemon.level,
gender: pokemon.gender,
fainted: pokemon.fainted,
active: (pokemon.position < pokemon.side.active.length),
moves: pokemon.moves,
ability: pokemon.ability,
item: pokemon.item
});
}
return data;
};
this.randomActive = function() {
var i = floor(Math.random() * selfS.active.length);
return selfS.active[i];
};
this.addSideCondition = function(status, source, sourceEffect) {
status = selfB.getEffect(status);
if (selfS.sideConditions[status.id])
{
selfB.singleEvent('Restart', status, selfS.sideConditions[status.id], selfS, source, sourceEffect);
return false;
}
selfS.sideConditions[status.id] = {id: status.id};
selfS.sideConditions[status.id].target = selfS;
if (source)
{
selfS.sideConditions[status.id].source = source;
selfS.sideConditions[status.id].sourcePosition = source.position;
}
if (status.duration)
{
selfS.sideConditions[status.id].duration = status.duration;
}
if (status.durationCallback)
{
selfS.sideConditions[status.id].duration = status.durationCallback.call(selfB, selfS, source, sourceEffect);
}
if (!selfB.singleEvent('Start', status, selfS.sideConditions[status.id], selfS, source, sourceEffect))
{
delete selfS.sideConditions[status.id];
return false;
}
selfB.update();
return true;
};
this.getSideCondition = function(status) {
status = selfB.getEffect(status);
if (!selfS.sideConditions[status.id]) return null;
return status;
};
this.removeSideCondition = function(status) {
status = selfB.getEffect(status);
if (!selfS.sideConditions[status.id]) return false;
selfB.singleEvent('End', status, selfS.sideConditions[status.id], selfS);
delete selfS.sideConditions[status.id];
selfB.update();
return true;
};
this.emitUpdate = function(update) {
if (selfS.user)
{
update.room = selfB.roomid;
selfS.user.emit('update', update);
}
};
this.destroy = function() {
// deallocate ourself
// deallocate children and get rid of references to them
for (var i=0; i<selfS.pokemon.length; i++)
{
if (selfS.pokemon[i]) selfS.pokemon[i].destroy();
selfS.pokemon[i] = null;
}
selfS.pokemon = null;
for (var i=0; i<selfS.active.length; i++)
{
selfS.active[i] = null;
}
selfS.active = null;
if (selfS.decision)
{
delete selfS.decision.side;
delete selfS.decision.pokemon;
delete selfS.decision.user;
}
selfS.decision = null;
// make sure no user is referencing us
if (selfS.user)
{
delete selfS.user.sides[selfB.roomid];
}
selfS.user = null;
// get rid of some possibly-circular references
selfS.battle = null;
selfS.foe = null;
selfB = null;
selfS = null;
};
}
function Battle(roomid, format, ranked)
{
var selfB = this;
this.log = [];
this.turn = 0;
this.sides = [null, null];
this.allySide = null;
this.foeSide = null;
this.lastUpdate = 0;
this.curCallback = '';
this.roomid = roomid;
this.ranked = ranked;
this.weather = '';
this.weatherData = {id:''};
this.pseudoWeather = {};
this.format = toId(format);
this.formatData = {id:''};
this.ended = false;
this.started = false;
this.active = false;
this.effect = {id:''};
this.effectData = {id:''};
this.event = {id:''};
this.eventDepth = 0;
this.toString = function() {
return 'Battle: '+selfS.format;
};
this.setWeather = function(status, source, sourceEffect) {
status = selfB.getEffect(status);
if (!sourceEffect && selfB.effect) sourceEffect = selfB.effect;
if (!source && selfB.event && selfB.event.target) source = selfB.event.target;
if (selfB.weather === status.id) return false;
if (selfB.weather && !status.id)
{
var oldstatus = selfB.getWeather();
selfB.singleEvent('End', oldstatus, selfB.weatherData, selfB);
}
var prevWeather = selfB.weather;
var prevWeatherData = selfB.weatherData;
selfB.weather = status.id;
selfB.weatherData = {id: status.id};
if (source)
{
selfB.weatherData.source = source;
selfB.weatherData.sourcePosition = source.position;
}
if (status.duration)
{
selfB.weatherData.duration = status.duration;
}
if (status.durationCallback)
{
selfB.weatherData.duration = status.durationCallback.call(selfB, source, sourceEffect);
}
if (!selfB.singleEvent('Start', status, selfB.weatherData, selfB, source, sourceEffect))
{
selfB.weather = prevWeather;
selfB.weatherData = prevWeatherData;
return false;
}
selfB.update();
return true;
};
this.clearWeather = function() {
return selfB.setWeather('');
};
this.getWeather = function() {
return selfB.getEffect(selfB.weather);
};
this.getFormat = function() {
return selfB.getEffect(selfB.format);
};
this.addPseudoWeather = function(status, source, sourceEffect) {
status = selfB.getEffect(status);
if (selfB.pseudoWeather[status.id])
{
selfB.singleEvent('Restart', status, selfB.pseudoWeather[status.id], selfB, source, sourceEffect);
return false;
}
selfB.pseudoWeather[status.id] = {id: status.id};
if (source)
{
selfB.pseudoWeather[status.id].source = source;
selfB.pseudoWeather[status.id].sourcePosition = source.position;
}
if (status.duration)
{
selfB.pseudoWeather[status.id].duration = status.duration;
}
if (status.durationCallback)
{
selfB.pseudoWeather[status.id].duration = status.durationCallback.call(selfB, source, sourceEffect);
}
if (!selfB.singleEvent('Start', status, selfB.pseudoWeather[status.id], selfB, source, sourceEffect))
{
delete selfB.pseudoWeather[status.id];
return false;
}
selfB.update();
return true;
};
this.getPseudoWeather = function(status) {
status = selfB.getEffect(status);
if (!selfB.pseudoWeather[status.id]) return null;
return status;
};
this.removePseudoWeather = function(status) {
status = selfB.getEffect(status);
if (!selfB.pseudoWeather[status.id]) return false;
selfB.singleEvent('End', status, selfB.pseudoWeather[status.id], selfB);
delete selfB.pseudoWeather[status.id];
selfB.update();
return true;
};
this.lastMove = '';
this.activeMove = null;
this.activePokemon = null;
this.activeTarget = null;
this.setActiveMove = function(move, pokemon, target) {
if (!move) move = null;
if (!pokemon) pokemon = null;
if (!target) target = pokemon;
selfB.activeMove = move;
selfB.activePokemon = pokemon;
selfB.activeTarget = target;
// Mold Breaker and the like
selfB.update();
};
this.clearActiveMove = function(failed) {
if (selfB.activeMove)
{
if (!failed)
{
selfB.lastMove = selfB.activeMove.id;
}
selfB.activeMove = null;
selfB.activePokemon = null;
selfB.activeTarget = null;
// Mold Breaker and the like, again
selfB.update();
}
};
this.update = function() {
var actives = selfB.allySide.active;
for (var i=0; i<actives.length; i++)
{
if (actives[i]) actives[i].update();
}
actives = selfB.foeSide.active;
for (var i=0; i<actives.length; i++)
{
if (actives[i]) actives[i].update();
}
};
// bubbles up
this.comparePriority = function(a, b) {
a.priority = a.priority || 0;
a.subPriority = a.subPriority || 0;
a.speed = a.speed || 0;
b.priority = b.priority || 0;
b.subPriority = b.subPriority || 0;
b.speed = b.speed || 0;
if (b.priority - a.priority)
{
return b.priority - a.priority;
}
if (b.speed - a.speed)
{
return b.speed - a.speed;
}
if (b.subPriority - a.subPriority)
{
return b.subPriority - a.subPriority;
}
return Math.random()-0.5;
};
this.getResidualStatuses = function(thing, callbackType) {
var statuses = selfB.getRelevantEffectsInner(thing || selfB, callbackType || 'residualCallback', null, null, false, true, 'duration');
statuses.sort(selfB.comparePriority);
//if (statuses[0]) selfB.debug('match '+(callbackType||'residualCallback')+': '+statuses[0].status.id);
return statuses;
};
this.eachEvent = function(eventid, effect, relayVar) {
var actives = [];
if (!effect && selfB.effect) effect = selfB.effect;
for (var i=0; i<selfB.sides.length;i++)
{
var side = selfB.sides[i];
for (var j=0; j<side.active.length; j++)
{
actives.push(side.active[j]);
}
}
actives.sort(function(a, b) {
if (b.stats.spe - a.stats.spe)
{
return b.stats.spe - a.stats.spe;
}
return Math.random()-0.5;
});
for (var i=0; i<actives.length; i++)
{
selfB.runEvent(eventid, actives[i], null, effect, relayVar);
}
};
this.residualEvent = function(eventid, relayVar) {
var statuses = selfB.getRelevantEffectsInner(selfB, 'on'+eventid, null, null, false, true, 'duration');
statuses.sort(selfB.comparePriority);
while (statuses.length)
{
var statusObj = statuses.shift();
var status = statusObj.status;
if (statusObj.thing.fainted) continue;
if (statusObj.statusData && statusObj.statusData.duration)
{
statusObj.statusData.duration--;
if (!statusObj.statusData.duration)
{
statusObj.end(status.id);
continue;
}
}
selfB.singleEvent(eventid, status, statusObj.statusData, statusObj.thing, relayVar);
}
};
// The entire event system revolves around this function
// (and its helper functions, getRelevant*)
this.singleEvent = function(eventid, effect, effectData, target, source, sourceEffect, relayVar) {
if (selfB.eventDepth >= 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<statuses.length; i++)
{
var status = statuses[i].status;
if (statuses[i].thing.fainted) continue;
//selfB.debug('match '+eventid+': '+status.id+' '+status.effectType);
if (status.effectType === 'Status' && statuses[i].thing.status !== status.id)
{
// it's changed; call it off
continue;
}
if (statuses[i].thing.ignore && statuses[i].thing.ignore[status.effectType] === 'A')
{
// ignore attacking events
var AttackingEvents = {
BeforeMove: 1,
BasePower: 1,
Immunity: 1,
Damage: 1,
SubDamage: 1,
Heal: 1,
TakeItem: 1,
UseItem: 1,
EatItem: 1,
SetStatus: 1,
CriticalHit: 1,
ModifyPokemon: 1,
ModifyStats: 1,
TryHit: 1,
Hit: 1,
TryFieldHit: 1,
Boost: 1,
DragOut: 1
};
if (AttackingEvents[eventid])
{
if (eventid !== 'ModifyPokemon' && eventid !== 'ModifyStats')
{
selfB.debug(eventid+' handler suppressed by Mold Breaker');
}
continue;
}
}
else if (statuses[i].thing.ignore && statuses[i].thing.ignore[status.effectType])
{
selfB.debug(eventid+' handler suppressed by Klutz or Magic Room');
continue;
}
else if (target.ignore && target.ignore[status.effectType+'Target'])
{
selfB.debug(eventid+' handler suppressed by Air Lock');
continue;
}
var returnVal;
if (typeof statuses[i].callback === 'function')
{
var parentEffect = selfB.effect;
var parentEffectData = selfB.effectData;
var parentEvent = selfB.event;
selfB.effect = statuses[i].status;
selfB.effectData = statuses[i].statusData;
selfB.effectData.target = statuses[i].thing;
selfB.event = {id: eventid, target: target, source: source, effect: effect};
selfB.eventDepth++;
returnVal = statuses[i].callback.apply(selfB, args);
selfB.eventDepth--;
selfB.effect = parentEffect;
selfB.effectData = parentEffectData;
selfB.event = parentEvent;
}
else
{
returnVal = statuses[i].callback;
}
if (typeof returnVal !== 'undefined')
{
relayVar = returnVal;
if (!relayVar) return relayVar;
if (hasRelayVar)
{
args[0] = relayVar;
}
}
}
return relayVar;
};
this.resolveLastPriority = function(statuses, callbackType) {
var priority = 0;
var subPriority = 0;
var status = statuses[statuses.length-1].status;
if (status[callbackType+'Priority'])
{
priority = status[callbackType+'Priority'];
}
else if (status[callbackType+'Order'])
{
priority = -status[callbackType+'Order'];
}
if (status[callbackType+'SubPriority'])
{
subPriority = status[callbackType+'SubPriority'];
}
else if (status[callbackType+'SubOrder'])
{
subPriority = -status[callbackType+'SubOrder'];
}
statuses[statuses.length-1].priority = priority;
statuses[statuses.length-1].subPriority = subPriority;
};
// bubbles up to parents
this.getRelevantEffects = function(thing, callbackType, foeCallbackType, foeThing, checkChildren) {
var statuses = selfB.getRelevantEffectsInner(thing, callbackType, foeCallbackType, foeThing, true, false);
statuses.sort(selfB.comparePriority);
//if (statuses[0]) selfB.debug('match '+callbackType+': '+statuses[0].status.id);
return statuses;
};
this.getRelevantEffectsInner = function(thing, callbackType, foeCallbackType, foeThing, bubbleUp, bubbleDown, getAll) {
if (!callbackType || !thing) return [];
var statuses = [];
var status;
if (thing.sides)
{
for (var i in selfB.pseudoWeather)
{
status = selfB.getPseudoWeather(i);
if (typeof status[callbackType] !== 'undefined' || (getAll && thing.pseudoWeather[i][getAll]))
{
statuses.push({status: status, callback: status[callbackType], statusData: selfB.pseudoWeather[i], end: selfB.removePseudoWeather, thing: thing});
selfB.resolveLastPriority(statuses,callbackType);
}
}
status = selfB.getWeather();
if (typeof status[callbackType] !== 'undefined' || (getAll && thing.weatherData[getAll]))
{
statuses.push({status: status, callback: status[callbackType], statusData: selfB.weatherData, end: selfB.clearWeather, thing: thing, priority: status[callbackType+'Priority']||0});
selfB.resolveLastPriority(statuses,callbackType);
}
if (bubbleDown)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(selfB.allySide, callbackType,null,null,false,true, getAll));
statuses = statuses.concat(selfB.getRelevantEffectsInner(selfB.foeSide, callbackType,null,null,false,true, getAll));
}
return statuses;
}
if (thing.pokemon)
{
for (var i in thing.sideConditions)
{
status = thing.getSideCondition(i);
if (typeof status[callbackType] !== 'undefined' || (getAll && thing.sideConditions[i][getAll]))
{
statuses.push({status: status, callback: status[callbackType], statusData: thing.sideConditions[i], end: thing.removeSideCondition, thing: thing});
selfB.resolveLastPriority(statuses,callbackType);
}
}
if (foeCallbackType)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(thing.foe, foeCallbackType,null,null,false,false, getAll));
}
if (bubbleUp)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(selfB, callbackType,null,null,true,false, getAll));
}
if (bubbleDown)
{
for (var i=0;i<thing.active.length;i++)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(thing.active[i], callbackType,null,null,false,true, getAll));
}
}
return statuses;
}
if (thing.fainted) return statuses;
if (!thing.getStatus)
{
selfB.debug(JSON.stringify(thing));
return statuses;
}
var status = thing.getStatus();
if (typeof status[callbackType] !== 'undefined' || (getAll && thing.statusData[getAll]))
{
statuses.push({status: status, callback: status[callbackType], statusData: thing.statusData, end: thing.clearStatus, thing: thing});
selfB.resolveLastPriority(statuses,callbackType);
}
for (var i in thing.volatiles)
{
status = thing.getVolatile(i);
if (typeof status[callbackType] !== 'undefined' || (getAll && thing.volatiles[i][getAll]))
{
statuses.push({status: status, callback: status[callbackType], statusData: thing.volatiles[i], end: thing.removeVolatile, thing: thing});
selfB.resolveLastPriority(statuses,callbackType);
}
}
status = thing.getAbility();
if (typeof status[callbackType] !== 'undefined' || (getAll && thing.abilityData[getAll]))
{
statuses.push({status: status, callback: status[callbackType], statusData: thing.abilityData, end: thing.clearAbility, thing: thing});
selfB.resolveLastPriority(statuses,callbackType);
}
status = thing.getItem();
if (typeof status[callbackType] !== 'undefined' || (getAll && thing.itemData[getAll]))
{
statuses.push({status: status, callback: status[callbackType], statusData: thing.itemData, end: thing.clearItem, thing: thing});
selfB.resolveLastPriority(statuses,callbackType);
}
if (foeThing && foeCallbackType && foeCallbackType.substr(0,8) !== 'onSource')
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(foeThing, foeCallbackType,null,null,false,false, getAll));
}
else if (foeCallbackType)
{
var foeActive = thing.side.foe.active;
var allyActive = thing.side.active;
var eventName = '';
if (foeCallbackType.substr(0,8) === 'onSource')
{
eventName = foeCallbackType.substr(8);
if (foeThing)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(foeThing, foeCallbackType,null,null,false,false, getAll));
}
foeCallbackType = 'onFoe'+eventName;
foeThing = null;
}
if (foeCallbackType.substr(0,5) === 'onFoe')
{
eventName = foeCallbackType.substr(5);
for (var i=0; i<allyActive.length; i++)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(allyActive[i], 'onAlly'+eventName,null,null,false,false, getAll));
statuses = statuses.concat(selfB.getRelevantEffectsInner(allyActive[i], 'onAny'+eventName,null,null,false,false, getAll));
}
for (var i=0; i<foeActive.length; i++)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(foeActive[i], 'onAny'+eventName,null,null,false,false, getAll));
}
}
for (var i=0; i<foeActive.length; i++)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(foeActive[i], foeCallbackType,null,null,false,false, getAll));
}
}
if (bubbleUp)
{
statuses = statuses.concat(selfB.getRelevantEffectsInner(thing.side, callbackType, foeCallbackType, null, true, false, getAll));
}
return statuses;
};
this.getPokemon = function(id) {
if (typeof id !== 'string') id = id.id;
for (var i=0; i<selfB.allySide.pokemon.length; i++)
{
var pokemon = selfB.allySide.pokemon[i];
if (pokemon.baseId === id) return pokemon;
}
for (var i=0; i<selfB.foeSide.pokemon.length; i++)
{
var pokemon = selfB.foeSide.pokemon[i];
if (pokemon.baseId === id) return pokemon;
}
return null;
};
this.callback = function(type) {
if (!selfB.allySide.user || !selfB.foeSide.user)
{
return;
}
selfB.update();
if (type==='switch')
{
if (selfB.allySide.active[0].fainted)
{
selfB.allySide.decision = null;
selfB.allySide.emitUpdate({request: {forceSwitch: true, side: selfB.allySide.getData()}});
}
else
{
selfB.allySide.decision = true;
selfB.allySide.emitUpdate({request: {wait: true}});
}
if (selfB.foeSide.active[0].fainted)
{
selfB.foeSide.decision = null;
selfB.foeSide.emitUpdate({request: {forceSwitch: true, side: selfB.foeSide.getData()}});
}
else
{
selfB.foeSide.decision = true;
selfB.foeSide.emitUpdate({request: {wait: true}});
}
}
else if (type==='switch-ally')
{
selfB.foeSide.decision = true;
selfB.allySide.decision = null;
selfB.foeSide.emitUpdate({request: {wait: true}});
selfB.allySide.emitUpdate({request: {forceSwitch: true, side: selfB.allySide.getData()}});
}
else if (type==='switch-foe')
{
selfB.allySide.decision = true;
selfB.allySide.emitUpdate({request: {wait: true}});
selfB.foeSide.decision = null;
selfB.foeSide.emitUpdate({request: {forceSwitch: true, side: selfB.foeSide.getData()}});
}
else if (type==='team-preview')
{
selfB.add('team-preview');
selfB.allySide.decision = null;
selfB.allySide.emitUpdate({request: {teamPreview: true, side: selfB.allySide.getData()}});
selfB.foeSide.decision = null;
selfB.foeSide.emitUpdate({request: {teamPreview: true, side: selfB.foeSide.getData()}});
selfB.decisionWaiting = true;
}
else
{
var moves;
var pokemon;
selfB.allySide.decision = null;
pokemon = selfB.allySide.active[0];
if (!pokemon)
{
selfB.add('message BATTLE CRASHED.');
return;
}
if (pokemon.disabledMoves['recharge'] === false)
{
moves = [{move: 'recharge'}];
}
selfB.allySide.emitUpdate({request: {moves: pokemon.getMoves(), trapped: pokemon.trapped, side: pokemon.side.getData()}});
selfB.foeSide.decision = null;
pokemon = selfB.foeSide.active[0];
if (pokemon.disabledMoves['recharge'] === false)
{
moves = [{move: 'recharge'}];
}
selfB.foeSide.emitUpdate({request: {moves: pokemon.getMoves(), trapped: pokemon.trapped, side: pokemon.side.getData()}});
}
if (selfB.foeSide.decision && selfB.allySide.decision)
{
if (type !== 'move')
{
selfB.add('message Attempting to recover from crash.');
selfB.callback('move');
return;
}
selfB.add('message BATTLE CRASHED.');
selfB.win();
return;
}
selfB.add('callback decision');
selfB.curCallback = type;
};
this.win = function(side) {
var winSide = false;
if (selfB.ended)
{
return false;
}
if (!side)
{
selfB.winner = '';
}
else if (side === selfB.allySide)
{
winSide = side;
if (selfB.allySide.user)
{
selfB.winner = selfB.allySide.user.userid;
}
}
else if (side === selfB.foeSide)
{
winSide = side;
if (selfB.foeSide.user)
{
selfB.winner = selfB.foeSide.user.userid;
}
}
else if (selfB.allySide.user && side === selfB.allySide.user.userid)
{
winSide = selfB.allySide;
selfB.winner = side;
}
else if (selfB.foeSide.user && side === selfB.foeSide.user.userid)
{
winSide = selfB.foeSide;
selfB.winner = side;
}
else if (selfB.ranked && side === selfB.ranked.p1)
{
winSide = selfB.allySide;
selfB.winner = side;
}
else if (selfB.ranked && side === selfB.ranked.p2)
{
winSide = selfB.foeSide;
selfB.winner = side;
}
else
{
return false;
}
selfB.add('');
if (winSide)
{
selfB.add('win '+winSide.name);
}
else
{
selfB.add('win');
}
selfB.ended = true;
selfB.active = false;
selfB.decisionWaiting = false;
return true;
};
this.switchIn = function(pokemon) {
if (!pokemon) return false;
var side = pokemon.side;
if (side.active[0] && !side.active[0].fainted)
{
selfB.add('switch-out '+side.active[0].id);
}
if (side.active[0])
{
var oldActive = side.active[0];
var oldpos = pokemon.position;
oldActive.isActive = false;
pokemon.position = oldActive.position;
oldActive.position = oldpos;
side.pokemon[pokemon.position] = pokemon;
side.pokemon[oldActive.position] = oldActive;
}
pokemon.clearVolatile();
var lastMove = null;
if (side.active[0]) lastMove = selfB.getMove(side.active[0].lastMove);
if (lastMove && (lastMove.batonPass || (lastMove.self && lastMove.self.batonPass)))
{
pokemon.copyVolatileFrom(side.active[0]);
}
side.active[0] = pokemon;
pokemon.isActive = true;
pokemon.activeTurns = 0;
for (var m in pokemon.moveset)
{
pokemon.moveset[m].used = false;
}
selfB.add('pokemon '+side.active[0].fullid);
selfB.add('switch-in '+side.active[0].id+side.active[0].getHealth());
selfB.runEvent('SwitchIn', pokemon);
selfB.addQueue({pokemon: pokemon, choice: 'runSwitch'});
};
this.canSwitch = function(side) {
var canSwitchIn = [];
for (var i=0; i<side.pokemon.length; i++)
{
var pokemon = side.pokemon[i];
if (pokemon !== side.active[0] && !pokemon.fainted)
{
canSwitchIn.push(pokemon);
}
}
return canSwitchIn.length;
};
this.getRandomSwitchable = function(side) {
var canSwitchIn = [];
for (var i=0; i<side.pokemon.length; i++)
{
var pokemon = side.pokemon[i];
if (pokemon !== side.active[0] && !pokemon.fainted)
{
canSwitchIn.push(pokemon);
}
}
if (!canSwitchIn.length)
{
return null;
}
return canSwitchIn[floor(Math.random()*canSwitchIn.length)];
};
this.dragIn = function(side) {
var pokemon = selfB.getRandomSwitchable(side);
if (!pokemon) return false;
if (side.active[0])
{
var oldActive = side.active[0];
var oldpos = pokemon.position;
if (!oldActive.hp)
{
return false;
}
if (!selfB.runEvent('DragOut', oldActive))
{
return false;
}
selfB.runEvent('SwitchOut', oldActive);
oldActive.isActive = false;
pokemon.position = oldActive.position;
oldActive.position = oldpos;
side.pokemon[pokemon.position] = pokemon;
side.pokemon[oldActive.position] = oldActive;
}
pokemon.clearVolatile();
side.active[0] = pokemon;
pokemon.isActive = true;
pokemon.activeTurns = 0;
for (var m in pokemon.moveset)
{
pokemon.moveset[m].used = false;
}
selfB.add('pokemon '+side.active[0].fullid);
selfB.add('drag-in '+side.active[0].id);
selfB.runEvent('SwitchIn', pokemon);
selfB.addQueue({pokemon: pokemon, choice: 'runSwitch'});
return true;
};
this.faint = function(pokemon, source, effect) {
pokemon.faint(source, effect);
};
this.nextTurn = function() {
selfB.turn++;
for (var i=0; i<selfB.sides.length; i++)
{
for (var j=0; j<selfB.sides[i].active.length; j++)
{
var pokemon = selfB.sides[i].active[j];
if (!pokemon) continue;
pokemon.movedThisTurn = false;
pokemon.usedItemThisTurn = false;
pokemon.newlySwitched = false;
if (pokemon.lastAttackedBy)
{
pokemon.lastAttackedBy.thisTurn = false;
}
pokemon.activeTurns++;
}
}
selfB.add('turn '+selfB.turn);
selfB.callback('move');
};
this.midTurn = false;
this.start = function() {
if (selfB.active) return;
if (!selfB.allySide || !selfB.allySide.user || !selfB.foeSide || !selfB.foeSide.user)
{
// need two players to start
return;
}
selfB.foeSide.emitUpdate({midBattle: selfB.started, side: 'foe', sideData: selfB.foeSide.getData()});
selfB.allySide.emitUpdate({midBattle: selfB.started, side: 'ally', sideData: selfB.allySide.getData()});
if (selfB.started)
{
selfB.callback(selfB.curCallback);
selfB.active = true;
selfB.activeTurns = 0;
return;
}
selfB.active = true;
selfB.activeTurns = 0;
selfB.started = true;
selfB.foeSide.foe = selfB.allySide;
selfB.allySide.foe = selfB.foeSide;
var format = selfB.getFormat();
selfB.add('tier '+format.name);
if (selfB.ranked)
{
selfB.add('rated');
}
if (format && format.ruleset)
{
for (var i=0; i<format.ruleset.length; i++)
{
selfB.addPseudoWeather(format.ruleset[i]);
}
}
if (!selfB.allySide.pokemon[0] || !selfB.foeSide.pokemon[0])
{
selfB.add('message Battle not started: One of you has an empty team.');
return;
}
selfB.residualEvent('TeamPreview');
selfB.addQueue({choice:'start'});
selfB.midTurn = true;
if (!selfB.decisionWaiting) selfB.go();
};
this.boost = function(boost, 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);
boost = selfB.runEvent('Boost', target, source, effect, boost);
for (var i in boost)
{
var currentBoost = {};
currentBoost[i] = boost[i];
if (boost[i] > 0 && target.boostBy(currentBoost))
{
switch (effect.id)
{
default:
if (effect.effectType === 'Ability')
{
selfB.add('r-ability-boost '+target.id+' '+i+' '+boost[i]+' '+effect.id);
}
else if (effect.effectType === 'Item')
{
selfB.add('r-item-boost '+target.id+' '+i+' '+boost[i]+' '+effect.id);
}
else
{
selfB.add('r-boost '+target.id+' '+i+' '+boost[i]);
}
break;
}
}
}
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('r-unboost '+target.id+' '+i+' '+(-boost[i]));
break;
default:
if (effect.effectType === 'Ability')
{
selfB.add('r-ability-unboost '+target.id+' '+i+' '+(-boost[i])+' '+effect.id);
}
else if (effect.effectType === 'Item')
{
selfB.add('r-item-unboost '+target.id+' '+i+' '+(-boost[i])+' '+effect.id);
}
else
{
selfB.add('r-unboost '+target.id+' '+i+' '+(-boost[i]));
}
break;
}
}
}
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;
}
switch (effect.id) {
case 'brn':
selfB.add('residual '+target.id+' burn '+target.hpPercent(damage)+target.getHealth());
break;
case 'psn':
case 'tox':
selfB.add('residual '+target.id+' poison '+target.hpPercent(damage)+target.getHealth());
break;
case 'Sandstorm':
selfB.add('residual '+target.id+' sandstorm '+target.hpPercent(damage)+target.getHealth());
break;
case 'Hail':
selfB.add('residual '+target.id+' hail '+target.hpPercent(damage)+target.getHealth());
break;
case 'recoil':
selfB.add('r-recoil '+target.id+' '+target.hpPercent(damage)+target.getHealth());
break;
case 'LifeOrb':
selfB.add('r-life-orb-recoil '+target.id+' '+target.hpPercent(damage)+target.getHealth());
break;
case 'partiallyTrapped':
var sourceEffect = selfB.getEffect(selfB.effectData.sourceEffect);
selfB.add('residual '+target.id+' damage '+sourceEffect.id+' '+target.hpPercent(damage)+target.getHealth());
break;
case 'StealthRock':
selfB.add('stealth-rock-damage '+target.id+' '+target.hpPercent(damage)+target.getHealth());
break;
case 'Spikes':
selfB.add('spikes-damage '+target.id+' '+target.hpPercent(damage)+target.getHealth());
break;
case 'LeechSeed':
// handled in heal step
break;
default:
if (effect.effectType === 'Ability')
{
selfB.add('residual '+target.id+' ability-damage '+effect.id+' '+target.hpPercent(damage)+target.getHealth());
}
else if (effect.effectType === 'Item')
{
selfB.add('residual '+target.id+' item-damage '+effect.id+' '+target.hpPercent(damage)+target.getHealth());
}
else
{
selfB.add('r-damage '+target.id+' '+target.hpPercent(damage)+target.getHealth());
}
}
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 'drain':
selfB.add('r-drain '+source.id+' '+target.id+' '+target.hpPercent(damage)+' '+target.getHealth());
break;
case 'AquaRing':
selfB.add('residual '+target.id+' heal aqua-ring '+target.hpPercent(damage)+target.getHealth());
break;
case 'LeechSeed':
break;
default:
if (effect.effectType === 'Ability')
{
selfB.add('residual '+target.id+' ability-heal '+effect.id+' '+target.hpPercent(damage)+target.getHealth());
}
else if (effect.effectType === 'Item')
{
selfB.add('residual '+target.id+' item-heal '+effect.id+' '+target.hpPercent(damage)+target.getHealth());
}
else
{
selfB.add('r-heal '+target.id+' '+target.hpPercent(damage)+target.getHealth());
}
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.ohko)
{
if (target.level > pokemon.level)
{
this.add('r-failed '+target.id);
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('r-super-effective');
baseDamage *= 2;
if (totalTypeMod >= 2)
{
baseDamage *= 2;
}
}
if (totalTypeMod < 0)
{
if (!suppressMessages) selfB.add('r-resisted');
baseDamage /= 2;
if (totalTypeMod <= -2)
{
baseDamage /= 2;
}
}
// crit
if (move.crit)
{
if (!suppressMessages) selfB.add('r-crit');
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.id);
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<selfB.queue.length; i++)
{
if (selfB.queue[i].choice === 'move' || selfB.queue[i].choice === 'switch')
{
return selfB.queue[i];
}
}
return null;
};
this.willMove = function(pokemon) {
for (var i=0; i<selfB.queue.length; i++)
{
if (selfB.queue[i].choice === 'move' && selfB.queue[i].pokemon === pokemon)
{
return selfB.queue[i];
}
}
return null;
};
this.cancelDecision = function(pokemon) {
var success = false;
for (var i=0; i<selfB.queue.length; i++)
{
if (selfB.queue[i].pokemon === pokemon)
{
selfB.queue.splice(i,1);
i--;
success = true;
}
}
return success;
};
this.cancelMove = function(pokemon) {
for (var i=0; i<selfB.queue.length; i++)
{
if (selfB.queue[i].choice === 'move' && selfB.queue[i].pokemon === pokemon)
{
selfB.queue.splice(i,1);
return true;
}
}
return false;
};
this.willSwitch = function(pokemon) {
for (var i=0; i<selfB.queue.length; i++)
{
if (selfB.queue[i].choice === 'switch' && selfB.queue[i].pokemon === pokemon)
{
return true;
}
}
return false;
};
this.runDecision = function(decision) {
// returns whether or not we ended in a callback
switch (decision.choice)
{
case 'start':
selfB.add('start');
selfB.switchIn(selfB.allySide.pokemon[0]);
selfB.switchIn(selfB.foeSide.pokemon[0]);
selfB.midTurn = true;
break;
case 'move':
if (decision.pokemon !== decision.pokemon.side.active[0]) return false;
if (decision.pokemon.fainted) return false;
selfB.runMove(decision.move, decision.pokemon, selfB.getTarget(decision));
break;
case 'beforeTurnMove':
if (decision.pokemon !== decision.pokemon.side.active[0]) return false;
if (decision.pokemon.fainted) return false;
selfB.debug('before turn callback: '+decision.move.id);
decision.move.beforeTurnCallback.call(selfB, decision.pokemon, selfB.getTarget(decision));
break;
case 'event':
selfB.runEvent(decision.event, decision.pokemon);
break;
case 'team':
var i = parseInt(decision.team[0]);
if (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(activity) {
selfB.log.push(activity);
};
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('foe-player '+selfB.foeSide.name);
selfB.add('foe-avatar '+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 '+selfB.allySide.name);
selfB.add('avatar '+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 '+selfB.allySide.name);
selfB.add('avatar '+user.avatar);
}
if (user.sides[selfB.roomid] === selfB.foeSide)
{
user.sides[selfB.roomid].name = user.name;
selfB.add('foe-player '+selfB.foeSide.name);
selfB.add('foe-avatar '+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<selfB.sides.length; i++)
{
if (selfB.sides[i]) selfB.sides[i].destroy();
selfB.sides[i] = null;
}
selfB.allySide = null;
selfB.foeSide = null;
for (var i=0; i<selfB.queue.length; i++)
{
delete selfB.queue[i].pokemon;
delete selfB.queue[i].side;
delete selfB.queue[i].user;
selfB.queue[i] = null;
}
selfB.queue = null;
// in case the garbage collector really sucks, at least deallocate the log
selfB.log = null;
// get rid of some possibly-circular references
selfB = null;
};
}
exports.BattlePokemon = BattlePokemon;
exports.BattleSide = BattleSide;
exports.Battle = Battle;