Skip to content

NPC Dialogue

Michael edited this page Jun 28, 2019 · 8 revisions

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 leave subcommand. 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.

How do intros and terminals work?

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.

Some of Old Man's intro states
"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"
                ]
            }
        },

So, then, how do intermediates work?

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.

Some of Old Man's intermediate states
            "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"
                        ]
                    }
                ]
            },

Quests and NPCs: Bread and Butter

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.

Advanced "flags" and "reqs"

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 an eval. 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 through eval for 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 single req evaluates to a falsy value, then a player will be barred from that dialogue state.

Clone this wiki locally