diff --git a/src/components/game/GameFlow.tsx b/src/components/game/GameFlow.tsx
deleted file mode 100644
index 2720a22..0000000
--- a/src/components/game/GameFlow.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useEffect, useRef } from "react";
-import { AudioManager } from "@/managers/AudioManager";
-import { useGameStore } from "@/managers/stores/useGameStore";
-import { AUDIO_PATHS } from "@/data/audioConfig";
-
-export function GameFlow(): null {
- const step = useGameStore((state) => state.intro.currentStep);
- const setStep = useGameStore((state) => state.setIntroStep);
- const setActivityCity = useGameStore((state) => state.setActivityCity);
- const setCanMove = useGameStore((state) => state.setCanMove);
- const completeIntro = useGameStore((state) => state.completeIntro);
- const hasInitialized = useRef(false);
-
- useEffect(() => {
- if (!hasInitialized.current && step === "intro") {
- hasInitialized.current = true;
- setStep("start-intro");
- }
- }, [step, setStep]);
-
- useEffect(() => {
- if (step === "start-intro") {
- const audio = AudioManager.getInstance();
- audio.playSoundWithCallback(AUDIO_PATHS.intro, 0.5, () => {
- setStep("naming");
- });
-
- return () => {};
- }
-
- 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);
- const timeoutId = window.setTimeout(() => {
- completeIntro();
- }, 1000);
-
- return () => {
- window.clearTimeout(timeoutId);
- };
- }
-
- return undefined;
- }, [completeIntro, step, setStep, setActivityCity, setCanMove]);
-
- return null;
-}
diff --git a/src/components/three/interaction/NPCHelper.tsx b/src/components/three/interaction/NPCHelper.tsx
deleted file mode 100644
index e89e981..0000000
--- a/src/components/three/interaction/NPCHelper.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { InteractableObject } from "@/components/three/interaction/InteractableObject";
-import { useGameStore } from "@/managers/stores/useGameStore";
-import { Debug } from "@/utils/debug/Debug";
-import type { Vector3Tuple } from "@/types/three/three";
-
-interface NPCHelperProps {
- position: Vector3Tuple;
-}
-
-export function NPCHelper({ position }: NPCHelperProps): React.JSX.Element {
- const step = useGameStore((state) => state.intro.currentStep);
- const setStep = useGameStore((state) => state.setIntroStep);
- const debug = Debug.getInstance();
-
- const handlePress = (): void => {
- if (step === "searching") {
- setStep("helped");
- }
- };
-
- const shouldShow = step === "searching" || debug.active;
-
- if (!shouldShow) {
- return <>>;
- }
-
- return (
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/three/interaction/PyloneDestroyed.tsx b/src/components/three/interaction/PyloneDestroyed.tsx
deleted file mode 100644
index f7c567c..0000000
--- a/src/components/three/interaction/PyloneDestroyed.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { InteractableObject } from "@/components/three/interaction/InteractableObject";
-import { useGameStore } from "@/managers/stores/useGameStore";
-import { Debug } from "@/utils/debug/Debug";
-import type { Vector3Tuple } from "@/types/three/three";
-
-interface PyloneDestroyedProps {
- position: Vector3Tuple;
-}
-
-export function PyloneDestroyed({
- position,
-}: PyloneDestroyedProps): React.JSX.Element {
- const step = useGameStore((state) => state.intro.currentStep);
- const setStep = useGameStore((state) => state.setIntroStep);
- const setCanMove = useGameStore((state) => state.setCanMove);
- const showDialog = useGameStore((state) => state.showDialog);
- const debug = Debug.getInstance();
-
- const handlePress = (): void => {
- if (step === "helped") {
- setCanMove(false);
- setStep("manipulation");
- } else if (step === "searching") {
- showDialog(
- "Cet objet est trop lourd pour le porter tout seul, trouve de l'aide",
- );
- }
- };
-
- const shouldShow =
- step === "helped" || step === "manipulation" || debug.active;
-
- if (!shouldShow) {
- return <>>;
- }
-
- return (
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/components/ui/IntroUI.tsx b/src/components/ui/IntroUI.tsx
deleted file mode 100644
index 45ace4e..0000000
--- a/src/components/ui/IntroUI.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { useState } from "react";
-import { useGameStore } from "@/managers/stores/useGameStore";
-
-export function IntroUI(): React.JSX.Element | null {
- const step = useGameStore((state) => state.intro.currentStep);
- const setPlayerName = useGameStore((state) => state.setPlayerName);
- const setStep = useGameStore((state) => state.setIntroStep);
- const [inputValue, setInputValue] = useState("");
-
- if (step !== "naming") return null;
-
- const handleSubmit = (): void => {
- if (inputValue.trim() === "") return;
-
- setPlayerName(inputValue.trim());
- setStep("bienvenue");
- };
-
- const handleKeyDown = (e: React.KeyboardEvent): void => {
- if (e.key === "Enter") {
- handleSubmit();
- }
- };
-
- return (
-
-
-
- Quel est votre prenom ?
-
- setInputValue(e.target.value)}
- onKeyDown={handleKeyDown}
- placeholder="Votre prenom"
- autoFocus
- style={{
- padding: "0.75rem",
- fontSize: "1rem",
- borderRadius: "6px",
- border: "1px solid #444",
- backgroundColor: "#2a2a2a",
- color: "#fff",
- outline: "none",
- }}
- />
-
-
-
- );
-}
-
-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;
-
- return (
-
-
- Bienvenue {playerName} !
-
-
- );
-}
diff --git a/src/components/zone/ZoneDetection.tsx b/src/components/zone/ZoneDetection.tsx
deleted file mode 100644
index 0bbb1c1..0000000
--- a/src/components/zone/ZoneDetection.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import { useEffect, useRef, useState } from "react";
-import { useFrame, useThree } from "@react-three/fiber";
-import * as THREE from "three";
-import { ZONES } from "@/data/zones";
-import { useGameStore } from "@/managers/stores/useGameStore";
-import { Debug } from "@/utils/debug/Debug";
-import { GAME_STEPS } from "@/data/game/gameStateConfig";
-
-const _playerPos = new THREE.Vector3();
-const _zonePos = new THREE.Vector3();
-
-export function ZoneDetection(): null {
- const camera = useThree((state) => state.camera);
- const triggeredZones = useRef>(new Set());
- const debug = Debug.getInstance();
- const step = useGameStore((state) => state.intro.currentStep);
- const setStep = useGameStore((state) => state.setIntroStep);
-
- useEffect(() => {
- if (!debug.active) return;
-
- const folder = debug.createFolder("Game");
- if (!folder) return;
-
- const gameState = { step: step };
- const playerPos = { x: 0, y: 0, z: 0 };
-
- folder.add(gameState, "step", GAME_STEPS).name("Game Step").disable();
-
- folder.add(playerPos, "x").name("Player X").listen().disable();
- folder.add(playerPos, "y").name("Player Y").listen().disable();
- folder.add(playerPos, "z").name("Player Z").listen().disable();
-
- const unsubStore = useGameStore.subscribe((state) => {
- gameState.step = state.intro.currentStep;
- folder.controllersRecursive().forEach((c) => c.updateDisplay());
- });
-
- let frameId: number;
- const updatePlayerPos = (): void => {
- camera.getWorldPosition(_playerPos);
- playerPos.x = Math.round(_playerPos.x * 100) / 100;
- playerPos.y = Math.round(_playerPos.y * 100) / 100;
- playerPos.z = Math.round(_playerPos.z * 100) / 100;
- folder.controllersRecursive().forEach((c) => c.updateDisplay());
- frameId = requestAnimationFrame(updatePlayerPos);
- };
- updatePlayerPos();
-
- return () => {
- cancelAnimationFrame(frameId);
- debug.destroyFolder("Game");
- unsubStore();
- };
- }, [debug, camera, step]);
-
- useFrame(() => {
- camera.getWorldPosition(_playerPos);
-
- for (const zone of ZONES) {
- if (triggeredZones.current.has(zone.id)) continue;
-
- _zonePos.set(...zone.position);
-
- const distanceSq = _playerPos.distanceToSquared(_zonePos);
-
- if (distanceSq <= zone.radius * zone.radius) {
- setStep(zone.targetStep);
- triggeredZones.current.add(zone.id);
- break;
- }
- }
- });
-
- return null;
-}
-
-export function ZoneDebugVisuals(): React.JSX.Element | null {
- const debug = Debug.getInstance();
- const camera = useThree((state) => state.camera);
- const [triggeredZones, setTriggeredZones] = useState>(new Set());
-
- useFrame(() => {
- camera.getWorldPosition(_playerPos);
-
- for (const zone of ZONES) {
- if (triggeredZones.has(zone.id)) continue;
-
- _zonePos.set(...zone.position);
-
- const distanceSq = _playerPos.distanceToSquared(_zonePos);
-
- if (distanceSq <= zone.radius * zone.radius) {
- setTriggeredZones((prev) => new Set(prev).add(zone.id));
- break;
- }
- }
- });
-
- if (!debug.active) return null;
-
- return (
- <>
- {ZONES.map((zone) => (
-
- ))}
- >
- );
-}
-
-function ZoneVisual({
- position,
- radius,
- height,
- triggered,
-}: {
- position: [number, number, number];
- radius: number;
- height: number;
- triggered: boolean;
-}): React.JSX.Element {
- const color = triggered ? "#00ff00" : "#ff0000";
-
- return (
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/data/game/gameStateConfig.ts b/src/data/game/gameStateConfig.ts
index d85af1a..be1b286 100644
--- a/src/data/game/gameStateConfig.ts
+++ b/src/data/game/gameStateConfig.ts
@@ -1,16 +1,24 @@
-import type { GameStep, MainGameState } from "@/types/game";
+import type { GameStep, MainGameState, SiteStep } from "@/types/game";
-export const GAME_STEPS: readonly GameStep[] = [
- "intro",
- "start-intro",
+/**
+ * Steps for the /site onboarding page
+ */
+export const SITE_STEPS: readonly SiteStep[] = [
+ "welcome",
+ "situation",
"naming",
- "bienvenue",
- "star-move",
- "mission2",
- "searching",
- "helped",
- "manipulation",
- "outOfFabrik",
+ "transition",
+];
+
+/**
+ * Steps for the intro sequence (after /site, on / route)
+ */
+export const GAME_STEPS: readonly GameStep[] = [
+ "loading-map",
+ "video",
+ "dialogue-intro",
+ "reveal",
+ "playing",
];
export const MAIN_GAME_STATES: readonly MainGameState[] = [
@@ -21,9 +29,14 @@ export const MAIN_GAME_STATES: readonly MainGameState[] = [
"outro",
] as const;
+const SITE_STEP_VALUES: ReadonlySet = new Set(SITE_STEPS);
const GAME_STEP_VALUES: ReadonlySet = new Set(GAME_STEPS);
const MAIN_GAME_STATE_VALUES: ReadonlySet = new Set(MAIN_GAME_STATES);
+export function isSiteStep(value: unknown): value is SiteStep {
+ return typeof value === "string" && SITE_STEP_VALUES.has(value);
+}
+
export function isGameStep(value: unknown): value is GameStep {
return typeof value === "string" && GAME_STEP_VALUES.has(value);
}
diff --git a/src/data/zones.ts b/src/data/zones.ts
deleted file mode 100644
index cf88747..0000000
--- a/src/data/zones.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Zone } from "@/types/game";
-import type { Vector3Tuple } from "@/types/three/three";
-
-export const ZONES: Zone[] = [
- {
- id: "fabrikExit",
- 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",
- },
-];
diff --git a/src/managers/stores/useGameStore.ts b/src/managers/stores/useGameStore.ts
index 099caab..e1eb7e5 100644
--- a/src/managers/stores/useGameStore.ts
+++ b/src/managers/stores/useGameStore.ts
@@ -255,7 +255,7 @@ function createInitialGameState(): GameState {
currentSpeed: PLAYER_WALK_SPEED,
},
intro: {
- currentStep: "intro",
+ currentStep: "loading-map",
dialogueAudio: null,
hasCompleted: false,
isEbikeUnlocked: false,
diff --git a/src/pages/page.tsx b/src/pages/page.tsx
index deae3e3..a148d3c 100644
--- a/src/pages/page.tsx
+++ b/src/pages/page.tsx
@@ -4,7 +4,6 @@ import * as THREE from "three";
import { DebugPerf } from "@/components/debug/DebugPerf";
import { DialogMessage } from "@/components/ui/DialogMessage";
import { GameUI } from "@/components/ui/GameUI";
-import { BienvenueDisplay, IntroUI } from "@/components/ui/IntroUI";
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
import { INITIAL_SCENE_LOADING_STATE } from "@/data/world/sceneLoadingConfig";
import { useGameStore } from "@/managers/stores/useGameStore";
@@ -94,8 +93,6 @@ export function HomePage(): React.JSX.Element {
-
-
{dialogMessage ? (
: null}
{sceneMode === "game" ? (
<>
-
-
-
-
-