feat: launch ebike repair from map interaction
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled

This commit is contained in:
tom-boullay
2026-05-28 10:13:59 +02:00
parent d654565f87
commit d9cf87d2d6
14 changed files with 107 additions and 71 deletions
+4 -2
View File
@@ -13,6 +13,7 @@ import { REPAIR_FRAGMENTATION_SEQUENCE_SECONDS } from "@/data/gameplay/repairGam
import { REPAIR_MISSIONS } from "@/data/gameplay/repairMissions"; import { REPAIR_MISSIONS } from "@/data/gameplay/repairMissions";
import { useRepairFragmentationInput } from "@/hooks/gameplay/useRepairFragmentationInput"; import { useRepairFragmentationInput } from "@/hooks/gameplay/useRepairFragmentationInput";
import { useRepairMissionStep } from "@/hooks/gameplay/useRepairMissionStep"; import { useRepairMissionStep } from "@/hooks/gameplay/useRepairMissionStep";
import { useTerrainSnappedPosition } from "@/hooks/three/useTerrainHeight";
import type { import type {
MissionStep, MissionStep,
RepairMissionConfig, RepairMissionConfig,
@@ -66,6 +67,7 @@ export function RepairGame({
readonly RepairScannedBrokenPart[] readonly RepairScannedBrokenPart[]
>([]); >([]);
const parsedScale = toVector3Scale(scale); const parsedScale = toVector3Scale(scale);
const snappedPosition = useTerrainSnappedPosition(position);
const readyForFragmentation = step === "inspected"; const readyForFragmentation = step === "inspected";
useRepairFragmentationInput({ useRepairFragmentationInput({
@@ -105,7 +107,7 @@ export function RepairGame({
if (step === "locked") return null; if (step === "locked") return null;
return ( return (
<group position={position} rotation={rotation} scale={parsedScale}> <group position={snappedPosition} rotation={rotation} scale={parsedScale}>
<Suspense fallback={null}> <Suspense fallback={null}>
<RepairMissionAssetPreloader config={config} /> <RepairMissionAssetPreloader config={config} />
</Suspense> </Suspense>
@@ -113,7 +115,7 @@ export function RepairGame({
{step === "waiting" ? ( {step === "waiting" ? (
<RepairInspectionObject <RepairInspectionObject
config={config} config={config}
worldPosition={position} worldPosition={snappedPosition}
onInspect={() => setMissionStep(mission, "inspected")} onInspect={() => setMissionStep(mission, "inspected")}
/> />
) : null} ) : null}
@@ -18,15 +18,15 @@ function toPascalCase(value: string): string {
export function GameStateDebugPanel(): React.JSX.Element { export function GameStateDebugPanel(): React.JSX.Element {
const mainState = useGameStore((state) => state.mainState); const mainState = useGameStore((state) => state.mainState);
const bikeStep = useGameStore((state) => state.bike.currentStep); const ebikeStep = useGameStore((state) => state.ebike.currentStep);
const pyloneStep = useGameStore((state) => state.pylone.currentStep); const pyloneStep = useGameStore((state) => state.pylone.currentStep);
const fermeStep = useGameStore((state) => state.ferme.currentStep); const fermeStep = useGameStore((state) => state.ferme.currentStep);
const detail = useGameStore((state) => { const detail = useGameStore((state) => {
switch (state.mainState) { switch (state.mainState) {
case "intro": case "intro":
return state.intro.currentStep; return state.intro.currentStep;
case "bike": case "ebike":
return state.bike.currentStep; return state.ebike.currentStep;
case "pylone": case "pylone":
return state.pylone.currentStep; return state.pylone.currentStep;
case "ferme": case "ferme":
@@ -37,7 +37,7 @@ export function GameStateDebugPanel(): React.JSX.Element {
}); });
const setMainState = useGameStore((state) => state.setMainState); const setMainState = useGameStore((state) => state.setMainState);
const setIntroStep = useGameStore((state) => state.setIntroStep); const setIntroStep = useGameStore((state) => state.setIntroStep);
const setBikeState = useGameStore((state) => state.setBikeState); const setEbikeState = useGameStore((state) => state.setEbikeState);
const setPyloneState = useGameStore((state) => state.setPyloneState); const setPyloneState = useGameStore((state) => state.setPyloneState);
const setFermeState = useGameStore((state) => state.setFermeState); const setFermeState = useGameStore((state) => state.setFermeState);
const setOutroState = useGameStore((state) => state.setOutroState); const setOutroState = useGameStore((state) => state.setOutroState);
@@ -67,8 +67,8 @@ export function GameStateDebugPanel(): React.JSX.Element {
if (!isMissionStep(nextSubState)) return; if (!isMissionStep(nextSubState)) return;
if (mainState === "bike") { if (mainState === "ebike") {
setBikeState({ currentStep: nextSubState }); setEbikeState({ currentStep: nextSubState });
return; return;
} }
@@ -86,8 +86,8 @@ export function GameStateDebugPanel(): React.JSX.Element {
function setDebugMainState(nextMainState: MainGameState): void { function setDebugMainState(nextMainState: MainGameState): void {
setMainState(nextMainState); setMainState(nextMainState);
if (nextMainState === "bike" && bikeStep === "locked") { if (nextMainState === "ebike" && ebikeStep === "locked") {
setBikeState({ currentStep: "waiting" }); setEbikeState({ currentStep: "waiting" });
return; return;
} }
+3 -3
View File
@@ -25,8 +25,8 @@ export const TEST_SCENE_REPAIR_ZONE_MARKER_TUBE_RADIUS = 0.045;
export const TEST_SCENE_REPAIR_ZONES = [ export const TEST_SCENE_REPAIR_ZONES = [
{ {
mission: "bike", mission: "ebike",
label: "Bike", label: "E-bike",
color: "#38bdf8", color: "#38bdf8",
position: [-12, 0, -12], position: [-12, 0, -12],
}, },
@@ -43,7 +43,7 @@ export const TEST_SCENE_REPAIR_ZONES = [
position: [12, 0, -12], position: [12, 0, -12],
}, },
] as const satisfies readonly { ] as const satisfies readonly {
mission: "bike" | "pylone" | "ferme"; mission: "ebike" | "pylone" | "ferme";
label: string; label: string;
color: string; color: string;
position: Vector3Tuple; position: Vector3Tuple;
+3 -3
View File
@@ -1,18 +1,18 @@
import type { Vector3Tuple } from "@/types/three/three"; import type { Vector3Tuple } from "@/types/three/three";
import type { RepairMissionId } from "@/types/gameplay/repairMission"; import type { RepairMissionId } from "@/types/gameplay/repairMission";
export const BIKE_REPAIR_POSITION = [ export const EBIKE_REPAIR_POSITION = [
42.2399, 4.5484, 34.6468, 42.2399, 4.5484, 34.6468,
] as const satisfies Vector3Tuple; ] as const satisfies Vector3Tuple;
const REPAIR_MISSION_POSITIONS = { const REPAIR_MISSION_POSITIONS = {
bike: BIKE_REPAIR_POSITION, ebike: EBIKE_REPAIR_POSITION,
pylone: [64, 0, -66], pylone: [64, 0, -66],
ferme: [-24, 0, 42], ferme: [-24, 0, 42],
} as const satisfies Record<RepairMissionId, Vector3Tuple>; } as const satisfies Record<RepairMissionId, Vector3Tuple>;
export const REPAIR_MISSION_POSITION_ENTRIES = [ export const REPAIR_MISSION_POSITION_ENTRIES = [
{ mission: "bike", position: REPAIR_MISSION_POSITIONS.bike }, { mission: "ebike", position: REPAIR_MISSION_POSITIONS.ebike },
{ mission: "pylone", position: REPAIR_MISSION_POSITIONS.pylone }, { mission: "pylone", position: REPAIR_MISSION_POSITIONS.pylone },
{ mission: "ferme", position: REPAIR_MISSION_POSITIONS.ferme }, { mission: "ferme", position: REPAIR_MISSION_POSITIONS.ferme },
] as const satisfies readonly { ] as const satisfies readonly {
+7 -7
View File
@@ -14,8 +14,8 @@ const DEFAULT_REPAIR_CASE = {
} satisfies RepairMissionCaseConfig; } satisfies RepairMissionCaseConfig;
export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = { export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
bike: { ebike: {
id: "bike", id: "ebike",
label: "E-bike", label: "E-bike",
description: description:
"Repair the damaged cooling module before relaunching the bike", "Repair the damaged cooling module before relaunching the bike",
@@ -25,10 +25,10 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
interactUiPath: REPAIR_INTERACT_UI_PATH, interactUiPath: REPAIR_INTERACT_UI_PATH,
brokenUiPath: REPAIR_BROKEN_UI_PATH, brokenUiPath: REPAIR_BROKEN_UI_PATH,
case: DEFAULT_REPAIR_CASE, case: DEFAULT_REPAIR_CASE,
requiredReplacementPartId: "bike-cooling-core-replacement", requiredReplacementPartId: "ebike-cooling-core-replacement",
brokenParts: [ brokenParts: [
{ {
id: "bike-cooling-core", id: "ebike-cooling-core",
label: "Cooling core", label: "Cooling core",
modelPath: "/models/refroidisseur/model.gltf", modelPath: "/models/refroidisseur/model.gltf",
nodeName: "refroidisseur", nodeName: "refroidisseur",
@@ -37,17 +37,17 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
], ],
replacementParts: [ replacementParts: [
{ {
id: "bike-cooling-core-replacement", id: "ebike-cooling-core-replacement",
label: "Replacement cooling core", label: "Replacement cooling core",
modelPath: "/models/refroidisseur/model.gltf", modelPath: "/models/refroidisseur/model.gltf",
}, },
{ {
id: "bike-radio-distractor", id: "ebike-radio-distractor",
label: "Radio module", label: "Radio module",
modelPath: "/models/talkie/model.gltf", modelPath: "/models/talkie/model.gltf",
}, },
{ {
id: "bike-glove-distractor", id: "ebike-glove-distractor",
label: "Insulation glove", label: "Insulation glove",
modelPath: "/models/gant_l/model.gltf", modelPath: "/models/gant_l/model.gltf",
}, },
+10 -2
View File
@@ -1,9 +1,15 @@
import type { TerrainSurfaceColorConfig } from "@/types/world/terrainSurface"; import type {
TerrainSurfaceColorConfig,
TerrainSurfaceProjectionConfig,
} from "@/types/world/terrainSurface";
export const TERRAIN_MODEL_PATH = "/models/terrain/model.gltf"; export const TERRAIN_MODEL_PATH = "/models/terrain/model.gltf";
export const TERRAIN_WATER_HEIGHT = 0.8; export const TERRAIN_WATER_HEIGHT = 0.8;
const TERRAIN_TILE_SIZE = 1; export const TERRAIN_TILE_SIZE = 1;
export const TERRAIN_SURFACE_COLOR_TOLERANCE = 5;
export const TERRAIN_SURFACE_PROJECTION =
{} satisfies TerrainSurfaceProjectionConfig;
export const TERRAIN_COLORS = { export const TERRAIN_COLORS = {
grass1: { grass1: {
@@ -54,3 +60,5 @@ export const TERRAIN_COLORS = {
kind: "rock", kind: "rock",
}, },
} satisfies Record<string, TerrainSurfaceColorConfig>; } satisfies Record<string, TerrainSurfaceColorConfig>;
export type TerrainColorKey = keyof typeof TERRAIN_COLORS;
@@ -4,8 +4,8 @@ import type { MissionStep } from "@/types/gameplay/repairMission";
export function useRepairMovementLocked(): boolean { export function useRepairMovementLocked(): boolean {
return useGameStore((state) => { return useGameStore((state) => {
switch (state.mainState) { switch (state.mainState) {
case "bike": case "ebike":
return isRepairMovementLocked(state.bike.currentStep); return isRepairMovementLocked(state.ebike.currentStep);
case "pylone": case "pylone":
return isRepairMovementLocked(state.pylone.currentStep); return isRepairMovementLocked(state.pylone.currentStep);
case "ferme": case "ferme":
+28 -28
View File
@@ -26,7 +26,7 @@ interface IntroState {
currentStep: GameStep; currentStep: GameStep;
dialogueAudio: string | null; dialogueAudio: string | null;
hasCompleted: boolean; hasCompleted: boolean;
isBikeUnlocked: boolean; isEbikeUnlocked: boolean;
} }
interface MissionState { interface MissionState {
@@ -46,7 +46,7 @@ export interface GameState {
isCinematicPlaying: boolean; isCinematicPlaying: boolean;
missionFlow: MissionFlowState; missionFlow: MissionFlowState;
intro: IntroState; intro: IntroState;
bike: MissionState & { ebike: MissionState & {
isRepaired: boolean; isRepaired: boolean;
}; };
pylone: MissionState & { pylone: MissionState & {
@@ -70,13 +70,13 @@ interface GameActions {
setIntroStep: (step: GameStep) => void; setIntroStep: (step: GameStep) => void;
setIntroState: (intro: Partial<IntroState>) => void; setIntroState: (intro: Partial<IntroState>) => void;
setPlayerName: (playerName: string) => void; setPlayerName: (playerName: string) => void;
setBikeState: (bike: Partial<GameState["bike"]>) => void; setEbikeState: (ebike: Partial<GameState["ebike"]>) => void;
setPyloneState: (pylone: Partial<GameState["pylone"]>) => void; setPyloneState: (pylone: Partial<GameState["pylone"]>) => void;
setFermeState: (ferme: Partial<GameState["ferme"]>) => void; setFermeState: (ferme: Partial<GameState["ferme"]>) => void;
setOutroState: (outro: Partial<GameState["outro"]>) => void; setOutroState: (outro: Partial<GameState["outro"]>) => void;
setMissionStep: (mission: RepairMissionId, step: MissionStep) => void; setMissionStep: (mission: RepairMissionId, step: MissionStep) => void;
completeIntro: () => void; completeIntro: () => void;
completeBike: () => void; completeEbike: () => void;
completePylone: () => void; completePylone: () => void;
completeFerme: () => void; completeFerme: () => void;
completeMission: (mission: RepairMissionId) => void; completeMission: (mission: RepairMissionId) => void;
@@ -104,24 +104,24 @@ function isBoolean(value: unknown): value is boolean {
function completeIntroState(state: GameState): GameStateUpdate { function completeIntroState(state: GameState): GameStateUpdate {
return { return {
mainState: "bike", mainState: "ebike",
intro: { intro: {
...state.intro, ...state.intro,
hasCompleted: true, hasCompleted: true,
isBikeUnlocked: true, isEbikeUnlocked: true,
}, },
bike: { ebike: {
...state.bike, ...state.ebike,
currentStep: "locked", currentStep: "locked",
}, },
}; };
} }
function completeBikeState(state: GameState): GameStateUpdate { function completeEbikeState(state: GameState): GameStateUpdate {
return { return {
mainState: "pylone", mainState: "pylone",
bike: { ebike: {
...state.bike, ...state.ebike,
currentStep: "done", currentStep: "done",
isRepaired: true, isRepaired: true,
}, },
@@ -180,8 +180,8 @@ function completeMissionState(
mission: RepairMissionId, mission: RepairMissionId,
): GameStateUpdate { ): GameStateUpdate {
switch (mission) { switch (mission) {
case "bike": case "ebike":
return completeBikeState(state); return completeEbikeState(state);
case "pylone": case "pylone":
return completePyloneState(state); return completePyloneState(state);
case "ferme": case "ferme":
@@ -236,9 +236,9 @@ function createInitialGameState(): GameState {
currentStep: "intro", currentStep: "intro",
dialogueAudio: null, dialogueAudio: null,
hasCompleted: false, hasCompleted: false,
isBikeUnlocked: false, isEbikeUnlocked: false,
}, },
bike: { ebike: {
currentStep: "locked", currentStep: "locked",
dialogueAudio: null, dialogueAudio: null,
isRepaired: false, isRepaired: false,
@@ -273,9 +273,9 @@ function hydrateIntroState(initial: IntroState, value: unknown): IntroState {
hasCompleted: isBoolean(value.hasCompleted) hasCompleted: isBoolean(value.hasCompleted)
? value.hasCompleted ? value.hasCompleted
: initial.hasCompleted, : initial.hasCompleted,
isBikeUnlocked: isBoolean(value.isBikeUnlocked) isEbikeUnlocked: isBoolean(value.isEbikeUnlocked)
? value.isBikeUnlocked ? value.isEbikeUnlocked
: initial.isBikeUnlocked, : initial.isEbikeUnlocked,
}; };
} }
@@ -320,7 +320,7 @@ function hydrateMissionFlowState(
function hydrateDebugGameState(initial: GameState, value: unknown): GameState { function hydrateDebugGameState(initial: GameState, value: unknown): GameState {
if (!isRecord(value)) return initial; if (!isRecord(value)) return initial;
const bike = hydrateMissionState(initial.bike, value.bike); const ebike = hydrateMissionState(initial.ebike, value.ebike);
const pylone = hydrateMissionState(initial.pylone, value.pylone); const pylone = hydrateMissionState(initial.pylone, value.pylone);
const ferme = hydrateMissionState(initial.ferme, value.ferme); const ferme = hydrateMissionState(initial.ferme, value.ferme);
const outro = isRecord(value.outro) ? value.outro : null; const outro = isRecord(value.outro) ? value.outro : null;
@@ -337,12 +337,12 @@ function hydrateDebugGameState(initial: GameState, value: unknown): GameState {
value.missionFlow, value.missionFlow,
), ),
intro: hydrateIntroState(initial.intro, value.intro), intro: hydrateIntroState(initial.intro, value.intro),
bike: { ebike: {
...bike, ...ebike,
isRepaired: isRepaired:
isRecord(value.bike) && isBoolean(value.bike.isRepaired) isRecord(value.ebike) && isBoolean(value.ebike.isRepaired)
? value.bike.isRepaired ? value.ebike.isRepaired
: initial.bike.isRepaired, : initial.ebike.isRepaired,
}, },
pylone: { pylone: {
...pylone, ...pylone,
@@ -384,7 +384,7 @@ function pickGameState(state: GameStore): GameState {
isCinematicPlaying: state.isCinematicPlaying, isCinematicPlaying: state.isCinematicPlaying,
missionFlow: state.missionFlow, missionFlow: state.missionFlow,
intro: state.intro, intro: state.intro,
bike: state.bike, ebike: state.ebike,
pylone: state.pylone, pylone: state.pylone,
ferme: state.ferme, ferme: state.ferme,
outro: state.outro, outro: state.outro,
@@ -415,8 +415,8 @@ export const useGameStore = create<GameStore>()((set) => ({
set((state) => ({ set((state) => ({
missionFlow: { ...state.missionFlow, playerName }, missionFlow: { ...state.missionFlow, playerName },
})), })),
setBikeState: (bike) => setEbikeState: (ebike) =>
set((state) => ({ bike: { ...state.bike, ...bike } })), set((state) => ({ ebike: { ...state.ebike, ...ebike } })),
setPyloneState: (pylone) => setPyloneState: (pylone) =>
set((state) => ({ pylone: { ...state.pylone, ...pylone } })), set((state) => ({ pylone: { ...state.pylone, ...pylone } })),
setFermeState: (ferme) => setFermeState: (ferme) =>
@@ -426,7 +426,7 @@ export const useGameStore = create<GameStore>()((set) => ({
setMissionStep: (mission, step) => setMissionStep: (mission, step) =>
set((state) => setMissionStepState(state, mission, step)), set((state) => setMissionStepState(state, mission, step)),
completeIntro: () => set(completeIntroState), completeIntro: () => set(completeIntroState),
completeBike: () => set((state) => completeMissionState(state, "bike")), completeEbike: () => set((state) => completeMissionState(state, "ebike")),
completePylone: () => set((state) => completeMissionState(state, "pylone")), completePylone: () => set((state) => completeMissionState(state, "pylone")),
completeFerme: () => set((state) => completeMissionState(state, "ferme")), completeFerme: () => set((state) => completeMissionState(state, "ferme")),
completeMission: (mission) => completeMission: (mission) =>
@@ -26,8 +26,8 @@ export function HandTrackingProvider({
const sceneMode = useSceneMode(); const sceneMode = useSceneMode();
const repairNeedsHands = useGameStore((state) => { const repairNeedsHands = useGameStore((state) => {
switch (state.mainState) { switch (state.mainState) {
case "bike": case "ebike":
return REPAIR_HAND_TRACKING_STEPS.has(state.bike.currentStep); return REPAIR_HAND_TRACKING_STEPS.has(state.ebike.currentStep);
case "pylone": case "pylone":
return REPAIR_HAND_TRACKING_STEPS.has(state.pylone.currentStep); return REPAIR_HAND_TRACKING_STEPS.has(state.pylone.currentStep);
case "ferme": case "ferme":
+2 -2
View File
@@ -27,11 +27,11 @@ export const GAME_STEPS: readonly GameStep[] = [
const GAME_STEP_VALUES: ReadonlySet<string> = new Set(GAME_STEPS); const GAME_STEP_VALUES: ReadonlySet<string> = new Set(GAME_STEPS);
export type MainGameState = "intro" | "bike" | "pylone" | "ferme" | "outro"; export type MainGameState = "intro" | "ebike" | "pylone" | "ferme" | "outro";
export const MAIN_GAME_STATES: readonly MainGameState[] = [ export const MAIN_GAME_STATES: readonly MainGameState[] = [
"intro", "intro",
"bike", "ebike",
"pylone", "pylone",
"ferme", "ferme",
"outro", "outro",
+2 -2
View File
@@ -4,7 +4,7 @@ import type {
Vector3Tuple, Vector3Tuple,
} from "@/types/three/three"; } from "@/types/three/three";
export type RepairMissionId = "bike" | "pylone" | "ferme"; export type RepairMissionId = "ebike" | "pylone" | "ferme";
export interface RepairMissionCaseConfig { export interface RepairMissionCaseConfig {
position: Vector3Tuple; position: Vector3Tuple;
@@ -54,7 +54,7 @@ export type MissionStep =
| "reassembling" | "reassembling"
| "done"; | "done";
const REPAIR_MISSION_IDS = ["bike", "pylone", "ferme"] as const; const REPAIR_MISSION_IDS = ["ebike", "pylone", "ferme"] as const;
const REPAIR_MISSION_ID_VALUES: ReadonlySet<string> = new Set( const REPAIR_MISSION_ID_VALUES: ReadonlySet<string> = new Set(
REPAIR_MISSION_IDS, REPAIR_MISSION_IDS,
); );
+28 -2
View File
@@ -1,4 +1,6 @@
type TerrainSurfaceKind = import type * as THREE from "three";
export type TerrainSurfaceKind =
| "grass" | "grass"
| "path" | "path"
| "water" | "water"
@@ -6,7 +8,19 @@ type TerrainSurfaceKind =
| "dirt" | "dirt"
| "rock"; | "rock";
type TerrainSurfaceRgb = readonly [number, number, number]; export type TerrainSurfaceRgb = readonly [number, number, number];
export interface TerrainSurfaceUv {
u: number;
v: number;
}
export interface TerrainSurfaceProjectionConfig {
flipX?: boolean;
flipZ?: boolean;
offsetX?: number;
offsetZ?: number;
}
export interface TerrainSurfaceBounds { export interface TerrainSurfaceBounds {
minX: number; minX: number;
@@ -23,3 +37,15 @@ export interface TerrainSurfaceColorConfig {
modelPath?: string; modelPath?: string;
tileSize?: number; tileSize?: number;
} }
export interface TerrainSurfaceSample {
rgb: TerrainSurfaceRgb;
key: string | null;
config: TerrainSurfaceColorConfig | null;
}
export interface TerrainSurfaceData {
bounds: TerrainSurfaceBounds;
imageData: ImageData;
raycastTarget: THREE.Object3D;
}
+2 -2
View File
@@ -301,9 +301,9 @@ function MapNodeInstance({
}): React.JSX.Element | null { }): React.JSX.Element | null {
const isGeneratedModel = isGeneratedMapModelName(node.name); const isGeneratedModel = isGeneratedMapModelName(node.name);
const mainState = useGameStore((state) => state.mainState); const mainState = useGameStore((state) => state.mainState);
const bikeStep = useGameStore((state) => state.bike.currentStep); const ebikeStep = useGameStore((state) => state.ebike.currentStep);
const hideEbikeMapModel = const hideEbikeMapModel =
node.name === "ebike" && mainState === "bike" && bikeStep !== "locked"; node.name === "ebike" && mainState === "ebike" && ebikeStep !== "locked";
useEffect(() => { useEffect(() => {
if (modelUrl !== null || isGeneratedModel) return; if (modelUrl !== null || isGeneratedModel) return;
+6 -6
View File
@@ -1,7 +1,7 @@
import { InteractableObject } from "@/components/three/interaction/InteractableObject"; import { InteractableObject } from "@/components/three/interaction/InteractableObject";
import { RepairGame } from "@/components/three/gameplay/RepairGame"; import { RepairGame } from "@/components/three/gameplay/RepairGame";
import { import {
BIKE_REPAIR_POSITION, EBIKE_REPAIR_POSITION,
REPAIR_MISSION_POSITION_ENTRIES, REPAIR_MISSION_POSITION_ENTRIES,
} from "@/data/gameplay/repairMissionAnchors"; } from "@/data/gameplay/repairMissionAnchors";
import { useGameStore } from "@/managers/stores/useGameStore"; import { useGameStore } from "@/managers/stores/useGameStore";
@@ -34,19 +34,19 @@ function StageAnchor({
function EbikeMissionTrigger(): React.JSX.Element | null { function EbikeMissionTrigger(): React.JSX.Element | null {
const mainState = useGameStore((state) => state.mainState); const mainState = useGameStore((state) => state.mainState);
const bikeStep = useGameStore((state) => state.bike.currentStep); const ebikeStep = useGameStore((state) => state.ebike.currentStep);
const setMissionStep = useGameStore((state) => state.setMissionStep); const setMissionStep = useGameStore((state) => state.setMissionStep);
if (mainState !== "bike" || bikeStep !== "locked") return null; if (mainState !== "ebike" || ebikeStep !== "locked") return null;
return ( return (
<group position={BIKE_REPAIR_POSITION}> <group position={EBIKE_REPAIR_POSITION}>
<InteractableObject <InteractableObject
kind="trigger" kind="trigger"
label="Réparer l'e-bike" label="Réparer l'e-bike"
position={BIKE_REPAIR_POSITION} position={EBIKE_REPAIR_POSITION}
radius={4} radius={4}
onPress={() => setMissionStep("bike", "waiting")} onPress={() => setMissionStep("ebike", "waiting")}
> >
<mesh> <mesh>
<sphereGeometry args={[1.3, 16, 16]} /> <sphereGeometry args={[1.3, 16, 16]} />