#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Core/DescentHandle.h" #include "Core/DescentWorldSubsystem.h" #include "UI/ViewModels/DashViewModel.h" #include "Vehicles/CarrierHelm.h" #include "DescentCarrier.generated.h" class UCarrierBlockoutComponent; class UCameraComponent; class UDescentDashComponent; class UCarrierCarouselComponent; class ADescentCarrier; /** Finds the one carrier of a world (set by the carrier's BeginPlay). */ UCLASS() class THEDESCENT_API UDescentCarrierSubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: static ADescentCarrier* GetCarrier(const UWorld* World); TWeakObjectPtr Carrier; }; /** * A kinematic motion for tests (R2, DESIGN_UE.md section 11): the carrier translates along its own axes, yaws at a constant rate and * pitches as a sine, in sim step 2. The real helm (M6) replaces it; the base-following it exercises is the same. */ struct FCarrierDebugMotion { bool bActive = false; FVector VelocityLocalMps = FVector::ZeroVector; // along the carrier's own axes (x = forward) double YawRateDps = 0.0; double PitchAmplitudeDeg = 0.0; double PitchPeriodSec = 10.0; double RemainingSec = 0.0; double ElapsedSec = 0.0; }; /** * CANTICLE (DESIGN_UE.md section 3). At M2 it is the procedural blockout (UCarrierBlockoutComponent), the dry volume (U5, U6), the * stream focus, fixtures, the `carrier` state section and the debug motion; the helm physics, carousel and systems arrive later. */ UCLASS() class THEDESCENT_API ADescentCarrier : public AActor { GENERATED_BODY() public: ADescentCarrier(); virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type Reason) override; virtual void Tick(float DeltaSeconds) override; UPROPERTY(VisibleAnywhere) TObjectPtr Blockout; /** Section 3.5: sub bay carousel, hoist and the three Tenders (M3). */ UPROPERTY(VisibleAnywhere) TObjectPtr Carousel; /** Section 3.3: the single dry/wet answer; the environment, fauna and the temperature readouts all key off it. */ bool IsDry(const FVector& LocalM) const; FVector ToLocalM(const FVector& WorldUU) const { return GetActorTransform().InverseTransformPosition(WorldUU) / 100.0; } FVector ToWorldUU(const FVector& LocalM) const { return GetActorTransform().TransformPosition(LocalM * 100.0); } /** Debug motion (Descent.Carrier.Motion): stops itself after Seconds. */ void SetDebugMotion(const FCarrierDebugMotion& Motion); void ClearDebugMotion(); const FCarrierDebugMotion& GetDebugMotion() const { return DebugMotion; } // ---- the helm (section 3.6; M6: U4, U9, V12) ---- FCarrierHelmState Helm; FCarrierAutopilot Autopilot; /** Somebody (the player in mode Helm, or Descent.Carrier.Helm 1) holds the helm. Released, the carrier comes to an all stop. */ void SetHelmHeld(bool bHeld); bool IsHelmHeld() const { return bHelmHeld; } /** 0 BRIDGE, 1 CHASE, 2 TOP-DOWN, 3 BOW CAM, 4 KEEL CAM. */ void SetHelmView(int32 View); int32 GetHelmView() const { return HelmView; } /** Moves the carrier and everything attached to it (debug). */ void TeleportCarrier(const FVector& PosM, double YawDeg); /** The helm dash: the Tender's widget fed with the carrier's numbers (V6). */ FTenderDashModel MakeDashModel() const; struct FProximityRow { FString Label; double DistanceM = 0.0; }; TArray Proximity; UPROPERTY(VisibleAnywhere) TObjectPtr HelmCamera; UPROPERTY(Transient) TObjectPtr HelmDash; FVector VelocityMps() const { return VelMps; } double YawRateDps() const { return YawRateDpsMeasured; } private: void SimStep(double Dt); FDescentHandle DryHandle; FDescentHandle StepHandle; FCarrierDebugMotion DebugMotion; double BaseYawDeg = 0.0; FVector VelMps = FVector::ZeroVector; double YawRateDpsMeasured = 0.0; bool bHelmHeld = false; int32 HelmView = 0; double ProximityTimer = 0.0; FDescentHandle DashHandle; void UpdateProximity(); };