Difference between revisions of "Options Menu"

From the CreationKit Wiki
Jump to navigation Jump to search
imported>Threedee
imported>JustinOther
m (→‎Multilevel, looping menu: GLOB Toggle refined (Int Δ Float since it'll be cast to Float anyway))
 
(25 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== Overview ==
=== Overview ===
Using [[Show - Message]], it is possible to make an options menu with any number of buttons and/or levels. This is enabling as one can maintain but a single plugin with an options menu offering multiple configuration setting rather than necessitating multiple versions. In these examples, we'll use apparel items and a book, but a menu can be prompted and managed in a number of ways. First, create a message(s) form(s) and add/fill the buttons with the options you'd like to offer. Note that no more than ten buttons can be in a message box and that the button indices are offset by one such that the first option's index is 0 and not 1. If offering a lot of options, it's best to think ahead regarding how you want to organize your options, making the message forms first, then plugging them into the script. Also note that if you use many buttons and if the button names are long, your menu might be too wide to fit on the screen, so keep the layout as compact as possible.
Using [[Show - Message]], it is possible to make an options menu with any number of buttons and/or levels. This is enabling as one can maintain but a single plugin with an options menu offering multiple configuration setting rather than necessitating multiple versions. In these examples, we'll use apparel items and a book, but a menu can be prompted and managed in a number of ways. First, create a message(s) form(s) and add/fill the buttons with the options you'd like to offer. Note that no more than ten buttons can be in a message box and that the button indices are offset by one such that the first option's index is 0 and not 1. If offering a lot of options, it's best to think ahead regarding how you want to organize your options, making the message forms first, then plugging them into the script.


== Examples ==
=== Examples ===
*For the first example, we'll have only three options: "Mage", "Thief", and "Warrior". When the token is added to the player, the menu will be prompted and will exit as soon as a button is selected, executing the appropriate code right after the token is silently removed.
== Single Level Menu ==
For the first examples, we'll have only three options: "Mage", "Thief", and "Warrior". An options menu can be accessed many ways, a few of which will be demonstrated below.
*First, we'll attach the script to an unplayable armor item. When the item is added to the player, the menu will be prompted and will exit as soon as a button is selected, executing the appropriate code right after the token is silently removed.
<source lang="papyrus">ScriptName OptionsMenuScript Extends ObjectReference


 
Actor Property PlayerREF Auto
<source lang="papyrus">ScriptName OptionsMenuScript extends ObjectReference
Armor Property MenuARMO Auto
 
Message Property OptionsMESG Auto
Message Property OptionsMESG Auto ; The Message form that configures the menu buttons


Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
If akNewContainer == Game.GetPlayer(); Only the player
If akNewContainer == PlayerREF ; Only the player
Int iButton = OptionsMESG.Show() ; Shows your menu. Note that the menu will stay open until a menu button (0-9) is clicked on.
Int iButton = OptionsMESG.Show() ; Shows your menu.
akNewContainer.RemoveItem(self, 1, True) ; Silently remove token from the player
PlayerREF.RemoveItem(MenuARMO, 1, True) ; Silently remove token. 'Self' does not work in this context, thus the property
If iButton == 0  ; Mage
If iButton == 0  ; Mage
Debug.Notification("Mage selected")
Debug.Notification("Mage selected")
Line 21: Line 23:
Debug.Notification("Warrior selected")
Debug.Notification("Warrior selected")
EndIf
EndIf
; note that if iButton (for whatever reason) returns any value other than 0-2, the menu will simply exit cleanly.
EndIf
EndIf
EndEvent</source>
EndEvent</source>
*Next, we'll show the same menu, but prompt it with a spell given to the player. Note the similarities of these examples and that code can be compartmentalized by creating custom functions. The Menu function below could be moved to another script of a different type, meaning you can offer access to your menu however you see fit.
<source  lang="papyrus">ScriptName OptionsMenuScript Extends ActiveMagicEffect
Actor Property PlayerREF Auto
Message Property OptionsMESG Auto ; The Message form that configures the menu buttons
Event OnEffectStart(Actor akTarget, Actor akCaster)
If akCaster == PlayerREF ; Only the player can open the menu
Menu()
EndIf
EndEvent


Function Menu(Int aiButton = 0) ; The menu will exit on its own after a selection is made.
aiButton = OptionsMESG.Show() ; Shows your menu.
If aiButton == 0  ; Mage
Debug.Notification("Mage selected")
ElseIf aiButton == 1 ; Thief
Debug.Notification("Thief selected")
ElseIf aiButton == 2 ; Warrior
Debug.Notification("Warrior selected")
EndIf
EndFunction</source>


*For the next example, we'll offer sub-options for each main selection. For a multilevel menu, functions can be used to keep the menu well organized. Keep in mind that you can assign conditions to each button in the Message forms, so you could hide "Lunch" and "Dinner" if it's time for breakfast, or hide "Lobster" if it's not currently available. In this case, to make it repeatable, we'll use a book so the menu will show each time it is read. A book cannot be favorited or hotkeyed, unlike an apparel item. A potion can be hotkeyed, but it will be consumed when used and not remain hotkeyed even if immediately replaced. This example will let the user choose breakfast, lunch, or dinner, then close after one meal is selected.
== Menu with sub-options ==
For this example, we'll offer sub-options for each main selection. For a multilevel menu, a function works well. Keep in mind each button can have conditions, so you could hide "Lunch" and "Dinner" if it's time for breakfast or hide "Lobster" if it's not currently available. In this case, to make it repeatable, we'll use a book so the menu will show each time it is read. A book cannot be favorited or hotkeyed, unlike an apparel item. A potion can be hotkeyed, but it will be consumed when used and not remain hotkeyed even if immediately replaced. This example will let the user choose breakfast, lunch, or dinner, then close after one meal is selected. As your options become fleshed out, keep in mind that you can add and use arguments to store information temporarily rather than necessitating declarations of more variables or properties.


<source lang="papyrus">ScriptName OptionsMenuScript Extends ObjectReference


<source lang="papyrus">
ScriptName OptionsMenuScript extends ObjectReference
{This script should be attached to a book.
It will open a menu with three submenu pages when the book is read}
;===================================================
Message Property MainMenuMESG Auto
Message Property MainMenuMESG Auto
Message Property BreakfastMESG Auto
Message Property BreakfastMESG Auto
Message Property LunchMESG Auto
Message Property LunchMESG Auto
Message Property DinnerMESG Auto
Message Property DinnerMESG Auto
;===================================================


Event OnRead()
Event OnRead()
Game.DisablePlayerControls(False, False, False, False, False, True) ; Temporarily disable other menus
Game.DisablePlayerControls(False, False, False, False, False, True) ; Ensure MessageBox is not on top of other menus & prevent book from opening normally.
Game.EnablePlayerControls(False, False, False, False, False, True) ; Undo DisablePlayerControls
Menu()
Menu()
Game.EnablePlayerControls(False, False, False, False, False, True) ; Re-enable Player controls
EndEvent  
EndEvent  
;===================================================
Function Menu()
int iButton = MainMenuMESG.Show() ; Main Menu
If iButton == 0 ; Breakfast
BreakfastMenu()
ElseIf iButton == 1 ; Lunch
LunchMenu()
ElseIf iButton == 2 ; Dinner
DinnerMenu()
ElseIf iButton == 3 ; Quit the menu
Return
EndIf
; note: the menu will exit here unless we call Menu() again
; note that if iButton (for whatever reason) returns any value other than 0-3, the menu will simply exit cleanly.
EndFunction
;===================================================
Function BreakfastMenu()
int iButton = BreakfastMESG.Show()
If iButton == 0
Debug.Notification("Choice = Sweet Roll & Coffee")
ElseIf iButton == 1
Debug.Notification("Choice = Pancakes, Bacon & Eggs")
ElseIf iButton == 2
Debug.Notification("Choice = Chicken Fried Pony Steak")
ElseIf iButton == 3 ; Return to the main menu
Return
EndIf
; note: the submenu will exit back to main menu from here unless we call BreakfastMenu() again
; note that if iButton (for whatever reason) returns any value other than 0-2, the submenu will simply exit back to the main menu.
EndFunction
;===================================================
Function LunchMenu()
int iButton = LunchMESG.Show()
If iButton == 0
Debug.Notification("Choice = Glazed Turkey Sandwich")
ElseIf iButton == 1
Debug.Notification("Choice = Grilled Ham Sandwich")
ElseIf iButton == 2
Debug.Notification("Choice = Shredded Pony Sandwich")
ElseIf iButton == 3 ; Return to the main menu
Return
EndIf
; note: the submenu will exit back to main menu from here unless we call LunchMenu() again
; note that if iButton (for whatever reason) returns any value other than 0-2, the submenu will simply exit back to the main menu.
EndFunction
;===================================================
Function DinnerMenu()
iButton = DinnerMESG.Show()
If iButton == 0
Debug.Notification("Choice = Filet Mignon")
ElseIf iButton == 1
Debug.Notification("Choice = Pony Fajitas")
ElseIf iButton == 2
Debug.Notification("Choice = Lobster")
ElseIf iButton == 3 ; Return to the main menu
Return
EndIf
; note: the submenu will exit back to main menu from here unless we call DinnerMenu() again
; note that if iButton (for whatever reason) returns any value other than 0-2, the submenu will simply exit back to the main menu.
EndFunction
;===================================================
</source>
*To make a multilevel, looping menu with thirty buttons that will not close until a "Done" button is pressed, use the above method but with an altered Menu() function. Sub-options as described in the previous example can be added to the example below in the same manner. Theoretically, any number of options can be added with the below structure. By structuring each submenu as a separate function, you can easily jump from one submenu to another by calling the corresponding function.


Function Menu(Bool abMenu = True, Int aiButton = 0)
While abMenu
If aiButton != -1 ; Wait for input (this can prevent problems if recycling the aiButton argument in submenus)
aiButton = MainMenuMESG.Show() ; Main Menu
abMenu = False ; End the function
If aiButton == 0 ; Breakfast
aiButton = BreakfastMESG.Show()
If aiButton == 0 ; Sweet Roll & Coffee
ElseIf aiButton == 1 ; Pancakes, Bacon & Eggs
ElseIf aiButton == 2 ; Chicken Fried Pony Steak
EndIf
ElseIf aiButton == 1 ; Lunch
aiButton = LunchMESG.Show()
If aiButton == 0 ;  Glazed Turkey Sandwich
ElseIf aiButton == 1 ; Grilled Ham Sandwich
ElseIf aiButton == 2 ; Shredded Pony Sandwich
EndIf
ElseIf aiButton == 2 ; Dinner
aiButton = DinnerMESG.Show()
If aiButton == 0 ; Filet Mignon
ElseIf aiButton == 1 ; Pony Fajitas
ElseIf aiButton == 2 ; Lobster
EndIf
EndIf
EndIf
EndWhile
EndFunction</source>


<source lang="papyrus">ScriptName OptionsMenuScript extends ObjectReference
== Multilevel, looping menu ==
{Presents a multilevel looped menu when this item is equipped}
To make a multilevel, looping menu with thirty buttons that will not close until a "Done" button is pressed, use the above method but with an altered Menu() function. Note that you can jump to a given message by specifying the aiMessage argument when calling the function. Sub-options as described in the previous example can be added to the below in the same manner. Theoretically, any number of options can be added with the below structure. By making it conditional, we can check its property values with MessageBox buttons using [[GetVMScriptVariable]] and pointing to the placed instance of the item this script is attached to. To ensure the player gets said reference, make a property for the specific reference in another script, and add it to the player with [[AddItem]] by passing the reference as akItemToAdd.
;===================================================
<source lang="papyrus">ScriptName OptionsMenuScript Extends ObjectReference Conditional


Message Property MainMenuMESG Auto ; main menu messagebox
Actor Property PlayerREF Auto
Message Property OptionsMenu00MESG Auto ; submenu page 1 messagebox
Armor Property MenuARMO Auto ; Playable apparel item
Message Property OptionsMenu01MESG Auto ; submenu page 2 messagebox
Bool Property bFeatureEnabled Auto Conditional ; Toggling of this demonstrated below.
Message Property OptionsMenu02MESG Auto ; submenu page 3 messagebox
GlobalVariable Property DragonsEnabled Auto ; Toggling of this demonstrated below.
 
Message Property OptionsMenu00MESG Auto
bool abMenu
Message Property OptionsMenu01MESG Auto
Message Property OptionsMenu02MESG Auto
;===================================================
YourQuestScriptName Property QuestScript Auto ; Is Conditional with a Conditional bQuickening property


Event OnEquipped(Actor akActor)
Event OnEquipped(Actor akActor)
If akActor == Game.GetPlayer()
If akActor == PlayerREF
Armor menuARMO = self as Armor ; cast self as Armor
Game.DisablePlayerControls(False, False, False, False, False, True) ; Momentarily disable other menus
akActor.UnequipItem(menuARMO, False, True) ; Silently unequip this item
PlayerREF.EquipItem(MenuARMO, True, True) ; Prevent unequip/reequip in favorites until the current menu is resolved
abMenu = TRUE
Utility.Wait(0.01) ; This ensures equipping the token from the favorites menu works
Game.DisablePlayerControls(False, False, False, False, False, True) ; Temporarily disable other menus
PlayerREF.UnequipItem(MenuARMO, False, True) ; Silently unequip item
Game.EnablePlayerControls(False, False, False, False, False, True) ; Undo DisablePlayerControls
Menu()
Menu()
Game.EnablePlayerControls(False, False, False, False, False, True) ; Re-enable Player controls
EndIf
EndIf
EndEvent
EndEvent
;===================================================
Function Menu()


Function Menu(Int aiMessage = 0, Int aiButton = 0, Bool abMenu = True)
While abMenu
While abMenu
int iButton = MainMenuMESG.Show()
If aiButton == -1 ; As above, can prevent problems if recycling aiButton
 
ElseIf aiMessage == 0
If iButton == 0
aiButton = OptionsMenu00MESG.Show()
Submenu00() ; goto submenu00
If aiButton < 2 ; Toggle script property. Buttons have opposite GetVMScriptVariable conditions so only the applicable option is given.
ElseIf iButton == 1
bFeatureEnabled = !bFeatureEnabled ; Set boolean to whatever it is not
Submenu01() ; goto submenu01
If bFeatureEnabled ; == True
ElseIf iButton == 2
Debug.Trace("Featured enabled. Set things up.")
Submenu02() ; goto submenu02
Else ; If bFeatureEnabled == False
ElseIf iButton == 3 ; quit
Debug.Trace("Featured disabled. Stop doing stuff and clean up.")
Return ; exits the menu function and while loop
EndIf
ElseIf aiButton < 4 ; Toggle quest property. Buttons have opposite conditions checking the property value with GetVMQuestVariable.
QuestScript.bQuickening = !QuestScript.bQuickening ; Set boolean to whatever it is not
If QuestScript.bQuickening ; == True
Debug.Trace("Start polling.")
Else ; If QuestScript.bQuickening == False
Debug.Trace("Stop polling.")
EndIf
ElseIf aiButton < 6 ; Toggle DragonsEnabled. Buttons have opposite conditions as above, but checking the global's value with GetGlobalValue.
DragonsEnabled.SetValue((!DragonsEnabled.GetValue() As Bool) As Float) ; If 1, set to 0. If 0, set to 1
If DragonsEnabled.GetValue() ; != 0
Debug.Trace("Dragons enabled.")
Else ; If DragonsEnabled.GetValue()  == 0
Debug.Trace("Dragons disabled.")
EndIf
ElseIf aiButton == 6
ElseIf aiButton == 7
ElseIf aiButton == 8 ; More
aiMessage = 1
ElseIf aiButton == 9 ; Done
abMenu = False
EndIf
ElseIf aiMessage == 1
aiButton = OptionsMenu01MESG.Show()
If aiButton == 0
ElseIf aiButton == 1
ElseIf aiButton == 2
ElseIf aiButton == 3
ElseIf aiButton == 4
ElseIf aiButton == 5
ElseIf aiButton == 6
ElseIf aiButton == 7 ; Back
aiMessage = 0
ElseIf aiButton == 8 ; More
aiMessage = 2
ElseIf aiButton == 9 ; Done
abMenu = False
EndIf
ElseIf aiMessage == 2
aiButton = OptionsMenu02MESG.Show()
If aiButton == 0
ElseIf aiButton == 1
ElseIf aiButton == 2
ElseIf aiButton == 3
ElseIf aiButton == 4
ElseIf aiButton == 5
ElseIf aiButton == 6
ElseIf aiButton == 7
ElseIf aiButton == 8 ; Back
aiMessage = 1
ElseIf aiButton == 9 ; Done
abMenu = False
EndIf
EndIf
EndIf
Menu() ; loops the main menu
; note that if iButton (for whatever reason) returns any value other than 0-2, the menu will simply re-display.
EndWhile
EndWhile
 
EndFunction</source>
EndFunction
=== Notes ===
 
;===================================================
 
Function Submenu00()
 
int iButton = OptionsMenu00MESG.Show()
 
If iButton == 0
; do stuff here
ElseIf iButton == 1
; do stuff here
ElseIf iButton == 2
; do stuff here
ElseIf iButton == 3
; do stuff here
ElseIf iButton == 4
; do stuff here
ElseIf iButton == 5
; do stuff here
ElseIf iButton == 6
; do stuff here
ElseIf iButton == 7
Submenu01() ; More - goto submenu01
ElseIf iButton == 8 ; Return to main menu
Menu()
ElseIf iButton == 9 ; Done - exit from all menus
abMenu = FALSE ; stop the while loop
Return
EndIf
 
Submenu00() ; loops this submenu
; note that if iButton (for whatever reason) returns any value other than 0-9, the submenu will simply re-display.
 
EndFunction
 
;===================================================
 
Function Submenu01()
 
int iButton = OptionsMenu01MESG.Show()
 
If iButton == 0 ; Back - goto submenu00
Submenu00()
ElseIf iButton == 1
; do stuff here
ElseIf iButton == 2
; do stuff here
ElseIf iButton == 3
; do stuff here
ElseIf iButton == 4
; do stuff here
ElseIf iButton == 5
; do stuff here
ElseIf iButton == 6
; do stuff here
ElseIf iButton == 7
Submenu02() ; More - goto submenu02
ElseIf iButton == 8 ; Return to main menu
Menu()
ElseIf iButton == 9 ; Done - exit from all menus
abMenu = FALSE ; stop the while loop
Return
EndIf
 
Submenu01() ; loops this submenu
; note that if iButton (for whatever reason) returns any value other than 0-9, the submenu will simply re-display.
 
EndFunction
 
;===================================================
 
Function Submenu02()
 
int iButton = OptionsMenu02MESG.Show()
 
If iButton == 0 ; Back - goto submenu01
Submenu01()
ElseIf iButton == 1
; do stuff here
ElseIf iButton == 2
; do stuff here
ElseIf iButton == 3
; do stuff here
ElseIf iButton == 4
; do stuff here
ElseIf iButton == 5
; do stuff here
ElseIf iButton == 6
; do stuff here
ElseIf iButton == 7
; do stuff here
ElseIf iButton == 8 ; Return to main menu
Menu()
ElseIf iButton == 9 ; Done - exit from all menus
abMenu = FALSE ; stop the while loop
Return
EndIf
 
Submenu02() ; loops this submenu
; note that if iButton (for whatever reason) returns any value other than 0-9, the submenu will simply re-display.
 
EndFunction
 
;===================================================
</source>
 
 
*This next example shows how you would open a simple menu using a script attached to a MagicEffect form used by a Spell:
 
 
<source  lang="papyrus">
ScriptName OptionsMenuScript extends ActiveMagicEffect
{ A template for a MagicEffect script that opens a simple menu.}
 
Message Property OptionsMESG Auto ; The Message form that configures the menu buttons
 
;==============================================================
 
Event OnEffectStart(Actor akTarget, Actor akCaster)
If akCaster == Game.GetPlayer(); Only the player can open the menu
Menu()
EndIf
Dispel()
EndEvent
 
Function Menu()
Int iButton = OptionsMESG.Show() ; Shows your menu.
 
If iButton == 0  ; Mage
Debug.Notification("Mage selected")
ElseIf iButton == 1 ; Thief
Debug.Notification("Thief selected")
ElseIf iButton == 2 ; Warrior
Debug.Notification("Warrior selected")
EndIf
; note that the menu will simply exit, no matter what value is returned by iButton.
EndFunction
 
;==============================================================
 
</source>
 
== Notes ==
*Given the buttons in Skyrim are listed from side to side, it is easy to spill over the edges of the user's monitor, particularly if it's a 4:3, in the event either the options are too verbose or there are too many options presented by a single message form. Currently, there's no way to list them from top to bottom as they were in previous Bethesda games. To mitigate this, keep the button text to a minimum and/or make sure to always set up conditions on mutually exclusive buttons to ensure only applicable options are presented.
*Given the buttons in Skyrim are listed from side to side, it is easy to spill over the edges of the user's monitor, particularly if it's a 4:3, in the event either the options are too verbose or there are too many options presented by a single message form. Currently, there's no way to list them from top to bottom as they were in previous Bethesda games. To mitigate this, keep the button text to a minimum and/or make sure to always set up conditions on mutually exclusive buttons to ensure only applicable options are presented.
*To conditionalize buttons upon script/quest variables, use [[GetVMScriptVariable]] and [[GetVMQuestVariable]].
*To conditionalize buttons using variables declared in your script/quest, use [[GetVMScriptVariable]] and [[GetVMQuestVariable]].
*To hide buttons you wish to fill in later, add an impossible condition like 'IsXBox == -1'.
*To hide buttons you wish to fill in later, add an impossible condition like 'IsXBox == -1'.
* Conditionalizing MessageBox buttons will not change their indices such that, for instance, button 9 will still execute the "Done" code in the last example even if buttons 0-8 are hidden.
*For debugging purposes, you could configure hidden menu buttons (in the Message forms) that only show when you set a GlobalVariable flag. For instance, create a GlobalVariable called "myDebugFlag" in the CK. Set a condition on one of your menu buttons to be "GetGlobalValue myDebugFlag == 1". In your script, have that button activate your debugging function. Now if you set myGlobalValue to 1 in the CK your button will appear in the menu. Before releasing the mod, remember to set myDebugFlag to 0 to keep the button hidden.
* To learn how to assign user-created messageboxes as values to the message box properties defined in the above scripts, see [[Bethesda_Tutorial_Papyrus_Introduction_to_Properties_and_Functions#Hooking_up_the_message_boxes_to_the_properties_in_the_script|the Papyrus tutorial's page on Properties and Functions]]
*Conditionalizing MessageBox buttons will not change their indices such that, for instance, button 9 will still execute the "Done" code in the last example even if buttons 0-8 are hidden.
*If the Message property isn't filled in the CK the Show() will always return a 0.
*To learn how to assign user-created messageboxes as values to the message box Properties defined in the above scripts, see [[Bethesda_Tutorial_Papyrus_Introduction_to_Properties_and_Functions#Hooking_up_the_message_boxes_to_the_properties_in_the_script|the Papyrus tutorial's page on Properties and Functions]]
*If the Message is a Notification (without buttons) instead of a Message Box the Show() will return a -1.
*If the Message Property isn't filled in the CK the Show() will always return a 0, '''and the Message will not be shown'''.
== See Also ==
*If the Message is a Notification (without buttons) instead of a Message Box the Show() will return a -1, '''in which case you will never be presented with an options menu'''.
 
=== See Also ===
[[Show - Message]]
[[Show - Message]]
[[Category:Scripting]]
[[Category:Scripting]]
[[Category:Papyrus]]
[[Category:Papyrus]]
[[Category:Papyrus_Tutorials]]
[[Category:Papyrus_Tutorials]]
[[Category:Tutorials]]
[[Category:Tutorials]]

Latest revision as of 22:43, 15 December 2012

Overview[edit | edit source]

Using Show - Message, it is possible to make an options menu with any number of buttons and/or levels. This is enabling as one can maintain but a single plugin with an options menu offering multiple configuration setting rather than necessitating multiple versions. In these examples, we'll use apparel items and a book, but a menu can be prompted and managed in a number of ways. First, create a message(s) form(s) and add/fill the buttons with the options you'd like to offer. Note that no more than ten buttons can be in a message box and that the button indices are offset by one such that the first option's index is 0 and not 1. If offering a lot of options, it's best to think ahead regarding how you want to organize your options, making the message forms first, then plugging them into the script.

Examples[edit | edit source]

Single Level Menu[edit | edit source]

For the first examples, we'll have only three options: "Mage", "Thief", and "Warrior". An options menu can be accessed many ways, a few of which will be demonstrated below.

  • First, we'll attach the script to an unplayable armor item. When the item is added to the player, the menu will be prompted and will exit as soon as a button is selected, executing the appropriate code right after the token is silently removed.
ScriptName OptionsMenuScript Extends ObjectReference

Actor Property PlayerREF Auto
Armor Property MenuARMO Auto
Message Property OptionsMESG Auto

Event OnContainerChanged(ObjectReference akNewContainer, ObjectReference akOldContainer)
	If akNewContainer == PlayerREF ; Only the player
		Int iButton = OptionsMESG.Show() ; Shows your menu.
		PlayerREF.RemoveItem(MenuARMO, 1, True) ; Silently remove token. 'Self' does not work in this context, thus the property
		If iButton == 0  ; Mage
			Debug.Notification("Mage selected")
		ElseIf iButton == 1 ; Thief
			Debug.Notification("Thief selected")
		ElseIf iButton == 2 ; Warrior
			Debug.Notification("Warrior selected")
		EndIf
	EndIf
EndEvent
  • Next, we'll show the same menu, but prompt it with a spell given to the player. Note the similarities of these examples and that code can be compartmentalized by creating custom functions. The Menu function below could be moved to another script of a different type, meaning you can offer access to your menu however you see fit.
ScriptName OptionsMenuScript Extends ActiveMagicEffect

Actor Property PlayerREF Auto
Message Property OptionsMESG Auto ; The Message form that configures the menu buttons

Event OnEffectStart(Actor akTarget, Actor akCaster)
	If akCaster == PlayerREF ; Only the player can open the menu
		Menu()
	EndIf
EndEvent

Function Menu(Int aiButton = 0) ; The menu will exit on its own after a selection is made.
	aiButton = OptionsMESG.Show() ; Shows your menu.
	If aiButton == 0  ; Mage
		Debug.Notification("Mage selected")
	ElseIf aiButton == 1 ; Thief
		Debug.Notification("Thief selected")
	ElseIf aiButton == 2 ; Warrior
		Debug.Notification("Warrior selected")
	EndIf
EndFunction

Menu with sub-options[edit | edit source]

For this example, we'll offer sub-options for each main selection. For a multilevel menu, a function works well. Keep in mind each button can have conditions, so you could hide "Lunch" and "Dinner" if it's time for breakfast or hide "Lobster" if it's not currently available. In this case, to make it repeatable, we'll use a book so the menu will show each time it is read. A book cannot be favorited or hotkeyed, unlike an apparel item. A potion can be hotkeyed, but it will be consumed when used and not remain hotkeyed even if immediately replaced. This example will let the user choose breakfast, lunch, or dinner, then close after one meal is selected. As your options become fleshed out, keep in mind that you can add and use arguments to store information temporarily rather than necessitating declarations of more variables or properties.

ScriptName OptionsMenuScript Extends ObjectReference

Message Property MainMenuMESG Auto
Message Property BreakfastMESG Auto
Message Property LunchMESG Auto
Message Property DinnerMESG Auto

Event OnRead()
	Game.DisablePlayerControls(False, False, False, False, False, True) ; Ensure MessageBox is not on top of other menus & prevent book from opening normally.
	Game.EnablePlayerControls(False, False, False, False, False, True) ; Undo DisablePlayerControls
	Menu()
EndEvent 

Function Menu(Bool abMenu = True, Int aiButton = 0)
	While abMenu
		If aiButton != -1 ; Wait for input (this can prevent problems if recycling the aiButton argument in submenus)
			aiButton = MainMenuMESG.Show() ; Main Menu
			abMenu = False ; End the function
			If aiButton == 0 ; Breakfast
				aiButton = BreakfastMESG.Show()
				If aiButton == 0 ; Sweet Roll & Coffee
				ElseIf aiButton == 1 ; Pancakes, Bacon & Eggs
				ElseIf aiButton == 2 ; Chicken Fried Pony Steak
				EndIf
			ElseIf aiButton == 1 ; Lunch
				aiButton = LunchMESG.Show()
				If aiButton == 0 ;  Glazed Turkey Sandwich
				ElseIf aiButton == 1 ; Grilled Ham Sandwich
				ElseIf aiButton == 2 ; Shredded Pony Sandwich
				EndIf
			ElseIf aiButton == 2 ; Dinner
				aiButton = DinnerMESG.Show()
				If aiButton == 0 ; Filet Mignon
				ElseIf aiButton == 1 ; Pony Fajitas
				ElseIf aiButton == 2 ; Lobster
				EndIf
			EndIf
		EndIf
	EndWhile
EndFunction

Multilevel, looping menu[edit | edit source]

To make a multilevel, looping menu with thirty buttons that will not close until a "Done" button is pressed, use the above method but with an altered Menu() function. Note that you can jump to a given message by specifying the aiMessage argument when calling the function. Sub-options as described in the previous example can be added to the below in the same manner. Theoretically, any number of options can be added with the below structure. By making it conditional, we can check its property values with MessageBox buttons using GetVMScriptVariable and pointing to the placed instance of the item this script is attached to. To ensure the player gets said reference, make a property for the specific reference in another script, and add it to the player with AddItem by passing the reference as akItemToAdd.

ScriptName OptionsMenuScript Extends ObjectReference Conditional

Actor Property PlayerREF Auto
Armor Property MenuARMO Auto ; Playable apparel item
Bool Property bFeatureEnabled Auto Conditional ; Toggling of this demonstrated below.
GlobalVariable Property DragonsEnabled Auto ; Toggling of this demonstrated below.
Message Property OptionsMenu00MESG Auto
Message Property OptionsMenu01MESG Auto
Message Property OptionsMenu02MESG Auto
YourQuestScriptName Property QuestScript Auto ; Is Conditional with a Conditional bQuickening property 

Event OnEquipped(Actor akActor)
	If akActor == PlayerREF
		Game.DisablePlayerControls(False, False, False, False, False, True) ; Momentarily disable other menus
		PlayerREF.EquipItem(MenuARMO, True, True) ; Prevent unequip/reequip in favorites until the current menu is resolved
		Utility.Wait(0.01) ; This ensures equipping the token from the favorites menu works
		PlayerREF.UnequipItem(MenuARMO, False, True) ; Silently unequip item
		Game.EnablePlayerControls(False, False, False, False, False, True) ; Undo DisablePlayerControls
		Menu()
	EndIf
EndEvent

Function Menu(Int aiMessage = 0, Int aiButton = 0, Bool abMenu = True)
	While abMenu
		If aiButton == -1 ; As above, can prevent problems if recycling aiButton
		ElseIf aiMessage == 0
			aiButton = OptionsMenu00MESG.Show()
			If aiButton < 2 ; Toggle script property. Buttons have opposite GetVMScriptVariable conditions so only the applicable option is given.
				bFeatureEnabled = !bFeatureEnabled ; Set boolean to whatever it is not
				If bFeatureEnabled ; == True
					Debug.Trace("Featured enabled. Set things up.")
				Else ; If bFeatureEnabled == False
					Debug.Trace("Featured disabled. Stop doing stuff and clean up.")
				EndIf
			ElseIf aiButton < 4 ; Toggle quest property. Buttons have opposite conditions checking the property value with GetVMQuestVariable.
				QuestScript.bQuickening = !QuestScript.bQuickening ; Set boolean to whatever it is not
				If QuestScript.bQuickening ; == True
					Debug.Trace("Start polling.")
				Else ; If QuestScript.bQuickening == False
					Debug.Trace("Stop polling.")
				EndIf
			ElseIf aiButton < 6 ; Toggle DragonsEnabled. Buttons have opposite conditions as above, but checking the global's value with GetGlobalValue.
				DragonsEnabled.SetValue((!DragonsEnabled.GetValue() As Bool) As Float) ; If 1, set to 0. If 0, set to 1
				If DragonsEnabled.GetValue() ; != 0
					Debug.Trace("Dragons enabled.")
				Else ; If DragonsEnabled.GetValue()  == 0
					Debug.Trace("Dragons disabled.")
				EndIf
			ElseIf aiButton == 6
			ElseIf aiButton == 7
			ElseIf aiButton == 8 ; More
				aiMessage = 1
			ElseIf aiButton == 9 ; Done
				abMenu = False
			EndIf
		ElseIf aiMessage == 1
			aiButton = OptionsMenu01MESG.Show()
			If aiButton == 0
			ElseIf aiButton == 1
			ElseIf aiButton == 2
			ElseIf aiButton == 3
			ElseIf aiButton == 4
			ElseIf aiButton == 5
			ElseIf aiButton == 6
			ElseIf aiButton == 7 ; Back
				aiMessage = 0
			ElseIf aiButton == 8 ; More
				aiMessage = 2
			ElseIf aiButton == 9 ; Done
				abMenu = False
			EndIf
		ElseIf aiMessage == 2
			aiButton = OptionsMenu02MESG.Show()
			If aiButton == 0
			ElseIf aiButton == 1
			ElseIf aiButton == 2
			ElseIf aiButton == 3
			ElseIf aiButton == 4
			ElseIf aiButton == 5
			ElseIf aiButton == 6
			ElseIf aiButton == 7
			ElseIf aiButton == 8 ; Back
				aiMessage = 1
			ElseIf aiButton == 9 ; Done
				abMenu = False
			EndIf
		EndIf
	EndWhile
EndFunction

Notes[edit | edit source]

  • Given the buttons in Skyrim are listed from side to side, it is easy to spill over the edges of the user's monitor, particularly if it's a 4:3, in the event either the options are too verbose or there are too many options presented by a single message form. Currently, there's no way to list them from top to bottom as they were in previous Bethesda games. To mitigate this, keep the button text to a minimum and/or make sure to always set up conditions on mutually exclusive buttons to ensure only applicable options are presented.
  • To conditionalize buttons using variables declared in your script/quest, use GetVMScriptVariable and GetVMQuestVariable.
  • To hide buttons you wish to fill in later, add an impossible condition like 'IsXBox == -1'.
  • For debugging purposes, you could configure hidden menu buttons (in the Message forms) that only show when you set a GlobalVariable flag. For instance, create a GlobalVariable called "myDebugFlag" in the CK. Set a condition on one of your menu buttons to be "GetGlobalValue myDebugFlag == 1". In your script, have that button activate your debugging function. Now if you set myGlobalValue to 1 in the CK your button will appear in the menu. Before releasing the mod, remember to set myDebugFlag to 0 to keep the button hidden.
  • Conditionalizing MessageBox buttons will not change their indices such that, for instance, button 9 will still execute the "Done" code in the last example even if buttons 0-8 are hidden.
  • To learn how to assign user-created messageboxes as values to the message box Properties defined in the above scripts, see the Papyrus tutorial's page on Properties and Functions
  • If the Message Property isn't filled in the CK the Show() will always return a 0, and the Message will not be shown.
  • If the Message is a Notification (without buttons) instead of a Message Box the Show() will return a -1, in which case you will never be presented with an options menu.

See Also[edit | edit source]

Show - Message