"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_sample_teams = require("../chat-plugins/sample-teams");
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.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;
    this.isEnded = false;
    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;
    }
    for (const player of this.players) {
      player.unlinkUser();
    }
    this.isEnded = true;
    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`Added bans - ${bans.join(", ")}`);
    if (unbans.length)
      html.push(import_lib.Utils.html`Removed bans - ${unbans.join(", ")}`);
    if (restrictions.length)
      html.push(import_lib.Utils.html`Added restrictions - ${restrictions.join(", ")}`);
    if (addedRules.length)
      html.push(import_lib.Utils.html`Added rules - ${addedRules.join(", ")}`);
    if (removedRules.length)
      html.push(import_lib.Utils.html`Removed rules - ${removedRules.join(", ")}`);
    return html.join(`
`);
  }
  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(`
The tournament was forcefully ended.
You can finish playing, but this battle is no longer considered a tournament battle.
`);
        }
      }
    }
    this.room.add("|tournament|forceend");
    this.remove();
  }
  updateFor(targetUser, connection) {
    if (!connection)
      connection = targetUser;
    if (this.isEnded)
      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 isJoined = targetUser.id in this.playerTable;
    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.isEnded)
      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)) {
      output.sendReply("|tournament|error|Banned");
      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) {
    if (!(userid in this.playerTable)) {
      if (output)
        output.sendReply("|tournament|error|UserNotAdded");
      return;
    }
    for (const player of this.players) {
      if (player.id === userid) {
        this.players.splice(this.players.indexOf(player), 1);
        break;
      }
    }
    this.playerTable[userid].destroy();
    delete this.playerTable[userid];
    this.playerCount--;
    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)) {
      output.errorReply(`${replacementUser.name} is banned from joining tournaments.`);
      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;
    }
    if (this.playerTable[user.id].pendingChallenge) {
      this.cancelChallenge(user, output);
    }
    this.renamePlayer(replacementUser, user.id);
    const newPlayer = this.playerTable[replacementUser.id];
    let matchPlayer = null;
    if (newPlayer.inProgressMatch) {
      matchPlayer = newPlayer;
    } else {
      for (const player of this.players) {
        if (player.inProgressMatch && player.inProgressMatch.to === newPlayer) {
          matchPlayer = player;
          break;
        }
      }
    }
    if (matchPlayer?.inProgressMatch) {
      matchPlayer.inProgressMatch.to.isBusy = false;
      matchPlayer.isBusy = false;
      matchPlayer.inProgressMatch.room.addRaw(
        import_lib.Utils.html`${user.name} is no longer in the tournament.
` + `You can finish playing, but this battle is no longer considered a tournament battle.
`
      ).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 = newPlayer.pendingChallenge && (newPlayer.pendingChallenge.from || newPlayer.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);
      if (matchFrom.room.battle)
        matchFrom.room.battle.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.battle)
        matchRoom.battle.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.isEnded)
      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).`);
  }
  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.`);
  }
  showSampleTeams() {
    if (import_sample_teams.teamData.teams[this.baseFormat]) {
      let buf = ``;
      for (const categoryName in import_sample_teams.teamData.teams[this.baseFormat]) {
        if (!Object.keys(import_sample_teams.teamData.teams[this.baseFormat][categoryName]).length)
          continue;
        if (buf)
          buf += `
`;
        buf += `${categoryName.toUpperCase()}
`;
        for (const [i, teamName] of Object.keys(import_sample_teams.teamData.teams[this.baseFormat][categoryName]).entries()) {
          if (i)
            buf += `
`;
          buf += import_sample_teams.SampleTeams.formatTeam(teamName, import_sample_teams.teamData.teams[this.baseFormat][categoryName][teamName], true);
        }
        buf += `Sample Teams for ${import_sample_teams.SampleTeams.getFormatName(this.baseFormat)}
${buf}`).update();
    }
  }
  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,
      p1: {
        user: from,
        team: challenge.team,
        hidden: challenge.hidden,
        inviteOnly: challenge.inviteOnly
      },
      p2: {
        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
    });
    if (!room?.battle)
      throw new Error(`Failed to create battle in ${room}`);
    challenge.from.pendingChallenge = null;
    player.pendingChallenge = null;
    from.sendTo(this.room, '|tournament|update|{"challenging":null}');
    user.sendTo(this.room, '|tournament|update|{"challenged":null}');
    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.battle.timer.start();
    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) {
      if (user.id === oldUserid) {
        this.playerTable[user.id].name = user.name;
      } else {
        this.playerTable[user.id] = this.playerTable[oldUserid];
        this.playerTable[user.id].id = user.id;
        this.playerTable[user.id].name = user.name;
        delete this.playerTable[oldUserid];
      }
    }
    this.updateFor(user);
  }
  onBattleJoin(room, user) {
    if (!room.p1 || !room.p2)
      return;
    if (this.allowScouting || this.isEnded || 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.battle)
      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.battle.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;
  }
  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);
    if (settings.showSampleTeams)
      tour.showSampleTeams();
  }
  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 += `
Previous tournaments: `;
      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} `);
      }
      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} ,  [, ]`);
      }
      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``
            ).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`
${section}:
• ${name}`;
        } else {
          buf += import_lib.Utils.html`
• ${name}`;
        }
      }
      this.sendReplyBox(`Valid Formats: 
${buf}
`);
    },
    banuser(target, room, user) {
      room = this.requireRoom();
      const [userid, ...reasonsArray] = target.split(",").map((item) => item.trim());
      if (!target) {
        return this.sendReply(`Usage: /tour banuser , `);
      }
      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);
      }
      const tour = room.getGame(Tournament);
      if (tour)
        tour.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 `);
      }
      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(
        `${users.length}/${tournament.players.length}` + import_lib.Utils.html` users remain in this tournament:
${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} `);
      }
      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|This tournament includes:
${tournament.getCustomRules()}
`);
    },
    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}  [, ]`);
      }
      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} ; The current player cap is ${tournament.playerCap}`);
        } else {
          return this.sendReply(`Usage: /tour ${cmd} `);
        }
      }
      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 ");
        this.sendReply("Rules can be: -bannedthing, +un[banned|restricted]thing, *restrictedthing, !removedrule, addedrule");
        this.parse("/tour viewrules");
        return this.sendReplyBox(`Source
/tour rules ${tournament.customRules}This tournament includes:
${tournament.getCustomRules()}
`
        );
        this.privateModAction(`${user.name} updated the tournament's custom rules.`);
        this.modlog("TOUR RULES", null, tournament.customRules.join(", "));
        this.sendReplyBox(`Source
/tour rules ${tournament.customRules}
- create/new <format>, <type>, [ <comma-separated arguments>]: Creates a new tournament in the current room.
- rules <comma-separated arguments>: Sets the custom rules for the tournament before it has started. Custom rules help/list
- end/stop/delete: Forcibly ends the tournament in the current room.
- begin/start: Starts the tournament in the current room.
Configuration Commands
- settype <type> [, <comma-separated arguments>]: Modifies the type of tournament after it's been created, but before it has started.
- cap/playercap <cap>: Sets the player cap of the tournament before it has started.
- viewrules/viewbanlist: Shows the custom rules for the tournament.
- clearrules/clearbanlist: Clears the custom rules for the tournament before it has started.
- name <name>: Sets a custom name for the tournament.
- clearname: Clears the custom name of the tournament.
- autostart/setautostart <on|minutes|off>: Sets the automatic start timeout.
- dq/disqualify <user>: Disqualifies a user.
- autodq/setautodq <minutes|off>: Sets the automatic disqualification timeout.
- runautodq: Manually run the automatic disqualifier.
- scouting <allow|disallow>: Specifies whether joining tournament matches while in a tournament is allowed.
- modjoin <allow|disallow>: Specifies whether players can modjoin their battles.
- forcetimer <on|off>: Turn on the timer for tournament battles.
- forcepublic <on|off>: Forces tournament battles and their replays to be public.
- getusers: Lists the users in the current tournament.
- announce/announcements <on|off>: Enables/disables tournament announcements for the current room.
- banuser/unbanuser <user>: Bans/unbans a user from joining tournaments in this room. Lasts 2 weeks.
- sub/replace <olduser>, <newuser>: Substitutes a new user for an old one
- settings: Do /help tour settings for more information
You can also consult more detailed help.`
    );
  }
};
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 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