#pragma once #include "CoreMinimal.h" #include "Core/DescentHandle.h" #include "Core/DescentWorldSubsystem.h" #include "DescentHorror.generated.h" class UInstancedStaticMeshComponent; class UStaticMeshComponent; /** The horror director (M9, requirement V3; DESIGN_UE.md section 8.3). Nothing here harms the player: audio and sights only. */ namespace Descent::Horror { constexpr int32 NumBeacons = 8; constexpr double BeaconTriggerM = 24.0, TypeCharsPerSec = 55.0; constexpr int32 LeviSegments = 40; constexpr double LeviSpacingM = 3.8, LeviSpeedMps = 8.5, LeviPathM = 520.0; /** dz: 0 at 1,760 m, 1 at 3,160 m. */ THEDESCENT_API double DepthFactor(double DepthM); /** hallu: 0 above 25 % nutrition, 1 at 5 %. */ THEDESCENT_API double Hallucination(double Nutrition); THEDESCENT_API double LeviRadiusM(int32 Segment); } UCLASS() class THEDESCENT_API UDescentHorrorSubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: static UDescentHorrorSubsystem* Get(const UWorld* World); virtual void OnWorldBeginPlay(UWorld& InWorld) override; virtual void Deinitialize() override; void OnSonarPing(const FVector& OriginM, double RangeM); void Heartbeat(); void StartLeviathan(bool bNear); void ShowPhantom(); void StartEnding(); int32 NumBeacons() const { return Descent::Horror::NumBeacons; } FVector BeaconPosM(int32 I) const; bool IsBeaconFound(int32 I) const { return I >= 0 && I < Descent::Horror::NumBeacons && bFound[I]; } bool IsLeviathanActive() const { return LeviS >= 0.0; } FVector LeviathanPointM(double AlongM) const; FVector LeviathanHeadM() const { return LeviathanPointM(LeviS); } double LeviathanAlongM() const { return LeviS; } double Dz = 0.0, Hallu = 0.0, Pulse = 0.0, EndingT = -1.0; int32 Heartbeats = 0, Creaks = 0, Groans = 0, Whispers = 0, Phantoms = 0, FalseRadios = 0, LogsFound = 0, Ghosts = 0, LeviPasses = 0; bool bEnding = false, bEndCardShown = false, bFelt = false; void FillWindows(); private: void Step(double Dt); void UpdateVisuals(double Dt); void Radio(const FString& Text, float Seconds = 7.f); bool bFound[Descent::Horror::NumBeacons] = { false, false, false, false, false, false, false, false }; double CreakIn = 20.0, GroanIn = 50.0, HeartIn = 85.0, HeartEcho = -1.0, WhisperIn = 30.0, PhantomIn = 30.0, LeviIn = 0.0, PhantomLeft = 0.0, WindowTimer = 0.0, Clock = 0.0; // the leviathan's pass double LeviS = -1.0; FVector LeviStart = FVector::ZeroVector, LeviDir = FVector::ForwardVector; bool bLeviFirst = true; // a log being typed on the radio FString TypeText; double TypePos = -1.0; FRandomStream Rng{ 66666 }; FDescentHandle StepHandle; UPROPERTY(Transient) TObjectPtr Visual; UPROPERTY(Transient) TObjectPtr LeviBody; UPROPERTY(Transient) TObjectPtr LeviLights; UPROPERTY(Transient) TObjectPtr BeaconMesh; UPROPERTY(Transient) TObjectPtr Phantom; };