#include "Player/DescentTools.h" #include "Core/DescentSimSubsystem.h" #include "Debug/DescentCommandUtil.h" #include "Debug/DescentStateRegistry.h" #include "Player/DescentCharacter.h" #include "Systems/DescentCraft.h" #include "Systems/DescentSave.h" #include "UI/DescentHudSubsystem.h" #include "World/DescentTerrainSubsystem.h" #include "DescentUnits.h" #include "TheDescent.h" #include "Components/PointLightComponent.h" #include "Components/StaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "Engine/World.h" #include "HAL/IConsoleManager.h" #include "Materials/MaterialInstanceDynamic.h" namespace { constexpr double FlareBurnSec = 120.0, FlareFadeSec = 10.0, FlareSpeedMps = 7.0, FlareGravity = -0.35, FlareDrag = 0.8, FlareRadiusM = 26.0; constexpr int32 FlareMax = 6; constexpr float FlareCandelas = 900.f; UMaterialInstanceDynamic* ToolSurface(UObject* Outer, const FLinearColor& Emissive, float Strength) { UMaterialInterface* Parent = LoadObject(nullptr, TEXT("/Game/Materials/M_DescentSurface.M_DescentSurface"), nullptr, LOAD_NoWarn); if (!Parent) { return nullptr; } UMaterialInstanceDynamic* MID = UMaterialInstanceDynamic::Create(Parent, Outer); MID->SetVectorParameterValue(TEXT("BaseColor"), Emissive * 0.1f); MID->SetVectorParameterValue(TEXT("EmissiveColor"), Emissive); MID->SetScalarParameterValue(TEXT("EmissiveStrength"), Strength); return MID; } } UDescentToolsSubsystem* UDescentToolsSubsystem::Get(const UWorld* World) { return World ? World->GetSubsystem() : nullptr; } void UDescentToolsSubsystem::OnWorldBeginPlay(UWorld& InWorld) { Super::OnWorldBeginPlay(InWorld); if (UDescentSimSubsystem* Sim = UDescentSimSubsystem::Get(&InWorld)) { FDescentSimRegisterOptions Opt; Opt.Owner = TEXT("Tools"); Opt.SubOrder = 10; StepHandle = Sim->RegisterStep(EDescentSimStep::Fauna, [this](const FDescentSimFrame& F) { Step(F.Dt); }, Opt); } if (UDescentSaveSubsystem* Save = UDescentSaveSubsystem::Get(&InWorld)) { Save->OnCapture.AddWeakLambda(this, [this](UDescentSaveGame* S) { S->Markers = Markers; }); Save->OnApply.AddWeakLambda(this, [this](const UDescentSaveGame* S) { for (UStaticMeshComponent* M : BeaconMeshes) { if (M) { M->DestroyComponent(); } } BeaconMeshes.Reset(); const TArray Saved = S->Markers; Markers.Reset(); for (const FVector& P : Saved) { DropBeacon(P); } }); } } void UDescentToolsSubsystem::Deinitialize() { if (UDescentSimSubsystem* Sim = UDescentSimSubsystem::Get(GetWorld())) { Sim->Unregister(StepHandle); } Super::Deinitialize(); } void UDescentToolsSubsystem::EnsureActor() { if (Visual || !GetWorld()) { return; } Visual = GetWorld()->SpawnActor(); USceneComponent* Root = NewObject(Visual, TEXT("Root")); Visual->SetRootComponent(Root); Root->RegisterComponent(); } bool UDescentToolsSubsystem::ThrowFlare(const FVector& FromM, const FVector& Dir) { EnsureActor(); if (!Visual || Flares.Num() >= FlareMax) { return false; } FFlare F; F.P = FromM; F.V = Dir.GetSafeNormal() * FlareSpeedMps; UStaticMeshComponent* M = NewObject(Visual); M->SetMobility(EComponentMobility::Movable); M->SetupAttachment(Visual->GetRootComponent()); M->SetStaticMesh(LoadObject(nullptr, TEXT("/Engine/BasicShapes/Cube.Cube"))); M->SetCollisionEnabled(ECollisionEnabled::NoCollision); M->SetCastShadow(false); M->SetWorldScale3D(FVector(0.22, 0.05, 0.05)); if (UMaterialInstanceDynamic* MID = ToolSurface(Visual, FLinearColor(1.0f, 0.25f, 0.12f), 8.f)) { M->SetMaterial(0, MID); } M->RegisterComponent(); UPointLightComponent* L = NewObject(Visual); L->SetMobility(EComponentMobility::Movable); L->SetupAttachment(Visual->GetRootComponent()); L->SetIntensityUnits(ELightUnits::Candelas); L->SetIntensity(FlareCandelas); L->SetAttenuationRadius(static_cast(Descent::MetresToUU(FlareRadiusM))); L->SetLightColor(FLinearColor(1.0f, 0.32f, 0.2f)); L->SetCastShadows(false); L->RegisterComponent(); F.Light = L; F.Mesh = M; Flares.Add(F); ++FlaresThrown; return true; } bool UDescentToolsSubsystem::DropBeacon(const FVector& PosM) { EnsureActor(); if (!Visual) { return false; } Markers.Add(PosM); UStaticMeshComponent* M = NewObject(Visual); M->SetMobility(EComponentMobility::Movable); M->SetupAttachment(Visual->GetRootComponent()); M->SetStaticMesh(LoadObject(nullptr, TEXT("/Engine/BasicShapes/Sphere.Sphere"))); M->SetCollisionEnabled(ECollisionEnabled::NoCollision); M->SetCastShadow(false); M->SetWorldLocation(Descent::MetresToUU(PosM)); M->SetWorldScale3D(FVector(0.3)); if (UMaterialInstanceDynamic* MID = ToolSurface(Visual, FLinearColor(1.0f, 0.6f, 0.1f), 6.f)) { M->SetMaterial(0, MID); } M->RegisterComponent(); BeaconMeshes.Add(M); return true; } bool UDescentToolsSubsystem::UseSelected(ADescentCharacter* User, bool bSwimming) { UDescentCraftSubsystem* Craft = UDescentCraftSubsystem::Get(GetWorld()); UDescentHudSubsystem* Hud = UDescentHudSubsystem::Get(GetWorld()); if (!User || !Craft) { return false; } const FName Item = Craft->SelectedItem(); if (Item == TEXT("flashlight")) { User->SetFlashlight(!User->IsFlashlightOn()); return true; } if (Item != TEXT("flare") && Item != TEXT("beacon")) { return false; } if (!bSwimming) { if (Hud) { Hud->Model().PushToast(TEXT("Flares and beacons work in the water")); } return false; } const FVector Eye = User->EyeWorldUU() / 100.0, Dir = User->GetControlRotation().Vector(); if (Item == TEXT("flare")) { if (Flares.Num() >= FlareMax) { if (Hud) { Hud->Model().PushToast(TEXT("Six flares are burning already")); } return false; } if (!Craft->RemoveItem(Item, 1)) { return false; } return ThrowFlare(Eye + Dir * 0.6, Dir); } if (!Craft->RemoveItem(Item, 1)) { return false; } if (Hud) { Hud->Model().PushToast(TEXT("Marker beacon placed"), EDescentHudToastKind::Good); } return DropBeacon(Eye + Dir * 1.2); } void UDescentToolsSubsystem::Step(double Dt) { UDescentTerrainSubsystem* Terrain = UDescentTerrainSubsystem::Get(GetWorld()); const double Time = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0; for (int32 I = Flares.Num() - 1; I >= 0; --I) { FFlare& F = Flares[I]; F.Age += Dt; if (!F.bRest) { F.V.Z += FlareGravity * Dt; F.V *= FMath::Exp(-FlareDrag * Dt); const FVector Next = F.P + F.V * Dt; if (Terrain && Terrain->Density(Next) > 0.0) { F.bRest = true; } else { F.P = Next; } // it rests on rock } const double Fade = FMath::Clamp((FlareBurnSec - F.Age) / FlareFadeSec, 0.0, 1.0); if (UPointLightComponent* L = F.Light.Get()) { L->SetWorldLocation(Descent::MetresToUU(F.P)); L->SetIntensity(FlareCandelas * static_cast(Fade * (0.85 + 0.15 * FMath::Sin(Time * 23.0 + I)))); } if (UStaticMeshComponent* M = F.Mesh.Get()) { M->SetWorldLocation(Descent::MetresToUU(F.P)); } if (F.Age >= FlareBurnSec) { if (UPointLightComponent* L = F.Light.Get()) { L->DestroyComponent(); } if (UStaticMeshComponent* M = F.Mesh.Get()) { M->DestroyComponent(); } Flares.RemoveAt(I); } } // the beacons as HUD markers if (UDescentHudSubsystem* Hud = UDescentHudSubsystem::Get(GetWorld())) { TArray& Out = Hud->Model().Markers; Out.RemoveAll([](const FDescentHudMarker& M) { return M.Id.ToString().StartsWith(TEXT("beacon")); }); for (int32 I = 0; I < Markers.Num(); ++I) { FDescentHudMarker M; M.Id = FName(*FString::Printf(TEXT("beacon%d"), I + 1)); M.Label = FString::Printf(TEXT("BEACON %d"), I + 1); M.WorldUU = Descent::MetresToUU(Markers[I]); M.Color = FLinearColor(1.0f, 0.7f, 0.2f); Out.Add(M); } } } static FDescentStateProviderRegistrar GToolsStateSection(TEXT("tools"), [](UWorld* World, FJsonObject& Out) { const UDescentToolsSubsystem* S = UDescentToolsSubsystem::Get(World); Out.SetBoolField(TEXT("available"), S != nullptr); if (!S) { return; } Out.SetNumberField(TEXT("flares_alive"), S->FlaresAlive()); Out.SetNumberField(TEXT("flares_thrown"), S->FlaresThrown); Out.SetNumberField(TEXT("beacons"), S->Markers.Num()); });