ShowMenu - Actually entering the text?

K.V.

Is there a way to do this?

For instance:

Please choose one of the following:
1. rock
2. rap
3. disco

I want to allow the player to be able to enter:

> rock


I know when you say something like take rock and there are two rocks, Quest will ask 'Please choose which rock you mean' and show a numbered menu. You can enter 1 or 2 or click on the rock you want. I'm assuming this is the same situation you are describing.

Maybe a look at that script will give you a clue. Then again one of the Quest prodigies will answer soon enough.
Seems a Get Input would be needed after the menu is shown, but you know this already and I;m home for lunch and just wanted to feel like I did something helpful and constructive. Nyuck Nyuck


Here's a first attempt off the top of my head. Three functions to override; not tested yet…

  <function name="ShowMenu" parameters="caption, options, allowCancel, callback">
    <![CDATA[
    outputsection = StartNewOutputSection()
    msg (caption)
    count = 0
    game.menuoptionskeys = NewStringList()
    game.menudisplayedoptions = NewStringDictionary()
    foreach (option, options) {
      count = count + 1
      if (TypeOf(options) = "stringdictionary") {
        optionText = StringDictionaryItem(options, option)
        optiontag = option
        style = GetCurrentLinkTextFormat()
        list add (game.menuoptionskeys, option)
      }
      else if (TypeOf(option) = "string") {
        optionText = option
        optiontag = option
        style = GetCurrentLinkTextFormat()
        list add (game.menuoptionskeys, option)
      }
      else if (TypeOf(option) = "object") {
        optionText = GetDisplayAlias(option)
        optiontag = option.name
        colour = ""
        if (HasString(option, "linkcolour") and GetUIOption("UseGameColours") = "true") {
          colour = option.linkcolour
        }
        else {
          colour = GetLinkTextColour()
        }
        style = GetCurrentTextFormat(colour)
        list add (game.menuoptionskeys, option.name)
      }
      else {
        error ("ShowMenu cannot handle a " + TypeOf(option))
      }
      dictionary add (game.menudisplayedoptions, optiontag, optionText)
      msg (count + ": <a class=\"cmdlink\" style=\"" + style + "\" onclick=\"ASLEvent('ShowMenuResponse','" + EscapeQuotes(optiontag) + "')\">" + optionText + "</a>")
    }
    EndOutputSection (outputsection)
    game.menuoptions = options
    game.menuallowcancel = allowCancel
    game.menucallback = callback
    game.menuoutputsection = outputsection
  ]]>
  </function>

  <function name="HandleMenuTextResponse" parameters="input" type="boolean">
    <![CDATA[
    handled = false
    if (IsInt(input)) {
      number = ToInt(input)
      if (number > 0 and number <= ListCount(game.menuoptionskeys)) {
        handled = true
        ShowMenuResponse(StringListItem(game.menuoptionskeys, number - 1))
      }
    }
    else if(HasAttribute(game, "menudisplayedoptions")) {
      foreach (option, game.menudisplayedoptions) {
        if (LCase(Trim(DictionaryItem(game.menudisplayedoptions, option))) = LCase(Trim(input))) {
          ShowMenuResponse(option)
          return (true)
        }
      }
    }
    return (handled)
    ]]>
  </function>

  <function name="ClearMenu">
    HideOutputSection(game.menuoutputsection)
    game.menuoutputsection = null
    game.menuoptions = null
    game.menudisplayedoptions = null
    game.menucallback = null
  </function>

(note that if two items have identical names, this will match the first one)


I was going to do something like this:

else if(HasAttribute(game, "menudisplayedoptions")) {
  possibilities = ListCompact(game.menuoptions)
  foreach (word, Split(LCase(input), " ")) {
    stillpossible = NewStringList()
    foreach (option, possibilities) {
      words_in_option = Split(LCase(StringDictionaryItem(game.menudisplayedoptions, option)), " ")
      found = false
      foreach (w, words_in_option) {
        if (StartsWith(w, word)) {
          found = true
        }
      }
      if (found) {
        list add (stillpossible, option)
      }
    }
    possibilities = stillpossible
  }
  if (ListCount (possibilities) = 1) {
    ShowMenuResponse(possibilities[0])
    handled = true
  }
}

That should trigger if there is exactly one menu option that contains every word the player typed (if they type the whole word or the start of the word); roughly similar to the way object identification works. But if the player just types "e", did they want to select the only menu option starting with an 'e'? Or did they want to go east?

This function could be fine tuned; but I'm not sure how a player would expect it to behave in that case.


two simple ways of doing it:

  1. using (optionally: a String List, and) a String Dictionary (pseudo: "1"="rock";"2"="rap";"3="disco")
  2. using basic 'if' Script (pseudo): if (pseudo: result = "1" or result = "rock"); else if (result = "2" or result = "rap"); else if (result = "3" or result = "disco")

(The corners of HK's lips rise up for just a fraction of a second, too fast for anyone to notice HK's quick brief knowing smirk/grin)


EDIT: This doesn't work. ClearMenu is called before commands are parsed. Though you could have HandleMenuTextResponse make a copy of the text and those attributes somewhere, and a turnscript to clear them away afterwards.

I suppose if you wanted it to only accept typed words if they don't match another command, you could modify ShowMenu and ClearMenu as above, then create a command with the pattern #text# (so it will only be called if no other command matches); and make it:

handled = false
if(HasAttribute(game, "menudisplayedoptions")) {
  possibilities = ListCompact(game.menuoptions)
  foreach (word, Split(LCase(text), " ")) {
    stillpossible = NewStringList()
    foreach (option, possibilities) {
      words_in_option = Split(LCase(StringDictionaryItem(game.menudisplayedoptions, option)), " ")
      found = false
      foreach (w, words_in_option) {
        if (StartsWith(w, word)) {
          found = true
        }
      }
      if (found) {
        list add (stillpossible, option)
      }
    }
    possibilities = stillpossible
  }
  if (ListCount (possibilities) = 1) {
    ShowMenuResponse(possibilities[0])
    handled = true
  }
}
if (not handled) {
  msg ("[UnrecognisedCommand]")
}

In the case where the menu can't be ignored, you'd want HandleMenuTextCommand to do all the checks itself.


@HK

Using a dictionary (or a list) is pretty obvious; but you seem to have missed the point of the question. I think the hardest part of this one was finding the right function to override, to get at the text entered by the player in response to a ShowMenu call.


[further comment - I added a couple of lines to the functions that are in my CoreFunctions.aslx. If any of those functions have changed in the latest version, I assume you'd have to merge the changes]


K.V.

Your first post was the solution, mrangel.

I only needed to change DictionaryItem to StringDictionaryItem in ShowMenuResponse():

if (LCase(Trim(StringDictionaryItem(game.menudisplayedoptions, option))) = LCase(Trim(input))) {

Thank you!


It's not perfect. I mean, if you're being asked "Did you mean: Red lorry / yellow lorry"; then typing "red" should be sufficient.

But when it's the disambiguation menu, "Did you mean: Heavy box, wooden box, open box", and the player responds with "> Open box" … what should we assume they mean?


K.V.

Forgewright,

I had tried a homemade menu followed by a GetInput() in a function which had the working title ShowGetMenuInput(), but it wasn't living up to its expectations.

(So we had a similar theory concerning this at one point.)


HK,

You answered what I asked correctly.

...but mrangel (somehow) understood what I really meant (even though I didn't word my question very well).


mrangel,

I saw your first post and immediately knew it would work, and I directly opened Quest to test it out. (I was so excited to finally see it in action, I honestly didn't read anything else which had been posted after that.)

It worked, and I finished up the part of the game I into which made me want to have this work in the first place. (Had to finish writing that bit, else I would have forgotten where I was headed with it.)

Then, I added a line to Ask() and an else if to HandleMenuTextResponse() to allow "y" or "n" when running Ask().

This is what I've got at the moment (it's your code; I barely changed anything), and I haven't been able to break it yet:

  <function name="Ask" parameters="question, callback">
    game.asking = true
    options = NewStringList()
    list add (options, "Yes")
    list add (options, "No")
    game.askcallback = callback
    ShowMenu (question, options, false) {
      parameters = NewDictionary()
      if (result = "Yes") {
        boolresult = true
      }
      else {
        boolresult = false
      }
      dictionary add (parameters, "result", boolresult)
      callback = game.askcallback
      game.askcallback = null
      invoke (callback, parameters)
    }
  </function>
  <function name="ShowMenu" parameters="caption, options, allowCancel, callback"><![CDATA[
    outputsection = StartNewOutputSection()
    msg (caption)
    count = 0
    game.menuoptionskeys = NewStringList()
    game.menudisplayedoptions = NewStringDictionary()
    foreach (option, options) {
      count = count + 1
      if (TypeOf(options) = "stringdictionary") {
        optionText = StringDictionaryItem(options, option)
        optiontag = option
        style = GetCurrentLinkTextFormat()
        list add (game.menuoptionskeys, option)
      }
      else if (TypeOf(option) = "string") {
        optionText = option
        optiontag = option
        style = GetCurrentLinkTextFormat()
        list add (game.menuoptionskeys, option)
      }
      else if (TypeOf(option) = "object") {
        optionText = GetDisplayAlias(option)
        optiontag = option.name
        colour = ""
        if (HasString(option, "linkcolour") and GetUIOption("UseGameColours") = "true") {
          colour = option.linkcolour
        }
        else {
          colour = GetLinkTextColour()
        }
        style = GetCurrentTextFormat(colour)
        list add (game.menuoptionskeys, option.name)
      }
      else {
        error ("ShowMenu cannot handle a " + TypeOf(option))
      }
      dictionary add (game.menudisplayedoptions, optiontag, optionText)
      msg (count + ": <a class=\"cmdlink\" style=\"" + style + "\" onclick=\"ASLEvent('ShowMenuResponse','" + EscapeQuotes(optiontag) + "')\">" + optionText + "</a>")
    }
    EndOutputSection (outputsection)
    game.menuoptions = options
    game.menuallowcancel = allowCancel
    game.menucallback = callback
    game.menuoutputsection = outputsection
  ]]></function>
  <function name="HandleMenuTextResponse" parameters="input" type="boolean"><![CDATA[
    handled = false
    if (IsInt(input)) {
      number = ToInt(input)
      if (number > 0 and number <= ListCount(game.menuoptionskeys)) {
        handled = true
        ShowMenuResponse (StringListItem(game.menuoptionskeys, number - 1))
      }
    }
    else if (HasAttribute(game, "menudisplayedoptions")) {
      foreach (option, game.menudisplayedoptions) {
        opt = LCase(Trim(StringDictionaryItem(game.menudisplayedoptions, option)))
        answer = LCase(Trim(input))
        if (opt = answer) {
          ShowMenuResponse (option)
          return (true)
        }
        else if (GetBoolean(game,"asking")) {
          if (answer = "y") {
            ShowMenuResponse ("Yes")
            game.asking = false
            return (true)
          }
          else if (answer = "n") {
            ShowMenuResponse ("No")
            game.asking = false
            return (true)
          }
        }
      }
    }
    return (handled)
  ]]></function>
  <function name="ClearMenu">
    HideOutputSection (game.menuoutputsection)
    game.menuoutputsection = null
    game.menuoptions = null
    game.menudisplayedoptions = null
    game.menucallback = null
  </function>

The disambiguation menu:

I've often wished I could enter "red" or "yellow" in such scenarios many a time, myself.

It works that way with Inform.

...and, if it's possible to do something with Inform, it can be done with Quest!


Could we start the script off with a check on the values in options, and, if they all contain the same word, remove that word from each, then remove that word from result before trimming it?

if (game.pov.currentcommandmultiobjectpending){
  // Do stuff to allow the responses "red" or "yellow"
}
// Run the actual script

I've often wished I could enter "red" or "yellow" in such scenarios many a time, myself.
It works that way with Inform.
...and, if it's possible to do something with Inform, it can be done with Quest!

Yep ... my second post was (hopefully) the code to do it.

I didn't include that in the first post because I wasn't sure how it should handle the case where the user enters something that is both one of the menu options, and a valid command.


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums