Improve ingame moderation for admins (#3678)

## Description:

Players with the `admin` flare can now kick players from any game
(including public lobbies), not just the lobby creator in private
lobbies.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

w.o.n
This commit is contained in:
Ryan
2026-04-20 19:09:04 +01:00
committed by GitHub
parent 52033597ef
commit c3d7d0373e
12 changed files with 308 additions and 25 deletions
+7 -1
View File
@@ -57,6 +57,7 @@ export interface LobbyConfig {
cosmetics: PlayerCosmeticRefs;
playerName: string;
playerClanTag: string | null;
playerRole: string | null;
gameID: GameID;
turnstileToken: string | null;
// GameStartInfo only exists when playing a singleplayer game.
@@ -259,7 +260,12 @@ async function createClientGame(
const canvas = createCanvas();
const soundManager = new SoundManager(eventBus, userSettings);
try {
const gameRenderer = createRenderer(canvas, gameView, eventBus);
const gameRenderer = createRenderer(
canvas,
gameView,
eventBus,
lobbyConfig.playerRole,
);
console.log(
`creating private game got difficulty: ${lobbyConfig.gameStartInfo.config.difficulty}`,
+3
View File
@@ -756,6 +756,8 @@ class Client {
if (lobby.source !== "public") {
this.updateJoinUrlForShare(lobby.gameID, config);
}
const auth = await userAuth();
const playerRole = auth !== false ? (auth.claims.role ?? null) : null;
const newLobbyHandle = joinLobby(this.eventBus, {
gameID: lobby.gameID,
serverConfig: config,
@@ -763,6 +765,7 @@ class Client {
turnstileToken: await this.getTurnstileToken(lobby),
playerName: this.usernameInput?.getUsername() ?? genAnonUsername(),
playerClanTag: this.usernameInput?.getClanTag() ?? null,
playerRole,
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info,
gameRecord: lobby.gameRecord,
});
+3
View File
@@ -51,6 +51,7 @@ export function createRenderer(
canvas: HTMLCanvasElement,
game: GameView,
eventBus: EventBus,
playerRole: string | null,
): GameRenderer {
const transformHandler = new TransformHandler(game, eventBus, canvas);
const userSettings = new UserSettings();
@@ -204,6 +205,8 @@ export function createRenderer(
playerPanel.emojiTable = emojiTable;
playerPanel.uiState = uiState;
playerPanel.setRole(playerRole);
const chatModal = document.querySelector("chat-modal") as ChatModal;
if (!(chatModal instanceof ChatModal)) {
console.error("chat modal not found");
@@ -18,6 +18,7 @@ export class PlayerModerationModal extends LitElement {
@property({ type: Boolean }) open: boolean = false;
@property({ type: Boolean }) alreadyKicked: boolean = false;
@property({ type: Boolean }) isAdmin: boolean = false;
createRenderRoot() {
return this;
@@ -44,7 +45,7 @@ export class PlayerModerationModal extends LitElement {
private canKick(my: PlayerView, other: PlayerView): boolean {
return (
my.isLobbyCreator() &&
(my.isLobbyCreator() || this.isAdmin) &&
other !== my &&
other.type() === PlayerType.Human &&
!!other.clientID()
+17 -3
View File
@@ -72,6 +72,15 @@ export class PlayerPanel extends LitElement implements Layer {
@state() private otherProfile: PlayerProfile | null = null;
@state() private suppressNextHide: boolean = false;
@state() private moderationTarget: PlayerView | null = null;
@state() private playerRole: string | null = null;
setRole(role: string | null): void {
this.playerRole = role;
}
private get isAdminRole(): boolean {
return this.playerRole === "admin" || this.playerRole === "root";
}
private ctModal: ChatModal;
@@ -441,8 +450,12 @@ export class PlayerPanel extends LitElement implements Layer {
`;
}
private renderModeration(my: PlayerView, other: PlayerView) {
if (!my.isLobbyCreator()) return html``;
private renderModeration(
my: PlayerView,
other: PlayerView,
isAdmin: boolean,
) {
if (!my.isLobbyCreator() && !isAdmin) return html``;
const moderationTitle = translateText("player_panel.moderation");
return html`
@@ -845,7 +858,7 @@ export class PlayerPanel extends LitElement implements Layer {
})}
</div>`
: ""}
${this.renderModeration(my, other)}
${this.renderModeration(my, other, this.isAdminRole)}
</div>
`;
}
@@ -963,6 +976,7 @@ export class PlayerPanel extends LitElement implements Layer {
.myPlayer=${my}
.target=${this.moderationTarget}
.eventBus=${this.eventBus}
.isAdmin=${this.isAdminRole}
.alreadyKicked=${this.kickedPlayerIDs.has(
String(this.moderationTarget.id()),
)}