// Console commands of the world and terrain (DESIGN_UE.md section 9.3): Goto, Density, Carve, Cleanup, Slice, // NearestDeposit, ClearRoute. One commands file per system. #include "Debug/DescentCommandUtil.h" #include "Debug/DescentDebugSubsystem.h" #include "World/DescentOreSubsystem.h" #include "World/DescentRoutes.h" #include "World/DescentTerrainSubsystem.h" #include "World/DescentWorldDef.h" #include "World/DescentWorldSlice.h" #include "DescentUnits.h" #include "TheDescent.h" #include "Engine/World.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "HAL/IConsoleManager.h" namespace { /** Where the controlled body is, metres. */ bool WorldCmdBodyLocation(UWorld* World, FVector& OutM) { APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr; const AActor* Target = PC ? (PC->GetPawn() ? static_cast(PC->GetPawn()) : PC->GetViewTarget()) : nullptr; if (!Target) { return false; } OutM = Descent::UUToMetres(Target->GetActorLocation()); return true; } void CmdWorldGoto(const TArray& Args, UWorld* World) { if (Args.Num() < 1) { Descent::Cmd::Fail(World, TEXT("Descent.World.Goto"), TEXT("usage: Descent.World.Goto (start canyon_mid wellA shelfA needle h1 bore void kilo7 drop shelfC hairpin undercroft antiphon gullet goal abyss hollow honest grotto sieve spur_e1)")); return; } FString Name = Args[0]; if (Name.StartsWith(TEXT("carrier:"))) { Descent::Cmd::Fail(World, TEXT("Descent.World.Goto"), TEXT("the carrier is not part of this build yet (carrier: prefix needs milestone M2)")); return; } const Descent::World::FPlace* Place = Descent::World::FDescentWorldDef::Get().FindPlace(FName(*Name)); if (!Place) { Descent::Cmd::Fail(World, TEXT("Descent.World.Goto"), FString::Printf(TEXT("no place called '%s'"), *Name)); return; } if (!UDescentDebugSubsystem::Teleport(World, Place->PosM)) { Descent::Cmd::Fail(World, TEXT("Descent.World.Goto"), TEXT("no pawn or view target to move")); return; } if (APlayerController* PC = World->GetFirstPlayerController()) { PC->SetControlRotation((Place->LookAtM - Place->PosM).Rotation()); } if (UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(World)) { T->RequestReplan(); } UE_LOG(LogDescent, Display, TEXT("World.Goto: %s at %.1f %.1f %.1f m"), *Name, Place->PosM.X, Place->PosM.Y, Place->PosM.Z); } void CmdWorldDensity(const TArray& Args, UWorld* World) { TArray N; UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(World); if (!T || !Descent::Cmd::ParseNumbers(Args, 0, 3, N)) { Descent::Cmd::Fail(World, TEXT("Descent.World.Density"), TEXT("usage: Descent.World.Density x y z (metres; needs a running game)")); return; } T->LastDensity = T->Density(FVector(N[0], N[1], N[2])); UE_LOG(LogDescent, Display, TEXT("World.Density(%.2f, %.2f, %.2f) = %.4f (%s)"), N[0], N[1], N[2], T->LastDensity, T->LastDensity > 0.0 ? TEXT("rock") : TEXT("water")); } void CmdWorldCarve(const TArray& Args, UWorld* World) { TArray N; UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(World); if (!T || !Descent::Cmd::ParseNumbers(Args, 0, 4, N) || N[3] <= 0.0) { Descent::Cmd::Fail(World, TEXT("Descent.World.Carve"), TEXT("usage: Descent.World.Carve x y z radius (metres; needs a running game)")); return; } const int32 Removed = T->Carve(FVector(N[0], N[1], N[2]), N[3], 999.0, 1.0, true); UE_LOG(LogDescent, Display, TEXT("World.Carve(%.2f, %.2f, %.2f, r %.2f): %d samples removed, ore mined total %lld"), N[0], N[1], N[2], N[3], Removed, UDescentOreSubsystem::Get(World) ? UDescentOreSubsystem::Get(World)->GetMinedTotal() : 0); } void CmdWorldCleanup(const TArray& Args, UWorld* World) { TArray N; UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(World); if (!T || !Descent::Cmd::ParseNumbers(Args, 0, 4, N) || N[3] <= 0.0) { Descent::Cmd::Fail(World, TEXT("Descent.World.Cleanup"), TEXT("usage: Descent.World.Cleanup x y z radius (metres; needs a running game)")); return; } const int32 Freed = T->CleanupFloating(FVector(N[0], N[1], N[2]), N[3]); UE_LOG(LogDescent, Display, TEXT("World.Cleanup(%.2f, %.2f, %.2f, r %.2f): %d samples freed, %d unconnected left"), N[0], N[1], N[2], N[3], Freed, T->FloatingAfterCleanup); } void CmdWorldSlice(const TArray& Args, UWorld* World) { TArray N; if (Args.Num() < 3 || Args[0].Len() != 1 || !Descent::Cmd::ParseNumbers(Args, 1, 1, N)) { Descent::Cmd::Fail(World, TEXT("Descent.World.Slice"), TEXT("usage: Descent.World.Slice [metresPerPixel=2]")); return; } double Mpp = 2.0; if (Args.Num() > 3) { TArray M; if (!Descent::Cmd::ParseNumbers(Args, 3, 1, M)) { Descent::Cmd::Fail(World, TEXT("Descent.World.Slice"), TEXT("metresPerPixel must be a number")); return; } Mpp = M[0]; } FString PathOrError; if (Descent::World::WriteSlicePng(World, Args[0][0], N[0], Args[2], Mpp, PathOrError)) { UE_LOG(LogDescent, Display, TEXT("World.Slice: wrote %s"), *PathOrError); } else { Descent::Cmd::Fail(World, TEXT("Descent.World.Slice"), PathOrError); } } void CmdWorldNearestDeposit(const TArray& Args, UWorld* World) { UDescentOreSubsystem* Ore = UDescentOreSubsystem::Get(World); FVector Where; if (!Ore || !WorldCmdBodyLocation(World, Where)) { Descent::Cmd::Fail(World, TEXT("Descent.World.NearestDeposit"), TEXT("needs a running game with a pawn or view")); return; } int32 Type = -1; if (Args.Num() > 0) { TArray N; if (Args[0].Equals(TEXT("ferrite"), ESearchCase::IgnoreCase)) { Type = 0; } else if (Args[0].Equals(TEXT("cobalt"), ESearchCase::IgnoreCase)) { Type = 1; } else if (Args[0].Equals(TEXT("pearl"), ESearchCase::IgnoreCase)) { Type = 2; } else if (Descent::Cmd::ParseNumbers(Args, 0, 1, N)) { Type = static_cast(N[0]); } else { Descent::Cmd::Fail(World, TEXT("Descent.World.NearestDeposit"), TEXT("usage: Descent.World.NearestDeposit [ferrite|cobalt|pearl|0|1|2]")); return; } } if (!Ore->QueryNearest(Where, Type)) { Descent::Cmd::Fail(World, TEXT("Descent.World.NearestDeposit"), TEXT("no deposit of that type")); return; } const UDescentOreSubsystem::FQuery& Q = Ore->LastQuery; UE_LOG(LogDescent, Display, TEXT("World.NearestDeposit: #%d %s at %.1f %.1f %.1f m, yield %d, mined %d"), Q.Index, Descent::World::OreName(static_cast(Q.Type)), Q.PosM.X, Q.PosM.Y, Q.PosM.Z, Q.Yield, Q.Mined); } void CmdWorldClearRoute(const TArray& Args, UWorld* World) { TArray N; UDescentTerrainSubsystem* T = UDescentTerrainSubsystem::Get(World); if (!T || !Descent::Cmd::ParseNumbers(Args, 0, 1, N) || N[0] < 1 || N[0] > 3) { Descent::Cmd::Fail(World, TEXT("Descent.World.ClearRoute"), TEXT("usage: Descent.World.ClearRoute ")); return; } const int32 Calls = Descent::World::FDescentRoutes::ClearRoute(*T, static_cast(N[0])); UE_LOG(LogDescent, Display, TEXT("World.ClearRoute %d: %d carve calls"), static_cast(N[0]), Calls); } FAutoConsoleCommandWithWorldAndArgs GWorldCmdGoto(TEXT("Descent.World.Goto"), TEXT("Descent.World.Goto - move the controlled body to a named place"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&CmdWorldGoto)); FAutoConsoleCommandWithWorldAndArgs GWorldCmdDensity(TEXT("Descent.World.Density"), TEXT("Descent.World.Density x y z - density of the terrain at a point (metres)"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&CmdWorldDensity)); FAutoConsoleCommandWithWorldAndArgs GWorldCmdCarve(TEXT("Descent.World.Carve"), TEXT("Descent.World.Carve x y z r - carve a sphere out of the rock"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&CmdWorldCarve)); FAutoConsoleCommandWithWorldAndArgs GWorldCmdCleanup(TEXT("Descent.World.Cleanup"), TEXT("Descent.World.Cleanup x y z r - remove rock fragments left floating near a point"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&CmdWorldCleanup)); FAutoConsoleCommandWithWorldAndArgs GWorldCmdSlice(TEXT("Descent.World.Slice"), TEXT("Descent.World.Slice [mpp] - map of the world cut by a plane"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&CmdWorldSlice)); FAutoConsoleCommandWithWorldAndArgs GWorldCmdNearestDeposit(TEXT("Descent.World.NearestDeposit"), TEXT("Descent.World.NearestDeposit [type] - fill ore.query with the nearest vein"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&CmdWorldNearestDeposit)); FAutoConsoleCommandWithWorldAndArgs GWorldCmdClearRoute(TEXT("Descent.World.ClearRoute"), TEXT("Descent.World.ClearRoute <1..3> - carve the carrier's swept volume along a route"), FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(&CmdWorldClearRoute)); }