"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|`).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|`
).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`${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 += `
`;
buf += ``;
for (const i of [0, null, 1]) {
if (i === null) {
buf += ` | `;
continue;
}
buf += import_lib.Utils.html`${this.players[i].name} | `;
}
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 += ` `;
buf += ` | `;
}
buf += `
`;
for (const i of [0, null, 1]) {
if (i === null) {
buf += ` vs | `;
continue;
}
const team = Teams.unpack(this.players[i].options.team || "");
if (!team || !Dex.formats.getRuleTable(this.format).has("teampreview")) {
buf += ``;
buf += ` `.repeat(3);
buf += ` `;
buf += ` `.repeat(3);
buf += ` | `;
continue;
}
const mirrorLeftPlayer = !i ? ' style="transform: scaleX(-1)"' : "";
buf += ``;
for (const [j, set] of team.entries()) {
if (j % 3 === 0 && j > 1)
buf += ` `;
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