mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 15:10:43 +00:00
Fix: prevent desync after clan team assignment for profane username (#2511)
## Description: Clan tag was removed when overwriting profane username. The local player still sees the name they put in though, and are assigned to a team based on the clan tag. Other player's browsers don't assign them to their team because the clan tag isn't visible to them. **Fixes:** - GameRunner.ts > username.ts: fetch clan tag before potentially overwriting bad username. Prepend non-profane clan tag back to the name string afterwards. - Util.ts: added getClanTagOriginalCase; we can't use getClanTag in censorNameWithClanTag because it returns all caps and we needed to retain the orginal capitalization. Explained in code comment. - Game.ts: no changes. By keeping the getClanTag in PlayerInfo contructor, TeamAssignment still gets clan param to correctly assign clan teams, other players get to see the clan tag of the "BeNicer" player, and GameServer archivegame() and LocalServer endGame() can still do getClanTag to have the same data at the end of the game, and test files keep working. **Screencap after fix:** https://github.com/user-attachments/assets/564c0ffd-577e-4653-ba33-155d2135a9f0 ## 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: tryout33
This commit is contained in:
+11
-12
@@ -30,7 +30,7 @@ import { loadTerrainMap as loadGameMap } from "./game/TerrainMapLoader";
|
||||
import { PseudoRandom } from "./PseudoRandom";
|
||||
import { ClientID, GameStartInfo, Turn } from "./Schemas";
|
||||
import { sanitize, simpleHash } from "./Util";
|
||||
import { fixProfaneUsername } from "./validations/username";
|
||||
import { censorNameWithClanTag } from "./validations/username";
|
||||
|
||||
export async function createGameRunner(
|
||||
gameStart: GameStartInfo,
|
||||
@@ -46,17 +46,16 @@ export async function createGameRunner(
|
||||
);
|
||||
const random = new PseudoRandom(simpleHash(gameStart.gameID));
|
||||
|
||||
const humans = gameStart.players.map(
|
||||
(p) =>
|
||||
new PlayerInfo(
|
||||
p.clientID === clientID
|
||||
? sanitize(p.username)
|
||||
: fixProfaneUsername(sanitize(p.username)),
|
||||
PlayerType.Human,
|
||||
p.clientID,
|
||||
random.nextID(),
|
||||
),
|
||||
);
|
||||
const humans = gameStart.players.map((p) => {
|
||||
return new PlayerInfo(
|
||||
p.clientID === clientID
|
||||
? sanitize(p.username)
|
||||
: censorNameWithClanTag(p.username),
|
||||
PlayerType.Human,
|
||||
p.clientID,
|
||||
random.nextID(),
|
||||
);
|
||||
});
|
||||
|
||||
const nations = gameStart.config.disableNPCs
|
||||
? []
|
||||
|
||||
+11
-2
@@ -339,9 +339,18 @@ export function sigmoid(
|
||||
|
||||
// Compute clan from name
|
||||
export function getClanTag(name: string): string | null {
|
||||
const clanTag = clanMatch(name);
|
||||
return clanTag ? clanTag[1].toUpperCase() : null;
|
||||
}
|
||||
|
||||
export function getClanTagOriginalCase(name: string): string | null {
|
||||
const clanTag = clanMatch(name);
|
||||
return clanTag ? clanTag[1] : null;
|
||||
}
|
||||
|
||||
function clanMatch(name: string): RegExpMatchArray | null {
|
||||
if (!name.includes("[") || !name.includes("]")) {
|
||||
return null;
|
||||
}
|
||||
const clanMatch = name.match(/\[([a-zA-Z0-9]{2,5})\]/);
|
||||
return clanMatch ? clanMatch[1].toUpperCase() : null;
|
||||
return name.match(/\[([a-zA-Z0-9]{2,5})\]/);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
skipNonAlphabeticTransformer,
|
||||
} from "obscenity";
|
||||
import { translateText } from "../../client/Utils";
|
||||
import { simpleHash } from "../Util";
|
||||
import { getClanTagOriginalCase, sanitize, simpleHash } from "../Util";
|
||||
|
||||
const matcher = new RegExpMatcher({
|
||||
...englishDataset.build(),
|
||||
@@ -45,6 +45,55 @@ export function isProfaneUsername(username: string): boolean {
|
||||
return matcher.hasMatch(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes and censors profane usernames and clan tags.
|
||||
* Profane username is overwritten, profane clan tag is removed.
|
||||
*
|
||||
* Preserves non-profane clan tag:
|
||||
* prevents desync after clan team assignment because local player's own clan tag and name aren't overwritten
|
||||
*
|
||||
* Removing bad clan tags won't hurt existing clans nor cause desyncs:
|
||||
* - full name including clan tag was overwritten in the past, if any part of name was bad
|
||||
* - only each seperate local player name with a profane clan tag will remain, no clan team assignment
|
||||
*
|
||||
* Examples:
|
||||
* - "GoodName" -> "GoodName"
|
||||
* - "Good$Name" -> "GoodName"
|
||||
* - "BadName" -> "Censored"
|
||||
* - "[CLAN]GoodName" -> "[CLAN]GoodName"
|
||||
* - "[CLaN]BadName" -> "[CLaN] Censored"
|
||||
* - "[BAD]GoodName" -> "GoodName"
|
||||
* - "[BAD]BadName" -> "Censored"
|
||||
*/
|
||||
export function censorNameWithClanTag(username: string): string {
|
||||
const sanitizedUsername = sanitize(username);
|
||||
|
||||
// Don't use getClanTag because that returns upperCase and if original isn't, str replace `[{$clanTag}]` won't match
|
||||
const clanTag = getClanTagOriginalCase(sanitizedUsername);
|
||||
|
||||
const nameWithoutClan = clanTag
|
||||
? sanitizedUsername.replace(`[${clanTag}]`, "").trim()
|
||||
: sanitizedUsername;
|
||||
|
||||
const clanTagIsProfane = clanTag ? isProfaneUsername(clanTag) : false;
|
||||
const usernameIsProfane = isProfaneUsername(nameWithoutClan);
|
||||
|
||||
const censoredNameWithoutClan = usernameIsProfane
|
||||
? fixProfaneUsername(nameWithoutClan)
|
||||
: nameWithoutClan;
|
||||
|
||||
// Restore clan tag if it existed and is not profane
|
||||
if (clanTag && !clanTagIsProfane) {
|
||||
if (usernameIsProfane) {
|
||||
return `[${clanTag}] ${censoredNameWithoutClan}`;
|
||||
}
|
||||
return sanitizedUsername;
|
||||
}
|
||||
|
||||
// Don't restore profane or nonexistent clan tag
|
||||
return censoredNameWithoutClan;
|
||||
}
|
||||
|
||||
export function validateUsername(username: string): {
|
||||
isValid: boolean;
|
||||
error?: string;
|
||||
|
||||
Reference in New Issue
Block a user