CeruleanCutlass-SpawnKit icon

SpawnKit

[ALPHA] Runtime enemy spawner for Outward: spawn any donor-scene creature beside the player as a real vanilla hostile, with full AI, aggro, combat, and vanilla death/loot. Ephemeral and save-safe by design. Early software — see README for known issues.

Last updated 2 days ago
Total downloads 17
Total rating 0 
Categories Libraries Utility
Dependency string CeruleanCutlass-SpawnKit-0.2.1
Dependants 0 other packages depend on this package

This mod requires the following mods to function

BepInEx-BepInExPack_Outward-5.4.19 icon
BepInEx-BepInExPack_Outward

BepInEx pack for Outward.

Preferred version: 5.4.19
CeruleanCutlass-CompanionKit-0.1.0 icon
CeruleanCutlass-CompanionKit

Persistent-creature companion mechanics for Outward mods: clone/brain-strip body factory, navmesh follower, invisible combat anchor, manual combat, donor-scene harvesting, and expedition template caching from region-only scenes.

Preferred version: 0.1.0
CeruleanCutlass-ForgeKit-0.1.0 icon
CeruleanCutlass-ForgeKit

Dependency-free dev-tooling for Outward BepInEx mods: file-driven dev command loop, self-test harness, on-screen toasts, player-ready lifecycle wait, and embedded/override table loaders.

Preferred version: 0.1.0

README

SpawnKit — spawn any creature, anywhere

⚠️ Alpha. This is early software: the core spawn/despawn/AI loop is solid and well-tested, but you will hit rough edges (see Known issues below) — roughly a quarter of the creature roster doesn't spawn yet, co-op is single-player-only for now, and there's an occasional cosmetic terrain glitch. Back up saves you care about before experimenting.

Press F11, pick a creature, spawn it beside you.

SpawnKit gives you a real vanilla hostile — not a dummy or a stand-in. It wanders, it aggros, it fights, it drops its normal loot, and it dies like the wild version, because it is the wild version. Spawn a pack of hyenas to train on. Drop a Tuanosaur next to a bandit camp and watch them fight each other. Restock a region you've already cleared out.

Spawns are ephemeral: they never enter your save file, and they vanish when you leave the area.

Requires: BepInEx 5 (Outward's Mono branch — see Compatibility), plus CompanionKit and ForgeKit. If you install through a mod manager these come along automatically.

Installing

Install from Thunderstore and the dependencies come with it. Installing by hand: drop all three folders — ForgeKit/, CompanionKit/, SpawnKit/ — side by side into BepInEx/plugins/. If BepInEx logs a dependency error and refuses to load SpawnKit, one of the other two is missing.

Using it

Launch the game, load a save, and press F11 for a draggable window listing every creature you can spawn.

  • means the creature is ready — it spawns instantly.
  • means it's cold: the first spawn pauses 1–6 seconds while SpawnKit fetches its body (see How it works). Click Prewarm to pay that cost up front instead.
  • Set a count, hit Spawn, and use Despawn All / Kill All to clean up.

The key is rebindable ([Menu] MenuKey; Backslash is the classic debug-menu choice), and the menu closes on Esc, on F11 again, or when you open a vanilla menu.

Settings

BepInEx/config/cobalt.spawnkit.cfg, created on first launch:

Setting Default What it does
[Spawner] Enabled true Master kill-switch. Off = no new spawns (cleanup still works).
MaxActiveSpawns 8 Cap on live spawns at once.
SpawnDistance 5 How far from you they appear, in meters.
MaxCachedTemplates 12 How many creature bodies stay in memory. Higher = more instant spawns, more RAM.
DefaultCorpsePolicy Vanilla Vanilla leaves the corpse and its loot. NoBody deletes the corpse (and its loot) after death.
[Menu] EnableMenu true The F11 menu.

Without the menu

Everything is also driven by writing a line into BepInEx/config/SpawnKit_cmd.txt — it runs on the next poll, even while the game is paused. Useful for scripting or if a keybind conflicts.

Verb What it does
spawn <species> [count] Spawn 1 or more beside you. Names may contain spaces: spawn Armored Hyena 3.
despawnall [kill] Remove every spawn. kill gives them a real death (animation + loot) instead of deleting them.
spawnex <species> [dist=N] [life=N] [faction=Name] [owner=tag] [body=vanilla|none] [linger=N] [keepquest] One spawn with the full option surface.
spawnlist / spawndump List spawnable creatures / show what's live right now.
spawnprewarm <species> / spawnprep <scene> Pay the body-fetch cost now so later spawns are instant.
spawnclearcache Drop cached bodies (frees memory; next spawn re-fetches).
selftest Sanity-check the install; look for [SELFTEST] … DONE in the log.

⚠️ Known issues — read this bit

Some creatures don't work. Roughly a quarter of the rows in the list fail to spawn — their body can't be found in the scene the table expects. You'll get a clear message in the log rather than anything harmful. This is being chipped away at.

Single-player / host-only. In co-op, only the host can spawn. Clients are refused cleanly (you'll see why in the log), and creatures the host spawns are not synced to clients. Treat this as a single-player mod for now.

A rare visual glitch. Very occasionally, after a lot of spawning in one session, a patch of ground can go invisible (you can still walk on it) — changing zone and coming back repairs it.

How it works

Outward has no way to create a creature from nothing — every creature is baked into the scene it lives in. So SpawnKit borrows one: it quietly loads the scene a species lives in, copies a live creature out of it, gives the copy a fresh identity, drops it on solid ground near you, and unloads the scene again. That's the 1–6 second pause on a cold spawn, and it's why the result behaves perfectly — it's the same creature the game itself would have spawned, not an imitation.

The creature roster comes from CompanionKit, which owns the table mapping each species to the scenes it can be found in. spawnlist prints it. You can add or fix an entry without rebuilding anything by dropping a DonorScenes.txt into BepInEx/config/:

Hyena=Emercar_Dungeon5,ChersoneseDungeonsSmall
Alpha Tuanosaur=Hallowed_Dungeon3,HallowedMarshNewTerrain

Region-only creatures need an extra step. Open-world regions and towns are too big to load in the background — trying it crashes the game, so SpawnKit refuses to. Creatures that live only out in a region (Pearlbird, Torcrab, Coralhorn and friends) therefore can't be fetched the usual way; their rows are labelled (expedition-only) and a cold spawn fails with a clear message. To stock them, write expedition <species> into BepInEx/config/ck_cmd.txt: CompanionKit takes you through the loading screen and back, and from then on that creature spawns like any other.

Troubleshooting

Symptom What it means What to do
No spawnable species matches '<x>' Not in the table Check spawnlist; add a line to BepInEx/config/DonorScenes.txt
has NO viable donor (all candidates oversized) A region-only creature, not stocked yet Run expedition <species> (see above)
First spawn of a creature pauses, later ones don't Normal — that's the body fetch Use spawnprewarm to pay it early
No mods load at all, no crash log Wrong Steam branch See Compatibility

Compatibility

Outward must be on its Mono Steam beta branch, not the default IL2CPP build (Properties → Betas → select mono). If the game runs but no BepInEx mods load and there's no crash log, this is almost always why.

For modders

SpawnKit is also a library. Reference it and declare the dependency:

[BepInDependency(SpawnKit.Plugin.GUID)]

Then spawning is one line:

SpawnHandle h = Spawner.Spawn("Hyena");

Everything else is optional — per-spawn customization rides a SpawnOptions, and the returned SpawnHandle is your window onto the creature's lifecycle:

Spawner.Spawn("Alpha Tuanosaur",
    new SpawnOptions
    {
        Faction = Character.Factions.Tuanosaurs, // null = the donor's own faction
        Distance = 12f,                          // ring-probe radius (clamped 1–50m)
        // Position = somewhereExact,            // skip the ring probe entirely
        LifetimeSeconds = 120f,                  // auto-despawn clock (null = forever)
        Corpse = Core.CorpsePolicy.NoBody,       // destroy the corpse after death (null = the
                                                 //   [Spawner] DefaultCorpsePolicy config;
                                                 //   NoBody takes the LOOT with it — see traps)
        CorpseLingerSeconds = 8f,                // death-anim time before the NoBody destroy
        OwnerTag = "mymod",                      // namespace your spawns (see DespawnAll)
        // StripQuestEvents = false,             // default true — leave it unless you KNOW
    },
    onReady: handle =>
    {
        if (!handle.IsAlive) { /* handle.FailReason says why */ return; }
        handle.OnDied += _ => DropReward();
        handle.OnDespawned += _ => CleanupMarker();
    });

The full surface (SpawnKit.Spawner, all static):

Call What it does
Spawn(species, options?, onReady?) Spawn ONE creature; returns a SpawnHandle immediately (PendingAlive/Failed; sync refusals return already-Failed with a FailReason). One call = one creature — loop for more; a cold-species loop still costs one fetch (in-flight acquires dedup).
Despawn(handle, kill=false) Remove one spawn: kill = real damage pipeline (death anim + loot, → Died); silent = destroy (→ Despawned). Cancels a still-Pending handle.
DespawnAll(ownerTag=null, kill=false) Sweep by owner tag. null = EVERYTHING — that's the operator verb; mods should pass their own tag.
Prewarm(species, onDone?) / IsPrewarmed(species) Pay/check the body fetch off your hot path.
IsExpeditionOnly(species) True when a species can only be stocked by an expedition (see How it works) — check before promising a boss fight.
Active(ownerTag=null) Snapshot of tracked handles (Pending + Alive).
SpeciesKeys() The spawnable species roster.

Trap table — read before shipping on this

Trap Rule
handle.Character can go Unity-null under you (scene unload, despawn) Check handle.IsAlive before every use; never cache the Character across scene loads.
Spawns are ephemeral by design Scene unload = OnDespawned, and saves skip SK_ UIDs both directions. Don't build persistence on top.
A cold species blocks ~1–6s before onReady Prewarm on your init path if the spawn must feel instant.
MaxActiveSpawns is global across all consumers (pending spawns count too) Tag your spawns (OwnerTag) and never call DespawnAll(null) from a mod — you'd sweep other mods' spawns.
Callbacks run on the main thread inside the watch tick Keep them cheap. A throwing callback is isolated (swallowed + logged) — it won't break SpawnKit, but you won't get a second chance either.
Co-op clients always get Failed/NotMaster Design your feature so only the host drives spawning.
StripQuestEvents = false on a quest-flagged creature fires real quest events on death The default (true) exists because live donors really do carry QuestEventOnDeath — leaving it on is what keeps spawns from mutating quest state.
CorpsePolicy.NoBody destroys the loot bag with the body The corpse GameObject IS the loot container — never combine NoBody with a drop-loot expectation.
Ring geometry (step angles, elevation band, navmesh snap) is not configurable By design. If you need exact placement, pass SpawnOptions.Position and own the spot yourself.
The species roster and creature supply are CompanionKit's, not SpawnKit's An expedition-only species fails cold spawns until an expedition stocks it.

License

Apache License 2.0 — see LICENSE. You may use, modify, and redistribute this kit (including in commercial mods) provided you keep the copyright/license notice; see the license text for the full terms.