Quest System¶
Summary: Tag-graph quest system — the player's quest state IS a replicated
FGameplayTagContainer, and all narrative progression is expressed as tag grants. There is deliberately no objective-checklist engine and no quest log: the Compendium (journal of discovered lore entries) is the player-facing record.UQuestManagerComponenton GameState holds the global registry;UPlayerQuestComponenton PlayerState tracks per-player tags. Dialogue (DlgSystem), item pickups, lore actors, and world triggers all funnel into the same server-authoritativeAddQuestTagpath.
Table of Contents¶
- Design Philosophy
- Architecture Overview
- Tag Conventions
- Quest Flow
- Component Responsibilities
- Quest Data Model
- Quest Rewards
- Compendium System
- Dialogue Integration
- Multiplayer
- Public Contracts
- Gotchas
- Source References
- Related Systems
- Recent Changes
Design Philosophy¶
The tag-graph model is final (WorldSystemsScalingReview RULING-8, Oliwer 2026-07-07):
- Tags are the quest state. A quest's progression is the set of
Quest.*tags a player has. There are no per-objective booleans, no counters, no stage machine — a "stage" is just a tag someone granted. - No quest log. The GDD's narrative stance (story through environments, items, echoes) means the player-facing
surface is the Compendium: lore entries unlock when their
RequiredTagis granted, and the journal doubles as the quest record. - Everything funnels through
AddQuestTag. Dialogue events, item pickups (FQuestFragment), lore actors (ALoreFragmentActor), world-hint triggers, and quest triggers all end up in the same server-authoritative grant path, which also resolves compendium unlocks. - NPCs branch on tags. Dialogue graphs check tags via
UQuestDialogueConditionrather than querying quest objects. FromSoft-style: the world reacts to what you've done, it doesn't enumerate what's left to do.
Do NOT add checklist-engine features (visibility conditions, per-objective completion flags, prerequisite chains) — they were built speculatively once, never read by anything, and deleted in the 2026-07 Phase 1 cleanup.
Architecture Overview¶
┌─────────────────────────────────────────────────────────────┐
│ AEternalGameState │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ UQuestManagerComponent (Global) │ │
│ │ • Quest Data Asset Registry (asset-registry scan) │ │
│ │ • Compendium Entry Registry (RequiredTag → entry) │ │
│ │ • Global Events (FGameplayTagContainer, replicated) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ AEternalPlayerState │
│ ┌─────────────────────────────────────────────────────── ┐ │
│ │ UPlayerQuestComponent │ │
│ │ • QuestTags / ActiveQuests / CompletedQuests │ │
│ │ • all replicated COND_OwnerOnly │ │
│ └─────────────────────────────────────────────────────── ┘ │
│ (GetCompendiumSystem() accessor only — see below) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ AEternalPlayer (PlayerController) │
│ ┌─────────────────────────────────────────────────────── ┐ │
│ │ UCompendiumSystem │ │
│ │ • Discovered entries (TMap, NOT replicated) │ │
│ │ • Read/unread state, persistence save/load │ │
│ └─────────────────────────────────────────────────────── ┘ │
└─────────────────────────────────────────────────────────────┘
Note the compendium's home: UCompendiumSystem is created on the PlayerController
(EternalPlayer.cpp constructor). AEternalPlayerState::GetCompendiumSystem() is just an accessor. Older docs
claimed it lived on PlayerState — wrong.
Key Design Principles¶
| Principle | Implementation |
|---|---|
| Tag-Based | All quest state tracked via FGameplayTag |
| Server-Authoritative | All mutations authority-gated; clients go through Server RPCs |
| Dual Components | Global registry + per-player tracking |
| Data-Driven | Quests defined entirely through data assets |
| Single Grant Funnel | Every grant path converges on AddQuestTag → compendium unlock → client toast |
Tag Conventions¶
Authored tags follow this shape (see Config/Tags/GameplayTags.ini, Quest.* block):
Quest.[Name] root quest tag (registered to a UQuestDataAsset)
Quest.[Name].Discovery.[How] discovery grants (e.g. Quest.BeautifulFountain.Discovery.NPCMentioned)
Quest.[Name].Objective.[What] objective grants (e.g. Quest.BeautifulFountain.Objective.PickedUpHead)
Quest.[Name].Completed granted automatically by CompleteQuest
CompleteQuestsynthesizes the completion tag by string append:QuestTag + ".Completed"(PlayerQuestComponent.cpp). The tag must exist in the ini for the grant to resolve.- There is no
.Stage.segment. GenericQuest.Stage.*/Quest.SubEvent.*scaffolding tags and theGetQuestDiscoveryTag/GetQuestCompletionTaghelpers that derived them were dead code from an idealized early model and were deleted (2026-07 Phase 1). Shipped content never used them. - Tags are hand-authored in the ini today; the NarrativeAuthoringWorkflow manifest→stamp pipeline will generate them
(see
ImplementationDocs/NarrativeAuthoringWorkflow.plan.md).
Quest Flow¶
Quest Lifecycle¶
Discovery Active Completed
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌───────────┐
│ Trigger │ │ Progress │ │ Rewards │
│ • NPC │─────StartQuest()─▶│ • Tags │──CompleteQuest()─▶│ • Items │
│ • Item │ │ • Dialog │ │ • Consume │
│ • Zone │ │ │ │ Items │
└─────────┘ └──────────┘ └───────────┘
State Transitions¶
AddQuestTag(Tag) [authority only]
│
├─ Reject invalid / already-present tag
├─ Add to QuestTags container
├─ Broadcast OnQuestTagAdded
├─ CheckForTagDrivenItemConsumption: consume inventory items whose FQuestFragment.ConsumeOnTag == Tag
├─ QuestManager registry lookup: does Tag unlock a compendium entry?
│ └─ yes → CompendiumSystem->AddEntry + ShowCompendiumUnlock (diegetic: entry title alone)
├─ QuestManager deepen lookup: does Tag deepen an existing entry?
│ └─ yes → CompendiumSystem->DeepenEntry (content grows, entry flips back to unread — no toast)
└─ Replication (COND_OwnerOnly) → OnRep on owning client
StartQuest(QuestTag) [authority only]
│
├─ Quest must be registered in QuestManager
├─ Reject if already active or completed
├─ Add to ActiveQuests
└─ Broadcast OnQuestStarted
CompleteQuest(QuestTag) [authority only]
│
├─ Quest must be registered and active, not already completed
├─ Move ActiveQuests → CompletedQuests
├─ AddQuestTag(QuestTag + ".Completed")
├─ Broadcast OnQuestCompleted
├─ CheckForQuestItemConsumption (FQuestFragment items)
└─ GrantQuestRewards (item pipeline — see Quest Rewards)
Grant Paths (all converge on AddQuestTag)¶
| Path | Mechanism |
|---|---|
| Dialogue | UQuestDialogueEvent → Server RPCs on UPlayerQuestComponent |
| Dialogue item hand-over | UGiveItemDialogueEvent → Server_GrantDialogueNodeItems (grants items + GrantedMarkerTag; see Dialogue Integration) |
| Item pickup/drop | FQuestFragment::OnPickup/OnDrop (bound to inventory events) |
| Lore actor | ALoreFragmentActor (LoreUnlockTag on examine) |
| World trigger | BP_StartQuestTrigger (Blueprint); AQuestTriggerActor (volume that opens a dialogue) |
| World hints | WorldHintComponent → Server_AddQuestTag |
| Enemy kill credit | AEternalEnemy::GrantKillCreditQuestTag() on death: for every damage contributor (not only the killing blow) grants FEnemyData::KillCreditQuestTag, runs CompleteQuest on KillCreditCompleteQuestTag (idempotent), and fires AEternalPlayer::Client_NotifyBossDefeated when BossBannerEpithet is set — the banner shows even for enemies with no quest tags. See Enemy AI |
| Encounter gate | AEncounterGateActor::GrantOnOpenTag — granted to every player on an announced open only; silent initialization opens (already-cleared restore) never grant. See Dungeon System |
Component Responsibilities¶
UQuestManagerComponent (Global)¶
Lives on AEternalGameState, server-authoritative.
| Method | Purpose |
|---|---|
LoadAllQuestDataAssets() |
Asset-registry scan at BeginPlay; builds all four registries (quests, entry unlocks, deepen stages, category display assets) |
RegisterQuest(QuestData) |
Add quest to registry (also indexes its compendium entries) |
GetQuestDataByTag(Tag) |
Lookup quest data asset |
GetCompendiumEntryByTag(RequiredTag) |
Fast entry lookup used by AddQuestTag |
GetCompendiumDeepenByTag(RequiredTag) |
Fast deepen-stage lookup used by AddQuestTag |
GetCompendiumCategoryByTag(CategoryTag) |
Display data (UCompendiumCategoryDataAsset) for a journal tile |
StartGlobalEvent(Tag) / EndGlobalEvent(Tag) |
Activate/deactivate world event |
IsGlobalEventActive(Tag) |
Check event status |
UPlayerQuestComponent (Per-Player)¶
Lives on AEternalPlayerState, server-authoritative. Every mutation early-returns unless
GetOwnerRole() == ROLE_Authority.
| Method | Purpose |
|---|---|
AddQuestTag(Tag) |
Add tag (authority) — the single grant funnel |
RemoveQuestTag(Tag) |
Remove tag (authority) |
StartQuest(Tag) / CompleteQuest(Tag) |
Quest lifecycle (authority) |
Server_AddQuestTag / Server_RemoveQuestTag / Server_StartQuest / Server_CompleteQuest |
Server RPC mirrors — the ONLY correct call surface from client-side code (dialogue, UI) |
HasQuestTag(Tag) |
Hierarchical check (see Gotchas) |
IsQuestActive(Tag) / IsQuestCompleted(Tag) |
Status queries |
GetQuestTags() / GetActiveQuestTags() / GetCompletedQuestTags() |
Container access |
Persistence goes through PersistenceHelpers / PersistenceComponent, not through this component — it has no
GetSaveData/LoadFromSaveData of its own (older doc claimed otherwise).
Quest Item Consumption¶
Two consumption moments, both on UPlayerQuestComponent and both shaped the same way:
CompleteQuest(QuestTag) AddQuestTag(Tag)
│ │
└─ CheckForQuestItemConsumption(QuestTag) └─ CheckForTagDrivenItemConsumption(Tag)
│ │
├─ ResolveInventory() ├─ ResolveInventory()
├─ COLLECT items whose ├─ COLLECT items whose
│ FQuestFragment.RelatedQuestTag == Tag │ FQuestFragment.ConsumeOnTag == Tag
└─ then for each: OnQuestCompleted └─ then for each: OnConsumeTagGranted
└─ bConsuming = true └─ bConsuming = true
IItemContainer::Execute_RemoveItemFromContainer
bConsuming = false
ResolveInventory()re-resolves throughIPlayerInventoryon the owning PlayerState on demand. The BeginPlay inventory binding can miss (the PlayerController links to the PlayerState late), and a null cached pointer used to make consumption a silent no-op.- Collect before removing. Consumption removes fast-array entries; removing mid-iteration invalidated the iteration.
- Removal goes through
IItemContainer::Execute_RemoveItemFromContainer, so grid occupancy clears — a raw entry removal left a ghost footprint in the inventory grid. bConsumingguard: the removal fires the sameOnItemRemovedpath a player drop does, andFQuestFragment::OnDropmust not strip the objective tag for a consumption.ConsumeOnTagis for items narratively handed over mid-quest (the recipient "takes" the item at that beat) without waiting for completion;RelatedQuestTag+bConsumeOnQuestCompleteremains the completion path.
Quest Data Model¶
UQuestDataAsset¶
| Property | Purpose |
|---|---|
QuestName |
Display name (also the journal tile title for legacy quest-tag-grouped entries) |
QuestDescription |
Full description text |
QuestBanner |
UI banner image |
QuestTag |
Unique identifier tag |
bSuppressCompletionBanner |
Skip the quest-completed event banner — for quests whose completion moment is already announced by something bigger (a boss-defeated banner); two stacked banners cheapen both |
Objectives |
Array of objectives (display metadata for tags) |
Rewards |
Array of rewards, granted on CompleteQuest |
CompendiumEntries |
Lore entries unlocked during this quest (inline — ruled N3, no per-entry assets) |
CompendiumDeepenStages |
Deepen stages this quest's tags apply to already-unlocked entries (theirs or another quest's) |
The checklist-era fields (DiscoveryCondition, CompletionCondition, bIsRepeatable, bIsHidden,
RequiredGearScore, FQuestCondition, FQuestSaveData) were never read by anything and were deleted in the
2026-07 Phase 1 cleanup. Do not reintroduce.
FQuestObjective¶
| Property | Purpose |
|---|---|
DisplayName |
Short objective name |
Description |
Objective details |
CompletionTag |
The tag whose grant represents this objective |
bIsOptional |
Design metadata (nothing enforces it — tags are the truth) |
FQuestReward¶
| Property | Purpose |
|---|---|
DisplayName |
Reward display name (falls back to the item's name fragment if empty) |
Description |
Reward description |
ItemTemplate |
TSoftObjectPtr<UItemManifestDataAsset> to grant |
ItemAmount |
Quantity (stack count for stackables) |
Quest Rewards¶
CompleteQuest → GrantQuestRewards (server-side, PlayerQuestComponent.cpp):
- Load each reward's
ItemTemplatemanifest asset. - Copy the manifest, apply stack count, and run
UItemGenerationLibrary::GenerateItemProperties— the same generated-item flow as vendor stock, so gear rewards roll modifiers server-side. Manifest()theUItemObject(outer = owning PlayerController, matching the pickup path) andTryAddItemToContaineron the player's inventory.- Inventory full → the reward is world-dropped at the player's feet via
AEternalGameState::Server_RequestSpawnIteminstead of being lost. Client_ShowRewardNotificationtoast on the owning client.
Compendium System¶
UCompendiumSystem¶
Journal/lore system on the PlayerController (AEternalPlayer).
| Method | Purpose |
|---|---|
AddEntry(Entry) |
Add new compendium entry (stamps DiscoveryTime, dedups by EntryID) |
UpdateEntry(ID, Content) |
Replace entry text — always flips the entry back to unread |
DeepenEntry(Stage) |
Apply a deepen stage: compose (append/replace) onto existing content via UpdateEntry; refuses (warn) if the entry isn't unlocked yet |
MarkEntryAsRead(ID) / Server_MarkEntryAsRead(ID) |
Clear unread indicator (server RPC for client UI) |
GetEntry(ID) / GetAllEntries() |
Retrieval (chronological, oldest first — the list reads as a story) |
GetEntriesByQuestTag(Tag) |
Filter by quest (hierarchical tag match) |
GetUnreadEntries() / GetRecentEntries(Count) |
Filtered views |
GetSaveData() / LoadFromSaveData(Entries) |
Persistence surface (used by PersistenceHelpers) |
Entries is an FCompendiumEntryArray (FastArraySerializer) replicated COND_OwnerOnly (ruling N4, shipped) —
the owning client's journal live-updates via PostReplicatedAdd/Change/Receive callbacks. Note the OnRegister
OwnerComponent = this re-bind: without it, archetype property copy points the callbacks at the CDO and a
remote client's journal never updates.
FCompendiumEntry¶
| Property | Purpose |
|---|---|
EntryID |
Unique identifier (FName) |
Title |
Entry title |
Content |
Full text content (single FText — class-voiced variants deferred, ruling N6) |
RelatedQuestTag |
Authoring quest (provenance + grouping fallback) |
CategoryTag |
Player-facing journal tile (Compendium.Category.*, thematic/locational — "The Ashwoods"). Unset → groups under RelatedQuestTag and titles from QuestName. Display data: UCompendiumCategoryDataAsset |
RequiredTag |
The tag whose grant unlocks this entry (exact match) |
DiscoveryTime |
When discovered (runtime, sort key — never rendered) |
bHasBeenRead |
Read state (runtime; also re-set false by deepening) |
FCompendiumDeepenStage¶
Deepening = the activity-safe forward pull: an already-unlocked entry gains content when a later tag lands, and
flips back to unread — the badge is the whole presentation (no toast, no counter, per the no-completion-metrics
pillar). Stages live on UQuestDataAsset.CompendiumDeepenStages and may target another quest's entry.
| Property | Purpose |
|---|---|
EntryID |
The unlocked entry to deepen |
RequiredTag |
Exact tag whose grant applies the stage |
Content |
New text |
bAppend |
Append below existing content (default) or replace outright |
Authoring flows through the quest manifest (deepen[] + categories[] sections —
Tools/Narrative/SPEC_FORMAT.md); the narrative validator checks deepen keys like unlock keys and errors on a
CategoryTag with no display asset.
Category Widget ViewModel Binding¶
UCompendiumController injects the ViewModel into each UCompendiumCategoryWidget at creation time
via SetViewModel. Injection before construction just stores the ViewModel; NativeConstruct performs the
binding. Looking the ViewModel up by widget name in the MVVM collection is now only a fallback for
Blueprint-placed instances, and every ViewModel dereference early-returns on null.
The name lookup could not stay the primary path: the collection is keyed by widget name, and a second world in the same GameInstance recreates the widget under a uniquified name. The lookup then misses and the tile silently renders unbound — a bug that only appears on the second session, never the first.
Red-Text Formatting¶
Compendium entry rich-text keyword formatting (red-text keywords etc.) lives entirely in asset data:
Content/DataAssets/Compendium/DefaultCompendiumFormattingRules.uasset, referenced by W_CompendiumEntry.
Adding a keyword is an asset edit, not a recompile — the old hardcoded C++ table in
CompendiumFormattingRules.cpp was removed (2026-07 Phase 1). If no rules asset is wired, content renders raw.
Dialogue Integration¶
The DlgSystem bridge is five thin classes under Quest/Dialogue/ (+ VendorDialogueEvent).
Walking Away Closes the Conversation¶
Movement is deliberately never blocked during dialogue (owner ruling: genre-standard PoE/Diablo feel). Walking
away therefore is the close gesture, and UDialogueController treats it as one.
A 0.25s timer poll — not a tick — measures distance to the speaker. Past WalkAwayEndDistance
(600, EditDefaultsOnly), or if the speaker despawns, it calls TryEndDialogue. The poll is cleared inside
TryEndDialogue, so it cannot outlive the conversation.
Cinematic dialogues (null instigator) never start the poll — there is no speaker to walk away from.
UQuestDialogueCondition¶
Gates dialogue nodes on quest state. Runs on the owning client against the replicated QuestTags container — safe because quest tags replicate owner-only.
Dialogue Node
│
└─ QuestDialogueCondition
├─ QuestTag: Quest.BeautifulFountain.Discovery.NPCMentioned
└─ bRequireTag: true (must have to show this dialogue)
UQuestDialogueEvent¶
Triggers quest actions from dialogue. Dialogue playback is client-side (UDialogueController runs the loop
on the interacting client; W_Dialogue renders it),
so the event routes through the Server RPCs — calling the authority-gated methods directly from a dialogue event
was a silent no-op on dedicated servers (fixed 2026-07, guarded by AQuestDialogueEventTest).
| Event Type | Routed To |
|---|---|
AddQuestTag |
Server_AddQuestTag |
RemoveQuestTag |
Server_RemoveQuestTag |
StartQuest |
Server_StartQuest |
CompleteQuest |
Server_CompleteQuest |
UGiveItemDialogueEvent¶
UDlgEventCustom that hands the player items when its node is entered, surfaced as a received-item toast:
ItemsToGive (all granted), or BuildGatedItems (only the entry whose RequiredKit the player's active preset
granted — every entry as a fallback when none match; supersedes ItemsToGive when set), plus a required
GrantedMarkerTag as the idempotency guard (held → skip; granted after the give, so re-entering the node or a second
appearance of the same NPC never double-gives). Because the RPC carries only a node identity, the marker is the sole
replay protection: the server refuses a give whose event has no marker, and UNarrativeValidator errors on the node.
The grant is authority-routed like UQuestDialogueEvent, but the Server RPC
(Server_GrantDialogueNodeItems(Dialogue, NodeGUID, EnterEventIndex)) takes only the event's identity — the
dialogue asset, the active node GUID and the enter-event index — and the server re-reads the authored item set from
that node (GrantAuthoredDialogueItems). A client-supplied item list was a dupe vector. Items are manifested and
generated through the same GrantGeneratedItemToInventory helper as quest rewards (modifiers roll server-side,
drop-at-feet when full).
UQuestDialogueTextArgument¶
Dynamic text substitution in dialogue lines (quest name/description/objective text, compendium entry content).
IQuestParticipant¶
Quest/Interfaces/QuestParticipant.h — one method, GetPlayerQuestComponent(). Implemented by
AEternalPlayerState (the dialogue participant for the player side) and AEternalNPC.
Multiplayer¶
- Authority: every quest/compendium mutation is authority-gated; client-side systems (dialogue, UI, cheats)
must use the
Server*RPCs. - Replication:
QuestTags/ActiveQuests/CompletedQuestsreplicateCOND_OwnerOnly— other players never see your quest state. Global events replicate to everyone. - Compendium: server + persistence only today (see warning above; N4 fix scheduled).
- Trust surface: the Server RPCs perform no server-side plausibility validation (a modified client could grant itself any quest tag). Accepted for the current co-op trust model; revisit if that model changes.
- Tests:
AQuestStartTest,AQuestCompleteTest,AQuestDialogueEventTestinContent/Maps/FunctionalTests/ReplicationTestMap.umap(listen server + client PIE).
Public Contracts¶
Events (UPlayerQuestComponent)¶
| Delegate | Payload | When Fired |
|---|---|---|
OnQuestTagAdded |
FGameplayTag |
Tag added |
OnQuestTagRemoved |
FGameplayTag |
Tag removed |
OnQuestStarted |
FGameplayTag |
Quest begins |
OnQuestCompleted |
FGameplayTag |
Quest finishes |
Events (UQuestManagerComponent)¶
| Delegate | Payload | When Fired |
|---|---|---|
OnGlobalEventStarted |
FGameplayTag |
World event begins |
OnGlobalEventEnded |
FGameplayTag |
World event ends |
Events (UCompendiumSystem)¶
| Delegate | Payload | When Fired |
|---|---|---|
OnEntryAdded |
FCompendiumEntry |
New entry discovered |
OnEntryUpdated |
FCompendiumEntry |
Entry content changed |
OnEntryRead |
FCompendiumEntry |
Entry marked as read |
OnCompendiumReloaded |
(none) | Bulk reload after LoadFromSaveData |
Replication¶
| Property | Replication |
|---|---|
QuestTags |
ReplicatedUsing = OnRep_QuestTags, COND_OwnerOnly |
ActiveQuests |
ReplicatedUsing = OnRep_ActiveQuests, COND_OwnerOnly |
CompletedQuests |
ReplicatedUsing = OnRep_CompletedQuests, COND_OwnerOnly |
ActiveGlobalEvents |
ReplicatedUsing = OnRep_ActiveGlobalEvents |
Gotchas¶
HasQuestTagis hierarchical (FGameplayTagContainer::HasTag): holdingQuest.X.Discovery.Yanswers true forHasQuestTag(Quest.X). ConsequentlyAddQuestTag(Quest.X)no-ops if any child ofQuest.Xis already held (the duplicate check uses the same hierarchical match). Grant roots before children, or treat root tags as implied. The planned narrative validator will flag graphs that depend on grant-root-after-child ordering.- Completion tags are string-synthesized —
QuestTag + ".Completed"must exist inGameplayTags.inior the grant silently resolves to an invalid tag and is rejected. - Never call
AddQuestTag/StartQuest/CompleteQuestfrom client-side code — they silently no-op off-authority. Use theServer*RPCs. - Dialogue gives never trust the client's item list.
UGiveItemDialogueEventsends the node identity only; the server re-reads the authored items. Keep it that way when adding new dialogue-driven grants. - Every give-node needs a
GrantedMarkerTag. A node identity is replayable from a modified client, so a markerless give would be farmable gear. The server refuses it and the narrative validator reports it as an error. AQuestTriggerActorpoll-scans after BeginPlay. A pawn that is deferred-spawned already inside the volume produces no begin-overlap, so the actor runs a boundedScanForOverlaps(InitialScanTimer, 0.5s, up to 20 attempts viaInitialScanAttempts) rather than relying on the overlap event alone.
Source References¶
| Component | Location |
|---|---|
UQuestManagerComponent |
Quest/QuestManagerComponent.h |
UPlayerQuestComponent |
Quest/PlayerQuestComponent.h |
UQuestDataAsset |
Quest/QuestDataAsset.h |
FQuestObjective, FQuestReward, FCompendiumEntry |
Quest/QuestTypes.h |
UCompendiumSystem |
Quest/CompendiumSystem.h |
UQuestDialogueCondition |
Quest/Dialogue/QuestDialogueCondition.h |
UQuestDialogueEvent |
Quest/Dialogue/QuestDialogueEvent.h |
UGiveItemDialogueEvent, FBuildGatedItem |
Quest/Dialogue/GiveItemDialogueEvent.h |
AQuestTriggerActor |
Quest/Actors/QuestTriggerActor.h |
Kill-credit fields (KillCreditCompleteQuestTag, BossBannerEpithet) |
AI/Data/BaseEnemyDataAsset.h; consumer AI/Enemy/EternalEnemy.h |
UQuestDialogueTextArgument |
Quest/Dialogue/QuestDialogueTextArgument.h |
IQuestParticipant |
Quest/Interfaces/QuestParticipant.h |
FQuestFragment |
Inventory/Items/Fragments/ItemFragment.h |
UCompendiumFormattingRules |
Data/CompendiumFormattingRules.h |
| Replication tests | Tests/QuestReplicationTest.h + Private/Tests/Replication/QuestReplicationTest.cpp |
Related Systems¶
- Inventory System - Quest item storage, reward grants
- Item Fragments - FQuestFragment
- Item Generation - Reward modifier rolling
- World Systems - Global events
- MVVM Framework - Compendium UI
- Replication Overview - Quest state sync
ImplementationDocs/NarrativeAuthoringWorkflow.plan.md- Authoring pipeline (manifest → stamp → validate → overview)
Recent Changes¶
| Date | Change | Impact |
|---|---|---|
| 2026-08-20 | Documented UGiveItemDialogueEvent (identity-only Server RPC, build-gated gives, marker tag), the reworked item consumption (ResolveInventory, collect-then-remove, Execute_RemoveItemFromContainer, bConsuming guard), FQuestFragment::ConsumeOnTag, bSuppressCompletionBanner, kill-credit / encounter-gate grant paths, and AQuestTriggerActor's bounded poll-scan |
Dialogue hand-overs, boss-kill quest completion and mid-quest item consumption were shipping undocumented; the old consumption diagram described a path that silently no-op'd on a late inventory bind |
| 2026-08-06 | Compendium category widgets now receive their ViewModel by injection (SetViewModel) from UCompendiumController; MVVM name lookup demoted to a Blueprint-placed fallback; all ViewModel dereferences early-return on null |
Name-keyed lookup broke on a second world in the same GameInstance — the recreated widget's name is uniquified, so the tile rendered unbound on every session after the first |
| 2026-07-23 | Walk-away auto-close (21bedd104): a 0.25s timer poll ends the conversation past WalkAwayEndDistance (600) or on speaker despawn; cinematic dialogues never start the poll |
Confirms the ruling that movement is never blocked during dialogue — walking away is the close gesture, and it no longer leaves an orphaned dialogue window |
| 2026-07-07 | Phase 1 cleanup (NarrativeAuthoringWorkflow): vestige deletion, dialogue-event server RPC routing, reward granting wired, red-text rules moved to asset data, doc rewritten around tag-graph philosophy | Checklist-era fields gone; dedicated-server dialogue events fixed |
| - | Initial documentation | - |