refactor: move mission flow state into game store
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled

This commit is contained in:
Tom Boullay
2026-05-11 18:02:00 +02:00
parent 91ebea8d99
commit 2c3f0db65b
21 changed files with 461 additions and 188 deletions
-95
View File
@@ -1,95 +0,0 @@
import type { GameStep, GameStepSnapshot } from "@/types/game";
import { useMissionFlowStore } from "@/managers/stores/useMissionFlowStore";
export class GameStepManager {
private static _instance: GameStepManager | null = null;
private _currentStep: GameStep = "intro";
private _playerName = "";
private _canMove = false;
private readonly _listeners = new Set<() => void>();
private _cachedSnapshot: GameStepSnapshot | null = null;
static getInstance(): GameStepManager {
if (!GameStepManager._instance) {
GameStepManager._instance = new GameStepManager();
}
return GameStepManager._instance;
}
private constructor() {}
getStep(): GameStep {
return this._currentStep;
}
getPlayerName(): string {
return this._playerName;
}
canMove(): boolean {
return this._canMove;
}
getSnapshot(): GameStepSnapshot {
if (!this._cachedSnapshot) {
this._cachedSnapshot = {
step: this._currentStep,
playerName: this._playerName,
canMove: this._canMove,
transitionTo: this.transitionTo.bind(this),
setPlayerName: this.setPlayerName.bind(this),
};
}
return this._cachedSnapshot;
}
transitionTo(step: GameStep): void {
if (this._currentStep === step) return;
this._currentStep = step;
this._cachedSnapshot = null;
useMissionFlowStore.getState().setStep(step);
this._emit();
}
setPlayerName(name: string): void {
if (this._playerName === name) return;
this._playerName = name;
this._cachedSnapshot = null;
useMissionFlowStore.getState().setPlayerName(name);
this._emit();
}
setCanMove(canMove: boolean): void {
if (this._canMove === canMove) return;
this._canMove = canMove;
this._cachedSnapshot = null;
useMissionFlowStore.getState().setCanMove(canMove);
this._emit();
}
subscribe(listener: () => void): () => void {
this._listeners.add(listener);
return () => {
this._listeners.delete(listener);
};
}
destroy(): void {
this._currentStep = "intro";
this._playerName = "";
this._canMove = false;
this._listeners.clear();
this._cachedSnapshot = null;
GameStepManager._instance = null;
}
private _emit(): void {
this._listeners.forEach((cb) => cb());
}
}
+45
View File
@@ -1,4 +1,5 @@
import { create } from "zustand";
import type { GameStep } from "@/types/game";
import {
isRepairMissionId,
type MissionStep,
@@ -19,9 +20,18 @@ interface MissionState {
dialogueAudio: string | null;
}
interface MissionFlowState {
activityCity: boolean;
canMove: boolean;
dialogMessage: string | null;
playerName: string;
step: GameStep;
}
interface GameState {
mainState: MainGameState;
isCinematicPlaying: boolean;
missionFlow: MissionFlowState;
intro: IntroState;
bike: MissionState & {
isRepaired: boolean;
@@ -41,7 +51,12 @@ interface GameState {
interface GameActions {
setMainState: (mainState: MainGameState) => void;
setCinematicPlaying: (isCinematicPlaying: boolean) => void;
hideDialog: () => void;
setActivityCity: (activityCity: boolean) => void;
setCanMove: (canMove: boolean) => void;
setFlowStep: (step: GameStep) => void;
setIntroState: (intro: Partial<IntroState>) => void;
setPlayerName: (playerName: string) => void;
setBikeState: (bike: Partial<GameState["bike"]>) => void;
setPyloneState: (pylone: Partial<GameState["pylone"]>) => void;
setFermeState: (ferme: Partial<GameState["ferme"]>) => void;
@@ -56,6 +71,7 @@ interface GameActions {
advanceGameState: () => void;
rewindGameState: () => void;
resetGame: () => void;
showDialog: (dialogMessage: string) => void;
}
type GameStore = GameState & GameActions;
@@ -225,6 +241,13 @@ function createInitialGameState(): GameState {
return {
mainState: "intro",
isCinematicPlaying: false,
missionFlow: {
activityCity: true,
canMove: false,
dialogMessage: null,
playerName: "",
step: "intro",
},
intro: {
dialogueAudio: null,
hasCompleted: false,
@@ -256,8 +279,26 @@ export const useGameStore = create<GameStore>()((set) => ({
...createInitialGameState(),
setMainState: (mainState) => set({ mainState }),
setCinematicPlaying: (isCinematicPlaying) => set({ isCinematicPlaying }),
hideDialog: () =>
set((state) => ({
missionFlow: { ...state.missionFlow, dialogMessage: null },
})),
setActivityCity: (activityCity) =>
set((state) => ({
missionFlow: { ...state.missionFlow, activityCity },
})),
setCanMove: (canMove) =>
set((state) => ({
missionFlow: { ...state.missionFlow, canMove },
})),
setFlowStep: (step) =>
set((state) => ({ missionFlow: { ...state.missionFlow, step } })),
setIntroState: (intro) =>
set((state) => ({ intro: { ...state.intro, ...intro } })),
setPlayerName: (playerName) =>
set((state) => ({
missionFlow: { ...state.missionFlow, playerName },
})),
setBikeState: (bike) =>
set((state) => ({ bike: { ...state.bike, ...bike } })),
setPyloneState: (pylone) =>
@@ -300,4 +341,8 @@ export const useGameStore = create<GameStore>()((set) => ({
return { outro: { ...state.outro, hasStarted: false } };
}),
resetGame: () => set(createInitialGameState()),
showDialog: (dialogMessage) =>
set((state) => ({
missionFlow: { ...state.missionFlow, dialogMessage },
})),
}));
@@ -1,35 +0,0 @@
import { create } from "zustand";
import type { GameStep } from "@/types/game";
interface MissionFlowState {
activityCity: boolean;
canMove: boolean;
dialogMessage: string | null;
playerName: string;
step: GameStep;
}
interface MissionFlowActions {
hideDialog: () => void;
setActivityCity: (value: boolean) => void;
setCanMove: (canMove: boolean) => void;
setPlayerName: (name: string) => void;
setStep: (step: GameStep) => void;
showDialog: (message: string) => void;
}
export const useMissionFlowStore = create<
MissionFlowState & MissionFlowActions
>((set) => ({
activityCity: true,
canMove: false,
dialogMessage: null,
playerName: "",
step: "intro",
hideDialog: () => set({ dialogMessage: null }),
setActivityCity: (activityCity) => set({ activityCity }),
setCanMove: (canMove) => set({ canMove }),
setPlayerName: (playerName) => set({ playerName }),
setStep: (step) => set({ step }),
showDialog: (dialogMessage) => set({ dialogMessage }),
}));