diff --git a/docs/blueprints/01-core/07_DA_ItemData.md b/docs/blueprints/01-core/07_DA_ItemData.md index 1e41720..5acbef6 100644 --- a/docs/blueprints/01-core/07_DA_ItemData.md +++ b/docs/blueprints/01-core/07_DA_ItemData.md @@ -5,6 +5,8 @@ The single source of truth for every item in the game. Each item is represented **C++ Status:** ✅ Full — `Source/PG_Framework/Public/Inventory/DA_ItemData.h` + `.cpp`. All enums, structs, variables, and editor validation are implemented in C++ with `UPROPERTY` metadata (EditCondition, EditConditionHides, ClampMin/Max) for designer-friendly editor UX. +> **Concrete examples:** See [`docs/game/`](../../game/README.md) for step-by-step walkthroughs of building actual items: [Flashlight Tool](../../game/item-flashlight.md), [Pistol Weapon](../../game/item-pistol.md), [MedKit Consumable](../../game/item-medkit.md), [Keycard Key Item](../../game/item-keycard.md). Each shows exact Blueprint graphs, component lists, and verification checklists. + > **Critical distinction:** `DA_ItemData` is a **Data Asset** — it lives in the Content Browser, NOT in the level. It defines *what* an item is (name, weight, icon, effects, type). To place an item in the world, create a `BP_ItemPickup` actor (spec #25) and assign the `DA_ItemData` to it. The Data Asset is the item's *identity card*; `BP_ItemPickup` is its *physical body* in the world. See [How to Set Up](#how-to-set-up) below. --- diff --git a/docs/blueprints/04-inventory/25_BP_ItemPickup.md b/docs/blueprints/04-inventory/25_BP_ItemPickup.md index 336b07d..bdc45b2 100644 --- a/docs/blueprints/04-inventory/25_BP_ItemPickup.md +++ b/docs/blueprints/04-inventory/25_BP_ItemPickup.md @@ -6,6 +6,8 @@ **Build Phase:** 4 — Inventory **C++ Status:** 🔵 BP-Only +> **Concrete step-by-step examples** of building specific item pickups (flashlight, pistol, medkit, keycard) with exact Blueprint graphs are in [`docs/game/`](../../game/README.md). The spec below defines the full architecture; the game examples show you exactly what nodes to connect. + --- ## 0. Quick Setup — How to Place an Item in the World diff --git a/docs/game/item-keycard.md b/docs/game/item-keycard.md new file mode 100644 index 0000000..76335f9 --- /dev/null +++ b/docs/game/item-keycard.md @@ -0,0 +1,262 @@ +# Item Example — Keycard (Key Item) + +**Item Type:** `KeyItem` +**Complexity:** Low +**Systems Used:** `DA_ItemData`, `BP_ItemPickup`, `BPC_InventorySystem`, `BPC_KeyItemSystem`, `I_Lockable`, `BP_DoorActor` +**What You Learn:** Key item Data Asset, auto-protection from drop/clear, used-once pattern, door unlocking via `I_Lockable` + +--- + +## 1. Create the DA_ItemData + +### 1.1 — Create the Data Asset + +``` +Content Browser → Game/Items/ + Right-click → Miscellaneous → Data Asset + Class: DA_ItemData + Name: DA_Item_Keycard_Omega +``` + +### 1.2 — Fill Properties + +| Property | Value | Notes | +|----------|-------|-------| +| `Item Tag` | `Framework.Item.KeyItem.KeycardOmega` | Unique — registered in `DA_GameTagRegistry` | +| `Display Name` | "Omega Keycard" | Player sees this | +| `Description` | "A high-security keycard labeled 'Omega Wing.'" | Shown in journal/inventory | +| `Icon` | `T_Keycard_Icon` | Card-shaped texture | +| `World Mesh` | `SM_Keycard` | Flat card mesh | +| `Weight` | `0.1` | Very light | +| `Stack Limit` | `1` | One per slot — cannot stack keycards | +| `Item Type` | `KeyItem` | Key items are auto-protected | +| `b Is Key Item` | ✓ (checked) | This property also appears — it's a boolean flag | +| `b Can Be Dropped` | ✗ (unchecked) | Key items should NOT be droppable (auto-forced by `bIsKeyItem`) | + +### 1.3 — Save + +No sub-data needed. Key items don't have Equipment or Consumable data. + +--- + +## 2. Create the BP_Pickup_KeycardOmega + +Same pattern as other pickups: + +### 2.1 — Create Blueprint + +``` +Content Browser → Game/Pickups/ + Right-click → Blueprint Class → Actor + Name: BP_Pickup_KeycardOmega +``` + +### 2.2 — Components + +| # | Component | Name | Purpose | +|---|-----------|------|---------| +| 1 | `StaticMeshComponent` | `Mesh` | Card model | +| 2 | `SphereComponent` | `PickupTrigger` | Radius 150, `OverlapOnlyPawn` | + +### 2.3 — Variable + +`ItemData` → Type: `DA_ItemData → Object Reference` → Instance Editable ✓ + +### 2.4 — Construction Script (same pattern) + +``` +ItemData → Get World Mesh → Load Synchronous → Set Static Mesh (Mesh) +ItemData → Get Display Name → Set Actor Label +``` + +### 2.5 — I_Interactable Wiring (same pattern) + +``` +Interactor → Get BPC_InventorySystem → CanAddItem(ItemData, 1) + True → AddItem → Destroy Actor → Return True + False → Print "Inventory Full" → Return False +``` + +--- + +## 3. Create the Lockable Door + +### 3.1 — Create Door Blueprint + +``` +Content Browser → Game/Actors/ + Right-click → Blueprint Class → Actor (or BP_DoorActor from framework) + Name: BP_Door_OmegaWing +``` + +### 3.2 — Add Components + +| # | Component | Name | Purpose | +|---|-----------|------|---------| +| 1 | `StaticMeshComponent` | `DoorFrame` | The door frame (static) | +| 2 | `StaticMeshComponent` | `DoorPanel` | The moving door part | +| 3 | `BoxComponent` | `InteractionTrigger` | Door interaction range | + +### 3.3 — Add Interfaces + +Class Settings → Interfaces → Add: +- `UInteractable` +- `ULockable` + +### 3.4 — Variables + +| Variable | Type | Default | Purpose | +|----------|------|---------|---------| +| `bIsLocked` | Boolean | `true` | Door starts locked | +| `bIsOpen` | Boolean | `false` | Door starts closed | +| `RequiredKeyTag` | GameplayTag | `Framework.Item.KeyItem.KeycardOmega` | Which key unlocks this | + +### 3.5 — Wire I_Lockable + +``` +Event Try Unlock (Interface -> ULockable) + │ Unlocker: AActor*, KeyTag: FGameplayTag + │ Return Boolean + │ + ├─ bIsLocked? → Branch + │ ├─ True: + │ │ ├─ KeyTag == RequiredKeyTag? → Branch + │ │ │ ├─ True: + │ │ │ │ ├─ Set bIsLocked = false + │ │ │ │ ├─ Unlocker → Get BPC_KeyItemSystem + │ │ │ │ │ └─ Consume Key (KeyTag) ← removes key from inventory + │ │ │ │ ├─ Print "Door unlocked with Omega Keycard" + │ │ │ │ └─ Return True + │ │ │ │ + │ │ │ └─ False: + │ │ │ ├─ Print "Wrong key — requires Omega Keycard" + │ │ │ └─ Return False + │ │ │ + │ │ └─ False: (already unlocked) + │ │ └─ Return True + +Event Is Locked + │ Return Boolean + └─ Return bIsLocked + +Event Get Required Key Tag + │ Return FGameplayTag + └─ Return RequiredKeyTag + +Event Try Lock + │ Locker: AActor* + │ Return Boolean + └─ Set bIsLocked = true → Return True (re-lockable for puzzle purposes) +``` + +### 3.6 — Wire I_Interactable (on door) + +``` +Event Interact (Interface -> UInteractable) + │ Interactor: AActor* + │ Return Boolean + │ + ├─ bIsLocked? → Branch + │ ├─ True: + │ │ ├─ Interactor → Get BPC_KeyItemSystem + │ │ │ └─ Has Key(RequiredKeyTag)? + │ │ │ ├─ True: + │ │ │ │ ├─ Call Try Unlock (self, Interactor, RequiredKeyTag) + │ │ │ │ └─ (If unlocked) → Open Door (see below) + │ │ │ │ + │ │ │ └─ False: + │ │ │ ├─ Print "Door is locked. Find Omega Keycard." + │ │ │ └─ Return False + │ │ │ + │ │ └─ Return False + │ │ + │ └─ False: (door is unlocked) + │ ├─ bIsOpen? → Branch + │ │ ├─ True → Close Door → Return True + │ │ └─ False → Open Door → Return True + +Function: Open Door + │ + ├─ Timeline (Rotation track, 0 → OpenAngle over OpenDuration) + │ └─ Update: DoorPanel → Set Relative Rotation + ├─ Set bIsOpen = true + └─ (Optional) Play door creak sound + +Function: Close Door + │ + ├─ Timeline (Rotation track, OpenAngle → 0 over CloseDuration) + │ └─ Update: DoorPanel → Set Relative Rotation + ├─ Set bIsOpen = false + └─ (Optional) Play door close sound +``` + +--- + +## 4. Wire BPC_KeyItemSystem (On Player Pawn) + +The `BPC_KeyItemSystem` tracks which key items the player holds. It automates: +- Protecting key items from being dropped/cleared +- Validating key item requirements on door/lock interactions +- Removing consumed key items after use + +In your BP child of `BPC_KeyItemSystem`: + +``` +Function: Has Key (KeyTag: GameplayTag) → Boolean + │ + ├─ Get Owner → Get BPC_InventorySystem + ├─ Get All Items → For Each Loop + │ ├─ Item → Get bIsKeyItem → Branch + │ │ ├─ True: Item → Get ItemTag == KeyTag? → Return True + │ │ └─ False: Continue + │ │ + │ └─ Loop end → Return False + +Function: Consume Key (KeyTag: GameplayTag) + │ + ├─ Get Owner → Get BPC_InventorySystem + ├─ Has Key(KeyTag)? → Branch + │ ├─ True: Inventory → Remove Item (by KeyTag, Quantity=1) + │ └─ False: Print "No such key in inventory" +``` + +> In the full framework, `BPC_KeyItemSystem` also prevents `Clear Inventory` and `Drop` operations from affecting key items by checking `bIsKeyItem` on the Data Asset. + +--- + +## 5. Verification Checklist + +**Step 1 — Pickup:** +- [ ] `BP_Pickup_KeycardOmega` placed in level, mesh visible +- [ ] Walk up → prompt "Pick up Omega Keycard" → press Interact → card goes to inventory + +**Step 2 — Inventory Protection:** +- [ ] Keycard appears in inventory with "KEY" badge/indicator +- [ ] Try to drop keycard → blocked (cannot drop key items) +- [ ] Try "Clear Inventory" → keycard remains (protected) + +**Step 3 — Door Interaction:** +- [ ] Walk up to `BP_Door_OmegaWing` while locked → prompt "Door is locked" +- [ ] Interact → door stays closed, shows "Requires Omega Keycard" +- [ ] Pick up keycard → walk to door → interact → door unlocks, keycard consumed from inventory +- [ ] Interact again → door opens + +**Step 4 — Wrong Key:** +- [ ] Create a `DA_Item_Keycard_Beta` with a different tag +- [ ] Pick it up, try on Omega door → "Wrong key — requires Omega Keycard" +- [ ] Keycard_Beta stays in inventory (not consumed on wrong door) + +--- + +## 6. KeyItem vs Regular Item — What's Different + +| Behavior | Regular Item | Key Item (`bIsKeyItem = true`) | +|----------|-------------|-------------------------------| +| Can be dropped? | Yes (if `bCanBeDropped`) | No (auto-protected) | +| Can be cleared (death/new game)? | Yes | No (preserved across death) | +| Stacks | Yes (up to StackLimit) | Usually StackLimit=1 | +| Appears in inventory? | Yes | Yes, with KEY indicator | +| Consumed on use? | Depends on type | Yes (used once, then removed) | +| Sells to vendor? | Yes | No (protected from sale) | + +Key items exist specifically to **gate progression**. They're a player's permanent proof of having solved a puzzle, explored an area, or defeated a boss. The framework ensures they can't be accidentally lost. diff --git a/docs/game/item-medkit.md b/docs/game/item-medkit.md new file mode 100644 index 0000000..11cf790 --- /dev/null +++ b/docs/game/item-medkit.md @@ -0,0 +1,197 @@ +# Item Example — MedKit (Consumable) + +**Item Type:** `Consumable` +**Complexity:** Low +**Systems Used:** `DA_ItemData`, `BP_ItemPickup`, `BPC_InventorySystem`, `BPC_ConsumableSystem`, `BPC_HealthSystem` +**What You Learn:** Consumable Data Asset, pickup actor, health restore, stackable item, quick-slot use + +--- + +## 1. Create the DA_ItemData + +### 1.1 — Create the Data Asset + +``` +Content Browser → Game/Items/ + Right-click → Miscellaneous → Data Asset + Class: DA_ItemData + Name: DA_Item_MedKit +``` + +### 1.2 — Fill Core Properties + +| Property | Value | Notes | +|----------|-------|-------| +| `Item Tag` | `Framework.Item.Consumable.MedKit` | Unique identity | +| `Display Name` | "MedKit" | Player sees this | +| `Description` | "A standard medical kit. Restores 25 health." | Shown in inventory | +| `Icon` | `T_MedKit_Icon` | Any red/white cross texture for prototype | +| `World Mesh` | `SM_MedKit` | Soft reference — Construction Script resolves it | +| `Weight` | `1.0` | Light carry weight | +| `Stack Limit` | `5` | Can carry up to 5 in one stack | +| `Item Type` | `Consumable` | Triggers Consumable Data panel | + +### 1.3 — Fill Consumable Data + +When `ItemType == Consumable`, the `Consumable Data` panel appears: + +| Field | Value | Notes | +|-------|-------|-------| +| `Health Restore` | `25.0` | How much HP to restore (0-100 clamp) | +| `Stress Reduce` | `0.0` | Optional stress relief | +| `Use Duration` | `2.0` | Seconds to consume (animation time) | +| `bConsumedOnUse` | ✓ (true) | Item is removed from inventory after use | + +### 1.4 — Save + +The Data Asset is ready. No other config needed for a simple consumable. + +--- + +## 2. Create the BP_Pickup_MedKit + +Follow the same pattern as the flashlight pickup, but simpler — no active-use actor needed. + +### 2.1 — Create Blueprint + +``` +Content Browser → Game/Pickups/ + Right-click → Blueprint Class → Actor + Name: BP_Pickup_MedKit +``` + +### 2.2 — Add Components + +| # | Component | Name | Purpose | +|---|-----------|------|---------| +| 1 | `StaticMeshComponent` | `Mesh` | Renders the medkit model | +| 2 | `SphereComponent` | `PickupTrigger` | Interaction radius (Sphere Radius → 150) | + +Select `PickupTrigger` → Details → Collision Presets → `OverlapOnlyPawn` + +### 2.3 — Create ItemData Variable + +**+ Variable** → Name: `ItemData` → Type: `DA_ItemData → Object Reference` + +Select the variable → Details: +- **Instance Editable** → ✓ +- **Category** → `Pickup` + +### 2.4 — Wire Construction Script + +``` +[Construction Script] + │ + ├─ ItemData → Is Valid? → Branch + │ ├─ True: + │ │ ├─ ItemData → Get World Mesh → Load Synchronous → Set Static Mesh (Mesh) + │ │ └─ ItemData → Get Display Name → Set Actor Label + │ └─ False: + │ └─ Print String ("No ItemData assigned to BP_Pickup_MedKit") +``` + +### 2.5 — Add I_Interactable and Wire + +Same pattern as flashlight. The key difference for consumables is **stacking**: the pickup can hold multiple units. + +``` +Event Interact + │ Interactor: AActor* + │ + ├─ Interactor → Get Component by Class (UBPC_InventorySystem) → Is Valid? + │ │ + │ └─ True → [Inventory] + │ │ + │ ├─ Can Add Item(ItemData, Quantity=1) + │ │ │ + │ │ ├─ True → Add Item(ItemData, Quantity=1) → Destroy Actor → Return True + │ │ │ + │ │ └─ False: + │ │ ├─ Print "Inventory Full" + │ │ └─ Return False +``` + +> For multi-pickup stacks (e.g., picking up 3 medkits at once), set a `Quantity` variable (Integer, Instance Editable) and pass it to `Add Item`. The inventory auto-stacks identical items. + +### 2.6 — Add Bobbing (optional) + +Add a Timeline to `Event BeginPlay` with a sine wave for Z offset — same as flashlight example. + +--- + +## 3. Wire Consumable Use (On Player Pawn) + +When the player uses a consumable from their inventory or quick-slot: + +### 3.1 — In BPC_ConsumableSystem (BP child on pawn) + +``` +Event Use Consumable (custom event) + │ ItemData: DA_ItemData* + │ + ├─ ItemData → Get Consumable Data (Break FItemConsumableData) + │ │ + │ ├─ Health Restore > 0? + │ │ └─ True: + │ │ ├─ Get Owner → Get Component by Class (UBPC_HealthSystem) + │ │ │ │ + │ │ │ └─ Is Valid? → True: + │ │ │ └─ Apply Healing (HealthRestore, Healer=self) + │ │ │ │ + │ │ │ └─ Print String: "Restored {HealthRestore} HP" + │ │ │ + │ │ └─ False: Print "No HealthSystem found" + │ │ + │ ├─ Stress Reduce > 0? + │ │ └─ True: + │ │ └─ Get BPC_StressSystem → Remove Stress (StressReduce) + │ │ + │ ├─ bConsumedOnUse? + │ │ └─ True: + │ │ └─ Get BPC_InventorySystem → Remove Item (ItemData, 1) + │ │ + │ └─ (Optional) Play sound, screen effect, widget animation +``` + +### 3.2 — Quick Test Without Full Integration + +Bind a test key (e.g., `IA_UseItem`) in your player character: + +``` +Event InputAction (IA_UseItem, Pressed) + │ + ├─ Get Component by Class (UBPC_InventorySystem) + ├─ Get All Items → For Each Loop + │ │ + │ ├─ Break FInventorySlot → Item + │ ├─ Item → Get Item Type == Consumable? + │ │ └─ True: + │ │ ├─ Item → Get Consumable Data → Health Restore + │ │ ├─ Get BPC_HealthSystem → Apply Healing (HealthRestore) + │ │ ├─ Inventory → Remove Item (Item, 1) + │ │ └─ Break (exit loop — only use one) + │ │ + │ └─ Continue loop +``` + +--- + +## 4. Verification Checklist + +- [ ] **Data Asset:** `DA_Item_MedKit` has `ItemType = Consumable`, `HealthRestore = 25`, `StackLimit = 5` +- [ ] **Pickup in level:** Mesh appears, bobs, interaction prompt shows "Pick up MedKit" +- [ ] **Inventory:** Pick up medkit → appears in inventory slot. Pick up 4 more → they stack (quantity = 5). Pick up 6th → fails (stack limit). +- [ ] **Use:** Press use item key → health increases by 25, stack count decreases by 1 +- [ ] **Weight:** Each medkit adds 1.0 to carry weight. `Get Remaining Weight` decreases by 1.0 per pickup. +- [ ] **Full inventory:** Pick up medkit when no slots left → "Inventory Full" feedback, pickup remains in world + +--- + +## 5. Key Concepts + +| Concept | How It Works | +|---------|-------------| +| **Stacking** | `StackLimit = 5` on the Data Asset. `BPC_InventorySystem.AddItem()` auto-finds existing stacks and adds to them instead of creating new slots. | +| **Consumed on use** | `bConsumedOnUse = true` → `BPC_ConsumableSystem` calls `RemoveItem()` after applying effects. Set to `false` for reusable items (e.g., a reusable syringe with cooldown). | +| **Soft references** | `WorldMesh` is a `TSoftObjectPtr` — it doesn't load into memory until Construction Script resolves it. This keeps memory low when assets aren't in use. | +| **Inventory weight** | `BPC_InventorySystem.CurrentWeight` auto-updates when items are added/removed. `CanAddItem()` checks weight capacity before pickup. | diff --git a/docs/game/item-pistol.md b/docs/game/item-pistol.md new file mode 100644 index 0000000..e98c1d5 --- /dev/null +++ b/docs/game/item-pistol.md @@ -0,0 +1,379 @@ +# Item Example — Pistol (Weapon) + +**Item Type:** `Weapon` +**Complexity:** High +**Systems Used:** `DA_ItemData`, `BP_ItemPickup`, `BP_WeaponBase`, `BPC_FirearmSystem`, `BPC_AmmoComponent`, `BPC_DamageReceptionSystem`, `BPC_RecoilSystem`, `BPC_ReloadSystem`, `BPC_EquipmentSlotSystem`, `BPC_InventorySystem`, `I_UsableItem`, `I_Damageable` +**What You Learn:** Full weapon pipeline — Data Asset with EquipmentData, pickup-to-equip flow, frame-driven fire with hitscan, ammo management, damage application, recoil + +--- + +## 1. Create the DA_ItemData + +### 1.1 — Create the Data Asset + +``` +Content Browser → Game/Items/ (or Game/Weapons/) + Right-click → Miscellaneous → Data Asset + Class: DA_ItemData + Name: DA_Item_Pistol +``` + +### 1.2 — Fill Core Properties + +| Property | Value | Notes | +|----------|-------|-------| +| `Item Tag` | `Framework.Item.Weapon.Pistol` | Registered in `DA_GameTagRegistry` | +| `Display Name` | "9mm Pistol" | | +| `Description` | "A standard-issue 9mm semi-automatic pistol." | | +| `Icon` | `T_Pistol_Icon` | | +| `World Mesh` | `SM_Pistol` | Soft reference | +| `Weight` | `3.0` | Heavier than consumables | +| `Stack Limit` | `1` | Cannot stack weapons | +| `Item Type` | `Weapon` | Triggers `Equipment Data` panel | + +### 1.3 — Fill Equipment Data + +When `ItemType == Weapon`, the `Equipment Data` panel appears: + +| Field | Value | Notes | +|-------|-------|-------| +| `Slot` | `Framework.Equipment.Slot.PrimaryWeapon` | Occupies player's primary weapon slot | +| `Damage` | `15.0` | Base damage per shot | +| `Fire Rate` | `0.15` | Seconds between shots (400 RPM) | +| `Range` | `5000.0` | Hitscan range in Unreal units | +| `Magazine Size` | `12` | Rounds per magazine | +| `Reload Time` | `2.0` | Seconds to reload | + +### 1.4 — Save + +--- + +## 2. Create the BP_Pickup_Pistol (World Pickup) + +Same as other pickups, with weapon mesh: + +### 2.1 — Blueprint + Components + +``` +Content Browser → Game/Pickups/ + Right-click → Blueprint Class → Actor + Name: BP_Pickup_Pistol + +Components: + ├── StaticMeshComponent "Mesh" + └── SphereComponent "PickupTrigger" (radius 200, OverlapOnlyPawn) +``` + +### 2.2 — Variable & Construction Script + +Same pattern as flashlight — `ItemData` → Get World Mesh → Load Synchronous → Set Static Mesh. + +### 2.3 — I_Interactable Wiring + +Same as other pickups, but **after adding item to inventory**, also auto-equip it: + +``` +Event Interact + │ + ├─ Interactor → Get BPC_InventorySystem + ├─ CanAddItem(ItemData, 1) → True → AddItem → store result + │ │ + │ ├─ Interactor → Get BPC_EquipmentSlotSystem + │ │ └─ Equip To Slot(ItemData, PrimaryWeapon slot tag) + │ │ └─ This spawns the BP_Pistol actor at the player's hand socket + │ │ + │ ├─ Destroy Actor + │ └─ Return True + │ + └─ False → Print "Inventory Full" → Return False +``` + +--- + +## 3. Create the BP_Pistol (Held Weapon Actor) + +This is the actor that appears in the player's hand when equipped. + +### 3.1 — Create Blueprint + +``` +Content Browser → Game/Actors/ (or Game/Weapons/) + Right-click → Blueprint Class → Actor (or BP_WeaponBase from framework) + Name: BP_Pistol +``` + +### 3.2 — Add Components + +| # | Component | Name | Purpose | +|---|-----------|------|---------| +| 1 | `SkeletalMeshComponent` | `WeaponMesh` | Animated weapon (slide, magazine) | +| 2 | `SceneComponent` | `MuzzleSocket` | Where the bullet trace starts (attach to muzzle bone) | +| 3 | `AudioComponent` | `FireSound` | Plays fire sound | +| 4 | `AudioComponent` | `ReloadSound` | Plays reload sound | +| 5 | `ParticleSystemComponent` | `MuzzleFlash` | Fire particle effect | +| 6 | *(Optional)* `PointLightComponent` | `MuzzleLight` | Brief flash on fire | + +Parent `MuzzleSocket` to the muzzle bone on `WeaponMesh`, or set its relative location to where the barrel ends. + +### 3.3 — Variables + +| Variable | Type | Default | Purpose | +|----------|------|---------|---------| +| `CurrentAmmoInMag` | Integer | `12` | Rounds currently loaded | +| `MaxMagazineSize` | Integer | `12` | From EquipmentData | +| `bIsFiring` | Boolean | `false` | Prevents re-trigger during fire rate cooldown | +| `FireRateTimer` | Float | `0.15` | Seconds between shots | +| `WeaponData` | DA_ItemData* | — | Reference to the pistol Data Asset | +| `FireRange` | Float | `5000.0` | Hitscan distance | + +### 3.4 — Interfaces + +Class Settings → Interfaces → Add: +- `UUsableItem` — so active item system can route fire input + +### 3.5 — Fire Logic (Hitscan) + +``` +Event Use Item (Interface -> UUsableItem) + │ User: AActor*, Target: AActor* + │ Return Boolean + │ + ├─ bIsFiring? → Branch + │ ├─ True → Return False (fire rate lockout) + │ └─ False: + │ │ + │ ├─ CurrentAmmoInMag > 0? → Branch + │ │ ├─ True: + │ │ │ ├─ Set bIsFiring = true + │ │ │ ├─ CurrentAmmoInMag = CurrentAmmoInMag - 1 + │ │ │ │ + │ │ │ ├─ [Play Effects] + │ │ │ │ ├─ MuzzleFlash → Activate + │ │ │ │ ├─ FireSound → Play + │ │ │ │ └─ MuzzleLight → Set Visibility (true) + │ │ │ │ └─ Delay 0.05 → Set Visibility (false) + │ │ │ │ + │ │ │ ├─ [Line Trace — Hitscan] + │ │ │ │ │ + │ │ │ │ ├─ Get Player Controller → Get Camera Location/Direction + │ │ │ │ ├─ Start: MuzzleSocket → Get World Location + │ │ │ │ ├─ End: Start + (CameraForward × FireRange) + │ │ │ │ │ + │ │ │ │ └─ Line Trace By Channel (Visibility) + │ │ │ │ ├─ Trace Complex: false + │ │ │ │ └─ Ignore Self, Ignore Owning Pawn + │ │ │ │ │ + │ │ │ │ ├─ Hit? → Branch + │ │ │ │ │ │ + │ │ │ │ │ ├─ True: + │ │ │ │ │ │ ├─ Hit Actor → Does Implement Interface (UDamageable)? + │ │ │ │ │ │ │ ├─ True: + │ │ │ │ │ │ │ │ ├─ Call I_Damageable.Take Damage + │ │ │ │ │ │ │ │ │ ├─ Damage Amount: WeaponData → Get Equipment Data → Damage + │ │ │ │ │ │ │ │ │ ├─ Damage Causer: Self (or OwningPawn) + │ │ │ │ │ │ │ │ │ ├─ Damage Type: Framework.Combat.Damage.Physical + │ │ │ │ │ │ │ │ │ ├─ Hit Location: HitResult.ImpactPoint + │ │ │ │ │ │ │ │ │ └─ Hit Direction: (ImpactPoint - Start) normalized + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ └─ Print String: "Hit {HitActor} for {Damage} damage" + │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ └─ False: Print "Hit non-damageable object" + │ │ │ │ │ │ │ + │ │ │ │ │ │ └─ Spawn Decal/Impact Effect at Hit Location + │ │ │ │ │ │ + │ │ │ │ │ └─ False (miss): nothing + │ │ │ │ │ + │ │ │ │ └─ Draw Debug Line (red for 1 second, editor only) + │ │ │ │ + │ │ │ ├─ [Recoil] + │ │ │ │ └─ Get BPC_RecoilSystem (on pawn) → Apply Recoil (WeaponData) + │ │ │ │ + │ │ │ └─ Delay (FireRateTimer) → Set bIsFiring = false → Return True + │ │ │ + │ │ └─ False: (out of ammo) + │ │ ├─ Play click/empty sound + │ │ ├─ (Optional) Auto-reload → Call Reload + │ │ └─ Return False +``` + +### 3.6 — Reload Logic + +``` +Function: Reload + │ + ├─ CurrentAmmoInMag >= MaxMagazineSize? → Return (already full) + ├─ Set bIsReloading = true + │ + ├─ Get Owning Pawn → Get BPC_AmmoComponent + │ │ + │ └─ Get Ammo Count (AmmoTypeTag = Framework.Item.Ammo.Pistol9mm) + │ │ + │ ├─ Branch (> 0?) + │ │ ├─ True: + │ │ │ ├─ Rounds To Load = Min(AmmoAvailable, MaxMagazineSize - CurrentAmmoInMag) + │ │ │ ├─ CurrentAmmoInMag += Rounds To Load + │ │ │ ├─ AmmoComponent → Consume Ammo (RoundsToLoad) + │ │ │ └─ Play ReloadSound + │ │ │ + │ │ └─ False: Print "No ammo" → Return + │ │ + │ ├─ Delay (ReloadTime) → Set bIsReloading = false + │ └─ Broadcast OnReloadComplete +``` + +--- + +## 4. Wire BPC_AmmoComponent (On Player Pawn) + +Create BP child of `BPC_AmmoComponent` (spec #70): + +| Variable | Type | Default | Purpose | +|----------|------|---------|---------| +| `AmmoPool` | Map (GameplayTag → Integer) | Empty | AmmoType → Count | + +``` +Function: Get Ammo Count (AmmoTypeTag) → Integer + │ + └─ AmmoPool.Find(AmmoTypeTag) → Return count (0 if not found) + +Function: Consume Ammo (AmmoTypeTag, Amount) + │ + ├─ Current = Get Ammo Count (AmmoTypeTag) + ├─ Current -= Amount + ├─ Clamp to 0 minimum + └─ AmmoPool[AmmoTypeTag] = Current + +Function: Add Ammo (AmmoTypeTag, Amount) + │ + ├─ AmmoPool.Find or Add (AmmoTypeTag) + └─ Increment by Amount +``` + +--- + +## 5. Wire BPC_RecoilSystem (On Player Pawn) + +Create BP child of `BPC_RecoilSystem` (spec #77): + +``` +Function: Apply Recoil (WeaponData: DA_ItemData*) + │ + ├─ WeaponData → Get Equipment Data → Get Damage (or FireRate) + ├─ Calculate kick: PitchUp = random(1.0, 2.5), YawSide = random(-0.5, 0.5) + │ + ├─ Get Player Controller → Add Controller Pitch Input (PitchUp × -1) + ├─ Get Player Controller → Add Controller Yaw Input (YawSide) + │ + └─ (For smooth recovery, use a Timeline or interp to return to original aim) +``` + +--- + +## 6. Create DA_Item_Ammo_Pistol9mm (Ammo Data Asset) + +``` +Content Browser → Game/Items/ + Right-click → Miscellaneous → Data Asset → DA_ItemData + Name: DA_Item_Ammo_Pistol9mm + +Properties: + ItemTag: Framework.Item.Ammo.Pistol9mm + DisplayName: "9mm Rounds" + ItemType: Ammo + StackLimit: 999 + Weight: 0.05 + (AmmoData sub-panel auto-appears — set PerPickupCount = 15) +``` + +Create `BP_Pickup_Ammo_Pistol9mm` following the same pattern as other pickups. On interact, call `BPC_AmmoComponent.Add Ammo(AmmoTypeTag, PerPickupCount)` instead of adding to inventory. + +--- + +## 7. Verification Checklist + +**Step 1 — Pickup:** +- [ ] Pistol pickup in level → mesh visible → interact → goes to inventory +- [ ] Auto-equips to primary weapon slot (weapon appears in player's hand) + +**Step 2 — Fire:** +- [ ] Equip pistol, press fire → muzzle flash + sound + hitscan line +- [ ] Hit an enemy actor implementing `I_Damageable` → health decreases +- [ ] Ammo counter decreases (12 → 11 → 10...) +- [ ] Camera kicks slightly on each shot (recoil) +- [ ] Fire rate enforced: cannot spam fire faster than 0.15s + +**Step 3 — Empty Magazine:** +- [ ] Fire all 12 rounds → click sound plays, no trace, no damage +- [ ] Auto-reload? Or manual reload prompt appears + +**Step 4 — Reload:** +- [ ] Press Reload key → reload animation/sound plays +- [ ] Ammo transfers from pool to magazine +- [ ] Magazine shows 12 rounds again +- [ ] If no ammo in pool → reload fails + +**Step 5 — Ammo Pickup:** +- [ ] Pick up `BP_Pickup_Ammo_Pistol9mm` → ammo pool increases by 15 +- [ ] Reload → magazine fills, pool decreases by rounds loaded + +**Step 6 — Damage Pipeline:** +- [ ] Enemy pawn has `BPC_DamageReceptionSystem` + `BPC_HealthSystem` +- [ ] Fire at enemy → `ApplyDamage(15.0, ...)` called +- [ ] Damage pipeline: resistance → armor → shield → health +- [ ] Enemy health decreases proportionally + +--- + +## 8. Weapon Data Flow (Complete) + +``` +Player Input (IA_Fire) + │ + └─ BPC_ActiveItemSystem routes to equipped weapon + │ + └─ BP_Pistol (implements I_UsableItem) + │ + ├─ [Ammo Check] → BPC_AmmoComponent (in-mag count) + ├─ [Hitscan] → Line Trace from MuzzleSocket + │ │ + │ └─ Hit Actor (implements I_Damageable) + │ │ + │ └─ BPC_DamageReceptionSystem.ApplyDamage(RawDamage, Causer, Type, Location, Direction) + │ │ + │ ├─ Calculate Resistance + │ ├─ Apply Shield (BPC_ShieldDefenseSystem) + │ ├─ Apply Health (BPC_HealthSystem) + │ ├─ Evaluate Hit Reaction (BPC_HitReactionSystem) + │ │ ├─ Stagger? (damage > threshold) + │ │ └─ Knockdown? (damage > higher threshold) + │ │ + │ └─ Broadcast OnDamageReceived → UI, audio, metrics + │ + ├─ [Recoil] → BPC_RecoilSystem.ApplyRecoil() + ├─ [Effects] → MuzzleFlash, FireSound, ImpactDecal + └─ [Fire Rate] → Timer lockout until next shot + +When magazine empty: + └─ Player presses IA_Reload + └─ BP_Pistol.Reload() + └─ BPC_AmmoComponent.ConsumeAmmo() (from pool) + └─ BP_Pistol.CurrentAmmoInMag += rounds loaded +``` + +--- + +## 9. Key Differences — Weapon vs Tool vs Consumable + +| Aspect | Pistol (Weapon) | Flashlight (Tool) | MedKit (Consumable) | +|--------|----------------|-------------------|---------------------| +| ItemType | `Weapon` | `Tool` | `Consumable` | +| Slot | `PrimaryWeapon` | `Tool` | None (inventory bag) | +| Use action | Frame-driven (continuous) | Toggle (single click) | One-shot (consumed) | +| Ammo | Yes (magazine + pool) | No (battery as optional resource) | No | +| Damage output | Yes | No | No (heals) | +| Fire rate | Throttled (timer) | N/A | Use duration (2s anim) | +| Recoil | Yes | No | No | +| Reload | Yes | No | No | +| Stackable | No (StackLimit=1) | No (StackLimit=1) | Yes (StackLimit=5) | +| Interfaces | `I_UsableItem` | `I_UsableItem` + `I_Toggleable` | `I_UsableItem` | +| Pickup → Equip | Auto-equips to slot | Auto-equips to slot | Goes to inventory only |