#pragma once // Helpers shared by the scenario files of M7 and later (the earlier files carry their own copies). #include "CoreMinimal.h" #include "Core/Tuning/DescentTune_Carrier.h" #include "Debug/DescentDebugSubsystem.h" #include "Debug/DescentScenario.h" #include "Debug/DescentStateRegistry.h" #include "Player/DescentCharacter.h" #include "Player/DescentPlayerController.h" #include "UI/DescentUiShot.h" #include "Vehicles/DescentCarrier.h" #include "Vehicles/KazahTender.h" #include "TheDescent.h" #include "Dom/JsonObject.h" #include "Serialization/JsonSerializer.h" #include "Serialization/JsonWriter.h" namespace Descent::Scn { inline ADescentPlayerController* PC(const FDescentScenarioContext& C) { return C.World ? Cast(C.World->GetFirstPlayerController()) : nullptr; } inline ADescentCharacter* Char(const FDescentScenarioContext& C) { ADescentPlayerController* P = PC(C); return P ? P->GetDescentCharacter() : nullptr; } inline ADescentCarrier* Carrier(const FDescentScenarioContext& C) { return UDescentCarrierSubsystem::GetCarrier(C.World); } /** The player's feet in carrier-local metres. */ inline FVector FeetM(const FDescentScenarioContext& C) { const ADescentCharacter* P = Char(C); const ADescentCarrier* K = Carrier(C); return (P && K) ? K->ToLocalM(P->GetFeetWorldUU()) : FVector::ZeroVector; } /** A screenshot with the HUD and any open window on it. */ inline void HudShot(FDescentScenarioBuilder& S, const FString& Label) { struct FSt { int32 Phase = 0; bool bDone = false; Descent::Ui::FUiShotResult R; }; TSharedRef St = MakeShared(); S.WaitFrames(8); // the HUD model is written at 10 Hz, the widgets read it the frame after S.Until(FString::Printf(TEXT("HudShot(%s)"), *Label), [St, Label](FDescentScenarioContext& C) { if (St->Phase == 0) { TSharedRef Keep = St; if (!Descent::Ui::RequestUiShot(C.ScenarioName + TEXT("_") + Label, [Keep](const Descent::Ui::FUiShotResult& R) { Keep->R = R; Keep->bDone = true; }) && !St->bDone) { C.Fail(TEXT("the UI screenshot request was refused")); return true; } St->Phase = 1; return false; } if (!St->bDone) { return false; } if (!St->R.bOk) { C.Fail(FString::Printf(TEXT("HudShot(%s) failed: %s"), *Label, *St->R.Error)); return true; } UE_LOG(LogDescent, Display, TEXT("HudShot(%s): %s %dx%d mean luma %.3f"), *Label, *St->R.Path, St->R.Size.X, St->R.Size.Y, St->R.Stats.MeanLuma); return true; }); } /** Back to the player's own view after a Camera / LookAt shot. */ inline void ViewPlayer(FDescentScenarioBuilder& S) { S.Do(TEXT("view: the player"), [](FDescentScenarioContext& C) { ADescentPlayerController* P = PC(C); if (!P) { return; } AActor* Target = P->GetTender() ? static_cast(P->GetTender()) : static_cast(P->GetDescentCharacter()); if (Target) { P->SetViewTarget(Target); } }); S.WaitFrames(2); } /** A paused shot from a camera given in carrier-local metres. */ inline void LocalShot(FDescentScenarioBuilder& S, const FString& Label, const FVector& CamLocalM, const FVector& LookLocalM, float Fov = 70.f) { S.Exec(TEXT("Descent.Sim.Pause 1")); S.Do(TEXT("camera for ") + Label, [CamLocalM, LookLocalM, Fov](FDescentScenarioContext& C) { const ADescentCarrier* K = Carrier(C); if (!K) { return; } const FVector Cam = K->ToWorldUU(CamLocalM) / 100.0, Look = K->ToWorldUU(LookLocalM) / 100.0; C.Debug->PlaceCamera(Cam, (Look - Cam).Rotation(), Fov); }); S.Shot(Label); S.Exec(TEXT("Descent.Sim.Pause 0")); ViewPlayer(S); } /** One state section as condensed JSON text ("" if there is none). */ inline FString SectionText(UWorld* World, const TCHAR* Section) { const TSharedRef Snap = FDescentStateRegistry::Get().BuildSnapshot(World, TEXT("probe")); const TSharedPtr* Obj = nullptr; FString Out; if (Snap->TryGetObjectField(FStringView(Section), Obj) && Obj && Obj->IsValid()) { const TSharedRef>> Writer = TJsonWriterFactory>::Create(&Out); FJsonSerializer::Serialize(Obj->ToSharedRef(), Writer); } return Out; } /** A number of a section, e.g. ("terrain", "chunks_modified"). NaN if missing. */ inline double SectionNumber(UWorld* World, const TCHAR* Section, const TCHAR* Field) { const TSharedRef Snap = FDescentStateRegistry::Get().BuildSnapshot(World, TEXT("probe")); const TSharedPtr* Obj = nullptr; double V = NAN; if (Snap->TryGetObjectField(FStringView(Section), Obj) && Obj && Obj->IsValid()) { (*Obj)->TryGetNumberField(FStringView(Field), V); } return V; } }