NPCs ‐ Conversations ‐ Ask Tell - ThePix/QuestJS GitHub Wiki
By default, this feature is turned off, as this allows Quest to tell the player that she is not going to get anywhere using ASK/TELL ABOUT. To turn it on, go to the setting.js file, and add this:
settings.noAskTell = false
One approach to conversations is to allow the player to ask the NPC about each topic, in the form ASK KYLE ABOUT THE GARDEN. By default, Quest will just response that the NPC has nothing to say on the subject, but we can give Kyle some opinions by giving him an "askOptions" attribute.
createItem("Kyle", NPC(false),
{
loc:"lounge",
examine:"A grizzly bear. But cute.",
properName:true,
askOptions:[
{
test:function(p) { return p.text.match(/house/); },
msg:"'I like it,' says Kyle.",
},
{
test:function(p) { return p.text.match(/garden/); },
msg:"'Needs some work,' Kyle says with a sign.",
},
{
test:function(p) { return p.text.match(/garden/); },
msg:"'I'm giving up hope of it ever getting sorted,' Kyle says.",
},
{
msg:"Kyle has no interest in that.",
failed:true,
}
],
});
Each response is a dictionary, with a "test" and either a "msg" or a "script" (or both, in which case the script runs before the message is shown). The test should be a function that return a Boolean; if true
this response is the one used. If it is used, the optional function "script" will be called, and the the optional string "msg" will be printed. At this point Quest stops the process. If the response is not used, Quest will go on to the next, and the next until it finds a match.
The "test" and "script" functions are sent a dictionary parameter, with these values:
name | value |
---|---|
text | what the player typed |
text2 | linking word (see later) |
actor | this character |
action | either "tell" or "ask" |
The last entry has no "test"; this is the default that gets used when all else fails. Note that it has "failed" set to true, so this will count as a failed turn to the player.
While it is technically possible to add new topics as the game progresses, they would not be saved with the game when the player does SAVE, so this should not be done.
A better way is to test the game progress in the "test" function. In the example below, another option has been added; this will be used if the text matches and the garden has been fixed. If the garden has not been fixed, it will fall through to the next option.
A counter has also been added, together with another entry, so there is less repetition (and this also has a "script"). The regular expression in the first option has been expanded so it will match "house" or "home".
createItem("Kyle", NPC(false),
{
loc:"lounge",
examine:"A grizzly bear. But cute.",
properName:true,
askOptions:[
{
test:function(p) { return p.text.match(/(house|home)/); },
msg:"'I like it,' says Kyle.",
},
{
test:function(p) { return p.text.match(/garden/) && w.garden.fixed; },
msg:"'Looks much better now,' Kyle says with a grin.",
},
{
test:function(p) { return p.text.match(/garden/) && w.Kyle.needsWorkCount === 0; },
msg:"'Needs some work,' Kyle says with a sign.",
script:function(p) { w.Kyle.needsWorkCount++; },
},
{
test:function(p) { return p.text.match(/garden/); },
msg:"'I'm giving up hope of it ever getting sorted,' Kyle says.",
},
{
msg:"Kyle has no interest in that.",
failed:true,
}
],
needsWorkCount:0,
});
The "test" function and similar words
At its simplest, the test function just matches any number of options, as in this example, which will match text containing either "house" or "home" (or both):
test:function(p) { return p.text.match(/(house|home)/); },
You can have as many alternatives as you like, separated by a vertical bar. Note that this will match either word anywhere in the text, so would match:
ASK KYLE ABOUT HOUSE
ASK KYLE ABOUT HOUSEFLY
ASK KYLE ABOUT LARA HOUSE
ASK KYLE WHY THE HOUSE IS MESS
Is that a problem? It depends on how likely the player is to try the alternatives, which very much depends on your game. If there is a housefly in your game, the player may well ask about it. If not, it is very unlikely to come up.
Let us suppose there is - there are a number of approaches. The simplest is to simply have an earlier entry that matches "housefly". Quest will go though until it finds a match, use that, then stop.
{
test:function(p) { return p.text.match(/(housefly)/); },
msg:"'I don't like that pesky fly,' says Kyle.",
},
{
test:function(p) { return p.text.match(/(house|home)/); },
msg:"'I like it,' says Kyle.",
},
Another approach is to specify an exact word match by putting a word boundary marker at the start and end of the word. A word boundary is either the start or end of a word; the point where the string changes from letters and number to white space or punctuation or vice verse. This is represented by a backslash, followed by the letter "b".
{
test:function(p) { return p.text.match(/(\bhouse\b|\bhome\b)/); },
msg:"'I like it,' says Kyle.",
},
The "test" function and conditions
You may want responses to vary depending on the state of the game. Just add the condition to the test. Let us suppose we have an attribute of Kyle that gets updated as the game advances called status:
{
test:function(p) { return p.text.match(/(house|home)/) && w.Kyle.status > 2; },
msg:"'I don't like it any more,' says Kyle.",
},
{
test:function(p) { return p.text.match(/(house|home)/); },
msg:"'I like it,' says Kyle.",
},
Note that the second does not test the condition; Quest will only find it if the condition in the first is false. You could add as many conditions as you like, but it is a good idea to have a default fall-back with no conditions.
Topics
The player can use the TOPICS command to see some suggested topics for a character (typing TOPICS on its own gives a message say it needs to target a character).
TOPICS FOR KYLE
TOPIC LARA
To be included in the list, a topic must be given a name (if you want it to have a name, but not get listed, flag it as "silent"). In the below example, the TOPICS command will pick up the two topics "House" and "Garden". Note that a topic will only be suggested if the test passes with its name as the text, so the list will change dynamically as the game state changes. In the example below, the "Garden" topic will disappear from the list once the garden is fixed. Note that we had to add an addition check to the last option to ensure it does not get picked up; for ASK KYLE ABOUT GARDEN only the first is used, so that was not necessary for TOPICS FOR KYLE, all the options are checked.
askoptions:[
{
name:"House",
test:function(p) { return p.text.match(/(house|home)/); },
msg:"'I like it,' says Kyle.",
},
{
test:function(p) { return p.text.match(/garden/) && w.garden.fixed; },
msg:"'Looks much better now,' Kyle says with a grin.",
},
{
name:"Garden",
test:function(p) { return p.text.match(/garden/) && w.Kyle.needsWorkCount === 0; },
msg:"'Needs some work,' Kyle says with a sign.",
script:function(p) { w.Kyle.needsWorkCount++; },
},
{
name:"Garden",
test:function(p) { return p.text.match(/garden/) && !w.garden.fixed;; },
msg:"'I'm giving up hope of it ever getting sorted,' Kyle says.",
},
{
msg:"Kyle has no interest in that.",
failed:true,
}
],
You may well want a topic only to appear once it has been mentioned. You can readily do that by flagging the response as "silent", but then adding a "mentions" entry to any response that mentions the topic, with an array listing each name as a string. The name has to match perfectly.
askOptions:[
{
name:'Park',
test:function(p) { return p.text.match(/park/) },
mentions:['Swings', ''Roundabouts'],
msg:"'Going to the park sounds like fun,' Kyle says with a grin. 'We can go on the swings and roundabouts!'",
},
{
name:'Swings',
silent:true,
test:function(p) { return p.text.match(/swing/) },
msg:"'The swings are fun!'",
},
{
msg:"Kyle has no interest in that subject.",
failed:true,
},
],
Note that the list of topics mentioned belongs to the player, so following on from the above example, we could have other characters with a "Swings" entry; that would also be listed for the other character once Kyle mentions it.
Also note that this does not stop the player asking about swings or change the response before it is mentioned. It only affects the TOPICS command.
An alternative way to have the TOPICS command change dynamically is to give your NPCs their own custom "topics" function.
As well as "ABOUT...
In fact the system can handle other question, allowing WHERE, WHAT, WHY, WHO, WHAT or HOW instead of ABOUT, so the player can potentially do:
ASK KYLE WHY THE GARDEN IS A MESS
ASK KYLE HOW TO GROW ONIONS
ASK KYLE WHERE SPADE IS
The questioning word will be in the dictionary sent to "test" and "script" as "text2". So, you could do this in test to match the first example above:
test:function(p) { return p.text.match(/mess/) && p.text2 === 'why'; },
As well as ASK and TELL
Users can also do TALK TO/ABOUT. By default, they will get everything in ASK/ABOUT and TELL/ABOUT combined, but you can set a dedicated "talkOptions" attribute on a character to have it done differently.
You can also modify the character's "askabout" attrribute to change the options. If you only want to access "askoptions", not "telloptions", you could do this:
talkabout:function(text1, text2) {
return this.askabout(text1, text2)
},
Notes
The ASK/ABOUT command uses the respond
function, allowing you to nest responses with each other. For more, see here.
By default, Quest will report that the player is asking the question, and then resolve the response, if any. To disable that, set settings.givePlayerAskTellMsg
to false
in settings.js.
You can also do TELL/ABOUT in just the same way.
If you choose to do this you should give the player some big hints about what an NPC should be asked about to complete the game and try to cover all reasonable options. This makes this approach to conversations perhaps the most work, but it is properly open-ended, in the spirit of a parser game (unlike dynamic conversations, which are more in the spirit of a game book).
If "dev" mode, you will see warnings in the console for characters with no "askoptions" or no "telloptions", as it is generally best to have at least a default response if you have ASK/TELL turned on. The warnings will not appear in "beta" or "play" modes.