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?
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)
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.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)