From eeb1bf82c9c3bb2b56c995c89ffc7f6972bc622f Mon Sep 17 00:00:00 2001 From: Lefteris Notas Date: Tue, 19 May 2026 18:37:42 +0300 Subject: [PATCH] feat: Enhance interaction and inventory systems with new components and functionality - Added BPC_UsableWorldObjectSystem for handling various interactable world objects with detailed manual implementation guide. - Introduced BPC_ActiveItemSystem to manage quick slots and active item usage, including cycling and selection logic. - Implemented BPC_DocumentArchiveSystem for managing collectible documents with read tracking and categorization. - Developed BPC_JournalSystem for narrative entries with auto-adding features based on gameplay events. - Created BPC_KeyItemSystem for key management with consumable and persistent key support. - Enhanced BPC_FirearmSystem for ranged weapon mechanics, including hitscan and projectile firing. - Updated BPC_MeleeSystem for melee combat with combo and blocking mechanics. - Established BPC_ReloadSystem for managing weapon reloading processes, including partial reloads and state management. --- .../03-interaction/19_BP_DoorActor.md | 135 +++++++++++++- .../03-interaction/20_BP_PuzzleDeviceActor.md | 165 +++++++++++++++++- .../21_BPC_ContextualTraversalSystem.md | 165 +++++++++++++++++- .../23_BPC_UsableWorldObjectSystem.md | 123 ++++++++++++- .../04-inventory/26_BPC_ActiveItemSystem.md | 146 +++++++++++++++- .../29_BPC_DocumentArchiveSystem.md | 127 +++++++++++++- .../04-inventory/33_BPC_JournalSystem.md | 117 ++++++++++++- .../04-inventory/34_BPC_KeyItemSystem.md | 118 ++++++++++++- .../08-weapons/74_BPC_FirearmSystem.md | 59 ++++++- .../08-weapons/76_BPC_MeleeSystem.md | 113 +++++++++++- .../08-weapons/78_BPC_ReloadSystem.md | 104 ++++++++++- 11 files changed, 1361 insertions(+), 11 deletions(-) diff --git a/docs/blueprints/03-interaction/19_BP_DoorActor.md b/docs/blueprints/03-interaction/19_BP_DoorActor.md index 985ebdd..9b7a0c8 100644 --- a/docs/blueprints/03-interaction/19_BP_DoorActor.md +++ b/docs/blueprints/03-interaction/19_BP_DoorActor.md @@ -370,4 +370,137 @@ Interact_Implementation ### Anti-Cheat - Server validates player is within interaction range before opening door. - Server validates key item is in player inventory before unlocking. -- Server validates barricade damage source. \ No newline at end of file +- Server validates barricade damage source. + +--- + +## 14. Manual Implementation Guide + +### 14.1 Class Setup +1. Create Blueprint Class: Parent = `Actor`, Name = `BP_DoorActor` +2. Add components: `DoorRoot` (SceneComponent), `DoorFrame` (StaticMesh — static), `DoorMesh` (StaticMesh — rotates/slides), `InteractionCollision` (Box or Sphere) +3. Implement Interfaces: `I_Interactable`, `I_Persistable` +4. Set `Replicates` = true in Class Defaults + +### 14.2 Variable Defaults +| Variable | Default | +|----------|---------| +| `CurrentState` | `Closed` | +| `CurrentLockState` | `Unlocked` | +| `CurrentBarricadeHealth` | `100.0` | +| `AutoCloseDelay` | `3.0` (0 = never) | +| `OpenAngle` | `90.0` | +| `OpenSpeed` | `2.0` | +| `CloseSpeed` | `1.5` | + +### 14.3 Function Node-by-Node + +#### `BeginPlay` +``` +Event BeginPlay + → Cache OwningActor (Self) + → Get DoorMesh by tag/name → Cache DoorMeshComponent + → Spawn Audio Component → Cache DoorAudioComponent + → Apply DefaultState from DoorConfig: + Switch on DoorConfig.DefaultState: + Closed → (nothing, already closed) + Locked → Call LockDoor + Open → Call ForceSetState(Open) ← for save/load or pre-opened doors +``` + +#### `Interact_Implementation(Instigator: Actor)` — I_Interactable +``` +Switch on CurrentState: + Closed → Call TryOpen(Instigator) + Open → Call TryClose(Instigator) + Locked → Call TryUnlockWithItem(Instigator) + Branch on result: + True → Call UnlockDoor → Call TryOpen(Instigator) + False → Call PlayLockedFeedback → Fire OnInteractionFailed + Barricaded → Call DamageBarricade(Instigator) + If attack breaks barricade → OnBarricadeBroken → SetState Closed + Opening/Closing → Return (ignored during animation) +``` + +#### `TryOpen(Instigator)` — **Must check HasAuthority() for MP** +``` + Branch: CurrentState != Closed → Return false + If DoorConfig.IsOneWay: + Check player side (dot product of door forward vs player forward) + If wrong side → Play locked feedback → Return false + SetWeaponState(Opening) + Fire OnDoorStateChanged + PlayOpenAnimation() ← uses Timeline, not Timer +``` + +#### `PlayOpenAnimation` — **Key node setup:** +``` + Create Timeline (float track, 0→1 over OpenSpeed seconds) + Timeline Update pin: + → Get DoorMesh → Set Relative Rotation: + Lerp (Quat): startRotation to startRotation + Rotator(0, OpenAngle, 0) by Timeline Alpha + Timeline Finished pin: + → Call OnOpenComplete + Play Sound: AudioConfig.OpenSound at DoorMesh location via SS_AudioManager +``` + +#### `OnOpenComplete` +``` + Set CurrentState = Open + Fire OnDoorOpened(Instigator, bIsOpenFromFront) + If AutoCloseDelay > 0: + Set Timer by Event (AutoCloseDelay) → Call TryClose(Self) + Use "Self" as instigator for auto-close + Notify LinkedActors with "Open" event +``` + +#### `TryClose(Instigator)` +``` + Branch: CurrentState != Open → Return false + Set CurrentState = Closing + Clear AutoCloseTimer (if running — prevents auto-close during manual close) + Fire OnDoorStateChanged + PlayCloseAnimation() +``` + +#### `TryUnlockWithItem(Instigator)` → `Boolean` +``` + If CurrentLockState == KeyRequired: + Get RequiredItemTag from DoorConfig + Get Instigator → Get BPC_InventorySystem → Call HasItemQuantity(RequiredItemTag, 1) + If found: + If DoorConfig.RemoveItemOnUse: + BPC_InventorySystem.RemoveItemByTag(RequiredItemTag, 1) + Return true + Return false + If CurrentLockState == PuzzleLinked: + Get LinkedPuzzle → Call IsPuzzleSolved + Return result +``` + +#### `DamageBarricade(Instigator, DamageAmount)` +``` + Branch: CurrentState != Barricaded → Return + CurrentBarricadeHealth -= DamageAmount + If CurrentBarricadeHealth <= 0: + Set CurrentBarricadeHealth = 0 + Set CurrentState = Closed + Fire OnBarricadeBroken(Instigator) + Fire OnDoorStateChanged + Mark variable dirty (replication) +``` + +### 14.4 Blueprint Build Checklist +- [ ] Create BP_DoorActor with DoorFrame + DoorMesh + InteractionCollision +- [ ] Add all config variables (S_DoorConfig, S_DoorAudioConfig, LinkedActors) +- [ ] Set DoorMesh mobility to Movable (for rotation during animation) +- [ ] Implement I_Interactable with Interact_Implementation +- [ ] Build TryOpen/TryClose with state validation +- [ ] Create Timeline for door open/close animation (NOT Timer — Timeline for smooth interpolation) +- [ ] Implement auto-close timer with reset on re-open +- [ ] Build TryUnlockWithItem querying player inventory +- [ ] Implement lock/barricade damage with health tracking +- [ ] Add DoorAudioComponent for spatialized sounds +- [ ] Set up LinkedActors — designer assigns in editor +- [ ] Add networking: replicated CurrentState, Server_Interact RPC +- [ ] Test: approach → interact → door opens → auto-closes → locked door → use key → unlocks \ No newline at end of file diff --git a/docs/blueprints/03-interaction/20_BP_PuzzleDeviceActor.md b/docs/blueprints/03-interaction/20_BP_PuzzleDeviceActor.md index fcc24e1..b4c2e6e 100644 --- a/docs/blueprints/03-interaction/20_BP_PuzzleDeviceActor.md +++ b/docs/blueprints/03-interaction/20_BP_PuzzleDeviceActor.md @@ -94,4 +94,167 @@ ## 7. Reuse Notes - All puzzle logic is data-driven via `DA_PuzzleData` -- LinkedActor pattern decouples puzzle from effect \ No newline at end of file +- LinkedActor pattern decouples puzzle from effect + +--- + +## 8. Manual Implementation Guide + +### 8.1 Class Setup +1. Create Blueprint Class: Parent = `Actor`, Name = `BP_PuzzleDeviceActor` +2. Add components: StaticMesh (visual), InteractionCollision (Sphere), WidgetComponent (for interaction prompt) +3. Implement Interfaces: `I_Interactable` +4. Save to: `Content/Framework/Interaction/` + +### 8.2 Variable Initialization (BeginPlay / Construction Script) +``` +Event BeginPlay + ├─ Set CurrentState = Unsolved (or Locked if requires unlock first) + ├─ Set CurrentAttempts = 0 + ├─ Load PuzzleData → if valid: + │ ├─ Set InteractionPrompt = PuzzleData.PromptText + │ ├─ Set MaxAttempts = PuzzleData.MaxAttempts + │ ├─ Set CooldownSeconds = PuzzleData.Cooldown + │ └─ Apply initial visual state (dim if locked, glowing if interactable) + └─ Register with SS_SaveManager if implements I_Persistable +``` + +### 8.3 Function Implementations + +#### `I_Interactable: OnInteract(Instigator: Actor)` → `Boolean` + +``` +[Function: OnInteract] — called by BPC_InteractionDetector + Step 1: Branch on CurrentState: + Case Unsolved → Call BeginInteraction(Instigator) → Return true + Case Solved → Play solved animation/sound → Return false (already solved) + Case Locked → Check if Instigator has required key: + If yes → unlock → BeginInteraction + If no → Play locked feedback → Return false + Case InProgress → Return false (already interacting) + Case Failed → If bResetOnFail: ResetPuzzle → BeginInteraction +``` + +#### `BeginInteraction(Instigator: Actor)` → `void` + +``` +[Function: BeginInteraction] + Step 1: Set CurrentState = InProgress + Step 2: Set bIsInteractable = false (block re-interaction during solve) + Step 3: Fire OnPuzzleStateChanged(InProgress) + + Step 4: Branch on PuzzleData.PuzzleType: + Lever, PressurePlate, ValveWheel: + - No UI overlay needed; player manipulates world objects + - Enable input on the puzzle's interactive levers/buttons + Keypad, CombinationLock, CircuitBoard: + - Open puzzle UI widget (attached to WidgetComponent or fullscreen) + - Set player input mode to UI Only + - Route input to puzzle widget + + Step 5: Block player movement (optional, per puzzle config) + Step 6: Set interaction camera view (zoom to puzzle if needed) +``` + +#### `CheckSolution(PlayerInput: varies by puzzle type)` → `Boolean` + +``` +[Function: CheckSolution] + Step 1: Read correct solution from PuzzleData.Solution + Step 2: Compare player input against solution: + - Lever sequence: check order of pulled levers matches solution + - Keypad: check entered code matches solution code + - CombinationLock: check each dial position + - CircuitBoard: check all connections match solution + - PressurePlate: check weight/actor matches + Step 3: If match: + Call OnPuzzleSolved(Instigator) + Return true + Step 4: If no match: + Increment CurrentAttempts + Branch on MaxAttempts != -1 AND CurrentAttempts >= MaxAttempts: + True → Call OnPuzzleFailed() + False → Show hint/wrong feedback, allow retry + Return false +``` + +**Node example for keypad puzzle:** +``` + Get SolutionString from PuzzleData + Branch: PlayerInputCode == SolutionString +``` + +#### `OnPuzzleSolved(Solver: Actor)` → `void` + +``` +[Function: OnPuzzleSolved] + Step 1: Set CurrentState = Solved + Step 2: Set bIsInteractable = false (permanently) + Step 3: Play solved visual: change material to green/solved glow + Step 4: Play solved sound via SS_AudioManager + Step 5: Fire OnPuzzleSolved(Solver) + Step 6: Fire OnPuzzleStateChanged(Solved) + + Step 7: Notify LinkedActor (if set): + If LinkedActor valid: + If LinkedActor implements I_Toggleable → Call Toggle(Solver) + If LinkedActor implements I_Lockable → Call Unlock(Solver) + Fire OnPuzzleLinkedActorTriggered(LinkedActor) + + Step 8: Grant rewards from PuzzleData: + Get BPC_InventorySystem → If PuzzleData.RewardItem valid → Call AddItem + Get BPC_NarrativeStateSystem → Set flag: PuzzleData.CompletionFlag + + Step 9: EndInteraction() +``` + +#### `OnPuzzleFailed()` → `void` + +``` +[Function: OnPuzzleFailed] + Step 1: Set CurrentState = Failed + Step 2: Play failure sound/visual + Step 3: Fire OnPuzzleFailed(CurrentAttempts) + Step 4: Fire OnPuzzleStateChanged(Failed) + + Step 5: Branch on bResetOnFail: + True → Start timer for CooldownSeconds → Call ResetPuzzle + False → If MaxAttempts reached: Set CurrentState = Locked permanently + + Step 6: EndInteraction() +``` + +#### `EndInteraction()` → `void` + +``` +[Function: EndInteraction] + Step 1: Close puzzle UI widget (if open) + Step 2: Restore player input mode to Game Only + Step 3: Re-enable player movement + Step 4: Restore camera to default + Step 5: If CurrentState != InProgress → Set bIsInteractable = true +``` + +#### `ResetPuzzle()` → `void` + +``` +[Function: ResetPuzzle] + Step 1: Set CurrentState = Unsolved + Step 2: Set CurrentAttempts = 0 + Step 3: Reset all puzzle levers/dials/inputs to default positions + Step 4: Set bIsInteractable = true + Step 5: Fire OnPuzzleStateChanged(Unsolved) +``` + +### 8.4 Blueprint Build Checklist +- [ ] Create BP_PuzzleDeviceActor actor with mesh + collision + widget components +- [ ] Implement I_Interactable interface +- [ ] Add all variables: PuzzleData, CurrentState, bIsInteractable, LinkedActor, MaxAttempts, etc. +- [ ] Implement BeginInteraction with type-switch (lever vs UI vs custom) +- [ ] Implement CheckSolution with type-specific validation +- [ ] Implement OnPuzzleSolved (visual, sound, LinkedActor, rewards, narrative flag) +- [ ] Implement OnPuzzleFailed (retry or lockout) +- [ ] Implement EndInteraction (UI close, input restore, camera restore) +- [ ] Implement ResetPuzzle for retryable puzzles +- [ ] Bind to BPC_InteractionDetector via I_Interactable +- [ ] Test: approach puzzle → interact → solve → door unlocks → save/load restores state \ No newline at end of file diff --git a/docs/blueprints/03-interaction/21_BPC_ContextualTraversalSystem.md b/docs/blueprints/03-interaction/21_BPC_ContextualTraversalSystem.md index 6103724..5bdd6df 100644 --- a/docs/blueprints/03-interaction/21_BPC_ContextualTraversalSystem.md +++ b/docs/blueprints/03-interaction/21_BPC_ContextualTraversalSystem.md @@ -85,4 +85,167 @@ ## 7. Reuse Notes - Uses UE5 Motion Warping for accurate target placement -- Detection uses capsule trace at player height ranges \ No newline at end of file +- Detection uses capsule trace at player height ranges + +--- + +## 8. Manual Implementation Guide + +### 8.1 Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_ContextualTraversalSystem` +2. Add to Player Character +3. Enable `Motion Warping` plugin in Project Settings + +### 8.2 Variable Initialization (BeginPlay) +``` +Event BeginPlay + ├─ Set bIsTraversing = false + ├─ Set DetectionRange = 200.0 (cm) + ├─ Set DetectionHalfAngle = 45.0 (degrees) + ├─ Set VaultHeightThreshold = 120.0 (waist height in cm) + ├─ Set TraversalSpeed = 1.0 + ├─ Set TraversalCooldown = 0.5 + ├─ Get Owner → Get Component by Class (BPC_MovementStateSystem) → Cache reference + ├─ Get Owner → Find Component by Class (BPC_CameraStateLayer) → Cache reference + └─ Get Owner → Cast to Character → Get CharacterMovement → Cache CMC reference +``` + +### 8.3 Function Implementations + +#### `DetectTraversableObstacle()` → `Hit Result, TraversalType, Height` + +``` +[Function: DetectTraversableObstacle] (Pure) + Step 1: Get Owner → Get Actor Location and Forward Vector + Step 2: Three capsule traces at different heights: + Trace A (Low: half-height = 30cm): + Start: OwnerLocation + UpVector * 30 + End: Start + ForwardVector * DetectionRange + Trace Channel: WorldStatic + Trace B (Medium: half-height = 60cm): + Same but offset by 60cm vertically + Trace C (High: half-height = 90cm): + Same but offset by 90cm vertically + + Step 3: For each trace that hits: + Get Hit Actor → Get Hit Location → Get Hit Normal + Calculate obstacle top: trace down from above obstacle to find top edge + ObstacleHeight = topHit.Location.Z - bottomHit.Location.Z + + Step 4: Classify by height: + ObstacleHeight <= 40cm → E_TraversalHeight::Low (step up) + ObstacleHeight <= VaultHeightThreshold → E_TraversalHeight::Medium (vault) + ObstacleHeight > VaultHeightThreshold → E_TraversalHeight::High (mantle) + + Step 5: Check clearance above obstacle (trace upward from top) + If ceiling too low → cannot mantle → return invalid + + Step 6: Determine traversal type: + - Low + no obstacle: just step (automatic) + - Medium + clearance: Vault + - High + ledge: Mantle + - Narrow gap (sides): Squeeze + - Low barrier with gap: Slide + + Step 7: Calculate landing position: + Forward trace from obstacle top to find landing spot + Set MotionWarpingTarget = landingPosition + + Step 8: Return HitResult, TraversalType, TraversalHeight struct +``` + +**Nodes:** `Line Trace by Channel` (x5+), `Break Hit Result`, `Get Hit Location`, `Get Hit Normal`, `Vector Up/Down/Forward`, `Make Vector`, `Branch`, `Switch on E_TraversalHeight` + +#### `AttemptTraversal(TraversalType, TargetLocation)` → `Boolean` + +``` +[Function: AttemptTraversal] + Step 1: Branch on bIsTraversing → If true, return false + Step 2: Branch on CooldownActive → If cooldown timer active, return false + Step 3: Query BPC_StateManager → IsActionPermitted("Action.Traverse") + False → Fire OnTraversalFailed("Blocked by state"), return false + + Step 4: Set bIsTraversing = true + Step 5: Set CurrentTraversalType = TraversalType + Step 6: Notify BPC_MovementStateSystem → SetMovementMode(Vaulting, Forced) + Step 7: Disable player input (optional — depends on traversal type) + Step 8: Fire OnTraversalStarted(TraversalType, TargetLocation) + + Step 9: Call ExecuteTraversal(TraversalType, TargetLocation) + Step 10: Return true +``` + +#### `ExecuteTraversal(Type, Target)` → `void` + +``` +[Function: ExecuteTraversal] + Step 1: Switch on Type: + Case Vault: + - Play Montage: AM_Vault (from Animation Blueprint) + - Add Motion Warping Target: "VaultTarget" → Target location + - Root motion in montage moves character to Target + Case Mantle: + - Play Montage: AM_Mantle + - Add Motion Warping Target: "MantleTop" → ledge top + - Add Motion Warping Target: "MantleLand" → landing position + Case Slide: + - Play Montage: AM_Slide + - Set capsule half-height to crouch height temporarily + - Add Motion Warping Target: "SlideTarget" + Case Squeeze: + - Play Montage: AM_Squeeze + - Add Motion Warping Target: "SqueezeTarget" + Case LedgeGrab: + - Play Montage: AM_LedgeGrab + - Disable gravity temporarily + - Attach to ledge point + + Step 2: On Montage Completed or Blending Out: + Call OnTraversalComplete() +``` + +**Nodes:** `Play Montage`, `Add or Update Motion Warping Target` (name + location), `Set Capsule Half Height`, `On Completed` delegate + +#### `OnTraversalComplete()` → `void` + +``` +[Function: OnTraversalComplete] + Step 1: Set bIsTraversing = false + Step 2: Restore capsule half-height (if modified) + Step 3: Re-enable player input (if disabled) + Step 4: Notify BPC_MovementStateSystem → restore previous movement mode + Step 5: Start TraversalCooldown timer → set bCanTraverse = false + On timer end → bCanTraverse = true + Step 6: Fire OnTraversalComplete(CurrentTraversalType) +``` + +#### `CancelTraversal()` → `void` + +``` +[Function: CancelTraversal] + Step 1: Branch on bIsTraversing → If false, return + Step 2: Stop Montage (AM currently playing) + Step 3: Set bIsTraversing = false + Step 4: Restore movement mode, input, capsule height + Step 5: Fire OnTraversalFailed("Cancelled") +``` + +### 8.4 Event Dispatcher Bindings + +| Bind to Dispatcher | Custom Event | Logic | +|-------------------|-------------|-------| +| `IA_Jump` (Pressed near obstacle) | `OnTraversalInput` | Call DetectTraversableObstacle → if valid: AttemptTraversal | +| `BPC_MovementStateSystem.OnMovementModeChanged` | `CheckTraversalState` | If in Vaulting mode and bIsTraversing false → force cancel | + +### 8.5 Blueprint Build Checklist +- [ ] Enable Motion Warping plugin +- [ ] Create BPC_ContextualTraversalSystem, add to Player Character +- [ ] Add all variables with defaults +- [ ] Create traversal montages: AM_Vault, AM_Mantle, AM_Slide, AM_Squeeze, AM_LedgeGrab +- [ ] Add Motion Warping notifies in montages at key frames +- [ ] Implement DetectTraversableObstacle with multi-height capsule traces +- [ ] Implement AttemptTraversal with state/cooldown checks +- [ ] Implement ExecuteTraversal with type-switch and motion warping targets +- [ ] Implement OnTraversalComplete cleanup +- [ ] Bind IA_Jump for traversal input +- [ ] Test: vault over low wall, mantle onto ledge, slide under barrier \ No newline at end of file diff --git a/docs/blueprints/03-interaction/23_BPC_UsableWorldObjectSystem.md b/docs/blueprints/03-interaction/23_BPC_UsableWorldObjectSystem.md index 7b5cb45..b007735 100644 --- a/docs/blueprints/03-interaction/23_BPC_UsableWorldObjectSystem.md +++ b/docs/blueprints/03-interaction/23_BPC_UsableWorldObjectSystem.md @@ -51,4 +51,125 @@ ## 7. Reuse Notes - Generic base for all "press E to use" world objects -- Can be subclassed for specialized behavior \ No newline at end of file +- Can be subclassed for specialized behavior + +--- + +## 8. Manual Implementation Guide + +### 8.1 Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_UsableWorldObjectSystem` +2. Attach to any world actor that should be "usable" (switches, buttons, valves, terminals, notes, audio logs) +3. The owner actor must implement `I_Interactable` (or this component implements it on behalf of owner) + +### 8.2 Variable Initialization (BeginPlay) +``` +Event BeginPlay + ├─ Set bIsActive = true + ├─ Set bHasBeenUsed = false + ├─ Load InteractionData (Data Asset reference) + │ ├─ Read PromptText → store for UI + │ ├─ Read InteractionDuration → for hold interactions + │ └─ Read ObjectType → set behavior mode + └─ Set LinkedActor from owner variable (designer-assigned in editor) +``` + +### 8.3 Function Implementations + +#### `OnUse(Instigator: Actor)` → `Boolean` + +``` +[Function: OnUse] — core interaction handler + + Step 1: Branch on bIsActive → If false, Fire OnObjectStateChanged(false), Return false + Step 2: Branch on bSingleUse AND bHasBeenUsed: + True → Return false (already used once) + + Step 3: Switch on ObjectType: + Case Switch: + - Toggle bIsActive (invert) + - Play switch animation: flip lever/button + - If LinkedActor: call I_Toggleable.Toggle(Instigator) on LinkedActor + - Start CooldownSeconds timer → bIsActive = true after cooldown + + Case Button: + - Set bIsActive = false (press in) + - Play button press animation + - Notify LinkedActor if set + - Start timer → spring back: bIsActive = true + + Case Valve: + - Begin rotate interaction (player holds E and moves mouse) + - Call Adjust on LinkedActor (I_Adjustable) with rotation delta + - On release: stop adjustment + + Case Terminal: + - Call OpenTerminal UI on LinkedActor or self + - Set player input mode to UI Only + - On close → restore input + + Case Readable: + - Get NoteText from InteractionData + - Call WBP_JournalDocumentViewer.OpenNote(NoteText) + - Play "paper rustle" sound via SS_AudioManager + + Case AudioLog: + - Get AudioClip from InteractionData + - Call SS_AudioManager.PlayDialogue(AudioClip) + - Show subtitle text if available + + Case Generic: + - Fire custom dispatcher or call blueprint event OnGenericUse + + Step 4: Set bHasBeenUsed = true (if bSingleUse) + Step 5: Fire OnObjectUsed(Instigator) + Step 6: Return true +``` + +**Nodes:** `Switch on E_UsableObjectType`, `Play Animation`, `I_Toggleable.Toggle`, `Open UI Widget`, `Play Sound` + +#### `SetEnabled(bEnabled: Boolean)` → `void` + +``` +[Function: SetEnabled] + Step 1: Set bIsActive = bEnabled + Step 2: Update visual: enable/disable highlight, change material + Step 3: Fire OnObjectStateChanged(bEnabled) +``` + +#### `ResetObject()` → `void` + +``` +[Function: ResetObject] + Step 1: Set bHasBeenUsed = false + Step 2: Set bIsActive = true + Step 3: Reset visual state to default + Step 4: Fire OnObjectStateChanged(true) +``` + +### 8.4 I_Interactable Implementation (on owner actor) + +If the component is on an actor that implements I_Interactable: + +``` +[Owner Actor: I_Interactable.OnInteract(Instigator)] + Step 1: Get Component by Class (BPC_UsableWorldObjectSystem) + Step 2: Call BPC_UsableWorldObjectSystem.OnUse(Instigator) + +[Owner Actor: I_Interactable.CanInteract(Instigator)] + Get UsableComp → Return UsableComp.bIsActive AND NOT (UsableComp.bSingleUse AND UsableComp.bHasBeenUsed) + +[Owner Actor: I_Interactable.GetInteractionPrompt()] + Get UsableComp → Return UsableComp.InteractionData.PromptText +``` + +### 8.5 Blueprint Build Checklist +- [ ] Define enum E_UsableObjectType and create DA_InteractionData with usage config +- [ ] Create BPC_UsableWorldObjectSystem component +- [ ] Add variables: ObjectType, InteractionData, bIsActive, bSingleUse, bHasBeenUsed, CooldownSeconds +- [ ] Implement OnUse with type-switch for all 7 object types +- [ ] Implement SetEnabled / ResetObject +- [ ] Create owner actor (BP_UsableObject) that implements I_Interactable +- [ ] Route I_Interactable calls to this component +- [ ] Set LinkedActor reference for Switch/Button objects +- [ ] Test: place switch → press E → linked light toggles → cooldown → press again \ No newline at end of file diff --git a/docs/blueprints/04-inventory/26_BPC_ActiveItemSystem.md b/docs/blueprints/04-inventory/26_BPC_ActiveItemSystem.md index dddf13f..05028dd 100644 --- a/docs/blueprints/04-inventory/26_BPC_ActiveItemSystem.md +++ b/docs/blueprints/04-inventory/26_BPC_ActiveItemSystem.md @@ -83,4 +83,148 @@ ## 7. Reuse Notes - Quick slots are configurable per project (4-8 slots) -- Active item system is the bridge between inventory data and gameplay input \ No newline at end of file +- Active item system is the bridge between inventory data and gameplay input + +--- + +## 8. Manual Implementation Guide + +### 8.1 Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_ActiveItemSystem` +2. Add to Player Character +3. Quick slots map inventory ItemIDs (Guid) to hotkey numbers (1-8) + +### 8.2 Variable Initialization (BeginPlay) +``` +Event BeginPlay + ├─ Set QuickSlots = empty Map + ├─ Set ActiveSlot = Slot_1 + ├─ Set ActiveItem = empty S_InventoryEntry + ├─ Set bHasActiveItem = false + ├─ Set bCanSwitchItems = true + ├─ Set SwitchCooldown = 0.2 + └─ Get Owner → Find Component by Class (BPC_InventorySystem) → Cache + └─ Bind to OnInventoryChanged → RefreshActiveItem +``` + +### 8.3 Function Implementations + +#### `SetQuickSlot(Slot: E_QuickSlot, ItemID: Guid)` → `void` + +``` +[Function: SetQuickSlot] + Step 1: Add/Update QuickSlots map: QuickSlots.Add(Slot, ItemID) + Step 2: If ActiveSlot == Slot → Refresh active item display + Step 3: Fire OnQuickSlotAssigned(Slot, ItemID) +``` + +#### `ClearQuickSlot(Slot: E_QuickSlot)` → `void` + +``` +[Function: ClearQuickSlot] + Step 1: QuickSlots.Remove(Slot) + Step 2: If ActiveSlot == Slot: + Set bHasActiveItem = false, ActiveItem = empty + Fire OnActiveItemChanged(empty, Slot) + Step 3: Fire OnQuickSlotCleared(Slot) +``` + +#### `SelectSlot(Slot: E_QuickSlot)` → `void` + +``` +[Function: SelectSlot] + Step 1: Branch on bCanSwitchItems → If false, return (switching blocked during animation) + Step 2: Get ItemID from QuickSlots[Slot] → if no entry, return + Step 3: Get InventorySystem → Call GetItemById(ItemID) + Step 4: If item found AND valid: + Set ActiveSlot = Slot + Set ActiveItem = found entry + Set bHasActiveItem = true + Fire OnActiveItemChanged(ActiveItem, Slot) + Step 5: If item NOT found (was removed from inventory): + Call ClearQuickSlot(Slot) — auto-clean stale slot +``` + +**Nodes:** `Map Find`, `GetItemById`, `Branch IsValid`, `Fire Event` + +#### `GetActiveItem()` → `S_InventoryEntry` *(Pure)* + +``` +[Function: GetActiveItem] + Return ActiveItem +``` + +#### `UseActiveItem()` → `Boolean` + +``` +[Function: UseActiveItem] + Step 1: Branch on bHasActiveItem → If false, return false + Step 2: Get InventorySystem → Call UseItem(ActiveItem.SlotIndex) + Step 3: Branch on result == Success: + True → Fire OnActiveItemUsed(ActiveItem) → Return true + False → Return false (item not usable, cooldown, etc.) +``` + +#### `CycleNextItem()` → `void` + +``` +[Function: CycleNextItem] + Step 1: CurrentIndex = ActiveSlot (as int, 1-8) + Step 2: Loop 8 times: + NextIndex = (CurrentIndex + i) % 8 + 1 + If NOT IsQuickSlotEmpty(NextIndex): + Call SelectSlot(NextIndex) + Return + Step 3: No populated slots → do nothing +``` + +#### `CyclePreviousItem()` → `void` + +``` +[Function: CyclePreviousItem] + Same as CycleNextItem but decrementing: NextIndex = (CurrentIndex - i + 7) % 8 + 1 +``` + +#### `IsQuickSlotEmpty(Slot: E_QuickSlot)` → `Boolean` + +``` +[Function: IsQuickSlotEmpty] + If QuickSlots.Find(Slot) returns valid → Return false + Return true +``` + +#### `RefreshActiveItem()` → `void` *(Called when inventory changes)* + +``` +[Function: RefreshActiveItem] + Step 1: If NOT bHasActiveItem → Return + Step 2: Get InventorySystem → GetItemAtSlot(ActiveItem.SlotIndex) + Step 3: If item still exists AND same ItemID: + Update ActiveItem (stack count may have changed) + Fire OnActiveItemChanged(ActiveItem, ActiveSlot) + Step 4: If item no longer exists: + Set bHasActiveItem = false, ActiveItem = empty + ClearQuickSlot(ActiveSlot) +``` + +### 8.4 Event Dispatcher Bindings +| Bind to Dispatcher | Custom Event | Logic | +|-------------------|-------------|-------| +| `BPC_InventorySystem.OnInventoryChanged` | `RefreshActiveItem` | Re-validate current active item exists | +| `InputAction IA_Hotkey1` through `IA_Hotkey8` | `OnHotkeyPressed(Slot)` | Call SelectSlot corresponding slot | +| `InputAction IA_NextItem` | `OnNextItem` | Call CycleNextItem | +| `InputAction IA_PreviousItem` | `OnPreviousItem` | Call CyclePreviousItem | +| `InputAction IA_UseItem` | `OnUseItem` | Call UseActiveItem | + +### 8.5 Blueprint Build Checklist +- [ ] Define E_QuickSlot enum (Slot_1 through Slot_8) +- [ ] Create BPC_ActiveItemSystem, add to Player Character +- [ ] Add variables: QuickSlots (Map), ActiveSlot, ActiveItem, bHasActiveItem, bCanSwitchItems, SwitchCooldown +- [ ] Implement SetQuickSlot/ClearQuickSlot for assignment +- [ ] Implement SelectSlot with validity check and stale cleanup +- [ ] Implement UseActiveItem routing to InventorySystem.UseItem +- [ ] Implement CycleNext/Previous with wrap-around +- [ ] Create 8 Input Actions: IA_Hotkey1 through IA_Hotkey8 +- [ ] Bind hotkey inputs + next/previous item cycling +- [ ] Bind to OnInventoryChanged for auto-refresh +- [ ] Test: assign item to slot 1 → press 1 to select → press Use to consume \ No newline at end of file diff --git a/docs/blueprints/04-inventory/29_BPC_DocumentArchiveSystem.md b/docs/blueprints/04-inventory/29_BPC_DocumentArchiveSystem.md index 4370f56..1d64217 100644 --- a/docs/blueprints/04-inventory/29_BPC_DocumentArchiveSystem.md +++ b/docs/blueprints/04-inventory/29_BPC_DocumentArchiveSystem.md @@ -46,4 +46,129 @@ | `BPC_LoreUnlockSystem` | Lore document triggers | ## 6. Reuse Notes -- Documents are inventory items with `E_ItemCategory = Document` \ No newline at end of file +- Documents are inventory items with `E_ItemCategory = Document` + +--- + +## 7. Manual Implementation Guide + +### 7.1 Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_DocumentArchiveSystem` +2. Add to Player Character +3. Define struct `S_DocumentEntry` with fields: `DocumentID` (Guid), `DocumentTag` (GameplayTag), `Title` (Text), `Body` (Text), `Category` (Enum), `bIsRead` (Boolean), `DateCollected` (Float), `bIsFlagged` (Boolean) + +### 7.2 Variable Initialization (BeginPlay) +``` +Event BeginPlay + ├─ Set CollectedDocuments = empty Array + ├─ Set UnreadCount = 0 + ├─ Set bShowUnreadBadge = true + ├─ Get Owner → Find Component by Class (BPC_InventorySystem) → Cache + │ └─ Bind to OnItemAdded → OnItemAddedHandler + └─ Get Owner → Find BP_DocumentArchiveSystem → self (ensure only one) +``` + +### 7.3 Function Implementations + +#### `AddDocument(ItemData: DA_ItemData)` → `Boolean` + +``` +[Function: AddDocument] + Step 1: Branch on ItemData.ItemType == Document → If not, return false + Step 2: Check if document with same ItemTag already in CollectedDocuments: + ForEach CollectedDocuments → if Entry.DocumentTag == ItemData.ItemTag: + Return false (already collected) + Step 3: Create S_DocumentEntry struct: + - DocumentID = New Guid + - DocumentTag = ItemData.ItemTag + - Title = ItemData.DisplayName + - Body = ItemData.Description (document content) + - Category = determine from tag (ItemTag starts with "Document.Notes" → Notes, etc.) + - bIsRead = false + - DateCollected = Get Game Time in Seconds + - bIsFlagged = false + Step 4: Add to CollectedDocuments array + Step 5: UnreadCount += 1 + Step 6: Fire OnDocumentCollected(NewEntry) + Step 7: Also notify BPC_LoreUnlockSystem if category is Lore + Step 8: Return true +``` + +**Nodes:** `New Guid`, `Make S_DocumentEntry`, `Add`, `Get Game Time in Seconds`, `Starts With (String)` + +#### `MarkAsRead(DocumentID: Guid)` → `void` + +``` +[Function: MarkAsRead] + Step 1: Find document in CollectedDocuments by DocumentID: + ForEach with Break → if Entry.DocumentID == DocumentID: + Branch on Entry.bIsRead: + False → Set bIsRead = true, UnreadCount -= 1 + True → Return (already read) + Step 2: Fire OnDocumentRead(DocumentID) + Step 3: Branch on UnreadCount == 0: + True → Fire OnAllDocumentsRead +``` + +#### `MarkAllAsRead()` → `void` + +``` +[Function: MarkAllAsRead] + Step 1: ForEach CollectedDocuments: + Set bIsRead = true + Step 2: Set UnreadCount = 0 + Step 3: Fire OnAllDocumentsRead +``` + +#### `GetDocumentsByCategory(Category: E_DocumentCategory, bUnreadOnly: Boolean)` → `Array` + +``` +[Function: GetDocumentsByCategory] (Pure) + Step 1: Create empty Array → Results + Step 2: ForEach CollectedDocuments: + If Entry.Category == Category AND (NOT bUnreadOnly OR NOT Entry.bIsRead): + Add to Results + Step 3: Return Results +``` + +#### `GetUnreadCount()` → `Integer` + +``` +[Function: GetUnreadCount] (Pure) + Return UnreadCount +``` + +#### `HasDocument(DocumentTag: GameplayTag)` → `Boolean` + +``` +[Function: HasDocument] (Pure) + Step 1: ForEach CollectedDocuments: + If Entry.DocumentTag == DocumentTag → Return true + Step 2: Return false +``` + +#### `ToggleFlag(DocumentID: Guid)` → `void` + +``` +[Function: ToggleFlag] + Step 1: Find document by DocumentID in CollectedDocuments + Step 2: Entry.bIsFlagged = NOT Entry.bIsFlagged + Step 3: If flagged → move to top of array (optional sorting) +``` + +### 7.4 Event Dispatcher Bindings +| Bind to Dispatcher | Custom Event | Logic | +|-------------------|-------------|-------| +| `BPC_InventorySystem.OnItemAdded(Item)` | `OnItemAddedHandler` | If Item.ItemData.ItemType == Document → Call AddDocument(Item.ItemData) | + +### 7.5 Blueprint Build Checklist +- [ ] Define enum `E_DocumentCategory`: Notes, Letters, Audio, Data, Photos +- [ ] Define struct `S_DocumentEntry` with all fields +- [ ] Create BPC_DocumentArchiveSystem, add to Player Character +- [ ] Add variables: CollectedDocuments, DocumentCategories, bShowUnreadBadge, UnreadCount +- [ ] Implement AddDocument with duplicate check and struct creation +- [ ] Implement MarkAsRead with UnreadCount tracking +- [ ] Implement GetDocumentsByCategory with optional unread filter +- [ ] Implement HasDocument for narrative queries +- [ ] Bind to BPC_InventorySystem.OnItemAdded +- [ ] Test: pick up document item → archive updates → UI shows badge \ No newline at end of file diff --git a/docs/blueprints/04-inventory/33_BPC_JournalSystem.md b/docs/blueprints/04-inventory/33_BPC_JournalSystem.md index 3e0e193..1fa4fef 100644 --- a/docs/blueprints/04-inventory/33_BPC_JournalSystem.md +++ b/docs/blueprints/04-inventory/33_BPC_JournalSystem.md @@ -43,4 +43,119 @@ | `WBP_JournalDocumentViewer` | UI display | ## 6. Reuse Notes -- Journal entries are narrative-gated via gameplay tags \ No newline at end of file +- Journal entries are narrative-gated via gameplay tags + +--- + +## 7. Manual Implementation Guide + +### 7.1 Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_JournalSystem` +2. Add to Player Character +3. Define enum `E_JournalCategory`: Quest, Character, Lore, Notes, Tips +4. Define struct `S_JournalEntry`: `EntryID` (Guid), `EntryTag` (GameplayTag), `Title` (Text), `Body` (Text), `Category` (E_JournalCategory), `bIsRead` (Boolean), `Timestamp` (Float), `RequiredFlag` (GameplayTag), `SourceObjective` (GameplayTag) + +### 7.2 Variable Initialization (BeginPlay) +``` +Event BeginPlay + ├─ Set JournalEntries = empty Array + ├─ Set UnreadCount = 0 + ├─ Set bAutoAddOnDiscovery = true + ├─ Get Owner → Find Component by Class (BPC_NarrativeStateSystem) → Cache + │ └─ Bind to OnNarrativeFlagSet → OnFlagSetHandler + └─ Get Owner → Find Component by Class (BPC_ObjectiveSystem) → Cache + └─ Bind to OnObjectiveCompleted → OnObjectiveCompleteHandler +``` + +### 7.3 Function Implementations + +#### `AddJournalEntry(EntryTag: GameplayTag, Title: Text, Body: Text, Category: E_JournalCategory)` → `S_JournalEntry` + +``` +[Function: AddJournalEntry] + Step 1: Check duplicate — ForEach JournalEntries: + If Entry.EntryTag == EntryTag → return existing entry (no dupes) + Step 2: Create S_JournalEntry: + - EntryID = New Guid + - EntryTag = input + - Title = input + - Body = input + - Category = input + - bIsRead = false + - Timestamp = Get Game Time in Seconds + Step 3: Add to JournalEntries array + Step 4: Increment UnreadCount + Step 5: Fire OnJournalEntryAdded(newEntry) + Step 6: Return newEntry +``` + +**Nodes:** `New Guid`, `Make S_JournalEntry`, `Add to Array`, `Increment Int` + +#### `AddEntryFromDataAsset(JournalData: DA_JournalData)` → `S_JournalEntry` + +``` +[Function: AddEntryFromDataAsset] + Step 1: Read fields from DA_JournalData + Step 2: Call AddJournalEntry(Data.EntryTag, Data.Title, Data.Body, Data.Category) + Step 3: If Data.RequiredFlag is valid AND bAutoAddOnDiscovery: + Register listener on NarrativeStateSystem for that flag + Step 4: Return entry +``` + +#### `MarkAsRead(EntryID: Guid)` → `void` + +``` +[Function: MarkAsRead] + Step 1: ForEach JournalEntries (with Break): + If Entry.EntryID == EntryID: + If NOT Entry.bIsRead: + Set bIsRead = true + Decrement UnreadCount + Fire OnJournalEntryRead(EntryID) + Return +``` + +#### `GetEntriesByCategory(Category: E_JournalCategory)` → `Array` + +``` +[Function: GetEntriesByCategory] (Pure) + Step 1: Results = empty Array + Step 2: ForEach JournalEntries: + If Entry.Category == Category → Add to Results + Step 3: Return Results +``` + +#### `GetUnreadEntries()` → `Array` + +``` +[Function: GetUnreadEntries] (Pure) + ForEach JournalEntries: + If NOT Entry.bIsRead → Add to Results + Return Results +``` + +#### `HasEntry(EntryTag: GameplayTag)` → `Boolean` + +``` +[Function: HasEntry] (Pure) + ForEach JournalEntries: + If Entry.EntryTag == EntryTag → Return true + Return false +``` + +### 7.4 Event Dispatcher Bindings +| Bind to Dispatcher | Custom Event | Logic | +|-------------------|-------------|-------| +| `BPC_NarrativeStateSystem.OnNarrativeFlagSet(Flag)` | `OnFlagSetHandler` | Check if any journal entry has RequiredFlag == Flag → AddJournalEntry for that entry | +| `BPC_ObjectiveSystem.OnObjectiveCompleted(ObjectiveTag)` | `OnObjectiveCompleteHandler` | Add journal entry summarizing completed objective | + +### 7.5 Blueprint Build Checklist +- [ ] Define enum `E_JournalCategory` and struct `S_JournalEntry` +- [ ] Create BPC_JournalSystem, add to Player Character +- [ ] Add variables: JournalEntries, EntryCategories, UnreadCount, bAutoAddOnDiscovery +- [ ] Implement AddJournalEntry with duplicate check +- [ ] Implement AddEntryFromDataAsset with DA_JournalData reading +- [ ] Implement MarkAsRead with unread counter +- [ ] Implement filtered query functions +- [ ] Bind to NarrativeStateSystem and ObjectiveSystem for auto-adding +- [ ] Test: progress story → journal entries appear → UI shows unread badge \ No newline at end of file diff --git a/docs/blueprints/04-inventory/34_BPC_KeyItemSystem.md b/docs/blueprints/04-inventory/34_BPC_KeyItemSystem.md index 393dc99..1392ad2 100644 --- a/docs/blueprints/04-inventory/34_BPC_KeyItemSystem.md +++ b/docs/blueprints/04-inventory/34_BPC_KeyItemSystem.md @@ -46,4 +46,120 @@ ## 6. Reuse Notes - Keys use gameplay tags for generic matching (any key with same tag works) -- Supports consumable keys (removed after single use) and persistent keys \ No newline at end of file +- Supports consumable keys (removed after single use) and persistent keys + +--- + +## 7. Manual Implementation Guide + +### 7.1 Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_KeyItemSystem` +2. Add to Player Character +3. Keys are inventory items with `ItemType = KeyItem` and `bIsKeyItem = true` in DA_ItemData. + +### 7.2 Variable Initialization (BeginPlay) +``` +Event BeginPlay + ├─ Set AcquiredKeys = empty Array + ├─ Set bShowKeyNotifications = true + ├─ Get Owner → Find Component by Class (BPC_InventorySystem) → Cache + │ └─ Bind to OnItemAdded → OnItemAddedHandler + └─ Bind to OnItemRemoved → OnItemRemovedHandler +``` + +### 7.3 Function Implementations + +#### `HasKey(KeyTag: GameplayTag)` → `Boolean` + +``` +[Function: HasKey] (Pure) + Step 1: ForEach AcquiredKeys: + If AcquiredKeys[i].MatchesTag(KeyTag) → Return true + Step 2: Optionally check inventory directly: + Get InventorySystem → Call FindItemByTag(KeyTag) + If found → Return true + Step 3: Return false +``` + +**Nodes:** `ForEachLoop`, `Matches Tag`, `Branch`, `FindItemByTag` + +#### `AddKey(KeyTag: GameplayTag)` → `void` + +``` +[Function: AddKey] + Step 1: Check if tag already in AcquiredKeys: + ForEach → if MatchesTag(KeyTag) → Return (no duplicates) + Step 2: Add KeyTag to AcquiredKeys array + Step 3: If bShowKeyNotifications: + Get WBP_HUDController → Create Notification Toast: "Key acquired: {GetTagDisplayName(KeyTag)}" + Step 4: Fire OnKeyAcquired(KeyTag) +``` + +#### `RemoveKey(KeyTag: GameplayTag)` → `Boolean` + +``` +[Function: RemoveKey] + Step 1: ForEach AcquiredKeys (iterate backwards): + If AcquiredKeys[i].MatchesTag(KeyTag): + Remove from array at index i + Fire OnKeyRemoved(KeyTag) + Return true + Step 2: Return false (key not found) +``` + +#### `GetAllKeys()` → `Array` + +``` +[Function: GetAllKeys] (Pure) + Step 1: Get BPC_InventorySystem → Call FindAllItemsByCategory(KeyItem) + Step 2: Also include AcquiredKeys (permanent keys that were consumed) + Step 3: Return combined unique list +``` + +#### `CanUnlock(Target: Actor)` → `Boolean, Text (failReason)` + +``` +[Function: CanUnlock] + Step 1: Check if Target implements I_Lockable: + DoesImplementInterface → If not, return (false, "Cannot be unlocked") + Step 2: Get RequiredKeyTag from Target: + Call I_Lockable.GetRequiredKeyTag(Target) → RequiredTag + Step 3: Call HasKey(RequiredTag): + True → Return (true, "") + False → Return (false, "Requires: " + GetTagDisplayName(RequiredTag)) +``` + +#### `UseKeyOnTarget(KeyTag: GameplayTag, Target: Actor)` → `Boolean` + +``` +[Function: UseKeyOnTarget] + Step 1: Verify HasKey(KeyTag) → If false, return false + Step 2: Call CanUnlock(Target): + If false → return false + Step 3: Check if Target has I_Lockable.TryUnlock: + Call I_Lockable.TryUnlock(Target, KeyTag) → bSuccess + Step 4: If bSuccess: + Fire OnKeyUsed(KeyTag, Target) + Step 5: Check if key should be consumed: + Get InventorySystem → FindItemByTag(KeyTag) + If ItemData.bConsumeOnUse: + Call RemoveKey(KeyTag) + Get InventorySystem → RemoveItemByTag(KeyTag, 1) + Step 6: Return bSuccess +``` + +### 7.4 Event Dispatcher Bindings +| Bind to Dispatcher | Custom Event | Logic | +|-------------------|-------------|-------| +| `BPC_InventorySystem.OnItemAdded(Item)` | `OnItemAddedHandler` | If Item.ItemData.ItemType == KeyItem → Call AddKey(Item.ItemData.ItemTag) | +| `BPC_InventorySystem.OnItemRemoved(Item)` | `OnItemRemovedHandler` | If Item.ItemData.ItemType == KeyItem → Call RemoveKey(Item.ItemData.ItemTag) | + +### 7.5 Blueprint Build Checklist +- [ ] Create BPC_KeyItemSystem, add to Player Character +- [ ] Add variables: AcquiredKeys (Array), bShowKeyNotifications, KeyItemCategory +- [ ] Implement HasKey with tag matching and inventory fallback +- [ ] Implement AddKey/RemoveKey with duplicate prevention +- [ ] Implement CanUnlock using I_Lockable interface +- [ ] Implement UseKeyOnTarget with consume-on-use check +- [ ] Bind to BPC_InventorySystem.OnItemAdded/OnItemRemoved +- [ ] Test: pick up key → attempt locked door → unlock succeeds → key consumed \ No newline at end of file diff --git a/docs/blueprints/08-weapons/74_BPC_FirearmSystem.md b/docs/blueprints/08-weapons/74_BPC_FirearmSystem.md index f4e5b1d..2e1e46a 100644 --- a/docs/blueprints/08-weapons/74_BPC_FirearmSystem.md +++ b/docs/blueprints/08-weapons/74_BPC_FirearmSystem.md @@ -102,4 +102,61 @@ Ranged weapon specialization. Handles line trace / projectile fire, weapon sprea - Spread is cumulative while firing, resets on recovery timer. - Projectile type requires a child BP implementing `AP_BaseProjectile` with velocity and lifetime. - Renamed from `BP_RangedWeapon` to `BPC_FirearmSystem` per Master naming convention. -- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`, `BPC_PlayerCameraManager` → `BPC_CameraStateLayer`, `DA_WeaponData` → `14-data-assets/`. \ No newline at end of file +- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`, `BPC_PlayerCameraManager` → `BPC_CameraStateLayer`, `DA_WeaponData` → `14-data-assets/`. + +--- + +## Manual Implementation Guide + +### Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_FirearmSystem` +2. Attach to `BP_WeaponBase` subclass: `BP_RangedWeapon` +3. Add FireSocket SceneComponent on weapon mesh (muzzle position) + +### Variable Init (BeginPlay) +``` +Event BeginPlay + ├─ Read WeaponData from Owner BP_WeaponBase + ├─ Set FireType = WeaponData.FireType (Hitscan/Projectile/Hybrid) + ├─ Set Range = WeaponData.EffectiveRange + ├─ Set SpreadAngle = WeaponData.BaseSpread + ├─ Set BulletsPerShot = WeaponData.PelletsPerShot + ├─ Set CurrentSpread = 0.0 + └─ Cache: BPC_AmmoComponent, BPC_CombatFeedbackComponent, BPC_CameraStateLayer +``` + +### Function Node-by-Node + +#### `OnFire_Implementation()` *(Overrides BP_WeaponBase.OnFire)* +``` +Step 1: Get Camera Forward Vector +Step 2: Switch on FireType: + Hitscan: ForLoop (BulletsPerShot): + AimDir = Forward + CalculateSpread() + LineTraceByChannel: Start=Camera, End=Camera+AimDir*Range + If Hit & Implements I_Damageable: ApplyDamage(WeaponData.Damage, HitResult) + SpawnImpactFX(HitResult) + Projectile: ForLoop (BulletsPerShot): + Spawn ProjectileClass at FireSocket, velocity=AimDir*Speed + Hybrid: Hitscan + projectile for near-miss area +Step 3: ApplyRecoil → CameraShake or AddControllerPitch/Yaw +Step 4: ConsumeAmmo on BPC_AmmoComponent +Step 5: PlayMuzzleFlash + PlayFireSound on CombatFeedback +Step 6: CurrentSpread += SpreadIncreasePerShot (clamped to MaxSpread) +``` + +#### `CalculateSpread()` → Vector *(Pure)* +``` +RandomFloat(-1,1)*CurrentSpread → YawOffset +RandomFloat(-1,1)*CurrentSpread → PitchOffset +MakeRotator(PitchOffset, YawOffset, 0) → GetForwardVector +``` + +### Build Checklist +- [ ] Create BPC_FirearmSystem, attach to BP_RangedWeapon +- [ ] Add FireSocket at muzzle +- [ ] Implement OnFire with hitscan/projectile/hybrid switch +- [ ] Implement CalculateSpread +- [ ] Wire to AmmoComponent, CombatFeedback, CameraStateLayer +- [ ] Create ProjectileClass with ProjectileMovement for projectile weapons +- [ ] Test: fire at target → damage registers → FX plays → ammo count drops \ No newline at end of file diff --git a/docs/blueprints/08-weapons/76_BPC_MeleeSystem.md b/docs/blueprints/08-weapons/76_BPC_MeleeSystem.md index 5746e96..23477b6 100644 --- a/docs/blueprints/08-weapons/76_BPC_MeleeSystem.md +++ b/docs/blueprints/08-weapons/76_BPC_MeleeSystem.md @@ -131,4 +131,115 @@ Melee weapon specialization. Handles swing detection, hitbox overlap checking du - Hit collision is a simple box or capsule that exists only during Active phase. - Block reduces incoming damage via DamageReception; parry reflects stagger. - Renamed from `BP_MeleeWeapon` to `BPC_MeleeSystem` per Master naming convention. -- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`. \ No newline at end of file +- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`. + +--- + +## Manual Implementation Guide + +### Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_MeleeSystem` +2. Attach to `BP_WeaponBase` subclass: `BP_MeleeWeapon` +3. Add `HitDetectionCollision` (Capsule or Box component) to weapon — disabled by default + +### Variable Init (BeginPlay) +``` +Event BeginPlay + ├─ Set SwingPhase = Idle + ├─ Set ComboStep = 0 + ├─ Set bCanCombo = false + ├─ Set bIsBlocking = false + ├─ Disable HitDetectionCollision (NoCollision) + ├─ Get Owner Weapon → Read DA_WeaponData for: ComboWindowDuration, ChargeDuration, ParryWindow, StaggerDuration + └─ Get Owner → Find BPC_DamageReceptionSystem → Cache +``` + +### Function Implementations + +#### `StartSwing(AttackType: EAttackType)` → `void` + +``` +[Function: StartSwing] + Step 1: Branch on SwingPhase != Idle AND SwingPhase != Recovery → Return (can't chain now) + Step 2: Set SwingPhase = WindUp + Step 3: Build montage section name: + If this is combo: "Combo_" + ComboStep + If AttackType == HeavyAttack: prefix "Heavy_" + If AttackType == ChargeAttack: "Charge_" + Step 4: Play Montage (SwingMontage, Section = built section name) + Step 5: Set bHitRegistered = false, Clear HitActors array + Step 6: Fire OnSwingStarted(AttackType, ComboStep) +``` + +**Nodes:** `Switch on EAttackType`, `Build String`, `Play Montage` (with Starting Section), `Clear Array` + +#### `OnSwingActive()` → `void` *(Called by Animation Notify: "Notify_Active")* + +``` + Step 1: Set SwingPhase = Active + Step 2: HitDetectionCollision.SetCollisionEnabled(QueryOnly) + Step 3: Set bHitRegistered = false +``` + +#### `OnSwingHit(HitResult)` → `void` *(Called by HitDetectionCollision OnComponentBeginOverlap)* + +``` + Step 1: Get Hit Actor from Overlap → Cast to Actor + Step 2: ForEach HitActors → if match found → Return (already hit this actor this swing) + Step 3: Add HitActor to HitActors + Step 4: If HitActor implements I_Damageable: + Get BPC_DamageReceptionSystem → Call ApplyMeleeDamage: + BaseDamage = WeaponData.Damage + Multiplier: Light=1.0, Heavy=1.8, Charge=2.5, Sprint=1.5 + HitLocation = HitResult.ImpactPoint + Apply impulse in swing direction + Step 5: Get BPC_CombatFeedbackComponent → Call PlayHitFX(HitResult) + Step 6: Apply Stagger to HitActor +``` + +#### `OnSwingRecovery()` → `void` *(Animation Notify: "Notify_Recovery")* + +``` + Step 1: Set SwingPhase = Recovery + Step 2: HitDetectionCollision.SetCollisionEnabled(NoCollision) + Step 3: Clear HitActors + Step 4: Set bCanCombo = true + Step 5: Start Timer (ComboWindowDuration) → On timer: if no combo received → OnSwingComplete() +``` + +#### `OnSwingComplete()` → `void` + +``` + Step 1: Set SwingPhase = Idle + Step 2: Set bCanCombo = false + Step 3: Set ComboStep = 0 + Step 4: Fire OnSwingFinished +``` + +#### `StartBlock()` → `void` + +``` + Step 1: Set bIsBlocking = true + Step 2: Play block montage loop section + Step 3: Get Owner Character → Get CharacterMovement → Set MaxWalkSpeed *= 0.5 + Step 4: Set ParryWindow timer: bParryActive = true for ParryWindow seconds + On timer end → bParryActive = false +``` + +#### `EndBlock()` → `void` + +``` + Step 1: Set bIsBlocking = false + Step 2: Stop block montage + Step 3: Restore MaxWalkSpeed +``` + +### Build Checklist +- [ ] Create BPC_MeleeSystem, add to BP_MeleeWeapon +- [ ] Add HitDetectionCollision component (Capsule/Box, NoCollision by default) +- [ ] Create SwingMontage with sections: Combo_1, Combo_2, Combo_3, Heavy_1, Charge_1 +- [ ] Place Animation Notifies: Notify_Active, Notify_Recovery, Notify_CanCombo, Notify_ParryWindow +- [ ] Implement phase state machine: Idle→WindUp→Active→Recovery→(combo)→Idle +- [ ] Bind HitDetectionCollision.OnComponentBeginOverlap → OnSwingHit +- [ ] Implement combo system with bCanCombo window timer +- [ ] Implement block/parry with damage reduction and stagger reflection \ No newline at end of file diff --git a/docs/blueprints/08-weapons/78_BPC_ReloadSystem.md b/docs/blueprints/08-weapons/78_BPC_ReloadSystem.md index 53dc3ae..d13cd9b 100644 --- a/docs/blueprints/08-weapons/78_BPC_ReloadSystem.md +++ b/docs/blueprints/08-weapons/78_BPC_ReloadSystem.md @@ -59,4 +59,106 @@ - ReloadMontage from DA_WeaponData per weapon; swap for each weapon type - Partial reload enabled per weapon (e.g. shotguns use individual shell reloads) -- AmmoTypeTag links to correct ammo pool in BPC_AmmoResourceSystem \ No newline at end of file +- AmmoTypeTag links to correct ammo pool in BPC_AmmoResourceSystem + +--- + +## Manual Implementation Guide + +### Class Setup +1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_ReloadSystem` +2. Attach to `BP_WeaponBase` or `BPC_FirearmSystem` +3. Weapon must have a `ReloadMontage` with Animation Notifies + +### Variable Init (BeginPlay) +``` +Event BeginPlay + ├─ Set bIsReloading = false + ├─ Set bPartialReload = false + ├─ Get Owner → Read DA_WeaponData → Load ReloadMontage, AmmoTypeTag, MagazineSize + └─ Cache: BPC_AmmoComponent, BP_WeaponBase (for state check) +``` + +### Function Implementations + +#### `RequestReload()` → `void` + +``` +[Function: RequestReload] + Step 1: Get BP_WeaponBase → Check WeaponState == Ready → If not, return (can't reload while firing) + Step 2: Call CanReload() → Branch: + False → Fire OnReloadFailed → Return + True → Call BeginReload() +``` + +#### `CanReload()` → `Boolean` + +``` +[Function: CanReload] (Pure) + Step 1: Get BPC_AmmoComponent → Call GetAmmoInMagazine() + If magazine >= MagazineSize → Return false (already full) + Step 2: Get BPC_AmmoComponent → Call GetReserveAmmo(AmmoTypeTag) + If reserve <= 0 → Return false (no ammo to load) + Step 3: Return true +``` + +#### `BeginReload()` → `void` + +``` +[Function: BeginReload] + Step 1: Set bIsReloading = true + Step 2: Notify BP_WeaponBase → SetWeaponState(Reloading) + Step 3: Play Montage (ReloadMontage) + Step 4: Fire OnReloadStarted + + Step 5: Animation Notify "TransferRounds" setup: + In the animation montage, add Notify at the frame where the magazine is inserted. + On trigger → Call CompleteReload() + + Step 6: Animation Notify "ReloadEnd" setup: + Add Notify at montage end → Call FinishReload() +``` + +#### `CompleteReload()` → `void` *(Called by Notify "TransferRounds")* + +``` +[Function: CompleteReload] + Step 1: Get BPC_AmmoComponent: + MagazineCurrent = GetAmmoInMagazine() + ReserveCurrent = GetReserveAmmo(AmmoTypeTag) + Step 2: RoundsToAdd = Min(MagazineSize - MagazineCurrent, ReserveCurrent) + Step 3: BPC_AmmoComponent.RemoveReserveAmmo(AmmoTypeTag, RoundsToAdd) + Step 4: BPC_AmmoComponent.AddMagazineAmmo(RoundsToAdd) +``` + +#### `FinishReload()` → `void` *(Called by Notify "ReloadEnd")* + +``` + Step 1: Set bIsReloading = false + Step 2: Notify BP_WeaponBase → SetWeaponState(Ready) + Step 3: Fire OnReloadCompleted(RoundsToAdd) +``` + +#### `InterruptReload()` → `void` + +``` +[Function: InterruptReload] + Step 1: Branch on bIsReloading → If false, return + Step 2: If bPartialReload AND CompleteReload already called: + Keep transferred rounds (partial reload success) + Step 3: Else: no rounds transferred + Step 4: Stop Montage (ReloadMontage) + Step 5: Set bIsReloading = false + Step 6: Notify BP_WeaponBase → SetWeaponState(Ready) + Step 7: Fire OnReloadInterrupted(RoundsTransferred) +``` + +### Build Checklist +- [ ] Create BPC_ReloadSystem, add to BP_RangedWeapon +- [ ] Create ReloadMontage per weapon with Notifies: TransferRounds, ReloadEnd +- [ ] Implement CanReload checking magazine fullness and reserve ammo +- [ ] Implement BeginReload with montage playback and state lock +- [ ] Wire CompleteReload to ammo transfer +- [ ] Wire FinishReload to state restoration +- [ ] Implement InterruptReload for mid-reload weapon swap +- [ ] Test: empty magazine → reload → rounds transfer → magazine full \ No newline at end of file