feat: sequencing
This commit is contained in:
@@ -7,56 +7,29 @@ export function GameFlow(): null {
|
||||
const step = useGameStore((state) => state.intro.currentStep);
|
||||
const setStep = useGameStore((state) => state.setIntroStep);
|
||||
const isCinematicPlaying = useGameStore((state) => state.isCinematicPlaying);
|
||||
const setActivityCity = useGameStore((state) => state.setActivityCity);
|
||||
const setCanMove = useGameStore((state) => state.setCanMove);
|
||||
const hasInitialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasInitialized.current && step === "intro") {
|
||||
hasInitialized.current = true;
|
||||
setStep("intro_sequence");
|
||||
setStep("sequence_video");
|
||||
}
|
||||
}, [step, setStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (step === "intro_sequence" && !isCinematicPlaying) {
|
||||
if (step === "sequence_video" && !isCinematicPlaying) {
|
||||
setStep("naming");
|
||||
}
|
||||
}, [step, isCinematicPlaying, setStep]);
|
||||
|
||||
useEffect(() => {
|
||||
if (step === "bienvenue") {
|
||||
const audio = AudioManager.getInstance();
|
||||
audio.playSoundWithCallback(AUDIO_PATHS.bienvenue, 0.5, () => {
|
||||
setCanMove(true);
|
||||
setStep("star-move");
|
||||
});
|
||||
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (step === "mission2") {
|
||||
setActivityCity(false);
|
||||
const audio = AudioManager.getInstance();
|
||||
audio.playSound(AUDIO_PATHS.alertCentral, 0.5);
|
||||
}
|
||||
|
||||
if (step === "searching") {
|
||||
const audio = AudioManager.getInstance();
|
||||
audio.playSound(AUDIO_PATHS.searching, 0.5);
|
||||
}
|
||||
|
||||
if (step === "helped") {
|
||||
const audio = AudioManager.getInstance();
|
||||
audio.playSound(AUDIO_PATHS.helped, 0.5);
|
||||
}
|
||||
|
||||
if (step === "manipulation") {
|
||||
setCanMove(false);
|
||||
if (step === "start-move") {
|
||||
setCanMove(true);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [step, setStep, setActivityCity, setCanMove]);
|
||||
}, [step, setCanMove]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ interface NPCHelperProps {
|
||||
}
|
||||
|
||||
export function NPCHelper({ position }: NPCHelperProps): React.JSX.Element {
|
||||
const step = useGameStore((state) => state.intro.currentStep);
|
||||
const setStep = useGameStore((state) => state.setIntroStep);
|
||||
const step = useGameStore((state) => state.pylone.currentStep);
|
||||
const setPyloneStep = useGameStore((state) => state.setPyloneState);
|
||||
const debug = Debug.getInstance();
|
||||
|
||||
const handlePress = (): void => {
|
||||
if (step === "searching") {
|
||||
setStep("helped");
|
||||
setPyloneStep({ currentStep: "helped" });
|
||||
}
|
||||
};
|
||||
|
||||
const shouldShow = step === "searching" || debug.active;
|
||||
const shouldShow = step === "searching" || step === "helped" || debug.active;
|
||||
|
||||
if (!shouldShow) {
|
||||
return <></>;
|
||||
|
||||
@@ -10,8 +10,8 @@ interface PyloneDestroyedProps {
|
||||
export function PyloneDestroyed({
|
||||
position,
|
||||
}: PyloneDestroyedProps): React.JSX.Element {
|
||||
const step = useGameStore((state) => state.intro.currentStep);
|
||||
const setStep = useGameStore((state) => state.setIntroStep);
|
||||
const step = useGameStore((state) => state.pylone.currentStep);
|
||||
const setPyloneStep = useGameStore((state) => state.setPyloneState);
|
||||
const setCanMove = useGameStore((state) => state.setCanMove);
|
||||
const showDialog = useGameStore((state) => state.showDialog);
|
||||
const debug = Debug.getInstance();
|
||||
@@ -19,7 +19,7 @@ export function PyloneDestroyed({
|
||||
const handlePress = (): void => {
|
||||
if (step === "helped") {
|
||||
setCanMove(false);
|
||||
setStep("manipulation");
|
||||
setPyloneStep({ currentStep: "manipulation" });
|
||||
} else if (step === "searching") {
|
||||
showDialog(
|
||||
"Cet objet est trop lourd pour le porter tout seul, trouve de l'aide",
|
||||
|
||||
@@ -13,7 +13,7 @@ export function IntroUI(): React.JSX.Element | null {
|
||||
if (inputValue.trim() === "") return;
|
||||
|
||||
setPlayerName(inputValue.trim());
|
||||
setStep("bienvenue");
|
||||
setStep("start-move");
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent): void => {
|
||||
@@ -100,7 +100,7 @@ export function BienvenueDisplay(): React.JSX.Element | null {
|
||||
const step = useGameStore((state) => state.intro.currentStep);
|
||||
const playerName = useGameStore((state) => state.missionFlow.playerName);
|
||||
|
||||
if (step !== "bienvenue") return null;
|
||||
if (step !== "start-move") return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "@/managers/stores/useGameStore";
|
||||
import { isMissionStep, MISSION_STEPS } from "@/types/gameplay/repairMission";
|
||||
import { GAME_STEPS, type GameStep } from "@/types/game";
|
||||
import { PYLONE_STEPS, type PyloneStep } from "@/types/gameplay/pylone";
|
||||
|
||||
const MAIN_STATES: MainGameState[] = [
|
||||
"intro",
|
||||
@@ -54,9 +55,11 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
const subStateOptions =
|
||||
mainState === "intro"
|
||||
? GAME_STEPS
|
||||
: mainState === "outro"
|
||||
? ["waiting", "started"]
|
||||
: MISSION_STEPS;
|
||||
: mainState === "pylone"
|
||||
? PYLONE_STEPS
|
||||
: mainState === "outro"
|
||||
? ["waiting", "started"]
|
||||
: MISSION_STEPS;
|
||||
|
||||
function setSubState(nextSubState: string): void {
|
||||
if (mainState === "intro") {
|
||||
@@ -64,6 +67,11 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "pylone") {
|
||||
setPyloneState({ currentStep: nextSubState as PyloneStep });
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "outro") {
|
||||
setOutroState({ hasStarted: nextSubState === "started" });
|
||||
return;
|
||||
@@ -76,11 +84,6 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "pylone") {
|
||||
setPyloneState({ currentStep: nextSubState });
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "ferme") {
|
||||
setFermeState({ currentStep: nextSubState });
|
||||
return;
|
||||
@@ -95,11 +98,6 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextMainState === "pylone" && pyloneStep === "locked") {
|
||||
setPyloneState({ currentStep: "waiting" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (nextMainState === "ferme" && fermeStep === "locked") {
|
||||
setFermeState({ currentStep: "waiting" });
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ export function ZoneDetection(): null {
|
||||
const triggeredZones = useRef<Set<string>>(new Set());
|
||||
const debug = Debug.getInstance();
|
||||
const step = useGameStore((state) => state.intro.currentStep);
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const setStep = useGameStore((state) => state.setIntroStep);
|
||||
const setPyloneStep = useGameStore((state) => state.setPyloneState);
|
||||
const advanceGameState = useGameStore((state) => state.advanceGameState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!debug.active) return;
|
||||
@@ -65,7 +68,11 @@ export function ZoneDetection(): null {
|
||||
const distanceSq = _playerPos.distanceToSquared(_zonePos);
|
||||
|
||||
if (distanceSq <= zone.radius * zone.radius) {
|
||||
setStep(zone.targetStep);
|
||||
if (zone.targetStep === "bike" && mainState === "intro") {
|
||||
advanceGameState();
|
||||
} else {
|
||||
setStep(zone.targetStep);
|
||||
}
|
||||
triggeredZones.current.add(zone.id);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
|
||||
description:
|
||||
"Repair the damaged cooling module before relaunching the bike",
|
||||
modelPath: "/models/ebike/model.gltf",
|
||||
modelScale: 0.50,
|
||||
modelScale: 0.5,
|
||||
stageUiPath: "/assets/UI/ebike.webm",
|
||||
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
||||
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
||||
@@ -85,51 +85,6 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
|
||||
},
|
||||
],
|
||||
},
|
||||
pylone: {
|
||||
id: "pylone",
|
||||
label: "Power pylon",
|
||||
description:
|
||||
"Restore the pylon lamp relay and damaged panel before reconnecting the grid",
|
||||
modelPath: "/models/pylone/model.gltf",
|
||||
stageUiPath: "/assets/UI/centrale.webm",
|
||||
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
||||
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
||||
case: DEFAULT_REPAIR_CASE,
|
||||
reassemblySeconds: 1.8,
|
||||
requiredReplacementPartId: "pylone-grid-relay-replacement",
|
||||
scanPartSeconds: 1.4,
|
||||
brokenParts: [
|
||||
{
|
||||
id: "pylone-grid-relay",
|
||||
label: "Grid relay",
|
||||
nodeName: "lampe",
|
||||
placeholderName: "placeholder_1",
|
||||
},
|
||||
{
|
||||
id: "pylone-damaged-panel",
|
||||
label: "Damaged solar panel",
|
||||
nodeName: "panneau2",
|
||||
placeholderName: "placeholder_2",
|
||||
},
|
||||
],
|
||||
replacementParts: [
|
||||
{
|
||||
id: "pylone-grid-relay-replacement",
|
||||
label: "Replacement grid relay",
|
||||
modelPath: "/models/pylone/model.gltf",
|
||||
},
|
||||
{
|
||||
id: "pylone-stone-decoy",
|
||||
label: "Stone counterweight",
|
||||
modelPath: "/models/galet/model.gltf",
|
||||
},
|
||||
{
|
||||
id: "pylone-cooling-decoy",
|
||||
label: "Cooling core",
|
||||
modelPath: "/models/refroidisseur/model.gltf",
|
||||
},
|
||||
],
|
||||
},
|
||||
ferme: {
|
||||
id: "ferme",
|
||||
label: "Vertical farm",
|
||||
|
||||
+2
-9
@@ -1,4 +1,4 @@
|
||||
import type { Zone } from "@/types/game";
|
||||
import type { Zone, GameStep } from "@/types/game";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
|
||||
export const ZONES: Zone[] = [
|
||||
@@ -7,13 +7,6 @@ export const ZONES: Zone[] = [
|
||||
position: [-5, 25, -15] as Vector3Tuple,
|
||||
radius: 10,
|
||||
height: 20,
|
||||
targetStep: "mission2",
|
||||
},
|
||||
{
|
||||
id: "searchingZone",
|
||||
position: [-5, 25, -30] as Vector3Tuple,
|
||||
radius: 10,
|
||||
height: 20,
|
||||
targetStep: "searching",
|
||||
targetStep: "bike" as GameStep,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2,14 +2,12 @@ import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type { MissionStep } from "@/types/gameplay/repairMission";
|
||||
|
||||
export function useRepairMovementLocked(): boolean {
|
||||
return false;
|
||||
|
||||
return useGameStore((state) => {
|
||||
switch (state.mainState) {
|
||||
case "bike":
|
||||
return isRepairMovementLocked(state.bike.currentStep);
|
||||
case "pylone":
|
||||
return isRepairMovementLocked(state.pylone.currentStep);
|
||||
return state.pylone.currentStep === "manipulation";
|
||||
case "ferme":
|
||||
return isRepairMovementLocked(state.ferme.currentStep);
|
||||
case "intro":
|
||||
|
||||
@@ -7,9 +7,10 @@ import {
|
||||
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 };
|
||||
export type { MissionStep, RepairMissionId, PyloneStep };
|
||||
|
||||
interface IntroState {
|
||||
currentStep: GameStep;
|
||||
@@ -38,7 +39,9 @@ interface GameState {
|
||||
bike: MissionState & {
|
||||
isRepaired: boolean;
|
||||
};
|
||||
pylone: MissionState & {
|
||||
pylone: {
|
||||
currentStep: PyloneStep;
|
||||
dialogueAudio: string | null;
|
||||
isPowered: boolean;
|
||||
};
|
||||
ferme: MissionState & {
|
||||
@@ -66,7 +69,6 @@ interface GameActions {
|
||||
setMissionStep: (mission: RepairMissionId, step: MissionStep) => void;
|
||||
completeIntro: () => void;
|
||||
completeBike: () => void;
|
||||
completePylone: () => void;
|
||||
completeFerme: () => void;
|
||||
completeMission: (mission: RepairMissionId) => void;
|
||||
startOutro: () => void;
|
||||
@@ -104,22 +106,7 @@ function completeBikeState(state: GameState): GameStateUpdate {
|
||||
},
|
||||
pylone: {
|
||||
...state.pylone,
|
||||
currentStep: "waiting",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function completePyloneState(state: GameState): GameStateUpdate {
|
||||
return {
|
||||
mainState: "ferme",
|
||||
pylone: {
|
||||
...state.pylone,
|
||||
currentStep: "done",
|
||||
isPowered: true,
|
||||
},
|
||||
ferme: {
|
||||
...state.ferme,
|
||||
currentStep: "waiting",
|
||||
currentStep: "locked",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -159,13 +146,42 @@ function completeMissionState(
|
||||
switch (mission) {
|
||||
case "bike":
|
||||
return completeBikeState(state);
|
||||
case "pylone":
|
||||
return completePyloneState(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 {
|
||||
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,
|
||||
@@ -273,7 +289,6 @@ export const useGameStore = create<GameStore>()((set) => ({
|
||||
set((state) => setMissionStepState(state, mission, step)),
|
||||
completeIntro: () => set(completeIntroState),
|
||||
completeBike: () => set((state) => completeMissionState(state, "bike")),
|
||||
completePylone: () => set((state) => completeMissionState(state, "pylone")),
|
||||
completeFerme: () => set((state) => completeMissionState(state, "ferme")),
|
||||
completeMission: (mission) =>
|
||||
set((state) => completeMissionState(state, mission)),
|
||||
@@ -281,9 +296,19 @@ export const useGameStore = create<GameStore>()((set) => ({
|
||||
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);
|
||||
}
|
||||
|
||||
+6
-18
@@ -2,29 +2,17 @@ import type { Vector3Tuple } from "@/types/three/three";
|
||||
|
||||
export type GameStep =
|
||||
| "intro"
|
||||
| "intro_sequence"
|
||||
| "start-intro"
|
||||
| "sequence_video"
|
||||
| "naming"
|
||||
| "bienvenue"
|
||||
| "star-move"
|
||||
| "mission2"
|
||||
| "searching"
|
||||
| "helped"
|
||||
| "manipulation"
|
||||
| "outOfFabrik";
|
||||
| "start-move"
|
||||
| "bike";
|
||||
|
||||
export const GAME_STEPS: readonly GameStep[] = [
|
||||
"intro",
|
||||
"intro_sequence",
|
||||
"start-intro",
|
||||
"sequence_video",
|
||||
"naming",
|
||||
"bienvenue",
|
||||
"star-move",
|
||||
"mission2",
|
||||
"searching",
|
||||
"helped",
|
||||
"manipulation",
|
||||
"outOfFabrik",
|
||||
"start-move",
|
||||
"bike",
|
||||
] as const;
|
||||
|
||||
export interface Zone {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export type PyloneStep =
|
||||
| "locked"
|
||||
| "alert"
|
||||
| "searching"
|
||||
| "helped"
|
||||
| "manipulation";
|
||||
|
||||
export const PYLONE_STEPS: readonly PyloneStep[] = [
|
||||
"locked",
|
||||
"alert",
|
||||
"searching",
|
||||
"helped",
|
||||
"manipulation",
|
||||
] as const;
|
||||
|
||||
export function isPyloneStep(value: string): value is PyloneStep {
|
||||
return PYLONE_STEPS.includes(value as PyloneStep);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export type RepairMissionId = "bike" | "pylone" | "ferme";
|
||||
export type RepairMissionId = "bike" | "ferme";
|
||||
|
||||
export type MissionStep =
|
||||
| "locked"
|
||||
@@ -10,7 +10,7 @@ export type MissionStep =
|
||||
| "reassembling"
|
||||
| "done";
|
||||
|
||||
export const REPAIR_MISSION_IDS = ["bike", "pylone", "ferme"] as const;
|
||||
export const REPAIR_MISSION_IDS = ["bike", "ferme"] as const;
|
||||
|
||||
export const MISSION_STEPS = [
|
||||
"locked",
|
||||
|
||||
Reference in New Issue
Block a user