mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-09-09 09:45:44 -05:00
Allow BestOfGames to be private (#10208)
This commit is contained in:
@@ -1088,10 +1088,11 @@ export const commands: Chat.ChatCommands = {
|
||||
unlistroom: 'privateroom',
|
||||
privateroom(target, room, user, connection, cmd) {
|
||||
room = this.requireRoom();
|
||||
if (room.battle) {
|
||||
const battle = room.battle || room.bestOf;
|
||||
if (battle) {
|
||||
this.checkCan('editprivacy', null, room);
|
||||
if (room.battle.forcedSettings.privacy) {
|
||||
return this.errorReply(`This battle is required to be public because a player has a name prefixed by '${room.battle.forcedSettings.privacy}'.`);
|
||||
if (battle.forcedSettings.privacy) {
|
||||
return this.errorReply(`This battle is required to be public because a player has a name prefixed by '${battle.forcedSettings.privacy}'.`);
|
||||
}
|
||||
if (room.tour?.forcePublic) {
|
||||
return this.errorReply(`This battle can't be hidden, because the tournament is set to be forced public.`);
|
||||
@@ -1136,7 +1137,7 @@ export const commands: Chat.ChatCommands = {
|
||||
if (room.parent && room.parent.settings.isPrivate) {
|
||||
return this.errorReply(`This room's parent ${room.parent.title} must be public for this room to be public.`);
|
||||
}
|
||||
if (room.settings.isPersonal && !room.battle) {
|
||||
if (room.settings.isPersonal && !battle) {
|
||||
return this.errorReply(`This room can't be made public.`);
|
||||
}
|
||||
if (room.privacySetter && user.can('nooverride', null, room) && !user.can('makeroom')) {
|
||||
@@ -1156,7 +1157,7 @@ export const commands: Chat.ChatCommands = {
|
||||
room.setPrivate(false);
|
||||
} else {
|
||||
const settingName = (setting === true ? 'secret' : setting);
|
||||
if (room.subRooms) {
|
||||
if (room.subRooms && !room.bestOf) {
|
||||
if (settingName === 'secret') return this.errorReply("Secret rooms cannot have subrooms.");
|
||||
for (const subRoom of room.subRooms.values()) {
|
||||
if (!subRoom.settings.isPrivate) {
|
||||
@@ -1173,7 +1174,7 @@ export const commands: Chat.ChatCommands = {
|
||||
}
|
||||
this.addModAction(`${user.name} made this room ${settingName}.`);
|
||||
this.modlog(`${settingName.toUpperCase()}ROOM`);
|
||||
if (!room.settings.isPersonal && !room.battle) room.setSection();
|
||||
if (!room.settings.isPersonal && !battle) room.setSection();
|
||||
room.setPrivate(setting);
|
||||
room.privacySetter = new Set([user.id]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Utils} from '../lib';
|
||||
import {RoomGamePlayer, RoomGame} from "./room-game";
|
||||
import type {RoomBattlePlayerOptions, RoomBattleOptions} from './room-battle';
|
||||
import type {PrivacySetting, RoomSettings} from './rooms';
|
||||
|
||||
const BEST_OF_IN_BETWEEN_TIME = 40;
|
||||
|
||||
@@ -67,7 +68,8 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
||||
bestOf: number;
|
||||
format: Format;
|
||||
winThreshold: number;
|
||||
options: Omit<RoomBattleOptions, 'players'> & {players: null};
|
||||
options: Omit<RoomBattleOptions, 'players'> & {parent: Room, players: null};
|
||||
forcedSettings: {modchat?: string | null, privacy?: string | null} = {};
|
||||
ties = 0;
|
||||
games: {room: GameRoom, winner: BestOfPlayer | null | undefined, rated: number}[] = [];
|
||||
playerNum = 0;
|
||||
@@ -89,9 +91,11 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
||||
if (!toID(this.title).includes('bestof')) {
|
||||
this.title += ` (Best-of-${this.bestOf})`;
|
||||
}
|
||||
this.room.bestOf = this;
|
||||
this.options = {
|
||||
...options,
|
||||
isBestOfSubBattle: true,
|
||||
parent: this.room,
|
||||
allowRenames: false,
|
||||
players: null,
|
||||
};
|
||||
@@ -114,6 +118,65 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
||||
this.room.auth.set(user.id, Users.PLAYER_SYMBOL);
|
||||
return player;
|
||||
}
|
||||
checkPrivacySettings(options: RoomBattleOptions & Partial<RoomSettings>) {
|
||||
let inviteOnly = false;
|
||||
const privacySetter = new Set<ID>([]);
|
||||
for (const p of options.players) {
|
||||
if (p.user) {
|
||||
if (p.inviteOnly) {
|
||||
inviteOnly = true;
|
||||
privacySetter.add(p.user.id);
|
||||
} else if (p.hidden) {
|
||||
privacySetter.add(p.user.id);
|
||||
}
|
||||
this.checkForcedUserSettings(p.user);
|
||||
}
|
||||
}
|
||||
|
||||
if (privacySetter.size) {
|
||||
const room = this.room;
|
||||
if (this.forcedSettings.privacy) {
|
||||
room.setPrivate(false);
|
||||
room.settings.modjoin = null;
|
||||
room.add(`|raw|<div class="broadcast-blue"><strong>This best-of set is required to be public due to a player having a name starting with '${this.forcedSettings.privacy}'.</div>`);
|
||||
} else if (!options.tour || (room.tour?.allowModjoin)) {
|
||||
room.setPrivate('hidden');
|
||||
if (inviteOnly) room.settings.modjoin = '%';
|
||||
room.privacySetter = privacySetter;
|
||||
if (inviteOnly) {
|
||||
room.settings.modjoin = '%';
|
||||
room.add(`|raw|<div class="broadcast-red"><strong>This best-of set is invite-only!</strong><br />Users must be invited with <code>/invite</code> (or be staff) to join</div>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkForcedUserSettings(user: User) {
|
||||
this.forcedSettings = {
|
||||
modchat: this.forcedSettings.modchat || Rooms.RoomBattle.battleForcedSetting(user, 'modchat'),
|
||||
privacy: this.forcedSettings.privacy || Rooms.RoomBattle.battleForcedSetting(user, 'privacy'),
|
||||
};
|
||||
if (
|
||||
this.players.some(p => p.getUser()?.battleSettings.special) ||
|
||||
(this.options.rated && this.forcedSettings.modchat)
|
||||
) {
|
||||
this.room.settings.modchat = '\u2606';
|
||||
}
|
||||
}
|
||||
setPrivacyOfGames(privacy: PrivacySetting) {
|
||||
for (let i = 0; i < this.games.length; i++) {
|
||||
const room = this.games[i].room;
|
||||
const prevRoom = this.games[i - 1]?.room;
|
||||
const gameNum = i + 1;
|
||||
|
||||
room.setPrivate(privacy);
|
||||
this.room.add(`|uhtmlchange|game${gameNum}|<a href="/${room.roomid}">${room.title}</a>`);
|
||||
room.add(`|uhtmlchange|bestof|<h2><strong>Game ${gameNum}</strong> of <a href="/${this.roomid}">a best-of-${this.bestOf}</a></h2>`).update();
|
||||
if (prevRoom) {
|
||||
prevRoom.add(`|uhtmlchange|next|Next: <a href="/${room.roomid}"><strong>Game ${gameNum} of ${this.bestOf}</strong></a>`).update();
|
||||
}
|
||||
}
|
||||
this.updateDisplay();
|
||||
}
|
||||
clearWaiting() {
|
||||
this.waitingBattle = null;
|
||||
for (const player of this.players) {
|
||||
@@ -159,7 +222,6 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
||||
const battleRoom = Rooms.createBattle(options);
|
||||
// shouldn't happen even in lockdown
|
||||
if (!battleRoom) throw new Error("Failed to create battle for " + this.title);
|
||||
battleRoom.setParent(this.room);
|
||||
this.games.push({
|
||||
room: battleRoom,
|
||||
winner: undefined,
|
||||
@@ -175,16 +237,18 @@ export class BestOfGame extends RoomGame<BestOfPlayer> {
|
||||
const p2 = this.players[1];
|
||||
battleRoom.add(
|
||||
Utils.html`|html|<table width="100%"><tr><td align="left">${p1.name}</td><td align="right">${p2.name}</tr>` +
|
||||
`<tr><td align="left">${this.renderWins(p1)}</td><td align="right">${this.renderWins(p2)}</tr></table>` +
|
||||
`<h2><strong>Game ${gameNum}</strong> of <a href="/${this.roomid}">a best-of-${this.bestOf}</a></h2>`
|
||||
`<tr><td align="left">${this.renderWins(p1)}</td><td align="right">${this.renderWins(p2)}</tr></table>`
|
||||
);
|
||||
battleRoom.add(
|
||||
`|uhtml|bestof|<h2><strong>Game ${gameNum}</strong> of <a href="/${this.roomid}">a best-of-${this.bestOf}</a></h2>`
|
||||
).update();
|
||||
|
||||
this.room.add(`|html|<h2>Game ${gameNum}</h2>`);
|
||||
this.room.add(Utils.html`|html|<a href="/${battleRoom.roomid}">${battleRoom.title}</a>`);
|
||||
this.room.add(Utils.html`|uhtml|game${gameNum}|<a href="/${battleRoom.roomid}">${battleRoom.title}</a>`);
|
||||
this.updateDisplay();
|
||||
|
||||
prevBattleRoom?.add(
|
||||
`|html|Next: <a href="/${battleRoom.roomid}"><strong>Game ${gameNum} of ${this.bestOf}</strong></a>`
|
||||
`|uhtml|next|Next: <a href="/${battleRoom.roomid}"><strong>Game ${gameNum} of ${this.bestOf}</strong></a>`
|
||||
).update();
|
||||
}
|
||||
renderWins(player: BestOfPlayer) {
|
||||
|
||||
@@ -178,6 +178,12 @@ export abstract class BasicRoom {
|
||||
* In all other rooms, `this.battle` is `null`.
|
||||
*/
|
||||
battle: RoomBattle | null;
|
||||
/**
|
||||
* The room's current best-of set. Best-of sets are a type of RoomGame, so in best-of set
|
||||
* rooms (which can only be `GameRoom`s), `this.bestof === this.game`.
|
||||
* In all other rooms, `this.bestof` is `null`.
|
||||
*/
|
||||
bestOf: BestOfGame | null;
|
||||
/**
|
||||
* The game room's current tournament. If the room is a battle room whose
|
||||
* battle is part of a tournament, `this.tour === this.parent.game`.
|
||||
@@ -234,6 +240,7 @@ export abstract class BasicRoom {
|
||||
this.muteQueue = [];
|
||||
|
||||
this.battle = null;
|
||||
this.bestOf = null;
|
||||
this.game = null;
|
||||
this.subGame = null;
|
||||
this.tour = null;
|
||||
@@ -824,7 +831,7 @@ export abstract class BasicRoom {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.battle) {
|
||||
if (this.battle || this.bestOf) {
|
||||
if (privacy) {
|
||||
if (this.roomid.endsWith('pw')) return true;
|
||||
|
||||
@@ -843,6 +850,7 @@ export abstract class BasicRoom {
|
||||
this.rename(this.title, this.roomid.slice(0, lastDashIndex) as RoomID);
|
||||
}
|
||||
}
|
||||
this.bestOf?.setPrivacyOfGames(privacy);
|
||||
}
|
||||
validateSection(section: string) {
|
||||
const target = toID(section);
|
||||
@@ -1889,6 +1897,7 @@ export class GameRoom extends BasicRoom {
|
||||
*/
|
||||
rated: number;
|
||||
declare battle: RoomBattle | null;
|
||||
declare bestOf: BestOfGame | null;
|
||||
declare game: RoomGame;
|
||||
modchatUser: string;
|
||||
constructor(roomid: RoomID, title: string, options: Partial<RoomSettings & RoomBattleOptions>) {
|
||||
@@ -1915,6 +1924,7 @@ export class GameRoom extends BasicRoom {
|
||||
this.rated = options.rated === true ? 1 : options.rated || 0;
|
||||
|
||||
this.battle = null;
|
||||
this.bestOf = null;
|
||||
this.game = null!;
|
||||
|
||||
this.modchatUser = '';
|
||||
@@ -2113,7 +2123,7 @@ export class GameRoom extends BasicRoom {
|
||||
function getRoom(roomid?: string | BasicRoom) {
|
||||
if (typeof roomid === 'string') {
|
||||
// Accounts for private battles that were made public
|
||||
if (roomid.startsWith('battle-') && roomid.endsWith('pw')) {
|
||||
if ((roomid.startsWith('battle-') || roomid.startsWith('game-bestof')) && roomid.endsWith('pw')) {
|
||||
const room = Rooms.rooms.get(roomid.slice(0, roomid.lastIndexOf('-')) as RoomID);
|
||||
if (room) return room;
|
||||
}
|
||||
@@ -2229,12 +2239,17 @@ export const Rooms = {
|
||||
roomid ||= Rooms.global.prepBattleRoom(options.format);
|
||||
options.isPersonal = true;
|
||||
const room = Rooms.createGameRoom(roomid, roomTitle, options);
|
||||
let game: RoomBattle | BestOfGame;
|
||||
if (options.isBestOfSubBattle || !isBestOf) {
|
||||
const battle = new Rooms.RoomBattle(room, options);
|
||||
room.game = battle;
|
||||
battle.checkPrivacySettings(options);
|
||||
game = new RoomBattle(room, options);
|
||||
} else {
|
||||
room.game = new BestOfGame(room, options);
|
||||
game = new BestOfGame(room, options);
|
||||
}
|
||||
room.game = game;
|
||||
if (options.isBestOfSubBattle && room.parent) {
|
||||
room.setPrivate(room.parent.settings.isPrivate || false);
|
||||
} else {
|
||||
game.checkPrivacySettings(options);
|
||||
}
|
||||
|
||||
for (const p of players) {
|
||||
|
||||
Reference in New Issue
Block a user