-
Notifications
You must be signed in to change notification settings - Fork 5
NPC Dialogue
So NPCs have a couple of neat things going on, although their structure might feel confusing at first, it's not hard to grok. It's also very crucial, for anyone looking to host their own game module, to understand how this system functions.
For the rest of this explanation, we'll use the Old Man – who is the first NPC in the demo's entities.json file – as our well fleshed example.
The first thing to understand is that the NPC dialog is based on the general theory of a state machine.
There are 3 types of dialogue states:
- intros –(start state)– The only allowable dialogue options for NPC talk initiation, and the player cannot return to them without restarting npc dialogue completely. They have a priority field to determine which is shown if a player meets requirements for multiple ones.
- intermediates –(transitional-type state)– Reachable at any point in a conversation, assuming requirements and proper dialogue tree traversal. Of the 3 types, they do not use a priority field, as the choice is determined by the user manually.
-
terminals –(end state)– Sent when a player chooses a terminal option of dialogue, or universally sent when the player uses the
leavesubcommand. The specific terminal option is automatically determined similar to the intros, although it is possible to call a specific terminal in dialogue.
NPC conversations begin at a start state, can meander through as many intermediate states as desired, and absolutely end once a terminal state is hit. Much like a theoretical state machine, the current state is dictated by input. User input is the main pathway, and sometimes some other optional parameters are used in tandem.
Below we see a snippet of the Old Man's structure: his intro options.
There are 2 of them, labelled "0" and "1". Note that only the second intro has a "reqs" field (short for requirements). This means that the other field is implicitly the default. If you note the priority fields, a higher integer will result in higher priority, so the highest priority option will be the one used, but in cases like this one, only if a player first meets the "reqs" for it.
It is also worth noting that terminal states do not have any "prompts". This is naturally due to their nature as endpoints in a conversation.
"0": {
"priority": "1",
"reply": "\"Hello! I'm so old!\"",
"prompts": [
{
"prompt": "Ask how he is",
"progression":{
"intermediates": "0"
}
},
{
"prompt": "Ask where you are",
"progression":{
"intermediates": "1"
}
},
{
"prompt": "Say Goodbye",
"progression":{
"terminals": "0"
}
}
]
},
"1": {
"priority": "2",
"reply": "\"Hi there!!! I hope I feel better soon!!\"",
"prompts": [
{
"prompt": "Ask how he is",
"progression":{
"intermediates": "3"
}
},
{
"prompt": "Ask where you are",
"progression":{
"intermediates": "4"
}
},
{
"prompt": "Say Goodbye",
"progression":{
"terminals": "1"
}
}
],
"reqs": [
"this.state.player.inventory.keys[6].used === true"
]
}
},Intermediates do not use "priority", and it's recommended to have at least one state that they allow selection of, otherwise it's awkward to the user (although maybe you want that).
Something of interest, state "2" has a "flags" property. This property can be associated with any of the 3 states, just as "reqs" may be. Where "reqs" allows for the roping-off/unlocking of content, "flags" allows you to change values while still in dialogue. Here a user's key-item used flag is set to true. This change allows the user to access the secondary set of dialogue options, and by using "reqs" elsewhere in the dialogues, we can reflect a permanent change in relation ship between player and NPC.
"2": {
"reply": "\"Oh wow!!! Thank you so much!!\"\n\nThe old man puts the ALEVE in his pocket...",
"prompts": [
{
"prompt": "Ask how he is",
"progression":{
"intermediates": "3"
}
},
{
"prompt": "Ask where you are",
"progression":{
"intermediates": "4"
}
},
{
"prompt": "Say Goodbye",
"progression":{
"terminals": "1"
}
}
],
"flags": [
{
"property": "this.state.player.inventory.keys[6].used = true"
}
]
},
"3" : {
"reply": "\"My back feels better already!!!\"\n\n...You don't mention that the ALEVE is still in his pocket...",
"prompts": [
{
"prompt": "Ask where you are",
"progression":{
"intermediates": "4"
}
},
{
"prompt": "Say Goodbye",
"progression":{
"terminals": "1"
},
"reqs": [
"this.state.player.inventory.keys[6].used"
]
}
]
},In the previous section, we touched on "flags" and "reqs" as fields that can be linked, and affect each other within a single NPC interaction. This can be thought of as the foundation of a questing system, in combination with having key items that act as flags for quest states.
In both cases, an array is iterated through, and this means we can have multiple "flags" and "reqs" per dialogue state.
-
"flags"– expressions that change key item values on the fly, as well as other options. It is a sorted array of objects, and each object's inner string is executed by aneval. What this means is you can use it to set variable values (players or otherwise) dynamically, and even make database changes mid dialogue. -
"reqs"– similar, but different to"flags", in that while all of the items in"reqs"are also sent throughevalfor execution, this array is not meant to change variables, but rather to check them. The req strings are treated as conditionals and executed for truthiness. If any singlereqevaluates to a falsy value, then a player will be barred from that dialogue state.