The quest framework is part of the Progression APIs. The APIs define a quest’s goals, track players’ progress, and grant players rewards when a goal is met.
In Verse, quests can be as short or long as you want. For instance, a quest can include one straightforward objective, a parent quest that includes a series of objectives chained together, or a series of unrelated objectives. The successful completion of a quest results in rewards.
Verse Basics
Before you start coding your quest, make sure to review the following APIs and documentation:
Review the questing system’s different Progression API references, Verse, UnrealEngine, and Fortnite.
Have a fundamental understanding of using Verse. To learn more, see Verse Programming Onboarding Guide.
Every quest example uses a Verse device. To create one, see Create Your Own Device Using Verse.
When implementing features, be sure to follow the Fortnite Developer Rules.
The Quests System Modules
The quests system uses multiple Progression API layers to manage all aspects of a quest. Each Progression module performs a hand shake with another to pass questing data which provides a way for you to create, manage, and track a quest.
Together these APIs create the parameters for how the quest begins and ends.
using { /Verse.org/Progression }- Used to determine how quests are joined, abandoned, activated, and completed.using { /UnrealEngine.com/Progression }- Basic functionality for quest objective and reward classes, player tracking and player quest progression through objectives and quest status.using { /Fortnite.com/Progression }- Functionality that hooks into the Fortnite quest UI.
Progression APIs track the player’s goals with:
Complimentary Modules
The following modules are also used alongside the Progression modules to add and track players throughout the game, and pass player information to the UI.
using { /Verse.org/AgentGroup }- Basic player handling functionality.using { /Verse.org/InputUI }- Basic functionality that maps player inputs to UI.using { /Fortnite.com/UI }- Basic UI functionality.
How the Quests System Works
Quests have three lifecycle states to track whether the quest is
Inactive,Active, orComplete. A quest transitions using guarded calls forActivate(),Deactivate(),Complete(Reason)to prevent quest states from changing prematurely and rewarding players before the quest completes. Events report a transition during the quest:ActivationEvent,DeactivationEvent,ProgressEvent, andCompleteEvent.
The
Activate()function call sets the state of quest toActiveand returns a result and in abasic_questthis is forwarded to its inner objective’s state fromInactivetoActive.The
Deactivate()function returns a quest’s state toInactiveand provides a way to restart the quest and kick players from the quest, causing players to go through the Join workflow again.The
Complete(Reason)function only moves the quest’s state toCompletewhen a valid reason is provided, otherwise the quest fails.A quests state is locked to
Completeonce a quest is completed. Objectives do not have a strict flow so it’s possible to move an objective's state fromCompleteback toActive.
Afterward, the
quest_participant_infois built for eachParticipant(player or even an NPC) by callingJoin(Participant, Info).Each successful join returns one new
quest_membership, memberships accumulate inquest_collection.Queststo determine who is participating in the quest and which quests they are participating in.Participants are added to quests and collections to track the Participant’s progress through the quest, this is a participant-to-quest relationship all quests have.
A quest that doesn’t use the
basic_questclass must have a collection for Verse to compile. A collection holds the core of the quests data for quest classes, it’s where you create your own custom framework for the objectives players need to complete and when players receive rewards for completing those quests and objectives.Collections can be layered and changed. You can change the collection of a quest later on by calling
SetCollection().
Progress through the quest is tracked through the
progress_objectivetype, a subtype of objective. Theprogress_objectivecan be used outside of quests as well. Objectives drive quests by measuring the quest’s work withRequiredProgress, this makes the quest completable.
The function
CanComplete(Reason)checks the attached objective and a completion reason is provided through an outcome using either,succeed_objective_reasonwhich provides a reason the objective succeeded, orfail_objective_reasonwhich provides a reason the objective failed.
A quest’s completion is gated by CanComplete(Reason).
IMPORTANT:
Quests are not persistable, you must build the persistence yourself.
You can nest one quest inside another quest’s objective using
SetObjective(InObjective:objective). For example, players must clear low level enemies from an area before getting a reward they need to defeat the boss in the next level.In that design completing quest A advances quest B. This is a structure you build deliberately. It isn't the default relationship.
Key Concepts
The quests system has five key concepts:
Objective - A completable goal.
Quest - An objective with dynamic participation and terminal completion state. An objective’s state inside a quest isn’t rigid, it can move from Complete to Active, and from Active to Inactive depending on your quest design. However, once a quest is completed, it stays completed.
Membership, participant, and info - A quest_participant joins a quest with a quest_participant_info and receives a quest_membership record. The info says whether the member contributes progress, observes state, and receives rewards.
Basic Quest - Pairs one objective with a list of rewards. A player who completes a certain objective receives the rewards associated with that quest (do X, get Y).
Reward - A stateless payout that a quest grants to its receiving members when it succeeds.
Quest Instancing and Progression Models
Quests have two types of instanced progression models:
One instance per participant - Each player who joined the quest has their own separate instance and independent progress.
One shared instance - When a group of players join one instance, their progression is shared. All players move through the quest together as one.
Progression models dictate how players advance, grow stronger, or unlock new content during a game. The Verse questing system has four different progression models for different types of quest gameplay:
| Model | Instances | How it behaves |
|---|---|---|
Personal | One per player | The player's own actions advance their count. Each player's progress is entirely their own. |
Assisted | One per player | Any joined player's action advances the group’s progress, by fanning the advance out across all joined members. A squad chips away together, yet each member keeps a separate record. |
Shared | One for the group | All Participants are part of a single instance with a single shared progress value. Because there is only one instance, this model is session-only and cannot be persisted per player. |
Co-op | One per player | Players are enrolled in the quest as silent contributors, each differentiated with participant flags,
|
Choosing how many participants share an instance is the essence of the progression model. All player progress lives on the quest instance. An objective's progress belongs to the quest object it sits on, not to any Participant.
Quest Information Sharing
Progression modules share information in a stack formation, each layer knows about the layer below it. The four sharing mechanisms include:
Inheritance — Every quest is an objective, so anything that can display an objective can display a quest. This is achieved using:
basic_quest → quest → objective.
quest_membershipas the shared record — One small class bundlingQuest,Participant, andInfo. It's the data passed between all three modules whenever someone joins, abandons a quest.Events (
listenable) — The main cross-module collaboration. A quest firesJoinEvent;quest_collectionandquest_participantboth expose matching events. The collection and the Participant learn about state changes by subscription. This is how a Fortnite-layer collection tracks Verse-layer quests without either knowing the other's concrete type.Interfaces as contracts — A UI or reward system uses any quest type that tracks what the player has. This is the extension point for your own quest types. This is achieved using:
has_objectivehas_quest_rewardshas_quest_presentationhas_objective_contributors
Quest Classes
The Quest system API has two authorable quest bases:
basic_quest- is a subclass with an inner objective and an array of rewards that distribute when the quest is complete. This is used for quests where rewards are granted automatically upon the quest’s completion. For example a quest where the only objective is to gather a resource or complete one task to get a reward.A player or NPC can join this type of quest. This class belongs to the UnrealEngine layer.
quest- functionality for joining and abandoning an objective. This is used for constructing a custom quest system where you decide which players are quest members, the objectives the players contribute toward, and whether the quest or objective grants a reward.For example, a quest with layers that uses gated quest objectives where only members who have completed a certain task have access to the quest, other players would only be able to observe the quest and not participate in it directly.
The quest’s objective introduces the concepts of participation, joining, and leaving. A creator subclass has literally nothing to display. This class belongs to the Verse layer.
By using hierarchical classes and sub-classes to create relationships (class inheritance) and contracts (interface implementation), the scope of a quest and players’ quest achievements can be codified.
Abstract classes such as objective, quest, basic_quest, quest_participant, quest_reward, quest_category must be subclassed to carry whatever data your game needs.
Any class can read variables modifiers, but only the owning class can write it.
Participants
All quests have Participants who join, this can be a player or an NPC. Participation is a record, not a list. Join stores a membership carrying a copy of the info. Contributors and reward recipients are derived from those records.
A quest_participant class is a stand alone object, not a component. It is the identity a membership is keyed on and a surface for per-participant hooks and events.
The agent_quest_participant class has one field (Agent<public>:agent) and is not <final>, which provides a way for it to be subclassed.
Only code one object per player. Keep one Participant per player for the session and reuse it for every quest.
Once a quest is complete, the Participant or Agent stays enrolled in the quest. This provides a way for participants to view quest toast messages in the HUD. The quest UI is only removed from players who abandon a completed quest.
Objectives
Objectives sit on the Verse layer and are the base of a quest and the smallest unit of trackable work in the Progression API and a general gameplay primitive that works outside quests too: game code can create, activate, and complete an objective with no quest attached. You either subclass an objective, or use a progress_objective class to track the tasks of a quest. A quest can measure objectives as its own progression model, but a quest can also be scripted using Verse concurrency to advance objectives as well and the condition under which it completes.
Objectives can be a device you create. See Create Your Own Device Using Verse for more information.
Objectives have no owner. An objective does not know which quest holds it. One objective may be held by two quests, and only one quest may run the objective at a time. The objective never asks its quest who is playing. When its quest activates, the objective keeps contributor records, and a contributor list from the quest's Join and Abandon hooks.
An objective may report completion more than once. The once-only rule belongs to quest. A quest completes once and refuses new joins afterwards. A Verse abstract type objective never completes on its own, it’s up to the subtypes to determine what completion means to them. The progress_objective class uses its data to determine when an objective is complete. A custom objective reaches Complete only when your code calls Complete.
Objective Progress
The progress_objective class is a subtype objective, it acts as a counter that completes when Progress is greater than RequiredProgress. The progress_objective is usually nested under a basic_quest. Your gameplay code in SetProgress writes to Progress, a field in progress_objective.
Progress Objective Example
The example code below shows how to use the progress_objective to track progress outside of a quest. Using a Button device to own the progress_objective provides a way to advance progress and read the objective’s state. The only determination that the objective is complete is that Progress >= RequiredProgress.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Progression }
using { /UnrealEngine.com/Progression }
# objective_state is an open enum, so the case needs a default.
StateName(State:objective_state):string =
case (State):
objective_state.Inactive => "Inactive"
objective_state.Active => "Active"
The device calls Activate, then SetProgress on each button press, and Deactivate from a second button. At completion of the objective, OnBegin prints Inactive, calls Activate, and prints Active.
Each button press calls SetProgress with one more, and ProgressEvent prints the X / Y progress message because progress_objective overrides the GetProgress() function to display its progress in that format. Another objective may override it and display it differently.
This example works with quests that use objective variables to track quest objectives through the progress_objective class. For example, a quest that tracks and counts collectibles, resources, or in-world money.
When Progress >= RequiredProgress the objective completes for a succeed_objective_reason.
The CompleteEvent reads State as Complete and prints the success line. The stop button calls Deactivate, which returns an error if the objective is not running.
A commanded Complete() also succeeds at any progress, but it does not advance Progress. Call SetProgress(RequiredProgress) first if the displayed X / Y must agree.
Custom Objectives
Custom objectives use the SignalProgressEvent():void function to track progress. After the custom objective's own state changes, call SignalProgressEvent to complete the owning quest if it now succeeds.
Custom Objective Example
The example code below shows how to build a custom_objective that runs without a quest. The summit_objective counts checkpoints, refuses a second player through CanActivate, and gates completion through CanActivate and CanComplete until every checkpoint is reached. The objective count can be reset using OnActivate, and OnDeactivate logs it. Each step of the custom objective calls the SignalProgressEvent.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Progression }
using { /UnrealEngine.com/Progression }
# objective_state is an open enum, so the case needs a default.
StateName(State:objective_state):string =
case (State):
objective_state.Inactive => "Inactive"
objective_state.Active => "Active"
Objective States and Transitions
An objective owns a stored State and transitions through Activate, Deactivate, and Complete. State is declared as a variable, var State:objective_state = objective_state.Inactive. You read it, and the framework writes it. A CompleteEvent subscriber reads Complete once the framework signals after writing its State variable.
In your own quest, states could determine objective progression, such as:
Inactive- not startedActive- started and in progressComplete- finished
The framework writes the objective’s State before it signals, so a CompleteEvent subscriber reads Complete. An objective’s State falls back from Complete when a progress_objective drops below its target, or when a custom objective is reactivated.
Quests
Quests sit on the Verse layer and build on objective classes rather than the other way around. A playable quest is built on the objective class and inherits the whole objective lifecycle (activate, deactivate, complete) and adds four things:
Membership is set through
JoinandAbandonwith guards and hooks.Events for joins and abandons.
A completion latch so a quest completes only once.
Players join and leave a quest while it runs, and once a quest completes it stays complete.
Quests can be a device you create. See Create Your Own Device Using Verse for more information.
Quest
A quest class can belong to only one quest_collection, the class inherits the objective’s State, Can guards, <final> drivers, On hooks for each transition, and events for Activate, Deactivate, Progress, and Complete. The class uses Collection, Join, Abandon, and their events.
In this framework, the objective can complete many times, but the quest itself can only complete once.
The class sets Objective and Rewards if there are any. Quests use has_quest_presentation, has_description, and has_icon for messages in the UI to inform players when a completed objective pays out a reward.
Scripted Quest Example
In the code example below the quest class uses has_quest_rewards to pay out its own rewards, unlike a basic_quest that automatically grants rewards when a success reason is supplied.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Assets }
using { /Verse.org/Progression }
using { /UnrealEngine.com/Progression }
EscortProgress<localizes>:message = "Escort the cart to the base"
EscortRewardName<localizes>:message = "Escort points"
EscortRewardDescription<localizes>:message = "Scoreboard points for every escort."
This example can easily be expanded or changed by:
Replacing the three localized messages and the icon.
Passing
Runyour own arrival and loss events and adding aSleepbranch to the race for a time limit.Replacing
escort_rewardwith your own reward types, or add more to the Rewards array.Override the quest's
CanReceiveto pay only some members.
Basic Quest
Quest systems are built using a basic_quest subclass. A basic_quest class determines that players must do X, to get Y. Basic quests have only one inner objective, to create multiple objectives inside a basic_quest, use a nested objective type that supports nesting in the way you need for your quest. For example, subclass the progress_objective for goals that involve counting, or subclass an objective directly for any other goal you want to achieve.
By subclassing objective you supply the necessary mechanisms for:
Tracking the objective’s progress through
GetProgress().Gating what players can access during the quest using CanActivate, CanDeactivate, and CanComplete gates.
Determining what happens next in the quest using the OnActivate, OnDeactivate, and OnComplete hooks.
Before a player is added to the quest, your code first constructs the objective then the quest by overriding the quest definition when setting up a class by using the <override> type. To move the basic_quest into a completed state a succeed_objective_reason is provided, then the quest’s State becomes Complete.
Once a quest is completed, rewards are granted to players based on their membership, Membership.Participant is cast to agent_quest_participant to reach the agent. The GrantRewards(Memberships) function runs the three gates and delivers through each reward's OnGrant(Memberships), returning a map that details which membership received which reward.
Members only receive their reward upon the successful completion of an objective.
Quest Collection
The quest_collection is the core data structure of the Quest API. It works as the framework’s participation container by tracking all active quest memberships for the session:
Which quests exist.
Which participants have joined each quest.
Each participant’s
quest_participant_info(Contributes/Observes/Receives).
Every single quest lives inside the quest_collection, and projects aren’t limited to a single quest_collection either, a collection is an ordinary object that gets instantiated as needed. The quest_collection gates player activity as well, such as Join and Abandon. This way you can organize quests across collections in whatever way suits your project.
A common split is one collection backing a player-facing quest board and a separate, world-scoped collection holding session-wide world quests.
A quest_collection also keeps two maps that are the practical query API:
var <private> Quests<public>:[quest][]quest_membership
var <private> Participants<public>:[quest_participant][]quest_membership
A bidirectional index:
quest → who's on it
player → what they're on.
Both are readable but writable only inside the collection.
Participants and Membership
A Participant and Membership generate information. A quest_participant, or agent_quest_participant, joins a quest with a quest_participant_info and receives a quest_membership record. The information gathered determines whether the member contributes, observes, and receives rewards. The participants are generated by your code, just like the quest_collection, but the quest_membership is generated in the quest framework.
A quest is only a template until something joins it. Joining creates the live instance that the framework tracks. Two objects appear at join time:
A participant — the code you built that joins a participant to the quest. Most often this refers to a player, but a participant can also be an NPC. Keep one participant per agent, created when the agent registers.
A membership — the record that a participant has joined a particular quest. The quest_collection holds every active membership: which quests exist, which participants joined each, and each participant's relationship to the quest.
Use the Observes flag to see the quest in the Quests UI for players who are only observing the quest. This means if an NPC is hired and it joins the quest, the flag provides a way to see the NPC’s progression and for the player to earn rewards from the NPC as well.
Contributor
Once a player becomes a Participant, their basic_quest progression is tracked through has_objective_contributors. This adds and removes a Contributor from a quest and logs the contributor as an Agent. A contributor is an agent whose gameplay counts toward an objective. An objective that implements has_objective_contributors owns its contributor list.
The basic_quest class uses logic in the class template to write and record player progress and contributor changes to its objective in three places:
On join, when the membership's info has Contributes set.
On abandon, gated on the same flag, because a shared objective may hold the same agent for another quest.
When
quest_participant_info.SetContributeschanges the flag, and whenSetObjectivemoves contributors to a new objective.
A regular quest uses these interfaces in any way you design in your quest logic.
Quest Participant Info
Every participant joins with a quest_participant_info:
quest_participant_info := class(member_info_interface):
var Contributes<public>:logic
var Observes<public>:logic
var Receives<public>:logic
The quest_participant_info class says how a member relates to a quest. Even a blank quest_participant_info{} is a full Participant. You can set flags on this class at Join or after Join.
Participants are passed in at join time — Join(Participant, Info) — and stored on that player's quest_membership. So one quest can have many participants, each with a different combination. They're independent, not a hierarchy. Any combination works.
A player’s quest_participant_info flags three things:
Contributes- Whether the player’s actions advance the objective.Observes- Whether the player sees the quest in their UI.Receives- Whether the player gets the reward.
After the Contributes flag is flipped, you can change a player’s membership and promote them from spectator to active contributor without them having to rejoin the quest.
Quest Participant Example
In the following example, players join a quest as part of a squad with pre-determined roles (Leader or Commander). Players have one Participant and accompanying Info each to track their progress.
Players Join the quest and are added to the Quest where they receive Membership and the quest collection.
using { /UnrealEngine.com/Progression }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Progression }
using { /Verse.org/Simulation }
# A role that a quest's guards can read with squad_leader_info[Membership.Info].
squad_leader_info := class(quest_participant_info){}
squad_manager := class:
Membership
There can only be one membership per member identity, not per participant object. An agent’s participant’s identity is its agent, so a second participant over the same player is refused.
The quest_membership class is the record binding one participant to one quest. It has three immutable fields: Quest, Participant, and Info. Info is the framework's live copy of the participation.
A player can only have one membership on a given quest, this prevents players from having more than one instance on the quest they have joined. Two participant objects for the same Agent count as one member, and the second join is refused. Membership and a player Participant are managed through the quest_participant class by checking on a player’s quest state:
OnJoinOnAbandonOnComplete
Membership is necessary for granting rewards from the basic_quest. The entire reward chain takes or returns memberships because a membership is the only object that carries the three things the reward pipeline uses to grant rewards: Info, Participant, and Quest.
Joining and Abandoning Quests
All functions receive the membership’s copy of Info, not the quest_membership template that was passed.
A hook that abandons the joiner does not fail the join. Join still returns the membership it made, the remaining join hooks are skipped, and the JoinEvents still fire after that hook's Abandon events.
Join(Participant, Info) «final» — sealed
├─ CanJoin(Participant, Info) refuses → returns the error, stops here
├─ quest.OnJoin(Participant, Info) basic_quest overrides this
├─ quest_participant.OnJoin(Quest, Info) participant-side hook
├─ quest_collection.OnJoin(Membership) collection-side hook
├─ membership pruned from Quests / Participants [inferred]
└─ JoinEvent fires at all three levels payload: quest_membership
Joining Quests
Joining a player to a quest is done in stages. When a quest begins, the framework:
Determines whether the quest has already completed, and turns away new
Participants if it is complete.Runs the quest’s
CanJoin(Participant, Info)and acts as a gate to the quest. You create custom rules that determine when aParticipantcan join a quest.Creates a membership to record player activity using
quest_membership. The Info gathered throughquest_membershipis treated as a template to store a copy of the membership. You can change theInfolater since it’s a reference.Runs
OnJoin(Participant, Info), then the participant’s joining functionOnJoin(Quest, Info), then the collection functionOnJoin(Membership). Each later hook runs only if the membership is still held.Triggers the
JoinEventon the quest, then the participant event, then the collection event. Delivered when the transaction commits.Returns
Joinon the membership.
Abandoning a Quest
Abandoned players drop out automatically. Abandoning a quest is allowed at any time during quest progression or on a completed quest. On a basic_quest, a member who abandons before the reward payout forfeits their rewards.
The Abandon function tracks whether a player has abandoned the quest by checking a Participant’s quest_collection for the participation info and then proceeding with the un-enrollment process.
Abandon has a number of steps it runs through before removing a player from the quest:
Step 1: Resolve the Participant
The quest_participant class can abandon a quest, but you have to build this ability using:
for (Membership : Collection.Quests[Quest]):
if (AgentParticipant := agent_quest_participant[Membership.Participant]):
if (AgentParticipant.Agent = TargetAgent):
Quest.Abandon(Membership.Participant)
Step 2: Flip the Gate
You have to override the refusal to remove a quest_participant using:
CanAbandon(Participant:quest_participant, Info:quest_participant_info)<reads>
:result(void, []quest_error)
It returns a result, so a refusal carries a reason rather than just failing. Compare Complete(Reason:complete_objective_reason) for the Abandon outcome. You have to set a flag on your device before calling Abandon() and read it inside CanAbandon.
Step 3: Running Abandon() Internally
There is asymmetry in the Abandon function. CanAbandon needs both the participant and the Info, but Abandon only receives the participant. So Abandon must resolve the Info itself from the membership in the collection.
Abandon(Participant) «final» — sealed
├─ CanAbandon(Participant, Info) refuses → returns the error, stops here
├─ quest.OnAbandon(Participant, Info) basic_quest overrides this
├─ quest_participant.OnAbandon(Quest, Info) participant-side hook
├─ quest_collection.OnAbandon(Membership) collection-side hook
├─ membership pruned from Quests / Participants [inferred]
└─ AbandonEvent fires at all three levels payload: quest_membership
Step 4: Cleanup Contributors
Subscribe to an AbandonEvent and do all the cleanup there, so voluntary and code-initiated removal share one cleanup path.
RemoveContributor(Agent)— if your objective implementshas_objective_contributors, abandoning does not touch that array. Skip this and the removed player keeps advancing progress.Your own per-agent tracking — contribution totals, UI state, any
[agent]… map you maintain.Cached membership lists — if you stored memberships from Join rather than reading the collection at grant time, prune them here or you'll reward someone who was kicked.
Complete Quest
A quest does not add a completion call. Completion means the goal ended, not that the completed objective succeeded. Every completion carries a reason that says how it ended, and the reason travels through the completion workflow in a specific order:
CompleteEventCanCompleteOnCompleteUp to the owning quest
How you order the completion workflow matters. A Verse objective has no completion rule the framework can read.
Every completion carries the following classes; complete_objective_reason{}, either succeed_objective_reason{} or fail_objective_reason{}. Completion is the inherited objective that uses the listed classes as a readable class to provide reasons for completion outcomes. Completion says the quest ended, the reason says how, and every completion hook and event carries it.
Calling Complete on an Inactive objective still completes it. A quest synchronizes its Complete state to the parameters defined by the complete_objective_reason{} class. You can override CanComplete, but you cannot bypass it.
When a quest completes, its OnComplete(Reason) records the outcome. This could record that a player has the correct amount of objects, resources, or money the quest required to complete the objective. This marks the quest Completed, then runs the collection's completion workflow:
For each membership, the
Participant'sOnComplete(Quest, Info, Reason)hook runs and itsCompleteEventis registered.The collection's
OnComplete(Quest, Reason)hook runs and itsCompleteEventis registered. This happens even when no one has joined the quest.The reason recorded could include the results from a variable counting a player’s collection of objects, or a flag that has been triggered moving the player’s state from
ActivetoComplete.
The quest's own CompleteEvent, inherited from objective, is registered before OnComplete runs. All CompleteEvents are delivered at commit. On a basic_quest, rewards are paid after this workflow, so a Participant that abandons from its OnComplete hook gets nothing.
An override of OnComplete that does not chain (super:)OnComplete(Reason) causes the quest to never complete.
Solo Quest Example
In the example below, participation flags use quest_participants in a per-player instance. All quest progress lives on the instance, the basic_quest class joins individual players to the quest in their own instance and gets the solo_quest where they’re the only quest member.
A Button device is used to count the player’s progress. A button press advances the instance of the player that pressed the button. The Button device’s OnPressed function maps an agent to the quest. When the device’s InteractedWithEvent maps the player’s (agent) to the instance, it casts Objective to the progress_objective class and calls SetProgress.
At the third button press the objective and the quest are completed, and the CompleteEvent fires for that player’s instance.
Players can abandon the quest and forfeit their rewards. This causes the device to drop their map entry.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Progression }
using { /Fortnite.com/Teams }
using { /Verse.org/Simulation }
using { /Verse.org/Assets }
using { /Verse.org/Progression }
using { /UnrealEngine.com/Progression }
SoloQuestName<localizes>:message = "Press the button three times"
SoloQuestDescription<localizes>:message = "Every player counts their own presses."
You can modify the code above by:
Swapping the
button_devicefor any device event that sends anagent.Changing the
RequiredProgressand the per-event increment inOnPressed.Adding rewards with a class-level
var Rewards<override>onsolo_quest.
You can make this a team quest by changing the instance to shared and using the Contribute and Contributors check on the quest Membership.
Quest Rewards
Rewards pay once, on success. All rewards are empty by default, and a quest without rewards completes normally and pays nothing. In the questing framework a reward is a unit of payout that looks for reward candidates then decides whether a reward can be handed out and to whom, then delivers the reward in the OnGrant function.
A quest’s Rewards are <protected> for writing and <public> for reading. You populate Rewards inside your subclass, and outside code can only look.
Rewards have two entry points that use the <final> type on the quest level and the reward level:
Quest Level
has_quest_rewards:
GrantRewards<final>(Memberships:[]quest_membership)<transacts>
:result([quest_reward][]quest_membership, []quest_reward_error)
Reward Level
quest_reward:
Grant<final>(Memberships:[]quest_membership)<transacts>
:result([]quest_membership, []quest_reward_error)
Rewards are gated at the quest and reward levels as well. The reward and quest can veto rewards independently so a quest can withhold a reward its own reward object would normally grant. This means that if a player fails to complete the objective, they don’t receive the quest reward. All rewards return result(void, []quest_reward_error)with a reason for failure.
At this point the player’s state could move from Active to Inactive, causing the player to rejoin the quest and try to successfully complete the quest a second time.
Granting Pipeline
The quest_reward class contains the whole reward. It asks whether the reward can be granted at all at this point in the quest. The grant runs on a succeeding completion only. OnComplete calls GrantRewards over the quest's live memberships after the quest has settled as completed. Only memberships whose Info.Receives is true are candidates for rewards.
The CanReceive(Membership,Reward) function on the quest can drop a member from one reward. This function asks whether specific players should get this reward.
A basic_quest grants rewards only when its completion reason is a succeed_objective_reason. A quest reward is the "get y" half of a basic_quest. The quest carries a list of quest_reward objects through the has_quest_rewards interface. The interface gates rewards at the quest level by asking if this quest permits this membership to receive this reward.
When the quest completes successfully, the interface traverses the quest_reward list and grants each reward to the quest's members who are allowed to receive it.
Failed Rewards
A failed payout does not undo the completion. A runtime error or error result from GrantRewards is logged as a warning, and the quest stays completed.
Abandoning before the payout forfeits the reward. Abandon is allowed on a completed quest.
Quest Failure
The engine has no failure state for quests. A quest is only ever joined or completed. The objective_state uses objective_state.Complete to handle completed objectives and failure. The only thing that separates winning from losing is the reason passed when the quest ends. Failure is expressed through:
complete_objective_reason := class<abstract><castable>:- The reason provided for the completion of the objective.succeed_objective_reason := class(complete_objective_reason):- The reason the quest succeeded.fail_objective_reason := class(complete_objective_reason):- The reason the quest failed.
Either way, player feedback is separate from quest state. Because a quest cannot show a Failed badge, any miss message has to come from your own UI. The CanComplete method receives the reason providing a way for you to apply different rules to winning and losing. The common pattern:
Refuse
succeeduntil the attached objective has actually hit its target.Always allow
fail— a player can lose at any time.
The basic_quest class overrides CanComplete(Reason), so the real choice is whether a failed attempt should be re-attemptable (abandon) or recorded as a done-but-unrewarded attempt (complete without reward). If you want players to have the choice to try again, you must construct a new quest instance — you can't revive the old one.
State alone can't tell you what happened. A CanJoin gate checking State <> objective_state.Complete blocks rejoining after a loss exactly as it does after a win. If you need to know the outcome later, store the reason when CompleteEvent fires.
Failure is all-or-nothing across participants. One player can't fail out while the others continue — the quest ends for everyone. Per-player failure needs per-player quest instances, the same constraint that applies to per-player progress.
Quest Notifications
A quest opts into notifications by implementing has_quest_presentation. Once opted in, the default behavior raises a notification for every progress or state change. That is fine for a short quest, but it buries the player once a quest has a large target or advances on a timer — a row that ticks once per second for three minutes would otherwise raise around 180 notifications.
ShowNotification is a var on has_quest_presentation, so the default is controllable. There are three approaches, in rising order of effort:
Leave it
true— correct for a short quest.Set it
falseas a class field — for a background tracker or hidden prerequisite that should never interrupt.Flip it at run time — for a quest where only some moments deserve a notification, such as a grant notification and a completion notification with silence in between.
Timing matters. The flag is not read at the moment you set it. The quest log does not pick it up until the next time it refreshes, so the flag must already hold its final value before the call that changes progress. Setting it afterward means the notification reflects a stale value.
Test the completing increment as NewProgress >= RequiredProgress, computed ahead of the call. Do not guess by flipping the flag near the end, because that guess breaks as soon as an increment is larger than 1 or the target is rescaled at run time with SetObjective. A quest also cannot flip its own flag on completion, so whatever advances the quest's progress is what has to own its notification management.
Questing System Scope and Caveats
No built-in cross-session persistence. The framework does not persist quest progress across sessions. A shared quest is session-only, and saving anything for a returning player is your work, not part of the Quest API.
See Short Coin Quest for an example of implementing persistence in a quest.