"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var room_battle_bestof_exports = {}; __export(room_battle_bestof_exports, { BestOfGame: () => BestOfGame, BestOfPlayer: () => BestOfPlayer }); module.exports = __toCommonJS(room_battle_bestof_exports); var import_lib = require("../lib"); var import_room_game = require("./room-game"); const BEST_OF_IN_BETWEEN_TIME = 40; class BestOfPlayer extends import_room_game.RoomGamePlayer { constructor(user, game, num, options) { super(user, game, num); this.wins = 0; this.ready = null; this.dcAutoloseTime = null; this.options = { ...options, user: null }; } avatar() { let avatar = Users.get(this.id)?.avatar; if (!avatar || typeof avatar === "number") avatar = "unknownf"; const url = Chat.plugins.avatars?.Avatars.src(avatar) || `https://${Config.routes.client}/sprites/trainers/${avatar}.png`; return url; } updateReadyButton() { const user = this.getUser(); if (!user?.connected) return; this.dcAutoloseTime = null; const room = this.game.room; const battleRoom = this.game.games[this.game.games.length - 1]?.room; const gameNum = this.game.games.length + 1; if (this.ready === false) { const notification = `|tempnotify|choice|Next game|It's time for game ${gameNum} in your best-of-${this.game.bestOf}!`; if (battleRoom && user.inRooms.has(battleRoom.roomid)) { battleRoom.send(notification); } else { this.sendRoom(notification); } } else { const notification = `|tempnotifyoff|choice`; battleRoom?.sendUser(user, notification); this.sendRoom(notification); } if (this.ready === null) { const button2 = `|c|~|/uhtml controls,`; this.sendRoom(button2); battleRoom?.sendUser(user, button2); return; } const cmd = `/msgroom ${room.roomid},/confirmready`; const button = `|c|~|/uhtml controls,

Are you ready for game ${gameNum}, ${this.name}?

` + (this.ready ? ` – waiting for opponent...` : ``) + `

`; this.sendRoom(button); battleRoom?.sendUser(user, button); } } class BestOfGame extends import_room_game.RoomGame { constructor(room, options) { super(room); this.gameid = "bestof"; this.allowRenames = false; this.forcedSettings = {}; this.ties = 0; this.games = []; this.playerNum = 0; /** null = tie, undefined = not ended */ this.winner = void 0; /** when waiting between battles, this is the just-ended battle room, the one with the |tempnotify| */ this.waitingBattle = null; this.nextBattleTimerEnd = null; this.nextBattleTimer = null; /** Does NOT control bestof's own timer, which is always-on. Controls timers in sub-battles. */ this.needsTimer = false; this.score = null; this.gameid = "bestof"; this.format = Dex.formats.get(options.format); this.bestOf = Number(Dex.formats.getRuleTable(this.format).valueRules.get("bestof")); this.winThreshold = Math.floor(this.bestOf / 2) + 1; this.title = this.format.name; if (!toID(this.title).includes("bestof")) { this.title += ` (Best-of-${this.bestOf})`; } this.room.bestOf = this; this.options = { ...options, isBestOfSubBattle: true, parent: this.room, allowRenames: false, players: null }; for (const playerOpts of options.players) { this.addPlayer(playerOpts.user, playerOpts); } process.nextTick(() => this.nextGame()); } onConnect(user) { const player = this.playerTable[user.id]; player?.sendRoom("|cantleave|"); player?.updateReadyButton(); } makePlayer(user, options) { return new BestOfPlayer(user, this, ++this.playerNum, options); } addPlayer(user, options) { const player = super.addPlayer(user, options); if (!player) throw new Error(`Failed to make player ${user} in ${this.roomid}`); this.room.auth.set(user.id, Users.PLAYER_SYMBOL); return player; } checkPrivacySettings(options) { let inviteOnly = false; const privacySetter = /* @__PURE__ */ new Set([]); for (const p of options.players) { if (p.user) { if (p.inviteOnly) { inviteOnly = true; privacySetter.add(p.user.id); } else if (p.hidden) { privacySetter.add(p.user.id); } this.checkForcedUserSettings(p.user); } } if (privacySetter.size) { const room = this.room; if (this.forcedSettings.privacy) { room.setPrivate(false); room.settings.modjoin = null; room.add(`|raw|
This best-of set is required to be public due to a player having a name starting with '${this.forcedSettings.privacy}'.
`); } else if (!options.tour || room.tour?.allowModjoin) { room.setPrivate("hidden"); if (inviteOnly) room.settings.modjoin = "%"; room.privacySetter = privacySetter; if (inviteOnly) { room.settings.modjoin = "%"; room.add(`|raw|
This best-of set is invite-only!
Users must be invited with /invite (or be staff) to join
`); } } } } checkForcedUserSettings(user) { this.forcedSettings = { modchat: this.forcedSettings.modchat || Rooms.RoomBattle.battleForcedSetting(user, "modchat"), privacy: this.forcedSettings.privacy || Rooms.RoomBattle.battleForcedSetting(user, "privacy") }; if (this.players.some((p) => p.getUser()?.battleSettings.special) || this.options.rated && this.forcedSettings.modchat) { this.room.settings.modchat = "\u2606"; } } setPrivacyOfGames(privacy) { for (let i = 0; i < this.games.length; i++) { const room = this.games[i].room; const prevRoom = this.games[i - 1]?.room; const gameNum = i + 1; room.setPrivate(privacy); this.room.add(`|uhtmlchange|game${gameNum}|${room.title}`); room.add(`|uhtmlchange|bestof|

Game ${gameNum} of a best-of-${this.bestOf}

`).update(); if (prevRoom) { prevRoom.add(`|uhtmlchange|next|Next: Game ${gameNum} of ${this.bestOf}`).update(); } } this.updateDisplay(); } clearWaiting() { this.waitingBattle = null; for (const player of this.players) { player.ready = null; player.updateReadyButton(); } if (this.nextBattleTimer) { clearInterval(this.nextBattleTimer); this.nextBattleTimerEnd = null; } this.nextBattleTimerEnd = null; this.nextBattleTimer = null; } getOptions() { const players = this.players.map((player) => ({ ...player.options, user: player.getUser() })); if (players.some((p) => !p.user)) { return null; } return { ...this.options, players }; } nextGame() { const prevBattleRoom = this.waitingBattle; if (!prevBattleRoom && this.games.length) return; this.clearWaiting(); const options = this.getOptions(); if (!options) { for (const p of this.players) { if (!p.getUser()) { this.forfeitPlayer(p, ` lost by being unavailable at the start of a game.`); return; } } throw new Error(`Failed to get options for ${this.roomid}`); } const battleRoom = Rooms.createBattle(options); if (!battleRoom) throw new Error("Failed to create battle for " + this.title); this.games.push({ room: battleRoom, winner: void 0, rated: battleRoom.rated }); battleRoom.rated = 0; if (this.needsTimer) { battleRoom.battle?.timer.start(); } const gameNum = this.games.length; const p1 = this.players[0]; const p2 = this.players[1]; battleRoom.add( import_lib.Utils.html`|html|` + `
${p1.name}${p2.name}
${this.renderWins(p1)}${this.renderWins(p2)}
` ); battleRoom.add( `|uhtml|bestof|

Game ${gameNum} of a best-of-${this.bestOf}

` ).update(); this.room.add(`|html|

Game ${gameNum}

`); this.room.add(import_lib.Utils.html`|uhtml|game${gameNum}|${battleRoom.title}`); this.updateDisplay(); prevBattleRoom?.add( `|uhtml|next|Next: Game ${gameNum} of ${this.bestOf}` ).update(); } renderWins(player) { const wins = this.games.filter((game) => game.winner === player).length; const winBuf = ` `.repeat(wins); const restBuf = ` `.repeat(this.winThreshold - wins); return player.num === 1 ? winBuf + restBuf : restBuf + winBuf; } updateDisplay() { const p1name = this.players[0].name; const p2name = this.players[1].name; let buf = import_lib.Utils.html`
${p1name} and ${p2name}'s Best-of-${this.bestOf} progress:
`; buf += ""; for (const p of this.players) { buf += import_lib.Utils.html``; } buf += `
${p.name}: `; for (let i = 0; i < this.bestOf; i++) { if (this.games[i]?.winner === p) { buf += ``; } else { buf += ``; } if (i !== this.bestOf - 1) { buf += ` `; } } buf += `


`; buf += ``; for (const i of [0, null, 1]) { if (i === null) { buf += ``; continue; } buf += import_lib.Utils.html``; } buf += ``; for (const i of [0, null, 1]) { if (i === null) { buf += ``; continue; } const p = this.players[i]; const mirrorLeftPlayer = !i ? ' style="transform: scaleX(-1)"' : ""; buf += ``; } buf += ``; for (const i of [0, null, 1]) { if (i === null) { buf += ``; continue; } const team = Teams.unpack(this.players[i].options.team || ""); if (!team || !Dex.formats.getRuleTable(this.format).has("teampreview")) { buf += ``; continue; } const mirrorLeftPlayer = !i ? ' style="transform: scaleX(-1)"' : ""; buf += ``; } buf += `
${this.players[i].name}
`; buf += ``; buf += `
vs `; buf += ` `.repeat(3); buf += `
`; buf += ` `.repeat(3); buf += `
`; for (const [j, set] of team.entries()) { if (j % 3 === 0 && j > 1) buf += `
`; buf += ``; } buf += `
`; this.room.add(`|fieldhtml|
${buf}
`); buf = this.games.map(({ room, winner }, index) => { let progress = `being played`; if (winner) progress = import_lib.Utils.html`won by ${winner.name}`; if (winner === null) progress = `tied`; return import_lib.Utils.html`

Game ${index + 1}: ${progress}

`; }).join(""); if (this.winner) { buf += import_lib.Utils.html`

${this.winner.name} won!

`; } else if (this.winner === null) { buf += `

The battle was tied.

`; } this.room.add(`|controlshtml|
${buf}
`); this.room.update(); } startTimer() { this.needsTimer = true; for (const { room } of this.games) { room.battle?.timer.start(); } } onBattleWin(room, winnerid) { if (this.ended) return; const winner = winnerid ? this.playerTable[winnerid] : null; this.games[this.games.length - 1].winner = winner; if (winner) { winner.wins++; const loserPlayer = room.battle.players.find((p) => p.num !== winner.num); if (loserPlayer && loserPlayer.dcSecondsLeft <= 0) { return this.forfeit(loserPlayer.name, ` lost the series due to inactivity.`); } this.room.add(import_lib.Utils.html`|html|${winner.name} won game ${this.games.length}!`).update(); if (winner.wins >= this.winThreshold) { return this.end(winner.id); } } else { this.ties++; this.winThreshold = Math.floor((this.bestOf - this.ties) / 2) + 1; this.room.add(`|html|Game ${this.games.length} was a tie.`).update(); } if (this.games.length >= this.bestOf) { return this.end(""); } this.promptNextGame(room); } promptNextGame(room) { if (!room.battle || this.winner) return; this.updateDisplay(); this.waitingBattle = room; const now = Date.now(); this.nextBattleTimerEnd = now + BEST_OF_IN_BETWEEN_TIME * 1e3; for (const player of this.players) { player.ready = false; const dcAutoloseTime = now + room.battle.players[player.num - 1].dcSecondsLeft * 1e3; if (dcAutoloseTime < this.nextBattleTimerEnd) { player.dcAutoloseTime = dcAutoloseTime; } player.updateReadyButton(); } this.nextBattleTimer = setInterval(() => this.pokeNextBattleTimer(), 1e4); } pokeNextBattleTimer() { if (!this.nextBattleTimerEnd || !this.nextBattleTimer) return; if (Date.now() >= this.nextBattleTimerEnd) { return this.nextGame(); } for (const p of this.players) { if (!p.ready) { const now = Date.now() - 100; if (p.dcAutoloseTime && now > p.dcAutoloseTime) { return this.forfeit(p.name, ` lost the series due to inactivity.`); } const message = p.dcAutoloseTime ? `|inactive|${p.name} has ${Chat.toDurationString(p.dcAutoloseTime - now)} to reconnect!` : `|inactive|${p.name} has ${Chat.toDurationString(this.nextBattleTimerEnd - now)} to confirm battle start!`; this.waitingBattle?.add(message); this.room.add(message); } } this.waitingBattle?.update(); this.room.update(); } confirmReady(user) { const player = this.playerTable[user.id]; if (!player) { throw new Chat.ErrorMessage("You aren't a player in this best-of set."); } if (!this.waitingBattle) { throw new Chat.ErrorMessage("The battle is not currently waiting for ready confirmation."); } player.ready = true; player.updateReadyButton(); const readyMsg = `||${player.name} is ready for game ${this.games.length + 1}.`; this.waitingBattle.add(readyMsg).update(); this.room.add(readyMsg).update(); if (this.players.every((p) => p.ready)) { this.nextGame(); } } setEnded() { this.clearWaiting(); super.setEnded(); } end(winnerid) { if (this.ended) return; this.setEnded(); this.room.add(`|allowleave|`).update(); const winner = winnerid ? this.playerTable[winnerid] : null; this.winner = winner; if (winner) { this.room.add(`|win|${winner.name}`); } else { this.room.add(`|tie`); } this.updateDisplay(); this.room.update(); const p1 = this.players[0]; const p2 = this.players[1]; this.score = this.players.map((p) => p.wins); this.room.parent?.game?.onBattleWin?.(this.room, winnerid); let p1score = 0.5; if (winner === p1) { p1score = 1; } else if (winner === p2) { p1score = 0; } const { rated, room } = this.games[this.games.length - 1]; if (rated) { void room.battle?.updateLadder(p1score, winnerid); } } forfeit(user, message = "") { const userid = typeof user !== "string" ? user.id : toID(user); const loser = this.playerTable[userid]; if (loser) this.forfeitPlayer(loser, message); } forfeitPlayer(loser, message = "") { if (this.ended || this.winner) return false; this.winner = this.players.find((p) => p !== loser); this.room.add(`||${loser.name}${message || " forfeited."}`); this.end(this.winner.id); const lastBattle = this.games[this.games.length - 1].room.battle; if (lastBattle && !lastBattle.ended) lastBattle.forfeit(loser.id, message); return true; } destroy() { this.setEnded(); for (const { room } of this.games) room.expire(); this.games = []; for (const p of this.players) p.destroy(); this.players = []; this.playerTable = {}; this.winner = null; } } //# sourceMappingURL=room-battle-bestof.js.map