Callout Base Class — BurnsieCallout
BurnsieCallout is a hardened base class over LSPDFR's Callout. A scenario author
writes only scene logic; the base class guarantees that no exit path can leak — every
scene entity/blip lives in an EntityBag and every fiber in a FiberRegistry, both torn
down exactly once whether the callout is declined, completed, aborted, or the plugin
unloads mid-scene. The LSPDFR lifecycle overrides are sealed and exception-fenced, so a
scenario bug can never fault LSPDFR's callout runner.
What you implement
[CalloutInfo("StreetBrawl", CalloutProbability.Medium)]
public class StreetBrawl : BurnsieCallout
{
protected override string CalloutName => "StreetBrawl";
protected override bool OnSetup()
{
// Choose a location; returning false aborts before the callout is offered.
Anchor = FindSpotNearPlayer();
CalloutMessage = "Street Brawl";
CalloutPosition = Anchor;
AddMinimumDistanceCheck(80f, Anchor);
// play dispatch scanner audio here
return true;
}
protected override bool OnAccepted()
{
// Spawn everything into Scene; run logic on Fibers gated by Flag.
var ped = Scene.Keep(new Ped(model, Anchor, 0f));
Fibers.StartLoop(CalloutName + ".Logic", 250, Flag, LogicTick);
EndWhenPlayerLeaves(150f); // auto-end leash (arms only after arrival)
return true; // false = abort cleanly
}
protected override void OnTick() { /* per-frame logic (optional) */ }
protected override void OnCleanup() { /* extra teardown before Scene empties (optional) */ }
}
Template methods
| Method | When | Contract |
|---|---|---|
OnSetup() | Before the callout is offered | Set Anchor, CalloutMessage/position/advisory, play audio, show the area blip. Return false to abort. |
OnAccepted() | Player accepted | Spawn the scene into Scene, start fibers on Fibers. Return false to abort cleanly. |
OnTick() | Every frame while running | Keep it cheap. Optional. |
OnCleanup() | During teardown, before Scene.ReleaseAll() | Restore anything you set outside the bag (roads, relationships…). Optional. |
DebugState() | On demand | One-line live state for diagnosis; base reports name, uptime, anchor. Override to expose phase/counters. |
What the base provides
| Member | Purpose |
|---|---|
Scene (EntityBag) | Register every entity/blip; released automatically at teardown |
Fibers / Flag | Callout-scoped fiber registry + cancellation; cancelled/aborted first at teardown |
Log | Logger prefixed with the callout name |
Anchor | The scene anchor position (you set it in OnSetup) |
Player | Convenience for Game.LocalPlayer.Character — still re-validate after yields |
Step("...") | Logs a phase transition at INFO — narrates the scenario in the log |
Finish("reason") | Ends the callout with a logged reason |
Active (static) | The currently running callout, or null — for diagnostics/console tooling only |
Built-in behaviors
Route on accept
On accept, the base drops a GPS-routed blip on Anchor (yellow by convention;
RouteColor to change) and clears the route once the player gets within ~35 m — the blip
itself stays for the scene. Set RouteOnAccept = false in OnSetup if your callout
manages its own arrival blip.
The leash — EndWhenPlayerLeaves(distance)
Auto-ends the callout when the player leaves distance meters from Anchor, with two
important subtleties baked in:
- It arms only after the player has actually arrived (first entered ~60% of the radius). At accept time the player is at dispatch distance — a leash that fires en route would kill every callout instantly.
- It is suspended while a pursuit is active (
Pursuits.IsPursuitActive()), and you can suspend it manually withLeashSuspended = truewhile a scene-owned chase legitimately ranges far from the anchor.
Player-death guard
If the player dies mid-scene, the callout ends cleanly ("player down") — a universal abort so scenes never keep running over a respawn.
Diagnostics for free
On accept, the base logs the scene summary and starts a 4-second state snapshot loop that
writes DebugState() to the debug channel. Debug lines always reach the attached
diagnostics file (see Logging) even with a quiet console — a playtest
produces a reviewable time series of how each callout unfolded, ending with a final-state
line at teardown.
Teardown order (why it's safe)
On every exit path, exactly once:
Flag.Cancel()thenFibers.AbortAll()— nothing can re-task, re-pursue or re-notify while cleanup below yields.OnCleanup()— your restores.Scene.ReleaseAll()— peds/vehicles dismissed to ambient, blips deleted.
Base lifecycle calls (base.OnCalloutAccepted() etc.) are chained internally — LSPDFR's
base methods carry required state plumbing and are never skipped.
Rules
- Everything you spawn goes through
Scene.Keep(...)— creation code should readScene.Keep(new Ped(...)). - Every loop goes through
FiberswithFlag— never a bareGameFiber.StartNewloop. - Anything you set outside the bag (roads closed, relationship groups, density
suppression) is restored in
OnCleanup(). OnTickruns on LSPDFR's callout fiber — keep it cheap; long work belongs on your own fibers.