Merge remote map editor updates
This commit is contained in:
+37
-38
@@ -21,18 +21,22 @@ import {
|
||||
isMapModelVisible,
|
||||
useMapPerformanceStore,
|
||||
} from "@/managers/stores/useMapPerformanceStore";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import { GameMapCollision } from "@/world/GameMapCollision";
|
||||
import { CloudSystem } from "@/world/clouds/CloudSystem";
|
||||
import { GeneratedMapNodeInstance } from "@/world/map-generated/GeneratedMapNodeInstance";
|
||||
import { isGeneratedMapModelName } from "@/world/map-generated/generatedMapModelConfig";
|
||||
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
|
||||
import { isInstancedMapNodeName } from "@/world/map-instancing/mapInstancingConfig";
|
||||
import { VegetationSystem } from "@/world/vegetation/VegetationSystem";
|
||||
import { WaterSystem } from "@/world/water/WaterSystem";
|
||||
import { WorldPlane } from "@/world/WorldPlane";
|
||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
import { loadMapSceneData } from "@/utils/map/loadMapSceneData";
|
||||
import {
|
||||
getTerrainMapNode,
|
||||
isRuntimeSingleMapNode,
|
||||
} from "@/utils/map/mapRuntimeClassification";
|
||||
import { logModelLoadError } from "@/utils/three/modelLoadLogger";
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import type { OctreeReadyHandler } from "@/types/three/three";
|
||||
@@ -42,16 +46,6 @@ interface LoadedMapNode {
|
||||
modelUrl: string | null;
|
||||
}
|
||||
|
||||
const MAP_STRUCTURE_NODE_NAMES = new Set(["Scene", "blocking", "terrain"]);
|
||||
const LITE_MAP_SKIPPED_NODE_NAMES = new Set([
|
||||
"arbre",
|
||||
"buisson",
|
||||
"champdeble",
|
||||
"champdesoja",
|
||||
"champsdetournesol",
|
||||
"sapin",
|
||||
]);
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback: ReactNode;
|
||||
@@ -120,9 +114,10 @@ export function GameMap({
|
||||
const [collisionMapNodes, setCollisionMapNodes] = useState<LoadedMapNode[]>(
|
||||
[],
|
||||
);
|
||||
const [terrainNode, setTerrainNode] = useState<MapNode | null>(null);
|
||||
const [mapLoaded, setMapLoaded] = useState(false);
|
||||
const [settledMapNodeCount, setSettledMapNodeCount] = useState(0);
|
||||
const mapReady = mapLoaded && settledMapNodeCount >= renderMapNodes.length;
|
||||
const mapReady = mapLoaded;
|
||||
|
||||
const handleMapNodeSettled = useCallback((index: number) => {
|
||||
if (settledMapNodesRef.current.has(index)) return;
|
||||
@@ -135,6 +130,7 @@ export function GameMap({
|
||||
(currentStep: string) => {
|
||||
setRenderMapNodes([]);
|
||||
setCollisionMapNodes([]);
|
||||
setTerrainNode(null);
|
||||
setMapLoaded(true);
|
||||
settledMapNodesRef.current.clear();
|
||||
setSettledMapNodeCount(0);
|
||||
@@ -169,7 +165,9 @@ export function GameMap({
|
||||
status: "loading",
|
||||
});
|
||||
|
||||
const visibleMapNodes = sceneData.mapNodes.filter(liteMap);
|
||||
const visibleMapNodes = sceneData.mapNodes.filter(
|
||||
isRuntimeSingleMapNode,
|
||||
);
|
||||
const skippedMapNodeCount =
|
||||
sceneData.mapNodes.length - visibleMapNodes.length;
|
||||
|
||||
@@ -189,6 +187,7 @@ export function GameMap({
|
||||
const modelUrl = sceneData.models.get(node.name);
|
||||
return { node, modelUrl: modelUrl ?? null };
|
||||
});
|
||||
const loadedTerrainNode = getTerrainMapNode(sceneData.mapNodes);
|
||||
const missingModelCount = loadedMapNodes.filter(
|
||||
(mapNode) => mapNode.modelUrl === null,
|
||||
).length;
|
||||
@@ -205,6 +204,7 @@ export function GameMap({
|
||||
|
||||
setRenderMapNodes(loadedMapNodes);
|
||||
setCollisionMapNodes(loadedCollisionNodes);
|
||||
setTerrainNode(loadedTerrainNode);
|
||||
setMapLoaded(true);
|
||||
settledMapNodesRef.current.clear();
|
||||
setSettledMapNodeCount(0);
|
||||
@@ -267,7 +267,15 @@ export function GameMap({
|
||||
<CloudSystem />
|
||||
<VegetationSystem />
|
||||
{isMapModelVisible("terrain", { groups, models }) ? (
|
||||
<TerrainModel />
|
||||
terrainNode ? (
|
||||
<TerrainModel
|
||||
position={terrainNode.position}
|
||||
rotation={terrainNode.rotation}
|
||||
scale={terrainNode.scale}
|
||||
/>
|
||||
) : (
|
||||
<TerrainModel />
|
||||
)
|
||||
) : null}
|
||||
<GameMapCollision
|
||||
buildOctree={buildOctree}
|
||||
@@ -289,29 +297,6 @@ function HiddenMapNode({ onSettled }: { onSettled: () => void }): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary development-only map reducer.
|
||||
*
|
||||
* TODO: replace this with a real map performance pass: merged static geometry,
|
||||
* instancing for repeated props, LOD, and/or zone-based loading. For now this
|
||||
* keeps the app usable on local machines by not rendering the densest exported
|
||||
* nodes from map.json.
|
||||
*/
|
||||
function liteMap(node: MapNode): boolean {
|
||||
if (MAP_STRUCTURE_NODE_NAMES.has(node.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.type === "Mesh") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
!LITE_MAP_SKIPPED_NODE_NAMES.has(node.name) &&
|
||||
!isInstancedMapNodeName(node.name)
|
||||
);
|
||||
}
|
||||
|
||||
function MapNodeInstance({
|
||||
node,
|
||||
modelUrl,
|
||||
@@ -320,8 +305,12 @@ function MapNodeInstance({
|
||||
node: MapNode;
|
||||
modelUrl: string | null;
|
||||
onSettled: () => void;
|
||||
}): React.JSX.Element {
|
||||
}): React.JSX.Element | null {
|
||||
const isGeneratedModel = isGeneratedMapModelName(node.name);
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const bikeStep = useGameStore((state) => state.bike.currentStep);
|
||||
const hideEbikeMapModel =
|
||||
node.name === "ebike" && mainState === "bike" && bikeStep !== "locked";
|
||||
|
||||
useEffect(() => {
|
||||
if (modelUrl !== null || isGeneratedModel) return;
|
||||
@@ -329,6 +318,16 @@ function MapNodeInstance({
|
||||
onSettled();
|
||||
}, [isGeneratedModel, modelUrl, onSettled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hideEbikeMapModel) return;
|
||||
|
||||
onSettled();
|
||||
}, [hideEbikeMapModel, onSettled]);
|
||||
|
||||
if (hideEbikeMapModel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isGeneratedModel) {
|
||||
return (
|
||||
<Suspense fallback={<FallbackMapNode node={node} />}>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
||||
import { RepairGame } from "@/components/three/gameplay/RepairGame";
|
||||
import { EBIKE_REPAIR_POSITION } from "@/data/gameplay/repairMissionAnchors";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
@@ -17,7 +19,7 @@ interface GameRepairZone {
|
||||
const GAME_REPAIR_ZONES = [
|
||||
{
|
||||
mission: "bike",
|
||||
position: [8, 0, -6],
|
||||
position: EBIKE_REPAIR_POSITION,
|
||||
},
|
||||
{
|
||||
mission: "pylone",
|
||||
@@ -48,6 +50,31 @@ function StageAnchor({
|
||||
);
|
||||
}
|
||||
|
||||
function EbikeMissionTrigger(): React.JSX.Element | null {
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const bikeStep = useGameStore((state) => state.bike.currentStep);
|
||||
const setMissionStep = useGameStore((state) => state.setMissionStep);
|
||||
|
||||
if (mainState !== "bike" || bikeStep !== "locked") return null;
|
||||
|
||||
return (
|
||||
<group position={EBIKE_REPAIR_POSITION}>
|
||||
<InteractableObject
|
||||
kind="trigger"
|
||||
label="Réparer l'e-bike"
|
||||
position={EBIKE_REPAIR_POSITION}
|
||||
radius={4}
|
||||
onPress={() => setMissionStep("bike", "waiting")}
|
||||
>
|
||||
<mesh>
|
||||
<sphereGeometry args={[1.3, 16, 16]} />
|
||||
<meshBasicMaterial transparent opacity={0} depthWrite={false} />
|
||||
</mesh>
|
||||
</InteractableObject>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export function GameStageContent(): React.JSX.Element {
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
|
||||
@@ -63,6 +90,7 @@ export function GameStageContent(): React.JSX.Element {
|
||||
position={zone.position}
|
||||
/>
|
||||
))}
|
||||
<EbikeMissionTrigger />
|
||||
{mainState === "outro" ? (
|
||||
<StageAnchor color="#fb7185" position={[0, 6, 10]} scale={1.25} />
|
||||
) : null}
|
||||
|
||||
@@ -62,3 +62,11 @@ export const INSTANCED_MAP_EXCEPTIONS = new Set([
|
||||
"blocking",
|
||||
"terrain",
|
||||
]);
|
||||
|
||||
export function getVegetationScaleMultiplier(name: string): number | null {
|
||||
const config = Object.values(VEGETATION_TYPES).find(
|
||||
(vegetationConfig) => vegetationConfig.mapName === name,
|
||||
);
|
||||
|
||||
return config?.scaleMultiplier ?? null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user