feat: ancre les réparations sur la map chargée

This commit is contained in:
tom-boullay
2026-05-28 15:47:53 +02:00
parent ba50224e6e
commit 9bbed06ddc
10 changed files with 150 additions and 19 deletions
+22 -2
View File
@@ -22,9 +22,11 @@ import {
useMapPerformanceStore,
} from "@/managers/stores/useMapPerformanceStore";
import { useGameStore } from "@/managers/stores/useGameStore";
import { useRepairMissionAnchorStore } from "@/managers/stores/useRepairMissionAnchorStore";
import { GameMapCollision } from "@/world/GameMapCollision";
import { GeneratedMapNodeInstance } from "@/world/map-generated/GeneratedMapNodeInstance";
import { isGeneratedMapModelName } from "@/data/world/generatedMapModelConfig";
import { getMapSingleModelScaleMultiplier } from "@/data/world/mapInstancingConfig";
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
import { logger } from "@/utils/core/Logger";
@@ -35,6 +37,7 @@ import {
isRuntimeSingleMapNode,
} from "@/utils/map/mapRuntimeClassification";
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
import { getRepairMissionMapAnchors } from "@/utils/map/repairMissionMapAnchors";
import type { MapNode } from "@/types/map/mapScene";
import type { OctreeReadyHandler } from "@/types/three/three";
@@ -114,6 +117,9 @@ export function GameMap({
const [terrainNode, setTerrainNode] = useState<MapNode | null>(null);
const [mapLoaded, setMapLoaded] = useState(false);
const [settledMapNodeCount, setSettledMapNodeCount] = useState(0);
const setRepairMissionAnchors = useRepairMissionAnchorStore(
(state) => state.setAnchors,
);
const mapReady = mapLoaded;
const handleMapNodeSettled = useCallback((index: number) => {
@@ -185,6 +191,9 @@ export function GameMap({
return { node, modelUrl: modelUrl ?? null };
});
const loadedTerrainNode = getTerrainMapNode(sceneData.mapNodes);
const repairMissionAnchors = getRepairMissionMapAnchors(
sceneData.mapNodes,
);
const missingModelCount = loadedMapNodes.filter(
(mapNode) => mapNode.modelUrl === null,
).length;
@@ -202,6 +211,7 @@ export function GameMap({
setRenderMapNodes(loadedMapNodes);
setCollisionMapNodes(loadedCollisionNodes);
setTerrainNode(loadedTerrainNode);
setRepairMissionAnchors(repairMissionAnchors);
setMapLoaded(true);
settledMapNodesRef.current.clear();
setSettledMapNodeCount(0);
@@ -219,7 +229,7 @@ export function GameMap({
};
loadMap();
}, [onLoadingStateChange, showEmptyMap]);
}, [onLoadingStateChange, setRepairMissionAnchors, showEmptyMap]);
useEffect(() => {
if (renderMapNodes.length === 0) return;
@@ -350,7 +360,17 @@ function ModelInstance({
onLoaded: () => void;
}): React.JSX.Element {
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 { scene } = useLoggedGLTF(modelUrl, {
scope: "GameMap.ModelInstance",
+27 -7
View File
@@ -3,11 +3,27 @@ import { RepairGame } from "@/components/three/gameplay/RepairGame";
import {
REPAIR_MISSION_POSITION_ENTRIES,
REPAIR_MISSION_TRIGGERS,
type RepairMissionTriggerConfig,
} from "@/data/gameplay/repairMissionAnchors";
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";
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 {
color: string;
position: Vector3Tuple;
@@ -42,10 +58,9 @@ function RepairMissionTrigger({
const missionStep = useGameStore(
(state) => state[config.mission].currentStep,
);
const anchors = useRepairMissionAnchorStore((state) => state.anchors);
const setMissionStep = useGameStore((state) => state.setMissionStep);
const position = REPAIR_MISSION_POSITION_ENTRIES.find(
(entry) => entry.mission === config.mission,
)?.position;
const position = getRepairMissionPosition(config.mission, anchors);
if (!position) return null;
if (mainState !== config.mission || missionStep !== "locked") return null;
@@ -70,15 +85,20 @@ function RepairMissionTrigger({
export function GameStageContent(): React.JSX.Element {
const mainState = useGameStore((state) => state.mainState);
const anchors = useRepairMissionAnchorStore((state) => state.anchors);
return (
<>
{mainState === "intro" ? (
<StageAnchor color="#7dd3fc" position={[0, 4, 0]} />
) : null}
{REPAIR_MISSION_POSITION_ENTRIES.map(({ mission, position }) => (
<RepairGame key={mission} mission={mission} position={position} />
))}
{REPAIR_MISSION_POSITION_ENTRIES.map(({ mission }) => {
const position = getRepairMissionPosition(mission, anchors);
if (!position) return null;
return (
<RepairGame key={mission} mission={mission} position={position} />
);
})}
{REPAIR_MISSION_TRIGGERS.map((config) => (
<RepairMissionTrigger key={config.mission} config={config} />
))}