diff --git a/src/components/three/gameplay/RepairGame.tsx b/src/components/three/gameplay/RepairGame.tsx index b017c45..17108fc 100644 --- a/src/components/three/gameplay/RepairGame.tsx +++ b/src/components/three/gameplay/RepairGame.tsx @@ -71,6 +71,7 @@ export function RepairGame({ useRepairFragmentationInput({ enabled: mainState === mission && readyForFragmentation, + keyboardEnabled: false, onFragment: () => setMissionStep(mission, "fragmented"), }); @@ -143,6 +144,11 @@ export function RepairGame({ open={step === "repairing"} zoomed={step === "repairing"} showFragmentationPrompt={readyForFragmentation} + onInteract={ + readyForFragmentation + ? () => setMissionStep(mission, "fragmented") + : undefined + } /> ) : null} diff --git a/src/components/three/gameplay/RepairMissionCase.tsx b/src/components/three/gameplay/RepairMissionCase.tsx index e112b3d..3489229 100644 --- a/src/components/three/gameplay/RepairMissionCase.tsx +++ b/src/components/three/gameplay/RepairMissionCase.tsx @@ -3,12 +3,14 @@ import { type RepairCasePlaceholder, } from "@/components/three/gameplay/RepairCaseModel"; import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo"; +import { TriggerObject } from "@/components/three/interaction/TriggerObject"; import { REPAIR_CASE_FOCUS_POSITION, REPAIR_CASE_FOCUS_SCALE, REPAIR_CASE_MODEL_PATH, } from "@/data/gameplay/repairCaseConfig"; import type { RepairMissionConfig } from "@/data/gameplay/repairMissions"; +import type { Vector3Tuple } from "@/types/three/three"; interface RepairMissionCaseProps { config: RepairMissionConfig; @@ -20,6 +22,7 @@ interface RepairMissionCaseProps { open?: boolean; zoomed?: boolean; showFragmentationPrompt?: boolean; + onInteract?: (() => void) | undefined; } export function RepairMissionCase({ @@ -30,25 +33,48 @@ export function RepairMissionCase({ open = false, zoomed = false, showFragmentationPrompt = false, + onInteract, }: RepairMissionCaseProps): React.JSX.Element { const casePosition = zoomed ? REPAIR_CASE_FOCUS_POSITION : config.case.position; const caseScale = zoomed ? REPAIR_CASE_FOCUS_SCALE : config.case.scale; + const modelPosition: Vector3Tuple = onInteract ? [0, 0, 0] : casePosition; return ( - + {onInteract ? ( + + + + ) : ( + + )} {showFragmentationPrompt && !exiting ? ( - + + + + ); })} @@ -207,6 +223,7 @@ export function RepairRepairingStep({ part, placeholderTargets, ); + const isDeposited = Boolean(depositedBrokenPartIds[part.id]); return ( + ); @@ -296,6 +316,46 @@ function RepairPlaceholderMarkers({ ); } +function RepairPartPlacementFeedback({ + state, +}: RepairPartPlacementFeedbackProps): React.JSX.Element | null { + if (!state) return null; + + const color = getPlacementFeedbackColor(state); + + return ( + + + + + + + + + + + ); +} + +function getPlacementFeedbackColor( + state: NonNullable, +): string { + if (state === "valid") return VALID_PART_COLOR; + if (state === "stored") return STORED_BROKEN_PART_COLOR; + + return INVALID_PART_COLOR; +} + +function getReplacementFeedbackState( + partId: string, + requiredPartId: string, + isPlaced: boolean, +): RepairPartPlacementFeedbackProps["state"] { + if (!isPlaced) return null; + + return partId === requiredPartId ? "valid" : "invalid"; +} + function getPlaceholderTargets( placeholders: readonly RepairCasePlaceholder[], ): readonly RepairCasePlaceholder[] { diff --git a/src/hooks/gameplay/useRepairFragmentationInput.ts b/src/hooks/gameplay/useRepairFragmentationInput.ts index b97773d..2324d2c 100644 --- a/src/hooks/gameplay/useRepairFragmentationInput.ts +++ b/src/hooks/gameplay/useRepairFragmentationInput.ts @@ -5,11 +5,13 @@ import { useBothFistsHold } from "@/hooks/handTracking/useBothFistsHold"; interface UseRepairFragmentationInputOptions { enabled: boolean; + keyboardEnabled?: boolean; onFragment: () => void; } export function useRepairFragmentationInput({ enabled, + keyboardEnabled = true, onFragment, }: UseRepairFragmentationInputOptions): void { const completedRef = useRef(false); @@ -29,7 +31,7 @@ export function useRepairFragmentationInput({ }, [enabled, onFragment]); useEffect(() => { - if (!enabled) return undefined; + if (!enabled || !keyboardEnabled) return undefined; const handleKeyDown = (event: KeyboardEvent): void => { if (event.key.toLowerCase() !== INTERACT_KEY) return; @@ -43,7 +45,7 @@ export function useRepairFragmentationInput({ return () => { window.removeEventListener("keydown", handleKeyDown); }; - }, [enabled, fragment]); + }, [enabled, fragment, keyboardEnabled]); useBothFistsHold({ enabled,