{ "version": 3, "sources": ["../../server/ladders.ts"], "sourcesContent": ["/**\n * Matchmaker\n * Pokemon Showdown - http://pokemonshowdown.com/\n *\n * This keeps track of challenges to battle made between users, setting up\n * matches between users looking for a battle, and starting new battles.\n *\n * @license MIT\n */\n\nconst LadderStore: typeof import('./ladders-remote').LadderStore = (\n\ttypeof Config === 'object' && Config.remoteladder ? require('./ladders-remote') : require('./ladders-local')\n).LadderStore;\n\nconst SECONDS = 1000;\nconst PERIODIC_MATCH_INTERVAL = 60 * SECONDS;\n\nimport type { ChallengeType } from './room-battle';\nimport { BattleReady, BattleChallenge, GameChallenge, BattleInvite, challenges } from './ladders-challenges';\n\n/**\n * Keys are formatids\n */\nconst searches = new Map,\n}>();\n\n/**\n * This keeps track of searches for battles, creating a new battle for a newly\n * added search if a valid match can be made, otherwise periodically\n * attempting to make a match with looser restrictions until one can be made.\n */\nclass Ladder extends LadderStore {\n\tasync prepBattle(connection: Connection, challengeType: ChallengeType, team: string | null = null, isRated = false) {\n\t\t// all validation for a battle goes through here\n\t\tconst user = connection.user;\n\t\tconst userid = user.id;\n\t\tif (team === null) team = user.battleSettings.team;\n\n\t\tif (Rooms.global.lockdown && Rooms.global.lockdown !== 'pre') {\n\t\t\tlet message = `The server is restarting. Battles will be available again in a few minutes.`;\n\t\t\tif (Rooms.global.lockdown === 'ddos') {\n\t\t\t\tmessage = `The server is under attack. Battles cannot be started at this time.`;\n\t\t\t}\n\t\t\tconnection.popup(message);\n\t\t\treturn null;\n\t\t}\n\t\tif (Punishments.isBattleBanned(user)) {\n\t\t\tconnection.popup(`You are barred from starting any new games until your battle ban expires.`);\n\t\t\treturn null;\n\t\t}\n\t\tconst gameCount = user.games.size;\n\t\tif (Monitor.countConcurrentBattle(gameCount, connection)) {\n\t\t\treturn null;\n\t\t}\n\t\tif (Monitor.countPrepBattle(connection.ip, connection)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.formatid = Dex.formats.validate(this.formatid);\n\t\t} catch (e: any) {\n\t\t\tconnection.popup(`Your selected format is invalid:\\n\\n- ${e.message}`);\n\t\t\treturn null;\n\t\t}\n\n\t\tlet rating = 0;\n\t\tlet valResult;\n\t\tlet removeNicknames = !!(user.locked || user.namelocked);\n\n\t\tconst regex = /(?:^|])([^|]*)\\|([^|]*)\\|/g;\n\t\tlet match = regex.exec(team);\n\t\tlet unownWord = '';\n\t\twhile (match) {\n\t\t\tconst nickname = match[1];\n\t\t\tconst speciesid = toID(match[2] || match[1]);\n\t\t\tif (speciesid.length <= 6 && speciesid.startsWith('unown')) {\n\t\t\t\tunownWord += speciesid.charAt(5) || 'a';\n\t\t\t}\n\t\t\tif (nickname) {\n\t\t\t\tconst filtered = Chat.nicknamefilter(nickname, user);\n\t\t\t\tif (typeof filtered === 'string' && (!filtered || filtered !== match[1])) {\n\t\t\t\t\tconnection.popup(\n\t\t\t\t\t\t`Your team was rejected for the following reason:\\n\\n` +\n\t\t\t\t\t\t`- Your Pok\u00E9mon has a banned nickname: ${match[1]}`\n\t\t\t\t\t);\n\t\t\t\t\treturn null;\n\t\t\t\t} else if (filtered === false) {\n\t\t\t\t\tremoveNicknames = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmatch = regex.exec(team);\n\t\t}\n\t\tif (unownWord) {\n\t\t\tconst filtered = Chat.nicknamefilter(unownWord, user);\n\t\t\tif (!filtered || filtered !== unownWord) {\n\t\t\t\tconnection.popup(\n\t\t\t\t\t`Your team was rejected for the following reason:\\n\\n` +\n\t\t\t\t\t`- Your Unowns spell out a banned word: ${unownWord.toUpperCase()}`\n\t\t\t\t);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\tif (isRated && !Ladders.disabled) {\n\t\t\tconst uid = user.id;\n\t\t\t[valResult, rating] = await Promise.all([\n\t\t\t\tTeamValidatorAsync.get(this.formatid).validateTeam(team, { removeNicknames, user: uid }),\n\t\t\t\tthis.getRating(uid),\n\t\t\t]);\n\t\t\tif (uid !== user.id) {\n\t\t\t\t// User feedback for renames handled elsewhere.\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (!rating) rating = 1;\n\t\t} else {\n\t\t\tif (Ladders.disabled) {\n\t\t\t\tconnection.popup(`The ladder is temporarily disabled due to technical difficulties - you will not receive ladder rating for this game.`);\n\t\t\t\trating = 1;\n\t\t\t}\n\t\t\tconst validator = TeamValidatorAsync.get(this.formatid);\n\t\t\tvalResult = await validator.validateTeam(team, { removeNicknames, user: user.id });\n\t\t}\n\n\t\tif (!valResult.startsWith('1')) {\n\t\t\tconnection.popup(\n\t\t\t\t`Your team was rejected for the following reasons:\\n\\n` +\n\t\t\t\t`- ` + valResult.slice(1).replace(/\\n/g, `\\n- `)\n\t\t\t);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst settings = { ...user.battleSettings, team: valResult.slice(1) };\n\t\tuser.battleSettings.inviteOnly = false;\n\t\tuser.battleSettings.hidden = false;\n\t\treturn new BattleReady(userid, this.formatid, settings, rating, challengeType);\n\t}\n\n\tstatic getChallenging(userid: ID) {\n\t\tconst userChalls = Ladders.challenges.get(userid);\n\t\tif (userChalls) {\n\t\t\tfor (const chall of userChalls) {\n\t\t\t\tif (chall.from === userid) return chall;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tasync makeChallenge(connection: Connection, targetUser: User) {\n\t\tconst user = connection.user;\n\t\tif (targetUser === user) {\n\t\t\tconnection.popup(`You can't battle yourself. The best you can do is open PS in Private Browsing (or another browser) and log into a different username, and battle that username.`);\n\t\t\treturn false;\n\t\t}\n\t\tif (Ladder.getChallenging(user.id)) {\n\t\t\tconnection.popup(`You are already challenging someone. Cancel that challenge before challenging someone else.`);\n\t\t\treturn false;\n\t\t}\n\n\t\tlet blockChallenge: boolean;\n\t\tif (typeof targetUser.settings.blockChallenges === 'boolean') {\n\t\t\tblockChallenge = targetUser.settings.blockChallenges;\n\t\t} else if (targetUser.settings.blockChallenges === 'friends') {\n\t\t\tblockChallenge = !targetUser.friends?.has(user.id);\n\t\t} else {\n\t\t\tblockChallenge = !Users.globalAuth.atLeast(user, targetUser.settings.blockChallenges);\n\t\t}\n\n\t\tif (blockChallenge && !user.can('bypassblocks', targetUser)) {\n\t\t\tconnection.popup(`The user '${targetUser.name}' is not accepting challenges right now.`);\n\t\t\tChat.maybeNotifyBlocked('challenge', targetUser, user);\n\t\t\treturn false;\n\t\t}\n\t\tif (Date.now() < user.lastChallenge + 10 * SECONDS && !Config.nothrottle) {\n\t\t\t// 10 seconds ago, probable misclick\n\t\t\tconnection.popup(`You challenged less than 10 seconds after your last challenge! It's cancelled in case it's a misclick.`);\n\t\t\treturn false;\n\t\t}\n\t\tconst currentChallenges = Ladders.challenges.get(targetUser.id);\n\t\tif (currentChallenges && currentChallenges.length >= 3 && !user.autoconfirmed) {\n\t\t\tconnection.popup(\n\t\t\t\t`This user already has 3 pending challenges.\\n` +\n\t\t\t\t`You must be autoconfirmed to challenge them.`\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t\tconst ready = await this.prepBattle(connection, 'challenge');\n\t\tif (!ready) return false;\n\t\t// If our target is already challenging us in the same format,\n\t\t// simply accept the pending challenge instead of creating a new one.\n\t\tconst existingChall = Ladders.challenges.search(user.id, targetUser.id);\n\t\tif (existingChall) {\n\t\t\tif (\n\t\t\t\texistingChall.from === targetUser.id &&\n\t\t\t\texistingChall.to === user.id &&\n\t\t\t\texistingChall.format === this.formatid &&\n\t\t\t\texistingChall.ready\n\t\t\t) {\n\t\t\t\tif (Ladders.challenges.remove(existingChall)) {\n\t\t\t\t\tLadders.match([existingChall.ready, ready]);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconnection.popup(`There's already a challenge (${existingChall.format}) between you and ${targetUser.name}!`);\n\t\t\t\tLadders.challenges.update(user.id, targetUser.id);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\tLadders.challenges.add(new BattleChallenge(user.id, targetUser.id, ready));\n\t\tLadders.challenges.send(user.id, targetUser.id, `/log ${user.name} wants to battle!`);\n\t\tuser.lastChallenge = Date.now();\n\t\tChat.runHandlers('onChallenge', user, targetUser, ready.formatid);\n\t\treturn true;\n\t}\n\tstatic async acceptChallenge(connection: Connection, chall: BattleChallenge) {\n\t\tconst ladder = Ladders(chall.format);\n\t\tconst ready = await ladder.prepBattle(connection, 'challenge');\n\t\tif (!ready) return;\n\t\tif (Ladders.challenges.remove(chall)) {\n\t\t\treturn Ladders.match([chall.ready, ready]);\n\t\t}\n\t}\n\n\tcancelSearch(user: User) {\n\t\tconst formatid = toID(this.formatid);\n\n\t\tconst formatTable = Ladders.searches.get(formatid);\n\t\tif (!formatTable) return false;\n\t\tif (!formatTable.searches.has(user.id)) return false;\n\t\tformatTable.searches.delete(user.id);\n\n\t\tLadder.updateSearch(user);\n\t\treturn true;\n\t}\n\n\tstatic cancelSearches(user: User) {\n\t\tlet cancelCount = 0;\n\n\t\tfor (const formatTable of Ladders.searches.values()) {\n\t\t\tconst search = formatTable.searches.get(user.id);\n\t\t\tif (!search) continue;\n\t\t\tformatTable.searches.delete(user.id);\n\t\t\tcancelCount++;\n\t\t}\n\n\t\tLadder.updateSearch(user);\n\t\treturn cancelCount;\n\t}\n\n\tgetSearcher(search: BattleReady) {\n\t\tconst formatid = toID(this.formatid);\n\t\tconst user = Users.get(search.userid);\n\t\tif (!user?.connected || user.id !== search.userid) {\n\t\t\tconst formatTable = Ladders.searches.get(formatid);\n\t\t\tif (formatTable) formatTable.searches.delete(search.userid);\n\t\t\tif (user?.connected) {\n\t\t\t\tuser.popup(`You changed your name and are no longer looking for a battle in ${formatid}`);\n\t\t\t\tLadder.updateSearch(user);\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t\treturn user;\n\t}\n\n\tstatic getSearches(user: User) {\n\t\tconst userSearches = [];\n\t\tfor (const [formatid, formatTable] of Ladders.searches) {\n\t\t\tif (formatTable.searches.has(user.id)) userSearches.push(formatid);\n\t\t}\n\t\treturn userSearches;\n\t}\n\tstatic updateSearch(user: User, connection: Connection | null = null) {\n\t\tlet games: { [k: string]: string } | null = {};\n\t\tlet atLeastOne = false;\n\t\tfor (const roomid of user.games) {\n\t\t\tconst room = Rooms.get(roomid);\n\t\t\tif (!room) {\n\t\t\t\tMonitor.warn(`while searching, room ${roomid} expired for user ${user.id} in rooms ${[...user.inRooms]} and games ${[...user.games]}`);\n\t\t\t\tuser.games.delete(roomid);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst game = room.game;\n\t\t\tif (!game) {\n\t\t\t\tMonitor.warn(`while searching, room ${roomid} has no game for user ${user.id} in rooms ${[...user.inRooms]} and games ${[...user.games]}`);\n\t\t\t\tuser.games.delete(roomid);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tgames[roomid] = game.title + (game.allowRenames ? '' : '*');\n\t\t\tatLeastOne = true;\n\t\t}\n\t\tif (!atLeastOne) games = null;\n\t\tconst searching = Ladders.getSearches(user);\n\t\t(connection || user).send(`|updatesearch|` + JSON.stringify({\n\t\t\tsearching,\n\t\t\tgames,\n\t\t}));\n\t}\n\thasSearch(user: User) {\n\t\tconst formatid = toID(this.formatid);\n\t\tconst formatTable = Ladders.searches.get(formatid);\n\t\tif (!formatTable) return false;\n\t\treturn formatTable.searches.has(user.id);\n\t}\n\n\t/**\n\t * Validates a user's team and fetches their rating for a given format\n\t * before creating a search for a battle.\n\t */\n\tasync searchBattle(user: User, connection: Connection) {\n\t\tif (!user.connected) return;\n\n\t\tconst format = Dex.formats.get(this.formatid);\n\t\tif (!format.searchShow) {\n\t\t\tconnection.popup(`Error: Your format ${format.id} is not ladderable.`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst oldUserid = user.id;\n\t\tconst search = await this.prepBattle(connection, format.rated ? 'rated' : 'unrated', null, format.rated !== false);\n\n\t\tif (oldUserid !== user.id) return;\n\t\tif (!search) return;\n\n\t\tthis.addSearch(search, user);\n\t}\n\n\t/**\n\t * Verifies whether or not a match made between two users is valid. Returns\n\t */\n\tmatchmakingOK(matches: [BattleReady, User][]) {\n\t\tconst formatid = toID(this.formatid);\n\t\tconst users = matches.map(([ready, user]) => user);\n\t\tconst userids = users.map(user => user.id);\n\n\t\t// users must be different\n\t\tif (new Set(users).size !== users.length) return false;\n\n\t\tif (Config.noipchecks) {\n\t\t\tusers[0].lastMatch = users[1].id;\n\t\t\tusers[1].lastMatch = users[0].id;\n\t\t\treturn true;\n\t\t}\n\n\t\t// users must have different IPs\n\t\tif (new Set(users.map(user => user.latestIp)).size !== users.length) return false;\n\n\t\t// users must not have been matched immediately previously\n\t\tfor (const user of users) {\n\t\t\tif (userids.includes(user.lastMatch)) return false;\n\t\t}\n\n\t\t// search must be within range\n\t\tlet searchRange = 100;\n\t\tconst times = matches.map(([search]) => search.time);\n\t\tconst elapsed = Date.now() - Math.min(...times);\n\t\tif (formatid === `gen${Dex.gen}ou` || formatid === `gen${Dex.gen}randombattle`) {\n\t\t\tsearchRange = 50;\n\t\t}\n\n\t\tsearchRange += elapsed / 300; // +1 every .3 seconds\n\t\tif (searchRange > 300) searchRange = 300 + (searchRange - 300) / 10; // +1 every 3 sec after 300\n\t\tif (searchRange > 600) searchRange = 600;\n\t\tconst ratings = matches.map(([search]) => search.rating);\n\t\tif (Math.max(...ratings) - Math.min(...ratings) > searchRange) return false;\n\n\t\tmatches[0][1].lastMatch = matches[1][1].id;\n\t\tmatches[1][1].lastMatch = matches[0][1].id;\n\t\treturn true;\n\t}\n\n\t/**\n\t * Starts a search for a battle for a user under the given format.\n\t */\n\taddSearch(newSearch: BattleReady, user: User) {\n\t\tconst formatid = newSearch.formatid;\n\t\tlet formatTable = Ladders.searches.get(formatid);\n\t\tif (!formatTable) {\n\t\t\tformatTable = {\n\t\t\t\tplayerCount: Dex.formats.get(formatid).playerCount,\n\t\t\t\tsearches: new Map(),\n\t\t\t};\n\t\t\tLadders.searches.set(formatid, formatTable);\n\t\t}\n\t\tif (formatTable.searches.has(user.id)) {\n\t\t\tuser.popup(`Couldn't search: You are already searching for a ${formatid} battle.`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst matches = [newSearch];\n\t\t// In order from longest waiting to shortest waiting\n\t\tfor (const search of formatTable.searches.values()) {\n\t\t\tconst searcher = this.getSearcher(search);\n\t\t\tif (!searcher) continue;\n\t\t\tconst matched = this.matchmakingOK([[search, searcher], [newSearch, user]]);\n\t\t\tif (matched) {\n\t\t\t\tmatches.push(search);\n\t\t\t}\n\t\t\tif (matches.length >= formatTable.playerCount) {\n\t\t\t\tfor (const matchedSearch of matches) formatTable.searches.delete(matchedSearch.userid);\n\t\t\t\tLadder.match(matches);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tformatTable.searches.set(newSearch.userid, newSearch);\n\t\tLadder.updateSearch(user);\n\t}\n\n\t/**\n\t * Creates a match for a new battle for each format in this.searches if a\n\t * valid match can be made. This is run periodically depending on\n\t * PERIODIC_MATCH_INTERVAL.\n\t */\n\tstatic periodicMatch() {\n\t\t// In order from longest waiting to shortest waiting\n\t\tfor (const [formatid, formatTable] of Ladders.searches) {\n\t\t\tif (formatTable.playerCount > 2) continue; // TODO: implement\n\t\t\tconst matchmaker = Ladders(formatid);\n\t\t\tlet longest: [BattleReady, User] | null = null;\n\t\t\tfor (const search of formatTable.searches.values()) {\n\t\t\t\tif (!longest) {\n\t\t\t\t\tconst longestSearcher = matchmaker.getSearcher(search);\n\t\t\t\t\tif (!longestSearcher) continue;\n\t\t\t\t\tlongest = [search, longestSearcher];\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst searcher = matchmaker.getSearcher(search);\n\t\t\t\tif (!searcher) continue;\n\n\t\t\t\tconst [longestSearch, longestSearcher] = longest;\n\t\t\t\tconst matched = matchmaker.matchmakingOK([[search, searcher], [longestSearch, longestSearcher]]);\n\t\t\t\tif (matched) {\n\t\t\t\t\tformatTable.searches.delete(search.userid);\n\t\t\t\t\tformatTable.searches.delete(longestSearch.userid);\n\t\t\t\t\tLadder.match([longestSearch, search]);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tstatic match(readies: BattleReady[]) {\n\t\tconst formatid = readies[0].formatid;\n\t\tif (readies.some(ready => ready.formatid !== formatid)) throw new Error(`Format IDs don't match`);\n\t\tconst players = [];\n\t\tlet missingUser = null;\n\t\tlet minRating = Infinity;\n\t\tfor (const ready of readies) {\n\t\t\tconst user = Users.get(ready.userid);\n\t\t\tif (!user) {\n\t\t\t\tmissingUser = ready.userid;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tplayers.push({\n\t\t\t\tuser,\n\t\t\t\tteam: ready.settings.team,\n\t\t\t\trating: ready.rating,\n\t\t\t\thidden: ready.settings.hidden,\n\t\t\t\tinviteOnly: ready.settings.inviteOnly,\n\t\t\t});\n\t\t\tif (ready.rating < minRating) minRating = ready.rating;\n\t\t}\n\t\tif (missingUser) {\n\t\t\tfor (const ready of readies) {\n\t\t\t\tUsers.get(ready.userid)?.popup(`Sorry, your opponent ${missingUser} went offline before your battle could start.`);\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\t\tconst format = Dex.formats.get(formatid);\n\t\tconst delayedStart = format.playerCount > players.length ? 'multi' : false;\n\t\treturn Rooms.createBattle({\n\t\t\tformat: formatid,\n\t\t\tplayers,\n\t\t\trated: minRating,\n\t\t\tchallengeType: readies[0].challengeType,\n\t\t\tdelayedStart,\n\t\t});\n\t}\n}\n\nfunction getLadder(formatid: string) {\n\treturn new Ladder(formatid);\n}\n\nconst periodicMatchInterval = setInterval(\n\t() => Ladder.periodicMatch(),\n\tPERIODIC_MATCH_INTERVAL\n);\n\nexport const Ladders = Object.assign(getLadder, {\n\tBattleReady,\n\tLadderStore,\n\tLadder,\n\n\tBattleChallenge,\n\tGameChallenge,\n\tBattleInvite,\n\n\tcancelSearches: Ladder.cancelSearches,\n\tupdateSearch: Ladder.updateSearch,\n\tacceptChallenge: Ladder.acceptChallenge,\n\tvisualizeAll: Ladder.visualizeAll,\n\tgetSearches: Ladder.getSearches,\n\tmatch: Ladder.match,\n\n\tsearches,\n\tchallenges,\n\tperiodicMatchInterval,\n\n\t// tells the client to ask the server for format information\n\tformatsListPrefix: LadderStore.formatsListPrefix,\n\tdisabled: false as boolean | 'db',\n});\n"], "mappings": "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", "names": ["longestSearcher"] }