diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index fbb1b83aa..9524e7c1b 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -162,6 +162,7 @@ async function createClientGame( const worker = new WorkerClient( lobbyConfig.gameStartInfo, lobbyConfig.clientID, + userSettings, ); await worker.initialize(); const gameView = new GameView( diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 55b214877..2abadcd5f 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -27,6 +27,7 @@ import { GameUpdateViewData, } from "./game/GameUpdates"; import { loadTerrainMap as loadGameMap } from "./game/TerrainMapLoader"; +import { IUserSettings } from "./game/UserSettings"; import { PseudoRandom } from "./PseudoRandom"; import { ClientID, GameStartInfo, Turn } from "./Schemas"; import { sanitize, simpleHash } from "./Util"; @@ -37,8 +38,9 @@ export async function createGameRunner( clientID: ClientID, mapLoader: GameMapLoader, callBack: (gu: GameUpdateViewData | ErrorUpdate) => void, + userSettings: IUserSettings, ): Promise { - const config = await getConfig(gameStart.config, null); + const config = await getConfig(gameStart.config, userSettings); const gameMap = await loadGameMap( gameStart.config.gameMap, gameStart.config.gameMapSize, diff --git a/src/core/configuration/ColorAllocator.ts b/src/core/configuration/ColorAllocator.ts index e4fc572f8..30257fcdd 100644 --- a/src/core/configuration/ColorAllocator.ts +++ b/src/core/configuration/ColorAllocator.ts @@ -3,7 +3,7 @@ import labPlugin from "colord/plugins/lab"; import lchPlugin from "colord/plugins/lch"; import Color from "colorjs.io"; import { ColoredTeams, Team } from "../game/Game"; -import { UserSettings } from "../game/UserSettings"; +import { IUserSettings } from "../game/UserSettings"; import { PseudoRandom } from "../PseudoRandom"; import { simpleHash } from "../Util"; import { @@ -31,7 +31,7 @@ export class ColorAllocator { constructor( colors: Colord[], fallback: Colord[], - private userSettings: UserSettings, + private userSettings: IUserSettings, ) { this.availableColors = [...colors]; this.fallbackColors = [...colors, ...fallback]; diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 0fb16b490..f55fb886a 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -16,7 +16,7 @@ import { } from "../game/Game"; import { GameMap, TileRef } from "../game/GameMap"; import { PlayerView } from "../game/GameView"; -import { UserSettings } from "../game/UserSettings"; +import { IUserSettings } from "../game/UserSettings"; import { GameConfig, GameID, TeamCountConfig } from "../Schemas"; import { NukeType } from "../StatsSchemas"; @@ -89,7 +89,7 @@ export interface Config { donateTroops(): boolean; instantBuild(): boolean; numSpawnPhaseTurns(): number; - userSettings(): UserSettings; + userSettings(): IUserSettings; playerTeams(): TeamCountConfig; startManpower(playerInfo: PlayerInfo): number; diff --git a/src/core/configuration/ConfigLoader.ts b/src/core/configuration/ConfigLoader.ts index 184902694..b9a040b7f 100644 --- a/src/core/configuration/ConfigLoader.ts +++ b/src/core/configuration/ConfigLoader.ts @@ -1,4 +1,4 @@ -import { UserSettings } from "../game/UserSettings"; +import { IUserSettings } from "../game/UserSettings"; import { GameConfig } from "../Schemas"; import { Config, GameEnv, ServerConfig } from "./Config"; import { DefaultConfig } from "./DefaultConfig"; @@ -10,7 +10,7 @@ export let cachedSC: ServerConfig | null = null; export async function getConfig( gameConfig: GameConfig, - userSettings: UserSettings | null, + userSettings: IUserSettings | null, isReplay: boolean = false, ): Promise { const sc = await getServerConfigFromClient(); diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 89f7daaed..c75a4cb9e 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -21,7 +21,7 @@ import { } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { PlayerView } from "../game/GameView"; -import { UserSettings } from "../game/UserSettings"; +import { IUserSettings } from "../game/UserSettings"; import { GameConfig, GameID, TeamCountConfig } from "../Schemas"; import { NukeType } from "../StatsSchemas"; import { assertNever, sigmoid, simpleHash, within } from "../Util"; @@ -225,7 +225,7 @@ export class DefaultConfig implements Config { constructor( private _serverConfig: ServerConfig, private _gameConfig: GameConfig, - private _userSettings: UserSettings | null, + private _userSettings: IUserSettings | null, private _isReplay: boolean, ) { this.pastelTheme = new PastelTheme(this.userSettings()); @@ -269,7 +269,7 @@ export class DefaultConfig implements Config { return this._serverConfig; } - userSettings(): UserSettings { + userSettings(): IUserSettings { if (this._userSettings === null) { throw new Error("userSettings is null"); } diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 20b63e8f9..c84c88f4b 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -1,5 +1,5 @@ import { UnitInfo, UnitType } from "../game/Game"; -import { UserSettings } from "../game/UserSettings"; +import { IUserSettings } from "../game/UserSettings"; import { GameConfig } from "../Schemas"; import { GameEnv, ServerConfig } from "./Config"; import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig"; @@ -52,7 +52,7 @@ export class DevConfig extends DefaultConfig { constructor( sc: ServerConfig, gc: GameConfig, - us: UserSettings | null, + us: IUserSettings | null, isReplay: boolean, ) { super(sc, gc, us, isReplay); diff --git a/src/core/configuration/PastelTheme.ts b/src/core/configuration/PastelTheme.ts index cda3d8bc2..a11f470df 100644 --- a/src/core/configuration/PastelTheme.ts +++ b/src/core/configuration/PastelTheme.ts @@ -3,7 +3,7 @@ import { PseudoRandom } from "../PseudoRandom"; import { PlayerType, Team, TerrainType } from "../game/Game"; import { GameMap, TileRef } from "../game/GameMap"; import { PlayerView } from "../game/GameView"; -import { UserSettings } from "../game/UserSettings"; +import { IUserSettings } from "../game/UserSettings"; import { ColorAllocator } from "./ColorAllocator"; import { botColors, fallbackColors, humanColors, nationColors } from "./Colors"; import { Theme } from "./Config"; @@ -18,7 +18,7 @@ export class PastelTheme implements Theme { private teamColorAllocator: ColorAllocator; private nationColorAllocator: ColorAllocator; - constructor(private userSettings: UserSettings) { + constructor(private userSettings: IUserSettings) { this.humanColorAllocator = new ColorAllocator( humanColors, fallbackColors, diff --git a/src/core/configuration/PastelThemeDark.ts b/src/core/configuration/PastelThemeDark.ts index 3f27564ae..d5a4dc0ad 100644 --- a/src/core/configuration/PastelThemeDark.ts +++ b/src/core/configuration/PastelThemeDark.ts @@ -1,7 +1,7 @@ import { Colord, colord } from "colord"; import { TerrainType } from "../game/Game"; import { GameMap, TileRef } from "../game/GameMap"; -import { UserSettings } from "../game/UserSettings"; +import { IUserSettings } from "../game/UserSettings"; import { PastelTheme } from "./PastelTheme"; export class PastelThemeDark extends PastelTheme { @@ -10,7 +10,7 @@ export class PastelThemeDark extends PastelTheme { private darkWater = colord({ r: 14, g: 11, b: 30 }); private darkShorelineWater = colord({ r: 50, g: 50, b: 50 }); - constructor(userSettings: UserSettings) { + constructor(userSettings: IUserSettings) { super(userSettings); } diff --git a/src/core/game/UserSettings.ts b/src/core/game/UserSettings.ts index 817484b5c..a2fc9c197 100644 --- a/src/core/game/UserSettings.ts +++ b/src/core/game/UserSettings.ts @@ -3,7 +3,41 @@ import { PlayerPattern } from "../Schemas"; const PATTERN_KEY = "territoryPattern"; -export class UserSettings { +export interface UserSettingsData { + emojis: boolean; + performanceOverlay: boolean; + alertFrame: boolean; + anonymousNames: boolean; + lobbyIdVisibility: boolean; + fxLayer: boolean; + structureSprites: boolean; + darkMode: boolean; + leftClickOpensMenu: boolean; + territoryPatterns: boolean; + focusLocked: boolean; + colorblindMode: boolean; + backgroundMusicVolume: number; + soundEffectsVolume: number; +} + +export interface IUserSettings { + emojis(): boolean; + performanceOverlay(): boolean; + alertFrame(): boolean; + anonymousNames(): boolean; + lobbyIdVisibility(): boolean; + fxLayer(): boolean; + structureSprites(): boolean; + darkMode(): boolean; + leftClickOpensMenu(): boolean; + territoryPatterns(): boolean; + focusLocked(): boolean; + colorblindMode(): boolean; + backgroundMusicVolume(): number; + soundEffectsVolume(): number; +} + +export class UserSettings implements IUserSettings { get(key: string, defaultValue: boolean): boolean { const value = localStorage.getItem(key); if (!value) return defaultValue; @@ -33,6 +67,25 @@ export class UserSettings { localStorage.setItem(key, value.toString()); } + getData(): UserSettingsData { + return { + emojis: this.emojis(), + performanceOverlay: this.performanceOverlay(), + alertFrame: this.alertFrame(), + anonymousNames: this.anonymousNames(), + lobbyIdVisibility: this.lobbyIdVisibility(), + fxLayer: this.fxLayer(), + structureSprites: this.structureSprites(), + darkMode: this.darkMode(), + leftClickOpensMenu: this.leftClickOpensMenu(), + territoryPatterns: this.territoryPatterns(), + focusLocked: this.focusLocked(), + colorblindMode: this.colorblindMode(), + backgroundMusicVolume: this.backgroundMusicVolume(), + soundEffectsVolume: this.soundEffectsVolume(), + }; + } + emojis() { return this.get("settings.emojis", true); } diff --git a/src/core/worker/Worker.worker.ts b/src/core/worker/Worker.worker.ts index 1014968fb..e4075adac 100644 --- a/src/core/worker/Worker.worker.ts +++ b/src/core/worker/Worker.worker.ts @@ -2,6 +2,7 @@ import version from "../../../resources/version.txt"; import { createGameRunner, GameRunner } from "../GameRunner"; import { FetchGameMapLoader } from "../game/FetchGameMapLoader"; import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates"; +import { IUserSettings, UserSettingsData } from "../game/UserSettings"; import { AttackAveragePositionResultMessage, InitializedMessage, @@ -17,6 +18,53 @@ const ctx: Worker = self as any; let gameRunner: Promise | null = null; const mapLoader = new FetchGameMapLoader(`/maps`, version); +class MockUserSettings implements IUserSettings { + constructor(private data: UserSettingsData) {} + + emojis(): boolean { + return this.data.emojis; + } + performanceOverlay(): boolean { + return this.data.performanceOverlay; + } + alertFrame(): boolean { + return this.data.alertFrame; + } + anonymousNames(): boolean { + return this.data.anonymousNames; + } + lobbyIdVisibility(): boolean { + return this.data.lobbyIdVisibility; + } + fxLayer(): boolean { + return this.data.fxLayer; + } + structureSprites(): boolean { + return this.data.structureSprites; + } + darkMode(): boolean { + return this.data.darkMode; + } + leftClickOpensMenu(): boolean { + return this.data.leftClickOpensMenu; + } + territoryPatterns(): boolean { + return this.data.territoryPatterns; + } + focusLocked(): boolean { + return this.data.focusLocked; + } + colorblindMode(): boolean { + return this.data.colorblindMode; + } + backgroundMusicVolume(): number { + return this.data.backgroundMusicVolume; + } + soundEffectsVolume(): number { + return this.data.soundEffectsVolume; + } +} + function gameUpdate(gu: GameUpdateViewData | ErrorUpdate) { // skip if ErrorUpdate if (!("updates" in gu)) { @@ -41,11 +89,13 @@ ctx.addEventListener("message", async (e: MessageEvent) => { break; case "init": try { + const userSettings = new MockUserSettings(message.userSettings); gameRunner = createGameRunner( message.gameStartInfo, message.clientID, mapLoader, gameUpdate, + userSettings, ).then((gr) => { sendMessage({ type: "initialized", diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index bde436f39..dc2f2b6e0 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -7,6 +7,7 @@ import { } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates"; +import { UserSettings } from "../game/UserSettings"; import { ClientID, GameStartInfo, Turn } from "../Schemas"; import { generateID } from "../Util"; import { WorkerMessage } from "./WorkerMessages"; @@ -22,6 +23,7 @@ export class WorkerClient { constructor( private gameStartInfo: GameStartInfo, private clientID: ClientID, + private userSettings: UserSettings, ) { this.worker = new Worker(new URL("./Worker.worker.ts", import.meta.url)); this.messageHandlers = new Map(); @@ -70,6 +72,7 @@ export class WorkerClient { id: messageId, gameStartInfo: this.gameStartInfo, clientID: this.clientID, + userSettings: this.userSettings.getData(), }); // Add timeout for initialization diff --git a/src/core/worker/WorkerMessages.ts b/src/core/worker/WorkerMessages.ts index a8d30e9b1..875602763 100644 --- a/src/core/worker/WorkerMessages.ts +++ b/src/core/worker/WorkerMessages.ts @@ -6,6 +6,7 @@ import { } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { GameUpdateViewData } from "../game/GameUpdates"; +import { UserSettingsData } from "../game/UserSettings"; import { ClientID, GameStartInfo, Turn } from "../Schemas"; export type WorkerMessageType = @@ -40,6 +41,7 @@ export interface InitMessage extends BaseWorkerMessage { type: "init"; gameStartInfo: GameStartInfo; clientID: ClientID; + userSettings: UserSettingsData; } export interface TurnMessage extends BaseWorkerMessage { diff --git a/tests/Colors.test.ts b/tests/Colors.test.ts index 02810c675..0151c31e2 100644 --- a/tests/Colors.test.ts +++ b/tests/Colors.test.ts @@ -14,7 +14,7 @@ import { yellow, } from "../src/core/configuration/Colors"; import { ColoredTeams } from "../src/core/game/Game"; -import { UserSettings } from "../src/core/game/UserSettings"; +import { IUserSettings } from "../src/core/game/UserSettings"; const mockColors: Colord[] = [ colord({ r: 255, g: 0, b: 0 }), @@ -31,7 +31,7 @@ const fallbackColors = [...fallbackMockColors, ...mockColors]; const mockUserSettings = { colorblindMode: () => false, -} as UserSettings; +} as IUserSettings; describe("ColorAllocator", () => { let allocator: ColorAllocator; @@ -157,6 +157,24 @@ describe("ColorAllocator", () => { expect(redColorPlayerOne.isEqual(redColorPlayerTwo)).toBe(false); }); + + test("assignTeamColor returns colorblind-friendly colors when colorblind mode is enabled", () => { + const mockUserSettingsColorblind = { + colorblindMode: () => true, + } as IUserSettings; + + const allocator = new ColorAllocator( + mockColors, + fallbackMockColors, + mockUserSettingsColorblind, + ); + + const redColor = allocator.assignTeamColor(ColoredTeams.Red); + const greenColor = allocator.assignTeamColor(ColoredTeams.Green); + + expect(redColor.toHex()).toBe(colord({ h: 30, s: 100, l: 50 }).toHex()); // Orange + expect(greenColor.toHex()).toBe(colord({ h: 210, s: 100, l: 50 }).toHex()); // Blue + }); }); describe("selectDistinctColor", () => {