164 lines
4.8 KiB
TypeScript
164 lines
4.8 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
|
import { useDialoguePlayback } from "@/hooks/gameplay/useDialoguePlayback";
|
|
import { ZoneDetection } from "@/components/zone/ZoneDetection";
|
|
import { PylonFarmerNPC } from "@/components/gameplay/pylon/PylonFarmerNPC";
|
|
import { PylonNarratorOutro } from "@/components/gameplay/pylon/PylonNarratorOutro";
|
|
import { PYLON_APPROACH_ZONE, PYLON_ARRIVED_ZONE } from "@/data/gameplay/zones";
|
|
import { PYLON_NARRATIVE_DIALOGUES } from "@/data/gameplay/pylonConfig";
|
|
import { AudioManager } from "@/managers/AudioManager";
|
|
import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest";
|
|
import { playDialogueById } from "@/utils/dialogues/playDialogue";
|
|
|
|
const PYLON_POWERDOWN_SFX = "/sounds/effect/generateur-powerdown.mp3";
|
|
const PYLON_POWERUP_SFX = "/sounds/effect/generateur-powerup.mp3";
|
|
|
|
export function PylonNarrativeFlow(): React.JSX.Element | null {
|
|
const mainState = useGameStore((state) => state.mainState);
|
|
const step = useGameStore((state) => state.pylon.currentStep);
|
|
const setMissionStep = useGameStore((state) => state.setMissionStep);
|
|
const setCanMove = useGameStore((state) => state.setCanMove);
|
|
|
|
// ── approaching : powerdown sfx → then electricOutage dialogue ────────────
|
|
useEffect(() => {
|
|
if (mainState !== "pylon" || step !== "approaching") return undefined;
|
|
|
|
let isCancelled = false;
|
|
setCanMove(false);
|
|
|
|
void (async () => {
|
|
// 1. Play the generator powerdown sound effect
|
|
const sfx = AudioManager.getInstance().playSound(
|
|
PYLON_POWERDOWN_SFX,
|
|
1,
|
|
{ category: "sfx" },
|
|
);
|
|
|
|
// 2. Wait for it to finish (or skip if it can't load)
|
|
if (sfx) {
|
|
await new Promise<void>((resolve) => {
|
|
sfx.addEventListener("ended", () => resolve(), { once: true });
|
|
sfx.addEventListener("error", () => resolve(), { once: true });
|
|
});
|
|
}
|
|
|
|
if (isCancelled) return;
|
|
|
|
// 3. Play the narrative dialogue
|
|
const manifest = await loadDialogueManifest();
|
|
if (isCancelled || !manifest) {
|
|
setCanMove(true);
|
|
return;
|
|
}
|
|
|
|
const audio = await playDialogueById(
|
|
manifest,
|
|
PYLON_NARRATIVE_DIALOGUES.electricOutage,
|
|
);
|
|
|
|
if (isCancelled || !audio) {
|
|
setCanMove(true);
|
|
return;
|
|
}
|
|
|
|
audio.addEventListener(
|
|
"ended",
|
|
() => {
|
|
setCanMove(true);
|
|
},
|
|
{ once: true },
|
|
);
|
|
})();
|
|
|
|
return () => {
|
|
isCancelled = true;
|
|
setCanMove(true);
|
|
};
|
|
}, [mainState, step, setCanMove]);
|
|
|
|
// ── arrived : searchCentral dialogue (unchanged) ──────────────────────────
|
|
useDialoguePlayback({
|
|
enabled: mainState === "pylon" && step === "arrived",
|
|
dialogueId: PYLON_NARRATIVE_DIALOGUES.searchCentral,
|
|
});
|
|
|
|
// ── inspected (demo skip) : jump straight to done after 5 s ─────────────
|
|
useEffect(() => {
|
|
if (mainState !== "pylon" || step !== "inspected") return undefined;
|
|
|
|
const timeoutId = window.setTimeout(() => {
|
|
setMissionStep("pylon", "done");
|
|
}, 5_000);
|
|
|
|
return () => {
|
|
window.clearTimeout(timeoutId);
|
|
};
|
|
}, [mainState, step, setMissionStep]);
|
|
|
|
// ── done : powerup sfx + lighting revert → auto-transition to narrator-outro
|
|
useEffect(() => {
|
|
if (mainState !== "pylon" || step !== "done") return undefined;
|
|
|
|
const sfx = AudioManager.getInstance().playSound(PYLON_POWERUP_SFX, 1, {
|
|
category: "sfx",
|
|
});
|
|
|
|
if (sfx) {
|
|
sfx.addEventListener(
|
|
"ended",
|
|
() => setMissionStep("pylon", "narrator-outro"),
|
|
{ once: true },
|
|
);
|
|
sfx.addEventListener(
|
|
"error",
|
|
() => setMissionStep("pylon", "narrator-outro"),
|
|
{ once: true },
|
|
);
|
|
} else {
|
|
// Fallback if the audio can't load
|
|
setMissionStep("pylon", "narrator-outro");
|
|
}
|
|
|
|
return undefined;
|
|
}, [mainState, step, setMissionStep]);
|
|
|
|
// narrator-outro audio sequence + completeMission are handled in PylonNarratorOutro
|
|
|
|
if (mainState !== "pylon") return null;
|
|
|
|
if (step === "locked") {
|
|
return (
|
|
<ZoneDetection
|
|
key="pylon-approach"
|
|
zone={PYLON_APPROACH_ZONE}
|
|
onEnter={() => setMissionStep("pylon", "approaching")}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (step === "approaching") {
|
|
return (
|
|
<ZoneDetection
|
|
key="pylon-arrived"
|
|
zone={PYLON_ARRIVED_ZONE}
|
|
onEnter={() => setMissionStep("pylon", "arrived")}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (
|
|
step === "arrived" ||
|
|
step === "npc-return" ||
|
|
step === "inspected" ||
|
|
step === "done"
|
|
) {
|
|
return <PylonFarmerNPC />;
|
|
}
|
|
|
|
if (step === "narrator-outro") {
|
|
return <PylonNarratorOutro />;
|
|
}
|
|
|
|
return null;
|
|
}
|