import { useCallback, useState } from "react"; import * as THREE from "three"; import { RepairObjectModel } from "@/components/three/gameplay/RepairObjectModel"; import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo"; import { GrabbableObject } from "@/components/three/interaction/GrabbableObject"; import { TriggerObject } from "@/components/three/interaction/TriggerObject"; import type { RepairMissionConfig, RepairMissionPartConfig, } from "@/data/gameplay/repairMissions"; import type { Vector3Tuple } from "@/types/three/three"; const INSTALL_TARGET_POSITION: Vector3Tuple = [0, 0.8, 0]; const INSTALL_TARGET_VECTOR = new THREE.Vector3(...INSTALL_TARGET_POSITION); const REPLACEMENT_START_OFFSETS: Vector3Tuple[] = [ [-0.9, 1.35, 1.8], [0, 1.35, 2.15], [0.9, 1.35, 1.8], ]; const REPAIR_INSTALL_RADIUS = 1.1; interface RepairRepairingStepProps { config: RepairMissionConfig; onRepair: () => void; } export function RepairRepairingStep({ config, onRepair, }: RepairRepairingStepProps): React.JSX.Element { const [placedPartIds, setPlacedPartIds] = useState>( {}, ); const replacementParts = getReplacementParts(config); const requiredReplacementPart = replacementParts.find( (part) => part.id === config.requiredReplacementPartId, ); const requiredReplacementLabel = requiredReplacementPart?.label ?? config.label; const hasCorrectPartPlaced = Boolean( placedPartIds[config.requiredReplacementPartId], ); const hasWrongPartPlaced = replacementParts.some( (part) => part.id !== config.requiredReplacementPartId && placedPartIds[part.id], ); const installColor = hasCorrectPartPlaced ? "#22c55e" : hasWrongPartPlaced ? "#ef4444" : "#f97316"; const installFillColor = hasCorrectPartPlaced ? "#86efac" : hasWrongPartPlaced ? "#fecaca" : "#fed7aa"; const handleReplacementPosition = useCallback( (partId: string, position: THREE.Vector3) => { const isPlaced = position.distanceTo(INSTALL_TARGET_VECTOR) <= REPAIR_INSTALL_RADIUS; setPlacedPartIds((current) => { if (current[partId] === isPlaced) return current; return { ...current, [partId]: isPlaced }; }); }, [], ); return ( { if (!hasCorrectPartPlaced) return; onRepair(); }} > {replacementParts.map((part, index) => { const offset = REPLACEMENT_START_OFFSETS[index % REPLACEMENT_START_OFFSETS.length] ?? REPLACEMENT_START_OFFSETS[0]!; return ( { handleReplacementPosition(part.id, position); }} > ); })} ); } function getReplacementParts( config: RepairMissionConfig, ): readonly RepairMissionPartConfig[] { if (config.replacementParts.length > 0) return config.replacementParts; return [ { id: config.requiredReplacementPartId, label: config.label, modelPath: config.modelPath, }, ]; }