Upgrade to GearsAPI 3
Convert a V1 ModSettings.xml to the V2 schema, move C# from GearsAPI 2.x to 3.0, and check the result in the game log.
Gears 8 loads only the V2 ModSettings.xml schema and ships GearsAPI 3.0.0. A mod written for the
old wiki, with a V1 ModSettings.xml or C# against GearsAPI 2.x, needs both halves brought forward.
Work through the parts that apply to your mod:
Part 1: Convert ModSettings.xml from V1 to V2
V2 moved almost everything off attributes and onto named child elements, made a setting's value type
explicit instead of guessing it from which property you used, and made defaultValue mandatory.
In short: add version="2", add type, give every value setting a defaultValue, turn
tooltipKey, <Property>, leftValue and rightValue, and wrap into child elements, rename
the Color defaultColor to defaultValue, and delete value and color.
Gears ignores V1 markup it does not recognize without logging it. A leftover <Property>,
leftValue, wrap or value loads cleanly and is simply dropped, so a clean log does not prove
the conversion is complete. See Part 3.
Mark the file as V2
<ModSettings> <!-- V1: rejected -->
<ModSettings version="2"> <!-- V2 -->The value must be exactly 2. A root with no version, which is every V1 file, is rejected with
an error from [ModSettingsFromXml], and so is any other value. Gears skips the whole file,
so check this first when a mod vanishes from the Mods menu.
Add type to every Selector, Slider and Switch
Gears drops a Selector, Slider or Switch with no type at load. The error goes to the log, and
nothing appears in the menu.
Work out the type from the V1 property you used:
| V1 gave the type away with | V2 type |
|---|---|
<Property type="incrementalValuesInt" ...> | int |
<Property type="incrementalValuesFloat" ...> | float |
<Property type="arrayValues" ...> | string |
leftValue and rightValue of true and false | bool |
leftValue and rightValue of anything else | string |
Gears accepts these aliases: int (Int32, integer), float (Single), string, bool
(boolean), and Color (Color32). Matching is case-insensitive, and whitespace is trimmed.
Binding takes no type. Color accepts one and ignores it. Sliders support int and float
only, and Gears drops a string or bool slider.
You can also name an enum your mod declares:
<Selector name="Difficulty" type="MyMod.Difficulty, MyMod" defaultValue="Normal" />The namespace-qualified name, the assembly-qualified name and the bare type name all resolve,
case-insensitively. An enum Selector seeds its allowed values with every declared member, so it
needs no <List>. An enum Switch takes the first two members as left and right unless you say
otherwise.
Give every value setting a defaultValue
This requirement is new in Gears 8. V1 tolerated a Selector, Slider, Switch or Color with no default, and V2 drops it:
[Gears] [XmlSettingsParserV2] Slider 'fov' has no defaultValue; defaultValue is required, skipping.Add a defaultValue to every setting that lacks one. Binding is exempt. An empty
defaultValue="" counts as missing.
Turn tooltipKey into a Description element
On a category and on every setting element, the tooltipKey attribute becomes a
<Description> child:
<!-- V1 -->
<Category name="Display" displayKey="myModCatDisplay" tooltipKey="myModCatDisplayDesc">
<!-- V2 -->
<Category name="Display" displayKey="myModCatDisplay">
<Description key="myModCatDisplayDesc" />If the old tooltipKey was empty, drop it rather than writing an empty key.
A <Tab> is the exception. A tab is not a setting, and it keeps its tooltipKey attribute:
<Tab name="General" displayKey="myModTabGeneral" tooltipKey="myModTabGeneralTip"> <!-- unchanged in V2 -->V2 also gives settings and categories a short <Caption>, which V1 had no equivalent for:
<Caption key="myModSettingCaption" />Turn the V1 properties into elements
A V1 <Property type="x" ... /> becomes an element named after what that type said:
| V1 | V2 |
|---|---|
<Property type="incrementalValuesInt" increment minValue maxValue /> | <Incremental increment minValue maxValue /> |
<Property type="incrementalValuesFloat" increment minValue maxValue /> | <Incremental increment minValue maxValue /> |
<Property type="arrayValues" allowedValues="a,b,c" /> | <List allowedValues="a,b,c" /> |
<Property type="formatter" formatString="0.0" /> | <Formatter ui="0.0" /> |
Both incremental variants collapse into one <Incremental>, because the int and float distinction
now lives in type. On a Selector, increment must be greater than 0 and minValue no greater
than maxValue, or Gears logs an error and ignores the range. A Slider does not check its range,
so check those values yourself.
<Formatter> gained a second half. ui is what the player sees, and serialize is what Gears
writes to the saved settings file and returns from modsetting(). You may leave out either one:
<Formatter ui="0.0" serialize="0.00" />V1 had only the first, so formatString always becomes ui. Two things V1 did not make clear:
<Formatter> is legal on Selector and Slider only and is ignored on Switch, Color and Binding; and
when a Selector has both <List> and <Incremental>, Gears applies the range last, so it wins.
Move the Switch values onto a Buttons element
<!-- V1 -->
<Switch name="ShowHints" displayKey="myModShowHints" defaultValue="Off" leftValue="Off" rightValue="On" />
<!-- V2 -->
<Switch name="ShowHints" displayKey="myModShowHints" type="string" defaultValue="Off">
<Buttons left="Off" right="On" />
</Switch>Both sides are required. When only one of left and right is given, Gears applies neither. A
bool switch does not need the element: it is false on the left and true on the right.
Rename the Color default
<Color name="Accent" defaultColor="255,0,0" /> <!-- V1 -->
<Color name="Accent" defaultValue="255,0,0" /> <!-- V2 -->defaultValue is spelled the same way on every setting type, and the R,G,B notation is unchanged.
The V1 color attribute, which held the current value, goes away entirely. See the next step.
Delete the value attribute
V1 wrote the player's current value back into the mod's own ModSettings.xml:
<Selector name="Quality" value="3" defaultValue="5" /> <!-- V1 -->
<Selector name="Quality" type="int" defaultValue="5" /> <!-- V2 -->V2 has no value attribute. ModSettings.xml describes only what a setting is. The player's value
lives in the saved settings file under their user data folder, and Gears restores it after load. A
setting with no saved value starts at defaultValue.
Move wrap onto an element
<Selector name="DayOfWeek" wrap="true" /> <!-- V1 attribute -->
<Selector name="DayOfWeek" type="string" defaultValue="Monday"> <!-- V2 element -->
<Wrap wrap="true" />
</Selector>Gears ignores anything that is not true or false, and wrapping stays off. <Wrap> is
Selector-only.
Optional: what V2 adds
None of this is required to convert a V1 file.
Preview images. A <Preview> block with an <Image> that has no value is the setting's own
image. An image that carries a value is shown while that value is selected.
<Selector name="Quality" type="int" defaultValue="1">
<List allowedValues="1,2" />
<Preview>
<Image value="1" path="@modfolder(MyMod)://Textures/low.png" />
<Image value="2" path="@modfolder(MyMod)://Textures/high.png" />
</Preview>
</Selector>Binding, Category and Color take only the value-less form. Gears skips a valued <Image> on a
Color with a warning, and ignores one on a Binding or Category.
Restart scope. Tells the player what a change costs: None, Reload for a world reload, or
Restart for a game restart, in any case.
<RestartScope scope="Restart" />Global settings only. Gears ignores it silently on anything under <World> and on a category, so a
leftover one does no harm.
Localization prefix. Gears prepends this to a Selector's or Switch's value when it looks the
value up for display, with no separator, so metric renders through the key myModUnitsmetric.
<LocalizationPrefix prefix="myModUnits" />Percentages everywhere. V1 already accepted a % suffix on a float's increment, minValue
and maxValue. V2 accepts it on every float value, including defaultValue, <List> entries and
preview values, and stores it as a fraction, so 50% becomes 0.5. It is a different thing from
<Formatter ui="0%" />, which only changes how the stored number is displayed.
Part 2: Move C# from GearsAPI 2.x to 3.0
GearsAPI 2.x stored every value as a string. GearsAPI 3.0 is typed: each setting interface takes
its value type as a generic parameter, and the single CurrentValue became three typed values.
Compare the old and new code
Here is the most common pattern, reading a value and reacting to a change:
using GearsAPI.Settings.Global;
public void OnGlobalSettingsLoaded(IModGlobalSettings modSettings)
{
ISliderGlobalSetting fov = modSettings.GetTab("General")
.GetCategory("Display")
.GetSetting<ISliderGlobalSetting>("Fov");
MyMod.Fov = int.Parse(fov.CurrentValue);
fov.OnSettingChanged += Fov_OnSettingChanged;
}
private void Fov_OnSettingChanged(IGlobalModSetting setting, string newValue)
{
MyMod.Fov = int.Parse(newValue);
}Most 3.0 code needs using GearsAPI.Settings; beside GearsAPI.Settings.Global or
GearsAPI.Settings.World, because the typed IValueModSetting<T> lives there. The binding and
listener attributes need using GearsAPI.Attributes;.
Behavior changes to check for
GetSetting<T>needs the closed generic.GetSetting<ISliderGlobalSetting>("Fov")no longer compiles.GetSetting<ISliderGlobalSetting<int>>("Fov")returns null when the setting is a different type, so check for null.- Decide which value you want.
CurrentValuewas the applied value, which is nowSettingValue. If you read it during a menu callback to preview a change, you probably wantSelectedValuenow. OnSettingChangedis nowOnSettingApplied, and carries no value. ReadSettingValuefrom the setting argument, or switch toOnValueChanged, which carries it, typed.- Call
RefreshUI()after changing a value from code. AssigningSelectedValueorSettingValuedoes not redraw the row, and Apply does not commit it, until you callRefreshUI()on the setting. See Change a setting from code. - Categories now appear in setting lists. A category is a setting in 3.0, so a category's
GetAllSettings()returns the category itself first, followed by its settings. TheGetAllGlobalSettings(),GetAllWorldSettings()and tabGetAllModSettings()lists include every category too. A 2.x loop that treats each entry as a value setting now meets categories, so skip any entry that is anIGlobalModSettingsCategoryorIWorldModSettingsCategory. - World settings have no
OnValueChangedorOnSettingApplied. ReadSettingValueinOnWorldSettingsLoaded, which fires every time a world's values arrive. A[SettingOnValueChanged]on a world path does work, but only as an invoke-only listener thatSyncSettingsToClasscalls — it is never raised. Write it withincludeInSync: true. Without the flag it never runs, and Gears logs nothing. InitModnow runs afterModSettings.xmlis read, not before. Settings you create in code there land on top of whatever the XML declared, so where the two disagree your code now wins where the XML used to; andGetOrCreateSetting<T>throwsArgumentExceptionif the XML declared that setting as a different type. Every setting exists by then, soBindSettingsClasscan be called fromInitModfor global and world settings alike.- Loading values raises no change events. Restoring the player's saved global values, loading a
world's values, and resetting them to defaults are all silent. Use
SyncSettingsToClassto hand the loaded values to your listeners. See Loading a value in raises nothing. - Settings you create in code need all three values set.
DefaultValue,SettingValueandSelectedValueall start atdefault(T). TooltipKeyon a setting is nowDescriptionKey. On a tab it is stillTooltipKey.CreateTab,CreateCategoryandCreateSettingthrow on a duplicate name, which matters now that your XML may declare the same names. Use theGetOrCreate…form when both sides may create it.- A callback that throws no longer silences your other
IGearsModApiclasses, but Gears still logs it as an error naming the type. Look for[GearsMod] Error thrown in.
The member map
| GearsAPI 2.x | GearsAPI 3.0 |
|---|---|
ISelectorGlobalSetting, ISliderGlobalSetting, ISwitchGlobalSetting (non-generic) | ISelectorGlobalSetting<T>, ISliderGlobalSetting<T>, ISwitchGlobalSetting<T> |
ISelectorWorldSetting, ISliderWorldSetting, ISwitchWorldSetting | ISelectorWorldSetting<T>, ISliderWorldSetting<T>, ISwitchWorldSetting<T> |
IGlobalValueSetting.CurrentValue : string | IGlobalValueSetting<T>.SettingValue : T (applied), SelectedValue : T (the menu), DefaultValue : T |
IGlobalValueSetting.DefaultValue : string | DefaultValue : T |
IWorldModSetting.CurrentValue and DefaultValue : string | on the typed interfaces through IValueModSetting<T>; IWorldModSetting keeps only Category |
| string in, string out everywhere | IValueModSettingBase.SetSettingValueFromString(string) and GetSerializedFormattedSettingValue() for code that must stay stringly typed |
OnSettingChanged, an OnSettingChangedEvent(IGlobalModSetting setting, string newValue) | OnSettingApplied, an OnSettingAppliedEvent(IGlobalModSetting setting), with no value argument |
| — | ValueChangedEvent<T>(IValueModSetting<T> setting, T newValue) through OnValueChanged, global only |
| — | OnSelectedChangedEvent<T>(IValueModSetting<T> setting, T newValue) through OnSelectedChanged |
ISelectorGlobalSetting.SetAllowedValues(string), a comma list | gone; split it yourself and call SetAllowedValues(T[]) |
SetAllowedValues(string[]) | SetAllowedValues(T[]) |
SetAllowedValues(int inc, int min, int max) and (float, float, float) | SetAllowedValues(T increment, T min, T max) |
GetAllowedValues() : string[] | GetAllowedValues() : T[], plus GetAllowedValuesDisplay() : string[] |
FormatterString on Selector and Slider | UiFormatter and SerializationFormatter |
| — | LocalizationPrefix and SelectByIndex(int) on Selector |
ISliderGlobalSetting.GetAllowedValues() | gone; use Increment, Min and Max |
ISwitchGlobalSetting.LeftValue and RightValue : string, SetSwitchValues(string, string) | LeftValue and RightValue : T, SetSwitchValues(T, T), plus GetLeftText(), GetRightText(), SelectButton and GetSelectedButton |
IColorSelectorGlobalSetting, string-based | IColorSelectorGlobalSetting : IGlobalValueSetting<UnityEngine.Color> |
IModSetting.TooltipKey, HasToolTip(), GetToolTipText() | DescriptionKey, HasDescription(), GetDescriptionText(), plus the new CaptionKey, HasCaption() and GetCaptionText(). TooltipKey survives only on IGlobalModSettingsTab. |
IGlobalModSettingsCategory : IModSetting | IGlobalModSettingsCategory : IGlobalModSetting |
IWorldModSettingsCategory : IModSetting | IWorldModSettingsCategory : IWorldModSetting |
| — | IModSetting.IsChanged, IsDefault, ResetToDefault(), DiscardCurrentChange(), ApplyCurrentChange(), RefreshUI(), the OnEnabled event, HasPreviews(), GetCurrentPreview(), AddPreview(string) |
| — | IValueModSetting<T>.GetPreview(T) and AddPreview(T, string) for per-value images |
| — | IGlobalModSetting.RestartScope : SettingRestartScope |
| — | BindSettingsClass(Type) and SyncSettingsToClass(Type) on both IModGlobalSettings and IModWorldSettings, the [Setting] and [SettingPlayerAction] binding attributes, and the [SettingOnValueChanged], [SettingOnSelectedChanged], [SettingOnApplied] and [SettingOnEnabled] attributes |
| — | [SettingsSerializationProvider], [SettingParser] and [SettingFormatter] |
| Unchanged | IGearsModApi, IGearsMod, GearsSettingsManager.GetMods() and GetGearsMod(name), all Get*, Create*, GetOrCreate*, Add* and Remove* on tabs and categories, SaveSettings(), IControlBindingSetting.PlayerAction and ClearBinding(), and IModSetting.Enabled |
Part 3: Check your work
Start the game and search the log for these lines. Each one names your mod, a setting, a type or an assembly:
| Log line | What it means |
|---|---|
[Gears] [ModSettingsFromXml] | The whole file was rejected. Almost always the version="2" is missing or wrong. |
[Gears] [XmlSettingsParserV2] | That setting was dropped, most often for a missing defaultValue (Step 3) or a missing or unknown type (Step 2). |
[Gears] [GearsMod] '<Mod>' is using an outdated GearsAPI | The C# still targets 2.x and reached a type that no longer exists. Rebuild against the shipped GearsAPI.dll. |
[Gears] [GearsMod] Error thrown in <Callback>() by <Type>, with a MissingMethodException | The code calls a 2.x member that was removed from a type that still exists, such as TooltipKey or OnSettingChanged. |
[Gears] Failed iterating types in assembly | A type in your assembly could not load, often because a field still uses a removed 2.x type. Gears found no IGearsModApi there, so none of its callbacks ran. |
The log does not catch everything. Gears ignores leftover V1 markup without a word, and drops a
Color or Binding under <World> the same way. Search your ModSettings.xml for <Property,
leftValue, rightValue, wrap=, a value or color attribute on a setting, and
tooltipKey on anything but a <Tab>.
Then open the Mods menu and confirm every setting you expect is there, with the text you expect. A
raw key such as myModFov means a missing Localization.csv row.
See Troubleshooting for every log line and its fix.
Use Custom Value Types
The value types Gears handles without registration, how to use your own enums, how to register a parser and formatter for another type, and the limit on which types can back a setting.
API Reference
Every public type in GearsAPI.dll 3.0.0, one page per type, grouped by namespace.