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,