Prefab cloning (Mirrored Devices template)¶
Full recipe for cloning a vanilla prefab, registering it with the game's prefab system, wiring multi-constructor kits, and preserving multiplayer/save-load compatibility. Lifted verbatim from RepairPrototype's reverse-engineering of the Mirrored Devices mod.
Problem¶
A mod that wants to add a new device whose behavior matches an existing one does NOT need to hand-build the prefab. Clone the vanilla prefab, override identity (name, hash), register with WorldManager.SourcePrefabs, and the game treats the clone as any other device. Multiplayer save/load "just works" because the clone shares all original components.
The recipe has three Harmony entry points, each serving a distinct phase of the prefab lifecycle, plus helpers for constructor conversion, localization, and crafting recipes.
Solution / recipe¶
Three Harmony patches¶
F0225 (Plans/RepairPrototype/plan.md:479-484):
Prefab.LoadAllPrefix (line 499): Runs before game loads prefabs. This is where cloning happens.Localization.LanguageFolder.LoadAllPrefix (line 828): Registers display names.InventoryManager.SetupConstructionCursorsPostfix (line 455): Fixes placement cursor arrows.
HiddenParent setup¶
F0226 (Plans/RepairPrototype/plan.md:487-495):
// Static field (lives for entire game session)
private static readonly GameObject HiddenParent = new GameObject("~HiddenGameObject");
// In Prefab.LoadAll prefix:
UnityEngine.Object.DontDestroyOnLoad(HiddenParent.gameObject);
HiddenParent.SetActive(value: false);
The hidden parent prevents clones from being visible. DontDestroyOnLoad survives scene loads.
CreateMirroredThing core¶
F0227 (Plans/RepairPrototype/plan.md:521-545):
// (1) Clone the entire GameObject hierarchy
GameObject mirroredGameObject = GameObject.Instantiate(source, HiddenParent.transform);
mirroredGameObject.name = mirrorDef.mirrorName;
// (2) Override identity
Thing mirroredThing = mirroredGameObject.GetComponent<Thing>();
mirroredThing.PrefabName = mirrorDef.mirrorName;
mirroredThing.PrefabHash = mirrorDef.mirrorHash;
// (3) [Mirror-specific: flip transform -- NOT NEEDED for repair mod]
FlipTransform(mirroredGameObject.transform);
// (4) Clone and process blueprint (placement ghost)
if (mirroredThing.Blueprint != null)
{
mirroredThing.Blueprint = GameObject.Instantiate(mirroredThing.Blueprint, HiddenParent.transform);
FlipTransform(mirroredThing.Blueprint.transform);
Wireframe blueprintWireframe = mirroredThing.Blueprint.GetComponent<Wireframe>();
FlipWireframe(blueprintWireframe);
}
// (5) REGISTER with the game
WorldManager.Instance.SourcePrefabs.Add(mirroredThing);
FindMirrorInfos + ConvertConstructorToMultiConstructor¶
F0229n (Plans/RepairPrototype/plan.md:497-514):
FindMirrorInfos()scansWorldManager.Instance.SourcePrefabs: if prefab isMultiConstructorwith matchingConstructable, store constructor reference; if plainConstructor, queue for upgrade; if matches by name, store asdeviceToMirror. Walks all prefabs'BuildStatesto findTool.ToolEntry/ToolEntry2references pointing at old Constructor.ConvertConstructorToMultiConstructor(line 526):var mctor = ctor.gameObject.AddComponent<MultiConstructor>(); mctor.Constructables = new List<Structure>() { buildStructure }; CopySharedFields((Stackable)ctor, (Stackable)mctor); WorldManager.Instance.SourcePrefabs[prefabIndex] = mctor; UnityEngine.Object.DestroyImmediate(ctor);CopySharedFieldsuses reflection to copy every field from Stackable up through Item/DynamicThing/Thing, with special handling for self-references and IListwith Parent refs pointing to source.
AddToConstructor + MirrorDefinition class¶
F0229o (Plans/RepairPrototype/plan.md:547-560):
AddToConstructor:int insertIndex = mirrorDef.constructor.Constructables.FindIndex(p => p.name == mirrorDef.deviceName); mirrorDef.constructor.Constructables.Insert(insertIndex + 1, mirroredDevice as Structure);Inserts clone right after original in kit's constructable list.MirrorDefinitionclass: deviceName, mirrorName (= deviceName + "Mirrored"), mirrorHash (=Animator.StringToHash(mirrorName)), mirrorDescription, connectionsToFlip, deviceToMirror, constructor, postfix delegate. Per-device postfix delegate runs device-specific tweaks.
Localization + free crafting recipes¶
F0229p (Plans/RepairPrototype/plan.md:583-610):
Localization registration via
[HarmonyPatch(typeof(Localization.LanguageFolder), nameof(Localization.LanguageFolder.LoadAll))]Prefix: finds original name in__instance.LanguagePages[0].Things, then addsnew Localization.RecordThing { Key = mirrorDef.mirrorName, Value = originalName + " (Mirrored)", ThingDescription = mirrorDef.mirrorDescription }. No XML needed. Crafting recipes FREE:GameObject.Instantiate()copies entireBuildStateschain. Clone inherits exact same recipe as original. No XML, no recipe code needed.
Save-load stability¶
F0229h (Plans/RepairPrototype/plan.md:452-459):
Stationeers saves use
PrefabHash(integer) to identify things.Animator.StringToHash(name)is deterministic - same name always produces same hash. Cloned prefabs with stable names survive save/load automatically. If mod removed, things with unknown PrefabHash fail to load (silently disappear or cause errors). No custom save data types needed if clone doesn't add new state fields.
Key takeaways¶
F0228 (Plans/RepairPrototype/plan.md:612-620):
- The actual cloning is ~30 lines. Everything else is bookkeeping.
Prefab.LoadAllprefix is the universal hook for prefab manipulation.Animator.StringToHash(name)is the hash function; deterministic, stable across saves.- Hidden parent with
DontDestroyOnLoad+ inactive is essential. - The Constructor/MultiConstructor distinction is the trickiest bookkeeping.
- Multiplayer and save/load "just work" because the clone shares all original components.
- This breaks the moment you add custom state or behavior; then all clients need the mod.
Cited verifications¶
Eight findings, all from RepairPrototype's plan, fit together as one template:
| Finding | Subject |
|---|---|
| F0225 | Three Harmony patches (Prefab.LoadAll, Localization.LoadAll, SetupConstructionCursors) |
| F0226 | HiddenParent + DontDestroyOnLoad setup |
| F0227 | CreateMirroredThing core (Instantiate + rename + register) |
| F0228 | Key-takeaways summary |
| F0229h | PrefabHash stability via Animator.StringToHash |
| F0229n | FindMirrorInfos + ConvertConstructorToMultiConstructor + CopySharedFields |
| F0229o | AddToConstructor + MirrorDefinition |
| F0229p | Localization registration + free crafting recipes |
Verification history¶
- 2026-04-20: page created from the Research migration; eight findings combined verbatim into one coherent template.
Open questions¶
None at creation.