#pragma once #include "CoreMinimal.h" #include "Containers/BitArray.h" #include "Core/DescentHandle.h" #include "Core/DescentWorldSubsystem.h" #include "DescentSonar.generated.h" /** Sonar and survey map (M9, requirement V2; DESIGN_UE.md sections 8.1, 8.2): the numbers and the pure parts. */ namespace Descent::Sonar { constexpr double RevealMps = 170.0, PointLifeSec = 20.0, ContactLifeSec = 16.0, GhostLifeSec = 2.2; constexpr int32 RaysPerStep = 420, MaxPoints = 30000; constexpr double TenderRangeM = 160.0, TenderModuleRangeM = 260.0, HandRangeM = 90.0, HelmRangeM = 420.0; constexpr int32 TenderRays = 2200, HandRays = 900, HelmRays = 4200; constexpr double TenderCooldownSec = 2.5, HandCooldownSec = 3.0, HelmCooldownSec = 4.0; /** Direction I of N on a Fibonacci sphere. */ THEDESCENT_API FVector FibonacciDir(int32 I, int32 N); // the survey map: cells 4 x 4 m in plan, 8 m tall, over the claim box constexpr double CellXY = 4.0, CellZ = 8.0; constexpr int32 MapNx = 473, MapNy = 365, MapNz = 220; /** Cell of a world point (metres); false outside the claim box. */ THEDESCENT_API bool CellOf(const FVector& Pm, FIntVector& OutCell); } /** The survey map: what sonar has hit (3D) and where the player has been (2D). Fills the PDA map. */ UCLASS() class THEDESCENT_API UDescentMapSubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: static UDescentMapSubsystem* Get(const UWorld* World); virtual void OnWorldBeginPlay(UWorld& InWorld) override; virtual void Deinitialize() override; void Mark(const FVector& Pm); void Visit(const FVector& Pm); bool IsKnown(const FVector& Pm) const; int32 KnownCells = 0, VisitedCells = 0; /** 0 AUTO (reference depth +- 60 m), 1 ALL, 2 fixed depth +- 40 m. */ int32 SliceMode = 0; double SliceDepthM = 1800.0; void FillWindow(); private: void Step(double Dt); TBitArray<> Known, Visited; double WindowTimer = 0.0; FDescentHandle StepHandle; }; /** Active sonar: pings march rays through the rock (420 per sim step); each hit is a point that appears as the ping front reaches it and lives 20 s. */ UCLASS() class THEDESCENT_API UDescentSonarSubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: static UDescentSonarSubsystem* Get(const UWorld* World); virtual void OnWorldBeginPlay(UWorld& InWorld) override; virtual void Deinitialize() override; /** R: a ping from whatever the player controls (Tender, helm, or the handheld sonar while swimming). */ bool PingFromPlayer(FString* OutWhyNot = nullptr); bool Ping(const FVector& OriginM, double RangeM, int32 Rays); /** Kind 2 unknown contact (red), 3 beacon (amber). */ void AddContact(const FVector& Pm, double LifeSec, int32 Kind, const FString& Label); int32 PointsAlive() const; int32 NumContacts() const { return Contacts.Num(); } int32 RaysPending() const; int32 Pings = 0, Hits = 0; double CooldownLeft = 0.0, LastRangeM = 0.0; FVector LastOriginM = FVector::ZeroVector; struct FContact { FVector P = FVector::ZeroVector; double Born = 0.0, Life = 16.0; int32 Kind = 2; FString Label; }; const TArray& GetContacts() const { return Contacts; } private: struct FPoint { FVector P = FVector::ZeroVector; double Born = 0.0; float Dist = 0.f; }; struct FPing { FVector O = FVector::ZeroVector; double Range = 0.0, T0 = 0.0; int32 Next = 0, Rays = 0; FQuat Rot = FQuat::Identity; }; void Step(double Dt); void WriteHud(); TArray Points; int32 Head = 0; TArray Active; TArray Contacts; double Now = 0.0; FRandomStream Rng{ 1180 }; FDescentHandle StepHandle, LateHandle; };