{
"version": 3,
"sources": ["../../../server/ladders-remote.ts"],
"sourcesContent": ["/**\r\n * Main server ladder library\r\n * Pokemon Showdown - http://pokemonshowdown.com/\r\n *\r\n * This file handles ladders for the main server on\r\n * play.pokemonshowdown.com.\r\n *\r\n * Ladders for all other servers is handled by ladders.ts.\r\n *\r\n * Matchmaking is currently still implemented in rooms.ts.\r\n *\r\n * @license MIT\r\n */\r\nimport {Utils} from '../lib';\r\n\r\nexport class LadderStore {\r\n\tformatid: string;\r\n\tstatic readonly formatsListPrefix = '';\r\n\r\n\tconstructor(formatid: string) {\r\n\t\tthis.formatid = formatid;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns [formatid, html], where html is an the HTML source of a\r\n\t * ladder toplist, to be displayed directly in the ladder tab of the\r\n\t * client.\r\n\t */\r\n\t// This requires to be `async` because it must conform with the `LadderStore` interface\r\n\t// eslint-disable-next-line @typescript-eslint/require-await\r\n\tasync getTop(prefix?: string): Promise<[string, string] | null> {\r\n\t\treturn null;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a Promise for the Elo rating of a user\r\n\t */\r\n\tasync getRating(userid: string) {\r\n\t\tconst formatid = this.formatid;\r\n\t\tconst user = Users.getExact(userid);\r\n\t\tif (user?.mmrCache[formatid]) {\r\n\t\t\treturn user.mmrCache[formatid];\r\n\t\t}\r\n\t\tconst [data] = await LoginServer.request('mmr', {\r\n\t\t\tformat: formatid,\r\n\t\t\tuser: userid,\r\n\t\t});\r\n\t\tlet mmr = NaN;\r\n\t\tif (data && !data.errorip) {\r\n\t\t\tmmr = Number(data);\r\n\t\t}\r\n\t\tif (isNaN(mmr)) return 1000;\r\n\r\n\t\tif (user && user.id === userid) {\r\n\t\t\tuser.mmrCache[formatid] = mmr;\r\n\t\t}\r\n\t\treturn mmr;\r\n\t}\r\n\r\n\t/**\r\n\t * Update the Elo rating for two players after a battle, and display\r\n\t * the results in the passed room.\r\n\t */\r\n\tasync updateRating(p1name: string, p2name: string, p1score: number, room: AnyObject): Promise<[\r\n\t\tnumber, AnyObject | undefined | null, AnyObject | undefined | null,\r\n\t]> {\r\n\t\tif (Ladders.disabled) {\r\n\t\t\troom.addRaw(`Ratings not updated. The ladders are currently disabled.`).update();\r\n\t\t\treturn [p1score, null, null];\r\n\t\t}\r\n\r\n\t\tconst formatid = this.formatid;\r\n\t\tconst p1 = Users.getExact(p1name);\r\n\t\tconst p2 = Users.getExact(p2name);\r\n\t\tconst p1id = toID(p1name);\r\n\t\tconst p2id = toID(p2name);\r\n\r\n\t\tconst ladderUpdatePromise = LoginServer.request('ladderupdate', {\r\n\t\t\tp1: p1name,\r\n\t\t\tp2: p2name,\r\n\t\t\tscore: p1score,\r\n\t\t\tformat: formatid,\r\n\t\t});\r\n\r\n\t\t// calculate new Elo scores and display to room while loginserver updates the ladder\r\n\t\tconst [p1OldElo, p2OldElo] = (await Promise.all([this.getRating(p1id), this.getRating(p2id)])).map(Math.round);\r\n\t\tconst p1NewElo = Math.round(this.calculateElo(p1OldElo, p1score, p2OldElo));\r\n\t\tconst p2NewElo = Math.round(this.calculateElo(p2OldElo, 1 - p1score, p1OldElo));\r\n\r\n\t\tconst p1Act = (p1score > 0.9 ? `winning` : (p1score < 0.1 ? `losing` : `tying`));\r\n\t\tlet p1Reasons = `${p1NewElo - p1OldElo} for ${p1Act}`;\r\n\t\tif (!p1Reasons.startsWith('-')) p1Reasons = '+' + p1Reasons;\r\n\t\troom.addRaw(Utils.html`${p1name}'s rating: ${p1OldElo} → ${p1NewElo}
(${p1Reasons})`);\r\n\r\n\t\tconst p2Act = (p1score > 0.9 || p1score < 0 ? `losing` : (p1score < 0.1 ? `winning` : `tying`));\r\n\t\tlet p2Reasons = `${p2NewElo - p2OldElo} for ${p2Act}`;\r\n\t\tif (!p2Reasons.startsWith('-'))\tp2Reasons = '+' + p2Reasons;\r\n\t\troom.addRaw(Utils.html`${p2name}'s rating: ${p2OldElo} → ${p2NewElo}
(${p2Reasons})`);\r\n\r\n\t\troom.rated = Math.min(p1NewElo, p2NewElo);\r\n\r\n\t\tif (p1) p1.mmrCache[formatid] = +p1NewElo;\r\n\t\tif (p2) p2.mmrCache[formatid] = +p2NewElo;\r\n\r\n\t\troom.update();\r\n\r\n\t\tconst [data, error] = await ladderUpdatePromise;\r\n\r\n\t\tlet problem = false;\r\n\t\tif (error) {\r\n\t\t\tif (error.message !== 'stream interrupt') {\r\n\t\t\t\troom.add(`||Ladder isn't responding, score probably updated but might not have (${error.message}).`);\r\n\t\t\t\tproblem = true;\r\n\t\t\t}\r\n\t\t} else if (!room.battle) {\r\n\t\t\tproblem = true;\r\n\t\t} else if (!data) {\r\n\t\t\troom.add(`|error|Unexpected response ${data} from ladder server.`);\r\n\t\t\troom.update();\r\n\t\t\tproblem = true;\r\n\t\t} else if (data.errorip) {\r\n\t\t\troom.add(`|error|This server's request IP ${data.errorip} is not a registered server.`);\r\n\t\t\troom.add(`|error|You should be using ladders.js and not ladders-remote.js for ladder tracking.`);\r\n\t\t\troom.update();\r\n\t\t\tproblem = true;\r\n\t\t}\r\n\r\n\t\tif (problem) {\r\n\t\t\t// We used to clear mmrCache for the format to get the users updated rating next search\r\n\t\t\t// we now no longer do that because that results in the user getting paired with other users as though they have 1000 elo\r\n\t\t\t// if the next query times out, which happens very frequently. This results in a lot of confusion, so we're just\r\n\t\t\t// going to not clear this cache. If the user gets the proper rating later - great. If they don't,\r\n\t\t\t// this will ensure they still get matched up in a much more accurate fashion.\r\n\t\t\treturn [p1score, null, null];\r\n\t\t}\r\n\r\n\t\treturn [p1score, data?.p1rating, data?.p2rating];\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a Promise for an array of strings of