#pragma once #include "CoreMinimal.h" #include "Debug/DescentScenario.h" #include "Debug/DescentDebugSubsystem.h" #include "Core/DescentFlyPawn.h" #include "World/DescentTerrainSubsystem.h" #include "DescentUnits.h" #include "TheDescent.h" #include "AssetCompilingManager.h" #include "RHIStats.h" #include "RenderTimer.h" #include "ShaderCompiler.h" #include "Camera/PlayerCameraManager.h" #include "Engine/StaticMeshActor.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "Materials/MaterialInstanceDynamic.h" #include "Engine/World.h" #include "EngineUtils.h" #include "GameFramework/PlayerController.h" /** * Scenario steps of the world / look track, written on top of the builder's `Do` and `Until` (DescentScenario.h is * not edited): possess a fly pawn explicitly (the real player replaces the fly pawn as the default pawn in M2), place * the view with the pawn so its headlamp goes along, and wait until the terrain has finished streaming. */ namespace DescentWorldScn { /** Makes sure the first player controller possesses an ADescentFlyPawn and views through it. */ inline ADescentFlyPawn* EnsureFlyPawn(UWorld* World) { APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr; if (!PC) { return nullptr; } ADescentFlyPawn* Fly = Cast(PC->GetPawn()); if (!Fly) { for (TActorIterator It(World); It; ++It) { Fly = *It; break; } if (!Fly) { const FVector Where = PC->GetPawn() ? PC->GetPawn()->GetActorLocation() : FVector::ZeroVector; FActorSpawnParameters Params; Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; Fly = World->SpawnActor(Where, FRotator::ZeroRotator, Params); } if (Fly) { if (PC->GetPawn() && PC->GetPawn() != Fly) { PC->UnPossess(); } PC->Possess(Fly); } } if (Fly) { PC->SetViewTarget(Fly); } return Fly; } /** Puts the fly pawn at LocM looking at LookM (metres) and asks the terrain to stream around it. */ inline bool PlaceView(UWorld* World, const FVector& LocM, const FVector& LookM, float FovDeg = 70.f) { ADescentFlyPawn* Fly = EnsureFlyPawn(World); APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr; if (!Fly || !PC) { return false; } Fly->SetActorLocation(Descent::MetresToUU(LocM), false, nullptr, ETeleportType::TeleportPhysics); PC->SetControlRotation((LookM - LocM).Rotation()); Fly->SetActorRotation((LookM - LocM).Rotation()); if (PC->PlayerCameraManager) { PC->PlayerCameraManager->SetFOV(FovDeg); } if (UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(World)) { T->RequestReplan(); } return true; } /** Possess a fly pawn: the first step of every scenario of this track. */ inline void StartWithFlyPawn(FDescentScenarioBuilder& S) { S.Do(TEXT("possess a fly pawn"), [](FDescentScenarioContext& C) { if (!EnsureFlyPawn(C.World)) { C.Fail(TEXT("could not possess a fly pawn")); } }); } /** Moves the view to Loc looking at Look, then waits for the terrain. */ inline void ViewAt(FDescentScenarioBuilder& S, const FString& Label, const FVector& LocM, const FVector& LookM, float FovDeg = 70.f) { S.Do(FString::Printf(TEXT("view %s"), *Label), [LocM, LookM, FovDeg](FDescentScenarioContext& C) { if (!PlaceView(C.World, LocM, LookM, FovDeg)) { C.Fail(TEXT("no pawn to place")); } }); } /** Waits (wall clock, up to MaxSeconds) until no terrain job is running or queued and everything near the view is meshed. */ inline void WaitStreamIdle(FDescentScenarioBuilder& S, float MaxSeconds = 120.f) { S.Do(TEXT("replan the stream"), [](FDescentScenarioContext& C) { if (UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(C.World)) { T->RequestReplan(); } }); S.Until(TEXT("wait for the terrain stream to go idle"), [MaxSeconds](FDescentScenarioContext& C) { UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(C.World); if (!T) { C.Fail(TEXT("no terrain subsystem")); return true; } if (C.FramesInStep() > 3 && T->IsStreamIdle()) { return true; } if (C.SecondsInStep() > MaxSeconds) { C.Fail(FString::Printf(TEXT("the terrain stream did not go idle in %.0f s (pending jobs %d)"), MaxSeconds, T->GetStats().PendingJobs)); return true; } return false; }); } /** Reads a number from the state dump: Section.Field. */ inline double SnapNumber(const FDescentScenarioContext& C, const TCHAR* Section, const TCHAR* Field) { const TSharedRef Snap = C.Snapshot(); const TSharedPtr* Sec = nullptr; double V = -1.0; if (Snap->TryGetObjectField(Section, Sec) && Sec) { (*Sec)->TryGetNumberField(Field, V); } return V; } /** * A screenshot like S.Shot but it does NOT fail on a black frame: the look tests of a dark scene (lamp off, deep) are * supposed to be nearly black. Writes Saved/Shots/_