add blueprints
This commit is contained in:
345
docs/blueprints/02-player/10_BPC_StressSystem.md
Normal file
345
docs/blueprints/02-player/10_BPC_StressSystem.md
Normal file
@@ -0,0 +1,345 @@
|
||||
# 10 — Stress / Sanity System (`BPC_StressSystem`)
|
||||
|
||||
## Purpose
|
||||
Tracks the player's psychological state via a stress meter that accumulates from environmental horror, enemy proximity, health deficits, and narrative events. Drives visual distortion, audio hallucinations, gameplay penalties, and narrative branching based on sanity thresholds.
|
||||
|
||||
## Dependencies
|
||||
- **Requires:** `BPC_HealthSystem` (low health triggers stress), `GI_GameFramework` (phase checks)
|
||||
- **Required By:** `BPC_PlayerController` (shader/audio effects), `WBP_HUD` (stress vignette), `NarrativeManager` (sanity-gated content), `BPC_AdaptiveEnvironment` (world distortion)
|
||||
- **Engine/Plugin Requirements:** GameplayTags, Timers, Material Parameter Collections (for post-process effects)
|
||||
|
||||
## Class Info
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Parent Class** | `ActorComponent` |
|
||||
| **Class Type** | Blueprint Component |
|
||||
| **Asset Path** | `Content/Framework/Player/BPC_StressSystem` |
|
||||
| **Implements Interfaces** | None |
|
||||
|
||||
---
|
||||
|
||||
## 1. Enums
|
||||
|
||||
### `E_StressTier`
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `Calm = 0` | No stress effects |
|
||||
| `Uneasy = 1` | Subtle audio cues, slight FOV shift |
|
||||
| `Distressed = 2` | Visual noise, heartbeat audio, reduced stamina regen |
|
||||
| `Panicked = 3` | Heavy distortion, screen shake, movement penalty |
|
||||
| `Terrified = 4` | Hallucinations, audio distortion, random input inversion |
|
||||
| `Catatonic = 5` | Brief freeze/stumble, forced slow walk |
|
||||
|
||||
### `E_StressSource`
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `EnemyProximity = 0` | Nearby hostile presence |
|
||||
| `EnvironmentalHorror = 1` | Gore, darkness, unsettling areas |
|
||||
| `HealthDeficit = 2` | Low health triggers fear |
|
||||
| `NarrativeEvent = 3` | Scripted story beats |
|
||||
| `Supernatural = 4` | Ghosts, paranormal activity |
|
||||
| `Isolation = 5` | Long periods alone |
|
||||
| `TraumaTrigger = 6` | PTSD flashback spots |
|
||||
|
||||
---
|
||||
|
||||
## 2. Structs
|
||||
|
||||
### `S_StressThresholds`
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `UneasyThreshold` | `Float` | Stress value to enter Uneasy tier |
|
||||
| `DistressedThreshold` | `Float` | Stress value to enter Distressed |
|
||||
| `PanickedThreshold` | `Float` | Stress value to enter Panicked |
|
||||
| `TerrifiedThreshold` | `Float` | Stress value to enter Terrified |
|
||||
| `CatatonicThreshold` | `Float` | Stress value to enter Catatonic |
|
||||
|
||||
### `S_StressSourceData`
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `SourceType` | `E_StressSource` | Category of source |
|
||||
| `CurrentIntensity` | `Float` | Current contribution from this source [0..Max] |
|
||||
| `MaxIntensity` | `Float` | Maximum contribution this source can apply |
|
||||
| `DecayRate` | `Float` | How fast this source fades when no longer active |
|
||||
| `SourceTags` | `GameplayTagContainer` | Contextual tags for filtering |
|
||||
| `SourceIdentifier` | `FName` | Unique name for this specific source instance |
|
||||
|
||||
### `S_StressEvent`
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Amount` | `Float` | Stress to add or remove |
|
||||
| `SourceType` | `E_StressSource` | Category of source |
|
||||
| `Instigator` | `Actor` | What caused the stress change |
|
||||
| `bIsInstant` | `Boolean` | If true, bypasses buildup curve |
|
||||
| `EventTags` | `GameplayTagContainer` | Additional context |
|
||||
|
||||
---
|
||||
|
||||
## 3. Variables
|
||||
|
||||
### Configuration (Instance Editable, Expose On Spawn)
|
||||
|
||||
| Variable | Type | Default | Category | Description |
|
||||
|----------|------|---------|----------|-------------|
|
||||
| `MaxStress` | `Float` | `100.0` | `Stress Config` | Maximum stress value |
|
||||
| `Thresholds` | `S_StressThresholds` | `(15, 30, 50, 75, 90)` | `Stress Config` | Threshold values for each tier |
|
||||
| `PassiveDecayRate` | `Float` | `2.0` | `Stress Config` | Stress lost per second when in calm area |
|
||||
| `PanicDecayDelay` | `Float` | `5.0` | `Stress Config` | Seconds at max tier before forced decay begins |
|
||||
| `bEnableHallucinations` | `Boolean` | `true` | `Stress Config` | Allow visual/audio hallucinations at high stress |
|
||||
| `bCanGoCatatonic` | `Boolean` | `true` | `Stress Config` | Allow catatonic freeze state |
|
||||
| `bStressAffectsMovement` | `Boolean` | `true` | `Stress Config` | Apply movement penalties from stress |
|
||||
| `HealthDeficitMultiplier` | `Float` | `1.5` | `Stress Config` | How much low health amplifies incoming stress |
|
||||
|
||||
### Internal (Private / Protected, No Expose)
|
||||
|
||||
| Variable | Type | Default | Category | Description |
|
||||
|----------|------|---------|----------|-------------|
|
||||
| `CurrentStress` | `Float` | `0.0` | `Stress State` | Current accumulated stress |
|
||||
| `CurrentTier` | `E_StressTier` | `Calm` | `Stress State` | Current stress tier |
|
||||
| `ActiveSources` | `Map<FName, S_StressSourceData>` | `{}` | `Stress State` | Currently contributing stress sources |
|
||||
| `StressDecayTimer` | `FTimerHandle` | `-` | `Stress State` | Timer for passive decay tick |
|
||||
| `bIsInSafeZone` | `Boolean` | `true` | `Stress State` | True when player is in a calm area |
|
||||
| `LastStressTime` | `Float` | `0.0` | `Stress State` | World time of last stress change |
|
||||
| `bHallucinationsActive` | `Boolean` | `false` | `Stress State` | Whether hallucinations are currently triggered |
|
||||
| `TierEntryTimes` | `Map<E_StressTier, float>` | `{}` | `Stress State` | World time when each tier was entered |
|
||||
|
||||
### Replicated (if multiplayer)
|
||||
|
||||
| Variable | Type | Condition | Description |
|
||||
|----------|------|-----------|-------------|
|
||||
| `CurrentStress` | `Float` | `RepNotify` | Replicated for UI and effects |
|
||||
| `CurrentTier` | `E_StressTier` | `RepNotify` | Replicated tier state |
|
||||
|
||||
---
|
||||
|
||||
## 4. Functions
|
||||
|
||||
### Public Functions
|
||||
|
||||
#### `AddStress` → `Float (actual stress added)`
|
||||
- **Description:** Adds stress from a source. Returns amount actually added.
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Event` | `S_StressEvent` | Stress event to apply |
|
||||
- **Blueprint Authority:** Any
|
||||
- **Flow:**
|
||||
1. If CurrentTier == Catatonic and !Event.bIsInstant: return 0 (immune at max)
|
||||
2. If HealthDeficitMultiplier > 1.0 and health < 50%: scale amount
|
||||
3. If Event.bIsInstant: CurrentStress += Event.Amount
|
||||
4. Else: add/update ActiveSources with Event data, recalculate from sources
|
||||
5. Clamp CurrentStress to [0, MaxStress]
|
||||
6. Update CurrentTier
|
||||
7. Fire OnStressChanged
|
||||
8. If tier changed: fire OnStressTierChanged
|
||||
9. If tier >= Panicked: start tick for hallucinations
|
||||
10. Return delta
|
||||
|
||||
#### `RemoveStress` → `Float`
|
||||
- **Description:** Reduces stress directly or removes a specific source.
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Amount` | `Float` | Amount to reduce |
|
||||
| `SourceIdentifier` | `FName` | Optional — if set, remove this source entirely |
|
||||
- **Flow:**
|
||||
1. If SourceIdentifier set: remove from ActiveSources, recalculate total
|
||||
2. Else: CurrentStress = FMath::Max(0, CurrentStress - Amount)
|
||||
3. Update CurrentTier
|
||||
4. Fire OnStressChanged
|
||||
5. If tier changed: fire OnStressTierChanged
|
||||
|
||||
#### `AddStressSource` → `void`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `SourceData` | `S_StressSourceData` | Source configuration to add |
|
||||
- **Flow:**
|
||||
1. If SourceData.SourceIdentifier already in ActiveSources: update MaxIntensity and DecayRate
|
||||
2. Else: add new entry
|
||||
3. Recalculate total stress from all sources
|
||||
|
||||
#### `RemoveStressSource` → `void`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `SourceIdentifier` | `FName` | Source to remove |
|
||||
- **Flow:**
|
||||
1. Remove from ActiveSources
|
||||
2. Recalculate total stress
|
||||
|
||||
#### `SetSafeZone` → `void`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `bSafe` | `Boolean` | Whether player is in a safe zone |
|
||||
- **Flow:**
|
||||
1. bIsInSafeZone = bSafe
|
||||
2. If bSafe: begin passive decay
|
||||
3. If !bSafe: stop passive decay
|
||||
4. Fire OnSafeZoneChanged
|
||||
|
||||
#### `GetStressNormalised` → `Float [0.0 - 1.0]`
|
||||
- **Flow:** Return CurrentStress / MaxStress
|
||||
|
||||
#### `GetTierDuration` → `Float`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Tier` | `E_StressTier` | Which tier to query |
|
||||
- **Description:** Returns how long the player has been in the given tier.
|
||||
- **Flow:** If TierEntryTimes contains Tier: return CurrentWorldTime - TierEntryTimes[Tier]; else return 0
|
||||
|
||||
### Protected / Private Functions
|
||||
|
||||
#### `UpdateTier` → `void`
|
||||
- **Flow:**
|
||||
1. OldTier = CurrentTier
|
||||
2. Check CurrentStress against Thresholds in descending order
|
||||
3. NewTier = matching tier (highest threshold exceeded)
|
||||
4. If NewTier != OldTier:
|
||||
- CurrentTier = NewTier
|
||||
- Record TierEntryTimes[NewTier] = CurrentWorldTime
|
||||
- Fire OnStressTierChanged
|
||||
|
||||
#### `RecalculateFromSources` → `void`
|
||||
- **Flow:**
|
||||
1. Total = 0.0
|
||||
2. For each ActiveSource: Total += Source.CurrentIntensity
|
||||
3. CurrentStress = FMath::Clamp(Total, 0, MaxStress)
|
||||
4. UpdateTier
|
||||
5. Fire OnStressChanged
|
||||
|
||||
#### `PassiveDecayTick` → `void`
|
||||
- **Flow:**
|
||||
1. If bIsInSafeZone or CurrentTier < Distressed or CurrentStress <= 0: return
|
||||
2. DecayAmount = PassiveDecayRate * 0.1
|
||||
3. CurrentStress = FMath::Max(0, CurrentStress - DecayAmount)
|
||||
4. Fire OnStressChanged
|
||||
|
||||
#### `HandleHallucinationCheck` → `void`
|
||||
- **Flow:**
|
||||
1. If CurrentTier < Terrified or !bEnableHallucinations:
|
||||
- If bHallucinationsActive: StopHallucinations
|
||||
- return
|
||||
2. Random chance each tick (e.g., 5% per second at Terrified)
|
||||
3. If roll succeeds: trigger random hallucination event
|
||||
4. Fire OnHallucinationTriggered
|
||||
|
||||
---
|
||||
|
||||
## 5. Event Dispatchers
|
||||
|
||||
| Dispatcher | Parameters | Bind Access | Description |
|
||||
|------------|-----------|-------------|-------------|
|
||||
| `OnStressChanged` | `float OldStress`, `float NewStress` | `Public` | Fired on any stress change |
|
||||
| `OnStressTierChanged` | `E_StressTier OldTier`, `E_StressTier NewTier` | `Public` | Fired when stress tier changes |
|
||||
| `OnStressSourceAdded` | `FName SourceIdentifier`, `E_StressSource SourceType` | `Public` | Fired when a new stress source is added |
|
||||
| `OnStressSourceRemoved` | `FName SourceIdentifier` | `Public` | Fired when a stress source is removed |
|
||||
| `OnSafeZoneChanged` | `bool bIsSafe` | `Public` | Fired when entering/leaving safe zone |
|
||||
| `OnHallucinationTriggered` | `E_HallucinationType Type` | `Public` | Fired when a hallucination event occurs |
|
||||
| `OnCatatonicStateEntered` | `none` | `Public` | Fired when entering Catatonic tier |
|
||||
| `OnStressRecovered` | `none` | `Public` | Fired when stress drops back to Calm |
|
||||
|
||||
---
|
||||
|
||||
## 6. Overridden Events / Custom Events
|
||||
|
||||
### Event: `BeginPlay`
|
||||
- **Description:** Initialises stress system, binds to health component if available.
|
||||
- **Flow:**
|
||||
1. CurrentStress = 0.0
|
||||
2. CurrentTier = Calm
|
||||
3. Find BPC_HealthSystem on owner
|
||||
4. If found: bind OnDamageTaken to a handler that adds stress for damage taken
|
||||
5. Start passive decay timer (interval 0.1s)
|
||||
|
||||
### Custom Event: `OnDamageTakenHandler`
|
||||
- **Parameters:** `S_DamageEvent DamageEvent`, `float EffectiveDamage`
|
||||
- **Description:** Stress response to taking damage.
|
||||
- **Flow:**
|
||||
1. StressAmount = EffectiveDamage * 0.5
|
||||
2. If DamageType == Fear: StressAmount *= 2.0
|
||||
3. Build S_StressEvent with SourceType = HealthDeficit
|
||||
4. Call AddStress
|
||||
|
||||
### Custom Event: `ForcePanic`
|
||||
- **Parameters:** `float Duration`
|
||||
- **Description:** Forcibly raises stress to Panicked tier for a duration, used by narrative events.
|
||||
- **Flow:**
|
||||
1. CurrentStress = Thresholds.PanickedThreshold
|
||||
2. UpdateTier
|
||||
3. Start timer for Duration
|
||||
4. On timer end: gradually return stress to previous level
|
||||
|
||||
---
|
||||
|
||||
## 7. Blueprint Graph Logic Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[AddStress called] --> B{CurrentTier == Catatonic?}
|
||||
B -->|Yes| C[Return 0]
|
||||
B -->|No| D[Apply HealthDeficitMultiplier]
|
||||
D --> E[Update ActiveSources map]
|
||||
E --> F[Recalculate total stress]
|
||||
F --> G[Clamp to 0..MaxStress]
|
||||
G --> H{NewTier == OldTier?}
|
||||
H -->|No| I[Update CurrentTier]
|
||||
I --> J[Record tier entry time]
|
||||
J --> K[Fire OnStressTierChanged]
|
||||
H -->|Yes| L[Skip tier update]
|
||||
K --> M{Tier >= Panicked?}
|
||||
M -->|Yes| N[Start hallucination ticker]
|
||||
M -->|No| O{Stress dropped below Distressed?}
|
||||
O -->|Yes| P[Stop hallucinations]
|
||||
O -->|No| Q[Continue passive decay]
|
||||
N --> R[Fire OnStressChanged]
|
||||
P --> R
|
||||
Q --> R
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Communication Matrix
|
||||
|
||||
| Who Talks | How | What Is Sent |
|
||||
|-----------|-----|-------------|
|
||||
| `BPC_StressSystem` | `Dispatcher` | `OnStressTierChanged` -> `PC_PlayerController` (post-process), `ABP_GASP` (anim), `WBP_HUD` (vignette) |
|
||||
| `BPC_StressSystem` | `Dispatcher` | `OnHallucinationTriggered` -> `BP_HallucinationManager`, `BP_AudioManager` |
|
||||
| `BPC_StressSystem` | `Dispatcher` | `OnStressChanged` -> `WBP_HUD` (stress bar) |
|
||||
| `BPC_StressSystem` | `Dispatcher` | `OnCatatonicStateEntered` -> `BPC_PlayerController` (input lockdown) |
|
||||
| `External (Enemies)` | `Direct` | Calls `AddStressSource` on detection |
|
||||
| `External (BP_DeathZone)` | `Direct` | Calls `AddStress` via interface |
|
||||
| `BPC_HealthSystem` | `Listener` | Binds to `OnDamageTaken` for health-deficit stress |
|
||||
| `BPC_StressSystem` | `Dispatcher` | `OnSafeZoneChanged` -> `BPC_AdaptiveAtmosphere` |
|
||||
|
||||
---
|
||||
|
||||
## 9. Validation / Testing Checklist
|
||||
|
||||
- [ ] Stress accumulates from multiple sources and sums correctly
|
||||
- [ ] Stress decays passively when in safe zone or above Distressed
|
||||
- [ ] Tier transitions fire exactly once per threshold crossing
|
||||
- [ ] Catatonic tier blocks further stress accumulation
|
||||
- [ ] Hallucinations trigger randomly at Terrified tier and stop below it
|
||||
- [ ] Stress sources with identifiers are properly tracked and removed
|
||||
- [ ] Edge case: Stress added while at MaxStress clamps and does not overflow
|
||||
- [ ] Edge case: RemoveStress with source identifier removes only that source
|
||||
- [ ] Edge case: ForcePanic temporarily locks tier and restores after duration
|
||||
- [ ] All timers clean up on component destroy
|
||||
|
||||
---
|
||||
|
||||
## 10. Reuse Notes
|
||||
|
||||
- Can be used on NPCs for a simplified "morale" system (remove hallucination features).
|
||||
- Stress tiers can gate narrative content — questgivers behave differently based on player stress.
|
||||
- Hallucination types can be configured via Data Asset (DA_HallucinationConfig).
|
||||
- For multiplayer: Stress is server-authoritative; clients receive tier changes for local effects.
|
||||
- The HealthDeficitMultiplier creates a death spiral feel — use carefully for horror pacing.
|
||||
- Safe zones double as narrative checkpoints and stress recovery areas.
|
||||
|
||||
---
|
||||
|
||||
*Blueprint Spec: Stress/Sanity System. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.*
|
||||
Reference in New Issue
Block a user