Duty State — DutyStateService
LSPDFR 0.4.9 has no query API for duty — the only way to know whether the player is on
duty is to have been listening when the event fired. DutyStateService caches the state
from Functions.OnOnDutyStateChanged and turns it into a clean start/stop signal for your
plugin's session lifecycle.
Usage — the canonical plugin skeleton
public class Main : LSPD_First_Response.Mod.API.Plugin
{
private static Log _log;
private static DutyStateService _duty;
private static MySession _session; // ALL your mutable state lives here
public override void Initialize() => Startup();
public override void InitializeAgain() => Startup(); // reload-safe: Startup is idempotent
private static void Startup()
{
_log = new Log("MyPlugin");
_duty?.Detach(); // unsubscribe-then-subscribe = idempotent
_duty = new DutyStateService(_log);
_duty.DutyStarted += () => GameFiber.StartNew(() => { _session = new MySession(_log); _session.Start(); });
_duty.DutyEnded += () => GameFiber.StartNew(() => { _session?.End(); _session = null; });
_duty.Attach();
}
public override void Finally() // must be safe even if never on duty
{
_session?.End();
_session = null;
_duty?.Detach();
}
}
API
| Member | Behavior |
|---|---|
IsOnDuty | Cached duty state. False until the first duty event (see limitation below). |
DutyStarted / DutyEnded | Raised on duty toggle. |
SelectionCompleted | Raised after the station/loadout selection UI completes — the point where the world is fully set up for the shift. |
Attach() / Detach() | Subscribe/unsubscribe to the LSPDFR events. Both are idempotent (attach detaches first), so they are safe in Initialize(), InitializeAgain() and Finally(). |
Rules
- Events arrive on LSPDFR's fiber. Copy any state you need and hand the real work to
your own fiber (
GameFiber.StartNew) immediately — as the skeleton above does. A slow handler here stalls LSPDFR itself. Initialize()runs before the world exists. Subscribe and log — nothing game-facing.- Handlers are exception-fenced internally (a throwing subscriber is logged, not propagated into LSPDFR), but don't rely on that as flow control.
Known limitation (documented, not solvable)
When a plugin is hot-loaded while the player is already on duty, the duty event has
already fired and will not re-fire — IsOnDuty stays false and the plugin remains
dormant until the next duty toggle. Log this possibility at startup so users understand
why a freshly force-reloaded plugin waits for an off/on duty cycle.