# 08 — Health System (`BPC_HealthSystem`)

> **⚡ C++ Status: Stub** — `Source/PG_Framework/Public/Player/BPC_HealthSystem.h` provides the UCLASS shell, `MaxHealth`/`CurrentHealth` variables, `OnHealthChanged` and `OnDeath` event dispatchers. **The C++ stub has NO gameplay logic** — it exists so other C++ classes (`BPC_StateManager`, `BPC_DamageReceptionSystem`) can forward-reference it. **Create a BP child** and build ALL logic from this spec: `TakeDamage()`, `Heal()`, `OnDeath()` transition, damage resistance modifiers, health regen tick, `I_Damageable` interface. See `docs/developer/cpp-integration-guide.md` for setup steps.
>
> ---

## Purpose
Manages the player's health pool, damage application with type-based resistance, health regeneration, death state, and critical-health events. Serves as the central survivability component on the player character.

## Dependencies
- **Requires:** `FL_GameUtilities` (for safe interface casts), `I_Damageable` (interface on the owner), `GI_GameFramework` (for GamePhase queries)
- **Required By:** `BPC_StressSystem` (health below threshold triggers fear events), `BPC_PlayerMetricsTracker` (death tracking), `WBP_HUD` (health bar), `AI_EnemyController` (player health as awareness trigger), `BPC_InteractionDetector` (low-health checks)
- **Engine/Plugin Requirements:** GameplayTags, DeveloperSettings

## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `ActorComponent` |
| **Class Type** | Blueprint Component |
| **Asset Path** | `Content/Framework/Player/BPC_HealthSystem` |
| **Implements Interfaces** | `I_Damageable` |

---

## 1. Enums

### `E_DamageType`
| Value | Description |
|-------|-------------|
| `Physical = 0` | Bullets, blunt force, falls |
| `Arcane = 1` | Magic, curse, psychic damage |
| `Fire = 2` | Burning, environmental heat |
| `Poison = 3` | Toxins, venom, chemical |
| `Fear = 4` | Psychological damage (stress overflow) |
| `Environmental = 5` | Traps, crushing, drowning |
| `True = 6` | Bypasses all resistances, no reduction |

### `E_DeathState`
| Value | Description |
|-------|-------------|
| `Alive = 0` | Normal gameplay |
| `Dying = 1` | Downed state, waiting for recovery or final death |
| `Dead = 2` | Character is dead, no further actions possible |
| `PermaDeath = 3` | Save-scrubbing locked, forced reload |

---

## 2. Structs

### `S_DamageEvent`
| Field | Type | Description |
|-------|------|-------------|
| `BaseAmount` | `Float` | Raw damage before resistance |
| `DamageType` | `E_DamageType` | Category of damage |
| `Instigator` | `Actor` | Who caused the damage |
| `HitLocation` | `Vector` | World location of impact |
| `DamageTags` | `GameplayTagContainer` | Additional contextual tags |
| `bIsCriticalHit` | `Boolean` | True if this is a headshot / weak-point hit |
| `SourceDescription` | `Text` | Human-readable source (for log / UI) |

### `S_DamageResistance`
| Field | Type | Description |
|-------|------|-------------|
| `DamageType` | `E_DamageType` | Type this resistance applies to |
| `Multiplier` | `Float` | 1.0 = normal, 0.5 = half damage, 2.0 = double |
| `bIsStackable` | `Boolean` | Can this resistance stack with others? |

---

## 3. Variables

### Configuration (Instance Editable, Expose On Spawn)

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `MaxHealth` | `Float` | `100.0` | `Health Config` | Maximum hit points |
| `bRegenEnabled` | `Boolean` | `true` | `Health Config` | Allow passive health regeneration |
| `RegenDelay` | `Float` | `3.0` | `Health Config` | Seconds after last damage before regen starts |
| `RegenRate` | `Float` | `2.0` | `Health Config` | HP recovered per second during regen |
| `RegenDelayUntil` | `Float` | `0.0` | `Health Config` | World time when regen can resume |
| `CriticalHealthPercent` | `Float` | `0.25` | `Health Config` | Below this fraction of MaxHealth, fire critical event |
| `DefaultResistances` | `Array<S_DamageResistance>` | `[]` | `Health Config` | Base damage resistances for this character |
| `bEnableFriendlyFire` | `Boolean` | `false` | `Health Config` | Can the player damage themselves? |

### Internal (Private / Protected, No Expose)

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentHealth` | `Float` | `100.0` | `Health State` | Current HP, clamped [0..MaxHealth] |
| `DeathState` | `E_DeathState` | `Alive` | `Health State` | Current death state |
| `ActiveDamageOverTime` | `Map<FName, FTimerHandle>` | `{}` | `Health State` | Active DoT timers keyed by source name |
| `InvincibilityTimer` | `FTimerHandle` | `-` | `Health State` | Timer handle for temporary invincibility |
| `RegenTimerHandle` | `FTimerHandle` | `-` | `Health State` | Timer handle for regen tick |
| `bIsInvincible` | `Boolean` | `false` | `Health State` | True during invincibility window |
| `LastDamageInstigator` | `Actor` | `None` | `Health State` | Most recent damage source (for kill credit) |
| `LastDamageTime` | `Float` | `0.0` | `Health State` | World time of last damage taken |

### Replicated (if multiplayer)

| Variable | Type | Condition | Description |
|----------|------|-----------|-------------|
| `CurrentHealth` | `Float` | `RepNotify` | Replicated with OnRep handler for UI updates |
| `DeathState` | `E_DeathState` | `RepNotify` | Replicated death state |

---

## 4. Functions

### Public Functions

#### `ApplyDamage` → `S_DamageResult (custom struct)`
- **Description:** Applies damage to the health pool after resistance calculation. Returns effective damage and any side effects.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `DamageEvent` | `S_DamageEvent` | The damage event to process |
- **Blueprint Authority:** Server (if MP), Any (single-player)
- **Flow:**
  1. Early-out if DeathState is Dead or bIsInvincible
  2. Calculate effective damage from BaseAmount * type resistances
  3. If damage > 0: reset RegenDelay timer, update CurrentHealth
  4. Fire OnDamageTaken dispatcher
  5. If CurrentHealth <= 0: call `HandleDeath`
  6. If CurrentHealth <= CriticalHealthPercent: fire OnHealthCritical
  7. Return damage result struct

#### `ApplyHealing` → `Float (effective healing)`
- **Description:** Adds health up to MaxHealth. Returns the amount actually healed.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `HealAmount` | `Float` | Raw healing value |
  | `HealTags` | `GameplayTagContainer` | Contextual tags |
- **Blueprint Authority:** Server (if MP), Any (single-player)
- **Flow:**
  1. Early-out if DeathState is Dead
  2. Clamp: `CurrentHealth = FMath::Min(MaxHealth, CurrentHealth + HealAmount)`
  3. Fire OnHealthChanged
  4. Return actual heal amount

#### `GetHealthNormalised` → `Float [0.0 - 1.0]`
- **Description:** Returns NormalisedCurrentHealth for UI widgets.
- **Flow:** `Return CurrentHealth / MaxHealth`

#### `SetMaxHealth` → `void`
- **Description:** Changes MaxHealth and optionally heals to match new ratio or clamps current.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `NewMax` | `Float` | New maximum health value |
  | `bMaintainRatio` | `Boolean` | If true, scale CurrentHealth to same ratio |
- **Flow:**
  1. Clamp NewMax to >= 1.0
  2. If bMaintainRatio: `CurrentHealth = (CurrentHealth / MaxHealth) * NewMax`
  3. Else: `CurrentHealth = FMath::Min(CurrentHealth, NewMax)`
  4. Set MaxHealth = NewMax
  5. Fire OnHealthChanged

#### `KillInstant` → `void`
- **Description:** Bypasses all resistances, immediately sets health to 0 and triggers death.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `Instigator` | `Actor` | Who caused the kill |
  | `DeathReason` | `FText` | Reason displayed on death screen |
- **Blueprint Authority:** Server (if MP), Any (single-player)
- **Flow:**
  1. Set CurrentHealth = 0
  2. Set DeathState = Dying
  3. Fire OnHealthChanged with CurrentHealth = 0
  4. Call HandleDeath with bInstant = true

#### `ApplyDamageOverTime` → `void`
- **Description:** Applies periodic damage with tick interval and total duration.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `DamagePerTick` | `Float` | Damage applied each interval |
  | `TickInterval` | `Float` | Seconds between ticks |
  | `TotalDuration` | `Float` | Total seconds the DoT lasts |
  | `DamageType` | `E_DamageType` | Type of each damage tick |
  | `SourceName` | `FName` | Unique name to prevent stacking same DoT |
  | `Instigator` | `Actor` | Who applied the DoT |
- **Flow:**
  1. If SourceName already exists in ActiveDamageOverTime, clear existing timer
  2. Create a Timer by Event with loop = TickInterval
  3. On each tick: build S_DamageEvent, call ApplyDamage
  4. After TotalDuration: clear timer, remove from ActiveDamageOverTime

#### `IsAlive` → `Boolean`
- **Flow:** `Return DeathState == Alive`

#### `AddResistance` → `void`
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `NewResistance` | `S_DamageResistance` | Resistance to add or update |
- **Flow:** If resistance for this DamageType exists, update Multiplier; if not, add to array.

#### `RemoveResistance` → `void`
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `DamageType` | `E_DamageType` | Type to remove |
- **Flow:** Remove all entries matching this DamageType from resistance array.

### Protected / Private Functions

#### `HandleDeath` → `void`
- **Description:** Internal death processing. Sets state, fires dispatchers, notifies GameMode.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `bInstant` | `Boolean` | If true, skip dying state go straight to dead |
- **Flow:**
  1. Set DeathState = (bInstant ? Dead : Dying)
  2. Clear all timers (regen, DoT, invincibility)
  3. Fire OnDeath with DeathState
  4. Call `I_Damageable::OnOwnerDied` on owner if interface exists
  5. Get `GM_CoreGameMode` via `GetWorld()->GetAuthGameMode()`
  6. Call `GM_CoreGameMode::HandlePlayerDead(LastDamageInstigator)`

#### `RegenTick` → `void`
- **Description:** Timer callback — applies one tick of regeneration.
- **Flow:**
  1. If CurrentHealth >= MaxHealth: stop regen timer, return
  2. `CurrentHealth = FMath::Min(MaxHealth, CurrentHealth + RegenRate * TickInterval)`
  3. Fire OnHealthChanged
  4. If CurrentHealth >= MaxHealth: stop regen timer

---

## 5. Event Dispatchers

| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnHealthChanged` | `float OldHealth`, `float NewHealth`, `float Delta` | `Public` | Fired when health changes by any amount |
| `OnDamageTaken` | `S_DamageEvent DamageEvent`, `float EffectiveDamage` | `Public` | Fired each time damage is applied |
| `OnHealingReceived` | `float HealAmount`, `GameplayTagContainer HealTags` | `Public` | Fired each time healing is applied |
| `OnHealthCritical` | `float CurrentHealth`, `float MaxHealth` | `Public` | Fired when health drops below critical threshold |
| `OnDeath` | `E_DeathState DeathState`, `Actor Instigator` | `Public` | Fired when character dies or enters dying state |
| `OnDeathStateChanged` | `E_DeathState OldState`, `E_DeathState NewState` | `Public` | Fired on any death state transition |
| `OnInvincibilityChanged` | `bool bIsInvincible` | `Public` | Fired when invincibility state toggles |

---

## 6. Overridden Events / Custom Events

### Event: `BeginPlay`
- **Description:** Initializes health, loads saved health if persisting, starts regen timer if enabled.
- **Flow:**
  1. Set CurrentHealth = MaxHealth
  2. Check if owner implements `I_Persistable`
  3. If yes, try to load saved health value
  4. If bRegenEnabled: start regen timer with initial delay = 0
  5. Bind to any relevant GamePhase change dispatchers

### Event: `OnComponentDestroyed`
- **Description:** Clean up all active timers.
- **Flow:**
  1. Clear all TimerHandles (Invincibility, Regen, all DoT timers)
  2. Clear ActiveDamageOverTime map

### Custom Event: `ApplyTemporaryInvincibility`
- **Parameters:** `float Duration`
- **Description:** Grants brief invincibility, often used after respawn or dodge.
- **Flow:**
  1. Set bIsInvincible = true
  2. Fire OnInvincibilityChanged(true)
  3. Start timer for Duration
  4. On timer end: set bIsInvincible = false, fire OnInvincibilityChanged(false)

---

## 7. Blueprint Graph Logic Flow

```mermaid
flowchart TD
    A[ApplyDamage called] --> B{DeathState == Dead?}
    B -->|Yes| C[Return 0 damage]
    B -->|No| D{bIsInvincible?}
    D -->|Yes| C
    D -->|No| E[Calculate resistance multiplier]
    E --> F[EffectiveDamage = Base * Multiplier]
    F --> G[CurrentHealth -= EffectiveDamage]
    G --> H[Reset RegenDelay timer]
    H --> I[Fire OnDamageTaken]
    I --> J{CurrentHealth <= 0?}
    J -->|Yes| K[HandleDeath]
    J -->|No| L{CurrentHealth <= CriticalThreshold?}
    L -->|Yes| M[Fire OnHealthCritical]
    L -->|No| N[Fire OnHealthChanged]
    K --> O[Fire OnDeath dispatcher]
    K --> P[Notify GameMode: HandlePlayerDead]
    K --> Q[Clear all timers]
    M --> N
```

---

## 8. Communication Matrix

| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| `BPC_HealthSystem` | `Dispatcher` | `OnHealthChanged` -> `WBP_HUD`, `BPC_StressSystem` |
| `BPC_HealthSystem` | `Dispatcher` | `OnDeath` -> `GM_CoreGameMode`, `SS_SaveSystem`, `BPC_PlayerMetricsTracker` |
| `BPC_HealthSystem` | `Dispatcher` | `OnDamageTaken` -> `BPC_StressSystem`, `BPC_AdaptiveEnvironment` |
| `BPC_HealthSystem` | `Interface` | `I_Damageable.Execute_ApplyDamage` route |
| `BPC_HealthSystem` | `Interface` | `I_Persistable` for save/load of CurrentHealth |
| `External (Weapons, Traps)` | `Interface` | `I_Damageable -> ApplyDamage` |
| `BPC_HealthSystem` | `Direct` | `GM_CoreGameMode.HandlePlayerDead` on death |
| `BPC_HealthSystem` | `Subsystem` | `GI_GameFramework.GetGamePhase` for phase-restricted actions |

---

## 9. Validation / Testing Checklist

- [ ] ApplyDamage with all E_DamageType values produces correct resistance-modified damage
- [ ] Health never drops below 0 or exceeds MaxHealth
- [ ] Regeneration does not start while RegenDelay timer is active
- [ ] Regeneration stops when health reaches MaxHealth
- [ ] Death sets DeathState properly and fires OnDeath exactly once
- [ ] Temporary invincibility blocks all damage for its duration
- [ ] Damage-over-time correctly applies ticks and cleans up timer on death
- [ ] Critical health event fires only when crossing below threshold from above
- [ ] Edge case: ApplyHealing on a dead character returns 0 with no dispatchers
- [ ] Edge case: ApplyDamage with negative BaseAmount is treated as 0
- [ ] Edge case: Multiple simultaneous DoT sources with same SourceName do not stack
- [ ] All dispatchers are cleaned up on component destroy

---

## 10. Reuse Notes

- This component can be used on AI characters and enemy pawns by exposing the same configuration variables.
- For enemies, consider disabling regen (bRegenEnabled = false) unless a specific healing mechanic exists.
- The resistance system supports temporary buffs via AddResistance / RemoveResistance at runtime.
- If implementing a downed / bleeding state, extend E_DeathState with custom values and add a recovery timer check in HandleDeath.
- Damage numbers displayed as floating text should bind to OnDamageTaken.
- For multiplayer: all ApplyDamage logic runs on Server; clients receive repnotified CurrentHealth and fire local cosmetic dispatchers only.

---

## 11. Multiplayer Networking (Expanded)

### Replicated Variables (Existing + New)

| Variable | Type | Condition | Description |
|----------|------|-----------|-------------|
| `CurrentHealth` | `Float` | `Replicated Using OnRep_CurrentHealth` | Clients sync via OnRep → dispatcher |
| `DeathState` | `E_DeathState` | `Replicated Using OnRep_DeathState` | Death state synced; OnRep triggers death effects |
| `bIsInvincible` | `Boolean` | `Replicated` | Invincibility visible to other players |
| `MaxHealth` | `Float` | `Replicated` | Synced for UI percentage calculations |

### Server RPCs

| RPC | Direction | Description |
|-----|-----------|-------------|
| `Server_ApplyDamage` | Client→Server | Client reports taking damage. Server validates source, range, damage type. |
| `Server_ApplyHealing` | Client→Server | Client requests healing. Server validates heal source, applies. |
| `Server_KillInstant` | Client→Server | Only accepted from authoritative sources (GM, traps). Client calls are rejected. |

### Authority Gates

```
Function ApplyDamage(DamageEvent)
  Switch HasAuthority
    Authority:
      → Calculate resistance, apply damage, fire dispatchers
    Remote:
      → Return (clients never modify health directly)
      → Server_ApplyDamage is called by damage source (weapon/trap), not victim

Function ApplyHealing(HealAmount)
  Switch HasAuthority
    Authority:
      → Apply healing, fire dispatchers
    Remote:
      → Return
      → Consumable use calls Server_UseConsumable → server calls ApplyHealing
```

### Client Prediction

- **Health bar:** Client predicts damage flash on hit; server-corrected value arrives via `OnRep_CurrentHealth`.
- **Death:** No prediction. `OnRep_DeathState` triggers all death effects (screen fade, ragdoll).
- **Healing:** Client shows green flash immediately + predicted health; server corrects within one RTT.
- **Invincibility:** Visual effect (shield shimmer) is local cosmetic; state is replicated.

### OnRep Handlers

```
OnRep_CurrentHealth()
  → Broadcast OnHealthChanged(OldHealth, CurrentHealth, Delta)
  → UI updates identically to single-player path

OnRep_DeathState()
  → Broadcast OnDeathStateChanged(OldState, NewState)
  → If Dead: play death animation, screen effects, notify GM
```

### Anti-Cheat

- Server validates all `Server_ApplyDamage` calls: check instigator range, weapon fire rate, damage type validity.
- Server never trusts `DamageEvent.BaseAmount` from client — always recalculates from weapon data.
- `Server_KillInstant` is only callable from server-authoritative systems (traps, death zones, GM).

---

*Blueprint Spec: Health System. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.*