The mission editor's unit placement panel (NewUnitPanel) pulls its aircraft list from Encyclopedia.i.aircraft. On first open, NewUnitPanel.Awake() populates a static Dictionary<string, UnitOptionProvider> called unitProviders:
// Only runs once — static cache
if (unitProviders.Count == 0)
{
unitProviders.Add("aircraft", UnitOptionProvider.Create<AircraftDefinition>(Encyclopedia.i.aircraft));
unitProviders.Add("vehicles", UnitOptionProvider.Create<VehicleDefinition>(Encyclopedia.i.vehicles));
// ... ships, buildings, scenery, missiles, otherUnits
}UnitOptionProvider.Create<T>() filters out definitions with disabled = true, sorts by unitName, and stores the result as an array.
| Class | Namespace | Role |
|---|---|---|
NewUnitPanel |
NuclearOption.MissionEditorScripts.Buttons |
UI panel for placing units in editor |
UnitOptionProvider |
Nested in NewUnitPanel |
Wraps a filtered/sorted UnitDefinition[] |
Encyclopedia |
Global | Singleton holding all unit definition lists |
AircraftDefinition |
Global | ScriptableObject defining an aircraft |
UnitDefinition |
Global | Base class for all unit definitions |
Clone an existing AircraftDefinition via UnityEngine.Object.Instantiate(). Must also clone aircraftParameters (separate ScriptableObject).
clonedDef = Object.Instantiate(original);
clonedDef.name = "MyCloneKey";
clonedDef.jsonKey = "MyCloneKey"; // unique key for save/load
clonedDef.unitName = "My Custom Aircraft";
clonedDef.unitPrefab = original.unitPrefab; // shares the same prefab
clonedDef.disabled = false; // must be false to appear in editor
clonedDef.aircraftParameters = Object.Instantiate(original.aircraftParameters);
clonedDef.aircraftParameters.aircraftName = "My Custom Aircraft";Add to Encyclopedia.i.aircraft, the Lookup dictionary, and IndexLookup (for network sync):
Encyclopedia enc = Encyclopedia.i;
enc.aircraft.Add(clonedDef);
if (!Encyclopedia.Lookup.ContainsKey(clonedDef.jsonKey))
Encyclopedia.Lookup.Add(clonedDef.jsonKey, clonedDef);
((INetworkDefinition)clonedDef).LookupIndex = enc.IndexLookup.Count;
enc.IndexLookup.Add(clonedDef);
clonedDef.CacheMass(); // required — caches prefab massBest place: Harmony postfix on Encyclopedia.AfterLoad.
NewUnitPanel.unitProviders is static and only populated once. If your Encyclopedia patch runs after the editor already cached the list, your unit won't appear.
Fix: Harmony prefix on NewUnitPanel.Awake that clears the cache:
[HarmonyPatch(typeof(NewUnitPanel), "Awake")]
public static class EditorInjectPatch
{
private static readonly FieldInfo unitProvidersField =
AccessTools.Field(typeof(NewUnitPanel), "unitProviders");
public static void Prefix()
{
var dict = unitProvidersField?.GetValue(null) as IDictionary;
if (dict != null && dict.Count > 0)
dict.Clear(); // forces Awake() to rebuild from Encyclopedia
}
}Requires: using NuclearOption.MissionEditorScripts.Buttons;
The editor spawns units via Spawner.SpawnAircraft. Since the clone shares the original's unitPrefab, the spawned aircraft will have the original's definition. You need patches to reassign:
Hangar.TrySpawnAircraft— flag when clone is being spawnedSpawner.SpawnAircraft— postfix to reassignUnit.definitionto clone
Missions save unit definitions by jsonKey. As long as your clone has a unique jsonKey registered in Encyclopedia.Lookup, it will save and load correctly. The INetworkDefinition.LookupIndex is needed for multiplayer sync.
unitProvidersis static — survives scene reloads. Clearing it inAwakeprefix ensures fresh data.UnitOptionProvider.Createfiltersdisabled == truedefinitions. Make suredisabled = false.CacheMass()must be called or the editor will show 0 mass.- The clone shares the original's prefab, so runtime patches (Harmony) are needed to differentiate behavior.
aircraftParametersmust be a separate clone, not a reference to the original's — otherwise changes affect both.codefield onUnitDefinitionis used for some UI displays (short name).