Quests consist of objectives - nodes connected with edges (transitions).
Quests and objectives have their names through which they a referenced. Quest works as a sort of namespaces for their Objectives. Therefore each objective is referred to via the parent quest name and the objective name (Example. LUA script QuestSystem.StartObjective(“q_quest1”,”objective1”) attempts to start the objective1 of quest q_quest1). Objective name must be unique within the scope of its namespace.
Flow Graph quest can have a Quest Smart Object (QSO) assigned to it. This QSO must carry the same name as the FG quest and will automatically receive messaged about quest and objective states changes.
A quest can go through these states:
Additional state:
Valid quest state transitions and related LUA functions and Expression Evaluator functions are described in the following image:
Quest must have at least one visible objective to appear in quest log.
Objectives can go through these states:
Valid state transitions for objectives are described in the following image:
Objectives are frequently used as persistent 5-state global variables in script.
Objective is completed automatically if all of the following conditions are met:
Otherwise Objective can be set manually (via lua, dedicated MBT tree node, or objective action in Skald) to any state.
Condition on a started Objective is evaluated each time any objective changes its state.
An Objective may be connected via edge to another Objective. If this connection is simple (obj1 -> obj2) the next objective is automatically started. If there are more preceding objectives the following objective is started when ALL preceding objectives are completed. If the preceding objectives are set to IsExclusive, the following objective is started when at least one preceding objective is complete. All the other exclusive objectives are automatically cancelled.
Objectives can be set to IsHidden TRUE or FALSE. Hidden objective nodes have grey color and are hidden in the game’s UI/HUD.
Visible objectives are blue and can be shown in game’s UI/HUD and carry compass/map markers. They must be linked to an objective element in Skald, which carries all the UI data (name, update messages, logs). Otherwise they are functionally the same.
Note: It is strongly advised against using a visible objective as carrier of visual information/guidance for the player and as a controlling variable in game script at the same time. More often than not you want to control the script with a hidden objective carrying the game-state information and properly update a separate visible objective as the design requires it. Splitting a fully functional but insufficient control-visible objective into a hidden controlling one and a visible one is often a time-consuming and bugs-introducing procedure
Objectives can track some state of various game assets or some game events. One or more Asset nodes must be properly set up and plugged to Objective port Tacked via interface node AssetTracked (commonly referred to as “tracker”). If more objectives need to track the same asset, additional tracker node is required for each objective. Tracking is active when the objective is started.
You can track these states and events:
Tracked assets can also have quest markers assigned to them. This is the only way to create quest markers in the game.
(Blue markers are of type side-quest marker, blue A is additionally an area marker. Red-yellow A marker is of type main-quest marker and is also an area marker)
Tracking system also allows a light-weight version where the asset has a marker but its state is not tracked. This saves some performance demands. To do that, simply plug the Asset node (it must have a specified entity, not a whole class of them!!) into MapAsset port of the tracker.
Notes:
-Use markers on items with caution. A marker on AssetItem disappears when the item is in any inventory (stash, NPC inventory)
-Markers cannot appear if the asset entity is not present in level at the time of tracker initialization. A tracker is re-initialized on save load so it may reappear that way.
-There is a 1:1 parity between an objective and a marker. An objective cannot have more that one marker and cannot be dynamically changed.
-Since markers are provided via the tracking system a marker is automatically turned on when the objective is started, and turned off when it’s cancelled or completed.
Timer and Autocomplete properties of the node Objective are the only persistent timers in game (beside the imprecise Timekeeper utility MBT tree). Timer defines when the started objective auto-fails. Autocomplete defines when the started objective can first attempt auto-completion (see Automatic Objective completion). The Quest Smart Object, if defined, receives objective state update message. This is frequently used for accurate and save/load persistent reactions to timed events.
The timers accept timer definition in the following format:
<IntegerNumber> <TimeUnit>#<TimeType>
Where:
TimeUnit = ms, s, m, h, d... for miliseconds, seconds, hours, days respectively
TimeType = WT, GT... for WorldTime and GameTime respectively
Examples:
30m#GT = 30 minutes of real-world time. Pausing the game pauses this time as well.
30m#WT = 30 minutes game-world time. Since 1 hour takes ca 4 minutes, this means the timer runs out in 2 minutes real-world time. Several systems may pause this time (open inventory, dialogs etc.) or it may be paused for a whole mission or its part in script (e.g. the whole Skalitz intro is paused).
5s#GT = The timer runs out within 5 seconds.
5s#WT = The timer runs out almost instantaneously. Basically in the next AI tick.
7d#GT = You probably wanted to use WT. You would have to play the game for 7 full days, that is 168 hours or real-world time. Most people play the game for 80-120 hours. They will never see the end of your timer. Yes, we had this typo in one quest.
Autosaves are also handled by objectives. Autosave points are defined in Skald objective elements. If a Skald objective element (with autosave setting) is linked with a Quest System Objective, a save request is created when the objective is completed. Save request may be rarely discarded for various reasons (see Save/Load documentation).
Quest named q_quest is a side quest and sends state update messages to QSO q_quest. When the quest is started objectives startObj1 and startObj2 are started. Objective startObj2 automatically completes itself immediately (condition=1). Both objectives are hidden and will not update the game UI/HUD.
Objective obj2 is automatically started when the obj1 is completed. Objectives are visible and capable of updating the game UI/HUD.
Objectives excl1 and excl2 are mutually exclusive. If one of them is completed, the other is auto-cancelled, and the objective reaction1 is started.
Objective didABC is started only when all the other objectives doA, doB, doC are completed. Objective doABC also auto-completes itself immediately after being started. This will also complete the quest.
Objective obj1 tracks the death of aus_bailiff. NPC aus_innkeeper must land the killing blow. Marker is on NPC aus_bailiff. Once that happens obj1 completes itself.
Objective obj1 tracks if the player entered area entity named area1 (The user, if undefined, is implicitly the player). Marker is on area1 and takes the form of an area marker. The objective completes itself if the player is inside AND the objective canEnterArea in quest1 is in completed state.
Objective findCamp tracks nothing. It only shows an area marker on the area entity named areaMarker1. The objective never completes itself (empty Condition = FALSE). It must be completed manually in MBT tree, through objective action in Skald, or via any LUA script.
Objective obj1 is tracking if player has at least 5 apples with quality over 50%. No marker. The objective completes itself when this condition is met AND if 1 hour of World Time (=4 real minutes) has elapsed since obj1 was started.
Objective named fired5Arrows tracks if the player fired at least 5 arrows. The objective is hidden. It completes itself when this condition is met.
An example of different objectives tracking the same asset (NPC mon_cellarius) for different reasons. Objective talkToNPC doesn’t track anything. It has a marker on the NPC and is visible. Hidden objective isNpcDead tracks if the NPC has died. If it’s dead the objective auto-completes. Hidden objective isNpcInArea never auto-completes. It tracks if the NPC entered the area entity named area1.
Mandatory. One instance only. Created automatically.
Behavior: Sends signal along the Out edges when the quest is Started.
Ports | |
---|---|
Name [string] | Defines name of the quest |
SmartObject [string] | Defines QSO which receives all quest-state and objective-state update messages |
Type [enum] | Defines which graphical set of icons will be used in journal and on map for quest markers. Use “side” or “main”. |
Counter [int] | How many times can you repeat this quest? -1 means repeatable infinite times. |
IsActivated [bool] | DON’T USE. A known bug prevents the initial state to be sent to the QSO. Leave set at 0. Defines if the quest is Activated from the start. This is better controlled from centralized brains (q_master_main) or from dialog exit script. |
EdgesEnabled [bool] | DON’T USE |
Mandatory. One instance only. Created automatically.
Behavior: If any flowgraph signal reaches this node the quest is set to Completed (if Started). This is the only way to complete quests.
Ports | |
---|---|
In [signal] | Accepts the signal for quest completion |
Objectives serve as persistent 5-state global variables, as the means to update the game UI/HUD, as on of the means of giving the player quest rewards, and as the only means to control auto-saves.
Ports | |
---|---|
In [signal] | Receives signals from Out ports of Begin and Objective nodes. Upon receiving the signal the Objective is set to Started if it was Unchanged or Canceled (see valid state transitions) |
Reward [Reward node] | All Reward nodes are plugged here. Can take any number of rewards at a time. |
Tracked [AssetTracked node] | Input for all asset tracking logic. Accepts only AssetTracked node. No more than one AssetTracked per one Objective is allowed. Has a say in auto-completion of the objective. |
Name [string] | Name of the objective. Used in all references to this objective (in combination with quest name) |
Exp [int] | DON’T USE. Obsolete function. |
IsHidden [bool] | 0 = objective will be listed in journal. 1 = objective is hidden but may still show state-update UI messages (set in Skald) |
IsExclusive [bool] | Allows you to auto-cancel this objective if another sibling exclusive objective is completed. |
Timer [string, specific format] | Objective cancels itself after the timer runs out. Accepts timer definition in specific format. |
Condition [string, specific format] | Accepts conditions in Expression Evaluator format (same as dialog entry conditions in Skald). Objective CANNOT be auto-completed until the conditions is evaluated as TRUE (1 is always true). |
Autocomplete [string, specific format] | The objective may attempt auto-completion after the set timer runs out. Accepts timer definition in specific format. |
Serves as the interface for asset tracking and defining tracking conditions. Plug an edge from Out out-port into “Tracked” in-port of an Objective node. Unplugged AssetTracked node is taken as an orphaned one.
Behavior: Is only active when the objective is started. Updates its state in continually.
Ports | |
---|---|
Asset [Asset node] | Accepts any Asset node. Defines the Asset that is being tracked. |
MapAsset [Asset node] | Defines an asset which is only meant to have a quest marker assigned and is not meant to be tracked. |
TrackCnt [int] | Track Count. Defines how many times the tracked action on the tracked Asset must be performed for the tracking to be considered “completed”. |
OnMap [bool] | If set to 1, the Asset or MapAsset is assigned a quest marker. |
User [Asset node] | Defines a soul which plays a role in the tracking. Player is the implicit default, i.e. you don’t have to plug player (soul dude) in. |
Duplicates results in error.
Behavior: Serves as input for AssetTracked node.
There are 4 ways to define the item you want to track. These options are mutually exclusive. You cannot track any specific instance of an item object (e.g. specific apple). If you wish to track such item it must be the only instance of its kind. So, for an apple you’d have to create a duplicate definition of an apple in the DB and place one in the level or some inventory. Quest item typically have only one instance of themselves in the game.
Ports | |
---|---|
Item id | Defines the kind of object to be tracked (as defined in DB item tables). Any instance of it - e.g. any apple, or any “Piercer” sword. |
Category | Defines a whole category of items to be tracked. E.g. any body armor. |
Type | Defines a whole type of items to be tracked. E.g. any chainmail body armor. |
Subtype | Defines a whole subtype of items to be tracked. E.g. any cuman helmets. You can define your own subtype for any non-food item. |
MinHelath [float] | Range [0-1]. Defines the minimal quality of the item for it to be counted. Any items in worse condition will not be counted towards the required TrackCnt value. |
Duplicates results in error.
Behavior: Serves as input for AssetTracked node.
Holds reference to an NPC or a group of NPCs.
If plugged into Asset in-port of AssetTracked, the tracker will track NPC’s death.
If plugged into Asset in-port of AssetTracked, this NPC will be marked by a quest marker.
If plugged into User in-port of AssetTracked, this NPC must carry out the action (e.g. enter an area, kill an Asset NPC, pick up the Asset Item)
There are 3 mutually exclusive ways to define an NPC to be tracked.
Ports | |
---|---|
IsVIP [bool] | If set to TRUE the NPC defined by Soul id will be revived when the quest starts. This VIP property is different from VIP status in DB table soul. |
Soul id | Defines a specific soul |
SoulClass | Defines a whole soul class to be tracked |
SoulFaction | Defines a whole faction to be tracked |
Duplicates result in error.
Behavior: Serves as input for AssetTracked node.
Holds reference to various level entities – typically areas and tagpoints.
If it’s an area and if plugged into Asset in-port of AssetTracked, the state of the User entering the area is being tracked.
Ports | |
---|---|
EntityName [string] | Reference to the entity by entity name, as it is set in the level. |
UIMap [string] | DON’T USE. Obsolte function |
ShowAsArea
If the entity is an area, and if OnMap in AssetTracked is TRUE, the marker will be on the area and will take the form of area marker.
Note: Don’t change the name of the entity after it’s used in the FG. You may break your tracker.
Determines the statistic to be tracked. Statistics are defined in DB table “statistic”. Examples: number of arrows shot, number of times you got drunk etc.
Behavior: Serves as input for AssetTracked node.
All reward nodes have one property in common: Immediately [bool]
If set to true, the reward is granted at the moment the objective is completed.
If set to false, the reward is granted when the quest is completed, but only if the objective is completed state.
Many reward nodes contain value definition in form of a predefined enum. This is done to facilitate balancing. You will find the actual values behind those enums in various DB tables.
Rewards the player with an achievement. Achievement are predefined in code and are platform independent (Steam, PS, XBox).
Rewards the player with XP in selected skill or stat.
Exp [enum] | Predefined XP amount |
RPG | Stat to receive the XP |
Exact values behind the Exp enums are defined in DB table exp_change
Rewards the player with an item.
Item | The item to be received |
Count [int] | |
IsExclusive [bool] | If more rewards, with this value set to TRUE, are plugged into one objective the player will be prompted to choose one of these rewards. |
Use only if you don’t mind the item is created out of thin air. To move existing items between inventories, use MBT tree script or LUA exit script in dialogs.
Rewards the player with money.
Count [enum] | Predefined amount of money |
IsExclusive [bool] | See RewardItem |
Rewards selected soul with a perk
SoulId | Soul to receive the perk |
PerkId | The perk to be received |
UnlockOnly [bool] | If set to TRUE the perk is only made available for selection in character sheet. Additional perks points are not granted. If set to FALSE the perk is received regardless of the number of achievement points available. |
Receive reputation reward or reputation hit towards specified faction or a soul. Soul and faction settings are mutually exclusive.
Reputation [enum] | Predefined change of reputation value. See additional info below this table. |
SoulId | Defines the soul towards wish your reputation should change. Don’t use Faction if you use this. |
Faction [enum] | Defines the whole faction towards which your reputation should change |
Actual values of Reputation change enums are defined in DB table “reputation_change”
If you reward faction reputation change, use only enums named “quest_rewardFaction*” or “quest_decreaseFaction*”.
If you reward individual reputation change, use only enums named “quest_reward*” or “quest_decrease*”. These enums are also used in Skald reputation changes. Skald is incapable of affecting faction reputation.
Runs a custom LUA script as a reward.
Debugging in Flow Graph is described in its documentation
Console Cvar
wh_quest_debugquest <questName>
allows you to see the state of the entire quest in simplified manner.
Grey = unchanged/reset objective
Yellow = started objective
Blue = objective in StartedTrackingDone
Green = completed objective
Quest state is described at the top