#include "Player/DescentCharacterMovement.h" #include "Core/DescentMath.h" #include "Core/Tuning/DescentTune_Player.h" #include "Core/Tuning/DescentTune_Carrier.h" #include "Core/Tuning/DescentTune_Tender.h" #include "DescentUnits.h" #include "Vehicles/CarrierHullShape.h" #include "Vehicles/DescentCarrier.h" #include "Vehicles/KazahTender.h" #include "Vehicles/TenderFlight.h" #include "World/DescentShear.h" #include "World/DescentTerrainSubsystem.h" #include "Components/PrimitiveComponent.h" #include "GameFramework/Character.h" UDescentCharacterMovement::UDescentCharacterMovement(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { namespace T = Descent::Tune::Player; MaxWalkSpeed = static_cast(T::WalkSpeedMps * 100.0); // the character sets it each tick (walk / sprint / carry) MaxAcceleration = 4000.f; GroundFriction = 8.f; BrakingDecelerationWalking = 4000.f; JumpZVelocity = static_cast(T::JumpSpeedMps * 100.0); GravityScale = static_cast(T::GravityMps2 * 100.0 / 980.0); MaxStepHeight = static_cast(T::StepHeightM * 100.0); SetWalkableFloorAngle(static_cast(T::WalkableAngleDeg)); bOrientRotationToMovement = false; bUseControllerDesiredRotation = false; // Section 3.1: the carrier is moved before this tick; follow its full transform, keep the capsule upright, jump with the hull. bFastAttachedMove = true; bStayBasedInAir = true; StayBasedInAirHeight = T::StayBasedInAirHeightCm; bImpartBaseVelocityX = true; bImpartBaseVelocityY = true; bImpartBaseVelocityZ = true; bIgnoreBaseRotation = false; } void UDescentCharacterMovement::CalcVelocity(float DeltaTime, float Friction, bool bFluid, float BrakingDeceleration) { namespace T = Descent::Tune::Player; if (!HasValidData() || DeltaTime < MIN_TICK_TIME || !(IsMovingOnGround() || IsFalling())) { Super::CalcVelocity(DeltaTime, Friction, bFluid, BrakingDeceleration); return; } FVector Wish = FVector::ZeroVector; if (!Acceleration.IsNearlyZero()) { const double Analog = FMath::Clamp(Acceleration.Size2D() / FMath::Max(1.0, static_cast(GetMaxAcceleration())), 0.0, 1.0); Wish = Acceleration.GetSafeNormal2D() * GetMaxSpeed() * Analog; } const double K = IsFalling() ? T::AirDampK : T::VelocityDampK; Velocity.X = Descent::Damp(Velocity.X, Wish.X, K, DeltaTime); Velocity.Y = Descent::Damp(Velocity.Y, Wish.Y, K, DeltaTime); if (Wish.IsNearlyZero() && FMath::Abs(Velocity.X) < 0.5 && FMath::Abs(Velocity.Y) < 0.5) { Velocity.X = 0.0; Velocity.Y = 0.0; } } // ---- swimming (section 5.3) ------------------------------------------------------------------------------------------ void UDescentCharacterMovement::StartSwim() { SetMovementMode(MOVE_Custom, EDescentCustomMove::Swim); SetBase(static_cast(nullptr)); // a swimmer is not carried by the deck it left SwimWish = FVector::ZeroVector; } void UDescentCharacterMovement::PhysCustom(float DeltaTime, int32 Iterations) { namespace T = Descent::Tune::Player; if (CustomMovementMode != EDescentCustomMove::Swim || DeltaTime < MIN_TICK_TIME || !UpdatedComponent) { Super::PhysCustom(DeltaTime, Iterations); return; } UWorld* World = GetWorld(); const double Dt = DeltaTime; const double MaxS = (bSwimSprint ? T::SwimSprintMps : T::SwimSpeedMps) * HungerMul; Velocity = FMath::Lerp(Velocity, Descent::MetresToUU(SwimWish.GetClampedToMaxSize(1.0) * MaxS), 1.0 - FMath::Exp(-T::SwimResponseK * Dt)); Velocity.Z -= Descent::MetresToUU(T::SwimSinkMps2) * Dt; // Position: velocity plus the Shear, swept so physics objects (Tender collision boxes, the shaft walls) block. NO CLAMP (U7). const FVector PosM0 = Descent::UUToMetres(UpdatedComponent->GetComponentLocation()); const FVector Delta = (Velocity + Descent::MetresToUU(Descent::World::FDescentShear::VelocityAt(PosM0))) * Dt; FHitResult Hit(1.f); SafeMoveUpdatedComponent(Delta, UpdatedComponent->GetComponentQuat(), true, Hit); if (Hit.IsValidBlockingHit()) { const double Into = -FVector::DotProduct(Velocity, Hit.Normal); if (Into > 0.0) { Velocity += Hit.Normal * Into; } SlideAlongSurface(Delta, 1.f - Hit.Time, Hit.Normal, Hit, true); } auto RemoveInto = [this](const FVector& OutwardNormal) { const double Into = -FVector::DotProduct(Velocity, OutwardNormal); if (Into > 0.0) { Velocity += OutwardNormal * Into; } }; FVector P = Descent::UUToMetres(UpdatedComponent->GetComponentLocation()); bool bMoved = false; SwimContacts = 0; // Rock: a 0.45 m sphere against the density field, twice. if (UDescentTerrainSubsystem* Terrain = UDescentTerrainSubsystem::Get(World)) { for (int32 I = 0; I < 2; ++I) { FVector N; if (Terrain->PushOutSphere(P, T::SwimSphereM, &N)) { RemoveInto(N); bMoved = true; ++SwimContacts; } } } if (const ADescentCarrier* Carrier = UDescentCarrierSubsystem::GetCarrier(World)) { // The hull is an analytic shape (no physics collision); the pool shaft is open water for a swimmer. FVector Local = Carrier->ToLocalM(Descent::MetresToUU(P)); FVector NLocal; bool bLocalMoved = Descent::Carrier::HullPushOut(Local, T::SwimHullRadiusM, /*bOpenPool*/ true, &NLocal); if (bLocalMoved) { RemoveInto(Carrier->GetActorTransform().TransformVectorNoScale(NLocal)); } // The pool is the only free surface: the head stays 0.35 m under it (nobody swims in air). Eased, so a drop from the deck sinks in. namespace CT = Descent::Tune::Carrier; const double EyeAbove = CharacterOwner ? CharacterOwner->BaseEyeHeight / 100.0 : 0.0; if (Local.X >= CT::PoolX0 && Local.X <= CT::PoolX1 && Local.Y >= CT::PoolY0 && Local.Y <= CT::PoolY1 && Local.Z < CT::DeckFloorZ1 + 2.0) { const double Excess = (Local.Z + EyeAbove) - (CT::PoolWaterZ - T::SwimHeadBelowSurfaceM); if (Excess > 0.0) { Local.Z -= FMath::Min(Excess, 2.5 * Dt); Velocity.Z = FMath::Min(Velocity.Z, 0.0); bLocalMoved = true; } } if (bLocalMoved) { P = Descent::UUToMetres(Carrier->ToWorldUU(Local)); bMoved = true; } } // Tenders that are out: kept 2.2 m from the origin (their boxes already block the sweep; this covers a Tender drifting into the swimmer). if (const UDescentTenderFleet* Fleet = UDescentTenderFleet::Get(World)) { for (const TWeakObjectPtr& W : Fleet->All()) { const AKazahTender* Tender = W.Get(); if (!Tender || !Tender->Flight->IsOut()) { continue; } const FVector D = P - Descent::UUToMetres(Tender->GetActorLocation()); const double Dist = D.Size(); if (Dist < Descent::Tune::Tender::SwimmerKeepOutM && Dist > 1e-3) { const FVector N = D / Dist; P += N * (Descent::Tune::Tender::SwimmerKeepOutM - Dist); RemoveInto(N); bMoved = true; } } } if (bMoved) { UpdatedComponent->SetWorldLocation(Descent::MetresToUU(P), false, nullptr, ETeleportType::TeleportPhysics); } }