#pragma once #include "CoreMinimal.h" #include "Core/DescentHandle.h" #include "Core/DescentWorldSubsystem.h" #include "DescentFauna.generated.h" class ADescentCarrier; class UInstancedStaticMeshComponent; /** Fauna (M8, requirements U3, U6; DESIGN_UE.md section 6.9): the school homes and the hull exclusion as plain data and pure functions. */ namespace Descent::Fauna { struct FHomeDef { FVector PosM = FVector::ZeroVector; uint8 Species = 0; // 0 silverfin (0.10 FU), 1 lanternfish (0.15 FU, glowing) }; THEDESCENT_API const TArray& Homes(); constexpr int32 SchoolSize = 26; constexpr double ActiveRangeM = 220.0, LeashM = 45.0; constexpr int32 MaxActiveHomes = 6; constexpr double HullSpawnMarginM = 6.0, HullSteerMarginM = 3.0, HullHardMarginM = 0.5; constexpr double CaptureM = 1.6, HandReachM = 2.6; /** U6: a fish at this carrier-local point with this distance to the hull (moon-pool shaft counted as hull). Moves it out when it is closer than the * hard margin and returns the outward acceleration to apply when it is inside the steer margin. */ THEDESCENT_API FVector HullExclusion(FVector& InOutLocalM, bool* bOutWasInside = nullptr); THEDESCENT_API FName SpeciesName(uint8 Species); } /** Schools of fish and the lantern polyps. Only the six nearest homes within 220 m of the player are simulated and drawn. */ UCLASS() class THEDESCENT_API UDescentFaunaSubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: static UDescentFaunaSubsystem* Get(const UWorld* World); virtual void OnWorldBeginPlay(UWorld& InWorld) override; virtual void Deinitialize() override; /** Tender suction: fish in the cone are pulled in and captured within 1.6 m of the nozzle. Returns the FU captured (never more than RoomFU). */ double Suck(const FVector& NozzleM, const FVector& Dir, double RangeM, double Pull, double Dt, double RoomFU, bool& bOutTankFull); /** A swimmer's E: the nearest polyp within 2.6 m is taken. Returns "polyp" or NAME_None. */ FName TakeByHand(const FVector& PosM); bool NearestPolyp(const FVector& PosM, double MaxM, FVector& OutM) const; /** Debug: puts a polyp cluster at a point (scenarios). */ void AddPolypCluster(const FVector& PosM, const FVector& Normal); int32 ActiveHomes() const; int32 FishAlive() const; int32 NumPolyps() const; int32 InsideHull = 0, InsideHullEver = 0, Captured = 0, TakenByHand = 0, ProjectedOut = 0; double MinHullDistM = 1e9, NearestFishM = 1e9; /** The fish nearest to a world point (metres); false if none is simulated. */ bool NearestFish(const FVector& PosM, FVector& OutM) const; private: struct FFish { FVector P = FVector::ZeroVector, V = FVector::ZeroVector; double Sucked = 0.0; }; struct FHome { Descent::Fauna::FHomeDef Def; int32 Alive = Descent::Fauna::SchoolSize; bool bActive = false, bPolypsMade = false; TArray Fish; FVector Target = FVector::ZeroVector; double TargetTimer = 0.0, Regrow = 0.0; }; struct FPolyp { FVector P = FVector::ZeroVector; bool bAlive = true; double Regrow = 0.0; }; void Step(double Dt); void Activate(FHome& H); bool FreeWater(const FVector& PosM, double HullMargin) const; void UpdateVisuals(); TArray HomeStates; TArray Polyps; FRandomStream Rng{ 20261006 }; FDescentHandle StepHandle; UPROPERTY(Transient) TObjectPtr Visual; UPROPERTY(Transient) TArray> Meshes; // silverfin, lanternfish, polyps };