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
+95
View File
@@ -0,0 +1,95 @@
import { GameMode, GameType } from "../../src/core/game/Game";
import { GameServer } from "../../src/server/GameServer";
// Admins see real clan tags in FFA so they can spot teaming live. The reveal is
// gated on FFA — that mode never runs assignTeams, so clanTag never feeds the
// simulation. A Team game with tags disabled DOES assign teams by clanTag, so a
// per-viewer reveal there would desync; those cases must stay stripped.
// Build a GameServer with start info already populated, mirroring how start()
// leaves it: gameStartInfo keeps real clan tags; wireGameStartInfo is the
// stripped copy clients normally receive when disableClanTags is set.
function makeGame(gameMode: GameMode, anonymizeNames = false) {
const logger: any = {
child: vi.fn().mockReturnThis(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const game = new GameServer(
"g1",
logger,
Date.now(),
{
gameType: GameType.Private,
gameMode,
disableClanTags: true,
anonymizeNames,
} as any,
"creator-pid",
);
const players = [
{ clientID: "creator", username: "CreatorReal", clanTag: "HOST" },
{ clientID: "alice", username: "AliceReal", clanTag: "AAA" },
{ clientID: "charlie", username: "CharlieReal", clanTag: null },
];
const startInfo = { gameID: "g1", lobbyCreatedAt: 0, config: {}, players };
(game as any).gameStartInfo = startInfo;
// Wire copy strips clan tags (what disableClanTags does in start()).
(game as any).wireGameStartInfo = {
...startInfo,
players: players.map((p) => ({ ...p, clanTag: null })),
};
return game;
}
const startInfoFor = (game: GameServer, viewer: string, isAdmin: boolean) =>
(game as any).startInfoFor(viewer, isAdmin);
const player = (info: any, id: string) =>
info.players.find((p: any) => p.clientID === id);
describe("startInfoFor: admin clan-tag reveal in FFA", () => {
beforeEach(() => vi.useFakeTimers());
afterEach(() => {
vi.clearAllTimers();
vi.useRealTimers();
});
it("FFA + admin: sees real clan tags", () => {
const info = startInfoFor(makeGame(GameMode.FFA), "admin", true);
expect(player(info, "creator").clanTag).toBe("HOST");
expect(player(info, "alice").clanTag).toBe("AAA");
expect(player(info, "charlie").clanTag).toBeNull(); // never had one
});
it("FFA + non-admin: clan tags stay stripped", () => {
const info = startInfoFor(makeGame(GameMode.FFA), "alice", false);
expect(player(info, "creator").clanTag).toBeNull();
expect(player(info, "alice").clanTag).toBeNull();
});
it("Team + tags disabled + admin: NOT revealed (desync guard)", () => {
// Team mode assigns teams by clanTag, so revealing it to only the admin
// would diverge that client's team assignment — must stay stripped.
const info = startInfoFor(makeGame(GameMode.Team), "admin", true);
expect(player(info, "creator").clanTag).toBeNull();
expect(player(info, "alice").clanTag).toBeNull();
});
it("never mutates gameStartInfo (the archived record stays real)", () => {
const game = makeGame(GameMode.FFA);
startInfoFor(game, "admin", true);
expect((game as any).gameStartInfo.players[0].clanTag).toBe("HOST");
// The shared wire copy stays stripped for non-admins.
expect((game as any).wireGameStartInfo.players[0].clanTag).toBeNull();
});
it("anonymized FFA + admin: reveals clan tags but still anonymizes others", () => {
const game = makeGame(GameMode.FFA, true);
const info = startInfoFor(game, "admin", true);
// Real tags are revealed...
expect(player(info, "alice").clanTag).toBe("AAA");
// ...but other players' usernames are still anonymized.
expect(player(info, "alice").username).not.toBe("AliceReal");
});
});