Feat/polish-mission1 #12

Merged
math-pixel merged 42 commits from feat/polish-mission1 into develop 2026-06-01 21:51:09 +00:00
3 changed files with 129 additions and 86 deletions
Showing only changes of commit bc862960a7 - Show all commits
+44 -27
View File
@@ -1,4 +1,5 @@
import { create } from "zustand"; import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { AudioManager } from "@/managers/AudioManager"; import { AudioManager } from "@/managers/AudioManager";
import type { AudioCategory } from "@/managers/AudioManager"; import type { AudioCategory } from "@/managers/AudioManager";
import type { SubtitleLanguage } from "@/types/settings/settings"; import type { SubtitleLanguage } from "@/types/settings/settings";
@@ -33,6 +34,8 @@ const DEFAULT_SETTINGS: SettingsState = {
subtitleLanguage: "fr", subtitleLanguage: "fr",
}; };
const SETTINGS_STORAGE_KEY = "la-fabrik-settings";
function clampVolume(volume: number): number { function clampVolume(volume: number): number {
return Math.max(0, Math.min(1, volume)); return Math.max(0, Math.min(1, volume));
} }
@@ -46,36 +49,50 @@ function setAudioCategoryVolume(
return nextVolume; return nextVolume;
} }
function applyDefaultAudioSettings(): void { function applyAudioSettings(
AudioManager.getInstance().setCategoryVolume( settings: Pick<SettingsState, "musicVolume" | "sfxVolume" | "dialogueVolume">,
"music", ): void {
DEFAULT_SETTINGS.musicVolume, AudioManager.getInstance().setCategoryVolume("music", settings.musicVolume);
); AudioManager.getInstance().setCategoryVolume("sfx", settings.sfxVolume);
AudioManager.getInstance().setCategoryVolume(
"sfx",
DEFAULT_SETTINGS.sfxVolume,
);
AudioManager.getInstance().setCategoryVolume( AudioManager.getInstance().setCategoryVolume(
"dialogue", "dialogue",
DEFAULT_SETTINGS.dialogueVolume, settings.dialogueVolume,
); );
} }
applyDefaultAudioSettings(); applyAudioSettings(DEFAULT_SETTINGS);
export const useSettingsStore = create<SettingsStore>()((set) => ({ export const useSettingsStore = create<SettingsStore>()(
...DEFAULT_SETTINGS, persist(
setSettingsMenuOpen: (isSettingsMenuOpen) => set({ isSettingsMenuOpen }), (set) => ({
setMusicVolume: (volume) => ...DEFAULT_SETTINGS,
set({ musicVolume: setAudioCategoryVolume("music", volume) }), setSettingsMenuOpen: (isSettingsMenuOpen) => set({ isSettingsMenuOpen }),
setSfxVolume: (volume) => setMusicVolume: (volume) =>
set({ sfxVolume: setAudioCategoryVolume("sfx", volume) }), set({ musicVolume: setAudioCategoryVolume("music", volume) }),
setDialogueVolume: (volume) => setSfxVolume: (volume) =>
set({ dialogueVolume: setAudioCategoryVolume("dialogue", volume) }), set({ sfxVolume: setAudioCategoryVolume("sfx", volume) }),
setSubtitlesEnabled: (subtitlesEnabled) => set({ subtitlesEnabled }), setDialogueVolume: (volume) =>
setSubtitleLanguage: (subtitleLanguage) => set({ subtitleLanguage }), set({ dialogueVolume: setAudioCategoryVolume("dialogue", volume) }),
resetSettings: () => { setSubtitlesEnabled: (subtitlesEnabled) => set({ subtitlesEnabled }),
applyDefaultAudioSettings(); setSubtitleLanguage: (subtitleLanguage) => set({ subtitleLanguage }),
set(DEFAULT_SETTINGS); resetSettings: () => {
}, applyAudioSettings(DEFAULT_SETTINGS);
})); set(DEFAULT_SETTINGS);
},
}),
{
name: SETTINGS_STORAGE_KEY,
storage: createJSONStorage(() => window.localStorage),
partialize: (state) => ({
dialogueVolume: state.dialogueVolume,
musicVolume: state.musicVolume,
sfxVolume: state.sfxVolume,
subtitleLanguage: state.subtitleLanguage,
subtitlesEnabled: state.subtitlesEnabled,
}),
onRehydrateStorage: () => (state) => {
if (state) applyAudioSettings(state);
},
},
),
);
+73 -56
View File
@@ -1,4 +1,5 @@
import { create } from "zustand"; import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { CLOUD_DEFAULTS, type CloudState } from "@/data/world/cloudConfig"; import { CLOUD_DEFAULTS, type CloudState } from "@/data/world/cloudConfig";
import { FOG_CONFIG, type FogState } from "@/data/world/fogConfig"; import { FOG_CONFIG, type FogState } from "@/data/world/fogConfig";
import { WIND_DEFAULTS, type WindState } from "@/data/world/windConfig"; import { WIND_DEFAULTS, type WindState } from "@/data/world/windConfig";
@@ -46,73 +47,89 @@ const DEFAULT_STATE: WorldSettingsState = {
graphics: { ...GRAPHICS_DEFAULTS }, graphics: { ...GRAPHICS_DEFAULTS },
}; };
export const useWorldSettingsStore = create<WorldSettingsStore>()((set) => ({ const WORLD_SETTINGS_STORAGE_KEY = "la-fabrik-world-settings";
...DEFAULT_STATE,
setClouds: (cloudsUpdate) => export const useWorldSettingsStore = create<WorldSettingsStore>()(
set((state) => ({ persist(
clouds: { ...state.clouds, ...cloudsUpdate }, (set) => ({
})), ...DEFAULT_STATE,
setFog: (fogUpdate) => setClouds: (cloudsUpdate) =>
set((state) => ({ set((state) => ({
fog: { ...state.fog, ...fogUpdate }, clouds: { ...state.clouds, ...cloudsUpdate },
})), })),
setWind: (windUpdate) => setFog: (fogUpdate) =>
set((state) => ({ set((state) => ({
wind: { ...state.wind, ...windUpdate }, fog: { ...state.fog, ...fogUpdate },
})), })),
setWindSpeed: (speed) => setWind: (windUpdate) =>
set((state) => ({ set((state) => ({
wind: { ...state.wind, speed }, wind: { ...state.wind, ...windUpdate },
})), })),
setWindDirection: (direction) => setWindSpeed: (speed) =>
set((state) => ({ set((state) => ({
wind: { ...state.wind, direction }, wind: { ...state.wind, speed },
})), })),
setWindStrength: (strength) => setWindDirection: (direction) =>
set((state) => ({ set((state) => ({
wind: { ...state.wind, strength }, wind: { ...state.wind, direction },
})), })),
setGraphics: (graphicsUpdate) => setWindStrength: (strength) =>
set((state) => ({ set((state) => ({
graphics: { ...state.graphics, ...graphicsUpdate }, wind: { ...state.wind, strength },
})), })),
setGraphicsPreset: (preset) => setGraphics: (graphicsUpdate) =>
set((state) => ({ set((state) => ({
graphics: { ...state.graphics, preset }, graphics: { ...state.graphics, ...graphicsUpdate },
})), })),
setDynamicGrass: (dynamicGrass) => setGraphicsPreset: (preset) =>
set((state) => ({ set((state) => ({
graphics: { ...state.graphics, dynamicGrass }, graphics: { ...state.graphics, preset },
})), })),
setDynamicTrees: (dynamicTrees) => setDynamicGrass: (dynamicGrass) =>
set((state) => ({ set((state) => ({
graphics: { ...state.graphics, dynamicTrees }, graphics: { ...state.graphics, dynamicGrass },
})), })),
setDynamicClouds: (dynamicClouds) => setDynamicTrees: (dynamicTrees) =>
set((state) => ({ set((state) => ({
graphics: { ...state.graphics, dynamicClouds }, graphics: { ...state.graphics, dynamicTrees },
})), })),
setShadowsEnabled: (shadowsEnabled) => setDynamicClouds: (dynamicClouds) =>
set((state) => ({ set((state) => ({
graphics: { ...state.graphics, shadowsEnabled }, graphics: { ...state.graphics, dynamicClouds },
})), })),
setGrassDensity: (grassDensity) => setShadowsEnabled: (shadowsEnabled) =>
set((state) => ({ set((state) => ({
graphics: { ...state.graphics, grassDensity }, graphics: { ...state.graphics, shadowsEnabled },
})), })),
resetToDefaults: () => set(DEFAULT_STATE), setGrassDensity: (grassDensity) =>
})); set((state) => ({
graphics: { ...state.graphics, grassDensity },
})),
resetToDefaults: () => set(DEFAULT_STATE),
}),
{
name: WORLD_SETTINGS_STORAGE_KEY,
storage: createJSONStorage(() => window.localStorage),
partialize: (state) => ({
clouds: state.clouds,
fog: state.fog,
graphics: state.graphics,
wind: state.wind,
}),
},
),
);
+12 -3
View File
@@ -9,6 +9,7 @@ const DEBUG_CONTROLS_STORAGE_KEY = "la-fabrik-debug-controls";
interface StoredDebugControls { interface StoredDebugControls {
cameraMode: CameraMode; cameraMode: CameraMode;
handTrackingSource: HandTrackingSource;
sceneMode: SceneMode; sceneMode: SceneMode;
} }
@@ -39,6 +40,10 @@ function isSceneMode(value: unknown): value is SceneMode {
return value === "game" || value === "physics"; return value === "game" || value === "physics";
} }
function isHandTrackingSource(value: unknown): value is HandTrackingSource {
return value === "browser" || value === "backend";
}
function getStoredDebugControls(): Partial<StoredDebugControls> { function getStoredDebugControls(): Partial<StoredDebugControls> {
try { try {
const rawValue = window.localStorage.getItem(DEBUG_CONTROLS_STORAGE_KEY); const rawValue = window.localStorage.getItem(DEBUG_CONTROLS_STORAGE_KEY);
@@ -51,6 +56,9 @@ function getStoredDebugControls(): Partial<StoredDebugControls> {
...(isCameraMode(parsedValue.cameraMode) ...(isCameraMode(parsedValue.cameraMode)
? { cameraMode: parsedValue.cameraMode } ? { cameraMode: parsedValue.cameraMode }
: {}), : {}),
...(isHandTrackingSource(parsedValue.handTrackingSource)
? { handTrackingSource: parsedValue.handTrackingSource }
: {}),
...(isSceneMode(parsedValue.sceneMode) ...(isSceneMode(parsedValue.sceneMode)
? { sceneMode: parsedValue.sceneMode } ? { sceneMode: parsedValue.sceneMode }
: {}), : {}),
@@ -94,7 +102,7 @@ export class Debug {
this.controls = { this.controls = {
cameraMode: storedControls.cameraMode ?? "player", cameraMode: storedControls.cameraMode ?? "player",
fogEnabled: FOG_CONFIG.enabled, fogEnabled: FOG_CONFIG.enabled,
handTrackingSource: "browser", handTrackingSource: storedControls.handTrackingSource ?? "browser",
showDebugOverlay: true, showDebugOverlay: true,
showHandTrackingSvg: false, showHandTrackingSvg: false,
showInteractionSpheres: false, showInteractionSpheres: false,
@@ -159,7 +167,7 @@ export class Debug {
.name("Source") .name("Source")
.onChange((value: HandTrackingSource) => { .onChange((value: HandTrackingSource) => {
this.controls.handTrackingSource = value; this.controls.handTrackingSource = value;
this.emit(); this.saveAndEmit();
}); });
} }
} }
@@ -246,7 +254,7 @@ export class Debug {
setHandTrackingSource(value: HandTrackingSource): void { setHandTrackingSource(value: HandTrackingSource): void {
this.controls.handTrackingSource = value; this.controls.handTrackingSource = value;
this.emit(); this.saveAndEmit();
} }
getFogEnabled(): boolean { getFogEnabled(): boolean {
@@ -285,6 +293,7 @@ export class Debug {
DEBUG_CONTROLS_STORAGE_KEY, DEBUG_CONTROLS_STORAGE_KEY,
JSON.stringify({ JSON.stringify({
cameraMode: this.controls.cameraMode, cameraMode: this.controls.cameraMode,
handTrackingSource: this.controls.handTrackingSource,
sceneMode: this.controls.sceneMode, sceneMode: this.controls.sceneMode,
}), }),
); );