MessageBox Tutorial
This article is believed to contain incorrect or misleading information. Please see the Talk page for details. If you can correct the article, please do so.
From some problems I've run into, the Centralizing your menu exits sub-tutorial should be considered as necessary, rather than optional, when using activators.
Intro
Most MessageBox mistakes stem from adapting a script that works in some cases, but not in more complex situations. To prevent this, this tutorial will show you how to make an all-purpose script that can be used and expanded for any situation. We'll start out with some of the basic mechanics of the MessageBox and related functions, followed by common mistakes in complex scripts, and then the all-purpose script and how to set it up. Finally, we'll go through how to use the script to easily move between multi-layered menus, and some extras that you can tack on to it.
In the middle of being rewritten
All of the information is still accurate (except for the above), and reasonably in order, however some of the information is redundant.
Basic Mechanics of MessageBox and Related Functions
Every menu requires two functions: MessageBox to display the menu, and GetButtonPressed to return which button the player pressed. Follow the links for more details, but here are the important things to remember:
GetButtonPressed
- GetButtonPressed returns numbers from –1 to 9:
- -1 means no decision has been made
- 0 means the player selected the first option
- 1 for the second
- ...
- 9 for the tenth(you can have 10 options at most)
- GetButtonPressed will only return the correct button press the first time it's called in a script; any other use of GetButtonPressed will return -1. For instance, if the player presses the first button, then in the following script
if (GetButtonPressed == -1) ... elseif (GetButtonPressed == 0)
GetButtonPressed will return 0 the first time, move on to the 'elseif' test, and return –1 the second time.
To take care of this, set a variable to GetButtonPressed, and test the variable instead, as such:
short Choice ... set Choice to GetButtonPressed if (Choice == -1) ... elseif (Choice == 0)
Likewise, GetButtonPressed will return -1 for all following frames (until the script runs another messagebox).
Timing
MessageBox takes one frame to display, so you can use any block to display it. However, it can take up to 15 frames before GetButtonPressed will return the player's button press (even if the player presses the button on the same frame as the MessageBox function). Therefore, it needs to be in a block that runs every frame, such as GameMode, MenuMode, and ScriptEffectUpdate. It also needs to be on a script that is running every frame. For objects this means it must be in a Loaded cell, for quests this means fQuestDelayTime must be set to .001 and they must be running, and for spells this means the duration must be long enough (more on that in the Spell Menus subsection).
Keep 'em separated
Putting it all together, so far we end up with a script like this:
short Choice ... messagebox "Your menu" "Button 0" ... "Button 9" set Choice to GetButtonPressed if (Choice == -1) ... elseif (Choice == 0) ...
The problem with this script – every time it repeats while waiting for GetButtonPressed to return the player's button press, the MessageBox will be displayed again. To prevent this, you need to use a variable to keep them separated, as such:
short Choosing short Choice ... if (Choosing == -1) messagebox "Your menu" "Button 0" ... "Button 9" set Choosing to 1 set Choice to GetButtonPressed elseif (Choosing == 1) set Choice to GetButtonPressed if (Choice == -1) ... elseif (Choice == 0) ...
Note that both halves are required for each menu. To help keep things organized, you can set one to a negative number, and the other to a positive number, as in the above script.
Avoiding Common Mistakes for More Complex Menus
You won't want your menu script to run all the time, so it needs a clear beginning and a clear ending. On activators you can use the onActivate block and use ReferenceEditorID.Activate player, 1 to start the menu:
begin onActivate messagebox "What would you like to do?" "Button 0" ... "Button 9" set Choosing to 1 end begin GameMode if (Choosing == 1) set Choice to GetButtonPressed ...
This works, but you will never be able to return to the first menu. To fix this, place the menu in the GameMode block. Set Choosing to -1 in the onActivate block to display the menu:
begin onActivate set Choosing to -1 end begin GameMode if (Choosing == -1) messagebox "What would you like to do?" "Button 0" ... "Button 9" elseif (Choosing == 1) set Choice to GetButtonPressed ...
To stop you can set Choosing back to 0. However, note that the GameMode block continues to run. To reduce the number of If tests run each frame use Return:
begin onActivate set Choosing to -1 end begin GameMode if (Choosing == 0) return elseif (Choosing == -1) ... set Choosing to 0 ;Whenever you want to exit the menu
Multiple menus require careful use of a governing variable to keep the menus separate. The most common error is to place the second menu within a button response, like this:
begin onActivate messagebox "What would you like to repair?" "Armor" ... "Weapons" end begin GameMode set Choice to GetButtonPressed if (Choice == -1) ... elseif (Choice == 0) ;Armor messagebox "Which armor would you like to repair?" "Helmet" ... "Boots" set Choice2 to GetButtonPressed if Choice2 == -1 ... elseif (Choice2 == 0) ;Helmet ... elseif (Choice2 == 9) ;Boots ... endif elseif (Choice == 9) ;Weapons messagebox "Which weapon would you like to repair?" "Blade" ... "Bow" ...
If the player selects "Armor" from the first menu, the second menu ("Which armor would you like to repair") will be displayed, but any choice the player makes will really be based on the first menu. For instance, if the player selects "Boots" in the second menu, the "Which weapons would you like to repair?" menu will be displayed.
Following the steps of the script, you can see why. The player will be shown the "What would you like to repair?" menu. They select "Armor". For a few frames GetButtonPressed sets Choice to -1. About 15 frames after the player makes their choice, GetButtonPressed will return 0. This will bring up the second menu "Which armor...?". Choice2 will be set to -1, as the player hasn't read it yet. The player selects "Boots". Again, there will be several frames between the menu, and when GetButtonPressed returns the player's choice. This means the script will be running from the beginning of the GameMode block again. This time, Choice is set to 9 (because "Boots" was the tenth button), and the "Which weapon...?" menu is opened.
To prevent this from happening, use the Choosing variable to keep menus separate:
begin onActivate set Choosing to -1 end begin GameMode if (Choosing == 0) return elseif (Choosing == -1) messagebox "What would you like to repair?" "Armor" ... "Weapons" set Choosing to 1 elseif (Choosing == 1) set Choice to GetButtonPressed if (Choice == -1) ... elseif (Choice == 0) ;Armor set Choosing to -10 ... elseif (Choice == 9) ;Weapons set Choosing to -11 endif elseif (Choosing == -10) ;Armor messagebox "Which armor would you like to repair?" "Helmet" ... "Boots" set Choosing to 10 elseif (Choosing == 10) set Choice to GetButtonPressed if (Choice == -1) ... elseif (Choice == 0) ;Helmet set Choosing to 0 ;To exit ... elseif (Choice == 9) ;Boots set Choosing to 0 ;To exit endif elseif (Choosing == -11) ;Weapon messagebox "Which weapon would you like to repair?" "Blade" ... "Blunt" set Choosing to 11 elseif (Choosing == 11) set Choice to GetButtonPressed if (Choice == -1) ... elseif (Choice == 0) ;Blade set Choosing to 0 ;To exit ... elseif (Choice == 9) ;Blunt set Choosing to 0 ;To exit endif endif end
Note that each menu has a pair of corresponding numbers: Main menu, -1/1; Armor menu, -10/10; Weapon menu, -11/11. When you set Choosing to the corresponding number, that menu will be shown. See a better example of this in the MessageBox Tutorial#Moving Between Multiple Menus.
Running the same choice for multiple frames
So far, we've set up the button catching section like this:
... elseif (Choosing == 1) set Choice to GetButtonPressed if (Choice == -1) return elseif (Choice == 0) ...
For 99% of what you'll do, this will work perfectly. However, let's say you need to scan through the player's inventory one item at a time:
... elseif (Choosing == -1) messagebox "What would you like to do?" "Duplicate everything" "Nothing" set Choosing to 1 set Choice to GetButtonPressed elseif (Choosing == 1) set Choice to GetButtonPressed if (Choice == -1) return elseif (Choice == 0) ;Duplicate items one by one from the player, into a container set InvPos to (InvPos + 1) set pInvObj to (player.GetInventoryObject pContainer.AddItem pInvObj 1 ... elseif (Choice == 1) ;Nothing set Choosing to 0 ;To exit endif ...
"Duplicate Everything" requires several frames to complete. Once GetButtonPressed returns the player's choice, it will start off and add the first item to the container. However, on the following frames GetButtonPressed will return -1, causing an infinite loop of 'if (Choice == -1) -> return'. To prevent this, only run GetButtonPressed when it had previously returned -1:
... elseif (Choosing == -1) messagebox "What would you like to do?" "Duplicate everything" "Nothing" set Choosing to 1 set Choice to -1 elseif (Choosing == 1) if (Choice == -1) set Choice to GetButtonPressed elseif (Choice == 0) ;Duplicate items one by one from the player, into a container set InvPos to (InvPos + 1) set pInvObj to (player.GetInventoryObject pContainer.AddItem pInvObj 1 ... elseif (Choice == 1) ;Nothing set Choosing to 0 ;To exit endif ...
Note that Choice is reset to -1 after displaying a menu. Otherwise, Choice would still be the same as for the last menu (let's say the player choose 0). This would not be -1, so GetButtonPressed would never be run, and that Choice (0) would run instead of the player's choice.
Creating Your New Menu
You'll need to set up some objects for the next script: an invisible activator, an XMarker, and your own cell:
Your own cell
- Scroll down the "Cell View" window
- Select "TestQuset01"
- Right-click it
- Select "Duplicate Cell"
- Rename your new cell to something you'll remember (and don't worry about the lack of floors, it'll work just fine)
XMarker
- Scroll down the "Object Window"
- Select Statics
- Scroll to the bottom
- Double-click your cell in the "Cell View" window to open it in the "Render Window"
- Drag the XMarker from the "Object Window" into the "Render Window"
- Right-click the red X (XMarker) in the "Render Window"
- Select edit.
- In the "Reference Editor ID" box, give it a name you'll remember (in these examples it will be "YourXMarker").
Activator
- Select an activator in the "Object Window"
- Edit the name
- Press enter (or select ok in the edit menu)
- Click "Yes" when it asks if you want to create a new item
- Drag your new activator into the "Render Window"
- Right-click it
- Give it a "Reference Editor ID"
- Mark it as "Persistent Reference" and "Initially Disabled"
- Place the following script on your new activator
- Make the following script
- In the "Object Window", right-click your new activator
- Select Edit
- In the "Script" pull-down box, select "YourMenuScript"
Activator Script
scn YourMenuScript Short Choosing Short Choice Begin onActivate Set Choosing to -1 If (GetInSameCell player == 0) ;always keep it near the player MoveTo player Endif End Begin GameMode If (Choosing == 0) ;meaning it shouldn't be running If (GetInSameCell YourXMarker == 0) MoveTo YourXMarker Endif Elseif (Choosing == -1) ;Display your menu Messagebox "Which option?" "First Option" "Second Option" ;... Set Choosing to 1 Set Choice to GetButtonPressed Return Elseif (Choosing == 1) ;Catch the player's decision If (Choice == -1) ;No choice yet Set Choice to GetButtonPressed Return Elseif (Choice == 0) ;First Option ;run your code for the first decision Set Choosing to 0 ;to finish up Elseif (Choice == 1) ;Second Option ;run your code for the second decision Set Choosing to 0 ;to finish up ;... ;Further illustrations of more options ; Elseif (Choice == #) ;Nth Option ;run your code for the nth decision ; Set Choosing to 0 ; Elseif (Choice == 9) ;Final/Tenth Option ;run your code for the tenth decision ; Set Choosing to 0 Endif Endif End
Ok, no games that time. You can start your menus from any script with YourActivatorsReferenceEditorID.Activate player, 1 and this script will do the rest.
Moving Between Multiple Menus
Not only will the above code work, but it makes multiple menus easy to do. Remember that each menu has two parts: the display of the menu and catching the player's decision. So, each menu can be broken into two numbers, a negative number (-1 in the example above) and a positive number (1 in the example above). Use different numbers for each menu, and whenever you want to move to a new menu, use set Choosing to -#. Here's several examples of menu switching: (also, please note that due to wiki limitations, the messageboxes below have been given line breaks, whereas in the CS they wouldn't have one)
Short Choosing Short Choice Begin onActivate Set Choosing to -1 If (GetInSameCell player == 0) MoveTo player Endif End Begin GameMode If (Choosing == 0) ;meaning it shouldn't be running If (GetInSameCell YourXMarker == 0) MoveTo YourXMarker Endif Elseif (Choosing == -1) ;Display your menu Messagebox "Would you like to donate gold or food?" "Gold" "Food" "Blood" "Cancel" Set Choosing to 1 Set Choice to GetButtonPressed Return Elseif (Choosing == 1) If (Choice == -1) ;No choice yet Set Choice to GetButtonPressed Return Elseif (Choice == 0) ;Gold Set Choosing to -2 ;to open the Gold menu Elseif (Choice == 1) ;Food Set Choosing to -3 ;to open the Food menu Elseif (Choice == 2) ;Blood Set Choosing to -4 ;to open the Blood menu Elseif (Choice == 3) ;Cancel Set Choosing to 0 ;to close the menus Endif Return Elseif (Choosing == -2) ;Gold menu Messagebox "How much Gold would you like to donate?" "25" "I've changed my mind" "I've changed my mind, I'll donate Food" "I've changed my mind, I'll donate Blood" "I've changed my mind, I won't donate anything" Set Choosing to 2 Set Choice to GetButtonPressed Return Elseif (Choosing == 2) If (Choice == -1) ;No choice yet Set Choice to GetButtonPressed Elseif (Choice == 0) ;25 If (player.GetGold > 25) Player.RemoveItem Gold001 25 Set Choosing to 0 Else Set Choosing to -99 ;a message that the player doesn't have enough Endif Elseif (Choice == 1) ; I've changed my mind Set Choosing to -1 ;to return to the opening menu Elseif (Choice == 2) ; I've changed my mind, I'll donate food Set Choosing to -3 ;to open the food menu Elseif (Choice == 3) ; I've changed my mind, I'll donate blood Set Choosing to -4 ;to open the blood menu Elseif (Choice == 4) ; I've changed my mind, I won't donate anything Set Choosing to 0 ;to close the menus Endif Return Elseif (Choosing == -3) ;Food menu Messagebox "How much food would you like to donate?" "Options" "More Options" ... "Cancel" Set Choosing to 3 Set Choice to GetButtonPressed Return Elseif (Choosing == 3) If (Choice == -1) ;No choice yet Set Choice to GetButtonPressed Elseif (Choice == 0) ;Options ... Elseif (Choice == 9) ;Cancel Set Choosing to 0 Endif Return Elseif (Choosing == -99) ;Player-doesn't-have-enough menu Messagebox "You don't have enough." Set Choosing to 99 Set Choice to GetButtonPressed Return Elseif (Choosing == 99) If (Choice == -1) ;No choice yet Set Choice to GetButtonPressed Elseif (Choice == 0) ;player pressed "Done", return to main menu Set Choosing to -1 Endif Return Endif End
I suggest using numbers instead of other variables when setting Choosing. Numbers give more meaning than words in this case, as the negative and positive numbers separate which part of the menu you're dealing with. You can also use numbers to signify which layer of the menu you are in. For instance, -1 was the first layer in the above example. For the sub-menus of the main menu (Gold, Food, Blood), you can use -11, -12, -13. And, for example, for the sub-menus of Food you can use -121, -122, -123 such that the first number signifies the menu of the first layer, the second the menu of the second layer, etc.
Multiple Menus in a Spell Script
A spell presents several challenges for multiple menus, but it can be done. Most importantly, you will need to set a high duration on the spell (see GetButtonPressed#GameMode or MenuMode?). However, the player could still open enough menus to run down the duration, no matter how long. To cover this scenario, all of the menu variables will be copied to a persistent object (in this case, the quest MenuVariables) when the spell runs out, and the spell will be recast.
This leads to one more small problem - if the variables are left set, then every time the spell is cast the menu would start more it left off before (well, ok, this could be cool, but I'll assume it's undesirable). To solve this, all of the external menu variables will be reset when the player exits the menu.
Note also that, as of now, this hasn't been tested for multiple menus.
The quest script:
scn MenuVariablesScript Short Choosing Short Choice
The spell script:
Short Choosing Short Choice Ref TargetRef Begin SpellEffectStart Set TargetRef to GetSelf if MenuVariables.Choosing Set Choosing to MenuVariables.Choosing Set Choice to MenuVariables.Choice else Set Choosing to -1 endif End Begin SpellEffectUpdate If (Choosing == 0) ;meaning it shouldn't be running set MenuVariables.Choosing to 0 set MenuVariables.Choice to 0 Elseif (Choosing == -1) ;Display your menu Messagebox "What do you want to do?", ["Button0"], ..., ["Button8"], "Next Page" Set Choosing to 1 Set Choice to GetButtonPressed Elseif (Choosing == 1) If (Choice == -1) ;No choice yet Set Choice to GetButtonPressed Return Elseif (Choice == 0) ;Button0 ;Whatever you want to do Set Choosing to 0 ... Elseif (Choice == 8) ;Button8 ;Whatever you want to do Set Choosing to 0 Elseif (Choice == 9) ;Next Page Set Choosing to -2 ;Following menu (choosing == -2) Endif Elseif (Choosing == -2) ;Gold menu Messagebox "What do you want to do?", ["Button0"], ..., ["Button8"], "Exit" Set Choosing to 2 Set Choice to GetButtonPressed Elseif (Choosing == 2) If (Choice == -1) ;No choice yet Set Choice to GetButtonPressed Elseif (Choice == 0) ;Button0 ;Whatever you want to do set Choosing to 0 ... Elseif (Choice == 8) ;Button8 ;Whatever you want to do Set Choosing to 0 Elseif (Choice == 9) ;Exit Set Choosing to 0 ;to close the menus Endif Endif End begin ScriptEffectFinish if Choosing set MenuVariables.Choosing to Choosing set MenuVariables.Choice to Choice TargetRef.Cast Spell TargetRef endif end
Notes:
- Using multiple menus, or catching a button press with GetButtonPressed in a magic effect script requires that the spell duration be more than 0 in order for GetButtonPressed to capture the button.
- The ScriptEffectFinish block will always run immediately after the last running of the ScriptEffectUpdate block. Due to this, if the ScriptEffectUpdate block has a Return statement that fires at this point of the script, the ScriptEffectFinish block will not run.
- Spell using the script must have a Range of Self
- Spell in the above script (TargetRef.Cast Spell TargetRef) must be replaced with Editor ID of the Spell using the script
Extras
That will take care of most menu systems you'll ever want to create. However, there is still more functioniality you can add to your menus. From here, you can either get it all by using the following script, or pick and choose using the mini-tutorials:
Centalizing your decision catching
Centralizing your menu exits
Running menus in both GameMode and MenuMode when your script is too large
Ensuring your menus are seen
Allowing the player to set a variable to any number
Controlling the menu system via external scripts
Applying it all
If you use want all of the above extras, your menu script will look like this: (due to wiki limitations, the large if test has been given line breaks, whereas in the CS it would all be on one line)
scn YourMenuScript Short Choosing Short Choice ;Centralized Menu Exiting variable Short Reset ;GameMode and MenuMode variables Short GMRun Short ExitButton ;Ensuring Your Menu Is Read variables Float MessageTimer Short MessageButton Begin onActivate If (Choosing >= 0) Set Choosing to -1 Endif Set Reset to 1 If (MenuMode == 0) Set GMRun to 1 Endif If (GetInSameCell player == 0) ;always keep it near the player MoveTo player Endif End Begin GameMode If (Choosing == 0) Set GMRun to 0 If (GetInSameCell YourXMarker == 0) MoveTo YourXMarker Endif Return Elseif GMRun Set GMRun to 0 Set ExitButton to 0 Messagebox "Exiting options..." Elseif (Choice == -1) If (MessageTimer > 0) || (MessageCounter > 0) Set Choice to GetButtonPressed If (Choice > -1) Return Endif If (MenuMode 1001 == 0) If (MenuMode 1004) || (MenuMode 1005) || (MenuMode 1006) || (MenuMode 1010) || (MenuMode 1011) || (MenuMode 1013) || (MenuMode 1015) || (MenuMode 1016) || (MenuMode 1017) || (MenuMode 1018) || (MenuMode 1019) || (MenuMode 1020) || (MenuMode 1021) || (MenuMode 1024) || (MenuMode 1038) || (MenuMode 1039) || (MenuMode 1044) || (MenuMode 1045) || (MenuMode 1046) || (MenuMode 1047) || (MenuMode 1057) Return Else Set MessageTimer to (MessageTimer - GetSecondsPassed) Set MessageCounter to (MessageCounter - 1) Endif Else ;MenuMode 1001 Set MessageTimer to 1 Set MessageCounter to 45 Return Endif Else ;Display menu again Message "Trying menu again..." Set Choosing to -(Choosing) Messagebox "Exiting options..." Endif Elseif (Choice != ExitButton) Set ExitButton to 0 Messagebox "Exiting options..." Endif End Begin MenuMode If (Choosing == 0) If Reset ;reset whatever you need to Set Reset to 0 Endif If (GetInSameCell YourXMarker == 0) MoveTo YourXMarker Endif Elseif (Choosing > 0) && (Choice == -1) ;No choice yet If (MessageTimer > 0) || (MessageCounter > 0) Set Choice to GetButtonPressed If (Choice > -1) Return Endif If (MenuMode 1001 == 0) If (MenuMode 1004) || (MenuMode 1005) || (MenuMode 1006) || (MenuMode 1010) || (MenuMode 1011) || (MenuMode 1013) || (MenuMode 1015) || (MenuMode 1016) || (MenuMode 1017) || (MenuMode 1018) || (MenuMode 1019) || (MenuMode 1020) || (MenuMode 1021) || (MenuMode 1024) || (MenuMode 1038) || (MenuMode 1039) || (MenuMode 1044) || (MenuMode 1045) || (MenuMode 1046) || (MenuMode 1047) || (MenuMode 1057) Return Else Set MessageTimer to (MessageTimer - GetSecondsPassed) Set MessageCounter to (MessageCounter - 1) Return Endif Else ;MenuMode 1001 Set MessageTimer to 1 Set MessageCounter to 45 Return Endif Else ;Display menu again Message "Trying menu again..." Set Choosing to -(Choosing) Return Endif Elseif (Choosing == -1) ;Display your menu Set ExitButton to # ;1 in this example Messagebox "What would you like to do?" "First Option" ... "Exit Menu" Set Choosing to 1 Set Choice to GetButtonPressed Return Elseif (Choosing == 1) ;Catch the player's decision Elseif (Choice == 0) ;First Option ;run your code for the first decision Set Choosing to 0 ;to finish up Elseif (Choice == 1) ;Second Option ;run your code for the second descision Set Choosing to 0 ;to finish up Endif Elseif (Choosing == -2) ... Endif End