big clean up
This commit is contained in:
@@ -101,7 +101,8 @@ la-fabrik/
|
||||
│ ├── editor/ # Editor-only parsing utilities
|
||||
│ ├── map/ # Map loading and validation
|
||||
│ └── three/ # Three.js helpers
|
||||
├── App.tsx # Canvas bootstrap
|
||||
├── types/ # Shared TypeScript domain types
|
||||
├── App.tsx # App bootstrap and route switch
|
||||
└── 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/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/gameplay/repairMission.ts` contains shared repair mission ids, mission steps, and guards used across store, config, debug UI, and gameplay components.
|
||||
|
||||
## 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` 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`.
|
||||
|
||||
|
||||
@@ -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/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/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
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { REPAIR_FRAGMENTATION_SEQUENCE_SECONDS } from "@/data/gameplay/repairGam
|
||||
import { REPAIR_MISSIONS } from "@/data/gameplay/repairMissions";
|
||||
import { useRepairFragmentationInput } from "@/hooks/gameplay/useRepairFragmentationInput";
|
||||
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 type { ModelTransformProps, Vector3Tuple } from "@/types/three/three";
|
||||
import { toVector3Scale } from "@/utils/three/scale";
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Component } from "react";
|
||||
import { SimpleModel } from "@/components/three/models/SimpleModel";
|
||||
import type { ModelTransformProps } from "@/types/three/three";
|
||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||
import { toVector3Scale } from "@/utils/three/scale";
|
||||
|
||||
interface RepairObjectModelProps extends ModelTransformProps {
|
||||
label: string;
|
||||
@@ -17,6 +18,13 @@ interface RepairObjectModelBoundaryState {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
interface RepairObjectFallbackProps {
|
||||
label: string;
|
||||
position?: ModelTransformProps["position"] | undefined;
|
||||
rotation?: ModelTransformProps["rotation"] | undefined;
|
||||
scale?: ModelTransformProps["scale"] | undefined;
|
||||
}
|
||||
|
||||
class RepairObjectModelBoundary extends Component<
|
||||
RepairObjectModelBoundaryProps,
|
||||
RepairObjectModelBoundaryState
|
||||
@@ -45,7 +53,14 @@ class RepairObjectModelBoundary extends Component<
|
||||
|
||||
render(): ReactNode {
|
||||
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;
|
||||
@@ -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 (
|
||||
<group>
|
||||
<group
|
||||
position={position}
|
||||
rotation={rotation}
|
||||
scale={toVector3Scale(scale)}
|
||||
>
|
||||
<mesh castShadow receiveShadow>
|
||||
<boxGeometry args={[1.4, 1.4, 1.4]} />
|
||||
<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 _placeholderPosition = new THREE.Vector3();
|
||||
const REPLACEMENT_START_OFFSETS: Vector3Tuple[] = [
|
||||
const FALLBACK_PLACEHOLDER_OFFSETS: Vector3Tuple[] = [
|
||||
[-1.15, 1, 0.25],
|
||||
[0, 1.05, 0.45],
|
||||
[1.15, 1, 0.25],
|
||||
@@ -38,6 +38,18 @@ interface RepairRepairingStepProps {
|
||||
onRepair: () => void;
|
||||
}
|
||||
|
||||
interface RepairInstallTargetProps {
|
||||
fillColor: string;
|
||||
isReadyToInstall: boolean;
|
||||
label: string;
|
||||
ringColor: string;
|
||||
onRepair: () => void;
|
||||
}
|
||||
|
||||
interface RepairPlaceholderMarkersProps {
|
||||
positions: readonly Vector3Tuple[];
|
||||
}
|
||||
|
||||
export function RepairRepairingStep({
|
||||
brokenParts,
|
||||
config,
|
||||
@@ -82,6 +94,13 @@ export function RepairRepairingStep({
|
||||
: hasWrongPartPlaced
|
||||
? "#fecaca"
|
||||
: "#fed7aa";
|
||||
const installLabel = isReadyToInstall
|
||||
? `Installer ${requiredReplacementLabel}`
|
||||
: hasWrongPartPlaced
|
||||
? `Mauvaise piece`
|
||||
: hasCorrectPartPlaced
|
||||
? `Ranger piece cassee`
|
||||
: `Approcher ${requiredReplacementLabel}`;
|
||||
|
||||
function handleReplacementPosition(
|
||||
partId: string,
|
||||
@@ -126,48 +145,15 @@ export function RepairRepairingStep({
|
||||
|
||||
return (
|
||||
<group>
|
||||
<TriggerObject
|
||||
position={INSTALL_TARGET_POSITION}
|
||||
colliders="ball"
|
||||
label={
|
||||
isReadyToInstall
|
||||
? `Installer ${requiredReplacementLabel}`
|
||||
: hasWrongPartPlaced
|
||||
? `Mauvaise piece`
|
||||
: hasCorrectPartPlaced
|
||||
? `Ranger piece cassee`
|
||||
: `Approcher ${requiredReplacementLabel}`
|
||||
}
|
||||
onTrigger={() => {
|
||||
if (!isReadyToInstall) return;
|
||||
|
||||
onRepair();
|
||||
}}
|
||||
>
|
||||
<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}
|
||||
<RepairInstallTarget
|
||||
fillColor={installFillColor}
|
||||
isReadyToInstall={isReadyToInstall}
|
||||
label={installLabel}
|
||||
ringColor={installColor}
|
||||
onRepair={onRepair}
|
||||
/>
|
||||
</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>
|
||||
))}
|
||||
<RepairPlaceholderMarkers positions={placeholderPositions} />
|
||||
|
||||
{replacementParts.map((part, index) => {
|
||||
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(
|
||||
placeholders: readonly RepairCasePlaceholder[],
|
||||
): readonly RepairCasePlaceholder[] {
|
||||
@@ -258,7 +293,7 @@ function getPlaceholderTargets(
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
return REPLACEMENT_START_OFFSETS.map(
|
||||
return FALLBACK_PLACEHOLDER_OFFSETS.map(
|
||||
(offset, index): RepairCasePlaceholder => ({
|
||||
name: `placeholder_${index + 1}`,
|
||||
position: [
|
||||
|
||||
@@ -13,6 +13,8 @@ interface ModelErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
modelPath: string;
|
||||
position?: Vector3Tuple | undefined;
|
||||
rotation?: Vector3Tuple | undefined;
|
||||
scale?: ModelTransformProps["scale"] | undefined;
|
||||
}
|
||||
|
||||
interface ModelErrorBoundaryState {
|
||||
@@ -38,6 +40,8 @@ class ModelErrorBoundary extends Component<
|
||||
modelPath: this.props.modelPath,
|
||||
scope: "ExplodableModel",
|
||||
position: this.props.position,
|
||||
rotation: this.props.rotation,
|
||||
scale: this.props.scale,
|
||||
},
|
||||
error,
|
||||
);
|
||||
@@ -45,7 +49,13 @@ class ModelErrorBoundary extends Component<
|
||||
|
||||
render(): ReactNode {
|
||||
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;
|
||||
@@ -67,6 +77,8 @@ export function ExplodableModel(
|
||||
key={props.modelPath}
|
||||
modelPath={props.modelPath}
|
||||
position={props.position}
|
||||
rotation={props.rotation}
|
||||
scale={props.scale}
|
||||
>
|
||||
<ExplodableModelInner {...props} />
|
||||
</ModelErrorBoundary>
|
||||
@@ -116,11 +128,15 @@ function ExplodableModelInner({
|
||||
|
||||
function MissingModelFallback({
|
||||
position = [0, 0, 0],
|
||||
rotation = [0, 0, 0],
|
||||
scale = 1,
|
||||
}: {
|
||||
position?: Vector3Tuple | undefined;
|
||||
rotation?: Vector3Tuple | undefined;
|
||||
scale?: ModelTransformProps["scale"] | undefined;
|
||||
}): React.JSX.Element {
|
||||
return (
|
||||
<mesh position={position}>
|
||||
<mesh position={position} rotation={rotation} scale={toVector3Scale(scale)}>
|
||||
<boxGeometry args={[0.7, 0.7, 0.7]} />
|
||||
<meshStandardMaterial color="#7f1d1d" wireframe />
|
||||
</mesh>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { RotateCcw, StepBack, StepForward } from "lucide-react";
|
||||
import {
|
||||
type MainGameState,
|
||||
type MissionStep,
|
||||
useGameStore,
|
||||
} from "@/managers/stores/useGameStore";
|
||||
import { isMissionStep, MISSION_STEPS } from "@/types/gameplay/repairMission";
|
||||
|
||||
const MAIN_STATES: MainGameState[] = [
|
||||
"intro",
|
||||
@@ -13,17 +13,6 @@ const MAIN_STATES: MainGameState[] = [
|
||||
"outro",
|
||||
];
|
||||
|
||||
const MISSION_STEPS: MissionStep[] = [
|
||||
"locked",
|
||||
"waiting",
|
||||
"inspected",
|
||||
"fragmented",
|
||||
"scanning",
|
||||
"repairing",
|
||||
"reassembling",
|
||||
"done",
|
||||
];
|
||||
|
||||
function toPascalCase(value: string): string {
|
||||
return value
|
||||
.split(/[-_\s]+/)
|
||||
@@ -71,22 +60,27 @@ export function GameStateDebugPanel(): React.JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "outro") {
|
||||
setOutroState({ hasStarted: nextSubState === "started" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isMissionStep(nextSubState)) return;
|
||||
|
||||
if (mainState === "bike") {
|
||||
setBikeState({ currentStep: nextSubState as MissionStep });
|
||||
setBikeState({ currentStep: nextSubState });
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "pylone") {
|
||||
setPyloneState({ currentStep: nextSubState as MissionStep });
|
||||
setPyloneState({ currentStep: nextSubState });
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainState === "ferme") {
|
||||
setFermeState({ currentStep: nextSubState as MissionStep });
|
||||
setFermeState({ currentStep: nextSubState });
|
||||
return;
|
||||
}
|
||||
|
||||
setOutroState({ hasStarted: nextSubState === "started" });
|
||||
}
|
||||
|
||||
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\` 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 :
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
export interface RepairMissionCaseConfig {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type {
|
||||
MissionStep,
|
||||
RepairMissionId,
|
||||
} from "@/managers/stores/useGameStore";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
} from "@/types/gameplay/repairMission";
|
||||
|
||||
export function useRepairMissionStep(mission: RepairMissionId): MissionStep {
|
||||
return useGameStore((state) => state[mission].currentStep);
|
||||
|
||||
@@ -20,7 +20,7 @@ export function getCameraStreamWithTimeout(
|
||||
didTimeout = true;
|
||||
reject(
|
||||
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);
|
||||
|
||||
@@ -125,7 +125,22 @@ export class AudioManager {
|
||||
|
||||
this._musicUnlockHandler = () => {
|
||||
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, {
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { create } from "zustand";
|
||||
import {
|
||||
isRepairMissionId,
|
||||
type MissionStep,
|
||||
type RepairMissionId,
|
||||
} from "@/types/gameplay/repairMission";
|
||||
|
||||
export type MainGameState = "intro" | "bike" | "pylone" | "ferme" | "outro";
|
||||
export type RepairMissionId = "bike" | "pylone" | "ferme";
|
||||
export type MissionStep =
|
||||
| "locked"
|
||||
| "waiting"
|
||||
| "inspected"
|
||||
| "fragmented"
|
||||
| "scanning"
|
||||
| "repairing"
|
||||
| "reassembling"
|
||||
| "done";
|
||||
export type { MissionStep, RepairMissionId };
|
||||
|
||||
interface IntroState {
|
||||
dialogueAudio: string | null;
|
||||
@@ -63,12 +59,6 @@ interface GameActions {
|
||||
type GameStore = GameState & GameActions;
|
||||
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 {
|
||||
switch (step) {
|
||||
case "locked":
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { useBrowserHandTracking } from "@/hooks/handTracking/useBrowserHandTracking";
|
||||
import { useRemoteHandTracking } from "@/hooks/handTracking/useRemoteHandTracking";
|
||||
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>([
|
||||
"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");
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
for (const [path, file] of fileMap.entries()) {
|
||||
|
||||
@@ -13,7 +13,8 @@ export async function loadMapSceneData(): Promise<SceneData | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mapNodes = parseMapNodes(await response.json());
|
||||
const mapPayload: unknown = await response.json();
|
||||
const mapNodes = parseMapNodes(mapPayload);
|
||||
return createSceneData(mapNodes);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ const saveMapPlugin = (): Plugin => ({
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(Buffer.concat(chunks).toString());
|
||||
const data: unknown = JSON.parse(Buffer.concat(chunks).toString());
|
||||
try {
|
||||
parseMapNodes(data);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user