Skip to content

Game.Tools.ApplyPrefabsSystem

Assembly:
Namespace: Game.Tools

Type: class

Base: GameSystemBase

Summary:
System that processes entities with SaveInstance to extract sub-objects, sub-nets and sub-areas from live scene instances and apply them to the corresponding PrefabBase asset. It reads dynamic buffers (SubObject, SubNet, SubArea) on saved instances, builds prefab component data (ObjectSubObjects, ObjectSubNets, ObjectSubAreas, ObjectSubLanes, EffectSource, ActivityLocation, etc.), updates construction/upkeep costs (for AssetStampPrefab), updates PrefabSystem and marks the asset dirty. Uses CityConfigurationSystem to handle left/right hand traffic inversion for net components. Intended for use in Cities: Skylines 2 modding where in-editor or saved-instance data must be baked back into prefab assets.


Fields

  • private CityConfigurationSystem m_CityConfigurationSystem
    Service reference used to read city configuration (e.g., leftHandTraffic) to adapt net inversion behavior when writing ObjectSubNets.

  • private PrefabSystem m_PrefabSystem
    Reference to the game's PrefabSystem used to lookup and update prefabs (GetPrefab, UpdatePrefab, mark assets dirty).

  • private EntityQuery m_SaveInstanceQuery
    EntityQuery used to find objects with SaveInstance that should be processed and applied to prefabs.

Properties

  • No public properties.
    This system exposes no public properties; it operates via overridden OnCreate/OnUpdate and static helper methods.

Constructors

  • public ApplyPrefabsSystem()
    Default constructor. Marked with [Preserve] in the source to avoid stripping. No special runtime initialization here; real initialization happens in OnCreate.

Methods

  • protected override void OnCreate()
    Initializes system references: acquires CityConfigurationSystem and PrefabSystem from World, builds the EntityQuery for objects having SaveInstance and Object, and calls RequireForUpdate to ensure the system runs only when matching entities exist.

  • protected override void OnUpdate()
    Main processing loop:

  • Converts the SaveInstance query to a NativeArray.
  • Removes SaveInstance component from matched entities (and from the whole query first).
  • For each entity: if it has SubObject/SubNet/SubArea buffers it:
    • Gets the PrefabRef and resolves the PrefabBase via PrefabSystem.
    • Aggregates construction and upkeep costs.
    • Calls UpdateObjectSubObjects, UpdateObjectSubNets, UpdateObjectSubAreas to build or remove prefab components based on instance contents.
    • If the prefab is an AssetStampPrefab, updates its m_ConstructionCost and m_UpKeepCost fields.
    • Calls m_PrefabSystem.UpdatePrefab(prefab, entity) and marks prefab.asset dirty (prefab.asset?.MarkDirty()).
  • Ensures the NativeArray is disposed in a finally block.

  • private void UpdateObjectSubObjects(Entity instanceEntity, PrefabBase prefabBase, ref uint constructionCost, ref uint upKeepCost)
    Reads SubObject buffer from the instance (via ListObjectSubObjects) and creates/updates or removes the prefab's ObjectSubObjects, EffectSource, and ActivityLocation components accordingly. Adds components when found, or removes them from the asset when lists are empty.

  • private void ListObjectSubObjects(Entity instanceEntity, out List<ObjectSubObjectInfo> subObjectList, out List<EffectSource.EffectSettings> subEffectList, out List<Game.Prefabs.ActivityLocation.LocationInfo> subActivityList, ref uint constructionCost, ref uint upKeepCost)
    Scans the Game.Objects.SubObject dynamic buffer on the instance entity. For each owned sub-object (skips Secondary/ServiceUpgrade and non-owned), it:

  • Computes local position/rotation relative to parent if needed (uses ObjectUtils.WorldToLocal and LocalTransformCache when available).
  • Uses EditorContainer components to distinguish effect/activity/prefab-subobject types.
  • Accumulates construction and upkeep costs from PlaceableObjectData and ConsumptionData on the sub-object's prefab.
  • Builds lists of ObjectSubObjectInfo, EffectSource.EffectSettings and ActivityLocation.LocationInfo. Returns null lists when no sub-objects present.

  • private void UpdateObjectSubNets(Entity instanceEntity, PrefabBase prefabBase, ref uint constructionCost, ref uint upKeepCost)
    Reads SubNet buffer (via ListObjectSubNets) and creates/updates or removes ObjectSubNets and ObjectSubLanes components on the prefab. Also handles NetInvertMode based on CityConfigurationSystem.leftHandTraffic to flip the prefab's m_InvertWhen value if needed.

  • private NetPieceRequirements[] CreateRequirementMap()
    Builds a lookup map (96 entries) mapping bit positions to NetPieceRequirements values for upgraded/requirement composition handling. Iterates NetPieceRequirements enum and uses NetCompositionHelpers.GetRequirementFlags to fill a 96-sized array.

  • private NetPieceRequirements[] CreateRequirementArray(NetPieceRequirements[] requirementMap, CompositionFlags flags)
    From a given CompositionFlags, collects the corresponding NetPieceRequirements values using a previously created requirementMap and returns them as an array (or null if none).

  • private void ListObjectSubNets(Entity instanceEntity, out List<ObjectSubNetInfo> subNetList, out List<ObjectSubLaneInfo> subLaneList, ref uint constructionCost, ref uint upKeepCost)
    Scans Game.Net.SubNet buffer on the instance entity. For each owned sub-net:

  • Reads Edge/Curve/Node components to build Bezier4x3 curves (transforms coordinates to local if needed).
  • Uses LocalCurveCache / LocalTransformCache to prefer cached values when close.
  • Computes construction/upkeep costs for placeable net compositions.
  • Distinguishes EditorContainer lanes vs actual nets and fills ObjectSubLaneInfo or ObjectSubNetInfo lists.
  • Builds upgrade lists for upgraded nets via CreateRequirementMap/CreateRequirementArray. Returns null lists when no sub-nets present.

  • private int GetNodeIndex(Entity node, Dictionary<Entity, int> dictionary)
    Helper to assign and return a compact index for nodes when building node index arrays; adds new nodes to the dictionary.

  • private int GetParentMesh(Entity node)
    Returns the parent mesh index by reading a LocalTransformCache on the node, or -1 if not present.

  • private bool HasEdgeStartOrEnd(DynamicBuffer<ConnectedEdge> connectedEdges, Entity node, Entity instanceEntity)
    Checks whether any ConnectedEdge in the buffer corresponds to an edge whose start/end is the given node and that the edge is owned by the instanceEntity. Used to avoid duplicating node-based sub-nets when an edge covers them.

  • private void UpdateObjectSubAreas(Entity instanceEntity, PrefabBase prefabBase, ref uint constructionCost, ref uint upKeepCost)
    Reads sub-areas (via ListObjectSubAreas) and creates/updates or removes the prefab's ObjectSubAreas component.

  • private List<ObjectSubAreaInfo> ListObjectSubAreas(Entity instanceEntity, ref uint constructionCost, ref uint upKeepCost)
    Scans Game.Areas.SubArea buffer on the instance entity. For each owned sub-area:

  • Reads area prefab and node positions (transforms to local space when needed).
  • Uses LocalNodeCache buffer when present to prefer cached positions and record parent mesh indices.
  • Builds and returns a list of ObjectSubAreaInfo or null if none.

  • private static void CheckCachedValue(ref float3 value, float3 cached)
    If the supplied value is within 0.01 units of cached, replace it with cached. Helps keep stable transformations when prefab caches exist.

  • private static void CheckCachedValue(ref quaternion value, quaternion cached)
    If the rotation difference is less than 1 degree (MathF.PI / 180), replace value with cached. Keeps rotations stable against caches.

  • public static T AddComponent<T>(PrefabBase asset) where T : ComponentBase
    Convenience wrapper: calls asset.AddComponent() to add a ComponentBase-derived component to a PrefabBase asset and returns it.

  • public static void RemoveComponent<T>(PrefabBase asset) where T : ComponentBase
    Generic wrapper that calls RemoveComponent(asset, typeof(T)).

  • public static void RemoveComponent(PrefabBase asset, Type componentType)
    Removes a prefab component of the given type from the asset: finds the exact component instance via asset.GetComponentExactly, calls asset.Remove(componentType) and destroys the component instance via UnityEngine.Object.DestroyImmediate(componentExactly, allowDestroyingAssets: true). Used to clean prefab components when instance has none.

Usage Example

[Preserve]
protected override void OnCreate()
{
    base.OnCreate();
    // Acquire systems and build query as the source implementation does:
    m_CityConfigurationSystem = World.GetOrCreateSystemManaged<CityConfigurationSystem>();
    m_PrefabSystem = World.GetOrCreateSystemManaged<PrefabSystem>();
    m_SaveInstanceQuery = GetEntityQuery(ComponentType.ReadOnly<Game.Objects.Object>(), ComponentType.ReadOnly<SaveInstance>());
    RequireForUpdate(m_SaveInstanceQuery);
}

Notes / Modding tips: - This system is tightly coupled to Unity.Entities ECS and the game's component types (Game.Objects.SubObject, Game.Net.SubNet, Game.Areas.SubArea, EditorContainer, LocalTransformCache, etc.). Use caution when calling or modifying it in mods — ensure matching type definitions. - NativeArray from ToEntityArray is disposed in a finally block; if copying similar code, ensure proper disposal to avoid leaks. - Removing components from PrefabBase will DestroyImmediate the component object on the asset — this modifies game assets and may require correct asset import/export handling. - CityConfigurationSystem.leftHandTraffic is consulted to flip NetInvertMode; if your mod changes traffic handedness dynamically, consider compatibility.