Global flags

What it is

A global flag is a named switch that is either on (true) or off (false). The game keeps a list of them and saves it with your save game. Scenes use flags to remember things, for example “this scene has already been set up”, and doors between levels store whether they are locked there. Flags are separate from quests, which the game tracks in its own system. A mod can read a flag, set one, list them all, and be told when one changes.

There is no fixed list of flag names. The game picks them per scene, so a mod looks for the names it cares about.

When to use it

  • Run code when the game switches a particular flag.
  • Read a flag to find out whether something has already happened in this save.
  • Set your own flag, so your mod can remember a yes or no answer inside the save game.
  • List every flag in the current save to see which ones the game uses.

Quick example

This mod logs every flag change. Once a save is loaded, it logs how many flags exist and sets its own flag the first time.

using ObenseuerCore;

[ModInfo("example.flags", "Flag Watcher", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class FlagWatcher : FrameworkMod
{
    private const string MyFlag = "example.flags.met";

    protected override void OnLoad()
    {
        GlobalFlags.Changed += args => Log.Info("flag " + args.Key + " is now " + args.Active);
        Events.WhenReady(OnReady);
    }

    private void OnReady()
    {
        Log.Info("flags in this save: " + GlobalFlags.All().Count);
        if (GlobalFlags.Get(MyFlag)) return;
        GlobalFlags.Set(MyFlag, true);
        Log.Info("set " + MyFlag + " for the first time");
    }
}

Changed is an event: the framework calls your code each time it happens. See Using events for more on events and WhenReady.

Common tasks

Check a flag. A missing flag reads as false, the same as one that is switched off:

bool done = GlobalFlags.Get("example.flags.met");

Switch a flag off again. Set returns false when no save is loaded or while the game is saving, so check the result when it matters:

if (!GlobalFlags.Set("example.flags.met", false)) Log.Warn("could not clear the flag");

List every flag the game has stored, switched on or off:

foreach (string key in GlobalFlags.All())
    Log.Info(key + " = " + GlobalFlags.Get(key));

Start your own flag names with your mod id, so they cannot clash with the game’s flags or another mod’s.

Reference

This is the full list of what the API gives you. Text in brackets at the end of a note is extra detail you can skip on a first read.

GlobalFlags

GlobalFlags reads and writes the game’s flags and tells your mod when one changes.

GlobalFlags read, write and watch flags

This is the class you start from. Get and Set read and switch a flag by name, All() lists the names, and Changed tells you when a flag changes.

public static class GlobalFlags
{
    public static bool Get(string key);
    public static bool Set(string key, bool active);
    public static List<string> All();

    public static event Action<GlobalFlagChangedArgs> Changed;

    public static bool IsAvailable { get; }
}
Method Returns What it does
Get(key) bool true when the flag exists and is on.
Set(key, active) bool Switches a flag on or off. Returns true when it now has that value.
All() List<string> The names of every flag, on or off.
Property Type What it means
IsAvailable bool false when the change event could not be set up.
Event Gives you When it fires
Changed GlobalFlagChangedArgs A flag has just been switched on or off.

GlobalFlagChangedArgs what Changed gives you

Changed gives you this. The framework builds it when a flag’s on or off value really changes, so it is the framework’s own type.

public sealed class GlobalFlagChangedArgs
{
    public string Key { get; }
    public bool Active { get; }
}
Property Type What it means
Key string The flag’s name.
Active bool true when the flag is now on.

Things to know

  • A flag has a name and an on or off value, and the game saves them with the save game. There is no fixed list of names, because the game picks them for each scene. (The flags are the game’s string-keyed store, GlobalState.state. The game’s scene logic reads it through RelayAuto and DoorChangelevel.)
  • Get(key) is true only when the flag exists and is on. A flag that was never set reads false, the same as one switched off.
  • Set(key, active) switches the flag, and leaves any extra data the game stored under the same name alone. Doors use that extra data to remember whether they are locked, so a plain switch would have wiped it. A flag that is new gets no extra data. (It calls the game’s GlobalState.SetState with the data already stored under the key, or null for a new key.)
  • Set returns true when the flag now has the value you asked for. It returns false when no save is loaded, and while a save is loading or being written.
  • All() lists every flag name, switched on or off. It also lists names that hold data for other parts of the game, such as the door keys. Use Get on each name to see which ones are on.
  • All() copies the list of names every time you call it. That is fine now and then, but do not call it every frame.
  • Changed fires whenever a flag really changes value, whoever changed it. That includes Set calls from any mod and the game’s own scene logic. A flag that goes from missing to off does not fire, because Get says false both before and after. A change only to the extra data does not fire either. (It fires for every game call to SetState that changes the value Get reports. The game’s own callers are DoorChangelevel at save time and RelayAuto at scene start.)
  • Changed is held back while a save is being written. The game switches some flags during the save itself, for example when a door writes its lock state the first time. The framework waits until the save has finished and then raises the event. (It is held and fired on SavingDone, after the save is written. This happens the first time each synced door key appears, because DoorChangelevel writes its key in OnSavingGame.)
  • Changed does not fire when a save loads. Loading puts all the saved flags back at once, without telling anyone. After a load the event can still fire for flags the scene sets up when it starts. (GlobalState.OnLoadingGame restores the dictionary without calling SetState. Only RelayAuto (in LateStart) can fire after a load, when it writes its start state.)
  • A handler that changes a flag can start a chain of changes. If your handler calls Set, that raises Changed again, and so on. Compare Active with the value you want before you call Set from a handler.
  • Flag names are case sensitive. MyFlag and myflag are different flags.
  • IsAvailable tells you whether Changed works. It is false when the framework could not set up the event, or when its feature group was turned off. Get, Set and All keep working when it is false.

Limits

  • Changes nested deeper than four levels are dropped without an error.
  • Changed can’t see a flag written some other way. If another mod writes straight into the game’s list, instead of going through the game’s switch method, no event fires. (The framework only watches GlobalState.SetState. A direct write to GlobalState.state is not reported.)

Details

Target: Obenseuer 0.5.08 (buildid 25969064), Unity 6000.0.77f1 Mono, BepInEx 6 Unity.Mono (be.788, the tested build) or BepInEx 5 (5.4.23 or later). Namespace: ObenseuerCore.

Everything not listed in this reference is internal and may change without notice.

The framework changes some of the game’s methods when it starts, so it can tell your mod when a flag changes. This page calls that a feature group: a named set of the framework’s changes for one feature. Its feature group is obenseuer.moddingframework.globalflags, the name you will see in the log.

When you can call it

  • Call everything from your mod’s normal code (OnLoad, event handlers, Update), not from a background thread you started. The game and Unity only run safely on the main thread (see the glossary).
  • It is safe to call at any time, even on the main menu. With no save loaded, Get returns false and All() returns an empty list. (There is no flag store yet (GlobalState.instance is missing).)
  • Your event handlers run inside the game’s own call, one at a time. If a handler throws an error, the framework logs it and still runs the other handlers.

What to check for and what comes back

  • An empty or null flag name throws an ArgumentException. Get and Set both check the name. Nothing else throws an error to your code.
  • If the game itself fails while the framework works, you get a plain answer instead of an error. Set returns false and All() returns an empty list, and the first failure of each member goes to the log.

If something fails to set up

The framework changes some of the game’s methods when it starts. If a game update breaks one of them, the framework turns off the feature group that depends on it, and writes error lines and a warning to the group’s log file in BepInEx\logs. The rest of the framework keeps working.

  • If the change to the game’s flag switch cannot be made, only Changed stops. GlobalFlags.IsAvailable becomes false and error lines and a warning go to the log. Get, Set and All() keep working. (The framework changes the three GlobalState.SetState overloads together. If any of them is missing, all of them are removed.)

Save loads

  • Changed is held back while a save is being written, and not raised by the load itself. Both are explained in the reference above. A handler that should ignore events raised while a save loads can check SaveData.Busy, which is true while a save is loading or being written.