Gears

Get Started with C#

Reference GearsAPI.dll, implement IGearsModApi, learn when each callback fires, and reach your settings without a callback.

Follow this page to connect a C# mod to Gears. You reference one assembly, implement one interface, and Gears hands you your settings when they are ready.

Two terms appear throughout. A global setting belongs to the player and applies in every world. A world setting belongs to one world and is chosen by the host.

Reference GearsAPI.dll

Gears ships GearsAPI.dll inside its own mod folder. Add a reference to that file from your mod project. Whether you also ship a copy of it depends on whether Gears is optional for your players:

Your modCopy LocalResult
Gears is requiredfalseGears loads the assembly, and your mod folder carries no second copy.
Gears is optionaltrueGearsAPI.dll sits in your own mod folder, so the types still resolve for a player who does not have Gears installed.

Optional means shipping the DLL

To keep your mod working for players without Gears, GearsAPI.dll must be present in your mod's folder. Nothing else provides the assembly when Gears is absent, and the runtime needs it to be loadable the moment it compiles any method that mentions a GearsAPI type.

If you ship your own copy, ship the GearsAPI.dll from the current Gears release. A player who has Gears installed then has two copies, and they must be the same version, or you get the mismatch described below.

When your mod is built against one GearsAPI version and the player runs a different one, the mismatch can surface as a TypeLoadException inside your callback. When Gears can tell that the two versions differ, it catches the exception and logs:

[Gears] [GearsMod] '<Your Mod>' is using an outdated GearsAPI: it was built against v2.0.1.0 but Gears is running v3.0.0.0, so '<type>' could not be loaded. The mod needs rebuilding against the current GearsAPI.

A member that no longer exists surfaces as a MissingMethodException or MissingFieldException instead, and Gears logs it as an ordinary callback error. Rebuild against the current GearsAPI.dll when you see either.

Keep Gears optional

Nothing in Gears requires your mod to depend on it. Gears finds your IGearsModApi class by scanning, so when Gears is absent, nothing scans and nothing is called.

Two things keep Gears optional for your players:

  • Ship GearsAPI.dll in your mod's folder. A mod that uses the C# half of Gears needs the assembly present to load at all. With your own copy, the types still resolve for a player who does not have Gears installed, and the game loads your mod without missing-type errors.
  • Declare your settings in XML and read them in XML. ModSettings.xml and the modsetting() function need no C# and no assembly reference. A player without Gears sees no settings page, and the patches that read a setting do not apply. See Read Settings in XML Patches.

Implement IGearsModApi

A C# mod talks to Gears through one interface in GearsAPI.Settings, IGearsModApi. It has three callbacks: InitMod, OnGlobalSettingsLoaded and OnWorldSettingsLoaded. They fire in the order described in Startup order.

Gears finds implementations by scanning every assembly of your mod for concrete classes that implement the interface. To be found, your class must meet these rules:

  • It must be concrete. Gears skips abstract classes, interfaces and open generic types even when they derive from IGearsModApi.
  • It must have a public parameterless constructor. Without one, Gears skips it and logs a warning naming the type.

Gears instantiates every matching class and invokes every callback on every instance. One class is the normal case, and several are allowed.

A constructor that throws is logged, and Gears skips that class. An assembly whose types cannot be loaded is logged and skipped as well. Any other error while scanning is logged and stops the scan of your mod's remaining assemblies.

Startup order

Gears calls your callbacks in this order:

The game loads mods: Vanilla calls each mod's own IModApi.InitMod. Gears is not involved yet. GearsSettingsManager.GetMods() returns an empty list here, and GetGearsMod(...) returns null.

GameAwake: Gears does the following, in order:

Scans all loaded assemblies for [SettingsSerializationProvider] classes. See Use Custom Value Types.

Builds one IGearsMod per loaded mod and reads <Icon> and <Banner> from ModInfo.xml.

Finds and instantiates every IGearsModApi, for every mod.

Parses every mod's ModSettings.xml into the settings tree.

Calls InitMod(IGearsMod) on each one. Everything ModSettings.xml declared already exists, so this is where to create any further settings from code, and where to call BindSettingsClass — for global and world settings alike. See Use Global Settings in C# and Bind Settings with Attributes.

Restores each saved global value into the global settings. This raises no OnValueChanged, so listeners bound in the previous step stay quiet through it.

Calls OnGlobalSettingsLoaded(IModGlobalSettings) on each one. Global values are final from here on, until the player changes them — call SyncSettingsToClass here to hand them to your listeners.

World Start OnWorldSettingsLoaded(IModWorldSettings) fires in one of three ways:

  • Hosting or single player: when the world starts, after the save's WorldModSettings.xml has been loaded. A new world starts with every world setting at its default, unless the player changed them with Mods World Settings on the New Game screen's Mods tab. Then it starts with the values last used on that screen.
  • Joining a server: when the server's world settings arrive with the other config files.
  • Joining a server that sent none: Gears resets every world setting to its default, then fires the callback.

OnWorldSettingsLoaded can fire more than once per session, for example when the player leaves one world and joins another. Gears skips it for a mod that has no world settings.

The world does not exist yet

OnWorldSettingsLoaded runs while the game is still starting or joining the world, before the world is loaded. Read your values there, but do not reach for GameManager.Instance.World, players or entities. Use the values later, from your own game hooks.

When one of your callbacks throws, Gears logs Error thrown in <Callback>() by <YourType> for <Your Mod> with the exception and carries on. Your other IGearsModApi classes, and every other mod, still get the callback.

Reach a setting without a callback

GearsSettingsManager has two static methods, GetMods() and GetGearsMod(string). The mod name is the <Name> from ModInfo.xml, and it may be another mod's name.

Both return an empty list or null before GameAwake, and GetGearsMod returns null for a name it does not know, so check the result:

IGearsMod mine = GearsSettingsManager.GetGearsMod("MyMod");
ISliderGlobalSetting<float> hudScale = mine?.GlobalSettings
    .GetTab("General")?.GetCategory("Display")?
    .GetSetting<ISliderGlobalSetting<float>>("HudScale");

What IGearsMod gives you

IGearsMod is what Gears knows about one mod: the game's Mod object, the GlobalSettings and WorldSettings trees, which are never null, and the IconPath and BannerPath read from ModInfo.xml.

Alongside those sit the Has… methods, which tell you whether the mod has settings of each kind, and SaveSettings(), which writes the saved global settings file on demand.

A complete minimal implementation

using GearsAPI.Attributes;
using GearsAPI.Settings;
using GearsAPI.Settings.Global;
using GearsAPI.Settings.World;

public class MyModGears : IGearsModApi
{
    // The rest of your mod reads these two properties.
    public static float HudScale { get; private set; } = 1f;
    public static bool BiggerHordes { get; private set; }

    // Bind here: ModSettings.xml has been read, so every setting exists, and this runs once.
    public void InitMod(IGearsMod modInstance)
    {
        modInstance.GlobalSettings.BindSettingsClass(typeof(GlobalBindings));
        modInstance.WorldSettings.BindSettingsClass(typeof(WorldBindings));
    }

    // Read values here, where they are final.
    public void OnGlobalSettingsLoaded(IModGlobalSettings modSettings)
    {
        HudScale = GlobalBindings.HudScale.SettingValue;
    }

    public void OnWorldSettingsLoaded(IModWorldSettings worldSettings)
    {
        BiggerHordes = WorldBindings.BiggerHordes.SettingValue;
    }

    // Global and world members need separate types. Each call resolves every path on the type it is
    // given, and the two kinds of path have different formats.
    private static class GlobalBindings
    {
        // [Setting] finds the setting for you and assigns it to the field.
        [Setting("General.Display.HudScale")]
        public static ISliderGlobalSetting<float> HudScale;

        [SettingOnValueChanged("General.Display.HudScale")]
        private static void HudScaleChanged(IValueModSetting<float> setting, float newValue)
        {
            MyModGears.HudScale = newValue;
        }
    }

    private static class WorldBindings
    {
        [Setting("Zombies.BiggerHordes")]
        public static ISwitchWorldSetting<bool> BiggerHordes;
    }
}

That example assumes a ModSettings.xml that declares a HudScale slider in the Display category of the General tab, and a BiggerHordes switch in the Zombies world category. See Getting Started for that file.

It reads each SettingValue in the loaded callbacks, which is the shortest way to start. To let the listeners do that work instead, write them with includeInSync: true and call SyncSettingsToClass in each loaded callback. See Sync settings to a class.

Where to go next

On this page