CeruleanCutlass-CompanionKit icon

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.

Last updated 2 days ago
Total downloads 17
Total rating 0 
Categories Libraries Utility
Dependency string CeruleanCutlass-CompanionKit-0.1.0
Dependants 1 other package depends 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-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

CompanionKit — persistent-companion mechanics for Outward mods

A BepInEx library for modders. It doesn't add anything a player sees on its own — install it because a mod you're using (a follower, a summon, a spawner) declares it as a dependency.

It exists because Outward has no way to spawn a fresh creature at runtime: every creature is baked into the scene it lives in. CompanionKit's answer is donor-scene harvesting — any area is an ordinary scene you can load additively, clone a live creature out of, and unload again in about a second. DonorHarvest wraps that whole recipe, plus the audio/Photon/save guards that make it safe, behind one call. On top of that sits everything needed to turn a cloned body into a companion that follows, fights, and survives zone changes.

Requires: BepInEx 5 (Outward's Mono branch — see Compatibility) and ForgeKit. No SideLoader.

Installing (for players)

Drop the CompanionKit folder into BepInEx/plugins/, alongside ForgeKit/. A mod manager does this for you. On its own the kit does nothing except expose the expedition command channel and config described below.

What's in the kit

Piece What it gives you
BodyFactory Clone a live creature into a usable body: consume vs no-consume modes, the deep brain-strip that makes a clone inert and safe (its AI, colliders and networking are the things that bite), a ghost stand-in path for humanoid rigs, and visibility diagnostics/repair.
CompanionBody NavMeshAgent-driven follow: zone re-placement, leash/warp with a run-home grace window, root-bone auto-calibration (the fix for creatures that drift and snap as they walk), per-rig yaw offsets, animator driving. Exposes OnAfterMove so subscribers run after the frame's pose is final.
CompanionAnchor The invisible vanilla ally that acts as the companion's real combat body — a stripped clone is untargetable, so the anchor absorbs enemy detection, aggro and damage, welded to the body every frame. Owns HP and stat seams.
CompanionCombat Manual combat for a brain-stripped body: target selection (commanded > defend-the-anchor > assist-the-owner), a damage pipeline that shows up in CombatHUD, species-typed damage profiles, engage/disengage.
AttributeCapture Reads a live Character's real stats (resistances, health, natural weapon damage, movement) into a portable Core.CreatureAttributes.
DonorHarvest + its guards "Body anywhere": additively load any donor scene, clone, unload — with the audio-registry, PhotonView-collision and mid-harvest-save guards that make it survivable. The species→scene table ships embedded (DonorScenes.txt) and is overridable from BepInEx/config/.
Companion / CommandStance The aggregate whose lifetime is the bond, not the body — bodies get destroyed and rebuilt, standing orders and anchor state must not.
ExpeditionHarvest / BodyTemplateCache Creatures from oversized scenes (region terrains, which cannot be loaded additively — it crashes): a real round trip through the vanilla loading screen that batch-caches dormant body templates for the rest of the session.

CompanionKit.Core is a game-reference-free, unit-tested compute layer: weld policy, target selection, the attribute codec, the donor table, the expedition manifest and warm policy, plus scene/species helpers.

Expeditions — the creature-supply chain

Most creatures can be fetched on demand by loading their dungeon scene in the background. But open-world regions and towns are too large to load that way (it crashes the game), so creatures that live only out in a region need a different route: an expedition — a real round trip through the vanilla loading screen that caches body templates for every species that scene can donate. Pay it once, and those creatures are available for the rest of the session.

Verbs — CompanionKit polls BepInEx/config/ck_cmd.txt (write a line, it runs on the next poll; works while paused):

Verb What it does
expedition Status: harvest state, template-cache census, manifest, config, this launch's auto-warm decision
expedition <scene|species> Round trip to an oversized donor scene, caching a body template for every species it donates. Hands-off — the loading-screen gates are passed automatically. Species with an ordinary dungeon donor are refused toward that cheaper path.
templateclear / templateprobe Free the session template cache / probe a cached template

Config ([Expedition] in cobalt.companionkit.cfg):

  • CaptureOnSceneEntry (default off) — when on, walking into a region that still has uncached species runs the capture in place, costing zero loading screens. It is off by default because it is the one path where the kit does substantial work the player never asked for, on every region entry. Turn it on if you'd rather stock creatures opportunistically than run expedition <species>.
  • AutoWarmAtBoot = off | needed | all — a once-per-launch warm at first player-ready. needed only fires when the consumer's active companion actually needs a body it hasn't got.
  • AlwaysWarmSpecies — comma-separated species warmed regardless of the mode; the packaging knob for a mod whose headline creature is region-only.

ManifestBepInEx/config/ck_expeditions.txt records every scene whose capture completed. Safe to edit or delete.

Consumer seams: ExpeditionOrchestrator.ActiveSpeciesProvider (a Func<string> naming the active companion's species; feeds needed-mode warm) and ExpeditionOrchestrator.AutoWarmDeferred (set true in your Awake to fire AutoWarm() yourself once your own save state is loaded).

Consuming the kit

<!-- your .csproj — both kit DLLs ship from the kit's own folder, never copy them into yours -->
<ProjectReference Include="..\CompanionKit\CompanionKit.csproj" Private="false" />
<ProjectReference Include="..\..\core\CompanionKit.Core\CompanionKit.Core.csproj" Private="false" />
[BepInPlugin(GUID, NAME, VERSION)]
[BepInDependency(CompanionKit.Plugin.GUID, BepInDependency.DependencyFlags.HardDependency)]
public class Plugin : BaseUnityPlugin
{
    void Awake()
    {
        CompanionRuntime.Log = Logger;                       // or leave the kit's own logger
        CompanionRuntime.DefaultSettings = new MySettings(); // ICompanionSettings over YOUR config
    }
}

Lifecycle:

var companion = new Companion();                          // or your subclass with policy state
companion.Anchor.OnAnchorDeath += ...;                    // your downed policy
companion.Anchor.OnAnchorCriticallyHurt += ...;

// acquire a body (any path):
CompanionBody body = BodyFactory.BuildPuppet(src, owner, consume: true);   // consumes the wild one
CompanionBody body = BodyFactory.BuildPuppet(src, owner, consume: false);  // the wild one survives
// or DonorHarvest.HarvestChain(scenes, species, owner, b => ...)          // no wild source needed
// or the ghost stand-in trio: SpawnGhostActive / GhostVisualReady / FinishGhostPuppet

companion.Body = body;
var combat = body.gameObject.AddComponent<CompanionCombat>();
combat.Anchor = companion.Anchor;
combat.Stance = companion.Stance;
combat.OnEnemyDefeated += ...;                            // your kill-credit policy
companion.Anchor.AttachBody(body);                        // welds the anchor to body.OnAfterMove

// on your tick:
companion.Anchor.Upkeep(owner, host, bodyFighting: body.CombatTarget != null);
companion.Anchor.ApplyVoice(body);

// stat seam (idempotent, re-push any time):
companion.Anchor.ApplyVitals(maxHealth);
companion.Anchor.ApplyCreatureStats(effectiveAttributes); // null = clear back to defaults
combat.SetAttackProfile(effectiveAttributes);

// orders:
companion.Stance.CommandEngage(target);   // priority 0, any range
companion.Stance.CommandDisengage();      // passive + run-home grace

// persistence is YOURS: store the species + Attributes (CreatureAttributes.Flatten/Parse is one
// save-safe string) in your own save file. The kit ships no save format.

Scope and caveats

  • The API is instance-based, but one companion per owner is what's actually been proven in play. Several at once should work and is unexercised — the vanilla summon slot is one-per-player, and multi-anchor aggro is untested.
  • The kit ships the attribute codec and every body-acquisition path, but no save file — consumers own their own format.
  • Special attacks stay consumer-side; CompanionCombat.DealDamage is the public seam.
  • Beast bodies are the well-trodden path. Humanoid bodies use a different (ghost stand-in) route that should work but has had far less mileage.

Compatibility

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

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.