#pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "Core/DescentHandle.h" #include "DescentCharacter.generated.h" class UCameraComponent; class USpotLightComponent; class UDescentInteractionComponent; class ADescentCarrier; class AKazahTender; class UDescentVitalsComponent; class UDescentArmViewmodelComponent; class UDescentPlayerBodyComponent; /** The debug walker behind Descent.Player.MoveTo: steers by writing a debug intent, so the movement is the real movement. */ struct FDescentAutoWalk { bool bActive = false; TArray Waypoints; // carrier-local XY int32 Index = 0; double SecondsLeft = 0.0; FString Label; }; /** * The only possessed pawn of the game (DESIGN_UE.md section 5.1). On foot it is a capsule (0.34 x 0.90 m, eye 1.65 m) whose camera * uses the control rotation; walking is UDescentCharacterMovement standing on the carrier's collision. It reads the sim's latched * intent (move, look, jump, sprint), so players and debug commands share one path (5.7). */ UCLASS() class THEDESCENT_API ADescentCharacter : public ACharacter { GENERATED_BODY() public: ADescentCharacter(const FObjectInitializer& ObjectInitializer); virtual void BeginPlay() override; virtual void Tick(float DeltaSeconds) override; /** A body teleported into water (a debug teleport) swims at once, without a frame of falling first. */ virtual bool TeleportTo(const FVector& DestLocation, const FRotator& DestRotation, bool bIsATest = false, bool bNoCheck = false) override; UPROPERTY(VisibleAnywhere) TObjectPtr Camera; UPROPERTY(VisibleAnywhere) TObjectPtr Flashlight; /** * The room's light on the first-person arm (fix list after M3). The arm takes only lighting channel 2 in dry air; this dim light * beside the eye, on channel 2 only and in the colour of the room's lamps, stands for the light the room's walls throw back, so the * back of the hand is not black when every lamp of the room is ahead of it. Off in the water. */ UPROPERTY(VisibleAnywhere) TObjectPtr ArmRoomFill; UPROPERTY(VisibleAnywhere) TObjectPtr Interaction; /** O2, health, death and respawn (section 5.8). */ UPROPERTY(VisibleAnywhere) TObjectPtr Vitals; /** * The old first-person cybernetic arm (Player/Arm), mounted on the camera. Since 2026-10-11 the player's view is the Twinblast body * (Body below) and the arm stays in its Hidden pose; `descent.Body.OldArm 1` gives it back (up on foot and swimming, as before). */ UPROPERTY(VisibleAnywhere) TObjectPtr Arm; /** The player's body: the Paragon Twinblast mesh on GetMesh(), its view settings, switches and animation input (Player/Body). */ UPROPERTY(VisibleAnywhere) TObjectPtr Body; /** Puts the character on foot at a carrier-local FEET position, facing a carrier-local yaw, view pitch in degrees. */ void PlaceOnCarrier(const FVector& FeetLocalM, double YawLocalDeg, double PitchDeg = 0.0); /** Turns the view to a carrier-local yaw and a pitch (degrees). */ void SetViewLocal(double YawLocalDeg, double PitchDeg); FVector GetFeetWorldUU() const; FVector GetFeetLocalM() const; // zero if there is no carrier double GetViewYawLocalDeg() const; FVector EyeWorldUU() const; FVector EyeDirection() const; void UseFocus(); // ---- swimming and the Tender seat (M3) ---- /** Starts swimming where the character is (optionally moved to a world position first). */ void StartSwimming(const FVector* WorldUU = nullptr); bool IsSwimming() const; /** In a Tender the body is hidden, has no collision and rides with the hull; null puts it back in the world where it is. */ void SetSeatedIn(AKazahTender* Tender); /** At the helm the body stands still and rides with the carrier (same flags as a seat; null releases). */ void SetHeldBy(AActor* Parent); bool IsSeated() const { return bSeated; } /** Seated in a Tender (not merely held at the helm): the body is not drawn there. */ bool IsInTender() const { return bInTender; } bool IsFrozen() const { return bFrozen; } /** * What the player sees of the wall-port jack-in (M8). The mechanic and its 4.8 s are UDescentBioSubsystem's. With the old arm as the * view this starts the arm's plug sequence; with the Twinblast body nothing is played (the pack has no such animation): he stands idle. */ void StartJackVisual(const FVector& PortWorldUU, const FVector& PlugDirectionWorld); /** Holds the body still (the ladder climb, death). */ void SetFrozen(bool bFreeze); void SetFlashlight(bool bOn); bool IsFlashlightOn() const; // ---- debug walker ---- bool StartAutoWalk(const TArray& CarrierLocalXY, const FString& Label); void StopAutoWalk() { AutoWalk = FDescentAutoWalk(); } const FDescentAutoWalk& GetAutoWalk() const { return AutoWalk; } /** Times the character fell out of the carrier and was put back on the bridge (a test must see 0). */ int32 RescueCount = 0; private: void ApplyIntent(double Dt); void SteerAutoWalk(double Dt); void RescueIfLost(); FDescentAutoWalk AutoWalk; bool bJumpHeld = false; bool bSeated = false; bool bInTender = false; bool bFrozen = false; double BobPhase = 0.0; double BobAmount = 0.0; double ViewBobCm = 0.0; public: /** The camera's vertical offset from the view bob right now, cm (tests, the state dump). */ double GetViewBobCm() const { return ViewBobCm; } /** descent.View.Bob: 0 = no bob, 1 = default. */ static double ViewBobStrength(); /** Half the peak-to-peak of the bob at a planar speed (before descent.View.Bob): 0.45 cm walking, 0.70 cm sprinting. */ static double ViewBobAmplitudeCm(double PlanarMps, double FootfallHz = 0.0); // FootfallHz > 0: also held to the camera speed of ViewBob*MaxCmS };