Burnsies HideoutDocsBurnsie.Common

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

MemberBehavior
IsOnDutyCached duty state. False until the first duty event (see limitation below).
DutyStarted / DutyEndedRaised on duty toggle.
SelectionCompletedRaised 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

  1. 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.
  2. Initialize() runs before the world exists. Subscribe and log — nothing game-facing.
  3. 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.