From 00b1ff9e93ab015c3a6c24c19b98e5fdf0eb954a Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Wed, 3 Jun 2026 00:46:30 +0200 Subject: [PATCH] fix(ebike): unlock walking during breakdown + hide interact prompt + 450m ride Three fixes for the ebike-breakdown substep: 1. PlayerController: the previous `if (!isEbikeBreakdown)` guard zeroed _wishDir for everyone during breakdown, including the player after they had been auto-dismounted to walk mode. Narrow the guard to `isEbikeMounted && isEbikeBreakdown` so the bike stops accepting drive input but the player on foot can move. 2. Ebike: track `window.ebikeBreakdownActive` in component state and hide the InteractableObject (and therefore the interact prompt UI) while the breakdown sequence is active. The bike must read as inert and non-interactive while the panne dialogue plays and during the auto-dismount that follows. 3. ebikeConfig: bump EBIKE_INTRO_BREAKDOWN_DISTANCE from 15 m to 450 m so the panne triggers after a real ride instead of a few meters from the parked spawn. --- src/components/ebike/Ebike.tsx | 8 +++++++- src/data/ebike/ebikeConfig.ts | 2 +- src/world/player/PlayerController.tsx | 6 +++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index e7de0dc..93186bf 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -344,13 +344,19 @@ export function Ebike({ // pollute the view. The prompt comes back the moment the bike comes to // a stop. window.ebikeDriveInputActive is published every frame by // PlayerController based on whether a movement key is currently held. + // Also hide entirely while the breakdown sequence is active — the bike + // must read as inert and non-interactive while the panne dialogue plays + // and during the auto-dismount that follows. const [isEbikeDriving, setIsEbikeDriving] = useState(false); + const [isEbikeBreakdown, setIsEbikeBreakdown] = useState(false); useFrame(() => { const driving = movementMode === "ebike" && window.ebikeDriveInputActive === true; if (driving !== isEbikeDriving) setIsEbikeDriving(driving); + const breakdown = window.ebikeBreakdownActive === true; + if (breakdown !== isEbikeBreakdown) setIsEbikeBreakdown(breakdown); }); - const showInteractPrompt = !isEbikeDriving; + const showInteractPrompt = !isEbikeDriving && !isEbikeBreakdown; const handleInteract = useCallback((): void => { if (window.ebikeBreakdownActive === true) return; diff --git a/src/data/ebike/ebikeConfig.ts b/src/data/ebike/ebikeConfig.ts index 9a90b75..c1279b4 100644 --- a/src/data/ebike/ebikeConfig.ts +++ b/src/data/ebike/ebikeConfig.ts @@ -19,7 +19,7 @@ export const EBIKE_WORLD_POSITION: Vector3Tuple = [68, 0.8, 65]; export const EBIKE_WORLD_ROTATION_Y = -2.5; export const EBIKE_WORLD_SCALE = 0.35; -export const EBIKE_INTRO_BREAKDOWN_DISTANCE = 15; +export const EBIKE_INTRO_BREAKDOWN_DISTANCE = 450; export const EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS = 250; export const EBIKE_ACCELERATION_DURATION_MS = 2000; diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 72cd0f2..8545f9c 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -363,7 +363,11 @@ export function PlayerController({ } _wishDir.set(0, 0, 0); - if (!isEbikeBreakdown) { + // Block drive input only when still on the bike during breakdown. + // Once auto-dismounted (movementMode === "walk"), the player must + // remain free to walk around even though ebikeBreakdownActive is true. + const blockDriveInput = isEbikeMounted && isEbikeBreakdown; + if (!blockDriveInput) { if (keys.current.forward) _wishDir.add(_forward); if (keys.current.backward) _wishDir.sub(_forward); if (!isEbikeMounted) {