#include "Systems/DescentLooseOre.h" #include "Core/DescentMath.h" #include "Core/DescentSimSubsystem.h" #include "Core/Tuning/DescentTune_Tender.h" #include "DescentUnits.h" #include "World/DescentOreSubsystem.h" #include "World/DescentShear.h" #include "World/DescentTerrainSubsystem.h" #include "World/DescentWorldDef.h" #include "Components/InstancedStaticMeshComponent.h" #include "Engine/StaticMesh.h" #include "Engine/World.h" #include "GameFramework/Actor.h" #include "Materials/MaterialInstanceDynamic.h" namespace LooseTune = Descent::Tune::Tender; UDescentLooseOreSubsystem* UDescentLooseOreSubsystem::Get(const UWorld* World) { return World ? World->GetSubsystem() : nullptr; } FName UDescentLooseOreSubsystem::TypeName(uint8 Type) { static const FName Names[3] = { TEXT("ferrite"), TEXT("cobalt"), TEXT("pearl") }; return Names[FMath::Min(Type, 2)]; } void UDescentLooseOreSubsystem::OnWorldBeginPlay(UWorld& InWorld) { Super::OnWorldBeginPlay(InWorld); if (UDescentSimSubsystem* Sim = UDescentSimSubsystem::Get(&InWorld)) { FDescentSimRegisterOptions Opt; Opt.Owner = TEXT("LooseOre"); StepHandle = Sim->RegisterStep(EDescentSimStep::Ore, [this](const FDescentSimFrame& F) { Step(F.Dt); }, Opt); } if (UDescentOreSubsystem* Ore = UDescentOreSubsystem::Get(&InWorld)) { // A completed chunk inside a vein breaks loose where the drill freed it, with a small push out of the wall. FreedHandle = Ore->OnOreFreed.AddLambda([this](int32 Deposit, const FVector& PosM) { const Descent::World::FDescentWorldDef& Def = Descent::World::FDescentWorldDef::Get(); if (!Def.Deposits.IsValidIndex(Deposit)) { return; } Descent::FRng R(Seed++); const FVector Push = Def.Deposits[Deposit].N * 1.2 + FVector(R.Range(-0.6, 0.6), R.Range(-0.6, 0.6), R.Range(-0.2, 0.6)); Spawn(static_cast(Def.Deposits[Deposit].Type), PosM + Def.Deposits[Deposit].N * 0.4, Push); }); } } void UDescentLooseOreSubsystem::Deinitialize() { if (UDescentSimSubsystem* Sim = UDescentSimSubsystem::Get(GetWorld())) { Sim->Unregister(StepHandle); } if (UDescentOreSubsystem* Ore = UDescentOreSubsystem::Get(GetWorld())) { Ore->OnOreFreed.Remove(FreedHandle); } Super::Deinitialize(); } void UDescentLooseOreSubsystem::Spawn(uint8 Type, const FVector& PosM, const FVector& VelMps) { if (Chunks.Num() >= LooseTune::LooseMax) { Chunks.RemoveAt(0); // the pool is full: the oldest chunk goes } FDescentLooseChunk C; C.Type = FMath::Min(Type, 2); C.PosM = PosM; C.VelMps = VelMps; Chunks.Add(C); } int32 UDescentLooseOreSubsystem::Suck(const FVector& NozzleM, const FVector& Dir, double Range, double Pull, double Dt, TFunctionRef TryCapture) { int32 Captured = 0; for (int32 I = Chunks.Num() - 1; I >= 0; --I) { FDescentLooseChunk& C = Chunks[I]; const FVector To = NozzleM - C.PosM; const double D = To.Size(); if (D > Range) { continue; } const FVector ToN = D > 1e-6 ? To / D : FVector::ZeroVector; if (D > LooseTune::SuctionNearM && FVector::DotProduct(-ToN, Dir) < LooseTune::SuctionConeCos) { continue; } if (D <= LooseTune::SuctionCaptureM) { if (TryCapture(C.Type)) { Chunks.RemoveAtSwap(I); ++Captured; } else { C.VelMps *= -0.3; C.PosM -= ToN * 0.5; } continue; } C.VelMps += ToN * (Pull * (LooseTune::SuctionFalloff - D / Range) * Dt); } return Captured; } int32 UDescentLooseOreSubsystem::TakeNearest(const FVector& PosM, double Range) { int32 Best = INDEX_NONE; double BestD = Range; for (int32 I = 0; I < Chunks.Num(); ++I) { const double D = FVector::Dist(Chunks[I].PosM, PosM); if (D <= BestD) { BestD = D; Best = I; } } if (Best == INDEX_NONE) { return -1; } const int32 Type = Chunks[Best].Type; Chunks.RemoveAtSwap(Best); return Type; } void UDescentLooseOreSubsystem::Step(double Dt) { UDescentTerrainSubsystem* Terrain = UDescentTerrainSubsystem::Get(GetWorld()); const double Drag = FMath::Exp(-LooseTune::LooseDragK * Dt); for (FDescentLooseChunk& C : Chunks) { C.VelMps.Z -= LooseTune::LooseGravity * Dt; C.VelMps *= Drag; C.PosM += (C.VelMps + Descent::World::FDescentShear::VelocityAt(C.PosM)) * Dt; // no clamp (U7) FVector N; if (Terrain && Terrain->PushOutSphere(C.PosM, LooseTune::LooseRadiusM, &N)) { const double Into = -FVector::DotProduct(C.VelMps, N); if (Into > 0.0) { C.VelMps = (C.VelMps + N * (Into * LooseTune::LooseBounce)) * LooseTune::LooseDamp; } } } UpdateVisuals(); } void UDescentLooseOreSubsystem::UpdateVisuals() { UWorld* World = GetWorld(); if (!World || (Chunks.Num() == 0 && !VisualActor)) { return; } if (!VisualActor) { VisualActor = World->SpawnActor(); USceneComponent* Root = NewObject(VisualActor, TEXT("Root")); VisualActor->SetRootComponent(Root); Root->RegisterComponent(); UStaticMesh* Mesh = LoadObject(nullptr, TEXT("/Engine/BasicShapes/Cube.Cube")); UMaterialInterface* Parent = LoadObject(nullptr, TEXT("/Game/Materials/M_DescentSurface.M_DescentSurface"), nullptr, LOAD_NoWarn); const FLinearColor Colours[3] = { FLinearColor(1.0f, 0.33f, 0.05f), FLinearColor(0.05f, 0.78f, 1.0f), FLinearColor(1.0f, 0.1f, 0.68f) }; for (int32 T = 0; T < 3; ++T) { UInstancedStaticMeshComponent* M = NewObject(VisualActor, *FString::Printf(TEXT("Loose_%d"), T)); M->SetupAttachment(Root); M->SetStaticMesh(Mesh); M->SetCollisionEnabled(ECollisionEnabled::NoCollision); M->SetCastShadow(false); if (Parent) { UMaterialInstanceDynamic* MID = UMaterialInstanceDynamic::Create(Parent, VisualActor); MID->SetVectorParameterValue(TEXT("BaseColor"), Colours[T] * 0.4f); MID->SetVectorParameterValue(TEXT("EmissiveColor"), Colours[T]); MID->SetScalarParameterValue(TEXT("EmissiveStrength"), 0.6f); // the chunk glows faintly in its ore colour M->SetMaterial(0, MID); } M->RegisterComponent(); Meshes.Add(M); } } for (UInstancedStaticMeshComponent* M : Meshes) { M->ClearInstances(); } int32 Index = 0; for (const FDescentLooseChunk& C : Chunks) { // a faceted look from a tumbling cube: each chunk keeps its own turn const FRotator Rot(37.0 * Index, 61.0 * Index, 23.0 * Index); Meshes[C.Type]->AddInstance(FTransform(Rot, Descent::MetresToUU(C.PosM), FVector(LooseTune::LooseSizeM)), /*bWorldSpace*/ true); ++Index; } }