diff --git a/public/assets/world/UI/intro-mission-notification.png b/public/assets/world/UI/intro-mission-notification.png new file mode 100644 index 0000000..fe207c3 --- /dev/null +++ b/public/assets/world/UI/intro-mission-notification.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0c92f57ef14cfa7ec19c9e6a8ed32eaabb3f3db9ea57f1c1bcc6a0ad7c00825 +size 8467 diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index addef6d..3c9e3de 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -14,6 +14,7 @@ import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig"; import { EBIKE_CAMERA_TRANSFORM, EBIKE_DROP_PLAYER_TRANSFORM, + EBIKE_WORLD_SCALE, EBIKE_WORLD_ROTATION_Y, } from "@/data/ebike/ebikeConfig"; import type { Vector3Tuple } from "@/types/three/three"; @@ -283,17 +284,18 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { ref={groupRef} position={position} rotation={[0, EBIKE_WORLD_ROTATION_Y, 0]} + scale={EBIKE_WORLD_SCALE} > - + diff --git a/src/components/game/EbikeIntroSequence.tsx b/src/components/game/EbikeIntroSequence.tsx index b5d0c90..3351952 100644 --- a/src/components/game/EbikeIntroSequence.tsx +++ b/src/components/game/EbikeIntroSequence.tsx @@ -1,11 +1,13 @@ import { useEffect, useRef, useState } from "react"; +import * as THREE from "three"; import { MissionNotification } from "@/components/ui/MissionNotification"; import { EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS, EBIKE_BREAKDOWN_DIALOGUE_ID, - EBIKE_INTRO_RIDE_DURATION_MS, + EBIKE_INTRO_BREAKDOWN_DISTANCE, EBIKE_SOUNDS, } from "@/data/ebike/ebikeConfig"; +import { INTRO_MISSION_NOTIFICATION_IMAGE_PATH } from "@/data/gameplay/missionNotifications"; import { AudioManager } from "@/managers/AudioManager"; import { useGameStore } from "@/managers/stores/useGameStore"; import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest"; @@ -18,6 +20,9 @@ export function EbikeIntroSequence(): React.JSX.Element | null { const completeIntro = useGameStore((state) => state.completeIntro); const [breakdownDialogueDone, setBreakdownDialogueDone] = useState(false); const hasStartedBreakdown = useRef(false); + const rideDistance = useRef(0); + const lastRidePosition = useRef(null); + const currentRidePosition = useRef(new THREE.Vector3()); useEffect(() => { if (introStep !== "await-ebike-mount" || movementMode !== "ebike") return; @@ -26,16 +31,45 @@ export function EbikeIntroSequence(): React.JSX.Element | null { }, [introStep, movementMode, setIntroStep]); useEffect(() => { - if (introStep !== "ebike-intro-ride") return undefined; + if (introStep !== "ebike-intro-ride") return; - const timeoutId = window.setTimeout(() => { - setIntroStep("ebike-breakdown"); - }, EBIKE_INTRO_RIDE_DURATION_MS); + rideDistance.current = 0; + lastRidePosition.current = null; + }, [introStep]); - return () => { - window.clearTimeout(timeoutId); + useEffect(() => { + if (introStep !== "ebike-intro-ride" || movementMode !== "ebike") { + return undefined; + } + + let animationFrameId = 0; + const tick = () => { + const parkedPosition = window.ebikeParkedPosition; + if (parkedPosition) { + currentRidePosition.current.set(...parkedPosition); + if (!lastRidePosition.current) { + lastRidePosition.current = currentRidePosition.current.clone(); + } else { + rideDistance.current += currentRidePosition.current.distanceTo( + lastRidePosition.current, + ); + lastRidePosition.current.copy(currentRidePosition.current); + } + + if (rideDistance.current >= EBIKE_INTRO_BREAKDOWN_DISTANCE) { + setIntroStep("ebike-breakdown"); + return; + } + } + + animationFrameId = window.requestAnimationFrame(tick); }; - }, [introStep, setIntroStep]); + + animationFrameId = window.requestAnimationFrame(tick); + return () => { + window.cancelAnimationFrame(animationFrameId); + }; + }, [introStep, movementMode, setIntroStep]); useEffect(() => { if (introStep !== "ebike-breakdown" || hasStartedBreakdown.current) { @@ -100,14 +134,27 @@ export function EbikeIntroSequence(): React.JSX.Element | null { } }, [introStep]); - if (introStep !== "await-ebike-mount" && introStep !== "ebike-intro-ride") { + if ( + introStep !== "reveal" && + introStep !== "await-ebike-mount" && + introStep !== "ebike-intro-ride" && + introStep !== "ebike-breakdown" + ) { return null; } + if (introStep === "ebike-breakdown") { + return ; + } + return ( ); } diff --git a/src/components/ui/MissionNotification.tsx b/src/components/ui/MissionNotification.tsx index 8b2d968..439ed9f 100644 --- a/src/components/ui/MissionNotification.tsx +++ b/src/components/ui/MissionNotification.tsx @@ -2,14 +2,19 @@ import { MISSION_NOTIFICATION_IMAGE_PATHS } from "@/data/gameplay/missionNotific import type { RepairMissionId } from "@/types/gameplay/repairMission"; interface MissionNotificationProps { - mission: RepairMissionId; + mission?: RepairMissionId; + imagePath?: string; visible?: boolean; } export function MissionNotification({ mission, + imagePath, visible = true, }: MissionNotificationProps): React.JSX.Element { + const src = + imagePath ?? (mission ? MISSION_NOTIFICATION_IMAGE_PATHS[mission] : ""); + return (
Nouvel objectif de mission diff --git a/src/data/ebike/ebikeConfig.ts b/src/data/ebike/ebikeConfig.ts index 12685d8..0930799 100644 --- a/src/data/ebike/ebikeConfig.ts +++ b/src/data/ebike/ebikeConfig.ts @@ -6,19 +6,20 @@ export interface CameraTransform { } export const EBIKE_CAMERA_TRANSFORM: CameraTransform = { - position: [-3.5, 6, 0], + position: [-2.6, 4.5, 0], rotation: [-10, -90, 0], }; export const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = { - position: [0, 1.5, -3], + position: [0, 1.3, -2.25], rotation: [0, 0, 0], }; -export const EBIKE_WORLD_POSITION: Vector3Tuple = [61.5, 8.4, 62.4]; +export const EBIKE_WORLD_POSITION: Vector3Tuple = [55.8, 1.75, 60.2]; export const EBIKE_WORLD_ROTATION_Y = 2.4107; +export const EBIKE_WORLD_SCALE = 0.25; -export const EBIKE_INTRO_RIDE_DURATION_MS = 5000; +export const EBIKE_INTRO_BREAKDOWN_DISTANCE = 15; export const EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS = 250; export const EBIKE_MAX_SPEED = 3; diff --git a/src/data/gameplay/missionNotifications.ts b/src/data/gameplay/missionNotifications.ts index 82caf13..167a063 100644 --- a/src/data/gameplay/missionNotifications.ts +++ b/src/data/gameplay/missionNotifications.ts @@ -1,5 +1,8 @@ import type { RepairMissionId } from "@/types/gameplay/repairMission"; +export const INTRO_MISSION_NOTIFICATION_IMAGE_PATH = + "/assets/world/UI/intro-mission-notification.png"; + export const MISSION_NOTIFICATION_IMAGE_PATHS: Record = { ebike: "/assets/world/UI/ebike-mission-notification.png", diff --git a/src/data/world/characters/characterConfig.ts b/src/data/world/characters/characterConfig.ts index 4c413e1..26776cb 100644 --- a/src/data/world/characters/characterConfig.ts +++ b/src/data/world/characters/characterConfig.ts @@ -43,7 +43,6 @@ export const CHARACTER_CONFIGS = { scale: [1.55, 1.55, 1.55], animations: ["idle", "walk"], defaultAnimation: "idle", - snapToTerrain: false, }, fermier: { id: "fermier",