Files
OpenFrontIO/src/client/Main.ts
T
Aotumuri 0402e609a4 replaces player names with randomized name (#340)
This PR replaces player names with randomized name
The goal is to reduce exposure to inappropriate or offensive names.
Additionally, content creators no longer need to worry about displaying
other players’ usernames.
<img width="1276" alt="スクリーンショット 2025-03-25 23 03 37"
src="https://github.com/user-attachments/assets/3d396bb0-336f-41a0-8d56-ff5229fe05f8"
/>
<img width="1048" alt="スクリーンショット 2025-03-25 23 03 48"
src="https://github.com/user-attachments/assets/a72711cf-9743-4879-8f2f-b8187b10a272"
/>
Use the upper left button (Ninja) to change settings.
<img width="1173" alt="スクリーンショット 2025-03-25 23 04 03"
src="https://github.com/user-attachments/assets/2d2fcbbd-7342-40b0-97c1-ecc184e5fbb6"
/>

Fixes #489

---------

Co-authored-by: evanpelle <evanpelle@gmail.com>
2025-04-22 20:26:45 -07:00

319 lines
9.9 KiB
TypeScript

import page from "page";
import favicon from "../../resources/images/Favicon.svg";
import { consolex } from "../core/Consolex";
import { GameRecord, GameStartInfo } from "../core/Schemas";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { GameType } from "../core/game/Game";
import { UserSettings } from "../core/game/UserSettings";
import { joinLobby } from "./ClientGameRunner";
import "./DarkModeButton";
import { DarkModeButton } from "./DarkModeButton";
import "./FlagInput";
import { FlagInput } from "./FlagInput";
import { GameStartingModal } from "./GameStartingModal";
import "./GoogleAdElement";
import GoogleAdElement from "./GoogleAdElement";
import { HelpModal } from "./HelpModal";
import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal";
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
import "./LangSelector";
import { LangSelector } from "./LangSelector";
import { LanguageModal } from "./LanguageModal";
import "./PublicLobby";
import { PublicLobby } from "./PublicLobby";
import "./RandomNameButton";
import { RandomNameButton } from "./RandomNameButton";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { UserSettingModal } from "./UserSettingModal";
import "./UsernameInput";
import { UsernameInput } from "./UsernameInput";
import { generateCryptoRandomUUID } from "./Utils";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import "./styles.css";
export interface JoinLobbyEvent {
clientID: string;
// Multiplayer games only have gameID, gameConfig is not known until game starts.
gameID: string;
// GameConfig only exists when playing a singleplayer game.
gameStartInfo?: GameStartInfo;
// GameRecord exists when replaying an archived game.
gameRecord?: GameRecord;
}
class Client {
private gameStop: () => void;
private usernameInput: UsernameInput | null = null;
private flagInput: FlagInput | null = null;
private darkModeButton: DarkModeButton | null = null;
private randomNameButton: RandomNameButton | null = null;
private joinModal: JoinPrivateLobbyModal;
private publicLobby: PublicLobby;
private googleAds: NodeListOf<GoogleAdElement>;
private userSettings: UserSettings = new UserSettings();
constructor() {}
initialize(): void {
const langSelector = document.querySelector(
"lang-selector",
) as LangSelector;
const LanguageModal = document.querySelector(
"lang-selector",
) as LanguageModal;
if (!langSelector) {
consolex.warn("Lang selector element not found");
}
if (!LanguageModal) {
consolex.warn("Language modal element not found");
}
this.flagInput = document.querySelector("flag-input") as FlagInput;
if (!this.flagInput) {
consolex.warn("Flag input element not found");
}
this.darkModeButton = document.querySelector(
"dark-mode-button",
) as DarkModeButton;
if (!this.darkModeButton) {
consolex.warn("Dark mode button element not found");
}
this.randomNameButton = document.querySelector(
"random-name-button",
) as RandomNameButton;
if (!this.randomNameButton) {
consolex.warn("Random name button element not found");
}
this.usernameInput = document.querySelector(
"username-input",
) as UsernameInput;
if (!this.usernameInput) {
consolex.warn("Username input element not found");
}
this.publicLobby = document.querySelector("public-lobby") as PublicLobby;
this.googleAds = document.querySelectorAll(
"google-ad",
) as NodeListOf<GoogleAdElement>;
window.addEventListener("beforeunload", () => {
consolex.log("Browser is closing");
if (this.gameStop != null) {
this.gameStop();
}
});
setFavicon();
document.addEventListener("join-lobby", this.handleJoinLobby.bind(this));
document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this));
const spModal = document.querySelector(
"single-player-modal",
) as SinglePlayerModal;
spModal instanceof SinglePlayerModal;
document.getElementById("single-player").addEventListener("click", () => {
if (this.usernameInput.isValid()) {
spModal.open();
}
});
const hlpModal = document.querySelector("help-modal") as HelpModal;
hlpModal instanceof HelpModal;
document.getElementById("help-button").addEventListener("click", () => {
hlpModal.open();
});
const settingsModal = document.querySelector(
"user-setting",
) as UserSettingModal;
settingsModal instanceof UserSettingModal;
document.getElementById("settings-button").addEventListener("click", () => {
settingsModal.open();
});
const hostModal = document.querySelector(
"host-lobby-modal",
) as HostPrivateLobbyModal;
hostModal instanceof HostPrivateLobbyModal;
document
.getElementById("host-lobby-button")
.addEventListener("click", () => {
if (this.usernameInput.isValid()) {
hostModal.open();
this.publicLobby.leaveLobby();
}
});
this.joinModal = document.querySelector(
"join-private-lobby-modal",
) as JoinPrivateLobbyModal;
this.joinModal instanceof JoinPrivateLobbyModal;
document
.getElementById("join-private-lobby-button")
.addEventListener("click", () => {
if (this.usernameInput.isValid()) {
this.joinModal.open();
}
});
if (this.userSettings.darkMode()) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
page("/join/:lobbyId", (ctx) => {
if (ctx.init && sessionStorage.getItem("inLobby")) {
// On page reload, go back home
page.redirect("/");
return;
}
const lobbyId = ctx.params.lobbyId;
this.joinModal.open(lobbyId);
consolex.log(`joining lobby ${lobbyId}`);
});
page();
function updateSliderProgress(slider) {
const percent =
((slider.value - slider.min) / (slider.max - slider.min)) * 100;
slider.style.setProperty("--progress", `${percent}%`);
}
document
.querySelectorAll("#bots-count, #private-lobby-bots-count")
.forEach((slider) => {
updateSliderProgress(slider);
slider.addEventListener("input", () => updateSliderProgress(slider));
});
}
private async handleJoinLobby(event: CustomEvent) {
const lobby = event.detail as JoinLobbyEvent;
consolex.log(`joining lobby ${lobby.gameID}`);
if (this.gameStop != null) {
consolex.log("joining lobby, stopping existing game");
this.gameStop();
}
const config = await getServerConfigFromClient();
this.gameStop = joinLobby(
{
gameID: lobby.gameID,
serverConfig: config,
flag:
this.flagInput.getCurrentFlag() == "xx"
? ""
: this.flagInput.getCurrentFlag(),
playerName: this.usernameInput.getCurrentUsername(),
persistentID: getPersistentIDFromCookie(),
clientID: lobby.clientID,
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.gameStartInfo,
gameRecord: lobby.gameRecord,
},
() => {
console.log("Closing modals");
document.getElementById("settings-button").classList.add("hidden");
[
"single-player-modal",
"host-lobby-modal",
"join-private-lobby-modal",
"game-starting-modal",
"top-bar",
"help-modal",
"user-setting",
].forEach((tag) => {
const modal = document.querySelector(tag) as HTMLElement & {
close?: () => void;
isModalOpen?: boolean;
};
if (modal?.close) {
modal.close();
} else if ("isModalOpen" in modal) {
modal.isModalOpen = false;
}
});
this.publicLobby.stop();
document.querySelectorAll(".ad").forEach((ad) => {
(ad as HTMLElement).style.display = "none";
});
// show when the game loads
const startingModal = document.querySelector(
"game-starting-modal",
) as GameStartingModal;
startingModal instanceof GameStartingModal;
startingModal.show();
},
() => {
this.joinModal.close();
this.publicLobby.stop();
document.querySelectorAll(".ad").forEach((ad) => {
(ad as HTMLElement).style.display = "none";
});
if (event.detail.gameConfig?.gameType != GameType.Singleplayer) {
window.history.pushState({}, "", `/join/${lobby.gameID}`);
sessionStorage.setItem("inLobby", "true");
}
},
);
}
private async handleLeaveLobby(/* event: CustomEvent */) {
if (this.gameStop == null) {
return;
}
consolex.log("leaving lobby, cancelling game");
this.gameStop();
this.gameStop = null;
this.publicLobby.leaveLobby();
}
}
// Initialize the client when the DOM is loaded
document.addEventListener("DOMContentLoaded", () => {
new Client().initialize();
});
function setFavicon(): void {
const link = document.createElement("link");
link.type = "image/x-icon";
link.rel = "shortcut icon";
link.href = favicon;
document.head.appendChild(link);
}
// WARNING: DO NOT EXPOSE THIS ID
export function getPersistentIDFromCookie(): string {
const COOKIE_NAME = "player_persistent_id";
// Try to get existing cookie
const cookies = document.cookie.split(";");
for (const cookie of cookies) {
const [cookieName, cookieValue] = cookie.split("=").map((c) => c.trim());
if (cookieName === COOKIE_NAME) {
return cookieValue;
}
}
// If no cookie exists, create new ID and set cookie
const newID = generateCryptoRandomUUID();
document.cookie = [
`${COOKIE_NAME}=${newID}`,
`max-age=${5 * 365 * 24 * 60 * 60}`, // 5 years
"path=/",
"SameSite=Strict",
"Secure",
].join(";");
return newID;
}