MCM Quickstart - schlangster/skyui GitHub Wiki

Introduction

The Mod Configuration Menu (MCM) originally is a general-purpose control panel for Fallout: New Vegas created by Pelinor. We took up the task to port the concept to Skyrim and implemented it as the SkyUI control panel.

This guide will give you a quick overview of all necessary steps to add your own config menu. Once you're familiar with the basic concepts, you should have no trouble picking up the rest from the examples we provide, or just by taking a look at the API reference.

For the rest of this guide, we assume that you're familiar with the Creation Kit and Papyrus. If that's not the case, you should probably head over to the Creation Kit wiki and do some reading there first.

In case you were linked to this guide directly from somewhere else, you should have a look at the overview first, especially to get the SkyUI SDK files: https://github.com/schlangster/skyui/wiki/

Creating a new Config Menu

A config menu is controlled by a Papyrus script. If you want a global script that's not attached to any particular actor or object in the game world, the common method is to use a quest script. Such a quest script has to be attached to an actual quest that has the sole purpose of binding the script. It doesn't do anything quests would normally do.

All config menus have to extend SKI_ConfigBase, which is a script provided by us.

Consequently, to create a new config menu for your mod, you have to

  • create a new script that will control your config menu. For now, it doesn't have to be more than this:
    scriptname MyConfigMenu extends SKI_ConfigBase

  • create a new quest and attach your script to it.

The first thing you'll want to customize for you new config menu is the name that will be displayed in the control panel. You do this by setting the ModName property of your attached script in the Creation Kit property editor. You might also notice the Pages property while doing that - it can be ignored for now.

There is still one, very important, thing left to do. Each config menu has to run some maintenance code when the game is reloaded. This has to be set up manually:

  • In the quest you created, select the Quest Aliases tab and add a new reference alias. Name it PlayerAlias.
  • For Fill Type, select the player reference (Specific Reference, Cell any, Ref PlayerRef).
  • In the Scripts list, add SKI_PlayerLoadGameAlias.

This screenshot shows what the reload alias setup it looks like in the end.

That's all it takes to make your menu show up in the control panel. It doesn't have any content and just shows an empty panel, but we are going to change that in the following sections.

Customizing your Config Menu

As you may recall, the config menu script you created extends SKI_ConfigBase. SKI_ConfigBase provides several events that are executed when the user interacts with the config menu. You customize your config menu by implementing these events, pretty much like you would implement OnUpdate or OnInit for other scripts.

For each of those events, you're expected to do certain things. An event that's generated when the user clicked a check box option, for example, expects you to react to that input.

Adding Options

The first event you should implement is

event OnPageReset(string page)
	{Called when a new page is selected, including the initial empty page}
endEvent

The config panel keeps very little internal state. It doesn't remember all the options for all the menus it manages. Instead, whenever the active config menu changes, or even when the page changes, the current content is completely cleared and forgotten. The config panel then calls OnPageReset on the active config menu, and in this event you are supposed to add content to the option list - or in other words: to fill the current page.

The event is called OnPageReset because each config menu supports up to 128 pages you can use to structure your options. You don't have to use multiple pages though - in this first example, we won't do it either. The initial page when selecting a mod in the config panel is the default page "".

You may add up to 128 option entries per page. Each entry is indexed by its position from 0 to 127. The list that holds these options shows two entries per row. That makes a grid with 2 columns and 64 rows. First row holds options 0 and 1, second row 2 and 3, and so on. Last row holds options 126 and 127.

To insert an option at a certain position, you can move the option cursor with SetCursorPosition, then call the function that adds the option.
For example

SetCursorPosition(3)
AddToggleOption("Hello world?", true)

adds a check box on the right half of the second row.

Adding an option automatically forwards the cursor to the next entry. Which entry that is depends on the fill mode. There are two supported fill modes: LEFT_TO_RIGHTand TOP_TO_BOTTOM. You set them with SetCursorFillMode.

``` SetCursorFillMode(LEFT_TO_RIGHT) AddToggleOption("A", true) AddToggleOption("B", true) AddToggleOption("C", true) AddToggleOption("D", true) ``` Result: ``` A | B C | D ``` ```

SetCursorFillMode(TOP_TO_BOTTOM) AddToggleOption("A", true) AddToggleOption("B", true) AddToggleOption("C", true) AddToggleOption("D", true)

Result:

A | B | C | D |

</td>
</tr>
</table>
You are free to change cursor position and fill mode at any time when adding options.

Let's have a look at the functions to add the actual options. We already used `AddToggleOption` in our last example, but there are more option types. Here's a complete list:
* empty - An empty entry, used to add spacing between options without having to re-position the cursor.
* header - A decorated header text.
* text - A generic text/value pair.
* toggle - A text/checkbox pair.
* slider - A text/number pair, pops up a slider dialog when selected.
* menu - A text/value pair, pops up a list dialog when selected.
* color - A text/color pair, pops up a color swatch dailog when selected.
* keymap - A text/keycode pair.

For each option type, there's a `Add*Option` function.  
In this guide, we will only use _empty_, _header_ and _toggle_.

Here's an implementation of `OnPageReset`:

; Toggle states bool aVal = false; bool bVal = false; bool cVal = false; bool dVal = false

event OnPageReset(string page) SetCursorFillMode(TOP_TO_BOTTOM) SetCursorPosition(0) ; Can be removed because it starts at 0 anyway

AddHeaderOption("Group 1")
AddToggleOption("A", aVal)

AddEmptyOption()

AddHeaderOption("Group 2")
AddToggleOption("B", bVal)
AddToggleOption("C", cVal)

SetCursorPosition(1) ; Move cursor to top right position

AddHeaderOption("Group 3")
AddToggleOption("D", dVal)

endEvent

Now, when opening our config menu, it shows a bunch of fancy check boxes. But when selecting them, nothing happens (which is normal at this point). This is going to be addressed in the next section.

## Handling Option Selection

When an option is selected, an event is generated; which one depends on the option type. For _toggle_ and _text_ the selection event is the generic

event OnOptionSelect(int option) {Called when a non-interactive option has been selected} endEvent

But how do we know, which option has been selected? The `option` parameter contains the actual option index. It would be very inconvenient if we had to keep track of where each option is positioned manually. That's why all `Add*Option` functions return the position they added the option at. This so-called option ID (OID) can be saved and tested for in `OnOptionSelect`:

; OIDs int aOID int bOID

; Toggle states bool aVal = false; bool bVal = true;

event OnPageReset(string page) AddHeaderOption("Group 1") aOID = AddToggleOption("A", aVal) bOID = AddToggleOption("B", bVal) endEvent

event OnOptionSelect(int option) if (option == aOID) ; ... handle A select elseIf (option == bOID) ; ... handle B select endIf endEvent

To handle the select, first the internal state should be updated, i.e. `aVal = !aVal`.  
You also have to update the changed option entry. Why doesn't the menu do that automatically? It would've been possible, but in case you fail to update the internal state (maybe for good reason), display and state would be out of sync. Instead, you are the one responsible for updating any options you changed.

Since doing a full page reset just for a single option would be wasteful, there are functions for each option type that allow you change its value later:

event OnOptionSelect(int option) if (option == aOID) aVal = !aVal SetToggleOptionValue(aOID, aVal) elseIf (option == bOID) bVal = !bVal SetToggleOptionValue(bOID, bVal) endIf endEvent


## Option Defaults

When the user requests an option to be reset to its default value, `OnOptionDefault` is executed. Implementing this is going to be straightforward, since it works the same way as `OnOptionSelect`:

event OnOptionDefault(int option) if (option == aOID) aVal = false ; default value SetToggleOptionValue(aOID, aVal) elseIf (option == bOID) bVal = true ; default value SetToggleOptionValue(bOID, bVal) endIf endEvent


## Option Highlight Text

You can display additional information about each of your options in the text field below the option list. Whenever the user highlights an option for about a second, `OnOptionHighlight` is executed. To set the option info text,  use `SetInfoText`:

event OnOptionHighlight(int option) if (option == aOID) SetInfoText("This is option A. It serves absolutely no purpose.\nDefault: false") elseIf (option == bOID) SetInfoText("This is option B.\nOption B this is.\nDefault: true") endIf endEvent

As you can see, it's possible to have multi-line text by  inserting "\n". To leave some space between the panel and the bottombar, you should not use more than three lines though.

## Conclusion

This covered all the basic events every config menu should implement: `OnPageReset`, `OnOptionDefault` and `OptionHighlight`. For handling selection of simple option types, we've taken a look at `OnOptionSelect`.

Where you can go from here:
* To learn how to use the other option types we didn't cover yet, check out the [Option Type Reference](MCM-Option-Types).
* The [Advanced Features Guide](MCM-Advanced-Features) explains - you guessed it - the more advanced features.
* For a complete listing of all available events and functions, have a look at the [API Reference](MCM-API-Reference).
* Have a look at the [examples](MCM-Examples).
⚠️ **GitHub.com Fallback** ⚠️