"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 tournaments_exports = {};
__export(tournaments_exports, {
  Tournament: () => Tournament,
  TournamentPlayer: () => TournamentPlayer,
  Tournaments: () => Tournaments
});
module.exports = __toCommonJS(tournaments_exports);
var import_generator_elimination = require("./generator-elimination");
var import_generator_round_robin = require("./generator-round-robin");
var import_lib = require("../../lib");
var import_prng = require("../../sim/prng");
const BRACKET_MINIMUM_UPDATE_INTERVAL = 2 * 1e3;
const AUTO_DISQUALIFY_WARNING_TIMEOUT = 30 * 1e3;
const MAX_AUTO_DISQUALIFY_TIMEOUT = 60 * 60 * 1e3;
const AUTO_START_MINIMUM_TIMEOUT = 30 * 1e3;
const MAX_REASON_LENGTH = 300;
const MAX_CUSTOM_NAME_LENGTH = 100;
const TOURBAN_DURATION = 14 * 24 * 60 * 60 * 1e3;
Punishments.addRoomPunishmentType({
  type: "TOURBAN",
  desc: "banned from tournaments"
});
const TournamentGenerators = {
  __proto__: null,
  roundrobin: import_generator_round_robin.RoundRobin,
  elimination: import_generator_elimination.Elimination
};
function usersToNames(users) {
  return users.map((user) => user.name);
}
class TournamentPlayer extends Rooms.RoomGamePlayer {
  constructor(user, game, num) {
    super(user, game, num);
    this.availableMatches = /* @__PURE__ */ new Set();
    this.isBusy = false;
    this.inProgressMatch = null;
    this.pendingChallenge = null;
    this.isDisqualified = false;
    this.isEliminated = false;
    this.autoDisqualifyWarned = false;
    this.lastActionTime = 0;
    this.wins = 0;
    this.losses = 0;
    this.games = 0;
    this.score = 0;
  }
}
class Tournament extends Rooms.RoomGame {
  constructor(room, format, generator, playerCap, isRated, name) {
    super(room);
    this.gameid = "tournament";
    const formatId = toID(format);
    this.title = format.name + " tournament";
    this.isTournament = true;
    this.completedMatches = /* @__PURE__ */ new Set();
    this.allowRenames = false;
    this.playerCap = (playerCap ? parseInt(playerCap) : Config.tourdefaultplayercap) || 0;
    this.baseFormat = formatId;
    this.fullFormat = formatId;
    this.name = name || formatId;
    this.customRules = [];
    this.generator = generator;
    this.isRated = isRated;
    this.allowScouting = true;
    this.allowModjoin = false;
    this.autoconfirmedOnly = false;
    this.forceTimer = false;
    this.autostartcap = false;
    this.forcePublic = false;
    if (Config.tourdefaultplayercap && this.playerCap > Config.tourdefaultplayercap) {
      Monitor.log(`[TourMonitor] Room ${room.roomid} starting a tour over default cap (${this.playerCap})`);
    }
    this.isTournamentStarted = false;
    this.isBracketInvalidated = true;
    this.lastBracketUpdate = 0;
    this.bracketUpdateTimer = null;
    this.bracketCache = null;
    this.isAvailableMatchesInvalidated = true;
    this.availableMatchesCache = { challenges: /* @__PURE__ */ new Map(), challengeBys: /* @__PURE__ */ new Map() };
    this.autoDisqualifyTimeout = Infinity;
    this.autoDisqualifyTimer = null;
    this.autoStartTimeout = Infinity;
    this.autoStartTimer = null;
    room.add(`|tournament|create|${this.baseFormat}|${generator.name}|${this.playerCap}${this.name === this.baseFormat ? `` : `|${this.name}`}`);
    const update = {
      format: this.name,
      generator: generator.name,
      playerCap: this.playerCap,
      isStarted: false,
      isJoined: false
    };
    if (this.name !== this.baseFormat)
      update.teambuilderFormat = this.baseFormat;
    room.send(`|tournament|update|${JSON.stringify(update)}`);
    this.update();
  }
  destroy() {
    this.forceEnd();
  }
  remove() {
    if (this.autoStartTimer)
      clearTimeout(this.autoStartTimer);
    if (this.autoDisqualifyTimer)
      clearTimeout(this.autoDisqualifyTimer);
    for (const roomid of this.completedMatches) {
      const room = Rooms.get(roomid);
      if (room)
        room.tour = null;
    }
    this.setEnded();
    this.room.game = null;
  }
  getRemainingPlayers() {
    return this.players.filter((player) => !player.isDisqualified && !player.isEliminated);
  }
  setGenerator(generator, output) {
    if (this.isTournamentStarted) {
      output.sendReply("|tournament|error|BracketFrozen");
      return;
    }
    this.generator = generator;
    this.room.send(`|tournament|update|${JSON.stringify({ generator: generator.name })}`);
    this.isBracketInvalidated = true;
    this.update();
    return true;
  }
  setCustomRules(rules) {
    let format;
    try {
      const tryFormat = Dex.formats.validate(`${this.baseFormat}@@@${rules}`);
      format = Dex.formats.get(tryFormat, true);
      if (format.team) {
        const testTeamSeed = import_prng.PRNG.generateSeed();
        const testTeamGenerator = Teams.getGenerator(format, testTeamSeed);
        testTeamGenerator.getTeam();
      }
      this.fullFormat = tryFormat;
    } catch (e) {
      throw new Chat.ErrorMessage(`Custom rule error: ${e.message}`);
    }
    const customRules = format.customRules;
    if (!customRules) {
      throw new Chat.ErrorMessage(`Invalid rules.`);
    }
    this.customRules = customRules;
    if (this.name === this.baseFormat) {
      this.name = this.getDefaultCustomName();
      this.room.send(`|tournament|update|${JSON.stringify({ format: this.name })}`);
      this.update();
    }
    return true;
  }
  getCustomRules() {
    const bans = [];
    const unbans = [];
    const restrictions = [];
    const addedRules = [];
    const removedRules = [];
    for (const ban of this.customRules) {
      const charAt0 = ban.charAt(0);
      if (charAt0 === "+") {
        unbans.push(ban.substr(1));
      } else if (charAt0 === "-") {
        bans.push(ban.substr(1));
      } else if (charAt0 === "*") {
        restrictions.push(ban.substr(1));
      } else if (charAt0 === "!") {
        removedRules.push(ban.substr(1));
      } else {
        addedRules.push(ban);
      }
    }
    const html = [];
    if (bans.length)
      html.push(import_lib.Utils.html`<b>Added bans</b> - ${bans.join(", ")}`);
    if (unbans.length)
      html.push(import_lib.Utils.html`<b>Removed bans</b> - ${unbans.join(", ")}`);
    if (restrictions.length)
      html.push(import_lib.Utils.html`<b>Added restrictions</b> - ${restrictions.join(", ")}`);
    if (addedRules.length)
      html.push(import_lib.Utils.html`<b>Added rules</b> - ${addedRules.join(", ")}`);
    if (removedRules.length)
      html.push(import_lib.Utils.html`<b>Removed rules</b> - ${removedRules.join(", ")}`);
    return html.join(`<br />`);
  }
  forceEnd() {
    if (this.isTournamentStarted) {
      if (this.autoDisqualifyTimer)
        clearTimeout(this.autoDisqualifyTimer);
      for (const player of this.players) {
        const match = player.inProgressMatch;
        if (match) {
          match.room.tour = null;
          match.room.setParent(null);
          match.room.addRaw(`<div class="broadcast-red"><b>The tournament was forcefully ended.</b><br />You can finish playing, but this battle is no longer considered a tournament battle.</div>`);
        }
      }
    }
    this.room.add("|tournament|forceend");
    this.remove();
  }
  updateFor(targetUser, connection) {
    if (!connection)
      connection = targetUser;
    if (this.ended)
      return;
    if (!this.bracketUpdateTimer && this.isBracketInvalidated || this.isTournamentStarted && this.isAvailableMatchesInvalidated) {
      this.room.add(
        "Error: update() called with a target user when data invalidated: " + (!this.bracketUpdateTimer && this.isBracketInvalidated) + ", " + (this.isTournamentStarted && this.isAvailableMatchesInvalidated) + "; Please report this to an admin."
      );
      return;
    }
    const possiblePlayer = this.playerTable[targetUser.id];
    let isJoined = false;
    if (possiblePlayer) {
      if (this.generator.name.includes("Elimination")) {
        isJoined = !possiblePlayer.isEliminated && !possiblePlayer.isDisqualified;
      } else if (this.generator.name.includes("Round Robin")) {
        if (possiblePlayer.isDisqualified) {
          isJoined = !possiblePlayer.isDisqualified;
        } else if (this.generator?.matchesPerPlayer) {
          isJoined = possiblePlayer.games !== this.generator.matchesPerPlayer;
        } else if (!this.isTournamentStarted) {
          isJoined = true;
        }
      } else {
        isJoined = true;
      }
    }
    const update = {
      format: this.name,
      generator: this.generator.name,
      isStarted: this.isTournamentStarted,
      isJoined,
      bracketData: this.bracketCache
    };
    if (this.name !== this.baseFormat)
      update.teambuilderFormat = this.baseFormat;
    connection.sendTo(this.room, `|tournament|update|${JSON.stringify(update)}`);
    if (this.isTournamentStarted && isJoined) {
      const update2 = {
        challenges: usersToNames(this.availableMatchesCache.challenges.get(this.playerTable[targetUser.id])),
        challengeBys: usersToNames(this.availableMatchesCache.challengeBys.get(this.playerTable[targetUser.id]))
      };
      connection.sendTo(this.room, `|tournament|update|${JSON.stringify(update2)}`);
      const pendingChallenge = this.playerTable[targetUser.id].pendingChallenge;
      if (pendingChallenge) {
        if (pendingChallenge.to) {
          connection.sendTo(this.room, `|tournament|update|${JSON.stringify({ challenging: pendingChallenge.to.name })}`);
        } else if (pendingChallenge.from) {
          connection.sendTo(this.room, `|tournament|update|${JSON.stringify({ challenged: pendingChallenge.from.name })}`);
        }
      }
    }
    connection.sendTo(this.room, "|tournament|updateEnd");
  }
  update() {
    if (this.ended)
      return;
    if (this.isBracketInvalidated) {
      if (Date.now() < this.lastBracketUpdate + BRACKET_MINIMUM_UPDATE_INTERVAL) {
        if (this.bracketUpdateTimer)
          clearTimeout(this.bracketUpdateTimer);
        this.bracketUpdateTimer = setTimeout(() => {
          this.bracketUpdateTimer = null;
          this.update();
        }, BRACKET_MINIMUM_UPDATE_INTERVAL);
      } else {
        this.lastBracketUpdate = Date.now();
        this.bracketCache = this.getBracketData();
        this.isBracketInvalidated = false;
        this.room.send(`|tournament|update|${JSON.stringify({ bracketData: this.bracketCache })}`);
      }
    }
    if (this.isTournamentStarted && this.isAvailableMatchesInvalidated) {
      this.availableMatchesCache = this.getAvailableMatches();
      this.isAvailableMatchesInvalidated = false;
      for (const [player, opponents] of this.availableMatchesCache.challenges) {
        player.sendRoom(`|tournament|update|${JSON.stringify({ challenges: usersToNames(opponents) })}`);
      }
      for (const [player, opponents] of this.availableMatchesCache.challengeBys) {
        player.sendRoom(`|tournament|update|${JSON.stringify({ challengeBys: usersToNames(opponents) })}`);
      }
    }
    this.room.send("|tournament|updateEnd");
  }
  static checkBanned(room, user) {
    return Punishments.hasRoomPunishType(room, toID(user), "TOURBAN");
  }
  removeBannedUser(userid) {
    userid = toID(userid);
    if (!(userid in this.playerTable))
      return;
    if (this.isTournamentStarted) {
      const player = this.playerTable[userid];
      if (!player.isDisqualified) {
        this.disqualifyUser(userid);
      }
    } else {
      this.removeUser(userid);
    }
    this.room.update();
  }
  addUser(user, output) {
    if (!user.named) {
      output.sendReply("|tournament|error|UserNotNamed");
      return;
    }
    if (user.id in this.playerTable) {
      output.sendReply("|tournament|error|UserAlreadyAdded");
      return;
    }
    if (this.playerCap && this.playerCount >= this.playerCap) {
      output.sendReply("|tournament|error|Full");
      return;
    }
    if (Tournament.checkBanned(this.room, user) || Punishments.isBattleBanned(user) || user.namelocked) {
      output.sendReply("|tournament|error|Banned");
      return;
    }
    if ((this.room.settings.tournaments?.autoconfirmedOnly || this.autoconfirmedOnly) && !user.autoconfirmed && !user.trusted) {
      user.popup("Signups for tournaments are only available for autoconfirmed users in this room.");
      return;
    }
    const gameCount = user.games.size;
    if (gameCount > 4) {
      output.errorReply("Due to high load, you are limited to 4 games at the same time.");
      return;
    }
    if (!Config.noipchecks) {
      for (const otherPlayer of this.players) {
        if (!otherPlayer)
          continue;
        const otherUser = Users.get(otherPlayer.id);
        if (otherUser && otherUser.latestIp === user.latestIp) {
          output.sendReply("|tournament|error|AltUserAlreadyAdded");
          return;
        }
      }
    }
    if (this.isTournamentStarted) {
      output.sendReply(`|tournament|error|BracketFrozen`);
      return;
    }
    const player = this.addPlayer(user);
    if (!player)
      throw new Error("Failed to add player.");
    this.playerTable[user.id] = player;
    this.room.add(`|tournament|join|${user.name}`);
    user.sendTo(this.room, '|tournament|update|{"isJoined":true}');
    this.isBracketInvalidated = true;
    this.update();
    if (this.playerCount === this.playerCap) {
      if (this.autostartcap === true) {
        this.startTournament(output);
      } else {
        this.room.add("The tournament is now full.");
      }
    }
  }
  makePlayer(user) {
    const num = this.players.length ? this.players[this.players.length - 1].num : 1;
    return new TournamentPlayer(user, this, num);
  }
  removeUser(userid, output) {
    const player = this.playerTable[userid];
    if (!player) {
      if (output)
        output.sendReply("|tournament|error|UserNotAdded");
      return;
    }
    this.removePlayer(player);
    const user = Users.get(userid);
    this.room.add(`|tournament|leave|${user ? user.name : userid}`);
    if (user)
      user.sendTo(this.room, '|tournament|update|{"isJoined":false}');
    this.isBracketInvalidated = true;
    this.update();
  }
  replaceUser(user, replacementUser, output) {
    if (!this.isTournamentStarted) {
      output.sendReply("|tournament|error|NotStarted");
      return;
    }
    if (!(user.id in this.playerTable)) {
      output.errorReply(`${user.name} isn't in the tournament.`);
      return;
    }
    if (!replacementUser.named) {
      output.errorReply(`${replacementUser.name} must be named to join the tournament.`);
      return;
    }
    if (replacementUser.id in this.playerTable) {
      output.errorReply(`${replacementUser.name} is already in the tournament.`);
      return;
    }
    if (Tournament.checkBanned(this.room, replacementUser) || Punishments.isBattleBanned(replacementUser) || replacementUser.namelocked) {
      output.errorReply(`${replacementUser.name} is banned from joining tournaments.`);
      return;
    }
    if ((this.room.settings.tournaments?.autoconfirmedOnly || this.autoconfirmedOnly) && !user.autoconfirmed) {
      user.popup("Signups for tournaments are only available for autoconfirmed users in this room.");
      return;
    }
    if (!Config.noipchecks) {
      for (const otherPlayer of this.players) {
        if (!otherPlayer)
          continue;
        const otherUser = Users.get(otherPlayer.id);
        if (otherUser && otherUser.latestIp === replacementUser.latestIp && replacementUser.latestIp !== user.latestIp) {
          output.errorReply(`${replacementUser.name} already has an alt in the tournament.`);
          return;
        }
      }
    }
    if (!(replacementUser.id in this.room.users)) {
      output.errorReply(`${replacementUser.name} is not in this room (${this.room.title}).`);
      return;
    }
    const player = this.playerTable[user.id];
    if (player.pendingChallenge) {
      this.cancelChallenge(user, output);
    }
    this.setPlayerUser(player, replacementUser);
    let matchPlayer = null;
    if (player.inProgressMatch) {
      matchPlayer = player;
    } else {
      for (const p of this.players) {
        if (p.inProgressMatch && p.inProgressMatch.to === player) {
          matchPlayer = p;
          break;
        }
      }
    }
    if (matchPlayer?.inProgressMatch) {
      matchPlayer.inProgressMatch.to.isBusy = false;
      matchPlayer.isBusy = false;
      matchPlayer.inProgressMatch.room.addRaw(
        import_lib.Utils.html`<div class="broadcast-red"><b>${user.name} is no longer in the tournament.<br />` + `You can finish playing, but this battle is no longer considered a tournament battle.</div>`
      ).update();
      matchPlayer.inProgressMatch.room.setParent(null);
      this.completedMatches.add(matchPlayer.inProgressMatch.room.roomid);
      matchPlayer.inProgressMatch = null;
    }
    this.isAvailableMatchesInvalidated = true;
    this.isBracketInvalidated = true;
    this.update();
    this.updateFor(user);
    this.updateFor(replacementUser);
    const challengePlayer = player.pendingChallenge && (player.pendingChallenge.from || player.pendingChallenge.to);
    if (challengePlayer) {
      const challengeUser = Users.getExact(challengePlayer.id);
      if (challengeUser)
        this.updateFor(challengeUser);
    }
    this.room.add(`|tournament|replace|${user.name}|${replacementUser.name}`);
    return true;
  }
  getBracketData() {
    let data;
    if (!this.isTournamentStarted) {
      data = this.generator.getPendingBracketData(this.players);
    } else {
      data = this.generator.getBracketData();
    }
    if (data.type === "tree") {
      if (!data.rootNode) {
        data.users = usersToNames(this.players.sort());
        return data;
      }
      const queue = [data.rootNode];
      while (queue.length > 0) {
        const node = queue.shift();
        if (node.state === "available") {
          const pendingChallenge = node.children[0].team.pendingChallenge;
          if (pendingChallenge && node.children[1].team === pendingChallenge.to) {
            node.state = "challenging";
          }
          const inProgressMatch = node.children[0].team.inProgressMatch;
          if (inProgressMatch && node.children[1].team === inProgressMatch.to) {
            node.state = "inprogress";
            node.room = inProgressMatch.room.roomid;
          }
        }
        if (node.team && typeof node.team !== "string") {
          node.team = node.team.name;
        }
        if (node.children) {
          for (const child of node.children) {
            queue.push(child);
          }
        }
      }
    } else if (data.type === "table") {
      if (this.isTournamentStarted) {
        for (const [r, row] of data.tableContents.entries()) {
          const pendingChallenge = data.tableHeaders.rows[r].pendingChallenge;
          const inProgressMatch = data.tableHeaders.rows[r].inProgressMatch;
          if (pendingChallenge || inProgressMatch) {
            for (const [c, cell] of row.entries()) {
              if (!cell)
                continue;
              if (pendingChallenge && data.tableHeaders.cols[c] === pendingChallenge.to) {
                cell.state = "challenging";
              }
              if (inProgressMatch && data.tableHeaders.cols[c] === inProgressMatch.to) {
                cell.state = "inprogress";
                cell.room = inProgressMatch.room.roomid;
              }
            }
          }
        }
      }
      data.tableHeaders.cols = usersToNames(data.tableHeaders.cols);
      data.tableHeaders.rows = usersToNames(data.tableHeaders.rows);
    }
    return data;
  }
  startTournament(output, isAutostart) {
    if (this.isTournamentStarted) {
      output.sendReply("|tournament|error|AlreadyStarted");
      return false;
    }
    if (this.players.length < 2) {
      if (isAutostart) {
        this.room.send("|tournament|error|NotEnoughUsers");
        this.forceEnd();
        this.room.update();
        output.modlog("TOUR END");
      } else {
        output.sendReply("|tournament|error|NotEnoughUsers");
      }
      return false;
    }
    this.generator.freezeBracket(this.players);
    const now = Date.now();
    for (const user of this.players) {
      user.lastActionTime = now;
    }
    this.isTournamentStarted = true;
    if (this.autoStartTimer)
      clearTimeout(this.autoStartTimer);
    if (this.autoDisqualifyTimeout !== Infinity) {
      this.autoDisqualifyTimer = setTimeout(() => this.runAutoDisqualify(), this.autoDisqualifyTimeout);
    }
    this.isBracketInvalidated = true;
    this.room.add(`|tournament|start|${this.players.length}`);
    output.modlog("TOUR START", null, `${this.players.length} players`);
    this.room.send('|tournament|update|{"isStarted":true}');
    this.update();
    return true;
  }
  getAvailableMatches() {
    const matches = this.generator.getAvailableMatches();
    if (typeof matches === "string")
      throw new Error(`Error from getAvailableMatches(): ${matches}`);
    const challenges = /* @__PURE__ */ new Map();
    const challengeBys = /* @__PURE__ */ new Map();
    const oldAvailableMatches = /* @__PURE__ */ new Map();
    for (const user of this.players) {
      challenges.set(user, []);
      challengeBys.set(user, []);
      let oldAvailableMatch = false;
      const availableMatches = user.availableMatches;
      if (availableMatches.size) {
        oldAvailableMatch = true;
        availableMatches.clear();
      }
      oldAvailableMatches.set(user, oldAvailableMatch);
    }
    for (const match of matches) {
      challenges.get(match[0]).push(match[1]);
      challengeBys.get(match[1]).push(match[0]);
      match[0].availableMatches.add(match[1]);
    }
    const now = Date.now();
    for (const player of this.players) {
      if (oldAvailableMatches.get(player))
        continue;
      if (player.availableMatches.size)
        player.lastActionTime = now;
    }
    return {
      challenges,
      challengeBys
    };
  }
  disqualifyUser(userid, output = null, reason = null, isSelfDQ = false) {
    const user = Users.get(userid);
    let sendReply;
    if (output) {
      sendReply = (msg) => output.sendReply(msg);
    } else if (user) {
      sendReply = (msg) => user.sendTo(this.roomid, msg);
    } else {
      sendReply = () => {
      };
    }
    if (!this.isTournamentStarted) {
      sendReply("|tournament|error|NotStarted");
      return false;
    }
    if (!(userid in this.playerTable)) {
      sendReply(`|tournament|error|UserNotAdded|${userid}`);
      return false;
    }
    const player = this.playerTable[userid];
    if (player.isDisqualified) {
      sendReply(`|tournament|error|AlreadyDisqualified|${userid}`);
      return false;
    }
    player.isDisqualified = true;
    const error = this.generator.disqualifyUser(player);
    if (error) {
      sendReply(`|tournament|error|${error}`);
      return false;
    }
    player.isBusy = false;
    const challenge = player.pendingChallenge;
    if (challenge) {
      player.pendingChallenge = null;
      if (challenge.to) {
        challenge.to.isBusy = false;
        challenge.to.pendingChallenge = null;
        challenge.to.sendRoom('|tournament|update|{"challenged":null}');
      } else if (challenge.from) {
        challenge.from.isBusy = false;
        challenge.from.pendingChallenge = null;
        challenge.from.sendRoom('|tournament|update|{"challenging":null}');
      }
    }
    const matchFrom = player.inProgressMatch;
    if (matchFrom) {
      matchFrom.to.isBusy = false;
      player.inProgressMatch = null;
      matchFrom.room.setParent(null);
      this.completedMatches.add(matchFrom.room.roomid);
      matchFrom.room.game?.forfeit?.(player.name);
    }
    let matchTo = null;
    for (const playerFrom of this.players) {
      const match = playerFrom.inProgressMatch;
      if (match && match.to === player)
        matchTo = playerFrom;
    }
    if (matchTo) {
      matchTo.isBusy = false;
      const matchRoom = matchTo.inProgressMatch.room;
      matchRoom.setParent(null);
      this.completedMatches.add(matchRoom.roomid);
      if (matchRoom.game)
        matchRoom.game.forfeit?.(player.id);
      matchTo.inProgressMatch = null;
    }
    if (isSelfDQ) {
      this.room.add(`|tournament|leave|${player.name}`);
    } else {
      this.room.add(`|tournament|disqualify|${player.name}`);
    }
    if (user) {
      user.sendTo(this.room, '|tournament|update|{"isJoined":false}');
      user.popup(`|modal|You have been disqualified from the tournament in ${this.room.title}${reason ? `:

${reason}` : `.`}`);
    }
    this.isBracketInvalidated = true;
    this.isAvailableMatchesInvalidated = true;
    if (this.generator.isTournamentEnded()) {
      this.onTournamentEnd();
    } else {
      this.update();
    }
    return true;
  }
  setAutoStartTimeout(timeout, output) {
    if (this.isTournamentStarted) {
      output.sendReply("|tournament|error|AlreadyStarted");
      return false;
    }
    if (timeout < AUTO_START_MINIMUM_TIMEOUT || isNaN(timeout)) {
      output.sendReply("|tournament|error|InvalidAutoStartTimeout");
      return false;
    }
    if (this.autoStartTimer)
      clearTimeout(this.autoStartTimer);
    if (timeout === Infinity) {
      this.room.add("|tournament|autostart|off");
    } else {
      this.autoStartTimer = setTimeout(() => this.startTournament(output, true), timeout);
      this.room.add(`|tournament|autostart|on|${timeout}`);
    }
    this.autoStartTimeout = timeout;
    return true;
  }
  setAutoDisqualifyTimeout(timeout, output) {
    if (isNaN(timeout) || timeout < AUTO_DISQUALIFY_WARNING_TIMEOUT || timeout > MAX_AUTO_DISQUALIFY_TIMEOUT && timeout !== Infinity) {
      output.sendReply("|tournament|error|InvalidAutoDisqualifyTimeout");
      return false;
    }
    this.autoDisqualifyTimeout = timeout;
    if (this.autoDisqualifyTimeout === Infinity) {
      this.room.add("|tournament|autodq|off");
      if (this.autoDisqualifyTimer)
        clearTimeout(this.autoDisqualifyTimer);
      for (const player of this.players)
        player.autoDisqualifyWarned = false;
    } else {
      this.room.add(`|tournament|autodq|on|${this.autoDisqualifyTimeout}`);
      if (this.isTournamentStarted)
        this.runAutoDisqualify();
    }
    return true;
  }
  runAutoDisqualify(output) {
    if (!this.isTournamentStarted) {
      if (output)
        output.sendReply("|tournament|error|NotStarted");
      return false;
    }
    if (this.autoDisqualifyTimer)
      clearTimeout(this.autoDisqualifyTimer);
    const now = Date.now();
    for (const player of this.players) {
      const time = player.lastActionTime;
      let availableMatches = false;
      if (player.availableMatches.size)
        availableMatches = true;
      const pendingChallenge = player.pendingChallenge;
      if (!availableMatches && !pendingChallenge) {
        player.autoDisqualifyWarned = false;
        continue;
      }
      if (pendingChallenge?.to)
        continue;
      if (now > time + this.autoDisqualifyTimeout && player.autoDisqualifyWarned) {
        let reason;
        if (pendingChallenge?.from) {
          reason = "You failed to accept your opponent's challenge in time.";
        } else {
          reason = "You failed to challenge your opponent in time.";
        }
        this.disqualifyUser(player.id, output, reason);
        this.room.update();
      } else if (now > time + this.autoDisqualifyTimeout - AUTO_DISQUALIFY_WARNING_TIMEOUT) {
        if (player.autoDisqualifyWarned)
          continue;
        let remainingTime = this.autoDisqualifyTimeout - now + time;
        if (remainingTime <= 0) {
          remainingTime = AUTO_DISQUALIFY_WARNING_TIMEOUT;
          player.lastActionTime = now - this.autoDisqualifyTimeout + AUTO_DISQUALIFY_WARNING_TIMEOUT;
        }
        player.autoDisqualifyWarned = true;
        player.sendRoom(`|tournament|autodq|target|${remainingTime}`);
      } else {
        player.autoDisqualifyWarned = false;
      }
    }
    if (!this.ended)
      this.autoDisqualifyTimer = setTimeout(() => this.runAutoDisqualify(), this.autoDisqualifyTimeout);
    if (output)
      output.sendReply("All available matches were checked for automatic disqualification.");
  }
  setScouting(allowed) {
    this.allowScouting = allowed;
    this.allowModjoin = !allowed;
    this.room.add(`|tournament|scouting|${this.allowScouting ? "allow" : "disallow"}`);
  }
  setModjoin(allowed) {
    this.allowModjoin = allowed;
    this.room.add(`Modjoining is now ${allowed ? "allowed" : "banned"} (Players can${allowed ? "" : "not"} modjoin their tournament battles).`);
  }
  setAutoconfirmedOnly(acOnly) {
    this.autoconfirmedOnly = acOnly;
    this.room.add(`This tournament is now ${acOnly ? "dis" : ""}allowing non-autoconfirmed users' joining.`);
  }
  setForceTimer(force) {
    this.forceTimer = force;
    this.room.add(`Forcetimer is now ${force ? "on" : "off"} for the tournament.`);
  }
  setForcePublic(force) {
    this.forcePublic = force;
    this.room.add(`Tournament battles forced public: ${force ? "ON" : "OFF"}`);
  }
  setAutostartAtCap(autostart) {
    this.autostartcap = true;
    this.room.add(`The tournament will start once ${this.playerCap} players have joined.`);
  }
  async challenge(user, targetUserid, output) {
    if (!this.isTournamentStarted) {
      output.sendReply("|tournament|error|NotStarted");
      return;
    }
    if (!(user.id in this.playerTable)) {
      output.sendReply("|tournament|error|UserNotAdded");
      return;
    }
    if (!(targetUserid in this.playerTable)) {
      output.sendReply("|tournament|error|InvalidMatch");
      return;
    }
    const from = this.playerTable[user.id];
    const to = this.playerTable[targetUserid];
    const availableMatches = from.availableMatches;
    if (!availableMatches?.has(to)) {
      output.sendReply("|tournament|error|InvalidMatch");
      return;
    }
    if (from.isBusy || to.isBusy) {
      this.room.add("Tournament backend breaks specifications. Please report this to an admin.");
      return;
    }
    from.isBusy = true;
    to.isBusy = true;
    this.isAvailableMatchesInvalidated = true;
    this.update();
    const ready = await Ladders(this.fullFormat).prepBattle(output.connection, "tour");
    if (!ready) {
      from.isBusy = false;
      to.isBusy = false;
      this.isAvailableMatchesInvalidated = true;
      this.update();
      return;
    }
    to.lastActionTime = Date.now();
    from.pendingChallenge = {
      to,
      team: ready.settings.team,
      hidden: ready.settings.hidden,
      inviteOnly: ready.settings.inviteOnly
    };
    to.pendingChallenge = {
      from,
      team: ready.settings.team,
      hidden: ready.settings.hidden,
      inviteOnly: ready.settings.inviteOnly
    };
    from.sendRoom(`|tournament|update|${JSON.stringify({ challenging: to.name })}`);
    to.sendRoom(`|tournament|update|${JSON.stringify({ challenged: from.name })}`);
    this.isBracketInvalidated = true;
    this.update();
  }
  cancelChallenge(user, output) {
    if (!this.isTournamentStarted) {
      if (output)
        output.sendReply("|tournament|error|NotStarted");
      return;
    }
    if (!(user.id in this.playerTable)) {
      if (output)
        output.sendReply("|tournament|error|UserNotAdded");
      return;
    }
    const player = this.playerTable[user.id];
    const challenge = player.pendingChallenge;
    if (!challenge?.to)
      return;
    player.isBusy = false;
    challenge.to.isBusy = false;
    player.pendingChallenge = null;
    challenge.to.pendingChallenge = null;
    user.sendTo(this.room, '|tournament|update|{"challenging":null}');
    challenge.to.sendRoom('|tournament|update|{"challenged":null}');
    this.isBracketInvalidated = true;
    this.isAvailableMatchesInvalidated = true;
    this.update();
  }
  async acceptChallenge(user, output) {
    if (!this.isTournamentStarted) {
      output.sendReply("|tournament|error|NotStarted");
      return;
    }
    if (!(user.id in this.playerTable)) {
      output.sendReply("|tournament|error|UserNotAdded");
      return;
    }
    const player = this.playerTable[user.id];
    const challenge = player.pendingChallenge;
    if (!challenge?.from)
      return;
    const ready = await Ladders(this.fullFormat).prepBattle(output.connection, "tour");
    if (!ready)
      return;
    const from = Users.get(challenge.from.id);
    if (!from?.connected || !user.connected)
      return;
    if (!challenge.from.pendingChallenge)
      return;
    if (!player.pendingChallenge)
      return;
    const room = Rooms.createBattle({
      format: this.fullFormat,
      isPrivate: this.room.settings.isPrivate,
      players: [{
        user: from,
        team: challenge.team,
        hidden: challenge.hidden,
        inviteOnly: challenge.inviteOnly
      }, {
        user,
        team: ready.settings.team,
        hidden: ready.settings.hidden,
        inviteOnly: ready.settings.inviteOnly
      }],
      rated: !Ladders.disabled && this.isRated,
      challengeType: ready.challengeType,
      tour: this,
      parentid: this.roomid
    });
    challenge.from.pendingChallenge = null;
    player.pendingChallenge = null;
    from.sendTo(this.room, '|tournament|update|{"challenging":null}');
    user.sendTo(this.room, '|tournament|update|{"challenged":null}');
    if (!room)
      return;
    challenge.from.inProgressMatch = { to: player, room };
    this.room.add(`|tournament|battlestart|${from.name}|${user.name}|${room.roomid}`).update();
    this.isBracketInvalidated = true;
    if (this.autoDisqualifyTimeout !== Infinity)
      this.runAutoDisqualify();
    if (this.forceTimer)
      room.game.startTimer();
    this.update();
  }
  getDefaultCustomName() {
    return Dex.formats.get(this.fullFormat).name + " (with custom rules)";
  }
  forfeit(user) {
    return this.disqualifyUser(user.id, null, "You left the tournament", true);
  }
  onConnect(user, connection) {
    this.updateFor(user, connection);
  }
  onUpdateConnection(user, connection) {
    this.updateFor(user, connection);
  }
  onRename(user, oldUserid) {
    if (oldUserid in this.playerTable) {
      this.renamePlayer(user, oldUserid);
    }
    this.updateFor(user);
  }
  onBattleJoin(room, user) {
    if (!room.p1 || !room.p2)
      return;
    if (this.allowScouting || this.ended || user.latestIp === room.p1.latestIp || user.latestIp === room.p2.latestIp) {
      return;
    }
    if (user.can("makeroom"))
      return;
    for (const otherPlayer of this.getRemainingPlayers()) {
      const otherUser = Users.get(otherPlayer.id);
      if (otherUser && otherUser.latestIp === user.latestIp) {
        return "Scouting is banned: tournament players can't watch other tournament battles.";
      }
    }
  }
  onBattleWin(room, winnerid) {
    if (this.completedMatches.has(room.roomid))
      return;
    this.completedMatches.add(room.roomid);
    room.setParent(null);
    if (!room.game)
      throw new Error("onBattleWin called without a battle");
    if (!room.p1 || !room.p2)
      throw new Error("onBattleWin called with missing players");
    const p1 = this.playerTable[room.p1.id];
    const p2 = this.playerTable[room.p2.id];
    const winner = this.playerTable[winnerid];
    const score = room.game.score || [0, 0];
    let result = "draw";
    if (p1 === winner) {
      p1.score += 1;
      p1.wins += 1;
      p2.losses += 1;
      result = "win";
    } else if (p2 === winner) {
      p2.score += 1;
      p2.wins += 1;
      p1.losses += 1;
      result = "loss";
    }
    p1.isBusy = false;
    p2.isBusy = false;
    p1.inProgressMatch = null;
    this.isBracketInvalidated = true;
    this.isAvailableMatchesInvalidated = true;
    if (result === "draw" && !this.generator.isDrawingSupported) {
      this.room.add(`|tournament|battleend|${p1.name}|${p2.name}|${result}|${score.join(",")}|fail|${room.roomid}`);
      if (this.autoDisqualifyTimeout !== Infinity)
        this.runAutoDisqualify();
      this.update();
      return this.room.update();
    }
    if (result === "draw") {
      p1.score += 0.5;
      p2.score += 0.5;
    }
    p1.games += 1;
    p2.games += 1;
    if (!(p1.isDisqualified || p2.isDisqualified)) {
      const error = this.generator.setMatchResult([p1, p2], result, score);
      if (error) {
        return this.room.add(`Unexpected ${error} from setMatchResult([${room.p1.id}, ${room.p2.id}], ${result}, ${score}) in onBattleWin(${room.roomid}, ${winnerid}). Please report this to an admin.`).update();
      }
    }
    this.room.add(`|tournament|battleend|${p1.name}|${p2.name}|${result}|${score.join(",")}|success|${room.roomid}`);
    if (this.generator.isTournamentEnded()) {
      if (!this.room.settings.isPrivate && this.generator.name.includes("Elimination") && !Config.autosavereplays) {
        const uploader = Users.get(winnerid);
        if (uploader?.connections[0]) {
          void Chat.parse("/savereplay", room, uploader, uploader.connections[0]);
        }
      }
      this.onTournamentEnd();
    } else {
      if (this.autoDisqualifyTimeout !== Infinity)
        this.runAutoDisqualify();
      this.update();
    }
    this.room.update();
  }
  onTournamentEnd() {
    const update = {
      results: this.generator.getResults().map(usersToNames),
      format: this.name,
      generator: this.generator.name,
      bracketData: this.getBracketData()
    };
    this.room.add(`|tournament|end|${JSON.stringify(update)}`);
    const settings = this.room.settings.tournaments;
    if (settings?.recentToursLength) {
      if (!settings.recentTours)
        settings.recentTours = [];
      const name = Dex.formats.get(this.name).exists ? Dex.formats.get(this.name).name : `${this.name} (${Dex.formats.get(this.baseFormat).name})`;
      settings.recentTours.unshift({ name, baseFormat: this.baseFormat, time: Date.now() });
      while (settings.recentTours.length > settings.recentToursLength) {
        settings.recentTours.pop();
      }
      this.room.saveSettings();
    }
    this.remove();
  }
}
function getGenerator(generator) {
  generator = toID(generator);
  switch (generator) {
    case "elim":
      generator = "elimination";
      break;
    case "rr":
      generator = "roundrobin";
      break;
  }
  return TournamentGenerators[generator];
}
function createTournamentGenerator(generatorName, modifier, output) {
  const TourGenerator = getGenerator(generatorName);
  if (!TourGenerator) {
    output.errorReply(`${generatorName} is not a valid type.`);
    const generatorNames = Object.keys(TournamentGenerators).join(", ");
    output.errorReply(`Valid types: ${generatorNames}`);
    return;
  }
  return new TourGenerator(modifier || "");
}
function createTournament(room, formatId, generator, playerCap, isRated, generatorMod, name, output) {
  if (room.type !== "chat") {
    output.errorReply("Tournaments can only be created in chat rooms.");
    return;
  }
  if (room.game) {
    output.errorReply(`You cannot have a tournament until the current room activity is over: ${room.game.title}`);
    return;
  }
  if (Rooms.global.lockdown) {
    output.errorReply("The server is restarting soon, so a tournament cannot be created.");
    return;
  }
  const format = Dex.formats.get(formatId);
  if (format.effectType !== "Format" || !format.tournamentShow) {
    output.errorReply(`${format.id} is not a valid tournament format.`);
    void output.parse(`/tour formats`);
    return;
  }
  const settings = room.settings.tournaments;
  if (settings?.blockRecents && settings.recentTours && settings.recentToursLength) {
    const recentTours = settings.recentTours.map((x) => x.baseFormat);
    if (recentTours.includes(format.id)) {
      output.errorReply(`A ${format.name} tournament was made too recently.`);
      return;
    }
  }
  if (!getGenerator(generator)) {
    output.errorReply(`${generator} is not a valid type.`);
    const generators = Object.keys(TournamentGenerators).join(", ");
    output.errorReply(`Valid types: ${generators}`);
    return;
  }
  if (playerCap && parseInt(playerCap) < 2) {
    output.errorReply("You cannot have a player cap that is less than 2.");
    return;
  }
  if (name?.trim().length) {
    if (output.checkChat(name) !== name) {
      throw new Chat.ErrorMessage(`You cannot use filtered words in tour names.`);
    }
    if (name.length > MAX_CUSTOM_NAME_LENGTH) {
      throw new Chat.ErrorMessage(`The tournament's name cannot exceed ${MAX_CUSTOM_NAME_LENGTH} characters.`);
    }
    if (name.includes("|"))
      throw new Chat.ErrorMessage("The tournament's name cannot include the | symbol.");
  }
  const tour = room.game = new Tournament(
    room,
    format,
    createTournamentGenerator(generator, generatorMod, output),
    playerCap,
    isRated,
    name
  );
  if (settings) {
    if (typeof settings.autostart === "number")
      tour.setAutoStartTimeout(settings.autostart, output);
    if (settings.playerCap) {
      tour.playerCap = settings.playerCap;
      if (settings.autostart === true)
        tour.setAutostartAtCap(true);
    }
    if (settings.autodq)
      tour.setAutoDisqualifyTimeout(settings.autodq, output);
    if (settings.forcePublic)
      tour.setForcePublic(true);
    if (settings.forceTimer)
      tour.setForceTimer(true);
    if (settings.allowModjoin === false)
      tour.setModjoin(false);
    if (settings.allowScouting === false)
      tour.setScouting(false);
  }
  return tour;
}
const commands = {
  pasttours: "recenttours",
  recenttours(target, room, user) {
    this.runBroadcast();
    room = this.requireRoom();
    if (!room.settings.tournaments?.recentToursLength) {
      throw new Chat.ErrorMessage(`Recent tournaments aren't documented in this room.`);
    }
    if (!room.settings.tournaments?.recentTours?.length) {
      throw new Chat.ErrorMessage(`There haven't been any documented tournaments in this room recently.`);
    }
    const array = room.settings.tournaments.recentTours;
    const { name, time } = array[0];
    let buf = `The last tournament ended ${Chat.toDurationString(Date.now() - time)} ago - ${name}`;
    if (array.length > 1) {
      buf += `<hr /><strong>Previous tournaments:</strong> `;
      buf += array.filter((x, i) => i !== 0).map((x) => x.name).join(", ");
    }
    this.sendReplyBox(buf);
  },
  recenttourshelp: [`/recenttours - Displays the n most recent tour(s), where n represents the number defined by staff (i.e. the 6 most recent tours).`],
  tour: "tournament",
  tours: "tournament",
  tournaments: "tournament",
  tournament: {
    ""(target, room, user) {
      room = this.requireRoom();
      if (!this.runBroadcast())
        return;
      const update = [];
      for (const tourRoom of Rooms.rooms.values()) {
        const tournament = tourRoom.getGame(Tournament);
        if (!tournament)
          continue;
        if (tourRoom.settings.isPrivate || tourRoom.settings.isPersonal || tourRoom.settings.staffRoom)
          continue;
        update.push({
          room: tourRoom.roomid,
          title: room.title,
          format: tournament.name,
          generator: tournament.generator.name,
          isStarted: tournament.isTournamentStarted
        });
      }
      this.sendReply(`|tournaments|info|${JSON.stringify(update)}`);
    },
    help() {
      return this.parse("/help tournament");
    },
    enable: "toggle",
    disable: "toggle",
    toggle(target, room, user, connection, cmd) {
      throw new Chat.ErrorMessage(`${this.cmdToken}${this.fullCmd} has been deprecated. Instead, use "${this.cmdToken}permissions set tournaments, [rank symbol]".`);
    },
    announcements: "announce",
    announce(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("gamemanagement", null, room);
      if (!target) {
        if (room.settings.tournaments?.announcements) {
          return this.sendReply("Tournament announcements are enabled.");
        } else {
          return this.sendReply("Tournament announcements are disabled.");
        }
      }
      const option = target.toLowerCase();
      if (this.meansYes(option)) {
        if (room.settings.tournaments?.announcements)
          return this.errorReply("Tournament announcements are already enabled.");
        if (!room.settings.tournaments)
          room.settings.tournaments = {};
        room.settings.tournaments.announcements = true;
        room.saveSettings();
        this.privateModAction(`Tournament announcements were enabled by ${user.name}`);
        this.modlog("TOUR ANNOUNCEMENTS", null, "ON");
      } else if (this.meansNo(option)) {
        if (!room.settings.tournaments?.announcements)
          return this.errorReply("Tournament announcements are already disabled.");
        if (!room.settings.tournaments)
          room.settings.tournaments = {};
        room.settings.tournaments.announcements = false;
        room.saveSettings();
        this.privateModAction(`Tournament announcements were disabled by ${user.name}`);
        this.modlog("TOUR ANNOUNCEMENTS", null, "OFF");
      } else {
        return this.sendReply(`Usage: /tour ${cmd} <on|off>`);
      }
      room.saveSettings();
    },
    new: "create",
    create(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const [format, generator, cap, mod, name] = target.split(",").map((item) => item.trim());
      if (!target || !format || !generator) {
        return this.sendReply(`Usage: /tour ${cmd} <format>, <type> [, <comma-separated arguments>]`);
      }
      const tour = createTournament(room, format, generator, cap, Config.ratedtours, mod, name, this);
      if (tour) {
        this.privateModAction(`${user.name} created a tournament in ${tour.baseFormat} format.`);
        this.modlog("TOUR CREATE", null, tour.baseFormat);
        if (room.settings.tournaments?.announcements) {
          const tourRoom = Rooms.search(Config.tourroom || "tournaments");
          if (tourRoom && tourRoom !== room) {
            tourRoom.addRaw(
              import_lib.Utils.html`<div class="infobox"><a href="/${room.roomid}" class="ilink">` + import_lib.Utils.html`<strong>${Dex.formats.get(tour.name).name}</strong> tournament created in` + ` <strong>${room.title}</strong>.</a></div>`
            ).update();
          }
        }
      }
    },
    formats(target, room, user) {
      if (!this.runBroadcast())
        return;
      let buf = ``;
      let section = void 0;
      for (const format of Dex.formats.all()) {
        if (!format.tournamentShow)
          continue;
        const name = format.name.startsWith(`[Gen ${Dex.gen}] `) ? format.name.slice(8) : format.name;
        if (format.section !== section) {
          section = format.section;
          buf += import_lib.Utils.html`<br /><strong>${section}:</strong><br />&bull; ${name}`;
        } else {
          buf += import_lib.Utils.html`<br />&bull; ${name}`;
        }
      }
      this.sendReplyBox(`<div class="chat"><details class="readmore"><summary>Valid Formats: </summary>${buf}</details></div>`);
    },
    banuser(target, room, user) {
      room = this.requireRoom();
      const [userid, ...reasonsArray] = target.split(",").map((item) => item.trim());
      if (!target) {
        return this.sendReply(`Usage: /tour banuser <user>, <reason>`);
      }
      const reason = reasonsArray.join(",");
      const targetUser = Users.get(userid);
      this.checkCan("gamemoderation", targetUser, room);
      const targetUserid = targetUser ? targetUser.id : toID(userid);
      if (!targetUser)
        return false;
      if (reason?.length > MAX_REASON_LENGTH) {
        return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`);
      }
      if (Tournament.checkBanned(room, targetUser))
        return this.errorReply("This user is already banned from tournaments.");
      const punishment = {
        type: "TOURBAN",
        id: targetUserid,
        expireTime: Date.now() + TOURBAN_DURATION,
        reason
      };
      if (targetUser) {
        Punishments.roomPunish(room, targetUser, punishment);
      } else {
        Punishments.roomPunishName(room, targetUserid, punishment);
      }
      room.getGame(Tournament)?.removeBannedUser(targetUserid);
      this.modlog("TOURBAN", targetUser, reason);
      this.privateModAction(
        `${targetUser ? targetUser.name : targetUserid} was banned from joining tournaments by ${user.name}. (${reason})`
      );
    },
    unbanuser(target, room, user) {
      room = this.requireRoom();
      target = target.trim();
      if (!target) {
        return this.sendReply(`Usage: /tour unbanuser <user>`);
      }
      const targetUser = Users.get(toID(target));
      this.checkCan("gamemoderation", targetUser, room);
      const targetUserid = toID(targetUser || toID(target));
      if (!Tournament.checkBanned(room, targetUserid))
        return this.errorReply("This user isn't banned from tournaments.");
      if (targetUser) {
        Punishments.roomUnpunish(room, targetUserid, "TOURBAN", false);
      }
      this.privateModAction(`${targetUser ? targetUser.name : targetUserid} was unbanned from joining tournaments by ${user.name}.`);
      this.modlog("TOUR UNBAN", targetUser, null, { noip: 1, noalts: 1 });
    },
    j: "join",
    in: "join",
    join(target, room, user) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      tournament.addUser(user, this);
    },
    l: "leave",
    out: "leave",
    leave(target, room, user) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      if (tournament.isTournamentStarted) {
        if (tournament.getRemainingPlayers().some((player) => player.id === user.id)) {
          tournament.disqualifyUser(user.id, this, null, true);
        } else {
          this.errorReply("You have already been eliminated from this tournament.");
        }
      } else {
        tournament.removeUser(user.id, this);
      }
    },
    getusers(target, room) {
      if (!this.runBroadcast())
        return;
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      const users = usersToNames(tournament.getRemainingPlayers().sort());
      this.sendReplyBox(
        `<strong>${users.length}/${tournament.players.length}` + import_lib.Utils.html` users remain in this tournament:</strong><br />${users.join(", ")}`
      );
    },
    getupdate(target, room, user) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      tournament.updateFor(user);
      this.sendReply("Your tournament bracket has been updated.");
    },
    challenge(target, room, user, connection, cmd) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      if (!target) {
        return this.sendReply(`Usage: /tour ${cmd} <user>`);
      }
      void tournament.challenge(user, toID(target), this);
    },
    cancelchallenge(target, room, user) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      tournament.cancelChallenge(user, this);
    },
    acceptchallenge(target, room, user) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      void tournament.acceptChallenge(user, this);
    },
    async vtm(target, room, user, connection) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      if (Monitor.countPrepBattle(connection.ip, connection)) {
        return;
      }
      const result = await TeamValidatorAsync.get(tournament.fullFormat).validateTeam(user.battleSettings.team);
      if (result.startsWith("1")) {
        connection.popup("Your team is valid for this tournament.");
      } else {
        const formatName = Dex.formats.get(tournament.baseFormat).name;
        const reasons = result.slice(1).split(formatName).join("this tournament");
        connection.popup(`Your team was rejected for the following reasons:

- ${reasons.replace(/\n/g, "\n- ")}`);
      }
    },
    viewruleset: "viewcustomrules",
    viewbanlist: "viewcustomrules",
    viewrules: "viewcustomrules",
    viewcustomrules(target, room) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      if (!this.runBroadcast())
        return;
      if (tournament.customRules.length < 1) {
        return this.errorReply("The tournament does not have any custom rules.");
      }
      this.sendReply(`|html|<div class='infobox infobox-limited'>This tournament includes:<br />${tournament.getCustomRules()}</div>`);
    },
    settype(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      if (!target) {
        return this.sendReply(`Usage: /tour ${cmd} <type> [, <comma-separated arguments>]`);
      }
      const [generatorType, cap, modifier] = target.split(",").map((item) => item.trim());
      const playerCap = parseInt(cap);
      const generator = createTournamentGenerator(generatorType, modifier, this);
      if (generator && tournament.setGenerator(generator, this)) {
        if (playerCap && playerCap >= 2) {
          tournament.playerCap = playerCap;
          if (Config.tourdefaultplayercap && tournament.playerCap > Config.tourdefaultplayercap) {
            Monitor.log(`[TourMonitor] Room ${tournament.room.roomid} starting a tour over default cap (${tournament.playerCap})`);
          }
          room.send(`|tournament|update|{"playerCap": "${playerCap}"}`);
        } else if (tournament.playerCap && !playerCap) {
          tournament.playerCap = 0;
          room.send(`|tournament|update|{"playerCap": "${playerCap}"}`);
        }
        const capNote = tournament.playerCap ? " with a player cap of " + tournament.playerCap : "";
        this.privateModAction(`${user.name} set tournament type to ${generator.name}${capNote}.`);
        this.modlog("TOUR SETTYPE", null, generator.name + capNote);
        this.sendReply(`Tournament set to ${generator.name}${capNote}.`);
      }
    },
    cap: "setplayercap",
    playercap: "setplayercap",
    setcap: "setplayercap",
    setplayercap(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      if (!target) {
        if (tournament.playerCap) {
          return this.sendReply(`Usage: /tour ${cmd} <cap>; The current player cap is ${tournament.playerCap}`);
        } else {
          return this.sendReply(`Usage: /tour ${cmd} <cap>`);
        }
      }
      if (tournament.isTournamentStarted) {
        return this.errorReply("The player cap cannot be changed once the tournament has started.");
      }
      const option = target.toLowerCase();
      if (["0", "infinity", "off", "false", "stop", "remove"].includes(option)) {
        if (!tournament.playerCap)
          return this.errorReply("The tournament does not have a player cap.");
        target = "0";
      }
      const playerCap = parseInt(target);
      if (playerCap === 0) {
        tournament.playerCap = 0;
        this.privateModAction(`${user.name} removed the tournament's player cap.`);
        this.modlog("TOUR PLAYERCAP", null, "removed");
        this.sendReply("Tournament cap removed.");
      } else {
        if (isNaN(playerCap) || playerCap < 2) {
          return this.errorReply("The tournament cannot have a player cap less than 2.");
        }
        if (playerCap === tournament.playerCap) {
          return this.errorReply(`The tournament's player cap is already ${playerCap}.`);
        }
        tournament.playerCap = playerCap;
        if (Config.tourdefaultplayercap && tournament.playerCap > Config.tourdefaultplayercap) {
          Monitor.log(`[TourMonitor] Room ${tournament.room.roomid} starting a tour over default cap (${tournament.playerCap})`);
        }
        this.privateModAction(`${user.name} set the tournament's player cap to ${tournament.playerCap}.`);
        this.modlog("TOUR PLAYERCAP", null, tournament.playerCap.toString());
        this.sendReply(`Tournament cap set to ${tournament.playerCap}.`);
      }
      room.send(`|tournament|update|{"playerCap": "${tournament.playerCap}"}`);
    },
    end: "delete",
    stop: "delete",
    delete(target, room, user) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      tournament.forceEnd();
      this.privateModAction(`${user.name} forcibly ended a tournament.`);
      this.modlog("TOUR END");
    },
    ruleset: "customrules",
    banlist: "customrules",
    rules: "customrules",
    customrules(target, room, user, connection, cmd) {
      room = this.requireRoom();
      const tournament = this.requireGame(Tournament);
      if (cmd === "banlist") {
        return this.errorReply("The new syntax is: /tour rules -bannedthing, +un[banned|restricted]thing, *restrictedthing, !removedrule, addedrule");
      }
      if (!target) {
        this.sendReply("Usage: /tour rules <list of rules>");
        this.sendReply("Rules can be: -bannedthing, +un[banned|restricted]thing, *restrictedthing, !removedrule, addedrule");
        this.parse("/tour viewrules");
        if (tournament.customRules.length) {
          return this.sendReplyBox(`<details><summary>Source</summary><code style="white-space: pre-wrap; display: table; tab-size: 3">/tour rules ${tournament.customRules}</code></details>`);
        }
        return;
      }
      this.checkCan("tournaments", null, room);
      if (tournament.isTournamentStarted) {
        return this.errorReply("The custom rules cannot be changed once the tournament has started.");
      }
      if (tournament.setCustomRules(target)) {
        room.addRaw(
          `<div class="infobox infobox-limited">This tournament includes:<br />${tournament.getCustomRules()}</div>`
        );
        this.privateModAction(`${user.name} updated the tournament's custom rules.`);
        this.modlog("TOUR RULES", null, tournament.customRules.join(", "));
        this.sendReplyBox(`<details><summary>Source</summary><code style="white-space: pre-wrap; display: table; tab-size: 3">/tour rules ${tournament.customRules}</code></details>`);
      }
    },
    clearruleset: "clearcustomrules",
    clearbanlist: "clearcustomrules",
    clearrules: "clearcustomrules",
    clearcustomrules(target, room, user) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      if (tournament.isTournamentStarted) {
        return this.errorReply("The custom rules cannot be changed once the tournament has started.");
      }
      if (tournament.customRules.length < 1) {
        return this.errorReply("The tournament does not have any custom rules.");
      }
      tournament.customRules = [];
      tournament.fullFormat = tournament.baseFormat;
      if (tournament.name === tournament.getDefaultCustomName()) {
        tournament.name = tournament.baseFormat;
        room.send(`|tournament|update|${JSON.stringify({ format: tournament.name })}`);
        tournament.update();
      }
      room.addRaw(`<b>The tournament's custom rules were cleared.</b>`);
      this.privateModAction(`${user.name} cleared the tournament's custom rules.`);
      this.modlog("TOUR CLEARRULES");
    },
    name: "setname",
    customname: "setname",
    setname(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      const name = target.trim();
      if (!name) {
        return this.sendReply(`Usage: /tour ${cmd} <comma-separated arguments>`);
      }
      this.checkChat(name);
      if (!name || typeof name !== "string")
        return;
      if (name.length > MAX_CUSTOM_NAME_LENGTH) {
        return this.errorReply(`The tournament's name cannot exceed ${MAX_CUSTOM_NAME_LENGTH} characters.`);
      }
      if (name.includes("|"))
        return this.errorReply("The tournament's name cannot include the | symbol.");
      tournament.name = name;
      room.send(`|tournament|update|${JSON.stringify({ format: tournament.name })}`);
      this.privateModAction(`${user.name} set the tournament's name to ${tournament.name}.`);
      this.modlog("TOUR NAME", null, tournament.name);
      tournament.update();
    },
    resetname: "clearname",
    clearname(target, room, user) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      if (tournament.name === tournament.baseFormat)
        return this.errorReply("The tournament does not have a name.");
      tournament.name = tournament.baseFormat;
      room.send(`|tournament|update|${JSON.stringify({ format: tournament.name })}`);
      this.privateModAction(`${user.name} cleared the tournament's name.`);
      this.modlog("TOUR CLEARNAME");
      tournament.update();
    },
    begin: "start",
    start(target, room, user) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      if (tournament.startTournament(this)) {
        room.sendMods(`(${user.name} started the tournament.)`);
      }
    },
    dq: "disqualify",
    disqualify(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      if (!target) {
        return this.sendReply(`Usage: /tour ${cmd} <user>`);
      }
      const [userid, reason] = target.split(",").map((item) => item.trim());
      const targetUser = Users.get(userid);
      const targetUserid = toID(targetUser || userid);
      if (reason?.length > MAX_REASON_LENGTH) {
        return this.errorReply(`The reason is too long. It cannot exceed ${MAX_REASON_LENGTH} characters.`);
      }
      if (tournament.disqualifyUser(targetUserid, this, reason)) {
        this.privateModAction(`${targetUser ? targetUser.name : targetUserid} was disqualified from the tournament by ${user.name}${reason ? " (" + reason + ")" : ""}`);
        this.modlog("TOUR DQ", targetUserid, reason);
      }
    },
    sub: "replace",
    replace(target, room, user) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      const [oldUser, newUser] = target.split(",").map((item) => Users.get(item.trim()));
      if (!oldUser)
        return this.errorReply(`User ${oldUser} not found.`);
      if (!newUser)
        return this.errorReply(`User ${newUser} not found.`);
      tournament.replaceUser(oldUser, newUser, this);
    },
    autostart: "setautostart",
    setautostart(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      if (!target) {
        return this.sendReply(`Usage: /tour ${cmd} <on|minutes|off>`);
      }
      const option = target.toLowerCase();
      if (this.meansYes(option) && option !== "1" || option === "start") {
        if (tournament.isTournamentStarted) {
          return this.errorReply("The tournament has already started.");
        } else if (!tournament.playerCap) {
          return this.errorReply("The tournament does not have a player cap set.");
        } else {
          if (tournament.autostartcap) {
            return this.errorReply("The tournament is already set to autostart when the player cap is reached.");
          }
          tournament.setAutostartAtCap(true);
          this.privateModAction(`The tournament was set to autostart when the player cap is reached by ${user.name}`);
          this.modlog("TOUR AUTOSTART", null, "when playercap is reached");
        }
      } else {
        if (option === "0" || option === "infinity" || this.meansNo(option) || option === "stop" || option === "remove") {
          if (!tournament.autostartcap && tournament.autoStartTimeout === Infinity) {
            return this.errorReply("The automatic tournament start timer is already off.");
          }
          target = "off";
          tournament.autostartcap = false;
        }
        const timeout = target.toLowerCase() === "off" ? Infinity : Number(target) * 60 * 1e3;
        if (timeout <= 0 || timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION) {
          return this.errorReply(`The automatic tournament start timer must be set to a positive number.`);
        }
        if (tournament.setAutoStartTimeout(timeout, this)) {
          this.privateModAction(`The tournament auto start timer was set to ${target} by ${user.name}`);
          this.modlog("TOUR AUTOSTART", null, timeout === Infinity ? "off" : target);
        }
      }
    },
    autodq: "setautodq",
    setautodq(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      if (!target) {
        if (tournament.autoDisqualifyTimeout !== Infinity) {
          return this.sendReply(`Usage: /tour ${cmd} <minutes|off>; The current automatic disqualify timer is set to ${tournament.autoDisqualifyTimeout / 1e3 / 60} minute(s)`);
        } else {
          return this.sendReply(`Usage: /tour ${cmd} <minutes|off>`);
        }
      }
      if (target.toLowerCase() === "infinity" || target === "0")
        target = "off";
      const timeout = target.toLowerCase() === "off" ? Infinity : Number(target) * 60 * 1e3;
      if (timeout <= 0 || timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION) {
        return this.errorReply(`The automatic disqualification timer must be set to a positive number.`);
      }
      if (timeout === tournament.autoDisqualifyTimeout) {
        return this.errorReply(`The automatic tournament disqualify timer is already set to ${target} minute(s).`);
      }
      if (tournament.setAutoDisqualifyTimeout(timeout, this)) {
        this.privateModAction(`The tournament auto disqualify timer was set to ${target} by ${user.name}`);
        this.modlog("TOUR AUTODQ", null, timeout === Infinity ? "off" : target);
      }
    },
    runautodq(target, room, user) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      if (tournament.autoDisqualifyTimeout === Infinity) {
        return this.errorReply("The automatic tournament disqualify timer is not set.");
      }
      tournament.runAutoDisqualify(this);
      this.roomlog(`${user.name} used /tour runautodq`);
    },
    scout: "setscouting",
    scouting: "setscouting",
    setscout: "setscouting",
    setscouting(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      if (!target) {
        if (tournament.allowScouting) {
          return this.sendReply("This tournament allows spectating other battles while in a tournament.");
        } else {
          return this.sendReply("This tournament disallows spectating other battles while in a tournament.");
        }
      }
      const option = target.toLowerCase();
      if (this.meansYes(option) || option === "allow" || option === "allowed") {
        if (tournament.allowScouting)
          return this.errorReply("Scouting for this tournament is already set to allowed.");
        tournament.setScouting(true);
        this.privateModAction(`The tournament was set to allow scouting by ${user.name}`);
        this.modlog("TOUR SCOUT", null, "allow");
      } else if (this.meansNo(option) || option === "disallow" || option === "disallowed") {
        if (!tournament.allowScouting)
          return this.errorReply("Scouting for this tournament is already disabled.");
        tournament.setScouting(false);
        this.privateModAction(`The tournament was set to disallow scouting by ${user.name}`);
        this.modlog("TOUR SCOUT", null, "disallow");
      } else {
        return this.sendReply(`Usage: /tour ${cmd}<allow|disallow>`);
      }
    },
    modjoin: "setmodjoin",
    setmodjoin(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      if (!target) {
        if (tournament.allowModjoin) {
          return this.sendReply("This tournament allows players to modjoin their battles.");
        } else {
          return this.sendReply("This tournament does not allow players to modjoin their battles.");
        }
      }
      const option = target.toLowerCase();
      if (this.meansYes(option) || option === "allowed") {
        if (tournament.allowModjoin)
          return this.errorReply("Modjoining is already allowed for this tournament.");
        tournament.setModjoin(true);
        this.privateModAction(`The tournament was set to allow modjoin by ${user.name}`);
        this.modlog("TOUR MODJOIN", null, option);
      } else if (this.meansNo(option) || option === "disallowed") {
        if (!tournament.allowModjoin)
          return this.errorReply("Modjoining is already not allowed for this tournament.");
        tournament.setModjoin(false);
        this.privateModAction(`The tournament was set to disallow modjoin by ${user.name}`);
        this.modlog("TOUR MODJOIN", null, option);
      } else {
        return this.sendReply(`Usage: /tour ${cmd} <allow|disallow>`);
      }
    },
    aconly: "autoconfirmedonly",
    onlyac: "autoconfirmedonly",
    onlyautoconfirmed: "autoconfirmedonly",
    autoconfirmedonly(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      if (!target) {
        return this.sendReply(
          `This tournament ${tournament.autoconfirmedOnly ? "does not allow" : "allows"} non-autoconfirmed users to join a tournament.`
        );
      }
      const value = this.meansYes(target) ? true : this.meansNo(target) ? false : null;
      target = value ? "ON" : "OFF";
      if (value === null || !toID(target)) {
        return this.parse(`/help tour`);
      }
      if (tournament.autoconfirmedOnly === value) {
        return this.errorReply(`This tournament is already set to ${value ? "disallow" : "allow"} non-autoconfirmed users.`);
      }
      tournament.setAutoconfirmedOnly(value);
      this.privateModAction(`${user.name} set this tournament to ${value ? "disallow" : "allow"} non-autoconfirmed users.`);
      this.modlog("TOUR AUTOCONFIRMEDONLY", null, target);
    },
    forcepublic(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      const option = target || "on";
      if (this.meansYes(option)) {
        if (tournament.forcePublic) {
          throw new Chat.ErrorMessage(`Tournament battles are already being forced public.`);
        }
        tournament.setForcePublic(true);
        this.privateModAction(`Tournament public battles were turned ON by ${user.name}`);
        this.modlog("TOUR FORCEPUBLIC", null, "ON");
      } else if (this.meansNo(option) || option === "stop") {
        if (!tournament.forcePublic) {
          throw new Chat.ErrorMessage(`Tournament battles are not being forced public.`);
        }
        tournament.setForcePublic(false);
        this.privateModAction(`Tournament public battles were turned OFF by ${user.name}`);
        this.modlog("TOUR FORCEPUBLIC", null, "OFF");
      } else {
        return this.sendReply(`Usage: /tour ${cmd} <on|off>`);
      }
    },
    forcetimer(target, room, user, connection, cmd) {
      room = this.requireRoom();
      this.checkCan("tournaments", null, room);
      const tournament = this.requireGame(Tournament);
      target = target.trim();
      const option = target ? target.toLowerCase() : "on";
      if (this.meansYes(option)) {
        tournament.setForceTimer(true);
        for (const player of tournament.players) {
          player.inProgressMatch?.room.game?.startTimer();
        }
        this.privateModAction(`The timer was turned on for the tournament by ${user.name}`);
        this.modlog("TOUR FORCETIMER", null, "ON");
      } else if (this.meansNo(option) || option === "stop") {
        tournament.setForceTimer(false);
        this.privateModAction(`The timer was turned off for the tournament by ${user.name}`);
        this.modlog("TOUR FORCETIMER", null, "OFF");
      } else {
        return this.sendReply(`Usage: /tour ${cmd} <on|off>`);
      }
    },
    settings: {
      modjoin(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        if (!target || !this.meansYes(target) && !this.meansNo(target)) {
          return this.parse(`/help tour settings`);
        }
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansYes(target)) {
          if (room.settings.tournaments.allowModjoin) {
            throw new Chat.ErrorMessage(`Modjoin is already enabled for every tournament.`);
          }
          if (tour && !tour.allowModjoin)
            this.parse(`/tour modjoin allow`);
          room.settings.tournaments.allowModjoin = true;
          room.saveSettings();
          this.privateModAction(`Modjoin was enabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "modjoin: ALLOW");
        } else {
          if (!room.settings.tournaments.allowModjoin) {
            throw new Chat.ErrorMessage(`Modjoin is already disabled for every tournament.`);
          }
          if (tour?.allowModjoin)
            this.parse(`/tour modjoin disallow`);
          room.settings.tournaments.allowModjoin = false;
          room.saveSettings();
          this.privateModAction(`Modjoin was disabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "modjoin: DISALLOW");
        }
      },
      scouting(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        if (!target || !this.meansYes(target) && !this.meansNo(target))
          return this.parse(`/help tour settings`);
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansYes(target)) {
          if (room.settings.tournaments.allowScouting) {
            throw new Chat.ErrorMessage(`Scouting is already enabled for every tournament.`);
          }
          if (tour && !tour.allowScouting)
            this.parse(`/tour scouting allow`);
          room.settings.tournaments.allowScouting = true;
          room.saveSettings();
          this.privateModAction(`Scouting was enabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "scouting: ALLOW");
        } else {
          if (!room.settings.tournaments.allowScouting) {
            throw new Chat.ErrorMessage(`Scouting is already disabled for every tournament.`);
          }
          if (tour?.allowScouting)
            this.parse(`/tour scouting disallow`);
          room.settings.tournaments.allowScouting = false;
          room.saveSettings();
          this.privateModAction(`Scouting was disabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "scouting: DISALLOW");
        }
      },
      aconly: "autoconfirmedonly",
      onlyac: "autoconfirmedonly",
      onlyautoconfirmed: "autoconfirmedonly",
      autoconfirmedonly(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        const value = this.meansYes(target) ? true : this.meansNo(target) ? false : null;
        if (!target || value === null)
          return this.parse(`/help tour settings`);
        if (room.settings.tournaments.autoconfirmedOnly === value) {
          return this.errorReply(`All tournaments are already set to ${value ? "disallow" : "allow"} non-autoconfimed users.`);
        }
        room.settings.tournaments.autoconfirmedOnly = value;
        room.saveSettings();
        target = value ? "ON" : "OFF";
        this.modlog("TOUR SETTINGS", null, `autoconfirmed only: ${target}`);
        if (tour)
          this.parse(`/tour autoconfirmedonly ${target}`);
        this.privateModAction(`${user.name} set all tournaments to ${value ? "disallow" : "allow"} non-autoconfirmed users.`);
      },
      forcepublic(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        if (!target || !this.meansNo(target) && !this.meansYes(target))
          return this.parse(`/help tour settings`);
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansNo(target)) {
          if (!room.settings.tournaments.forcePublic) {
            throw new Chat.ErrorMessage(`Forced public battles are already disabled for every tournament.`);
          }
          if (tour?.forcePublic)
            this.parse(`/tour forcepublic off`);
          room.settings.tournaments.forcePublic = false;
          room.saveSettings();
          this.privateModAction(`Forced public battles were disabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "forcepublic: DISABLE");
        } else {
          if (room.settings.tournaments.forcePublic) {
            throw new Chat.ErrorMessage(`Forced public battles are already enabled for every tournament.`);
          }
          if (tour && !tour.forcePublic)
            this.parse(`/tour forcepublic on`);
          room.settings.tournaments.forcePublic = true;
          room.saveSettings();
          this.privateModAction(`Forced public battles were enabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "forcepublic: ENABLE");
        }
      },
      forcetimer(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        if (!target || !this.meansNo(target) && !this.meansYes(target))
          return this.parse(`/help tour settings`);
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansNo(target)) {
          if (!room.settings.tournaments.forceTimer) {
            throw new Chat.ErrorMessage(`Forced timer is already disabled for every tournament.`);
          }
          if (tour?.forceTimer)
            this.parse(`/tour forcetimer off`);
          room.settings.tournaments.forceTimer = false;
          room.saveSettings();
          this.privateModAction(`Forced timer was disabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "forcetimer: DISABLE");
        } else {
          if (room.settings.tournaments.forceTimer) {
            throw new Chat.ErrorMessage(`Forced timer is already enabled for every tournament.`);
          }
          if (tour && !tour.forceTimer)
            this.parse(`/tour forcetimer on`);
          room.settings.tournaments.forceTimer = true;
          room.saveSettings();
          this.privateModAction(`Forced timer was enabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "forcetimer: ENABLE");
        }
      },
      autostart(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        const num = Number(target);
        if (!target || !this.meansYes(target) && !this.meansNo(target) && isNaN(num)) {
          return this.parse(`/help tour settings`);
        }
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansNo(target)) {
          if (!room.settings.tournaments.autostart) {
            throw new Chat.ErrorMessage(`Autostart is already disabled for every tournament.`);
          }
          if (tour && !tour.isTournamentStarted && tour.autoDisqualifyTimeout !== Infinity) {
            this.parse(`/tour setautojoin off`);
          }
          room.settings.tournaments.autostart = false;
          room.saveSettings();
          this.privateModAction(`Autostart was disabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "autostart: DISABLE");
        } else if (this.meansYes(target) && target !== "1") {
          if (room.settings.tournaments.autostart === true) {
            throw new Chat.ErrorMessage(`Autostart for every tournament is already set to true.`);
          }
          room.settings.tournaments.autostart = true;
          if (tour && !tour.isTournamentStarted && tour.playerCap)
            this.parse(`/tour setautostart on`);
          room.saveSettings();
          this.privateModAction(`Autostart was set to true for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, `autostart: ON`);
        } else if (!isNaN(num)) {
          const timeout = num * 60 * 1e3;
          if (timeout < 0.5 * 60 * 1e3 || timeout > Chat.MAX_TIMEOUT_DURATION) {
            throw new Chat.ErrorMessage(`The autostart must be set to at least 0.5.`);
          }
          if (room.settings.tournaments.autostart === timeout) {
            throw new Chat.ErrorMessage(`Autostart for every tournament is already set to ${num}.`);
          }
          room.settings.tournaments.autostart = timeout;
          if (tour && !tour.isTournamentStarted && tour.autoStartTimeout === Infinity) {
            this.parse(`/tour setautostart ${num}`);
          }
          room.saveSettings();
          this.privateModAction(`Autostart was set to ${num} minute(s) for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, `autostart: ${num} minute(s)`);
        }
      },
      autodq(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        const num = Number(target);
        if (!target || !this.meansNo(target) && isNaN(num))
          return this.parse(`/help tour settings`);
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansNo(target)) {
          if (!room.settings.tournaments.autodq) {
            throw new Chat.ErrorMessage(`Automatic disqualification is already disabled for every tournament.`);
          }
          if (tour && !tour.isTournamentStarted && tour.autoDisqualifyTimeout !== Infinity) {
            this.parse(`/tour autodq off`);
          }
          delete room.settings.tournaments.autodq;
          room.saveSettings();
          this.privateModAction(`Automatic disqualification was disabled for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "autodq: DISABLE");
        } else if (!isNaN(num)) {
          const timeout = num * 60 * 1e3;
          if (timeout < 0.5 * 60 * 1e3 || timeout > Chat.MAX_TIMEOUT_DURATION) {
            throw new Chat.ErrorMessage(`The autodq must be set to a number greater than 1.`);
          }
          if (room.settings.tournaments.autodq === timeout) {
            throw new Chat.ErrorMessage(`Automatic disqualification for every tournament is already set to ${num}.`);
          }
          room.settings.tournaments.autodq = timeout;
          if (tour?.autoDisqualifyTimeout === Infinity) {
            this.parse(`/tour autodq ${num}`);
          }
          room.saveSettings();
          this.privateModAction(`Automatic disqualification was set to ${num} minute(s) for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, `autodq: ${num} minute(s)`);
        }
      },
      playercap(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        const num = parseInt(target);
        if (!target || !this.meansNo(target) && isNaN(num))
          return this.parse(`/help tour settings`);
        const tour = room.getGame(Tournament);
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansNo(target)) {
          if (!room.settings.tournaments.playerCap) {
            throw new Chat.ErrorMessage(`Player Cap is already removed for every tournament.`);
          }
          if (tour && !tour.isTournamentStarted && tour.playerCap) {
            this.parse(`/tour setplayercap off`);
          }
          delete room.settings.tournaments.playerCap;
          room.saveSettings();
          this.privateModAction(`Player Cap was removed for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, "playercap: REMOVE");
        } else if (!isNaN(num)) {
          if (num < 2) {
            throw new Chat.ErrorMessage(`The Player Cap must be at least 2.`);
          }
          if (room.settings.tournaments.playerCap === num) {
            throw new Chat.ErrorMessage(`Player Cap for every tournament is already set to ${num}.`);
          }
          room.settings.tournaments.playerCap = num;
          if (tour && !tour.isTournamentStarted && !tour.playerCap) {
            this.parse(`/tour setplayercap ${num}`);
            if (room.settings.tournaments.autostart === true)
              this.parse(`/tour autostart on`);
          }
          room.saveSettings();
          this.privateModAction(`Player Cap was set to ${num} for every tournament by ${user.name}`);
          this.modlog("TOUR SETTINGS", null, `playercap: ${num}`);
          if (Config.tourdefaultplayercap && room.settings.tournaments.playerCap > Config.tourdefaultplayercap) {
            Monitor.log(`[TourMonitor] Room ${room.roomid} setting cap for every tour over default cap (${room.settings.tournaments.playerCap})`);
          }
        } else {
          return this.sendReply(`Usage: ${this.cmdToken}${this.fullCmd} <number|off>`);
        }
      },
      recenttours(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        let num = parseInt(target);
        const forcedelete = toID(target) === "forcedelete";
        if (this.meansNo(target) || forcedelete)
          num = 0;
        if (isNaN(num) || num > 15 || num < 0) {
          return this.parse(`/help tour settings`);
        }
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (num >= 1) {
          if (room.settings.tournaments.recentToursLength === num) {
            throw new Chat.ErrorMessage(`Number of recent tournaments to record is already set to ${num}.`);
          }
          room.settings.tournaments.recentToursLength = num;
          if (room.settings.tournaments.recentTours) {
            while (room.settings.tournaments.recentTours.length > num) {
              room.settings.tournaments.recentTours.pop();
            }
          }
          room.saveSettings();
          this.privateModAction(`Number of recent tournaments to record was set to ${num} by ${user.name}.`);
          this.modlog("TOUR SETTINGS", null, `recent tours: ${num} most recent`);
        } else {
          if (forcedelete && room.settings.tournaments.recentTours) {
            delete room.settings.tournaments.recentTours;
            this.privateModAction(`Recent tournaments list was deleted by ${user.name}.`);
            this.modlog("TOUR SETTINGS", null, `recent tours: delete`);
          }
          if (!room.settings.tournaments.recentToursLength) {
            throw new Chat.ErrorMessage(`Number of recent tournaments to record is already disabled.`);
          }
          delete room.settings.tournaments.recentToursLength;
          room.saveSettings();
          this.privateModAction(`Number of recent tournaments to record was turned off by ${user.name}.`);
          this.modlog("TOUR SETTINGS", null, `recent tours: off`);
        }
      },
      blockrecents(target, room, user) {
        var _a;
        room = this.requireRoom();
        this.checkCan("declare", null, room);
        target = toID(target);
        if (!target || !this.meansYes(target) && !this.meansNo(target)) {
          if (room.settings.tournaments?.blockRecents) {
            this.sendReply(`Recent tournaments are currently ${room.settings.tournaments.blockRecents ? "" : "NOT "} blocked from being made.`);
          }
          return this.parse(`/help tour settings`);
        }
        (_a = room.settings).tournaments || (_a.tournaments = {});
        if (this.meansYes(target)) {
          if (room.settings.tournaments.blockRecents) {
            throw new Chat.ErrorMessage(`Recent tournaments are already blocked from being made.`);
          }
          room.settings.tournaments.blockRecents = true;
          room.saveSettings();
          this.privateModAction(`Recent tournaments were blocked from being made by ${user.name}.`);
          this.modlog("TOUR SETTINGS", null, `recent tour block: on`);
        } else {
          if (!room.settings.tournaments.blockRecents) {
            throw new Chat.ErrorMessage(`Recent tournaments are already allowed to be remade.`);
          }
          delete room.settings.tournaments.blockRecents;
          room.saveSettings();
          this.privateModAction(`Recent tournaments were allowed to be remade by ${user.name}.`);
          this.modlog("TOUR SETTINGS", null, `recent tour block: off`);
        }
      },
      "": "help",
      help() {
        this.parse(`${this.cmdToken}help tour settings`);
      }
    },
    settingshelp: [
      `/tour settings autodq <minutes|off> - Sets the automatic disqualification timeout for every tournament.`,
      `/tour settings autostart <on|minutes|off> - Sets the automatic start timeout for every tournament.`,
      `/tour settings forcepublic <on|off> - Specifies whether users can hide their battles for every tournament.`,
      `/tour settings forcetimer <on|off> - Specifies whether users can toggle the timer for every tournament.`,
      `/tour settings modjoin <on|off> - Specifies whether users can modjoin their battles for every tournament.`,
      `/tour settings autoconfirmedonly: <on|off> - Set requirement for signups for this tournament. If this is on, only autoconfirmed users can join a tournament.`,
      `/tour settings playercap <number> - Sets the playercap for every tournament.`,
      `/tour settings scouting <on|off> - Specifies whether users can spectate other participants for every tournament.`,
      `/tour settings recenttours <number|off|forcedelete> - Specifies the amount of recent tournaments to list in /recenttours.`,
      `/tour settings blockrecents <on|off> - Toggles blocking tours in /recenttours from being made.`,
      `Requires: # &`
    ]
  },
  tournamenthelp() {
    if (!this.runBroadcast())
      return;
    this.sendReplyBox(
      `Tournament Commands<br/>- create/new &lt;format>, &lt;type>, [ &lt;comma-separated arguments>]: Creates a new tournament in the current room.<br />- rules &lt;comma-separated arguments>: Sets the custom rules for the tournament before it has started. <a href="view-battlerules">Custom rules help/list</a><br />- end/stop/delete: Forcibly ends the tournament in the current room.<br />- begin/start: Starts the tournament in the current room.<br /><br /><details class="readmore"><summary>Configuration Commands</summary>- settype &lt;type> [, &lt;comma-separated arguments>]: Modifies the type of tournament after it's been created, but before it has started.<br />- cap/playercap &lt;cap>: Sets the player cap of the tournament before it has started.<br />- viewrules/viewbanlist: Shows the custom rules for the tournament.<br />- clearrules/clearbanlist: Clears the custom rules for the tournament before it has started.<br />- name &lt;name>: Sets a custom name for the tournament.<br />- clearname: Clears the custom name of the tournament.<br />- autostart/setautostart &lt;on|minutes|off>: Sets the automatic start timeout.<br />- dq/disqualify &lt;user>: Disqualifies a user.<br />- autodq/setautodq &lt;minutes|off>: Sets the automatic disqualification timeout.<br />- runautodq: Manually run the automatic disqualifier.<br />- autoconfirmedonly/onlyautoconfirmed/aconly/onlyac &lt;on|off>: Set requirement for signups for this tournament. If this is on, only autoconfirmed users can join a tournament.<br />- scouting &lt;allow|disallow>: Specifies whether joining tournament matches while in a tournament is allowed.<br />- modjoin &lt;allow|disallow>: Specifies whether players can modjoin their battles.<br />- forcetimer &lt;on|off>: Turn on the timer for tournament battles.<br />- forcepublic &lt;on|off>: Forces tournament battles and their replays to be public.<br />- getusers: Lists the users in the current tournament.<br />- announce/announcements &lt;on|off>: Enables/disables tournament announcements for the current room.<br />- banuser/unbanuser &lt;user>: Bans/unbans a user from joining tournaments in this room. Lasts 2 weeks.<br />- sub/replace &lt;olduser>, &lt;newuser>: Substitutes a new user for an old one<br />- settings: Do <code>/help tour settings</code> for more information<br /></details><br />You can also consult <a href="https://www.smogon.com/forums/threads/3570628/#post-6777489">more detailed help</a>.`
    );
  }
};
const roomSettings = [
  (room) => ({
    label: "Tournament Forced Public Battles",
    permission: "editroom",
    options: [
      ["on", room.settings.tournaments?.forcePublic || "tour settings forcepublic on"],
      ["off", !room.settings.tournaments?.forcePublic || "tour settings forcepublic off"]
    ]
  }),
  (room) => ({
    label: "Tournament Forced Timer",
    permission: "editroom",
    options: [
      ["on", room.settings.tournaments?.forceTimer || "tour settings forcetimer on"],
      ["off", !room.settings.tournaments?.forceTimer || "tour settings forcetimer off"]
    ]
  }),
  (room) => ({
    label: "Tournament Modjoin",
    permission: "editroom",
    options: [
      ["allow", room.settings.tournaments?.allowModjoin || "tour settings modjoin allow"],
      ["disallow", !room.settings.tournaments?.allowModjoin || "tour settings modjoin disallow"]
    ]
  }),
  (room) => ({
    label: "Tournament Autoconfirmed Only",
    permission: "editroom",
    options: [
      ["on", room.settings.tournaments?.autoconfirmedOnly || "tour settings aconly on"],
      ["off", !room.settings.tournaments?.autoconfirmedOnly || "tour settings aconly off"]
    ]
  }),
  (room) => ({
    label: "Tournament Scouting",
    permission: "editroom",
    options: [
      ["allow", room.settings.tournaments?.allowScouting || "tour settings scouting allow"],
      ["disallow", !room.settings.tournaments?.allowScouting || "tour settings scouting disallow"]
    ]
  }),
  (room) => ({
    label: "Tournament Recent Tours",
    permission: "editroom",
    options: ["off", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(
      (setting) => [
        `${setting}`,
        setting === (room.settings.tournaments?.recentToursLength || "off") || `tour settings recenttours ${setting}`
      ]
    )
  }),
  (room) => ({
    label: "Tournament Block Recent Tours",
    permission: "editroom",
    options: [
      ["on", room.settings.tournaments?.blockRecents || "tour settings blockrecents on"],
      ["off", !room.settings.tournaments?.blockRecents || "tour settings blockrecents off"]
    ]
  })
];
const Tournaments = {
  TournamentGenerators,
  TournamentPlayer,
  Tournament,
  createTournament,
  commands,
  roomSettings
};
for (const room of Rooms.rooms.values()) {
  const announcements = room.settings.tourAnnouncements;
  delete room.settings.tourAnnouncements;
  if (!announcements) {
    room.saveSettings();
    continue;
  }
  if (!room.settings.tournaments)
    room.settings.tournaments = {};
  room.settings.tournaments.announcements = announcements;
  room.saveSettings();
}
//# sourceMappingURL=index.js.map
