Handling ask in situations

In my game, I'd like the player to be able to ask an npc using the "ask [npc] about [topic]"
However, the trouble I have is when the player asks the question when the npc has a certain flag. I want, if the flag is true, to produce an output.
Let's say the npc is asleep; if the player asks the npc a question, I want it to respond with "They are sleeping".
I can do the tedious time of having each question check if the npc has the flag, but I'd prefer to simplify this by having it check in... say, the attributes tab.

Again, by not using the if function in each ask answer.

In short, if the npc is asleep, I want it to respond with "They're sleeping". If they aren't sleeping, I want it to respond with the actual answer. How do i get the game to recognize the ask command on this character and the flag?


I'm also interested in hearing a good way to handle such situations. The workaround I've used recently is to have two npcs and flip between them, but looking back that feels worse than putting a condition on each response.


Hmm… I think I see two ways of handling this. Both off the top of my head, so may or may not work.

Click to expand:

Better option - desktop editor only The best way of handling this would probably be modifying the function which handles Ask/Tell a little. If you have the desktop editor, you could change it like this (off the top of my head):
  <function name="DoAskTell" parameters="object, text, property, defaultscript, defaulttemplate">
    <![CDATA[
    handled = false
    maxstrength = 0
    match = null
    text = LCase(text)
    if (not HasAttribute (game, "text_processor_variables")) {
      game.text_processor_variables = NewDictionary()
    }
    DictionaryAdd (game.text_processor_variables, "npc", object)
    DictionaryAdd (game.text_processor_variables, "topic", text)
    if (HasScript (game.pov, "canttalk")) {
      do (game.pov, "canttalk", QuickParams ("topic", text, "npc", object))
      handled = true
    }
    else if (HasString (game.pov, "canttalk")) {
      msg (game.pov.canttalk.)
      handled = true
    }
    else if (HasScript (object, "canttalk")) {
      do (object, "canttalk", QuickParams ("topic", text, "npc", object))
      handled = true
    }
    else if (HasString (object, "canttalk")) {
      msg (object.canttalk)
      handled = true
    }
    else if (GetBoolean (game.pov, "canttalk") or GetBoolean (object, "canttalk")) {
      // use the default text
      handled = false
    }
    else if (TypeOf(object, property) = "scriptdictionary") {
      dictionary = GetAttribute(object, property)
      foreach (keywords, dictionary) {
        strength = GetKeywordsMatchStrength(LCase(keywords), text)
        if (strength >= maxstrength and strength>0) {
          match = ScriptDictionaryItem(dictionary, keywords)
          maxstrength = strength
        }
      }
      if (not match = null) {
        parameters = NewObjectDictionary()
        dictionary add(parameters, "this", object)
        invoke (match, parameters)
        handled = true
      }
    }
    if (not handled) {
      if (HasScript(object, defaultscript)) {
        d = NewDictionary()
        dictionary add(d, "text", text)
        do (object, defaultscript, d)
      } else {
        msg (DynamicTemplate(defaulttemplate, object))
      }
    }
    ]]>
  </function>

This allows you to give either the NPC or the player an attribute "canttalk" which will prevent them talking.

For example, you could do:

dave.canttalk = "Dave isn't going to tell you anything about {topic} while he's asleep."

and this will override all the ask/tell functions.
When he wakes up, you would just do:

dave.canttalk = null

You can set canttalk to be a script (which will be run), a text string (which will be printed), or true (in which case it will do the default behaviour as if the NPC doesn't have anything to say about that.

And if the player is gagged or something so they can't talk, in the same way you could do:

player.canttalk = "You try talking to {object:npc} but {npc.gender} can't hear you."

or…

Alternate option - ugly workaround You could temporarily remove a character's conversation topics when they fall asleep, by moving them to a different attribute.

So, when they fall asleep, you could do:

defaultscript => {
  msg ("Dave can't talk right now, he's asleep.")
}
foreach (attr, Split("ask;tell;askto;tellto")) {
  set (dave, "backup_" + attr, GetAttribute(dave, attr))
  set (dave, "backup_" + attr + "default", GetAttribute(dave, attr + "default"))
  set (dave, attr, false)
  set (dave, attr + "default", defaultscript)
}

and then to wake him up again, you would do:

foreach (attr, Split("ask;tell;askto;tellto")) {
  set (dave, attr, GetAttribute(dave, "backup_" + attr))
  set (dave, attr + "default", GetAttribute(dave, "backup_" + attr + "default"))
}

Ah, I see! Thank you very much


Thanks mrangel. I was helping someone who had to use the online editor and unfortunately it doesn't seem to allow built-in functions to be replaced? The 'ugly' version is still an improvement!


Mr. angel! those collapsable menus are so cool! How did you make them?


Mr. angel! those collapsable menus are so cool! How did you make them?

You mean for the code snippets? They're details/summary blocks. Like this:

<details><summary>Click to expand</summary>
This text will be hidden until you click on it
</details>

Although I usually make the link underlined and change the cursor, so people recognise it's clickable. And in this case, I put borders around them to separate the two options from each other. So:

<details style="border: 1px solid green">
<summary style="cursor: pointer; text-decoration: underline">Click to expand</summary>
This text will be hidden until you click on it
</details>

Worth noting, however, that some forum-specific markup (like ```) doesn't work on the first line of the contents, and sometimes on the line after an expanded block.


...'ugly' solution can be made less ugly by putting the code sections into functions so that they can be used for multiple npcs, i.e.

function ask_off (npc, response)
npc.off_response = response
defaultscript => {
  msg (this.off_response)
}
foreach (attr, Split("ask;tell;askto;tellto")) {
  set (npc, "backup_" + attr, GetAttribute(npc, attr))
  set (npc, "backup_" + attr + "default", GetAttribute(npc, attr + "default"))
  set (npc, attr, false)
  set (npc, attr + "default", defaultscript)
}

and

function ask_on (npc)
foreach (attr, Split("ask;tell;askto;tellto")) {
  set (npc, attr, GetAttribute(npc, "backup_" + attr))
  set (npc, attr + "default", GetAttribute(npc, "backup_" + attr + "default"))
}

with calls

ask_off (dave, "Dave can't talk right now, he's asleep.")
ask_on (dave)

'ugly' solution can be made less ugly by putting the code sections into functions so that they can be used for multiple npcs

Yep, I thought about including that.

Another point of ugliness with it would be that if you have a script somewhere which adds responses to an NPC (for example, if they learn about something new), you would have to make it check whether they are asleep or not, and add it to backup_ask rather than ask.

However, I realised that there's a slightly less clunky way to do the same thing.
You can't edit the core functions in the web editor, but you can change commands at runtime. So on the web editor, you could put this in your start script:

asktellhandler => {
  if (not HasAttribute (game, "text_processor_variables")) {
    game.text_processor_variables = NewDictionary()
  }
  DictionaryAdd (game.text_processor_variables, "object", object)
  DictionaryAdd (game.text_processor_variables, "npc", object)
  DictionaryAdd (game.text_processor_variables, "text", text)
  DictionaryAdd (game.text_processor_variables, "topic", text)
  if (HasScript (game.pov, "canttalk")) {
    do (game.pov, "canttalk", QuickParams ("topic", text, "npc", object))
  }
  else if (HasString (game.pov, "canttalk")) {
    msg (game.pov.canttalk)
  }
  else if (HasScript (object, "canttalk")) {
    do (object, "canttalk", QuickParams ("topic", text, "npc", object))
  }
  else if (HasString (object, "canttalk")) {
    msg (object.canttalk)
  }
  else {
    if (GetBoolean (game.pov, "canttalk") or GetBoolean (object, "canttalk")) {
      override = "disabled_"
    }
    else {
      override = ""
    }
    switch (this.name) {
      case ("tellto", "alttellto") {
        DoAskTell (object, text, override + "tellto", "telltodefault", "DefaultTellTo")
      }
      default {
        DoAskTell (object, text, override + this.name, this.name + "default", "Default" + CapFirst (this.name))
      }
    }
  }
}
ask.script = asktellhandler
tell.script = asktellhandler
tellto.script = asktellhandler
alttellto.script = asktellhandler

Then you've got the same donttalk attribute as in the first solution.
In this case, you can also create an alternate question dictionary named disabled_ask, disabled_tell, in case you want to have some questions you can still ask an NPC when they/you are asleep.


Interesting mrangel! I'm away from home at the moment but will try it out when I get back.


...tried out the your code in the start script mrangel but the editor complained about three lines ("Failed to load script"). These are marked with "->>".

  else if (HasString (game.pov, "canttalk")) {
->>    msg (game.pov, "canttalk")
  }
  else if (HasScript (object, "canttalk")) {
    do (object, "canttalk", QuickParams ("topic", text, "npc", object))
  }
  else if (HasString (object, "canttalk")) {
->>    msg (object, "canttalk")
  }
  else {
    if (GetBoolean (game.pov, "canttalk") or GetBoolean (object, "canttalk")) {
      override = "disabled_"
    }
    else {
      override = ""
    }
    switch (this.name) {
      case ("tellto", "alttellto") {
        DoAskTell (object, text, override + "tellto", "telltodefault", "DefaultTellTo")
      }
      default {
        DoAskTell (object, text, override + this.name, this.name + "default", "Default" + CapFirst (this.name))
      }
    }
  }
}
->> ask.script = asktellhandler

OK, my brain must have been on strike when I wrote that. Sorry, there shouldn't be two parameters to msg.

Editing above to fix that


Sorry mrangel, there is a third line causing a problem ask.script = asktellhandler.


Oh, wonderful! Weird editor bug.

Try changing that line to:

set (ask, "script", asktellhandler)

Because although the core code includes an object named ask (a command), a line of code starting with ask is assumed to be referring to the function ask. This is why you're not allowed to have an object and a function with the same name… except that you can if one of them is built-in and the other is provided by the core library.


Yup! that's now working. I did the same with tell, tellto and alttellto. Job done!


Yup! that's now working. I did the same with tell, tellto and alttellto. Job done!

The others shouldn't need changing; the problem is specifically with commands whose name is a reserved word.

(The editor won't actually allow you to create a command/verb/object named foreach or msg, but if you use a text editor to add them to a library it causes the same issue with not being able to do objectname.attributename = value on them)


The others shouldn't need changing; the problem is specifically with commands whose name is a reserved word.

Yes, but it looks tidier to have them all in the same form ;)


I should thank you, too :p

I've been trying to put together a pure javascript port of the Quest engine, to reduce the reliance on the server. But avoiding this bug would involve a lot of work.

If Quest doesn't avoid this bug, I guess I don't need to either.


Log in to post a reply.

Support

Forums