let admins see clan tags in FFA (#4459)

Resolves #4446 

## Description:

left is an admin, right is a normal player:
<img width="3834" height="669" alt="image"
src="https://github.com/user-attachments/assets/c280485c-ae62-4287-bc66-53a2bb68cb2b"
/>

admin can see their own tag, and the UON tag, the non-admin can only see
their own tag.

## 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

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

w.o.n

Co-authored-by: iamlewis <lewismmmm@gmail.com>
This commit is contained in:
Ryan
2026-07-15 15:06:36 -07:00
committed by GitHub
co-authored by iamlewis
parent e05dce3437
commit 8f1bbdd20e
2 changed files with 116 additions and 6 deletions
+21 -6
View File
@@ -6,7 +6,7 @@ import { z } from "zod";
import { anonAnimalName } from "../core/AnonAnimals";
import { isAdminRole } from "../core/ApiSchemas";
import { GameEnv } from "../core/configuration/Config";
import { GameType } from "../core/game/Game";
import { GameMode, GameType } from "../core/game/Game";
import {
ClientID,
ClientMessageSchema,
@@ -1020,16 +1020,28 @@ export class GameServer {
// clients desync. Only the username of players this viewer can't see is
// anonymized, and their cosmetics hidden, neither of which the simulation
// reads.
private startInfoFor(viewer: ClientID): GameStartInfo {
if (!this.gameConfig.anonymizeNames) return this.wireGameStartInfo;
//
// Exception: admins in FFA get the real clan tags (the display pipeline then
// shows them everywhere) so they can spot teaming live. Safe ONLY in FFA —
// that mode never runs assignTeams, so clanTag never reaches the simulation,
// and the desync hash (Player.hash) excludes names. Gated on FFA, NOT
// disableClanTags: a Team game with tags disabled DOES assign teams by
// clanTag, so a per-viewer reveal there would desync.
private startInfoFor(viewer: ClientID, isAdmin: boolean): GameStartInfo {
const revealClanTags = isAdmin && this.gameConfig.gameMode === GameMode.FFA;
if (!this.gameConfig.anonymizeNames) {
return revealClanTags ? this.gameStartInfo : this.wireGameStartInfo;
}
return {
...this.wireGameStartInfo,
players: this.wireGameStartInfo.players.map((p) => {
players: this.wireGameStartInfo.players.map((p, i) => {
const real = this.seesReal(viewer, p.clientID);
return {
...p,
username: real ? p.username : this.anonName(viewer, p.clientID),
clanTag: null,
clanTag: revealClanTags
? this.gameStartInfo.players[i].clanTag
: null,
friends: undefined,
cosmetics: real ? p.cosmetics : undefined,
};
@@ -1063,7 +1075,10 @@ export class GameServer {
JSON.stringify({
type: "start",
turns: this.turns.slice(lastTurn),
gameStartInfo: this.startInfoFor(client.clientID),
gameStartInfo: this.startInfoFor(
client.clientID,
isAdminRole(client.role),
),
lobbyCreatedAt: this.createdAt,
myClientID: client.clientID,
} satisfies ServerStartGameMessage),