Adding new Settings

From PMDOWiki

Version 0.8.4 contained a heavy rework of the Settings menu. The new menu supports multiple pages of settings, and allows modders to add new pages at will thanks to the AddMenu service callback. Please refer to the Editing Menus#Prerequisites section to know how to interact with menus using this callback.

The label we need is literally just "SETTINGS_MENU". Keep in mind that this is the label of the settings menu TITLE window. The actual settings pages have no label, and are supposed to be populated indirectly thanks to the functions described in this page.

Despite the menu being technically an InteractableMenu, using any function described in the Editing Menus page is highly discouraged, with the ILabeled functions being the only exception.

Feel free to visit the Settings Menu Reference page for a more in-depth explanation of all of the functions and values provided by the settings engine specifically.

Making a new settings page

With all that said, adding new settings is very simple. Call SettingsTitleMenu:NewPage, and then register new settings with SettingsPage:AddSetting, like this:

if menu:HasLabel() and menu.Label == labels.SETTINGS_MENU and menu.InGame then
    local page = menu:AddPage("my_namespace", "My Mod")

    local options = {"Off", 1, 2, 3, 4, 5}
    local saveFunction = function(setting) SV.MySetting = setting.CurrentChoice end
    local choiceChangedFunction = function(setting) PrintInfo("MySetting preview: "..setting.CurrentChoice) end
    page:AddSetting("My Setting", options, SV.MySetting or 0, saveFunction, choiceChangedFunction)

    page.GlobalSaveAction = function() PrintInfo("SV.MySetting has been set to "..SV.MySetting) end
end

Of course, the code has something more going on, so let's check it out in more detail: The first thing we need to do is, as usual, to make sure that we're editing the right menu. Then we check if we're in game or not. Making a setting work from the Top Menu requires a bit more work than what is shown here, so we're going avoid that entirely for now.

The first step now is to create a SettingsPage and store it in a variable. Then we prepare the structures we need for our setting: a list of options and a function that saves our settings when the player presses the confirm button. Since this is a demonstration, we will also include a function that runs every time the setting's value change, even though it is both optional and not necessary here.

Finally, we create our settings, giving it also a display name and using its previous value (or a default value if there wasn't one yet) as the starting choice for the setting. This default value is a 0-based index, so, for this setting, valid values go from 0 to 5.

Finally, the last line registers a special function that is ran at the end of every other save actions in the page when the player confirms their changes. This is also optional, and it's included here simply for demonstration.

Notice how the saveFunction and the choiceChangedFunction both accept an argument. This is because the engine always passes the MenuSetting in question to our function. In almost all cases, all we are interested in doing is store its CurrentChoice value somewhere in our code, but it IS technically possible to tamper with any of the object's public properties and fields.

Top Menu settings

As stated previously, making a setting work from the Top Menu requires a bit more work. The SV structure is already loaded when a player reaches the Top Menu, but it is also loaded again as soon as the Continue button is pressed, so any changes made to it in this state will be completely lost.

Fortunately for us, we don't HAVE to save our changes on the SV structure immediately, and we can use a buffer instead:

if menu:HasLabel() and menu.Label == labels.SETTINGS_MENU then
    local page = menu:AddPage("my_namespace", "My Mod")

    MY_SETTINGS = {
        Setting = SV.MySetting or 0
    }
    local options = {"Off", 1, 2, 3, 4, 5}
    local saveFunction = function(setting)
        if menu.InGame then
            SV.MySetting = setting.CurrentChoice
        else
            MY_SETTINGS.Setting = setting.CurrentChoice
        end
    end
    page:AddSetting("My Setting", options, MY_SETTINGS.Setting, saveFunction, choiceChangedFunction)
end

The first noticeable difference is that the initial check does not care about us being in game or not anymore. We are then creating a new global table called MY_SETTINGS and loading the current state of the setting inside of it (or a default value if it doesn't exist yet). The save function has also changed to account for the two cases of being inside the game or in the top menu. We save our data directly in the SV structure if we're in game, and save in the MY_SETTINGS table if we're not.

And that's it!
Except, it's not. We need to load our temporary table in the SV structure if we want to ACTUALLY save our setting. For that, we will need another service callback: LoadSavedData.

This is all we need to finish the job. Once subscribed to the callback, the function below will be called as soon as the Continue button is pressed on the Top Menu, finally storing our setting inside the SV structure.

function MenuTools.OnSaveLoad()
    if MY_SETTINGS then
        SV.MySetting = MY_SETTINGS.Setting
    end
end