# 12 — Hiding System (`BPC_HidingSystem`) ## Purpose Manages the player's ability to hide inside, behind, or under environmental objects. Handles entering/exiting hiding spots, line-of-sight checks against enemies, peeking, and stress reduction while concealed. The central component for stealth gameplay. ## Dependencies - **Requires:** `FL_GameUtilities` (interface casts), `I_HidingSpot` (interface on world actors) - **Required By:** `AI_EnemyController` (query hiding state for awareness), `BPC_StressSystem` (stress decay while hidden), `BPC_PlayerController` (movement/input restriction) - **Engine/Plugin Requirements:** GameplayTags, LineTraceByChannel (LOS checks) ## Class Info | Property | Value | |----------|-------| | **Parent Class** | `ActorComponent` | | **Class Type** | Blueprint Component | | **Asset Path** | `Content/Framework/Player/BPC_HidingSystem` | | **Implements Interfaces** | None | --- ## 1. Enums ### `E_HideState` | Value | Description | |-------|-------------| | `Exposed = 0` | Not hiding, fully visible | | `Entering = 1` | Transient — animation playing to enter hide spot | | `Hidden = 2` | Inside hide spot, concealed | | `Peeking = 3` | Partially visible, can look around | | `Exiting = 4` | Transient — animation playing to exit hide spot | ### `E_HideType` | Value | Description | |-------|-------------| | `Locker = 0` | Fully enclosed (locker, wardrobe) | | `BehindCover = 1` | Behind low wall, crate, counter | | `Under = 2` | Under bed, table, porch | | `InShadow = 3` | Standing in a shadow volume | | `TallGrass = 4` | Crouch-moving through vegetation | ### `E_PeekDirection` | Value | Description | |-------|-------------| | `Left = 0` | Peek left from behind cover | | `Right = 1` | Peek right from behind cover | | `Over = 2` | Peek over low cover | --- ## 2. Structs ### `S_HideSpotInfo` | Field | Type | Description | |-------|------|-------------| | `HidingActor` | `AActor` | The hide spot actor | | `HideType` | `E_HideType` | Type of hiding | | `SlotCount` | `Integer` | How many can hide here (-1 = infinite) | | `bHasPeekAbility` | `Boolean` | Can player peek from this spot? | | `PeekSocketLeft` | `FName` | Socket name for left peek camera | | `PeekSocketRight` | `FName` | Socket name for right peek camera | | `PeekSocketOver` | `FName` | Socket name for over peek camera | | `ExitLocation` | `Vector` | World location to place player on exit | | `ExitRotation` | `Rotator` | Rotation to face on exit | | `bBlocksStress` | `Boolean` | Whether this spot reduces stress gain | | `HideTags` | `GameplayTagContainer` | Tags for filtering | | `LOSTraceChannel` | `TEnumAsByte` | Trace channel for LOS checks from this spot | --- ## 3. Variables ### Configuration (Instance Editable, Expose On Spawn) | Variable | Type | Default | Category | Description | |----------|------|---------|----------|-------------| | `bCanHide` | `Boolean` | `true` | `Hiding Config` | Can this player hide at all? | | `StressDecayWhileHidden` | `Float` | `5.0` | `Hiding Config` | Stress lost per second while hidden | | `bEnemyCanFindHidingSpot` | `Boolean` | `true` | `Hiding Config` | Can enemies discover this spot | | `MaxPeekDuration` | `Float` | `3.0` | `Hiding Config` | Max seconds player can peek before forced back | | `PeekCooldown` | `Float` | `1.5` | `Hiding Config` | Seconds before player can peek again | | `CapsuleCheckRadius` | `Float` | `50.0` | `Hiding Config` | Radius for capsule trace when finding hide spots | ### Internal (Private / Protected, No Expose) | Variable | Type | Default | Category | Description | |----------|------|---------|----------|-------------| | `CurrentHideState` | `E_HideState` | `Exposed` | `Hiding State` | Current hiding state | | `PreviousHideState` | `E_HideState` | `Exposed` | `Hiding State` | Previous state for transition detection | | `CurrentHideSpot` | `S_HideSpotInfo` | `-` | `Hiding State` | Active hide spot data | | `PendingHideSpot` | `AActor` | `None` | `Hiding State` | Hide spot being entered | | `PeekTimerHandle` | `FTimerHandle` | `-` | `Hiding State` | Timer for max peek duration | | `PeekCooldownTimerHandle` | `FTimerHandle` | `-` | `Hiding State` | Timer for peek cooldown | | `bCanPeek` | `Boolean` | `true` | `Hiding State` | Whether peeking is allowed | | `LastLOSCheckTime` | `Float` | `0.0` | `Hiding State` | World time of last LOS check | | `LOSTimerHandle` | `FTimerHandle` | `-` | `Hiding State` | Timer for periodic LOS checks | | `BreathHeldTimer` | `FTimerHandle` | `-` | `Hiding State` | Timer for breath-hold mechanic | | `bIsHoldingBreath` | `Boolean` | `false` | `Hiding State` | Whether player is holding breath | ### Replicated (if multiplayer) | Variable | Type | Condition | Description | |----------|------|-----------|-------------| | `CurrentHideState` | `E_HideState` | `RepNotify` | Replicated hide state | | `CurrentHideSpot` | `S_HideSpotInfo` | `Replicated` | Replicated spot reference | --- ## 4. Functions ### Public Functions #### `EnterHideSpot` → `Boolean` - **Description:** Attempts to enter a hiding spot. Returns true if successful. - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `HideSpotActor` | `AActor` | The hide spot to enter | - **Blueprint Authority:** Server (if MP), Any (single-player) - **Flow:** 1. Early-out if CurrentHideState != Exposed or !bCanHide 2. Validate that HideSpotActor implements I_HidingSpot 3. Check slot availability (I_HidingSpot.Execute_HasAvailableSlots) 4. If available: - Set CurrentHideState = Entering - Store HideSpotActor info in CurrentHideSpot - Disable player movement input - Play entering animation (via ABP_GASP) - On animation complete: Set CurrentHideState = Hidden - Fire OnHideStateChanged - Start stress decay timer - Start periodic LOS check timer - Return true 5. If full: return false #### `ExitHideSpot` → `void` - **Description:** Exits the current hiding spot. - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `bForceExit` | `Boolean` | If true, skip exit animation | - **Flow:** 1. If CurrentHideState != Hidden and CurrentHideState != Peeking: return 2. Set CurrentHideState = Exiting 3. If bForceExit: skip animation, immediately set Exposed 4. Else: play exiting animation 5. On animation complete or bForceExit: - Teleport player to CurrentHideSpot.ExitLocation with ExitRotation - Re-enable movement input - Set CurrentHideState = Exposed - Clear CurrentHideSpot - Stop stress decay and LOS timers - Fire OnHideStateChanged - Fire OnExitedHidingSpot #### `StartPeek` → `void` - **Description:** Begins peeking from behind cover. - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `Direction` | `E_PeekDirection` | Which direction to peek | - **Flow:** 1. Early-out if CurrentHideState != Hidden or !bCanPeek or !CurrentHideSpot.bHasPeekAbility 2. Set CurrentHideState = Peeking 3. Move camera to peek socket location 4. Start MaxPeekDuration timer 5. Start PeekCooldown timer (bCanPeek = false during cooldown) 6. Fire OnPeekStarted #### `StopPeek` → `void` - **Description:** Ends the peek and returns to Hidden state. - **Flow:** 1. If CurrentHideState != Peeking: return 2. Set CurrentHideState = Hidden 3. Return camera to default hide position 4. Fire OnPeekEnded #### `IsPlayerDetectable` → `Boolean` - **Description:** Checks if the player is detectable from a given enemy location, considering hide type and edges. - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `EnemyLocation` | `Vector` | Location of the enemy | | `DetectionRange` | `Float` | Max range for detection check | - **Flow:** 1. If CurrentHideState != Hidden: return true (fully detectable) 2. If EnemyLocation distance > DetectionRange: return false 3. Perform line trace from enemy to player's approximate position 4. If trace hits CurrentHideSpot.HidingActor before player: return false (blocked by cover) 5. If trace hits player directly: return true (cover not fully effective) 6. Edge case: peek state increases detection radius #### `TryBreathHold` → `void` - **Description:** Player holds breath to reduce noise/detectability for a short time. - **Flow:** 1. bIsHoldingBreath = true 2. Start breath-hold timer (e.g., 8 seconds) 3. On timer end: forced exhale, brief noise event, bIsHoldingBreath = false 4. Fire OnBreathHoldChanged ### Protected / Private Functions #### `PerformLOSCheck` → `void` - **Description:** Timer callback — checks if any enemy has line of sight to this hide spot. - **Flow:** 1. Get all AI_EnemyController instances within detection range 2. For each enemy: call IsPlayerDetectable(EnemyLocation) 3. If any enemy has LOS: - Fire OnHideSpotCompromised - If CurrentHideSpot.bEnemyCanFindHidingSpot: fire OnForcedExitWarning #### `OnAnimationHideEnterComplete` → `void` - **Description:** Animation notify callback when enter animation finishes. - **Flow:** 1. Set CurrentHideState = Hidden 2. Attach player to hide spot socket if applicable 3. Fire OnHideStateChanged 4. Start stress decay loop #### `OnAnimationHideExitComplete` → `void` - **Description:** Animation notify callback when exit animation finishes. - **Flow:** 1. Perform final exit steps already queued in ExitHideSpot --- ## 5. Event Dispatchers | Dispatcher | Parameters | Bind Access | Description | |------------|-----------|-------------|-------------| | `OnHideStateChanged` | `E_HideState OldState`, `E_HideState NewState` | `Public` | Fired on any hide state change | | `OnEnteredHidingSpot` | `AActor HideSpot`, `E_HideType HideType` | `Public` | Fired when player successfully hides | | `OnExitedHidingSpot` | `AActor HideSpot` | `Public` | Fired when player leaves hiding | | `OnPeekStarted` | `E_PeekDirection Direction` | `Public` | Fired when player starts peeking | | `OnPeekEnded` | `none` | `Public` | Fired when player stops peeking | | `OnHideSpotCompromised` | `AActor EnemyActor` | `Public` | Fired when an enemy sees the player's spot | | `OnForcedExitWarning` | `float TimeUntilDiscovered` | `Public` | Fired when enemy is approaching known spot | | `OnBreathHoldChanged` | `bool bIsHoldingBreath` | `Public` | Fired when breath hold toggles | --- ## 6. Overridden Events / Custom Events ### Event: `BeginPlay` - **Description:** Initialises state, binds to input actions. - **Flow:** 1. Set CurrentHideState = Exposed 2. Register with GI_GameTagRegistry if needed 3. Cache reference to CharacterMovementComponent for disabling during hide ### Custom Event: `ForceKickFromHide` - **Description:** Called externally (e.g., by enemy discovery) to force player out of hiding. - **Flow:** 1. Call ExitHideSpot(bForceExit = true) 2. Apply brief stun or camera shake 3. Fire OnForcedExitWarning with 0 seconds ### Custom Event: `OnDamageWhileHiding` - **Description:** Listener for when damage is taken while hidden. - **Flow:** 1. If damage source penetrates cover: force exit 2. Else: reduce stress instead of health (environmental damage) --- ## 7. Blueprint Graph Logic Flow ```mermaid flowchart TD A[EnterHideSpot called] --> B{Is Exposed?} B -->|No| C[Return false] B -->|Yes| D[Validate I_HidingSpot] D --> E{Has available slots?} E -->|No| F[Return false] E -->|Yes| G[Set Entering state] G --> H[Disable movement input] H --> I[Play Enter animation] I --> J[Animation notify: OnComplete] J --> K[Set Hidden state] K --> L[Start stress decay loop] L --> M[Start periodic LOS check] M --> N[Fire OnEnteredHidingSpot] N --> O[Return true] ``` --- ## 8. Communication Matrix | Who Talks | How | What Is Sent | |-----------|-----|-------------| | `BPC_HidingSystem` | `Dispatcher` | `OnHideStateChanged` -> `BPC_PlayerController` (input), `ABP_GASP` (anim), `BPC_StressSystem` (decay), `AI_EnemyController` (awareness reset) | | `BPC_HidingSystem` | `Dispatcher` | `OnHideSpotCompromised` -> `BPC_StressSystem` (stress spike), `BP_AudioManager` (tension music) | | `BPC_HidingSystem` | `Dispatcher` | `OnForcedExitWarning` -> `WBP_HUD` (hide icon flash), `BP_AudioManager` (heartbeat) | | `External (AI)` | `Query` | `IsPlayerDetectable` called by `AI_EnemyController` for LOS checks | | `External (World)` | `Interface` | `I_HidingSpot` on `BP_Locker`, `BP_Wardrobe`, `BP_Cover` | | `BPC_HidingSystem` | `Dispatcher` | `OnPeekStarted/Ended` -> `BPC_CameraStateLayer` (FOV/position) | --- ## 9. Validation / Testing Checklist - [ ] EnterHideSpot correctly transitions through Entering -> Hidden - [ ] ExitHideSpot correctly transitions through Exiting -> Exposed - [ ] Forced exit with bForceExit = true skips animation, immediately exits - [ ] Peek timer forces player back to Hidden after MaxPeekDuration - [ ] PeekCooldown prevents rapid peek toggling - [ ] IsPlayerDetectable returns correct values for all hide types and peek states - [ ] Multiple hide spots with limited slots: full slots reject entry - [ ] Stress decays while hidden at configured rate - [ ] LOS check detects enemies within range and fires compromise dispatcher - [ ] Edge case: EnterHideSpot called while already hiding returns false - [ ] Edge case: ExitHideSpot called while Exposed does nothing - [ ] Edge case: Destroying hide spot while inside force-exits player --- ## 10. Reuse Notes - Enemy AI characters can use a simplified version to find and occupy hide spots. - Hide spots can be pre-placed (lockers, beds) or dynamic (shadow volumes, tall grass). - The LOS trace channel should be custom (e.g., DetectionChannel) to ignore small props. - Breath-hold mechanic adds depth to stealth gameplay near enemies. - For multiplayer: only the hiding player knows their state; other players see a generic "occupado" on the spot. - Hide spots with bBlocksStress = true can double as narrative safe rooms. --- *Blueprint Spec: Hiding System. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.*