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