User Tools

Site Tools


building:quests:dql

Dialog Quest Language or DQL

In order to reduce the need to create scripts to support dialogs, especially for quests, a very simple new scripting language has been added to support most of the common function used inside the dialog line Appears When script. The language support most of the common predicates and actions require to create a working quest and also provides and interface to PMOTE script commands to enable creature animations and other object manipulation in the same area.

How DQL Works

Usually, it's possible to modify the behaviour of NWN scripts by passing them information via local variables, but for multi-line dialogues it quickly becomes unmanageable. DQL solves this by storing the extra information in the dialogue line itself. It then removes the information so that the player only sees the dialogue part and not the DQL information intended for the script. To add DQL to a dialogue line just add quest_aw as the Appears When script and enter the commands at the end of the dialogue as follows. These lines could, for example, be added to a door conversation.

  • [The panel opens and you hear a rough gravely voice.] Who do the gangs answer to?
  • →Only the Bishop|IF GetJournalState(“QUEST_THIEF_GUILD”) > 3 OR Check(Lore, 30)
  • →→[The panel closes and the door opens.]|PMOTE Unlock,Open,Wait:60,Close,Lock
  • →The King
  • →→[The panel closes with a loud thunk.]
  • →Goodbye

The “|IF” part tells quest_aw to treat the result as a predicate. The dialogue “Only the Bishop” will only show for PCs that have made progress in the specified thief guild quest or pass the lore skill check. If they click on that line, they will see “[The panel closes and the door opens.]” and the door will unlock and open for a minute. Other PC's will not be able to open the door using the conversation.

Quest State Information

So how do quests actually work? Typically quests are an example of a finiate state machine. It is often possible to track the state of the quest by looking for items in the player's inventory but this can be difficult as you need to create lots of items. It can also look awkward and contrived if you are being passed notes all the time. A more general solution is to store the state of the quest in a local integer variable stored either on:

  • PC object
  • An item in the PC's inventory.
  • As the state of an entry in the PC's journal.

Each of the above has its own merit and you may well decide to use them all in different circumstances.

PC Object Local Integer Variable

Storting the state on the PC is the easiest and cleanest option but keep in mind that the PC object is reset when the server resets. This makes it a poor choice if the quest is very long or the result of the quest is important to NPCs or other quests. Sinfar is often up for many days or even weeks, so this is usually a good choice if the result of the quest is not important.

Inventory Item Local Integer Variable

In many ways, this is the best solution of all. It will survive server resets and does not clutter up the PC object. The major downside is that PC has to carry around an item with no other purpose and if every builder decides to add their own it could become annoying. Obviously, you also need to dump the item on the PC somehow and ideally tell them what it does OOCly so they can use to manage their character quest history. There is already a system in place to address these issues, the quest_census dialogue can be attached to an NPC of your choice or you can just spawn the quest_census NPC. The dialogue will ask a few background questions and ask about SRP lights then place the quest_census item in the PC's inventory. This item can then be used for all your quest status information tracking needs. The main downside is that the PC can't do any quests without a census form, so you will need to trap for this every time you offer a quest.

Journal Entry State

This is the official NWN mechanism for tracking quest progress. The journal has the advantage that it is properly integrated into the game and allows PC's to see what quests are open and those that are complete. Each journal entry has a space for the quest name, instructions for the player on what to do next and a private state variable that can be used to track the progress made. Sinfar has its own system for storing journal information including persistence past server resets using a database. The only major downside is the PC can end up with hundreds of pointless journal entries. Builders can help by removing journal entries when a quest or quest chain is complete. If you want to avoid journal clutter but still want to track quest history, then consider using both Journal and the quest_census item. Remove the journal entry but leave the quest_census local variable. DQL will actually cache the journal state in the quest_census if found, or the PC is not, so using the quest_census to store quests results is seamless. Include an NPC in the city, perhaps the library, who can clean up all the quests for your island.

Example Quest Using States

Let us design a quest given by a collector, to the PC, to acquire a silver Statue from another collector. The quest has a few paths:

  • Simply steal the statue;
  • Persuade or intimidate collector B to sell it;
  • Exchange the statue for a painting in collector A's collection;
  • Make a fake statue and use that instead.
  State
  0 = Not started:
      When the PC talks to the collecter A, offer them the quest and if they accept, move to 1 and tell them to obtain the statue from collecter B.
      If the PC is in the thieves guild and passes a lore and INT check, also suggest that they could obtain a fake statue from guild forger.  
  1 = Looking for the Statue:
      Collector A will accept the statue and pay a bonus if it is not stolen, move to state 10 and close the quest.
      Guild forger will give the PC a fake for some gold, move to state 2, the journal should update to reflect the new objective.
      Collector B will sell the statue if the PC makes a hard persuasion/intimidate check, move to state 2 and update objective.
      Collector B will offer to exchange the statue in return for a painting owned by Collector A, move to state 3 and update the objective
  2 = Statue Obtained:
      Collector A will accept the statue and pay a bonus if it is not stolen, move to state 10 and close the quest.
      Collector A will accept the fake statue and pay for it, move to state 11 and close the quest.
  3 = Looking for Painting:
      Collector A will accept the statue and pay a bonus if it is not stolen, move to state 10 and close the quest.
      Collector A will sell the painting to the PC if they do not have it and can make hard persuasion check, update objective.
      Collector B will give the statue to the PC if they have the painting, move to state 2 and update the journal.
  10 = Quest Completed:
  11 = Quest Completed with fake: There may be consequences for the PC.
  

Potentially the PC is free to steal the statue in any state but it is good practice to only spawn it when the quest is running, same for the painting. You could also add other requirements such as checking that Collector B is still alive if Collector A is squeamish. Ideally, a quest should offer lots of alternative solutions and alternative paths for completion using different skills/abilities and also allow them alternate moral choices.

Syntax

The quest_aw script searches for the following three special character sequences. |IF - Use the result from the DQL expression to control if the text appears. |DO - Evaluate the DQL expression and ignore its result, used for actions. |PMOTE - Pass the following text to the pmote script with Self as Subject and the PC Speaker as the partner.

DQL itself has a very simple syntax consisting of a option object reference with a dot and a function name followed by the function arguments inside a pair of parenthesis, optionally followed by an operator and more DQL. For example, if the PC if paying for something we would need to take something, then give something back.

  • [The shop keeper takes your gold and hands over the hammer.]|DO PC.Take(GOLD, 500) AND PC.Give(QUEST_MAGIC_HAMMER)

Usually it is not necessary to include the “PC.” part as the PC Speaker is selected by default. The AND operator is very important as DQL evaluates left hand side first and only evaluates the right hand side if the left and side if true. This will stop the merchant from handing over the hammer if the player does not have the gold.

Operators

DQL supports the following operators

  • , - Ignore the result of the LHS and return the result of the RHS, used execute a sequence of functions.
  • OR | - returns true if either the LHS or RHS is true, RHS is only evaluated if the LHS is false.
  • AND & - returns true if both the LHS and RHS is true, RHS is only evaluated if the LHS is true.
  • = - Equality, returns true when both sides evaluate to the same result.
  • != - Not Equal, opposite of equality.
  • > - Greater then, true if the LHS if greater then the RHS.
  • >= - Greater or equal, as you would expect.
  • < - Less than, as you would expect.
  • - Less than or equal, as you would expect.

Function and Operator Results

DQL always returns a string, in a Boolean context it will return TRUE or FALSE, in a numeric context it will return a string holding a decimal number. The operators try their best to figure things out but its not a good idea to mix and match.

Object References

By default functions are passed PC speaker as object reference, so there is no need to put “PC.” in front of every function.

  • PC - The Player Character speaking in the conversation.
  • SELF OBJECT_SELF - The NPC or object that the PC is speaking to.

Anything that is not one of the above is assumed to be Tag reference. If the Tag begins with “I_”, then it is assumed to be an object in the PC's inventory. For other Tags it first searches for the nearest, failing that it looks globally.

Constants

Constants can be used in the place of functions with some limitations. DQL will accept TRUE and FALSE for Boolean values, decimals or floats when numbers are required or strings. Actually everything is a string really, the function should document what it needs. No need for string quotes in function arguments, in fact currently, string quotes will mess things up when used in a function argument. Regex recursion is needed to fix all of this. With the current limitations on string parsing its best to avoid “)” and “,” in your strings. Journal entries have a work around that lets it accept “,”.

While string constants are currently a horrible mess, its not actually much of a problem in practice. Most functions are expecting either a word or a object Tag, seldom free text.

Functions

Has

Get

Get(Attribute)

The following attributes are supported and are matched case intensive.

  • Wisdom or WIS
  • Constitution or CON
  • Dexterity or DEX
  • Intelligence or INT
  • Strength or STR
  • Wisdom or WIS
  • Age
  • RacialType
  • Reputation
  • ApperanceType
  • PhenoType - Can be used to detect Crawl and Bound animations
  • CasterLevel
  • ChallengeRating
  • HitPoints
  • SpellResitance
  • GOLD
  • Area Name
  • All skills defined in skill.2da, no underscores please.

Returns an integer value except for ChallengeRating which is a float.

GetBase(Attribute)

Same as Get but will return the unmodified base values.

Set

Set(Attribute, Value)

Not the same as Get or even close. Supported attributes

  • ACTIVE - Allowed for traps, value must be TRUE or FALSE

Returns true if successful.

Is

Is(Attribute)

The following attributes are supported and are matched case intensive.

  • Good
  • Evil
  • Lawful
  • Chaotic
  • Male
  • Female
  • Pet - Has a slave item with the Pet Tag
  • Slave - Has a slave item with the Slave Tag
  • Active - Only for traps, active or not

Returns TRUE or FALSE as expected.

Local Variables

GetInt(LocalVariableName)

GetString(LocalVariableName)

SetInt(LocalVariableName, Integer)

SetString(LocalVariableName, String)

Ability Checks

Check(SkillOrAbility, Value, [, OpposingCreatureTag])

Returns true if the creature has enough points in the relevant skill or ability (CHA, CON etc) If a OpposingCreatureTag is supplied then the check is opposed and the creature must beat the opponent score + the value supplied. This function also adds a note to the dialog line to indicate that the relevant Skill or Ability was used.

CheckRoll(SkillOrAbility, Rank)

Same as Check except that a d20 role is added, also for the opponent if opposed.

Inventory Fuctions

Has(ItemTag)

Returns true if the item was found on the creature.

Give(resref, [NumberOfItems=1])

  • resref Should be GOLD, XP or an item resref.
  • NumberOfItems The number items, gold or XP added (optional default is 1).

Returns TRUE unless the item could not be created and placed in the creature's inventory.

Take(ItemTag, [NumberOfItems=1])

  • ItemTag can also be GOLD
  • NumberOfItems The number of items or gold removed.

Returns TRUE if NumberOfItems was removed. It will always try to remove as many of the items as is specified. In theory, you should always have a dialogue line that counts the number of items before they are taken, but players can cheat so you should also check that Take() is successful before passing on a reward. Unfortunately, this will result in removing any items found without compensation but that is a fair reward for cheating I think?

ActonTake(resref, [NumberOfItems=1])

Same as Take but is added to the action queue to be done later.

Count(ItemTag)

  • ItemTag can also be GOLD

Like, Take it searches both the equipment slots and the inventory, no place to hide!

ActionEquipItem(ItemTag, Slot)

Adds an action to equip an inventory item to the action queue. Atm, Slot must be a number, may support names later on.

Journal

AddJournalEntry(PlotId, Title, Priority, State, Text)

  • PlotId - Unique quest identifier, should begin with the ERF Prefix to guaranty uniqueness
  • Title - Free text holding the quest title. As usual with strings please avoid commas.
  • Priority - Integer holding the journal entry priority, lets pick 5 as standard value maybe.
  • State - Positive integer value used to track quest progress, zero implies that the quest has not started.
  • Text - Free text, usually used to tell the player what to do next, you can actually use commas here.

AJE(PlotId, Title, Priority, State, Text)

Same as AddJournalEntry()

UpdateJournalEntry(PlotId, State[, Text])

If State is negative it will be ignored and only the text will be updated. Will also make make the complete status false. As with AddJournalEntry() you may include commas.

UJE(PlotId, State[, Text])

Same as UpdateJournalEntry()

CompeteJournalEntry(PlotId, State[, Text])

Same as UpdateJournalEntry() except that the complete status is changed to True.

CJE(PlotId, State[, Text])

Same as CompeteJournalEntry()

GetJournalState(PlotId)

Returns the current State of the quest in the journal. The value is cached on the PC so you may use without worrying about performance issues.

GJS(PlotId)

Same as GetJournalState(PlotId)

RemoveJournalEntry(PlotId)

Removes the quest from the journal, this will allow the PC to repeat the quest. It is also a good helpful to keep journals uncluttered unless you really need to keep the entry.

RJE(PlotId)

Same as RemoveJournalEntry()

Action Queue

Most of these functions have been sorted into other sections.

ActionWait(Seconds)

Add an action to wait this many seconds, can be a float.

ActionFaceNearest()

Makes the NPC face in the direction of the nearest peaceable. Used for aligning them with chairs and tables.

EscapeArea()

Returns TRUE unless a the creature was a PC, the creature will leave the area.

Conversation

ActionTalkTo([oInterlocutor=SELF[, sDialogRefRes=default[, bPrivate=FALSE]]])

Sadly the default behavior for this is terrible. You almost always want to target SELF and select PC as the person to talk to.

  • oInterlocutor is the creature that you wish to speak to.
  • sDialogRefRes is the conversation.
  • bPrivate controls if others can hear.

TalkTo()

Same as ActionTalkTo() but the default object is SELF and it automatically selects PC as the interlocutor. It does actually accept the same arguments but defaults work in the most common case. The main use for this function is to provide a work around for when you need to repeat dialog entries. For DQL to work properly it has to remove itself from the dialog. Consequently if you return to a DQL dialog line it will no longer have the DQL instructions. The work around is to restart the conversation. Its not ideal but seems work well enough. Just add “, TalkTo()” end of the DQL and the NPC will start the conversation again.

Objects

Create(ObjectType, resref, [NewTag=""])

  • ObjectType Any of ITEM, CREATURE, PLACEABLE, STORE or WAYPOINT.
  • resref A refres for an object template of the correct type.
  • NewTag Overides the tag specified in the template if not empty.

Returns TRUE if the object was created.

Animation

PMOTE may be a better option.

PlayAnimation(nAnimation[, fSpeed=1[, fDurationSeconds=0]])

Passes everything though to PlayAnimation

ActionPlayAnimation(nAnimation[, fSpeed=1[, fDurationSeconds=0]])

Same as PlayAnimation() but added to the action queue for later.

Speaking

SpeakString(sText)

ActionSpeakString(sText)

Die Rolls

D(DieSize,DieCount=1)

Random number generator that simulates a dice throw.

  • DieSize The size of the dice, d(4),d(6),d(8),d(20), could be anything though.
  • discount The number of dice thrown. Defaults to 1.

Examples

Stand Alone Example NPCs

Can be spawned safely anywhere for testing purposes. I have linked into thier conversations.

  • quest_example - A simple example quest using note items.
  • quest_ex_shop - An example of a vendor who can be payed with tokens, price list is here.
  • quest_ex_journal - An example showing how to work with the journal.
  • quest_ex_counter - An example showing how to work with inventory functions.
  • quest_ex_bouncer - Night club bouncer, spawn near a locked door if possible.
  • quest_census - Originally intended to store quests state information and record player preferences, SRP lights and such. Perhaps less relevant now with Journal available?

In Game Example NPCs

building/quests/dql.txt · Last modified: 2019/08/22 19:19 by EternalSenenity