Spaces:
Paused
Paused
| ; | |
| var __defProp = Object.defineProperty; | |
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | |
| var __getOwnPropNames = Object.getOwnPropertyNames; | |
| 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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | |
| var winrates_exports = {}; | |
| __export(winrates_exports, { | |
| commands: () => commands, | |
| getSpeciesName: () => getSpeciesName, | |
| handlers: () => handlers, | |
| pages: () => pages, | |
| saveStats: () => saveStats, | |
| stats: () => stats | |
| }); | |
| module.exports = __toCommonJS(winrates_exports); | |
| var import_lib = require("../../../lib"); | |
| const STATS_PATH = Monitor.logPath("randbats/{{MONTH}}-winrates.json").path; | |
| const stats = getDefaultStats(); | |
| try { | |
| const path = STATS_PATH.replace("{{MONTH}}", getMonth()); | |
| if (!Monitor.logPath("randbats/").existsSync()) { | |
| Monitor.logPath("randbats/").mkdirSync(); | |
| } | |
| const savedStats = JSON.parse((0, import_lib.FS)(path).readSync()); | |
| stats.elo = savedStats.elo; | |
| stats.month = savedStats.month; | |
| for (const k in stats.formats) { | |
| stats.formats[k] = savedStats.formats[k] || stats.formats[k]; | |
| } | |
| } catch { | |
| } | |
| function getDefaultStats() { | |
| return { | |
| elo: 1500, | |
| month: getMonth(), | |
| formats: { | |
| // all of these requested by rands staff. they don't anticipate it being changed much | |
| // so i'm not spending the time to add commands to toggle this | |
| gen9randombattle: { mons: {} }, | |
| gen9randomdoublesbattle: { mons: {} }, | |
| gen9babyrandombattle: { mons: {} }, | |
| gen9superstaffbrosultimate: { mons: {} }, | |
| gen8randombattle: { mons: {} }, | |
| gen7randombattle: { mons: {} }, | |
| gen6randombattle: { mons: {} }, | |
| gen5randombattle: { mons: {} }, | |
| gen4randombattle: { mons: {} }, | |
| gen3randombattle: { mons: {} }, | |
| gen2randombattle: { mons: {} }, | |
| gen1randombattle: { mons: {} } | |
| } | |
| }; | |
| } | |
| function saveStats(month = getMonth()) { | |
| const curStats = { ...stats }; | |
| (0, import_lib.FS)(STATS_PATH.replace("{{MONTH}}", month)).writeUpdate(() => JSON.stringify(curStats)); | |
| } | |
| function getMonth() { | |
| return Chat.toTimestamp(new Date()).split(" ")[0].slice(0, -3); | |
| } | |
| function getSpeciesName(set, format) { | |
| const species = set.species; | |
| const item = Dex.items.get(set.item); | |
| const moves = set.moves; | |
| const megaRayquazaPossible = ["gen6", "gen7"].includes(format.mod) && !format.ruleset.includes("Mega Rayquaza Clause"); | |
| if (species.startsWith("Pikachu-")) { | |
| return "Pikachu"; | |
| } else if (species.startsWith("Unown-")) { | |
| return "Unown"; | |
| } else if (species === "Gastrodon-East") { | |
| return "Gastrodon"; | |
| } else if (species === "Magearna-Original") { | |
| return "Magearna"; | |
| } else if (species === "Genesect-Douse") { | |
| return "Genesect"; | |
| } else if (species === "Dudunsparce-Three-Segment") { | |
| return "Dudunsparce"; | |
| } else if (species === "Maushold-Four") { | |
| return "Maushold"; | |
| } else if (species === "Greninja-Bond") { | |
| return "Greninja"; | |
| } else if (species === "Keldeo-Resolute") { | |
| return "Keldeo"; | |
| } else if (species === "Zarude-Dada") { | |
| return "Zarude"; | |
| } else if (species === "Polteageist-Antique") { | |
| return "Polteageist"; | |
| } else if (species === "Sinistcha-Masterpiece") { | |
| return "Sinistcha"; | |
| } else if (species === "Squawkabilly-Blue") { | |
| return "Squawkabilly"; | |
| } else if (species === "Squawkabilly-White") { | |
| return "Squawkabilly-Yellow"; | |
| } else if (species.startsWith("Basculin-")) { | |
| return "Basculin"; | |
| } else if (species.startsWith("Sawsbuck-")) { | |
| return "Sawsbuck"; | |
| } else if (species.startsWith("Vivillon-")) { | |
| return "Vivillon"; | |
| } else if (species.startsWith("Florges-")) { | |
| return "Florges"; | |
| } else if (species.startsWith("Furfrou-")) { | |
| return "Furfrou"; | |
| } else if (species.startsWith("Minior-")) { | |
| return "Minior"; | |
| } else if (species.startsWith("Toxtricity-")) { | |
| return "Toxtricity"; | |
| } else if (species.startsWith("Tatsugiri-")) { | |
| return "Tatsugiri"; | |
| } else if (species.startsWith("Alcremie-")) { | |
| return "Alcremie"; | |
| } else if (species === "Zacian" && item.name === "Rusted Sword") { | |
| return "Zacian-Crowned"; | |
| } else if (species === "Zamazenta" && item.name === "Rusted Shield") { | |
| return "Zamazenta-Crowned"; | |
| } else if (species === "Kyogre" && item.name === "Blue Orb") { | |
| return "Kyogre-Primal"; | |
| } else if (species === "Groudon" && item.name === "Red Orb") { | |
| return "Groudon-Primal"; | |
| } else if (item.megaStone) { | |
| return item.megaStone; | |
| } else if (species === "Rayquaza" && moves.includes("Dragon Ascent") && !item.zMove && megaRayquazaPossible) { | |
| return "Rayquaza-Mega"; | |
| } else if (species === "Poltchageist-Artisan") { | |
| return "Poltchageist"; | |
| } else if (species === "Shellos-East") { | |
| return "Shellos"; | |
| } else if (species === "Sinistea-Antique") { | |
| return "Sinistea"; | |
| } else if (species.startsWith("Deerling-")) { | |
| return "Deerling"; | |
| } else if (species.startsWith("Flabe\u0301be\u0301-")) { | |
| return "Flabe\u0301be\u0301"; | |
| } else { | |
| return species; | |
| } | |
| } | |
| function checkRollover() { | |
| if (stats.month !== getMonth()) { | |
| saveStats(stats.month); | |
| Object.assign(stats, getDefaultStats()); | |
| saveStats(); | |
| } | |
| } | |
| const getZScore = (data) => 2 * Math.sqrt(data.timesGenerated) * (data.numWins / data.timesGenerated - 0.5); | |
| const handlers = { | |
| onBattleEnd(battle, winner, players) { | |
| void collectStats(battle, winner, players); | |
| } | |
| }; | |
| async function collectStats(battle, winner, players) { | |
| const formatData = stats.formats[battle.format]; | |
| let eloFloor = stats.elo; | |
| const format = Dex.formats.get(battle.format); | |
| if (format.mod === "gen2" || format.team === "randomBaby") { | |
| eloFloor = 1150; | |
| } else if (format.mod !== `gen${Dex.gen}`) { | |
| eloFloor = 1300; | |
| } else if (format.gameType === "doubles") { | |
| eloFloor = 1400; | |
| } | |
| if (!formatData || format.mod !== "gen9ssb" && battle.rated < eloFloor || !winner) | |
| return; | |
| checkRollover(); | |
| for (const p of battle.players) { | |
| const team = await battle.getPlayerTeam(p); | |
| if (!team) | |
| return; | |
| const mons = team.map((f) => getSpeciesName(f, format)); | |
| for (const mon of mons) { | |
| if (!formatData.mons[mon]) | |
| formatData.mons[mon] = { timesGenerated: 0, numWins: 0 }; | |
| formatData.mons[mon].timesGenerated++; | |
| if (toID(winner) === toID(p.name)) { | |
| formatData.mons[mon].numWins++; | |
| } | |
| } | |
| } | |
| saveStats(); | |
| } | |
| const commands = { | |
| rwr: "randswinrates", | |
| randswinrates(target, room, user) { | |
| target = toID(target); | |
| if (/^(gen|)[0-9]+$/.test(target)) { | |
| if (target.startsWith("gen")) | |
| target = target.slice(3); | |
| target = `gen${target}randombattle`; | |
| } | |
| return this.parse(`/j view-winrates-${target ? Dex.formats.get(target).id : `gen${Dex.gen}randombattle`}`); | |
| }, | |
| randswinrateshelp: [ | |
| "/randswinrates OR /rwr [format] - Get a list of the win rates for all Pokemon in the given Random Battles format." | |
| ], | |
| async removewinrates(target, room, user) { | |
| this.checkCan("rangeban"); | |
| if (!/^[0-9]{4}-[0-9]{2}$/.test(target) || target === getMonth()) { | |
| return this.errorReply(`Invalid month: ${target}`); | |
| } | |
| const path = STATS_PATH.replace("{{MON}}", target); | |
| if (!await (0, import_lib.FS)(path).exists()) { | |
| return this.errorReply(`No stats for the month ${target}.`); | |
| } | |
| await (0, import_lib.FS)(path).unlinkIfExists(); | |
| this.globalModlog("REMOVEWINRATES", null, target); | |
| this.privateGlobalModAction(`${user.name} removed Random Battle winrates for the month of ${target}`); | |
| } | |
| }; | |
| const pages = { | |
| async winrates(query, user) { | |
| if (!user.named) | |
| return Rooms.RETRY_AFTER_LOGIN; | |
| query = query.join("-").split("--"); | |
| const format = toID(query.shift()); | |
| if (!format) | |
| return this.errorReply(`Specify a format to view winrates for.`); | |
| if (!stats.formats[format]) { | |
| return this.errorReply(`That format does not have winrates tracked.`); | |
| } | |
| checkRollover(); | |
| const sorter = toID(query.shift() || "zscore"); | |
| if (!["zscore", "raw"].includes(sorter)) { | |
| return this.errorReply(`Invalid sorting method. Must be either 'zscore' or 'raw'.`); | |
| } | |
| const month = query.shift() || getMonth(); | |
| if (!/^[0-9]{4}-[0-9]{2}$/.test(month)) { | |
| return this.errorReply(`Invalid month: ${month}`); | |
| } | |
| const isOldMonth = month !== getMonth(); | |
| if (isOldMonth && !await (0, import_lib.FS)(STATS_PATH.replace("{{MONTH}}", month)).exists()) { | |
| return this.errorReply(`There are no winrates for that month.`); | |
| } | |
| const formatTitle = Dex.formats.get(format).name; | |
| let buf = `<div class="pad"><h2>Winrates for ${formatTitle} (${month})</h2>`; | |
| const prevMonth = new Date(new Date(`${month}-15`).getTime() - 30 * 24 * 60 * 60 * 1e3).toISOString().slice(0, 7); | |
| let hasButton = false; | |
| if (await (0, import_lib.FS)(STATS_PATH.replace("{{MONTH}}", prevMonth)).exists()) { | |
| buf += `<a class="button" href="/view-winrates-${format}--${sorter}--${prevMonth}">Previous month</a>`; | |
| hasButton = true; | |
| } | |
| const nextMonth = new Date(new Date(`${month}-15`).getTime() + 30 * 24 * 60 * 60 * 1e3).toISOString().slice(0, 7); | |
| if (await (0, import_lib.FS)(STATS_PATH.replace("{{MONTH}}", nextMonth)).exists()) { | |
| if (hasButton) | |
| buf += ` | `; | |
| buf += `<a class="button" href="/view-winrates-${format}--${sorter}--${nextMonth}">Next month</a>`; | |
| hasButton = true; | |
| } | |
| buf += hasButton ? ` | ` : ""; | |
| const otherSort = sorter === "zscore" ? "Raw" : "Z-Score"; | |
| buf += `<a class="button" target="replace" href="/view-winrates-${format}--${toID(otherSort)}--${month}">`; | |
| buf += `Sort by ${otherSort} descending</a>`; | |
| buf += `<hr />`; | |
| const statData = month === stats.month ? stats : JSON.parse(await (0, import_lib.FS)(STATS_PATH.replace("{{MONTH}}", month)).read()); | |
| const formatData = statData.formats[format]; | |
| if (!formatData) { | |
| buf += `<div class="message-error">No stats for that format found on that month.</div>`; | |
| return buf; | |
| } | |
| this.title = `[Winrates] [${format}] ${month}`; | |
| let sortFn; | |
| if (sorter === "zscore") { | |
| sortFn = ([_, data]) => [-getZScore(data), -data.timesGenerated]; | |
| } else { | |
| sortFn = ([_, data]) => [ | |
| -(data.numWins / data.timesGenerated), | |
| -data.numWins, | |
| -data.timesGenerated | |
| ]; | |
| } | |
| const mons = import_lib.Utils.sortBy(Object.entries(formatData.mons), sortFn); | |
| buf += `<div class="ladder pad"><table><tr><th>Pokemon</th><th>Win %</th><th>Z-Score</th>`; | |
| buf += `<th>Raw wins</th><th>Times generated</th></tr>`; | |
| for (const [mon, data] of mons) { | |
| buf += `<tr><td>${Dex.species.get(mon).name}</td>`; | |
| const { timesGenerated, numWins } = data; | |
| buf += `<td>${(numWins / timesGenerated * 100).toFixed(2)}%</td>`; | |
| buf += `<td>${getZScore(data).toFixed(3)}</td>`; | |
| buf += `<td>${numWins}</td><td>${timesGenerated}</td>`; | |
| buf += `</tr>`; | |
| } | |
| buf += `</table></div></div>`; | |
| return buf; | |
| } | |
| }; | |
| //# sourceMappingURL=winrates.js.map | |