MCM Quickstart

schlangster edited this page Nov 26, 2013 · 40 revisions

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 | ```

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: