mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 01:15:21 +00:00
fix: Resolve userSettings is null error in worker
This commit fixes the "userSettings is null" error that occurred in the worker when trying to join or create a game. - Introduced IUserSettings interface to define the contract for user settings used in the worker. - Updated UserSettings class to implement IUserSettings and provide a getData() method for serialization. - Modified WorkerMessages to include serialized user settings in the InitMessage. - Passed user settings from ClientGameRunner to WorkerClient, and then to the worker. - Updated createGameRunner to accept IUserSettings and pass it to getConfig. - Corrected type inconsistencies across various configuration and theme classes to align with IUserSettings. - Re-added missing imports in relevant files.
This commit is contained in:
@@ -162,6 +162,7 @@ async function createClientGame(
|
||||
const worker = new WorkerClient(
|
||||
lobbyConfig.gameStartInfo,
|
||||
lobbyConfig.clientID,
|
||||
userSettings,
|
||||
);
|
||||
await worker.initialize();
|
||||
const gameView = new GameView(
|
||||
|
||||
@@ -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<GameRunner> {
|
||||
const config = await getConfig(gameStart.config, null);
|
||||
const config = await getConfig(gameStart.config, userSettings);
|
||||
const gameMap = await loadGameMap(
|
||||
gameStart.config.gameMap,
|
||||
gameStart.config.gameMapSize,
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Config> {
|
||||
const sc = await getServerConfigFromClient();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<GameRunner> | 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<MainThreadMessage>) => {
|
||||
break;
|
||||
case "init":
|
||||
try {
|
||||
const userSettings = new MockUserSettings(message.userSettings);
|
||||
gameRunner = createGameRunner(
|
||||
message.gameStartInfo,
|
||||
message.clientID,
|
||||
mapLoader,
|
||||
gameUpdate,
|
||||
userSettings,
|
||||
).then((gr) => {
|
||||
sendMessage({
|
||||
type: "initialized",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+20
-2
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user