refactor: clean map gameplay architecture

This commit is contained in:
tom-boullay
2026-05-28 11:15:45 +02:00
parent d9cf87d2d6
commit 1a91b1d7ae
69 changed files with 791 additions and 1112 deletions
+9 -1
View File
@@ -8,6 +8,7 @@ export function GameFlow(): null {
const setStep = useGameStore((state) => state.setIntroStep);
const setActivityCity = useGameStore((state) => state.setActivityCity);
const setCanMove = useGameStore((state) => state.setCanMove);
const completeIntro = useGameStore((state) => state.completeIntro);
const hasInitialized = useRef(false);
useEffect(() => {
@@ -55,10 +56,17 @@ export function GameFlow(): null {
if (step === "manipulation") {
setCanMove(false);
const timeoutId = window.setTimeout(() => {
completeIntro();
}, 1000);
return () => {
window.clearTimeout(timeoutId);
};
}
return undefined;
}, [step, setStep, setActivityCity, setCanMove]);
}, [completeIntro, step, setStep, setActivityCity, setCanMove]);
return null;
}
@@ -41,8 +41,27 @@ type InteractableObjectProps =
const _cameraPos = new THREE.Vector3();
const _cameraDir = new THREE.Vector3();
const _objectPos = new THREE.Vector3();
const _objectBounds = new THREE.Box3();
const _raycaster = new THREE.Raycaster();
function getInteractableWorldPosition(
group: THREE.Group,
debugSphere: THREE.Mesh | null,
): THREE.Vector3 {
_objectBounds.makeEmpty();
for (const child of group.children) {
if (child === debugSphere) continue;
_objectBounds.expandByObject(child);
}
if (!_objectBounds.isEmpty()) {
return _objectBounds.getCenter(_objectPos);
}
return group.getWorldPosition(_objectPos);
}
function createInteractableHandle(
props: InteractableObjectProps,
): InteractableHandle {
@@ -158,7 +177,7 @@ export function InteractableObject(
const t = bodyRef.current.translation();
_objectPos.set(t.x, t.y, t.z);
} else if (group) {
group.getWorldPosition(_objectPos);
getInteractableWorldPosition(group, debugSphereRef.current);
} else {
_objectPos.set(...position);
}
@@ -2,7 +2,6 @@ import { useEffect, useMemo } from "react";
import * as THREE from "three";
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
import type { ModelTransformProps, Vector3Tuple } from "@/types/three/three";
import { disposeObject3D } from "@/utils/three/dispose";
function applyShadowSettings(
object: THREE.Object3D,
@@ -48,12 +47,6 @@ export function SimpleModel({
applyShadowSettings(model, castShadow, receiveShadow);
}, [castShadow, model, receiveShadow]);
useEffect(() => {
return () => {
disposeObject3D(model);
};
}, [model]);
const parsedScale =
typeof scale === "number" ? ([scale, scale, scale] as Vector3Tuple) : scale;
+39 -20
View File
@@ -1,12 +1,15 @@
import { RotateCcw, StepBack, StepForward } from "lucide-react";
import { useGameStore } from "@/managers/stores/useGameStore";
import { isMissionStep, MISSION_STEPS } from "@/types/gameplay/repairMission";
import {
GAME_STEPS,
isGameStep,
MAIN_GAME_STATES,
type MainGameState,
} from "@/types/game";
} from "@/data/game/gameStateConfig";
import {
isMissionStep,
MISSION_STEPS,
} from "@/data/gameplay/repairMissionState";
import { useGameStore } from "@/managers/stores/useGameStore";
import type { MainGameState } from "@/types/game";
function toPascalCase(value: string): string {
return value
@@ -19,18 +22,18 @@ function toPascalCase(value: string): string {
export function GameStateDebugPanel(): React.JSX.Element {
const mainState = useGameStore((state) => state.mainState);
const ebikeStep = useGameStore((state) => state.ebike.currentStep);
const pyloneStep = useGameStore((state) => state.pylone.currentStep);
const fermeStep = useGameStore((state) => state.ferme.currentStep);
const pylonStep = useGameStore((state) => state.pylon.currentStep);
const farmStep = useGameStore((state) => state.farm.currentStep);
const detail = useGameStore((state) => {
switch (state.mainState) {
case "intro":
return state.intro.currentStep;
case "ebike":
return state.ebike.currentStep;
case "pylone":
return state.pylone.currentStep;
case "ferme":
return state.ferme.currentStep;
case "pylon":
return state.pylon.currentStep;
case "farm":
return state.farm.currentStep;
case "outro":
return state.outro.hasStarted ? "started" : "waiting";
}
@@ -38,8 +41,8 @@ export function GameStateDebugPanel(): React.JSX.Element {
const setMainState = useGameStore((state) => state.setMainState);
const setIntroStep = useGameStore((state) => state.setIntroStep);
const setEbikeState = useGameStore((state) => state.setEbikeState);
const setPyloneState = useGameStore((state) => state.setPyloneState);
const setFermeState = useGameStore((state) => state.setFermeState);
const setPylonState = useGameStore((state) => state.setPylonState);
const setFarmState = useGameStore((state) => state.setFarmState);
const setOutroState = useGameStore((state) => state.setOutroState);
const advanceGameState = useGameStore((state) => state.advanceGameState);
const rewindGameState = useGameStore((state) => state.rewindGameState);
@@ -72,13 +75,13 @@ export function GameStateDebugPanel(): React.JSX.Element {
return;
}
if (mainState === "pylone") {
setPyloneState({ currentStep: nextSubState });
if (mainState === "pylon") {
setPylonState({ currentStep: nextSubState });
return;
}
if (mainState === "ferme") {
setFermeState({ currentStep: nextSubState });
if (mainState === "farm") {
setFarmState({ currentStep: nextSubState });
return;
}
}
@@ -86,18 +89,34 @@ export function GameStateDebugPanel(): React.JSX.Element {
function setDebugMainState(nextMainState: MainGameState): void {
setMainState(nextMainState);
if (
nextMainState === "pylon" ||
nextMainState === "farm" ||
nextMainState === "outro"
) {
setEbikeState({ currentStep: "done", isRepaired: true });
}
if (nextMainState === "farm" || nextMainState === "outro") {
setPylonState({ currentStep: "done", isPowered: true });
}
if (nextMainState === "outro") {
setFarmState({ currentStep: "done", irrigationFixed: true });
}
if (nextMainState === "ebike" && ebikeStep === "locked") {
setEbikeState({ currentStep: "waiting" });
return;
}
if (nextMainState === "pylone" && pyloneStep === "locked") {
setPyloneState({ currentStep: "waiting" });
if (nextMainState === "pylon" && pylonStep === "locked") {
setPylonState({ currentStep: "waiting" });
return;
}
if (nextMainState === "ferme" && fermeStep === "locked") {
setFermeState({ currentStep: "waiting" });
if (nextMainState === "farm" && farmStep === "locked") {
setFarmState({ currentStep: "waiting" });
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import * as THREE from "three";
import { ZONES } from "@/data/zones";
import { useGameStore } from "@/managers/stores/useGameStore";
import { Debug } from "@/utils/debug/Debug";
import { GAME_STEPS } from "@/types/game";
import { GAME_STEPS } from "@/data/game/gameStateConfig";
const _playerPos = new THREE.Vector3();
const _zonePos = new THREE.Vector3();