Jump to content

MessageBox, Dialog and GetInput enhancement script


matejcik

Recommended Posts

This script changes the way Message Boxes, Inputs and Dialogs work in a cartridge.

 

terminology:

MessageBox means "show a message to the player" - the thing where you can specify names of buttons and add callback scripts

Dialog means "show a series of dialog messages to the player" - you can write multiple messages at once, but can't rename buttons and add scripts for the buttons

GetInput means "get input from player" - the function that puts an Input that you created on the screen.

 

Usually, if you call any of those, the previous one is automatically cancelled. That means that if you do the following in a script:

MessageBox ("hello and welcome to this zone!")
MessageBox ("i hope your journey went well.")
GetInput (zinputHowWasYourJourney)

then the player won't see the first two MessageBoxes, because they will be immediately cancelled - the first one by the second one, and the second one by the GetInput.

If you want to show a long series of messages ("do some talking") and then ask a question, your only choice would be to put only one MessageBox in the original script, and put every other MessageBox into the button script of the previous one.

Which is a pain to do.

 

The following Author Script changes the behavior.

When you put a Dialog in the script, it's contents will not be shown immediately. Instead, the contents will be stored internally ("buffered"). Then, when you put a stand-alone MessageBox or GetInput into the script, the Dialog will be started.

Example:

Dialog ("hello and welcome to this zone!" .... "i hope your journey went well.")
GetInput (zinputHowWasYourJourney)

In unmodified cartridge, this would mean that the GetInput cancels the Dialog and the player won't see it.

When using the author script, this code will do exactly what you want - show two messages, and after the player reads them, ask a question.

 

You can stack as many Dialogs as you want in a single script. For example, you can use one Dialog to start the "conversation", use two more in an if-then-else clause (one for each branch), and finish it with a common ending in another Dialog.

Then, when you want to show the contents of that Dialog, you have to do one of the following:

a) finish the sequence with a MessageBox or a GetInput

B) put one more "dialog message" into the last dialog and leave it empty.

 

the author script

just copy&paste the following code into your Author Script editor in the Builder.

it is self-contained, so you should be able to put any other author script above or below it.

originalFns = {Dialog = Wherigo.Dialog, MessageBox = Wherigo.MessageBox, GetInput = Wherigo.GetInput}
dialogStrings = {}
require "table"

function rundialog ()
 local ds = dialogStrings
 dialogStrings = {}
 local cb = {}
 cb = function(thing)
if thing ~= nil then
  local item = table.remove(ds,1)
  if item ~= nil then
	if item.Callback == nil then item.Callback = cb end
	originalFns.MessageBox(item)
  end
end
 end
 cb(1)
end

function Wherigo.Dialog (tbl)
 for k,v in ipairs(tbl) do
table.insert(dialogStrings, v)
 end
 if #dialogStrings > 0 and dialogStrings[#dialogStrings].Text == nil and dialogStrings[#dialogStrings].Media == nil then
table.remove(dialogStrings)
rundialog()
 end
end

function Wherigo.MessageBox(tbl)
 table.insert(dialogStrings, tbl)
 rundialog()
end

function Wherigo.GetInput (input)
 if #dialogStrings == 0 then
originalFns.GetInput(input)
 else
dialogStrings[#dialogStrings].Callback = function(thing) originalFns.GetInput(input) end
rundialog()
 end
end

 

Edited by Ranger Fox per request.

Edited by Ranger Fox
Link to comment

I found the function a few days before and I like it.

 

I looked at the code and think, if you could change the Dialog function, you need not a MessageBox.

 

function Wherigo.Dialog (tbl)
 if tbl ~= nil and tbl[1] ~= nil and tbl[1].Text ~= nil then
for k,v in ipairs(tbl) do
  table.insert(dialogStrings, v)
end
 else
if dialogStrings ~= nil and #dialogStrings > 0 then 
  tbl = {Text = table.remove(dialogStrings).Text,Buttons = {"Ok",}}
  Wherigo.MessageBox(tbl)
end
 end
end

 

You could start displaying the messages with an empty Dialog (No Text). In code it would be

 

Wherigo.Dialog{{},}

.

 

Best regards.

Dirk

Link to comment

sure, but there's no point in doing that, it doesn't increase the amount of "sense" the builder code would make.

however, it might be good to allow an empty messagebox to start the sequence. idea being that you don't have to specialcase the last message of the dialog, and still retain the logic where "dialog stores, messagebox runs"

 

i was also thinking of automatically building wrappers ... something along the lines of:

function wrapper(func)
 return function() 
  enableBuffering()
  func()
  replayBuffer()
 end
end

and then have a loop automatically wrap all event handlers.

but that just might fail spectacularly if the user tried to do something weird (for example, launch two messageboxes with callbacks)

Link to comment

After a night of sleep I found a better way for my problem.

 

If you want only make a series of dialogs this wouldn't work with your aproach. You should make a MessageBox at the end. If you would us the following code

 

function Wherigo.Dialog (tbl)
 for k,v in ipairs(tbl) do
table.insert(dialogStrings, v)
 end
 if dialogStrings ~= nil and #dialogStrings > 0 and dialogStrings[#dialogStrings].Text == nil then
table.remove(dialogStrings)
if #dialogStrings > 0 then
  tbl = {Text = table.remove(dialogStrings).Text,Buttons = {"Ok",}}
  Wherigo.MessageBox(tbl)
end
 end
end

 

You could make a series of dialogs with the builder and the last line leave empty. In the builder you would see [[No Text]] and in the cartridge the dialogs want be shown. There is no need of an extra MessageBox.

 

Better?

Link to comment

I have another idea for this functions: if you have all text of Dialogs, MessageBox and Input you could replace text.

 

I expanded the function, so you can write for exapmle "Hello [Player.Name], how are you?" will expanded on screen to "Hello Builder, how are you?". All varaibles which are standing between "[" and "]" are expanded. Even all functions. You could write "The result is [25*2]." and it will expanded to "The result is 50." Or if you have timer ztimerTest you could show a dialog with text "You have [ztimerTest.Remaining] seconds left. Hurry up". Or you can even call your one functions if the result is a string. And the best: you must not leave the builder, all could be done inside normal Dialogs, MessageBoxes and Inputs.

 

I didn't test the input function (time is to short ;-).

 

Here is the code

 

function Wherigo.Dialog (tbl)
 for k,v in ipairs(tbl) do
table.insert(dialogStrings, v)
 end
 if dialogStrings ~= nil and #dialogStrings > 0 and dialogStrings[#dialogStrings].Text == nil then
table.remove(dialogStrings)
if #dialogStrings > 0 then
  tbl = {Text = table.remove(dialogStrings).Text,Buttons = {"Ok",}}
  Wherigo.MessageBox(tbl)
end
 end
end

function Wherigo.MessageBox(tbl)
 local ds = dialogStrings
 dialogStrings = {}
 local cb = {}
 cb = function(thing)
if thing ~= [[Button1]] then
  if tbl.Callback ~= nil then
	tbl.Callback(thing)
  end
else
  local item = table.remove(ds,1)
  if item == nil then
	tbl = MessageBoxReplace(tbl)
	originalFns.MessageBox(tbl)
  else
	item.Callback = cb
	item = MessageBoxReplace(item)
	originalFns.MessageBox(item)
  end
end
 end
 cb([[Button1]])
end

function Wherigo.GetInput (input)
 local item = table.remove(dialogStrings)
 input = MessageBoxReplace(input)
 item.Callback = function(thing) originalFns.GetInput(input) end
 item = MessageBoxReplace(item)
 Wherigo.MessageBox(item)
end

function MessageBoxReplace(tbl)
 if tbl.Text ~= nil then
tbl.Text = string.gsub(tbl.Text, "(%b[])", function (x) 
											 x = "return " .. string.sub(x, 2, -2) 
											 local f = loadstring(x) 
											 return f() 
											 end)
 end
 return tbl
end

Link to comment

After a night of sleep I found a better way for my problem.

 

(snip)

 

You could make a series of dialogs with the builder and the last line leave empty. In the builder you would see [[No Text]] and in the cartridge the dialogs want be shown. There is no need of an extra MessageBox.

 

Better?

yes, much better.

i'll put something like that into the script listing, i can still edit it now :e)

 

I expanded the function, so you can write for exapmle "Hello [Player.Name], how are you?" (snip)
this is not necessary. you can write: [[Hello, ]] .. Player.Name .. [[, how are you?]] in the builder and it will use that as code, not as string. go check it out ;e)
Link to comment

matejcik, you are right. You could use each variable or function you want in the builder if you use [[]].. ..[[]]. I didn't know this.

 

Thanks.

 

But couldn't we use it for a translation on the fly? You could have for each language an own textfile wie the translations. In the builder you use only say "[text01]" and the functions read the text for "text01" from the textfile? For Dialogs and so one it is no problem, but for Names and Descriptions. I try this in the next days.

 

Best regards.

Edited by charlenni
Link to comment
But couldn't we use it for a translation on the fly? You could have for each language an own textfile wie the translations. In the builder you use only say "[text01]" and the functions read the text for "text01" from the textfile? For Dialogs and so one it is no problem, but for Names and Descriptions. I try this in the next days.

this is another thing i've been wondering about ... do you have proof that you can open files that aren't "media" from the cartridge? in emulator, it might work, because its working directory can be the same as the source directory, but unless i'm mistaken, you can't put separate files (Lua includes, translation files etc) into the cartridge itself.

and if you can, i'd love to see such cartridge in its compiled form

Link to comment

You are right! We couldn't use extern files.

 

It's terrible. You couldn't work with extern files. Now I have rewriten the code, so the files with the translations are medias. You have to medias with name say langEnglish and langDeutsch. Each of that medias contain one resource which ist a textfile in the format"Ident=Text" for all text you want translate. At each point you would be translated you write "{Ident}" (works for all objects defined in builder and all Dialog and MessageBoxes). At start you call a function which looks for all languages in the medias and than show a input, which will to be used. Now this media will be read which you choose. After that, all text in objects will be transfered. Dialogs and MessageBoxes will be transfered on the fly. All text is embedded in the gwc file.

 

Now the problemes:

1. Normal input problem (should be the last call in Cartridge:OnStart())

2. I didn't get the choice of the input. The variable is empty. I need help in programaticaly create an input.

3. On the Colorado the access to the textfile didn't work (I get a memorycard error)

 

I think, Garmin didn't have implemented the io-library :) I will only need io.open, io.lines and io.close. But if they implement it, we can also make a library for making images on the fly ;-)

 

If you like to see the code, I could send it to you. But there are no comments in the code ;-)

Link to comment

yup, send me the code :e) sorry to disappoint you, but i still don't think that it will work. if i understand it correctly, the io library accesses the -external- filesystem, you'd have to find and parse the gwc file from lua.

i'd love to be mistaken on this, though :e)

 

i'll write a more detailed answer later, unless somebody beats me to it.

 

for now, why don't you write translations into the lua code as a table?

Link to comment

My builder "Earwigo" (someone please think of a better name...) supports multilingual cartridges in a way which is simpler than this, but probably less powerful. You just pick a separator string (I use '@@') and when you enter a text into the builder, you write it in each language, separated by that string.

 

So for example a message text might be "Hello@@Bonjour@@Guten Tag". Then when you generate the Lua file (which is a separate step), you tell it to make language version 0 or 1 or 2. If you don't provide a translation for a given string for language N, it defaults to language 0, so "Hello@@Bonjour" would give "Hello" for language 2, and a text with no translation separator string is the same in every language.

 

The on-the-fly approach is how Earwigo works for its internal multilingual capability (ie, the Javascript/PHP code of the builder has no text, only tags which are looked up at runtime). Maybe a future version will implement this for cartridges too, but the current approach is perhaps easier to understand, and doesn't need a separate data structure to be maintained.

Link to comment

Here is the code.

 

It works all fine except the io functions on the colorado and the input of the first screen (selection of the language). It is hardwired to "english.txt". If you want to change it to "deutsch.txt" you hade to change line 181. It works also in the emulator without the text-files in the same directory. You only need the gwc-file.

 

I think, garmin "forgot" to implement the io functions ;-)

DialogTest.zip

Link to comment

The disadvantage of my approch is: it didn't work.

 

The advantage is: you could select with the frist screen of the cartridge which language you want. And, if it's work (io on colorado), you could also make images on the fly (assumed you have a Lua jpg-library). Also in larger projects sometimes it is better, if you could give all text to someone, who could translate it.

 

You could name your builder "WherIBuild" as against "WherISwear" for the original one ;-)

Link to comment

The disadvantage of my approch is: it didn't work.

so, if you put your object library as a lua file inside the cartridge and "require" it, that does work on colorado?

 

No, it didn't work. I get an "memorycard reading error". With the emulator extern *.lua-files work.

 

If I put the modul in the author script section, I get an error, if I try an io-operation. And the problem with the selfmade input are further exists.

Link to comment

now this is very very interesting.

but if the io library operates on files contained within the cartridge, there's no reason garmin would "forget to implement" it, they would just take Groundspeak's implementation ... very weird indeed.

let me do some tests with this.

 

I have made a small Lua-file for testing. It has only one media (txt-file). If you start the cartridge with the emulator, you should see the abc in a MessageBox and in the Lua debug of the emulator. If you start it on the garmin, you will get an "Unexpected error / Closing cartridge".

IOTest.zip

Link to comment

Now I have a solution for one of my two problems: the input of the language. Also I have added the translation of text for EmptyYouSeeListText, EmptyTasksListText and EmptyInventoryListText. So you could choose your language at the begining of the cartridge.

 

So the translate modul works fine except it would not run on colorados (and on oregons as well) because of the io problem. If this is solved, it is ready to use.

DialogTest.zip

Link to comment

Now I have a solution for one of my two problems: the input of the language. Also I have added the translation of text for EmptyYouSeeListText, EmptyTasksListText and EmptyInventoryListText. So you could choose your language at the begining of the cartridge.

 

So the translate modul works fine except it would not run on colorados (and on oregons as well) because of the io problem. If this is solved, it is ready to use.

That's an innovative way to do things. I don't know, though, if Garmin will ever allow IO. They'd need a heck of a push for that and perhaps a promising return on investment.

 

Were I translating my cartridge to different languages, I may have elected to create several arrays, one array each to hold all translations for one text string.

Link to comment

Thanks a lot for this excellent Author Script.

 

I have a problem when showing several dialog boxes and a message box (or only a message box). If the player doesn't confirm the windows by clicking on "ok" and the next dialog and/or message boxes appear, the cartridge crashes.

 

This happens in the Emulator as well as with the Oregon.

 

Example: the player enters a zone - a message box appears - the player is sleeping and doing nothing - he is leaving the zone (for example by mistake) - the next message box appears --- that's the end - the cartridge crashes: "Lua Error ... :934: attempt to index global 'tbl' (a nil value)".

 

Any idea?

 

Thanks, mutle

Link to comment

right, that makes sense. and the original code didn't. how i came up with it, i'll never know...

 

try to replace rundialog function with this:

function rundialog ()
 local ds = dialogStrings
 dialogStrings = {}
 local cb = {}
 cb = function(thing)
if thing ~= nil then
  local item = table.remove(ds,1)
  if item ~= nil then
	if item.Callback == nil then item.Callback = cb end
	originalFns.MessageBox(item)
  end
end
 end
 cb(1)
end

 

i didn't test it, but unless i did a syntax error, it should fix your issue.

if it does, please report back and i'll try to convince RangerFox to edit my original post ;e)

Link to comment

First tests in the emulator worked very well - many many thanks to matejcik!!!

This helps a lot and it will make the cartridge much more stable.

 

The behaviour is like that:

when there is a message or dialog box displayed and not yet confirmed (ok-button) and a new dialog or message box appears (for example "When the player enters a zone"), then the new one will be shown and the fist ones are canceled.

 

Best regards

mutle

Link to comment

function zitemThing:OnHelpMe()
 Dialog("Erste Dialogmessage", "Zweite Dialogmessage")
 MessageBox("Und hier die letzte Message")
end

 

In emulator I'm getting the following error message: 'attempt to call global 'Dialog' (a nil value)'

 

Of course i pasted the author script at the right place in the lua file.

 

How is the correct usage of this cool code? Thx for answering.

Link to comment

you have to use the "Wherigo" prefix:

function zitemThing:OnHelpMe()
 Wherigo.Dialog("Erste Dialogmessage", "Zweite Dialogmessage")
 Wherigo.MessageBox("Und hier die letzte Message")
end

This is how Builder generates the code.

 

Or you can add these two lines to your author script:

Dialog = Wherigo.Dialog
MessageBox = Wherigo.MessageBox

then your code will work unchanged

Edited by matejcik
Link to comment

also, the way you are calling it, it will not work. you need to do it this way:

Wherigo.Dialog{ {Text="Erste message"}, {Text = "Zweite message"} }
Wherigo.MessageBox{Text="Letzte message"}

or if you just need simple things like this one, do not write the code in AuthorScript and just use Builder's "message box" and "series of messages" commands.

Link to comment

no, i'm afraid you think wrong.

 

can you give an example in pseudo-code?

something like:

1. say "Hello and welcome to this zone."

2. if (you are here for the first time) say "I see this is your first time here."

else say "I see you've been here before, glad to have you back."

3. say "Do you want to buy a nice woolen sweater?"

4. ask question: "Buy sweater? yes/no"

something like this?

 

if that is what you are trying to do, you don't have to write this in author script. you only copy-paste my author script into your cartridge, and then you create this code in the builder normally

Link to comment

The pattern of your example is perfect. That's what I'm intending to do.

 

Sorry if I'm nerding around here. Just for my understanding:

 

If I paste your code in the author script part of the file and i use MessageBox etc. in the builder like I always used to, your code will already come to effect without any further change in code (can't give it a try at moment)?

Link to comment

Hi,

 

am also using Urwigo, does this work in this builder I am having troubles getting the screen sequencing right:

 

introduction text

question

if yes

another question

if yes

messages

else

action

 

can you help?

 

Best regards,

 

Gotthard (jGda)

Link to comment

I have used this code for the first time today and all went well using the both the Groundspeak emulator, and the WebWigo emulator, it also worked fine in the field on a Garmin Oregon 450, however the iPhone 5 running the Wherigo app only showed the last message in the sequence, none of the dialog messages were shown.

 

Has an update to the iPhone app meant this script no longer works within it?

Link to comment

I have used this code for the first time today and all went well using the both the Groundspeak emulator, and the WebWigo emulator, it also worked fine in the field on a Garmin Oregon 450, however the iPhone 5 running the Wherigo app only showed the last message in the sequence, none of the dialog messages were shown.

 

Has an update to the iPhone app meant this script no longer works within it?

Is there any further information on what might be stopping this script working with iPhone? Is it still a problem or have subsequent software updates resolved the issue?

 

I only have Android so I'm not able to test on iPhone and wondered if I needed to account for this.

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...