{ "version": 3, "sources": ["../../../server/room-game.ts"], "sourcesContent": ["/**\r\n * Room games\r\n * Pokemon Showdown - http://pokemonshowdown.com/\r\n *\r\n * Room games are an abstract representation of an activity that a room\r\n * can be focused on, such as a battle, tournament, or chat game like\r\n * Hangman. Rooms are limited to one roomgame at a time.\r\n *\r\n * Room games can keep track of designated players. If a user is a player,\r\n * they will not be allowed to change name until their games are complete.\r\n *\r\n * The player system is optional: Some games, like Hangman, don't designate\r\n * players and just allow any user in the room to play.\r\n *\r\n * @license MIT\r\n */\r\n\r\n// globally Rooms.RoomGamePlayer\r\nexport class RoomGamePlayer {\r\n\treadonly num: number;\r\n\treadonly game: GameClass;\r\n\t/**\r\n\t * Will be the username of the user playing, but with some exceptions:\r\n\t *\r\n\t * - Creating a game with no users will initialize player names to\r\n\t * \"Player 1\", \"Player 2\", etc.\r\n\t * - Players will retain the name of the last active user, even if that\r\n\t * user abandons the game.\r\n\t */\r\n\tname: string;\r\n\t/**\r\n\t * This will be '' if there's no user associated with the player.\r\n\t *\r\n\t * we explicitly don't hold a direct reference to the user\r\n\t */\r\n\tid: ID;\r\n\tconstructor(user: User | string | null, game: GameClass, num = 0) {\r\n\t\tthis.num = num;\r\n\t\tif (!user) user = num ? `Player ${num}` : `Player`;\r\n\t\tthis.game = game;\r\n\t\tthis.name = (typeof user === 'string' ? user : user.name);\r\n\t\tif (typeof user === 'string') user = null;\r\n\t\tthis.id = user ? user.id : '';\r\n\t\tif (user && !this.game.isSubGame) {\r\n\t\t\tuser.games.add(this.game.roomid);\r\n\t\t\tuser.updateSearch();\r\n\t\t}\r\n\t}\r\n\tunlinkUser() {\r\n\t\tif (!this.id) return;\r\n\t\tconst user = Users.getExact(this.id);\r\n\t\tif (user && !this.game.isSubGame) {\r\n\t\t\tuser.games.delete(this.game.roomid);\r\n\t\t\tuser.updateSearch();\r\n\t\t}\r\n\t\tthis.id = '';\r\n\t}\r\n\tdestroy() {\r\n\t\tthis.unlinkUser();\r\n\t}\r\n\r\n\ttoString() {\r\n\t\treturn this.id;\r\n\t}\r\n\tsend(data: string) {\r\n\t\tconst user = Users.getExact(this.id);\r\n\t\tif (user) user.send(data);\r\n\t}\r\n\tsendRoom(data: string) {\r\n\t\tconst user = Users.getExact(this.id);\r\n\t\tif (user) user.sendTo(this.game.roomid, data);\r\n\t}\r\n}\r\n\r\n/**\r\n * globally Rooms.RoomGame\r\n *\r\n * If you don't want to define your own player class, you should extend SimpleRoomGame.\r\n */\r\nexport abstract class RoomGame {\r\n\troomid: RoomID;\r\n\t/**\r\n\t * The room this roomgame is in. Rooms can have two RoomGames at a time,\r\n\t * which are available as `this.room.game === this` and `this.room.subGame === this`.\r\n\t */\r\n\troom: Room;\r\n\tgameid: ID;\r\n\ttitle: string;\r\n\tallowRenames: boolean;\r\n\tisSubGame: boolean;\r\n\t/**\r\n\t * userid:player table.\r\n\t *\r\n\t * Does not contain userless players: use playerList for the full list.\r\n\t */\r\n\tplayerTable: {[userid: string]: PlayerClass};\r\n\tplayers: PlayerClass[];\r\n\tplayerCount: number;\r\n\tplayerCap: number;\r\n\tended: boolean;\r\n\t/** Does `/guess` or `/choose` require the user to be able to talk? */\r\n\tcheckChat = false;\r\n\t/**\r\n\t * We should really resolve this collision at _some_ point, but it will have\r\n\t * to be later. The /timer command is written to be resilient to this.\r\n\t */\r\n\ttimer?: {timerRequesters?: Set, start: (force?: User) => void, stop: (force?: User) => void} | NodeJS.Timer | null;\r\n\tconstructor(room: Room, isSubGame = false) {\r\n\t\tthis.roomid = room.roomid;\r\n\t\tthis.room = room;\r\n\t\tthis.gameid = 'game' as ID;\r\n\t\tthis.title = 'Game';\r\n\t\tthis.allowRenames = false;\r\n\t\tthis.isSubGame = isSubGame;\r\n\t\tthis.playerTable = Object.create(null);\r\n\t\tthis.players = [];\r\n\t\tthis.playerCount = 0;\r\n\t\tthis.playerCap = 0;\r\n\t\tthis.ended = false;\r\n\r\n\t\tif (this.isSubGame) {\r\n\t\t\tthis.room.subGame = this;\r\n\t\t} else {\r\n\t\t\tthis.room.game = this;\r\n\t\t}\r\n\t}\r\n\r\n\tdestroy() {\r\n\t\tif (this.isSubGame) {\r\n\t\t\tthis.room.subGame = null;\r\n\t\t} else {\r\n\t\t\tthis.room.game = null;\r\n\t\t}\r\n\t\t// @ts-ignore\r\n\t\tthis.room = null;\r\n\t\tfor (const player of this.players) {\r\n\t\t\tplayer.destroy();\r\n\t\t}\r\n\t\t// @ts-ignore\r\n\t\tthis.players = null;\r\n\t\t// @ts-ignore\r\n\t\tthis.playerTable = null;\r\n\t}\r\n\r\n\taddPlayer(user: User | string | null = null, ...rest: any[]): PlayerClass | null {\r\n\t\tif (typeof user !== 'string' && user) {\r\n\t\t\tif (user.id in this.playerTable) return null;\r\n\t\t}\r\n\t\tif (this.playerCap > 0 && this.playerCount >= this.playerCap) return null;\r\n\t\tconst player = this.makePlayer(user, ...rest);\r\n\t\tif (!player) return null;\r\n\t\tif (typeof user === 'string') user = null;\r\n\t\tthis.players.push(player);\r\n\t\tif (user) {\r\n\t\t\tthis.playerTable[user.id] = player;\r\n\t\t\tthis.playerCount++;\r\n\t\t}\r\n\t\treturn player;\r\n\t}\r\n\r\n\tupdatePlayer(player: PlayerClass, user: User | null) {\r\n\t\tif (!this.allowRenames) return;\r\n\t\tif (player.id) {\r\n\t\t\tdelete this.playerTable[player.id];\r\n\t\t}\r\n\t\tif (user) {\r\n\t\t\tplayer.id = user.id;\r\n\t\t\tplayer.name = user.name;\r\n\t\t\tthis.playerTable[player.id] = player;\r\n\t\t\tthis.room.auth.set(user.id, Users.PLAYER_SYMBOL);\r\n\t\t} else {\r\n\t\t\tplayer.unlinkUser();\r\n\t\t}\r\n\t}\r\n\r\n\tabstract makePlayer(user: User | string | null, ...rest: any[]): PlayerClass;\r\n\r\n\tremovePlayer(player: PlayerClass | User) {\r\n\t\tif (player instanceof Users.User) {\r\n\t\t\t// API changed\r\n\t\t\t// TODO: deprecate\r\n\t\t\tplayer = this.playerTable[player.id];\r\n\t\t\tif (!player) throw new Error(\"Player not found\");\r\n\t\t}\r\n\t\tif (!this.allowRenames) return false;\r\n\t\tconst playerIndex = this.players.indexOf(player);\r\n\t\tif (playerIndex < 0) return false;\r\n\t\tif (player.id) delete this.playerTable[player.id];\r\n\t\tthis.players.splice(playerIndex, 1);\r\n\t\tplayer.destroy();\r\n\t\tthis.playerCount--;\r\n\t\treturn true;\r\n\t}\r\n\r\n\trenamePlayer(user: User, oldUserid: ID) {\r\n\t\tif (user.id === oldUserid) {\r\n\t\t\tthis.playerTable[user.id].name = user.name;\r\n\t\t} else {\r\n\t\t\tthis.playerTable[user.id] = this.playerTable[oldUserid];\r\n\t\t\tthis.playerTable[user.id].id = user.id;\r\n\t\t\tthis.playerTable[user.id].name = user.name;\r\n\t\t\tdelete this.playerTable[oldUserid];\r\n\t\t}\r\n\t}\r\n\r\n\trenameRoom(roomid: RoomID) {\r\n\t\tfor (const player of this.players) {\r\n\t\t\tconst user = Users.get(player.id);\r\n\t\t\tuser?.games.delete(this.roomid);\r\n\t\t\tuser?.games.add(roomid);\r\n\t\t}\r\n\t\tthis.roomid = roomid;\r\n\t}\r\n\r\n\t// Commands:\r\n\r\n\t// These are all optional to implement:\r\n\r\n\t/**\r\n\t * Called when a user uses /forfeit\r\n\t * Also planned to be used for some force-forfeit situations, such\r\n\t * as when a user changes their name and .allowRenames === false\r\n\t * This is strongly recommended to be supported, as the user is\r\n\t * extremely unlikely to keep playing after this function is\r\n\t * called.\r\n\t */\r\n\tforfeit?(user: User): void;\r\n\r\n\t/**\r\n\t * Called when a user uses /choose [text]\r\n\t * If you have buttons, you are recommended to use this interface\r\n\t * instead of making your own commands.\r\n\t */\r\n\tchoose?(user: User, text: string): void;\r\n\r\n\t/**\r\n\t * Called when a user uses /undo [text]\r\n\t */\r\n\tundo?(user: User, text: string): void;\r\n\r\n\t/**\r\n\t * Called when a user uses /joingame [text]\r\n\t */\r\n\tjoinGame?(user: User, text?: string): void;\r\n\r\n\t/**\r\n\t * Called when a user uses /leavegame [text]\r\n\t */\r\n\tleaveGame?(user: User, text?: string): void;\r\n\r\n\t// Events:\r\n\r\n\t// Note:\r\n\t// A user can have multiple connections. For instance, if you have\r\n\t// two tabs open and connected to PS, those tabs represent two\r\n\t// connections, but a single PS user. Each tab can be in separate\r\n\t// rooms.\r\n\r\n\t/**\r\n\t * Called when a user joins a room. (i.e. when the user's first\r\n\t * connection joins)\r\n\t *\r\n\t * While connection is passed, it should not usually be used:\r\n\t * Any handling of connections should happen in onConnect.\r\n\t */\r\n\tonJoin(user: User, connection: Connection) {}\r\n\r\n\t/**\r\n\t * Called when a user is banned from the room this game is taking\r\n\t * place in.\r\n\t */\r\n\tremoveBannedUser(user: User) {\r\n\t\tif (this.forfeit) this.forfeit(user);\r\n\t}\r\n\r\n\t/**\r\n\t * Called when a user in the game is renamed. `isJoining` is true\r\n\t * if the user was previously a guest, but now has a username.\r\n\t * Check `!user.named` for the case where a user previously had a\r\n\t * username but is now a guest. By default, updates a player's\r\n\t * name as long as allowRenames is set to true.\r\n\t */\r\n\tonRename(user: User, oldUserid: ID, isJoining: boolean, isForceRenamed: boolean) {\r\n\t\tif (!this.allowRenames || (!user.named && !isForceRenamed)) {\r\n\t\t\tif (!(user.id in this.playerTable) && !this.isSubGame) {\r\n\t\t\t\tuser.games.delete(this.roomid);\r\n\t\t\t\tuser.updateSearch();\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (!(oldUserid in this.playerTable)) return;\r\n\t\tif (!user.named) {\r\n\t\t\treturn this.onLeave(user, oldUserid);\r\n\t\t}\r\n\t\tthis.renamePlayer(user, oldUserid);\r\n\t}\r\n\r\n\t/**\r\n\t * Called when a user leaves the room. (i.e. when the user's last\r\n\t * connection leaves)\r\n\t */\r\n\tonLeave(user: User, oldUserid?: ID) {}\r\n\r\n\t/**\r\n\t * Called each time a connection joins a room (after onJoin if\r\n\t * applicable). By default, this is also called when connection\r\n\t * is updated in some way (such as by changing user or renaming).\r\n\t * If you don't want this behavior, override onUpdateConnection\r\n\t * and/or onRename.\r\n\t */\r\n\tonConnect(user: User, connection: Connection) {}\r\n\r\n\t/**\r\n\t * Called for each connection in a room that changes users by\r\n\t * merging into a different user. By default, runs the onConnect\r\n\t * handler.\r\n\t *\r\n\t * Player updates and an up-to-date report of what's going on in\r\n\t * the game should be sent during `onConnect`. You should rarely\r\n\t * need to handle the other events.\r\n\t */\r\n\tonUpdateConnection(user: User, connection: Connection) {\r\n\t\tthis.onConnect(user, connection);\r\n\t}\r\n\r\n\t/**\r\n\t * Called for every message a user sends while this game is active.\r\n\t * Return an error message to prevent the message from being sent,\r\n\t * an empty string to prevent it with no error message, or\r\n\t * `undefined` to let it through.\r\n\t */\r\n\tonChatMessage(message: string, user: User): string | void {}\r\n\r\n\t/**\r\n\t * Called for every message a user sends while this game is active.\r\n\t * Unlike onChatMessage, this function runs after the message has been added to the room's log.\r\n\t * Do not try to use this to block messages, use onChatMessage for that.\r\n\t */\r\n\tonLogMessage(message: string, user: User) {}\r\n}\r\n\r\n/**\r\n * globally Rooms.SimpleRoomGame\r\n *\r\n * A RoomGame without a custom player class. Gives a default implementation for makePlayer.\r\n */\r\nexport class SimpleRoomGame extends RoomGame {\r\n\tmakePlayer(user: User | string | null, ...rest: any[]): RoomGamePlayer {\r\n\t\tconst num = this.players.length ? this.players[this.players.length - 1].num : 1;\r\n\t\treturn new RoomGamePlayer(user, this, num);\r\n\t}\r\n}\r\n"], "mappings": "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", "names": [] }