Difference between revisions of "Adding an Options Menu"

From the Fallout3 GECK Wiki
Jump to navigation Jump to search
imported>Cipscis
(Restructured menu navigation to make it clearer; added "Defaults" button info)
imported>DewiMorgan
(Converting to wiki markup)
 
(7 intermediate revisions by one other user not shown)
Line 1: Line 1:
<!--Start WIP -->
=Disclaimer=
{| style="width:650px;height:50px;background:lavender;border:1px solid royalblue;margin: 1em auto 1em auto"
| align="center" | '''This article is a Work in Progress.'''  <br /><small>If you have any feedback or suggestions, please leave a comment in the discussion page.</small>
|-
|}
<!-- End WIP -->


<h1>Setting Up Your Plugin To Use An Options Menu</h1>
This tutorial is split into two main parts.  The first section is about how to set up your plugin to use an options menu, and focuses on where and how to set up your settings, and how to use them remotely.  The second section is about making the actual menu, and focuses on giving the player access to the menu and the scripting involved in creating the menu.


<p>Creating an Options Menu is a good way to keep your plugin modular without having to make multiple plugin files availableIt allows the user to change settings in-game.</p>
If you're only reading this tutorial because you want to learn how to make a scripted menu, then I recommend that you skip straight to section 3Alternately, a tutorial that focuses entirely on the scripting behind [[ShowMessage]] menus is available on [http://www.cipscis.com/ cipscis.com].  To view this tutorial, see the [[Adding_an_Options_Menu#External_Links|External Links]] section at the bottom of this page


<p>Depending on how each setting works, it will utilise a slightly different menu structure.  In order to make it clear which menu structure should be used for each setting, I'm going to sort them into four categories:</p>
==Setting Up Your Plugin To Use An Options Menu==


<ul>
Creating an Options Menu is a good way to keep your plugin modular without having to make multiple plugin files availableIt allows the user to change settings in-game.
<li>
General Toggle Settings, or GTs.  GTs affect the game as a whole, and can be toggled on and offThey have the simplest menu structure of all setting types, and can be toggled simply by clicking on their button in the main menu.
</li>


<li>
The settings that can be changed in your options menu may be used in many different places in your Mod, so it's important that you know where to find them so that you can change them.  If you know the location of the script in which the variable you want to use or change was declared, then you can access that variable from any script.  To do this is we're going to create a Quest with a Variable Reservoir scriptLet's give it the EditorID "ExampleVRQuest".
General Select Settings, or GSs.  GSs affect the game as a whole, and have a range of values available to them.  Their menu structure consists of a button on the main menu, and a sub-menu in which the user can select a new value for themNote that these values can include an "off" value.
</li>


<li>
===Variable Reservoirs===
Object-Specific Toggle Settings, or OTs.  OTs affect specific objects, and can be toggled on and off.  Usually, many different objects will have their own versions of each OT.  The menu structure used by OTs requires the user to select the appropriate object before toggling the OT by pressing the relevant button.
</li>


<li>
A Variable Reservoir script, or VR script, is one which contains only variable declarationsIt will never actually run, but it gives you a specific location at which you can define all of your settings.  The VR script that we'll be using in this tutorial will declare four variables:
Object-Specific Select Settings, or OSs.  OSs affect specific objects, and have a range of values available to themOSs have the most complex menu structure of any setting, in that the user must first select the relevant object, then the setting, and then choose a new value for the setting.
</li>
</ul>
 
<p>Although each of these categories utilises a slightly different menu structure, they can all be incorporated into the same Options Menu.</p>
 
<h2>Declaring Your Variables</h2>
 
<p>The first step in making these settings editable is to define them in a "Variable Reservoir", or "VR" Quest ScriptA VR script is a script that contains no Begin/End blocks, but consists entirely of variable declarations.  This example VR script, which is attached to the "Start Game Enabled" quest "ExampleVRQuest", will be used in this tutorial:</p>


<pre>ScriptName ExampleVRSCRIPT
<pre>ScriptName ExampleVRSCRIPT
short sAddExampleItem ; Default 1
short sItemCount ; Default 3


; ==============================================
short sAddDamagedWeapon ; Default 0
; General Settings
float fDamagedWeaponHealthPercent ; Default 0.75</pre>
 
short sGT1 ; Default 1 - On
short sGS1 ; Default 3
ref rGS2 ; Default ExampleRef
; ==============================================
 
; ==============================================
; Object-Specific Settings
 
; Item1
short sItem1OT1 ; Default 0 - Off
short sItem1OS1 ; Default 5
 
; ==============================================
</pre>
 
<p>Note that it consists entirely of variable declarations, and will never actually run.  That means that these variables need some method of being initialised to their default values - at the moment they are all 0.</p>
 
<p>Keep in mind that your variables should be named according to their function.  The variable names I have used here were chosen to illustrate the type of setting they are used for, and are not practical.</p>


<p>You should name your variables according to the setting that they affect, and the object that they are associated to (for OTs and OSs).</p>
Keep in mind that these settings are only examples, yours will probably be completely different.  As you can see, I've prefixed these variables according to their type (s for short, f for float), and named them according to their function.  While it's up to you to choose a naming system that you're comfortable with, it's important that you name your variables according to their function.  Don't start using variable names like "Variable1" or "ReferenceVariable", as these are ambiguous and it'll make your scripts very difficult to understand.


<h2>Initialising Your Variables</h2>
Now that you've defined your variables in a specific location, you can use them remotely in any script.  The syntax for using a variable remotely is "LocationEditorID.VariableName", where LocationEditorID is the EditorID of the quest or reference that the script where the variable is declared is attached to.


<p>To initialise these variables to their default values, we are going to add a result script to Stage 1 of our VR quest.  A result script is different from regular scripts in that it can't declare any variables of its own, and it doesn't contain any Begin/End blocks.  Instead, a quest result script will run once when its quest reaches the stage that it is attached to, provided that the conditions assigned to it evaluate to true.</p>
===Initialising Your Settings===


<p>This is the result script that would used to initialise the variables in ExampleVRSCRIPT:</p>
At the moment, the variables that have just been declared have been initialised to a value of "0".  However, we want most of our variables to be set to a default value other than 0.  To do this, we're going to create a quest stage result script for stage 1 of ExampleVRQuest:


<pre>set ExampleVRQuest.sGT1 to 1 ; On
<pre>set ExampleVRQuest.sAddExampleItem to 1
set ExampleVRQuest.sGS1 to 3
set ExampleVRQuest.sItemCount to 3
set ExampleVRQuest.rGS2 to ExampleRef


set ExampleVRQuest.sItem1OT1 to 0 ; Off
set ExampleVRQuest.sAddDamagedWeapon to 0
set ExampleVRQuest.sItem1OS1 to 5
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.75</pre>
</pre>


<p>Note that, even though variables initialise to 0 anyway, we have set sItem1OT1 to 0 in this result scriptThis ensures that a "Defaults" option can be included in the Options Menu that can set or reset all settings to their default values with a simple "SetStage ExampleVRQuest 1" command.</p>
Note that, even though these variables are all initialised to 0, I have still set sAddDamagedWeapon to its default value of 0.  Doing this means that you can create a "Defaults" button in your Options Menu that sets all settings to their default values by using [[SetStage]].


<p>Of course, we need some way in which to run this result script initially - as it will not be run until our VR quest reaches Stage 1Attaching the result script to Stage 0 (which quests default to when they first start running) won't work, we need to actually use "SetStage ExampleVRQuest 1" somewhere.</p>
Now, you need a way of running this initialisation script in order to set your settings to their default valuesThere are a few ways to do this, the one I'm going to describe here involves creating a separate "Initialisation Quest".  Let's give it the EditorID "ExampleInitQuest".


<p>To do this, we will create another "Start Game Enabled" quest, this time in the interest of initialising our variablesThe quest script attached to ExampleInitQuest will look like this:</p>
An Initialisation Quest is a Start Game Enabled quest that stops itself with the [[StopQuest]] command after running its script onceIt can be used to initialise any number of things - in this example we'll use it to initialise our variables by using [[SetStage]]:


<pre>ScriptName ExampleInitSCRIPT
<pre>ScriptName ExampleInitSCRIPT
Line 89: Line 49:
StopQuest ExampleInitQuest
StopQuest ExampleInitQuest


End
End</pre>
</pre>


<p>As you can see, it will run only once - using "SetStage ExampleVRQuest 1" to initialise our settings to their default values.</p>
Now, when you first load the game with your plugin installed, your settings will be initialised to their default values. This means that you can now use them remotely, wherever they need to be used.


<h2>Utilising Your Variables</h2>
===Using Your Settings===


<p>At the moment, all our variables are doing is sitting in our VR questIf we make any changes to their values they won't affect the plugin in any way.</p>
At the moment, all your settings are doing is sitting in your VR Quest at their default values.  They're not actually <em>doing</em> anything, and changing them at this point wouldn't have any effectBefore there's any point in creating an options menu to change them, you'll have to utilise them remotely.


<p>Tying these variables to settings will always depend on just how the setting works, and is generally done differently depending on the category that the setting belongs to.</p>
How exactly you do this will, of course, depend on the nature of your settings.  The example settings used in this tutorial are just that, examples. Here's how I will be implementing them:


<h3>GTs</h3>
<pre>ScriptName ExampleItemDispenserSCRIPT


<p>General Toggle settings are usually used to toggle features on and off.  Therefore, the most common way of linking GTs to their feature is to simply enclose the relevant code in an if statement:</p>
; This script is attached to an item dispenser
; When activated, it gives out either ExampleItems or Pencils


<pre>if ExampleVRQuest.sGT1
ref rActionRef
; Function code goes here
endif
</pre>


<p>As you can see, enclosing the relevant code in an if statement like the one above will cause the feature to be present if and only if ExampleVRQuest.sGT1 is not 0.  This will enable the feature to be toggled on and off by toggling ExampleVRQuest.sGT1 on and off.</p>
Begin OnActivate


<p>For General Toggle settings, the relevant feature's code will usually be present in a single place - usually a quest script.  This means that only one section of code usually needs to be enclosed in an if statement.</p>
set rActionRef to GetActionRef
if rActionRef
if ExampleVRQuest.sAddExampleItem
rActionRef.AddItem ExampleItem ExampleVRQuest.sItemCount
else
rActionRef.AddItem Pencil01 ExampleVRQuest.sItemCount
endif
endif


<h3>GSs</h3>
End
</pre>


<p>General Select settings are usually used to change how a certain feature works.  They will usually be seen in calculations, or as parameters for certain functions.  For example:</p>
<pre>ScriptName ExampleWeaponsRackSCRIPT


<pre>ExampleVRQuest.rGS2.KillActor ExampleVRQuest.rGS2 ExampleVRQuest.sGS1
; This script is attached to a weapons rack
</pre>
; When activated, it gives out ExampleWeapons


<p>In the above code, ExampleVRQuest.rGS2 controls which actor will be killed, and ExampleVRQuest.sGS1 controls which limb will be dismembered.  They can also be used to switch on/off certain aspects of the feature:</p>
ref rActionRef


<ul>
Begin OnActivate
<li>
If ExampleVRQuest.sGS1 is set to -1, no limb will be dismembered
</li>
<li>
If ExampleVRQuest.rGS2 is set to point to a dummy reference (i.e. one the player will never encounter), no actor will be killed.
</li>
</ul>


<h3>OTs</h3>
set rActionRef to GetActionRef
if rActionRef
if ExampleVRQuest.sAddDamagedWeapon
rActionRef.AddItemHealthPercent ExampleWeapon 1 ExampleVRQuest.fDamagedWeaponHealthPercent
else
rActionRef.AddItem ExampleWeapon 1
endif
endif


<p>Object-Specific Toggle settings are usually used to toggle features on and off.  Therefore, the most common way of linking TTs to their feature is to simply enclose the relevant code in an if statement:</p>
End
 
<pre>if ExampleVRQuest.sItem1OT1
; Function code goes here
endif
</pre>
</pre>


<p>As you can see, enclosing the relevant code in an if statement like the one above will cause the feature to be present if and only if ExampleVRQuest.sItem1OT1 is not 0.  This will enable the feature to be toggled on and off by toggling ExampleVRQuest.sItem1OT1 on and off.</p>
The way in which I've implemented them isn't important to this tutorial, I've just give you those scripts as examples of how to utilise your settings.
 
<p>For Object-Specific Toggle settings, the relevant feature's code will usually be present in a multiple places, as multiple objects will have their own copy of the feature.  This means that many sections of code usually need to be enclosed in the same if statement.</p>
 
<h3>OSs</h3>
 
<p>Object-Specific Select settings are usually used to change how a certain feature works.  They will usually be seen in calculations, or as parameters for certain functions. For example:</p>


<pre>player.AddItem Item1 ExampleVRQuest.sItem1OS1
Note the syntax that I've used with these remote variables. You'll have to do the same thing, but obviously replacing ExampleVRQuest with the EditorID of your VR Quest and using your variable name instead of the example one.
</pre>


<p>In the above code, ExampleVRQuest.sItem1OS1 controls how many of Item1 is added to the playerIf sItem1OS1 is set to 0, none will be given to the player, effectively switching the feature off.</p>
RightNow that your variables have been utilised, it's time to create the options menu.


<h1>Creating An Options Menu</h1>
==Creating An Options Menu==


<p>Now that your plugin is set up for using an options menu, you can finally add the menu to your plugin.  To do this, you'll need to do two things:</p>
Now that your plugin is set up for using an options menu, you can finally add the menu to your plugin.  To do this, you'll need to do two things:


<ul>
# Give the player access to the menu
<li>Give the player access to the menu</li>
# Create the menu
<li>Create the menu</li>
</ul>


<h2>Giving the player access to the menu</h2>
===Giving The Player Access To The Menu===


<p>Before we create the actual menu, we'll have to set up the plugin so that the player is able to access the menu.  By doing this before we start working with the menu code, it'll make testing a whole lot easier.</p>
Before you create the actual menu, you'll have to set up the plugin so that the player is able to access the menu.  By doing this before you start working with the menu code, it'll make testing a whole lot easier.


<p>The method I'm going to describe here is not the only method, although it is the method that I prefer.  It consists of two stages:</p>
The method I'm going to describe here is not the only method, although it is the method that I prefer.  It consists of two stages:


<ol>
# Give the player a "Menu Key" - A piece of equipment which the player can equip in order to open the menu.
<li>Give the player a "Menu Key" - A piece of equipment which the player can equip in order to open the menu.</li>
# When the player equips the Key, a token will be added to them.  A token is a piece of unplayable (invisible to the player) equipment, and in this case will contain the script that controls the options menu.
<li>When the player equips the Key, a token will be added to them.  A token is a piece of unplayable (invisible to the player) equipment, and in this case will contain the script that controls the options menu.</li>
</ol>


<p>Before we can use the Menu Key, we're going to have to make it.  It's going to be a piece of armour with no Biped Objects (slots) so that equipping it won't force anything else to be unequipped.  It's also going to be a quest item, so the player can't lose it anywhere (this also has the side effect of preventing the player from attaching it to a hotkey).</p>
Before you can use the Menu Key in a script, you're going to have to make it.  It's going to be a piece of armour with no Biped Objects (slots) so that equipping it won't force anything else to be unequipped.  It's also going to be a quest item, so that the player can't lose it anywhere (this also has the side effect of preventing the player from attaching it to a hotkey).


<p>Remember when we initialised all our variables by using a [[SetStage]] command to run a result script?  Now we're going to go back to that result script now and add three more lines:</p>
Remember when you initialised all of your variables by using a [[SetStage]] command to run a result script?  Now you're going to go back to that result script now and add three more lines:


<pre>if player.GetItemCount ExampleMenuKey == 0
<pre>if player.GetItemCount ExampleMenuKey == 0
Line 182: Line 132:
</pre>
</pre>


<p>Now, when our variables are first initialised, the Menu Key that we just made will be automatically added to the player.</p>
Now, when your variables are first initialised, the Menu Key that you just made will be automatically added to the player.


<p>When the Menu Key is equipped, we want it to run some code that adds a token to the player, but before we can do this we have to make the token.  It's also going to be a piece of armour, but unlike the Menu Key it's going to have the "playable" flag unchecked.  This ensures that the player can't see it in their inventory.</p>
When the Menu Key is equipped, you'll want it to run some code that adds a token to the player, but before you can do this you have to make the token.  It's also going to be a piece of armour, but unlike the Menu Key it's going to have the "playable" flag unchecked.  This ensures that the player can't see it in their inventory.


<p>Now that we've created the token, we can write a script for the Menu Key that adds the token to the player's inventory.  Because the Menu Key is a quest item, it can't be given a hotkey - meaning that the player has to equip it manually from their inventory.  If you want the menu to open right then and there, this script will suffice:</p>
Once you've created the token, you can write a script for the Menu Key that adds the token to the player's inventory.  Because the Menu Key is a quest item, it can't be given a hotkey - meaning that the player has to equip it manually from their inventory.  If you want the menu to open right then and there, this script will suffice:


<pre>ScriptName ExampleMenuKeySCRIPT
<pre>ScriptName ExampleMenuKeySCRIPT
Line 200: Line 150:
</pre>
</pre>


<p>However, I personally prefer to have the menu open after the player has put away their Pip-Boy.  To do this, a timer will need to be used so that the menu doesn't appear as soon as the player starts to put away their Pip-Boy (that's when the game returns to GameMode).  It takes about half a second for the player to put the Pip-Boy away, so let's use a script like this:</p>
However, I personally prefer to have the menu open after the player has put away their Pip-Boy.  To do this, a timer will need to be used so that the menu doesn't appear as soon as the player starts to put away their Pip-Boy (that's when the game returns to GameMode).  It takes about half a second for the player to put the Pip-Boy away, so let's use a script like this:


<pre>ScriptName ExampleMenuKeySCRIPT
<pre>ScriptName ExampleMenuKeySCRIPT
Line 231: Line 181:
</pre>
</pre>


<p>That's looking very nice now, but because the menu is being opened in GameMode you may also want to prevent the player from opening the menu while they're in combat.  To do this, you'll need to check whether or not the player is in combat when they equip the Menu Key, and unequip the Key without adding the token if this is the case.</p>
That's looking very nice now, but because the menu is being opened in GameMode you may also want to prevent the player from opening the menu while they're in combat.  To do this, you'll need to check whether or not the player is in combat when they equip the Menu Key, and unequip the Key without adding the token if this is the case.


<pre>ScriptName ExampleMenuKeySCRIPT
<pre>ScriptName ExampleMenuKeySCRIPT
Line 268: Line 218:
</pre>
</pre>


<p>Now that that's set up, all that's left is the actual Menu script, which will be attached to the token.</p>
Now that that's set up, all that's left is the actual Menu script, which will be attached to the token.


<h2>Creating the menu</h2>
===Creating The Menu===


<p>The main difference between a single-level menu and a multi-level menu is that the latter requires navigation - the script needs to remember the path that the player took to get to where they are.  Our script will achieve this by having a "sMenuLevel" variable to store the current level of navigation, and giving each level of navigation its own "sButton" variable.  That way, the script can remember which buttons the player pressed on each level of navigation.</p>
The main difference between a single-level menu and a multi-level menu is that the latter requires navigation - the script needs to remember the path that the player took to get to where they are.  Our script will achieve this by having a "sMenuLevel" variable to store the current level of navigation, and giving each level of navigation its own "sButton" variable.  That way, the script can remember which buttons the player pressed on each level of navigation.


<p>The example menu that I'm going to use for this tutorial will have four levels, each with their own sButton variables:</p>
The example menu that I'm going to use for this tutorial will have five levels, each with their own sButton variables:


<ul>
* Level 0 - Initialisation->Introduction Message
<li>Level 0 - Initialisation->Introduction Message</li>
* Level 1 - Introduction Message->Main Menu
<li>Level 1 - Introduction Message->Main Menu</li>
* Level 2 - Main Menu->Sub Menu
<li>Level 2 - Main Menu->Sub Menu</li>
* Level 3 - Sub menu->Setting
<li>Level 3 - Sub menu->Settings</li>
* Level 4 - Setting->Value
</ul>


<p>The basic structure of the script consists of one big GameMode block filled with a list of "if" statements checking the values of sMenuLevel and the various sButton variables.  At the highest level, it only checks sMenuLevel:</p>
The basic structure of the script consists of one big GameMode block filled with a list of "if" statements checking the values of sMenuLevel and the various sButton variables.  At the highest level, it only checks sMenuLevel:


<pre>ScriptName ExampleMenuSCRIPT
<pre>ScriptName ExampleMenuSCRIPT
Line 292: Line 241:
; 2 - Main Menu -> Sub Menu
; 2 - Main Menu -> Sub Menu
; 3 - Sub Menu -> Setting
; 3 - Sub Menu -> Setting
; 4 - Setting -> Value


; This GameMode block contains all of the ShowMessage framework
; This GameMode block contains all of the ShowMessage framework
Line 324: Line 274:
; =================================================
; =================================================
if sMenuLevel == 3
if sMenuLevel == 3
endif
; =================================================
; SETTING --> VALUE
; =================================================
if sMenuLevel == 4
endif
endif
Line 330: Line 288:
</pre>
</pre>


<p>As you can see, the GameMode block is now divided up into four different segments.  Which one runs will depend on the menu's current level of navigation.</p>
As you can see, the GameMode block is now divided up into five different segments.  Which one runs will depend on the menu's current level of navigation.
 
Before we look at what any of these blocks will contain in specific detail, I'd like to go over their structure in general.
 
====Basic Structure Of A Menu Level====
 
Each level of our menu is contained within an "if" statement, which checks the value of sMenuLevel.  Within this "if" statement the structure of each menu level is basically the same, although deeper levels will be more complicated.  Each level, unless stated otherwise,  has the following structures in common:
 
'''Button Set'''
 
Each menu level begins by checking if its sButton variable has been set/reset (i.e. has a value of -1) and, if so, sets it to [[GetButtonPressed]]:
 
<pre>set sButtonX to GetButtonPressed</pre>
 
Using this method ensures that the value of each sButton variable is maintained until it is explicitly reset.
 
'''Button Check 1'''
 
If the sButton variable still has a value of -1 after Button Set, then "Return" is used to end the script for the current frame.
 
<pre>if sButtonX == -1
Return
elseif ...</pre>
 
'''Navigation Check'''
 
If the script has got this far, then the player must have pressed a button.  If the menu level is deep enough, "if" statements are used to determine, via previous sButton variables, which path the player took to their current menu.  If the menu level is not deep enough, or there is only one possible path that the player could have taken, there is no need for a navigation check.
 
<pre>if sButtonX-1 == 0 ; Player came through button 0
; Button Check 2
elseif sButtonX-1 == 0 ; Player came through button 1
; Button Check 2
elseif ...</pre>
 
'''Button Check 2'''
 
"If" statements are used to cycle through the various buttons that the player could possibly have pressed.  If there is a "Back" or "Done" button (normally there won't be both), then it is usually the bottom button.  This allows it to be checked with an "else" statement as opposed to an "elseif" statement, which makes it slightly easier to add more buttons to the menu later.
 
<pre>if sButtonX == 0 ; Player pressed button 0
; Button Result
elseif sButtonX == 1 ; Player pressed button 1
; Button Result
elseif...
else ; Player pressed Back/Done button
; Button Result
endif</pre>
 
'''Button Result'''
 
This is essentially the button's "result script".  It contains the code that will execute itself when the button is pressed.  With the exceptions of "Done" buttons and buttons that leave the player on the same menu, this code will contain a [[ShowMessage]] function.
 
If the button doesn't take the player to the <em>next</em> menu level, the result script is terminated with a "Return" statement.  The deepest level is an exception to this, in that it is the buttons that don't leave the player on the <em>same</em> level that are terminated with a "Return" statement.
<pre>ShowMessage PreviousMessage
set sMenuLevel to X-1
Return
</pre>
Because the deepest level leaves the player on the same level instead of taking them back to the previous level, the path cannot branch from here so most of the buttons will probably take the player to the same menu.  Because of this, the [[ShowMessage]] function can be called at the end of the navigation check, after button check 2, instead of being called multiple times in the result for each button.
<pre>if sButtonX == 0 ; Player pressed button 0
...
else ; Player pressed "Back" button
ShowMessage PreviousMessage
set sMenuLevel to X - 1
Return
endif
ShowMessage CurrentMessage</pre>
 
'''Menu Level Incrementation'''
 
Because of the various "Return" statements utilised in each menu level, this code will only execute if the player has selected a button that takes them to the next menu level (or, in the case of the deepest level, leaves them on the same level).
 
<pre>set sMenuLevel to X + 1</pre>
 
 
Keep these structures in mind as you read through the following sections, which describe the contents of each section of this example menu.


<p>The first section of the script runs when sMenuLevel == 0.  Because variables are initialised to 0, this section will run when the script first runs.  This section is also the simplest - all that happens in it is the introduction message is called, sMenuLevel is incremented to 1, and sButton1 is intialised:</p>
===Contents Of An Example Menu===
 
====Initialisation====
 
The first section of the script runs when sMenuLevel == 0.  Because variables are initialised to 0, this section will run when the script first runs.  This section is also the simplest - all that happens in it is the introduction message is called, sMenuLevel is incremented to 1, and sButton1 is intialised:


<pre> ; =================================================
<pre> ; =================================================
Line 340: Line 375:
ShowMessage introMessage
ShowMessage introMessage
set sMenuLevel to 1
set sMenuLevel to 1
set sButton1 to -1
endif
endif
</pre>
</pre>


<p>If you're wondering why I've included an Introduction message before the Main Message, it's useful if you have conditions for the buttons in the Main Menu that have to be initialised before each time it's opened.  Usually, you'll use an OnAdd block in this script to initialise them.</p>
If you're wondering why I've included an Introduction message before the Main Message, it's quite useful if you have conditions for the buttons in the Main Menu that have to be initialised before each time it's opened.  Usually, you'll use an OnAdd block in this script to initialise them.
 
====Introduction Message====


<p>Now, because we've set sMenuLevel to 1, the next section of the script will run.  In this section, we need to check when the player clicks the button to continue from the Introduction message into the Main Menu.  When this happens, the script will call the Main Menu, increment sMenuLevel to 2, and initialise the next sButton variable - sButton2:</p>
Now, because you've set sMenuLevel to 1, the next section of the script will run.  In this section, you need to check when the player clicks the button to continue from the Introduction message into the Main Menu by using [[GetButtonPressed]].  When the player clicks the button, the script will call the Main Menu, increment sMenuLevel to 2, and initialise the next sButton variable - sButton2:


<pre> ; =================================================
<pre> ; =================================================
Line 353: Line 389:
if sMenuLevel == 1
if sMenuLevel == 1


if sButton1 == -1
set sButton1 to GetButtonPressed
set sButton1 to GetButtonPressed
endif


if sButton1 == -1 ; None of the buttons have been pressed yet
if sButton1 == -1 ; None of the buttons have been pressed yet
Line 366: Line 400:
ShowMessage MainMenuMessage
ShowMessage MainMenuMessage
set sMenuLevel to 2
set sMenuLevel to 2
set sButton2 to -1


endif
endif
Line 373: Line 406:
</pre>
</pre>


<p>The next section is a step up again in complexity.  Unlike the Introduction Message, the Main Menu contains multiple buttons.  That means that, in this section of the script, we need to check which button the player presses and update the Menu accordingly.</p>
====Main Menu====
 
This section is a step up again in complexity.  Unlike the Introduction Message, the Main Menu contains multiple buttons.  That means that, in this section of the script, you need to check which button the player presses and update the Menu accordingly.


<p>There are three types of button in this Main Menu:</p>
There are three types of button in this example Main Menu:


<ul>
* Buttons that take the player to a Sub Menu
<li>Buttons that take the player to a Sub Menu</li>
* A "Defaults" button that sets all settings to their default values
<li>A "Defaults" button that sets all settings to their default values</li>
* A "Done" button that closes the menu
<li>A "Done" button that closes the menu</li>
</ul>


<p>If the player clicks on a Sub Menu button, we'll need to show the appropriate message, as well as increment sMenuLevel to 3 and set sButton3 to -1.  If they click the "Defaults" button, we'll need to set all settings to their default values (with "SetStage ExampleVRQuest 1", remember?), then show the Main Menu again and reset sButton2 to -1.  If the player clicks on the "Done" button, we'll remove the token from their inventory to close the menu.</p>
If the player clicks on a Sub Menu button, we'll need to show the appropriate message, as well as increment sMenuLevel to 3 and set sButton3 to -1.  If they click the "Defaults" button, we'll need to set all settings to their default values (with "SetStage ExampleVRQuest 1", remember?), then show the Main Menu again and reset sButton2 to -1.  If the player clicks on the "Done" button, we'll remove the token from their inventory to close the menu.


<pre> ; =================================================
<pre> ; =================================================
Line 390: Line 423:
if sMenuLevel == 2
if sMenuLevel == 2


if sButton2 == -1
set sButton2 to GetButtonPressed
set sButton2 to GetButtonPressed
endif


if sButton2 == -1 ; None of the buttons have been pressed yet
if sButton2 == -1 ; None of the buttons have been pressed yet
Line 398: Line 429:
Return
Return


elseif sButton2 == 0 ; Show the 1st Sub Menu
elseif sButton2 == 0 ; Show the "Weapons Rack" Sub Menu


ShowMessage SubMenu1Message
ShowMessage WeaponsRackSubMenuMessage


elseif sButton2 == 1 ; Show the 2nd Sub Menu
elseif sButton2 == 1 ; Show the "Item Dispenser" Sub Menu


ShowMessage SubMenu2Message
ShowMessage ItemDispenserSubMenuMessage


elseif sButton2 == 2 ; "Defaults" button - reinitialise all settings
elseif sButton2 == 2 ; "Defaults" button - reinitialise all settings
Line 410: Line 441:
SetStage ExampleVRQuest 1
SetStage ExampleVRQuest 1
ShowMessage MainMenuMessage
ShowMessage MainMenuMessage
set sButton2 to -1
Return
Return


Line 421: Line 451:


set sMenuLevel to 3
set sMenuLevel to 3
set sButton3 to -1


endif
endif
</pre>
</pre>


<p>As you can see, this section has the same organisation as the previous section.  The only differences are that there are "Done" and "Defaults" buttons and, depending on which button the player pressed, a different menu is shown.  We still use [[GetButtonPressed]] to check if a button has been pressed, and when one has we increment sMenuLevel and initialise the next sButton variable.</p>
As you can see, this section has the same organisation as the previous section.  The only differences are that there are "Done" and "Defaults" buttons and, depending on which button the player pressed, a different menu is shown.  We still use [[GetButtonPressed]] to check if a button has been pressed, and when one has we increment sMenuLevel and initialise the next sButton variable.
 
====Sub Menu====
 
This section is the similar to the previous section - it's where the player selects the setting that they'd like to change.  Once again, there are multiple types of button in these menus:


<p>The next section is the final section for this example script - it's where the actual settings are changed.  It differs from the previous level in that, when a button is pressed, we change a quest variable (remember them?) instead of showing a menu, and it has a "Back" button instead of a "Done" button.</p>
* Buttons that take the player to a Setting Menu, where the can select a new value for the setting
* Buttons that toggle a setting between two values


<p>There are two ways this section can be organised - by which setting was selected and by which sub-menu the player came throughIn this example, the script is sorted by which sub-menu the player came through.  The reason for this is that it is easier to visualise and can shorten the script a fair amount.</p>
We're now deep enough in the menu that there are multiple paths that the player could have taken to reach their current menu levelBecause of this, we'll need to check the values of the sButton variables of the higher levels, in order to build a kind of cookie-trail.


<pre> ; =================================================
<pre> ; =================================================
Line 437: Line 471:
if sMenuLevel == 3
if sMenuLevel == 3


if sButton3 == -1
set sButton3 to GetButtonPressed
set sButton3 to GetButtonPressed
endif


if sButton3 == -1
if sButton3 == -1
Line 445: Line 477:
Return ; None of the buttons have been pressed yet
Return ; None of the buttons have been pressed yet


elseif sButton2 == 0 ; The player came through the 1st Sub Menu
elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu
 
if sButton3 == 0 ; The "Damaged" setting was selected
set ExampleVRQuest.sAddDamagedWeapon to (ExampleVRQuest.sAddDamagedWeapon == 0)
ShowMessage WeaponsRackSubMenuMessage
Return
elseif sButton3 == 1 ; The "Condition" setting was selected
ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent
else ; "Back" button was pressed
ShowMessage MainMenuMessage
set sMenuLevel to 2
Return
endif
 
elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu


if sButton3 == 0 ; The 1st setting was selected
if sButton3 == 0 ; The "Item" setting was selected
set VariablesQuest.Variable1 to (VariablesQuest.Variable1 == 0)
set ExampleVRQuest.sAddExampleItem to (ExampleVRQuest.sAddExampleItem == 0)
elseif sButton3 == 1 ; The 2nd setting was selected
ShowMessage ItemDispenserSubMenuMessage
set VariablesQuest.Variable2 to (VariablesQuest.Variable2 == 0)
Return
elseif sButton3 == 1 ; The "Amount" setting was selected
ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount
else ; "Back" button was pressed
ShowMessage MainMenuMessage
set sMenuLevel to 2
Return
endif
endif
ShowMessage SubMenu1Message


elseif sButton2 == 1 ; The player came through the 2nd Sub Menu
endif
 
set sMenuLevel to 4
 
endif
</pre>
 
As you can see, the highest level of "if" statements in this example checks which Sub Menu the player came though, and the next level of "if" statements checks Setting they just selected.  Depending on which button the player selected, either the setting will be toggled or the player will enter a new menu in which they can select a new value for the setting that they selected.
 
The next section is the final section in this example menu.  At this level, the player will be able to choose a new value for the setting that they just selected.
 
====Setting====
 
This section differs slightly from all previous sections, because the player can advance no further in the menu.  This means that, instead of taking the player to the next menu level, most of the buttons will leave the player on the current level.  This means a couple of structural changes need to take place:
 
* The "menu level incrementation" doesn't actually increment sMenuLevel - all it does is reset the current sButton variable to -1
* Because most buttons in any menu will leave the player on the same menu, the [[ShowMessage]] section of the results of these buttons can be moved to the end of the navigation check, after button check 2.
 
<pre> ; =================================================
; SETTING --> VALUE
; =================================================
if sMenuLevel == 4
 
set sButton4 to GetButtonPressed
 
if sButton4 == -1
 
Return ; None of the buttons have been pressed yet
 
elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu


if sButton3 == 0 ; The 1st setting was selected
; Don't need to check sButton3 - it must be equal to 1
set VariablesQuest.Variable3 to (VariablesQuest.Variable3 == 0)
if sButton4 == 0
elseif sButton3 == 1 ; The 2nd setting was selected
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.25
set VariablesQuest.Variable4 to (VariablesQuest.Variable4 == 0)
elseif sButton4 == 1
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.50
elseif sButton4 == 2
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.75
else ; "Back" button was pressed
ShowMessage WeaponsRackSubMenuMessage
set sMenuLevel to 3
Return
endif
endif
ShowMessage SubMenu2Message
ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent


else ; "Back" button was pressed
elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu


ShowMessage MainMenuMessage
; Don't need to check sButton3 - it must be equal to 1
set sMenuLevel to 2
if sButton4 == 0
set sButton2 to -1
set ExampleVRQuest.sItemCount to 1
Return
if sButton4 == 1
set ExampleVRQuest.sItemCount to 2
if sButton4 == 2
set ExampleVRQuest.sItemCount to 3
else ; "Back" button was pressed
ShowMessage ItemDispenserSubMenuMessage
set sMenuLevel to 3
Return
endif
ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount


endif
endif
set sMenuLevel to 3
set sButton3 to -1


endif
endif
</pre>
</pre>


<p>As you can see, the highest level of "if" statements in this example checks which Sub Menu the player came though, and the next level of "if" statements checks setting they just selected.  It would be perfectly acceptable to swap these two around if you'd like.</p>
===Conclusion===


<p>This method of making multi-level menus can be used to make menus with any number of levels that you'd like, although obviously the script will become more complicated at higher levels.  You should also remember that your menu doesn't have to function as an options menu.  It could be used for many other things - for example, determining which path the player will progress along in a quest.</p>
This method of making multi-level menus can be used to make menus with any number of levels that you'd like, although obviously the script will become more complicated at higher levels.  You should also remember that your menu doesn't have to function as an options menu.  It could be used for many other things - for example, determining which path the player will progress along in a quest.


<p>Now that the script is finished, let's look at the whole thing in one piece!</p>
Now that the script is finished, let's look at the whole thing in one piece!


<pre>ScriptName ExampleMenuSCRIPT
<pre>ScriptName ExampleMenuTokenSCRIPT


short sMenuLevel ; Records the current depth of the ShowMessage framework
short sMenuLevel ; Records the current depth of the ShowMessage framework
Line 491: Line 584:
; 2 - Main Menu -> Sub Menu
; 2 - Main Menu -> Sub Menu
; 3 - Sub Menu -> Setting
; 3 - Sub Menu -> Setting
; 4 - Setting -> Value


short sButton1 ; sButton variable for the Introduction Message level - Menu level 1
short sButton1 ; sButton variable for the Introduction Message level - Menu level 1
short sButton2 ; sButton variable for the Main Menu level - Menu level 2
short sButton2 ; sButton variable for the Main Menu level - Menu level 2
short sButton3 ; sButton variable for the Sub Menu level - Menu level 3
short sButton3 ; sButton variable for the Sub Menu level - Menu level 3
short sButton4 ; sButton variable for the Setting level - Menu level 4


; This GameMode block contains all of the ShowMessage framework
; This GameMode block contains all of the ShowMessage framework
Line 505: Line 600:
ShowMessage introMessage
ShowMessage introMessage
set sMenuLevel to 1
set sMenuLevel to 1
set sButton1 to -1
endif
endif


Line 514: Line 608:
if sMenuLevel == 1
if sMenuLevel == 1


if sButton1 == -1
set sButton1 to GetButtonPressed
set sButton1 to GetButtonPressed
endif


if sButton1 == -1 ; None of the buttons have been pressed yet
if sButton1 == -1 ; None of the buttons have been pressed yet
Line 527: Line 619:
ShowMessage MainMenuMessage
ShowMessage MainMenuMessage
set sMenuLevel to 2
set sMenuLevel to 2
set sButton2 to -1


endif
endif
Line 539: Line 630:
if sMenuLevel == 2
if sMenuLevel == 2


if sButton2 == -1
set sButton2 to GetButtonPressed
set sButton2 to GetButtonPressed
endif


if sButton2 == -1 ; None of the buttons have been pressed yet
if sButton2 == -1 ; None of the buttons have been pressed yet
Line 547: Line 636:
Return
Return


elseif sButton2 == 0 ; Show the 1st Sub Menu
elseif sButton2 == 0 ; Show the "Weapons Rack" Sub Menu


ShowMessage SubMenu1Message
ShowMessage WeaponsRackSubMenuMessage


elseif sButton2 == 1 ; Show the 2nd Sub Menu
elseif sButton2 == 1 ; Show the "Item Dispenser" Sub Menu


ShowMessage SubMenu2Message
ShowMessage ItemDispenserSubMenuMessage


elseif sButton2 == 2 ; "Defaults" button - reinitialise all settings
elseif sButton2 == 2 ; "Defaults" button - reinitialise all settings
Line 559: Line 648:
SetStage ExampleVRQuest 1
SetStage ExampleVRQuest 1
ShowMessage MainMenuMessage
ShowMessage MainMenuMessage
set sButton2 to -1
Return
Return


Line 570: Line 658:


set sMenuLevel to 3
set sMenuLevel to 3
set sButton3 to -1


endif
endif
Line 580: Line 667:
if sMenuLevel == 3
if sMenuLevel == 3


if sButton3 == -1
set sButton3 to GetButtonPressed
set sButton3 to GetButtonPressed
endif


if sButton3 == -1
if sButton3 == -1
Line 588: Line 673:
Return ; None of the buttons have been pressed yet
Return ; None of the buttons have been pressed yet


elseif sButton2 == 0 ; The player came through the 1st Sub Menu
elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu


if sButton3 == 0 ; The 1st setting was selected
if sButton3 == 0 ; The "Damaged" setting was selected
set VariablesQuest.Variable1 to (VariablesQuest.Variable1 == 0)
set ExampleVRQuest.sAddDamagedWeapon to (ExampleVRQuest.sAddDamagedWeapon == 0)
elseif sButton3 == 1 ; The 2nd setting was selected
ShowMessage WeaponsRackSubMenuMessage
set VariablesQuest.Variable2 to (VariablesQuest.Variable2 == 0)
Return
elseif sButton3 == 1 ; The "Condition" setting was selected
ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent
else ; "Back" button was pressed
ShowMessage MainMenuMessage
set sMenuLevel to 2
Return
endif
endif
ShowMessage SubMenu1Message


elseif sButton2 == 1 ; The player came through the 2nd Sub Menu
elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu


if sButton3 == 0 ; The 1st setting was selected
if sButton3 == 0 ; The "Item" setting was selected
set VariablesQuest.Variable3 to (VariablesQuest.Variable3 == 0)
set ExampleVRQuest.sAddExampleItem to (ExampleVRQuest.sAddExampleItem == 0)
elseif sButton3 == 1 ; The 2nd setting was selected
ShowMessage ItemDispenserSubMenuMessage
set VariablesQuest.Variable4 to (VariablesQuest.Variable4 == 0)
Return
elseif sButton3 == 1 ; The "Amount" setting was selected
ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount
else ; "Back" button was pressed
ShowMessage MainMenuMessage
set sMenuLevel to 2
Return
endif
endif
ShowMessage SubMenu2Message


else ; "Back" button was pressed
endif
 
set sMenuLevel to 4
 
endif
 


ShowMessage MainMenuMessage
; =================================================
set sMenuLevel to 2
; SETTING --> VALUE
set sButton2 to -1
; =================================================
Return
if sMenuLevel == 4
 
set sButton4 to GetButtonPressed
 
if sButton4 == -1
 
Return ; None of the buttons have been pressed yet
 
elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu
 
; Don't need to check sButton3 - it must be equal to 1
if sButton4 == 0
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.25
elseif sButton4 == 1
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.50
elseif sButton4 == 2
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.75
else ; "Back" button was pressed
ShowMessage WeaponsRackSubMenuMessage
set sMenuLevel to 3
Return
endif
ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent
 
elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu
 
; Don't need to check sButton3 - it must be equal to 1
if sButton4 == 0
set ExampleVRQuest.sItemCount to 1
if sButton4 == 1
set ExampleVRQuest.sItemCount to 2
if sButton4 == 2
set ExampleVRQuest.sItemCount to 3
else ; "Back" button was pressed
ShowMessage ItemDispenserSubMenuMessage
set sMenuLevel to 3
Return
endif
ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount


endif
endif
set sMenuLevel to 3
set sButton3 to -1


endif
endif
Line 623: Line 758:
</pre>
</pre>


<h2>See Also</h2>
==External Links==
* [http://www.cipscis.com/fallout/tutorials/menu.aspx Making a Menu]


<ul>
[[Category:Scripting]]
<li>
[[Category:Tutorials]]
[[SetStage]]
</li>
<li>
[[ShowMessage]]
</li>
<li>
[[GetButtonPressed]]
</li>
<li>
[[RemoveMe]]
</li>
<li>
[[AddItem]]
</li>
<li>
[[UnequipItem]]
</li>
<li>
[[IsInCombat]]
</li>
</ul>

Latest revision as of 22:12, 23 July 2010

Disclaimer[edit | edit source]

This tutorial is split into two main parts. The first section is about how to set up your plugin to use an options menu, and focuses on where and how to set up your settings, and how to use them remotely. The second section is about making the actual menu, and focuses on giving the player access to the menu and the scripting involved in creating the menu.

If you're only reading this tutorial because you want to learn how to make a scripted menu, then I recommend that you skip straight to section 3. Alternately, a tutorial that focuses entirely on the scripting behind ShowMessage menus is available on cipscis.com. To view this tutorial, see the External Links section at the bottom of this page

Setting Up Your Plugin To Use An Options Menu[edit | edit source]

Creating an Options Menu is a good way to keep your plugin modular without having to make multiple plugin files available. It allows the user to change settings in-game.

The settings that can be changed in your options menu may be used in many different places in your Mod, so it's important that you know where to find them so that you can change them. If you know the location of the script in which the variable you want to use or change was declared, then you can access that variable from any script. To do this is we're going to create a Quest with a Variable Reservoir script. Let's give it the EditorID "ExampleVRQuest".

Variable Reservoirs[edit | edit source]

A Variable Reservoir script, or VR script, is one which contains only variable declarations. It will never actually run, but it gives you a specific location at which you can define all of your settings. The VR script that we'll be using in this tutorial will declare four variables:

ScriptName ExampleVRSCRIPT
short sAddExampleItem			; Default 1
short sItemCount			; Default 3

short sAddDamagedWeapon		; Default 0
float fDamagedWeaponHealthPercent	; Default 0.75

Keep in mind that these settings are only examples, yours will probably be completely different. As you can see, I've prefixed these variables according to their type (s for short, f for float), and named them according to their function. While it's up to you to choose a naming system that you're comfortable with, it's important that you name your variables according to their function. Don't start using variable names like "Variable1" or "ReferenceVariable", as these are ambiguous and it'll make your scripts very difficult to understand.

Now that you've defined your variables in a specific location, you can use them remotely in any script. The syntax for using a variable remotely is "LocationEditorID.VariableName", where LocationEditorID is the EditorID of the quest or reference that the script where the variable is declared is attached to.

Initialising Your Settings[edit | edit source]

At the moment, the variables that have just been declared have been initialised to a value of "0". However, we want most of our variables to be set to a default value other than 0. To do this, we're going to create a quest stage result script for stage 1 of ExampleVRQuest:

set ExampleVRQuest.sAddExampleItem to 1
set ExampleVRQuest.sItemCount to 3

set ExampleVRQuest.sAddDamagedWeapon to 0
set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.75

Note that, even though these variables are all initialised to 0, I have still set sAddDamagedWeapon to its default value of 0. Doing this means that you can create a "Defaults" button in your Options Menu that sets all settings to their default values by using SetStage.

Now, you need a way of running this initialisation script in order to set your settings to their default values. There are a few ways to do this, the one I'm going to describe here involves creating a separate "Initialisation Quest". Let's give it the EditorID "ExampleInitQuest".

An Initialisation Quest is a Start Game Enabled quest that stops itself with the StopQuest command after running its script once. It can be used to initialise any number of things - in this example we'll use it to initialise our variables by using SetStage:

ScriptName ExampleInitSCRIPT

Begin GameMode

	SetStage ExampleVRQuest 1
	StopQuest ExampleInitQuest

End

Now, when you first load the game with your plugin installed, your settings will be initialised to their default values. This means that you can now use them remotely, wherever they need to be used.

Using Your Settings[edit | edit source]

At the moment, all your settings are doing is sitting in your VR Quest at their default values. They're not actually doing anything, and changing them at this point wouldn't have any effect. Before there's any point in creating an options menu to change them, you'll have to utilise them remotely.

How exactly you do this will, of course, depend on the nature of your settings. The example settings used in this tutorial are just that, examples. Here's how I will be implementing them:

ScriptName ExampleItemDispenserSCRIPT

; This script is attached to an item dispenser
; When activated, it gives out either ExampleItems or Pencils

ref rActionRef

Begin OnActivate

	set rActionRef to GetActionRef
	if rActionRef
		if ExampleVRQuest.sAddExampleItem
			rActionRef.AddItem ExampleItem ExampleVRQuest.sItemCount
		else
			rActionRef.AddItem Pencil01 ExampleVRQuest.sItemCount
		endif
	endif

End
ScriptName ExampleWeaponsRackSCRIPT

; This script is attached to a weapons rack
; When activated, it gives out ExampleWeapons

ref rActionRef

Begin OnActivate

	set rActionRef to GetActionRef
	if rActionRef
		if ExampleVRQuest.sAddDamagedWeapon
			rActionRef.AddItemHealthPercent ExampleWeapon 1 ExampleVRQuest.fDamagedWeaponHealthPercent
		else
			rActionRef.AddItem ExampleWeapon 1
		endif
	endif

End

The way in which I've implemented them isn't important to this tutorial, I've just give you those scripts as examples of how to utilise your settings.

Note the syntax that I've used with these remote variables. You'll have to do the same thing, but obviously replacing ExampleVRQuest with the EditorID of your VR Quest and using your variable name instead of the example one.

Right. Now that your variables have been utilised, it's time to create the options menu.

Creating An Options Menu[edit | edit source]

Now that your plugin is set up for using an options menu, you can finally add the menu to your plugin. To do this, you'll need to do two things:

  1. Give the player access to the menu
  2. Create the menu

Giving The Player Access To The Menu[edit | edit source]

Before you create the actual menu, you'll have to set up the plugin so that the player is able to access the menu. By doing this before you start working with the menu code, it'll make testing a whole lot easier.

The method I'm going to describe here is not the only method, although it is the method that I prefer. It consists of two stages:

  1. Give the player a "Menu Key" - A piece of equipment which the player can equip in order to open the menu.
  2. When the player equips the Key, a token will be added to them. A token is a piece of unplayable (invisible to the player) equipment, and in this case will contain the script that controls the options menu.

Before you can use the Menu Key in a script, you're going to have to make it. It's going to be a piece of armour with no Biped Objects (slots) so that equipping it won't force anything else to be unequipped. It's also going to be a quest item, so that the player can't lose it anywhere (this also has the side effect of preventing the player from attaching it to a hotkey).

Remember when you initialised all of your variables by using a SetStage command to run a result script? Now you're going to go back to that result script now and add three more lines:

if player.GetItemCount ExampleMenuKey == 0
	player.AddItem ExampleMenuKey 1
endif

Now, when your variables are first initialised, the Menu Key that you just made will be automatically added to the player.

When the Menu Key is equipped, you'll want it to run some code that adds a token to the player, but before you can do this you have to make the token. It's also going to be a piece of armour, but unlike the Menu Key it's going to have the "playable" flag unchecked. This ensures that the player can't see it in their inventory.

Once you've created the token, you can write a script for the Menu Key that adds the token to the player's inventory. Because the Menu Key is a quest item, it can't be given a hotkey - meaning that the player has to equip it manually from their inventory. If you want the menu to open right then and there, this script will suffice:

ScriptName ExampleMenuKeySCRIPT

short sDoOnce

Begin OnEquip

	set sDoOnce to 1
	AddItem ExampleMenuToken 1 1

End

However, I personally prefer to have the menu open after the player has put away their Pip-Boy. To do this, a timer will need to be used so that the menu doesn't appear as soon as the player starts to put away their Pip-Boy (that's when the game returns to GameMode). It takes about half a second for the player to put the Pip-Boy away, so let's use a script like this:

ScriptName ExampleMenuKeySCRIPT

short sDoOnce

float fTimer

Begin OnEquip

	; Don't need to check MenuMode here, as Quest Items can't be attached to hotkeys
	set fTimer to 0.5 ; It takes about 0.5 seconds to put the Pip-Boy away
	set sDoOnce to 1

End

Begin GameMode

	if sDoOnce == 1
		if fTimer > 0
			set fTimer to fTimer - GetSecondsPassed ; Count down to 0
		else
			; Once the Pip-Boy has been put away, open the Options Menu
			player.AddItem ExampleMenuToken 1 1
			set sDoOnce to 0
		endif
	endif

End

That's looking very nice now, but because the menu is being opened in GameMode you may also want to prevent the player from opening the menu while they're in combat. To do this, you'll need to check whether or not the player is in combat when they equip the Menu Key, and unequip the Key without adding the token if this is the case.

ScriptName ExampleMenuKeySCRIPT

short sDoOnce

float fTimer

Begin OnEquip

	; Don't need to check MenuMode here, as Quest Items can't be attached to hotkeys
	if player.IsInCombat == 1
		ShowMessage ExampleNoMenuCombatMsg ; Prevent the player from opening the menu
		; during combat
		player.UnequipItem ExampleMenuKey 0 1
	else
		set fTimer to 0.5 ; It takes about 0.5 seconds to put the Pip-Boy away
		set sDoOnce to 1
	endif

End

Begin GameMode

	if sDoOnce == 1
		if fTimer > 0
			set fTimer to fTimer - GetSecondsPassed ; Count down to 0
		else
			; Once the Pip-Boy has been put away, open the Options Menu
			player.AddItem ExampleMenuToken 1 1
			set sDoOnce to 0
		endif
	endif

End

Now that that's set up, all that's left is the actual Menu script, which will be attached to the token.

Creating The Menu[edit | edit source]

The main difference between a single-level menu and a multi-level menu is that the latter requires navigation - the script needs to remember the path that the player took to get to where they are. Our script will achieve this by having a "sMenuLevel" variable to store the current level of navigation, and giving each level of navigation its own "sButton" variable. That way, the script can remember which buttons the player pressed on each level of navigation.

The example menu that I'm going to use for this tutorial will have five levels, each with their own sButton variables:

  • Level 0 - Initialisation->Introduction Message
  • Level 1 - Introduction Message->Main Menu
  • Level 2 - Main Menu->Sub Menu
  • Level 3 - Sub menu->Setting
  • Level 4 - Setting->Value

The basic structure of the script consists of one big GameMode block filled with a list of "if" statements checking the values of sMenuLevel and the various sButton variables. At the highest level, it only checks sMenuLevel:

ScriptName ExampleMenuSCRIPT

short sMenuLevel ; Records the current depth of the ShowMessage framework
; 0 - Initialisation -> Introduction Message
; 1 - Introduction Message -> Main Menu
; 2 - Main Menu -> Sub Menu
; 3 - Sub Menu -> Setting
; 4 - Setting -> Value

; This GameMode block contains all of the ShowMessage framework
Begin GameMode

	; =================================================
	; INITIALISATION --> INTRODUCTION MESSAGE
	; =================================================
	if sMenuLevel == 0 ; Show Introduction Message
	
	endif


	; =================================================
	; INTRODUCTION MESSAGE --> MAIN MENU
	; =================================================
	if sMenuLevel == 1
	
	endif


	; =================================================
	; MAIN MENU --> SUB MENU
	; =================================================
	if sMenuLevel == 2
	
	endif


	; =================================================
	; SUB MENU --> SETTING
	; =================================================
	if sMenuLevel == 3
	
	endif


	; =================================================
	; SETTING --> VALUE
	; =================================================
	if sMenuLevel == 4
	
	endif

End

As you can see, the GameMode block is now divided up into five different segments. Which one runs will depend on the menu's current level of navigation.

Before we look at what any of these blocks will contain in specific detail, I'd like to go over their structure in general.

Basic Structure Of A Menu Level[edit | edit source]

Each level of our menu is contained within an "if" statement, which checks the value of sMenuLevel. Within this "if" statement the structure of each menu level is basically the same, although deeper levels will be more complicated. Each level, unless stated otherwise, has the following structures in common:

Button Set

Each menu level begins by checking if its sButton variable has been set/reset (i.e. has a value of -1) and, if so, sets it to GetButtonPressed:

set sButtonX to GetButtonPressed

Using this method ensures that the value of each sButton variable is maintained until it is explicitly reset.

Button Check 1

If the sButton variable still has a value of -1 after Button Set, then "Return" is used to end the script for the current frame.

if sButtonX == -1
	Return
elseif ...

Navigation Check

If the script has got this far, then the player must have pressed a button. If the menu level is deep enough, "if" statements are used to determine, via previous sButton variables, which path the player took to their current menu. If the menu level is not deep enough, or there is only one possible path that the player could have taken, there is no need for a navigation check.

if sButtonX-1 == 0 ; Player came through button 0
	; Button Check 2
elseif sButtonX-1 == 0 ; Player came through button 1
	; Button Check 2
elseif ...

Button Check 2

"If" statements are used to cycle through the various buttons that the player could possibly have pressed. If there is a "Back" or "Done" button (normally there won't be both), then it is usually the bottom button. This allows it to be checked with an "else" statement as opposed to an "elseif" statement, which makes it slightly easier to add more buttons to the menu later.

if sButtonX == 0 ; Player pressed button 0
	; Button Result
elseif sButtonX == 1 ; Player pressed button 1
	; Button Result
elseif...
else ; Player pressed Back/Done button
	; Button Result
endif

Button Result

This is essentially the button's "result script". It contains the code that will execute itself when the button is pressed. With the exceptions of "Done" buttons and buttons that leave the player on the same menu, this code will contain a ShowMessage function.

If the button doesn't take the player to the next menu level, the result script is terminated with a "Return" statement. The deepest level is an exception to this, in that it is the buttons that don't leave the player on the same level that are terminated with a "Return" statement.

ShowMessage PreviousMessage
	set sMenuLevel to X-1
	Return

Because the deepest level leaves the player on the same level instead of taking them back to the previous level, the path cannot branch from here so most of the buttons will probably take the player to the same menu. Because of this, the ShowMessage function can be called at the end of the navigation check, after button check 2, instead of being called multiple times in the result for each button.

if sButtonX == 0 ; Player pressed button 0
	...
else ; Player pressed "Back" button
	ShowMessage PreviousMessage
	set sMenuLevel to X - 1
	Return
endif
ShowMessage CurrentMessage

Menu Level Incrementation

Because of the various "Return" statements utilised in each menu level, this code will only execute if the player has selected a button that takes them to the next menu level (or, in the case of the deepest level, leaves them on the same level).

set sMenuLevel to X + 1


Keep these structures in mind as you read through the following sections, which describe the contents of each section of this example menu.

Contents Of An Example Menu[edit | edit source]

Initialisation[edit | edit source]

The first section of the script runs when sMenuLevel == 0. Because variables are initialised to 0, this section will run when the script first runs. This section is also the simplest - all that happens in it is the introduction message is called, sMenuLevel is incremented to 1, and sButton1 is intialised:

	; =================================================
	; INITIALISATION --> INTRODUCTION MESSAGE
	; =================================================
	if sMenuLevel == 0 ; Show Introduction Message
		ShowMessage introMessage
		set sMenuLevel to 1
	endif

If you're wondering why I've included an Introduction message before the Main Message, it's quite useful if you have conditions for the buttons in the Main Menu that have to be initialised before each time it's opened. Usually, you'll use an OnAdd block in this script to initialise them.

Introduction Message[edit | edit source]

Now, because you've set sMenuLevel to 1, the next section of the script will run. In this section, you need to check when the player clicks the button to continue from the Introduction message into the Main Menu by using GetButtonPressed. When the player clicks the button, the script will call the Main Menu, increment sMenuLevel to 2, and initialise the next sButton variable - sButton2:

	; =================================================
	; INTRODUCTION MESSAGE --> MAIN MENU
	; =================================================
	if sMenuLevel == 1

		set sButton1 to GetButtonPressed

		if sButton1 == -1 ; None of the buttons have been pressed yet

			Return

		else ; Show the Main Menu

			; MainMenuMessage contains buttons corresponding to Sub Menus
			ShowMessage MainMenuMessage
			set sMenuLevel to 2

		endif

	endif

Main Menu[edit | edit source]

This section is a step up again in complexity. Unlike the Introduction Message, the Main Menu contains multiple buttons. That means that, in this section of the script, you need to check which button the player presses and update the Menu accordingly.

There are three types of button in this example Main Menu:

  • Buttons that take the player to a Sub Menu
  • A "Defaults" button that sets all settings to their default values
  • A "Done" button that closes the menu

If the player clicks on a Sub Menu button, we'll need to show the appropriate message, as well as increment sMenuLevel to 3 and set sButton3 to -1. If they click the "Defaults" button, we'll need to set all settings to their default values (with "SetStage ExampleVRQuest 1", remember?), then show the Main Menu again and reset sButton2 to -1. If the player clicks on the "Done" button, we'll remove the token from their inventory to close the menu.

	; =================================================
	; MAIN MENU --> SUB MENU
	; =================================================
	if sMenuLevel == 2

		set sButton2 to GetButtonPressed

		if sButton2 == -1 ; None of the buttons have been pressed yet

			Return

		elseif sButton2 == 0 ; Show the "Weapons Rack" Sub Menu

			ShowMessage WeaponsRackSubMenuMessage

		elseif sButton2 == 1 ; Show the "Item Dispenser" Sub Menu

			ShowMessage ItemDispenserSubMenuMessage

		elseif sButton2 == 2 ; "Defaults" button - reinitialise all settings

			SetStage ExampleVRQuest 1
			ShowMessage MainMenuMessage
			Return

		else ; "Done" button - close Options Menu

			player.UnequipItem OptionsMenuEquipKey 0 1
			RemoveMe

		endif

		set sMenuLevel to 3

	endif

As you can see, this section has the same organisation as the previous section. The only differences are that there are "Done" and "Defaults" buttons and, depending on which button the player pressed, a different menu is shown. We still use GetButtonPressed to check if a button has been pressed, and when one has we increment sMenuLevel and initialise the next sButton variable.

Sub Menu[edit | edit source]

This section is the similar to the previous section - it's where the player selects the setting that they'd like to change. Once again, there are multiple types of button in these menus:

  • Buttons that take the player to a Setting Menu, where the can select a new value for the setting
  • Buttons that toggle a setting between two values

We're now deep enough in the menu that there are multiple paths that the player could have taken to reach their current menu level. Because of this, we'll need to check the values of the sButton variables of the higher levels, in order to build a kind of cookie-trail.

	; =================================================
	; SUB MENU --> SETTING
	; =================================================
	if sMenuLevel == 3

		set sButton3 to GetButtonPressed

		if sButton3 == -1

			Return ; None of the buttons have been pressed yet

		elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu

			if sButton3 == 0 ; The "Damaged" setting was selected
				set ExampleVRQuest.sAddDamagedWeapon to (ExampleVRQuest.sAddDamagedWeapon == 0)
				ShowMessage WeaponsRackSubMenuMessage
				Return
			elseif sButton3 == 1 ; The "Condition" setting was selected
				ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent
			else ; "Back" button was pressed
				ShowMessage MainMenuMessage
				set sMenuLevel to 2
				Return
			endif

		elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu

			if sButton3 == 0 ; The "Item" setting was selected
				set ExampleVRQuest.sAddExampleItem to (ExampleVRQuest.sAddExampleItem == 0)
				ShowMessage ItemDispenserSubMenuMessage
				Return
			elseif sButton3 == 1 ; The "Amount" setting was selected
				ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount
			else ; "Back" button was pressed
				ShowMessage MainMenuMessage
				set sMenuLevel to 2
				Return
			endif

		endif

		set sMenuLevel to 4

	endif

As you can see, the highest level of "if" statements in this example checks which Sub Menu the player came though, and the next level of "if" statements checks Setting they just selected. Depending on which button the player selected, either the setting will be toggled or the player will enter a new menu in which they can select a new value for the setting that they selected.

The next section is the final section in this example menu. At this level, the player will be able to choose a new value for the setting that they just selected.

Setting[edit | edit source]

This section differs slightly from all previous sections, because the player can advance no further in the menu. This means that, instead of taking the player to the next menu level, most of the buttons will leave the player on the current level. This means a couple of structural changes need to take place:

  • The "menu level incrementation" doesn't actually increment sMenuLevel - all it does is reset the current sButton variable to -1
  • Because most buttons in any menu will leave the player on the same menu, the ShowMessage section of the results of these buttons can be moved to the end of the navigation check, after button check 2.
	; =================================================
	; SETTING --> VALUE
	; =================================================
	if sMenuLevel == 4

		set sButton4 to GetButtonPressed

		if sButton4 == -1

			Return ; None of the buttons have been pressed yet

		elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu

			; Don't need to check sButton3 - it must be equal to 1
			if sButton4 == 0
				set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.25
			elseif sButton4 == 1
				set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.50
			elseif sButton4 == 2
				set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.75
			else ; "Back" button was pressed
				ShowMessage WeaponsRackSubMenuMessage
				set sMenuLevel to 3
				Return
			endif
			ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent

		elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu

			; Don't need to check sButton3 - it must be equal to 1
			if sButton4 == 0
				set ExampleVRQuest.sItemCount to 1
			if sButton4 == 1
				set ExampleVRQuest.sItemCount to 2
			if sButton4 == 2
				set ExampleVRQuest.sItemCount to 3
			else ; "Back" button was pressed
				ShowMessage ItemDispenserSubMenuMessage
				set sMenuLevel to 3
				Return
			endif
			ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount

		endif

	endif

Conclusion[edit | edit source]

This method of making multi-level menus can be used to make menus with any number of levels that you'd like, although obviously the script will become more complicated at higher levels. You should also remember that your menu doesn't have to function as an options menu. It could be used for many other things - for example, determining which path the player will progress along in a quest.

Now that the script is finished, let's look at the whole thing in one piece!

ScriptName ExampleMenuTokenSCRIPT

short sMenuLevel ; Records the current depth of the ShowMessage framework
; 0 - Initialisation -> Introduction Message
; 1 - Introduction Message -> Main Menu
; 2 - Main Menu -> Sub Menu
; 3 - Sub Menu -> Setting
; 4 - Setting -> Value

short sButton1 ; sButton variable for the Introduction Message level - Menu level 1
short sButton2 ; sButton variable for the Main Menu level - Menu level 2
short sButton3 ; sButton variable for the Sub Menu level - Menu level 3
short sButton4 ; sButton variable for the Setting level - Menu level 4

; This GameMode block contains all of the ShowMessage framework
Begin GameMode

	; =================================================
	; INITIALISATION --> INTRODUCTION MESSAGE
	; =================================================
	if sMenuLevel == 0 ; Show Introduction Message
		ShowMessage introMessage
		set sMenuLevel to 1
	endif


	; =================================================
	; INTRODUCTION MESSAGE --> MAIN MENU
	; =================================================
	if sMenuLevel == 1

		set sButton1 to GetButtonPressed

		if sButton1 == -1 ; None of the buttons have been pressed yet

			Return

		else ; Show the Main Menu

			; MainMenuMessage contains buttons corresponding to Sub Menus
			ShowMessage MainMenuMessage
			set sMenuLevel to 2

		endif

	endif


	; =================================================
	; MAIN MENU --> SUB MENU
	; =================================================
	if sMenuLevel == 2

		set sButton2 to GetButtonPressed

		if sButton2 == -1 ; None of the buttons have been pressed yet

			Return

		elseif sButton2 == 0 ; Show the "Weapons Rack" Sub Menu

			ShowMessage WeaponsRackSubMenuMessage

		elseif sButton2 == 1 ; Show the "Item Dispenser" Sub Menu

			ShowMessage ItemDispenserSubMenuMessage

		elseif sButton2 == 2 ; "Defaults" button - reinitialise all settings

			SetStage ExampleVRQuest 1
			ShowMessage MainMenuMessage
			Return

		else ; "Done" button - close Options Menu

			player.UnequipItem OptionsMenuEquipKey 0 1
			RemoveMe

		endif

		set sMenuLevel to 3

	endif


	; =================================================
	; SUB MENU --> SETTING
	; =================================================
	if sMenuLevel == 3

		set sButton3 to GetButtonPressed

		if sButton3 == -1

			Return ; None of the buttons have been pressed yet

		elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu

			if sButton3 == 0 ; The "Damaged" setting was selected
				set ExampleVRQuest.sAddDamagedWeapon to (ExampleVRQuest.sAddDamagedWeapon == 0)
				ShowMessage WeaponsRackSubMenuMessage
				Return
			elseif sButton3 == 1 ; The "Condition" setting was selected
				ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent
			else ; "Back" button was pressed
				ShowMessage MainMenuMessage
				set sMenuLevel to 2
				Return
			endif

		elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu

			if sButton3 == 0 ; The "Item" setting was selected
				set ExampleVRQuest.sAddExampleItem to (ExampleVRQuest.sAddExampleItem == 0)
				ShowMessage ItemDispenserSubMenuMessage
				Return
			elseif sButton3 == 1 ; The "Amount" setting was selected
				ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount
			else ; "Back" button was pressed
				ShowMessage MainMenuMessage
				set sMenuLevel to 2
				Return
			endif

		endif

		set sMenuLevel to 4

	endif


	; =================================================
	; SETTING --> VALUE
	; =================================================
	if sMenuLevel == 4

		set sButton4 to GetButtonPressed

		if sButton4 == -1

			Return ; None of the buttons have been pressed yet

		elseif sButton2 == 0 ; The player came through the "Weapons Rack" Sub Menu

			; Don't need to check sButton3 - it must be equal to 1
			if sButton4 == 0
				set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.25
			elseif sButton4 == 1
				set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.50
			elseif sButton4 == 2
				set ExampleVRQuest.fDamagedWeaponHealthPercent to 0.75
			else ; "Back" button was pressed
				ShowMessage WeaponsRackSubMenuMessage
				set sMenuLevel to 3
				Return
			endif
			ShowMessage ConditionSettingMessage ExampleVRQuest.fDamagedWeaponHealthPercent

		elseif sButton2 == 1 ; The player came through the "Item Dispenser" Sub Menu

			; Don't need to check sButton3 - it must be equal to 1
			if sButton4 == 0
				set ExampleVRQuest.sItemCount to 1
			if sButton4 == 1
				set ExampleVRQuest.sItemCount to 2
			if sButton4 == 2
				set ExampleVRQuest.sItemCount to 3
			else ; "Back" button was pressed
				ShowMessage ItemDispenserSubMenuMessage
				set sMenuLevel to 3
				Return
			endif
			ShowMessage AmountSettingMessage ExampleVRQuest.sItemCount

		endif

	endif

End

External Links[edit | edit source]