Files
UE5-Modular-Game-Framework/docs/blueprints/16-state/130_BPC_StateManager.md

668 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 130 — Central State Authority (`BPC_StateManager`)
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Player/BPC_StateManager.h` provides the complete state authority: `IsActionPermitted()` hot-path query, `RequestStateChange()`, `ForceStateChange()`/`RestorePreviousState()` force stack, heart rate BPM smoothing with tier detection, gating rule evaluation. **Attach directly to player pawn** (component slot 0). Set `GatingTable` → `DA_StateGatingTable` in Details panel. Do NOT create a BP child. See `docs/developer/cpp-integration-guide.md` for usage patterns.
>
> ---
## Purpose
Single source of truth for "what can the player do right now?" Every system queries `IsActionPermitted(Tag)` instead of checking other systems' states directly. Manages exclusive action states, upper-body overlay states, action action gating, vital signs (heart rate), and the force-stack pattern for nested overrides (death, cutscenes, void space). Communicates with GASP exclusively through overlay state variables — never touches motion matching internals.
## Dependencies
- **Requires:** `GI_GameFramework` (04 — game phase changes), `BPC_HealthSystem` (08 — death + injury bindings), `BPC_StressSystem` (10 — stress tier), `BPC_StaminaSystem` (09 — exhaustion), `BPC_MovementStateSystem` (11 — movement mode tracking), `DA_GameTagRegistry` (01 — tag queries), `SS_EnhancedInputManager` (128 — context switching), `ABP_GASP` (external — overlay + intensity variables)
- **Required By:** EVERY gameplay system (central query point for action permission)
- **Engine/Plugin Requirements:** GameplayTags plugin, GASP plugin (read-only)
## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `ActorComponent` |
| **Class Type** | Blueprint Component |
| **Asset Path** | `Content/Framework/Player/BPC_StateManager.uasset` |
| **Implements Interfaces** | `I_AnimNotifyInterface` (receives GASP notifies) |
---
## 1. Enums
### E_PlayerActionState — Exclusive Action States (45 values)
```text
Enum Name: E_PlayerActionState
(DisplayName = "Player Action State")
Values:
Idle = 0 // Standing, not performing any action
Walking = 1 // Moving at walk speed
Jogging = 2 // Moving at jog speed
Sprinting = 3 // Moving at sprint speed (stamina draining)
Crouching = 4 // Crouched posture, possibly moving
Crawling = 5 // Prone, crawling
Sitting = 6 // Sitting on surface — immobile, can look around
Vaulting = 7 // Transient — vaulting over obstacle
Climbing = 8 // Ledge or ladder climbing
Sliding = 9 // Transient — sliding under or down
Interacting = 10 // Performing a hold/press interaction
Inspecting = 11 // Inspecting an item in close-up view
InInventory = 12 // Inventory menu is open
InJournal = 13 // Journal/document viewer is open
InPauseMenu = 14 // Pause menu is open
InDialogue = 15 // In dialogue with an NPC
InCutscene = 16 // Pre-authored cutscene playing
SwingMelee = 17 // Performing a melee attack
AimingFirearm = 18 // Aiming a firearm
FiringFirearm = 19 // Firing a firearm (transient)
Reloading = 20 // Reloading a firearm
DeployingShield = 21 // Raising a shield
Hiding = 22 // Inside a hiding spot (enclosed/behind/under/shadow)
Peeking = 23 // Peeking from behind cover
GrabbingObject = 24 // Holding a physics object
UsingObject = 25 // Using a world object (lever, panel, etc.)
Dead = 26 // Dead — death animation playing or death loop active
InVoidSpace = 27 // In alt death / void space
InPuzzle = 28 // Using a puzzle device
InTrialScenario = 29 // In a timed trial scenario
InMainMenu = 30 // At main menu (no gameplay)
Loading = 31 // Loading screen active
DraggingCorpse = 32 // Dragging a physics body
HoldingBreath = 33 // Holding breath while hiding
Injured = 34 // Health ≤ threshold — reduced speed, limp anim, restricted actions
Staggered = 35 // Transient — hit reaction / stagger
KnockedDown = 36 // Knocked to ground
Exhausted = 37 // Stamina fully depleted — panting/immobile
Panicked = 38 // Stress at catatonic tier — stumble/freeze
PushingObject = 39 // Pushing a movable object
AimingThrowable = 40 // Aiming a throwable item
ThrowingItem = 41 // Throwing an item (transient)
UsingConsumable = 42 // Using a consumable (heal, inject, etc.)
InPhotoMode = 43 // Photo mode active — free cam, no gameplay input
CustomAction = 44 // Reserved for mod/expansion actions
```
### E_OverlayState — Upper-Body Overlay for GASP AnimBP (18 values)
```text
Enum Name: E_OverlayState
(DisplayName = "Upper Body Overlay State")
Values:
Empty = 0 // No overlay — default locomotion
Flashlight = 1 // Holding flashlight
Melee_OneHanded = 2 // One-handed melee weapon (knife, baton)
Melee_TwoHanded = 3 // Two-handed melee weapon (axe, pipe)
Pistol = 4 // Pistol / handgun
Shotgun = 5 // Shotgun
Rifle = 6 // Rifle / carbine
Shield = 7 // Shield only
Shield_And_Melee = 8 // Shield + one-handed melee
Shield_And_Pistol = 9 // Shield + pistol
DualWield = 10 // Dual-wielding one-handed weapons
Inspect = 11 // Inspecting an object
DualHand_Flashlight_Melee = 12 // Flashlight + melee
Injured = 13 // Injured arm (lowered, favoring)
Throwable = 14 // Holding a throwable (grenade, bottle)
Consumable = 15 // Using consumable (syringe, bandage)
TwoHanded_Heavy = 16 // Heavy two-handed (sledgehammer, chainsaw)
EmptyHands_Raised = 17 // Empty hands raised (surrender, blocking bare)
```
### E_ActionRequestResult — Result Enum for RequestStateChange (8 values)
```text
Enum Name: E_ActionRequestResult
Values:
Permitted = 0 // State change approved
Blocked_CurrentState = 1 // Blocked because player is in an incompatible state
Blocked_GamePhase = 2 // Blocked by game phase (menu, cutscene, loading)
Blocked_Dead = 3 // Player is dead
Blocked_Stamina = 4 // Insufficient stamina
Blocked_Equipment = 5 // Missing required equipment
Blocked_Cooldown = 6 // Action on cooldown
Blocked_Custom = 7 // Blocked by a custom gating rule
```
### E_PlayerVitalSignals — Vital Signal Enum for HeartRate / Breathing (5 values)
```text
Enum Name: E_PlayerVitalSignals
(DisplayName = "Player Vital Signals")
Values:
Normal = 0 // Heart rate 60-80 BPM — resting/exploring
Elevated = 1 // Heart rate 80-110 BPM — sprinting, light combat
High = 2 // Heart rate 110-150 BPM — heavy combat, chased
Critical = 3 // Heart rate 150+ BPM — near death, terrified
Erratic = 4 // Irregular rhythm — panic state, hallucinating
```
---
## 2. Structs
### `S_StateChangeRequest`
| Field | Type | Description |
|-------|------|-------------|
| `RequestedState` | `E_PlayerActionState` | The state the requester wants to enter |
| `RequesterTag` | `GameplayTag` | Identifies which system is requesting |
| `Priority` | `Integer` | 0=Normal, 50=High, 100=Force (bypasses all gating) |
| `OverrideReason` | `Text` | Human-readable reason for override/force |
### `S_StateGatingRule`
| Field | Type | Description |
|-------|------|-------------|
| `ActionTag` | `GameplayTag` | The action being gated |
| `BlockedStates` | `Array<E_PlayerActionState>` | States that block this action |
| `BlockedGamePhases` | `Array<E_GamePhase>` | Game phases that block this |
| `RequiredTags` | `GameplayTagContainer` | Tags the player must have |
| `BlockedTags` | `GameplayTagContainer` | Tags the player must NOT have |
| `BlockReason` | `Text` | Reason shown to player when blocked |
| `Priority` | `Integer` | Rule evaluation order (lower = checked first) |
### `S_ActionPermissionResult`
| Field | Type | Description |
|-------|------|-------------|
| `bPermitted` | `Boolean` | Whether the action is allowed |
| `BlockReason` | `Text` | Why it was blocked (if not permitted) |
| `ResultCode` | `E_ActionRequestResult` | Machine-readable result code |
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `GatingRules` | `Array<S_StateGatingRule>` | `DefaultRules` | `State Config` | All gating rules defined in blueprint |
| `DefaultState` | `E_PlayerActionState` | `Idle` | `State Config` | State to initialize on BeginPlay |
| `DefaultOverlay` | `E_OverlayState` | `Empty` | `State Config` | Default overlay state |
| `StateTransitionDelay` | `Float` | `0.0` | `State Config` | Minimum time between state changes (anti-spam) |
| `InjuryHealthThreshold` | `Float` | `0.30` | `State Config` | Health ratio (0-1) below which player becomes Injured |
| `InjurySpeedMultiplier` | `Float` | `0.65` | `State Config` | Walk speed multiplier when injured |
| `HeartRateBase` | `Float` | `70.0` | `State Config` | Base heart rate in BPM when calm |
| `HeartRateMax` | `Float` | `180.0` | `State Config` | Maximum heart rate in BPM |
| `StateGatingTable` | `DA_StateGatingTable` | `None` | `State Config` | External Data Asset for designer-tunable gating rules |
| `bLogStateTransitions` | `Boolean` | `false` | `Debug` | Log all state transitions to console |
### Internal (Private / Protected, No Expose)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentState` | `E_PlayerActionState` | `Idle` | `State` | Current exclusive action state |
| `PreviousState` | `E_PlayerActionState` | `Idle` | `State` | Previous state for transitions |
| `CurrentOverlay` | `E_OverlayState` | `Empty` | `State` | Current upper-body overlay state |
| `LastStateChangeTime` | `Float` | `0.0` | `State` | Game time of last state change |
| `StateHistory` | `Array<E_PlayerActionState>` | `Empty` | `State` | History of last N states for debugging |
| `ForceStack` | `Array<E_PlayerActionState>` | `Empty` | `State` | Stack of forced states for nested overrides |
| `bIsTransitioning` | `Boolean` | `false` | `State` | True during a state transition |
| `PendingRequests` | `Array<S_StateChangeRequest>` | `Empty` | `State` | Queued requests during transition |
| `CachedABPRef` | `ABP_GASP` (soft ref) | `None` | `State` | Cached reference to GASP AnimBP |
| `CurrentActionFlags` | `Map<GameplayTag, Float>` | `Empty` | `State` | Active action tags with duration/timestamp |
| `CurrentHeartRate` | `Float` | `70.0` | `Vital Signs` | Current heart rate in BPM (recalculated each tick, smoothed) |
| `CurrentVitalSignal` | `E_PlayerVitalSignals` | `Normal` | `Vital Signs` | Current vital signal category |
| `bIsInjured` | `Boolean` | `false` | `Vital Signs` | True when health ≤ InjuryHealthThreshold |
---
## 4. Functions
### Public Functions
#### `RequestStateChange` → `E_ActionRequestResult`
- **Description:** The primary entry point. Any system requests to transition the player to a new exclusive action state. State Manager checks all gating rules and returns the result.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `NewState` | `E_PlayerActionState` | The desired new action state |
| `RequesterTag` | `GameplayTag` | Identifies which system is requesting |
| `Priority` | `Integer` (default 0) | 0=Normal, 50=High, 100=Force |
- **Blueprint Authority:** Any
- **Flow:**
1. If Priority == 100 (Force): bypass gating, go directly to state transition
2. If `bIsTransitioning`: queue the request on `PendingRequests`, return `Blocked_CurrentState`
3. Check time since `LastStateChangeTime` — if < `StateTransitionDelay`: return `Blocked_Cooldown`
4. If `CurrentState` == `Dead` and `NewState` != `Idle` (respawn): return `Blocked_Dead`
5. Fire `OnStateChangeRequested`
6. Iterate `GatingRules` matching `RequesterTag`:
- If `NewState` is in `BlockedStates` for any rule fire `OnStateChangeDenied`, return `Blocked_CurrentState` with `BlockReason`
- If `CurrentGamePhase` is in `BlockedGamePhases` return `Blocked_GamePhase`
- If required tags missing or blocked tags present return appropriate result
7. Execute state transition (set PreviousState, set CurrentState, update GASP, fire dispatchers)
8. Return `Permitted`
#### `ForceStateChange` → `void`
- **Description:** Bypasses ALL gating. Used by death system, cutscene bridge, void space entry, and developer cheats. Pushes current state onto `ForceStack`.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `NewState` | `E_PlayerActionState` | The forced state to enter |
| `Reason` | `Text` | Human-readable reason for force |
- **Blueprint Authority:** Any (Server-authoritative if multiplayer)
- **Flow:**
1. If `ForceStack` already has `NewState` at top: return (prevents double-force)
2. Push `CurrentState` onto `ForceStack`
3. Fire `OnForceOverridePushed`
4. Execute state transition with `NewState`
5. Log reason to console if `bLogStateTransitions`
#### `RestorePreviousState` → `void`
- **Description:** Pops the force stack. Used when exiting cutscenes, void space, or force-override states.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any
- **Flow:**
1. If `ForceStack` is empty: set to `Idle` or previously known normal state, return
2. Pop top of `ForceStack`
3. Fire `OnForceOverridePopped`
4. Execute state transition to popped state
#### `GetCurrentState` → `E_PlayerActionState`
- **Description:** Pure getter returns `CurrentState`. No side effects.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `GetPreviousState` → `E_PlayerActionState`
- **Description:** Pure getter returns `PreviousState`. No side effects.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `IsActionPermitted` → `S_ActionPermissionResult`
- **Description:** Non-mutating query. Does NOT change state. Any system can ask "can I do this right now?"
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `ActionTag` | `GameplayTag` | The action tag to check |
- **Blueprint Authority:** Any (Pure)
- **Flow:**
1. Find all `GatingRules` where `ActionTag` matches
2. Check `CurrentState` against `BlockedStates`
3. Check `GI_GameFramework.CurrentGamePhase` against `BlockedGamePhases`
4. Check `RequiredTags` and `BlockedTags`
5. Return `S_ActionPermissionResult` with `bPermitted`, `BlockReason`, `ResultCode`
#### `SetOverlayState` → `void`
- **Description:** Tells GASP AnimBP which upper-body overlay to use. Does NOT change `E_PlayerActionState`. Called by equipment system, weapon system, and inspection.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Overlay` | `E_OverlayState` | The new overlay state |
- **Blueprint Authority:** Any
- **Flow:**
1. If `Overlay` == `CurrentOverlay`: return (no-op)
2. Set `CurrentOverlay` = `Overlay`
3. If `CachedABPRef` is valid: set `ABP_GASP.OverlayState` = `Overlay`
4. Fire `OnOverlayStateChanged`
5. Update visible first-person arm mesh via `BPC_EmbodimentSystem`
#### `CanTransitionTo` → `S_ActionPermissionResult`
- **Description:** Pre-flight check without executing. Used by UI to enable/disable buttons.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `TargetState` | `E_PlayerActionState` | The state to check transition eligibility for |
- **Blueprint Authority:** Any (Pure)
#### `UpdateHeartRate` → `void`
- **Description:** Recalculates heart rate each tick based on current state, stress, stamina, and health. Evaluates `E_PlayerVitalSignals` thresholds. Called automatically from Tick.
- **Parameters:** *(none — reads internal state and bound systems)*
- **Blueprint Authority:** Internal (Tick-driven)
- **Flow:**
1. Calculate base rate from current state:
- Idle/Walking/Sitting `HeartRateBase` (60-80)
- Jogging Base + 15
- Sprinting Base + 30
- Combat (any weapon state) Base + 40
- Hiding/Peeking Base + 5 (suppressed by stealth)
- Panicked Base + 50
- Dead 0
2. Apply health modifier: (1.0 - HealthRatio) * 20 added
3. Apply stress modifier: StressRatio * 30 added
4. Apply stamina modifier: if Exhausted +25
5. Clamp to `HeartRateMax`
6. Smooth interpolation: `CurrentHeartRate = FMath::FInterpTo(CurrentHeartRate, Target, DeltaTime, 3.0)`
7. Evaluate `E_PlayerVitalSignals` from `CurrentHeartRate`:
- 80 Normal
- 110 Elevated
- 150 High
- 180 Critical
- If Panicked state OR FearSystem active Erratic (overrides BPM threshold)
8. If `CurrentVitalSignal` changed, fire `OnVitalSignalChanged`
9. Write `CurrentHeartRate` to `ABP_GASP.HeartRate` for breathing animation blend
#### `EvaluateInjuryState` → `void`
- **Description:** Called when health changes. Sets `bIsInjured` flag and auto-transitions to/from `Injured` state if health crosses threshold.
- **Parameters:** *(none — reads BPC_HealthSystem)*
- **Blueprint Authority:** Internal (called by health change binding)
- **Flow:**
1. Get `HealthRatio` from `BPC_HealthSystem.CurrentHealth / MaxHealth`
2. If `HealthRatio <= InjuryHealthThreshold` AND `!bIsInjured`:
- Set `bIsInjured = true`
- If CurrentState is Idle/Walking/Jogging/Sprinting/Crouching: `RequestStateChange(Injured, Tag.Health)`
- Apply `InjurySpeedMultiplier` to movement
- Fire `OnInjuryStateChanged(true, HealthRatio)`
3. If `HealthRatio > InjuryHealthThreshold` AND `bIsInjured`:
- Set `bIsInjured = false`
- If CurrentState == Injured: `RequestStateChange(Idle, Tag.Health)`
- Restore movement speed
- Fire `OnInjuryStateChanged(false, HealthRatio)`
#### `IsInCombat` → `Boolean`
- **Description:** Convenience returns true if `CurrentState` is `SwingMelee`, `AimingFirearm`, `FiringFirearm`, `Reloading`, or `DeployingShield`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `IsMovementBlocked` → `Boolean`
- **Description:** Convenience returns true if player cannot move at all. True when `CurrentState` is `Dead`, `InCutscene`, `InDialogue`, `InInventory`, `InJournal`, `InPauseMenu`, `InMainMenu`, `UsingObject`, `Sitting`, `KnockedDown`, `Panicked`, `Staggered`, `Exhausted`, `Loading`, `InPhotoMode`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `IsMovementRestricted` → `Boolean`
- **Description:** Convenience returns true if movement is partially restricted (slowed/impaired but not fully blocked). True when `CurrentState` is `Injured`, `Crouching`, `Crawling`, `HoldingBreath`, `AimingFirearm`, `DeployingShield`, `Hiding`, `Peeking`, `GrabbingObject`, `PushingObject`, `UsingConsumable`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `GetHeartRate` → `Float`
- **Description:** Returns `CurrentHeartRate` in BPM. Used by audio system for heartbeat SFX tempo, by UI for vitals display, by haptic system for controller pulse rate.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `GetVitalSignal` → `E_PlayerVitalSignals`
- **Description:** Returns `CurrentVitalSignal`. Used by atmosphere system (audio tension layers), camera (breathing sway), and narrative system (dialogue conditionals).
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `CanEquipWeapon` → `Boolean`
- **Description:** Checks if weapon equip is permitted in current state. Convenience wrapper around `IsActionPermitted`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `RegisterActionFlag` → `void`
- **Description:** Lightweight flag system for transient action permissions. Duration of -1 = permanent until unregistered. Fires `OnActionFlagRegistered`.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Tag` | `GameplayTag` | The flag tag to register |
| `Duration` | `Float` (default -1.0) | How long the flag persists (-1 = permanent) |
#### `UnregisterActionFlag` → `void`
- **Description:** Removes a previously registered action flag. Fires `OnActionFlagExpired` if the flag was active.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Tag` | `GameplayTag` | The flag tag to remove |
#### `HasActionFlag` → `Boolean`
- **Description:** Returns true if the given tag is currently registered as an action flag.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Tag` | `GameplayTag` | The flag tag to check |
- **Blueprint Authority:** Any (Pure)
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnStateChanged` | `OldState: E_PlayerActionState`, `NewState: E_PlayerActionState` | `Public` | Fired on any state transition |
| `OnStateChangeRequested` | `Request: S_StateChangeRequest` | `Public` | Fired when request is made (before evaluation) |
| `OnStateChangeDenied` | `Request: S_StateChangeRequest`, `Result: E_ActionRequestResult`, `Reason: Text` | `Public` | Fired when a request is denied |
| `OnOverlayStateChanged` | `OldOverlay: E_OverlayState`, `NewOverlay: E_OverlayState` | `Public` | Fired on overlay change |
| `OnActionFlagRegistered` | `Tag: GameplayTag`, `Duration: Float` | `Public` | Fired when a flag is registered |
| `OnActionFlagExpired` | `Tag: GameplayTag` | `Public` | Fired when a timed flag expires |
| `OnForceOverridePushed` | `ForcedState: E_PlayerActionState`, `PoppedState: E_PlayerActionState` | `Public` | Fired when force stack is pushed |
| `OnForceOverridePopped` | `RestoredState: E_PlayerActionState` | `Public` | Fired when force stack is popped |
| `OnVitalSignalChanged` | `OldSignal: E_PlayerVitalSignals`, `NewSignal: E_PlayerVitalSignals` | `Public` | Fired when vital signal tier changes |
| `OnHeartRateChanged` | `NewBPM: Float` | `Public` | Fired when heart rate changes significantly 5 BPM) |
| `OnInjuryStateChanged` | `bIsInjured: Boolean`, `HealthRatio: Float` | `Public` | Fired when injury state toggles |
---
## 6. Overridden Events / Custom Events
### Event: `BeginPlay` → `Initialize`
- **Description:** Sets up all bindings, caches references, loads default state, and enables tick.
- **Flow:**
1. Get owner as Character (validate log error and disable if invalid)
2. Cache reference to `ABP_GASP` from character's skeletal mesh
3. Set `CurrentState` = `DefaultState`, `CurrentOverlay` = `DefaultOverlay`
4. Populate default `GatingRules` from `DA_StateGatingTable` if assigned
5. Bind to `GI_GameFramework.OnGamePhaseChanged` (for game-phase-gated rules)
6. Bind to `BPC_HealthSystem.OnDeath` (auto-call `ForceStateChange(Dead, "Death")`)
7. Register with `DA_GameTagRegistry` for tag-based queries
8. Bind to `BPC_HealthSystem.OnHealthChanged` call `EvaluateInjuryState()`
9. Bind to `BPC_StressSystem.OnStressTierChanged` recalculate heart rate
10. Bind to `BPC_StaminaSystem.OnExhaustionStateChanged` recalculate heart rate
11. Enable component tick (required for heart rate smoothing)
### Event: `Tick` → `UpdateHeartRate`
- **Description:** Called every tick. Recalculates heart rate and evaluates vital signals.
- **Flow:** (see `UpdateHeartRate` function flow above)
### Event: `OnComponentDestroyed`
- **Description:** Cleanup unbind all bound delegates, null cached references.
- **Flow:**
1. Unbind from `GI_GameFramework.OnGamePhaseChanged`
2. Unbind from `BPC_HealthSystem.OnDeath` and `OnHealthChanged`
3. Unbind from `BPC_StressSystem.OnStressTierChanged`
4. Unbind from `BPC_StaminaSystem.OnExhaustionStateChanged`
5. Set `CachedABPRef` = `None`
---
## 7. Blueprint Graph Logic Flow
### RequestStateChange Flow
```mermaid
flowchart TD
A[System calls RequestStateChange] --> B{Priority == 100?}
B -->|Yes Force| C[ExecuteStateTransition]
B -->|No| D{bIsTransitioning?}
D -->|Yes| E[Queue on PendingRequests - return Blocked_CurrentState]
D -->|No| F{Within StateTransitionDelay?}
F -->|Yes| G[Return Blocked_Cooldown]
F -->|No| H{CurrentState == Dead?}
H -->|Yes and NewState != Idle| I[Return Blocked_Dead]
H -->|No| J[Fire OnStateChangeRequested]
J --> K[Evaluate GatingRules for RequesterTag]
K --> L{NewState in BlockedStates?}
L -->|Yes| M[Fire OnStateChangeDenied - return Blocked_CurrentState]
L -->|No| N{GamePhase blocks?}
N -->|Yes| O[Fire OnStateChangeDenied - return Blocked_GamePhase]
N -->|No| P{Required tags missing?}
P -->|Yes| Q[Fire OnStateChangeDenied - return reason]
P -->|No| R{Blocked tags present?}
R -->|Yes| Q
R -->|No| C
C --> S[PreviousState = CurrentState]
S --> T[CurrentState = NewState]
T --> U[Update bIsInAction on GASP]
U --> V[Update ActionIntensity on GASP]
V --> W[Fire OnStateChanged - OldState, NewState]
W --> X[Notify SS_EnhancedInputManager - context change]
X --> Y[Return Permitted]
```
### Overlay State Pipeline
```mermaid
sequenceDiagram
participant ES as EquipmentSystem
participant SM as BPC_StateManager
participant GASP as ABP_GASP
participant AP as AnimPose
ES->>SM: SetOverlayState(Pistol)
SM->>SM: CurrentOverlay = Pistol
SM->>GASP: ABP_GASP.OverlayState = Pistol
GASP->>GASP: Blend upper body to Pistol pose
GASP->>AP: Apply overlay pose
SM->>SM: Fire OnOverlayStateChanged
SM->>ES: Confirm overlay set
```
---
## 8. State Gating Reference Tables
### Complete Action → Blocking State Matrix
| Requested Action (Tag) | Blocked If CurrentState Is | Reason |
|------------------------|----------------------------|--------|
| `Action.Move.Walk` | Dead, InCutscene, InDialogue, KnockedDown, Panicked, Exhausted, Sitting, InInventory, InJournal, InPauseMenu, UsingObject, GrabbingObject, InPhotoMode | Movement disabled |
| `Action.Move.Sprint` | Dead, InCutscene, InDialogue, Crouching, Crawling, Hiding, Peeking, Sitting, KnockedDown, Exhausted, Panicked, Reloading, UsingConsumable, Injured | Sprint requires standing + uninjured |
| `Action.Move.Crouch` | Dead, Vaulting, Climbing, Sliding, InCutscene, InDialogue, Sitting, KnockedDown, Exhausted, Panicked | Cannot crouch during traversal |
| `Action.Move.Crawl` | Dead, Standing required states, Vaulting, Climbing, InCutscene, Sitting, KnockedDown | Must be prone first |
| `Action.Move.Jump` | Dead, Hiding, Peeking, Crouching (maybe), Crawling, InCutscene, InDialogue, Sitting, KnockedDown, Panicked, Exhausted, UsingObject, InInventory, InPauseMenu | Jump blocked |
| `Action.Move.Sit` | Dead, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, InInventory, InCutscene, InDialogue | Sit requires neutral standing state |
| `Action.Move.StandUp` | Must be Sitting state | Must be sitting first |
| `Action.Traversal.Vault` | Dead, Hiding, Peeking, InCutscene, InDialogue, Reloading, SwingMelee, FiringFirearm, KnockedDown, Panicked | Traversal requires free hands |
| `Action.Traversal.Climb` | Dead, InCutscene, InDialogue, Reloading, SwingMelee, KnockedDown, Panicked | Climbing requires free hands |
| `Action.Traversal.Slide` | Dead, InCutscene, InDialogue, Reloading, SwingMelee, KnockedDown, Panicked | Slide requires two hands |
| `Action.Hide.Enter` | Dead, InCutscene, InDialogue, SwingMelee, FiringFirearm, Reloading, Vaulting, Climbing, KnockedDown, Panicked, Exhausted, GrabbingObject | Hide requires exposed state |
| `Action.Hide.Peek` | Dead, SwingMelee, FiringFirearm, Reloading | Peek only from hidden |
| `Action.Hide.Exit` | InCutscene, Dead (if not force-exit) | Forced exit allowed |
| `Action.Interact.Hold` | Dead, InCutscene, SwingMelee, FiringFirearm, Reloading, Hiding, Peeking, Vaulting, Climbing, KnockedDown, Panicked, InInventory, InPauseMenu, InDialogue, UsingObject | Interaction requires free state |
| `Action.Interact.Instant` | Dead, InCutscene, KnockedDown, Panicked | Instant interaction only blocked by critical |
| `Action.Inventory.Open` | Dead, InCutscene, Vaulting, Climbing, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, Hiding, GrabbingObject, InDialogue, InPuzzle, InTrialScenario | Inventory blocked during actions |
| `Action.Inventory.Close` | (always permitted but may not open if blocked) | Close always works |
| `Action.Inventory.Equip` | Dead, InCutscene, Vaulting, Climbing, KnockedDown, Panicked | Equip requires stability |
| `Action.Inventory.UseItem` | Dead, InCutscene, Vaulting, Climbing, KnockedDown, Panicked, Hiding | Use requires stable state |
| `Action.Weapon.Fire` | Dead, Hiding, Peeking, Reloading, Vaulting, Climbing, InCutscene, InDialogue, KnockedDown, Panicked, InInventory, InPauseMenu, UsingConsumable, GrabbingObject | Cannot fire in these states |
| `Action.Weapon.Aim` | Dead, Hiding, Peeking, Vaulting, Climbing, InCutscene, InDialogue, KnockedDown, Panicked, InInventory, InPauseMenu, GrabbingObject | Cannot aim |
| `Action.Weapon.Reload` | Dead, Hiding, Peeking, Vaulting, Climbing, SwingMelee, InCutscene, InDialogue, KnockedDown, Panicked, InPauseMenu, GrabbingObject | Cannot reload |
| `Action.Weapon.Melee` | Dead, Hiding, Peeking, Reloading, Vaulting, Climbing, InCutscene, InDialogue, KnockedDown, Panicked, InInventory, InPauseMenu, GrabbingObject, UsingConsumable | Cannot melee |
| `Action.Weapon.Block` | Dead, Reloading, Vaulting, Climbing, InCutscene, KnockedDown, Panicked, Hiding, InInventory, InPauseMenu | Cannot block |
| `Action.Dialogue.Start` | Dead, InCutscene, Vaulting, Climbing, SwingMelee, FiringFirearm, Hiding, InInventory, InPauseMenu, Reloading, KnockedDown, Panicked | Dialogue requires neutral state |
| `Action.Dialogue.Choose` | Must be InDialogue state | Must be in dialogue |
| `Action.Cutscene.Play` | (handled by GI_GameFramework game phase, not action state) | Cutscene overrides via Force |
| `Action.Cutscene.Skip` | Must be InCutscene, bCanSkip | Must be in cutscene |
| `Action.HoldBreath` | Hiding state only, not Peeking | Breath hold only while hidden |
| `Action.Grab.PhysicsObject` | Dead, InCutscene, Hiding, Vaulting, Climbing, Reloading, SwingMelee, FiringFirearm, KnockedDown, Panicked, InInventory, InPauseMenu | Grab requires free hands |
| `Action.Puzzle.Use` | Dead, InCutscene, InDialogue, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked | Puzzle requires focus |
| `Action.Consumable.Use` | Dead, InCutscene, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, InInventory | Consumable requires free hands |
| `Action.Pause.Open` | InCutscene, Dead, InMainMenu, Loading | Cannot pause in cutscene/death |
| `Action.Pause.Close` | (always permitted) | Close always works |
| `Action.Journal.Open` | Dead, InCutscene, InDialogue, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, InInventory, InPuzzle, InTrialScenario | Journal requires neutral state |
| `Action.Settings.Open` | InCutscene, Loading | Settings limited during cutscene |
| `Action.MainMenu.Return` | (always permitted) | Return to menu always works |
| `Action.QuestLog.Open` | Same as Journal | Same blocking states |
| `Action.Map.Open` | Dead, InCutscene, Vaulting, Climbing, Hiding, InInventory, InPauseMenu | Map in neutral/explore states |
| `Action.Throwable.Aim` | Dead, Hiding, Peeking, Vaulting, Climbing, InCutscene, InDialogue, Reloading, KnockedDown, Panicked, InInventory, InPauseMenu | Throw requires free hands |
| `Action.Throwable.Throw` | Same as Aim + must be in AimingThrowable state | Must be aiming first |
| `Action.PhotoMode.Enter` | Dead, InCutscene, InDialogue, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, KnockedDown, Panicked | Photo mode requires neutral state |
| `Action.PhotoMode.Exit` | Must be InPhotoMode | Must be in photo mode |
### Restricted Movement States
| State | Walk Allowed | Sprint Allowed | Jump Allowed | Crouch Allowed | Camera Freelook | Notes |
|-------|-------------|----------------|-------------|----------------|-----------------|-------|
| `Sitting` | **No** | No | No | No | Yes (head only) | Must StandUp to move |
| `Injured` | **Slowed** (×0.65) | No | No | Yes | Yes | Limp anim. Heals above threshold |
| `Hiding` (Locker/Under) | **No** | No | No | No | No (fixed cam) | Fully enclosed zero movement |
| `Hiding` (BehindCover) | **No** | No | No | N/A | Limited (peek) | Cannot leave cover position |
| `Hiding` (InShadow) | **Yes** (slow) | No | No | Yes | Yes | Standing in shadow |
| `Hiding` (TallGrass) | **Yes** (crouch-walk) | No | No | Yes | Yes | Crouch-moving through vegetation |
| `Staggered` | **No** | No | No | No | Locked | Forced animation no input |
| `KnockedDown` | **No** | No | No | No | Locked (ground view) | Must GetUp animation first |
| `Exhausted` | **No** | No | No | No | Yes | Heavy breathing wait for regen |
| `Panicked` | **Stumble** | No | No | No | Random jerks | Camera shakes |
| `HoldingBreath` | **No** | No | No | No | Yes | Must release breath before moving |
| `InPhotoMode` | **No** (free cam) | No | No | No | Full (free cam) | Gameplay input disabled |
---
## 9. GASP Integration Notes
### What BPC_StateManager READS from GASP
| GASP Variable / Event | How Used |
|----------------------|----------|
| `ABP_GASP.bStrafing` | Read to detect if GASP is in strafing mode informs combat state eligibility |
| `ABP_GASP.bSprinting` | Read to confirm sprint state when `BPC_MovementStateSystem` reports sprint |
| `ABP_GASP.MovementMode` | Read to cross-validate `BPC_MovementStateSystem.CurrentMovementMode` |
| `ABP_GASP.GroundState` | Read to know if airborne (blocks certain actions) |
| `AnimNotify_StateEnter` | Any GASP notify received via `I_AnimNotifyInterface` is forwarded to listeners |
### What BPC_StateManager WRITES to GASP
| GASP Variable / Event | How Written |
|----------------------|-------------|
| `ABP_GASP.OverlayState` | Set via `SetOverlayState(E_OverlayState)` direct enum value set on cached AnimBP reference |
| `ABP_GASP.bIsInAction` | Set to `true` when `CurrentState` is any action state (not Idle/Walking/Jogging/Sprinting/Crouching) |
| `ABP_GASP.ActionIntensity` | Float 0.01.0 set based on action type: 0.0=Idle, 0.3=Interacting, 0.7=SwingMelee, 1.0=FiringFirearm |
| `AnimNotify_OverlayChanged` | Fired when overlay changes GASP blends to new upper-body pose |
### What BPC_StateManager NEVER Touches
- **Motion Matching Database** GASP's motion matching is sacred, never modified
- **Camera Manager** Camera blends, FOV, and offsets belong to `BPC_CameraStateLayer`
- **GASP's Internal State Machine** Ground/air transitions, rotation mode, strafing logic
- **Animation Blueprint Graph Logic** Only sets variables, never modifies graph nodes
- **GASP's Root Motion** Root motion extraction is GASP's domain
---
## 10. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| Any System | `RequestStateChange()` | `E_PlayerActionState`, `RequesterTag: GameplayTag`, `Priority: Int` |
| Any System | `IsActionPermitted()` | `GameplayTag` `S_ActionPermissionResult` |
| BPC_StateManager | `ABP_GASP` (Direct variable set) | `OverlayState`, `bIsInAction`, `ActionIntensity` |
| BPC_StateManager | `Dispatcher: OnStateChanged` | `OldState`, `NewState` to all bound listeners |
| BPC_StateManager | `Dispatcher: OnOverlayStateChanged` | `OldOverlay`, `NewOverlay` to `BPC_EmbodimentSystem`, `WBP_HUDController` |
| BPC_StateManager | `Dispatcher: OnStateChangeDenied` | Request, Result, Reason to `WBP_InteractionPromptDisplay`, `WBP_NotificationToast` |
| BPC_StateManager | `SS_EnhancedInputManager` (Direct) | Context change notification when state changes |
| BPC_StateManager | `BPC_MovementStateSystem` (Direct) | Receives `OnMovementModeChanged` to track walking/sprinting |
| BPC_StateManager | `BPC_HealthSystem` (Listener on OnDeath) | Auto-transitions to Dead state |
| BPC_StateManager | `Dispatcher: OnVitalSignalChanged` | `OldSignal`, `NewSignal` to `SS_AudioManager`, `BPC_CameraStateLayer` |
| BPC_StateManager | `Dispatcher: OnHeartRateChanged` | `NewBPM: Float` to `SS_AudioManager`, `WBP_AccessibilityUI` |
| `GI_GameFramework` | `Dispatcher: OnGamePhaseChanged` | Notifies StateManager of phase changes for gating |
| `BPC_CameraStateLayer` | `Dispatcher: OnStateChanged` | Adjusts camera based on state (e.g., combat FOV) |
| `BPC_StressSystem` | `Direct: IsActionPermitted` | Checks if stress effects are currently permitted |
| `WBP_InteractionPromptDisplay` | `Listener on OnStateChanged` | Shows/hides interaction prompts based on state |
---
## 11. Validation / Testing Checklist
- [ ] `RequestStateChange` with valid state returns `Permitted` and fires `OnStateChanged`
- [ ] `RequestStateChange` while Dead (except to Idle) returns `Blocked_Dead`
- [ ] `RequestStateChange` during transition queues and returns `Blocked_CurrentState`
- [ ] `ForceStateChange` bypasses all gating and pushes to ForceStack
- [ ] `RestorePreviousState` pops ForceStack and restores correct state
- [ ] `IsActionPermitted` returns correct `S_ActionPermissionResult` for all 35+ actions
- [ ] `SetOverlayState` correctly writes to `ABP_GASP.OverlayState`
- [ ] Overlay change fires `OnOverlayStateChanged` dispatcher
- [ ] Game phase change (MainMenu InGame Cutscene) correctly gates actions
- [ ] Death auto-transitions to Dead state via `BPC_HealthSystem.OnDeath` binding
- [ ] Respawn restores previous state from ForceStack
- [ ] Rapid state change requests are throttled by `StateTransitionDelay`
- [ ] `IsInCombat()` returns true for `SwingMelee`, `AimingFirearm`, `FiringFirearm`, `Reloading`, `DeployingShield`
- [ ] `IsMovementBlocked()` returns true for all blocking states
- [ ] Heart rate correctly interpolates and fires threshold change events
- [ ] Vital signal changes fire `OnVitalSignalChanged`
- [ ] Injury state auto-transitions when health crosses threshold
- [ ] Edge case: `ForceStateChange` called twice with same state second call is no-op
- [ ] Edge case: `RestorePreviousState` with empty ForceStack defaults to Idle
- [ ] Edge case: State Manager handles owner destruction gracefully (null checks on cached ABP ref)
- [ ] Edge case: `RequestStateChange` with invalid/missing RequesterTag logs warning
---
## 12. Reuse Notes
- BPC_StateManager replaces the need for every system to check every other system's state. Systems call `IsActionPermitted(Tag)` instead of `IsPlayerInState(X) && !IsPlayerInState(Y) && ...`
- New states can be added to `E_PlayerActionState` and `DA_StateGatingTable` without modifying the State Manager blueprint logic.
- The `DA_StateGatingTable` Data Asset allows designers to tune gating rules without opening blueprints.
- Force stack pattern supports nested force overrides (e.g., Death Cutscene flashback Respawn).
- GASP variables `bIsInAction` and `ActionIntensity` allow AnimBP to blend differently during action vs exploration without the AnimBP knowing about game logic.
- The action flag system (`RegisterActionFlag`/`UnregisterActionFlag`/`HasActionFlag`) enables lightweight transient permission tracking without formal state transitions.
- `E_PlayerVitalSignals` drives audio heartbeat tempo, camera breathing sway, controller haptics, and accessibility visual heartbeat all from a single source of truth.
---
*Blueprint Spec: BPC_StateManager — Central Nervous System for the Modular Game Framework. Architecture document: [`bpc-statemanager.md`](../../architecture/bpc-statemanager.md)*