NPCs: Conversations: Say - ThePix/QuestJS GitHub Wiki

A superficially simple approach is to allow the user to type SAY and whatever phrase she wants.

SAY HELLO

That is easy to implement, but getting it to trigger something in game is not so simple. The first issue is who responds if there are a crowd of NPCs present? The built-in SAY command decides this by going through everything in the game, and running its sayCanHear function if it has one. By default, all NPCs have such a function that will return true if the NPC is in the same room as the player (but you could modify it, say for an NPC so he is also included if he is in the next room and the player shouts).

Next Quest will see who has the highest sayPriority. Whoever is highest gets the chance to respond first, with their sayResponse function. If this returns true, we are done. Otherwise, we pass the honour on to the next NPC. What this means is that we try each NPC present until we find one who wants to respond to the phrase.

The next issue is responding to a phrase. The player could type anything; how do we handle that? We cannot. We can only handle a few basics and hope to fake it.

Responses are held in an array for each NPC called "sayResponses". Each entry is itself a dictionary, with a "regex" that will be matched against what the user typed, a "response", a function that runs if there is a match, and an optional "id" (this will be remembered when the response is used, and the system will know not to use it again).

Here is an example for an NPC, Lara, with just one response, which will be used if the user types SAY HI or SAY HELLO.

    sayResponses:[
      {
        regex:/^(hi|hello)$/,
        id:"hello",
        script:function() {
          msg("'Oh, hello there,' replies Lara.")
        },
      }
    ],

Asking a question

We can modify the above to have an NPC ask a question. The player can then choose to SAY an answer, and the NPC will assume that is a response to the question.

We use the "askQuestion" function attribute of an NPC, and pass it the name of the question object.

    sayResponses:[
      {
        regex:/^(hi|hello)$/,
        id:"hello",
        script:function() {
          msg("'Oh, hello there,' replies Lara.")
          if (w.Kyle.isHere()) {
            msg("'Have you two met before?' asks Kyle.")
            w.Kyle.askQuestion("kyle_question")
          }
        },
      }
    ],

It does not matter if Kyle has his own "sayResponses", the question will take priority for the next five turns (modify settings.turnsQuestionsLast in settings.js to change that). What we do need is to create a Question object. To do that, use util.createQuestion. It needs to be given a name, and a list of possible responses. There are only two in this example, but you can have dozens if the question is open-ended. Each answer has a "regex" to match against, and a "response" to run when it matches:

util.createQuestion("kyle_question", [
  {
    regex:/^(yes)$/,
    script:function() {
      msg("'Oh, cool,' says Kyle.");
    },
  },
  {
    regex:/^(no)$/,
    script:function() {
      msg("'Oh, well, Lara, this is Tester, he or she is testing Quest 6,' says Kyle.")
    },
  },
])

You can add a default by including a response that has no regex. This must be at the end, as Quest checks them in order, and goes with the first it finds that matches. Here the above question has been modified so if the player says anything other than "yes" or "no", Kyle will complain, and the question is asked again. This does not guarantee the player will give an answer; he is not obliged to say anything within the five turn limit.

util.createQuestion("kyle_question", [
  {
    regex:/^(yes)$/,
    script:function() {
      msg("'Oh, cool,' says Kyle.");
    },
  },
  {
    regex:/^(no)$/,
    script:function() {
      msg("'Oh, well, Lara, this is Tester, he or she is testing Quest 6,' says Kyle.")
    },
  },
  {
    script:function() {
      msg("'I don't know what that means,' says Kyle. 'It's a simple yes-no question.'")
      w.Kyle.askQuestion("kyle_question")
    },
  },
])  

If you ask the user for the player's name, you need to respond to any text at all. That means just the one response, using the "text" attribute of the sent parameters (the "char" attribute will have the NPC object).

util.createQuestion("name_question", [
  {
    response:function(p) {
      player.alias = sentenceCase(p.text.toLowerCase())
      msg("'Good to meet you, " + player.alias + ".'")
    },
  },
])

By default, Quest will first report that the player has said whatever, then will check for responses. If you set settings.givePlayerAskTellMsg to false in setting.js, your response can report what the player said, which may be better as it can tell the player what the game understand the player to have said.

The example above, could be modified to include the player talking:

util.createQuestion("name_question", [
  {
    response:function(p) {
      player.alias = sentenceCase(p.text.toLowerCase())
      msg("'My name is " + game.player.alias + ".'")
      msg("'Good to meet you, " + player.alias + ".'")
    },
  },
])

Further options

You can pass other options when creating a question - they will all get added as attributes. Not sure how usefdul that will be in general, but three have a specific use. If there is an "afterScript" function that will be run after the player responds, whatever the response. If there is an "expiredScript" function that will be run after the turns have passed and the question has expired. You can also set "countdown" to the number of turns to wait.

util.createQuestion("kyle_question", [
  {
    regex:/^(yes)$/,
    script:function() {
      msg("'Oh, cool,' says Kyle.");
    },
  },
  {
    regex:/^(no)$/,
    script:function() {
      msg("'Oh, well, Lara, this is Tester, he or she is testing Quest 6,' says Kyle.")
    },
  },
  {
    script:function() {
      msg("'I don't know what that means,' says Kyle. 'It's a simple yes-no question.'")
      w.Kyle.askQuestion("kyle_question")
    },
  },
], {
  afterScript:function() {
    msg("Kyle looks around excitedly")
  },
  expiredScript:function() {
    msg("'Well, you can keep it secret if you like,' sighs Kyle. 'I suppose.'")
  },
})