Here we are again, folks!
Here's my attempt at overriding some of the core functions so that ShowMenu gives you more flexibility.
Sadly untested, due to not having a windows machine on which to run the desktop version of quest. So there may be typos in here. Would appreciate if anyone can tell me if this works; or wrap these functions in a library to make them easier to use.
Main features:
game.alwaysshowmenunumbers
- boolean. If true, menu options that aren't in a numbered list will have a number in parentheses displayed after them.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.StartShowMenu (caption)
- Starts a menu, and displays the caption.
EndShowMenu (allowCancel, hideAfterMenu, 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)
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)
.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)
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.
result
in the callback will be the stringresult
in the callback will be the object's nameresult
in the callback will be option
's name if it's an object, and option
otherwisedisplayNumber
- 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)
options
- I think this should handle an options list in any of the formats ShowMenu supportsAddMenuBulletList (options, bullet)
game.alwaysshowmenunumbers
is set, will add bracketed numbers after each optionbullet
- string, to display before each option. If ""
, will return a HTML <ul>
element instead.AddMenuFlatList (options, lastjoiner)
FormatList
.lastjoiner
- you probably want "[Or]"
or "[And]"
here.ShowMenu
StartShowMenu
, then either AddMenuNumberedList, AddMenuBulletList, or AddMenuFlatList (depending on the value of game.showmenutype
), then EndShowMenu.<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()
}
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))
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, input)
return (handled)
}
else if (GetBoolean(game, "showmenuunresolvedcommand")) {
if (HasScript(game, "unresolvedcommandhandler")) {
game.unresolvedcommandbackup = game.unresolvedcommandhandler
}
SetTurnTimeout(0) {
if (HasScript (showmenu_data_object, "menucancelcallback")) {
params = NewDictionary()
dictionary add (params, "options", showmenu_data_object.menudisplayedoptions)
dictionary add (params, "input", game.pov.currentcommand)
dictionary add (params, "command", game.pov.currentcommandpattern)
do (showmenu_data_object, "menucancelcallback", params)
}
ClearMenu()
if (HasScript(game, "unresolvedcommandbackup")) {
game.unresolvedcommandhandler = game.unresolvedcommandbackup
game.unresolvedcommandbackup = null
}
}
game.unresolvedcommandhandler => {
handled = HandleMenuTextHarder (showmenu_data_object, command)
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="menu, input">
possibilities = NewStringList()
options = menu.menudisplayedoptions
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])
ClearMenu()
return (true)
}
else if (HasScript(menu, "menucancelcallback")) {
params = NewDictionary()
dictionary add (params, "options", options)
dictionary add (params, "input", input)
do (menu, "menucancelcallback", params)
if (GetBoolean(game, "menuallowcancel")) {
ClearMenu()
}
}
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
}
// If we're running an unresolved command script to check player input against menu options
// we need to move all the menu data into a new object
// to ensure that it doesn't screw up if the player entered a command that creates a new menu
//
// But destroy it the next time ClearMenu is called
if (not GetObject("showmenu_data_object") = null) {
destroy ("showmenu_data_object")
}
foreach (attr, Split("menuoptionskeys;menudisplayedoptions;menuoutputsection;menucallback;menuallowcancel;menucancelcallback")) {
if (HasAttribute(game, attr)) {
if (GetBoolean(game, "showmenuunresolvedcommand")) {
if (GetObject("showmenu_data_object") = null) {
create ("showmenu_data_object")
}
set (showmenu_data_object, attr, GetAttribute(game, attr))
}
set (game, attr, null)
}
}
</function>
I personally like using numbers for input (and like using typed-in input), as much as possible / whenever possible, as it's the least amount of typing possible, to help with arithritis, carpal tunnel syndrome, etc stuff, lol, and it's also easy and fast too.
So, I'm trying to create my own menu system using numbers for typed-in input (and thus menu coding), though I'm still fumbling my way through it, as I'm not quite at your level, mrangel, lol.
anyways, awesome code/library thread/post, as I'll be studying it for help/ideas for my own menu system (crediting you of course as/if I use any of it: concrete code or concept/design ideas), hehe
ShowMenu allows you to type in the numbers, which is pretty neat.
I just tweaked it so that it allows you to click the option or type the number or type the start of the option text.
(as well as allowing some different stylistic choices)
This isn't very neat code, and I wouldn't be surprised if there's still errors in there.
Awesome coding as always, like the names of some of your attributes too.
Being a bit silly here, so please forgive me.
alwaysshowmenunumbers - always show me nun umbers - I do like the look of an umber on a nun too.
showmenutryharder - show me nutry harder - Can't work out if that's a nut-tree or you're just boasting.
Seriously though, good piece of coding.
Awesome :D I love this Mr.Angel, especially customizing different messages that can be printed if the player cancels the menu or something. So cool! It definitely would be great too to have the option of turning on or off the numbered lists. Man, I wish I was talented >.<
Anonynn.