Some questions about 'show menu'

Is it possible to create menu options without numbers?

Is it possible to create menu options without starting a new line?
Or having a menu option inside normal text?


Just found this thread.


there's also the built-in 'DisplayList' Script/Function (though creating your own menu in how you want it is best): http://docs.textadventures.co.uk/quest/functions/corelibrary/displaylist.html

DisplayList (NAME_OF_LIST, true) // the 'true' Boolean Value will cause the displayed list items to be numbered
DisplayList (NAME_OF_LIST, false) // the 'false' Boolean Value will cause the displayed list items to be NOT numbered


there's a few Scripts/Functions that have popup window and in-line (in the big text box as hyperlinks) menus:

'show menu (XXX)' (popup window) vs 'ShowMenu (XXX)' (in-line/hyperlinks)
'ask (XXX)' (popup window) vs 'Ask (XXX)' (in-line/hyperlinks)

and there's probably some more that I'm forgetting right now, lol


Popup sounds interesting ...

My problem with DisplayList in the past was the fact I haven't figured out, if possible, how to get a response to run a script on click. Like you click on a menu option and baddabadammm... Script!


if you want the 'verb' drop downs on hyperlink click, then you can use the 'object' text processor command (and create/have that Object you're using in the text processor command, of course):

msg ("{object:NAME_OF_OBJECT}")


here's an example:

<game name="example_game">
  <attr name="start" type="script">
    msg ("{object:example_ball_object:ball}")
  </attr>
</game>

<object name="example_ball_object">
  <attr name="kick" type="script">
    msg ("You kick the ball")
  </attr>
</object>

<verb>
  <property>kick</property>
  <pattern>kick</pattern>
  <defaultexpression>You can't kick that!</defaultexpression>
</verb>

if you want a script to run on hyperlink click, then you can use the 'command' text processor command (and create/have that Command you're using in the text processor, of course):

msg ("{command:NAME_OF_COMMAND}")


here's an example:

<game name="example_game">
  <attr name="start" type="script">
    msg ("{command:example_help_command:help}")
  </attr>
</game>

<command name="example_help_command">
  <pattern>help</pattern>
  <script>
    msg ("When you need help, you can always type in: help")
  </script>
</command>

if you want a script to run on hyperlink click, then you can use the 'command' text processor command (and create/have that Command you're using in the text processor, of course):

msg ("{command:NAME_OF_COMMAND}")

That's very helpful. Thanks.

Why do you add ':help' in the text processor?

Edit:
Okay I got it. This prints the command as any text you want.


OK ... I've rolled the code from the previous post you linked, and an earlier one on a similar subject, together.

I've got some code (not yet tested) that adds a few more attributes you can change on the game object to change the behaviour of ShowMenu:

  • game.alwaysshowmenunumbers - boolean. Setting to false will make the numbers disappear.
  • game.showmenutype - string. Can be "number", "bullet", "flat", or some other string which will be used as a bullet when ShowMenu is called. ("flat" displays the menu as a single line using FormatList).
  • game.hidemenus - boolean. If set to false, choosing a menu option will remove the links, but won't actually vanish the menu.
  • game.showmenutryharder - boolean. If true, typing a word that appears in exactly one of the menu options will choose that option; or the start of a word (like when typing object names).
  • game.showmenuunresolvedcommand - boolean. As above, but this behaviour will be pushed into the unresolved command handler, so that it only happens if the word isn't also a command. This may cause bugs if you have a command which modifies game.unresolvedcommandhandler during play.

There are also functions you can use to get finer-grained control, outputting a menu in pieces:

  • StartShowMenu (caption)
    • Starts a menu, and displays the caption. If you do this more than once in the same menu, subsequent ones will just print the caption.
  • EndShowMenu (allowCancel, hideAfterMenu, callback)
    • Marks the end of the menu, and sets the callback.
    • hideAfterMenu - boolean. If true, everything between StartShowMenu and EndShowMenu will be hidden once an option is chosen. If false, the links will be disabled but remain on screen.
  • MenuCancelledCallback (script)
    • Set a script to be run if the menu is not called.
    • Note that this is a misleading function name, as I couldn't think of a better one.
    • In the case of a menu with allowCancel set to false, this will be run each time the player enters something that can't be parsed as a valid choice. If you call ClearMenu from within the callback, you also need to call MenuCancelledCallback(null).
    • The callback will get the parameters:
      • options - a dictionary of options the player didn't choose from
      • input - what the player actually entered
      • command - if applicable, the command that was executed instead of choosing a menu option (note that in this case, the callback will be run after the command)
  • AddMenuOption (option, result, displayNumber)
    • Returns a string containing all the HTML for a clickable link menu option. You need to use msg() to pass this to the player.
    • option - The option to display. Calls GetDisplayAlias() if it's an object, using linkcolour if set, and ToString() for any other type that isn't a string.
    • result - The value that the 'result' variable will be set to in the callback.
      • String - result in the callback will be the string
      • Object - result in the callback will be the object's name
      • Anything else - result in the callback will be option's name if it's an object, and option otherwise
    • displayNumber - boolean. If true, will allow the player to select the option by typing its number. Will display like "Would you like Red (1), Blue (2), or Yellow (3)?". If this is a format string instead, ! will be replaced by the option, and # by the number.
  • AddMenuNumberedList (options)
    • Returns the HTML for a numbered list, like the original ShowMenu displays. You can modify this string if you want before outputting it.
    • options - I think this should handle an options list in any of the formats ShowMenu supports
  • AddMenuBulletList (options, bullet)
    • Returns a bulleted list; as above, but without numbering the options
    • If game.alwaysshowmenunumbers is set, will add bracketed numbers after each option
    • bullet - string, to display before each option. If "", will return a HTML <ul> element instead.
  • AddMenuFlatList (options, lastjoiner)
    • Returns a list of options on a single line, generated using FormatList.
    • lastjoiner - you probably want "[Or]" or "[And]" here.
  • ShowMenu
    • Same parameters as the default one. This will call StartShowMenu, then either AddMenuNumberedList, AddMenuBulletList, or AddMenuFlatList (depending on the value of game.showmenutype), then EndShowMenu.
    • I've attempted to ensure that if none of the game attributes mentioned above are set, this will behave exactly the same as the standard ShowMenu function.

Is there anything else I should add before I share this rather ugly code?


(A silly example that came to mind for the menu-cancelled callback; if it's a conversation menu, you could have something like:

MenuCancelledCallback () {
  if (not IsDefined("command")) {
    // This is called if the player types something the parser can't understand
    msg ("Bob taps his foot impatiently as you drift off into your own thoughts, then gives up and goes back to his work. Maybe you should try {command:talk to bob:talking to him again} and hope he's not too upset?")
  }
  else if (command = go) {
    // could also test game.pov.parent, to see if you've actually moved or just walked into a wall/locked door/etc
    msg ("You hear Bob's voice call after you “Hey, don't walk off while I'm talking, jerk!”")
  }
  else if (ListCount(game.pov.currentcommandresolvedobjects) > 0) {
    msg ("“Hey!” Bob mutters angrily, “Stop messing 'round with that " + GetDisplayAlias(PickOneObject(game.pov.currentcommandresolvedobjects)) + "! You gotta pay attention when somebody's talking to you!”")
    msg ("He turns away from you and goes back to reading the paper.")
  }
  else {
    msg ("Bob snorts angrily as you ignore him, and goes back to reading the paper.")
  }
}

This is off the top of my head, but you get the idea)


OK, here's the code. Again, untested, may contain typos (or I may have missed a CDATA block), as I don't have a Windows machine handy to test it. But hopefully fewer errors this time.

Edit: Slight change (improvement to linkcolour handling)

By default it should behave just like the standard ShowMenu, except that typing the text from one of the menu options will select it.

<function name="StartShowMenu" parameters="caption">
  if (not HasString(game, "menuoutputsection")) {
    game.menuoutputsection = StartNewOutputSection()
  }
  msg (caption)
  game.menuoptionskeys = NewStringList()
  game.menudisplayedoptions = NewStringDictionary()
  game.menucallback => {
    error ("Menu callback not set")
  }
</function>

<function name="MenuCancelledCallback" parameters="callback">
  game.menucancelcallback = callback
</function>

<function name="AddMenuOption" type="string" parameters="option, result, displayNumber">
  <![CDATA[
  if (not HasString(game, "menuoutputsection")) {
    StartShowMenu("")
  }
  if (TypeOf(option) = "object") {
    optionText = GetDisplayAlias(option)
    optionTag = option.name
    if (HasString(option, "linkcolour") and GetUIOption("UseGameColours") = "true") {
      colour = option.linkcolour
    }
    else {
      colour = GetLinkTextColour()
      if (TypeOf(result) = "object") {
        if (HasString(result, "linkcolour") and GetUIOption("UseGameColours") = "true") {
          colour = result.linkcolour
        }
      }
    }
    style = GetCurrentTextFormat(colour)
  }
  else if (TypeOf(option) = "string") {
    optionText = option
    optionTag = option
    style = GetCurrentLinkTextFormat()
  }
  else {
    optionText = ToString(option)
    optionTag = optionText
    style = ""
  }
  if (IsDefined("result")) {
    if (TypeOf(result) = "string") {
      if (LengthOf(result) > 0) {
        optionTag = result
      }
    }
    else if (TypeOf(result) = "object") {
      optionTag = result.name
    }
  }
  result = "<a class=\"cmdlink\" style=\"" + style + "\" onclick=\"ASLEvent('ShowMenuResponse','" + EscapeQuotes(optionTag) + "')\">" + optionText + "</a>"
  dictionary add (game.menudisplayedoptions, optionTag, optionText)
  if (TypeOf(displayNumber) = "string") {
    if (IndexOf (displayNumber, "!") > 0) {
      result = Replace (displayNumber, "!", result)
    }
    else {
      result = displayNumber + " " + result
    }
    if (IndexOf (result, "#") > 0) {
      list add (game.menuoptionskeys, optionTag)
      result = Replace (displaynumber, "#", ListCount(game.menuoptionskeys))
    }
  }
  else if (Equal(displayNumber, true) or GetBoolean(game, "alwaysshowmenunumbers")) {
    list add (game.menuoptionskeys, optionTag)
    result = result + " (" + ListCount(game.menuoptionskeys) + ")"
  }
  return (result)
  ]]>
</function>

<function name="EndShowMenu" parameters="allowCancel, hideAfterMenu, callback">
  if (not HasString(game, "menuoutputsection")) {
    error("Menu not started")
  }
  EndOutputSection (game.menuoutputsection)
  game.menuallowcancel = allowCancel
  game.menucallback = callback
  game.menuhideafter = hideAfterMenu
</function>

<function name="AddMenuNumberedList" parameters="options">
<![CDATA[
  if (TypeOf(options) = "object") {
    options = GetDirectChildren(options)
  }
  else if (TypeOf(options) = "string") {
    options = Split(options)
  }

  result = NewStringList()
  foreach (o, options) {
    optionText = o
    if (EndsWith(TypeOf(o), "dictionary")) {
      optionText = DictionaryItem(options, o)
    }
    list add (result, AddMenuOption(optionText, o, "#. "))
  }
  return (Join (result, "<br/>"))
]]>
</function>

<function name="AddMenuBulletList" parameters="options, bullet">
<![CDATA[
  if (not TypeOf(bullet) = "string") {
    bullet = ""
  }
  if (TypeOf(options) = "object") {
    options = GetDirectChildren(options)
  }
  else if (TypeOf(options) = "string") {
    options = Split(options)
  }

  result = NewStringList()
  foreach (o, options) {
    optionText = o
    if (EndsWith(TypeOf(o), "dictionary")) {
      optionText = DictionaryItem(options, o)
    }
    list add (result, bullet + AddMenuOption(optionText, o, false))
  }
  if (bullet = "") {
    return ("<ul><li>" + Join(result, "</li>\n<li>") + "</li></ul>\n")
  }
  else {
    return (Join (result, "<br/>"))
  }
]]>
</function>

<function name="AddMenuFlatList" parameters="options, lastjoiner">
<![CDATA[
  if (TypeOf(options) = "object") {
    options = GetDirectChildren(options)
  }
  else if (TypeOf(options) = "string") {
    options = Split(options)
  }

  result = NewStringList()
  foreach (o, options) {
    optionText = o
    if (EndsWith(TypeOf(o), "dictionary")) {
      optionText = DictionaryItem(options, o)
    }
    list add (result, "· " + AddMenuOption(optionText, o, false))
  }
  return (FormatList(result, ",", lastjoiner, ""))
]]>
</function>

<function name="ShowMenu" parameters="caption, options, allowCancel, callback">
  StartShowMenu(caption)
  type = "number"
  if (Equal (game.alwaysshowmenunumbers, false)) {
    type = "bullet"
  }
  if (HasString (game, "showmenutype")) {
    type = LCase(game.showmenutype)
  }
  if (type = "bullet") {
    msg (AddMenuBulletList(options), "")
  }
  else if (type = "flat") {
    msg (AddMenuFlatList(options, "[Or]"))
  }
  else if (type = "number") {
    msg (AddMenuNumberedList(options))
  }
  else {
    msg (AddMenuBulletList(options), type)
  }
  if (not HasBoolean(game, "hidemenus")) {
    game.hidemenus = true
  }
  EndShowMenu(allowCancel, game.hidemenus, callback)
</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)) {
      ShowMenuResponse(StringListItem(game.menuoptionskeys, number - 1))
      if(HasAttribute(game, "menudisplayedoptions")) {
        game.menudisplayedoptions = null
      }
      game.menucancelcallback = null
      return (true)
    }
  }
  else if(HasAttribute(game, "menudisplayedoptions")) {
    foreach (option, game.menudisplayedoptions) {
      if (LCase(Trim(StringDictionaryItem(game.menudisplayedoptions, option))) = LCase(Trim(input))) {
        ShowMenuResponse(option)
        game.menucancelcallback = null
        return (true)
      }
    }
    if (GetBoolean(game, "showmenutryharder") or not GetBoolean(game, "menuallowcancel")) {
      handled = HandleMenuTextHarder (game.menudisplayedoptions, input, "menucancelcallback")
      game.menudisplayedoptions = null
      return (handled)
    }
    else if (GetBoolean(game, "showmenuunresolvedcommand")) {
      if (HasScript(game, "unresolvedcommandhandler")) {
        game.unresolvedcommandbackup = game.unresolvedcommandhandler
      }
      if (HasScript(game, "menucancelcallback")) {
        game.menucancelcallbackdelayed = game.menucancelcallback
        game.menucancelcallback = null
      }
      SetTurnTimeout(0) {
        if (HasScript (game, "menucancelcallbackdelayed")) {
          params = NewDictionary()
          dictionary add (params, "options", game.menuoptionsforsecondtry)
          dictionary add (params, "input", game.pov.currentcommand)
          dictionary add (params, "command", game.pov.currentcommandpattern)
          do (game, "menucancelcallbackdelayed", params)
          game.menucancelcallbackdelayed = null
        }
        game.menuoptionsforsecondtry = null
        if (HasScript(game, "unresolvedcommandbackup")) {
          game.unresolvedcommandhandler = game.unresolvedcommandbackup
          game.unresolvedcommandbackup = null
        }
      }
      game.unresolvedcommandhandler => {
        handled = HandleMenuTextHarder (game.menuoptionsforsecondtry, command, "menucancelcallbackdelayed")
        if (HasScript(game, "unresolvedcommandbackup")) {
          game.unresolvedcommandhandler = game.unresolvedcommandbackup
          game.unresolvedcommandbackup = null
        }
        if (not handled) {
          if (HasScript(game, "unresolvedcommandhandler")) {
            params = NewDictionary()
            dictionary add(params, "command", command)
            do (game, "unresolvedcommandhandler", params)
          } else {
            msg (Template("UnrecognisedCommand"))
          }
        }
      }
    }
  }
  return (false)
  ]]>
</function>

<function name="HandleMenuTextHarder" parameters="options, input, cancelcallback">
  possibilities = NewStringList()
  foreach (option, options) {
    list add (possibilities, option)
  }
  foreach (word, Split(LCase(input), " ")) {
    stillpossible = NewStringList()
    foreach (option, possibilities) {
      words_in_option = Split(LCase(StringDictionaryItem(options, 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])
    set (game, cancelcallback, null)
    return (true)
  }
  else if (HasScript(game, cancelcallback)) {
    params = NewDictionary()
    dictionary add (params, "options", options)
    dictionary add (params, "input", input)
    do (game, cancelcallback, params)
    if (GetBoolean(game, "menuallowcancel")) {
      set (game, cancelcallback, null)
    }
  }
  return (false)
</function>
  
<function name="ClearMenu">
  if (HasString(game, "menuoutputsection")) {
    if (GetBoolean(game, "menuhideafter")) {
      HideOutputSection(game.menuoutputsection)
    }
    else {
      JS.eval("name = '"+game.menuoutputsection+"';EndOutputSection(name);$('.' + name + ' .cmdlink').attr('onclick', '');")
    }
    game.menuoutputsection = null
  }
  game.menuoptions = null
  if (HasAttribute(game, "menudisplayedoptions")) {
    game.menuoptionsforsecondtry = game.menudisplayedoptions
    game.menudisplayedoptions = null
  }
  game.menucallback = null
</function>

(OK, I messed up there. That needs fixing, I know what to do, it'll just take a little more effort… I need to play with ClearMenu so that it backs up all of the menu attributes, in case TryHarder is called after the menu has been cleared, and double-check that nothing messes up if the callback function calls ShowMenu itself)


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

Support

Forums