From 0b519a20dc185300ad2484a3a223986079d6adcc Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Fri, 8 May 2026 02:41:57 +0100 Subject: [PATCH] add: configure mission-specific repair variants --- docs/technical/zustand.md | 2 ++ docs/user/features.md | 2 +- docs/user/main-feature.md | 2 ++ .../three/gameplay/RepairReassemblyStep.tsx | 6 ++-- .../three/gameplay/RepairRepairingStep.tsx | 2 +- .../three/gameplay/RepairScanSequence.tsx | 5 ++-- src/data/docs/docsTranslations.ts | 2 +- src/data/gameplay/repairMissions.ts | 28 +++++++++++++++++-- 8 files changed, 40 insertions(+), 9 deletions(-) diff --git a/docs/technical/zustand.md b/docs/technical/zustand.md index 685225c..a36ba07 100644 --- a/docs/technical/zustand.md +++ b/docs/technical/zustand.md @@ -139,6 +139,8 @@ For repair missions, it mounts the reusable `RepairGame` component with a missio `RepairGame` reads the active mission step from the store and writes transitions through generic actions such as `setMissionStep` and `completeMission`. This keeps the scene component small and avoids mission-specific branching inside the repair flow. The production repair flow currently supports `waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission` state transitions. +Mission-specific behavior stays in `src/data/gameplay/repairMissions.ts`: each mission can define its broken nodes, placeholder targets, scan duration, and reassembly duration without adding mission branches to `RepairGame`. + That means the scene can progressively move toward this pattern: ```tsx diff --git a/docs/user/features.md b/docs/user/features.md index 0b8eb83..07e5ba0 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -30,7 +30,7 @@ This document lists features that are implemented in the current codebase. ## Repair Gameplay - Reusable production `RepairGame` mounted for `bike`, `pylone`, and `ferme` mission states -- Repair mission config shared through `src/data/gameplay/repairMissions.ts` +- Repair mission config shared through `src/data/gameplay/repairMissions.ts`, including per-mission broken nodes, placeholder targets, scan timing, and reassembly timing - Repair-game flow supports `waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission` with `.webm` prompts, repair case spawn/opening/exit, focused repair-case view, case placeholder traversal, snap-to-placeholder placement, broken-part deposit, `E`, two-fists hold input, exploded and inverse reassembly transitions, completion particles, per-part scan visuals, persistent red broken-part markers, centered broken-part UI videos, multiple grabbable replacement choices, correct-part install validation, and mission completion ## Audio diff --git a/docs/user/main-feature.md b/docs/user/main-feature.md index ae88d1d..fc3ed0f 100644 --- a/docs/user/main-feature.md +++ b/docs/user/main-feature.md @@ -37,6 +37,8 @@ In `inspected`, `RepairGame` can also move to `fragmented`. The player can use t In `fragmented`, the repair object is rendered with `ExplodableModel`, then automatically advances to `scanning`. In `scanning`, the exploded model remains visible, a blue scan visual moves from part to part, and a red halo/wire marker plus the configured broken UI video stay attached to configured broken parts after the scanner reaches them. The scan can match a specific `nodeName` when mission data provides one, otherwise it falls back to the first scanned parts as placeholder broken parts. In `repairing`, the case opens in a larger focused transform, `RepairCaseModel` traverses the case GLTF for empty nodes named `placeholder_*`, several grabbable replacement parts appear on those placeholder positions, and releasing a part near a placeholder snaps it into place with a short GSAP animation. Scanned broken parts are also rendered as grabbable objects and must be deposited into a compatible placeholder before the final install target validates. If `brokenParts[].placeholderName` is configured, that broken part snaps only to the matching placeholder; otherwise it can use any available placeholder. If the current case asset has no placeholder nodes, the flow keeps using fallback focus positions. The install target only validates when the configured correct replacement part is placed and all scanned broken parts have been deposited. In `reassembling`, the exploded model animates back into its assembled position with green completion particles before the flow moves to `done`. In `done`, the repaired object remains visible with a completion target that plays the case exit animation before advancing the global mission progression. +The mission config now carries the mission-specific variations. `bike` repairs one cooling core, `pylone` scans and stores both the lamp relay and a damaged panel with slower scan/reassembly timing, and `ferme` scans and stores an irrigation pump plus humidity sensor with faster scan/reassembly timing. + ## Key Files - `src/world/GameStageContent.tsx` mounts production `RepairGame` instances for `bike`, `pylone`, and `ferme`. diff --git a/src/components/three/gameplay/RepairReassemblyStep.tsx b/src/components/three/gameplay/RepairReassemblyStep.tsx index 869c96f..1818daa 100644 --- a/src/components/three/gameplay/RepairReassemblyStep.tsx +++ b/src/components/three/gameplay/RepairReassemblyStep.tsx @@ -14,6 +14,8 @@ export function RepairReassemblyStep({ onComplete, }: RepairReassemblyStepProps): React.JSX.Element { const [split, setSplit] = useState(true); + const reassemblySeconds = + config.reassemblySeconds ?? REPAIR_REASSEMBLY_SECONDS; useEffect(() => { const closeTimeoutId = window.setTimeout(() => { @@ -21,13 +23,13 @@ export function RepairReassemblyStep({ }, 50); const completeTimeoutId = window.setTimeout(() => { onComplete(); - }, REPAIR_REASSEMBLY_SECONDS * 1000); + }, reassemblySeconds * 1000); return () => { window.clearTimeout(closeTimeoutId); window.clearTimeout(completeTimeoutId); }; - }, [onComplete]); + }, [onComplete, reassemblySeconds]); return ( diff --git a/src/components/three/gameplay/RepairRepairingStep.tsx b/src/components/three/gameplay/RepairRepairingStep.tsx index 0048c19..3b40ad3 100644 --- a/src/components/three/gameplay/RepairRepairingStep.tsx +++ b/src/components/three/gameplay/RepairRepairingStep.tsx @@ -260,7 +260,7 @@ function getPlaceholderTargets( return REPLACEMENT_START_OFFSETS.map( (offset, index): RepairCasePlaceholder => ({ - name: `fallback_${index + 1}`, + name: `placeholder_${index + 1}`, position: [ REPAIR_CASE_FOCUS_POSITION[0] + offset[0], REPAIR_CASE_FOCUS_POSITION[1] + offset[1], diff --git a/src/components/three/gameplay/RepairScanSequence.tsx b/src/components/three/gameplay/RepairScanSequence.tsx index d473012..65b0b83 100644 --- a/src/components/three/gameplay/RepairScanSequence.tsx +++ b/src/components/three/gameplay/RepairScanSequence.tsx @@ -30,6 +30,7 @@ export function RepairScanSequence({ const [parts, setParts] = useState([]); const [activePartIndex, setActivePartIndex] = useState(0); const activePart = parts[activePartIndex]; + const scanPartSeconds = config.scanPartSeconds ?? REPAIR_SCAN_PART_SECONDS; const brokenPartIndexes = getBrokenPartIndexes(parts, config.brokenParts); const visibleBrokenPartIndexes = brokenPartIndexes.filter( (partIndex) => partIndex <= activePartIndex, @@ -48,12 +49,12 @@ export function RepairScanSequence({ return nextIndex; }); - }, REPAIR_SCAN_PART_SECONDS * 1000); + }, scanPartSeconds * 1000); return () => { window.clearTimeout(timeoutId); }; - }, [activePartIndex, config, onComplete, parts]); + }, [activePartIndex, config, onComplete, parts, scanPartSeconds]); return ( diff --git a/src/data/docs/docsTranslations.ts b/src/data/docs/docsTranslations.ts index ecd0bc8..4e1cf3d 100644 --- a/src/data/docs/docsTranslations.ts +++ b/src/data/docs/docsTranslations.ts @@ -441,7 +441,7 @@ Ce document liste les fonctionnalités présentes dans le code actuel. ## Gameplay de réparation - \`RepairGame\` de production réutilisable monté pour les états de mission \`bike\`, \`pylone\` et \`ferme\` -- Configuration de mission partagée via \`src/data/gameplay/repairMissions.ts\` +- Configuration de mission partagée via \`src/data/gameplay/repairMissions.ts\`, avec nodes cassés, placeholders cibles, timing de scan et timing de réassemblage propres à chaque mission - Flow repair-game avec \`waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission\`, prompts \`.webm\`, apparition/ouverture/sortie de la mallette, vue focalisée de la mallette, traverse des placeholders de mallette, placement avec snap vers placeholder, dépôt des pièces cassées, touche \`E\`, hold deux poings, transition de modèle explosé, réassemblage inverse avec particules, scan visuel par pièce, marqueur rouge persistant et vidéo UI centrée sur les pièces cassées, plusieurs choix de pièces grabbables, validation de la bonne pièce et complétion de mission ## Audio diff --git a/src/data/gameplay/repairMissions.ts b/src/data/gameplay/repairMissions.ts index 6262301..ed88846 100644 --- a/src/data/gameplay/repairMissions.ts +++ b/src/data/gameplay/repairMissions.ts @@ -24,7 +24,9 @@ export interface RepairMissionConfig { interactUiPath: string; brokenUiPath: string; case: RepairMissionCaseConfig; + reassemblySeconds?: number; requiredReplacementPartId: string; + scanPartSeconds?: number; brokenParts: readonly RepairMissionPartConfig[]; replacementParts: readonly RepairMissionPartConfig[]; } @@ -54,6 +56,8 @@ export const REPAIR_MISSIONS = { { id: "bike-cooling-core", label: "Cooling core", + nodeName: "Cylinder", + placeholderName: "placeholder_1", }, ], replacementParts: [ @@ -77,17 +81,28 @@ export const REPAIR_MISSIONS = { pylone: { id: "pylone", label: "Power pylon", - description: "Generic description", + description: + "Restore the pylon lamp relay and damaged panel before reconnecting the grid", modelPath: "/models/pylone/model.gltf", stageUiPath: "/assets/UI/centrale.webm", interactUiPath: REPAIR_INTERACT_UI_PATH, brokenUiPath: REPAIR_BROKEN_UI_PATH, case: DEFAULT_REPAIR_CASE, + reassemblySeconds: 1.8, requiredReplacementPartId: "pylone-grid-relay-replacement", + scanPartSeconds: 1.4, brokenParts: [ { id: "pylone-grid-relay", label: "Grid relay", + nodeName: "lampe", + placeholderName: "placeholder_1", + }, + { + id: "pylone-damaged-panel", + label: "Damaged solar panel", + nodeName: "panneau2", + placeholderName: "placeholder_2", }, ], replacementParts: [ @@ -111,17 +126,26 @@ export const REPAIR_MISSIONS = { ferme: { id: "ferme", label: "Vertical farm", - description: "Generic description", + description: + "Stabilize the irrigation loop and humidity sensor before restarting the farm", modelPath: "/models/fermeverticale/model.gltf", stageUiPath: "/assets/UI/laferme.webm", interactUiPath: REPAIR_INTERACT_UI_PATH, brokenUiPath: REPAIR_BROKEN_UI_PATH, case: DEFAULT_REPAIR_CASE, + reassemblySeconds: 1.2, requiredReplacementPartId: "ferme-irrigation-pump-replacement", + scanPartSeconds: 0.9, brokenParts: [ { id: "ferme-irrigation-pump", label: "Irrigation pump", + placeholderName: "placeholder_1", + }, + { + id: "ferme-humidity-sensor", + label: "Humidity sensor", + placeholderName: "placeholder_2", }, ], replacementParts: [