diff --git a/src/components/three/gameplay/RepairGame.tsx b/src/components/three/gameplay/RepairGame.tsx index c6e0a8b..febbaca 100644 --- a/src/components/three/gameplay/RepairGame.tsx +++ b/src/components/three/gameplay/RepairGame.tsx @@ -1,7 +1,10 @@ import { Suspense, useEffect, useMemo, useState } from "react"; import { useGLTF } from "@react-three/drei"; import { ExplodableModel } from "@/components/three/models/ExplodableModel"; -import type { RepairCasePlaceholder } from "@/components/three/gameplay/RepairCaseModel"; +import type { + RepairCasePartAnchors, + RepairCasePlaceholder, +} from "@/components/three/gameplay/RepairCaseModel"; import { RepairCompletionStep } from "@/components/three/gameplay/RepairCompletionStep"; import { RepairInspectionObject } from "@/components/three/gameplay/RepairInspectionObject"; import { RepairMissionCase } from "@/components/three/gameplay/RepairMissionCase"; @@ -63,6 +66,7 @@ export function RepairGame({ const [casePlaceholders, setCasePlaceholders] = useState< readonly RepairCasePlaceholder[] >([]); + const [caseAnchors, setCaseAnchors] = useState({}); const [scannedBrokenParts, setScannedBrokenParts] = useState< readonly RepairScannedBrokenPart[] >([]); @@ -81,6 +85,7 @@ export function RepairGame({ const timeoutId = window.setTimeout(() => { setCasePlaceholders([]); + setCaseAnchors({}); setScannedBrokenParts([]); }, 0); @@ -137,6 +142,7 @@ export function RepairGame({ ) : null} {step === "repairing" ? ( void) | undefined; + onAnchorsChange?: ((anchors: RepairCasePartAnchors) => void) | undefined; onExitComplete?: (() => void) | undefined; open?: boolean; zoomed?: boolean; @@ -30,6 +32,7 @@ export function RepairMissionCase({ config, exiting = false, onPlaceholdersChange, + onAnchorsChange, onExitComplete, open = false, zoomed = false, @@ -57,6 +60,7 @@ export function RepairMissionCase({ exiting={exiting} onExitComplete={onExitComplete} onPlaceholdersChange={onPlaceholdersChange} + onAnchorsChange={onAnchorsChange} open={open} floating={!zoomed} position={modelPosition} @@ -70,6 +74,7 @@ export function RepairMissionCase({ exiting={exiting} onExitComplete={onExitComplete} onPlaceholdersChange={onPlaceholdersChange} + onAnchorsChange={onAnchorsChange} open={open} floating={!zoomed} position={modelPosition} diff --git a/src/components/three/gameplay/RepairRepairingStep.tsx b/src/components/three/gameplay/RepairRepairingStep.tsx index 8b30064..27d20de 100644 --- a/src/components/three/gameplay/RepairRepairingStep.tsx +++ b/src/components/three/gameplay/RepairRepairingStep.tsx @@ -1,6 +1,9 @@ import { useEffect, useRef, useState } from "react"; import * as THREE from "three"; -import type { RepairCasePlaceholder } from "@/components/three/gameplay/RepairCaseModel"; +import type { + RepairCasePartAnchors, + RepairCasePlaceholder, +} from "@/components/three/gameplay/RepairCaseModel"; import { RepairObjectModel } from "@/components/three/gameplay/RepairObjectModel"; import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo"; import { GrabbableObject } from "@/components/three/interaction/GrabbableObject"; @@ -38,6 +41,7 @@ const STORED_BROKEN_PART_COLOR = "#38bdf8"; let hasWarnedMissingPlaceholders = false; interface RepairRepairingStepProps { + anchors?: RepairCasePartAnchors; brokenParts: readonly RepairScannedBrokenPart[]; config: RepairMissionConfig; placeholders: readonly RepairCasePlaceholder[]; @@ -63,6 +67,7 @@ interface RepairPartPlacementFeedbackProps { } export function RepairRepairingStep({ + anchors = {}, brokenParts, config, placeholders, @@ -193,7 +198,11 @@ export function RepairRepairingStep({ {replacementParts.map((part, index) => { + const anchorPosition = part.caseAnchor + ? anchors[part.caseAnchor] + : undefined; const placeholderPosition = + anchorPosition ?? placeholderPositions[index % placeholderPositions.length] ?? placeholderPositions[0]!; const isPlaced = Boolean(placedPartIds[part.id]); diff --git a/src/data/gameplay/repairMissions.ts b/src/data/gameplay/repairMissions.ts index 261d29a..01b4e22 100644 --- a/src/data/gameplay/repairMissions.ts +++ b/src/data/gameplay/repairMissions.ts @@ -38,13 +38,33 @@ export const REPAIR_MISSIONS: Record = { replacementParts: [ { id: "ebike-cooling-core-replacement", - label: "Replacement cooling core", + label: "Refroidisseur", modelPath: "/models/refroidisseur/model.gltf", + caseAnchor: "refroidisseur", }, { - id: "ebike-glove-distractor", - label: "Insulation glove", - modelPath: "/models/gant_l/model.gltf", + id: "ebike-cable-right-distractor", + label: "Câble droit", + modelPath: "/models/cable1/model.gltf", + caseAnchor: "cabledroit", + }, + { + id: "ebike-cable-left-distractor", + label: "Câble gauche", + modelPath: "/models/cable2/model.gltf", + caseAnchor: "cablegauche", + }, + { + id: "ebike-puce-haut-distractor", + label: "Puce haute", + modelPath: "/models/puce/model.gltf", + caseAnchor: "pucehaut", + }, + { + id: "ebike-puce-bas-distractor", + label: "Puce basse", + modelPath: "/models/puce/model.gltf", + caseAnchor: "pucebas", }, ], }, @@ -59,7 +79,10 @@ export const REPAIR_MISSIONS: Record = { brokenUiPath: REPAIR_BROKEN_UI_PATH, case: DEFAULT_REPAIR_CASE, reassemblySeconds: 1.8, - requiredReplacementPartIds: ["pylon-grid-relay-replacement"], + requiredReplacementPartIds: [ + "pylon-cable-right-replacement", + "pylon-cable-left-replacement", + ], scanPartSeconds: 1.4, brokenParts: [ { @@ -77,19 +100,36 @@ export const REPAIR_MISSIONS: Record = { ], replacementParts: [ { - id: "pylon-grid-relay-replacement", - label: "Replacement grid relay", - modelPath: "/models/pylone/model.gltf", + id: "pylon-cable-right-replacement", + label: "Câble droit", + modelPath: "/models/cable1/model.gltf", + caseAnchor: "cabledroit", + caseLockGroup: "pylon-cable", }, { - id: "pylon-stone-distractor", - label: "Stone counterweight", - modelPath: "/models/galet/model.gltf", + id: "pylon-cable-left-replacement", + label: "Câble gauche", + modelPath: "/models/cable2/model.gltf", + caseAnchor: "cablegauche", + caseLockGroup: "pylon-cable", }, { id: "pylon-cooling-distractor", - label: "Cooling core", + label: "Refroidisseur", modelPath: "/models/refroidisseur/model.gltf", + caseAnchor: "refroidisseur", + }, + { + id: "pylon-puce-haut-distractor", + label: "Puce haute", + modelPath: "/models/puce/model.gltf", + caseAnchor: "pucehaut", + }, + { + id: "pylon-puce-bas-distractor", + label: "Puce basse", + modelPath: "/models/puce/model.gltf", + caseAnchor: "pucebas", }, ], },