ShowMenu - Actually entering the text?


Forgewright
25 Feb 2018, 07:33I 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
mrangel
25 Feb 2018, 09:39Here'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)
mrangel
25 Feb 2018, 09:55I 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.
hegemonkhan
25 Feb 2018, 11:15two simple ways of doing it:
- using (optionally: a String List, and) a String Dictionary (pseudo: "1"="rock";"2"="rap";"3="disco")
- 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)
mrangel
25 Feb 2018, 12:41EDIT: 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.
mrangel
25 Feb 2018, 14:04@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.
mrangel
25 Feb 2018, 14:15[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.
25 Feb 2018, 14:27Your 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!
mrangel
25 Feb 2018, 14:37It'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.
25 Feb 2018, 17:20Forgewright,
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
mrangel
25 Feb 2018, 21:27I'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.