- Updated references from GI_GameTagRegistry to DA_GameTagRegistry in architecture overview and implementation patterns documentation. - Added new Blueprint specification for GI_StarterGameInstance, detailing its purpose, configuration, and integration pattern. - Introduced DA_GameTagRegistry Blueprint specification, centralizing GameplayTag management and providing functions for tag validation and logging. - Created documentation for the Starter GameInstance, outlining its role in the project setup and how other systems can integrate with it.
664 lines
39 KiB
Markdown
664 lines
39 KiB
Markdown
# 130 — Central State Authority (`BPC_StateManager`)
|
||
|
||
## 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.0–1.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)*
|