Add Entry Options EN - Drova-Modding/Drova-Modding-API GitHub Wiki

First steps

Creation

The Option Menu is dynamically created and requires you to register to OptionMenuAccess.Instance.OnOptionMenuOpen.

You should not unregister from the callback, because when you switch into the game, the option menu gets destroyed. But of course, you should unsubscribe from it when the Application closes.

Your own Panel

In this callback you need to create your Panel with

Sprite yourSprite; // Icon from the Panel
Transform parent = OptionMenuAccess.Instance.AddPanel(yourSprite, "GUI_Button_OptionTab_X"); 
if(!panel) return; // Panel already created

Now you are able to create the options in the Panel with

// ...
if(!panel) return; // Panel already created
OptionMenuAccess.GetBuilder(parent)

Modding Panel

If you don't have that many options or don't want to create a own Panel, you can share your settings in the shared Modding Panel. Best Practice is to start with an Title to make the user aware of the next Region.

var builder = OptionMenuAccess.Instance.GetBuilder("YourUniqueModId");
// The callbacks are pretty uncontrollable and you get null, when your menu already exists
if !(builder) return;
// Build the options into the modding menu

Builder

This uses the common Builder Pattern

and to create it just call at the end .Build(). Optional you can also get the List<GameObject> of your created GameObjects sorted after your call order from the var list = builder.Build().

Dropdowns

enum Test {
    Yes,
    No
}

Dictionary<Test, LocalizedString> pairs = new()
{
      { Test.No, new LocalizedString("ExampleMod", "ExampleModDropdownItem") },
      { Test.Yes, new LocalizedString("ExampleMod", "ExampleModDropdownItem2") }
};

OptionMenuAccess.Instance.GetBuilder(parent)
                .CreateDropdown(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModDropdown"), "ExampleModModDropdown", pairs, Test.No)

The first param of CreateDropdown is the title of it, the second is the option key, the third are the dropdown values, with the enum as Key and their displayed text as Value, the fourth is the default, when the user did not changed yet the option, otherwise its ignored.

Title

image

OptionMenuAccess.Instance.GetBuilder(parent)
    .CreateTitle(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModTitle"))

Disclaimer

image

OptionMenuAccess.Instance.GetBuilder(parent)
    .CreateDisclaimer(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModDisclaimer"))

Header

image

OptionMenuAccess.Instance.GetBuilder(parent)
    .CreateHeader(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModHeader"))

Switches

image

OptionMenuAccess.Instance.GetBuilder(parent)
   .CreateSwitch(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSwitch"), LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSwitch_On"), LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSwitch_Off"), "ExampleModAllow")

First argument is the title, second is the displayed on value, third is the displayed off value, fourth is the optionKey

Slider

image

Slider comes with 2 Variants, one for floats and one for ints.

OptionMenuAccess.Instance.GetBuilder(parent)
     .CreateSlider(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSliderInt"), "ExampleModSliderInt", 0)
     .CreateSlider(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSliderFloat"), "ExampleModSliderFloat", 0.0f)

The first argument is the title, the second is the option key, the third let you either create a float or int, depending on what value you give in, for the min value fourth is max value with default to 100 fifth is defaultValue with default to 0, which is ignored when the user already set a value sixth is float specific and allows you to only set whole numbers

Buttons

image

builder
        .CreateButton(LocalizationAccess.GetLocalizedString("TwitchMod", "TwitchButton"), LocalizationAccess.GetLocalizedString("TwitchMod", "TwitchButton"), () => MelonLogger.Msg("Button Pressed"))

First argument is the left title, second argument is the button displayed text, third argument is the action callback for the click event

Keybinding

image

builder
                .CreateInputActionSection([new(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSprint"), "TestAction")])

The Parameter is an array of Keybindings you want to have. The Action needs to be created. If you don't want gamepad support, than don't give your InputAction a Binding for Gamepad, or rename the group name different, if you want to support it, but doesn't want to expose it.

Full Example

Here is an example how it could look for a basic mod. It is not required to do it like that, you can also do stuff later on or in different classes as you wish.

using UnityEngine;
using MelonLoader;
using Drova_Modding_API.Access;
using Il2Cpp;
using UnityEngine.UI;

[assembly: MelonInfo(typeof(ExampleMod .Core), "ExampleMod", "1.0.0", "ExampleAuthor", null)]
[assembly: MelonGame("Just2D", "Drova")]
[assembly: MelonAdditionalDependencies("Drova_Modding_API")]

namespace ExampleMod {
    public class Core : MelonMod {
        public const string MainScene = "Scene_MainMenu";
        private InputAction testAction;


        public override void OnInitializeMelon()
        {
            OptionMenuAccess.Instance.OnOptionMenuOpen += BuildPanel;
            // Callback for when the InputActions are loaded, if you try to add it before, than your changes will be lost.
            InputActionRegister.OnInputActionsLoaded += () =>
            {
                // Your actions will be first enabled when the user enters the Game and disabled when he leaves it.
                var actionToRegister = InputActionRegister.Instance.AddActionToGameplayMap("TestAction");
                // This means our Action already exist, do here your migrations if you need to and get your reference
                if(actionToRegister == null)
                {
                    testAction = InputActionRegister.Instance["TestAction"];
                    return;
                }
                // The groups are very important and tells the Option UI what it is allowed to bind where
                actionToRegister.AddBinding("<Keyboard>/t", null, null, "Keyboard;Mouse");
                actionToRegister.AddBinding("<Gamepad>/buttonSouth", null, null, "Gamepad");
                testAction = actionToRegister;
            };
        }

        public override void OnSceneWasLoaded(int buildIndex, string sceneName)
        {
            if (sceneName == MainScene)
            {
                LocalizationAccess.CreateLocalizationEntries(
                [
                    new("ExampleMod", "ExampleMod", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModDisclaimer", "Wow Cool!", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModHeader", "Ich bin nen Header", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModSwitch", "Switches", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModSwitch_On", "Ja", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModSwitch_Off", "Nein", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModSliderInt", "Slider mit Int", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModSliderFloat", "Slider mit float", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModSprint", "Sprinten", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModDropdown", "Dropdown", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModDropdownItem", "Ein Item", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                    new("ExampleModDropdownItem2", "Zweites Item", Il2CppCustomFramework.Localization.LocalizationDB.ELanguage.de),
                ], "ExampleMod");
            }
        }

        public override void OnApplicationQuit()
        {
            base.OnApplicationQuit();
            OptionMenuAccess.Instance.OnOptionMenuOpen -= BuildPanel;
        }

        internal void BuildPanel()
        {
            var builder = OptionMenuAccess.Instance.GetBuilder("ExampleMod");
            if(!builder) return;
            Dictionary<Test, LocalizedString> pairs = new()
            {
                { Test.No, new LocalizedString("ExampleMod", "ExampleModDropdownItem") },
                { Test.Yes, new LocalizedString("ExampleMod", "ExampleModDropdownItem2") }
            };
            builder
                .CreateTitle(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleMod"))
                .CreateDisclaimer(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModDisclaimer"))
                .CreateHeader(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModHeader"))
                .CreateSwitch(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSwitch"), LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSwitch_On"), LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSwitch_Off"), "ExampleModAllow")
                .CreateSlider(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSliderInt"), "ExampleModSliderInt", 0)
                .CreateSlider(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSliderFloat"), "ExampleModSliderFloat", 0.0f)
                .CreateInputActionSection([new(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModSprint"), "TestAction")])
                .CreateDropdown(LocalizationAccess.GetLocalizedString("ExampleMod", "ExampleModDropdown"), "ExampleModDropdown", pairs, Test.No)
                .Build();
        }

  
   }
   public enum Test
   {
       No,
       Yes
   }
}
⚠️ **GitHub.com Fallback** ⚠️