358 lines
9.0 KiB
TypeScript
358 lines
9.0 KiB
TypeScript
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<IntroState>) => void;
|
|
setPlayerName: (playerName: string) => void;
|
|
setBikeState: (bike: Partial<GameState["bike"]>) => void;
|
|
setPyloneState: (pylone: Partial<GameState["pylone"]>) => void;
|
|
setFermeState: (ferme: Partial<GameState["ferme"]>) => void;
|
|
setOutroState: (outro: Partial<GameState["outro"]>) => 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<GameState>;
|
|
|
|
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<GameStore>()((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 },
|
|
})),
|
|
}));
|