mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-22 01:35:31 -05:00
TypeScript!
Currently TypeScript is validating tools.js and is not particularly strict about anything and we use 'any' a lot and it's not part of 'npm test' yet, but everything has to start somewhere! tools.js has also been refactored majorly to use accessors rather than loader functions. This basically means you don't need to do Tools.includeData() or anything like that anymore. The new system is also easier to make TypeScript-compatible. See #3278
This commit is contained in:
parent
c201e22841
commit
3716f360ff
|
|
@ -2817,7 +2817,7 @@ class Battle extends Tools.BattleDex {
|
|||
if (!rest.length) throw new TypeError("Event handlers must have a callback");
|
||||
|
||||
if (target.effectType !== 'Format') {
|
||||
throw new TypeError(`${target.effectType} targets are not supported at this time`);
|
||||
throw new TypeError(`${target.name} is a ${target.effectType} but only Format targets are supported right now`);
|
||||
}
|
||||
|
||||
let callback, priority, order, subOrder, data;
|
||||
|
|
|
|||
|
|
@ -592,7 +592,7 @@ function runDexsearch(target, cmd, canAll, message) {
|
|||
if (alts.tiers[dex[mon].tier]) continue;
|
||||
if (Object.values(alts.tiers).includes(false) && alts.tiers[dex[mon].tier] !== false) continue;
|
||||
// some LC Pokemon are also in other tiers and need to be handled separately
|
||||
if (alts.tiers.LC && !dex[mon].prevo && dex[mon].nfe && dex[mon].tier !== 'LC Uber' && !Tools.data.Formats.lc.banlist.includes(dex[mon].species)) continue;
|
||||
if (alts.tiers.LC && !dex[mon].prevo && dex[mon].nfe && dex[mon].tier !== 'LC Uber' && !Tools.formats.lc.banlist.includes(dex[mon].species)) continue;
|
||||
}
|
||||
|
||||
for (let type in alts.types) {
|
||||
|
|
|
|||
|
|
@ -1335,7 +1335,7 @@ exports.commands = {
|
|||
let format = Tools.getFormat(targetId);
|
||||
if (format.effectType === 'Format') formatList = [targetId];
|
||||
if (!formatList) {
|
||||
formatList = Object.keys(Tools.data.Formats).filter(formatid => Tools.data.Formats[formatid].effectType === 'Format');
|
||||
formatList = Object.keys(Tools.formats);
|
||||
}
|
||||
|
||||
// Filter formats and group by section
|
||||
|
|
|
|||
|
|
@ -313,8 +313,8 @@ class Ladder {
|
|||
*/
|
||||
static visualizeAll(username) {
|
||||
let ratings = [];
|
||||
for (let i in Tools.data.Formats) {
|
||||
if (Tools.data.Formats[i].searchShow) {
|
||||
for (let i in Tools.formats) {
|
||||
if (Tools.formats[i].searchShow) {
|
||||
ratings.push(Ladders(i).visualize(username));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
rooms.js
4
rooms.js
|
|
@ -418,8 +418,8 @@ class GlobalRoom {
|
|||
this.formatList = '|formats' + (Ladders.formatsListPrefix || '');
|
||||
let section = '', prevSection = '';
|
||||
let curColumn = 1;
|
||||
for (let i in Tools.data.Formats) {
|
||||
let format = Tools.data.Formats[i];
|
||||
for (let i in Tools.formats) {
|
||||
let format = Tools.formats[i];
|
||||
if (format.section) section = format.section;
|
||||
if (format.column) curColumn = format.column;
|
||||
if (!format.name) continue;
|
||||
|
|
|
|||
152
tools.js
152
tools.js
|
|
@ -21,7 +21,7 @@
|
|||
* Note that you don't need this for Dex.mod, Dex.mod will
|
||||
* automatically populate this.
|
||||
* - Dex.includeFormats() ~30ms
|
||||
* As above, but will also populate Dex.data.Formats, giving an object
|
||||
* As above, but will also populate Dex.formats, giving an object
|
||||
* containing formats.
|
||||
* - Dex.includeData() ~500ms
|
||||
* As above, but will also populate all of Dex.data, giving access to
|
||||
|
|
@ -34,6 +34,9 @@
|
|||
* Note that Dex.mod(...) will automatically populate .data, so use
|
||||
* this only if you need to manually iterate Dex.dexes.
|
||||
*
|
||||
* Note that preloading is unnecessary. The getters for Dex.data etc
|
||||
* will automatically load this data as needed.
|
||||
*
|
||||
* @license MIT license
|
||||
*/
|
||||
|
||||
|
|
@ -49,6 +52,7 @@ if (!Object.values) {
|
|||
for (let k in object) values.push(object[k]);
|
||||
return values;
|
||||
};
|
||||
// @ts-ignore
|
||||
Object.entries = function (object) {
|
||||
let entries = [];
|
||||
for (let k in object) entries.push([k, object[k]]);
|
||||
|
|
@ -68,8 +72,10 @@ if (!Object.values) {
|
|||
// };
|
||||
// }
|
||||
|
||||
/** @type {{[mod: string]: BattleDex}} */
|
||||
let dexes = {};
|
||||
|
||||
/** @typedef {'Pokedex' | 'FormatsData' | 'Learnsets' | 'Movedex' | 'Statuses' | 'TypeChart' | 'Scripts' | 'Items' | 'Abilities' | 'Natures' | 'Formats' | 'Aliases'} DataType */
|
||||
const DATA_TYPES = ['Pokedex', 'FormatsData', 'Learnsets', 'Movedex', 'Statuses', 'TypeChart', 'Scripts', 'Items', 'Abilities', 'Natures', 'Formats', 'Aliases'];
|
||||
|
||||
const DATA_FILES = {
|
||||
|
|
@ -87,6 +93,13 @@ const DATA_FILES = {
|
|||
'Natures': 'natures',
|
||||
};
|
||||
|
||||
/** @typedef {{
|
||||
id: string,
|
||||
name: string,
|
||||
}} DexTemplate */
|
||||
|
||||
/** @typedef {{Pokedex: object, Movedex: object, Statuses: object, TypeChart: object, Scripts: object, Items: object, Abilities: object, FormatsData: object, Learnsets: object, Aliases: object, Natures: object}} BattleDexData */
|
||||
|
||||
const BattleNatures = {
|
||||
adamant: {name:"Adamant", plus:'atk', minus:'spa'},
|
||||
bashful: {name:"Bashful"},
|
||||
|
|
@ -128,32 +141,60 @@ function toId(text) {
|
|||
|
||||
class BattleDex {
|
||||
|
||||
constructor(mod) {
|
||||
if (!mod) mod = 'base';
|
||||
/**
|
||||
* @param {string=} mod
|
||||
*/
|
||||
constructor(mod = 'base') {
|
||||
this.gen = 0;
|
||||
|
||||
this.formatsLoaded = false;
|
||||
this.modsLoaded = false;
|
||||
this.dataLoaded = false;
|
||||
this.name = "[BattleDex]";
|
||||
|
||||
this.isBase = (mod === 'base');
|
||||
this.currentMod = mod;
|
||||
this.parentMod = '';
|
||||
this.data = null;
|
||||
this.dexes = dexes;
|
||||
|
||||
/** @type {any} */
|
||||
this.dataCache = null;
|
||||
/** @type {any} */
|
||||
this.formatsCache = null;
|
||||
this.modsLoaded = false;
|
||||
|
||||
this.BattleDex = BattleDex;
|
||||
}
|
||||
|
||||
get data() {
|
||||
this.includeData();
|
||||
return this.dataCache;
|
||||
}
|
||||
get formats() {
|
||||
this.includeFormats();
|
||||
return this.formatsCache;
|
||||
}
|
||||
get dexes() {
|
||||
this.includeMods();
|
||||
return dexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} mod
|
||||
* @return {BattleDex}
|
||||
*/
|
||||
mod(mod) {
|
||||
if (!dexes['base'].modsLoaded) dexes['base'].includeMods();
|
||||
if (!mod) mod = 'base';
|
||||
return dexes[mod].includeData();
|
||||
return dexes[mod];
|
||||
}
|
||||
format(format) {
|
||||
if (!this.modsLoaded) this.includeMods();
|
||||
const mod = this.getFormat(format).mod;
|
||||
// TODO: change default format mod as gen7 becomes stable
|
||||
if (!mod) return dexes['gen6'].includeData();
|
||||
return dexes[mod].includeData();
|
||||
if (!mod) return dexes['gen6'];
|
||||
return dexes[mod];
|
||||
}
|
||||
/**
|
||||
* @param {DataType} dataType
|
||||
* @param {string} id
|
||||
*/
|
||||
modData(dataType, id) {
|
||||
if (this.isBase) return this.data[dataType][id];
|
||||
if (this.data[dataType][id] !== dexes[this.parentMod].data[dataType][id]) return this.data[dataType][id];
|
||||
|
|
@ -173,7 +214,7 @@ class BattleDex {
|
|||
* or isn't a function. Instead, Tools.getString simply returns '' if the
|
||||
* passed variable isn't a string or a number.
|
||||
*
|
||||
* @param {mixed} str
|
||||
* @param {any} str
|
||||
* @return {string}
|
||||
*/
|
||||
getString(str) {
|
||||
|
|
@ -202,7 +243,7 @@ class BattleDex {
|
|||
* characters in the name, although this is not strictly necessary for
|
||||
* safety.
|
||||
*
|
||||
* @param {mixed} name
|
||||
* @param {any} name
|
||||
* @return {string}
|
||||
*/
|
||||
getName(name) {
|
||||
|
|
@ -228,7 +269,7 @@ class BattleDex {
|
|||
* Tools.getId is generally assigned to the global toId, because of how
|
||||
* commonly it's used.
|
||||
*
|
||||
* @param {mixed} text
|
||||
* @param {any} text
|
||||
* @return {string}
|
||||
*/
|
||||
getId(text) {
|
||||
|
|
@ -421,6 +462,7 @@ class BattleDex {
|
|||
}
|
||||
if (id.substr(0, 11) === 'hiddenpower') {
|
||||
let matches = /([a-z]*)([0-9]*)/.exec(id);
|
||||
// @ts-ignore
|
||||
id = matches[1];
|
||||
}
|
||||
if (id && this.data.Movedex.hasOwnProperty(id)) {
|
||||
|
|
@ -814,9 +856,15 @@ class BattleDex {
|
|||
return arr;
|
||||
}
|
||||
|
||||
levenshtein(s, t, l) { // s = string 1, t = string 2, l = limit
|
||||
/**
|
||||
* @param {string} s - string 1
|
||||
* @param {string} t - string 2
|
||||
* @param {number} l - limit
|
||||
*/
|
||||
levenshtein(s, t, l) {
|
||||
// Original levenshtein distance function by James Westgate, turned out to be the fastest
|
||||
let d = []; // 2d matrix
|
||||
/** @type {number[][]} */
|
||||
let d = [];
|
||||
|
||||
// Step 1
|
||||
let n = s.length;
|
||||
|
|
@ -895,6 +943,7 @@ class BattleDex {
|
|||
|
||||
let cmpTarget = target.toLowerCase();
|
||||
let maxLd = 3;
|
||||
let fuzzyResults = [];
|
||||
if (cmpTarget.length <= 1) {
|
||||
return false;
|
||||
} else if (cmpTarget.length <= 4) {
|
||||
|
|
@ -919,18 +968,19 @@ class BattleDex {
|
|||
|
||||
let ld = this.levenshtein(cmpTarget, word.toLowerCase(), maxLd);
|
||||
if (ld <= maxLd) {
|
||||
searchResults.push({word: word, ld: ld});
|
||||
fuzzyResults.push({word: word, ld: ld});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (searchResults.length) {
|
||||
if (fuzzyResults.length) {
|
||||
/** @type {any} */
|
||||
let newTarget = "";
|
||||
let newLD = 10;
|
||||
for (let i = 0, l = searchResults.length; i < l; i++) {
|
||||
if (searchResults[i].ld < newLD) {
|
||||
newTarget = searchResults[i];
|
||||
newLD = searchResults[i].ld;
|
||||
for (let i = 0, l = fuzzyResults.length; i < l; i++) {
|
||||
if (fuzzyResults[i].ld < newLD) {
|
||||
newTarget = fuzzyResults[i];
|
||||
newLD = fuzzyResults[i].ld;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1193,7 +1243,7 @@ class BattleDex {
|
|||
}
|
||||
|
||||
includeMods() {
|
||||
if (!this.isBase) throw new Error("This must be called on the base Dex.");
|
||||
if (!this.isBase) throw new Error(`This must be called on the base Dex`);
|
||||
if (this.modsLoaded) return this;
|
||||
|
||||
let modList = fs.readdirSync(path.resolve(__dirname, 'mods'));
|
||||
|
|
@ -1206,16 +1256,15 @@ class BattleDex {
|
|||
}
|
||||
|
||||
includeModData() {
|
||||
this.includeMods();
|
||||
for (const mod in dexes) {
|
||||
for (const mod in this.dexes) {
|
||||
dexes[mod].includeData();
|
||||
}
|
||||
}
|
||||
|
||||
includeData() {
|
||||
if (this.dataLoaded) return this;
|
||||
if (this.dataCache) return this;
|
||||
dexes['base'].includeMods();
|
||||
if (!this.data) this.data = {mod: this.currentMod};
|
||||
this.dataCache = {};
|
||||
|
||||
let basePath = this.isBase ? './data/' : './mods/' + this.currentMod + '/';
|
||||
|
||||
|
|
@ -1226,29 +1275,29 @@ class BattleDex {
|
|||
if (this.parentMod) {
|
||||
parentDex = dexes[this.parentMod];
|
||||
if (!parentDex || parentDex === this) throw new Error("Unable to load " + this.currentMod + ". `inherit` should specify a parent mod from which to inherit data, or must be not specified.");
|
||||
parentDex.includeData();
|
||||
}
|
||||
|
||||
for (let dataType of DATA_TYPES) {
|
||||
if (dataType === 'Natures' && this.isBase) {
|
||||
this.data[dataType] = BattleNatures;
|
||||
this.dataCache[dataType] = BattleNatures;
|
||||
continue;
|
||||
}
|
||||
let BattleData = this.loadDataFile(basePath, dataType);
|
||||
if (!BattleData || typeof BattleData !== 'object') throw new TypeError("Exported property `Battle" + dataType + "`from `" + './data/' + DATA_FILES[dataType] + "` must be an object except `null`.");
|
||||
if (BattleData !== this.data[dataType]) this.data[dataType] = Object.assign(BattleData, this.data[dataType]);
|
||||
if (BattleData !== this.dataCache[dataType]) this.dataCache[dataType] = Object.assign(BattleData, this.dataCache[dataType]);
|
||||
if (dataType === 'Formats' && !parentDex) Object.assign(BattleData, this.formats);
|
||||
}
|
||||
this.data['MoveCache'] = new Map();
|
||||
this.data['ItemCache'] = new Map();
|
||||
this.data['AbilityCache'] = new Map();
|
||||
this.data['TemplateCache'] = new Map();
|
||||
if (this.isBase) {
|
||||
this.dataCache['MoveCache'] = new Map();
|
||||
this.dataCache['ItemCache'] = new Map();
|
||||
this.dataCache['AbilityCache'] = new Map();
|
||||
this.dataCache['TemplateCache'] = new Map();
|
||||
if (!parentDex) {
|
||||
// Formats are inherited by mods
|
||||
this.includeFormats();
|
||||
} else {
|
||||
for (let dataType of DATA_TYPES) {
|
||||
const parentTypedData = parentDex.data[dataType];
|
||||
const childTypedData = this.data[dataType] || (this.data[dataType] = {});
|
||||
const childTypedData = this.dataCache[dataType] || (this.dataCache[dataType] = {});
|
||||
for (let entryId in parentTypedData) {
|
||||
if (childTypedData[entryId] === null) {
|
||||
// null means don't inherit
|
||||
|
|
@ -1278,25 +1327,20 @@ class BattleDex {
|
|||
}
|
||||
|
||||
// Flag the generation. Required for team validator.
|
||||
this.gen = this.data.Scripts.gen || 7;
|
||||
this.gen = this.dataCache.Scripts.gen || 7;
|
||||
|
||||
// Execute initialization script.
|
||||
if (BattleScripts.init) BattleScripts.init.call(this);
|
||||
|
||||
this.dataLoaded = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
includeFormats() {
|
||||
if (!this.isBase) throw new Error(`This should only be run on the base mod`);
|
||||
this.includeMods();
|
||||
if (this.formatsLoaded) return this;
|
||||
if (this.formatsCache) return this;
|
||||
|
||||
if (!this.data) this.data = {mod: this.currentMod};
|
||||
if (!this.data.Formats) this.data.Formats = {};
|
||||
|
||||
// Load [formats] aliases
|
||||
let BattleAliases = this.loadDataFile('./data/', 'Aliases');
|
||||
this.data.Aliases = BattleAliases;
|
||||
if (!this.formatsCache) this.formatsCache = {};
|
||||
|
||||
// Load formats
|
||||
let Formats;
|
||||
|
|
@ -1305,7 +1349,7 @@ class BattleDex {
|
|||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
||||
}
|
||||
if (!Array.isArray(Formats)) throw new TypeError("Exported property `Formats` from `" + "./config/formats.js" + "` must be an array.");
|
||||
if (!Array.isArray(Formats)) throw new TypeError(`Exported property 'Formats' from "./config/formats.js" must be an array`);
|
||||
|
||||
let section = '';
|
||||
let column = 1;
|
||||
|
|
@ -1315,27 +1359,28 @@ class BattleDex {
|
|||
if (format.section) section = format.section;
|
||||
if (format.column) column = format.column;
|
||||
if (!format.name && format.section) continue;
|
||||
if (!id) throw new RangeError("Format #" + (i + 1) + " must have a name with alphanumeric characters");
|
||||
if (!id) throw new RangeError(`Format #${i + 1} must have a name with alphanumeric characters, not '${format.name}'`);
|
||||
if (!format.section) format.section = section;
|
||||
if (!format.column) format.column = column;
|
||||
if (this.data.Formats[id]) throw new Error("Format #" + (i + 1) + " has a duplicate ID: `" + id + "`");
|
||||
if (this.formatsCache[id]) throw new Error(`Format #${i + 1} has a duplicate ID: '${id}'`);
|
||||
format.effectType = 'Format';
|
||||
if (format.challengeShow === undefined) format.challengeShow = true;
|
||||
if (format.searchShow === undefined) format.searchShow = true;
|
||||
if (format.tournamentShow === undefined) format.tournamentShow = true;
|
||||
if (format.mod === undefined) format.mod = 'gen6';
|
||||
if (!dexes[format.mod]) throw new Error("Format `" + format.name + "` requires nonexistent mod: `" + format.mod + "`");
|
||||
this.installFormat(id, format);
|
||||
if (!dexes[format.mod]) throw new Error(`Format "${format.name}" requires nonexistent mod: '${format.mod}'`);
|
||||
this.formatsCache[id] = format;
|
||||
}
|
||||
|
||||
this.formatsLoaded = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
installFormat(id, format) {
|
||||
this.data.Formats[id] = format;
|
||||
dexes['base'].includeFormats();
|
||||
dexes['base'].formatsCache[id] = format;
|
||||
if (this.dataCache) this.dataCache.Formats[id] = format;
|
||||
if (!this.isBase) {
|
||||
dexes['base'].data.Formats[id] = format;
|
||||
if (dexes['base'].dataCache) dexes['base'].dataCache.Formats[id] = format;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1350,7 +1395,6 @@ class BattleDex {
|
|||
}
|
||||
|
||||
dexes['base'] = new BattleDex();
|
||||
dexes['base'].BattleDex = BattleDex;
|
||||
|
||||
// "gen7" is an alias for the current base data
|
||||
dexes['gen7'] = dexes['base'];
|
||||
|
|
|
|||
|
|
@ -965,7 +965,7 @@ function createTournament(room, format, generator, playerCap, isRated, args, out
|
|||
format = Tools.getFormat(format);
|
||||
if (format.effectType !== 'Format' || !format.tournamentShow) {
|
||||
output.errorReply(format.id + " is not a valid tournament format.");
|
||||
output.errorReply("Valid formats: " + Object.values(Tools.data.Formats).filter(f => f.effectType === 'Format' && f.tournamentShow).map(format => format.name).join(", "));
|
||||
output.errorReply("Valid formats: " + Object.values(Tools.formats).filter(f => f.tournamentShow).map(format => format.name).join(", "));
|
||||
return;
|
||||
}
|
||||
if (!TournamentGenerators[toId(generator)]) {
|
||||
|
|
|
|||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2017"],
|
||||
"noEmit": true,
|
||||
"target": "ESNext",
|
||||
"strictNullChecks": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true
|
||||
},
|
||||
"types": ["node"],
|
||||
"include": [
|
||||
"./tools.js"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user