#pragma once #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "Core/DescentHandle.h" #include "Player/DescentMode.h" #include "DescentPlayerController.generated.h" class UInputAction; class UInputMappingContext; class ADescentCharacter; class AKazahTender; class UCarrierCarouselComponent; /** * The player controller (DESIGN_UE.md sections 5.6, 5.7, 5.9). Enhanced Input is created here in C++ (no input assets): handlers only * record which keys are down and how far the mouse moved; every frame the controller turns that into an FDescentIntent and hands it to the * sim, which latches it. One-shot keys (E, F, Space in the cradle, Tab, M, Esc) call the mode actions below; the debug commands call the * same actions, so a test and a player go through one path. * * Modes at M3: Title, Foot, Board, Docked, Launch, Sub, Dock, Swim, Ladder, Dead. The timed ones are advanced in the sim (step 3, after * the carousel). The UI overlay is a flag on top of the mode: while a window is open or the game is paused, move and look are ignored. * (DescentPlayerController.cpp: input, intent, HUD model, boot, state. DescentPlayerModes.cpp: the mode state machine.) */ UCLASS() class THEDESCENT_API ADescentPlayerController : public APlayerController { GENERATED_BODY() public: ADescentPlayerController(); virtual void SetupInputComponent() override; virtual void BeginPlay() override; virtual void EndPlay(const EEndPlayReason::Type Reason) override; virtual void PlayerTick(float DeltaTime) override; EDescentMode GetMode() const { return Mode; } void SetMode(EDescentMode NewMode); /** True while a UI window is open (PDA, map, pause, title, a station): game input is ignored. */ bool IsUiOpen() const; /** Every mode entered since the start, oldest first (scenarios assert the sequence). */ const TArray& GetModeHistory() const { return ModeHistory; } void ClearModeHistory() { ModeHistory.Reset(); ModeHistory.Add(Mode); } /** Section 5.9: the point every depth, compass and temperature readout uses. Metres, world. */ FVector RefPointM() const; ADescentCharacter* GetDescentCharacter() const; /** The Tender the player sits in or flies (Board .. Dock), or null. */ AKazahTender* GetTender() const { return Tender.Get(); } UCarrierCarouselComponent* GetCarousel() const; // ---- mode actions: keys and debug commands share them. False and a reason when refused. ---- /** Foot -> Board (1.4 s) -> Docked. The Tender must hang at BOARD. */ bool BeginBoard(AKazahTender* T, FString* OutWhyNot = nullptr); /** Docked -> Launch (3.2 s) -> Sub. */ bool Launch(FString* OutWhyNot = nullptr); /** Sub -> Dock (4.2 s) -> Docked. Only under the moon pool. */ bool Dock(FString* OutWhyNot = nullptr); /** Docked -> Foot at the pool rim. */ bool LeaveDocked(FString* OutWhyNot = nullptr); /** Sub -> Swim: the roof hatch opens, the swimmer appears beside the Tender. */ bool ExitToSwim(FString* OutWhyNot = nullptr); /** Swim -> Sub: into a Tender that is out, within 4.5 m (bIgnoreRange: debug). */ bool EnterTender(AKazahTender* T, bool bIgnoreRange = false, FString* OutWhyNot = nullptr); /** Foot -> Swim where the body is (or at a world position, debug). */ bool EnterSwim(const FVector* WorldUU = nullptr); /** Foot -> Swim in the moon pool (E at the ladder sign, Descent.Player.PoolDrop). */ bool PoolDrop(FString* OutWhyNot = nullptr); /** Swim -> Ladder (1.6 s) -> Foot at the pool rim. bIgnoreRange: debug. */ bool Climb(bool bIgnoreRange = false, FString* OutWhyNot = nullptr); bool CanClimb() const; /** The Tender a swimmer could enter right now (out, within 4.5 m), or null. */ AKazahTender* FindEnterableTender() const; /** Foot -E helm-> Helm; E again (or LeaveHelm) -> Foot, and the carrier comes to an all stop. */ bool EnterHelm(FString* OutWhyNot = nullptr); bool LeaveHelm(); /** E. */ void PressUse(); /** LMB or G on foot with a box in the hands: puts it down (section 5.5). */ void PutDownCarried(); /** F. */ void ToggleLight(); /** The vitals report a death (drown), or the piloted Tender imploded: fade, 2.6 s, respawn in the galley. */ void OnPlayerDied(); /** The body was put on a deck by a command or a respawn: the mode follows. */ void OnPlacedOnFoot(); /** Tab, M, Esc / P / H: opens the window over the current mode (not in Title or Dead). */ bool OpenUi(const FString& WindowName); private: struct FKeys { bool Forward = false, Back = false, Left = false, Right = false, Jump = false, Down = false, Sprint = false, Primary = false, Secondary = false, Use = false, Alt = false; bool LookLeft = false, LookRight = false, LookUp = false, LookDown = false; FVector2D Mouse = FVector2D::ZeroVector; // accumulated this frame, counts }; /** Heap state shared with the input lambdas (the controller owns it). */ TSharedRef Keys = MakeShared(); void BindKey(class UEnhancedInputComponent* EIC, UInputMappingContext* Ctx, const FKey& Key, bool FKeys::* Member); void BindPress(class UEnhancedInputComponent* EIC, UInputMappingContext* Ctx, const FKey& Key, TFunction Fn); void WriteHudModel(); void UpdateFocus(); void StepModes(double Dt); void Respawn(); void SeatIn(AKazahTender* T, float BlendSeconds); void Unseat(); FString UsePrompt() const; EDescentMode Mode = EDescentMode::Title; TArray ModeHistory; FDescentHandle FocusStep, HudStep, ModeStep; TWeakObjectPtr Tender; double ModeTime = 0.0; // sim seconds in the current mode bool bBoardViewStarted = false; bool bDeadByImplosion = false; TWeakObjectPtr LidTender; // a lid that was opened and closes again after LidTimer double LidTimer = 0.0; // M7: E on a placed object is a short press (use it) or a 0.8 s hold (pick it up) TWeakObjectPtr UseHoldTarget; double UseHoldTime = 0.0; UPROPERTY(Transient) TObjectPtr MappingContext; UPROPERTY(Transient) TArray> Actions; };