import { Suspense, useEffect, useMemo, useRef } from "react"; import { Canvas, useFrame } from "@react-three/fiber"; import { useGLTF } from "@react-three/drei"; import * as THREE from "three"; import { useGameStore } from "@/managers/stores/useGameStore"; import { useSubtitleStore } from "@/managers/stores/useSubtitleStore"; const TALKIE_MODEL_PATH = "/models/talkie/model.gltf"; const TALKIE_REVEAL_STEPS = new Set([ "reveal", "await-ebike-mount", "ebike-intro-ride", "ebike-breakdown", "completed", ]); function TalkieModel(): React.JSX.Element { const { scene } = useGLTF(TALKIE_MODEL_PATH); const model = useMemo(() => scene.clone(true), [scene]); const groupRef = useRef(null); useEffect(() => { model.traverse((child) => { if (child instanceof THREE.Mesh) { child.castShadow = false; child.receiveShadow = false; child.frustumCulled = false; } }); }, [model]); useFrame(({ clock }) => { if (!groupRef.current) return; const t = clock.getElapsedTime(); groupRef.current.rotation.z = Math.sin(t * 22) * 0.025; groupRef.current.position.y = Math.sin(t * 6) * 0.012; }); return ( ); } function TalkieSignalLines(): React.JSX.Element { return ( ); } export function TalkieDialogueOverlay(): React.JSX.Element | null { const activeSubtitle = useSubtitleStore((state) => state.activeSubtitle); const mainState = useGameStore((state) => state.mainState); const introStep = useGameStore((state) => state.intro.currentStep); const isAfterReveal = mainState !== "intro" || TALKIE_REVEAL_STEPS.has(introStep); const isNarratorDialogue = activeSubtitle?.speaker === "Narrateur"; if (!isAfterReveal || !isNarratorDialogue) return null; return ( ); } useGLTF.preload(TALKIE_MODEL_PATH);