-
Notifications
You must be signed in to change notification settings - Fork 169
MCM Advanced Features
People interested in advanced features shouldn't need guides. Just look at the API Reference and put 2+2 together!
(Translation: This guide will be finished later.)
Topics to cover:
- Pages
- Option flags
- Grouped updating (may not be necessary anymore after next skse update because UI functions should execute instantly)
- Keymap conflict management
- Script versioning
- Loading/unloading custom content
- Localization
Normally, when working with arrays or other variables that cannot be initialized immediately at the point of declaration, you have to put initialization code in OnInit
.
For your config menu, however, you should rather use the OnConfigInit
event we provide and avoid OnInit
.
If that's not possible and you have to use OnInit
for some reason, make sure to call parent.OnInit()
the original event handler of SKI_ConfigBase is not skipped.
string[] myArray
; WRONG
event OnInit()
myArray= new myArray[5]
; ...
endEvent
; Meh... avoid using OnInit()
event OnInit()
parent.OnInit()
myArray= new myArray[5]
; ...
endEvent
; OK
event OnConfigInit()
myArray= new myArray[5]
; ...
endEvent
The reason you should avoid OnInit()
is that config menus should be designed so that MCM (which is part of SkyUI) can be removed at any time and added again later. If that happens, the variables of your script will be reset, BUT OnInit()
won't be called again. This may leave some of variables uninitialized. OnConfigInit()
doesn't have this problem.
There are certain settings you might want to apply after each game reload, for example scripted INI changes or other modifications that persist in memory.
To do this, extend OnGameReload
of SKI_ConfigBase. Be aware that this is not part of the base API, so you'll be responsible for calling parent.OnGameReload()
. Not doing that will break the menu.
event OnGameReload()
parent.OnGameReload() ; Don't forget to call the parent!
Utility.SetINIFloat("someSetting", someValue)
endEvent
If your config menu contains a lot of options, you might want to consider diving them into several categories. Or, even if all options would fit into a single page, it might still be a good idea to separate general from advanced settings.
For this purpose config menus support multiple pages. Setting them up is straightforward.
Define your page names by setting the Pages
property of your config menu. You can either do that in the Creation Kit property editor, just like you set ModName
, or you can do it in the OnConfigInit
event of your script. An example for the scripted variant:
event OnConfigInit()
Pages = new string[2]
Pages[0] = "Page 1"
Pages[1] = "Page 2"
endEvent
Now, whenever your config menu is active, the list of pages will be shown below the mod name.
In general, setting up options for multiple pages works exactly the same as using a single page; you add them in OnPageReset
. The only difference is that you check the page
parameter when deciding which options to add:
event OnPageReset(string page)
if (page == "Page 1")
; Add page 1 options
elseIf (page == "Page 2")
; Add page 2 options
endIf
endEvent
By default, after selecting your mod from the main list, no page is active and the page
parameter is is ""
. Since we didn't handle this case in our example, the option list will be blank until the user has chosen a page. On way to fill that void is adding a custom logo of your mod . For further details on this topic, see Loading custom content.
Since each option ID is unique, for all the other events it won't matter which option a page is on. However, if you have a lot of options, you may want to spread your code over several functions.
In this scenario, it's helpful being able to check the current page even outside of OnPageReset
; you do that by accessing the CurrentPage
property:
event OnOptionSelect(int option)
if (CurrentPage == "Gameplay")
HandleGameplayOptionSelect(option)
elseIf (CurrentPage == "Immersion")
HandleImmersionOptionSelect(option)
endIf
endIf
Be aware that this makes moving around options between several pages, or renaming pages, slightly more tedious, since you have to change more than just OnPageReset
when doing so.
While the basics about options have already been covered, there are two more topics that have been left out so far: Option flags and grouped updating.
All Add*Option
functions have an optional flags
parameter. It can be used to enable certain behavior for the option. Accepted flags are
-
OPTION_FLAG_NONE
, to clear the flags; -
OPTION_FLAG_DISABLED
, to grey out and disable the option.
int flags
if (isMyOptionDisabled)
flags = OPTION_FLAG_DISABLED
else
flags = OPTION_FLAG_NONE
endIf
AddToggleOption("Toggle this", myToggleValue, flags)
To change the flags later in combination with Set*OptionValue
functions, use SetOptionFlags(optionID, flags)
.
If you use the Set*OptionValue
functions to change several options at once, Papyrus may slightly delay function execution at any point in the script. This results in asynchronous updating of the option display.
To prevent this, Set*OptionValue
and SetOptionFlags
support an optional noUpdate
parameter. Example:
SetTextOptionValue(oid1, "Value1", true) ; Don't redraw the list yet
SetTextOptionValue(oid2, "Value2", true) ; ...
SetTextOptionValue(oid3, "Value3", true) ; ...
SetTextOptionValue(oid4, "Value4") ; Refresh now
It's possible to load content from an external file into the option list area. The original option list will be hidden when that happens.
Supported source file formats are SWF for Flash movies and DDS for images. PNG and some other image formats may work as well, but they will most likely be imported in bad quality.
event OnPageReset(string a_page)
; Load custom .swf for animated logo that's displayed when no page is selected yet.
if (a_page == "")
LoadCustomContent("skyui/skyui_splash.swf") ; Path relative to Data/Interface
return
else
UnloadCustomContent()
endIf
; ... rest of the OnPageReset
To support for multiple languages in your config menu, you can utilize the UI localization capabilities provided by SKSE. This section will explain how this works exactly.
The game itself stores the translated strings in Data/Interface/Translate_LANGUAGE.txt
, where LANGUAGE
is replaced by the current game language, i.e. Translate_ENGLISH.txt
. This file contains lines of key/value pairs for the translated strings, separated by a tab stop. Each key has to start with the $
sign. Example:
...
$Back Back
$Backstabs Backstabs
$Barters Barters
...
Whenever the contents of a textfield in UI match a key in this file, it's replaced with the translated value. Even without SKSE, you could add your own translations by modifying the original translates file. The problem is that this would lead to conflicts if multiple mods want to extend this file.
That's why SKSE adds support to load translations from additional files. For each active mod in the load order, Data/Interface/Translations/modname_LANGUAGE.txt
is checked for translations, where modname
is replaced by the name of the mod data file (i.e. SkyUI_ENGLISH.txt
, if the data file is SkyUI.esp
). The translation format is the same as described above.
Be aware, however, that these translations only work, if the textfield contents match the translation key exactly. Given
$Hello Hello
"$Hello"
results in "Hello"
, but "$Hello World"
stays "$Hello World"
.
Loading translation files from inside BSA file is possible. Languages the game supports are CZECH, ENGLISH, FRENCH, GERMAN, ITALIAN, POLISH, RUSSIAN, SPANISH and JAPANESE. Only the file that matches your current language is loaded; there is no fallback to ENGLISH as default.
If your mod is named MyMod.esp
, localize your page names by adding
$General General
$Advanced Advanced
$Help Help
to Data/Interface/Translations/MyMod_ENGLISH.txt
,
$General Allgemein
$Advanced Fortgeschritten
$Help Hilfe
to Data/Interface/Translations/MyMod_GERMAN.txt
etc.
Then, in the config menu, name your pages accordingly:
Pages[0] = "$General"
Pages[1] = "$Advanced"
Pages[2] = "$Help"