big clean up
This commit is contained in:
@@ -101,7 +101,8 @@ la-fabrik/
|
|||||||
│ ├── editor/ # Editor-only parsing utilities
|
│ ├── editor/ # Editor-only parsing utilities
|
||||||
│ ├── map/ # Map loading and validation
|
│ ├── map/ # Map loading and validation
|
||||||
│ └── three/ # Three.js helpers
|
│ └── three/ # Three.js helpers
|
||||||
├── App.tsx # Canvas bootstrap
|
├── types/ # Shared TypeScript domain types
|
||||||
|
├── App.tsx # App bootstrap and route switch
|
||||||
└── main.tsx
|
└── main.tsx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ Keep the player and map octree outside the Rapier provider until there is a deli
|
|||||||
- `src/utils/editor/loadEditorScene.ts` handles editor-only folder upload parsing.
|
- `src/utils/editor/loadEditorScene.ts` handles editor-only folder upload parsing.
|
||||||
- `src/utils/map/loadMapSceneData.ts` is shared by the game scene and editor to load `public/map.json` and resolve model URLs.
|
- `src/utils/map/loadMapSceneData.ts` is shared by the game scene and editor to load `public/map.json` and resolve model URLs.
|
||||||
- `src/types/editor/editor.ts` contains the shared `MapNode`, `SceneData`, and `TransformMode` types.
|
- `src/types/editor/editor.ts` contains the shared `MapNode`, `SceneData`, and `TransformMode` types.
|
||||||
|
- `src/types/gameplay/repairMission.ts` contains shared repair mission ids, mission steps, and guards used across store, config, debug UI, and gameplay components.
|
||||||
|
|
||||||
## Map Data
|
## Map Data
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ For repair missions, it mounts the reusable `RepairGame` component with a missio
|
|||||||
<RepairGame mission="bike" position={[8, 0, -6]} />
|
<RepairGame mission="bike" position={[8, 0, -6]} />
|
||||||
```
|
```
|
||||||
|
|
||||||
`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.
|
`RepairGame` reads the active mission step from the store and writes transitions through generic actions such as `setMissionStep` and `completeMission`. Shared repair ids, mission steps, and runtime guards live in `src/types/gameplay/repairMission.ts` so static mission config does not depend on the Zustand store. 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`.
|
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`.
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ The mission config now carries the mission-specific variations. `bike` repairs o
|
|||||||
- `src/data/gameplay/repairGameConfig.ts` stores repair flow timing constants.
|
- `src/data/gameplay/repairGameConfig.ts` stores repair flow timing constants.
|
||||||
- `src/data/gameplay/repairMissions.ts` stores reusable repair mission config for `bike`, `pylone`, and `ferme`.
|
- `src/data/gameplay/repairMissions.ts` stores reusable repair mission config for `bike`, `pylone`, and `ferme`.
|
||||||
- `src/managers/stores/useGameStore.ts` stores mission progression state and generic mission step helpers.
|
- `src/managers/stores/useGameStore.ts` stores mission progression state and generic mission step helpers.
|
||||||
|
- `src/types/gameplay/repairMission.ts` contains shared repair mission ids, mission steps, and guards used by the store, data config, debug UI, and gameplay components.
|
||||||
|
|
||||||
## Runtime Requirements
|
## Runtime Requirements
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { REPAIR_FRAGMENTATION_SEQUENCE_SECONDS } from "@/data/gameplay/repairGam
|
|||||||
import { REPAIR_MISSIONS } from "@/data/gameplay/repairMissions";
|
import { REPAIR_MISSIONS } from "@/data/gameplay/repairMissions";
|
||||||
import { useRepairFragmentationInput } from "@/hooks/gameplay/useRepairFragmentationInput";
|
import { useRepairFragmentationInput } from "@/hooks/gameplay/useRepairFragmentationInput";
|
||||||
import { useRepairMissionStep } from "@/hooks/gameplay/useRepairMissionStep";
|
import { useRepairMissionStep } from "@/hooks/gameplay/useRepairMissionStep";
|
||||||
import type { RepairMissionId } from "@/managers/stores/useGameStore";
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three";
|
import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three";
|
||||||
import { toVector3Scale } from "@/utils/three/scale";
|
import { toVector3Scale } from "@/utils/three/scale";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Component } from "react";
|
|||||||
import { SimpleModel } from "@/components/three/models/SimpleModel";
|
import { SimpleModel } from "@/components/three/models/SimpleModel";
|
||||||
import type { ModelTransformProps } from "@/types/three/three";
|
import type { ModelTransformProps } from "@/types/three/three";
|
||||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||||
|
import { toVector3Scale } from "@/utils/three/scale";
|
||||||
|
|
||||||
interface RepairObjectModelProps extends ModelTransformProps {
|
interface RepairObjectModelProps extends ModelTransformProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -17,6 +18,13 @@ interface RepairObjectModelBoundaryState {
|
|||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RepairObjectFallbackProps {
|
||||||
|
label: string;
|
||||||
|
position?: ModelTransformProps["position"] | undefined;
|
||||||
|
rotation?: ModelTransformProps["rotation"] | undefined;
|
||||||
|
scale?: ModelTransformProps["scale"] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
class RepairObjectModelBoundary extends Component<
|
class RepairObjectModelBoundary extends Component<
|
||||||
RepairObjectModelBoundaryProps,
|
RepairObjectModelBoundaryProps,
|
||||||
RepairObjectModelBoundaryState
|
RepairObjectModelBoundaryState
|
||||||
@@ -45,7 +53,14 @@ class RepairObjectModelBoundary extends Component<
|
|||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return <RepairObjectFallback label={this.props.label} />;
|
return (
|
||||||
|
<RepairObjectFallback
|
||||||
|
label={this.props.label}
|
||||||
|
position={this.props.position}
|
||||||
|
rotation={this.props.rotation}
|
||||||
|
scale={this.props.scale}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
@@ -77,9 +92,21 @@ export function RepairObjectModel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RepairObjectFallback({ label }: { label: string }): React.JSX.Element {
|
function RepairObjectFallback({
|
||||||
|
label,
|
||||||
|
position = [0, 0, 0],
|
||||||
|
rotation = [0, 0, 0],
|
||||||
|
scale = 1,
|
||||||
|
}: Pick<
|
||||||
|
RepairObjectFallbackProps,
|
||||||
|
"label" | "position" | "rotation" | "scale"
|
||||||
|
>): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<group>
|
<group
|
||||||
|
position={position}
|
||||||
|
rotation={rotation}
|
||||||
|
scale={toVector3Scale(scale)}
|
||||||
|
>
|
||||||
<mesh castShadow receiveShadow>
|
<mesh castShadow receiveShadow>
|
||||||
<boxGeometry args={[1.4, 1.4, 1.4]} />
|
<boxGeometry args={[1.4, 1.4, 1.4]} />
|
||||||
<meshStandardMaterial color="#facc15" roughness={0.6} wireframe />
|
<meshStandardMaterial color="#facc15" roughness={0.6} wireframe />
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import type { Vector3Tuple } from "@/types/three/three";
|
|||||||
|
|
||||||
const INSTALL_TARGET_POSITION: Vector3Tuple = [0, 0.8, 0];
|
const INSTALL_TARGET_POSITION: Vector3Tuple = [0, 0.8, 0];
|
||||||
const _placeholderPosition = new THREE.Vector3();
|
const _placeholderPosition = new THREE.Vector3();
|
||||||
const REPLACEMENT_START_OFFSETS: Vector3Tuple[] = [
|
const FALLBACK_PLACEHOLDER_OFFSETS: Vector3Tuple[] = [
|
||||||
[-1.15, 1, 0.25],
|
[-1.15, 1, 0.25],
|
||||||
[0, 1.05, 0.45],
|
[0, 1.05, 0.45],
|
||||||
[1.15, 1, 0.25],
|
[1.15, 1, 0.25],
|
||||||
@@ -38,6 +38,18 @@ interface RepairRepairingStepProps {
|
|||||||
onRepair: () => void;
|
onRepair: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RepairInstallTargetProps {
|
||||||
|
fillColor: string;
|
||||||
|
isReadyToInstall: boolean;
|
||||||
|
label: string;
|
||||||
|
ringColor: string;
|
||||||
|
onRepair: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RepairPlaceholderMarkersProps {
|
||||||
|
positions: readonly Vector3Tuple[];
|
||||||
|
}
|
||||||
|
|
||||||
export function RepairRepairingStep({
|
export function RepairRepairingStep({
|
||||||
brokenParts,
|
brokenParts,
|
||||||
config,
|
config,
|
||||||
@@ -82,6 +94,13 @@ export function RepairRepairingStep({
|
|||||||
: hasWrongPartPlaced
|
: hasWrongPartPlaced
|
||||||
? "#fecaca"
|
? "#fecaca"
|
||||||
: "#fed7aa";
|
: "#fed7aa";
|
||||||
|
const installLabel = isReadyToInstall
|
||||||
|
? `Installer ${requiredReplacementLabel}`
|
||||||
|
: hasWrongPartPlaced
|
||||||
|
? `Mauvaise piece`
|
||||||
|
: hasCorrectPartPlaced
|
||||||
|
? `Ranger piece cassee`
|
||||||
|
: `Approcher ${requiredReplacementLabel}`;
|
||||||
|
|
||||||
function handleReplacementPosition(
|
function handleReplacementPosition(
|
||||||
partId: string,
|
partId: string,
|
||||||
@@ -126,48 +145,15 @@ export function RepairRepairingStep({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<TriggerObject
|
<RepairInstallTarget
|
||||||
position={INSTALL_TARGET_POSITION}
|
fillColor={installFillColor}
|
||||||
colliders="ball"
|
isReadyToInstall={isReadyToInstall}
|
||||||
label={
|
label={installLabel}
|
||||||
isReadyToInstall
|
ringColor={installColor}
|
||||||
? `Installer ${requiredReplacementLabel}`
|
onRepair={onRepair}
|
||||||
: hasWrongPartPlaced
|
/>
|
||||||
? `Mauvaise piece`
|
|
||||||
: hasCorrectPartPlaced
|
|
||||||
? `Ranger piece cassee`
|
|
||||||
: `Approcher ${requiredReplacementLabel}`
|
|
||||||
}
|
|
||||||
onTrigger={() => {
|
|
||||||
if (!isReadyToInstall) return;
|
|
||||||
|
|
||||||
onRepair();
|
<RepairPlaceholderMarkers positions={placeholderPositions} />
|
||||||
}}
|
|
||||||
>
|
|
||||||
<mesh>
|
|
||||||
<torusGeometry args={[0.95, 0.045, 12, 96]} />
|
|
||||||
<meshBasicMaterial color={installColor} transparent opacity={0.85} />
|
|
||||||
</mesh>
|
|
||||||
<mesh position={[0, 0.02, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
|
||||||
<ringGeometry args={[0.15, 0.9, 96]} />
|
|
||||||
<meshBasicMaterial
|
|
||||||
color={installFillColor}
|
|
||||||
transparent
|
|
||||||
opacity={0.35}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
</TriggerObject>
|
|
||||||
|
|
||||||
{placeholderPositions.map((position, index) => (
|
|
||||||
<mesh
|
|
||||||
key={`${position.join(":")}-${index}`}
|
|
||||||
position={position}
|
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
|
||||||
>
|
|
||||||
<torusGeometry args={[0.26, 0.018, 8, 48]} />
|
|
||||||
<meshBasicMaterial color="#38bdf8" transparent opacity={0.55} />
|
|
||||||
</mesh>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{replacementParts.map((part, index) => {
|
{replacementParts.map((part, index) => {
|
||||||
const placeholderPosition =
|
const placeholderPosition =
|
||||||
@@ -251,6 +237,55 @@ export function RepairRepairingStep({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RepairInstallTarget({
|
||||||
|
fillColor,
|
||||||
|
isReadyToInstall,
|
||||||
|
label,
|
||||||
|
ringColor,
|
||||||
|
onRepair,
|
||||||
|
}: RepairInstallTargetProps): React.JSX.Element {
|
||||||
|
return (
|
||||||
|
<TriggerObject
|
||||||
|
position={INSTALL_TARGET_POSITION}
|
||||||
|
colliders="ball"
|
||||||
|
label={label}
|
||||||
|
onTrigger={() => {
|
||||||
|
if (!isReadyToInstall) return;
|
||||||
|
|
||||||
|
onRepair();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<mesh>
|
||||||
|
<torusGeometry args={[0.95, 0.045, 12, 96]} />
|
||||||
|
<meshBasicMaterial color={ringColor} transparent opacity={0.85} />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0, 0.02, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
||||||
|
<ringGeometry args={[0.15, 0.9, 96]} />
|
||||||
|
<meshBasicMaterial color={fillColor} transparent opacity={0.35} />
|
||||||
|
</mesh>
|
||||||
|
</TriggerObject>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RepairPlaceholderMarkers({
|
||||||
|
positions,
|
||||||
|
}: RepairPlaceholderMarkersProps): React.JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{positions.map((position, index) => (
|
||||||
|
<mesh
|
||||||
|
key={`${position.join(":")}-${index}`}
|
||||||
|
position={position}
|
||||||
|
rotation={[Math.PI / 2, 0, 0]}
|
||||||
|
>
|
||||||
|
<torusGeometry args={[0.26, 0.018, 8, 48]} />
|
||||||
|
<meshBasicMaterial color="#38bdf8" transparent opacity={0.55} />
|
||||||
|
</mesh>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getPlaceholderTargets(
|
function getPlaceholderTargets(
|
||||||
placeholders: readonly RepairCasePlaceholder[],
|
placeholders: readonly RepairCasePlaceholder[],
|
||||||
): readonly RepairCasePlaceholder[] {
|
): readonly RepairCasePlaceholder[] {
|
||||||
@@ -258,7 +293,7 @@ function getPlaceholderTargets(
|
|||||||
return placeholders;
|
return placeholders;
|
||||||
}
|
}
|
||||||
|
|
||||||
return REPLACEMENT_START_OFFSETS.map(
|
return FALLBACK_PLACEHOLDER_OFFSETS.map(
|
||||||
(offset, index): RepairCasePlaceholder => ({
|
(offset, index): RepairCasePlaceholder => ({
|
||||||
name: `placeholder_${index + 1}`,
|
name: `placeholder_${index + 1}`,
|
||||||
position: [
|
position: [
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ interface ModelErrorBoundaryProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
modelPath: string;
|
modelPath: string;
|
||||||
position?: Vector3Tuple | undefined;
|
position?: Vector3Tuple | undefined;
|
||||||
|
rotation?: Vector3Tuple | undefined;
|
||||||
|
scale?: ModelTransformProps["scale"] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModelErrorBoundaryState {
|
interface ModelErrorBoundaryState {
|
||||||
@@ -38,6 +40,8 @@ class ModelErrorBoundary extends Component<
|
|||||||
modelPath: this.props.modelPath,
|
modelPath: this.props.modelPath,
|
||||||
scope: "ExplodableModel",
|
scope: "ExplodableModel",
|
||||||
position: this.props.position,
|
position: this.props.position,
|
||||||
|
rotation: this.props.rotation,
|
||||||
|
scale: this.props.scale,
|
||||||
},
|
},
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
@@ -45,7 +49,13 @@ class ModelErrorBoundary extends Component<
|
|||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return <MissingModelFallback position={this.props.position} />;
|
return (
|
||||||
|
<MissingModelFallback
|
||||||
|
position={this.props.position}
|
||||||
|
rotation={this.props.rotation}
|
||||||
|
scale={this.props.scale}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.props.children;
|
return this.props.children;
|
||||||
@@ -67,6 +77,8 @@ export function ExplodableModel(
|
|||||||
key={props.modelPath}
|
key={props.modelPath}
|
||||||
modelPath={props.modelPath}
|
modelPath={props.modelPath}
|
||||||
position={props.position}
|
position={props.position}
|
||||||
|
rotation={props.rotation}
|
||||||
|
scale={props.scale}
|
||||||
>
|
>
|
||||||
<ExplodableModelInner {...props} />
|
<ExplodableModelInner {...props} />
|
||||||
</ModelErrorBoundary>
|
</ModelErrorBoundary>
|
||||||
@@ -116,11 +128,15 @@ function ExplodableModelInner({
|
|||||||
|
|
||||||
function MissingModelFallback({
|
function MissingModelFallback({
|
||||||
position = [0, 0, 0],
|
position = [0, 0, 0],
|
||||||
|
rotation = [0, 0, 0],
|
||||||
|
scale = 1,
|
||||||
}: {
|
}: {
|
||||||
position?: Vector3Tuple | undefined;
|
position?: Vector3Tuple | undefined;
|
||||||
|
rotation?: Vector3Tuple | undefined;
|
||||||
|
scale?: ModelTransformProps["scale"] | undefined;
|
||||||
}): React.JSX.Element {
|
}): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<mesh position={position}>
|
<mesh position={position} rotation={rotation} scale={toVector3Scale(scale)}>
|
||||||
<boxGeometry args={[0.7, 0.7, 0.7]} />
|
<boxGeometry args={[0.7, 0.7, 0.7]} />
|
||||||
<meshStandardMaterial color="#7f1d1d" wireframe />
|
<meshStandardMaterial color="#7f1d1d" wireframe />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { RotateCcw, StepBack, StepForward } from "lucide-react";
|
import { RotateCcw, StepBack, StepForward } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
type MainGameState,
|
type MainGameState,
|
||||||
type MissionStep,
|
|
||||||
useGameStore,
|
useGameStore,
|
||||||
} from "@/managers/stores/useGameStore";
|
} from "@/managers/stores/useGameStore";
|
||||||
|
import { isMissionStep, MISSION_STEPS } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
const MAIN_STATES: MainGameState[] = [
|
const MAIN_STATES: MainGameState[] = [
|
||||||
"intro",
|
"intro",
|
||||||
@@ -13,17 +13,6 @@ const MAIN_STATES: MainGameState[] = [
|
|||||||
"outro",
|
"outro",
|
||||||
];
|
];
|
||||||
|
|
||||||
const MISSION_STEPS: MissionStep[] = [
|
|
||||||
"locked",
|
|
||||||
"waiting",
|
|
||||||
"inspected",
|
|
||||||
"fragmented",
|
|
||||||
"scanning",
|
|
||||||
"repairing",
|
|
||||||
"reassembling",
|
|
||||||
"done",
|
|
||||||
];
|
|
||||||
|
|
||||||
function toPascalCase(value: string): string {
|
function toPascalCase(value: string): string {
|
||||||
return value
|
return value
|
||||||
.split(/[-_\s]+/)
|
.split(/[-_\s]+/)
|
||||||
@@ -71,22 +60,27 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mainState === "outro") {
|
||||||
|
setOutroState({ hasStarted: nextSubState === "started" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMissionStep(nextSubState)) return;
|
||||||
|
|
||||||
if (mainState === "bike") {
|
if (mainState === "bike") {
|
||||||
setBikeState({ currentStep: nextSubState as MissionStep });
|
setBikeState({ currentStep: nextSubState });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainState === "pylone") {
|
if (mainState === "pylone") {
|
||||||
setPyloneState({ currentStep: nextSubState as MissionStep });
|
setPyloneState({ currentStep: nextSubState });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mainState === "ferme") {
|
if (mainState === "ferme") {
|
||||||
setFermeState({ currentStep: nextSubState as MissionStep });
|
setFermeState({ currentStep: nextSubState });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOutroState({ hasStarted: nextSubState === "started" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ Pour les missions de réparation, il monte le composant réutilisable \`RepairGa
|
|||||||
<RepairGame mission="bike" position={[8, 0, -6]} />
|
<RepairGame mission="bike" position={[8, 0, -6]} />
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
\`RepairGame\` lit l'étape de mission active depuis le store et écrit les transitions via des actions génériques comme \`setMissionStep\` et \`completeMission\`. Cela garde le composant de scène petit et évite les branches spécifiques à chaque mission dans le flow de réparation. Le flow de réparation de production supporte actuellement les transitions \`waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission\`.
|
\`RepairGame\` lit l'étape de mission active depuis le store et écrit les transitions via des actions génériques comme \`setMissionStep\` et \`completeMission\`. Les ids de mission, étapes de mission et guards partagés vivent dans \`src/types/gameplay/repairMission.ts\`, ce qui évite à la configuration statique des missions de dépendre du store Zustand. Le flow de réparation de production supporte actuellement les transitions \`waiting -> inspected -> fragmented -> scanning -> repairing -> reassembling -> done -> next mission\`.
|
||||||
|
|
||||||
La scène peut donc évoluer progressivement vers ce pattern :
|
La scène peut donc évoluer progressivement vers ce pattern :
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { RepairMissionId } from "@/managers/stores/useGameStore";
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
import type { Vector3Scale, Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Scale, Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
export interface RepairMissionCaseConfig {
|
export interface RepairMissionCaseConfig {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import type {
|
import type {
|
||||||
MissionStep,
|
MissionStep,
|
||||||
RepairMissionId,
|
RepairMissionId,
|
||||||
} from "@/managers/stores/useGameStore";
|
} from "@/types/gameplay/repairMission";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
|
||||||
|
|
||||||
export function useRepairMissionStep(mission: RepairMissionId): MissionStep {
|
export function useRepairMissionStep(mission: RepairMissionId): MissionStep {
|
||||||
return useGameStore((state) => state[mission].currentStep);
|
return useGameStore((state) => state[mission].currentStep);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function getCameraStreamWithTimeout(
|
|||||||
didTimeout = true;
|
didTimeout = true;
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
new Error(
|
||||||
"Camera request timed out. Restart Arc or check camera permissions for localhost:5173.",
|
"Camera request timed out. Restart the browser or check camera permissions for localhost:5173.",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, HAND_TRACKING_CAMERA_TIMEOUT_MS);
|
}, HAND_TRACKING_CAMERA_TIMEOUT_MS);
|
||||||
|
|||||||
@@ -125,7 +125,22 @@ export class AudioManager {
|
|||||||
|
|
||||||
this._musicUnlockHandler = () => {
|
this._musicUnlockHandler = () => {
|
||||||
this._removeMusicUnlockHandler();
|
this._removeMusicUnlockHandler();
|
||||||
void this._music?.play();
|
const music = this._music;
|
||||||
|
if (!music) return;
|
||||||
|
|
||||||
|
void music.play().catch((error: unknown) => {
|
||||||
|
if (
|
||||||
|
error instanceof DOMException &&
|
||||||
|
AudioManager.IGNORED_PLAYBACK_ERRORS.has(error.name)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error("AudioManager", "Failed to unlock music playback", {
|
||||||
|
path: this._musicPath,
|
||||||
|
error: AudioManager._toLogValue(error),
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("pointerdown", this._musicUnlockHandler, {
|
window.addEventListener("pointerdown", this._musicUnlockHandler, {
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
import {
|
||||||
|
isRepairMissionId,
|
||||||
|
type MissionStep,
|
||||||
|
type RepairMissionId,
|
||||||
|
} from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
export type MainGameState = "intro" | "bike" | "pylone" | "ferme" | "outro";
|
export type MainGameState = "intro" | "bike" | "pylone" | "ferme" | "outro";
|
||||||
export type RepairMissionId = "bike" | "pylone" | "ferme";
|
export type { MissionStep, RepairMissionId };
|
||||||
export type MissionStep =
|
|
||||||
| "locked"
|
|
||||||
| "waiting"
|
|
||||||
| "inspected"
|
|
||||||
| "fragmented"
|
|
||||||
| "scanning"
|
|
||||||
| "repairing"
|
|
||||||
| "reassembling"
|
|
||||||
| "done";
|
|
||||||
|
|
||||||
interface IntroState {
|
interface IntroState {
|
||||||
dialogueAudio: string | null;
|
dialogueAudio: string | null;
|
||||||
@@ -63,12 +59,6 @@ interface GameActions {
|
|||||||
type GameStore = GameState & GameActions;
|
type GameStore = GameState & GameActions;
|
||||||
type GameStateUpdate = Partial<GameState>;
|
type GameStateUpdate = Partial<GameState>;
|
||||||
|
|
||||||
export const REPAIR_MISSION_IDS = ["bike", "pylone", "ferme"] as const;
|
|
||||||
|
|
||||||
function isRepairMissionId(value: MainGameState): value is RepairMissionId {
|
|
||||||
return REPAIR_MISSION_IDS.includes(value as RepairMissionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNextMissionStep(step: MissionStep): MissionStep {
|
function getNextMissionStep(step: MissionStep): MissionStep {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case "locked":
|
case "locked":
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import { useBrowserHandTracking } from "@/hooks/handTracking/useBrowserHandTracking";
|
import { useBrowserHandTracking } from "@/hooks/handTracking/useBrowserHandTracking";
|
||||||
import { useRemoteHandTracking } from "@/hooks/handTracking/useRemoteHandTracking";
|
import { useRemoteHandTracking } from "@/hooks/handTracking/useRemoteHandTracking";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import type { MissionStep } from "@/managers/stores/useGameStore";
|
import type { MissionStep } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
const REPAIR_HAND_TRACKING_STEPS = new Set<MissionStep>([
|
const REPAIR_HAND_TRACKING_STEPS = new Set<MissionStep>([
|
||||||
"inspected",
|
"inspected",
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
export type RepairMissionId = "bike" | "pylone" | "ferme";
|
||||||
|
|
||||||
|
export type MissionStep =
|
||||||
|
| "locked"
|
||||||
|
| "waiting"
|
||||||
|
| "inspected"
|
||||||
|
| "fragmented"
|
||||||
|
| "scanning"
|
||||||
|
| "repairing"
|
||||||
|
| "reassembling"
|
||||||
|
| "done";
|
||||||
|
|
||||||
|
export const REPAIR_MISSION_IDS = ["bike", "pylone", "ferme"] as const;
|
||||||
|
|
||||||
|
export const MISSION_STEPS = [
|
||||||
|
"locked",
|
||||||
|
"waiting",
|
||||||
|
"inspected",
|
||||||
|
"fragmented",
|
||||||
|
"scanning",
|
||||||
|
"repairing",
|
||||||
|
"reassembling",
|
||||||
|
"done",
|
||||||
|
] as const satisfies readonly MissionStep[];
|
||||||
|
|
||||||
|
export function isRepairMissionId(value: string): value is RepairMissionId {
|
||||||
|
return (REPAIR_MISSION_IDS as readonly string[]).includes(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMissionStep(value: string): value is MissionStep {
|
||||||
|
return (MISSION_STEPS as readonly string[]).includes(value);
|
||||||
|
}
|
||||||
@@ -17,7 +17,8 @@ export async function createSceneDataFromFiles(
|
|||||||
throw new Error("Fichier map.json manquant à la racine du dossier");
|
throw new Error("Fichier map.json manquant à la racine du dossier");
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapNodes = parseMapNodes(JSON.parse(await mapFile.text()));
|
const mapPayload: unknown = JSON.parse(await mapFile.text());
|
||||||
|
const mapNodes = parseMapNodes(mapPayload);
|
||||||
const models = new Map<string, string>();
|
const models = new Map<string, string>();
|
||||||
|
|
||||||
for (const [path, file] of fileMap.entries()) {
|
for (const [path, file] of fileMap.entries()) {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export async function loadMapSceneData(): Promise<SceneData | null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapNodes = parseMapNodes(await response.json());
|
const mapPayload: unknown = await response.json();
|
||||||
|
const mapNodes = parseMapNodes(mapPayload);
|
||||||
return createSceneData(mapNodes);
|
return createSceneData(mapNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ const saveMapPlugin = (): Plugin => ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(Buffer.concat(chunks).toString());
|
const data: unknown = JSON.parse(Buffer.concat(chunks).toString());
|
||||||
try {
|
try {
|
||||||
parseMapNodes(data);
|
parseMapNodes(data);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user