Feat/map-environment #6
@@ -37,7 +37,7 @@ Mission progression is not owned by a manager. Components update the store throu
|
|||||||
|
|
||||||
- `src/components/game/GameFlow.tsx` reacts to `missionFlow.step` and triggers one-off side effects such as intro audio and movement unlocks.
|
- `src/components/game/GameFlow.tsx` reacts to `missionFlow.step` and triggers one-off side effects such as intro audio and movement unlocks.
|
||||||
- `src/components/zone/ZoneDetection.tsx` reads the camera position and moves the flow to a target step when the player enters a configured zone.
|
- `src/components/zone/ZoneDetection.tsx` reads the camera position and moves the flow to a target step when the player enters a configured zone.
|
||||||
- `src/components/three/interaction/CentralObject.tsx` and `VillageoisHelperObject.tsx` expose temporary interactive mission objects.
|
- `src/world/GameStageContent.tsx` mounts repair games and their mission-start triggers.
|
||||||
- `src/pages/page.tsx` mounts mission HTML overlays: `IntroUI`, `BienvenueDisplay`, and `DialogMessage`.
|
- `src/pages/page.tsx` mounts mission HTML overlays: `IntroUI`, `BienvenueDisplay`, and `DialogMessage`.
|
||||||
- `src/world/player/PlayerController.tsx` reads `missionFlow.canMove` as an additional movement lock.
|
- `src/world/player/PlayerController.tsx` reads `missionFlow.canMove` as an additional movement lock.
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ The repair case appears near the mission object. The player can:
|
|||||||
|
|
||||||
Both paths move to `fragmented`.
|
Both paths move to `fragmented`.
|
||||||
|
|
||||||
Important current detail: `useRepairMovementLocked()` currently returns `false`, so the movement-lock rule and indicator are present but disabled in the current branch.
|
`useRepairMovementLocked()` locks player movement during focused repair steps and drives the repair movement indicator.
|
||||||
|
|
||||||
### Fragmented
|
### Fragmented
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
import type {
|
||||||
|
RepairMissionId,
|
||||||
export interface RepairMissionTriggerConfig {
|
RepairMissionTriggerConfig,
|
||||||
mission: RepairMissionId;
|
} from "@/types/gameplay/repairMission";
|
||||||
label: string;
|
|
||||||
radius: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EBIKE_REPAIR_POSITION = [
|
export const EBIKE_REPAIR_POSITION = [
|
||||||
42.2399, 4.5484, 34.6468,
|
42.2399, 4.5484, 34.6468,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
|
|||||||
description:
|
description:
|
||||||
"Repair the damaged cooling module before relaunching the bike",
|
"Repair the damaged cooling module before relaunching the bike",
|
||||||
modelPath: "/models/ebike/model.gltf",
|
modelPath: "/models/ebike/model.gltf",
|
||||||
modelScale: 0.5,
|
modelScale: 0.3,
|
||||||
stageUiPath: "/assets/UI/ebike.webm",
|
stageUiPath: "/assets/UI/ebike.webm",
|
||||||
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
||||||
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
boiteauxlettres: {
|
boiteauxlettres: {
|
||||||
mapName: "boiteauxlettres",
|
mapName: "boiteauxlettres",
|
||||||
modelPath: "/models/boiteauxlettres/model.gltf",
|
modelPath: "/models/boiteauxlettres/model.gltf",
|
||||||
|
scaleMultiplier: 2,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -9,6 +10,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
pylone: {
|
pylone: {
|
||||||
mapName: "pylone",
|
mapName: "pylone",
|
||||||
modelPath: "/models/pylone/model.gltf",
|
modelPath: "/models/pylone/model.gltf",
|
||||||
|
scaleMultiplier: 1,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -16,6 +18,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
immeuble1: {
|
immeuble1: {
|
||||||
mapName: "immeuble1",
|
mapName: "immeuble1",
|
||||||
modelPath: "/models/immeuble1/model.gltf",
|
modelPath: "/models/immeuble1/model.gltf",
|
||||||
|
scaleMultiplier: 1,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -23,6 +26,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
maison1: {
|
maison1: {
|
||||||
mapName: "maison1",
|
mapName: "maison1",
|
||||||
modelPath: "/models/maison1/model.gltf",
|
modelPath: "/models/maison1/model.gltf",
|
||||||
|
scaleMultiplier: 3,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -30,6 +34,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
eolienne: {
|
eolienne: {
|
||||||
mapName: "eolienne",
|
mapName: "eolienne",
|
||||||
modelPath: "/models/eolienne/model.gltf",
|
modelPath: "/models/eolienne/model.gltf",
|
||||||
|
scaleMultiplier: 0.85,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -37,6 +42,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
parcebike: {
|
parcebike: {
|
||||||
mapName: "parcebike",
|
mapName: "parcebike",
|
||||||
modelPath: "/models/parcebike/model.gltf",
|
modelPath: "/models/parcebike/model.gltf",
|
||||||
|
scaleMultiplier: 2,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -44,6 +50,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
panneauaffichage: {
|
panneauaffichage: {
|
||||||
mapName: "panneauaffichage",
|
mapName: "panneauaffichage",
|
||||||
modelPath: "/models/panneauaffichage/model.gltf",
|
modelPath: "/models/panneauaffichage/model.gltf",
|
||||||
|
scaleMultiplier: 1,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -51,6 +58,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
panneauclassique: {
|
panneauclassique: {
|
||||||
mapName: "panneauclassique",
|
mapName: "panneauclassique",
|
||||||
modelPath: "/models/panneauclassique/model.gltf",
|
modelPath: "/models/panneauclassique/model.gltf",
|
||||||
|
scaleMultiplier: 1,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -58,6 +66,7 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
panneaufleche: {
|
panneaufleche: {
|
||||||
mapName: "panneaufleche",
|
mapName: "panneaufleche",
|
||||||
modelPath: "/models/panneaufleche/model.gltf",
|
modelPath: "/models/panneaufleche/model.gltf",
|
||||||
|
scaleMultiplier: 1,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -65,12 +74,40 @@ export const MAP_INSTANCING_ASSETS = {
|
|||||||
panneausolaire: {
|
panneausolaire: {
|
||||||
mapName: "panneausolaire",
|
mapName: "panneausolaire",
|
||||||
modelPath: "/models/panneausolaire/model.gltf",
|
modelPath: "/models/panneausolaire/model.gltf",
|
||||||
|
scaleMultiplier: 0.85,
|
||||||
castShadow: true,
|
castShadow: true,
|
||||||
receiveShadow: true,
|
receiveShadow: true,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const MAP_SINGLE_MODEL_SCALE_MULTIPLIERS = {
|
||||||
|
ebike: 0.3,
|
||||||
|
} as const satisfies Record<string, number>;
|
||||||
|
|
||||||
|
export function getMapSingleModelScaleMultiplier(name: string): number {
|
||||||
|
return (
|
||||||
|
MAP_SINGLE_MODEL_SCALE_MULTIPLIERS[
|
||||||
|
name as keyof typeof MAP_SINGLE_MODEL_SCALE_MULTIPLIERS
|
||||||
|
] ?? 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMapInstancedModelScaleMultiplier(name: string): number {
|
||||||
|
return (
|
||||||
|
Object.values(MAP_INSTANCING_ASSETS).find(
|
||||||
|
(config) => config.mapName === name,
|
||||||
|
)?.scaleMultiplier ?? 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMapModelScaleMultiplier(name: string): number {
|
||||||
|
return (
|
||||||
|
getMapSingleModelScaleMultiplier(name) *
|
||||||
|
getMapInstancedModelScaleMultiplier(name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const MAP_INSTANCING_ASSET_TYPES = [
|
export const MAP_INSTANCING_ASSET_TYPES = [
|
||||||
"boiteauxlettres",
|
"boiteauxlettres",
|
||||||
"pylone",
|
"pylone",
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
|
interface RepairMissionAnchorStore {
|
||||||
|
anchors: Partial<Record<RepairMissionId, Vector3Tuple>>;
|
||||||
|
setAnchors: (anchors: Partial<Record<RepairMissionId, Vector3Tuple>>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRepairMissionAnchorStore = create<RepairMissionAnchorStore>(
|
||||||
|
(set) => ({
|
||||||
|
anchors: {},
|
||||||
|
setAnchors: (anchors) => set({ anchors }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -6,6 +6,12 @@ import type {
|
|||||||
|
|
||||||
export type RepairMissionId = "ebike" | "pylon" | "farm";
|
export type RepairMissionId = "ebike" | "pylon" | "farm";
|
||||||
|
|
||||||
|
export interface RepairMissionTriggerConfig {
|
||||||
|
mission: RepairMissionId;
|
||||||
|
label: string;
|
||||||
|
radius: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RepairMissionCaseConfig {
|
export interface RepairMissionCaseConfig {
|
||||||
position: Vector3Tuple;
|
position: Vector3Tuple;
|
||||||
rotation: Vector3Tuple;
|
rotation: Vector3Tuple;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
|
import type { MapNode } from "@/types/map/mapScene";
|
||||||
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
|
const REPAIR_MISSION_MAP_NODE_NAMES = {
|
||||||
|
ebike: "ebike",
|
||||||
|
pylon: "pylone",
|
||||||
|
farm: "fermeverticale",
|
||||||
|
} as const satisfies Record<RepairMissionId, string>;
|
||||||
|
|
||||||
|
function isOriginPosition(position: Vector3Tuple): boolean {
|
||||||
|
return position.every((value) => Math.abs(value) < 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRepairMissionMapAnchors(
|
||||||
|
mapNodes: readonly MapNode[],
|
||||||
|
): Partial<Record<RepairMissionId, Vector3Tuple>> {
|
||||||
|
const anchors: Partial<Record<RepairMissionId, Vector3Tuple>> = {};
|
||||||
|
|
||||||
|
for (const [mission, mapName] of Object.entries(
|
||||||
|
REPAIR_MISSION_MAP_NODE_NAMES,
|
||||||
|
) as [RepairMissionId, string][]) {
|
||||||
|
const node = mapNodes.find(
|
||||||
|
(candidate) =>
|
||||||
|
candidate.name === mapName &&
|
||||||
|
candidate.type === "Object3D" &&
|
||||||
|
!isOriginPosition(candidate.position),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
anchors[mission] = node.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchors;
|
||||||
|
}
|
||||||
+22
-2
@@ -22,9 +22,11 @@ import {
|
|||||||
useMapPerformanceStore,
|
useMapPerformanceStore,
|
||||||
} from "@/managers/stores/useMapPerformanceStore";
|
} from "@/managers/stores/useMapPerformanceStore";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
|
import { useRepairMissionAnchorStore } from "@/managers/stores/useRepairMissionAnchorStore";
|
||||||
import { GameMapCollision } from "@/world/GameMapCollision";
|
import { GameMapCollision } from "@/world/GameMapCollision";
|
||||||
import { GeneratedMapNodeInstance } from "@/world/map-generated/GeneratedMapNodeInstance";
|
import { GeneratedMapNodeInstance } from "@/world/map-generated/GeneratedMapNodeInstance";
|
||||||
import { isGeneratedMapModelName } from "@/data/world/generatedMapModelConfig";
|
import { isGeneratedMapModelName } from "@/data/world/generatedMapModelConfig";
|
||||||
|
import { getMapSingleModelScaleMultiplier } from "@/data/world/mapInstancingConfig";
|
||||||
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
|
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
|
||||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||||
import { logger } from "@/utils/core/Logger";
|
import { logger } from "@/utils/core/Logger";
|
||||||
@@ -35,6 +37,7 @@ import {
|
|||||||
isRuntimeSingleMapNode,
|
isRuntimeSingleMapNode,
|
||||||
} from "@/utils/map/mapRuntimeClassification";
|
} from "@/utils/map/mapRuntimeClassification";
|
||||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||||
|
import { getRepairMissionMapAnchors } from "@/utils/map/repairMissionMapAnchors";
|
||||||
import type { MapNode } from "@/types/map/mapScene";
|
import type { MapNode } from "@/types/map/mapScene";
|
||||||
import type { OctreeReadyHandler } from "@/types/three/three";
|
import type { OctreeReadyHandler } from "@/types/three/three";
|
||||||
|
|
||||||
@@ -114,6 +117,9 @@ export function GameMap({
|
|||||||
const [terrainNode, setTerrainNode] = useState<MapNode | null>(null);
|
const [terrainNode, setTerrainNode] = useState<MapNode | null>(null);
|
||||||
const [mapLoaded, setMapLoaded] = useState(false);
|
const [mapLoaded, setMapLoaded] = useState(false);
|
||||||
const [settledMapNodeCount, setSettledMapNodeCount] = useState(0);
|
const [settledMapNodeCount, setSettledMapNodeCount] = useState(0);
|
||||||
|
const setRepairMissionAnchors = useRepairMissionAnchorStore(
|
||||||
|
(state) => state.setAnchors,
|
||||||
|
);
|
||||||
const mapReady = mapLoaded;
|
const mapReady = mapLoaded;
|
||||||
|
|
||||||
const handleMapNodeSettled = useCallback((index: number) => {
|
const handleMapNodeSettled = useCallback((index: number) => {
|
||||||
@@ -185,6 +191,9 @@ export function GameMap({
|
|||||||
return { node, modelUrl: modelUrl ?? null };
|
return { node, modelUrl: modelUrl ?? null };
|
||||||
});
|
});
|
||||||
const loadedTerrainNode = getTerrainMapNode(sceneData.mapNodes);
|
const loadedTerrainNode = getTerrainMapNode(sceneData.mapNodes);
|
||||||
|
const repairMissionAnchors = getRepairMissionMapAnchors(
|
||||||
|
sceneData.mapNodes,
|
||||||
|
);
|
||||||
const missingModelCount = loadedMapNodes.filter(
|
const missingModelCount = loadedMapNodes.filter(
|
||||||
(mapNode) => mapNode.modelUrl === null,
|
(mapNode) => mapNode.modelUrl === null,
|
||||||
).length;
|
).length;
|
||||||
@@ -202,6 +211,7 @@ export function GameMap({
|
|||||||
setRenderMapNodes(loadedMapNodes);
|
setRenderMapNodes(loadedMapNodes);
|
||||||
setCollisionMapNodes(loadedCollisionNodes);
|
setCollisionMapNodes(loadedCollisionNodes);
|
||||||
setTerrainNode(loadedTerrainNode);
|
setTerrainNode(loadedTerrainNode);
|
||||||
|
setRepairMissionAnchors(repairMissionAnchors);
|
||||||
setMapLoaded(true);
|
setMapLoaded(true);
|
||||||
settledMapNodesRef.current.clear();
|
settledMapNodesRef.current.clear();
|
||||||
setSettledMapNodeCount(0);
|
setSettledMapNodeCount(0);
|
||||||
@@ -219,7 +229,7 @@ export function GameMap({
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadMap();
|
loadMap();
|
||||||
}, [onLoadingStateChange, showEmptyMap]);
|
}, [onLoadingStateChange, setRepairMissionAnchors, showEmptyMap]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (renderMapNodes.length === 0) return;
|
if (renderMapNodes.length === 0) return;
|
||||||
@@ -350,7 +360,17 @@ function ModelInstance({
|
|||||||
onLoaded: () => void;
|
onLoaded: () => void;
|
||||||
}): React.JSX.Element {
|
}): React.JSX.Element {
|
||||||
const { position, rotation, scale } = node;
|
const { position, rotation, scale } = node;
|
||||||
const normalizedScale = normalizeMapScale(scale);
|
const scaleMultiplier = getMapSingleModelScaleMultiplier(node.name);
|
||||||
|
const baseScale = normalizeMapScale(scale);
|
||||||
|
const normalizedScale = useMemo(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
baseScale[0] * scaleMultiplier,
|
||||||
|
baseScale[1] * scaleMultiplier,
|
||||||
|
baseScale[2] * scaleMultiplier,
|
||||||
|
] satisfies [number, number, number],
|
||||||
|
[baseScale, scaleMultiplier],
|
||||||
|
);
|
||||||
const terrainHeight = useTerrainHeightSampler();
|
const terrainHeight = useTerrainHeightSampler();
|
||||||
const { scene } = useLoggedGLTF(modelUrl, {
|
const { scene } = useLoggedGLTF(modelUrl, {
|
||||||
scope: "GameMap.ModelInstance",
|
scope: "GameMap.ModelInstance",
|
||||||
|
|||||||
@@ -3,11 +3,27 @@ import { RepairGame } from "@/components/three/gameplay/RepairGame";
|
|||||||
import {
|
import {
|
||||||
REPAIR_MISSION_POSITION_ENTRIES,
|
REPAIR_MISSION_POSITION_ENTRIES,
|
||||||
REPAIR_MISSION_TRIGGERS,
|
REPAIR_MISSION_TRIGGERS,
|
||||||
type RepairMissionTriggerConfig,
|
|
||||||
} from "@/data/gameplay/repairMissionAnchors";
|
} from "@/data/gameplay/repairMissionAnchors";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
|
import { useRepairMissionAnchorStore } from "@/managers/stores/useRepairMissionAnchorStore";
|
||||||
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
|
import type { RepairMissionTriggerConfig } from "@/types/gameplay/repairMission";
|
||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
|
const FALLBACK_REPAIR_MISSION_POSITIONS = new Map(
|
||||||
|
REPAIR_MISSION_POSITION_ENTRIES.map(({ mission, position }) => [
|
||||||
|
mission,
|
||||||
|
position,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
function getRepairMissionPosition(
|
||||||
|
mission: RepairMissionId,
|
||||||
|
anchors: Partial<Record<RepairMissionId, Vector3Tuple>>,
|
||||||
|
): Vector3Tuple | undefined {
|
||||||
|
return anchors[mission] ?? FALLBACK_REPAIR_MISSION_POSITIONS.get(mission);
|
||||||
|
}
|
||||||
|
|
||||||
interface StageAnchorProps {
|
interface StageAnchorProps {
|
||||||
color: string;
|
color: string;
|
||||||
position: Vector3Tuple;
|
position: Vector3Tuple;
|
||||||
@@ -42,10 +58,9 @@ function RepairMissionTrigger({
|
|||||||
const missionStep = useGameStore(
|
const missionStep = useGameStore(
|
||||||
(state) => state[config.mission].currentStep,
|
(state) => state[config.mission].currentStep,
|
||||||
);
|
);
|
||||||
|
const anchors = useRepairMissionAnchorStore((state) => state.anchors);
|
||||||
const setMissionStep = useGameStore((state) => state.setMissionStep);
|
const setMissionStep = useGameStore((state) => state.setMissionStep);
|
||||||
const position = REPAIR_MISSION_POSITION_ENTRIES.find(
|
const position = getRepairMissionPosition(config.mission, anchors);
|
||||||
(entry) => entry.mission === config.mission,
|
|
||||||
)?.position;
|
|
||||||
|
|
||||||
if (!position) return null;
|
if (!position) return null;
|
||||||
if (mainState !== config.mission || missionStep !== "locked") return null;
|
if (mainState !== config.mission || missionStep !== "locked") return null;
|
||||||
@@ -70,15 +85,20 @@ function RepairMissionTrigger({
|
|||||||
|
|
||||||
export function GameStageContent(): React.JSX.Element {
|
export function GameStageContent(): React.JSX.Element {
|
||||||
const mainState = useGameStore((state) => state.mainState);
|
const mainState = useGameStore((state) => state.mainState);
|
||||||
|
const anchors = useRepairMissionAnchorStore((state) => state.anchors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{mainState === "intro" ? (
|
{mainState === "intro" ? (
|
||||||
<StageAnchor color="#7dd3fc" position={[0, 4, 0]} />
|
<StageAnchor color="#7dd3fc" position={[0, 4, 0]} />
|
||||||
) : null}
|
) : null}
|
||||||
{REPAIR_MISSION_POSITION_ENTRIES.map(({ mission, position }) => (
|
{REPAIR_MISSION_POSITION_ENTRIES.map(({ mission }) => {
|
||||||
<RepairGame key={mission} mission={mission} position={position} />
|
const position = getRepairMissionPosition(mission, anchors);
|
||||||
))}
|
if (!position) return null;
|
||||||
|
return (
|
||||||
|
<RepairGame key={mission} mission={mission} position={position} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
{REPAIR_MISSION_TRIGGERS.map((config) => (
|
{REPAIR_MISSION_TRIGGERS.map((config) => (
|
||||||
<RepairMissionTrigger key={config.mission} config={config} />
|
<RepairMissionTrigger key={config.mission} config={config} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user