Janitors
This page uses GameObject from Unity. The glossary says what each one is.
What it is
A janitor in this game is not a cleaning character. It is an invisible script that the map designers place on an object in a level. It does one chore when the game triggers it by a tag, a word the game uses to pick a group of janitors. There are two kinds. A storage janitor restocks a container: it puts an item (or a liquid) back in it, and may empty it first. A relay janitor fires the scripted actions (relays) that the map has attached to it. This API lets your mod list the janitors in the scene and get told each time one does its chore. It only reads. It cannot place a janitor or trigger one.
When to use it
- React when a shop shelf, a fridge or another container is restocked, for example to refresh your own display of it.
- Run your own code when a relay janitor fires.
- Tell a real trigger apart from the game replaying old triggers while it loads a save.
Quick example
This mod writes a log line each time a janitor does its chore. It ignores the chores the game repeats while it loads a save.
using ObenseuerCore;
[ModInfo("example.janitorwatch", "Janitor Watch", "0.1.0")]
[ModDependency("obenseuer.moddingframework", "0.11.0")]
public sealed class JanitorWatch : FrameworkMod
{
protected override void OnLoad()
{
Janitors.Acted += janitor =>
{
if (janitor.Replay) return;
if (janitor.Kind == JanitorActionKind.Storage)
Log.Info("A storage was restocked with item " + janitor.ItemId + " (emptied first: " + janitor.Cleared + ")");
else
Log.Info("A relay janitor fired: " + janitor.Id);
};
}
}
Acted is an event: the framework calls your code each time it happens. See Using events for more on events.
Common tasks
List the objects that carry a janitor script, and check that the event works.
Events.WhenReady(() =>
{
foreach (GameObject janitor in Janitors.All)
Log.Info(janitor.name);
if (!Janitors.IsAvailable) Log.Warn("The janitor event is not working in this game version.");
});
Read the container a storage janitor just restocked. Container is null when the janitor’s object has no storage on it.
Janitors.Acted += janitor =>
{
if (janitor.Kind == JanitorActionKind.Storage && janitor.Container != null)
Log.Info(janitor.Container.Name + " was restocked");
};
Reference
This is the full list of what the API gives you.
Janitors the janitor event
Subscribe to Acted to hear each time a janitor does its chore, and read All to list the objects that hold a janitor script.
public static class Janitors
{
public static event Action<JanitorActedArgs> Acted;
public static bool IsAvailable { get; }
public static List<GameObject> All { get; }
}
| Event | Gives you | When it fires |
|---|---|---|
Acted | JanitorActedArgs | A janitor has just done its chore. |
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | false when the Acted event could not be set up. |
All | List<GameObject> | The object each janitor sits on. Empty when none are loaded. |
JanitorActionKind what kind of chore a janitor does
JanitorActedArgs.Kind gives you this: Storage for a storage janitor and Relay for a relay janitor. The game has no such enum. The framework sets it from which of the game’s two janitor actions ran.
public enum JanitorActionKind
{
Storage,
Relay
}
| Value | Meaning |
|---|---|
Storage | A storage janitor. It restocks a container. |
Relay | A relay janitor. It fires the actions the map attached to it. |
JanitorActedArgs what one janitor just did
The Acted event gives you one of these each time a janitor does its chore. Check Kind first, since Container, ItemId and LiquidId only carry real values for a storage janitor.
public sealed class JanitorActedArgs
{
public string Id { get; }
public JanitorActionKind Kind { get; }
public ContainerInfo Container { get; }
public int ItemId { get; }
public int LiquidId { get; }
public bool Cleared { get; }
public bool Replay { get; }
}
| Property | Type | What it means |
|---|---|---|
Id | string | A name for this janitor. It is the same every time it fires. |
Kind | JanitorActionKind | Storage or Relay. |
Container | ContainerInfo | The container on the same object. null for a relay or when there is none. |
ItemId | int | The item this janitor is set to stock. -1 when none. |
LiquidId | int | The liquid this janitor is set to stock. -1 when none. |
Cleared | bool | true when the janitor empties the container before it stocks it. |
Replay | bool | true when the game is repeating an old trigger while loading a save. |
ContainerInfo is described on the Inventory and container page.
Things to know
Actedfires once each time a janitor finishes its chore. It tells you that the chore ran, not what changed. A storage janitor that finds no free space and is not set to empty the container first still firesActedwith the item it is set to stock. If you need to know whether items were really added, look at the container. (It fires after the game’sTriggerActionreturns onStorageJanitorActionorRelayJanitor.Acteddoes not list the items added or removed, because that would need a before and after snapshot of every slot.)- For a storage janitor,
Container,ItemId,LiquidIdandCleareddescribe how the janitor is set up.ItemIdandLiquidIdare the item and liquid it is set to stock, not what it actually added.Containeris a snapshot taken when the event fires. (They come from theitemToFill,liquidToFillandoverrideStoragefields ofStorageJanitorAction.ItemIdandLiquidIdare theItemReference.IDvalues.) - For a relay janitor, there is no container or item.
Containerisnull,ItemIdandLiquidIdare-1, andClearedisfalse. Clearedistruewhen the janitor first empties the container. The janitor then fills it from scratch. (It is theoverrideStorageflag. When it is true the game callsstorage.Empty()before it fills.)Replayistruewhen the game repeats an old trigger while it loads a save. The game remembers which janitor tags have run, and when a save loads it runs them again for each scene that has not seen them yet. These are not real new events. A mod that reacts to a real trigger should ignore events whereReplayistrue. (JanitorController.LoadDelayreplays the saved actions through the sameTriggerAction. The framework marks a call as live when it happens insideJanitorController.TriggerJanitorAction(tag).)- One tag triggers every janitor that has it, and each one fires
Acted. So a single trigger can give you several events. (TriggerJanitorActionis registered as a Lua function for the game’s scripts. It runsTriggerActionon each matching janitor.) Idis the id of the janitor script. It stays the same between sessions when the script has a save id. Otherwise it is made from the script’s place in the scene. (It is the save GUID when the component has one, otherwise the scene path.)Alllists the objects that carry a janitor script, in the order the game registered them. It is empty when no save is loaded. (It reads the game’sprotectedlistjanitorablesonJanitorController.instancethrough reflection, and returns theGameObjectof each entry that is aComponent.)
Limits
- You can’t place a janitor or trigger one from your mod.
IsAvailable,ActedandAllare everything this API offers.
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. Logs go to BepInEx\logs\obenseuer.moddingframework.janitors.log.
Everything not listed in this reference is internal and may change without notice.
When you can call it
- Call
AllandIsAvailablefrom your mod’s normal code (OnLoad, event handlers,Update), not from a background thread you started. The game only runs safely on the main thread (see the glossary). - Nothing here throws an error to you. If the framework fails while it reads,
Allwrites the error to the log and gives you an empty list. - Your
Actedhandlers run inside the game’s own call, one at a time, right after the janitor finished. If one of your handlers throws an error, the framework logs it and still runs the other handlers. - Events fire during a save load too. That is why
Replayexists. A handler that should ignore everything during a load can check whether the game is busy loading or saving. (SaveData.Busyis true while a save is loading or being written.)
If something fails to set up
The framework changes some of the game’s methods when it starts, so it can tell your mod when a janitor acts. If a game update breaks one of them, the framework turns the feature group off, so the Acted event stops, and writes error lines to the group’s log file in BepInEx\logs. Its feature group is obenseuer.moddingframework.janitors, the name you will see in the log. A feature group is the named set of the framework’s changes for one feature.
Janitors.IsAvailableisfalsewhen the event is switched off. The change needs all three game methods to be found. If any one is missing, none of them is used. (The methods areTriggerActiononStorageJanitorActionand onRelayJanitor, andJanitorController.TriggerJanitorAction. The last one is how the framework tells a live trigger from a replay.)