Files
UE5-Modular-Game-Framework/docs/developer/02-player-systems.md
Lefteris Notas ccd1872e59 Update documentation for player, inventory, and weapon systems
- Added C++ status updates for player systems (02-player-systems.md) indicating the implementation status of systems 08-11 and their relation to BPC_StateManager.
- Enhanced inventory systems documentation (04-inventory-systems.md) with C++ status for BPC_InventorySystem and DA_ItemData, clarifying their implementation details.
- Updated weapons systems documentation (08-weapons-systems.md) to reflect the C++ implementation status of BPC_DamageReceptionSystem and stubs for hit reaction and shield defense systems.
2026-05-21 18:41:43 +03:00

488 lines
28 KiB
Markdown

# 02 — Player State & Embodiment Systems (Systems 08-15)
**Category Purpose:** These 8 systems form the player character's core simulation layer — health, stamina, stress, movement, hiding, first-person body, camera, and metrics tracking. They cascade: health affects stress, stress affects movement, movement affects camera. All feed into the animation system (ABP_GASP) via event dispatchers.
**C++ Status:** Systems 08-11 have C++ stubs (`Source/PG_Framework/Public/Player/`) providing UCLASS definition, basic variables, and event dispatchers. The Blueprint child provides the full runtime implementation. Systems 12-15 are BP-only. All 8 are referenced by `BPC_StateManager` (130) for gating evaluations.
---
## System Index
| # | System | Asset Type | Role |
|---|--------|-----------|------|
| 08 | `BPC_HealthSystem` | Component | Health pool, damage resistance, death trigger, healing |
| 09 | `BPC_StaminaSystem` | Component | Stamina pool, sprint/action drain, exhaustion states |
| 10 | `BPC_StressSystem` | Component | Psychological stress, tiers (Calm→Catatonic), hallucinations |
| 11 | `BPC_MovementStateSystem` | Component | Movement mode + posture tracking; GASP liaison; footsteps |
| 12 | `BPC_HidingSystem` | Component | Hide/peek/breath-hold; LOS checks; stress decay while hidden |
| 13 | `BPC_EmbodimentSystem` | Component | First-person body mesh, arm IK, visibility modes, overlays |
| 14 | `BPC_CameraStateLayer` | Component | Camera FOV/offset/rotation layers for all states; shake system |
| 15 | `BPC_PlayerMetricsTracker` | Component | Player metrics: accuracy, deaths, playstyle ratios |
---
## Category Data Flow — The Cascade
```
┌──────────────────────────────────────────────────────────────────────┐
│ PLAYER SIMULATION CASCADE │
│ │
│ Combat Damage │
│ ↓ │
│ BPC_HealthSystem │
│ ↓ dispatchers: OnDamageTaken → │
│ BPC_StressSystem ←── also fed by: enemy proximity, environment, │
│ ↓ dispatchers: OnStressTierChanged → narrative events │
│ BPC_CameraStateLayer (FOV pulse, chromatic aberration) │
│ BPC_MovementStateSystem (movement penalties at high stress) │
│ ↓ dispatchers: OnMovementModeChanged → │
│ BPC_StaminaSystem (regen blocked while sprinting) │
│ ABP_GASP (animation selection) │
│ ↓ dispatchers: OnSprintStateChanged → │
│ BPC_StaminaSystem (continuous drain during sprint) │
│ │
│ BPC_HidingSystem ←── interacts with both: │
│ ↓ stress decay while hidden │
│ ↓ camera constrained while hiding │
│ ↓ movement disabled while inside spot │
│ │
│ BPC_EmbodimentSystem ←── visual overlays from damage/water │
│ BPC_PlayerMetricsTracker ←── records everything for analytics │
└──────────────────────────────────────────────────────────────────────┘
```
---
## 08 — BPC_HealthSystem: Health, Damage & Death
**What It Does:** The central survivability component. Manages a health pool (MaxHealth → CurrentHealth), applies damage with type-based resistance calculation, handles healing, tracks death state (Alive → Dying → Dead → PermaDeath), and fires dispatchers that cascade to stress, UI, and death handling systems.
**How It Works Internally:**
**Damage Application Flow:**
1. `ApplyDamage(DamageEvent)` called by weapon, trap, or environment
2. Early-out checks: Is DeathState == Dead? Is bIsInvincible? → return 0
3. Calculate resistance: look up `S_DamageResistance` for `DamageType`, multiply `BaseAmount` by `Multiplier`
4. `CurrentHealth -= EffectiveDamage`
5. Reset `RegenDelay` timer (regen starts after delay from last damage)
6. Fire `OnDamageTaken``BPC_StressSystem` reacts, HUD updates
7. Check thresholds: if `CurrentHealth <= 0``HandleDeath()`; if `CurrentHealth <= CriticalHealthPercent * MaxHealth` → fire `OnHealthCritical`
8. If DamageType is `Fear` → stress multiplier on `BPC_StressSystem`
**Resistance System:**
- Default resistances stored as array of `S_DamageResistance` (type → multiplier)
- 7 damage types: Physical, Arcane, Fire, Poison, Fear, Environmental, True (bypasses all)
- `AddResistance()` / `RemoveResistance()` at runtime for buffs/debuffs
**Death States:**
- `Alive` → normal; `Dying` → downed, waiting for recovery/final death; `Dead` → no actions; `PermaDeath` → save-scrubbing locked
- On death: clear all timers (regen, DoT, invincibility), fire `OnDeath`, notify `GM_CoreGameMode.HandlePlayerDead()`
**Health Regeneration:**
- `RegenDelay` seconds after last damage before regen starts
- Timer ticks `RegenRate` HP per second until `CurrentHealth >= MaxHealth`
- Blocked by death state
**Implementation Patterns:**
- `GetHealthNormalised()` returns 0.0-1.0 for UI bars — smooth interpolation, never raw polling
- `KillInstant()` bypasses everything for scripted deaths
- `ApplyDamageOverTime()` creates a looping timer with unique SourceName to prevent stacking
- `SetMaxHealth(NewMax, bMaintainRatio)` scales CurrentHealth proportionally or clamps
- All dispatchers fire on authority; UI binds and reacts
**Integration Points:**
- **Listens to:** Nothing directly (called by damagers)
- **Broadcasts:** `OnHealthChanged`, `OnDamageTaken`, `OnHealthCritical`, `OnDeath`, `OnDeathStateChanged`, `OnInvincibilityChanged`
- **Key consumers:** `BPC_StressSystem` (damage → stress), `WBP_HUD` (health bar), `GM_CoreGameMode` (death), `BPC_HitReactionSystem` (stagger)
**Edge Cases:**
- Invincibility blocks all damage for its duration (used after respawn/dodge)
- DoT with same SourceName replaces existing (no stacking)
- ApplyHealing on dead character returns 0, no dispatchers
- Negative damage treated as 0
---
## 09 — BPC_StaminaSystem: Stamina Pool & Exhaustion
**What It Does:** Manages a stamina resource that drains during sprinting, jumping, climbing, dodging, and special attacks. Enforces minimum stamina thresholds per action, applies exhaustion penalties (Normal → Low → Exhausted), and coordinates with MovementState for regen blocking while moving.
**How It Works Internally:**
**Drain System:**
- Each `E_StaminaActionType` (Sprint, Jump, Climb, SpecialAttack, Dodge, Environment) has a `S_StaminaDrainRate` config: `DrainPerSecond` (continuous), `DrainFlat` (one-shot), `MinStamina` (threshold to perform), `CooldownAfterUse`
- `DrainStamina(ActionType)` checks: cooldown active? sufficient stamina? → deduct, update exhaustion, start regen delay
- `StartContinuousDrain(ActionType)` for sprint/climb — timer runs at 0.1s intervals
- `StopContinuousDrain(ActionType)` stops when action ends
**Exhaustion States:**
- `Normal` (full operation) → `Low` (below LowThreshold, heavy breathing) → `Exhausted` (below ExhaustedThreshold, cannot sprint, slow regen)
- `UpdateExhaustionState()` checks `CurrentStamina / MaxStamina` against configured thresholds
- Transition downward is instant; recovery is gradual
- `OnExhausted` dispatcher → player controller shows feedback, sprint blocked
**Regeneration:**
- `RegenSettings` configures: `RegenRate`, `RegenDelay`, `ExhaustedRegenRate`, `ExhaustedRegenDelay`, `RegenBlockedWhileMoving`
- If `RegenBlockedWhileMoving`: listens to `BPC_MovementStateSystem.OnMovementModeChanged`, pauses regen when speed > walk
- `BlockRegen(Duration)` for external debuffs
**Implementation Patterns:**
- `CanAffordAction(ActionType)` checks both stamina level and cooldown before allowing
- Per-action cooldowns prevent dodge/jump spamming
- `RestoreStamina(Amount, Tags)` for potions/resting
- `SetMaxStamina(NewMax, bMaintainRatio)` scales current proportionally
**Integration Points:**
- **Called by:** `PC_PlayerController` (sprint, jump, dodge input)
- **Broadcasts:** `OnStaminaChanged``WBP_HUD`, `OnExhausted` → movement/audio, `OnExhaustionStateChanged``ABP_GASP` (breathing anim)
- **Listens to:** `BPC_MovementStateSystem.OnMovementModeChanged` (regen blocking)
- **Key consumers:** `BPC_MovementStateSystem` (exhausted blocks sprint), `BPC_CameraStateLayer` (exhaustion effects)
**Edge Cases:**
- Multiple continuous drains stack independently
- DrainStamina during cooldown returns false without side effects
- MaxStamina change with bMaintainRatio preserves current percentage
- All timers cleaned on component destroy
---
## 10 — BPC_StressSystem: Psychological Stress & Sanity
**What It Does:** Simulates the player's psychological state via an accumulating stress meter. Multiple stress sources contribute simultaneously (enemy proximity, environmental horror, health deficit, narrative events, supernatural, isolation). Stress tiers (Calm→Uneasy→Distressed→Panicked→Terrified→Catatonic) drive visual distortion, audio hallucinations, movement penalties, and narrative branching.
**How It Works Internally:**
**Multi-Source Architecture:**
- `ActiveSources` map (FName identifier → `S_StressSourceData`) tracks all contributing sources
- Each source has: `CurrentIntensity`, `MaxIntensity`, `DecayRate`, `SourceTags`
- `AddStressSource()` adds or updates a source; `RemoveStressSource()` removes it
- `RecalculateFromSources()` sums all source intensities and updates total
**Stress Events:**
- `AddStress(Event)` can be instant (`bIsInstant`) or gradual (adds to source system)
- `HealthDeficitMultiplier` amplifies incoming stress when health < 50% creates death spiral feel
- `OnDamageTakenHandler` converts damage amount to stress (Fear damage type gets 2x multiplier)
- `ForcePanic(Duration)` temporarily forces Panicked tier for narrative use
**Tier System:**
- 6 tiers with configurable thresholds: `Uneasy` (subtle audio), `Distressed` (visual noise, heartbeat), `Panicked` (heavy distortion, movement penalty), `Terrified` (hallucinations, input inversion), `Catatonic` (freeze/stumble)
- `UpdateTier()` compares `CurrentStress` against threshold values
- Tier transitions fire `OnStressTierChanged` systems react to tier, not raw stress value
- `TierEntryTimes` map records when each tier was entered for duration tracking
**Hallucination System:**
- At `Terrified` tier: `HandleHallucinationCheck()` rolls random chance each tick
- Triggers random hallucination events (visual, audio, dialogue)
- Stops when tier drops below `Distressed`
**Safe Zones & Recovery:**
- `SetSafeZone(bSafe)` toggles passive decay
- `PassiveDecayTick()` slowly reduces stress when in safe area or below Distressed
- `OnSafeZoneChanged` dispatcher atmosphere system adjusts
**Implementation Patterns:**
- Sources with identifiers enable precise tracking (e.g., "Enemy_Guard_12", "Area_Basement")
- `GetTierDuration(Tier)` for narrative systems to query how long player has been panicked
- At Catatonic, further stress accumulation is blocked (immune at max)
- All effects driven by tier dispatchers, never by polling
**Integration Points:**
- **Called by:** Enemies (proximity), environment triggers, `BPC_HealthSystem` (damage handler)
- **Broadcasts:** `OnStressTierChanged` `BPC_CameraStateLayer` (post-process), `ABP_GASP` (anim), `WBP_HUD` (vignette), `OnHallucinationTriggered` hallucination/audio managers, `OnCatatonicStateEntered` input lockdown
- **Listens to:** `BPC_HealthSystem.OnDamageTaken`
**Edge Cases:**
- Catatonic blocks all non-instant stress additions
- Stress clamping prevents overflow past MaxStress
- Source with matching identifier updates existing entry (no duplicate sources)
- ForcePanic temporarily locks tier and restores gradually after duration
---
## 11 — BPC_MovementStateSystem: Movement Mode & Posture
**What It Does:** The "movement oracle" tracks the player's current state (movement mode + posture), reports changes via dispatchers, and applies movement settings to the CharacterMovementComponent. Other systems query here instead of polling the movement component directly. Bridges gameplay state to GASP animation.
**How It Works Internally:**
**Dual State Tracking:**
- `CurrentMovementMode` (Idle, Walking, Jogging, Sprinting, CrouchWalk, Sneaking) speed tier
- `CurrentPosture` (Standing, Crouching, Prone, Sliding, Climbing, Vaulting) body position
- These are independent you can be Sprinting while Standing, or CrouchWalk while Crouching
- `PreviousMovementMode` and `PreviousPosture` stored for transition detection
**Movement Settings Application:**
- `MovementSettings` map (Mode `S_MovementSettings`) defines: `MaxWalkSpeed`, `Acceleration`, `Deceleration`, `GroundFriction`, `bCanSprint`, `bCanCrouch`, `StaminaDrainMultiplier`
- `SetMovementMode(NewMode)` applies the corresponding settings to `CharacterMovementComponent`
- `SetPosture(NewPosture)` adjusts CapsuleComponent half-height (Standing=96, Crouch=60, Prone=30)
- Prone requires going through Crouching first (enforced)
**Movement Penalties:**
- `ApplyMovementPenalty(Multiplier, Duration, PenaltyTag)` temporarily multiplies MaxWalkSpeed
- Timer restores original value after Duration
- Used by stress system (movement penalty at high stress), injury system, debuffs
- Each penalty tagged for tracking multiple penalties stack multiplicatively?
**Footstep System:**
- `CalculateFootstep()`: line trace downward from foot get surface physical material look up `S_FootstepProfile` matching surface and mode
- `DefaultFootstepProfile` as fallback
- Fires `OnFootstep` dispatcher with mode and surface type for audio system
- Velocity threshold determines soft vs hard footstep sound
**GASP Integration:**
- On every mode/posture change: notify ABP_GASP via direct reference or interface
- Sets GASP-specific animation variables (bStrafing, bSprinting, gait, stance)
- This is the ONLY component that talks directly to ABP_GASP for movement state
**Implementation Patterns:**
- Does NOT handle input input remains in `PC_PlayerController`
- `CanTransitionToPosture(TargetPosture)` traces for ceiling clearance
- `SetSprinting(bSprinting)` wraps SetMovementMode with stamina awareness
- `OnMovementUpdated(Tick)` checks velocity, detects start/stop, tracks ground state
- `GetCurrentSpeedNormalised()` for smooth animation blend values
**Integration Points:**
- **Called by:** `PC_PlayerController` (input mode changes)
- **Broadcasts:** `OnMovementModeChanged`, `OnPostureChanged`, `OnMovementStart/Stop`, `OnSprintStateChanged`, `OnJumped/Landed`, `OnFootstep`
- **Key consumers:** `ABP_GASP` (animation), `BPC_StaminaSystem` (drain/regen), `BPC_CameraStateLayer` (FOV), `BP_AudioManager` (footsteps), `BPC_InteractionDetector` (range scaling)
**Edge Cases:**
- Forced transitions (knockdown) apply impulse regardless of current state
- Cannot transition to upright if ceiling is too low (traced by CanTransitionToPosture)
- Rapid mode switching is protected by TransitionBlendTime
- Sprinting auto-stops when stamina hits 0 (external listener)
---
## 12 — BPC_HidingSystem: Stealth & Concealment
**What It Does:** Manages the player entering, occupying, peeking from, and exiting environmental hiding spots. Handles line-of-sight checks against enemies, breath-holding mechanics, and stress reduction while concealed. The central hub for all stealth gameplay.
**How It Works Internally:**
**Hide State Machine:**
```
Exposed → Entering → Hidden ⇄ Peeking → Exiting → Exposed
```
- `Entering/Exiting`: transient animation states
- `Hidden`: fully concealed, LOS blocked, stress decaying
- `Peeking`: partial exposure, camera offset, timed max duration
- Each transition broadcasts `OnHideStateChanged`
**Hide Spot Types:**
- `Locker`: fully enclosed (wardrobe, locker) complete concealment
- `BehindCover`: behind low wall/crate peeking possible
- `Under`: under bed/table prone entry
- `InShadow`: standing in shadow volume dynamic, proximity-based
- `TallGrass`: crouch-moving through vegetation partial concealment
- Each type stored in `S_HideSpotInfo` from the hiding spot actor
**Entry Protocol:**
1. Player presses interact near a hide spot
2. `EnterHideSpot(HideSpotActor)` validate I_HidingSpot interface, check slot availability
3. Set `CurrentHideState = Entering`, disable movement input
4. Play entering animation animation notify triggers `OnAnimationHideEnterComplete`
5. Set `CurrentHideState = Hidden`, start stress decay timer, start periodic LOS check
**Line of Sight (LOS) System:**
- `IsPlayerDetectable(EnemyLocation, DetectionRange)` called by AI perception
- If not hidden: always detectable
- If hidden: line trace from enemy to player if trace hits hide spot actor before player: concealed
- Peeking state increases detection radius
- `PerformLOSCheck()` runs on timer: iterates all enemies in range, checks each
- If any enemy has LOS: fire `OnHideSpotCompromised` stress spike, forced exit warning
**Peek System:**
- `StartPeek(Direction)` Left/Right/Over from behind cover
- Camera moves to peek socket location on hide spot actor
- `MaxPeekDuration` timer forces return to Hidden
- `PeekCooldown` prevents rapid toggling
**Stress Interaction:**
- `StressDecayWhileHidden` applied per second while in Hidden state
- `bBlocksStress` on hide spot prevents new stress sources from affecting player
- Hide spots double as psychological safe rooms
**Implementation Patterns:**
- `ForceKickFromHide()` for enemy discovery bForceExit skips animation
- `TryBreathHold()` reduces noise for 8 seconds near enemies
- `OnDamageWhileHiding` handler: penetrating damage forces exit; non-penetrating converts to stress
- Destroying a hide spot while player is inside force-exits them
**Integration Points:**
- **Called by:** `PC_PlayerController` (interact input), AI (IsPlayerDetectable queries)
- **Broadcasts:** `OnHideStateChanged`, `OnEntered/ExitedHidingSpot`, `OnPeekStarted/Ended`, `OnHideSpotCompromised`, `OnForcedExitWarning`, `OnBreathHoldChanged`
- **Key consumers:** `BPC_CameraStateLayer` (peek/hide offsets), `BPC_StressSystem` (decay), `ABP_GASP` (hide anims), `AI_EnemyController` (awareness), `WBP_HUD` (hide indicators)
**Edge Cases:**
- EnterHideSpot while already hiding returns false
- ExitHideSpot while Exposed does nothing
- Multiple players can't occupy limited-slot spots
- Peek max duration and cooldown enforced
- Shutting down the hide spot actor force-exits the player
---
## 13 — BPC_EmbodimentSystem: First-Person Body
**What It Does:** Creates first-person body awareness the player sees their arms, shadow, and optionally legs/torso in context-appropriate situations. Manages mesh visibility modes, environmental body overlays (blood, water, mud), wall proximity detection for arm IK, and shadow casting.
**How It Works Internally:**
**Visibility Modes:**
- `FullBody` (third-person, mirrors, death) `ArmsOnly` (default first-person) `ArmsAndShadow` `Minimal` (only hands, high stress) `Hidden` (cutscenes, UI)
- `SetVisibilityMode(NewMode, bInstant)` toggles mesh components: ArmsMesh, BodyShadowComponent, optional TorsoMesh
- `S_BodyPartVisibility` struct derived from mode controls individual part visibility
**Overlay System:**
- `E_BodyOverlayState`: Clean, BloodSpatter, WaterDroplets, Mud, Toxic
- `ApplyOverlay(Overlay)` with blend: sets MaterialParameter on ArmsMesh toward `TargetValue` at `BlendSpeed`
- Auto-fades after `Duration` seconds
- `ClearOverlay(Type)` removes single or all overlays
- Multiple overlays stack: blood + water = combined effect on material
- `ApplyOverlayBlendTick()` runs each frame for smooth transitions
**Wall Proximity / Arm IK:**
- `CheckWallProximity()`: line trace from camera forward by `BrushTraceDistance`
- If wall detected: apply IK offset to arms (avoids clipping), set `bIsNearWall = true`
- Fires `OnWallProximityChanged` ABP_Arms for IK adjustment
**Damage/Environment Integration:**
- `OnDamageTakenBloodHandler`: blood overlay intensity scales with damage amount
- `OnEnterWaterHandler`: water droplets overlay with fast blend, auto-dries
- Bound to `BPC_HealthSystem.OnDamageTaken` and water volume triggers
**Implementation Patterns:**
- Mesh references cached at BeginPlay from owner (first-person skeletal mesh)
- Shadow uses separate component with shadow-only material not full character mesh
- All overlay blending uses Material Parameter Collections for real-time updates
- Wall proximity is simple line trace; advanced IK requires arms animation blueprint
**Integration Points:**
- **Listens to:** `BPC_HealthSystem.OnDamageTaken` (blood), water volume triggers
- **Broadcasts:** `OnVisibilityModeChanged` `BPC_CameraStateLayer` (FOV adjust), `OnOverlayChanged` `WBP_HUD` (screen effects), `OnWallProximityChanged` ABP_Arms
**Edge Cases:**
- Rapid visibility changes don't cause rendering glitches due to fade transitions
- Overlays stack: intensity values combined via material logic
- Hidden mode overrides all hides everything regardless of other settings
---
## 14 — BPC_CameraStateLayer: Dynamic Camera Controller
**What It Does:** Centralizes all camera-modifying logic: field-of-view changes per state, camera shake system with priority queuing, post-process overrides (vignette, chromatic aberration, color grading), head bob, and location/rotation offsets per game state. Other systems request camera changes here instead of directly manipulating the camera.
**How It Works Internally:**
**Camera State System:**
- 10 states: Default, Aiming, Sprinting, Crouching, Peeking, Hiding, Stressed, Injured, Death, Cutscene
- Each has `S_CameraStateConfig`: TargetFOV, BlendSpeed, HeadOffset, HeadRotation, pitch constraints, head bob settings
- `RequestCameraState(NewState, bImmediate)` sets targets and starts blending
- `BlendToTargetFOV()` runs per tick: smooth interpolation to target FOV
- `BlendCameraOffset()` interpolates head position/rotation offsets
**Camera Shake System:**
- `S_CameraShakeRequest`: ShakeClass, Scale, Priority (Low/Medium/High/Cinematic), Duration, bIsLooping, Tag
- Priority system: higher priority shakes override lower; same tag replaces existing
- `PlayCameraShake(ShakeRequest)` adds to ActiveShakeRequests map and starts playback
- If Duration > 0: timer auto-stops shake
- `StopCameraShake(ShakeTag)` removes specific shake
**Post-Process Overrides:**
- `S_PostProcessOverride`: vignette, chromatic aberration, color grading LUT
- `ApplyPostProcessOverride(Override, bInstant)` blends Material Parameter Collection values
- `ClearPostProcessOverride(BlendTime)` returns to defaults
- Used by stress system (CA at high stress), injury (vignette), death (fade to black)
**Dynamic Effects:**
- **Sprint:** FOV widens by SprintFOVMultiplier
- **Stress:** `UpdateStressPulse()` applies sine-wave FOV oscillation for "unease" feel
- **Crouch:** slight downward offset
- **Peek:** camera moves to hide spot peek socket
- **Hiding:** constricted FOV, darkness effect
**Reactivity:**
- Binds to `BPC_MovementStateSystem.OnMovementModeChanged` → Sprint/Crouch/Default FOV
- Binds to `BPC_StressSystem.OnStressTierChanged` → stress effects at Panicked+
- Binds to `BPC_HealthSystem.OnDamageTaken` → injury shake, vignette
- Called directly by `BPC_HidingSystem` for peek/hide camera states
**Implementation Patterns:**
- Uses Material Parameter Collection (MPC_CameraEffects) for post-process parameters
- Camera shakes authored as Blueprint Camera Shake classes
- Pitch constraints enforced during hiding/peeking states
- All blending is frame-rate independent via DeltaTime
**Integration Points:**
- **Broadcasts:** `OnCameraStateChanged`, `OnFOVChanged`, `OnShakeStarted/Ended`, `OnPostProcessChanged`
- **Called by:** Any system needing camera change
- **Key consumers:** PlayerCameraManager (direct application), `WBP_HUD` (post-process materials)
**Edge Cases:**
- Shake with same tag and lower priority is ignored (not stacked)
- Rapid FOV switching blends smoothly — no snap transitions
- Stress pulse uses sine wave to prevent jitter
- All active shakes stopped on component destroy or pawn unpossess
---
## 15 — BPC_PlayerMetricsTracker: Analytics & Playstyle
**What It Does:** Records player behavior metrics throughout a session: accuracy (shots fired vs hit), deaths count, damage dealt/received, items used, hiding time, sprint time, objectives completed. Used by `BPC_PlaystyleClassifier` to categorize player as Aggressive/Stealthy/Explorer/Balanced, and by `BPC_DifficultyManager` for adaptive tuning.
**How It Works Internally:**
- Listens to relevant dispatchers from HealthSystem (deaths), WeaponBase (shots/hits), InventorySystem (items used), HidingSystem (hide time), MovementState (sprint time)
- Accumulates counters and ratios internally
- Provides query functions for other adaptive systems
- Persists across sessions via `I_Persistable` for long-term playstyle tracking
**Implementation Patterns:**
- Pure listener — never calls into other systems, only receives events
- All metrics are gameplay-tag keyed for extensibility
- Ratios computed on demand, not continuously
---
## Common Implementation Patterns in This Category
1. **Cascade Architecture:** Systems don't poll each other — they bind to dispatchers. Health change → stress reaction → camera effect → movement penalty. Each system reacts independently.
2. **Normalized Values for UI:** `GetHealthNormalised()`, `GetStaminaNormalised()`, `GetStressNormalised()` all return 0.0-1.0 — UI widgets interpolate smoothly.
3. **Timer-Based Ticks:** Regen, drain, stress decay all use 0.1s interval timers — not event tick. Better performance.
4. **State Not Polling:** Camera doesn't ask "am I sprinting?" — it binds to `OnMovementModeChanged` and reacts.
5. **Threshold Not Continuous:** Stress/hiding systems use tier/state thresholds — other systems react to tier changes, not raw values.
6. **Data Config Everything:** Damage resistances, movement speeds, stamina drain rates, stress thresholds — all in exposed variables or Data Assets, never hardcoded.
---
## Multiplayer Networking
### Category Authority Map
| System | Authority Pattern | Client Prediction |
|--------|------------------|-------------------|
| `BPC_HealthSystem` | Server-authoritative damage/healing/death | Client predicts damage flash; server corrects health via OnRep |
| `BPC_StaminaSystem` | Server-authoritative drain/restore | Client predicts bar decrease; server corrects via OnRep |
| `BPC_StressSystem` | Server-authoritative stress accumulation | Client plays local hallucinations; server is canonical |
| `BPC_MovementStateSystem` | Server-authoritative mode/posture | Native CMC replication handles position/velocity |
| `BPC_HidingSystem` | Server-authoritative enter/exit/peek | Client predicts animation; server validates slot availability |
| `BPC_EmbodimentSystem` | Minimal — visibility/overlay state only synced | Rendering is local per-client |
| `BPC_CameraStateLayer` | State enum only synced | FOV, shake, post-process are local per-client |
| `BPC_PlayerMetricsTracker` | Server accumulates; snapshot replicates | No client prediction needed |
### Key Server RPCs
- `Server_ApplyDamage` / `Server_ApplyHealing` — validated by server before health modification
- `Server_DrainStamina` / `Server_StartContinuousDrain` — validated against MinStamina thresholds
- `Server_AddStress` / `Server_AddStressSource` — stress sources validated server-side
- `Server_SetMovementMode` / `Server_SetPosture` — validated against MovementSettings
- `Server_EnterHideSpot` / `Server_ExitHideSpot` — validated against slot availability
### Multiplayer Cascade
The cascade (Health→Stress→Camera→Movement) works identically in multiplayer because each system fires the same dispatchers regardless of whether the state change came from local mutation or `OnRep`. No special-case multiplayer code needed in the cascade chain.
### Full Spec
See [`docs/architecture/multiplayer-networking.md`](../architecture/multiplayer-networking.md) Section 3.2.
---
*Developer Reference v1.0 — 02 Player Systems. Companion to docs/blueprints/02-player/ specs.*