Difference between revisions of "MessageBox Tutorial"

From the Oblivion ConstructionSet Wiki
Jump to navigation Jump to search
imported>Haama
imported>Darkness X
 
(99 intermediate revisions by 5 users not shown)
Line 1: Line 1:
by haama
{{Tools|req0=[[The Elder Scrolls Construction Set|Construction Set]]|opt0=[[OBSE]]}}
==Intro==
==Intro==
Most of your menu needs can be taken care of with a simple [[Unplayable Items|token]] script:
Most MessageBox mistakes are made when simple scripts are used as a base for more complex menu scripts. To prevent this, this tutorial will work towards a single all-purpose script that can be used and expanded for any situation. By the end, you will know the problems that can pop-up in a menu script, how to prevent them, and why every line of the all-purpose script is needed.
<pre>Begin onAdd
 
  Messagebox "Your message" "Some options"
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.
End


Begin GameMode
Note that there are 4 mediums to attach a menu script to: Activators, Quests, Tokens (Object scripts on items in the player's possession), and Spells (or Magic Effect scripts). This article will focus mainly on activators, but the other methods, their differences, and how to set them up will be discussed on other pages. Activators are the focus as they are easier and safer to use in most situations (see [[MessageBox_Tutorial#In_which_I_try_to_convince_you_to_use_an_activator|"In which I try to convince you to use an activator"]] for the reasons, though it's highly suggested to read the article first).
  if (GetButtonPressed == -1)
    return
  else
    ;do stuff
    RemoveMe
  Endif
End</pre>
Just add the token to the player whenever you want to run the menu, but be aware that this script is severely limited in functionality. There are many ways to do menus, so this guide will lead up from the simplest menu (Done!) to a fully featured menu (well, featured enough for me) that will be able to:
*Use multiple choices
*Run the same choice for multiple frames
*Run multiple menus from one script
*Make it very easy to cut and paste for each new menu
*As well as some extras


==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:


==Basics==
===GetButtonPressed===
First, some basic information on menus:
#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
<pre>if (GetButtonPressed == -1)
...
elseif (GetButtonPressed == 0)</pre>
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:
<pre>short Choice
...
set Choice to GetButtonPressed
if (Choice == -1)
...
elseif (Choice == 0)</pre>
Likewise, GetButtonPressed will return -1 for all following frames (until the script runs another messagebox).


There are two sides to every menu – the display of the menu and catching the player's decision. You display the menu with the function [[MessageBox]] and catch the player's decision with the function [[GetButtonPressed]]. You can keep these two separated by changing a variable (i.e., "Choosing")
===Timing===
MessageBox takes one frame to display, so you can use any [[:Category:Blocktypes|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 [[Special variables#Locals|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:
<pre>short Choice
...
messagebox "Your menu" "Button 0" ... "Button 9"
set Choice to GetButtonPressed
if (Choice == -1)
...
elseif (Choice == 0)
...</pre>
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:
<pre>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)
  ...</pre>
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.


[[MessageBox]] takes one frame to display, so you can use any block to display it. However, [[GetButtonPressed]] needs to be in a block that continuously runs (i.e., [[GameMode]], [[MenuMode]], [[ScriptEffectUpdate]]) and needs to be on a script that is running every frame (i.e., an activator that is in a loaded cell, a quest running every .001 seconds, etc.). The reason for this – in order to read it, make a decision, click the option, and let go of the mouse button it will take the player longer than a frame, so you need to run the script every frame until then.
==Avoiding Common Mistakes for More Complex Menus==
===Starting, stopping, and the first menu===
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 [[Activate|ReferenceEditorID.Activate player, 1]] to start the menu:
<pre>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
...</pre>
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:
<pre>begin onActivate
  set Choosing to -1
end


Another oddity - [[GetButtonPressed]] returns somewhat odd numbers for each decision:
begin GameMode
*'''''-1''''' means no decision has been made
  if (Choosing == -1)
*'''''0''''' means the player selected the first option
    messagebox "What would you like to do?" "Button 0" ... "Button 9"
*'''''1''''' for the second
  elseif (Choosing == 1)
*...
    set Choice to GetButtonPressed
*'''''9''''' for the tenth(you can have 10 options at most)
...</pre>


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]]:
<pre>begin onActivate
  set Choosing to -1
end


With those in mind, here's a basic menu script. (You may also notice that it doesn't quite work, that'll be explained afterwards.)
begin GameMode
<pre>Short Choosing
  if (Choosing == 0)
    return


  elseif (Choosing == -1)
...
    set Choosing to 0 ;Whenever you want to exit the menu</pre>


===Keeping multiple menus separated===
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:
<pre>begin onActivate
  messagebox "What would you like to repair?" "Armor" ... "Weapons"
end


Begin GameMode
begin GameMode
   If (Choosing == 0)
   set Choice to GetButtonPressed
     Messagebox "What would you like to do?" "First Option"
  if (Choice == -1)
     Set Choosing to 1
  ...
     Return
  elseif (Choice == 0) ;Armor
  Else
     messagebox "Which armor would you like to repair?" "Helmet" ... "Boots"
     If (GetButtonPressed > -1) ;Player has made a decision
     set Choice2 to GetButtonPressed
      If (GetButtonPressed == 0) ;First Option
    if Choice2 == -1
        ;whatever you want to do
     ...
        Set Choosing to 0
     elseif (Choice2 == 0) ;Helmet
      Endif
    ...
     Else ;if (GetButtonPressed <= -1) - no decision
     elseif (Choice2 == 9) ;Boots
      Return ;Try to catch the decision in the next frame
     ...
     Endif
    endif
  Endif
End</pre>
The variable "Choosing" is used to separate the display of the menu and the catching of the player's decision. Since it's in a [[GameMode]] block, the script will run every frame. [[GetButtonPressed]] will return '''''-1''''' until the player makes a decision, in which case it will return '''''0''''' or '''''1''''', depending on the player's decision.


  elseif (Choice == 9) ;Weapons
    messagebox "Which weapon would you like to repair?" "Blade" ... "Bow"
...</pre>
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 from the second menu 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.


==Avoiding Common Mistakes for More Complex Menus==
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, and nothing happens. 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 displayed.
There are 2 and a half problems with the above script:


To prevent this from happening, use the Choosing variable to keep menus separate:
<pre>begin onActivate
  set Choosing to -1
end


First, [[GetButtonPressed]] only returns the player's decision once. On this section of code
begin GameMode
<pre>    If (GetButtonPressed > -1)
  if (Choosing == 0)
      If (GetButtonPressed == 0)</pre>
    return
that means that, once the player has made a decision, [[GetButtonPressed]] will return '''''0''''' for the first line, but will return '''''-1''''' for the second (this would be true for an <u>'''''if/elseif'''''</u> test as well). This can be fixed by setting a variable (for this tutorial it will be '''''Choice''''') to [[GetButtonPressed]] with <u>'''''set Choice to GetButtonPressed'''''</u>. Place this line after displaying the menu, as such:
  elseif (Choosing == -1)
<pre>Short Choosing
    messagebox "What would you like to repair?" "Armor" ... "Weapons"
Short Choice
    set Choosing to 1
...
  elseif (Choosing == 1)
   Else ;if (Choosing == 1) or menu has been shown
    set Choice to GetButtonPressed
     Set Choice to GetButtonPressed
    if (Choice == -1)
     If (Choice > -1) ;Player has made a decision
    ...
      If (Choice == 0) ;First Option
    elseif (Choice == 0) ;Armor
        ;whatever you want to do
      set Choosing to -10
        Set Choosing to 0
    ...
      Endif
    elseif (Choice == 9) ;Weapons
     Else ;if (Choice <= -1) - no decision
      set Choosing to -11
       Return ;Try to catch the decision in the next frame
    endif
     Endif
 
...</pre>
  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</pre>
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. You can find more information in the [[MessageBox Tutorial#Moving Between Multiple Menus|Moving Between Multiple Menus]] section.


The half problem – any of your code in the <u>''''';whatever you want to do'''''</u> section will only run for a single frame. This is good enough in most cases, however, if you need to run that section for more than one frame (i.e., waiting for another process to finish) you will have to set up things a bit differently. The reason, extending on the previous reason – [[GetButtonPressed]] will only return the player's decision once, and only for one frame. To fix this, set the variable only when [[GetButtonPressed]] returns '''''-1''''', as such:
===Running the same choice for multiple frames===
So far, we've been using GetButtonPressed like this:
<pre>...
<pre>...
   else ;if (Choosing == 1) or menu has been shown
   elseif (Choosing == 1)
     If (Choice == -1) ;Player hasn't made a decision
    set Choice to GetButtonPressed
    if (Choice == -1)
      return
    elseif (Choice == 0)
...</pre>
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:
<pre>...
  elseif (Choosing == -1)
    messagebox "What would you like to do?" "Count food" "Nothing"
    set Choosing to 1
    set Choice to GetButtonPressed
  elseif (Choosing == 1)
     set Choice to GetButtonPressed
    if (Choice == -1)
      return
    elseif (Choice == 0) ;Count the number of food items in the player's possession
      set InvPos to (InvPos + 1)
      set pInvObj to (player.GetInventoryObject
      if (IsFood pInvObj)
        set FoodCount to (FoodCount + 1)
      endif
      ...
    elseif (Choice == 1) ;Nothing
      set Choosing to 0 ;To exit
    endif
...</pre>
"Count food" requires several frames to complete. Once GetButtonPressed returns 0, it will start off and test the first item. 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:
<pre>...
  elseif (Choosing == -1)
    messagebox "What would you like to do?" "Count food" "Nothing"
    set Choosing to 1
    set Choice to -1
  elseif (Choosing == 1)
    if (Choice == -1)
       set Choice to GetButtonPressed
       set Choice to GetButtonPressed
       return
    elseif (Choice == 0) ;Count the number of food items in the player's possession
     elseif (Choice == 0) ;Player has selected the first option
      set InvPos to (InvPos + 1)
      set pInvObj to (player.GetInventoryObject
      if (IsFood pInvObj)
        set FoodCount to (FoodCount + 1)
      endif
       ...
     elseif (Choice == 1) ;Nothing
      set Choosing to 0 ;To exit
    endif
...</pre>
...</pre>
Note that Choice is reset to -1 after displaying a menu. Otherwise, Choice would still be the same as for the last menu (0, in this case). GetButtonPressed would never be run because Choice isn't equal to -1, and that Choice (0) would run instead of the player's choice.
==Creating Your New Menu==
===In which I try to convince you to use an activator===
This is still being discussed. Feel free to join and share your thoughts and opinions on the Discussion page.
There are 4 mediums you could use for the script: Quest, Activator, Tokens (Object scripts on items in the player's possession), and Spells (or Magic Effect scripts). Each has their own advantages and disadvantages, but it's suggested you use an activator.
====Activator Advantages====
* Activators can keep persistent variables that other scripts can use (unlike tokens and spells)
** Quest variables can be reset by using 'StartQuest' when the quest is already running (and there are reports of quests requiring resetting if a variable is added in a mod's update).
** Activators are safe from Oblivion cell resets. These resets will return a container's inventory to it's original state, objects to their start location, etc., but the variables on the activator will remain the same.
* There's a clear beginning to it ([[OnActivate]], unlike quests)
* It's easy and fast to start (harder for a quest)
* Tokens can randomly cause CTDs when removed (unless you wait for 5 frames first, and may cause [[:Category:Tidbits#Tokens_lead_to_crashes.3F|other problems]] upon removal)
* Spells are difficult to manage
** Spells will only run in GameMode
** There will always be time inbetween menus, in which the spell's duration will decrease. Giving the spell a long duration can work, but is not a guarantee for multi-layered menus.
====Activator Disadvantages====
* They need to be [[Loaded]] to run every frame. The easiest way to keep it loaded is to set one of it's own variables every frame. Setting a variable loads an object into memory, as if it were in the same cell as the player. Like this:
<pre>short Working
...
begin GameMode
...
  set Working to 1
...
</pre>
* Their script can run even when not around the player (though only for a single frame, but enough to cause [[:Category:Tidbits#When_do_remote_activators_run.3F|problems]]). To solve this problem, set a flag variable (for this tutorial, ''Working'') to 1 when the script should be running, and 0 when it should end. Use an '''if''' test to check ''Working'', like so:
<pre>short Working
...
begin onActivate
...
  set Working to 1
end


begin GameMode
  if Working
    if (Choosing == 0)
      set Working to 0
...
  endif
end</pre>
Set Working to 0 when you wish to keep the script from running.
* These solutions work hand in hand. When the script should be running, and Working is 1, set Working to 1 at the beginning of every frame to keep it loaded for the next frame. When it's 0, don't set any variables and the activator will be unloaded, preventing the script from errantly running. Combining them looks like this:
<pre>short Working
...
begin onActivate
...
  set Working to 1
end


The second problem is a bit more drastic – every time the player makes a decision the menu will be displayed again. This problem can be fixed by changing the <u>'''''If (Choosing == 0)'''''</u> test to <u>'''''If (Choosing == -1)'''''</u>, and having a clear start to the script (i.e., [[OnActivate]], [[OnAdd]], etc.) that will <u>'''''set Choosing to -1'''''</u>. There are a few ways to start the script off, but my preferred method is to use a persistent activator. The advantages of a persistent activator:
begin GameMode
*There's a clear beginning to it ([[OnActivate]])
  if Working
*It's easy to start (unlike a quest)
    set Working to 1
*It's fast to start (harder for a quest)
*It can run every frame (harder for a quest)
*The variables can be global (harder for a [[Unplayable Items|token]])
*And it's simply easier to manage than a spell (if a spell is really possible at all, I haven't seen one yet)
The only disadvantage is that you will have to remember to add a few lines to move the activator to the player when starting and away when finished. Of course, with some work, quests and tokens can be made to do the same, but I don't find them quite as easy to set up.


    if (Choosing == 0)
      set Working to 0
      return
    elseif (Choosing == 1)
...
    endif
  endif
end</pre>


==Creating Your New Menu==
====Other methods====
* [[MessageBox_Tutorial/Quest_Scripts|Quests]]
* [[MessageBox_Tutorial/Token_Scripts|Tokens]]
* [[MessageBox_Tutorial/Spell_Scripts|Spells]]
 
===What you'll need===
You'll need to set up some objects for the next script: an invisible activator, an XMarker, and your own cell:
You'll need to set up some objects for the next script: an invisible activator, an XMarker, and your own cell:


===Your own cell===
===Your own cell===
Line 118: Line 308:
#Select "Duplicate Cell"
#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)
#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===
===Activator===
#Select an activator in the "Object Window"
#Select an activator in the "Object Window"
#*It needs to be an activator. Statics can't have scripts, [[Crashes#Activating_a_Container_(including_NPC)|NPCs and containers can cause a CTD if they have a scripted item in their inventory]], and items can be picked up.
#Edit the name
#Edit the name
#Press enter (or select ok in the edit menu)
#Press enter (or select ok in the edit menu)
Line 138: Line 317:
#Drag your new activator into the "Render Window"
#Drag your new activator into the "Render Window"
#Right-click it
#Right-click it
#Give it a "Reference Editor ID"
#Give it a "Reference Editor ID". This is the reference you'll use to start up the menu, so make it something memorable.
#Mark it as "Persistent Reference" and "Initially Disabled"
#Mark it as "Persistent Reference" and "Initially Disabled"
#Place the following script on your new activator
#Place the following script on your new activator
Line 145: Line 324:
##Select Edit
##Select Edit
##In the "Script" pull-down box, select "YourMenuScript"
##In the "Script" pull-down box, select "YourMenuScript"


===Activator Script===
===Activator Script===
<pre>scn YourMenuScript
<pre>scn YourMenuScript
Short Working
Short Choosing
Short Choosing
Short Choice
Short Choice
Line 156: Line 335:
Begin onActivate
Begin onActivate
   Set Choosing to -1
   Set Choosing to -1
  Set Working to 1
End
End


Line 161: Line 341:


Begin GameMode
Begin GameMode
   If (GetInSameCell player == 0) ;always keep it near the player
   If Working
     MoveTo player
     Set Working to 1
    Return
  Endif


    If (Choosing == 0) ;meaning it shouldn't be running
      Set Working to 0
      ;Add anything that needs to be re-initialized,
   
    Elseif (Choosing == -1) ;Display your menu
      Messagebox "Which option?" "First Option" "Second Option" ... "Tenth Option"
      Set Choosing to 1
      Set Choice to -1
    Elseif (Choosing == 1) ;Catch the player's decision
      If (Choice == -1) ;No choice yet
        Set Choice to GetButtonPressed
      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
;...
;      Elseif (Choice == 9) ;Tenth Option
        ;run your code for the tenth decision
;        Set Choosing to 0 ;to finish up


  If (Choosing == 0) ;meaning it shouldn't be running
       Endif
    If (GetInSameCell YourXMarker == 0)
       MoveTo YourXMarker
     Endif
     Endif
  Endif
End</pre>
You can start your menus from any script with <pre>YourActivatorsReferenceEditorID.Activate player, 1</pre>.


==Moving Between Multiple Menus==
===Review===
As stated before, each menu has a pair of corresponding numbers. For clarity and to keep the two necessary halves of menus separate, those pairs of numbers have been set up a a negative number (i.e., -1) and a positive number (1). The negative number refers to the code that displays the menu:
<pre>...
  if (Choosing == -1)
    messagebox "What would you like to do?"
    set Choosing to 1
...</pre>
while the positive number refers to the code to return the player's choice:
<pre>...
  elseif (Choosing == 1)
    if (Choice == -1)
      set Choice to GetButtonPressed
    elseif (Choice == 0)
...</pre>
You can start up any menu by setting Choice to the corresponding negative number:
<pre>set Choosing to -1</pre>
from anywhere else in the script.


  Elseif (Choosing == -1) ;Display your menu
===Using numbers to track menu layers===
    Messagebox "What would you like to do?" "First Option" "Second Option"
If you're making multi-layered menus, you can use the numbers to know which layer you're on and the previous menu(s). For the first layer (the main menu), use the pair -1/1. For the branching menus, use -11/11, -12/12, etc. If menu -11 has more branches, set them to -111/111, -112/112, etc.
    Set Choosing to 1
    Set Choice to GetButtonPressed
    Return


  Elseif (Choosing == 1) ;Catch the player's decision
Each new layer gets an extra number. For example, a sub-branch from menu -12 would be -121. The number on the right refers to the sub-branch, while the numbers on the left refer to the menu they branched from. This is recursive, so in this example -121 is the first sub-branch from menu -12, and -12 is the second sub-branch from the menu -1.
    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 descision
      Set Choosing to 0 ;to finish up
    Endif
  Endif
End</pre>
Ok, no games that time. You can start your menus from any script with <u>'''''YourActivatorsReferenceEditorID.Activate player, 1'''''</u> and this script will do the rest.


 
===Example script===
==Multiple Menus in One Script==
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 would be on one line)
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 <u>'''''set Choosing to -#'''''</u>. 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)
<pre>Short Working
<pre>Short Choosing
Short Choosing
Short Choice
Short Choice


Line 203: Line 405:


Begin onActivate
Begin onActivate
  Set Working to 1
   Set Choosing to -1
   Set Choosing to -1
End
End
Line 209: Line 412:


Begin GameMode
Begin GameMode
   If (GetInSameCell player == 0)
   If Working
     MoveTo player
     Set Working to 1
    Return
  Endif


    If (Choosing == 0) ;meaning it shouldn't be running
      Set Working to 0
      ;Add anything that needs to be re-initialized,


  If (Choosing == 0) ;meaning it shouldn't be running
    Elseif (Choosing == -1) ;Display your menu
     If (GetInSameCell YourXMarker == 0)
      Messagebox "What would you like to donate?" "Gold" "Food" "Blood" "Cancel"
       MoveTo YourXMarker
      Set Choosing to 1
    Endif
      Set Choice to -1
     Elseif (Choosing == 1)
      If (Choice == -1) ;No choice yet
        Set Choice to GetButtonPressed
      Elseif (Choice == 0) ;Gold
        Set Choosing to -11 ;to open the Gold menu
      Elseif (Choice == 1) ;Food
        Set Choosing to -12 ;to open the Food menu
      Elseif (Choice == 2) ;Blood
        Set Choosing to -13 ;to open the Blood menu
       Elseif (Choice == 3) ;Cancel
        Set Choosing to 0 ;to close the menus
      Endif


 
     Elseif (Choosing == -11) ;Gold menu
  Elseif (Choosing == -1) ;Display your menu
       Messagebox "How much Gold would you like to donate?" "25"
    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've changed my mind, I'll donate Food"
             "I've changed my mind, I'll donate Blood"
             "I've changed my mind, I'll donate Blood"
             "I've changed my mind, I won't donate anything"  
             "I've changed my mind, I won't donate anything"  
     Set Choosing to 2
      Set Choosing to 11
    Set Choice to GetButtonPressed
      Set Choice to -1
    Return
     Elseif (Choosing == 11)
      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 -12 ;to open the food menu
      Elseif (Choice == 3) ; I've changed my mind, I'll donate blood
        Set Choosing to -13 ;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


  Elseif (Choosing == 2)
    Elseif (Choosing == -12) ;Food menu
    If (Choice == -1) ;No choice yet
      Messagebox "How much food would you like to donate?" "An apple"
                                                          "Two apples"
                                                          ...
                                                          "Cancel"
      Set Choosing to 12
       Set Choice to GetButtonPressed
       Set Choice to GetButtonPressed
     Elseif (Choice == 0) ;25
     Elseif (Choosing == 12)
       If (player.GetGold > 25)
      If (Choice == -1) ;No choice yet
         Player.RemoveItem Gold001 25
        Set Choice to GetButtonPressed
      Elseif (Choice == 0) ;An Apple
        If (player.GetItemCount Apple)
          player.RemoveItem Apple 1
        Else
          set Choosing to -99 ;Go to special 'Not enough' menu
        Endif
        Set Choosing to -1 ;To return to main menu
       Elseif (Choice == 1)  ;Two Apple
        If ((player.GetItemCount Apple) > 1)
          player.RemoveItem Apple 2
        Else
          set Choosing to -99 ;Go to special 'Not enough' menu
         Endif
        Set Choosing to -1 ;To return to main menu
;...
      Elseif (Choice == 9)  ;Cancel
         Set Choosing to 0
         Set Choosing to 0
      Else
        Set Choosing to -99 ;a message that the player doesn't have enough
       Endif
       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
    Elseif (Choosing == -13) ;Blood menu
    Messagebox "How much food would you like to donate?" "Options"
      Messagebox "How much blood would you like to donate?" "1 HP"
                                                        "More Options"
                                                            "2 HP"
                                                        ...
                                                            ...
                                                        "Cancel"
                                                            "Cancel"
    Set Choosing to 3
      Set Choosing to 13
    Set Choice to GetButtonPressed
    Return
 
  Elseif (Choosing == 3)
    If (Choice == -1) ;No choice yet
       Set Choice to GetButtonPressed
       Set Choice to GetButtonPressed
     Elseif (Choice == 0)  ;Options
     Elseif (Choosing == 13)
...
      If (Choice == -1) ;No choice yet
    Elseif (Choice == 9)  ;Cancel
        Set Choice to GetButtonPressed
      Set Choosing to 0
      Elseif (Choice == 0)  ;1 HP
    Endif
        player.ModAV Health -1 ;don't actually use this, just an easy example
    Return
        set Choosing to -1 ;to return to the main menu
 
      Elseif (Choice == 1)  ;2 HP
        player.ModAV Health -2 ;don't actually use this, just an easy example
        set Choosing to -1 ;to return to the main menu
;...
      Elseif (Choice == 9)  ;Cancel
        Set Choosing to 0
      Endif


     Elseif (Choosing == -99) ;Player-doesn't-have-enough menu
     Elseif (Choosing == -99) ;Player-doesn't-have-enough menu
Line 297: Line 517:
       Set Choosing to 99
       Set Choosing to 99
       Set Choice to GetButtonPressed
       Set Choice to GetButtonPressed
      Return
     Elseif (Choosing == 99)
     Elseif (Choosing == 99)
    If (Choice == -1) ;No choice yet
      If (Choice == -1) ;No choice yet
      Set Choice to GetButtonPressed
        Set Choice to GetButtonPressed
    Elseif (Choice == 0) ;player pressed "Done", return to main menu
      Elseif (Choice == 0) ;player pressed "Done", return to main menu
      Set Choosing to -1
        Set Choosing to -1
      Endif
     Endif
     Endif
    Return
  Endif
End</pre>


==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:
<br>[[MessageBox_Tutorial/Centralized_Decision_Catching|Centalizing your decision catching]]
<br>[[Messagebox_Tutorial/GameMode_And_MenuMode|Running menus in both GameMode and MenuMode when your script is too large]]
<br>[[MessageBox_Tutorial/Ensuring_Your_Menu_Is_Seen|Ensuring your menus are seen]]
<br>[[Messagebox_Tutorial/Set_Variables|Allowing the player to set a variable to any number]]
<br>[[MessageBox_Tutorial/External_Menu_Selection|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)
<pre>scn YourMenuScript
Short Working
Short Choosing
Short Choice
;GameMode and MenuMode variables
Short GMRun
Short ExitButton
;Ensuring Your Menu Is Read variables
Short MessageCounter
Begin onActivate
  If (Choosing >= 0) ;Controlling the menu system via external scripts
    Set Choosing to -1
  Endif
  Set Working to 1
  Set MessageCounter to 0 ;Ensuring your menus are seen
  If (MenuMode == 0) ;Running menus in both GameMode and MenuMode
    Set GMRun to 1
  Endif
End
Begin GameMode ;Running menus in both GameMode and MenuMode
  If Working
    Set Working to 1
    If (Choosing == 0)
      Set GMRun to 0
      Set Working to 0
      Set MessageCounter to 0 ;Ensuring your menu is seen
      ;Add anything that needs to be re-initialized
    Elseif GMRun
      Set GMRun to 0
      Set ExitButton to 0
      Messagebox "Exiting options..."
    Elseif (Choice == -1) ;Ensuring your menus are seen
      if (MessageCounter < 30)
        set Choice to GetButtonPressed
        if (Choice == -1)
          if (MenuMode 1001 == 0)
            if (MenuMode 1004 == 0) && (MenuMode 1005 == 0) && (MenuMode 1006 == 0) &&
              (MenuMode 1010 == 0) && (MenuMode 1011 == 0) && (MenuMode 1013 == 0) &&
              (MenuMode 1015 == 0) && (MenuMode 1016 == 0) && (MenuMode 1017 == 0) &&
              (MenuMode 1018 == 0) && (MenuMode 1019 == 0) && (MenuMode 1020 == 0) &&
              (MenuMode 1021 == 0) && (MenuMode 1024 == 0) && (MenuMode 1038 == 0) &&
              (MenuMode 1039 == 0) && (MenuMode 1044 == 0) && (MenuMode 1045 == 0) &&
              (MenuMode 1046 == 0) && (MenuMode 1047 == 0) && (MenuMode 1057 == 0)
              set MessageCounter to (MessageCounter + 1)
            endif
          else ;MenuMode 1001
            set MessageCounter to 0
          endif
        else ;Choice > -1
          set MessageCounter to 0
        endif
      else ;Display menu again
        set Choosing to -(Choosing)
        set MessageCounter to 0
        message "Trying menu again..."
      endif
    Elseif (Choice != ExitButton)
      Set ExitButton to 0
      Messagebox "Exiting options..."
    Elseif (Choice == ExitButton)
      Set Choosing to 0
    Endif
   Endif
   Endif
End</pre>
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.
 
 
 
Begin MenuMode
  If Working
    Set Working to 1
 
    If (Choosing == 0)
      Set GMRun to 0 ;Running menus in both GameMode and MenuMode
      Set Working to 0
      Set MessageCounter to 0 ;Ensuring your menu is seen
      ;Add anything that needs to be re-initialized
 
 
    ;Ensuring your menu is seen
    ;Centralizing your menu decisions
    Elseif (Choosing > 0) && (Choice == -1) ;No choice yet
      if (MessageCounter < 30)
        set Choice to GetButtonPressed
        if (Choice == -1)
          if (MenuMode 1001 == 0)
            if (MenuMode 1004 == 0) && (MenuMode 1005 == 0) && (MenuMode 1006 == 0) &&
              (MenuMode 1010 == 0) && (MenuMode 1011 == 0) && (MenuMode 1013 == 0) &&
              (MenuMode 1015 == 0) && (MenuMode 1016 == 0) && (MenuMode 1017 == 0) &&
              (MenuMode 1018 == 0) && (MenuMode 1019 == 0) && (MenuMode 1020 == 0) &&
              (MenuMode 1021 == 0) && (MenuMode 1024 == 0) && (MenuMode 1038 == 0) &&
              (MenuMode 1039 == 0) && (MenuMode 1044 == 0) && (MenuMode 1045 == 0) &&
              (MenuMode 1046 == 0) && (MenuMode 1047 == 0) && (MenuMode 1057 == 0)
              set MessageCounter to (MessageCounter + 1)
            endif
          else ;MenuMode 1001
            set MessageCounter to 0
          endif
        else ;Choice > -1
          set MessageCounter to 0
        endif
      else ;Display menu again
        set Choosing to -(Choosing)
        set MessageCounter to 0
        message "Trying menu again..."
      endif
 


    Elseif (Choosing == -1) ;Display your menu
      Set ExitButton to 1 ;Running menus in both GameMode and MenuMode
      Messagebox "What would you like to do?" "First Option" "Exit Menu"
      Set Choosing to 1
      Set Choice to -1
    Elseif (Choosing == 1) ;Catch the player's decision
      If (Choice == 0) ;First Option
        ;run your code for the first decision
      Elseif (Choice == 1) ;Exit Menu
        ;run your code for the second descision
        Set Choosing to 0 ;to finish up
      Endif


==Extras==
    ;Allowing the player to set any number
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:
    short NumNewIngsDefault
<br>'''''Mmmm... sleep... I'll add these shortly...'''''
    elseif (Choosing == -2)
<br>[[Messagebox_Tutorial:_Centralizing_Your_Menu_Exits|Centralizing your menu exits]]
          set ExitButton to -2 ;Running menus in both GameMode and MenuMode
<br>[[Messagebox_Tutorial:_GameMode_And_MenuMode|Running menus in both GameMode and MenuMode when your script is too large]]
          messagebox "Set the default number of essences to be created.
<br>Ensuring your menus are seen
                    (Currently %g)", NumNewIngsDefault,
<br>[[Messagebox_Tutorial:_Set_Variables|Allowing the player to set a variable to any number]]
                            "About 10 fewer (-12 to -8)"
<br>Controlling the menu system via external scripts
                            "About 5 fewer (-7 to -3)"
<br>[[User:Haama|Haama]] 02:08, 14 June 2007 (EDT)
                            "Default to that amount -2"
                            "Default to that amount -1"
                            "Default to that amount"
                            "Default to that amount +1"
                            "Default to that amount +2"
                            "About 5 more (+3 to +7)"
                            "About 10 more (+8 to +12)"
                            "Return to previous menu"
          set Choosing to 2
          set Choice to -1
    elseif (Choosing == 2)
          if (Choice == 0) ;10 fewer
            set NumNewIngsDefault to (NumNewIngsDefault - 10)
            set Choosing to -2
          elseif (Choice == 1) ;5 fewer
            set NumNewIngsDefault to (NumNewIngsDefault - 5)
            set Choosing to -2
          elseif (Choice == 7) ;5 more
            set NumNewIngsDefault to (NumNewIngsDefault + 5)
            set Choosing to -2
          elseif (Choice == 8) ;10 more
            set NumNewIngsDefault to (NumNewIngsDefault + 10)
            set Choosing to -2
          elseif (Choice == 2) ;-2
            set NumNewIngsDefault to (NumNewIngsDefault - 2)
            set Choosing to -1
          elseif (Choice == 3) ;-1
            set NumNewIngsDefault to (NumNewIngsDefault - 1)
            set Choosing to -1
          elseif (Choice == 4) ;Cancel
            set Choosing to -
          elseif (Choice == 5) ;+1
            set NumNewIngsDefault to (NumNewIngsDefault + 1)
            set Choosing to -1
          elseif (Choice == 6) ;+2
            set NumNewIngsDefault to (NumNewIngsDefault + 2)
            set Choosing to -1
          elseif (Choice == 9) ;Return to previous menu
            set Choosing to -1
          endif
    Endif
  Endif
End</pre>
[[Category: Scripting Tutorials]]
[[Category: Scripting Tutorials]]

Latest revision as of 09:37, 15 April 2010

Tools used in this tutorial

Required

Optional


Intro[edit | edit source]

Most MessageBox mistakes are made when simple scripts are used as a base for more complex menu scripts. To prevent this, this tutorial will work towards a single all-purpose script that can be used and expanded for any situation. By the end, you will know the problems that can pop-up in a menu script, how to prevent them, and why every line of the all-purpose script is needed.

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.

Note that there are 4 mediums to attach a menu script to: Activators, Quests, Tokens (Object scripts on items in the player's possession), and Spells (or Magic Effect scripts). This article will focus mainly on activators, but the other methods, their differences, and how to set them up will be discussed on other pages. Activators are the focus as they are easier and safer to use in most situations (see "In which I try to convince you to use an activator" for the reasons, though it's highly suggested to read the article first).

Basic Mechanics of MessageBox and Related Functions[edit | edit source]

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[edit | edit source]

  1. 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)
  2. 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[edit | edit source]

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[edit | edit source]

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[edit | edit source]

Starting, stopping, and the first menu[edit | edit source]

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

Keeping multiple menus separated[edit | edit source]

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 from the second menu 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, and nothing happens. 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 displayed.

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. You can find more information in the Moving Between Multiple Menus section.

Running the same choice for multiple frames[edit | edit source]

So far, we've been using GetButtonPressed 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?" "Count food" "Nothing"
    set Choosing to 1
    set Choice to GetButtonPressed
  elseif (Choosing == 1)
    set Choice to GetButtonPressed
    if (Choice == -1)
      return
    elseif (Choice == 0) ;Count the number of food items in the player's possession
      set InvPos to (InvPos + 1)
      set pInvObj to (player.GetInventoryObject
      if (IsFood pInvObj)
        set FoodCount to (FoodCount + 1)
      endif
      ...
    elseif (Choice == 1) ;Nothing
      set Choosing to 0 ;To exit
    endif
...

"Count food" requires several frames to complete. Once GetButtonPressed returns 0, it will start off and test the first item. 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?" "Count food" "Nothing"
    set Choosing to 1
    set Choice to -1
  elseif (Choosing == 1)
    if (Choice == -1)
      set Choice to GetButtonPressed
    elseif (Choice == 0) ;Count the number of food items in the player's possession
      set InvPos to (InvPos + 1)
      set pInvObj to (player.GetInventoryObject
      if (IsFood pInvObj)
        set FoodCount to (FoodCount + 1)
      endif
      ...
    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 (0, in this case). GetButtonPressed would never be run because Choice isn't equal to -1, and that Choice (0) would run instead of the player's choice.

Creating Your New Menu[edit | edit source]

In which I try to convince you to use an activator[edit | edit source]

This is still being discussed. Feel free to join and share your thoughts and opinions on the Discussion page.

There are 4 mediums you could use for the script: Quest, Activator, Tokens (Object scripts on items in the player's possession), and Spells (or Magic Effect scripts). Each has their own advantages and disadvantages, but it's suggested you use an activator.

Activator Advantages[edit | edit source]

  • Activators can keep persistent variables that other scripts can use (unlike tokens and spells)
    • Quest variables can be reset by using 'StartQuest' when the quest is already running (and there are reports of quests requiring resetting if a variable is added in a mod's update).
    • Activators are safe from Oblivion cell resets. These resets will return a container's inventory to it's original state, objects to their start location, etc., but the variables on the activator will remain the same.
  • There's a clear beginning to it (OnActivate, unlike quests)
  • It's easy and fast to start (harder for a quest)
  • Tokens can randomly cause CTDs when removed (unless you wait for 5 frames first, and may cause other problems upon removal)
  • Spells are difficult to manage
    • Spells will only run in GameMode
    • There will always be time inbetween menus, in which the spell's duration will decrease. Giving the spell a long duration can work, but is not a guarantee for multi-layered menus.

Activator Disadvantages[edit | edit source]

  • They need to be Loaded to run every frame. The easiest way to keep it loaded is to set one of it's own variables every frame. Setting a variable loads an object into memory, as if it were in the same cell as the player. Like this:
short Working
...
begin GameMode
...
  set Working to 1
...
  • Their script can run even when not around the player (though only for a single frame, but enough to cause problems). To solve this problem, set a flag variable (for this tutorial, Working) to 1 when the script should be running, and 0 when it should end. Use an if test to check Working, like so:
short Working
...
begin onActivate
...
  set Working to 1
end

begin GameMode
  if Working
    if (Choosing == 0)
      set Working to 0
...
  endif
end

Set Working to 0 when you wish to keep the script from running.

  • These solutions work hand in hand. When the script should be running, and Working is 1, set Working to 1 at the beginning of every frame to keep it loaded for the next frame. When it's 0, don't set any variables and the activator will be unloaded, preventing the script from errantly running. Combining them looks like this:
short Working
...
begin onActivate
...
  set Working to 1
end

begin GameMode
  if Working
    set Working to 1

    if (Choosing == 0)
      set Working to 0
      return
    elseif (Choosing == 1)
...
    endif
  endif
end

Other methods[edit | edit source]

What you'll need[edit | edit source]

You'll need to set up some objects for the next script: an invisible activator, an XMarker, and your own cell:

Your own cell[edit | edit source]

  1. Scroll down the "Cell View" window
  2. Select "TestQuset01"
  3. Right-click it
  4. Select "Duplicate Cell"
  5. Rename your new cell to something you'll remember (and don't worry about the lack of floors, it'll work just fine)

Activator[edit | edit source]

  1. Select an activator in the "Object Window"
  2. Edit the name
  3. Press enter (or select ok in the edit menu)
  4. Click "Yes" when it asks if you want to create a new item
  5. Drag your new activator into the "Render Window"
  6. Right-click it
  7. Give it a "Reference Editor ID". This is the reference you'll use to start up the menu, so make it something memorable.
  8. Mark it as "Persistent Reference" and "Initially Disabled"
  9. Place the following script on your new activator
    1. Make the following script
    2. In the "Object Window", right-click your new activator
    3. Select Edit
    4. In the "Script" pull-down box, select "YourMenuScript"

Activator Script[edit | edit source]

scn YourMenuScript
Short Working
Short Choosing
Short Choice



Begin onActivate
  Set Choosing to -1
  Set Working to 1
End



Begin GameMode
  If Working
    Set Working to 1

    If (Choosing == 0) ;meaning it shouldn't be running
      Set Working to 0
      ;Add anything that needs to be re-initialized,
    
    Elseif (Choosing == -1) ;Display your menu
      Messagebox "Which option?" "First Option" "Second Option" ... "Tenth Option"
      Set Choosing to 1
      Set Choice to -1
    Elseif (Choosing == 1) ;Catch the player's decision
      If (Choice == -1) ;No choice yet
        Set Choice to GetButtonPressed
      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
;...
;      Elseif (Choice == 9) ;Tenth Option
         ;run your code for the tenth decision
;        Set Choosing to 0 ;to finish up

      Endif
    Endif
  Endif
End

You can start your menus from any script with

YourActivatorsReferenceEditorID.Activate player, 1

.

Moving Between Multiple Menus[edit | edit source]

Review[edit | edit source]

As stated before, each menu has a pair of corresponding numbers. For clarity and to keep the two necessary halves of menus separate, those pairs of numbers have been set up a a negative number (i.e., -1) and a positive number (1). The negative number refers to the code that displays the menu:

...
  if (Choosing == -1)
    messagebox "What would you like to do?"
    set Choosing to 1
...

while the positive number refers to the code to return the player's choice:

...
  elseif (Choosing == 1)
    if (Choice == -1)
      set Choice to GetButtonPressed
    elseif (Choice == 0)
...

You can start up any menu by setting Choice to the corresponding negative number:

set Choosing to -1

from anywhere else in the script.

Using numbers to track menu layers[edit | edit source]

If you're making multi-layered menus, you can use the numbers to know which layer you're on and the previous menu(s). For the first layer (the main menu), use the pair -1/1. For the branching menus, use -11/11, -12/12, etc. If menu -11 has more branches, set them to -111/111, -112/112, etc.

Each new layer gets an extra number. For example, a sub-branch from menu -12 would be -121. The number on the right refers to the sub-branch, while the numbers on the left refer to the menu they branched from. This is recursive, so in this example -121 is the first sub-branch from menu -12, and -12 is the second sub-branch from the menu -1.

Example script[edit | edit source]

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 would be on one line)

Short Working
Short Choosing
Short Choice



Begin onActivate
  Set Working to 1
  Set Choosing to -1
End



Begin GameMode
  If Working
    Set Working to 1

    If (Choosing == 0) ;meaning it shouldn't be running
      Set Working to 0
      ;Add anything that needs to be re-initialized,

    Elseif (Choosing == -1) ;Display your menu
      Messagebox "What would you like to donate?" "Gold" "Food" "Blood" "Cancel"
      Set Choosing to 1
      Set Choice to -1
    Elseif (Choosing == 1)
      If (Choice == -1) ;No choice yet
        Set Choice to GetButtonPressed
      Elseif (Choice == 0) ;Gold
        Set Choosing to -11 ;to open the Gold menu
      Elseif (Choice == 1) ;Food
        Set Choosing to -12 ;to open the Food menu
      Elseif (Choice == 2) ;Blood
        Set Choosing to -13 ;to open the Blood menu
      Elseif (Choice == 3) ;Cancel
        Set Choosing to 0 ;to close the menus
      Endif

    Elseif (Choosing == -11) ;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 11
      Set Choice to -1
    Elseif (Choosing == 11)
      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 -12 ;to open the food menu
      Elseif (Choice == 3) ; I've changed my mind, I'll donate blood
        Set Choosing to -13 ;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

    Elseif (Choosing == -12) ;Food menu
      Messagebox "How much food would you like to donate?" "An apple"
                                                           "Two apples"
                                                           ...
                                                           "Cancel"
      Set Choosing to 12
      Set Choice to GetButtonPressed
    Elseif (Choosing == 12)
      If (Choice == -1) ;No choice yet
        Set Choice to GetButtonPressed
      Elseif (Choice == 0)  ;An Apple
        If (player.GetItemCount Apple)
          player.RemoveItem Apple 1
        Else
          set Choosing to -99 ;Go to special 'Not enough' menu
        Endif
        Set Choosing to -1 ;To return to main menu
      Elseif (Choice == 1)  ;Two Apple
        If ((player.GetItemCount Apple) > 1)
          player.RemoveItem Apple 2
        Else
          set Choosing to -99 ;Go to special 'Not enough' menu
        Endif
        Set Choosing to -1 ;To return to main menu
;...
      Elseif (Choice == 9)  ;Cancel
        Set Choosing to 0
      Endif

    Elseif (Choosing == -13) ;Blood menu
      Messagebox "How much blood would you like to donate?" "1 HP"
                                                            "2 HP"
                                                            ...
                                                            "Cancel"
      Set Choosing to 13
      Set Choice to GetButtonPressed
    Elseif (Choosing == 13)
      If (Choice == -1) ;No choice yet
        Set Choice to GetButtonPressed
      Elseif (Choice == 0)  ;1 HP
        player.ModAV Health -1 ;don't actually use this, just an easy example
        set Choosing to -1 ;to return to the main menu
      Elseif (Choice == 1)  ;2 HP
        player.ModAV Health -2 ;don't actually use this, just an easy example
        set Choosing to -1 ;to return to the main menu
;...
      Elseif (Choice == 9)  ;Cancel
        Set Choosing to 0
      Endif

    Elseif (Choosing == -99) ;Player-doesn't-have-enough menu
      Messagebox "You don't have enough."
      Set Choosing to 99
      Set Choice to GetButtonPressed
    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
    Endif
  Endif
End

Extras[edit | edit source]

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
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[edit | edit source]

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 Working
Short Choosing
Short Choice

;GameMode and MenuMode variables
Short GMRun
Short ExitButton

;Ensuring Your Menu Is Read variables
Short MessageCounter



Begin onActivate
  If (Choosing >= 0) ;Controlling the menu system via external scripts
    Set Choosing to -1
  Endif
  Set Working to 1
  Set MessageCounter to 0 ;Ensuring your menus are seen
  If (MenuMode == 0) ;Running menus in both GameMode and MenuMode
    Set GMRun to 1
  Endif
End



Begin GameMode ;Running menus in both GameMode and MenuMode
  If Working
    Set Working to 1

    If (Choosing == 0)
      Set GMRun to 0
      Set Working to 0
      Set MessageCounter to 0 ;Ensuring your menu is seen
      ;Add anything that needs to be re-initialized
    Elseif GMRun
      Set GMRun to 0
      Set ExitButton to 0
      Messagebox "Exiting options..."
    Elseif (Choice == -1) ;Ensuring your menus are seen
      if (MessageCounter < 30)
        set Choice to GetButtonPressed
        if (Choice == -1)
          if (MenuMode 1001 == 0)
            if (MenuMode 1004 == 0) && (MenuMode 1005 == 0) && (MenuMode 1006 == 0) &&
               (MenuMode 1010 == 0) && (MenuMode 1011 == 0) && (MenuMode 1013 == 0) &&
               (MenuMode 1015 == 0) && (MenuMode 1016 == 0) && (MenuMode 1017 == 0) &&
               (MenuMode 1018 == 0) && (MenuMode 1019 == 0) && (MenuMode 1020 == 0) &&
               (MenuMode 1021 == 0) && (MenuMode 1024 == 0) && (MenuMode 1038 == 0) &&
               (MenuMode 1039 == 0) && (MenuMode 1044 == 0) && (MenuMode 1045 == 0) &&
               (MenuMode 1046 == 0) && (MenuMode 1047 == 0) && (MenuMode 1057 == 0)
              set MessageCounter to (MessageCounter + 1)
            endif
          else ;MenuMode 1001
            set MessageCounter to 0
          endif
        else ;Choice > -1
          set MessageCounter to 0
        endif
      else ;Display menu again
        set Choosing to -(Choosing)
        set MessageCounter to 0
        message "Trying menu again..."
      endif
    Elseif (Choice != ExitButton)
      Set ExitButton to 0
      Messagebox "Exiting options..."
    Elseif (Choice == ExitButton)
      Set Choosing to 0
    Endif
  Endif
End



Begin MenuMode
  If Working
    Set Working to 1

    If (Choosing == 0)
      Set GMRun to 0 ;Running menus in both GameMode and MenuMode
      Set Working to 0
      Set MessageCounter to 0 ;Ensuring your menu is seen
      ;Add anything that needs to be re-initialized


    ;Ensuring your menu is seen
    ;Centralizing your menu decisions
    Elseif (Choosing > 0) && (Choice == -1) ;No choice yet
      if (MessageCounter < 30)
        set Choice to GetButtonPressed
        if (Choice == -1)
          if (MenuMode 1001 == 0)
            if (MenuMode 1004 == 0) && (MenuMode 1005 == 0) && (MenuMode 1006 == 0) &&
               (MenuMode 1010 == 0) && (MenuMode 1011 == 0) && (MenuMode 1013 == 0) &&
               (MenuMode 1015 == 0) && (MenuMode 1016 == 0) && (MenuMode 1017 == 0) &&
               (MenuMode 1018 == 0) && (MenuMode 1019 == 0) && (MenuMode 1020 == 0) &&
               (MenuMode 1021 == 0) && (MenuMode 1024 == 0) && (MenuMode 1038 == 0) &&
               (MenuMode 1039 == 0) && (MenuMode 1044 == 0) && (MenuMode 1045 == 0) &&
               (MenuMode 1046 == 0) && (MenuMode 1047 == 0) && (MenuMode 1057 == 0)
              set MessageCounter to (MessageCounter + 1)
            endif
          else ;MenuMode 1001
            set MessageCounter to 0
          endif
        else ;Choice > -1
          set MessageCounter to 0
        endif
      else ;Display menu again
        set Choosing to -(Choosing)
        set MessageCounter to 0
        message "Trying menu again..."
      endif


    Elseif (Choosing == -1) ;Display your menu
      Set ExitButton to 1 ;Running menus in both GameMode and MenuMode
      Messagebox "What would you like to do?" "First Option" "Exit Menu"
      Set Choosing to 1
      Set Choice to -1
    Elseif (Choosing == 1) ;Catch the player's decision
      If (Choice == 0) ;First Option
        ;run your code for the first decision
      Elseif (Choice == 1) ;Exit Menu
        ;run your code for the second descision
        Set Choosing to 0 ;to finish up
      Endif

    ;Allowing the player to set any number
    short NumNewIngsDefault
    elseif (Choosing == -2)
          set ExitButton to -2 ;Running menus in both GameMode and MenuMode
          messagebox "Set the default number of essences to be created.
                     (Currently %g)", NumNewIngsDefault,
                            "About 10 fewer (-12 to -8)"
                            "About 5 fewer (-7 to -3)"
                            "Default to that amount -2"
                            "Default to that amount -1"
                            "Default to that amount"
                            "Default to that amount +1"
                            "Default to that amount +2"
                            "About 5 more (+3 to +7)"
                            "About 10 more (+8 to +12)"
                            "Return to previous menu"
          set Choosing to 2
          set Choice to -1
    elseif (Choosing == 2)
          if (Choice == 0) ;10 fewer
            set NumNewIngsDefault to (NumNewIngsDefault - 10)
            set Choosing to -2
          elseif (Choice == 1) ;5 fewer
            set NumNewIngsDefault to (NumNewIngsDefault - 5)
            set Choosing to -2
          elseif (Choice == 7) ;5 more
            set NumNewIngsDefault to (NumNewIngsDefault + 5)
            set Choosing to -2
          elseif (Choice == 8) ;10 more
            set NumNewIngsDefault to (NumNewIngsDefault + 10)
            set Choosing to -2
          elseif (Choice == 2) ;-2
            set NumNewIngsDefault to (NumNewIngsDefault - 2)
            set Choosing to -1
          elseif (Choice == 3) ;-1
            set NumNewIngsDefault to (NumNewIngsDefault - 1)
            set Choosing to -1
          elseif (Choice == 4) ;Cancel
            set Choosing to -1  
          elseif (Choice == 5) ;+1
            set NumNewIngsDefault to (NumNewIngsDefault + 1)
            set Choosing to -1
          elseif (Choice == 6) ;+2
            set NumNewIngsDefault to (NumNewIngsDefault + 2)
            set Choosing to -1
          elseif (Choice == 9) ;Return to previous menu
            set Choosing to -1
          endif
    Endif
  Endif
End