Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Adding In-game options for your Humankind DLL Mod
#1
This tutorial will show what is required to add to the in-game options. It is assumed that you already know the basics of creating a DLL mod. If not, try this tutorial to get started.

To add a game option to an existing menu, you will need to add records to 3 of the in-game database tables.
GameOptionDefinition - The option itself with all of its potential values
LocalizedStringElement - The text displayed for your option and its potential values
UIMapper - Find an existing UIMapper for the options group to which you want to add your options and modify the set to include your own.

How to add records to the database:
To add records to the database, you will need to patch an existing function as an entry point. Make sure that it is something that is only run once or something that runs at the load and unload of the game to make and revert changes to the database. In this example, I'll use OptionsManager<GameOptionDefinition>.Load to add it once and be sure that it executes before the options are loaded into the UI.

Code:
    [HarmonyPatch(typeof(OptionsManager<GameOptionDefinition>))]
    public class MyMod_GameOptions
    {
        [HarmonyPatch(nameof(Load))]
        [HarmonyPrefix]
        public static bool Load(OptionsManager<GameOptionDefinition> __instance)
        {
            //Patch entry point

            return true;
        }
    }

Accessing the database:
You can get access to any of the databases like so:
Code:
var gameOptions = Databases.GetDatabase<GameOptionDefinition>();

The database is enumerable and can be looped. 
Code:
foreach(var option in gameOptions){
  
}

You can also get specific records by their StaticString key.
Code:
if (gameOptions.TryGetValue(new StaticString("Some Option"), out var someOption))
{

}


To create a new record, use the ScriptableObject.CreateInstance method:
Code:
var myOption = ScriptableObject.CreateInstance<GameOptionDefinition>();

You can update or save records to a database with the Touch method:
Code:
gameOptions.Touch(myOption);

Adding the game options:

Using the database manipulation methods above, here is how we can add a new in-game option.

Below are few helper classes that will simplify the addition and use of options:
Code:
public class GameOptionInfo
    {
        public string Key { get; set; }
        public string GroupKey { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string DefaultValue { get; set; }
        public UIControlType ControlType { get; set; }
        public List<GameOptionStateInfo> States { get; set; } = new List<GameOptionStateInfo>();
    }
    public class GameOptionStateInfo
    {
        public string Value { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
    }
    public static class GameOptionHelper
    {
        static IGameOptionsService _gameOptions;
        static IGameOptionsService GameOptions
        {
            get
            {
                if (_gameOptions == null)
                {
                    _gameOptions = Services.GetService<IGameOptionsService>();
                }
                return _gameOptions;
            }
        }

        public static string GetGameOption(GameOptionInfo info)
        {
            return GameOptions.GetOption(new StaticString(info.Key)).CurrentValue;
        }
        public static bool CheckGameOption(GameOptionInfo info, string checkValue, bool caseSensitive=false)
        {
            var val = GetGameOption(info);
            if (caseSensitive)
            {
                return val == checkValue;
            }
            return val?.ToLower() == checkValue?.ToLower();
        }
        public static void Initialize(params GameOptionInfo[] Options)
        {
            var gameOptions = Databases.GetDatabase<GameOptionDefinition>();
            var uiMappers = Databases.GetDatabase<UIMapper>();
            var localizedStrings = Databases.GetDatabase<LocalizedStringElement>();

            foreach (var optionVal in Options)
            {
                //Get the last key value so that you can increment it and have a unique value for your new option.
                var lastKey = gameOptions.Max(x => x.Key);
                var gameOptionName = optionVal.Key;
                
                var option = ScriptableObject.CreateInstance<GameOptionDefinition>();

                option.CanBeRandomized = false;
                option.Key = ++lastKey;
                option.XmlSerializableName = gameOptionName;
                option.name = gameOptionName;
                option.Default = optionVal.DefaultValue;
                option.States = new OptionState[optionVal.States.Count];
                for (int i = 0; i < option.States.Length; i++)
                {
                    option.States[i] = new OptionState { Value = optionVal.States[i].Value };
                };
                gameOptions.Touch(option);
                localizedStrings.Touch(new LocalizedStringElement()
                {
                    LineId = $"%{gameOptionName}Title",
                    LocalizedStringElementFlag = LocalizedStringElementFlag.None,
                    CompactedNodes = new LocalizedNode[] {
                        new LocalizedNode{ Id= LocalizedNodeType.Terminal, TextValue=optionVal.Title}
                    },
                    TagCodes = new[] { 0 }
                });
                localizedStrings.Touch(new LocalizedStringElement()
                {
                    LineId = $"%{gameOptionName}Description",
                    LocalizedStringElementFlag = LocalizedStringElementFlag.None,
                    CompactedNodes = new LocalizedNode[] {
                        new LocalizedNode{ Id= LocalizedNodeType.Terminal, TextValue=optionVal.Description}
                    },
                    TagCodes = new[] { 0 }
                });
                foreach (var opt in optionVal.States)
                {
                    localizedStrings.Touch(new LocalizedStringElement()
                    {
                        LineId = $"%{gameOptionName}{opt.Value}Title",
                        LocalizedStringElementFlag = LocalizedStringElementFlag.None,
                        CompactedNodes = new LocalizedNode[] {
                            new LocalizedNode{ Id= LocalizedNodeType.Terminal, TextValue=opt.Title }
                        },
                        TagCodes = new[] { 0 }
                    });
                    localizedStrings.Touch(new LocalizedStringElement()
                    {
                        LineId = $"%{gameOptionName}{opt.Value}Description",
                        LocalizedStringElementFlag = LocalizedStringElementFlag.None,
                        CompactedNodes = new LocalizedNode[] {
                            new LocalizedNode{ Id= LocalizedNodeType.Terminal, TextValue=opt.Description }
                        },
                        TagCodes = new[] { 0 }
                    });
                }
                var optionGroupNameField = typeof(OptionsGroupUIMapper).GetField("optionsName", BindingFlags.Instance | BindingFlags.NonPublic);
                var optionMapper = ScriptableObject.CreateInstance<OptionUIMapper>();
                optionMapper.name = gameOptionName;
                optionMapper.XmlSerializableName = gameOptionName;
                optionMapper.OptionFlags = OptionUIMapper.Flags.None;
                optionMapper.ControlType = optionVal.ControlType;
                optionMapper.Title = $"%{gameOptionName}Title";
                optionMapper.Description = $"%{gameOptionName}Description";
                optionMapper.Initialize();
                uiMappers.Touch(optionMapper);
                if (uiMappers.TryGetValue(new StaticString(optionVal.GroupKey), out var paceGroup))
                {
                    var optionGroup = (OptionsGroupUIMapper)paceGroup;
                    var optionsName = (string[])optionGroupNameField.GetValue(optionGroup);
                    optionsName = optionsName.Union(new[] { gameOptionName }).ToArray();
                    optionGroupNameField.SetValue(optionGroup, optionsName);
                    optionGroup.Initialize();
                    uiMappers.Touch(optionGroup);
                }
            }
        }
    }

You can add the helper classes to your project or re-implement them as you like. 

If you do use the helper classes above, you can add new options like so:
Code:
// define an option
        var AllowPeacefulAnimals = new GameOptionInfo
        {
            Key = "GameOption_AOM_EnablePeacefulAnimals",
            GroupKey = "GameOptionGroup_LobbyWorldOptions",
            DefaultValue = "True",
            Title = "Peaceful Animals",
            Description = "Toggles peaceful animals.",
            States = {
                new GameOptionStateInfo{
                    Title = "On",
                    Description = "On",
                    Value = "True"
                },
                new GameOptionStateInfo{
                    Title = "Off",
                    Description = "Off",
                    Value = "False"
                },
            }
        };

       
            GameOptionHelper.Initialize(
                AllowPeacefulAnimals
            );

Anywhere in your mod's code, you can check the value of your new option:
Code:
if(GameOptionHelper.CheckGameOption(AllowPeacefulAnimals, "True")){
 //Do something only if the option is set
}

Here are some existing option groups that you can use to add your option to the place of your choice:
Code:
"GameOptionGroup_LobbyWorldOptions"
"GameOptionGroup_LobbyPaceOptions"
"GameOptionGroup_LobbyDifficultyOptions"

Example:
For a full example, you can take a look at the source for the Minor Faction Config Mod:

Minor Faction Config Source
Reply


Messages In This Thread
Adding In-game options for your Humankind DLL Mod - by _A_ - 10-02-2021, 11:00 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)