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
🔍 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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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]} />
|
||||||
|
|||||||
Reference in New Issue
Block a user