// Milestone M3 scenarios (DESIGN_UE.md section 10, "Board, launch, dive, swim, dock"): m3_launch_dock, m3_carousel, m3_rotor, // m3_no_walls (requirement U7), m3_swim, m3_crush. The player is driven through the same actions the keys call (Descent.Player.Use, // Descent.Tender.*, Descent.Player.Intent); shots are taken with the sim paused so a picture does not cost the animation its time. #include "Core/DescentMath.h" #include "Core/DescentSimSubsystem.h" #include "Core/Tuning/DescentTune_Carrier.h" #include "Core/Tuning/DescentTune_Player.h" #include "Core/Tuning/DescentTune_Tender.h" #include "Debug/DescentDebugSubsystem.h" #include "Debug/DescentScenario.h" #include "Player/DescentCharacter.h" #include "Player/DescentCharacterMovement.h" #include "Player/DescentInteraction.h" #include "Player/DescentPlayerController.h" #include "Player/DescentVitals.h" #include "UI/DescentDash.h" #include "UI/DescentHudSubsystem.h" #include "UI/DescentUiShot.h" #include "Vehicles/CarrierCarousel.h" #include "Vehicles/CarrierLayout.h" #include "Vehicles/DescentCarrier.h" #include "Vehicles/KazahTender.h" #include "Vehicles/TenderFlight.h" #include "World/DescentShear.h" #include "World/DescentTerrainSubsystem.h" #include "World/DescentWorldDef.h" #include "World/DescentWorldScenarioSteps.h" #include "DescentUnits.h" #include "TheDescent.h" #include "Components/StaticMeshComponent.h" #include "Engine/World.h" namespace { namespace M3T = Descent::Tune::Tender; namespace M3P = Descent::Tune::Player; const FVector M3CarrierOrigin(0.0, 0.0, Descent::Tune::Carrier::StartZM); // the carrier does not move in these scenarios const FVector M3Pocket(5000.0, 0.0, -300.0); // outside every v2 bound (v2: 560 x 700 x 600 m) ADescentPlayerController* M3PC(const FDescentScenarioContext& C) { return C.World ? Cast(C.World->GetFirstPlayerController()) : nullptr; } ADescentCharacter* M3Char(const FDescentScenarioContext& C) { ADescentPlayerController* PC = M3PC(C); return PC ? PC->GetDescentCharacter() : nullptr; } UCarrierCarouselComponent* M3Carousel(const FDescentScenarioContext& C) { ADescentPlayerController* PC = M3PC(C); return PC ? PC->GetCarousel() : nullptr; } AKazahTender* M3Tender(const FDescentScenarioContext& C, int32 Index = 0) { const UDescentTenderFleet* Fleet = UDescentTenderFleet::Get(C.World); return Fleet ? Fleet->At(Index) : nullptr; } FDescentHudModel* M3Hud(const FDescentScenarioContext& C) { UDescentHudSubsystem* Hud = UDescentHudSubsystem::Get(C.World); return Hud ? &Hud->Model() : nullptr; } /** Waits (sim time) until the player's mode is M. */ void M3WaitMode(FDescentScenarioBuilder& S, EDescentMode M, double MaxSimSeconds = 12.0) { TSharedRef Start = MakeShared(-1.0); S.Until(FString::Printf(TEXT("mode becomes %s"), DescentModeName(M)), [M, MaxSimSeconds, Start](FDescentScenarioContext& C) { const ADescentPlayerController* PC = M3PC(C); const UDescentSimSubsystem* Sim = C.Sim(); if (!PC || !Sim) { C.Fail(TEXT("no player controller")); return true; } if (*Start < 0.0) { *Start = Sim->SimTime(); } if (PC->GetMode() == M) { return true; } if (Sim->SimTime() - *Start > MaxSimSeconds) { C.Fail(FString::Printf(TEXT("mode is %s after %.1f s of sim, expected %s"), DescentModeName(PC->GetMode()), MaxSimSeconds, DescentModeName(M))); return true; } return false; }); } void M3ExpectMode(FDescentScenarioBuilder& S, EDescentMode M) { S.Expect(FString::Printf(TEXT("mode is %s"), DescentModeName(M)), [M](FDescentScenarioContext& C) { const ADescentPlayerController* PC = M3PC(C); if (!PC || PC->GetMode() != M) { C.Fail(FString::Printf(TEXT("mode is %s, expected %s"), PC ? DescentModeName(PC->GetMode()) : TEXT("(none)"), DescentModeName(M))); return false; } return true; }); } /** Gives the view back to what the player would see (the Tender's cockpit camera, or the body's eyes). */ void M3ViewPlayer(FDescentScenarioBuilder& S) { S.Do(TEXT("view: the player's own eyes"), [](FDescentScenarioContext& C) { ADescentPlayerController* PC = M3PC(C); if (!PC) { return; } AActor* Target = PC->GetTender() ? static_cast(PC->GetTender()) : static_cast(PC->GetDescentCharacter()); if (Target) { PC->SetViewTarget(Target); } }); S.WaitFrames(2); } /** Board with the command, wait for Docked, launch, wait for Sub. */ void M3BoardAndLaunch(FDescentScenarioBuilder& S) { S.Exec(TEXT("Descent.Tender.Board")); M3WaitMode(S, EDescentMode::Docked); S.Exec(TEXT("Descent.Tender.Launch")); M3WaitMode(S, EDescentMode::Sub); } /** Carves a room around Centre: a sphere of radius R at the centre and one at +-Step on each axis. */ void M3CarveRoom(FDescentScenarioBuilder& S, const FVector& Centre, double R, double Step) { const FVector Offsets[7] = { FVector(0, 0, 0), FVector(Step, 0, 0), FVector(-Step, 0, 0), FVector(0, Step, 0), FVector(0, -Step, 0), FVector(0, 0, Step), FVector(0, 0, -Step) }; for (const FVector& O : Offsets) { const FVector P = Centre + O; S.Exec(FString::Printf(TEXT("Descent.World.Carve %.1f %.1f %.1f %.1f"), P.X, P.Y, P.Z, R)); } } /** Pauses the sim, takes a picture from a fixed camera, resumes. */ void M3PausedShot(FDescentScenarioBuilder& S, const FString& Label, const FVector& CamM, const FVector& TargetM, float Fov) { S.Exec(TEXT("Descent.Sim.Pause 1")); S.LookAt(CamM, TargetM, Fov); S.Shot(Label); S.Exec(TEXT("Descent.Sim.Pause 0")); } /** The piloted Tender flies with an intent for Seconds of sim, without rendering. */ void M3Fly(FDescentScenarioBuilder& S, double Fwd, double Right, double Up, double Seconds, bool bBoost) { S.Exec(FString::Printf(TEXT("Descent.Player.Intent %.2f %.2f %.2f 0 0 %.2f 0 0 %d"), Fwd, Right, Up, Seconds, bBoost ? 1 : 0)); S.FastForward(Seconds); } /** * A picture WITH the HUD (fix list after M3: every other shot is taken with the UI off, so none showed the real HUD over real play). * Saved/Shots/_