Some questions about 'show menu'
Curt A. P.
17 Mar 2018, 17:10Is 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?
Curt A. P.
17 Mar 2018, 17:16Just found this thread.
hegemonkhan
17 Mar 2018, 17:22there'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
Curt A. P.
17 Mar 2018, 21:59Popup 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!
hegemonkhan
18 Mar 2018, 13:55if 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>
Curt A. P.
18 Mar 2018, 15:34if 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.
mrangel
19 Mar 2018, 14:18OK ... 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 modifiesgame.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 callClearMenu
from within the callback, you also need to callMenuCancelledCallback(null)
. - The callback will get the parameters:
options
- a dictionary of options the player didn't choose frominput
- what the player actually enteredcommand
- 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, usinglinkcolour
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 beoption
's name if it's an object, andoption
otherwise
- String -
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.
- Returns a string containing all the HTML for a clickable link menu option. You need to use
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.
- Returns a list of options on a single line, generated using
ShowMenu
- Same parameters as the default one. This will call
StartShowMenu
, then either AddMenuNumberedList, AddMenuBulletList, or AddMenuFlatList (depending on the value ofgame.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.
- Same parameters as the default one. This will call
Is there anything else I should add before I share this rather ugly code?
mrangel
19 Mar 2018, 16:25(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)
mrangel
19 Mar 2018, 16:28OK, 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>
mrangel
19 Mar 2018, 18:28(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)