#pragma once #include "CoreMinimal.h" /** * What the player (or a test) wants to do this step, DESIGN_UE.md section 5.7. Input handlers never act directly: * they write the live intent, UDescentSimSubsystem latches it at the start of every Step, and the systems read * the latched copy (UDescentSimSubsystem::GetIntent). Debug commands write the same struct (Descent.Player.Intent), * so a test and a player go through one path. * * Units: Move is a unit-ish vector, X forward, Y right, Z up, each -1..1. Look is an angular RATE in degrees per * second (X = yaw, positive turns right; Y = pitch, positive looks up): a rate holds for a duration, which a debug * override needs, and the controller turns a frame's mouse delta into a rate by dividing by that frame's dt. * Only the keys that are held (not one-shot presses such as Use) are in here, as in the design. */ struct FDescentIntent { FVector Move = FVector::ZeroVector; FVector2D Look = FVector2D::ZeroVector; bool bPrimary = false; bool bSecondary = false; bool bSprint = false; bool bJump = false; bool bDown = false; /** Move clamped to -1..1 per axis and non-finite values zeroed. The latch applies this to everything it hands out. */ FDescentIntent Sanitized() const { FDescentIntent Out = *this; auto Fix = [](double V) { return FMath::IsFinite(V) ? FMath::Clamp(V, -1.0, 1.0) : 0.0; }; Out.Move = FVector(Fix(Move.X), Fix(Move.Y), Fix(Move.Z)); if (!FMath::IsFinite(Out.Look.X)) { Out.Look.X = 0.0; } if (!FMath::IsFinite(Out.Look.Y)) { Out.Look.Y = 0.0; } return Out; } bool IsNeutral() const { return Move.IsNearlyZero(1e-9) && Look.IsNearlyZero(1e-9) && !bPrimary && !bSecondary && !bSprint && !bJump && !bDown; } bool operator==(const FDescentIntent& O) const { return Move == O.Move && Look == O.Look && bPrimary == O.bPrimary && bSecondary == O.bSecondary && bSprint == O.bSprint && bJump == O.bJump && bDown == O.bDown; } bool operator!=(const FDescentIntent& O) const { return !(*this == O); } };