{ "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 s for ladder ratings of the user\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\tstatic async visualizeAll(username: string) {\r\n\t\treturn [`Please use the official client at play.pokemonshowdown.com`];\r\n\t}\r\n\t/**\r\n\t * Calculates Elo based on a match result\r\n\t *\r\n\t */\r\n\tcalculateElo(oldElo: number, score: number, foeElo: number): number {\r\n\t\t// see lib/ntbb-ladder.lib.php in the pokemon-showdown-client repo for the login server implementation\r\n\t\t// *intentionally* different from calculation in ladders-local, due to the high activity on the main server\r\n\r\n\t\t// The K factor determines how much your Elo changes when you win or\r\n\t\t// lose games. Larger K means more change.\r\n\t\t// In the \"original\" Elo, K is constant, but it's common for K to\r\n\t\t// get smaller as your rating goes up\r\n\t\tlet K = 50;\r\n\r\n\t\t// dynamic K-scaling (optional)\r\n\t\tif (oldElo < 1100) {\r\n\t\t\tif (score < 0.5) {\r\n\t\t\t\tK = 20 + (oldElo - 1000) * 30 / 100;\r\n\t\t\t} else if (score > 0.5) {\r\n\t\t\t\tK = 80 - (oldElo - 1000) * 30 / 100;\r\n\t\t\t}\r\n\t\t} else if (oldElo > 1300) {\r\n\t\t\tK = 40;\r\n\t\t}\r\n\r\n\t\t// main Elo formula\r\n\t\tconst E = 1 / (1 + Math.pow(10, (foeElo - oldElo) / 400));\r\n\r\n\t\tconst newElo = oldElo + K * (score - E);\r\n\r\n\t\treturn Math.max(newElo, 1000);\r\n\t}\r\n}\r\n"], "mappings": "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", "names": [] }