Adding new Settings: Difference between revisions

From PMDOWiki
MistressNebula (talk | contribs)
m Added TOC limit + small introduction changes
MistressNebula (talk | contribs)
Account for settings pages now actually having a label
 
(3 intermediate revisions by the same user not shown)
Line 1: Line 1:
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 [[Service Callbacks#AddMenu|AddMenu]] service callback. Please refer to the [[Editing Menus#Prerequisites]] section to know how to interact with menus using this callback.
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 [[Service Callbacks#AddMenu|AddMenu]] service callback. Please refer to the [[Editing Menus#Prerequisites]] section to know how to interact with menus using this callback.


The settings menu's label is literally just <code>"SETTINGS_MENU"</code>. Keep in mind that this retrieves the label of the settings menu's TITLE window. The actual settings pages have no label, and are supposed to be populated using the functions described in this page.
The label we need is literally just <code>"SETTINGS_MENU"</code>. Keep in mind that this is the label of the settings menu TITLE window. The actual settings pages are supposed to be populated indirectly by using 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 sole exception of the ILabeled functions.
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.


That said, adding new settings is very simple: just call <code>SettingsTitleMenu:NewPage</code> and then register new settings with <code>SettingsPage:AddSetting</code>.
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.


{|limit=2
== Making a new settings page ==
  |__TOC__
With all that said, adding new settings is very simple. Call <code>SettingsTitleMenu:NewPage</code>, and then register new settings with <code>SettingsPage:AddSetting</code>, like this:
  |}


== SettingsTitleMenu ==
<pre>
This is the menu included in the AddMenu callback as argument.
if menu:HasLabel() and menu.Label == labels.SETTINGS_MENU and menu.InGame then
    local page = menu:AddPage("my_namespace", "My Mod")


=== SettingsTitleMenu:HasPage(System.String) ===
    local options = {"Off", 1, 2, 3, 4, 5}
Returns <code>true</code> if a SettingsPage is already associated with the provided key, <code>false</code> otherwise.
    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)


====Arguments====
    page.GlobalSaveAction = function() PrintInfo("SV.MySetting has been set to "..SV.MySetting) end
* <code>key</code> the unique id key to test for.
end
</pre>


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 [[Settings Menu Reference#SettingsPage|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.


=== SettingsTitleMenu:AddPage(System.String, System.String) ===
It is now time to create our setting, 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 into the list of options, so, for this setting, valid values go from 0 to 5.
Creates and returns a new SettingsPage object, assigning it to a key and giving it a title.
Using a key that already exixts results in an error.
It is recommended to always use a mod's namespace as either the prefix of a page's key or the key itself. This will minimize conflicts and ensure that multiple pages for the same mod stay as close together as possible. Good examples are: <code>my_namespace</code>, <code>my_namespace_general</code>, <code>my_namespace_qol</code>.


====Arguments====
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.
* <code>key</code> the unique id key of the newly created page.
* <code>title</code> the localized display title of the page.


Notice how the saveFunction and the choiceChangedFunction both accept an argument. This is because the engine always passes the [https://github.com/RogueCollab/RogueEssence/tree/master/RogueEssence/Menu/MenuElements/MenuSetting.cs MenuSetting] in question to our function. In almost all cases, all we are interested in doing is store its <code> CurrentChoice</code> value somewhere in our code, but it IS technically possible to tamper with any of the object's <code>public</code> 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.


== SettingsPage ==
Fortunately for us, we don't HAVE to save our changes on the SV structure immediately, and we can use a buffer instead:
This object is what gets returned by <code>SettingsTitleMenu:AddPage</code>. This is the object that actually allows the user to add new settings to the game.


=== SettingsPage.GlobalSaveAction ===
<pre>
This property is an Action; a function that takes no parameters and returns nothing. If set, it is called after all of the settings' SaveActions have been ran. PMDO uses this in its original page to bring the player back to the top menu if the language setting has been edited.
if menu:HasLabel() and menu.Label == labels.SETTINGS_MENU then
    local page = menu:AddPage("my_namespace", "My Mod")
    if not MY_SETTINGS then
        MY_SETTINGS = {
            Setting = SV.MySetting or 0
        }
    end
    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
</pre>


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). Of course, we also check if MY_SETTINGS already exists, because we don't want to reset the player's choices if they already opened the menu once.
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!</br>
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: [[Service Callbacks#LoadSavedData|LoadSavedData]].


=== SettingsPage.AddSetting(System.String, NLua.LuaTable, System.Int32, System.Action<RogueEssence.Menu.MenuSetting>, System.Action<RogueEssence.Menu.MenuSetting>) ===
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.
This function registers a new setting, giving it a name, a list of state, a starting position and some special callbacks.


==== Arguments ====
<pre>
* <code>name</code> the localized display name of the setting.
function MenuTools.OnSaveLoad()
* <code>options</code> a table containing a list of strings. Each string is one of the possible displayed values of the setting.
    if MY_SETTINGS then
* <code>defaultValue</code> the option this setting will show as its starting value. It must be a number between 0 and <code>#options-1</code>.
        SV.MySetting = MY_SETTINGS.Setting
* <code>action</code> the function that will be called when the player presses the confirm button. It will have its corresponding [https://github.com/RogueCollab/RogueEssence/blob/master/RogueEssence/Menu/MenuElements/MenuSetting.cs MenuSetting] passed to it, so that you can access its CurrentChoice parameter and use it to actually update whatever value this setting represents.
    end
* <code>settingChangedAction</code> the function that will be called whenever the player changes the value of this specific setting. Useful to display changes immediately. PMDO uses this in its original page to temporarily change the window borders without actually saving the setting. It will have its corresponding MenuSetting passed to it, so that you can access its CurrentChoice parameter and use it to actually update whatever temporary value this setting is tied to. Defaults to <code>nil</code>.
end
</pre>

Latest revision as of 12:35, 13 March 2025

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 are supposed to be populated indirectly by using 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.

It is now time to create our setting, 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 into the list of options, 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")
    if not MY_SETTINGS then
        MY_SETTINGS = {
            Setting = SV.MySetting or 0
        }
    end
    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). Of course, we also check if MY_SETTINGS already exists, because we don't want to reset the player's choices if they already opened the menu once. 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