mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-22 01:35:31 -05:00
Cleanup more battle implementations
This is mostly just refactoring, but the refactor caught a few bugs: - Fix a race condition when destroying bestof games - Fix `/tour settings scouting off` error message
This commit is contained in:
parent
6bccd4f622
commit
2a48cbd064
|
|
@ -311,7 +311,7 @@ export class HelpTicket extends Rooms.SimpleRoomGame {
|
|||
|
||||
forfeit(user: User) {
|
||||
if (!(user.id in this.playerTable)) return;
|
||||
this.removePlayer(user);
|
||||
this.removePlayer(this.playerTable[user.id]);
|
||||
if (!this.ticket.open) return;
|
||||
this.room.modlog({action: 'TICKETABANDON', isGlobal: false, loggedBy: user.id});
|
||||
this.addText(`${user.name} is no longer interested in this ticket.`, user);
|
||||
|
|
|
|||
|
|
@ -486,7 +486,7 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
|
|||
if (player.completed) return user.sendTo(this.room, "You have already completed this scavenger hunt.");
|
||||
this.runEvent('Leave', player);
|
||||
this.joinedIps = this.joinedIps.filter(ip => !player.joinIps.includes(ip));
|
||||
this.removePlayer(user);
|
||||
this.removePlayer(player);
|
||||
this.leftHunt[user.id] = 1;
|
||||
user.sendTo(this.room, "You have left the scavenger hunt.");
|
||||
}
|
||||
|
|
@ -903,8 +903,7 @@ export class ScavengerHunt extends Rooms.RoomGame<ScavengerHuntPlayer> {
|
|||
// do not remove players that have completed - they should still get to see the answers
|
||||
if (player.completed) return true;
|
||||
|
||||
player.destroy();
|
||||
delete this.playerTable[userid];
|
||||
this.removePlayer(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -577,14 +577,14 @@ export class Trivia extends Rooms.RoomGame<TriviaPlayer> {
|
|||
this.kickedUsers.add(id);
|
||||
}
|
||||
|
||||
super.removePlayer(user);
|
||||
this.removePlayer(this.playerTable[user.id]);
|
||||
}
|
||||
|
||||
leave(user: User) {
|
||||
if (!this.playerTable[user.id]) {
|
||||
throw new Chat.ErrorMessage(this.room.tr`You are not a player in the current game.`);
|
||||
}
|
||||
super.removePlayer(user);
|
||||
this.removePlayer(this.playerTable[user.id]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1387,7 +1387,7 @@ export class Mastermind extends Rooms.SimpleRoomGame {
|
|||
if (lbEntry) {
|
||||
this.leaderboard.set(user.id, {...lbEntry, hasLeft: true});
|
||||
}
|
||||
super.removePlayer(user);
|
||||
this.removePlayer(this.playerTable[user.id]);
|
||||
}
|
||||
|
||||
kick(toKick: User, kicker: User) {
|
||||
|
|
@ -1411,7 +1411,7 @@ export class Mastermind extends Rooms.SimpleRoomGame {
|
|||
}
|
||||
}
|
||||
|
||||
super.removePlayer(toKick);
|
||||
this.removePlayer(this.playerTable[toKick.id]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -177,7 +177,8 @@ export class UNO extends Rooms.RoomGame<UNOPlayer> {
|
|||
|
||||
override leaveGame(user: User) {
|
||||
if (!(user.id in this.playerTable)) return false;
|
||||
if ((this.state === 'signups' && this.removePlayer(user)) || this.eliminate(user.id)) {
|
||||
const player = this.playerTable[user.id];
|
||||
if ((this.state === 'signups' && this.removePlayer(player)) || this.eliminate(user.id)) {
|
||||
this.sendToRoom(`${user.name} has left the game of UNO.`);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -694,9 +694,7 @@ export class CommandContext extends MessageContext {
|
|||
Chat.PrivateMessages.send(message, this.user, this.pmTarget);
|
||||
} else if (this.room) {
|
||||
this.room.add(`|c|${this.user.getIdentity(this.room)}|${message}`);
|
||||
if (this.room.game && this.room.game.onLogMessage) {
|
||||
this.room.game.onLogMessage(message, this.user);
|
||||
}
|
||||
this.room.game?.onLogMessage?.(message, this.user);
|
||||
} else {
|
||||
this.connection.popup(`Your message could not be sent:\n\n${message}\n\nIt needs to be sent to a user or room.`);
|
||||
}
|
||||
|
|
@ -784,8 +782,7 @@ export class CommandContext extends MessageContext {
|
|||
return this.checkBanwords(room.parent as ChatRoom, message);
|
||||
}
|
||||
checkGameFilter() {
|
||||
if (!this.room?.game || !this.room.game.onChatMessage) return;
|
||||
return this.room.game.onChatMessage(this.message, this.user);
|
||||
return this.room?.game?.onChatMessage?.(this.message, this.user);
|
||||
}
|
||||
pmTransform(originalMessage: string, sender?: User, receiver?: User | null | string) {
|
||||
if (!sender) {
|
||||
|
|
|
|||
|
|
@ -1339,9 +1339,7 @@ export const Punishments = new class {
|
|||
if (room.subRooms) {
|
||||
for (const subRoom of room.subRooms.values()) {
|
||||
for (const curUser of affected) {
|
||||
if (subRoom.game && subRoom.game.removeBannedUser) {
|
||||
subRoom.game.removeBannedUser(curUser);
|
||||
}
|
||||
subRoom.game?.removeBannedUser?.(curUser);
|
||||
curUser.leaveRoom(subRoom.roomid);
|
||||
}
|
||||
}
|
||||
|
|
@ -1359,9 +1357,7 @@ export const Punishments = new class {
|
|||
for (const curUser of affected) {
|
||||
// ensure there aren't roombans so nothing gets mixed up
|
||||
Punishments.roomUnban(room, (curUser as any).id || curUser);
|
||||
if (room.game && room.game.removeBannedUser) {
|
||||
room.game.removeBannedUser(curUser);
|
||||
}
|
||||
room.game?.removeBannedUser?.(curUser);
|
||||
curUser.leaveRoom(room.roomid);
|
||||
}
|
||||
|
||||
|
|
@ -1841,9 +1837,7 @@ export const Punishments = new class {
|
|||
}
|
||||
if (punishment.type !== 'ROOMBAN' && punishment.type !== 'BLACKLIST') return null;
|
||||
const room = Rooms.get(roomid)!;
|
||||
if (room.game && room.game.removeBannedUser) {
|
||||
room.game.removeBannedUser(user);
|
||||
}
|
||||
room.game?.removeBannedUser?.(user);
|
||||
user.leaveRoom(room.roomid);
|
||||
}
|
||||
return punishments;
|
||||
|
|
|
|||
|
|
@ -494,7 +494,7 @@ export interface RoomBattleOptions {
|
|||
|
||||
export class RoomBattle extends RoomGame<RoomBattlePlayer> {
|
||||
override readonly gameid = 'battle' as ID;
|
||||
override readonly room: GameRoom;
|
||||
override readonly room!: GameRoom;
|
||||
override readonly title: string;
|
||||
override readonly allowRenames: boolean;
|
||||
readonly format: string;
|
||||
|
|
@ -538,7 +538,6 @@ export class RoomBattle extends RoomGame<RoomBattlePlayer> {
|
|||
constructor(room: GameRoom, options: RoomBattleOptions) {
|
||||
super(room);
|
||||
const format = Dex.formats.get(options.format, true);
|
||||
this.room = room;
|
||||
this.title = format.name;
|
||||
this.options = options;
|
||||
if (!this.title.endsWith(" Battle")) this.title += " Battle";
|
||||
|
|
@ -863,7 +862,6 @@ export class RoomBattle extends RoomGame<RoomBattlePlayer> {
|
|||
Chat.parse(command, this.room, uploader, uploader.connections[0]);
|
||||
}
|
||||
}
|
||||
// @ts-ignore - Tournaments aren't TS'd yet
|
||||
this.room.parent?.game?.onBattleWin?.(this.room, winnerid);
|
||||
// If the room's replay was hidden, don't let users join after the game is over
|
||||
if (this.room.hideReplay) {
|
||||
|
|
@ -942,9 +940,6 @@ export class RoomBattle extends RoomGame<RoomBattlePlayer> {
|
|||
}
|
||||
if (!player.active) this.onJoin(user);
|
||||
}
|
||||
override onUpdateConnection(user: User, connection: Connection | null = null) {
|
||||
this.onConnect(user, connection);
|
||||
}
|
||||
override onRename(user: User, oldUserid: ID, isJoining: boolean, isForceRenamed: boolean) {
|
||||
if (user.id === oldUserid) return;
|
||||
if (!this.playerTable) {
|
||||
|
|
@ -1362,6 +1357,7 @@ export class BestOfPlayer extends RoomGamePlayer<BestOfGame> {
|
|||
export class BestOfGame extends RoomGame<BestOfPlayer> {
|
||||
override readonly gameid = 'bestof' as ID;
|
||||
override allowRenames = false;
|
||||
override room!: GameRoom;
|
||||
bestOf: number;
|
||||
format: Format;
|
||||
winThreshold: number;
|
||||
|
|
@ -1378,7 +1374,7 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
|||
/** Does NOT control bestof's own timer, which is always-on. Controls timers in sub-battles. */
|
||||
needsTimer = false;
|
||||
score: number[] | null = null;
|
||||
constructor(room: Room, options: RoomBattleOptions) {
|
||||
constructor(room: GameRoom, options: RoomBattleOptions) {
|
||||
super(room);
|
||||
this.format = Dex.formats.get(options.format);
|
||||
this.bestOf = Number(Dex.formats.getRuleTable(this.format).valueRules.get('bestof'))!;
|
||||
|
|
@ -1584,7 +1580,7 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
|||
}
|
||||
|
||||
onBattleWin(room: Room, winnerid: ID) {
|
||||
if (this.ended) return; // ???
|
||||
if (this.ended) return; // can happen if the bo3 is destroyed fsr
|
||||
|
||||
const winner = winnerid ? this.playerTable[winnerid] : null;
|
||||
this.games[this.games.length - 1].winner = winner;
|
||||
|
|
@ -1596,7 +1592,6 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
|||
}
|
||||
this.room.add(Utils.html`|html|${winner.name} won game ${this.games.length}!`).update();
|
||||
if (winner.wins >= this.winThreshold) {
|
||||
this.setEnded();
|
||||
return this.onEnd(winner.id);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1667,8 +1662,13 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
|||
this.nextGame();
|
||||
}
|
||||
}
|
||||
async onEnd(winnerid: ID) {
|
||||
override setEnded() {
|
||||
this.clearWaiting();
|
||||
super.setEnded();
|
||||
}
|
||||
async onEnd(winnerid: ID) {
|
||||
if (this.ended) return;
|
||||
this.setEnded();
|
||||
this.room.add(`|allowleave|`).update();
|
||||
const winner = winnerid ? this.playerTable[winnerid] : null;
|
||||
this.winner = winner;
|
||||
|
|
@ -1682,8 +1682,7 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
|||
const p1 = this.players[0];
|
||||
const p2 = this.players[1];
|
||||
this.score = this.players.map(p => p.wins);
|
||||
// @ts-ignore - Tournaments aren't TS'd yet
|
||||
this.room.parent?.game?.onBattleWin?.(this.room, winner.id);
|
||||
this.room.parent?.game?.onBattleWin?.(this.room, winnerid);
|
||||
// run elo stuff here
|
||||
let p1score = 0.5;
|
||||
if (winner === p1) {
|
||||
|
|
@ -1694,7 +1693,7 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
|||
|
||||
const {rated, room} = this.games[this.games.length - 1];
|
||||
const battle = room.battle;
|
||||
if (!battle) throw new Error(`Room ${room.roomid} has no battle???`);
|
||||
if (!battle) return; // expired room
|
||||
if (rated) {
|
||||
room.rated = rated; // just in case
|
||||
const winnerUser = winner?.getUser();
|
||||
|
|
@ -1728,12 +1727,12 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
|||
return true;
|
||||
}
|
||||
override destroy() {
|
||||
this.clearWaiting();
|
||||
this.setEnded();
|
||||
for (const {room} of this.games) room.expire();
|
||||
this.games = [];
|
||||
for (const p of this.players) p.destroy();
|
||||
this.players = [];
|
||||
this.playerTable = {};
|
||||
for (const {room} of this.games) room.destroy();
|
||||
this.games = [];
|
||||
this.winner = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,23 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
// globally Rooms.RoomGamePlayer
|
||||
/**
|
||||
* Available globally as `Rooms.RoomGamePlayer`.
|
||||
*
|
||||
* Players are an abstraction for the people playing a `RoomGame`. They
|
||||
* may or may not be associated with a user. If they are, the game will
|
||||
* appear on the user's games list.
|
||||
*
|
||||
* Either way, they give a level of abstraction to players, allowing you
|
||||
* to easily sub out users or otherwise associate/dissociate users.
|
||||
*
|
||||
* You should mostly only be adding/removing players with functions like
|
||||
* `addPlayer`, `removePlayer`, etc, and changing the associated user
|
||||
* with `setPlayerUser` or `setEnded`.
|
||||
*
|
||||
* Do not modify `playerTable` or `player.id` yourself, it will make
|
||||
* users' games lists get out of sync.
|
||||
*/
|
||||
export class RoomGamePlayer<GameClass extends RoomGame = SimpleRoomGame> {
|
||||
readonly num: number;
|
||||
readonly game: GameClass;
|
||||
|
|
@ -31,11 +47,15 @@ export class RoomGamePlayer<GameClass extends RoomGame = SimpleRoomGame> {
|
|||
/**
|
||||
* This will be '' if there's no user associated with the player.
|
||||
*
|
||||
* This includes after the game ends, where the game should no longer
|
||||
* be in the user's game list.
|
||||
* After the game ends, this will still be the user's ID, but it
|
||||
* won't be in the user's game list anymore.
|
||||
*
|
||||
* We intentionally don't hold a direct reference to the user.
|
||||
*
|
||||
* Do not modify directly. You usually want `game.updatePlayer`
|
||||
* (respects allowRenames) or `game.setPlayerUser` (overrides
|
||||
* allowRenames).
|
||||
*
|
||||
* If modifying: remember to sync `this.game.playerTable` and
|
||||
* `this.getUser().games`.
|
||||
*/
|
||||
|
|
@ -91,6 +111,11 @@ export abstract class RoomGame<PlayerClass extends RoomGamePlayer = RoomGamePlay
|
|||
*
|
||||
* Does not contain userless players: use this.players for the full list.
|
||||
*
|
||||
* Do not iterate. You usually want to iterate `game.players` instead.
|
||||
*
|
||||
* Do not modify directly. You usually want `game.addPlayer` or
|
||||
* `game.removePlayer` instead.
|
||||
*
|
||||
* Not a source of truth. Should be kept in sync with
|
||||
* `Object.fromEntries(this.players.filter(p => p.id).map(p => [p.id, p]))`
|
||||
*/
|
||||
|
|
@ -187,14 +212,7 @@ export abstract class RoomGame<PlayerClass extends RoomGamePlayer = RoomGamePlay
|
|||
|
||||
abstract makePlayer(user: User | string | null, ...rest: any[]): PlayerClass;
|
||||
|
||||
removePlayer(player: PlayerClass | User) {
|
||||
if (player instanceof Users.User) {
|
||||
// API changed
|
||||
// TODO: deprecate
|
||||
player = this.playerTable[player.id];
|
||||
if (!player) throw new Error("Player not found");
|
||||
}
|
||||
|
||||
removePlayer(player: PlayerClass) {
|
||||
this.setPlayerUser(player, null);
|
||||
const playerIndex = this.players.indexOf(player);
|
||||
if (playerIndex < 0) return false;
|
||||
|
|
@ -243,13 +261,16 @@ export abstract class RoomGame<PlayerClass extends RoomGamePlayer = RoomGamePlay
|
|||
|
||||
/**
|
||||
* Called when a user uses /forfeit
|
||||
* Also planned to be used for some force-forfeit situations, such
|
||||
* Also used for some force-forfeit situations, such
|
||||
* as when a user changes their name and .allowRenames === false
|
||||
* This is strongly recommended to be supported, as the user is
|
||||
* extremely unlikely to keep playing after this function is
|
||||
* called.
|
||||
*
|
||||
* @param user
|
||||
* @param reason if a forced forfeit; should start with space
|
||||
*/
|
||||
forfeit?(user: User | string): void;
|
||||
forfeit?(user: User | string, reason?: string): void;
|
||||
|
||||
/**
|
||||
* Called when a user uses /choose [text]
|
||||
|
|
@ -290,12 +311,18 @@ export abstract class RoomGame<PlayerClass extends RoomGamePlayer = RoomGamePlay
|
|||
*/
|
||||
onJoin(user: User, connection: Connection) {}
|
||||
|
||||
/**
|
||||
* Called when a subroom game (battle or bestof) ends, on the
|
||||
* parent game (bestof or tournament).
|
||||
*/
|
||||
onBattleWin?(room: GameRoom, winnerid: ID): void;
|
||||
|
||||
/**
|
||||
* Called when a user is banned from the room this game is taking
|
||||
* place in.
|
||||
*/
|
||||
removeBannedUser(user: User) {
|
||||
this.forfeit?.(user);
|
||||
this.forfeit?.(user, " lost by being banned.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -332,6 +359,10 @@ export abstract class RoomGame<PlayerClass extends RoomGamePlayer = RoomGamePlay
|
|||
* is updated in some way (such as by changing user or renaming).
|
||||
* If you don't want this behavior, override onUpdateConnection
|
||||
* and/or onRename.
|
||||
*
|
||||
* This means that by default, it's called twice: once when
|
||||
* connected to the server (as guest1763 or whatever), and once
|
||||
* when logged in.
|
||||
*/
|
||||
onConnect(user: User, connection: Connection) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -778,9 +778,9 @@ export abstract class BasicRoom {
|
|||
if (this.parent === room) return;
|
||||
|
||||
if (this.parent) {
|
||||
(this as any).parent.subRooms.delete(this.roomid);
|
||||
(this.parent.subRooms as any).delete(this.roomid);
|
||||
if (!this.parent.subRooms!.size) {
|
||||
(this as any).parent.subRooms = null;
|
||||
(this.parent.subRooms as any) = null;
|
||||
}
|
||||
}
|
||||
(this as any).parent = room;
|
||||
|
|
@ -1048,7 +1048,7 @@ export abstract class BasicRoom {
|
|||
if (user.named) {
|
||||
this.reportJoin('l', user.getIdentity(this), user);
|
||||
}
|
||||
if (this.game && this.game.onLeave) this.game.onLeave(user);
|
||||
this.game?.onLeave?.(user);
|
||||
this.runAutoModchat();
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1468,8 +1468,7 @@ const commands: Chat.ChatCommands = {
|
|||
} else {
|
||||
Punishments.roomPunishName(room, targetUserid, punishment);
|
||||
}
|
||||
const tour = room.getGame(Tournament);
|
||||
if (tour) tour.removeBannedUser(targetUserid);
|
||||
room.getGame(Tournament)?.removeBannedUser(targetUserid);
|
||||
|
||||
this.modlog('TOURBAN', targetUser, reason);
|
||||
this.privateModAction(
|
||||
|
|
@ -1986,13 +1985,7 @@ const commands: Chat.ChatCommands = {
|
|||
if (this.meansYes(option)) {
|
||||
tournament.setForceTimer(true);
|
||||
for (const player of tournament.players) {
|
||||
const curMatch = player.inProgressMatch;
|
||||
if (curMatch) {
|
||||
const battle = curMatch.room.game;
|
||||
if (battle) {
|
||||
battle.startTimer();
|
||||
}
|
||||
}
|
||||
player.inProgressMatch?.room.game?.startTimer();
|
||||
}
|
||||
this.privateModAction(`The timer was turned on for the tournament by ${user.name}`);
|
||||
this.modlog('TOUR FORCETIMER', null, 'ON');
|
||||
|
|
@ -2012,27 +2005,25 @@ const commands: Chat.ChatCommands = {
|
|||
return this.parse(`/help tour settings`);
|
||||
}
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansYes(target)) {
|
||||
if (!room.settings.tournaments.allowModjoin) {
|
||||
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 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) {
|
||||
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');
|
||||
} 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) {
|
||||
|
|
@ -2040,27 +2031,25 @@ const commands: Chat.ChatCommands = {
|
|||
this.checkCan('declare', null, room);
|
||||
if (!target || (!this.meansYes(target) && !this.meansNo(target))) return this.parse(`/help tour settings`);
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansYes(target)) {
|
||||
if (!room.settings.tournaments.allowScouting) {
|
||||
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 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) {
|
||||
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');
|
||||
} 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',
|
||||
|
|
@ -2070,7 +2059,7 @@ const commands: Chat.ChatCommands = {
|
|||
room = this.requireRoom();
|
||||
this.checkCan('declare', null, room);
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.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) {
|
||||
|
|
@ -2088,27 +2077,25 @@ const commands: Chat.ChatCommands = {
|
|||
this.checkCan('declare', null, room);
|
||||
if (!target || (!this.meansNo(target) && !this.meansYes(target))) return this.parse(`/help tour settings`);
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansNo(target)) {
|
||||
if (room.settings.tournaments.forcePublic) {
|
||||
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 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) {
|
||||
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');
|
||||
} 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) {
|
||||
|
|
@ -2116,27 +2103,25 @@ const commands: Chat.ChatCommands = {
|
|||
this.checkCan('declare', null, room);
|
||||
if (!target || (!this.meansNo(target) && !this.meansYes(target))) return this.parse(`/help tour settings`);
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansNo(target)) {
|
||||
if (room.settings.tournaments.forceTimer) {
|
||||
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 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) {
|
||||
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');
|
||||
} 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) {
|
||||
|
|
@ -2147,19 +2132,18 @@ const commands: Chat.ChatCommands = {
|
|||
return this.parse(`/help tour settings`);
|
||||
}
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansNo(target)) {
|
||||
if (room.settings.tournaments.autostart) {
|
||||
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 (!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.`);
|
||||
|
|
@ -2192,19 +2176,18 @@ const commands: Chat.ChatCommands = {
|
|||
const num = Number(target);
|
||||
if (!target || (!this.meansNo(target) && isNaN(num))) return this.parse(`/help tour settings`);
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansNo(target)) {
|
||||
if (room.settings.tournaments.autodq) {
|
||||
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 (!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 * 1000;
|
||||
if (timeout < 0.5 * 60 * 1000 || timeout > Chat.MAX_TIMEOUT_DURATION) {
|
||||
|
|
@ -2228,19 +2211,18 @@ const commands: Chat.ChatCommands = {
|
|||
const num = parseInt(target);
|
||||
if (!target || (!this.meansNo(target) && isNaN(num))) return this.parse(`/help tour settings`);
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansNo(target)) {
|
||||
if (room.settings.tournaments.playerCap) {
|
||||
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 (!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.`);
|
||||
|
|
@ -2268,26 +2250,24 @@ const commands: Chat.ChatCommands = {
|
|||
this.checkCan('declare', null, room);
|
||||
if (!target) return this.parse(`/help tour settings`);
|
||||
const tour = room.getGame(Tournament);
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansYes(target)) {
|
||||
if (!room.settings.tournaments.showSampleTeams) {
|
||||
if (tour && !tour.isTournamentStarted) tour.showSampleTeams();
|
||||
room.settings.tournaments.showSampleTeams = true;
|
||||
room.saveSettings();
|
||||
this.privateModAction(`Show Sample Teams was set to ON for every tournament by ${user.name}`);
|
||||
this.modlog('TOUR SETTINGS', null, `show sample teams: ON`);
|
||||
} else {
|
||||
if (room.settings.tournaments.showSampleTeams) {
|
||||
throw new Chat.ErrorMessage(`Sample teams are already shown for every tournament.`);
|
||||
}
|
||||
if (tour && !tour.isTournamentStarted) tour.showSampleTeams();
|
||||
room.settings.tournaments.showSampleTeams = true;
|
||||
room.saveSettings();
|
||||
this.privateModAction(`Show Sample Teams was set to ON for every tournament by ${user.name}`);
|
||||
this.modlog('TOUR SETTINGS', null, `show sample teams: ON`);
|
||||
} else if (this.meansNo(target)) {
|
||||
if (room.settings.tournaments.showSampleTeams) {
|
||||
delete room.settings.tournaments.showSampleTeams;
|
||||
room.saveSettings();
|
||||
this.privateModAction(`Show Sample Teams was set to OFF for every tournament by ${user.name}`);
|
||||
this.modlog('TOUR SETTINGS', null, `show sample teams: OFF`);
|
||||
} else {
|
||||
if (!room.settings.tournaments.showSampleTeams) {
|
||||
throw new Chat.ErrorMessage(`Sample teams are already not shown for every tournament.`);
|
||||
}
|
||||
delete room.settings.tournaments.showSampleTeams;
|
||||
room.saveSettings();
|
||||
this.privateModAction(`Show Sample Teams was set to OFF for every tournament by ${user.name}`);
|
||||
this.modlog('TOUR SETTINGS', null, `show sample teams: OFF`);
|
||||
} else {
|
||||
return this.sendReply(`Usage: ${this.cmdToken}${this.fullCmd} <on|off>`);
|
||||
}
|
||||
|
|
@ -2295,15 +2275,14 @@ const commands: Chat.ChatCommands = {
|
|||
recenttours(target, room, user) {
|
||||
room = this.requireRoom();
|
||||
this.checkCan('declare', null, room);
|
||||
const num = parseInt(target);
|
||||
const force = toID(target) === 'forcedelete';
|
||||
if (!target ||
|
||||
(!this.meansNo(target) && !force &&
|
||||
(isNaN(num) || num > 15 || num < 0))) {
|
||||
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`);
|
||||
}
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
if (!isNaN(num) && num <= 15 && num >= 1) {
|
||||
room.settings.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}.`);
|
||||
}
|
||||
|
|
@ -2316,24 +2295,19 @@ const commands: Chat.ChatCommands = {
|
|||
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 (this.meansNo(target) || force) {
|
||||
if (room.settings.tournaments.recentToursLength) {
|
||||
delete room.settings.tournaments.recentToursLength;
|
||||
if (force) {
|
||||
delete room.settings.tournaments.recentTours;
|
||||
this.privateModAction(`Recent tournaments list was deleted by ${user.name}.`);
|
||||
this.modlog('TOUR SETTINGS', null, `recent tours: delete`);
|
||||
}
|
||||
room.saveSettings();
|
||||
this.privateModAction(`Number of recent tournaments to record was turned off by ${user.name}.`);
|
||||
this.modlog('TOUR SETTINGS', null, `recent tours: off`);
|
||||
} else {
|
||||
if (!force) {
|
||||
throw new Chat.ErrorMessage(`Number of recent tournaments to record is already disabled.`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sendReply(`Usage: ${this.cmdToken}${this.fullCmd} <number|off|forcedelete>`);
|
||||
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) {
|
||||
|
|
@ -2346,7 +2320,7 @@ const commands: Chat.ChatCommands = {
|
|||
}
|
||||
return this.parse(`/help tour settings`);
|
||||
}
|
||||
if (!room.settings.tournaments) room.settings.tournaments = {};
|
||||
room.settings.tournaments ||= {};
|
||||
if (this.meansYes(target)) {
|
||||
if (room.settings.tournaments.blockRecents) {
|
||||
throw new Chat.ErrorMessage(`Recent tournaments are already blocked from being made.`);
|
||||
|
|
|
|||
|
|
@ -1081,12 +1081,10 @@ export class User extends Chat.MessageContext {
|
|||
room.onJoin(this, connection);
|
||||
this.inRooms.add(roomid);
|
||||
}
|
||||
if (room.game && room.game.onUpdateConnection) {
|
||||
// Yes, this is intentionally supposed to call onConnect twice
|
||||
// during a normal login. Override onUpdateConnection if you
|
||||
// don't want this behavior.
|
||||
room.game.onUpdateConnection(this, connection);
|
||||
}
|
||||
// Yes, this is intentionally supposed to call onConnect twice
|
||||
// during a normal login. Override onUpdateConnection if you
|
||||
// don't want this behavior.
|
||||
room.game?.onUpdateConnection?.(this, connection);
|
||||
}
|
||||
this.updateReady(connection);
|
||||
}
|
||||
|
|
@ -1297,20 +1295,17 @@ export class User extends Chat.MessageContext {
|
|||
async tryJoinRoom(roomid: RoomID | Room, connection: Connection) {
|
||||
roomid = roomid && (roomid as Room).roomid ? (roomid as Room).roomid : roomid as RoomID;
|
||||
const room = Rooms.search(roomid);
|
||||
if (!room && roomid.startsWith('view-')) {
|
||||
return Chat.resolvePage(roomid, this, connection);
|
||||
}
|
||||
if (!room?.checkModjoin(this)) {
|
||||
if (!this.named) {
|
||||
return Rooms.RETRY_AFTER_LOGIN;
|
||||
} else {
|
||||
if (room) {
|
||||
connection.sendTo(roomid, `|noinit|joinfailed|The room "${roomid}" is invite-only, and you haven't been invited.`);
|
||||
} else {
|
||||
connection.sendTo(roomid, `|noinit|nonexistent|The room "${roomid}" does not exist.`);
|
||||
}
|
||||
return false;
|
||||
if (!room) {
|
||||
if (roomid.startsWith('view-')) {
|
||||
return Chat.resolvePage(roomid, this, connection);
|
||||
}
|
||||
connection.sendTo(roomid, `|noinit|nonexistent|The room "${roomid}" does not exist.`);
|
||||
return false;
|
||||
}
|
||||
if (!room.checkModjoin(this)) {
|
||||
if (!this.named) return Rooms.RETRY_AFTER_LOGIN;
|
||||
connection.sendTo(roomid, `|noinit|joinfailed|The room "${roomid}" is invite-only, and you haven't been invited.`);
|
||||
return false;
|
||||
}
|
||||
if ((room as GameRoom).tour) {
|
||||
const errorMessage = (room as GameRoom).tour!.onBattleJoin(room as GameRoom, this);
|
||||
|
|
@ -1546,20 +1541,13 @@ export class User extends Chat.MessageContext {
|
|||
destroy() {
|
||||
// deallocate user
|
||||
for (const roomid of this.games) {
|
||||
const room = Rooms.get(roomid);
|
||||
if (!room) {
|
||||
Monitor.warn(`while deallocating, room ${roomid} did not exist for ${this.id} in rooms ${[...this.inRooms]} and games ${[...this.games]}`);
|
||||
this.games.delete(roomid);
|
||||
continue;
|
||||
}
|
||||
const game = room.game;
|
||||
const game = Rooms.get(roomid)?.game;
|
||||
if (!game) {
|
||||
Monitor.warn(`while deallocating, room ${roomid} did not have a game for ${this.id} in rooms ${[...this.inRooms]} and games ${[...this.games]}`);
|
||||
this.games.delete(roomid);
|
||||
continue;
|
||||
}
|
||||
if (game.ended) continue;
|
||||
if (game.forfeit) game.forfeit(this);
|
||||
if (!game.ended) game.forfeit?.(this, " lost by being offline too long.");
|
||||
}
|
||||
this.clearChatQueue();
|
||||
this.destroyPunishmentTimer();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user