The following is a guide to creating your own dialog-driven quest.
By dialog-driven, I mean that most (but not all) of the events in the quest occur from talking to NPCs.
Dialogues can be used without having any associated quests at all, likewise you can create a quest without having any dialog, but you will find it much easier if you use dialogue to drive a narrative forward.
The first section of this guide covers dialogues.
Dialogs are specified using a JSON syntax, we'll have a simple example first, followed by a general explanation of all the options:
{ "location" : "friendlypeep", "rules": [ { "match" : ["*"], "pre" : [["token", "seenbefore", "yes"], ["item", "money", "100"]], "post": [], "msg" : ["Hello again $you, you are rich."] },{ "match" : ["*"], "pre" : [["token", "seenbefore", "yes"]], "post": [], "msg" : ["Hello again $you."] },{ "include" : ["filewithmoredialog1.msg"] },{ "match" : ["*"], "pre" : [], "post": [["settoken", "seenbefore", "yes"]], "msg" : ["Hello I'm $me."] },{ "include" : ["filewithmoredialog2.msg", "filewithmoredialog3.msg"] } ]}
Each potential line of dialog forms a 'rule' these rules are checked in sequence, from the top of the file down. Note that because of this, the most restrictive rule (with the most pre conditions) should go first. In the example above, the rules in filewithmoredialog2.msg and filewithmoredialog3.msg can never be matched because there is a rule which matches everything before they are reached. (They are however, included in the list of rules to check)
Each rule has one of two forms:
These rules contain something that the NPC should say, they contain the following elements:
Any other types of element will be ignored and generate a warning when the file is checked.
These rules determine whether to include a file which can contain additional rules. These included files can also include more files, there is no theoretical limit to this, although you probably don't want to go more than 3 or 4 levels down just because it becomes a little impractical then.
The following elements are allowed.
Any other types of element will be ignored this does mean you can put in something like a 'comment': element to explain why some really complex rules are structured the way they are, this is recommended for very complex dialog.
We'll look at the topmost rule first.
{ "match" : ["*"], "pre" : [["token", "seenbefore", "yes"], ["item", "money", "100"]], "post": [], "msg" : ["Hello again $you, you are rich."] }
There are two conditions we must meet.
The first condition is:
["token", "seenbefore", "yes"]
This checks that the token “seenbefore” has been set to yes. Tokens are set in dialogs by the 'post' actions, if this is your first time speaking to this character, it won't have been set yet.
The second condition is:
["item", "money", "100"]
This checks if the player has enough of a certain item, first there is the item to check for, then the number required. “Money” is a special case, it checks for silver, gold or platinum, up to the required value in silver. (so in this instance 2 platinum would meet the target, as would 1 platinum, 4 gold and 10 silver)
There are no post actions for this rule, so now we look at the message:
"msg" : ["Hello again $you, you are rich."]
This is the text that the NPC will say to the player. $you is replaced by the name of the player
Rule 2 is similar to rule 1, except that the item condition is missing, as such it will trigger when the token is present, but the amount of money held by the player is less than 100
{ "match" : ["*"], "pre" : [], "post": [["settoken", "seenbefore", "yes"]], "msg" : ["Hello I'm $me."] }
Rule 3 has no pre block, and matches *, so it will always work if one of the other rules hasn't triggered first (that is why it is the last rule to be checked).
Here we have a 'post' block which sets the token 'seenbefore' to yes, this means that the next time that we speak to this character, the check [“token”, “seenbefore”, “yes”] will pass and one of the first two rules will be used.
Tokens are stored in the player file, under the “location” which was specified at the top of the file. Any dialog files which point to the same location have access to the same tokens (and can affect the values they hold). Two tokens with the same name but in different locations are treated separately.
Finally there is the message block where $me is replaced by the name of the NPC.
{ include" : ["filewithmoredialog2.msg", "filewithmoredialog3.msg"] }
This is an include rule. The include section lists the files that should be opened to get more dialog rules, any rules in these files will be added to the list of rules that is actually evaluated.
There are two files listed here, rules from them will be added in order from left to right.
There is no 'pre' block for this rule, so these files will always be added.
The options that can be used vary in the different blocks:
If a rule has an include block, then either:
No other types of block will do anything when an include block is present, so they should not be used.
The include block itself should list one or more files.
eg
"include": ["gateguardcommon.msg"]
Each of these files should contain additional rules that the npc should follow.
If a pre block is included, then all of the checks in the pre block must pass for the files to be included, otherwise they are ignored.
Included files can include more files, there is no limit to the depth of recursion, but do be careful not to get a situation where file A includes file B and file B includes file A, the python interpreter should stop that running away indefinitely, but your speech will not work correctly.
This holds the text that the player should say before we check if the rule applies.
There can be one or multiple words specified, or “*” which matches anything. eg
"match" : ["gork","hoard","yes","want"]
This will match any out of “gork”, “hoard”, “yes” or “want”
In the Pre Block, the following checks are supported:
Quest is followed by two fields, the name of the quest, and the step that the player must be at or past. eg
["quest", "testquest", "5"]
Checks that the player has reached at least step 5 of the quest 'testquest'
Have a look at the 'Quest' Section for an explanation of the 'steps'
QuestDone is followed by the name of the quest. eg
["questdone", "testquest"]
Will match if the player already completed the quest at least once, whatever the current state is now. Useful to for instance give a big reward the first time, and less things if the quest is done again.
Item is followed by the name of the item to check for, and optionally the number that are required.
eg
["item", "testquesttarget"] or ["item", "money", "100"]
The first example looks to see if the player is holding at least one item called “testquesttarget”, the second looks to see if he is holding at least 100 “money”.
“Money” is a special case, it checks for silver, gold or platinum, up to the required value in silver. (so if you look for 100 money then 2 platinum would meet the target, as would 1 platinum, 4 gold and 10 silver, or 1 gold and 90 silver, etc)
The number of items required may be omitted, if so it will look for '1' of the item. The number may also be set to '0', this checks that the player does not have the item.
Checks for Token to have a value from the list of values provided. eg
["token", "pester", "1"]
Checks whether the token “pester” holds the value “1” There can be more than 1 value provided.
Tokens are stored in the player file, under the “location” which was specified at the top of the file. Any dialog files which point to the same location have access to the same tokens (and can affect the values they hold). Two tokens with the same name but in different locations are treated separately.
This works like Token
above, except the value will be saved in the NPC itself. Thus if the map is reset, this token will be lost.
This is useful to store temporary data that can be lost without harm, like a conversation state if the conversation can restart.
Checks if the player is at or above the specified level,
"pre" : [["level", "10"]],
Will trigger if the player is at least at level 10, and will not trigger if they are below it. While you can use this to block of entire quest lines until certain levels, it is probably better in general to use this test to bypass a warning about being too weak.
This checks if the date of a time marker is older than the specified age.
there are 6 things you will want to set.
The marker name, which should've been set with 'marktime' (see the post section) then the time to wait in
"pre" : [["age", "mymarker", "0", "0", "1", "10", "0"]],
is true if the marker “mymarker” was last marked as being set at least 1 day and 10 hours ago.
This time is in game time, which is accelerated from real-time, it is also the time defined in the game world. In the crossfire game world:
Any attempt to check for an bigger numbers than these will be silently rewritten in use - ie,
"pre" : [["age", "mymarker", "0", "0", "36", "0", "0"]],
gets treated the same as:
"pre" : [["age", "mymarker", "0", "1", "1", "0", "0"]],
Although it would be better to use the second version because it is easier to compare to other age checks.
This check evaluates to true if the player has in her inventory an item with the specified archetype name. eg
["archininventory", "skill_inscription"]
will evaluate to true if the player has the inscription
skill.
This check evaluates to true if the player knows, through the knowledge system, the specified knowledge item.
["knowledgeknown", "item"]
item
is a special format, describing the knowledge item to check for.
Example: alchemy:1:3829:the wise
will check for the water of the wise
alchemy formulae.
TODO
After the Pre block is used to decide whether a message should trigger, the post block holds the consequences.
There are 9 different options here:
This sets the named quest to the stage which is specified.
eg:
["quest", "testquest" "5"]
Moves the player to stage 5 in “testquest”
If the quest hasn't been started yet, this also starts the quest.
Note that you can not set a quest to a stage which is earlier than the one it is currently at.
In order to prevent that happening, you should have an earlier rule which checks for the stage you want to advance to, typically this not going to do much other than remind the player what already happened.
eg
"match" : ["*"], "pre" : [["quest", "testquest" "5"]], "post": [], "msg" : ["I've already opened the door."] },{ "match" : ["*"], "pre" : [["quest", "testquest" "4"]], "post": [["connection", "4"], ["quest", "testquest" "5"]], "msg" : ["I'll just open the door for you."]
The first rule triggers when we are already at stage 5, the second rule when we are at stage 4, and then moves us to stage 5
Gives the player an item or items from the NPCs inventory.
eg
["giveitem", "biccie", "2"] or ["giveitem", "money", "74"]
The first of these gives the player a stack of 2 'biccie's - There must be at least one such item in the NPC's inventory already for this to work. This item may be customised in any manner you want in the map editor. It does not matter how many biccies the NPC actually has, one is enough; when they give items, they create copies of the ones they already have.
The second of these gives the player 74 money, made up of the smallest possible number of platinum, gold and silver coins. The NPC does not need to have money in his inventory in order to give it.
The name should be the name of the item to be given, or money. The number may be omitted, if so it is treated as 1.
Note that items which are given away are in the NPCs inventory, so if the NPC is killed, they will drop. If you don't want to be able to acquire these items through killing the NPC, then you should create an 'NPC_Gift_Box' item in the NPCs inventory, and put the items in there. This is checked before the rest of the NPCs inventory is, so if you want to give an item that is similar to one that might be generated from the NPCs treasure list, then it is probably better to put that in their also.
You will particularly want to use the NPC_Gift_Box if you have any easily accessible NPCs who are rewarding inaccessible quests. (eg, someone in a town who asks you to fetch something from a very deep dungeon).
This is like giveitem, except that it gives whatever is in the container specified, rather than the container.
You should use this when:
Warning: You probably Don't want to use a treasure list that contains monsters, it'll sort of work, but don't do it.
Removes the given number of an item from the inventory of the player.
eg
["takeitem","testquesttarget"]
Removes one instance of an item called testquesttarget from the players inventory.
You may specify “money” and coins will be removed to the correct value. You may omit the number, and 1 will be assumed. You may specify '0', which will take all of the items that the player has. - Be careful with this, while you can specify [“takeitem”, “money”, “0”], you probably shouldn't.
Note that you will need to check that the player has the item(s) to take first, as such you should always use this in the following way:
"match" : ["*"], "pre" : [["item", "testquesttarget"]] "post": [["takeitem","testquesttarget"]]
That is to say, having a matching item check before removing the item.
Sets the specified token to the specified value, other rules can query this value using 'token' in the pre block.
Tokens are stored in the player file, under the “location” which was specified at the top of the file. Any dialog files which point to the same location have access to the same tokens (and can affect the values they hold). Two tokens with the same name but in different locations are treated separately.
NB: Tokens and time markers are actually the same thing, so if you need to reset a timemarker, then you can use settoken, and set it to 0, if you do, all age checks against the marker will fail until it is set again.
Same as settoken
, except the token is set in the NPC itself, and will be lost eg if the map resets. Tokens are player-specific, so no collision can occur between players.
Useful to store transitory data, like a conversation state.
Tokens are checked with npctoken
in the pre
conditions.
Triggers the specified connection on the map
eg
["connection", "4"]
This allows NPCs to appear to do things for the player.
eg
{ "match" : ["*"], "pre" : [["quest", "testquest" "4"]], "post": [["connection", "4"], ["quest", "testquest" "5"]], "msg" : ["I'll just open the door for you."] }
will give the impression the NPC has opened a door as long as there is a door with connection 4 set in the map editor.
Stores the current server time in a time token with the name specified.
eg
["marktime", "mymarker"]
Stores the current time in “mymarker”, you can then check this with 'age' in a 'pre' block to check how long has passed since that point.
This can be used for putting time limits on parts of dialog/quests etc.
Start an animation. The only argument is the path to the animation file.
eg
["animate", "/test/quest_handling/ninja.animation"]
explain, describe animation in its own page
This is send directly to the player, if the pre conditions are matched, with the following exceptions: $you is replaced by the name of the player $me is replaced by the name of the NPC \n is replaced by a new line character.
Obviously, it probably doesn't make sense to use $you on the first line of dialogue that will be spoken, unless there is a story-based reason for the NPC to know who the player is.
Note also that you can expect clients to wrap message text for you, the reason to use \n is if you use
some blank space
for emphasis.
This lets you specify replies that will be suggested to the player.
The block should be a list of items in the form [word, text, type]
, with word
the actual word the player should say, text
the text the player will actually say if she says the word, type
an optional integer to specify if the text is a regular sentence (0
), a reply (1
) or a question to ask (2
).
Ok, so now you have finished writing your dialog file, the next step will be testing it. You can do this with a local server and watch for error messages that it spits out, but you can also save yourself some time by checking the dialog file beforehand.
There is a script in maps/python/dialog called check_dialog.py which can do this for you.
run it against your dialogue file, like this: maps/python/dialog $ python dialog_check.py ../../scorn/shops/smith.msg and you should see an output like this: checked 12 rules from file ../../scorn/shops/smith_finished.msg Found 0 errors and 0 warnings checked 47 rules from file ../../scorn/shops/smith.msg Found 0 errors and 0 warnings
If there are errors in the dialog file, this will pick them up and allow you to correct them before needing to run a server and speak to the character the dialog is bound to.
NB This script only checks for grammatical errors in your dialog files, there is no check that the rules you have set up actually make sense, nor is there any checking for the order of your rules, it is up to you to ensure your rules do what you want when you want.
Quests are defined in a text file, in a similar way to archetypes. There are a collection of fields that may be used to express different properties of the quest.
The following is an example of a quest definition.
quest scorn/GorksTreasure title Gorks Treasure Hoard description Gain access to Gork's treasure end_description restart 1 step 10 description I have been told that there is a troll in town called Gork, who guards a stash of treasure, it may be worth paying him a visit. end_description end_step step 20 description I have spoken to a peaceful, but distinctly unpleasant troll called Gork, who is guarding a stash of treasure. Maybe if I continue to speak with him I can find a way to get access to his stash. end_description end_step step 30 description Gork has indicated that his friend Mork can also open the treasure room. Maybe if I can find Mork he will be more agreeable than Gork is end_description end_step step 40 description I have spoken to Mork, and he has given me the key to Mork's treasure room end_description end_step step 50 finishes_quest description I have used Mork's key to gain access to Gork's treasure room. end_description end_step end_quest
Again, I'll take this apart step by step:
quest scorn/GorksTreasure title Gorks Treasure Hoard description Gain access to Gork's treasure end_description restart 1
There are a number of entries here, I'll cover them in the order they appear, then explain the other options this quest doesn't use.
The start of any quest is the 'quest' line, which gives the name the quest is known by internally. This is a mandatory entry Good practice is to set up quests as region/questname -
The title provides the name that the players will know the quest by - This is optional (but recommended)
'description' appears on a line on its own on a line on its own, everything that follows until a line containing 'end_description' is a summary of the purpose of the quest overall. - This is optional (but recommended for non-hidden quests)
either 0 or 1. Whether it is permitted for a player to play through this quest multiple times. 1 indicates a restartable quest, 0 a non-restartable one. If constructing a quest which is restartable, then you should be careful of items like keys, which might be able to be kept between attempts allowing parts of the quest to be bypassed. consider also the use of a marktime/age combination in a conversation to stop the quest restarting immediately.
Other options:
TODO: Indicates that the quest is tracked, but that updates are not sent to the player. You can use this to create convenience quests which silently track progress in other quests.
TODO: define how the quest should interact with party membership, options are:
Members of a party can find quests to help their party members with by using the 'party quest' command TODO Not implemented yet.
A quest step is a single 'state' within a quest. Any given player may only be in one state in a quest at any one time.
A quest step looks like this:
step 40 finishes_quest description I have found another way through the gate. end_description setwhen scorn/PortGate finished scorn/CityGate finished scorn/ScornHero <=10 end_setwhen end_step
entries are as follows:
Followed by a number, this marks the start of the step definition, which continues until you reach the next end_step The step numbers are what you will need to use to refer to this quest in dialogs and other events. It is good practice (though not required) to number steps 10,20,30, etc, that way if it is necessary to add additional steps at a later date, it will not be necessary to hunt through maps and dialogs for existing step numbers to change.
If this line is present in the quest step, then being advanced to this step causes the quest to be marked as 'complete.'
This is used in a block, until 'end_setwhen' appears every line in between contains a condition in the form of a quest and a stage requirement.
Every time a quest is updated, all other quests are checked to see if the conditions to advance are met.
A condition can take one of the following forms:
questcode n (the quest questcode must be at step n) questcode ⇐n (the quest questcode must not be beyond step n) questcode m-n (the quest questcode must be between steps m and n) questcode finished (the quest questcode must have been completed)
Where m and n are positive integers.
From the example above:
setwhen scorn/PortGate finished scorn/CityGate finished scorn/ScornHero <=10 end_setwhen
This will trigger when the quest scorn/PortGate is finished, and when scorn/CityGate is finished, and when scorn/ScornHero is at step 10 or earlier.
If these conditions are never met at the same time (if the player were beyond step 10 in scorn/ScornHero before completing the other two quests, say) then the player will not advance to step 40 through meeting the conditions. (it is, however, still possible to advance to this stage by some other means, such as a script triggered on a map).
Quests will not go to an earlier step than the one they are on currently as a result of conditions, and when several steps meet the conditions to advance at the same time, then they will only advance once, to the latest (biggest numbered) step where all conditions match.
Advancing a quest may trigger further quests to advance, in this way it can be possible to have one quest be advanced to several steps sequentially. - However you should be careful with very complex dependancies.
Consider the following example:
quest foo step 10 finishes_quest end_step end_quest quest bar step 10 finishes_quest setwhen foo finished end_setwhen end_step end_quest quest baz step 10 setwhen foo finished end_setwhen end_step step 20 setwhen bar finished end_setwhen end_step step 30 setwhen bar finished baz 10 end_setwhen end_step end_quest
Here quests bar and baz update in response to quest foo, if quest foo is finished, then bar and baz will update, but it is Not Defined which order they will update in.
ie; The end result will depend on the order in which the quests update, you have no way to know what this will be.
TODO Should be followed by the name of a region, as defined in the regions file; indicates that the region in question is somehow connected to the quest. Once client side interfaces to this are supported, then expect there to be a dropdown - 'show only quests connected to …..'
This should be used when updating to a quest stage would let the player know to go there, not before. (ie, if the NPC says go to 'navar and speak to foo' then link_region navar, if he merely says 'try and find where foo lives and speak to him' then you probably shouldn't link Navar until some other character hints to go to navar - and these would be two separate steps in the quest).
descriptions act the same as the description for the quest overall, but have a slightly different meaning. The description of a step should be thought of as a 'what just happened/what should I be doing now' type of text, whereas the quest description is more abstract.
TODO If you have a quest that can be played by a party of people, your description, rather than saying I or me or my, use $I $me and $my, if the player is part of a party, then we, us and our will be substituted in, if they are alone then I, me and my will be. As long as you write in the correct tenses past, perfect or imperative (I think?) then the grammer will be the same.
TODO may appear on its own, and indicates that an update to this step should not apply to other members of the team, this can be used for steps where items are given out so that all players in a team can get the item(s) in question.
Once you have created the .msg
file and defined the quest, open the map containing the NPC you wish to have interact. Add to its inventory a npc_dialog
archetype (it is located in the “system” tab of the “archetypes panel”), and set the name
attribute, or the script options
, to the .msg
file.
That's it, your NPC will interact based on your dialog rules.
We have covered how to handle quest updates in Dialog, and how to define the quests that are updated, but what about updating a quest in response to picking up an object, killing a particular monster or reaching a certain place?
Interaction is based on adding archetypes in the inventory of NPCs, monsters or items you wish to add special behaviour to.
Archetypes are located in the system
tab of the “Archetypes” panel of Gridarta.
They need to have their name defined to the quest parameters to match to interact.
item
refers to the item the archetype is inserted in, whether a NPC, a monster or another item.
Advance a quest when event happens and if the conditions match.
The following archetypes exist:
quest_advance_apply
: the item is appliedquest_advance_death
: the item (monster) is killedquest_advance_pickup
: the item is picked upquest_advance_trigger
: the item is triggered
name
must be set to questname rule1 rule2 …
, with
questname
the quest's internal name, for instance scorn/CoffeeGoose
rule1
, rule2
, … are state intervals, in the format n-p>q
, with n
and p
defining the interval the quest's step must be, and q
the new step to set. -p
can be omitted, in which case the state must be the specified value.Example:
name darcap/Elemental-Water 10>20
name scorn/GoblinKing 10>20
This archetype will prevent its item to be applied unless the conditions match.
name
must be set to questname condition1 condition2 …
with
questname
the quest's internal namecondition
a quest step value, either an interval like n-p
or a single value like n
If inserted in a monster, this archetype will prevent some items this monster owns to be dropped when it dies unless conditions match.
Items from the monster to be conditionally dropped must have a drop_if_quest
value set, with the format drop_if_quest questname condition1 condition2 …
, with
questname
the quest's internal namecondition
a quest step value, either an interval like n-p
or a single value like n
The item will be dropped if any condition matches.
This archetype will prevent its item from being dropped by a player unless the specified quest step is reached. If the item it can be dropped, it will be marked as god-given
so it disappears. This is to prevent both players losing important items and cheating by having someone else give said item.
name
must be set to questname queststep
, queststep
being the step from which the item can be dropped.
Note: this section is a technical description of the previous one, and intended for developers only. Map makers shouldn't be concerned by that.
In the map editor there is the option to bind events to objects, you will already have used this to bind event_say to an NPC.
In order to have map-based updates, you will want to bind other events to items.
The following page lists the types of events you can bind to, and describes when they trigger.
http://wiki.metalforge.net/doku.php/server_plugin#hooking_to_an_object-specific_event
For quest building, you will probably find that apply, attack, death, pickup and trigger are the most useful events, although you may find occasional use for other events too.
When you bind events to objects, you need to pick a script to use, so far you will have been using python/dialog/npc_dialog.py, but you can use any script you want.
For general-purpose quest updating the first place to look will be QuestAdvance.py
QuestAdvance.py should be used as in the following example:
Here we see that the script is bound to event_pickup, which means it will trigger whenever this item is picked up.
First the name of the quest is specified, then the update rules.
An update rule is a step number or range of step numbers and then a step number to move to.
So 0>3 means, if the player is currently at step 0 (ie, haven't started the quest yet) then move them to step 3 1>2 means, if the player is currently at step 1, move them to step 2
if the player were not at either step 0 or 1 in this example, then nothing would happen when the event triggered.
NB: Be careful about binding to event_pickup for items that can be dropped and picked up multiple times, see the next section for how to stop that happening.
This script should be bound to the drop event of items. It prevents players disposing of quest items in a way that would make the quest impossible to finish. It should be used as in the following example.
Here you have the script bound to an event on the 'mcguffin'. If the player tries to drop the mcguffin and has started the quest 'testquest2' but isn't yet up to step 40, he won't be able to drop the item, if he has passed that stage, he will be able to drop the item, but it will be considered god-given and disappear (in the same way that starting equipment does).
If the player has never started the quest, then:
This script should be bound to an apply
event of an item. It prevents players to apply the item unless the specified quest is in specific states.
This is useful to prevent using stairs for instance.
The syntax is: <questname> <rule> <rule> …
, with rule
being either a precise step like 10
, or a step range like 20-40
.
This script is used to trigger connections, like open gates or such, if the player is at a certain quest step.
The syntax is: <questname> <rule> <connection> …
, with rule
being either a precise step like 10
, or a step range like 20-40
, and connection
the connection code to trigger if the rule matches.
{ "match" : ["cookie"], "pre" : [["quest", "cookiequest", "30"]], "post" : [], "msg" : ["I already gave you a cookie."] },{ "match" : ["cookie"], "pre" : [["quest", "cookiequest", "20"]], "post" : [["quest", "cookiequest", "30"], ["giveitem", "cookie"]], "msg" : ["Well done, have a cookie."] }
This will only give the player one cookie when they do the quest (with more possible later if it is a restartable quest.
You can modify the weight of the item in the map editor.
The following are common things to want to do, and how to go about doing them.
Now the player can either kill the NPC and claim the item, or he can talk to him and claim the item, but can not talk to the NPC, get the item, then kill the NPC and get another copy of the item.
Put a checkinv under the floor, set it to match type =1. Now put an event_trigger in the inventory of the check inv and point it at python/dialog/QuestAdvance.py giving the appropriate stage update rules.
Hop on IRC and and ask for advice.
I don't know yet, working on it.