Quests¶
All · 15 min read · No code
What the player sees: an NPC with a "!" bubble offers a quest on a screen with its title, who is asking, the objectives and the reward. Accepting it puts it in the Quest Log and on the HUD tracker; the minimap marks what it still wants; and talking to the giver when it is done pays the reward under a "quest complete" banner.
A quest is a data asset: a title, some objectives, a reward, and optional conditions for when it may be offered. You make one by duplicating a sample quest and filling in its Details panel. The party's progress is remembered per quest asset, so the quest asset is the quest's identity: a quest keeps working when its giver is unloaded, the leader changes, or the game is saved and loaded.
What you can change¶
- A "bring me N of something" quest: Make a collection quest.
- A defeat-enemies, talk-to-someone or go-somewhere quest: Give a quest to defeat enemies, talk to someone, or go somewhere.
- Which NPC gives it and what they say: the NPC's Quest and quest dialogue fields; see Interaction, NPCs and dialogue.
- When it may be offered: its Gates (below).
- How the quest screens look: their Blueprints' class defaults under XenoCore › Quest.
How it fits together¶
| Piece | Kind | Job |
|---|---|---|
Quests/DA_XenoCore_Quest_Collection01, DA_XenoCore_Quest_Monster01 |
Quest definitions (data assets) | The two Gym sample quests |
Quests/GaurPlain/DA_XenoCore_Quest_GP_* |
Quest definitions | The seven quests of the Gaur Plain slice, given by NPCs placed in Maps/L_XenoCore_GaurPlain |
| Quest ledger | Built in (on the player controller) | Status, objective counts, who asked, which quest is tracked. Reach it in a Blueprint with Get Party Quests |
| Offer Quest | Blueprint node with Accepted and Declined outputs | Opens the offer screen, continues on Accepted or Declined |
UserInterface/HUD/W_XenoCore_QuestOffer |
Screen | Title, requester, summary, objectives, rewards, Accept/Decline |
UserInterface/W_XenoCore_QuestLog |
Screen | Every accepted and finished quest, from the menu |
UserInterface/HUD/W_XenoCore_QuestTracker |
HUD plate | The tracked quest and its outstanding objectives |
UserInterface/HUD/W_XenoCore_QuestReward |
HUD plate | The "quest complete" banner with what was paid |
| Quest UI nodes | Blueprint nodes (XenoCore › Quest) | Row info for the log, requester name, Open Quest Log |
Every XenoCore Experience has the quest ledger; there is nothing to add to a map.
The sample NPCs are in Maps/L_XenoCore_Gym: NPC_QuestGiver gives DA_XenoCore_Quest_Collection01
and NPC_MonsterQuestGiver gives DA_XenoCore_Quest_Monster01. Characters/NPC/B_XenoCore_Npc_Sample
also carries Collection01, as a ready-made example; the plain B_XenoCore_Npc gives no quest.
What the two sample quests do¶
- Collection01 is a collect quest: one Collect Item objective for 2
ID_XenoCore_EtherCrystal, taken from the bag on turn-in, rewardingID_XenoCore_BlueChain, 500 gold and 200 experience. - Monster01 is a kill quest: one Defeat Enemy objective for 2
B_XenoCore_Minion_Melee, rewardingID_XenoCore_ScrapPlating, 300 gold and 400 experience. Every minion killed while the quest is active counts, including any child Blueprint of the minion. Its Party Level At Least gate is set to 1, so it never blocks: it is there as a worked example. Raise the level to see a gate work.
Lifecycle¶
A quest's status, shown on screen as the log's status column:
| Status | Meaning | Offerable? |
|---|---|---|
| Unknown | Never answered | Yes |
| Declined | Turned down once | Yes, the player can come back |
| Active | Accepted, objectives outstanding | No |
| Ready To Turn In | Every objective met | No |
| Complete | Turned in and paid. Final | No |
- Offer. The NPC's graph calls Offer Quest. If the quest is not offerable (already taken, or a gate does not hold) nothing opens. The screen pauses the conversation, which resumes on the answer. Exactly one of Accepted or Declined always fires, even if the screen is torn down.
- Accept. The status becomes Active and the counts are filled straight away from the bag. A collect quest for something you already carry can be Ready To Turn In the moment you accept it. The giver's name is recorded. If nothing is tracked yet, this quest becomes the tracked one.
- Progress.
- Collect Item counts are recomputed from the bag every time the bag changes. They are never added up from pickups, so spending or selling the item lowers the count again.
- Defeat Enemy counts go up by one per kill, only while the quest is Active. Kills before you accepted do not count. They cannot be recomputed, so they are what the save file keeps.
- Talk To NPC counts when the party talks to the placed NPC whose Quest Id matches, before that NPC's own graph runs, so a last-step talk to the giver can hand in within the same conversation. Reach Location counts when the leader walks into the matching Quest Location (companions never count), once per visit for each quest.
- When every objective is met the status moves to Ready To Turn In, and back to Active if a collected item leaves the bag.
- Turn in. The NPC's graph calls Turn In. It re-checks the bag first. If the quest is not
really ready it returns false and stays Active. Otherwise it:
- removes the collected items whose objective has Consume On Turn In ticked,
- pays the gold, and experience to the whole roster (reserve members at the Reserve Experience Rate,
0in the sample, see Progression), - adds the reward items to the party bag,
- marks the quest Complete, moves tracking to another running quest, and shows the reward banner.
Gates are checked when a quest is offered, never again. A quest you already have is not taken away because the leader changed to a lower-level character.
The quest marker ("!" bubble) over a giver shows while the quest is offerable or ready to turn in, and hides while it is Active. A gated quest shows no marker at all.
Who asked. The offer screen and the tracker name the requester in this order: the quest's own Requester Name; otherwise the NPC offering it; otherwise the name recorded at Accept. The tracker has no NPC to ask, which is why the name is recorded. It reads "Return to name" when the quest is ready.
The minimap marks what the tracked quest still wants in blue: the enemies a Defeat Enemy objective needs, the NPC of a Talk To NPC step, the Quest Location of a Reach Location step, and the giver once the quest is ready. See Minimap.
The tracker follows exactly one quest. Pick it in the Quest Log. Show or hide it with the quest tracker button (see Controls).
Saving. Every quest's status (by name), its counts, its recorded requester and the tracked quest are saved. On load, collect counts are recomputed from the restored bag; kill counts come from the file. See Save and front end.
Settings¶
On a quest definition:
| Setting | Where it lives | What it does |
|---|---|---|
| Title | XenoCore › Quest | The quest's name on every screen |
| Kind | XenoCore › Quest | Collection, Material, Monster, Challenge, Search, Normal, Story. A label only: it titles the offer screen and sorts the log, and changes no behaviour |
| Area | XenoCore › Quest | Free text shown beside the quest in the log |
| Requester Name | XenoCore › Quest | Who is asking. Leave empty to use the NPC's Display Name |
| Requester Portrait | XenoCore › Quest | The face on the offer screen. Empty hides the frame |
| Summary | XenoCore › Quest | The paragraph under the requester |
| Objectives | XenoCore › Quest | What must be done (below) |
| Gates | XenoCore › Quest | Conditions before it is offered. All must hold. Empty means always offerable |
| Reward | XenoCore › Quest | Gold, Experience, and Items (one entry per item, so ten of a thing is ten entries) |
| Can Decline | XenoCore › Quest | Untick to show only Accept, and ignore Back |
Each objective:
| Setting | What it does |
|---|---|
| Description | The prose line on the offer screen and in the log |
| Counter Label | The noun in "Name 0/2". Empty uses the item's own name |
| Kind | Collect Item, Defeat Enemy, Talk To NPC or Reach Location |
| Item Target | The item to hold (Collect Item) |
| Enemy Target | The enemy class to defeat (Defeat Enemy). A parent class counts every child. A soft reference: the quest does not load the enemy Blueprint |
| Target Id | Talk To NPC: the placed NPC's Quest Id. Reach Location: a placed XenoCore Quest Location's Location Id. Matched by name |
| Required Count | How many |
| Consume On Turn In | Take the items away on turn-in (Collect Item) |
| Icon Source, Icon Category, Icon Texture | The icon beside this objective on the offer screen, log and tracker |
Each gate is Party Level At Least (the leader's level) or Quest Completed (another quest must be turned in, not just accepted).
Talk and location steps match by name
A quest asset cannot point at an actor placed in a level, so Talk To NPC and Reach Location match the objective's Target Id against an NPC's Quest Id or a Quest Location's Location Id. Saving an objective with an empty Target Id reports an error. See Create a kill, talk or location quest.
Fails silently: a gate that can never pass
A quest whose gate never holds is simply never offered. There is no marker to notice missing,
and nothing is logged. To check, run XenoQuestStatus in the console, or call Get Gate
Requirement Text on Get Party Quests from a Blueprint: it returns the reason as player
text. No shipped screen shows it yet.
Reordering objectives breaks existing saves
Saved counts are stored in objective order. Reorder a shipped quest's Objectives and a player's saved kill counts land on the wrong objective.
The screens' styling and spacing live on their Blueprints' class defaults under XenoCore › Quest: colours for the tracker and reward banner, the banner's duration, and so on.
Extending it¶
- A new quest is a new data asset, not code: duplicate
DA_XenoCore_Quest_Collection01intoContent/MyGame/Quests/, edit it, and set it as the Quest of a placed NPC. See Make a collection quest. - Custom quest flow (extra lines, a cutscene between accept and the first objective) goes in the NPC's On Interact graph around Offer Quest and Turn In. Use Get Lines For with Greeting, Accepted, Declined, In Progress or Turn In to pick the NPC's authored lines.
- Accepting from script (a story quest with no screen): call Accept on Get Party Quests. Leave Giver empty; the tracker then says "Return to the quest giver".
- A custom step with no objective kind of its own (pull a lever, win a minigame): use a Talk To NPC or Reach Location objective and advance it from any Blueprint with Notify Objective on Get Party Quests, passing its Kind and the objective's Target Id.
- A new objective kind needs a programmer: the list of kinds is fixed in C++.
- Reacting to quests from your own Blueprints: listen for
XenoCore.Quest.Message.Changedand re-read the component, orXenoCore.Quest.Message.TurnedInfor the payout. See Events you can listen to.
Console command for checking the ledger: XenoQuestStatus prints every quest the party has touched.
For programmers: classes
Definition UXenoCoreQuestDefinition; ledger UXenoCoreQuestComponent (a controller component
added by the plugin's GameFeatureData, XenoCore.uasset); Offer Quest is the latent node
UXenoCoreOfferQuest; screens derive from UXenoCoreQuestOfferBase, UXenoCoreQuestLogBase,
UXenoCoreQuestTrackerBase and UXenoCoreQuestRewardBase; UI helpers are
UXenoCoreQuestUIStatics. A new objective kind is a value appended to EXenoCoreObjectiveKind
(never reorder it) plus a listener in UXenoCoreQuestComponent for whatever advances it.
Why it's built this way¶
For programmers
- On the controller, not a world subsystem. Quest state must outlive a map and be saved. The controller is also where Lyra and XenoCore keep other party-wide state, such as the bag.
- Keyed by the definition. Same reason the progression component keys characters by their member definition: the NPC may be gone, and the asset outlives everything.
- No "Offered" status. A state that exists only while a screen is up can be stranded by a crash or a level change. The screen's being open is the screen's business.
- Counts recomputed from the bag. A running total drifts the first time anything changes the bag without the message it expects. Re-reading the bag is correct by construction.
- Changed carries no detail; TurnedIn does. Every screen re-reads the component on
Changed, so no copy can go stale. The turn-in payout is the exception: once paid, the gold is part of a running total and cannot be reconstructed by re-reading. - Kind is only a label. If code branched on it, adding a new kind would stop being data.