NPCs: Conversations: Simple TALK TO - 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 TALK TO. To turn it on, go to the setting.js file, and add this:

settings.noTalkTo = false

The simple TALK TO system gives the player the options of either talking to an NPC or not. There is no choice in what they will talk about.

This is very simple, just give the character a "talkto" attribute. For example:

createItem("Mary", NPC(true),{ 
  loc:"lounge",
  examine:"An attractive young lady.",
  properName:true,
  talkto:"You say 'Hello,' to Mary, and she replies in kind.",
});

Note that "properName" is set to true, so Quest knows not to put "the" or "a" before her name.

This is a bit dull, if the player repeatedly does TALK TO MARY, they will just be saying hello to each other all the time.

We can make the "talkto" attribute a function, and have it print something different every time (at least, as many as you are willing to write). Quest will automatically do the counting in the "talkto_count" attribute, so the function can just use that with a switch. Here is an example:

createItem("Mary",  NPC(true), { 
  loc:"lounge",
  examine:"An attractive young lady.",
  properName:true,
  talkto:function() {
    switch (this.talkto_count) {
      case 0 : msg("You say 'Hello,' to Mary, and she replies in kind."); break;
      case 1 : msg("You ask Mary how to get upstairs. 'You know,' she replies, 'I have no idea.'"); break;
      case 2 : msg("'Where do you sleep?' you ask Mary."); msg("'What's \"sleep\"?'"); break;
      default: msg("You wonder what you can talk to Mary about."); break;
    }
  },
});

Note that JavaScript switch statements need a break at the end of every case (otherwise the next one also get done).

Allowing the player to answer

This technique only really works if the command line is turned off, and the player is interacting through the side pane. We can set it up so that if the NPC asks a question, a set of responses appears as options for one turn only.

responses

In code.js, you need to put this command and function. The function will get called by the NPC from the "talkto" function, and selects a topic to display, and - if applicable - sets up the responses. The command handles the player selecting one of those responses, finding the topic, then the response.

commands.unshift(new Cmd('Response', {
  regex:/^response\: (.+) (\w+)$/,
  objects:[
    {text:true},
    {scope:parser.isPresent}
  ],
  script:function(objects) {
    const c = objects[1][0]
    const text = objects[0]
    const q = talkTopics[c.responseSet][c.responseNumber]
    const response = q.responses.find(el => el.title.toLowerCase() === text)
    for (const el of response.ex) msg(el, {npc:c})
    if (response.fn) response.fn(c)
    c.hereVerbs = []
    return world.SUCCESS
  },
}));

const chat = function(c, responseSet, n) {
  const q = talkTopics[responseSet][n]
  for (const el of q.ex) msg(el, {npc:c})
  if (q.fn) q.fn(c)
  if (q.responses) {
    c.hereVerbs = q.responses.map(el => "Response: " + el.title)
    c.questionsActive = true
    c.responseNumber = n
    c.responseSet = responseSet
    c.sayTakeTurn = function() {
      if (this.questionsActive) {
        this.questionsActive = false
      }
      else if (this.hereVerbs.length > 0) {
        const q = talkTopics[c.responseSet][c.responseNumber]
        if (q.noResponse) q.noResponse(c)
        this.hereVerbs = []
      }
    }
  }
}

We also need to add a "talkto" attribute to every NPC we want to use this system. This needs to call the "chat" function we added above. It needs three parameters, the character (which we access using this), the list to pick from, and the topic to pick. You could pick a topic at random or select in some complex way that takes account of how the NPC feels, etc. This simple example just goes though the list in order.

  talkToCount:0,
  talkto:function() {
    chat(this, 'basic', this.talkToCount)
    if (this.talkToCount < talkTopics.basic.length - 1) this.talkToCount++
  },

Finally, we need the data. This is where it gets complicated, as this will be unique to your game. The data goes into a dictionary called "talkTopics". Inside that you can have any number of arrays. This example just has one called "basic", but you can name them whatever you want. The second parameter in the call to "chat" must match one of these names.

const talkTopics = {}

talkTopics.basic = [
  {
    ex:["'How is it going?' you ask {nm:npc}.", "'Good,' {pv:npc:reply}. 'How are you?'"],
    responses:[
      {title:"Good", ex:["'Good,'you say", "'That's nice'"],},
      {title:"So so", ex:["'Hmm, so-so, I guess'", "'That's not great'"],},
      {title:"Bad", ex:["'Bad.'", "'That's sad'"], fn:function(c) { c.sad = true }},
    ],
    noResponse:function(c) { c.ignored = true },
  },
  {
    ex:["'I like carrots,' says {nm:npc}."],
  },    
]

Each entry in the array is itself a dictionary, with these attributes.

Attribute Required Type Comment
ex yes array of strings The exchange between the player and the NPC
responses no array of dictionaries See below
fn no function Run after the exchange is printed, passed the character as a parameter
noResponse no function Run after the next turn if the player does not choose one of the responses

The responses are themselves an array of dictionaries, with these attributes.

Attribute Required Type Comment
title yes string This is what will appear in the list of verbs
ex yes array of strings The exchange between the player and the NPC
fn no function Run after the exchange is printed, passed the character as a parameter

Note that you can use "npc" with text processor directives - this will refer to the NPC. This allows you to potentially use the same topics for numerous characters.

You can tie this into the reactions system very easily. This simple example has a single reaction that fires when the player first meets the NPC, and will call the same chat function. Now the NPC takes the initiative, and will ask the player how he is, and will expect the player to reply.

  reactions:[
    {
      name:'greeting',
      test:function() { return true },
      action:function(c) { 
        chat(c, 'basic', 0)
      },
    },
  ],

We could add it to agendas too, by adding this agenda item (in code.js). It will wait until the player is here, then present the topic.

agenda.chat = function(c, arr) {
  if (!c.isHere()) return false
  chat(c, arr[0], parseInt(arr[1]))
  return true;
}

The agenda for the NPC might look like this:

  agenda:[
    "text:Lara looks like she want to say something.",
    "chat:basic:0",
  ],

You could then assign a new agenda to the NPC, depending on how the player responded.

talkTopics.basic = [
  {
    ex:["'How is it going?' you ask {nm:npc}.", "'Good,' {pv:npc:reply}. 'How are you?'"],
    responses:[
      {title:"Good", ex:["'Good,'you say", "'That's nice'"], fn:function(c) { c.agenda = happyAgenda}},
      {title:"So so", ex:["'Hmm, so-so, I guess'", "'That's not great'"], fn:function(c) { c.agenda = cheerUpAgenda}},
      {title:"Bad", ex:["'Bad.'", "'That's sad'"], fn:function(c) { c.agenda = cheerUpAgenda}},
    ],
    noResponse:function(c) { c.agenda = sulkAgenda },
  },
]