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 throughRelayAutoandDoorChangelevel.) Get(key)istrueonly when the flag exists and is on. A flag that was never set readsfalse, 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’sGlobalState.SetStatewith thedataalready stored under the key, ornullfor a new key.)Setreturnstruewhen the flag now has the value you asked for. It returnsfalsewhen 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. UseGeton 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.Changedfires whenever a flag really changes value, whoever changed it. That includesSetcalls from any mod and the game’s own scene logic. A flag that goes from missing to off does not fire, becauseGetsaysfalseboth before and after. A change only to the extra data does not fire either. (It fires for every game call toSetStatethat changes the valueGetreports. The game’s own callers areDoorChangelevelat save time andRelayAutoat scene start.)Changedis 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 onSavingDone, after the save is written. This happens the first time each synced door key appears, becauseDoorChangelevelwrites its key inOnSavingGame.)Changeddoes 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.OnLoadingGamerestores the dictionary without callingSetState. OnlyRelayAuto(inLateStart) 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 raisesChangedagain, and so on. CompareActivewith the value you want before you callSetfrom a handler. - Flag names are case sensitive.
MyFlagandmyflagare different flags. IsAvailabletells you whetherChangedworks. It isfalsewhen the framework could not set up the event, or when its feature group was turned off.Get,SetandAllkeep working when it isfalse.
Limits
- Changes nested deeper than four levels are dropped without an error.
Changedcan’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 watchesGlobalState.SetState. A direct write toGlobalState.stateis 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,
GetreturnsfalseandAll()returns an empty list. (There is no flag store yet (GlobalState.instanceis 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
nullflag name throws anArgumentException.GetandSetboth 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.
SetreturnsfalseandAll()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
Changedstops.GlobalFlags.IsAvailablebecomesfalseand error lines and a warning go to the log.Get,SetandAll()keep working. (The framework changes the threeGlobalState.SetStateoverloads together. If any of them is missing, all of them are removed.)
Save loads
Changedis 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 checkSaveData.Busy, which istruewhile a save is loading or being written.