Feat/repair game #2
@@ -4,6 +4,7 @@ import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo
|
|||||||
import { RepairMissionCase } from "@/components/three/gameplay/RepairMissionCase";
|
import { RepairMissionCase } from "@/components/three/gameplay/RepairMissionCase";
|
||||||
import { TriggerObject } from "@/components/three/interaction/TriggerObject";
|
import { TriggerObject } from "@/components/three/interaction/TriggerObject";
|
||||||
import { REPAIR_CASE_ANIMATION_DURATION } from "@/data/gameplay/repairCaseConfig";
|
import { REPAIR_CASE_ANIMATION_DURATION } from "@/data/gameplay/repairCaseConfig";
|
||||||
|
import { REPAIR_INTERACTION_RADIUS } from "@/data/gameplay/repairGameConfig";
|
||||||
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
||||||
|
|
||||||
interface RepairCompletionStepProps {
|
interface RepairCompletionStepProps {
|
||||||
@@ -42,7 +43,7 @@ export function RepairCompletionStep({
|
|||||||
<RepairObjectModel
|
<RepairObjectModel
|
||||||
label={config.label}
|
label={config.label}
|
||||||
modelPath={config.modelPath}
|
modelPath={config.modelPath}
|
||||||
scale={1}
|
scale={config.modelScale ?? 1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isClosingCase ? (
|
{!isClosingCase ? (
|
||||||
@@ -50,6 +51,7 @@ export function RepairCompletionStep({
|
|||||||
position={[0, 1.1, 0]}
|
position={[0, 1.1, 0]}
|
||||||
colliders="ball"
|
colliders="ball"
|
||||||
label={`Valider ${config.label}`}
|
label={`Valider ${config.label}`}
|
||||||
|
radius={REPAIR_INTERACTION_RADIUS}
|
||||||
onTrigger={() => setIsClosingCase(true)}
|
onTrigger={() => setIsClosingCase(true)}
|
||||||
>
|
>
|
||||||
<mesh>
|
<mesh>
|
||||||
|
|||||||
@@ -122,7 +122,11 @@ export function RepairGame({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{step === "fragmented" ? (
|
{step === "fragmented" ? (
|
||||||
<ExplodableModel modelPath={config.modelPath} split />
|
<ExplodableModel
|
||||||
|
modelPath={config.modelPath}
|
||||||
|
scale={config.modelScale ?? 1}
|
||||||
|
split
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{step === "scanning" ? (
|
{step === "scanning" ? (
|
||||||
<RepairScanSequence
|
<RepairScanSequence
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
||||||
import { RepairObjectModel } from "@/components/three/gameplay/RepairObjectModel";
|
import { RepairObjectModel } from "@/components/three/gameplay/RepairObjectModel";
|
||||||
import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo";
|
import { RepairPromptVideo } from "@/components/three/gameplay/RepairPromptVideo";
|
||||||
|
import { REPAIR_INTERACTION_RADIUS } from "@/data/gameplay/repairGameConfig";
|
||||||
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
@@ -20,14 +21,15 @@ export function RepairInspectionObject({
|
|||||||
kind="trigger"
|
kind="trigger"
|
||||||
label={`Inspecter ${config.label}`}
|
label={`Inspecter ${config.label}`}
|
||||||
position={worldPosition}
|
position={worldPosition}
|
||||||
|
radius={REPAIR_INTERACTION_RADIUS}
|
||||||
onPress={onInspect}
|
onPress={onInspect}
|
||||||
>
|
>
|
||||||
<RepairObjectModel
|
<RepairObjectModel
|
||||||
label={config.label}
|
label={config.label}
|
||||||
modelPath={config.modelPath}
|
modelPath={config.modelPath}
|
||||||
scale={0.9}
|
scale={config.modelScale ?? 0.9}
|
||||||
/>
|
/>
|
||||||
<RepairPromptVideo src={config.interactUiPath} />
|
<RepairPromptVideo src={config.stageUiPath} />
|
||||||
</InteractableObject>
|
</InteractableObject>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
REPAIR_CASE_FOCUS_SCALE,
|
REPAIR_CASE_FOCUS_SCALE,
|
||||||
REPAIR_CASE_MODEL_PATH,
|
REPAIR_CASE_MODEL_PATH,
|
||||||
} from "@/data/gameplay/repairCaseConfig";
|
} from "@/data/gameplay/repairCaseConfig";
|
||||||
|
import { REPAIR_INTERACTION_RADIUS } from "@/data/gameplay/repairGameConfig";
|
||||||
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
import type { RepairMissionConfig } from "@/data/gameplay/repairMissions";
|
||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ export function RepairMissionCase({
|
|||||||
position={casePosition}
|
position={casePosition}
|
||||||
colliders="ball"
|
colliders="ball"
|
||||||
label={`Ouvrir ${config.label}`}
|
label={`Ouvrir ${config.label}`}
|
||||||
|
radius={REPAIR_INTERACTION_RADIUS}
|
||||||
onTrigger={onInteract}
|
onTrigger={onInteract}
|
||||||
>
|
>
|
||||||
<RepairCaseModel
|
<RepairCaseModel
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export function RepairReassemblyStep({
|
|||||||
<group>
|
<group>
|
||||||
<ExplodableModel
|
<ExplodableModel
|
||||||
modelPath={config.modelPath}
|
modelPath={config.modelPath}
|
||||||
|
scale={config.modelScale ?? 1}
|
||||||
split={split}
|
split={split}
|
||||||
splitDistance={1.2}
|
splitDistance={1.2}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
REPAIR_CASE_PLACEHOLDER_SNAP_DURATION,
|
REPAIR_CASE_PLACEHOLDER_SNAP_DURATION,
|
||||||
REPAIR_CASE_PLACEHOLDER_SNAP_RADIUS,
|
REPAIR_CASE_PLACEHOLDER_SNAP_RADIUS,
|
||||||
} from "@/data/gameplay/repairCaseConfig";
|
} from "@/data/gameplay/repairCaseConfig";
|
||||||
|
import { REPAIR_INTERACTION_RADIUS } from "@/data/gameplay/repairGameConfig";
|
||||||
import type {
|
import type {
|
||||||
RepairMissionConfig,
|
RepairMissionConfig,
|
||||||
RepairMissionPartConfig,
|
RepairMissionPartConfig,
|
||||||
@@ -299,6 +300,7 @@ function RepairInstallTarget({
|
|||||||
position={INSTALL_TARGET_POSITION}
|
position={INSTALL_TARGET_POSITION}
|
||||||
colliders="ball"
|
colliders="ball"
|
||||||
label={label}
|
label={label}
|
||||||
|
radius={REPAIR_INTERACTION_RADIUS}
|
||||||
onTrigger={() => {
|
onTrigger={() => {
|
||||||
if (!isReadyToInstall) {
|
if (!isReadyToInstall) {
|
||||||
onBlocked();
|
onBlocked();
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export function RepairScanSequence({
|
|||||||
<group>
|
<group>
|
||||||
<ExplodableModel
|
<ExplodableModel
|
||||||
modelPath={config.modelPath}
|
modelPath={config.modelPath}
|
||||||
|
scale={config.modelScale ?? 1}
|
||||||
split
|
split
|
||||||
onPartsReady={setParts}
|
onPartsReady={setParts}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import type { Vector3Tuple } from "@/types/three/three";
|
|||||||
interface InteractableObjectBaseProps {
|
interface InteractableObjectBaseProps {
|
||||||
label: string;
|
label: string;
|
||||||
position: Vector3Tuple;
|
position: Vector3Tuple;
|
||||||
|
radius?: number;
|
||||||
bodyRef?: RefObject<RapierRigidBody | null>;
|
bodyRef?: RefObject<RapierRigidBody | null>;
|
||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -64,7 +65,15 @@ function createInteractableHandle(
|
|||||||
export function InteractableObject(
|
export function InteractableObject(
|
||||||
props: InteractableObjectProps,
|
props: InteractableObjectProps,
|
||||||
): React.JSX.Element {
|
): React.JSX.Element {
|
||||||
const { kind, label, position, bodyRef, onPress, children } = props;
|
const {
|
||||||
|
kind,
|
||||||
|
label,
|
||||||
|
position,
|
||||||
|
radius = INTERACTION_RADIUS,
|
||||||
|
bodyRef,
|
||||||
|
onPress,
|
||||||
|
children,
|
||||||
|
} = props;
|
||||||
const onRelease = props.kind === "grab" ? props.onRelease : null;
|
const onRelease = props.kind === "grab" ? props.onRelease : null;
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
@@ -156,7 +165,7 @@ export function InteractableObject(
|
|||||||
|
|
||||||
camera.getWorldPosition(_cameraPos);
|
camera.getWorldPosition(_cameraPos);
|
||||||
const dist = _cameraPos.distanceTo(_objectPos);
|
const dist = _cameraPos.distanceTo(_objectPos);
|
||||||
const isNearby = dist <= INTERACTION_RADIUS;
|
const isNearby = dist <= radius;
|
||||||
|
|
||||||
manager.setNearby(handle.current, isNearby);
|
manager.setNearby(handle.current, isNearby);
|
||||||
|
|
||||||
@@ -169,7 +178,7 @@ export function InteractableObject(
|
|||||||
|
|
||||||
camera.getWorldDirection(_cameraDir);
|
camera.getWorldDirection(_cameraDir);
|
||||||
_raycaster.set(_cameraPos, _cameraDir);
|
_raycaster.set(_cameraPos, _cameraDir);
|
||||||
_raycaster.far = INTERACTION_RADIUS;
|
_raycaster.far = radius;
|
||||||
|
|
||||||
const hits = group ? _raycaster.intersectObject(group, true) : [];
|
const hits = group ? _raycaster.intersectObject(group, true) : [];
|
||||||
const validHit = hits.find((h) => h.object !== debugSphereRef.current);
|
const validHit = hits.find((h) => h.object !== debugSphereRef.current);
|
||||||
@@ -187,7 +196,7 @@ export function InteractableObject(
|
|||||||
<mesh ref={debugSphereRef} visible={false}>
|
<mesh ref={debugSphereRef} visible={false}>
|
||||||
<sphereGeometry
|
<sphereGeometry
|
||||||
args={[
|
args={[
|
||||||
INTERACTION_RADIUS,
|
radius,
|
||||||
INTERACTION_DEBUG_SPHERE_SEGMENTS,
|
INTERACTION_DEBUG_SPHERE_SEGMENTS,
|
||||||
INTERACTION_DEBUG_SPHERE_SEGMENTS,
|
INTERACTION_DEBUG_SPHERE_SEGMENTS,
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { RapierRigidBody } from "@react-three/rapier";
|
|||||||
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
||||||
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
||||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||||
|
import { INTERACTION_RADIUS } from "@/data/interaction/interactionConfig";
|
||||||
import {
|
import {
|
||||||
TRIGGER_DEFAULT_COLLIDERS,
|
TRIGGER_DEFAULT_COLLIDERS,
|
||||||
TRIGGER_DEFAULT_LABEL,
|
TRIGGER_DEFAULT_LABEL,
|
||||||
@@ -23,6 +24,7 @@ interface TriggerObjectProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
colliders?: ColliderShape;
|
colliders?: ColliderShape;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
radius?: number;
|
||||||
soundPath?: string;
|
soundPath?: string;
|
||||||
soundVolume?: number;
|
soundVolume?: number;
|
||||||
spawnModel?: string;
|
spawnModel?: string;
|
||||||
@@ -53,6 +55,7 @@ export function TriggerObject({
|
|||||||
children,
|
children,
|
||||||
colliders = TRIGGER_DEFAULT_COLLIDERS,
|
colliders = TRIGGER_DEFAULT_COLLIDERS,
|
||||||
label = TRIGGER_DEFAULT_LABEL,
|
label = TRIGGER_DEFAULT_LABEL,
|
||||||
|
radius = INTERACTION_RADIUS,
|
||||||
soundPath,
|
soundPath,
|
||||||
soundVolume = TRIGGER_DEFAULT_SOUND_VOLUME,
|
soundVolume = TRIGGER_DEFAULT_SOUND_VOLUME,
|
||||||
spawnModel,
|
spawnModel,
|
||||||
@@ -74,6 +77,7 @@ export function TriggerObject({
|
|||||||
kind="trigger"
|
kind="trigger"
|
||||||
label={label}
|
label={label}
|
||||||
position={position}
|
position={position}
|
||||||
|
radius={radius}
|
||||||
bodyRef={rbRef}
|
bodyRef={rbRef}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (soundPath) {
|
if (soundPath) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export const REPAIR_FRAGMENTATION_FIST_HOLD_SECONDS = 1;
|
export const REPAIR_FRAGMENTATION_FIST_HOLD_SECONDS = 1;
|
||||||
export const REPAIR_FRAGMENTATION_SEQUENCE_SECONDS = 4;
|
export const REPAIR_FRAGMENTATION_SEQUENCE_SECONDS = 4;
|
||||||
|
export const REPAIR_INTERACTION_RADIUS = 10;
|
||||||
export const REPAIR_SCAN_PART_SECONDS = 1.2;
|
export const REPAIR_SCAN_PART_SECONDS = 1.2;
|
||||||
export const REPAIR_REASSEMBLY_SECONDS = 1.4;
|
export const REPAIR_REASSEMBLY_SECONDS = 1.4;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||||
import type { Vector3Scale, Vector3Tuple } from "@/types/three/three";
|
import type {
|
||||||
|
ModelTransformProps,
|
||||||
|
Vector3Scale,
|
||||||
|
Vector3Tuple,
|
||||||
|
} from "@/types/three/three";
|
||||||
|
|
||||||
export interface RepairMissionCaseConfig {
|
export interface RepairMissionCaseConfig {
|
||||||
position: Vector3Tuple;
|
position: Vector3Tuple;
|
||||||
@@ -20,6 +24,7 @@ export interface RepairMissionConfig {
|
|||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
modelPath: string;
|
modelPath: string;
|
||||||
|
modelScale?: ModelTransformProps["scale"];
|
||||||
stageUiPath: string;
|
stageUiPath: string;
|
||||||
interactUiPath: string;
|
interactUiPath: string;
|
||||||
brokenUiPath: string;
|
brokenUiPath: string;
|
||||||
@@ -40,13 +45,14 @@ const DEFAULT_REPAIR_CASE = {
|
|||||||
scale: 1.5,
|
scale: 1.5,
|
||||||
} satisfies RepairMissionCaseConfig;
|
} satisfies RepairMissionCaseConfig;
|
||||||
|
|
||||||
export const REPAIR_MISSIONS = {
|
export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
|
||||||
bike: {
|
bike: {
|
||||||
id: "bike",
|
id: "bike",
|
||||||
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",
|
||||||
modelPath: "/models/refroidisseur/model.gltf",
|
modelPath: "/models/ebike/model.gltf",
|
||||||
|
modelScale: 0.25,
|
||||||
stageUiPath: "/assets/UI/ebike.webm",
|
stageUiPath: "/assets/UI/ebike.webm",
|
||||||
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
||||||
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
||||||
@@ -56,7 +62,8 @@ export const REPAIR_MISSIONS = {
|
|||||||
{
|
{
|
||||||
id: "bike-cooling-core",
|
id: "bike-cooling-core",
|
||||||
label: "Cooling core",
|
label: "Cooling core",
|
||||||
nodeName: "Cylinder",
|
modelPath: "/models/refroidisseur/model.gltf",
|
||||||
|
nodeName: "refroidisseur",
|
||||||
placeholderName: "placeholder_1",
|
placeholderName: "placeholder_1",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -74,7 +81,7 @@ export const REPAIR_MISSIONS = {
|
|||||||
{
|
{
|
||||||
id: "bike-glove-decoy",
|
id: "bike-glove-decoy",
|
||||||
label: "Insulation glove",
|
label: "Insulation glove",
|
||||||
modelPath: "/models/gant/model.gltf",
|
modelPath: "/models/gant_l/model.gltf",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -166,4 +173,4 @@ export const REPAIR_MISSIONS = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} satisfies Record<RepairMissionId, RepairMissionConfig>;
|
};
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { useGameStore } from "@/managers/stores/useGameStore";
|
|||||||
import type { MissionStep } from "@/types/gameplay/repairMission";
|
import type { MissionStep } from "@/types/gameplay/repairMission";
|
||||||
|
|
||||||
export function useRepairMovementLocked(): boolean {
|
export function useRepairMovementLocked(): boolean {
|
||||||
|
return false;
|
||||||
|
|
||||||
return useGameStore((state) => {
|
return useGameStore((state) => {
|
||||||
switch (state.mainState) {
|
switch (state.mainState) {
|
||||||
case "bike":
|
case "bike":
|
||||||
|
|||||||
Reference in New Issue
Block a user