# 46 — BPC_LoreUnlockSystem

## Blueprint Spec — UE 5.5–5.7

---

### Parent Class
`ActorComponent`

### Dependencies
- [`BPC_NarrativeStateSystem`](58_BPC_NarrativeStateSystem.md)
- [`DA_LoreEntryData`](48_DA_NarrativeDataAssets.md)
- [`WBP_JournalDisplay`](../06-ui/43_WBP_JournalDisplay.md)

### Purpose
Manages the discovery, unlocking, and tracking of lore entries (documents, notes, recordings, environmental texts, bestiary entries). Provides query methods for lore completeness, and dispatches events when new lore is discovered.

### Responsibilities
- Register all discoverable lore entries from `DA_LoreEntryData` content
- Unlock lore entries when narrative flags are set or when player picks up physical lore items
- Track which lore entries have been read / unread
- Provide category-based filtering (e.g., "All", "Notes", "Audio Logs", "Bestiary")
- Dispatch notifications for newly discovered lore
- Support lore completion tracking (X of Y discovered in a category)
- Handle lore entry grouping (collections, series)

### Does NOT Handle
- Displaying lore entries in UI (that is `WBP_JournalDisplay`)
- Playing audio logs (that is `BPC_DialoguePlaybackSystem` or audio component)
- Physical lore pickup interaction (that is `BPC_InteractionDetector`)

### Variables

| Name | Type | Description |
|------|------|-------------|
| `AllLoreEntries` | Array of DA_LoreEntryData | All discoverable lore in game |
| `UnlockedLoreTags` | Set of GameplayTag | Tags for unlocked entries |
| `ReadLoreTags` | Set of GameplayTag | Tags for entries player has opened |
| `LoreByCategory` | Map: GameplayTag (category) → Array of DA_LoreEntryData | Grouped for UI |
| `UnreadCount` | Integer | Number of unread unlocked entries |
| `NewLoreQueue` | Array of DA_LoreEntryData | Recently unlocked entries for notification |
| `TotalLoreCount` | Integer | Total discoverable entries |
| `TotalUnlockedCount` | Integer | Count of unlocked entries |

### Structs

| Struct | Fields | Description |
|--------|--------|-------------|
| `FLoreDiscovery` | Entry: DA_LoreEntryData, DiscoveredAt: Float (game time), DiscoverySource: FName (e.g., "pickup", "narrative", "exploration") | Record of a lore discovery event |
| `FLoreCategoryProgress` | CategoryTag: GameplayTag, CategoryName: FText, TotalInCategory: Integer, UnlockedInCategory: Integer, ReadInCategory: Integer | Completion per category |

### Enums

| Enum | Values | Description |
|------|--------|-------------|
| `ELoreEntryType` | Document, AudioLog, Note, EnvironmentalText, BestiaryEntry, Collectible, Epistolary, VHSRecording, Photo | Type of lore entry (affects UI icon) |
| `ELoreDiscoveryMethod` | OnPickup, OnNarrativeFlag, OnLocationEnter, OnInteraction, OnConsequence, OnEndingReward | How the entry is discovered |

### Functions / Events

| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `RegisterLoreEntry` | Entry: DA_LoreEntryData | — | Add entry to tracking set |
| `UnlockLoreByTag` | LoreTag: GameplayTag | Bool (success) | Unlock specific entry by tag |
| `UnlockLoreByMethod` | DiscoveryMethod: ELoreDiscoveryMethod | Array of DA_LoreEntryData | Unlock all entries matching a discovery method |
| `MarkLoreAsRead` | LoreTag: GameplayTag | — | Player has opened/viewed the entry |
| `MarkLoreAsUnread` | LoreTag: GameplayTag | — | Reset read status |
| `IsLoreUnlocked` | LoreTag: GameplayTag | Bool | Is the entry unlocked? |
| `IsLoreRead` | LoreTag: GameplayTag | Bool |
| `GetLoreByCategory` | CategoryTag: GameplayTag | Array of DA_LoreEntryData | Filter by category |
| `GetAllUnlockedLore` | — | Array of DA_LoreEntryData |
| `GetUnreadLore` | — | Array of DA_LoreEntryData |
| `GetCategoryProgress` | CategoryTag: GameplayTag | FLoreCategoryProgress |
| `GetOverallProgress` | — | FLoreCategoryProgress (total all categories) |
| `GetLatestDiscoveries` | Count: Integer | Array of DA_LoreEntryData | Newest N unlocked entries |
| `GetUnlockedCountByType` | Type: ELoreEntryType | Integer |
| `DismissNewLoreNotification` | — | — | Clears new lore queue after UI displays |
| `HasUnreadLore` | — | Bool |

### Event Dispatchers

| Name | Parameters | Fired When |
|------|-----------|-----------|
| `OnLoreEntryUnlocked` | Entry: DA_LoreEntryData, DiscoveryMethod: ELoreDiscoveryMethod | Any lore entry is newly unlocked |
| `OnLoreEntryRead` | Entry: DA_LoreEntryData | Lore entry marked as read |
| `OnLoreCategoryCompleted` | CategoryTag: GameplayTag, CategoryName: FText | All entries in a category discovered |
| `OnNewLoreNotification` | NewEntries: Array of DA_LoreEntryData, Count: Integer | Batch of new lore for notification queue |
| `OnLoreProgressUpdated` | OverallProgress: FLoreCategoryProgress | Any unlock/read changed progress |

### Blueprint Flow

```
[Initialization]
  └─► At game start: load all DA_LoreEntryData from asset registry
  └─► Group by CategoryTag into LoreByCategory map
  └─► Set TotalLoreCount = AllLoreEntries.Num()
  └─► Bind to BPC_NarrativeStateSystem.OnFlagChanged for OnNarrativeFlag unlocks

[UnlockLoreByTag: LoreTag]
  └─► Find DA_LoreEntryData by LoreTag from AllLoreEntries
  └─► If not found → return false
  └─► If already in UnlockedLoreTags → return true (already unlocked, no duplicate notification)
  └─► Add LoreTag to UnlockedLoreTags
  └─► Increment TotalUnlockedCount
  └─► Add to NewLoreQueue
  └─► Broadcast OnLoreEntryUnlocked(Entry, DiscoveryMethod)
  └─► Broadcast OnLoreProgressUpdated
  └─► If NewLoreQueue size == 1 → broadcast OnNewLoreNotification
  └─► Check if category is now complete → broadcast OnLoreCategoryCompleted if yes
  └─► Return true

[MarkLoreAsRead: LoreTag]
  └─► If LoreTag not in UnlockedLoreTags → return
  └─► If already in ReadLoreTags → return
  └─► Add to ReadLoreTags
  └─► Decrement UnreadCount
  └─► Broadcast OnLoreEntryRead
  └─► Broadcast OnLoreProgressUpdated

[GetCategoryProgress: CategoryTag]
  └─► Get entries in this category
  └─► Count unlocked in category
  └─► Count read in category
  └─► Return FLoreCategoryProgress
```

### Communications With

| Target System | Method | Why |
|---------------|--------|-----|
| [`BPC_NarrativeStateSystem`](58_BPC_NarrativeStateSystem.md) | Dispatcher (receives) + Direct (unlock by flag) | Narrative flags trigger lore unlocks |
| [`WBP_JournalDisplay`](../06-ui/43_WBP_JournalDisplay.md) | Dispatcher | Notify UI of new lore, progress updates |
| [`BPC_InteractionDetector`](../02-interaction/15_BPC_InteractionDetector.md) | Dispatcher (on pickup) | Physical lore items call UnlockLoreByTag |
| [`BPC_BranchingConsequenceSystem`](42_BPC_BranchingConsequenceSystem.md) | Dispatcher | Consequences can unlock lore entries |
| [Save System](../04-save/32_SS_SaveManager.md) | Direct (via I_Persistable) | Save unlocked/read state |

### Reuse Notes
Pure data-driven: all lore content is defined in `DA_LoreEntryData` assets. The system requires zero blueprint changes to add new lore entries. Supports any mix of discovery methods (pickup, narrative flag, location trigger, consequence). Lore categories are defined by gameplay tags, allowing flexible grouping without code changes.

---

## Manual Implementation Guide

### Class Setup
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_LoreUnlockSystem`
2. Add to Player Character

### Variable Init (BeginPlay)
```
Event BeginPlay
  ├─ Set AllLoreEntries = loaded DA_LoreEntryData array
  ├─ Set UnlockedLoreTags = empty Set<GameplayTag>
  ├─ Set ReadLoreTags = empty Set<GameplayTag>
  ├─ Set TotalLoreCount = AllLoreEntries.Length
  └─ Bind to BPC_NarrativeStateSystem.OnFlagChanged → check for lore unlock triggers
```

### Function Node-by-Node

#### `UnlockLoreByTag(LoreTag: GameplayTag)` → `void`
```
Step 1: If UnlockedLoreTags.Contains(LoreTag) → Return (already unlocked)
Step 2: UnlockedLoreTags.Add(LoreTag)
Step 3: Find entry in AllLoreEntries by LoreTag
Step 4: Add to NewLoreQueue for notification
Step 5: Increment TotalUnlockedCount
Step 6: Fire OnLoreUnlocked(LoreTag, EntryData)
Step 7: Show notification: "New lore discovered: {Entry.Title}"
```

#### `MarkLoreAsRead(LoreTag: GameplayTag)` → `void`
```
Step 1: If NOT UnlockedLoreTags.Contains(LoreTag) → Return
Step 2: If NOT ReadLoreTags.Contains(LoreTag):
  ReadLoreTags.Add(LoreTag)
  Decrement UnreadCount
  Fire OnLoreRead(LoreTag)
```

#### `GetLoreByCategory(CategoryTag: GameplayTag)` → `Array<DA_LoreEntryData>` *(Pure)*
```
ForEach AllLoreEntries:
  If Entry.CategoryTag == CategoryTag AND UnlockedLoreTags.Contains(Entry.LoreTag):
    Add to results
Return results
```

### Build Checklist
- [ ] Create BPC_LoreUnlockSystem, add to Player Character
- [ ] Define DA_LoreEntryData Data Asset (Title, Body, Category, RequiredFlag, LoreTag)
- [ ] Implement UnlockLoreByTag with dedup check
- [ ] Implement MarkLoreAsRead with unread counter
- [ ] Implement category-based filtering for UI
- [ ] Bind to NarrativeStateSystem for flag-triggered unlocks
- [ ] Test: pick up lore item → notification appears → journal entry unlocks