feat(types): add SiteStep and refactor GameStep for new intro flow
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<InteractableObject
|
|
||||||
kind="trigger"
|
|
||||||
label="villageois_helper"
|
|
||||||
position={position}
|
|
||||||
onPress={handlePress}
|
|
||||||
>
|
|
||||||
<group position={position}>
|
|
||||||
<mesh>
|
|
||||||
<sphereGeometry args={[0.5, 16, 16]} />
|
|
||||||
<meshStandardMaterial color="cyan" />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
</InteractableObject>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<InteractableObject
|
|
||||||
kind="trigger"
|
|
||||||
label="central"
|
|
||||||
position={position}
|
|
||||||
onPress={handlePress}
|
|
||||||
>
|
|
||||||
<group position={position}>
|
|
||||||
<mesh>
|
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
|
||||||
<meshStandardMaterial color="orange" />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
</InteractableObject>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.7)",
|
|
||||||
zIndex: 1000,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#1a1a1a",
|
|
||||||
padding: "2rem",
|
|
||||||
borderRadius: "12px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "1.5rem",
|
|
||||||
minWidth: "300px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
color: "#fff",
|
|
||||||
margin: 0,
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Quel est votre prenom ?
|
|
||||||
</h2>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={inputValue}
|
|
||||||
onChange={(e) => 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",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={handleSubmit}
|
|
||||||
disabled={inputValue.trim() === ""}
|
|
||||||
style={{
|
|
||||||
padding: "0.75rem",
|
|
||||||
fontSize: "1rem",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "none",
|
|
||||||
backgroundColor: inputValue.trim() ? "#4a9" : "#444",
|
|
||||||
color: "#fff",
|
|
||||||
cursor: inputValue.trim() ? "pointer" : "not-allowed",
|
|
||||||
transition: "background-color 0.2s",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Valider
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: "20%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.8)",
|
|
||||||
padding: "1rem 2rem",
|
|
||||||
borderRadius: "8px",
|
|
||||||
zIndex: 100,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
color: "#fff",
|
|
||||||
margin: 0,
|
|
||||||
fontSize: "1.25rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Bienvenue {playerName} !
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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<Set<string>>(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<Set<string>>(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) => (
|
|
||||||
<ZoneVisual
|
|
||||||
key={zone.id}
|
|
||||||
position={[zone.position[0], 0.1, zone.position[2]]}
|
|
||||||
radius={zone.radius}
|
|
||||||
height={zone.height}
|
|
||||||
triggered={triggeredZones.has(zone.id)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<group position={position}>
|
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
|
||||||
<ringGeometry args={[radius - 0.3, radius, 32]} />
|
|
||||||
<meshBasicMaterial color={color} side={THREE.DoubleSide} />
|
|
||||||
</mesh>
|
|
||||||
<mesh position={[0, height / 2, 0]}>
|
|
||||||
<cylinderGeometry args={[radius, radius, height, 32, 1, true]} />
|
|
||||||
<meshBasicMaterial
|
|
||||||
color={color}
|
|
||||||
transparent
|
|
||||||
opacity={0.15}
|
|
||||||
side={THREE.DoubleSide}
|
|
||||||
depthWrite={false}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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",
|
* Steps for the /site onboarding page
|
||||||
"start-intro",
|
*/
|
||||||
|
export const SITE_STEPS: readonly SiteStep[] = [
|
||||||
|
"welcome",
|
||||||
|
"situation",
|
||||||
"naming",
|
"naming",
|
||||||
"bienvenue",
|
"transition",
|
||||||
"star-move",
|
];
|
||||||
"mission2",
|
|
||||||
"searching",
|
/**
|
||||||
"helped",
|
* Steps for the intro sequence (after /site, on / route)
|
||||||
"manipulation",
|
*/
|
||||||
"outOfFabrik",
|
export const GAME_STEPS: readonly GameStep[] = [
|
||||||
|
"loading-map",
|
||||||
|
"video",
|
||||||
|
"dialogue-intro",
|
||||||
|
"reveal",
|
||||||
|
"playing",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MAIN_GAME_STATES: readonly MainGameState[] = [
|
export const MAIN_GAME_STATES: readonly MainGameState[] = [
|
||||||
@@ -21,9 +29,14 @@ export const MAIN_GAME_STATES: readonly MainGameState[] = [
|
|||||||
"outro",
|
"outro",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const SITE_STEP_VALUES: ReadonlySet<string> = new Set(SITE_STEPS);
|
||||||
const GAME_STEP_VALUES: ReadonlySet<string> = new Set(GAME_STEPS);
|
const GAME_STEP_VALUES: ReadonlySet<string> = new Set(GAME_STEPS);
|
||||||
const MAIN_GAME_STATE_VALUES: ReadonlySet<string> = new Set(MAIN_GAME_STATES);
|
const MAIN_GAME_STATE_VALUES: ReadonlySet<string> = 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 {
|
export function isGameStep(value: unknown): value is GameStep {
|
||||||
return typeof value === "string" && GAME_STEP_VALUES.has(value);
|
return typeof value === "string" && GAME_STEP_VALUES.has(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -255,7 +255,7 @@ function createInitialGameState(): GameState {
|
|||||||
currentSpeed: PLAYER_WALK_SPEED,
|
currentSpeed: PLAYER_WALK_SPEED,
|
||||||
},
|
},
|
||||||
intro: {
|
intro: {
|
||||||
currentStep: "intro",
|
currentStep: "loading-map",
|
||||||
dialogueAudio: null,
|
dialogueAudio: null,
|
||||||
hasCompleted: false,
|
hasCompleted: false,
|
||||||
isEbikeUnlocked: false,
|
isEbikeUnlocked: false,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import * as THREE from "three";
|
|||||||
import { DebugPerf } from "@/components/debug/DebugPerf";
|
import { DebugPerf } from "@/components/debug/DebugPerf";
|
||||||
import { DialogMessage } from "@/components/ui/DialogMessage";
|
import { DialogMessage } from "@/components/ui/DialogMessage";
|
||||||
import { GameUI } from "@/components/ui/GameUI";
|
import { GameUI } from "@/components/ui/GameUI";
|
||||||
import { BienvenueDisplay, IntroUI } from "@/components/ui/IntroUI";
|
|
||||||
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
|
import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay";
|
||||||
import { INITIAL_SCENE_LOADING_STATE } from "@/data/world/sceneLoadingConfig";
|
import { INITIAL_SCENE_LOADING_STATE } from "@/data/world/sceneLoadingConfig";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
@@ -94,8 +93,6 @@ export function HomePage(): React.JSX.Element {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
<GameUI />
|
<GameUI />
|
||||||
<IntroUI />
|
|
||||||
<BienvenueDisplay />
|
|
||||||
{dialogMessage ? (
|
{dialogMessage ? (
|
||||||
<DialogMessage
|
<DialogMessage
|
||||||
message={dialogMessage}
|
message={dialogMessage}
|
||||||
|
|||||||
+17
-19
@@ -1,24 +1,22 @@
|
|||||||
import type { Vector3Tuple } from "@/types/three/three";
|
|
||||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steps for the /site onboarding page
|
||||||
|
*/
|
||||||
|
export type SiteStep =
|
||||||
|
| "welcome" // Écran 1: Bienvenue à Altera
|
||||||
|
| "situation" // Écran 2: Quelle est votre situation
|
||||||
|
| "naming" // Écran 3: Quel est votre prénom (Danyl)
|
||||||
|
| "transition"; // Fondu noir + dialogue final
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Steps for the intro sequence (after /site, on / route)
|
||||||
|
*/
|
||||||
export type GameStep =
|
export type GameStep =
|
||||||
| "intro"
|
| "loading-map" // Chargement des assets
|
||||||
| "start-intro"
|
| "video" // Vidéo intro.mp4
|
||||||
| "naming"
|
| "dialogue-intro" // Dialogues post-vidéo (écran noir)
|
||||||
| "bienvenue"
|
| "reveal" // Fondu noir → jeu visible
|
||||||
| "star-move"
|
| "playing"; // Intro terminée, jeu actif
|
||||||
| "mission2"
|
|
||||||
| "searching"
|
|
||||||
| "helped"
|
|
||||||
| "manipulation"
|
|
||||||
| "outOfFabrik";
|
|
||||||
|
|
||||||
export type MainGameState = "intro" | RepairMissionId | "outro";
|
export type MainGameState = "intro" | RepairMissionId | "outro";
|
||||||
|
|
||||||
export interface Zone {
|
|
||||||
id: string;
|
|
||||||
position: Vector3Tuple;
|
|
||||||
radius: number;
|
|
||||||
height: number;
|
|
||||||
targetStep: GameStep;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,16 +12,9 @@ import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
|||||||
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
||||||
import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading";
|
import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import { GameFlow } from "@/components/game/GameFlow";
|
|
||||||
import {
|
|
||||||
ZoneDebugVisuals,
|
|
||||||
ZoneDetection,
|
|
||||||
} from "@/components/zone/ZoneDetection";
|
|
||||||
import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls";
|
import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls";
|
||||||
import { DebugHelpers } from "@/components/debug/scene/DebugHelpers";
|
import { DebugHelpers } from "@/components/debug/scene/DebugHelpers";
|
||||||
import { HandTrackingGlove } from "@/components/three/handTracking/HandTrackingGlove";
|
import { HandTrackingGlove } from "@/components/three/handTracking/HandTrackingGlove";
|
||||||
import { PyloneDestroyed } from "@/components/three/interaction/PyloneDestroyed";
|
|
||||||
import { NPCHelper } from "@/components/three/interaction/NPCHelper";
|
|
||||||
import { Environment } from "@/world/Environment";
|
import { Environment } from "@/world/Environment";
|
||||||
import { GameCinematics } from "@/world/GameCinematics";
|
import { GameCinematics } from "@/world/GameCinematics";
|
||||||
import { GameDialogues } from "@/world/GameDialogues";
|
import { GameDialogues } from "@/world/GameDialogues";
|
||||||
@@ -80,11 +73,6 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
|||||||
{cameraMode === "debug" ? <DebugCameraControls /> : null}
|
{cameraMode === "debug" ? <DebugCameraControls /> : null}
|
||||||
{sceneMode === "game" ? (
|
{sceneMode === "game" ? (
|
||||||
<>
|
<>
|
||||||
<GameFlow />
|
|
||||||
<ZoneDetection />
|
|
||||||
<ZoneDebugVisuals />
|
|
||||||
<NPCHelper position={[1, 12, -55]} />
|
|
||||||
<PyloneDestroyed position={[1, 15, -45]} />
|
|
||||||
<GameMap
|
<GameMap
|
||||||
onLoaded={handleGameMapLoaded}
|
onLoaded={handleGameMapLoaded}
|
||||||
onLoadingStateChange={onLoadingStateChange}
|
onLoadingStateChange={onLoadingStateChange}
|
||||||
|
|||||||
Reference in New Issue
Block a user