import { create } from "zustand"; import type { GameStep } from "@/types/game"; import { isRepairMissionId, getNextMissionStep, getPreviousMissionStep, type MissionStep, type RepairMissionId, } from "@/types/gameplay/repairMission"; import { type PyloneStep } from "@/types/gameplay/pylone"; export type MainGameState = "intro" | "bike" | "pylone" | "ferme" | "outro"; export type { MissionStep, RepairMissionId, PyloneStep }; interface IntroState { currentStep: GameStep; dialogueAudio: string | null; hasCompleted: boolean; isBikeUnlocked: boolean; } interface MissionState { currentStep: MissionStep; dialogueAudio: string | null; } interface MissionFlowState { activityCity: boolean; canMove: boolean; dialogMessage: string | null; playerName: string; currentVideo: string | null; } interface GameState { mainState: MainGameState; isCinematicPlaying: boolean; sceneReady: boolean; missionFlow: MissionFlowState; intro: IntroState; bike: MissionState & { isRepaired: boolean; }; pylone: { currentStep: PyloneStep; dialogueAudio: string | null; isPowered: boolean; }; ferme: MissionState & { irrigationFixed: boolean; }; outro: { dialogueAudio: string | null; hasStarted: boolean; }; } interface GameActions { setMainState: (mainState: MainGameState) => void; setCinematicPlaying: (isCinematicPlaying: boolean) => void; setSceneReady: (sceneReady: boolean) => void; hideDialog: () => void; setActivityCity: (activityCity: boolean) => void; setCanMove: (canMove: boolean) => void; setIntroStep: (step: GameStep) => void; setIntroState: (intro: Partial) => void; setPlayerName: (playerName: string) => void; setBikeState: (bike: Partial) => void; setPyloneState: (pylone: Partial) => void; setFermeState: (ferme: Partial) => void; setOutroState: (outro: Partial) => void; setMissionStep: (mission: RepairMissionId, step: MissionStep) => void; completeIntro: () => void; completeBike: () => void; completeFerme: () => void; completeMission: (mission: RepairMissionId) => void; startOutro: () => void; advanceGameState: () => void; rewindGameState: () => void; resetGame: () => void; showDialog: (dialogMessage: string) => void; playVideo: (videoSrc: string) => void; clearVideo: () => void; } type GameStore = GameState & GameActions; type GameStateUpdate = Partial; function completeIntroState(state: GameState): GameStateUpdate { return { mainState: "bike", intro: { ...state.intro, hasCompleted: true, isBikeUnlocked: true, }, bike: { ...state.bike, currentStep: "waiting", }, }; } function completeBikeState(state: GameState): GameStateUpdate { return { mainState: "pylone", bike: { ...state.bike, currentStep: "done", isRepaired: true, }, pylone: { ...state.pylone, currentStep: "locked", }, }; } function completeFermeState(state: GameState): GameStateUpdate { return { mainState: "outro", ferme: { ...state.ferme, currentStep: "done", irrigationFixed: true, }, outro: { ...state.outro, hasStarted: true, }, }; } function setMissionStepState( state: GameState, mission: RepairMissionId, step: MissionStep, ): GameStateUpdate { return { [mission]: { ...state[mission], currentStep: step, }, }; } function completeMissionState( state: GameState, mission: RepairMissionId, ): GameStateUpdate { switch (mission) { case "bike": return completeBikeState(state); case "ferme": return completeFermeState(state); } } function getNextPyloneStep(step: PyloneStep): PyloneStep { switch (step) { case "locked": return "alert"; case "alert": return "searching"; case "searching": return "helped"; case "helped": return "manipulation"; case "manipulation": return "manipulation"; } } function advancePyloneStep(state: GameState): GameStateUpdate { if (state.pylone.currentStep === "locked") { return { pylone: { ...state.pylone, currentStep: "alert" }, }; } const nextStep = getNextPyloneStep(state.pylone.currentStep); if ( nextStep === "manipulation" && state.pylone.currentStep === "manipulation" ) { return { mainState: "outro", pylone: { ...state.pylone, currentStep: "manipulation" }, }; } return { pylone: { ...state.pylone, currentStep: nextStep }, }; } function advanceRepairMissionState( state: GameState, mission: RepairMissionId, ): GameStateUpdate { const nextStep = getNextMissionStep(state[mission].currentStep); if (nextStep === "done") { return completeMissionState(state, mission); } return setMissionStepState(state, mission, nextStep); } function rewindRepairMissionState( state: GameState, mission: RepairMissionId, ): GameStateUpdate { return setMissionStepState( state, mission, getPreviousMissionStep(state[mission].currentStep), ); } function startOutroState(state: GameState): GameStateUpdate { return { mainState: "outro", outro: { ...state.outro, hasStarted: true, }, }; } function createInitialGameState(): GameState { return { mainState: "intro", isCinematicPlaying: false, sceneReady: false, missionFlow: { activityCity: true, canMove: false, dialogMessage: null, playerName: "", currentVideo: null, }, intro: { currentStep: "intro", dialogueAudio: null, hasCompleted: false, isBikeUnlocked: false, }, bike: { currentStep: "locked", dialogueAudio: null, isRepaired: false, }, pylone: { currentStep: "locked", dialogueAudio: null, isPowered: false, }, ferme: { currentStep: "locked", dialogueAudio: null, irrigationFixed: false, }, outro: { dialogueAudio: null, hasStarted: false, }, }; } export const useGameStore = create()((set) => ({ ...createInitialGameState(), setMainState: (mainState) => set({ mainState }), setCinematicPlaying: (isCinematicPlaying) => set({ isCinematicPlaying }), setSceneReady: (sceneReady) => set({ sceneReady }), hideDialog: () => set((state) => ({ missionFlow: { ...state.missionFlow, dialogMessage: null }, })), setActivityCity: (activityCity) => set((state) => ({ missionFlow: { ...state.missionFlow, activityCity }, })), setCanMove: (canMove) => set((state) => ({ missionFlow: { ...state.missionFlow, canMove }, })), setIntroStep: (step: GameStep) => set((state) => ({ intro: { ...state.intro, currentStep: 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) => set((state) => ({ pylone: { ...state.pylone, ...pylone } })), setFermeState: (ferme) => set((state) => ({ ferme: { ...state.ferme, ...ferme } })), setOutroState: (outro) => set((state) => ({ outro: { ...state.outro, ...outro } })), setMissionStep: (mission, step) => set((state) => setMissionStepState(state, mission, step)), completeIntro: () => set(completeIntroState), completeBike: () => set((state) => completeMissionState(state, "bike")), completeFerme: () => set((state) => completeMissionState(state, "ferme")), completeMission: (mission) => set((state) => completeMissionState(state, mission)), startOutro: () => set(startOutroState), advanceGameState: () => set((state) => { if (state.mainState === "intro") { if (state.intro.currentStep === "bike") { return { mainState: "bike", intro: { ...state.intro, hasCompleted: true }, }; } return completeIntroState(state); } if (state.mainState === "pylone") { return advancePyloneStep(state); } if (isRepairMissionId(state.mainState)) { return advanceRepairMissionState(state, state.mainState); } return startOutroState(state); }), rewindGameState: () => set((state) => { if (state.mainState === "intro") { return { intro: { ...state.intro, hasCompleted: false } }; } if (isRepairMissionId(state.mainState)) { return rewindRepairMissionState(state, state.mainState); } return { outro: { ...state.outro, hasStarted: false } }; }), resetGame: () => set(createInitialGameState()), showDialog: (dialogMessage) => set((state) => ({ missionFlow: { ...state.missionFlow, dialogMessage }, })), playVideo: (videoSrc) => set((state) => ({ missionFlow: { ...state.missionFlow, currentVideo: videoSrc }, })), clearVideo: () => set((state) => ({ missionFlow: { ...state.missionFlow, currentVideo: null }, })), }));