{
"version": 3,
"sources": ["../../../../../server/chat-plugins/randombattles/index.ts"],
"sourcesContent": ["/**\n * Random Battles chat-plugin\n * Written by Kris with inspiration from sirDonovan and The Immortal\n *\n * Set probability code written by Annika\n */\n\nimport {FS, Utils} from '../../../lib';\nimport {SSBSet, ssbSets} from '../../../data/mods/ssb/random-teams';\n\n\ninterface SetCriteria {\n\tmoves: {mustHave: Move[], mustNotHave: Move[]};\n\tability: {mustHave?: Ability, mustNotHave: Ability[]};\n\titem: {mustHave?: Item, mustNotHave: Item[]};\n\tnature: {mustHave?: Nature, mustNotHave: Nature[]};\n\tteraType: {mustHave?: TypeInfo, mustNotHave: TypeInfo[]};\n}\n\n\nfunction getHTMLCriteriaDescription(criteria: SetCriteria) {\n\tconst format = (list: {name: string}[]) => list.map(m => Utils.html`${m.name}`);\n\tconst parts = [];\n\n\tconst {moves, ability, item, nature, teraType} = criteria;\n\n\tif (moves.mustHave.length) {\n\t\tparts.push(`had the move${Chat.plural(moves.mustHave)} ${Chat.toListString(format(moves.mustHave))}`);\n\t}\n\tif (moves.mustNotHave.length) {\n\t\tparts.push(`did not have the move${Chat.plural(moves.mustNotHave)} ${Chat.toListString(format(moves.mustNotHave), 'or')}`);\n\t}\n\n\tif (ability.mustHave) {\n\t\tparts.push(Utils.html`had the ability ${ability.mustHave.name}`);\n\t}\n\tif (ability.mustNotHave.length) {\n\t\tparts.push(`did not have the ${Chat.plural(ability.mustNotHave, 'abilities', 'ability')} ${Chat.toListString(format(ability.mustNotHave), 'or')}`);\n\t}\n\n\tif (item.mustHave) {\n\t\tparts.push(Utils.html`had the item ${item.mustHave.name}`);\n\t}\n\tif (item.mustNotHave.length) {\n\t\tparts.push(`did not have the item${Chat.plural(item.mustNotHave)} ${Chat.toListString(format(item.mustNotHave), 'or')}`);\n\t}\n\n\tif (nature.mustHave) {\n\t\tparts.push(Utils.html`had the nature ${nature.mustHave.name}`);\n\t}\n\tif (nature.mustNotHave.length) {\n\t\tparts.push(`did not have the nature${Chat.plural(nature.mustNotHave)} ${Chat.toListString(format(nature.mustNotHave), 'or')}`);\n\t}\n\n\tif (teraType.mustHave) {\n\t\tparts.push(Utils.html`had the Tera Type ${teraType.mustHave.name}`);\n\t}\n\tif (teraType.mustNotHave.length) {\n\t\tparts.push(`did not have the Tera Type${Chat.plural(teraType.mustNotHave)} ${Chat.toListString(format(teraType.mustNotHave), 'or')}`);\n\t}\n\n\treturn Chat.toListString(parts, 'and');\n}\n\nfunction setProbability(\n\tspecies: Species,\n\tformat: Format,\n\tcriteria: SetCriteria,\n\trounds = 700\n): {rounds: number, matches: number} {\n\tconst results = {rounds, matches: 0};\n\tconst generator = Teams.getGenerator(format);\n\n\tfor (let i = 0; i < rounds; i++) {\n\t\tconst set = generator.randomSet(\n\t\t\tspecies,\n\t\t\t{},\n\t\t\tfalse,\n\t\t\tformat.gameType !== 'singles',\n\t\t\tformat.ruleTable?.has('dynamaxclause')\n\t\t);\n\n\t\tif (criteria.item.mustHave && set.item !== criteria.item.mustHave.name) continue;\n\t\tif (criteria.item.mustNotHave.some(item => item.name === set.item)) continue;\n\n\t\tif (criteria.ability.mustHave && set.ability !== criteria.ability.mustHave.name) continue;\n\t\tif (criteria.ability.mustNotHave.some(ability => ability.name === set.ability)) continue;\n\n\t\tif (criteria.nature.mustHave && set.nature !== criteria.nature.mustHave.name) continue;\n\t\tif (criteria.nature.mustNotHave.some(nature => nature.name === set.nature)) continue;\n\n\t\tif (criteria.teraType.mustHave && set.teraType !== criteria.teraType.mustHave.name) continue;\n\t\tif (criteria.teraType.mustNotHave.some(type => type.name === set.teraType)) continue;\n\n\t\tconst setHasMove = (move: Move) => {\n\t\t\tconst id = move.id === 'hiddenpower' ? `${move.id}${toID(move.type)}` : move.id;\n\t\t\treturn set.moves.includes(id);\n\t\t};\n\t\tif (!criteria.moves.mustHave.every(setHasMove)) continue;\n\t\tif (criteria.moves.mustNotHave.some(setHasMove)) continue;\n\n\t\tresults.matches++;\n\t}\n\n\treturn results;\n}\n\nconst GEN_NAMES: {[k: string]: string} = {\n\tgen1: '[Gen 1]', gen2: '[Gen 2]', gen3: '[Gen 3]', gen4: '[Gen 4]', gen5: '[Gen 5]', gen6: '[Gen 6]', gen7: '[Gen 7]',\n\tgen8: '[Gen 8]', gen9: '[Gen 9]',\n};\n\nconst STAT_NAMES: {[k: string]: string} = {\n\thp: \"HP\", atk: \"Atk\", def: \"Def\", spa: \"SpA\", spd: \"SpD\", spe: \"Spe\",\n};\n\nconst TIERS: {[k: string]: string} = {\n\tuber: \"Uber\", ubers: \"Uber\",\n\tou: \"OU\", uu: \"UU\", ru: \"RU\", nu: \"NU\", pu: \"PU\",\n\tmono: \"Mono\", monotype: \"Mono\", lc: \"LC\", littlecup: \"LC\",\n};\n\nfunction formatAbility(ability: Ability | string) {\n\tability = Dex.abilities.get(ability);\n\treturn `${ability.name}`;\n}\nfunction formatNature(n: string) {\n\tconst nature = Dex.natures.get(n);\n\treturn nature.name;\n}\n\nfunction formatMove(move: Move | string) {\n\tmove = Dex.moves.get(move);\n\treturn `${move.name}`;\n}\n\nfunction formatItem(item: Item | string) {\n\tif (typeof item === 'string' && item === \"No Item\") {\n\t\treturn `No Item`;\n\t} else {\n\t\titem = Dex.items.get(item);\n\t\treturn `${item.name}`;\n\t}\n}\n\n/**\n * Gets the sets for a Pokemon for a format that uses the new schema.\n * Old formats will use getData()\n */\nfunction getSets(species: string | Species, format: string | Format = 'gen9randombattle'): {\n\tlevel: number,\n\tsets: any[],\n} | null {\n\tconst dex = Dex.forFormat(format);\n\tformat = Dex.formats.get(format);\n\tspecies = dex.species.get(species);\n\tconst isDoubles = format.gameType === 'doubles';\n\tconst setsFile = JSON.parse(\n\t\tFS(`data/${dex.isBase ? '' : `mods/${dex.currentMod}/`}random-${isDoubles ? `doubles-` : ``}sets.json`)\n\t\t\t.readIfExistsSync() || '{}'\n\t);\n\tconst data = setsFile[species.id];\n\tif (!data?.sets?.length) return null;\n\treturn data;\n}\n\n/**\n * Gets the random battles data for a Pokemon for formats with the old schema.\n */\nfunction getData(species: string | Species, format: string | Format): any | null {\n\tconst dex = Dex.forFormat(format);\n\tformat = Dex.formats.get(format);\n\tspecies = dex.species.get(species);\n\t// Gen 7 Random Doubles has a separate file to Gen 7 singles but still uses the old system.\n\tconst isGen7Doubles = format.gameType === 'doubles' && dex.gen === 7;\n\tconst dataFile = JSON.parse(\n\t\tFS(`data/mods/${dex.currentMod}/random-${isGen7Doubles ? 'doubles-' : ''}data.json`).readIfExistsSync() || '{}'\n\t);\n\tconst data = dataFile[species.id];\n\tif (!data) return null;\n\treturn data;\n}\n\n/**\n * Gets the default level for a Pokemon in the given format.\n * Returns 0 if the format doesn't use default levels or it can't be determined.\n */\nfunction getLevel(species: string | Species, format: string | Format): number {\n\tconst dex = Dex.forFormat(format);\n\tformat = Dex.formats.get(format);\n\tspecies = dex.species.get(species);\n\tswitch (format.id) {\n\t// Only formats where levels are not all manually assigned should be copied here\n\tcase 'gen2randombattle':\n\t\tconst levelScale: {[k: string]: number} = {\n\t\t\tZU: 81,\n\t\t\tZUBL: 79,\n\t\t\tPU: 77,\n\t\t\tPUBL: 75,\n\t\t\tNU: 73,\n\t\t\tNUBL: 71,\n\t\t\tUU: 69,\n\t\t\tUUBL: 67,\n\t\t\tOU: 65,\n\t\t\tUber: 61,\n\t\t};\n\t\treturn levelScale[species.tier] || 80;\n\t\t// TODO: Gen 7 Doubles (currently uses BST-based scaling that accounts for the set's ability/item)\n\t}\n\treturn 0;\n}\n\nfunction getRBYMoves(species: string | Species) {\n\tspecies = Dex.mod(`gen1`).species.get(species);\n\tconst data = getData(species, 'gen1randombattle');\n\tif (!data) return false;\n\tlet buf = `
Level: ${data.level}`;\n\tif (data.comboMoves) {\n\t\tbuf += `
Combo moves: `;\n\t\tbuf += data.comboMoves.map(formatMove).sort().join(\", \");\n\t}\n\tif (data.exclusiveMoves) {\n\t\tbuf += `
Exclusive moves: `;\n\t\tbuf += data.exclusiveMoves.map(formatMove).sort().join(\", \");\n\t}\n\tif (data.essentialMoves) {\n\t\tbuf += `
Essential move${Chat.plural(data.essentialMoves)}: `;\n\t\tbuf += data.essentialMoves.map(formatMove).sort().join(\", \");\n\t}\n\tif (data.moves) {\n\t\tbuf += `
Randomized moves: `;\n\t\tbuf += data.moves.map(formatMove).sort().join(\", \");\n\t}\n\tif (\n\t\t!data.moves && !data.comboMoves &&\n\t\t!data.exclusiveMoves && !data.essentialMove\n\t) {\n\t\treturn false;\n\t}\n\treturn buf;\n}\n\nfunction getLetsGoMoves(species: string | Species) {\n\tspecies = Dex.species.get(species);\n\tconst data = getData(species, 'gen7letsgorandombattle');\n\tif (!data) return false;\n\tconst isLetsGoLegal = (\n\t\t(species.num <= 151 || ['Meltan', 'Melmetal'].includes(species.name)) &&\n\t\t(!species.forme || ['Alola', 'Mega', 'Mega-X', 'Mega-Y', 'Starter'].includes(species.forme))\n\t);\n\tif (!isLetsGoLegal) return false;\n\tif (!data.moves?.length) return false;\n\treturn data.moves.map(formatMove).sort().join(`, `);\n}\n\nfunction battleFactorySets(species: string | Species, tier: string | null, gen = 'gen8', isBSS = false) {\n\tspecies = Dex.species.get(species);\n\tif (typeof species.battleOnly === 'string') {\n\t\tspecies = Dex.species.get(species.battleOnly);\n\t}\n\tgen = toID(gen);\n\tconst genNum = parseInt(gen[3]);\n\tif (isNaN(genNum) || genNum < 6 || (isBSS && genNum < 7)) return null;\n\tconst statsFile = JSON.parse(\n\t\tFS(`data${gen === 'gen9' ? '/' : `/mods/${gen}`}/${isBSS ? `bss-` : ``}factory-sets.json`).readIfExistsSync() ||\n\t\t\"{}\"\n\t);\n\tif (!Object.keys(statsFile).length) return null;\n\tlet buf = ``;\n\tif (!isBSS) {\n\t\tif (!tier) return {e: `Please provide a valid tier.`};\n\t\tif (!(toID(tier) in TIERS)) return {e: `That tier isn't supported.`};\n\t\tif (!(TIERS[toID(tier)] in statsFile)) {\n\t\t\treturn {e: `${TIERS[toID(tier)]} is not included in [Gen ${genNum}] Battle Factory.`};\n\t\t}\n\t\tconst t = statsFile[TIERS[toID(tier)]];\n\t\tif (!(species.id in t)) {\n\t\t\tconst formatName = Dex.formats.get(`${gen}battlefactory`).name;\n\t\t\treturn {e: `${species.name} doesn't have any sets in ${TIERS[toID(tier)]} for ${formatName}.`};\n\t\t}\n\t\tconst setObj = t[species.id];\n\t\tbuf += `Sets for ${species.name} in${genNum === 8 ? `` : ` ${GEN_NAMES[gen]}`} ${TIERS[toID(tier)]}:
`;\n\t\tfor (const [i, set] of setObj.sets.entries()) {\n\t\t\tbuf += `Set ${i + 1}
`;\n\t\t\tbuf += ``;\n\t\t\tbuf += `
`;\n\t\tfor (const [i, set] of setObj.sets.entries()) {\n\t\t\tbuf += `Set ${i + 1}
`;\n\t\t\tbuf += ``;\n\t\t\tbuf += `
`;\n\tfor (const [i, set] of statsFile[species.name].entries()) {\n\t\tbuf += `Set ${i + 1}
`;\n\t\tbuf += ``;\n\t\tbuf += `
Set
`;\n\tbuf += `
`;\n\tbuf += `
/randombattlesetprobabilities [optional format], [species], [conditions]: Gives the probability of a set matching the conditions appearing for the given species.[conditions] is a comma-separated list of conditions of the form [component]=[matching value], where [component] can be any of the following: ` +\n\t\t\t`moves: matches all generated sets that contain every move specified. [matching value] should be a list of moves separated with &.` +\n\t\t\t`item: matches all generated sets that have the specified item. [matching value] should be an item name.` +\n\t\t\t`ability: matches all generated sets with the specified ability. [matching value] should be an ability name.` +\n\t\t\t`nature: matches all generated sets with the specified nature. [matching value] should be a nature name.` +\n\t\t\t`tera: matches all generated sets with the specified Tera Type. [matching value] should be a type. Gen 9 only.` +\n\t\t\t`[matching value] with !.