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