Spaces:
Running
Running
; | |
var __create = Object.create; | |
var __defProp = Object.defineProperty; | |
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | |
var __getOwnPropNames = Object.getOwnPropertyNames; | |
var __getProtoOf = Object.getPrototypeOf; | |
var __hasOwnProp = Object.prototype.hasOwnProperty; | |
var __export = (target, all) => { | |
for (var name in all) | |
__defProp(target, name, { get: all[name], enumerable: true }); | |
}; | |
var __copyProps = (to, from, except, desc) => { | |
if (from && typeof from === "object" || typeof from === "function") { | |
for (let key of __getOwnPropNames(from)) | |
if (!__hasOwnProp.call(to, key) && key !== except) | |
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | |
} | |
return to; | |
}; | |
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | |
// If the importer is in node compatibility mode or this is not an ESM | |
// file that has been converted to a CommonJS file using a Babel- | |
// compatible transform (i.e. "__esModule" has not been set), then set | |
// "default" to the CommonJS "module.exports" for node compatibility. | |
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | |
mod | |
)); | |
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | |
var importer_exports = {}; | |
__export(importer_exports, { | |
TIERS: () => TIERS, | |
fetch: () => fetch, | |
getStatisticsURL: () => getStatisticsURL, | |
importAll: () => importAll | |
}); | |
module.exports = __toCommonJS(importer_exports); | |
var http = __toESM(require("http")); | |
var https = __toESM(require("https")); | |
var url = __toESM(require("url")); | |
var util = __toESM(require("util")); | |
var smogon = __toESM(require("smogon")); | |
var import_lib = require("../../lib"); | |
var import_dex = require("../../sim/dex"); | |
var import_team_validator = require("../../sim/team-validator"); | |
import_dex.Dex.includeModData(); | |
const TIERS = /* @__PURE__ */ new Set([ | |
"ubers", | |
"ou", | |
"uu", | |
"ru", | |
"nu", | |
"pu", | |
"zu", | |
"lc", | |
"cap", | |
"nationaldex", | |
"doublesou", | |
"battlespotsingles", | |
"battlespotdoubles", | |
"battlestadiumsingles", | |
// UGH | |
"battlestadiumsinglesseries2", | |
"battlestadiumsinglesregulationc", | |
// | |
"vgc2016", | |
"vgc2017", | |
"vgc2018", | |
"vgc2019ultraseries", | |
"vgc2020", | |
"vgc2023regulatione", | |
"vgc", | |
"1v1", | |
"anythinggoes", | |
"nationaldexag", | |
"almostanyability", | |
"balancedhackmons", | |
"letsgoou", | |
"monotype", | |
"purehackmons", | |
"nationaldexmonotype" | |
]); | |
const FORMATS = /* @__PURE__ */ new Map(); | |
const VALIDATORS = /* @__PURE__ */ new Map(); | |
for (let gen = 1; gen <= 9; gen++) { | |
for (const tier of TIERS) { | |
const format = import_dex.Dex.formats.get(`gen${gen}${tier}`); | |
if (format.effectType === "Format") { | |
FORMATS.set(format.id, { gen, format }); | |
VALIDATORS.set(format.id, new import_team_validator.TeamValidator(format)); | |
} | |
} | |
} | |
async function importAll() { | |
const index = await request(smogon.Statistics.URL); | |
const imports = []; | |
for (let gen = 1; gen <= 9; gen++) { | |
imports.push(importGen(gen, index)); | |
} | |
return Promise.all(imports); | |
} | |
async function importGen(gen, index) { | |
const data = {}; | |
const smogonSetsByFormat = {}; | |
const thirdPartySetsByFormat = {}; | |
const numByFormat = {}; | |
const imports = []; | |
const dex = import_dex.Dex.forFormat(`gen${gen}ou`); | |
for (const id in dex.data.Pokedex) { | |
if (!eligible(dex, id)) | |
continue; | |
const species = dex.species.get(id); | |
if (species.battleOnly) | |
continue; | |
imports.push(importSmogonSets(dex.species.get(id).name, gen, smogonSetsByFormat, numByFormat)); | |
} | |
await Promise.all(imports); | |
for (const { format, gen: g } of FORMATS.values()) { | |
if (g !== gen) | |
continue; | |
if (smogonSetsByFormat[format.id] && Object.keys(smogonSetsByFormat[format.id]).length) { | |
data[format.id] = {}; | |
data[format.id]["dex"] = smogonSetsByFormat[format.id]; | |
report(format, numByFormat[format.id], "dex"); | |
} | |
for (const source in thirdPartySetsByFormat) { | |
if (thirdPartySetsByFormat[source][format.id] && Object.keys(thirdPartySetsByFormat[source][format.id]).length) { | |
data[format.id] = data[format.id] || {}; | |
data[format.id][source] = thirdPartySetsByFormat[source][format.id]; | |
} | |
} | |
const stats = await getStatisticsURL(index, format); | |
if (!stats) | |
continue; | |
try { | |
const statistics = smogon.Statistics.process(await request(stats.url)); | |
const sets = importUsageBasedSets(gen, format, statistics, stats.count); | |
if (Object.keys(sets).length) { | |
data[format.id] = data[format.id] || {}; | |
data[format.id]["stats"] = sets; | |
} | |
data[format.id] = data[format.id] || {}; | |
} catch (err) { | |
error(`${stats.url} = ${err}`); | |
} | |
} | |
return data; | |
} | |
function eligible(dex, id) { | |
const gen = toGen(dex, id); | |
if (!gen || gen > dex.gen) | |
return false; | |
const species = dex.species.get(id); | |
if (["Mega", "Primal", "Ultra"].some((f) => species.forme.startsWith(f))) | |
return true; | |
const unique = ["darmanitan", "meloetta", "greninja", "zygarde"]; | |
const similar = ["pichu", "pikachu", "genesect", "basculin", "magearna", "keldeo", "vivillon"]; | |
if (species.battleOnly && !unique.some((f) => id.startsWith(f))) | |
return false; | |
const capNFE = species.isNonstandard === "CAP" && species.nfe; | |
return !id.endsWith("totem") && !capNFE && !similar.some((f) => id.startsWith(f) && id !== f); | |
} | |
function toGen(dex, name) { | |
const pokemon = dex.species.get(name); | |
if (pokemon.isNonstandard === "LGPE") | |
return 7; | |
if (!pokemon.exists || pokemon.isNonstandard && pokemon.isNonstandard !== "CAP") | |
return void 0; | |
if (pokemon.gen) | |
return pokemon.gen; | |
const n = pokemon.num; | |
if (n > 905) | |
return 9; | |
if (n > 810) | |
return 8; | |
if (n > 721) | |
return 7; | |
if (n > 649) | |
return 6; | |
if (n > 493) | |
return 5; | |
if (n > 386) | |
return 4; | |
if (n > 251) | |
return 3; | |
if (n > 151) | |
return 2; | |
if (n > 0) | |
return 1; | |
} | |
async function importSmogonSets(pokemon, gen, setsByFormat, numByFormat) { | |
const analysesByFormat = await getAnalysesByFormat(pokemon, gen); | |
if (!analysesByFormat) | |
return; | |
for (const [format, analyses] of analysesByFormat.entries()) { | |
const dex = import_dex.Dex.forFormat(format); | |
let setsForPokemon = setsByFormat[format.id]; | |
if (!setsForPokemon) { | |
setsForPokemon = {}; | |
setsByFormat[format.id] = setsForPokemon; | |
} | |
let baseSpecies = dex.species.get(pokemon); | |
if (baseSpecies.baseSpecies !== baseSpecies.name) | |
baseSpecies = dex.species.get(baseSpecies.baseSpecies); | |
const battleOnlyFormes = []; | |
if (baseSpecies.otherFormes) { | |
for (const forme of baseSpecies.otherFormes) { | |
const formeSpecies = dex.species.get(forme); | |
if (formeSpecies.battleOnly && eligible(dex, (0, import_dex.toID)(formeSpecies))) { | |
battleOnlyFormes.push(formeSpecies); | |
} | |
} | |
} | |
for (const analysis of analyses) { | |
for (const moveset of analysis.movesets) { | |
const set = movesetToPokemonSet(dex, format, pokemon, moveset); | |
const name = cleanName(moveset.name); | |
addSmogonSet(dex, format, pokemon, name, set, setsForPokemon, numByFormat); | |
for (const battleOnlyForme of battleOnlyFormes) { | |
const s = { ...set }; | |
if (!format.id.includes("balancedhackmons")) | |
s.ability = battleOnlyForme.abilities[0]; | |
if (typeof battleOnlyForme.battleOnly !== "string") { | |
if (!battleOnlyForme.battleOnly.includes(pokemon)) | |
continue; | |
const species = dex.species.get(pokemon); | |
const disambiguated = `${name} - ${species.baseForme || species.forme}`; | |
addSmogonSet(dex, format, battleOnlyForme.name, disambiguated, s, setsForPokemon, numByFormat, pokemon); | |
} else if (battleOnlyForme.battleOnly === pokemon) { | |
addSmogonSet(dex, format, battleOnlyForme.name, name, s, setsForPokemon, numByFormat); | |
} | |
} | |
} | |
} | |
} | |
} | |
function addSmogonSet(dex, format, pokemon, name, set, setsForPokemon, numByFormat, outOfBattleSpeciesName) { | |
if (validSet("dex", dex, format, pokemon, name, set, outOfBattleSpeciesName)) { | |
setsForPokemon[pokemon] = setsForPokemon[pokemon] || {}; | |
setsForPokemon[pokemon][name] = set; | |
numByFormat[format.id] = (numByFormat[format.id] || 0) + 1; | |
} | |
} | |
function cleanName(name) { | |
return name.replace(/"/g, `'`); | |
} | |
function movesetToPokemonSet(dex, format, pokemon, set) { | |
const level = getLevel(format, set.levels[0]); | |
return { | |
level: level === 100 ? void 0 : level, | |
moves: set.moveslots.map((ms) => ms[0]).map((s) => s.type ? `${s.move} ${s.type}` : s.move), | |
ability: fixedAbility(dex, pokemon, set.abilities[0]), | |
item: set.items[0] === "No Item" ? void 0 : set.items[0], | |
nature: set.natures[0], | |
teraType: set.teratypes ? set.teratypes[0] : void 0, | |
ivs: toStatsTable(set.ivconfigs[0], 31), | |
evs: toStatsTable(set.evconfigs[0]) | |
}; | |
} | |
function toStatsTable(stats, elide = 0) { | |
if (!stats) | |
return void 0; | |
const s = {}; | |
let stat; | |
for (stat in stats) { | |
const val = stats[stat]; | |
if (val !== elide) | |
s[stat] = val; | |
} | |
return s; | |
} | |
function fixedAbility(dex, pokemon, ability) { | |
if (dex.gen <= 2) | |
return void 0; | |
const species = dex.species.get(pokemon); | |
if (ability && !["Mega", "Primal", "Ultra"].some((f) => species.forme.startsWith(f))) | |
return ability; | |
return species.abilities[0]; | |
} | |
function validSet(source, dex, format, pokemon, name, set, outOfBattleSpeciesName) { | |
if (skip(dex, format, pokemon, set)) | |
return false; | |
const pset = toPokemonSet(dex, format, pokemon, set, outOfBattleSpeciesName); | |
let invalid = VALIDATORS.get(format.id).validateSet(pset, {}); | |
if (!invalid) | |
return true; | |
if (invalid.length === 1 && invalid[0].includes("must be shiny")) { | |
set.shiny = true; | |
pset.shiny = true; | |
invalid = VALIDATORS.get(format.id).validateSet(pset, {}); | |
if (!invalid) | |
return true; | |
} | |
if (format.id === "gen4ubers" && invalid.includes(`${pokemon} is banned.`)) | |
return true; | |
const title = `${format.name}: ${pokemon} (${name})'`; | |
const details = `${JSON.stringify(set)} = ${invalid.join(", ")}`; | |
console.error(color(`${source} Invalid set ${title}: ${details}`, 90)); | |
return false; | |
} | |
function skip(dex, format, pokemon, set) { | |
const { gen } = FORMATS.get(format.id); | |
const hasMove = (m) => set.moves?.includes(m); | |
const bh = format.id.includes("balancedhackmons"); | |
if (pokemon === "Groudon-Primal" && set.item !== "Red Orb") | |
return true; | |
if (pokemon === "Kyogre-Primal" && set.item !== "Blue Orb" && !(bh && gen === 7)) | |
return true; | |
if (bh) | |
return false; | |
if (dex.species.get(pokemon).forme.startsWith("Mega")) { | |
if (pokemon === "Rayquaza-Mega") { | |
return format.id.includes("ubers") || !hasMove("Dragon Ascent"); | |
} else { | |
return dex.items.get(set.item).megaStone !== pokemon; | |
} | |
} | |
if (pokemon === "Necrozma-Ultra" && set.item !== "Ultranecrozium Z") | |
return true; | |
if (pokemon === "Greninja-Ash" && set.ability !== "Battle Bond") | |
return true; | |
if (pokemon === "Zygarde-Complete" && set.ability !== "Power Construct") | |
return true; | |
if (pokemon === "Darmanitan-Zen" && set.ability !== "Zen Mode") | |
return true; | |
if (pokemon === "Meloetta-Pirouette" && !hasMove("Relic Song")) | |
return true; | |
return false; | |
} | |
function toPokemonSet(dex, format, pokemon, set, outOfBattleSpeciesName) { | |
const hp = set.moves?.find((m) => m.startsWith("Hidden Power")); | |
let fill = dex.gen === 2 ? 30 : 31; | |
if (hp) { | |
const type = hp.slice(13); | |
if (type && dex.getHiddenPower(fillStats(set.ivs, fill)).type !== type) { | |
if (!set.ivs || dex.gen >= 7 && (!set.level || set.level === 100)) { | |
set.hpType = type; | |
fill = 31; | |
} else if (dex.gen === 2) { | |
const dvs = { ...dex.types.get(type).HPdvs }; | |
let stat; | |
for (stat in dvs) { | |
dvs[stat] *= 2; | |
} | |
set.ivs = { ...dvs, ...set.ivs }; | |
set.ivs.hp = expectedHP(set.ivs); | |
} else { | |
set.ivs = { ...dex.types.get(type).HPivs, ...set.ivs }; | |
} | |
} | |
} | |
const copy = { species: pokemon, ...set }; | |
copy.ivs = fillStats(set.ivs, fill); | |
if (!set.evs && dex.gen >= 3 && format.id !== "gen7letsgoou") | |
set.evs = { spe: 1 }; | |
copy.evs = fillStats(set.evs, dex.gen <= 2 ? 252 : 0); | |
copy.ability = copy.ability || "None"; | |
const species = dex.species.get(pokemon); | |
if (species.battleOnly && !format.id.includes("balancedhackmons")) { | |
if (outOfBattleSpeciesName) { | |
copy.species = outOfBattleSpeciesName; | |
} else if (typeof species.battleOnly === "string") { | |
copy.species = species.battleOnly; | |
} else { | |
throw new Error(`Unable to disambiguate out of battle species for ${species.name} in ${format.id}`); | |
} | |
copy.ability = dex.species.get(copy.species).abilities[0]; | |
} | |
return copy; | |
} | |
function expectedHP(ivs) { | |
ivs = fillStats(ivs, 31); | |
const atkDV = Math.floor(ivs.atk / 2); | |
const defDV = Math.floor(ivs.def / 2); | |
const speDV = Math.floor(ivs.spe / 2); | |
const spcDV = Math.floor(ivs.spa / 2); | |
return 2 * (atkDV % 2 * 8 + defDV % 2 * 4 + speDV % 2 * 2 + spcDV % 2); | |
} | |
function fillStats(stats, fill = 0) { | |
return import_team_validator.TeamValidator.fillStats(stats || null, fill); | |
} | |
const SMOGON = { | |
uber: "ubers", | |
doubles: "doublesou", | |
lgpeou: "letsgoou", | |
ag: "anythinggoes", | |
bh: "balancedhackmons", | |
vgc16: "vgc2016", | |
vgc17: "vgc2017", | |
vgc18: "vgc2018", | |
vgc19: "vgc2019ultraseries", | |
vgc24regulatione: "vgc2023regulatione", | |
// bssseries1: 'battlestadiumsinglesseries1', // ? | |
bssseries2: "battlestadiumsinglesseries2" | |
}; | |
const getAnalysis = retrying(async (u) => { | |
try { | |
return smogon.Analyses.process(await request(u)); | |
} catch (err) { | |
if (err.message.startsWith("HTTP")) { | |
return Promise.reject(err); | |
} else { | |
return Promise.reject(new RetryableError(err.message)); | |
} | |
} | |
}, 3, 50); | |
async function getAnalysesByFormat(pokemon, gen) { | |
const u = smogon.Analyses.url(pokemon === "Meowstic" ? "Meowstic-M" : pokemon, gen); | |
try { | |
const analysesByTier = await getAnalysis(u); | |
if (!analysesByTier) { | |
error(`Unable to process analysis for ${pokemon} in generation ${gen}`); | |
return void 0; | |
} | |
const analysesByFormat = /* @__PURE__ */ new Map(); | |
for (const [tier, analyses] of analysesByTier.entries()) { | |
let t = (0, import_dex.toID)(tier); | |
if (gen === 9 && t === "battlestadiumsingles") { | |
t = "battlestadiumsinglesregulationc"; | |
} | |
const f = FORMATS.get(`gen${gen}${SMOGON[t] || t}`); | |
if (f) | |
analysesByFormat.set(f.format, analyses); | |
} | |
return analysesByFormat; | |
} catch { | |
error(`Unable to process analysis for ${pokemon} in generation ${gen}`); | |
return void 0; | |
} | |
} | |
function getLevel(format, level = 0) { | |
const ruleTable = import_dex.Dex.formats.getRuleTable(format); | |
if (ruleTable.adjustLevel) | |
return ruleTable.adjustLevel; | |
const maxLevel = ruleTable.maxLevel; | |
const adjustLevelDown = ruleTable.adjustLevelDown || maxLevel; | |
if (!level) | |
level = ruleTable.defaultLevel; | |
return level > adjustLevelDown ? adjustLevelDown : level; | |
} | |
async function getStatisticsURL(index, format) { | |
const current = index.includes(format.id); | |
const latest = await smogon.Statistics.latestDate(format.id, !current); | |
if (!latest) | |
return void 0; | |
return { url: smogon.Statistics.url(latest.date, format.id, current || 1500), count: latest.count }; | |
} | |
function importUsageBasedSets(gen, format, statistics, count) { | |
const sets = {}; | |
const dex = import_dex.Dex.forFormat(format); | |
const threshold = getUsageThreshold(format, count); | |
let num = 0; | |
for (const pokemon in statistics.data) { | |
const stats = statistics.data[pokemon]; | |
if (eligible(dex, (0, import_dex.toID)(pokemon)) && stats.usage >= threshold) { | |
const set = { | |
level: getLevel(format), | |
moves: top(stats.Moves, 4).map((m) => dex.moves.get(m).name).filter((m) => m) | |
}; | |
if (gen >= 2 && format.id !== "gen7letsgoou") { | |
const id = top(stats.Items); | |
set.item = dex.items.get(id).name; | |
if (set.item === "nothing") | |
set.item = void 0; | |
} | |
if (gen >= 3) { | |
const id = top(stats.Abilities); | |
set.ability = fixedAbility(dex, pokemon, dex.abilities.get(id).name); | |
const { nature, evs } = fromSpread(top(stats.Spreads)); | |
set.nature = nature; | |
if (format.id !== "gen7letsgoou") { | |
if (!evs || !Object.keys(evs).length) | |
continue; | |
set.evs = evs; | |
} | |
} | |
const name = "Showdown Usage"; | |
if (validSet("stats", dex, format, pokemon, name, set)) { | |
sets[pokemon] = {}; | |
sets[pokemon][name] = set; | |
num++; | |
} | |
} | |
} | |
report(format, num, "stats"); | |
return sets; | |
} | |
function getUsageThreshold(format, count) { | |
if (count < 100) | |
return Infinity; | |
if (count < 400) | |
return 0.05; | |
return /uber|anythinggoes|doublesou/.test(format.id) ? 0.03 : 0.01; | |
} | |
const STATS = import_dex.Dex.stats.ids(); | |
function fromSpread(spread) { | |
const [nature, revs] = spread.split(":"); | |
const evs = {}; | |
for (const [i, rev] of revs.split("/").entries()) { | |
const ev = Number(rev); | |
if (ev) | |
evs[STATS[i]] = ev; | |
} | |
return { nature, evs }; | |
} | |
function top(weighted, n = 1) { | |
if (n === 0) | |
return void 0; | |
if (n === 1) { | |
let max; | |
for (const key in weighted) { | |
if (!max || weighted[max] < weighted[key]) | |
max = key; | |
} | |
return max; | |
} | |
return Object.entries(weighted).sort((a, b) => b[1] - a[1]).slice(0, n).map((x) => x[0]); | |
} | |
class RetryableError extends Error { | |
constructor(message) { | |
super(message); | |
Object.setPrototypeOf(this, new.target.prototype); | |
} | |
} | |
const request = retrying(throttling(fetch, 1, 50), 5, 20); | |
function fetch(u) { | |
const client = u.startsWith("http:") ? http : https; | |
return new Promise((resolve, reject) => { | |
const req = client.get(u, (res) => { | |
if (res.statusCode !== 200) { | |
if (res.statusCode >= 500 && res.statusCode < 600) { | |
return reject(new RetryableError(`HTTP ${res.statusCode}`)); | |
} else if (res.statusCode >= 300 && res.statusCode <= 400 && res.headers.location) { | |
resolve(fetch(url.resolve(u, res.headers.location))); | |
} else { | |
return reject(new Error(`HTTP ${res.statusCode}`)); | |
} | |
} | |
import_lib.Streams.readAll(res).then(resolve, reject); | |
}); | |
req.on("error", reject); | |
req.end(); | |
}); | |
} | |
function retrying(fn, retries, wait) { | |
const retry = async (args, attempt = 0) => { | |
try { | |
return await fn(args); | |
} catch (err) { | |
if (err instanceof RetryableError) { | |
attempt++; | |
if (attempt > retries) | |
return Promise.reject(err); | |
const timeout = Math.round(attempt * wait * (1 + Math.random() / 2)); | |
warn(`Retrying ${args} in ${timeout}ms (${attempt}):`, err); | |
return new Promise((resolve) => { | |
setTimeout(() => { | |
resolve(retry(args, attempt++)); | |
}, timeout); | |
}); | |
} else { | |
return Promise.reject(err); | |
} | |
} | |
}; | |
return retry; | |
} | |
function throttling(fn, limit, interval) { | |
const queue = /* @__PURE__ */ new Map(); | |
let currentTick = 0; | |
let activeCount = 0; | |
const throttled = (args) => { | |
let timeout; | |
return new Promise((resolve, reject) => { | |
const execute = () => { | |
resolve(fn(args)); | |
queue.delete(timeout); | |
}; | |
const now = Date.now(); | |
if (now - currentTick > interval) { | |
activeCount = 1; | |
currentTick = now; | |
} else if (activeCount < limit) { | |
activeCount++; | |
} else { | |
currentTick += interval; | |
activeCount = 1; | |
} | |
timeout = setTimeout(execute, currentTick - now); | |
queue.set(timeout, reject); | |
}); | |
}; | |
return throttled; | |
} | |
function color(s, code) { | |
return util.format(`\x1B[${code}m%s\x1B[0m`, s); | |
} | |
function report(format, num, source) { | |
console.info(`${format.name}: ${color(num, 33)} ${color(`(${source})`, 90)}`); | |
} | |
function warn(s, err) { | |
console.warn(`${color(s, 33)} ${color(err.message, 90)}`); | |
} | |
function error(s) { | |
console.error(color(s, 91)); | |
} | |
//# sourceMappingURL=importer.js.map | |