// Volumes of a game: shared by all games unless the game has been given its own. import type { Volumes } from "./audio"; type Store = Pick; function usable(v: any): v is number { return typeof v === "number" && Number.isFinite(v); } const clamp = (v: number) => Math.max(0, Math.min(1, v)); export class VolumeStore { constructor(private key: string | null, private storage: Store = localStorage) {} get available() { return this.key !== null; } /** This game's own volumes, or null when it has none (it then plays with the shared ones). */ read(): Volumes | null { if (!this.key) return null; try { const data = JSON.parse(this.storage.getItem(this.key) ?? "null"); if (data && usable(data.music) && usable(data.sound) && usable(data.voice)) { return { music: clamp(data.music), sound: clamp(data.sound), voice: clamp(data.voice) }; } } catch { /* storage unavailable or damaged: fall back to the shared volumes */ } return null; } write({ music, sound, voice }: Volumes) { if (!this.key) return; try { this.storage.setItem(this.key, JSON.stringify({ music, sound, voice })); } catch { /* the change still applies for this session */ } } clear() { if (!this.key) return; try { this.storage.removeItem(this.key); } catch { /* nothing to remove */ } } }