Esempio n. 1
0
        public override InstanceState GetState()
        {
            ushort node = id.NetNode;

            NodeState state = new NodeState();

            state.instance = this;
            state.Info     = Info;

            state.position      = nodeBuffer[node].m_position;
            state.terrainHeight = TerrainManager.instance.SampleOriginalRawHeightSmooth(state.position);

            state.flags = nodeBuffer[node].m_flags;

            MoveableBuilding pillarInstance = Pillar;

            if (Pillar != null)
            {
                state.pillarState = Pillar.GetState() as BuildingState;
            }

            for (int i = 0; i < 8; i++)
            {
                ushort segment = nodeBuffer[node].GetSegment(i);
                if (segment != 0)
                {
                    state.segmentsSave[i].startDirection = segmentBuffer[segment].m_startDirection;
                    state.segmentsSave[i].endDirection   = segmentBuffer[segment].m_endDirection;
                }
            }

            return(state);
        }
Esempio n. 2
0
        // For Deletion Undo
        public override Instance Clone(InstanceState instanceState, Dictionary <ushort, ushort> clonedNodes)
        {
            BuildingState state = instanceState as BuildingState;

            MoveableBuilding cloneInstance = null;
            BuildingInfo     info          = state.Info.Prefab as BuildingInfo;

            if (BuildingManager.instance.CreateBuilding(out ushort clone, ref SimulationManager.instance.m_randomizer,
                                                        info, state.position, state.angle,
                                                        state.length, SimulationManager.instance.m_currentBuildIndex))
            {
                SimulationManager.instance.m_currentBuildIndex++;

                InstanceID cloneID = default;
                cloneID.Building = clone;
                cloneInstance    = new MoveableBuilding(cloneID);

                buildingBuffer[clone].m_flags = state.flags;

                if (info.m_subBuildings != null && info.m_subBuildings.Length != 0)
                {
                    Matrix4x4 subMatrix4x = default;
                    subMatrix4x.SetTRS(state.position, Quaternion.AngleAxis(state.angle * Mathf.Rad2Deg, Vector3.down), Vector3.one);
                    for (int i = 0; i < info.m_subBuildings.Length; i++)
                    {
                        BuildingInfo subInfo     = info.m_subBuildings[i].m_buildingInfo;
                        Vector3      subPosition = subMatrix4x.MultiplyPoint(info.m_subBuildings[i].m_position);
                        float        subAngle    = info.m_subBuildings[i].m_angle * 0.0174532924f + state.angle;

                        if (BuildingManager.instance.CreateBuilding(out ushort subClone, ref SimulationManager.instance.m_randomizer,
                                                                    subInfo, subPosition, subAngle, 0, SimulationManager.instance.m_currentBuildIndex))
                        {
                            SimulationManager.instance.m_currentBuildIndex++;
                            if (info.m_subBuildings[i].m_fixedHeight)
                            {
                                buildingBuffer[subClone].m_flags = buildingBuffer[subClone].m_flags | Building.Flags.FixedHeight;
                            }
                        }
                        if (clone != 0 && subClone != 0)
                        {
                            buildingBuffer[clone].m_subBuilding       = subClone;
                            buildingBuffer[subClone].m_parentBuilding = clone;
                            buildingBuffer[subClone].m_flags          = buildingBuffer[subClone].m_flags | Building.Flags.Untouchable;
                            clone = subClone;
                        }
                    }
                }
                cloneInstance.ResetSubInstances();
            }

            return(cloneInstance);
        }
Esempio n. 3
0
        public override void ReplaceInstance(Instance instance)
        {
            base.ReplaceInstance(instance);

            MoveableBuilding building = instance as MoveableBuilding;

            int count = 0;

            foreach (Instance subInstance in building.subInstances)
            {
                subStates[count++].instance = subInstance;
            }
        }
Esempio n. 4
0
        public void FinaliseDrag()
        {
            MoveItTool.dragging = false;

            foreach (InstanceState instanceState in savedStates)
            {
                MoveableBuilding mb = instanceState.instance as MoveableBuilding;
                if (mb != null)
                {
                    mb.FinaliseDrag();
                }
            }
        }
Esempio n. 5
0
        public void InitialiseDrag()
        {
            MoveItTool.dragging = true;

            foreach (InstanceState instanceState in m_states)
            {
                MoveableBuilding mb = instanceState.instance as MoveableBuilding;
                if (mb != null)
                {
                    mb.InitialiseDrag();
                }
            }
        }
Esempio n. 6
0
        public override void SetHeight(float height)
        {
            Vector3 newPosition = position;

            MoveableBuilding nodePillar = Pillar;

            if (nodePillar != null)
            {
                Vector3 subPosition = nodePillar.position;
                subPosition.y = subPosition.y - newPosition.y + height;

                nodePillar.Move(subPosition, nodePillar.angle);
            }

            newPosition.y = height;
            Move(newPosition, angle);
        }
Esempio n. 7
0
        public override Instance Clone(InstanceState instanceState, ref Matrix4x4 matrix4x, float deltaHeight, float deltaAngle, Vector3 center, bool followTerrain, Dictionary <ushort, ushort> clonedNodes)
        {
            BuildingState state = instanceState as BuildingState;

            Vector3 newPosition = matrix4x.MultiplyPoint(state.position - center);

            newPosition.y = state.position.y + deltaHeight;

            float terrainHeight = TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition);

            if (followTerrain)
            {
                newPosition.y = newPosition.y + terrainHeight - state.terrainHeight;
            }
            MoveableBuilding cloneInstance = null;
            BuildingInfo     info          = state.info as BuildingInfo;

            float newAngle = state.angle + deltaAngle;

            if (BuildingManager.instance.CreateBuilding(out ushort clone, ref SimulationManager.instance.m_randomizer,
                                                        info, newPosition, newAngle,
                                                        state.length, SimulationManager.instance.m_currentBuildIndex))
            {
                SimulationManager.instance.m_currentBuildIndex++;

                InstanceID cloneID = default(InstanceID);
                cloneID.Building = clone;
                cloneInstance    = new MoveableBuilding(cloneID);

                if ((state.flags & Building.Flags.Completed) != Building.Flags.None)
                {
                    buildingBuffer[clone].m_flags = buildingBuffer[clone].m_flags | Building.Flags.Completed;
                }
                if ((state.flags & Building.Flags.FixedHeight) != Building.Flags.None)
                {
                    buildingBuffer[clone].m_flags = buildingBuffer[clone].m_flags | Building.Flags.FixedHeight;
                }

                // TODO: when should the flag be set?
                if (Mathf.Abs(terrainHeight - newPosition.y) > 0.01f)
                {
                    AddFixedHeightFlag(clone);
                }
                else
                {
                    RemoveFixedHeightFlag(clone);
                }

                if (info.m_subBuildings != null && info.m_subBuildings.Length != 0)
                {
                    Matrix4x4 subMatrix4x = default(Matrix4x4);
                    subMatrix4x.SetTRS(newPosition, Quaternion.AngleAxis(newAngle * Mathf.Rad2Deg, Vector3.down), Vector3.one);
                    for (int i = 0; i < info.m_subBuildings.Length; i++)
                    {
                        BuildingInfo subInfo     = info.m_subBuildings[i].m_buildingInfo;
                        Vector3      subPosition = subMatrix4x.MultiplyPoint(info.m_subBuildings[i].m_position);
                        float        subAngle    = info.m_subBuildings[i].m_angle * 0.0174532924f + newAngle;

                        if (BuildingManager.instance.CreateBuilding(out ushort subClone, ref SimulationManager.instance.m_randomizer,
                                                                    subInfo, subPosition, subAngle, 0, SimulationManager.instance.m_currentBuildIndex))
                        {
                            SimulationManager.instance.m_currentBuildIndex++;
                            if (info.m_subBuildings[i].m_fixedHeight)
                            {
                                buildingBuffer[subClone].m_flags = buildingBuffer[subClone].m_flags | Building.Flags.FixedHeight;
                            }
                        }
                        if (clone != 0 && subClone != 0)
                        {
                            buildingBuffer[clone].m_subBuilding       = subClone;
                            buildingBuffer[subClone].m_parentBuilding = clone;
                            buildingBuffer[subClone].m_flags          = buildingBuffer[subClone].m_flags | Building.Flags.Untouchable;
                            clone = subClone;
                        }
                    }
                }
            }

            return(cloneInstance);
        }
Esempio n. 8
0
        protected HashSet <InstanceState> ProcessPillars(HashSet <InstanceState> states, bool makeClone)
        {
            if (!MoveItTool.advancedPillarControl)
            {
                return(states);
            }

            HashSet <ushort> nodesWithAttachments = new HashSet <ushort>();

            var watch = new System.Diagnostics.Stopwatch();

            watch.Start();
            foreach (InstanceState instanceState in states)
            {
                if (instanceState is NodeState ns && ((NetNode)(ns.instance.data)).m_building > 0 &&
                    ((buildingBuffer[((NetNode)(ns.instance.data)).m_building].m_flags & Building.Flags.Hidden) != Building.Flags.Hidden))
                {
                    nodesWithAttachments.Add(ns.instance.id.NetNode);
                    //Debug.Log($"Node {ns.instance.id.NetNode} found");
                }
            }
            HashSet <InstanceState> newStates = new HashSet <InstanceState>(states);

            foreach (InstanceState instanceState in states)
            {
                ushort buildingId = instanceState.instance.id.Building;
                if (instanceState is BuildingState originalState && MoveItTool.m_pillarMap.ContainsKey(buildingId) && MoveItTool.m_pillarMap[buildingId] > 0)
                {
                    ushort nodeId = MoveItTool.m_pillarMap[buildingId];
                    if (nodesWithAttachments.Contains(nodeId)) // The node is also selected
                    {
                        //Debug.Log($"Pillar {buildingId} for selected node {nodeId}");
                        continue;
                    }
                    MoveableBuilding original = (MoveableBuilding)instanceState.instance;
                    buildingBuffer[buildingId].m_flags |= Building.Flags.Hidden;
                    selection.Remove(original);
                    newStates.Remove(originalState);
                    BuildingState cloneState = null;
                    if (makeClone)
                    {
                        MoveableBuilding clone = original.Duplicate();
                        selection.Add(clone);
                        cloneState = (BuildingState)clone.SaveToState();
                        newStates.Add(cloneState);
                        Debug.Log($"Pillar {buildingId} for node {nodeId} duplicated to {clone.id.Building}");
                    }
                    else
                    {
                        Debug.Log($"Pillar {buildingId} for node {nodeId} hidden");
                    }
                    pillarsOriginalToClone.Add(originalState, cloneState);
                    original.isHidden = true;
                }
            }
            if (pillarsOriginalToClone.Count > 0)
            {
                MoveItTool.UpdatePillarMap();
            }
            states = newStates;
            watch.Stop();
            Debug.Log($"Pillars handled in {watch.ElapsedMilliseconds} ms\nSelected nodes:{nodesWithAttachments.Count}, total selection:{states.Count}, dups mapped:{pillarsOriginalToClone.Count}");
            PillarsProcessed = true;

            return(states);
        }
Esempio n. 9
0
        public void UndoImplementation(bool reset = false)
        {
            if (m_states == null)
            {
                return;
            }

            Dictionary <Instance, Instance> toReplace   = new Dictionary <Instance, Instance>();
            Dictionary <ushort, ushort>     clonedNodes = new Dictionary <ushort, ushort>();

            Building[] buildingBuffer = BuildingManager.instance.m_buildings.m_buffer;

            // Recreate nodes
            foreach (InstanceState state in m_states)
            {
                if (state.instance.id.Type == InstanceType.NetNode)
                {
                    Instance clone = state.instance.Clone(state, null);
                    toReplace.Add(state.instance, clone);
                    clonedNodes.Add(state.instance.id.NetNode, clone.id.NetNode);
                    ActionQueue.instance.UpdateNodeIdInStateHistory(state.instance.id.NetNode, clone.id.NetNode);
                }
            }

            // Recreate everything except nodes and segments
            foreach (InstanceState state in m_states)
            {
                if (state.instance.id.Type == InstanceType.NetNode)
                {
                    continue;
                }
                if (state.instance.id.Type == InstanceType.NetSegment)
                {
                    continue;
                }
                if (state is ProcState)
                {
                    continue;
                }

                Instance clone = state.instance.Clone(state, clonedNodes);
                toReplace.Add(state.instance, clone);

                if (state.instance.id.Type == InstanceType.Prop)
                {
                    PropManager.instance.m_props.m_buffer[clone.id.Prop].FixedHeight = ((PropState)state).fixedHeight;
                }
                else if (state.instance.id.Type == InstanceType.Building)
                {
                    // Add attached nodes to the clonedNode list so other segments reconnect
                    BuildingState buildingState = state as BuildingState;
                    List <ushort> origNodeIds   = new List <ushort>();

                    MoveableBuilding cb          = clone as MoveableBuilding;
                    ushort           cloneNodeId = ((Building)cb.data).m_netNode;

                    if (reset)
                    {
                        ushort cloneId = cb.id.Building;

                        buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags & ~Building.Flags.BurnedDown;
                        buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags & ~Building.Flags.Collapsed;
                        buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags & ~Building.Flags.Abandoned;
                        buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags | Building.Flags.Active;
                        Thread.Sleep(50);
                    }

                    if (cloneNodeId != 0)
                    {
                        int c = 0;
                        foreach (InstanceState i in buildingState.subStates)
                        {
                            if (i is NodeState ns)
                            {
                                InstanceID instanceID = default;
                                instanceID.RawData = ns.id;
                                origNodeIds.Insert(c++, instanceID.NetNode);
                            }
                        }

                        c = 0;
                        while (cloneNodeId != 0)
                        {
                            ushort origNodeId = origNodeIds[c];

                            NetNode clonedAttachedNode = Singleton <NetManager> .instance.m_nodes.m_buffer[cloneNodeId];
                            if (clonedAttachedNode.Info.GetAI() is TransportLineAI)
                            {
                                cloneNodeId = clonedAttachedNode.m_nextBuildingNode;
                                continue;
                            }

                            if (clonedNodes.ContainsKey(origNodeId))
                            {
                                Debug.Log($"Node #{origNodeId} is already in clone list!");
                            }

                            clonedNodes.Add(origNodeId, cloneNodeId);

                            cloneNodeId = clonedAttachedNode.m_nextBuildingNode;

                            if (++c > 32768)
                            {
                                CODebugBase <LogChannel> .Error(LogChannel.Core, "Nodes: Invalid list detected!\n" + Environment.StackTrace);

                                break;
                            }
                        }
                    }
                }
            }

            // Recreate segments
            foreach (InstanceState state in m_states)
            {
                if (state is SegmentState segmentState)
                {
                    if (!clonedNodes.ContainsKey(segmentState.startNodeId))
                    {
                        InstanceID instanceID = InstanceID.Empty;
                        instanceID.NetNode = segmentState.startNodeId;

                        // Don't clone if node is missing
                        if (!((Instance)instanceID).isValid)
                        {
                            continue;
                        }

                        clonedNodes.Add(segmentState.startNodeId, segmentState.startNodeId);
                    }

                    if (!clonedNodes.ContainsKey(segmentState.endNodeId))
                    {
                        InstanceID instanceID = InstanceID.Empty;
                        instanceID.NetNode = segmentState.endNodeId;

                        // Don't clone if node is missing
                        if (!((Instance)instanceID).isValid)
                        {
                            continue;
                        }

                        clonedNodes.Add(segmentState.endNodeId, segmentState.endNodeId);
                    }

                    Instance clone = state.instance.Clone(state, clonedNodes);
                    toReplace.Add(state.instance, clone);
                    MoveItTool.NS.SetSegmentModifiers(clone.id.NetSegment, segmentState);
                }
            }

            if (replaceInstances)
            {
                ReplaceInstances(toReplace);
                ActionQueue.instance.ReplaceInstancesBackward(toReplace);

                selection = new HashSet <Instance>();
                foreach (Instance i in m_oldSelection)
                {
                    if (i is MoveableProc)
                    {
                        continue;
                    }
                    selection.Add(i);
                }
                MoveItTool.m_debugPanel.UpdatePanel();
            }
        }
Esempio n. 10
0
        public void UndoImplementation(bool reset = false)
        {
            if (m_states == null)
            {
                return;
            }

            Dictionary <Instance, Instance> toReplace   = new Dictionary <Instance, Instance>();
            Dictionary <ushort, ushort>     clonedNodes = new Dictionary <ushort, ushort>();

            var stateToClone           = new Dictionary <InstanceState, Instance>();
            var InstanceID_origToClone = new Dictionary <InstanceID, InstanceID>();

            Building[] buildingBuffer = BuildingManager.instance.m_buildings.m_buffer;

            // Recreate nodes
            foreach (InstanceState state in m_states)
            {
                try
                {
                    if (state.instance.id.Type == InstanceType.NetNode)
                    {
                        Instance clone = state.instance.Clone(state, null);
                        toReplace.Add(state.instance, clone);
                        stateToClone.Add(state, clone);
                        InstanceID_origToClone.Add(state.instance.id, clone.id);
                        clonedNodes.Add(state.instance.id.NetNode, clone.id.NetNode);
                        ActionQueue.instance.UpdateNodeIdInStateHistory(state.instance.id.NetNode, clone.id.NetNode);
                    }
                }
                catch (Exception e)
                {
                    Debug.Log($"Undo Bulldoze failed on {(state is InstanceState ? state.prefabName : "unknown")}\n{e}");
                }
            }

            // Recreate everything except nodes and segments
            foreach (InstanceState state in m_states)
            {
                try
                {
                    if (state.instance.id.Type == InstanceType.NetNode)
                    {
                        continue;
                    }
                    if (state.instance.id.Type == InstanceType.NetSegment)
                    {
                        continue;
                    }
                    if (state is ProcState)
                    {
                        continue;
                    }

                    Instance clone = state.instance.Clone(state, clonedNodes);
                    toReplace.Add(state.instance, clone);
                    stateToClone.Add(state, clone);
                    InstanceID_origToClone.Add(state.instance.id, clone.id);

                    if (state.instance.id.Type == InstanceType.Prop)
                    {
                        PropManager.instance.m_props.m_buffer[clone.id.Prop].FixedHeight = ((PropState)state).fixedHeight;
                    }
                    else if (state.instance.id.Type == InstanceType.Building)
                    {
                        // Add attached nodes to the clonedNode list so other segments reconnect
                        BuildingState buildingState = state as BuildingState;
                        List <ushort> origNodeIds   = new List <ushort>();

                        MoveableBuilding cb          = clone as MoveableBuilding;
                        ushort           cloneNodeId = ((Building)cb.data).m_netNode;

                        if (reset)
                        {
                            ushort cloneId = cb.id.Building;

                            buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags & ~Building.Flags.BurnedDown;
                            buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags & ~Building.Flags.Collapsed;
                            buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags & ~Building.Flags.Abandoned;
                            buildingBuffer[cloneId].m_flags = buildingBuffer[cloneId].m_flags | Building.Flags.Active;
                            Thread.Sleep(50);
                        }

                        if (cloneNodeId != 0)
                        {
                            int c = 0;
                            foreach (InstanceState i in buildingState.subStates)
                            {
                                if (i is NodeState ns)
                                {
                                    InstanceID instanceID = default;
                                    instanceID.RawData = ns.id;
                                    origNodeIds.Insert(c++, instanceID.NetNode);
                                }
                            }

                            c = 0;
                            while (cloneNodeId != 0)
                            {
                                ushort origNodeId = origNodeIds[c];

                                NetNode clonedAttachedNode = Singleton <NetManager> .instance.m_nodes.m_buffer[cloneNodeId];
                                if (clonedAttachedNode.Info.GetAI() is TransportLineAI)
                                {
                                    cloneNodeId = clonedAttachedNode.m_nextBuildingNode;
                                    continue;
                                }

                                if (clonedNodes.ContainsKey(origNodeId))
                                {
                                    Debug.Log($"Node #{origNodeId} is already in clone list!");
                                }

                                clonedNodes.Add(origNodeId, cloneNodeId);

                                cloneNodeId = clonedAttachedNode.m_nextBuildingNode;

                                if (++c > 32768)
                                {
                                    CODebugBase <LogChannel> .Error(LogChannel.Core, "Nodes: Invalid list detected!\n" + Environment.StackTrace);

                                    break;
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Debug.Log($"Undo Bulldoze failed on {(state is InstanceState ? state.prefabName : "unknown")}\n{e}");
                }
            }

            // Recreate segments
            foreach (InstanceState state in m_states)
            {
                try
                {
                    if (state is SegmentState segmentState)
                    {
                        if (!clonedNodes.ContainsKey(segmentState.startNodeId))
                        {
                            InstanceID instanceID = InstanceID.Empty;
                            instanceID.NetNode = segmentState.startNodeId;

                            // Don't clone if node is missing
                            if (!((Instance)instanceID).isValid)
                            {
                                continue;
                            }

                            clonedNodes.Add(segmentState.startNodeId, segmentState.startNodeId);
                        }

                        if (!clonedNodes.ContainsKey(segmentState.endNodeId))
                        {
                            InstanceID instanceID = InstanceID.Empty;
                            instanceID.NetNode = segmentState.endNodeId;

                            // Don't clone if node is missing
                            if (!((Instance)instanceID).isValid)
                            {
                                continue;
                            }

                            clonedNodes.Add(segmentState.endNodeId, segmentState.endNodeId);
                        }

                        Instance clone = state.instance.Clone(state, clonedNodes);
                        toReplace.Add(state.instance, clone);
                        stateToClone.Add(state, clone);
                        InstanceID_origToClone.Add(state.instance.id, clone.id);
                        MoveItTool.NS.SetSegmentModifiers(clone.id.NetSegment, segmentState);
                    }
                }
                catch (Exception e)
                {
                    Debug.Log($"Undo Bulldoze failed on {(state is InstanceState ? state.prefabName : "unknown")}\n{e}");
                }
            }

            // clone integrations.
            foreach (var item in stateToClone)
            {
                foreach (var data in item.Key.IntegrationData)
                {
                    try
                    {
                        data.Key.Paste(item.Value.id, data.Value, InstanceID_origToClone);
                    }
                    catch (Exception e)
                    {
                        InstanceID sourceInstanceID = item.Key.instance.id;
                        InstanceID targetInstanceID = item.Value.id;
                        Debug.LogError($"integration {data.Key} Failed to paste from " +
                                       $"{sourceInstanceID.Type}:{sourceInstanceID.Index} to {targetInstanceID.Type}:{targetInstanceID.Index}");
                        DebugUtils.LogException(e);
                    }
                }
            }

            if (replaceInstances)
            {
                ReplaceInstances(toReplace);
                ActionQueue.instance.ReplaceInstancesBackward(toReplace);

                selection = new HashSet <Instance>();
                foreach (Instance i in m_oldSelection)
                {
                    if (i is MoveableProc)
                    {
                        continue;
                    }
                    selection.Add(i);
                }
                MoveItTool.m_debugPanel.UpdatePanel();
            }

            // Does not check MoveItTool.advancedPillarControl, because even if disabled now advancedPillarControl may have been active earlier in action queue
            foreach (KeyValuePair <BuildingState, BuildingState> pillarClone in pillarsOriginalToClone)
            {
                BuildingState originalState = pillarClone.Key;
                originalState.instance.isHidden = false;
                buildingBuffer[originalState.instance.id.Building].m_flags &= ~Building.Flags.Hidden;
                selection.Add(originalState.instance);
                m_states.Add(originalState);
            }
            if (pillarsOriginalToClone.Count > 0)
            {
                MoveItTool.UpdatePillarMap();
            }
        }
Esempio n. 11
0
        public override void Undo()
        {
            if (m_states == null)
            {
                return;
            }

            Dictionary <Instance, Instance> toReplace   = new Dictionary <Instance, Instance>();
            Dictionary <ushort, ushort>     clonedNodes = new Dictionary <ushort, ushort>();

            // Recreate nodes
            foreach (InstanceState state in m_states)
            {
                if (state.instance.id.Type == InstanceType.NetNode)
                {
                    Instance clone = state.instance.Clone(state, null);
                    toReplace.Add(state.instance, clone);
                    clonedNodes.Add(state.instance.id.NetNode, clone.id.NetNode);
                    ActionQueue.instance.UpdateNodeIdInStateHistory(state.instance.id.NetNode, clone.id.NetNode);
                    //Debug.Log($"Cloned N:{state.instance.id.NetNode}->{clone.id.NetNode}");
                }
            }

            // Recreate everything except nodes and segments
            foreach (InstanceState state in m_states)
            {
                if (state.instance.id.Type == InstanceType.NetNode)
                {
                    continue;
                }
                if (state.instance.id.Type == InstanceType.NetSegment)
                {
                    continue;
                }

                Instance clone = state.instance.Clone(state, clonedNodes);
                toReplace.Add(state.instance, clone);

                // Add attached nodes to the clonedNode list so other segments reconnect
                if (state.instance.id.Type == InstanceType.Building)
                {
                    BuildingState buildingState = state as BuildingState;
                    List <ushort> origNodeIds   = new List <ushort>();

                    MoveableBuilding cb          = clone as MoveableBuilding;
                    ushort           cloneNodeId = ((Building)cb.data).m_netNode;

                    if (cloneNodeId != 0)
                    {
                        int    c    = 0;
                        string msg2 = "Original attached nodes:";
                        foreach (InstanceState i in buildingState.subStates)
                        {
                            if (i is NodeState ns)
                            {
                                InstanceID instanceID = default(InstanceID);
                                instanceID.RawData = ns.id;
                                //msg2 += $"\n{c} - Attached node #{instanceID.NetNode}: {ns.Info.Name}";
                                origNodeIds.Insert(c++, instanceID.NetNode);
                            }
                        }
                        //Debug.Log(msg2);

                        c    = 0;
                        msg2 = "";
                        while (cloneNodeId != 0)
                        {
                            ushort origNodeId = origNodeIds[c];

                            NetNode clonedAttachedNode = NetManager.instance.m_nodes.m_buffer[cloneNodeId];
                            if (clonedAttachedNode.Info.GetAI() is TransportLineAI)
                            {
                                cloneNodeId = clonedAttachedNode.m_nextBuildingNode;
                                continue;
                            }

                            if (clonedNodes.ContainsKey(origNodeId))
                            {
                                Debug.Log($"Node #{origNodeId} is already in clone list!");
                            }

                            clonedNodes.Add(origNodeId, cloneNodeId);

                            msg2       += $"\n{c} - {origNodeId} -> {cloneNodeId} {clonedAttachedNode.Info.GetAI()}";
                            cloneNodeId = clonedAttachedNode.m_nextBuildingNode;

                            if (++c > 32768)
                            {
                                CODebugBase <LogChannel> .Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);

                                break;
                            }
                        }
                        Debug.Log(msg2);
                    }
                }
            }

            //string msg = "Cloned Nodes:\n";
            //foreach (KeyValuePair<ushort, ushort> kvp in clonedNodes)
            //{
            //    msg += $"{kvp.Key} => {kvp.Value}\n";
            //}
            //Debug.Log(msg);

            // Recreate segments
            foreach (InstanceState state in m_states)
            {
                if (state.instance.id.Type == InstanceType.NetSegment)
                {
                    SegmentState segState = state as SegmentState;

                    if (!clonedNodes.ContainsKey(segState.startNode))
                    {
                        InstanceID instanceID = InstanceID.Empty;
                        instanceID.NetNode = segState.startNode;

                        // Don't clone if node is missing
                        if (!((Instance)instanceID).isValid)
                        {
                            continue;
                        }

                        clonedNodes.Add(segState.startNode, segState.startNode);
                    }

                    if (!clonedNodes.ContainsKey(segState.endNode))
                    {
                        InstanceID instanceID = InstanceID.Empty;
                        instanceID.NetNode = segState.endNode;

                        // Don't clone if node is missing
                        if (!((Instance)instanceID).isValid)
                        {
                            continue;
                        }

                        clonedNodes.Add(segState.endNode, segState.endNode);
                    }

                    Instance clone = state.instance.Clone(state, clonedNodes);
                    toReplace.Add(state.instance, clone);
                }
            }

            if (replaceInstances)
            {
                ReplaceInstances(toReplace);
                ActionQueue.instance.ReplaceInstancesBackward(toReplace);

                selection = m_oldSelection;
            }
        }