-
Notifications
You must be signed in to change notification settings - Fork 0
Multiplayer Compatibility
Namespace: ScavLib.compat.krokmp
ScavLib ships a built-in compatibility bridge for the KrokoshaCasualtiesMP multiplayer plugin. The bridge is a soft dependency: if KrokMP is present at startup, the bridge activates automatically; if not, ScavLib continues to operate normally in single-player. There is no opt-in for mod authors and no opt-out for players.
The goal of this page is to describe what the bridge fixes, what it guarantees, and the limitations you need to design around when targeting multiplayer.
KrokMP synchronizes networked GameObjects by passing their
resource_stringid to Resources.Load(...) on every client. That works
for vanilla items, whose prefabs live in Resources/, but custom items
registered through CustomItemBuilder have no Resources/ prefab —
ScavLib spawns them by cloning a vanilla template at runtime. Without
intervention, every custom item would either fail to spawn on remote
clients or trigger an infinite-spawn loop on the host.
The bridge installs three Harmony patches at startup:
The vanilla MP path resolves an incoming sync packet by calling
Resources.Load(resource_stringid). The bridge intercepts the call:
- If the id is not a registered custom item, vanilla logic runs unchanged.
- If the id is a registered custom item, the bridge instantiates the
cached clone template (the same one
Utils.Createwould use), stamps the mod id ontoItem.id, attaches aCustomItemTag, runs the registeredOnSpawnhook on this client's instance, and registers the GameObject withNetObjectRegistry._RegisterGO.
End result: every client sees the custom item materialise correctly, including its sprite, tags, and component-level configuration.
/spawn on a dedicated server uses Con.SpawnThingOnPlayer, which has
its own setup path independent of the normal world spawn. The bridge
re-implements that path for custom ids: it routes through
Utils.Create, fills ammo / chamber state for guns, and chooses between
container insertion and auto-pickup based on the original arguments.
KrokMP periodically asks "can this object be ignored for network sync?"
and answers true for objects whose resource id cannot be located via
Resources. For custom items, that check would always fail — repeatedly
— and KrokMP would respond by destroying and re-creating the object,
producing an infinite spawn loop that was visible in the wild.
The bridge forces objects carrying CustomItemTag to report
canBeIgnored = false regardless of Resources resolution, breaking the
loop.
When the bridge is active, it installs itself as the SaveDirResolver on
SaveCompanionFile. Resolution order:
- If KrokMP's network system is not running (single-player or
menu) — return
Application.persistentDataPath. - If
SavesystemPatch.savedatapathreplacementis non-empty — use it (this is KrokMP's own override hook). - If
SavesystemPatch.HasMultiplayerSaveFile()returns true — useSavesystemPatch.mpsavefolder. - Otherwise fall back to
Application.persistentDataPath.
So companion files written by SaveCompanionFile automatically follow
the active save context: single-player saves, dedicated-server saves,
and KrokMP's own save-path overrides are all respected without any
per-mod plumbing.
Nothing. Anything you register through CustomItemBuilder is
MP-aware as soon as KrokMP is installed. Your Awake() looks the same
whether or not KrokMP is present.
private void Awake()
{
CustomItemBuilder.Create("mymod_salve", "MyMod", ItemTemplate.Bandage)
.Register(out _);
}If you want to detect MP at runtime (e.g. to skip a UI flow that only makes sense in single-player), check the bridge's enabled flag:
if (ScavLib.compat.krokmp.KrokMpBridge.IsEnabled)
{
// KrokMP is loaded; we may or may not be currently in an MP session.
}For the in-session check, query KrokoshaCasualtiesMP.KrokoshaScavMultiplayer.network_system_is_running directly. ScavLib does not wrap that bool — it's KrokMP's source of truth.
The bridge does several things, but it does not silently turn every
custom item into a fully-synchronised state machine. The constraints
below come from how OnSpawn and CustomItemTag are designed, not from
the bridge itself.
- The same custom item id resolves to the same clone template on every client.
- Every spawned instance carries an identical
CustomItemTagwith the correctCustomItemIdandOwner. - Vanilla item systems that read
ItemInfo(display name, weight, tags, container capacity, etc.) see consistent values everywhere, becauseItemInfolives in each client'sItem.GlobalItemsafter registration. - Save companion files follow the active save directory.
-
OnSpawnruns on every client independently. Each instance of the hook fires on its own client's GameObject. Side effects must either be deterministic (computed from inputs that all clients see) or you must explicitly synchronize them via your own packet. -
CustomItemTag.InstanceDatais not automatically replicated. It is a plain in-memory dictionary on the local MonoBehaviour. Two clients who both see the same item see two unrelated bags. If you rely on per-instance state, treatInstanceDataas client-local cache and synchronise the source of truth throughGOSyncPacketor another KrokMP primitive yourself. - Anything you mutate inside
OnSpawnbased on world state (for example, deriving stats from "current biome depth") will diverge if the world state can differ across clients.
-
OnSpawnruns after Unity'sAwakeon the spawned clone (this was a behaviour change in the 0.7.2 spawn rewrite). On both single-player and MP, you can rely on every component's Awake-time fields being initialised before your hook reads them. - The bridge hooks
GOSyncPacket.Applyat Harmony Prefix returningfalsefor handled ids. If another mod also patchesGOSyncPacket.Apply, test the combination — ScavLib does not intercept ids it does not own, but ordering can still matter. - The bridge runs at default priority. ScavLib's own
Utils.Createpatch isPriority.Low(other frameworks run first), but the KrokMP patches are not — they are intentionally normal priority because they are cooperating with KrokMP, not competing with it.
If a custom item misbehaves in MP, work through this list before filing a bug:
- Run
scavlib checkon every client. The output should list your item id under "Commands (via ScavLib)" — sorry, "items" — with a consistent owner mod name. A divergent owner across clients means the mod was registered on different versions or with different ownership. - Confirm the bridge activated. The startup log line
[KrokMpBridge] Krokosha MP bridge initialized with ValidationFix.should appear on every client. If it only appears on the host, KrokMP is missing on the other client. - If a
RivalFrameworkDetectorwarning appears, another custom-item framework is loaded alongside ScavLib. Cross-framework conflicts in MP are not auto-resolvable; mention the warning in your bug report. - Check for
Failed to resolve template '<id>'errors. Those mean the clone template is not inResources/— usually a typo in the template id passed toCustomItemBuilder.Create(...).
-
Cross-framework coexistence (RshLib + ScavLib). Both libraries
patch
Utils.Create. ScavLib runs atPriority.Lowand will not hijack ids it does not own, but if both libraries register the same id, the result is undefined and theRivalFrameworkDetectorwarning is the only signal. Use mod-prefixed ids. -
Late-joining clients with newer mod content. A client that joins
a session running a different version of your mod may have different
ItemInfofor the same id. KrokMP will sync the GameObject, but every client renders it according to its ownItem.GlobalItems. Ship matched mod versions to your players. -
Hot-disabling mods mid-session. ScavLib does not currently
support unregistering custom items at runtime in MP. The single-player
unregister path exists but is not synchronised to remote clients;
unloading a mod mid-session leaves orphan instances that, on
re-registration, are handled by
MissingItemTagfor save-game recovery — see the SaveCompanionFile page.