A Remix settings interface binds typed configuration values, registers them under a mod ID, and builds controls when the Remix menu opens the mod’s page. OptionInterface, Configurable<T>, ConfigurableBase, ConfigurableInfo, and MachineConnector are global types. Controls such as Menu.Remix.MixedUI.OpTab, Menu.Remix.MixedUI.OpCheckBox, and Menu.Remix.MixedUI.OpSlider belong to Menu.Remix.MixedUI.

The compiled RemixSettings example defines a RemixSettings plugin with a nested Settings : OptionInterface. It exposes a showHint Boolean and a hintOpacity integer limited to 0 through 100. These are configuration controls only. The example does not create or display a HUD hint.

Register during mod initialization

Subscribe to On.RainWorld.OnModsInit from the plugin lifecycle described in First plugin. Call orig(self) once before registration, then reuse the same options object when Rain World repeats mod initialization:

orig(self);
if (options == null)
    options = new Settings();
if (!ReferenceEquals(MachineConnector.GetRegisteredOI(ModId), options) &&
    !MachineConnector.SetRegisteredOI(ModId, options))
    Logger.LogWarning("The mod ID was not registered with Remix.");

global::MachineConnector.SetRegisteredOI(System.String,global::OptionInterface) replaces an existing registered interface and transfers the global::ModManager.Mod reference owned by the game into it. It returns false when the supplied ID is absent from Remix’s registered mod entries. Use the exact ID from modinfo.json. The reference check keeps a repeated initialization pass from replacing the interface with the same object. See Hook lifecycle for the surrounding initialization order.

Bind values in the OptionInterface constructor. Do not bind them in Initialize(), which may run again whenever the menu rebuilds the page. The public example uses:

showHint = config.Bind("showHint", true,
    new ConfigurableInfo("Example toggle, without a HUD implementation."));
hintOpacity = config.Bind("hintOpacity", 70,
    new ConfigurableInfo("Example opacity value.",
        new ConfigAcceptableRange<int>(0, 100)));

Binding keys accept letters, digits, and underscores. An empty key becomes _, duplicate keys throw, and a key beginning with _ is cosmetic. Cosmetic values are excluded from normal persisted settings. Configurable<T>.Value passes assignments through its acceptable range and invokes OnChange when the resulting value changes.

Build the options page

Override Initialize(), call base.Initialize(), replace Tabs, and add each control through OpTab.AddItems:

public override void Initialize()
{
    base.Initialize();
    Tabs = new[] { new OpTab(this, "Settings") };
    Tabs[0].AddItems(
        new OpCheckBox(showHint, new Vector2(30f, 460f)),
        new OpSlider(hintOpacity, new Vector2(30f, 400f), 200));
}

The example also adds labels. Translate shipped label keys at this point as described in Localized text. Each configurable can be bound to only one Menu.Remix.MixedUI.UIconfig control at a time. Creating a second bound control for the same value throws MultiuseConfigurableException.

The menu initializes the interface lazily. It calls Initialize(), loads the configuration file, then copies persisted values into the controls. This is why fields must be bound before the menu loads and controls must be recreated in Initialize().

When values move

Editing a control changes UIconfig.value and raises its value events. That edit does not write the configuration file. The visible Save button sends the APPLY signal and starts the persisted value flow:

flowchart TD
    A["Edit the control value"] --> B["Press Save"]
    B --> C["Copy values into the configurables"]
    C --> D["Write the mod configuration file"]

During that save route, bound non-cosmetic control values are copied into their configurables, OnConfigChanged is invoked, and ConfigHolder.Save() writes sorted key = value lines. A setting with no attached control keeps its current value unless a reset is pending. Reset supplies defaults to bound controls and marks the pending reset before a later save.

ConfigHolder.Reload() accepts # comments and retains unknown pairs. Retention helps a temporarily absent setting survive, but it does not migrate a renamed key. ConfigHolder.SaveConfig(string) and LoadConfig() instead use an opaque configs_<mod id> entry in Rain World’s options file. Neither storage route is campaign save data.

The example compiled against the recorded baseline with examples/build.ps1, .NET SDK 10.0.400, and the Unity input legacy reference included by the build. It has not been opened in the Remix menu. Registration across enable cycles, rebuilding the page, Save and Reset behavior, and file persistence still need a game test. A future overlay that reads showHint or hintOpacity should use the ownership rules in HUD parts rather than treating these controls as an overlay implementation.

Sources

  • global::RainWorld.OnModsInit(), global::MachineConnector, global::OptionInterface, global::OptionInterface.ConfigHolder, global::Configurable<T>, global::ConfigurableBase, global::ConfigAcceptableRange<System.Int32>, and global::Menu.Remix.MixedUI.UIconfig in Assembly-CSharp.dll, SHA-256 B6BE1D4E18CE219D21091B51564CB6A11C1E4106B41DE903EB8E58849CB16FDB. See Runtime reference.
  • Compiled RemixSettings example