-
Notifications
You must be signed in to change notification settings - Fork 0
Lifecycle System
Namespace: ScavLib.mods
Since: v0.4.0
The lifecycle system lets your mod receive automatic callbacks when the game
world changes state — without manually subscribing to the EventBus. Register
once in Awake() and ScavLib handles the wiring for you.
Inherit from ModLifecycleBase and override only the callbacks you need,
then pass an instance to ModRegistry.Register():
using BepInEx;
using ScavLib.mods;
using ScavLib.event_bus.events;
using ScavLib.util;
[BepInDependency("com.kanisuko.scavlib", "0.4.0")]
[BepInPlugin("com.yourname.yourmod", "YourMod", "1.0.0")]
public class YourPlugin : BaseUnityPlugin
{
private void Awake()
{
ModRegistry.Register(
new ModInfo("YourMod", "1.0.0", "Does cool things.", "YourName"),
new YourModLifecycle()
);
}
}
public class YourModLifecycle : ModLifecycleBase
{
public override void OnEnabled()
{
// Runs immediately after Register() completes.
// Safe to set up static state here.
}
public override void OnWorldLoaded(WorldLoadedEvent e)
{
// World is fully initialized. Body and PlayerCamera.main are safe.
PlayerUtil.HealAll();
}
public override void OnLayerLoaded(LayerLoadedEvent e)
{
// Runs on every layer load, including the first.
GameUtil.Log($"Layer {e.BiomeDepth} loaded. First={e.IsFirstLoad}");
}
public override void OnWorldUnloading(WorldUnloadingEvent e)
{
// Last chance to read world/player state before the layer tears down.
if (e.IsExitToMenu)
GameUtil.Log("Exiting to menu — save persistent data here.");
}
public override void OnWorldDestroyed(WorldDestroyedEvent e)
{
// WorldGeneration.OnDestroy() has completed. Clear static caches here.
}
}The full interface. All methods have default empty implementations in
ModLifecycleBase, so you only need to override what you use.
public interface IModLifecycle
{
void OnEnabled();
void OnDisabled();
void OnWorldLoaded(WorldLoadedEvent e);
void OnLayerLoaded(LayerLoadedEvent e);
void OnWorldUnloading(WorldUnloadingEvent e);
void OnWorldDestroyed(WorldDestroyedEvent e);
}| Callback | When it fires |
|---|---|
OnEnabled() |
Immediately after ModRegistry.Register() succeeds |
OnDisabled() |
Reserved for future use (not yet triggered automatically) |
OnWorldLoaded(e) |
Once per session — world fully initialized, Body safe |
OnLayerLoaded(e) |
Every layer (biome) finishes generating, including the first |
OnWorldUnloading(e) |
Before layer transition or save-and-exit |
OnWorldDestroyed(e) |
After WorldGeneration.OnDestroy() completes |
An abstract base class that provides empty default implementations for all
six callbacks. Inherit from this rather than implementing IModLifecycle
directly so your code stays forward-compatible if new callbacks are added.
public abstract class ModLifecycleBase : IModLifecycle
{
public virtual void OnEnabled() { }
public virtual void OnDisabled() { }
public virtual void OnWorldLoaded(WorldLoadedEvent e) { }
public virtual void OnLayerLoaded(LayerLoadedEvent e) { }
public virtual void OnWorldUnloading(WorldUnloadingEvent e) { }
public virtual void OnWorldDestroyed(WorldDestroyedEvent e) { }
}ModInfo accepts optional dependency names as extra constructor arguments.
These are advisory — missing dependencies log a LogWarning at registration
time but do not block the mod from loading. Actual load-order enforcement
must use BepInEx's [BepInDependency] attribute.
// Declare that MyMod depends on ScavLib and AnotherMod
new ModInfo("MyMod", "1.0.0", "desc", "Author", "ScavLib", "AnotherMod")Declared dependencies appear in scavlib status output:
MyMod v1.0.0 by Author [F] Deps: [ScavLib, AnotherMod]
Each lifecycle callback is wrapped in a try/catch inside ScavLib. An
exception in one mod's OnLayerLoaded will not prevent other mods'
callbacks from firing. Errors are logged to the BepInEx console with the
mod name and callback name included.
Mods registered with a lifecycle object are annotated with [F] in
scavlib status output. Mods registered with the basic Register(ModInfo)
overload (no lifecycle) show no tag. Both registration styles are fully
supported and backward compatible.
The lifecycle system is built on top of EventBus internally. ScavLib
creates a LifecycleEventAdapter per registered lifecycle and calls
EventBus.Register() on it automatically. You do not need to call
EventBus.Register() for the lifecycle object yourself.
If you need to subscribe to events that are not covered by IModLifecycle
(e.g. ItemPickedUpEvent, custom events), use EventBus.Register(this)
as usual alongside the lifecycle registration.