Gears

Use Global Settings in C#

Navigate tabs and categories, create settings from code, read the three values each setting holds, and subscribe to the events that fire when a value changes.

A global setting is a mod setting that belongs to the player and applies in every world. Use this page to read one from C#, create one from code, and react when the player changes it.

Reach global settings through IModGlobalSettings, which Gears hands to OnGlobalSettingsLoaded and which is also available as IGearsMod.GlobalSettings. The types live in GearsAPI.Settings, GearsAPI.Settings.Global and GearsAPI.Settings.Base.

Find a setting in the tree

Global settings form three levels:

IModGlobalSettingsPassed to OnGlobalSettingsLoaded, and available as IGearsMod.GlobalSettings.

Every level offers the same three lookups. Get… returns null when the name is unknown, Create… throws when the name is already taken, and GetOrCreate… does whichever is needed.

A tab also has AddCategory and RemoveCategory, and a category has AddSetting and RemoveSetting. Add… moves a category or setting that Gears created, such as one you removed from somewhere else, and returns false for any object Gears did not create. There is no AddTab or RemoveTab. Every level can list its children. For the full member lists, see IModGlobalSettings, IGlobalModSettingsTab and IGlobalModSettingsCategory.

CreateTab throws ArgumentException when the name already exists. Use GetOrCreateTab when your XML may declare the same tab. The same applies to categories and settings.

A category is itself an IGlobalModSetting, which is how it gets a caption, description and preview image. Its RestartScope always reads None, and GetAllSettings() on a category returns the category first, followed by its settings.

Create a setting from code

CreateSetting<T> takes one of the closed setting interfaces:

InterfaceValue types that work
ISelectorGlobalSetting<T>int, float, string, any enum
ISliderGlobalSetting<T>int, float
ISwitchGlobalSetting<T>bool, string, any enum
IColorSelectorGlobalSettingfixed, UnityEngine.Color
IControlBindingSettingfixed, no value

Create settings in InitMod, which runs once ModSettings.xml has been read:

public void InitMod(IGearsMod mod)
{
    IGlobalModSettingsTab tab = mod.GlobalSettings.GetOrCreateTab("General", "myModTabGeneral");
    IGlobalModSettingsCategory display = tab.GetOrCreateCategory("Display", "myModCatDisplay");

    // "Fov" is the name you look the setting up by; "myModFov" is a key in your Localization.csv.
    ISliderGlobalSetting<int> fov = display.GetOrCreateSetting<ISliderGlobalSetting<int>>("Fov", "myModFov");
    fov.SetAllowedValues(increment: 5, min: 45, max: 120);
    fov.DefaultValue = 65;
    fov.SettingValue = 65;
    fov.SelectedValue = 65;
    fov.UiFormatter = "0";
    fov.DescriptionKey = "myModFovDesc";
    fov.RestartScope = SettingRestartScope.None;
}

Always set DefaultValue, SettingValue and SelectedValue on a setting you create. A fresh setting holds default(T) in all three, which is 0, false, null or black.

GetOrCreate… is the right shape here precisely because the XML has already been read. If ModSettings.xml declared the tab, category or setting, you get that object back and whatever you assign onto it overrides what the XML said; if it did not, you get a fresh one. The player's saved value is restored on top of either, after InitMod returns.

The display key you pass to GetOrCreate… applies only when Gears creates the object. For an object the XML already declared, the XML's display key stays, and DisplayKey is read-only.

Avoid declaring the same setting in both ModSettings.xml and code. One silently overrides the other, and it is easy to lose track of which. Pick one place per setting.

CreateSetting<T> throws in two cases:

  • ArgumentException when T is not one of the interfaces above, such as IGlobalModSetting itself.
  • InvalidOperationException when the value type has no implementation for that family, such as ISliderGlobalSetting<string> or ISelectorGlobalSetting<double>, or has no registered parser, such as ISelectorGlobalSetting<TimeSpan>. See Use Custom Value Types.

GetOrCreateSetting<T> throws ArgumentException on top of those, when a setting of that name already exists but is not a T:

Setting 'Fov' in category 'Display' is a ISliderGlobalSetting<Single>, not the requested
ISliderGlobalSetting<Int32>. A setting declared in both ModSettings.xml and code must use the same
type in both.

That happens when ModSettings.xml and your code disagree about a setting's type — an int slider in one and a float slider in the other, say. Since the XML is read before InitMod, the setting already exists by the time your code asks for it, so the two declarations have to agree.

Members every setting has

IModSetting is what every setting has, categories included:

  • Name and DisplayKey for identity.
  • CaptionKey and DescriptionKey for the description pane.
  • Enabled, which grays the row out when false, and the OnEnabled event that fires when it flips. See Disabling a setting for what else it changes.
  • The IsChanged and IsDefault flags.
  • Get…Text() helpers that do the localization lookups for you.
  • AddPreview(string path) for the setting's own image, and AddPreview(T value, string path) on IValueModSetting<T> for an image shown while one value is selected. These are the code equivalent of the <Preview> element. The last setting-wide image wins, and the first image registered for a given value wins.
  • RefreshUI(), which you call after changing a value from code. See Change a setting from code.

Three more methods drive the settings window, and they define the three-value model below:

MethodEffect
ResetToDefault()Sets the selected value to the default value. The applied value is untouched.
DiscardCurrentChange()Sets the selected value to the applied value.
ApplyCurrentChange()Sets the applied value to the selected value, raising OnValueChanged if the value changed. On a global setting it also raises OnSettingApplied, even when the value did not change.

ResetToDefault() moves only the selected value. To reset the value your mod acts on, call ApplyCurrentChange() after it.

IGlobalModSetting adds what only a player-level setting has: a RestartScope, the Category and Tab it belongs to, and the OnSettingApplied event that ApplyCurrentChange() raises.

The player sees only categories and settings that are in the settings tree. A category that was never added to a tab, or a setting that was never added to a category, has a null Tab and does not appear in the settings window. The same applies to one you removed with Remove….

Change a setting from code

Assigning SelectedValue or SettingValue changes the value and raises the matching event, but the settings window neither redraws the row nor learns that the setting moved. Call RefreshUI() on the setting afterwards and it does both: the row redraws, Apply commits the change along with whatever the player edited, and closing the window or switching to another mod discards it.

hudScale.SelectedValue = 1.25f;
hudScale.RefreshUI();          // without this the row still reads the old value

If your code changes SettingValue, set SelectedValue to the same value before you call RefreshUI(). Otherwise the row counts as changed, and Apply puts SettingValue back to the old selected value:

hudScale.SettingValue = 1.25f;
hudScale.SelectedValue = 1.25f;
hudScale.RefreshUI();

Call it after changing the allowed values too, since the row caches the list it draws. Changing Enabled is the exception: that refreshes the row on its own.

Disabling a setting

Set Enabled to false to gray a row out, which is how you make one setting depend on another. On a value setting it also does two things that are easy to miss:

  • Assigning SelectedValue still changes the value, but raises no OnSelectedChanged.
  • ApplyCurrentChange() does nothing at all. The applied value does not move, and neither OnValueChanged nor OnSettingApplied fires.

So a disabled setting keeps the applied value it had, and your listeners go quiet until you enable it again. World settings have the same gate. See Make One Setting Depend on Another for what that means in practice.

The three values a setting holds

Every value setting holds three values of its type T:

PropertyMeaningChanged by
DefaultValuewhat a reset goes back todefaultValue in XML, or your code
SettingValuethe applied value: saved to disk, returned by modsetting(), and the one your mod should act onthe player pressing Apply, the saved-settings restore, or your code
SelectedValuewhat the player currently has picked in the menu, which they may not have applied yetthe settings window, ResetToDefault, DiscardCurrentChange, or your code

All three live on IValueModSetting<T>, which also carries the per-value preview images and the OnSelectedChanged event. IGlobalValueSetting<T> adds OnValueChanged on top of that.

Read SettingValue when you want the value the player has committed to, and subscribe to OnValueChanged to react when it changes. OnSelectedChanged fires on every click in the menu, before the player has saved, so use it for live previews only.

Every setter is a no-op when the new value equals the old one, so these events fire only on a real change.

Read and write a setting as a string

Code that handles settings without knowing their value type goes through IValueModSettingBase. Its Set…FromString methods parse with the type's registered parser; when the text does not parse, Gears logs it and keeps the old value.

On the way out, GetUiFormattedSettingValue() returns the text the player sees, after the formatter, the prefix and localization. GetSerializedFormattedSettingValue() returns what Gears writes to disk and what modsetting() returns.

Selector

ISelectorGlobalSetting<T> sets the allowed values, either from an explicit T[] or from an increment, minimum and maximum. ISelectorSettingBase underneath it carries the presentation: UiFormatter, SerializationFormatter, LocalizationPrefix, Wrap and SelectByIndex.

Gears builds the display strings when they are first asked for, so a formatter or prefix you assign after the values still applies.

SetAllowedValues(increment, min, max) works only on an int or float selector. On a string or enum selector it logs that it needs a numeric type and does nothing. It also requires increment > 0 and min <= max. Otherwise it logs the problem and leaves the values alone. An enum selector starts with every declared member.

Slider

ISliderGlobalSetting<T> exposes the range as the read-only Increment, Min and Max, which you set together through SetAllowedValues. The formatting comes from ISliderSettingBase.

Only int and float sliders exist. A slider's SetAllowedValues does not check its arguments, so pass an increment greater than 0 and a minimum no larger than the maximum.

Switch

ISwitchGlobalSetting<T> holds the two sides as LeftValue and RightValue, which you set together through SetSwitchValues. ISwitchSettingBase underneath it has the LocalizationPrefix, the GetLeftText() and GetRightText() lookups, and SelectButton and GetSelectedButton for working in terms of sides rather than values.

A bool switch is false on the left and true on the right. An enum switch takes the first two declared members. A string switch has no sides until you call SetSwitchValues. Switches ignore the formatter string.

SelectedButton is nested inside ISwitchSettingBase, so write ISwitchSettingBase.SelectedButton.Left or add using static GearsAPI.Settings.Base.ISwitchSettingBase; to the file.

Color

IColorSelectorGlobalSetting adds no members of its own. Everything comes from IGlobalValueSetting<Color>, where Color is UnityEngine.Color.

Gears serializes the value as R,G,B in the range 0–255, and shows only the setting-wide preview image. Per-value previews are ignored.

Key binding

IControlBindingSetting is not a value setting. It has no SettingValue, and it adds only a PlayerAction property and a ClearBinding() method of its own.

Declaring a <Binding> in XML or creating one in code gives the player a row to rebind. The row works with the action's keyboard and mouse binding only. Controller bindings are not shown or changed by it. Attach the game PlayerAction it controls yourself:

// myPlayerActionSet is your own InControl.PlayerActionSet, and Boost is a PlayerAction on it.
IControlBindingSetting boost = category.GetSetting<IControlBindingSetting>("Boost");
if (boost != null)
{
    boost.PlayerAction = myPlayerActionSet.Boost;
}

You can also tag a static PlayerAction field or property with [SettingPlayerAction] and let BindSettingsClass assign it. See Attach a PlayerAction to a Binding.

Assigning PlayerAction records the action's current binding as the baseline the row compares against, so assign it once, after your action set exists. Until you do, the row reads - Missing PlayerAction -.

The methods IModSetting declares act on that PlayerAction rather than on a stored value:

MemberEffect on a Binding
ResetToDefault()Calls PlayerAction.ResetBindings(), putting the action back to the bindings it was declared with, controller bindings included.
DiscardCurrentChange()Restores the keyboard and mouse binding recorded when you assigned PlayerAction, or when the row was last applied.
ApplyCurrentChange()Raises OnSettingApplied and records the current binding as the new baseline.
ClearBinding()Removes the keyboard and mouse binding, which is what the row's clear button does.
IsChangedTrue while the action's binding differs from the baseline.
IsDefaultAlways false.

All of them do nothing to the binding while PlayerAction is null. ApplyCurrentChange() still raises OnSettingApplied then.

Gears does not store bindings in its saved settings file. The binding lives with the PlayerAction set it belongs to, and that set is responsible for saving it.

Restart scope

Set RestartScope to None, Reload or Restart on a global setting to tell the player that a change needs a world reload or a game restart. Gears only shows the warning. Applying the value is still your code's job.

Save the player's values

Gears writes the saved global settings file when the player presses Apply in the Mods window, and again at startup.

Call IModGlobalSettings.SaveSettings(), or IGearsMod.SaveSettings(), to write it at any other time, such as after your code has changed a SettingValue. The file is <user data folder>\Gears\ModSettings.xml, shared by every mod, and you never need to edit it by hand.

On this page