Files
OpenFrontIO/src/core/game/UserSettings.ts
T
2026-03-25 13:34:34 -07:00

269 lines
7.0 KiB
TypeScript

import { Cosmetics } from "../CosmeticSchemas";
import { PlayerPattern } from "../Schemas";
const PATTERN_KEY = "territoryPattern";
export class UserSettings {
private emitChange(key: string, value: any): void {
try {
const maybeDispatch = (globalThis as any)?.dispatchEvent;
if (typeof maybeDispatch !== "function") return;
(globalThis as any).dispatchEvent(
new CustomEvent(`event:user-settings-changed:${key}`, {
detail: value,
}),
);
} catch {
// Ignore - settings should still be applied even if event dispatch fails.
}
}
get(key: string, defaultValue: boolean): boolean {
const value = localStorage.getItem(key);
if (!value) return defaultValue;
if (value === "true") return true;
if (value === "false") return false;
return defaultValue;
}
set(key: string, value: boolean) {
localStorage.setItem(key, value ? "true" : "false");
this.emitChange(key, value);
}
getFloat(key: string, defaultValue: number): number {
const value = localStorage.getItem(key);
if (!value) return defaultValue;
const floatValue = parseFloat(value);
if (isNaN(floatValue)) return defaultValue;
return floatValue;
}
setFloat(key: string, value: number) {
localStorage.setItem(key, value.toString());
this.emitChange(key, value);
}
emojis() {
return this.get("settings.emojis", true);
}
performanceOverlay() {
return this.get("settings.performanceOverlay", false);
}
alertFrame() {
return this.get("settings.alertFrame", true);
}
anonymousNames() {
return this.get("settings.anonymousNames", false);
}
lobbyIdVisibility() {
return this.get("settings.lobbyIdVisibility", true);
}
fxLayer() {
return this.get("settings.specialEffects", true);
}
structureSprites() {
return this.get("settings.structureSprites", true);
}
darkMode() {
return this.get("settings.darkMode", false);
}
leftClickOpensMenu() {
return this.get("settings.leftClickOpensMenu", false);
}
territoryPatterns() {
return this.get("settings.territoryPatterns", true);
}
attackingTroopsOverlay() {
return this.get("settings.attackingTroopsOverlay", true);
}
toggleAttackingTroopsOverlay() {
this.set("settings.attackingTroopsOverlay", !this.attackingTroopsOverlay());
}
cursorCostLabel() {
const legacy = this.get("settings.ghostPricePill", true);
return this.get("settings.cursorCostLabel", legacy);
}
focusLocked() {
return false;
// TODO: re-enable when performance issues are fixed.
this.get("settings.focusLocked", true);
}
toggleLeftClickOpenMenu() {
this.set("settings.leftClickOpensMenu", !this.leftClickOpensMenu());
}
toggleFocusLocked() {
this.set("settings.focusLocked", !this.focusLocked());
}
toggleEmojis() {
this.set("settings.emojis", !this.emojis());
}
togglePerformanceOverlay() {
this.set("settings.performanceOverlay", !this.performanceOverlay());
}
toggleAlertFrame() {
this.set("settings.alertFrame", !this.alertFrame());
}
toggleRandomName() {
this.set("settings.anonymousNames", !this.anonymousNames());
}
toggleLobbyIdVisibility() {
this.set("settings.lobbyIdVisibility", !this.lobbyIdVisibility());
}
toggleFxLayer() {
this.set("settings.specialEffects", !this.fxLayer());
}
toggleStructureSprites() {
this.set("settings.structureSprites", !this.structureSprites());
}
toggleCursorCostLabel() {
this.set("settings.cursorCostLabel", !this.cursorCostLabel());
}
toggleTerritoryPatterns() {
this.set("settings.territoryPatterns", !this.territoryPatterns());
}
toggleDarkMode() {
this.set("settings.darkMode", !this.darkMode());
if (this.darkMode()) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}
// For development only. Used for testing patterns, set in the console manually.
getDevOnlyPattern(): PlayerPattern | undefined {
const data = localStorage.getItem("dev-pattern") ?? undefined;
if (data === undefined) return undefined;
return {
name: "dev-pattern",
patternData: data,
colorPalette: {
name: "dev-color-palette",
primaryColor: localStorage.getItem("dev-primary") ?? "#ffffff",
secondaryColor: localStorage.getItem("dev-secondary") ?? "#000000",
},
} satisfies PlayerPattern;
}
getSelectedPatternName(cosmetics: Cosmetics | null): PlayerPattern | null {
if (cosmetics === null) return null;
let data = localStorage.getItem(PATTERN_KEY) ?? null;
if (data === null) return null;
const patternPrefix = "pattern:";
if (data.startsWith(patternPrefix)) {
data = data.slice(patternPrefix.length);
}
const [patternName, colorPalette] = data.split(":");
const pattern = cosmetics.patterns[patternName];
if (pattern === undefined) return null;
return {
name: patternName,
patternData: pattern.pattern,
colorPalette: cosmetics.colorPalettes?.[colorPalette],
} satisfies PlayerPattern;
}
setSelectedPatternName(patternName: string | undefined): void {
if (patternName === undefined) {
localStorage.removeItem(PATTERN_KEY);
} else {
localStorage.setItem(PATTERN_KEY, patternName);
}
this.emitChange("pattern", patternName);
}
getSelectedColor(): string | undefined {
const data = localStorage.getItem("settings.territoryColor") ?? undefined;
if (data === undefined) return undefined;
return data;
}
setSelectedColor(color: string | undefined): void {
if (color === undefined) {
localStorage.removeItem("settings.territoryColor");
} else {
localStorage.setItem("settings.territoryColor", color);
}
}
getFlag(): string | null {
let flag = localStorage.getItem("flag");
if (!flag) return null;
// Migrate bare country codes to country: prefix
if (!flag.startsWith("flag:") && !flag.startsWith("country:")) {
flag = `country:${flag}`;
localStorage.setItem("flag", flag);
}
return flag;
}
setFlag(flag: string): void {
if (flag === "country:xx") {
this.clearFlag();
} else {
localStorage.setItem("flag", flag);
}
console.log("emitting change!");
this.emitChange("flag", flag);
}
clearFlag(): void {
localStorage.removeItem("flag");
}
backgroundMusicVolume(): number {
return this.getFloat("settings.backgroundMusicVolume", 0);
}
setBackgroundMusicVolume(volume: number): void {
this.setFloat("settings.backgroundMusicVolume", volume);
}
attackRatioIncrement(): number {
const increment = Math.round(
this.getFloat("settings.attackRatioIncrement", 10),
);
if (!Number.isFinite(increment) || increment <= 0) return 10;
return increment;
}
soundEffectsVolume(): number {
return this.getFloat("settings.soundEffectsVolume", 1);
}
setSoundEffectsVolume(volume: number): void {
this.setFloat("settings.soundEffectsVolume", volume);
}
}