Spawner
Population spawners over server entities: keep N alive in an area, respawn on removal. Server-only.
Services you call 1
Crossplay implements these. Resolve them from DI and call them from your own systems.
ISpawnerService
Population spawners: keep Count entities alive in an area and respawn them on a timer after they are removed — HOWEVER they are removed (the game's death→IServerEntities.Despawn, a dungeon reset, anything). The piece never knows what an entity is; the game attaches hp/brains/loot through the Spawned hook. Alongside the keep-N-alive spawners it also runs one-shot PACED SETS (StartSpawnSet): a fixed wave spaced out over time, no respawns.
-
long AddSpawner(SpawnerDefinition definition)Registers a population and spawns its first wave immediately.
-
bool CancelSpawnSet(long setId)Stops a set's pacing. Already-spawned entities stay alive (the set simply forgets them), and a canceled set never raises SpawnSetCleared.
-
bool RemoveSpawner(long spawnerId)Removes a spawner and despawns its live entities (no respawns follow).
-
event Action<long> SpawnSetClearedRaised once per set — with its set id — when the set has FULLY spawned AND every entity it spawned has been removed from the world (however removed). The "wave down" hook a composition bridge uses to advance whatever paced the wave. Never raised for canceled sets; fires on the poll thread.
-
event Action<long, long> SpawnedRaised once per spawned entity: (spawnerId, entityId) — the game-glue hook. The piece only keeps a bare entity alive; attach everything a live entity needs here — hp via Stats, an NPC brain via IAgentService.Attach, a loot table, etc. Fires on the first wave and on every respawn, on the poll thread. Paced sets raise it too, with the set id first (ids never collide — spawners and sets share one counter).
-
long StartSpawnSet(SpawnSetDefinition definition)Starts a PACED SET: Total spawns paced out over time (one per PacingSeconds; all at once when ≤ 0) at the definition's points/zone, each spawned entity carrying the definition's opaque appearance. Unlike a spawner, a set NEVER respawns — it is how a composition layer paces a population wave from data; growth/cycling policy deliberately lives OUTSIDE the piece (the bridge computes Total/Pacing per cycle). Spawned fires per entity (set id first) so downstream hooks work unchanged.
-
bool TryGetSpawnerOf(long entityId, out long spawnerId)The spawner a live entity belongs to, if any.
Configuration 1
Every tunable lives in an options object — there are no magic numbers to hunt for.
SpawnerOptions
Spawner tuning (defaults here, never inline).
-
double DefaultRespawnSeconds { get; set; }Respawn delay used when a definition leaves it at 0.
-
List<SpawnerDefinition> Populations { get; set; }Ambient populations registered automatically at server start — each through the same AddSpawner path (first wave spawns immediately, kept alive on the respawn timer). This is what makes a standing population reachable from CONFIGURATION: a generated server binds this list from its Crossplay:Spawner section, so an entity "simply exists in the world" with zero server code. The game still attaches hp/brains/loot through the Spawned hook (or the World EntitySpawned event) exactly as for a code-registered spawner. Empty (the default) = nothing is auto-registered — byte-identical to the bare piece; the game may still call AddSpawner itself.