/** * 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 */ 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': 'formats.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]; if (fs.existsSync(path)) { data[dataType] = require(path)['Battle' + dataType]; } } catch (e) { console.log('CRASH LOADING DATA: '+e.stack); } if (!data[dataType]) data[dataType] = {}; }, this); try { var path = './config/formats.js'; if (fs.existsSync(path)) { var configFormats = require(path).Formats; for (var i=0; i= 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; } 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); 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'; this.getBanlistTable(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.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.substr(0,1).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; }; var BattleNatures = { Adamant: {plus:'atk', minus:'spa'}, Bashful: {}, Bold: {plus:'def', minus:'atk'}, Brave: {plus:'atk', minus:'spe'}, Calm: {plus:'spd', minus:'atk'}, Careful: {plus:'spd', minus:'spa'}, Docile: {}, Gentle: {plus:'spd', minus:'def'}, Hardy: {}, Hasty: {plus:'spe', minus:'def'}, Impish: {plus:'def', minus:'spa'}, Jolly: {plus:'spe', minus:'spa'}, Lax: {plus:'def', minus:'spd'}, Lonely: {plus:'atk', minus:'def'}, Mild: {plus:'spa', minus:'def'}, Modest: {plus:'spa', minus:'atk'}, Naive: {plus:'spe', minus:'spd'}, Naughty: {plus:'atk', minus:'spd'}, Quiet: {plus:'spa', minus:'spe'}, Quirky: {}, Rash: {plus:'spa', minus:'spd'}, Relaxed: {plus:'def', minus:'spe'}, Sassy: {plus:'spd', minus:'spe'}, Serious: {}, Timid: {plus:'spe', minus:'atk'} }; Tools.prototype.getNature = function(nature) { if (typeof nature === 'string') nature = BattleNatures[nature]; if (!nature) nature = {}; return nature; }; Tools.prototype.natureModify = function(stats, nature) { if (typeof nature === 'string') nature = BattleNatures[nature]; if (!nature) return stats; if (nature.plus) stats[nature.plus] *= 1.1; if (nature.minus) stats[nature.minus] *= 0.9; return stats; }; Tools.prototype.checkLearnset = function(move, template, lsetData) { move = toId(move); template = this.getTemplate(template); lsetData = lsetData || {}; var set = (lsetData.set || (lsetData.set={})); var format = (lsetData.format || (lsetData.format={})); var alreadyChecked = {}; var level = set.level || 100; var limit1 = true; var sketch = false; var sometimesPossible = false; // is this move in the learnset at all? // This is a pretty complicated algorithm // Abstractly, what it does is construct the union of sets of all // possible ways this pokemon could be obtained, and then intersect // it with a the pokemon's existing set of all possible ways it could // be obtained. If this intersection is non-empty, the move is legal. // We apply several optimizations to this algorithm. The most // important is that with, for instance, a TM move, that Pokemon // could have been obtained from any gen at or before that TM's gen. // Instead of adding every possible source before or during that gen, // we keep track of a maximum gen variable, intended to mean "any // source at or before this gen is possible." // set of possible sources of a pokemon with this move, represented as an array var sources = []; // the equivalent of adding "every source at or before this gen" to sources var sourcesBefore = 0; var noPastGen = format.noPokebank || format.requirePentagon; do { alreadyChecked[template.speciesid] = true; // Stabmons hack to avoid copying all of validateSet to formats. if (format.id === 'gen5stabmons' && template.types.indexOf(this.getMove(move).type) > -1) return false; if (template.learnset) { if (template.learnset[move] || template.learnset['sketch']) { sometimesPossible = true; var lset = template.learnset[move]; if (!lset || template.speciesid === 'smeargle') { lset = template.learnset['sketch']; sketch = true; // Chatter, Struggle and Magikarp's Revenge cannot be sketched if (move in {'chatter':1, 'struggle':1, 'magikarpsrevenge':1}) return true; // Signature moves are unavailable in XY pre-Pokebank if (format.noPokebank && move in {'conversion':1,'conversion2':1,'aeroblast':1,'sacredfire':1,'mistball':1,'lusterpurge':1,'doomdesire':1,'psychoboost':1,'roaroftime':1,'spacialrend':1,'magmastorm':1,'crushgrip':1,'shadowforce':1,'lunardance':1,'heartswap':1,'darkvoid':1,'seedflare':1,'judgment':1,'searingshot':1,'vcreate':1,'fusionflare':1,'fusionbolt':1,'blueflare':1,'boltstrike':1,'glaciate':1,'freezeshock':1,'iceburn':1,'secretsword':1,'relicsong':1,'technoblast':1}) { return true; } } if (typeof lset === 'string') lset = [lset]; for (var i=0, len=lset.length; i this.gen) continue; if (learned.substr(0,2) in {'4L':1,'5L':1,'6L':1}) { // gen 4-6 level-up moves if (level >= parseInt(learned.substr(2),10)) { // we're past the required level to learn it return false; } if (!template.gender || template.gender === 'F') { // available as egg move learned = learned.charAt(0)+'Eany'; } else { // this move is unavailable, skip it continue; } } if (learned.charAt(1) in {L:1,M:1,T:1}) { if (learned.charAt(0) === '6') { // current-gen TM or tutor moves: // always available return false; } // past-gen level-up, TM, or tutor moves: // available as long as the source gen was or was before this gen limit1 = false; sourcesBefore = Math.max(sourcesBefore, parseInt(learned.charAt(0),10)); } else if (learned.charAt(1) in {E:1,S:1,D:1}) { // egg, event, or DW moves: // only if that was the source if (format.noPokebank) { if (move === 'extremespeed') continue; if (move === 'perishsong' && template.id === 'gastly') continue; if (move === 'stealthrock' && template.id === 'skarmory') continue; if (move === 'drillrun' && template.id === 'karrablast') continue; } if (learned.charAt(1) === 'E') { // it's an egg move, so we add each pokemon that can be bred with to its sources if (learned.charAt(0) === '6') { // gen 6 doesn't have egg move incompatibilities sources.push('6E'); continue; } var eggGroups = template.eggGroups; if (!eggGroups) continue; if (eggGroups[0] === 'Undiscovered') eggGroups = this.getTemplate(template.evos[0]).eggGroups; var atLeastOne = false; var fromSelf = (learned.substr(1) === 'Eany'); learned = learned.substr(0,2); for (var templateid in this.data.Pokedex) { var dexEntry = this.getTemplate(templateid); if ( // CAP pokemon can't breed !dexEntry.isNonstandard && // can't breed mons from future gens dexEntry.gen <= parseInt(learned.charAt(0),10) && // genderless pokemon can't pass egg moves dexEntry.gender !== 'N') { if ( // chainbreeding fromSelf || // otherwise parent must be able to learn the move !alreadyChecked[dexEntry.speciesid] && dexEntry.learnset && (dexEntry.learnset[move]||dexEntry.learnset['sketch'])) { if (dexEntry.eggGroups.intersect(eggGroups).length) { // we can breed with it atLeastOne = true; sources.push(learned+dexEntry.id); } } } } // chainbreeding with itself from earlier gen if (!atLeastOne) sources.push(learned+template.id); } else if (learned.charAt(1) === 'S') { sources.push(learned+' '+template.id); } else { sources.push(learned); } } } } if (format.mimicGlitch && template.gen < 5) { // include the Mimic Glitch when checking this mon's learnset var glitchMoves = {metronome:1, copycat:1, transform:1, mimic:1, assist:1}; var getGlitch = false; for (var i in glitchMoves) { if (template.learnset[i]) { if (i === 'mimic' && this.getAbility(set.ability).gen == 4 && !template.prevo) { // doesn't get the glitch } else { getGlitch = true; break; } } } if (getGlitch) { sourcesBefore = Math.max(sourcesBefore, 4); if (this.getMove(move).gen < 5) { limit1 = false; } } } } // also check to see if the mon's prevo or freely switchable formes can learn this move if (!template.learnset && template.baseSpecies !== template.species) { // forme takes precedence over prevo only if forme has no learnset template = this.getTemplate(template.baseSpecies); } else if (template.prevo) { template = this.getTemplate(template.prevo); } else if (template.speciesid === 'shaymin') { template = this.getTemplate('shayminsky'); } else if (template.baseSpecies !== template.species && template.baseSpecies !== 'Kyurem') { template = this.getTemplate(template.baseSpecies); } else { template = null; } } while (template && template.species && !alreadyChecked[template.speciesid]); if (limit1 && sketch) { // limit 1 sketch move if (lsetData.sketchMove) { return {type:'oversketched', maxSketches: 1}; } lsetData.sketchMove = move; } // Now that we have our list of possible sources, intersect it with the current list if (!sourcesBefore && !sources.length) { if (noPastGen && sometimesPossible) return {type:'pokebank'}; return true; } if (!sources.length) sources = null; if (sourcesBefore || lsetData.sourcesBefore) { // having sourcesBefore is the equivalent of having everything before that gen // in sources, so we fill the other array in preparation for intersection if (sourcesBefore && lsetData.sources) { if (!sources) sources = []; for (var i=0, len=lsetData.sources.length; i8) 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 0) { var plusPlusPos = subformat.banlist[i].indexOf('++'); if (plusPlusPos && plusPlusPos > 0) { var complexList = subformat.banlist[i].split('++'); for (var j=0; j6) { return ["Your team has more than 6 pokemon."]; } var teamHas = {}; for (var i=0; i= maxForcedLevel) { set.forcedLevel = maxForcedLevel; } if (set.level > maxLevel || set.level == set.forcedLevel || set.level == set.maxForcedLevel) { set.level = maxLevel; } var nameTemplate = this.getTemplate(set.name); if (nameTemplate.exists && nameTemplate.name.toLowerCase() === set.name.toLowerCase()) set.name = null; set.species = set.species; set.name = set.name || set.species; var name = set.species; if (set.species !== set.name) name = set.name + " ("+set.species+")"; var isHidden = false; var lsetData = {set:set, format:format}; var setHas = {}; if (!template || !template.abilities) { set.species = 'Bulbasaur'; template = this.getTemplate('Bulbasaur'); } var banlistTable = this.getBanlistTable(format); var check = toId(set.species); var clause = ''; setHas[check] = true; if (banlistTable[check]) { clause = typeof banlistTable[check] === 'string' ? " by "+ banlistTable[check] : ''; problems.push(set.species+' is banned'+clause+'.'); } check = toId(set.ability); setHas[check] = true; if (banlistTable[check]) { clause = typeof banlistTable[check] === 'string' ? " by "+ banlistTable[check] : ''; problems.push(name+"'s ability "+set.ability+" is banned"+clause+"."); } check = toId(set.item); setHas[check] = true; if (banlistTable[check]) { clause = typeof banlistTable[check] === 'string' ? " by "+ banlistTable[check] : ''; problems.push(name+"'s item "+set.item+" is banned"+clause+"."); } if (banlistTable['illegal'] && item.isUnreleased) { problems.push(name+"'s item "+set.item+" is unreleased."); } if (banlistTable['Unreleased'] && template.isUnreleased) { if (!format.requirePentagon || (template.eggGroups[0] === 'Undiscovered' && !template.evos)) { problems.push(name+" ("+template.species+") is unreleased."); } } setHas[toId(set.ability)] = true; if (banlistTable['illegal']) { var totalEV = 0; for (var k in set.evs) { if (typeof set.evs[k] !== 'number') { set.evs[k] = 0; } totalEV += set.evs[k]; } if (totalEV > 510) { problems.push(name+" has more than 510 total EVs."); } // Don't check abilities for metagames with All Abilities if (this.gen <= 2) { set.ability = ''; } else if (!banlistTable['ignoreillegalabilities']) { if (!ability.name) { problems.push(name+" needs to have an ability."); } else if (ability.name !== template.abilities['0'] && ability.name !== template.abilities['1'] && ability.name !== template.abilities['H']) { problems.push(name+" can't have "+set.ability+"."); } if (ability.name === template.abilities['H']) { isHidden = true; if (template.unreleasedHidden && banlistTable['illegal']) { problems.push(name+"'s hidden ability is unreleased."); } else if (this.gen === 5 && set.level < 10 && (template.maleOnlyHidden || template.gender === 'N')) { problems.push(name+" must be at least level 10 with its hidden ability."); } if (template.maleOnlyHidden) { set.gender = 'M'; lsetData.sources = ['5D']; } } } } if (set.moves && Array.isArray(set.moves)) { set.moves = set.moves.filter(function(val){ return val; }); } if (!set.moves || !set.moves.length) { problems.push(name+" has no moves."); } else { // A limit is imposed here to prevent too much engine strain or // too much layout deformation - to be exact, this is the Debug // Mode limitation. // The usual limit of 4 moves is handled elsewhere - currently // in the cartridge-compliant set validator: formats.js:pokemon set.moves = set.moves.slice(0,24); for (var i=0; i1?"s":"")+"."); } else if (problem.type === 'pokebank') { problemString = problemString.concat(" because it's not possible to transfer pokemon from earlier games to XY yet (Pokébank comes out in December)."); } else { problemString = problemString.concat("."); } problems.push(problemString); } } } if (lsetData.sources && lsetData.sources.length === 1 && !lsetData.sourcesBefore) { // we're restricted to a single source var source = lsetData.sources[0]; if (source.substr(1,1) === 'S') { // it's an event var eventData = null; var splitSource = source.substr(2).split(' '); var eventTemplate = this.getTemplate(splitSource[1]); if (eventTemplate.eventPokemon) eventData = eventTemplate.eventPokemon[parseInt(splitSource[0],10)]; if (eventData) { if (eventData.nature && eventData.nature !== set.nature) { problems.push(name+" must have a "+eventData.nature+" nature because it comes from a specific event."); } if (eventData.shiny) { set.shiny = true; } if (eventData.generation < 5) eventData.isHidden = false; if (eventData.isHidden !== undefined && eventData.isHidden !== isHidden) { problems.push(name+(isHidden?" can't have":" must have")+" its hidden ability because it comes from a specific event."); } if (eventData.abilities && eventData.abilities.indexOf(ability.id) < 0) { problems.push(name+" must have "+eventData.abilities.join(" or ")+" because it comes from a specific event."); } if (eventData.gender) { set.gender = eventData.gender; } if (eventData.level && set.level < eventData.level) { problems.push(name+" must be at least level "+eventData.level+" because it comes from a specific event."); } } isHidden = false; } } if (isHidden && lsetData.sourcesBefore < 5) { if (!lsetData.sources) { problems.push(name+" has a hidden ability - it can't have moves only learned before gen 5."); } else if (template.gender) { var compatibleSource = false; for (var i=0,len=lsetData.sources.length; i= 10)) { compatibleSource = true; break; } } if (!compatibleSource) { problems.push(name+" has moves incompatible with its hidden ability."); } } } if (set.level < template.evoLevel) { // FIXME: Event pokemon given at a level under what it normally can be attained at gives a false positive problems.push(name+" must be at least level "+template.evoLevel+"."); } if (!lsetData.sources && lsetData.sourcesBefore <= 3 && this.getAbility(set.ability).gen === 4 && !template.prevo && this.gen <= 5) { problems.push(name+" has a gen 4 ability and isn't evolved - it can't use anything from gen 3."); } } setHas[toId(template.tier)] = true; if (banlistTable[template.tier]) { problems.push(name+" is in "+template.tier+", which is banned."); } if (teamHas) { for (var i in setHas) { teamHas[i] = true; } } for (var i=0; i 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.dataSearch = function(target, searchIn) { if (!target) { return false; } searchIn = searchIn || ['Pokedex', 'Movedex', 'Abilities', 'Items']; var searchFunctions = { Pokedex: 'getTemplate', Movedex: 'getMove', Abilities: 'getAbility', Items: 'getItem' }; var searchTypes = { Pokedex: 'pokemon', Movedex: 'move', Abilities: 'ability', Items: 'item' }; var searchResults = []; for (var i = 0; i < searchIn.length; i++) { if (typeof this[searchFunctions[searchIn[i]]] === "function") { 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; }; /** * 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) { ret.init(); } return ret; }; moddedTools.base = Tools.construct(); // "gen6" is an alias for the current base data moddedTools.gen6 = moddedTools.base; var parentMods = {}; try { var mods = fs.readdirSync('./mods/'); mods.forEach(function(mod) { if (fs.existsSync('./mods/'+mod+'/scripts.js')) { parentMods[mod] = require('./mods/'+mod+'/scripts.js').BattleScripts.inherit || 'base'; } else { parentMods[mod] = 'base'; } }); 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.log("Error while loading mods: "+e); } moddedTools.base.__proto__.moddedTools = moddedTools; return moddedTools.base; })();