<|start_filename|>MoveIt/MIT_ToolManagement.cs<|end_filename|>
﻿using ColossalFramework;
using ColossalFramework.Math;
using ColossalFramework.IO;
using ColossalFramework.Plugins;
using ColossalFramework.UI;
using MoveItIntegration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Serialization;
using UnityEngine;

namespace MoveIt
{
    public partial class MoveItTool : ToolBase
    {
        public static void SetToolState(ToolStates state = ToolStates.Default, MT_Tools tool = MT_Tools.Off, ushort toolPhase = 0)
        {
            ToolStates previousState = ToolState;
            if (ToolState != state)
            {
                if (state != ToolStates.ToolActive && state != ToolStates.Aligning)
                {
                    UIMoreTools.m_activeToolMenu = null;
                }

                if (ToolState == ToolStates.ToolActive)
                {
                    if (MT_Tool == MT_Tools.MoveTo)
                    {
                        m_moveToPanel.Visible(false);
                    }
                }
            }

            ToolState = state;
            m_toolsMode = tool;
            m_alignToolPhase = toolPhase;

            if (state == ToolStates.ToolActive || state == ToolStates.Aligning || previousState == ToolStates.ToolActive || previousState == ToolStates.Aligning)
            {
                UIMoreTools.UpdateMoreTools();
            }
            m_debugPanel?.UpdatePanel();
        }

        public void ProcessAligning(MT_Tools mode)
        {
            if (ToolState == ToolStates.Aligning && MT_Tool == mode)
            {
                StopTool();
            }
            else
            {
                StartTool(ToolStates.Aligning, mode);
            }
        }

        public bool StartTool(ToolStates newToolState, MT_Tools mode)
        {
            if (ToolState == ToolStates.Cloning || ToolState == ToolStates.RightDraggingClone)
            {
                StopCloning();
            }

            if (ToolState != ToolStates.Default && ToolState != ToolStates.Aligning && ToolState != ToolStates.ToolActive) return false;

            if (Action.selection.Count == 0) return false;

            SetToolState(newToolState, mode, 1);
            UIMoreTools.CheckCloseMenu();
            return true;
        }

        // Called when a tool might not be active
        public void StopTool()
        {
            if (ToolState != ToolStates.Aligning && ToolState != ToolStates.ToolActive) return;

            DeactivateTool();
        }

        public bool DeactivateTool()
        {
            if (MT_Tool == MT_Tools.MoveTo)
            {
                m_moveToPanel.Visible(false);
            }

            SetToolState();
            Action.UpdateArea(Action.GetTotalBounds(false));
            return false;
        }

        public void StartCloning()
        {
            lock (ActionQueue.instance)
            {
                if (ToolState != ToolStates.Default && ToolState != ToolStates.Aligning) return;

                if (Action.selection.Count > 0)
                {
                    CloneAction action = new CloneAction();

                    if (action.Count > 0)
                    {
                        UpdateSensitivityMode();

                        m_sensitivityTogglePosAbs = m_clickPositionAbs = action.center;

                        ActionQueue.instance.Push(action);

                        SetToolState(ToolStates.Cloning);
                        UIToolOptionPanel.RefreshCloneButton();
                        UIToolOptionPanel.RefreshAlignHeightButton();
                    }
                }
            }
        }

        public void StopCloning()
        {
            lock (ActionQueue.instance)
            {
                if (ToolState == ToolStates.Cloning || ToolState == ToolStates.RightDraggingClone)
                {
                    ProcessSensitivityMode(false);

                    ActionQueue.instance.Undo();
                    ActionQueue.instance.Invalidate();
                    SetToolState();

                    UIToolOptionPanel.RefreshCloneButton();
                }
            }
        }

        public void StartBulldoze()
        {
            if (ToolState != ToolStates.Default) return;

            if (Action.selection.Count > 0)
            {
                lock (ActionQueue.instance)
                {
                    ActionQueue.instance.Push(new BulldozeAction());
                }
                m_nextAction = ToolAction.Do;
            }
        }

        public void StartReset()
        {
            if (ToolState != ToolStates.Default) return;

            if (Action.selection.Count > 0)
            {
                lock (ActionQueue.instance)
                {
                    ActionQueue.instance.Push(new ResetAction());
                }
                m_nextAction = ToolAction.Do;
            }
        }

    }
}


<|start_filename|>MoveIt/Moveable/MoveableSegment.cs<|end_filename|>
﻿using ColossalFramework;
using ColossalFramework.Math;
using JetBrains.Annotations;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
using UnityEngine;

namespace MoveIt
{
    public class SegmentState : InstanceState
    {
        public ushort startNodeId;
        public ushort endNodeId;

        public Vector3 startPosition;
        public Vector3 endPosition;

        public Vector3 startDirection;
        public Vector3 endDirection;

        public bool smoothStart;
        public bool smoothEnd;

        public bool invert;

        [XmlIgnore]
        public object NS_Modifiers;

        [UsedImplicitly]
        public string NS_ModifiersBase64
        {
            get
            {
                return MoveItTool.NS.EncodeModifiers(NS_Modifiers);
            }
            set
            {
                NS_Modifiers = MoveItTool.NS.DecodeModifiers(value);
            }
        }

        [XmlIgnore]
        public List<uint> LaneIDs;

        [UsedImplicitly]
        public string LaneIDsBase64
        {
            get => EncodeUtil.Encode64(LaneIDs);
            set => LaneIDs = EncodeUtil.Decode64(value) as List<uint>;
        }

        public override void ReplaceInstance(Instance instance)
        {
            base.ReplaceInstance(instance);

            NetSegment[] segmentBuffer = NetManager.instance.m_segments.m_buffer;

            startNodeId = segmentBuffer[instance.id.NetSegment].m_startNode;
            endNodeId = segmentBuffer[instance.id.NetSegment].m_endNode;
        }

        // Move It <= 2.6 compatiblity
        public ushort startNode
        {
            get => startNodeId;
            set => startNodeId = value;
        }
        public ushort endNode
        {
            get => endNodeId;
            set => endNodeId = value;
        }
    }

    public class MoveableSegment : Instance
    {
        public override HashSet<ushort> segmentList
        {
            get
            {
                return new HashSet<ushort>{id.NetSegment};
            }
        }

        public MoveableSegment(InstanceID instanceID) : base(instanceID)
        {
            Info = new Info_Prefab(NetManager.instance.m_segments.m_buffer[instanceID.NetSegment].Info);
        }

        public override InstanceState SaveToState(bool integrate = true)
        {
            ushort segment = id.NetSegment;

            SegmentState state = new SegmentState
            {
                instance = this,
                Info = Info,

                position = GetControlPoint(),

                startNodeId = segmentBuffer[segment].m_startNode,
                endNodeId = segmentBuffer[segment].m_endNode,

                startDirection = segmentBuffer[segment].m_startDirection,
                endDirection = segmentBuffer[segment].m_endDirection,

                NS_Modifiers = MoveItTool.NS.GetSegmentModifiers(segment),
               
                LaneIDs = GetLaneIds(segment),

                isCustomContent = Info.Prefab.m_isCustomContent
            };

            state.startPosition = nodeBuffer[state.startNodeId].m_position;
            state.endPosition = nodeBuffer[state.endNodeId].m_position;

            state.smoothStart = ((nodeBuffer[state.startNodeId].m_flags & NetNode.Flags.Middle) != NetNode.Flags.None);
            state.smoothEnd = ((nodeBuffer[state.endNodeId].m_flags & NetNode.Flags.Middle) != NetNode.Flags.None);

            state.invert = ((segmentBuffer[segment].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.Invert);

            state.SaveIntegrations(integrate);

            return state;
        }

        public static List<uint> GetLaneIds(ushort segmentId)
        {
            if (segmentBuffer[segmentId].Info == null)
            {
                Log.Error("null info: potentially cuased by missing assets");
                return null;
            }

            var lanes = segmentBuffer[segmentId].Info.m_lanes;
            List<uint> ret = new List<uint>(lanes.Length);
            uint laneId = segmentBuffer[segmentId].m_lanes;
            for (int laneIndex = 0; laneIndex < lanes.Length && laneId !=0; ++laneIndex)
            {
                ret.Add(laneId);
                laneId = laneBuffer[laneId].m_nextLane;
            }
            return ret; 
        }

        public override void LoadFromState(InstanceState state)
        {
            if (!(state is SegmentState segmentState)) return;

            ushort segment = id.NetSegment;

            segmentBuffer[segment].m_startDirection = segmentState.startDirection;
            segmentBuffer[segment].m_endDirection = segmentState.endDirection;

            UpdateSegmentBlocks(segment, ref segmentBuffer[segment]);

            netManager.UpdateNode(segmentBuffer[segment].m_startNode);
            netManager.UpdateNode(segmentBuffer[segment].m_endNode);

            MoveItTool.NS.SetSegmentModifiers(segment, segmentState);
        }

        public override Vector3 position
        {
            get
            {
                if (id.IsEmpty) return Vector3.zero;
                return GetControlPoint();
            }
            set { }
        }

        public override float angle
        {
            get { return 0f; }
            set { }
        }

        private MoveableNode _startNode = null;
        public MoveableNode StartNode
        {
            get
            {
                InstanceID instanceID = default;
                instanceID.NetNode = segmentBuffer[id.NetSegment].m_startNode;
                _startNode = GetNodeFromSelection(instanceID);
                if (_startNode == null)
                {
                    _startNode = new MoveableNode(instanceID);
                }

                return _startNode;
            }
        }

        private MoveableNode _endNode = null;
        public MoveableNode EndNode
        {
            get
            {
                InstanceID instanceID = default;
                instanceID.NetNode = segmentBuffer[id.NetSegment].m_endNode;
                _endNode = GetNodeFromSelection(instanceID);
                if (_endNode == null)
                {
                    _endNode = new MoveableNode(instanceID);
                }

                return _endNode;
            }
        }

        public override bool isValid
        {
            get
            {
                if (id.IsEmpty) return false;
                return (segmentBuffer[id.NetSegment].m_flags & NetSegment.Flags.Created) != NetSegment.Flags.None;
            }
        }

        internal override void InitialiseTransform()
        {
            TransformPosition = GetControlPointReal();
        }

        public static bool isSegmentValid(ushort segmentId)
        {
            return (segmentBuffer[segmentId].m_flags & NetSegment.Flags.Created) != NetSegment.Flags.None;
        }

        public override void Transform(InstanceState state, ref Matrix4x4 matrix4x, float deltaHeight, float deltaAngle, Vector3 center, bool followTerrain)
        {
            Vector3 newPosition = matrix4x.MultiplyPoint(state.position - center);

            Move(newPosition, 0);
        }

        public override void Move(Vector3 location, float angle)
        {
            if (!isValid) return;

            TransformPosition = location;

            if (!isVirtual())
            {
                ushort segment = id.NetSegment;
                ushort startNode = segmentBuffer[segment].m_startNode;
                ushort endNode = segmentBuffer[segment].m_endNode;

                segmentBuffer[segment].m_startDirection = location - nodeBuffer[startNode].m_position;
                segmentBuffer[segment].m_endDirection = location - nodeBuffer[endNode].m_position;

                CalculateSegmentDirections(ref segmentBuffer[segment], segment);

                netManager.UpdateSegmentRenderer(segment, true);
                UpdateSegmentBlocks(segment, ref segmentBuffer[segment]);

                netManager.UpdateNode(startNode);
                netManager.UpdateNode(endNode);
            }
        }

        public MoveableNode GetNodeByDistance(bool furthest = false)
        {
            Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
            Vector3 clickPos = MoveItTool.RaycastMouseLocation(mouseRay);
            if (Vector3.Distance(StartNode.position, clickPos) < Vector3.Distance(EndNode.position, clickPos))
            {
                if (furthest)
                {
                    return EndNode;
                }
                else
                {
                    return StartNode;
                }
            }
            else
            {
                if (furthest)
                {
                    return StartNode;
                }
                else
                {
                    return EndNode;
                }
            }
        }

        public override void SetHeight(float height) { }
        public override void SetHeight() { }

        public override Instance Clone(InstanceState instanceState, ref Matrix4x4 matrix4x, float deltaHeight, float deltaAngle, Vector3 center, bool followTerrain, Dictionary<ushort, ushort> clonedNodes, Action action)
        {
            SegmentState state = instanceState as SegmentState;

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

            Instance cloneInstance = null;

            ushort startNodeId = state.startNodeId;
            ushort endNodeId = state.endNodeId;

            // Nodes should exist
            try
            {
                startNodeId = clonedNodes[startNodeId];
                endNodeId = clonedNodes[endNodeId];
            }
            catch (KeyNotFoundException)
            {
                //Log.Debug($"{startNodeId}->{state.startNodeId}, {endNodeId}->{state.endNodeId}");
            }

            Vector3 startDirection = newPosition - nodeBuffer[startNodeId].m_position;
            Vector3 endDirection = newPosition - nodeBuffer[endNodeId].m_position;

            startDirection.y = 0;
            endDirection.y = 0;

            startDirection.Normalize();
            endDirection.Normalize();

            if (netManager.CreateSegment(out ushort clone, ref SimulationManager.instance.m_randomizer, state.Info.Prefab as NetInfo,
                startNodeId, endNodeId, startDirection, endDirection,
                SimulationManager.instance.m_currentBuildIndex, SimulationManager.instance.m_currentBuildIndex,
                state.invert))
            {
                SimulationManager.instance.m_currentBuildIndex++;

                InstanceID cloneID = default;
                cloneID.NetSegment = clone;
                cloneInstance = new MoveableSegment(cloneID);
            }

            return cloneInstance;
        }

        public override Instance Clone(InstanceState instanceState, Dictionary<ushort, ushort> clonedNodes)
        {
            SegmentState state = instanceState as SegmentState;

            Instance cloneInstance = null;

            ushort startNodeId = state.startNodeId;
            ushort endNodeId = state.endNodeId;

            // Nodes should exist
            startNodeId = clonedNodes[startNodeId];
            endNodeId = clonedNodes[endNodeId];

            if (netManager.CreateSegment(out ushort clone, ref SimulationManager.instance.m_randomizer, state.Info.Prefab as NetInfo,
                startNodeId, endNodeId, state.startDirection, state.endDirection,
                SimulationManager.instance.m_currentBuildIndex, SimulationManager.instance.m_currentBuildIndex,
                state.invert))
            {
                SimulationManager.instance.m_currentBuildIndex++;

                InstanceID cloneID = default;
                cloneID.NetSegment = clone;
                cloneInstance = new MoveableSegment(cloneID);
            }

            return cloneInstance;
        }

        public override void Delete()
        {
            if (isValid) NetManager.instance.ReleaseSegment(id.NetSegment, true);
        }

        public override Bounds GetBounds(bool ignoreSegments = true)
        {
            if (Virtual)
            {
                Bounds b = new Bounds(OverlayPosition, Vector3.one);
                b.Encapsulate(StartNode.GetBounds());
                b.Encapsulate(EndNode.GetBounds());
                return b;
            }

            return segmentBuffer[id.NetSegment].m_bounds;
        }

        public override void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color toolColor, Color despawnColor)
        {
            if (!isValid) return;
            if (MoveItTool.m_isLowSensitivity) return;

            ushort segment = id.NetSegment;
            NetInfo netInfo = segmentBuffer[segment].Info;

            ushort startNode = segmentBuffer[segment].m_startNode;
            ushort endNode = segmentBuffer[segment].m_endNode;

            bool smoothStart = (nodeBuffer[startNode].m_flags & NetNode.Flags.Middle) != NetNode.Flags.None;
            bool smoothEnd = (nodeBuffer[endNode].m_flags & NetNode.Flags.Middle) != NetNode.Flags.None;

            Bezier3 bezier;
            bezier.a = StartNode.OverlayPosition;
            bezier.d = EndNode.OverlayPosition;

            Vector3 startDirection = OverlayPosition - bezier.a;
            Vector3 endDirection = OverlayPosition - bezier.d;

            startDirection.y = 0;
            endDirection.y = 0;

            startDirection.Normalize();
            endDirection.Normalize();

            NetSegment.CalculateMiddlePoints(
                bezier.a, startDirection,
                bezier.d, endDirection,
                smoothStart, smoothEnd, out bezier.b, out bezier.c);

            RenderManager.instance.OverlayEffect.DrawBezier(cameraInfo, toolColor, bezier, netInfo.m_halfWidth * 4f / 3f, 100000f, -100000f, -1f, 1280f, false, true);

            Segment3 segment1, segment2;

            segment1.a = StartNode.OverlayPosition;
            segment2.a = EndNode.OverlayPosition;

            segment1.b = GetControlPoint(startDirection, endDirection);
            segment2.b = segment1.b;

            toolColor.a /= 2;

            RenderManager.instance.OverlayEffect.DrawSegment(cameraInfo, toolColor, segment1, segment2, 0, 10f, -1f, 1280f, false, true);
            RenderManager.instance.OverlayEffect.DrawCircle(cameraInfo, toolColor, segment1.b, netInfo.m_halfWidth / 2f, -1f, 1280f, false, true);
        }

        public override void RenderCloneOverlay(InstanceState instanceState, ref Matrix4x4 matrix4x, Vector3 deltaPosition, float deltaAngle, Vector3 center, bool followTerrain, RenderManager.CameraInfo cameraInfo, Color toolColor)
        {
            if (MoveItTool.m_isLowSensitivity) return;

            SegmentState state = instanceState as SegmentState;

            NetInfo netInfo = state.Info.Prefab as NetInfo;

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

            if (followTerrain)
            {
                newPosition.y = newPosition.y - state.terrainHeight + TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition);
            }

            Bezier3 bezier;
            bezier.a = matrix4x.MultiplyPoint(state.startPosition - center);
            bezier.d = matrix4x.MultiplyPoint(state.endPosition - center);

            if (followTerrain)
            {
                bezier.a.y = bezier.a.y + TerrainManager.instance.SampleOriginalRawHeightSmooth(bezier.a) - TerrainManager.instance.SampleOriginalRawHeightSmooth(state.startPosition);
                bezier.d.y = bezier.d.y + TerrainManager.instance.SampleOriginalRawHeightSmooth(bezier.d) - TerrainManager.instance.SampleOriginalRawHeightSmooth(state.endPosition);
            }
            else
            {
                bezier.a.y = state.startPosition.y + deltaPosition.y;
                bezier.d.y = state.endPosition.y + deltaPosition.y;
            }

            Vector3 startDirection = newPosition - bezier.a;
            Vector3 endDirection = newPosition - bezier.d;

            startDirection.y = 0;
            endDirection.y = 0;

            startDirection.Normalize();
            endDirection.Normalize();

            NetSegment.CalculateMiddlePoints(
                bezier.a, startDirection,
                bezier.d, endDirection,
                state.smoothStart, state.smoothEnd, out bezier.b, out bezier.c);

            RenderManager.instance.OverlayEffect.DrawBezier(cameraInfo, toolColor, bezier, netInfo.m_halfWidth * 4f / 3f, 100000f, -100000f, -1f, 1280f, false, true);
        }

        public override void RenderCloneGeometry(InstanceState instanceState, ref Matrix4x4 matrix4x, Vector3 deltaPosition, float deltaAngle, Vector3 center, bool followTerrain, RenderManager.CameraInfo cameraInfo, Color toolColor)
        {
            SegmentState state = instanceState as SegmentState;

            NetInfo netInfo = state.Info.Prefab as NetInfo;

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

            if (followTerrain)
            {
                newPosition.y = newPosition.y - state.terrainHeight + TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition);
            }

            Bezier3 bezier;
            bezier.a = matrix4x.MultiplyPoint(state.startPosition - center);
            bezier.d = matrix4x.MultiplyPoint(state.endPosition - center);

            if (followTerrain)
            {
                bezier.a.y = bezier.a.y + TerrainManager.instance.SampleOriginalRawHeightSmooth(bezier.a) - TerrainManager.instance.SampleOriginalRawHeightSmooth(state.startPosition);
                bezier.d.y = bezier.d.y + TerrainManager.instance.SampleOriginalRawHeightSmooth(bezier.d) - TerrainManager.instance.SampleOriginalRawHeightSmooth(state.endPosition);
            }
            else
            {
                bezier.a.y = state.startPosition.y + deltaPosition.y;
                bezier.d.y = state.endPosition.y + deltaPosition.y;
            }

            Vector3 startDirection = newPosition - bezier.a;
            Vector3 endDirection = newPosition - bezier.d;

            startDirection.y = 0;
            endDirection.y = 0;

            startDirection.Normalize();
            endDirection.Normalize();
            RenderSegment.Invoke(null, new object[] { netInfo, NetSegment.Flags.All, bezier.a, bezier.d, startDirection, -endDirection, state.smoothStart, state.smoothEnd });
        }

        public override void RenderGeometry(RenderManager.CameraInfo cameraInfo, Color toolColor)
        {
            if (!Virtual) return;

            ushort segment = id.NetSegment;
            ushort startNode = segmentBuffer[segment].m_startNode;
            ushort endNode = segmentBuffer[segment].m_endNode;

            bool smoothStart = (nodeBuffer[startNode].m_flags & NetNode.Flags.Middle) != NetNode.Flags.None;
            bool smoothEnd = (nodeBuffer[endNode].m_flags & NetNode.Flags.Middle) != NetNode.Flags.None;

            Bezier3 bezier;
            bezier.a = StartNode.OverlayPosition;
            bezier.d = EndNode.OverlayPosition;

            Vector3 startDirection = OverlayPosition - bezier.a;
            Vector3 endDirection = OverlayPosition - bezier.d;

            startDirection.y = 0;
            endDirection.y = 0;

            startDirection.Normalize();
            endDirection.Normalize();

            NetSegment.CalculateMiddlePoints(
                bezier.a, startDirection,
                bezier.d, endDirection,
                smoothStart, smoothEnd, out bezier.b, out bezier.c);

            // Flip the segment geometry unless the segment is one of inverted
            if ((segmentBuffer[segment].m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.Invert)
            {
                RenderSegment.Invoke(null, new object[] { Info.Prefab as NetInfo, NetSegment.Flags.All, bezier.a, bezier.d, startDirection, -endDirection, smoothStart, smoothEnd });
            }
            else
            {
                RenderSegment.Invoke(null, new object[] { Info.Prefab as NetInfo, NetSegment.Flags.All, bezier.d, bezier.a, endDirection, -startDirection, smoothStart, smoothEnd });
            }
        }

        private Vector3 GetControlPoint()
        {
            return GetControlPoint(segmentBuffer[id.NetSegment].m_startDirection, segmentBuffer[id.NetSegment].m_endDirection);
        }

        private Vector3 GetControlPoint(Vector3 startDir, Vector3 endDir)
        {
            if (Virtual)
            {
                return TransformPosition;
            }

            return GetControlPointReal(startDir, endDir);
        }

        private Vector3 GetControlPointReal()
        {
            return GetControlPointReal(segmentBuffer[id.NetSegment].m_startDirection, segmentBuffer[id.NetSegment].m_endDirection);
        }

        private Vector3 GetControlPointReal(Vector3 startDir, Vector3 endDir)
        { 
            Vector3 startPos = StartNode.OverlayPosition;
            Vector3 endPos = EndNode.OverlayPosition;

            if (!NetSegment.IsStraight(startPos, startDir, endPos, endDir, out float _))
            {
                float dot = startDir.x * endDir.x + startDir.z * endDir.z;
                if (dot >= -0.999f && Line2.Intersect(VectorUtils.XZ(startPos), VectorUtils.XZ(startPos + startDir), VectorUtils.XZ(endPos), VectorUtils.XZ(endPos + endDir), out float u, out float _))
                {
                    return startPos + startDir * u;
                }
                else
                {
                    DebugUtils.Warning("Invalid segment directions!");
                }
            }

            return (startPos + endPos) / 2f;
        }

        private MoveableNode GetNodeFromSelection(InstanceID instanceID)
        {
            foreach (Instance i in Action.selection)
            {
                if (i is MoveableNode mn)
                {
                    if (mn.id.NetNode == instanceID.NetNode)
                    {
                        return mn;
                    }
                }
            }

            return null;
        }

        private static readonly MethodInfo RenderSegment = typeof(NetTool).GetMethod("RenderSegment", BindingFlags.NonPublic | BindingFlags.Static);
    }
}


<|start_filename|>MoveIt/MIT_Filtering.cs<|end_filename|>
﻿using ColossalFramework;
using ColossalFramework.Math;
using System;
using System.Collections.Generic;
using UnityEngine;

namespace MoveIt
{
    public partial class MoveItTool : ToolBase
    {
        static bool _stepProcessed = false;

        private void RaycastHoverInstance(Ray mouseRay)
        {
            Vector3 origin = mouseRay.origin;
            Vector3 normalized = mouseRay.direction.normalized;
            Vector3 vector = mouseRay.origin + normalized * Camera.main.farClipPlane;
            Segment3 ray = new Segment3(origin, vector);

            Building[] buildingBuffer = Singleton<BuildingManager>.instance.m_buildings.m_buffer;
            NetNode[] nodeBuffer = Singleton<NetManager>.instance.m_nodes.m_buffer;
            NetSegment[] segmentBuffer = Singleton<NetManager>.instance.m_segments.m_buffer;
            TreeInstance[] treeBuffer = Singleton<TreeManager>.instance.m_trees.m_buffer;

            Vector3 location = RaycastMouseLocation(mouseRay);

            InstanceID id = InstanceID.Empty;

            ItemClass.Layer itemLayers = GetItemLayers();

            bool selectPicker = false;
            bool selectBuilding = true;
            bool selectProps = true;
            bool selectDecals = true;
            bool selectSurfaces = true;
            bool selectNodes = true;
            bool selectSegments = true;
            bool selectTrees = true;
            bool selectProc = PO.Active;

            if (marqueeSelection)
            {
                selectPicker = filterPicker;
                selectBuilding = filterBuildings;
                selectProps = filterProps;
                selectDecals = filterDecals;
                selectSurfaces = filterSurfaces;
                selectNodes = filterNodes;
                selectSegments = filterSegments;
                selectTrees = filterTrees;
                selectProc = PO.Active && filterProcs;
            }

            if (MT_Tool == MT_Tools.Group || MT_Tool == MT_Tools.Inplace)
            {
                selectNodes = false;
                selectTrees = false;
            }
            else if (MT_Tool == MT_Tools.Mirror)
            {
                selectBuilding = false;
                selectProps = false;
                selectDecals = false;
                selectSurfaces = false;
                selectProc = false;
                selectTrees = false;
                selectNodes = false;
            }

            float smallestDist = 640000f;

            bool repeatSearch;
            do
            {
                if (PO.Active && selectProc)
                {
                    foreach (PO_Object obj in PO.Objects)
                    {
                        if (!obj.isHidden() && stepOver.isValidPO(obj.Id))
                        {
                            float radius = obj.Size / 2;
                            bool inXBounds = obj.Position.x > (location.x - radius) && obj.Position.x < (location.x + radius);
                            bool inZBounds = obj.Position.z > (location.z - radius) && obj.Position.z < (location.z + radius);
                            if (inXBounds && inZBounds)
                            {
                                float t = obj.GetDistance(location);
                                if (t < smallestDist)
                                {
                                    id.NetLane = obj.Id;
                                    smallestDist = t;
                                }
                            }
                        }
                    }
                }

                int gridMinX = Mathf.Max((int)((location.x - 16f) / 64f + 135f) - 1, 0);
                int gridMinZ = Mathf.Max((int)((location.z - 16f) / 64f + 135f) - 1, 0);
                int gridMaxX = Mathf.Min((int)((location.x + 16f) / 64f + 135f) + 1, 269);
                int gridMaxZ = Mathf.Min((int)((location.z + 16f) / 64f + 135f) + 1, 269);

                for (int i = gridMinZ; i <= gridMaxZ; i++)
                {
                    for (int j = gridMinX; j <= gridMaxX; j++)
                    {
                        if (selectBuilding || selectSurfaces || (selectPicker && Filters.Picker.IsBuilding))
                        {
                            ushort building = BuildingManager.instance.m_buildingGrid[i * 270 + j];
                            int count = 0;
                            while (building != 0u)
                            {
                                if (stepOver.isValidB(building) && IsBuildingValid(ref buildingBuffer[building], itemLayers) && buildingBuffer[building].RayCast(building, ray, out float t) && t < smallestDist)
                                {
                                    if (Filters.Filter(buildingBuffer[building].Info, ref buildingBuffer[building], true))
                                    {
                                        id.Building = Building.FindParentBuilding(building);
                                        if (id.Building == 0) id.Building = building;
                                        smallestDist = t;
                                    }
                                }
                                building = buildingBuffer[building].m_nextGridBuilding;

                                if (++count > 49152)
                                {
                                    CODebugBase<LogChannel>.Error(LogChannel.Core, "Buildings: Invalid list detected!\n" + Environment.StackTrace);
                                    break;
                                }
                            }
                        }

                        if (selectProps || selectDecals || selectSurfaces || (selectPicker && Filters.Picker.IsProp))
                        {
                            PropLayer.Manager.RaycastHoverInstance(ref i, ref j, ref stepOver, ref ray, ref smallestDist, ref id);
                        }

                        if (selectNodes || selectBuilding || (selectPicker && Filters.Picker.IsNode))
                        {
                            ushort node = NetManager.instance.m_nodeGrid[i * 270 + j];
                            int count = 0;
                            while (node != 0u)
                            {
                                if (stepOver.isValidN(node) && IsNodeValid(ref nodeBuffer[node], itemLayers) && RayCastNode(ref nodeBuffer[node], ray, -1000f, out float t, out float priority) && t < smallestDist)
                                {
                                    ushort building = 0;
                                    if (!Event.current.alt)
                                    {
                                        building = NetNode.FindOwnerBuilding(node, 363f);
                                    }

                                    if (building != 0)
                                    {
                                        if (selectBuilding)
                                        {
                                            id.Building = Building.FindParentBuilding(building);
                                            if (id.Building == 0) id.Building = building;
                                            smallestDist = t;
                                        }
                                    }
                                    else if (selectNodes || (selectPicker && Filters.Picker.IsNode))
                                    {
                                        if (Filters.Filter(nodeBuffer[node]))
                                        {
                                            id.NetNode = node;
                                            smallestDist = t;
                                        }
                                    }
                                }
                                node = nodeBuffer[node].m_nextGridNode;

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

                        if (selectSegments || selectBuilding || (selectPicker && Filters.Picker.IsSegment))
                        {
                            ushort segment = NetManager.instance.m_segmentGrid[i * 270 + j];
                            int count = 0;
                            while (segment != 0u)
                            {
                                if (stepOver.isValidS(segment) && IsSegmentValid(ref segmentBuffer[segment], itemLayers) &&
                                            segmentBuffer[segment].RayCast(segment, ray, -1000f, false, out float t, out float priority) && t < smallestDist)
                                {
                                    ushort building = 0;
                                    if (!Event.current.alt)
                                    {
                                        building = FindOwnerBuilding(segment, 363f);
                                    }

                                    if (building != 0)
                                    {
                                        if (selectBuilding)
                                        {
                                            id.Building = Building.FindParentBuilding(building);
                                            if (id.Building == 0) id.Building = building;
                                            smallestDist = t;
                                        }
                                    }
                                    else if (selectSegments || (selectPicker && Filters.Picker.IsSegment))
                                    {
                                        if (!selectNodes || (
                                            (!stepOver.isValidN(segmentBuffer[segment].m_startNode) || !RayCastNode(ref nodeBuffer[segmentBuffer[segment].m_startNode], ray, -1000f, out float t2, out priority)) &&
                                            (!stepOver.isValidN(segmentBuffer[segment].m_endNode) || !RayCastNode(ref nodeBuffer[segmentBuffer[segment].m_endNode], ray, -1000f, out t2, out priority))
                                        ))
                                        {
                                            if (Filters.Filter(segmentBuffer[segment]))
                                            {
                                                id.NetSegment = segment;
                                                smallestDist = t;
                                            }
                                        }
                                    }
                                }
                                segment = segmentBuffer[segment].m_nextGridSegment;

                                if (++count > 36864)
                                {
                                    CODebugBase<LogChannel>.Error(LogChannel.Core, "Segments: Invalid list detected!\n" + Environment.StackTrace);
                                    segment = 0;
                                }
                            }
                        }
                    }
                }

                if (selectTrees || (selectPicker && Filters.Picker.IsTree))
                {
                    gridMinX = Mathf.Max((int)((location.x - 8f) / 32f + 270f), 0);
                    gridMinZ = Mathf.Max((int)((location.z - 8f) / 32f + 270f), 0);
                    gridMaxX = Mathf.Min((int)((location.x + 8f) / 32f + 270f), 539);
                    gridMaxZ = Mathf.Min((int)((location.z + 8f) / 32f + 270f), 539);

                    for (int i = gridMinZ; i <= gridMaxZ; i++)
                    {
                        for (int j = gridMinX; j <= gridMaxX; j++)
                        {
                            uint tree = TreeManager.instance.m_treeGrid[i * 540 + j];
                            while (tree != 0)
                            {
                                if (stepOver.isValidT(tree) && treeBuffer[tree].RayCast(tree, ray, out float t, out float targetSqr) && t < smallestDist)
                                {
                                    if (Filters.Filter(treeBuffer[tree].Info))
                                    {
                                        id.Tree = tree;
                                        smallestDist = t;
                                    }
                                }
                                tree = treeBuffer[tree].m_nextGridTree;
                            }
                        }
                    }
                }

                repeatSearch = false;
                if (OptionsKeymapping.stepOverKey.IsPressed())
                {
                    if (!_stepProcessed)
                    {
                        _stepProcessed = true;
                        repeatSearch = true;
                        stepOver.Add(id);
                    }
                }
                else
                {
                    _stepProcessed = false;
                }
            }
            while (repeatSearch);

            m_hoverInstance = id;
            m_debugPanel?.UpdatePanel(id);
            ActionQueue.instance.current?.OnHover();
        }

        private HashSet<Instance> GetMarqueeList(Ray mouseRay)
        {
            HashSet<Instance> list = new HashSet<Instance>();

            Building[] buildingBuffer = Singleton<BuildingManager>.instance.m_buildings.m_buffer;
            NetNode[] nodeBuffer = Singleton<NetManager>.instance.m_nodes.m_buffer;
            NetSegment[] segmentBuffer = Singleton<NetManager>.instance.m_segments.m_buffer;
            TreeInstance[] treeBuffer = Singleton<TreeManager>.instance.m_trees.m_buffer;

            m_selection.a = m_clickPositionAbs;
            m_selection.c = RaycastMouseLocation(mouseRay);

            if (m_selection.a.x == m_selection.c.x && m_selection.a.z == m_selection.c.z)
            {
                m_selection = default;
            }
            else
            {
                float angle = Camera.main.transform.localEulerAngles.y * Mathf.Deg2Rad;
                Vector3 down = new Vector3(Mathf.Cos(angle), 0, -Mathf.Sin(angle));
                Vector3 right = new Vector3(-down.z, 0, down.x);

                Vector3 a = m_selection.c - m_selection.a;
                float dotDown = Vector3.Dot(a, down);
                float dotRight = Vector3.Dot(a, right);

                if ((dotDown > 0 && dotRight > 0) || (dotDown <= 0 && dotRight <= 0))
                {
                    m_selection.b = m_selection.a + dotDown * down;
                    m_selection.d = m_selection.a + dotRight * right;
                }
                else
                {
                    m_selection.b = m_selection.a + dotRight * right;
                    m_selection.d = m_selection.a + dotDown * down;
                }

                Vector3 min = m_selection.Min();
                Vector3 max = m_selection.Max();

                int gridMinX = Mathf.Max((int)((min.x - 16f) / 64f + 135f), 0);
                int gridMinZ = Mathf.Max((int)((min.z - 16f) / 64f + 135f), 0);
                int gridMaxX = Mathf.Min((int)((max.x + 16f) / 64f + 135f), 269);
                int gridMaxZ = Mathf.Min((int)((max.z + 16f) / 64f + 135f), 269);

                InstanceID id = new InstanceID();
                ItemClass.Layer itemLayers = GetItemLayers();

                if (PO.Active && filterProcs)
                {
                    foreach (PO_Object obj in PO.Objects)
                    {
                        if (!obj.isHidden() && PointInRectangle(m_selection, obj.Position))
                        {
                            id.NetLane = obj.Id;
                            list.AddObject(id);
                        }
                    }
                }

                for (int i = gridMinZ; i <= gridMaxZ; i++)
                {
                    for (int j = gridMinX; j <= gridMaxX; j++)
                    {
                        if (filterBuildings || filterSurfaces || (filterPicker && Filters.Picker.IsBuilding))
                        {
                            ushort building = BuildingManager.instance.m_buildingGrid[i * 270 + j];
                            int count = 0;
                            while (building != 0u)
                            {
                                if (IsBuildingValid(ref buildingBuffer[building], itemLayers) && PointInRectangle(m_selection, buildingBuffer[building].m_position))
                                {
                                    if (Filters.Filter(buildingBuffer[building].Info, ref buildingBuffer[building]))
                                    {
                                        id.Building = Building.FindParentBuilding(building);
                                        if (id.Building == 0) id.Building = building;
                                        list.Add(id);
                                    }
                                }
                                building = buildingBuffer[building].m_nextGridBuilding;

                                if (++count > 49152)
                                {
                                    CODebugBase<LogChannel>.Error(LogChannel.Core, "Buildings: Invalid list detected!\n" + Environment.StackTrace);
                                    break;
                                }
                            }
                        }

                        if (filterProps || filterDecals || filterSurfaces || (filterPicker && Filters.Picker.IsProp))
                        {
                            PropLayer.Manager.GetMarqueeList(ref i, ref j, ref id, ref m_selection, ref list);
                        }

                        if (filterNodes || filterBuildings || (filterPicker && Filters.Picker.IsNode))
                        {
                            ushort node = NetManager.instance.m_nodeGrid[i * 270 + j];
                            int count = 0;
                            while (node != 0u)
                            {
                                if (IsNodeValid(ref nodeBuffer[node], itemLayers) && PointInRectangle(m_selection, nodeBuffer[node].m_position))
                                {
                                    ushort building = NetNode.FindOwnerBuilding(node, 363f);

                                    if (building != 0)
                                    {
                                        if (filterBuildings)
                                        {
                                            id.Building = Building.FindParentBuilding(building);
                                            if (id.Building == 0) id.Building = building;
                                            list.Add(id);
                                        }
                                    }
                                    else if (filterNodes || (filterPicker && Filters.Picker.IsNode))
                                    {
                                        if (Filters.Filter(nodeBuffer[node]))
                                        {
                                            id.NetNode = node;
                                            list.Add(id);
                                        }
                                    }
                                }
                                node = nodeBuffer[node].m_nextGridNode;

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

                        if (filterSegments || filterBuildings || (filterPicker && Filters.Picker.IsSegment))
                        {
                            ushort segment = NetManager.instance.m_segmentGrid[i * 270 + j];
                            int count = 0;
                            while (segment != 0u)
                            {
                                if (IsSegmentValid(ref segmentBuffer[segment], itemLayers) && PointInRectangle(m_selection, segmentBuffer[segment].m_bounds.center))
                                {
                                    ushort building = FindOwnerBuilding(segment, 363f);

                                    if (building != 0)
                                    {
                                        if (filterBuildings)
                                        {
                                            id.Building = Building.FindParentBuilding(building);
                                            if (id.Building == 0) id.Building = building;
                                            list.Add(id);
                                        }
                                    }
                                    else if (filterSegments || (filterPicker && Filters.Picker.IsSegment))
                                    {
                                        if (Filters.Filter(segmentBuffer[segment]))
                                        {
                                            id.NetSegment = segment;
                                            list.Add(id);
                                        }
                                    }
                                }
                                segment = segmentBuffer[segment].m_nextGridSegment;

                                if (++count > 36864)
                                {
                                    CODebugBase<LogChannel>.Error(LogChannel.Core, "Segments: Invalid list detected!\n" + Environment.StackTrace);
                                }
                            }
                        }
                    }
                }

                if (filterTrees || (filterPicker && Filters.Picker.IsTree))
                {
                    gridMinX = Mathf.Max((int)((min.x - 8f) / 32f + 270f), 0);
                    gridMinZ = Mathf.Max((int)((min.z - 8f) / 32f + 270f), 0);
                    gridMaxX = Mathf.Min((int)((max.x + 8f) / 32f + 270f), 539);
                    gridMaxZ = Mathf.Min((int)((max.z + 8f) / 32f + 270f), 539);

                    for (int i = gridMinZ; i <= gridMaxZ; i++)
                    {
                        for (int j = gridMinX; j <= gridMaxX; j++)
                        {
                            uint tree = TreeManager.instance.m_treeGrid[i * 540 + j];

                            while (tree != 0)
                            {
                                if (PointInRectangle(m_selection, treeBuffer[tree].Position))
                                {
                                    if (Filters.Filter(treeBuffer[tree].Info))
                                    {
                                        id.Tree = tree;
                                        list.Add(id);
                                    }
                                }
                                tree = treeBuffer[tree].m_nextGridTree;
                            }
                        }
                    }
                }
            }

            return list;
        }

        public static ushort FindOwnerBuilding(ushort segment, float maxDistance)
        {
            Building[] buildingBuffer = BuildingManager.instance.m_buildings.m_buffer;
            ushort[] buildingGrid = BuildingManager.instance.m_buildingGrid;
            NetNode[] nodeBuffer = NetManager.instance.m_nodes.m_buffer;
            NetSegment[] segmentBuffer = NetManager.instance.m_segments.m_buffer;

            ushort startNode = segmentBuffer[segment].m_startNode;
            ushort endNode = segmentBuffer[segment].m_endNode;
            Vector3 startPosition = nodeBuffer[startNode].m_position;
            Vector3 endPosition = nodeBuffer[endNode].m_position;
            Vector3 vector = Vector3.Min(startPosition, endPosition);
            Vector3 vector2 = Vector3.Max(startPosition, endPosition);
            int gridMinX = Mathf.Max((int)((vector.x - maxDistance) / 64f + 135f), 0);
            int gridMinZ = Mathf.Max((int)((vector.z - maxDistance) / 64f + 135f), 0);
            int gridMaxX = Mathf.Min((int)((vector2.x + maxDistance) / 64f + 135f), 269);
            int gridMaxZ = Mathf.Min((int)((vector2.z + maxDistance) / 64f + 135f), 269);

            ushort result = 0;
            float maxDistSqr = maxDistance * maxDistance;
            for (int i = gridMinZ; i <= gridMaxZ; i++)
            {
                for (int j = gridMinX; j <= gridMaxX; j++)
                {
                    ushort building = buildingGrid[i * 270 + j];
                    int count = 0;
                    while (building != 0)
                    {
                        Vector3 position2 = buildingBuffer[building].m_position;
                        float num8 = position2.x - startPosition.x;
                        float num9 = position2.z - startPosition.z;
                        float num10 = num8 * num8 + num9 * num9;
                        if (num10 < maxDistSqr && buildingBuffer[building].ContainsNode(startNode) && buildingBuffer[building].ContainsNode(endNode))
                        {
                            return building;
                        }
                        building = buildingBuffer[building].m_nextGridBuilding;
                        if (++count >= 49152)
                        {
                            CODebugBase<LogChannel>.Error(LogChannel.Core, "Buildings: Invalid list detected!\n" + Environment.StackTrace);
                            break;
                        }
                    }
                }
            }
            return result;
        }

        private bool isLeft(Vector3 P0, Vector3 P1, Vector3 P2)
        {
            return ((P1.x - P0.x) * (P2.z - P0.z) - (P2.x - P0.x) * (P1.z - P0.z)) > 0;
        }

        internal bool PointInRectangle(Quad3 rectangle, Vector3 p)
        {
            return isLeft(rectangle.a, rectangle.b, p) && isLeft(rectangle.b, rectangle.c, p) && isLeft(rectangle.c, rectangle.d, p) && isLeft(rectangle.d, rectangle.a, p);
        }

        private ItemClass.Layer GetItemLayers()
        {
            ItemClass.Layer itemLayers = ItemClass.Layer.Default;

            if (InfoManager.instance.CurrentMode == InfoManager.InfoMode.Water)
            {
                itemLayers |= ItemClass.Layer.WaterPipes;
            }
            if (InfoManager.instance.CurrentMode == InfoManager.InfoMode.Fishing)
            {
                itemLayers |= ItemClass.Layer.FishingPaths;
            }
            else if (InfoManager.instance.CurrentMode == InfoManager.InfoMode.Transport)
            { // Removes Default assignment
                itemLayers = ItemClass.Layer.MetroTunnels | ItemClass.Layer.BlimpPaths | ItemClass.Layer.FerryPaths | ItemClass.Layer.ShipPaths | ItemClass.Layer.AirplanePaths | ItemClass.Layer.HelicopterPaths;
            }
            else if (InfoManager.instance.CurrentMode == InfoManager.InfoMode.Traffic || InfoManager.instance.CurrentMode == InfoManager.InfoMode.Transport)
            {
                itemLayers |= ItemClass.Layer.MetroTunnels;
            }
            else if (InfoManager.instance.CurrentMode == InfoManager.InfoMode.Underground)
            { // Removes Default assignment
                itemLayers = ItemClass.Layer.MetroTunnels; 
            }
            else
            {
                itemLayers |= ItemClass.Layer.Markers;
            }

            return itemLayers;
        }

        private bool IsBuildingValid(ref Building building, ItemClass.Layer itemLayers)
        {
            if ((building.m_flags & Building.Flags.Created) == Building.Flags.Created)
            {
                return (building.Info.m_class.m_layer & itemLayers) != ItemClass.Layer.None;
            }

            return false;
        }

        private bool IsNodeValid(ref NetNode node, ItemClass.Layer itemLayers)
        {
            if ((node.m_flags & NetNode.Flags.Created) == NetNode.Flags.Created)
            {
                return (node.Info.GetConnectionClass().m_layer & itemLayers) != ItemClass.Layer.None;
            }

            return false;
        }

        private bool IsSegmentValid(ref NetSegment segment, ItemClass.Layer itemLayers)
        {
            if ((segment.m_flags & NetSegment.Flags.Created) == NetSegment.Flags.Created)
            {
                return (segment.Info.GetConnectionClass().m_layer & itemLayers) != ItemClass.Layer.None;
            }

            return false;
        }

        private static bool RayCastNode(ref NetNode node, Segment3 ray, float snapElevation, out float t, out float priority)
        {
            NetInfo info = node.Info;
            float num = (float)node.m_elevation + info.m_netAI.GetSnapElevation();
            float t2;
            if (info.m_netAI.IsUnderground())
            {
                t2 = Mathf.Clamp01(Mathf.Abs(snapElevation + num) / 12f);
            }
            else
            {
                t2 = Mathf.Clamp01(Mathf.Abs(snapElevation - num) / 12f);
            }
            float collisionHalfWidth = Mathf.Max(3f, info.m_netAI.GetCollisionHalfWidth());
            float num2 = Mathf.Lerp(info.GetMinNodeDistance(), collisionHalfWidth, t2);
            if (Segment1.Intersect(ray.a.y, ray.b.y, node.m_position.y, out t))
            {
                float num3 = Vector3.Distance(ray.Position(t), node.m_position);
                if (num3 < num2)
                {
                    priority = Mathf.Max(0f, num3 - collisionHalfWidth);
                    return true;
                }
            }
            t = 0f;
            priority = 0f;
            return false;
        }
    }
}


<|start_filename|>MoveIt/Integration/PO_Group.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;

namespace MoveIt
{
    internal class PO_Group
    {
        internal List<PO_Object> objects = new List<PO_Object>();
        internal PO_Object root = null;
        internal Type tPO = null, tPOGroup = null, tPOLogic = null;
        internal readonly object POGroup = null;

        /// <summary>
        /// Create a new PO Group
        /// </summary>
        public PO_Group()
        {
            tPOLogic = PO_Logic.POAssembly.GetType("ProceduralObjects.ProceduralObjectsLogic");

            object groupList = tPOLogic.GetField("groups").GetValue(PO_Logic.POLogic);
            POGroup = tPOLogic.Assembly.CreateInstance("ProceduralObjects.Classes.POGroup");

            groupList.GetType().GetMethod("Add", new Type[] { PO_Logic.tPOGroup }).Invoke(groupList, new[] { POGroup });
            MoveItTool.PO.Groups.Add(this);
        }

        /// <summary>
        /// Load an existing group
        /// </summary>
        /// <param name="group">The PO Group data</param>
        public PO_Group(object group)
        {
            tPO = PO_Logic.POAssembly.GetType("ProceduralObjects.Classes.ProceduralObject");
            tPOGroup = PO_Logic.POAssembly.GetType("ProceduralObjects.Classes.POGroup");
            tPOLogic = PO_Logic.POAssembly.GetType("ProceduralObjects.ProceduralObjectsLogic");
            POGroup = group;

            var objList = tPOGroup.GetField("objects").GetValue(group);
            int count = (int)objList.GetType().GetProperty("Count").GetValue(objList, null);

            for (int i = 0; i < count; i++)
            {
                var v = objList.GetType().GetMethod("get_Item").Invoke(objList, new object[] { i });
                PO_Object obj = MoveItTool.PO.GetProcObj(Convert.ToUInt32(tPO.GetField("id").GetValue(v)) + 1);
                obj.Group = this;
                objects.Add(obj);

                if (obj.isGroupRoot())
                {
                    root = obj;
                }
            }
        }

        /// <summary>
        /// Change a group's root PO object
        /// </summary>
        /// <param name="newRoot">The PO object to set as root</param>
        internal void SetNewRoot(PO_Object newRoot)
        {
            if (root != null)
            {
                root.SetGroupRoot(false);
            }
            root = newRoot;
            POGroup.GetType().GetField("root").SetValue(POGroup, newRoot.procObj);
            root.SetGroupRoot(true);
        }

        /// <summary>
        /// Add an object to this group
        /// </summary>
        /// <param name="obj">The PO_Object</param>
        internal void AddObject(PO_Object obj)
        {
            objects.Add(obj);

            // Add object to group
            var objList = POGroup.GetType().GetField("objects").GetValue(POGroup);
            objList.GetType().GetMethod("Add").Invoke(objList, new[] { obj.procObj });

            // Add group to object
            var groupField = obj.procObj.GetType().GetField("group");
            groupField.SetValue(obj.procObj, POGroup);
        }
    }
}


<|start_filename|>MoveIt/ModInfo.cs<|end_filename|>
﻿using ColossalFramework;
using ColossalFramework.Globalization;
using ColossalFramework.IO;
using ColossalFramework.UI;
using ICities;
using MoveIt.Localization;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace MoveIt
{
    public class ModInfo : IUserMod
    {
        public static string URLPatreon { get; } = "https://www.patreon.com/Quboid";
        public static string URLPaypal { get; } = "https://www.paypal.me/QuboidCSL1";
        public static string URLCoffee { get; } = "https://www.buymeacoffee.com/Quboid";

        public ModInfo()
        {
            try
            {
                // Creating setting file
                if (GameSettings.FindSettingsFileByName(MoveItTool.settingsFileName) == null)
                {
                    GameSettings.AddSettingsFile(new SettingsFile[] { new SettingsFile() { fileName = MoveItTool.settingsFileName } });
                }
            }
            catch (Exception e)
            {
                DebugUtils.Log("Could not load/create the setting file.");
                DebugUtils.LogException(e);
            }
        }

        public string Name
        {
            get { return "Move It " + version; }
        }

        public string Description
        {
            get { return Str.mod_description; }
        }

        internal static CultureInfo Culture => new CultureInfo(SingletonLite<LocaleManager>.instance.language == "zh" ? "zh-cn" : SingletonLite<LocaleManager>.instance.language);

        public const string version = "2.9.5";

        private static bool debugInitialised = false;
        public static readonly string debugPath = Path.Combine(DataLocation.localApplicationData, "MoveIt.log");

        public static void DebugLine(string line, bool newLine = true)
        {
            if (!debugInitialised)
            {
                File.WriteAllText(debugPath, $"Move It debug log:\n");
                debugInitialised = true;
            }

            Log.Debug(line);
            File.AppendAllText(debugPath, line);
            if (newLine)
            {
                File.AppendAllText(debugPath, "\n");
            }
        }

        public static void OpenUrl(string url)
        {
            Process.Start(url);
        }

        public void OnSettingsUI(UIHelperBase helper)
        {
            try
            {
                LocaleManager.eventLocaleChanged -= MoveItLoader.LocaleChanged;
                MoveItLoader.LocaleChanged();
                LocaleManager.eventLocaleChanged += MoveItLoader.LocaleChanged;

                UIHelperBase group = helper.AddGroup(Name);
                UIPanel panel = ((UIHelper)group).self as UIPanel;

                UILabel sellout = panel.AddUIComponent<UILabel>();
                sellout.name = "sellout";
                sellout.textScale = 1.2f;
                sellout.text = Str.options_Beg;

                UIButton button = (UIButton)group.AddButton("Buy Me A Coffee", () => OpenUrl(URLCoffee));
                button.autoSize = false;
                button.textHorizontalAlignment = UIHorizontalAlignment.Center;
                button.size = new Vector2(250, 40);
                button.tooltip = "Buy Me A Coffee";

                button = (UIButton)group.AddButton(Str.options_Patreon, () => OpenUrl(URLPatreon));
                button.autoSize = false;
                button.textHorizontalAlignment = UIHorizontalAlignment.Center;
                button.size = new Vector2(250, 40);
                button.tooltip = Str.options_PatreonTooltip;

                button = (UIButton)group.AddButton(Str.options_Paypal, () => OpenUrl(URLPaypal));
                button.autoSize = false;
                button.textHorizontalAlignment = UIHorizontalAlignment.Center;
                button.size = new Vector2(250, 40);
                button.tooltip = Str.options_PaypalTooltip;

                group.AddSpace(10);

                group = helper.AddGroup(Str.options_Options);

                UICheckBox checkBox = (UICheckBox)group.AddCheckbox(Str.options_AutoCloseToolbox, MoveItTool.autoCloseAlignTools.value, (b) =>
                {
                    MoveItTool.autoCloseAlignTools.value = b;
                    if (UIMoreTools.MoreToolsPanel != null)
                    {
                        UIMoreTools.CloseMenu();
                    }
                });
                checkBox.tooltip = Str.options_AutoCloseToolbox_Tooltip;

                group.AddSpace(10);

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_PreferFastmove, MoveItTool.fastMove.value, (b) =>
                {
                    MoveItTool.fastMove.value = b;
                });
                checkBox.tooltip = Str.options_PreferFastmove_Tooltip;

                group.AddSpace(10);

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_UseCompass, MoveItTool.useCardinalMoves.value, (b) =>
                {
                    MoveItTool.useCardinalMoves.value = b;
                });
                checkBox.tooltip = Str.options_UseCompass_Tooltip;

                group.AddSpace(10);

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_RightClickCancel, MoveItTool.rmbCancelsCloning.value, (b) =>
                {
                    MoveItTool.rmbCancelsCloning.value = b;
                });
                checkBox.tooltip = Str.options_RightClickCancel_Tooltip;

                group.AddSpace(10);

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_AdvancedPillarControl, MoveItTool.advancedPillarControl.value, (b) =>
                {
                    MoveItTool.advancedPillarControl.value = b;
                });
                checkBox.tooltip = Str.options_AdvancedPillarControl_Tooltip;

                group.AddSpace(10);

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_AltForPillars, MoveItTool.altSelectNodeBuildings.value, (b) =>
                {
                    MoveItTool.altSelectNodeBuildings.value = b;
                });

                group.AddSpace(10);
                group = helper.AddGroup(Str.options_ShortcutsGeneral);
                panel = ((UIHelper)group).self as UIPanel;
                group.AddSpace(10);

                ((UIPanel)((UIHelper)group).self).gameObject.AddComponent<OptionsKeymappingMain>();

                group.AddSpace(10);
                group = helper.AddGroup(Str.options_ShortcutsToolbox);
                panel = ((UIHelper)group).self as UIPanel;
                group.AddSpace(10);

                ((UIPanel)((UIHelper)group).self).gameObject.AddComponent<OptionsKeymappingToolbox>();

                group.AddSpace(10);
                group = helper.AddGroup(Str.options_ExtraOptions);
                panel = ((UIHelper)group).self as UIPanel;
                group.AddSpace(10);

                button = (UIButton)group.AddButton(Str.options_RemoveGhostNodes, MoveItTool.CleanGhostNodes);
                button.tooltip = Str.options_RemoveGhostNodes_Tooltip;

                button = (UIButton)group.AddButton(Str.options_fixTreeSnapping, MoveItTool.FixTreeFixedHeightFlag);
                button.tooltip = Str.options_fixTreeSnappingTooltip;

                group.AddSpace(10);

                button = (UIButton)group.AddButton(Str.options_ResetButtonPosition, () =>
                {
                    UIMoveItButton.savedX.value = -1000;
                    UIMoveItButton.savedY.value = -1000;
                    MoveItTool.instance?.m_button?.ResetPosition();
                });

                group.AddSpace(20);

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_DisableDebugLogging, DebugUtils.hideDebugMessages.value, (b) =>
                {
                    DebugUtils.hideDebugMessages.value = b;
                });
                checkBox.tooltip = Str.options_DisableDebugLogging_Tooltip;

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_ShowDebugPanel, MoveItTool.showDebugPanel.value, (b) =>
                {
                    MoveItTool.showDebugPanel.value = b;
                    if (MoveItTool.m_debugPanel != null)
                    {
                        MoveItTool.m_debugPanel.Visible(b);
                    }
                });
                checkBox.name = "MoveIt_DebugPanel";

                group.AddSpace(5);
                UILabel emlLabel = panel.AddUIComponent<UILabel>();
                emlLabel.name = "emlLabel";
                emlLabel.text = PropLayer.getVersionText();

                UILabel nsLabel = panel.AddUIComponent<UILabel>();
                nsLabel.name = "nsLabel";
                nsLabel.text = NS_Manager.getVersionText();

                group = helper.AddGroup(Str.options_ProceduralObjects);
                panel = ((UIHelper)group).self as UIPanel;

                UILabel poLabel = panel.AddUIComponent<UILabel>();
                poLabel.name = "poLabel";
                poLabel.text = PO_Manager.getVersionText();

                // TODO add users of MoveITIntegration.dll here by name/description

                UILabel poWarning = panel.AddUIComponent<UILabel>();
                poWarning.name = "poWarning";
                poWarning.padding = new RectOffset(25, 0, 0, 15);
                poWarning.text = Str.options_PODeleteWarning;

                checkBox = (UICheckBox)group.AddCheckbox(Str.options_HidePODeletionWarning, !MoveItTool.POShowDeleteWarning.value, (b) =>
                {
                    MoveItTool.POShowDeleteWarning.value = !b;
                });

                group.AddSpace(15);

                panel.gameObject.AddComponent<OptionsKeymappingPO>();

                group.AddSpace(15);
            }
            catch (Exception e)
            {
                DebugUtils.Log("OnSettingsUI failed");
                DebugUtils.LogException(e);
            }
        }

        internal static bool InGame() => SceneManager.GetActiveScene().name == "Game";

        public void OnEnabled()
        {
            if (InGame())
            {
                // basic ingame hot reload
                MoveItLoader.loadMode = LoadMode.NewGame;
                MoveItLoader.InstallMod();
            }
        }

        public void OnDisabled()
        {
            if (InGame())
            {
                // basic in game hot unload
                MoveItLoader.UninstallMod();
            }
        }
    }

}


<|start_filename|>MoveIt/PropInstance.cs<|end_filename|>
﻿using ColossalFramework;
using EManagersLib.API;
using UnityEngine;

namespace MoveIt
{
    public interface IProp
    {
        Vector3 Position { get; set; }
        float Angle { get; set; }
        bool FixedHeight { get; set; }
        bool Single { get; set; }
        ushort m_flags { get; set; }
        PropInfo Info { get; }
        uint Index { get; }

        void MoveProp(Vector3 position);
        void UpdatePropRenderer(bool updateGroup);
        void ReleaseProp();
    }

    class PropWrapper : IProp
    {
        private readonly ushort index;
        private readonly PropInstance[] Buffer = PropManager.instance.m_props.m_buffer;

        public void MoveProp(Vector3 position)
        {
            Singleton<SimulationManager>.instance.AddAction(() => PropManager.instance.MoveProp(index, position));
        }

        public void UpdatePropRenderer(bool updateGroup)
        {
            Singleton<SimulationManager>.instance.AddAction(() => PropManager.instance.UpdatePropRenderer(index, updateGroup));
        }

        public void ReleaseProp()
        {
            Singleton<SimulationManager>.instance.AddAction(() => PropManager.instance.ReleaseProp(index));
        }

        public uint Index => index;


        public PropWrapper(ushort i)
        {
            index = i;
        }

        public Vector3 Position
        {
            get => Buffer[index].Position;
            set => Buffer[index].Position = value;
        }

        public float Angle
        {
            get => Buffer[index].Angle;
            set => Buffer[index].Angle = value;
        }

        public bool FixedHeight
        {
            get => Buffer[index].FixedHeight;
            set => Buffer[index].FixedHeight = value;
        }

        public bool Single
        {
            get => Buffer[index].Single;
            set => Buffer[index].Single = value;
        }

        public ushort m_flags
        {
            get => Buffer[index].m_flags;
            set => Buffer[index].m_flags = value;
        }

        public PropInfo Info
        {
            get => Buffer[index].Info;
        }
    }

    // Extended Managers Library support
    class EPropWrapper : IProp
    {
        private readonly uint index;
        public uint Index => index;

        public EPropWrapper(uint i)
        {
            index = i;
        }

        public void MoveProp(Vector3 position)
        {
            PropAPI.Wrapper.MoveProp(index, position);
        }

        public void UpdatePropRenderer(bool updateGroup)
        {
            PropAPI.Wrapper.UpdatePropRenderer(index, updateGroup);
        }

        public void ReleaseProp()
        {
            PropAPI.Wrapper.ReleaseProp(index);
        }

        public Vector3 Position
        {
            get => PropAPI.Wrapper.GetPosition(index);
            set => PropAPI.Wrapper.SetPosition(index, value);
        }

        public float Angle
        {
            get => PropAPI.Wrapper.GetAngle(index);
            set => PropAPI.Wrapper.SetAngle(index, value);
        }

        public bool FixedHeight
        {
            get => PropAPI.Wrapper.GetFixedHeight(index);
            set => PropAPI.Wrapper.SetFixedHeight(index, value);
        }

        public bool Single
        {
            get => PropAPI.Wrapper.GetSingle(index);
            set => PropAPI.Wrapper.SetSingle(index, value);
        }

        public ushort m_flags
        {
            get => PropAPI.Wrapper.GetFlags(index);
            set => PropAPI.Wrapper.SetFlags(index, value);
        }

        public PropInfo Info
        {
            get => PropAPI.Wrapper.GetInfo(index);
        }
    }
}

<|start_filename|>MoveIt/Moveable/MoveableBuilding.cs<|end_filename|>
﻿using ColossalFramework;
using ColossalFramework.Math;
using System;
using System.Threading;
using System.Collections.Generic;
using System.Xml.Serialization;
using UnityEngine;

namespace MoveIt
{
    public class BuildingState : InstanceState
    {
        public Building.Flags flags;
        public int length;
        public bool isSubInstance;
        public bool isHidden;

        [XmlElement("subStates")]
        public InstanceState[] subStates;
        
        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;
            }
        }
    }

    public class MoveableBuilding : Instance
    {
        internal bool isSubInstance = false;

        public override HashSet<ushort> segmentList
        {
            get
            {
                HashSet<ushort> segments = new HashSet<ushort>();

                ushort node = buildingBuffer[id.Building].m_netNode;
                int count = 0;
                while (node != 0)
                {
                    ItemClass.Layer layer = nodeBuffer[node].Info.m_class.m_layer;
                    if (layer != ItemClass.Layer.PublicTransport)
                    {
                        InstanceID nodeID = default;
                        nodeID.NetNode = node;
                        segments.UnionWith(new MoveableNode(nodeID).segmentList);
                    }

                    node = nodeBuffer[node].m_nextBuildingNode;

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

                return segments;
            }
        }

        public MoveableBuilding(InstanceID instanceID, bool sub = false) : base(instanceID)
        {
            isSubInstance = sub;
            isHidden = (buildingBuffer[id.Building].m_flags & Building.Flags.Hidden) == Building.Flags.Hidden;
            Info = new Info_Prefab(BuildingManager.instance.m_buildings.m_buffer[instanceID.Building].Info);
            
            ResetSubInstances();
        }

        public override InstanceState SaveToState(bool integrate = true)
        {
            BuildingState state = new BuildingState
            {
                instance = this,
                Info = Info,
                position = buildingBuffer[id.Building].m_position,
                angle = buildingBuffer[id.Building].m_angle,
                flags = buildingBuffer[id.Building].m_flags,
                length = buildingBuffer[id.Building].Length,
                isSubInstance = isSubInstance,
                isHidden = isHidden,
                isCustomContent = Info.Prefab.m_isCustomContent
            };
            state.terrainHeight = TerrainManager.instance.SampleOriginalRawHeightSmooth(state.position);

            List<InstanceState> subStates = new List<InstanceState>();

            foreach (Instance subInstance in subInstances)
            {
                if (subInstance != null && subInstance.isValid)
                {
                    if (subInstance.id.Building > 0)
                    {
                        subStates.Add(((MoveableBuilding)subInstance).SaveToState());
                    }
                    else
                    {
                        subStates.Add(subInstance.SaveToState());
                    }
                }
            }

            if (subStates.Count > 0)
                state.subStates = subStates.ToArray();

            state.SaveIntegrations(integrate);

            return state;
        }

        public override void LoadFromState(InstanceState state)
        {
            if (!(state is BuildingState buildingState)) return;

            ushort building = buildingState.instance.id.Building;

            buildingBuffer[building].m_flags = buildingState.flags;
            AddFixedHeightFlag(building);
            RelocateBuilding(building, ref buildingBuffer[building], buildingState.position, buildingState.angle);
            isSubInstance = buildingState.isSubInstance;
            isHidden = buildingState.isHidden;

            if (buildingState.subStates != null)
            {
                foreach (InstanceState subState in buildingState.subStates)
                {
                    subState.instance.LoadFromState(subState);
                }
            }
            buildingBuffer[building].m_flags = buildingState.flags;
        }

        public override Vector3 position
        {
            get
            {
                if (id.IsEmpty) return Vector3.zero;
                return buildingBuffer[id.Building].m_position;
            }
            set
            {
                if (id.IsEmpty) return;
                buildingBuffer[id.Building].m_position = value;
            }
        }

        public override float angle
        {
            get
            {
                if (id.IsEmpty) return 0f;
                return buildingBuffer[id.Building].m_angle;
            }
            set
            {
                if (id.IsEmpty) return;
                buildingBuffer[id.Building].m_angle = (value + Mathf.PI * 2) % (Mathf.PI * 2);
            }
        }

        public override bool isValid
        {
            get
            {
                if (id.IsEmpty) return false;
                return (buildingBuffer[id.Building].m_flags & Building.Flags.Created) != Building.Flags.None;
            }
        }

        public int Length
        {
            get
            {
                return buildingBuffer[id.Building].m_length;
            }
        }

        public override void Transform(InstanceState instanceState, ref Matrix4x4 matrix4x, float deltaHeight, float deltaAngle, Vector3 center, bool followTerrain)
        {
            BuildingState state = instanceState as BuildingState;

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

            float terrainHeight = TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition);
            bool isFixed = GetFixedHeightFlag(id.Building);
            if (!isFixed) AddFixedHeightFlag(id.Building);

            if (followTerrain)
            {
                newPosition.y = newPosition.y + terrainHeight - state.terrainHeight;
            }

            AddFixedHeightFlag(id.Building);
            Move(newPosition, state.angle + deltaAngle);

            Matrix4x4 matrixSub = default;
            matrixSub.SetTRS(Vector3.zero, Quaternion.AngleAxis(deltaAngle * Mathf.Rad2Deg, Vector3.down), Vector3.one);

            if (state.subStates != null)
            {
                foreach (InstanceState subState in state.subStates)
                {
                    Vector3 subOffset = (subState.position - center) - (state.position - center);
                    Vector3 subPosition = TransformPosition + matrixSub.MultiplyPoint(subOffset);

                    subPosition.y = subState.position.y - state.position.y + newPosition.y;

                    subState.instance.Move(subPosition, subState.angle + deltaAngle);
                    if (subState.instance is MoveableNode mn)
                    {
                        if (mn.Pillar != null)
                        {
                            mn.Pillar.Move(subPosition, subState.angle + deltaAngle);
                        }
                    }

                    if (subState is BuildingState bs)
                    {
                        if (bs.subStates != null)
                        {
                            foreach (InstanceState subSubState in bs.subStates)
                            {
                                Vector3 subSubOffset = (subSubState.position - center) - (state.position - center);
                                Vector3 subSubPosition = TransformPosition + matrixSub.MultiplyPoint(subSubOffset);

                                subSubPosition.y = subSubState.position.y - state.position.y + newPosition.y;

                                subSubState.instance.Move(subSubPosition, subSubState.angle + deltaAngle);
                                if (subSubState.instance is MoveableNode mn2)
                                {
                                    if (mn2.Pillar != null)
                                    {
                                        mn2.Pillar.Move(subSubPosition, subSubState.angle + deltaAngle);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (!isFixed && Mathf.Abs(terrainHeight - newPosition.y) < 0.01f)
            {
                RemoveFixedHeightFlag(id.Building);
            }
        }

        internal override void SetHidden(bool hide)
        {
            buildingBuffer[id.Building].m_flags = ToggleBuildingHiddenFlag(id.Building, hide);
        }

        public void InitialiseDrag()
        {
            Bounds bounds = new Bounds(position, new Vector3(Length, 0, Length));
            bounds.Expand(32f);
            Action.UpdateArea(bounds);
        }

        public void FinaliseDrag()
        {
            Bounds bounds = new Bounds(position, new Vector3(Length, 0, Length));
            bounds.Expand(32f);
            Action.UpdateArea(bounds);
        }

        public override void Move(Vector3 location, float angle)
        {
            if (!isValid) return;

            TransformAngle = angle;
            TransformPosition = location;

            if (!isVirtual())
            {
                RelocateBuilding(id.Building, ref buildingBuffer[id.Building], location, angle);
            }
        }

        public override void SetHeight(float height)
        {
            Vector3 newPosition = position;

            float terrainHeight = TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition);
            bool isFixed = GetFixedHeightFlag(id.Building);
            if (!isFixed) AddFixedHeightFlag(id.Building);

            foreach (Instance subInstance in subInstances)
            {
                Vector3 subPosition = subInstance.position;
                subPosition.y = subPosition.y - newPosition.y + height;

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

            newPosition.y = height;
            Move(newPosition, angle);

            if (!isFixed && Mathf.Abs(terrainHeight - newPosition.y) < 0.01f)
            {
                RemoveFixedHeightFlag(id.Building);
            }
        }

        public override void SetHeight()
        {
            Building b = (Building)data;
            b.m_baseHeight = 0;
            SetHeight(TerrainManager.instance.SampleOriginalRawHeightSmooth(position));
        }

        internal MoveableBuilding Duplicate()
        {
            if (BuildingManager.instance.CreateBuilding(out ushort clone, ref SimulationManager.instance.m_randomizer,
                (BuildingInfo)Info.Prefab, position, angle,
                buildingBuffer[id.Building].Length, SimulationManager.instance.m_currentBuildIndex))
            {
                buildingBuffer[clone].m_flags |= Building.Flags.FixedHeight;
                buildingBuffer[clone].m_position = position;
                InstanceID cloneID = default;
                cloneID.Building = clone;
                return new MoveableBuilding(cloneID);
            }

            throw new Exception($"Failed to duplicate {id.Building}!");
        }

        public override Instance Clone(InstanceState instanceState, ref Matrix4x4 matrix4x, float deltaHeight, float deltaAngle, Vector3 center, bool followTerrain, Dictionary<ushort, ushort> clonedNodes, Action action)
        {
            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.Prefab 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;
                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;
                }
                if ((state.flags & Building.Flags.Historical) != Building.Flags.None)
                {
                    buildingBuffer[clone].m_flags = buildingBuffer[clone].m_flags | Building.Flags.Historical;
                }
                if ((state.flags & Building.Flags.Hidden) != Building.Flags.None)
                {
                    buildingBuffer[clone].m_flags = buildingBuffer[clone].m_flags | Building.Flags.Hidden;
                }

                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;
                    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;
                        }
                    }
                }
                cloneInstance.ResetSubInstances();
            }

            return cloneInstance;
        }

        // 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;
        }

        public override void Delete()
        {
            if (isValid)
            {
                SimulationManager.instance.AddAction(() =>
                {
                    BuildingManager.instance.ReleaseBuilding(id.Building);
                });
            }
        }

        public void AddFixedHeightFlag(ushort building)
        {
            buildingBuffer[building].m_flags = buildingBuffer[building].m_flags | Building.Flags.FixedHeight;
        }

        public void RemoveFixedHeightFlag(ushort building)
        {
            buildingBuffer[building].m_flags = buildingBuffer[building].m_flags & ~Building.Flags.FixedHeight;
        }

        public bool GetFixedHeightFlag(ushort building)
        {
            return (buildingBuffer[building].m_flags & Building.Flags.FixedHeight) == Building.Flags.FixedHeight;
        }
        
        internal void ResetSubInstances()
        {
            List<Instance> instances = new List<Instance>();
            int count = 0;

            if (!isSubInstance)
            {
                ushort building = buildingBuffer[id.Building].m_subBuilding;
                while (building != 0)
                {
                    InstanceID buildingID = default;
                    buildingID.Building = building;

                    instances.Add(new MoveableBuilding(buildingID, true));
                    building = buildingBuffer[building].m_subBuilding;

                    if (++count > 49152)
                    {
                        CODebugBase<LogChannel>.Error(LogChannel.Core, "Buildings: Invalid list detected!\n" + Environment.StackTrace);
                        break;
                    }
                }
            }

            ushort node = buildingBuffer[id.Building].m_netNode;
            count = 0;
            while (node != 0)
            {
                ItemClass.Layer layer = nodeBuffer[node].Info.m_class.m_layer;
                if (layer != ItemClass.Layer.PublicTransport)
                {
                    InstanceID nodeID = default;
                    nodeID.NetNode = node;
                    instances.Add(new MoveableNode(nodeID));
                }

                node = nodeBuffer[node].m_nextBuildingNode;
                if ((nodeBuffer[node].m_flags & NetNode.Flags.Created) != NetNode.Flags.Created)
                {
                    node = 0;
                }

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

            subInstances = instances;
        }

        public override Bounds GetBounds(bool ignoreSegments = true)
        {
            BuildingInfo info = buildingBuffer[id.Building].Info;

            float radius = Mathf.Max(info.m_cellWidth * 4f, info.m_cellLength * 4f);
            Bounds bounds = new Bounds(OverlayPosition, new Vector3(radius, 0, radius));

            foreach (Instance subInstance in subInstances)
            {
                if (subInstance is MoveableBuilding mb)
                {
                    bounds.Encapsulate(mb.GetBounds(ignoreSegments));
                }
                else
                {
                    bounds.Encapsulate(subInstance.GetBounds(ignoreSegments));
                }
            }

            return bounds;
        }

        public override void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color toolColor, Color despawnColor)
        {
            if (!isValid) return;
            if (MoveItTool.m_isLowSensitivity) return;

            ushort building = id.Building;
            BuildingInfo buildingInfo = buildingBuffer[building].Info;

            if (toolColor != MoveItTool.m_alignColor && WillBuildingDespawn(building))
            {
                toolColor = despawnColor;
            }

            float alpha = 1f;
            BuildingTool.CheckOverlayAlpha(buildingInfo, ref alpha);
            toolColor.a *= alpha;

            int length = buildingBuffer[building].Length;
            BuildingTool.RenderOverlay(cameraInfo, buildingInfo, length, OverlayPosition, OverlayAngle, toolColor, false);

            foreach (Instance subInstance in subInstances)
            {
                if (subInstance is MoveableNode mn)
                {
                    ushort node = mn.id.NetNode;
                    for (int k = 0; k < 8; k++)
                    {
                        ushort segment = netManager.m_nodes.m_buffer[node].GetSegment(k);
                        if (segment != 0 && netManager.m_segments.m_buffer[segment].m_startNode == node && (netManager.m_segments.m_buffer[segment].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None)
                        {
                            NetTool.RenderOverlay(cameraInfo, ref netManager.m_segments.m_buffer[segment], toolColor, toolColor);
                        }
                    }
                }
                else if (subInstance is MoveableBuilding mb)
                {
                    Building b = buildingBuffer[mb.id.Building];
                    BuildingTool.RenderOverlay(cameraInfo, (BuildingInfo)mb.Info.Prefab, b.Length, mb.OverlayPosition, mb.OverlayAngle, toolColor, false);
                }
            }
        }

        public override void RenderCloneOverlay(InstanceState instanceState, ref Matrix4x4 matrix4x, Vector3 deltaPosition, float deltaAngle, Vector3 center, bool followTerrain, RenderManager.CameraInfo cameraInfo, Color toolColor)
        {
            if (MoveItTool.m_isLowSensitivity) return;

            BuildingState state = instanceState as BuildingState;

            BuildingInfo buildingInfo = state.Info.Prefab as BuildingInfo;

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

            if (followTerrain)
            {
                newPosition.y = newPosition.y - state.terrainHeight + TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition);
            }

            float newAngle = state.angle + deltaAngle;

            buildingInfo.m_buildingAI.RenderBuildOverlay(cameraInfo, toolColor, newPosition, newAngle, default);
            BuildingTool.RenderOverlay(cameraInfo, buildingInfo, state.length, newPosition, newAngle, toolColor, false);
            if (buildingInfo.m_subBuildings != null && buildingInfo.m_subBuildings.Length != 0)
            {
                Matrix4x4 subMatrix4x = default;
                subMatrix4x.SetTRS(newPosition, Quaternion.AngleAxis(newAngle * Mathf.Rad2Deg, Vector3.down), Vector3.one);
                for (int i = 0; i < buildingInfo.m_subBuildings.Length; i++)
                {
                    BuildingInfo buildingInfo2 = buildingInfo.m_subBuildings[i].m_buildingInfo;
                    Vector3 position = subMatrix4x.MultiplyPoint(buildingInfo.m_subBuildings[i].m_position);
                    float angle = buildingInfo.m_subBuildings[i].m_angle * 0.0174532924f + newAngle;
                    buildingInfo2.m_buildingAI.RenderBuildOverlay(cameraInfo, toolColor, position, angle, default);
                    BuildingTool.RenderOverlay(cameraInfo, buildingInfo2, 0, position, angle, toolColor, true);
                }
            }
        }

        public override void RenderCloneGeometry(InstanceState instanceState, ref Matrix4x4 matrix4x, Vector3 deltaPosition, float deltaAngle, Vector3 center, bool followTerrain, RenderManager.CameraInfo cameraInfo, Color toolColor)
        {
            RenderCloneGeometryImplementation(instanceState, ref matrix4x, deltaPosition, deltaAngle, center, followTerrain, cameraInfo);
        }

        public static void RenderCloneGeometryImplementation(InstanceState instanceState, ref Matrix4x4 matrix4x, Vector3 deltaPosition, float deltaAngle, Vector3 center, bool followTerrain, RenderManager.CameraInfo cameraInfo)
        {
            BuildingInfo info = instanceState.Info.Prefab as BuildingInfo;
            Color color = GetColor(instanceState.instance.id.Building, info);

            Vector3 newPosition = matrix4x.MultiplyPoint(instanceState.position - center);
            newPosition.y = instanceState.position.y + deltaPosition.y;

            if (followTerrain)
            {
                newPosition.y = newPosition.y - instanceState.terrainHeight + TerrainManager.instance.SampleOriginalRawHeightSmooth(newPosition);
            }

            float newAngle = instanceState.angle + deltaAngle;

            info.m_buildingAI.RenderBuildGeometry(cameraInfo, newPosition, newAngle, 0);
            BuildingTool.RenderGeometry(cameraInfo, info, info.GetLength(), newPosition, newAngle, false, color);
            if (info.m_subBuildings != null && info.m_subBuildings.Length != 0)
            {
                Matrix4x4 subMatrix4x = default;
                subMatrix4x.SetTRS(newPosition, Quaternion.AngleAxis(newAngle * Mathf.Rad2Deg, Vector3.down), Vector3.one);
                for (int i = 0; i < info.m_subBuildings.Length; i++)
                {
                    BuildingInfo buildingInfo2 = info.m_subBuildings[i].m_buildingInfo;
                    Vector3 position = subMatrix4x.MultiplyPoint(info.m_subBuildings[i].m_position);
                    float angle = info.m_subBuildings[i].m_angle * Mathf.Deg2Rad + newAngle;
                    buildingInfo2.m_buildingAI.RenderBuildGeometry(cameraInfo, position, angle, 0);
                    BuildingTool.RenderGeometry(cameraInfo, buildingInfo2, 0, position, angle, true, color);
                }
            }
        }

        public override void RenderGeometry(RenderManager.CameraInfo cameraInfo, Color toolColor)
        {
            if (isHidden) return;
            if (!Virtual) return;
            BuildingInfo buildingInfo = Info.Prefab as BuildingInfo;
            Color color = GetColor(id.Building, buildingInfo);

            buildingInfo.m_buildingAI.RenderBuildGeometry(cameraInfo, OverlayPosition, OverlayAngle, 0);
            BuildingTool.RenderGeometry(cameraInfo, buildingInfo, Length, OverlayPosition, OverlayAngle, false, color);

            foreach (Instance subInstance in subInstances)
            {
                if (subInstance is MoveableBuilding msb)
                {
                    msb.RenderGeometry(cameraInfo, toolColor);
                }
            }
        }

        internal static Color GetColor(ushort buildingID, BuildingInfo info)
        {
            if (!info.m_useColorVariations)
            {
                return info.m_color0;
            }
            Randomizer randomizer = new Randomizer((int)buildingID);
            switch (randomizer.Int32(4u))
            {
                case 0:
                    return info.m_color0;
                case 1:
                    return info.m_color1;
                case 2:
                    return info.m_color2;
                case 3:
                    return info.m_color3;
                default:
                    return info.m_color0;
            }
        }

        private bool WillBuildingDespawn(ushort building)
        {
            BuildingInfo info = buildingBuffer[building].Info;

            ItemClass.Zone zone1 = info.m_class.GetZone();
            ItemClass.Zone zone2 = info.m_class.GetSecondaryZone();

            if (info.m_placementStyle != ItemClass.Placement.Automatic || zone1 == ItemClass.Zone.None)
            {
                return false;
            }

            info.m_buildingAI.CheckRoadAccess(building, ref buildingBuffer[building]);
            if ((buildingBuffer[building].m_problems & Notification.Problem.RoadNotConnected) == Notification.Problem.RoadNotConnected ||
                !buildingBuffer[building].CheckZoning(zone1, zone2, true))
            {
                return true;
            }

            return false;
        }

        private void RelocateBuilding(ushort building, ref Building data, Vector3 position, float angle)
        {
            RemoveFromGrid(building, ref data);

            //BuildingInfo info = data.Info;
            //if (info.m_hasParkingSpaces != VehicleInfo.VehicleType.None)
            //{
            //    Log.Debug($"PARKING (RB)\n#{building}:{info.name}");
            //    BuildingManager.instance.UpdateParkingSpaces(building, ref data);
            //}

            data.m_position = position;
            data.m_angle = (angle + Mathf.PI * 2) % (Mathf.PI * 2);

            AddToGrid(building, ref data);
            data.CalculateBuilding(building);
            Singleton<SimulationManager>.instance.AddAction(() => BuildingManager.instance.UpdateBuildingRenderer(building, true));
        }

        private static void AddToGrid(ushort building, ref Building data)
        {
            int num = Mathf.Clamp((int)(data.m_position.x / 64f + 135f), 0, 269);
            int num2 = Mathf.Clamp((int)(data.m_position.z / 64f + 135f), 0, 269);
            int num3 = num2 * 270 + num;
            while (!Monitor.TryEnter(BuildingManager.instance.m_buildingGrid, SimulationManager.SYNCHRONIZE_TIMEOUT))
            {
            }
            try
            {
                buildingBuffer[building].m_nextGridBuilding = BuildingManager.instance.m_buildingGrid[num3];
                BuildingManager.instance.m_buildingGrid[num3] = building;
            }
            finally
            {
                Monitor.Exit(BuildingManager.instance.m_buildingGrid);
            }
        }

        private static void RemoveFromGrid(ushort building, ref Building data)
        {
            BuildingManager buildingManager = BuildingManager.instance;

            BuildingInfo info = data.Info;
            int num = Mathf.Clamp((int)(data.m_position.x / 64f + 135f), 0, 269);
            int num2 = Mathf.Clamp((int)(data.m_position.z / 64f + 135f), 0, 269);
            int num3 = num2 * 270 + num;
            while (!Monitor.TryEnter(buildingManager.m_buildingGrid, SimulationManager.SYNCHRONIZE_TIMEOUT))
            {
            }
            try
            {
                ushort num4 = 0;
                ushort num5 = buildingManager.m_buildingGrid[num3];
                int num6 = 0;
                while (num5 != 0)
                {
                    if (num5 == building)
                    {
                        if (num4 == 0)
                        {
                            buildingManager.m_buildingGrid[num3] = data.m_nextGridBuilding;
                        }
                        else
                        {
                            buildingBuffer[(int)num4].m_nextGridBuilding = data.m_nextGridBuilding;
                        }
                        break;
                    }
                    num4 = num5;
                    num5 = buildingBuffer[(int)num5].m_nextGridBuilding;
                    if (++num6 > 49152)
                    {
                        CODebugBase<LogChannel>.Error(LogChannel.Core, "Invalid list detected!\n" + Environment.StackTrace);
                        break;
                    }
                }
                data.m_nextGridBuilding = 0;
            }
            finally
            {
                Monitor.Exit(buildingManager.m_buildingGrid);
            }
            if (info != null)
            {
                Singleton<RenderManager>.instance.UpdateGroup(num * 45 / 270, num2 * 45 / 270, info.m_prefabDataLayer);
            }
        }
    }
}
