#include "UI/DescentHudSubsystem.h" #include "Core/DescentSimSubsystem.h" #include "Debug/DescentStateRegistry.h" #include "TheDescent.h" #include "Engine/World.h" // --------------------------------------------------------------------------------------------------------- // The model // --------------------------------------------------------------------------------------------------------- void FDescentHudModel::PushToast(const FString& Text, EDescentHudToastKind Kind, float Seconds) { FDescentHudToast T; T.Text = Text; T.Kind = Kind; T.SecondsLeft = Seconds; Toasts.Add(MoveTemp(T)); while (Toasts.Num() > MaxToasts) { Toasts.RemoveAt(0); } } void FDescentHudModel::SetRadio(const FString& Text, float Seconds) { Radio = Text; RadioSecondsLeft = Seconds; } void FDescentHudModel::Advance(float DeltaSeconds) { for (FDescentHudToast& T : Toasts) { T.SecondsLeft -= DeltaSeconds; } Toasts.RemoveAll([](const FDescentHudToast& T) { return T.SecondsLeft <= 0.f; }); if (RadioSecondsLeft > 0.f) { RadioSecondsLeft -= DeltaSeconds; if (RadioSecondsLeft <= 0.f) { Radio.Reset(); RadioSecondsLeft = 0.f; } } } FString FDescentHudModel::FormatThousands(double Value) { const int64 Rounded = static_cast(FMath::RoundToDouble(Value)); const bool bNegative = Rounded < 0; FString Digits = FString::Printf(TEXT("%lld"), bNegative ? -Rounded : Rounded); FString Out; for (int32 I = 0; I < Digits.Len(); ++I) { if (I > 0 && (Digits.Len() - I) % 3 == 0) { Out.AppendChar(TEXT(',')); } Out.AppendChar(Digits[I]); } return bNegative ? TEXT("-") + Out : Out; } FString FDescentHudModel::FormatDepth(double DepthMetres) { return FormatThousands(DepthMetres) + TEXT(" m"); } FString FDescentHudModel::FormatTemperature(double Celsius, const FString& Label) { return FString::Printf(TEXT("%s %.1f °C"), *Label, Celsius); } // --------------------------------------------------------------------------------------------------------- // The subsystem // --------------------------------------------------------------------------------------------------------- UDescentHudSubsystem* UDescentHudSubsystem::Get(const UWorld* World) { return World ? World->GetSubsystem() : nullptr; } void UDescentHudSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Collection.InitializeDependency(); Super::Initialize(Collection); if (UDescentSimSubsystem* Sim = UDescentSimSubsystem::Get(GetWorld())) { // First in the HudModel entry (SubOrder very low), so the writers that run after it see counted-down toasts. FDescentSimRegisterOptions Options; Options.Owner = TEXT("HudModel"); Options.SubOrder = -1000; StepHandle = Sim->RegisterLateStep(EDescentLateStep::HudModel, [this](const FDescentSimFrame& F) { HudModel.Advance(static_cast(F.Dt)); ++Housekeeping; }, Options); } } void UDescentHudSubsystem::Deinitialize() { if (UDescentSimSubsystem* Sim = UDescentSimSubsystem::Get(GetWorld())) { Sim->Unregister(StepHandle); } StepHandle.Reset(); Super::Deinitialize(); } // --------------------------------------------------------------------------------------------------------- // State section "ui", part "hud" (section 9.4). The UI track adds the other parts ("widgets", "window", // "cargo_display") with its own FDescentStatePartRegistrar. // --------------------------------------------------------------------------------------------------------- static FDescentStatePartRegistrar GHudPart(TEXT("ui"), TEXT("hud"), [](UWorld* World, FJsonObject& Out) { const UDescentHudSubsystem* Sub = UDescentHudSubsystem::Get(World); Out.SetBoolField(TEXT("available"), Sub != nullptr); if (!Sub) { return; } const FDescentHudModel& M = Sub->Model(); Out.SetStringField(TEXT("mode"), M.Mode.IsNone() ? FString() : M.Mode.ToString()); Out.SetNumberField(TEXT("depth_m"), Descent::Json::Round(M.DepthM)); Out.SetStringField(TEXT("depth_text"), M.DepthText); Out.SetStringField(TEXT("band"), M.Band); Out.SetStringField(TEXT("rating_text"), M.RatingText); Out.SetStringField(TEXT("prompt"), M.Prompt); Out.SetStringField(TEXT("radio"), M.Radio); Out.SetStringField(TEXT("objective"), M.Objective); Out.SetStringField(TEXT("temperature_text"), M.TemperatureText); Out.SetNumberField(TEXT("temperature_c"), Descent::Json::Round(M.TemperatureC)); Out.SetBoolField(TEXT("shear_warning"), M.Warnings.bShear); Out.SetStringField(TEXT("warning_text"), M.Warnings.Text); TArray> Toasts; for (const FDescentHudToast& T : M.Toasts) { Toasts.Add(MakeShared(T.Text)); } Out.SetArrayField(TEXT("toasts"), Toasts); TSharedRef Vitals = MakeShared(); Vitals->SetNumberField(TEXT("health"), Descent::Json::Round(M.Vitals.Health)); Vitals->SetNumberField(TEXT("nutrition"), Descent::Json::Round(M.Vitals.Nutrition)); Vitals->SetNumberField(TEXT("o2_s"), Descent::Json::Round(M.Vitals.O2Seconds)); Vitals->SetNumberField(TEXT("bio_fu"), Descent::Json::Round(M.Vitals.BioReserveFu)); Out.SetObjectField(TEXT("vitals"), Vitals); TSharedRef Vehicle = MakeShared(); Vehicle->SetBoolField(TEXT("visible"), M.Vehicle.bVisible); Vehicle->SetNumberField(TEXT("hull_pct"), Descent::Json::Round(M.Vehicle.HullPct)); Vehicle->SetNumberField(TEXT("power_pct"), Descent::Json::Round(M.Vehicle.PowerPct)); Vehicle->SetNumberField(TEXT("speed_mps"), Descent::Json::Round(M.Vehicle.SpeedMps)); Out.SetObjectField(TEXT("vehicle"), Vehicle); Out.SetNumberField(TEXT("markers"), M.Markers.Num()); Out.SetNumberField(TEXT("late_housekeeping_count"), static_cast(Sub->HousekeepingCount())); });