diff --git a/.eslintignore b/.eslintignore index 1567cc07bf..d2bf6b26d5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ tournaments/lib/ logs/ +dev-tools/globals.js diff --git a/dev-tools/globals.ts b/dev-tools/globals.ts new file mode 100644 index 0000000000..38e21a279a --- /dev/null +++ b/dev-tools/globals.ts @@ -0,0 +1,4 @@ +interface AnyObject {[k: string]: any} + +var Dex = require('./sim/dex'); +// var Sim = require('./sim'); diff --git a/sim/dex-data.js b/sim/dex-data.js index e69de29bb2..61b2de845f 100644 --- a/sim/dex-data.js +++ b/sim/dex-data.js @@ -0,0 +1,115 @@ +/** + * Simulator Battle + * Pokemon Showdown - http://pokemonshowdown.com/ + * + * @license MIT license + */ +'use strict'; + +class Tools { + /** + * Safely converts the passed variable into a string. Unlike '' + str, + * String(str), or str.toString(), Dex.getString is guaranteed not to + * crash. + * + * Specifically, the fear with untrusted JSON is an object like: + * + * let a = {"toString": "this is not a function"}; + * console.log(`a is ${a}`); + * + * This will crash (because a.toString() is not a function). Instead, + * Dex.getString simply returns '' if the passed variable isn't a + * string or a number. + * + * @param {any} str + * @return {string} + */ + static getString(str) { + if (typeof str === 'string' || typeof str === 'number') return '' + str; + return ''; + } + + /** + * Converts anything to an ID. An ID must have only lowercase alphanumeric + * characters. + * If a string is passed, it will be converted to lowercase and + * non-alphanumeric characters will be stripped. + * If an object with an ID is passed, its ID will be returned. + * Otherwise, an empty string will be returned. + * + * Dex.getId is generally assigned to the global toId, because of how + * commonly it's used. + * + * @param {any} text + * @return {string} + */ + static getId(text) { + if (text && text.id) { + text = text.id; + } else if (text && text.userid) { + text = text.userid; + } + if (typeof text !== 'string' && typeof text !== 'number') return ''; + return ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, ''); + } +} +const toId = Tools.getId; + +class Effect { + /** + * @param {AnyObject} data + */ + constructor(data) { + /** + * ID. This will be a lowercase version of the name with all the + * alphanumeric characters removed. So, for instance, "Mr. Mime" + * becomes "mrmime", and "Basculin-Blue-Striped" becomes + * "basculinbluestriped". + * + * @type {string} + */ + this.id = ''; + /** + * Name. Currently does not support Unicode letters, so "Flabébé" + * is "Flabebe" and "Nidoran♀" is "Nidoran-F". + * + * @type {string} + */ + this.name = ''; + /** + * Full name. Prefixes the name with the effect type. For instance, + * Leftovers would be "item: Leftovers", confusion the status + * condition would be "confusion", etc. + * + * @type {string} + */ + this.fullname = ''; + /** + * Effect type. + * + * @type {'Effect' | 'Pokemon' | 'Move' | 'Item' | 'Ability'} + */ + this.effectType = 'Effect'; + /** + * Does it exist? For historical reasons, when you use an accessor + * for an effect that doesn't exist, you get a dummy effect that + * doesn't do anything, and this field set to false. + * + * @type {boolean} + */ + this.exists = false; + + Object.assign(this, data); + this.name = Tools.getString(data.name); + this.fullname = Tools.getString(data.fullname) || this.name; + this.id = toId(this.name); + this.effectType = Tools.getString(data.effectType) || "Effect"; + this.exists = !!this.exists; + } + toString() { + return this.name; + } +} + +exports.Tools = Tools; +exports.Effect = Effect; diff --git a/sim/dex.js b/sim/dex.js index 6cac1169fb..2f8b53ae4d 100644 --- a/sim/dex.js +++ b/sim/dex.js @@ -45,6 +45,8 @@ const fs = require('fs'); const path = require('path'); +const DexData = require('./dex-data'); + const DATA_DIR = path.resolve(__dirname, '../data'); const MODS_DIR = path.resolve(__dirname, '../mods'); const FORMATS = path.resolve(__dirname, '../config/formats'); @@ -77,14 +79,11 @@ if (!Object.values) { // }; // } -/** @typedef {{[k: string]: any}} AnyObject */ - /** @type {{[mod: string]: ModdedDex}} */ let dexes = {}; /** @typedef {'Pokedex' | 'FormatsData' | 'Learnsets' | 'Movedex' | 'Statuses' | 'TypeChart' | 'Scripts' | 'Items' | 'Abilities' | 'Natures' | 'Formats' | 'Aliases'} DataType */ /** @type {DataType[]} */ -// @ts-ignore TypeScript bug const DATA_TYPES = ['Pokedex', 'FormatsData', 'Learnsets', 'Movedex', 'Statuses', 'TypeChart', 'Scripts', 'Items', 'Abilities', 'Natures', 'Formats', 'Aliases']; const DATA_FILES = { @@ -135,20 +134,7 @@ const BattleNatures = { timid: {name:"Timid", plus:'spe', minus:'atk'}, }; -/** - * @param {any} text - * @return {string} - */ -function toId(text) { - // this is a duplicate of Dex.getId, for performance reasons - if (text && text.id) { - text = text.id; - } else if (text && text.userid) { - text = text.userid; - } - if (typeof text !== 'string' && typeof text !== 'number') return ''; - return ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, ''); -} +const toId = DexData.Tools.getId; class ModdedDex { @@ -170,6 +156,8 @@ class ModdedDex { this.formatsCache = null; this.modsLoaded = false; + this.getString = DexData.Tools.getString; + this.getId = DexData.Tools.getId; this.ModdedDex = ModdedDex; } @@ -229,23 +217,6 @@ class ModdedDex { return this.name; } - /** - * Safely converts the passed variable into a string. Unlike '' + str, - * String(str), or str.toString(), Dex.getString is guaranteed not to - * crash. - * - * The other methods of casting to string can crash if str.toString crashes - * or isn't a function. Instead, Dex.getString simply returns '' if the - * passed variable isn't a string or a number. - * - * @param {any} str - * @return {string} - */ - getString(str) { - if (typeof str === 'string' || typeof str === 'number') return '' + str; - return ''; - } - /** * Sanitizes a username or Pokemon nickname * @@ -282,30 +253,6 @@ class ModdedDex { return name; } - /** - * Converts anything to an ID. An ID must have only lowercase alphanumeric - * characters. - * If a string is passed, it will be converted to lowercase and - * non-alphanumeric characters will be stripped. - * If an object with an ID is passed, its ID will be returned. - * Otherwise, an empty string will be returned. - * - * Dex.getId is generally assigned to the global toId, because of how - * commonly it's used. - * - * @param {any} text - * @return {string} - */ - getId(text) { - if (text && text.id) { - text = text.id; - } else if (text && text.userid) { - text = text.userid; - } - if (typeof text !== 'string' && typeof text !== 'number') return ''; - return ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, ''); - } - /** * returns false if the target is immune; true otherwise * @@ -589,8 +536,7 @@ class ModdedDex { let id = toId(name); let effect = {}; if (id && this.data.Statuses[id]) { - effect = this.data.Statuses[id]; - effect.name = effect.name || this.data.Statuses[id].name; + effect = new DexData.Effect(this.data.Statuses[id]); } 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; @@ -1039,6 +985,7 @@ class ModdedDex { return false; } + /** @type {DataType[]} */ searchIn = searchIn || ['Pokedex', 'Movedex', 'Abilities', 'Items', 'Natures']; let searchFunctions = {Pokedex: 'getTemplate', Movedex: 'getMove', Abilities: 'getAbility', Items: 'getItem', Natures: 'getNature'}; @@ -1046,7 +993,7 @@ class ModdedDex { let searchResults = []; for (let i = 0; i < searchIn.length; i++) { /** @type {AnyObject} */ - // @ts-ignore TypeScript bug + // @ts-ignore let res = this[searchFunctions[searchIn[i]]](target); if (res.exists) { searchResults.push({ @@ -1541,7 +1488,6 @@ class ModdedDex { dexes['base'].formatsCache[id] = format; if (this.dataCache) this.dataCache.Formats[id] = format; if (!this.isBase) { - // @ts-ignore if (dexes['base'].dataCache) dexes['base'].dataCache.Formats[id] = format; } } diff --git a/tsconfig.json b/tsconfig.json index 810d62e3b4..1ba31d4c2c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,9 @@ }, "types": ["node"], "include": [ + "./dev-tools/globals.ts", "./sim/dex.js", + "./sim/dex-data.js", "./sim/prng.js" ] }