#pragma once #include "CoreMinimal.h" #include "Core/DescentHandle.h" #include "Core/DescentWorldSubsystem.h" #include "Core/Tuning/DescentTune_Env.h" #include "DescentEnvQuery.generated.h" /** * The environment query seam (DESIGN_UE.md sections 3.3 and 7.4; requirements U5 and U6): "is this point dry, and * how warm is it". By default everything is water and the temperature is the water formula of section 2.7. A system * that owns dry space registers a dry-volume provider; the systems that only ask (look, fauna, HUD, dashboards) * never include the registering system: * * // carrier (M2), in its own Initialize / BeginPlay: * DryHandle = UDescentEnvQuerySubsystem::Get(World)->RegisterDryVolume(TEXT("Carrier"), * [this](const FVector& WorldUU) { return IsDry(GetTransform().InverseTransformPosition(WorldUU) / 100.0); }); * * // look / fauna / HUD: * if (Descent::IsDry(World, CameraUU)) { ... } // free functions, null-safe * const double TempC = Descent::AmbientTemperatureAt(World, RefPointUU); * * Positions are Unreal units (what GetActorLocation returns); the *Metres variants take metres. */ UCLASS() class THEDESCENT_API UDescentEnvQuerySubsystem : public UDescentWorldSubsystem { GENERATED_BODY() public: /** Answers "is WorldUU inside my dry volume". Called often (every frame, per fauna spawn): keep it cheap and allocation-free. */ using FDryVolumeFn = TFunction; static UDescentEnvQuerySubsystem* Get(const UWorld* World); /** * Registers a dry volume. Any provider answering true makes the point dry. TemperatureC is what a point inside it * reads (26 degrees by default, requirement U5); if several volumes contain a point the first by owner name wins. */ FDescentHandle RegisterDryVolume(FName Owner, FDryVolumeFn IsInside, double TemperatureC = Descent::Tune::Env::DryCabinTemperatureC); bool UnregisterDryVolume(FDescentHandle Handle); TArray GetDryVolumeOwners() const; /** True if some registered dry volume contains the point. False everywhere when nothing is registered. */ bool IsDry(const FVector& WorldUU) const; bool IsDryAtMetres(const FVector& WorldM) const; /** Dry volume's temperature inside one, otherwise the water temperature at the point's depth. Degrees Celsius. */ double AmbientTemperatureC(const FVector& WorldUU) const; double AmbientTemperatureAtMetres(const FVector& WorldM) const; /** Section 2.7: 4.2 + 9 clamp((Depth - 1740) / 1400, 0, 1) + (Depth > 2300 ? 6 : 0). Depth in metres. */ static double WaterTemperatureC(double DepthM); private: struct FVolume { uint32 Id = 0; FName Owner; FDryVolumeFn IsInside; double TemperatureC = 26.0; }; /** Kept sorted by (owner name, id) so the answer never depends on registration order. */ TArray Volumes; uint32 NextId = 1; }; namespace Descent { /** True if WorldUU is inside a registered dry volume (no world, no subsystem or no provider: false, i.e. water). */ THEDESCENT_API bool IsDry(const UWorld* World, const FVector& WorldUU); /** Section 3.3: dry volume temperature (26.0 C) inside, the water formula outside. Every temperature readout calls this with its own reference point. */ THEDESCENT_API double AmbientTemperatureAt(const UWorld* World, const FVector& WorldUU); }