pokemon-showdown/tools.js
Joimer 0b43420727 Fix Thunder Wave hitting Ground-types
Since move.ignoreImmunity was defined as `false` for Thunder Wave, the statement !move.ignoreImmunity would always evaluate to true.
This would lead to move.ignoreImmunity being true for all status moves.
This commit fixes that problem, checking if the property was not defined as opposed to anything JavaScript deems `false`.

Related: 2e56120ad9
2015-04-15 00:29:09 +02:00

952 lines
28 KiB
JavaScript

/**
* Tools
* Pokemon Showdown - http://pokemonshowdown.com/
*
* Handles getting data about pokemon, items, etc.
*
* This file is used by the main process (to validate teams)
* as well as the individual simulator processes (to get
* information about pokemon, items, etc to simulate).
*
* @license MIT license
*/
var fs = require('fs');
module.exports = (function () {
var moddedTools = {};
var dataTypes = ['FormatsData', 'Learnsets', 'Pokedex', 'Movedex', 'Statuses', 'TypeChart', 'Scripts', 'Items', 'Abilities', 'Formats', 'Aliases'];
var dataFiles = {
'Pokedex': 'pokedex.js',
'Movedex': 'moves.js',
'Statuses': 'statuses.js',
'TypeChart': 'typechart.js',
'Scripts': 'scripts.js',
'Items': 'items.js',
'Abilities': 'abilities.js',
'Formats': 'rulesets.js',
'FormatsData': 'formats-data.js',
'Learnsets': 'learnsets.js',
'Aliases': 'aliases.js'
};
function Tools(mod, parentMod) {
if (!mod) {
mod = 'base';
this.isBase = true;
} else if (!parentMod) {
parentMod = 'base';
}
this.currentMod = mod;
var data = this.data = {
mod: mod
};
if (mod === 'base') {
dataTypes.forEach(function (dataType) {
try {
var path = './data/' + dataFiles[dataType];
data[dataType] = require(path)['Battle' + dataType];
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') console.error('CRASH LOADING DATA: ' + e.stack);
}
if (!data[dataType]) data[dataType] = {};
}, this);
try {
var path = './config/formats.js';
var configFormats = require(path).Formats;
for (var i = 0; i < configFormats.length; i++) {
var format = configFormats[i];
var id = toId(format.name);
format.effectType = 'Format';
if (format.challengeShow === undefined) format.challengeShow = true;
if (format.searchShow === undefined) format.searchShow = true;
data.Formats[id] = format;
}
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') console.error('CRASH LOADING FORMATS: ' + e.stack);
}
} else {
var parentData = moddedTools[parentMod].data;
dataTypes.forEach(function (dataType) {
try {
var path = './mods/' + mod + '/' + dataFiles[dataType];
data[dataType] = require(path)['Battle' + dataType];
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') console.error('CRASH LOADING MOD DATA: ' + e.stack);
}
if (!data[dataType]) data[dataType] = {};
for (var i in parentData[dataType]) {
if (data[dataType][i] === null) {
// null means don't inherit
delete data[dataType][i];
} else if (!(i in data[dataType])) {
// If it doesn't exist it's inherited from the parent data
if (dataType === 'Pokedex') {
// Pokedex entries can be modified too many different ways
data[dataType][i] = Object.clone(parentData[dataType][i], true);
} else {
data[dataType][i] = parentData[dataType][i];
}
} else if (data[dataType][i] && data[dataType][i].inherit) {
// {inherit: true} can be used to modify only parts of the parent data,
// instead of overwriting entirely
delete data[dataType][i].inherit;
Object.merge(data[dataType][i], parentData[dataType][i], false, false);
}
}
});
}
data['Natures'] = {
adamant: {name:"Adamant", plus:'atk', minus:'spa'},
bashful: {name:"Bashful"},
bold: {name:"Bold", plus:'def', minus:'atk'},
brave: {name:"Brave", plus:'atk', minus:'spe'},
calm: {name:"Calm", plus:'spd', minus:'atk'},
careful: {name:"Careful", plus:'spd', minus:'spa'},
docile: {name:"Docile"},
gentle: {name:"Gentle", plus:'spd', minus:'def'},
hardy: {name:"Hardy"},
hasty: {name:"Hasty", plus:'spe', minus:'def'},
impish: {name:"Impish", plus:'def', minus:'spa'},
jolly: {name:"Jolly", plus:'spe', minus:'spa'},
lax: {name:"Lax", plus:'def', minus:'spd'},
lonely: {name:"Lonely", plus:'atk', minus:'def'},
mild: {name:"Mild", plus:'spa', minus:'def'},
modest: {name:"Modest", plus:'spa', minus:'atk'},
naive: {name:"Naive", plus:'spe', minus:'spd'},
naughty: {name:"Naughty", plus:'atk', minus:'spd'},
quiet: {name:"Quiet", plus:'spa', minus:'spe'},
quirky: {name:"Quirky"},
rash: {name:"Rash", plus:'spa', minus:'spd'},
relaxed: {name:"Relaxed", plus:'def', minus:'spe'},
sassy: {name:"Sassy", plus:'spd', minus:'spe'},
serious: {name:"Serious"},
timid: {name:"Timid", plus:'spe', minus:'atk'}
};
}
Tools.loadMods = function () {
if (Tools.modsLoaded) return;
var parentMods = Object.create(null);
var mods;
try {
mods = fs.readdirSync('./mods/');
} catch (e) {
console.error("Error while loading mods: " + e.stack);
Tools.modsLoaded = true;
return;
}
mods.forEach(function (mod) {
try {
parentMods[mod] = require('./mods/' + mod + '/scripts.js').BattleScripts.inherit || 'base';
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
parentMods[mod] = 'base';
} else {
console.error("Error while loading mods: " + e.stack);
}
}
});
try {
var didSomething = false;
do {
didSomething = false;
for (var i in parentMods) {
if (!moddedTools[i] && moddedTools[parentMods[i]]) {
moddedTools[i] = Tools.construct(i, parentMods[i]);
didSomething = true;
}
}
} while (didSomething);
} catch (e) {
console.error("Error while loading mods: " + (e.stack || e));
}
Tools.modsLoaded = true;
};
Tools.prototype.mod = function (mod) {
Tools.loadMods();
if (!moddedTools[mod]) {
mod = this.getFormat(mod).mod;
}
if (!mod) mod = 'base';
return moddedTools[mod];
};
Tools.prototype.modData = function (dataType, id) {
if (this.isBase) return this.data[dataType][id];
var parentMod = this.data.Scripts.inherit;
if (!parentMod) parentMod = 'base';
if (this.data[dataType][id] !== moddedTools[parentMod].data[dataType][id]) return this.data[dataType][id];
return (this.data[dataType][id] = Object.clone(this.data[dataType][id], true));
};
Tools.prototype.effectToString = function () {
return this.name;
};
Tools.prototype.getImmunity = function (source, target) {
// returns false if the target is immune; true otherwise
// also checks immunity to some statuses
var sourceType = source.type || source;
var targetTyping = target.getTypes && target.getTypes() || target.types || target;
if (Array.isArray(targetTyping)) {
for (var i = 0; i < targetTyping.length; i++) {
if (!this.getImmunity(sourceType, targetTyping[i])) return false;
}
return true;
}
var typeData = this.data.TypeChart[targetTyping];
if (typeData && typeData.damageTaken[sourceType] === 3) return false;
return true;
};
Tools.prototype.getEffectiveness = function (source, target) {
var sourceType = source.type || source;
var totalTypeMod = 0;
var targetTyping = target.getTypes && target.getTypes() || target.types || target;
if (Array.isArray(targetTyping)) {
for (var i = 0; i < targetTyping.length; i++) {
totalTypeMod += this.getEffectiveness(sourceType, targetTyping[i]);
}
return totalTypeMod;
}
var typeData = this.data.TypeChart[targetTyping];
if (!typeData) return 0;
switch (typeData.damageTaken[sourceType]) {
case 1: return 1; // super-effective
case 2: return -1; // resist
// in case of weird situations like Gravity, immunity is
// handled elsewhere
default: return 0;
}
};
Tools.prototype.getTemplate = function (template) {
if (!template || typeof template === 'string') {
var name = (template || '').trim();
var id = toId(name);
if (this.data.Aliases[id]) {
name = this.data.Aliases[id];
id = toId(name);
}
template = {};
if (id && this.data.Pokedex[id]) {
template = this.data.Pokedex[id];
if (template.cached) return template;
template.cached = true;
template.exists = true;
}
name = template.species || template.name || name;
if (this.data.FormatsData[id]) {
Object.merge(template, this.data.FormatsData[id]);
}
if (this.data.Learnsets[id]) {
Object.merge(template, this.data.Learnsets[id]);
}
if (!template.id) template.id = id;
if (!template.name) template.name = name;
if (!template.speciesid) template.speciesid = id;
if (!template.species) template.species = name;
if (!template.baseSpecies) template.baseSpecies = name;
if (!template.forme) template.forme = '';
if (!template.formeLetter) template.formeLetter = '';
if (!template.spriteid) template.spriteid = toId(template.baseSpecies) + (template.baseSpecies !== name ? '-' + toId(template.forme) : '');
if (!template.prevo) template.prevo = '';
if (!template.evos) template.evos = [];
if (!template.nfe) template.nfe = !!template.evos.length;
if (!template.gender) template.gender = '';
if (!template.genderRatio && template.gender === 'M') template.genderRatio = {M:1, F:0};
if (!template.genderRatio && template.gender === 'F') template.genderRatio = {M:0, F:1};
if (!template.genderRatio && template.gender === 'N') template.genderRatio = {M:0, F:0};
if (!template.genderRatio) template.genderRatio = {M:0.5, F:0.5};
if (!template.tier && template.baseSpecies !== template.species) template.tier = this.data.FormatsData[toId(template.baseSpecies)].tier;
if (!template.tier) template.tier = 'Illegal';
if (!template.gen) {
if (template.forme && template.forme in {'Mega':1, 'Mega-X':1, 'Mega-Y':1}) {
template.gen = 6;
template.isMega = true;
} else if (template.forme === 'Primal') {
template.gen = 6;
template.isPrimal = true;
} else if (template.num >= 650) template.gen = 6;
else if (template.num >= 494) template.gen = 5;
else if (template.num >= 387) template.gen = 4;
else if (template.num >= 252) template.gen = 3;
else if (template.num >= 152) template.gen = 2;
else if (template.num >= 1) template.gen = 1;
else template.gen = 0;
}
}
return template;
};
Tools.prototype.getMove = function (move) {
if (!move || typeof move === 'string') {
var name = (move || '').trim();
var id = toId(name);
if (this.data.Aliases[id]) {
name = this.data.Aliases[id];
id = toId(name);
}
move = {};
if (id.substr(0, 11) === 'hiddenpower') {
var matches = /([a-z]*)([0-9]*)/.exec(id);
id = matches[1];
}
if (id && this.data.Movedex[id]) {
move = this.data.Movedex[id];
if (move.cached) return move;
move.cached = true;
move.exists = true;
}
if (!move.id) move.id = id;
if (!move.name) move.name = name;
if (!move.fullname) move.fullname = 'move: ' + move.name;
move.toString = this.effectToString;
if (!move.critRatio) move.critRatio = 1;
if (!move.baseType) move.baseType = move.type;
if (!move.effectType) move.effectType = 'Move';
if (!move.secondaries && move.secondary) move.secondaries = [move.secondary];
if (!move.gen) {
if (move.num >= 560) move.gen = 6;
else if (move.num >= 468) move.gen = 5;
else if (move.num >= 355) move.gen = 4;
else if (move.num >= 252) move.gen = 3;
else if (move.num >= 166) move.gen = 2;
else if (move.num >= 1) move.gen = 1;
else move.gen = 0;
}
if (!move.priority) move.priority = 0;
if (move.ignoreImmunity === undefined) move.ignoreImmunity = (move.category === 'Status');
if (!move.flags) move.flags = {};
}
return move;
};
/**
* Ensure we're working on a copy of a move (and make a copy if we aren't)
*
* Remember: "ensure" - by default, it won't make a copy of a copy:
* moveCopy === Tools.getMoveCopy(moveCopy)
*
* If you really want to, use:
* moveCopyCopy = Tools.getMoveCopy(moveCopy.id)
*
* @param move Move ID, move object, or movecopy object describing move to copy
* @return movecopy object
*/
Tools.prototype.getMoveCopy = function (move) {
if (move && move.isCopy) return move;
move = this.getMove(move);
var moveCopy = Object.clone(move, true);
moveCopy.isCopy = true;
return moveCopy;
};
Tools.prototype.getEffect = function (effect) {
if (!effect || typeof effect === 'string') {
var name = (effect || '').trim();
var id = toId(name);
effect = {};
if (id && this.data.Statuses[id]) {
effect = this.data.Statuses[id];
effect.name = effect.name || this.data.Statuses[id].name;
} else if (id && this.data.Movedex[id] && this.data.Movedex[id].effect) {
effect = this.data.Movedex[id].effect;
effect.name = effect.name || this.data.Movedex[id].name;
} else if (id && this.data.Abilities[id] && this.data.Abilities[id].effect) {
effect = this.data.Abilities[id].effect;
effect.name = effect.name || this.data.Abilities[id].name;
} else if (id && this.data.Items[id] && this.data.Items[id].effect) {
effect = this.data.Items[id].effect;
effect.name = effect.name || this.data.Items[id].name;
} else if (id && this.data.Formats[id]) {
effect = this.data.Formats[id];
effect.name = effect.name || this.data.Formats[id].name;
if (!effect.mod) effect.mod = this.currentMod;
if (!effect.effectType) effect.effectType = 'Format';
} else if (id === 'recoil') {
effect = {
effectType: 'Recoil'
};
} else if (id === 'drain') {
effect = {
effectType: 'Drain'
};
}
if (!effect.id) effect.id = id;
if (!effect.name) effect.name = name;
if (!effect.fullname) effect.fullname = effect.name;
effect.toString = this.effectToString;
if (!effect.category) effect.category = 'Effect';
if (!effect.effectType) effect.effectType = 'Effect';
}
return effect;
};
Tools.prototype.getFormat = function (effect) {
if (!effect || typeof effect === 'string') {
var name = (effect || '').trim();
var id = toId(name);
if (this.data.Aliases[id]) {
name = this.data.Aliases[id];
id = toId(name);
}
effect = {};
if (id && this.data.Formats[id]) {
effect = this.data.Formats[id];
if (effect.cached) return effect;
effect.cached = true;
effect.name = effect.name || this.data.Formats[id].name;
if (!effect.mod) effect.mod = this.currentMod;
if (!effect.effectType) effect.effectType = 'Format';
}
if (!effect.id) effect.id = id;
if (!effect.name) effect.name = name;
if (!effect.fullname) effect.fullname = effect.name;
effect.toString = this.effectToString;
if (!effect.category) effect.category = 'Effect';
if (!effect.effectType) effect.effectType = 'Effect';
}
return effect;
};
Tools.prototype.getItem = function (item) {
if (!item || typeof item === 'string') {
var name = (item || '').trim();
var id = toId(name);
if (this.data.Aliases[id]) {
name = this.data.Aliases[id];
id = toId(name);
}
item = {};
if (id && this.data.Items[id]) {
item = this.data.Items[id];
if (item.cached) return item;
item.cached = true;
item.exists = true;
}
if (!item.id) item.id = id;
if (!item.name) item.name = name;
if (!item.fullname) item.fullname = 'item: ' + item.name;
item.toString = this.effectToString;
if (!item.category) item.category = 'Effect';
if (!item.effectType) item.effectType = 'Item';
if (item.isBerry) item.fling = {basePower: 10};
if (item.onPlate) item.fling = {basePower: 90};
if (item.onDrive) item.fling = {basePower: 70};
if (item.megaStone) item.fling = {basePower: 80};
if (!item.gen) {
if (item.num >= 577) item.gen = 6;
else if (item.num >= 537) item.gen = 5;
else if (item.num >= 377) item.gen = 4;
// Due to difference in storing items, gen 2 items must be specified specifically
else item.gen = 3;
}
}
return item;
};
Tools.prototype.getAbility = function (ability) {
if (!ability || typeof ability === 'string') {
var name = (ability || '').trim();
var id = toId(name);
ability = {};
if (id && this.data.Abilities[id]) {
ability = this.data.Abilities[id];
if (ability.cached) return ability;
ability.cached = true;
ability.exists = true;
}
if (!ability.id) ability.id = id;
if (!ability.name) ability.name = name;
if (!ability.fullname) ability.fullname = 'ability: ' + ability.name;
ability.toString = this.effectToString;
if (!ability.category) ability.category = 'Effect';
if (!ability.effectType) ability.effectType = 'Ability';
if (!ability.gen) {
if (ability.num >= 165) ability.gen = 6;
else if (ability.num >= 124) ability.gen = 5;
else if (ability.num >= 77) ability.gen = 4;
else if (ability.num >= 1) ability.gen = 3;
else ability.gen = 0;
}
}
return ability;
};
Tools.prototype.getType = function (type) {
if (!type || typeof type === 'string') {
var id = toId(type);
id = id.charAt(0).toUpperCase() + id.substr(1);
type = {};
if (id && this.data.TypeChart[id]) {
type = this.data.TypeChart[id];
if (type.cached) return type;
type.cached = true;
type.exists = true;
type.isType = true;
type.effectType = 'Type';
}
if (!type.id) type.id = id;
if (!type.effectType) {
// man, this is really meta
type.effectType = 'EffectType';
}
}
return type;
};
Tools.prototype.getNature = function (nature) {
if (!nature || typeof nature === 'string') {
var name = (nature || '').trim();
var id = toId(name);
nature = {};
if (id && this.data.Natures[id]) {
nature = this.data.Natures[id];
if (nature.cached) return nature;
nature.cached = true;
nature.exists = true;
}
if (!nature.id) nature.id = id;
if (!nature.name) nature.name = name;
nature.toString = this.effectToString;
if (!nature.effectType) nature.effectType = 'Nature';
}
return nature;
};
Tools.prototype.natureModify = function (stats, nature) {
nature = this.getNature(nature);
if (nature.plus) stats[nature.plus] *= 1.1;
if (nature.minus) stats[nature.minus] *= 0.9;
return stats;
};
Tools.prototype.getBanlistTable = function (format, subformat, depth) {
var banlistTable;
if (!depth) depth = 0;
if (depth > 8) return; // avoid infinite recursion
if (format.banlistTable && !subformat) {
banlistTable = format.banlistTable;
} else {
if (!format.banlistTable) format.banlistTable = {};
if (!format.setBanTable) format.setBanTable = [];
if (!format.teamBanTable) format.teamBanTable = [];
banlistTable = format.banlistTable;
if (!subformat) subformat = format;
if (subformat.banlist) {
for (var i = 0; i < subformat.banlist.length; i++) {
// don't revalidate what we already validate
if (banlistTable[toId(subformat.banlist[i])]) continue;
banlistTable[subformat.banlist[i]] = subformat.name || true;
banlistTable[toId(subformat.banlist[i])] = subformat.name || true;
var plusPos = subformat.banlist[i].indexOf('+');
var complexList;
if (plusPos && plusPos > 0) {
var plusPlusPos = subformat.banlist[i].indexOf('++');
if (plusPlusPos && plusPlusPos > 0) {
complexList = subformat.banlist[i].split('++');
for (var j = 0; j < complexList.length; j++) {
complexList[j] = toId(complexList[j]);
}
format.teamBanTable.push(complexList);
} else {
complexList = subformat.banlist[i].split('+');
for (var j = 0; j < complexList.length; j++) {
complexList[j] = toId(complexList[j]);
}
format.setBanTable.push(complexList);
}
}
}
}
if (subformat.ruleset) {
for (var i = 0; i < subformat.ruleset.length; i++) {
// don't revalidate what we already validate
if (banlistTable['Rule:' + toId(subformat.ruleset[i])]) continue;
banlistTable['Rule:' + toId(subformat.ruleset[i])] = subformat.ruleset[i];
if (format.ruleset.indexOf(subformat.ruleset[i]) === -1) format.ruleset.push(subformat.ruleset[i]);
var subsubformat = this.getFormat(subformat.ruleset[i]);
if (subsubformat.ruleset || subsubformat.banlist) {
this.getBanlistTable(format, subsubformat, depth + 1);
}
}
}
}
return banlistTable;
};
Tools.prototype.levenshtein = function (s, t, l) { // s = string 1, t = string 2, l = limit
// Original levenshtein distance function by James Westgate, turned out to be the fastest
var d = []; // 2d matrix
// Step 1
var n = s.length;
var m = t.length;
if (n === 0) return m;
if (m === 0) return n;
if (l && Math.abs(m - n) > l) return Math.abs(m - n);
// Create an array of arrays in javascript (a descending loop is quicker)
for (var i = n; i >= 0; i--) d[i] = [];
// Step 2
for (var i = n; i >= 0; i--) d[i][0] = i;
for (var j = m; j >= 0; j--) d[0][j] = j;
// Step 3
for (var i = 1; i <= n; i++) {
var s_i = s.charAt(i - 1);
// Step 4
for (var j = 1; j <= m; j++) {
// Check the jagged ld total so far
if (i === j && d[i][j] > 4) return n;
var t_j = t.charAt(j - 1);
var cost = (s_i === t_j) ? 0 : 1; // Step 5
// Calculate the minimum
var mi = d[i - 1][j] + 1;
var b = d[i][j - 1] + 1;
var c = d[i - 1][j - 1] + cost;
if (b < mi) mi = b;
if (c < mi) mi = c;
d[i][j] = mi; // Step 6
}
}
// Step 7
return d[n][m];
};
Tools.prototype.clampIntRange = function (num, min, max) {
if (typeof num !== 'number') num = 0;
num = Math.floor(num);
if (num < min) num = min;
if (max !== undefined && num > max) num = max;
return num;
};
Tools.prototype.escapeHTML = function (str) {
if (!str) return '';
return ('' + str).escapeHTML();
};
Tools.prototype.dataSearch = function (target, searchIn) {
if (!target) {
return false;
}
searchIn = searchIn || ['Pokedex', 'Movedex', 'Abilities', 'Items', 'Natures'];
var searchFunctions = {Pokedex: 'getTemplate', Movedex: 'getMove', Abilities: 'getAbility', Items: 'getItem', Natures: 'getNature'};
var searchTypes = {Pokedex: 'pokemon', Movedex: 'move', Abilities: 'ability', Items: 'item', Natures: 'nature'};
var searchResults = [];
for (var i = 0; i < searchIn.length; i++) {
var res = this[searchFunctions[searchIn[i]]](target);
if (res.exists) {
res.searchType = searchTypes[searchIn[i]];
searchResults.push(res);
}
}
if (searchResults.length) {
return searchResults;
}
var cmpTarget = target.toLowerCase();
var maxLd = 3;
if (cmpTarget.length <= 1) {
return false;
} else if (cmpTarget.length <= 4) {
maxLd = 1;
} else if (cmpTarget.length <= 6) {
maxLd = 2;
}
for (var i = 0; i < searchIn.length; i++) {
var searchObj = this.data[searchIn[i]];
if (!searchObj) {
continue;
}
for (var j in searchObj) {
var word = searchObj[j];
if (typeof word === "object") {
word = word.name || word.species;
}
if (!word) {
continue;
}
var ld = this.levenshtein(cmpTarget, word.toLowerCase(), maxLd);
if (ld <= maxLd) {
searchResults.push({word: word, ld: ld});
}
}
}
if (searchResults.length) {
var newTarget = "";
var newLD = 10;
for (var i = 0, l = searchResults.length; i < l; i++) {
if (searchResults[i].ld < newLD) {
newTarget = searchResults[i];
newLD = searchResults[i].ld;
}
}
// To make sure we aren't in an infinite loop...
if (cmpTarget !== newTarget.word) {
return this.dataSearch(newTarget.word);
}
}
return false;
};
Tools.prototype.packTeam = function (team) {
if (!team) return '';
var buf = '';
for (var i = 0; i < team.length; i++) {
var set = team[i];
if (buf) buf += ']';
// name
buf += (set.name || set.species);
// species
var id = toId(set.species || set.name);
buf += '|' + (toId(set.name || set.species) === id ? '' : id);
// item
buf += '|' + toId(set.item);
// ability
var template = moddedTools.base.getTemplate(set.species || set.name);
var abilities = template.abilities;
id = toId(set.ability);
if (abilities) {
if (id === toId(abilities['0'])) {
buf += '|';
} else if (id === toId(abilities['1'])) {
buf += '|1';
} else if (id === toId(abilities['H'])) {
buf += '|H';
} else {
buf += '|' + id;
}
} else {
buf += '|' + id;
}
// moves
buf += '|' + set.moves.map(toId).join(',');
// nature
buf += '|' + set.nature;
// evs
var evs = '|';
if (set.evs) {
evs = '|' + (set.evs['hp'] || '') + ',' + (set.evs['atk'] || '') + ',' + (set.evs['def'] || '') + ',' + (set.evs['spa'] || '') + ',' + (set.evs['spd'] || '') + ',' + (set.evs['spe'] || '');
}
if (evs === '|,,,,,') {
buf += '|';
} else {
buf += evs;
}
// gender
if (set.gender && set.gender !== template.gender) {
buf += '|' + set.gender;
} else {
buf += '|';
}
// ivs
var ivs = '|';
if (set.ivs) {
ivs = '|' + (set.ivs['hp'] === 31 || set.ivs['hp'] === undefined ? '' : set.ivs['hp']) + ',' + (set.ivs['atk'] === 31 || set.ivs['atk'] === undefined ? '' : set.ivs['atk']) + ',' + (set.ivs['def'] === 31 || set.ivs['def'] === undefined ? '' : set.ivs['def']) + ',' + (set.ivs['spa'] === 31 || set.ivs['spa'] === undefined ? '' : set.ivs['spa']) + ',' + (set.ivs['spd'] === 31 || set.ivs['spd'] === undefined ? '' : set.ivs['spd']) + ',' + (set.ivs['spe'] === 31 || set.ivs['spe'] === undefined ? '' : set.ivs['spe']);
}
if (ivs === '|,,,,,') {
buf += '|';
} else {
buf += ivs;
}
// shiny
if (set.shiny) {
buf += '|S';
} else {
buf += '|';
}
// level
if (set.level && set.level !== 100) {
buf += '|' + set.level;
} else {
buf += '|';
}
// happiness
if (set.happiness !== undefined && set.happiness !== 255) {
buf += '|' + set.happiness;
} else {
buf += '|';
}
}
return buf;
};
Tools.prototype.fastUnpackTeam = function (buf) {
if (!buf) return null;
var team = [];
var i = 0, j = 0;
// limit to 24
for (var count = 0; count < 24; count++) {
var set = {};
team.push(set);
// name
j = buf.indexOf('|', i);
if (j < 0) return;
set.name = buf.substring(i, j);
i = j + 1;
// species
j = buf.indexOf('|', i);
if (j < 0) return;
set.species = buf.substring(i, j) || set.name;
i = j + 1;
// item
j = buf.indexOf('|', i);
if (j < 0) return;
set.item = buf.substring(i, j);
i = j + 1;
// ability
j = buf.indexOf('|', i);
if (j < 0) return;
var ability = buf.substring(i, j);
var template = moddedTools.base.getTemplate(set.species);
set.ability = (template.abilities && ability in {'':1, 0:1, 1:1, H:1} ? template.abilities[ability || '0'] : ability);
i = j + 1;
// moves
j = buf.indexOf('|', i);
if (j < 0) return;
set.moves = buf.substring(i, j).split(',');
i = j + 1;
// nature
j = buf.indexOf('|', i);
if (j < 0) return;
set.nature = buf.substring(i, j);
i = j + 1;
// evs
j = buf.indexOf('|', i);
if (j < 0) return;
if (j !== i) {
var evs = buf.substring(i, j).split(',');
set.evs = {
hp: Number(evs[0]) || 0,
atk: Number(evs[1]) || 0,
def: Number(evs[2]) || 0,
spa: Number(evs[3]) || 0,
spd: Number(evs[4]) || 0,
spe: Number(evs[5]) || 0
};
}
i = j + 1;
// gender
j = buf.indexOf('|', i);
if (j < 0) return;
if (i !== j) set.gender = buf.substring(i, j);
i = j + 1;
// ivs
j = buf.indexOf('|', i);
if (j < 0) return;
if (j !== i) {
var ivs = buf.substring(i, j).split(',');
set.ivs = {
hp: ivs[0] === '' ? 31 : Number(ivs[0]) || 0,
atk: ivs[1] === '' ? 31 : Number(ivs[1]) || 0,
def: ivs[2] === '' ? 31 : Number(ivs[2]) || 0,
spa: ivs[3] === '' ? 31 : Number(ivs[3]) || 0,
spd: ivs[4] === '' ? 31 : Number(ivs[4]) || 0,
spe: ivs[5] === '' ? 31 : Number(ivs[5]) || 0
};
}
i = j + 1;
// shiny
j = buf.indexOf('|', i);
if (j < 0) return;
if (i !== j) set.shiny = true;
i = j + 1;
// level
j = buf.indexOf('|', i);
if (j < 0) return;
if (i !== j) set.level = parseInt(buf.substring(i, j), 10);
i = j + 1;
// happiness
j = buf.indexOf(']', i);
if (j < 0) {
if (buf.substring(i)) {
set.happiness = Number(buf.substring(i));
}
break;
}
if (i !== j) set.happiness = Number(buf.substring(i, j));
i = j + 1;
}
return team;
};
/**
* Install our Tools functions into the battle object
*/
Tools.prototype.install = function (battle) {
for (var i in this.data.Scripts) {
battle[i] = this.data.Scripts[i];
}
};
Tools.construct = function (mod, parentMod) {
var tools = new Tools(mod, parentMod);
// Scripts override Tools.
var ret = Object.create(tools);
tools.install(ret);
if (ret.init) {
if (parentMod && ret.init === moddedTools[parentMod].data.Scripts.init) {
// don't inherit init
delete ret.init;
} else {
ret.init();
}
}
return ret;
};
moddedTools.base = Tools.construct();
// "gen6" is an alias for the current base data
moddedTools.gen6 = moddedTools.base;
Object.getPrototypeOf(moddedTools.base).moddedTools = moddedTools;
return moddedTools.base;
})();