Spaces:
Running
Running
{ | |
"version": 3, | |
"sources": ["../../server/ladders-challenges.ts"], | |
"sourcesContent": ["import type { ChallengeType } from './room-battle';\n\n/**\n * A bundle of:\n - a ID\n * - a battle format\n * - a valid team for that format\n * - misc other preferences for the battle\n *\n * To start a battle, you need one of these for every player.\n */\nexport class BattleReady {\n\treadonly userid: ID;\n\treadonly formatid: string;\n\treadonly settings: User['battleSettings'];\n\treadonly rating: number;\n\treadonly challengeType: ChallengeType;\n\treadonly time: number;\n\tconstructor(\n\t\tuserid: ID,\n\t\tformatid: string,\n\t\tsettings: User['battleSettings'],\n\t\trating = 0,\n\t\tchallengeType: ChallengeType = 'challenge'\n\t) {\n\t\tthis.userid = userid;\n\t\tthis.formatid = formatid;\n\t\tthis.settings = settings;\n\t\tthis.rating = rating;\n\t\tthis.challengeType = challengeType;\n\t\tthis.time = Date.now();\n\t}\n}\n\nexport abstract class AbstractChallenge {\n\tfrom: ID;\n\tto: ID;\n\tready: BattleReady | null;\n\tformat: string;\n\tacceptCommand: string | null;\n\tmessage: string;\n\tacceptButton: string;\n\trejectButton: string;\n\troomid: RoomID;\n\tconstructor(from: ID, to: ID, ready: BattleReady | string, options: {\n\t\tacceptCommand?: string, rejectCommand?: string, roomid?: RoomID,\n\t\tmessage?: string, acceptButton?: string, rejectButton?: string,\n\t} = {}) {\n\t\tthis.from = from;\n\t\tthis.to = to;\n\t\tthis.ready = typeof ready === 'string' ? null : ready;\n\t\tthis.format = typeof ready === 'string' ? ready : ready.formatid;\n\t\tthis.acceptCommand = options.acceptCommand || null;\n\t\tthis.message = options.message || '';\n\t\tthis.roomid = options.roomid || '';\n\t\tthis.acceptButton = options.acceptButton || '';\n\t\tthis.rejectButton = options.rejectButton || '';\n\t}\n\tdestroy(accepted?: boolean) {}\n}\n/**\n * As a regular battle challenge, acceptCommand will be null, but you\n * can set acceptCommand to use this for custom requests wanting a\n * team for something.\n */\nexport class BattleChallenge extends AbstractChallenge {\n\tdeclare ready: BattleReady;\n\tdeclare acceptCommand: string | null;\n}\nexport class GameChallenge extends AbstractChallenge {\n\tdeclare ready: null;\n\tdeclare acceptCommand: string;\n}\n/**\n * Invites for `/importinputlog` (`ready: null`) or 4-player battles\n * (`ready: BattleReady`)\n */\nexport class BattleInvite extends AbstractChallenge {\n\tdeclare acceptCommand: string;\n\tdestroy(accepted?: boolean) {\n\t\tif (accepted) return;\n\n\t\tconst room = Rooms.get(this.roomid);\n\t\tif (!room) return; // room expired?\n\t\tconst battle = room.battle!;\n\t\tlet invitesFull = true;\n\t\tfor (const player of battle.players) {\n\t\t\tif (!player.invite && !player.id) invitesFull = false;\n\t\t\tif (player.invite === this.to) player.invite = '';\n\t\t}\n\t\tif (invitesFull) battle.sendInviteForm(true);\n\t}\n}\n\n/**\n * The defining difference between a BattleChallenge and a GameChallenge is\n * that a BattleChallenge has a Ready (and is for a RoomBattle format) and\n * a GameChallenge doesn't (and is for a RoomGame).\n *\n * But remember that both can have a custom acceptCommand.\n */\nexport type Challenge = BattleChallenge | GameChallenge;\n\n/**\n * Lists outgoing and incoming challenges for each user ID.\n */\nexport class Challenges extends Map<ID, Challenge[]> {\n\tgetOrCreate(userid: ID): Challenge[] {\n\t\tlet challenges = this.get(userid);\n\t\tif (challenges) return challenges;\n\t\tchallenges = [];\n\t\tthis.set(userid, challenges);\n\t\treturn challenges;\n\t}\n\t/** Throws Chat.ErrorMessage if a challenge between these users is already in the table */\n\tadd(challenge: Challenge): true {\n\t\tconst oldChallenge = this.search(challenge.to, challenge.from);\n\t\tif (oldChallenge) {\n\t\t\tthrow new Chat.ErrorMessage(`There is already a challenge (${challenge.format}) between ${challenge.to} and ${challenge.from}!`);\n\t\t}\n\t\tconst to = this.getOrCreate(challenge.to);\n\t\tconst from = this.getOrCreate(challenge.from);\n\t\tto.push(challenge);\n\t\tfrom.push(challenge);\n\t\tthis.update(challenge.to, challenge.from);\n\t\treturn true;\n\t}\n\t/** Returns false if the challenge isn't in the table */\n\tremove(challenge: Challenge, accepted?: boolean): boolean {\n\t\tconst to = this.getOrCreate(challenge.to);\n\t\tconst from = this.getOrCreate(challenge.from);\n\n\t\tconst toIndex = to.indexOf(challenge);\n\t\tlet success = false;\n\t\tif (toIndex >= 0) {\n\t\t\tto.splice(toIndex, 1);\n\t\t\tif (!to.length) this.delete(challenge.to);\n\t\t\tsuccess = true;\n\t\t}\n\n\t\tconst fromIndex = from.indexOf(challenge);\n\t\tif (fromIndex >= 0) {\n\t\t\tfrom.splice(fromIndex, 1);\n\t\t\tif (!from.length) this.delete(challenge.from);\n\t\t}\n\t\tif (success) {\n\t\t\tthis.update(challenge.to, challenge.from);\n\t\t\tchallenge.destroy(accepted);\n\t\t}\n\t\treturn success;\n\t}\n\tsearch(userid1: ID, userid2: ID): Challenge | null {\n\t\tconst challenges = this.get(userid1);\n\t\tif (!challenges) return null;\n\t\tfor (const challenge of challenges) {\n\t\t\tif (\n\t\t\t\t(challenge.to === userid1 && challenge.from === userid2) ||\n\t\t\t\t(challenge.to === userid2 && challenge.from === userid1)\n\t\t\t) {\n\t\t\t\treturn challenge;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\tsearchByRoom(userid: ID, roomid: RoomID) {\n\t\tconst challenges = this.get(userid);\n\t\tif (!challenges) return null;\n\t\tfor (const challenge of challenges) {\n\t\t\tif (challenge.roomid === roomid) return challenge;\n\t\t}\n\t\treturn null;\n\t}\n\t/**\n\t * Try to accept a custom challenge, throwing `Chat.ErrorMessage` on failure,\n\t * and returning the user the challenge was from on a success.\n\t */\n\tresolveAcceptCommand(context: Chat.CommandContext) {\n\t\tconst targetid = context.target as ID;\n\t\tconst chall = this.search(context.user.id, targetid);\n\t\tif (!chall || chall.to !== context.user.id || chall.acceptCommand !== context.message) {\n\t\t\tthrow new Chat.ErrorMessage(`Challenge not found. You are using the wrong command. Challenges should be accepted with /accept`);\n\t\t}\n\t\treturn chall;\n\t}\n\taccept(context: Chat.CommandContext) {\n\t\tconst chall = this.resolveAcceptCommand(context);\n\t\tthis.remove(chall, true);\n\t\tconst fromUser = Users.get(chall.from);\n\t\tif (!fromUser) throw new Chat.ErrorMessage(`User \"${chall.from}\" is not available right now.`);\n\t\treturn fromUser;\n\t}\n\tclearFor(userid: ID, reason?: string): number {\n\t\tconst user = Users.get(userid);\n\t\tconst userIdentity = user ? user.getIdentity() : ` ${userid}`;\n\t\tconst challenges = this.get(userid);\n\t\tif (!challenges) return 0;\n\t\tfor (const challenge of challenges) {\n\t\t\tconst otherid = challenge.to === userid ? challenge.from : challenge.to;\n\t\t\tconst otherUser = Users.get(otherid);\n\t\t\tconst otherIdentity = otherUser ? otherUser.getIdentity() : ` ${otherid}`;\n\n\t\t\tconst otherChallenges = this.get(otherid)!;\n\t\t\tconst otherIndex = otherChallenges.indexOf(challenge);\n\t\t\tif (otherIndex >= 0) otherChallenges.splice(otherIndex, 1);\n\t\t\tif (otherChallenges.length === 0) this.delete(otherid);\n\n\t\t\tif (!user && !otherUser) continue;\n\t\t\tconst header = `|pm|${userIdentity}|${otherIdentity}|`;\n\t\t\tlet message = `${header}/challenge`;\n\t\t\tif (reason) message = `${header}/text Challenge cancelled because ${reason}.\\n${message}`;\n\t\t\tuser?.send(message);\n\t\t\totherUser?.send(message);\n\t\t}\n\t\tthis.delete(userid);\n\t\treturn challenges.length;\n\t}\n\tgetUpdate(challenge: Challenge | null) {\n\t\tif (!challenge) return `/challenge`;\n\t\tconst teambuilderFormat = challenge.ready ? challenge.ready.formatid : '';\n\t\treturn `/challenge ${challenge.format}|${teambuilderFormat}|${challenge.message}|${challenge.acceptButton}|${challenge.rejectButton}`;\n\t}\n\tupdate(userid1: ID, userid2: ID) {\n\t\tconst challenge = this.search(userid1, userid2);\n\t\tuserid1 = challenge ? challenge.from : userid1;\n\t\tuserid2 = challenge ? challenge.to : userid2;\n\t\tthis.send(userid1, userid2, this.getUpdate(challenge));\n\t}\n\tsend(userid1: ID, userid2: ID, message: string) {\n\t\tconst user1 = Users.get(userid1);\n\t\tconst user2 = Users.get(userid2);\n\t\tconst user1Identity = user1 ? user1.getIdentity() : ` ${userid1}`;\n\t\tconst user2Identity = user2 ? user2.getIdentity() : ` ${userid2}`;\n\t\tconst fullMessage = `|pm|${user1Identity}|${user2Identity}|${message}`;\n\t\tuser1?.send(fullMessage);\n\t\tuser2?.send(fullMessage);\n\t}\n\tupdateFor(connection: Connection | User) {\n\t\tconst user = connection.user;\n\t\tconst challenges = this.get(user.id);\n\t\tif (!challenges) return;\n\n\t\tconst userIdentity = user.getIdentity();\n\t\tlet messages = '';\n\t\tfor (const challenge of challenges) {\n\t\t\tlet fromIdentity, toIdentity;\n\t\t\tif (challenge.from === user.id) {\n\t\t\t\tfromIdentity = userIdentity;\n\t\t\t\tconst toUser = Users.get(challenge.to);\n\t\t\t\ttoIdentity = toUser ? toUser.getIdentity() : ` ${challenge.to}`;\n\t\t\t} else {\n\t\t\t\tconst fromUser = Users.get(challenge.from);\n\t\t\t\tfromIdentity = fromUser ? fromUser.getIdentity() : ` ${challenge.from}`;\n\t\t\t\ttoIdentity = userIdentity;\n\t\t\t}\n\t\t\tmessages += `|pm|${fromIdentity}|${toIdentity}|${this.getUpdate(challenge)}\\n`;\n\t\t}\n\t\tconnection.send(messages);\n\t}\n}\n\nexport const challenges = new Challenges();\n"], | |
"mappings": "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", | |
"names": ["challenges"] | |
} | |