private static int GetNumberOfMainRoads(ushort nodeId, ref NetNode node)
        {
            var numMainRoads = 0;

            for (var s = 0; s < 8; s++)
            {
                var segmentId2 = node.GetSegment(s);

                if (segmentId2 == 0 ||
                    !TrafficPriority.IsPrioritySegment(nodeId, segmentId2))
                {
                    continue;
                }
                var prioritySegment2 = TrafficPriority.GetPrioritySegment(nodeId,
                                                                          segmentId2);

                if (prioritySegment2.Type == SegmentEnd.PriorityType.Main)
                {
                    numMainRoads++;
                }
            }
            return(numMainRoads);
        }
        /// <summary>
        /// Displays segment ids over segments
        /// </summary>
        private void _guiSegments()
        {
            GUIStyle             _counterStyle = new GUIStyle();
            Array16 <NetSegment> segments      = Singleton <NetManager> .instance.m_segments;

            for (int i = 1; i < segments.m_size; ++i)
            {
                if (segments.m_buffer[i].m_flags == NetSegment.Flags.None)                 // segment is unused
                {
                    continue;
                }
#if !DEBUG
                if ((segments.m_buffer[i].m_flags & NetSegment.Flags.Untouchable) != NetSegment.Flags.None)
                {
                    continue;
                }
#endif
                var segmentInfo = segments.m_buffer[i].Info;

                Vector3 centerPos = segments.m_buffer[i].m_bounds.center;
                var     screenPos = Camera.main.WorldToScreenPoint(centerPos);
                screenPos.y = Screen.height - screenPos.y;

                if (screenPos.z < 0)
                {
                    continue;
                }

                var camPos = Singleton <SimulationManager> .instance.m_simulationView.m_position;
                var diff   = centerPos - camPos;
                if (diff.magnitude > DebugCloseLod)
                {
                    continue;                     // do not draw if too distant
                }
                if (Options.nodesOverlay)
                {
                    var zoom = 1.0f / diff.magnitude * 150f;

                    _counterStyle.fontSize         = (int)(12f * zoom);
                    _counterStyle.normal.textColor = new Color(1f, 0f, 0f);

                    String labelStr = "Segment " + i;
#if DEBUGx
                    labelStr += ", flags: " + segments.m_buffer[i].m_flags.ToString() + ", condition: " + segments.m_buffer[i].m_condition;
#endif
#if DEBUG
                    SegmentEnd startEnd = TrafficPriority.GetPrioritySegment(segments.m_buffer[i].m_startNode, (ushort)i);
                    SegmentEnd endEnd   = TrafficPriority.GetPrioritySegment(segments.m_buffer[i].m_endNode, (ushort)i);
                    labelStr += "\nstart? " + (startEnd != null) + " veh.: " + startEnd?.GetRegisteredVehicleCount() + ", end? " + (endEnd != null) + " veh.: " + endEnd?.GetRegisteredVehicleCount();
#endif
                    labelStr += "\nTraffic: " + segments.m_buffer[i].m_trafficDensity + " %";
#if MARKCONGESTEDSEGMENTS
                    if (CustomRoadAI.initDone && CustomRoadAI.segmentCongestion[i])
                    {
                        labelStr += " congested!";
                    }
#endif

                    float meanLaneSpeed = 0f;

                    int  lIndex     = 0;
                    uint laneId     = segments.m_buffer[i].m_lanes;
                    int  validLanes = 0;
                    while (lIndex < segmentInfo.m_lanes.Length && laneId != 0u)
                    {
                        NetInfo.Lane lane = segmentInfo.m_lanes[lIndex];
                        if (lane.CheckType(NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle, VehicleInfo.VehicleType.Car))
                        {
                            if (CustomRoadAI.laneMeanSpeeds[i] != null && lIndex < CustomRoadAI.laneMeanSpeeds[i].Length)
                            {
                                if (CustomRoadAI.laneMeanSpeeds[i][lIndex] >= 0)
                                {
                                    meanLaneSpeed += (float)CustomRoadAI.laneMeanSpeeds[i][lIndex];
                                    ++validLanes;
                                }
                            }
                        }
                        lIndex++;
                        laneId = Singleton <NetManager> .instance.m_lanes.m_buffer[laneId].m_nextLane;
                    }

                    if (validLanes > 0)
                    {
                        meanLaneSpeed /= Convert.ToSingle(validLanes);
                    }

                    /*if (CustomRoadAI.InStartupPhase)
                     *      labelStr += " (in start-up phase)";
                     * else*/
                    labelStr += " (avg. speed: " + String.Format("{0:0.##}", meanLaneSpeed) + " %)";

#if DEBUG
                    labelStr += "\nstart: " + segments.m_buffer[i].m_startNode + ", end: " + segments.m_buffer[i].m_endNode;
#endif

                    Vector2 dim       = _counterStyle.CalcSize(new GUIContent(labelStr));
                    Rect    labelRect = new Rect(screenPos.x - dim.x / 2f, screenPos.y, dim.x, dim.y);

                    GUI.Label(labelRect, labelStr, _counterStyle);

                    if (Options.showLanes)
                    {
                        _guiLanes((ushort)i, ref segments.m_buffer[i], ref segmentInfo);
                    }
                }
            }
        }
        /// <summary>
        /// Checks for traffic lights and priority signs when changing segments (for road & rail vehicles).
        /// Sets the maximum allowed speed <paramref name="maxSpeed"/> if segment change is not allowed (otherwise <paramref name="maxSpeed"/> has to be set by the calling method).
        /// </summary>
        /// <param name="vehicleId">vehicle id</param>
        /// <param name="vehicleData">vehicle data</param>
        /// <param name="lastFrameData">last frame data of vehicle</param>
        /// <param name="isRecklessDriver">if true, this vehicle ignores red traffic lights and priority signs</param>
        /// <param name="prevPos">previous path position</param>
        /// <param name="prevTargetNodeId">previous target node</param>
        /// <param name="prevLaneID">previous lane</param>
        /// <param name="position">current path position</param>
        /// <param name="targetNodeId">transit node</param>
        /// <param name="laneID">current lane</param>
        /// <param name="nextPosition">next path position</param>
        /// <param name="nextTargetNodeId">next target node</param>
        /// <param name="maxSpeed">maximum allowed speed (only valid if method returns false)</param>
        /// <returns>true, if the vehicle may change segments, false otherwise.</returns>
        internal static bool MayChangeSegment(ushort vehicleId, ref Vehicle vehicleData, ref Vehicle.Frame lastFrameData, bool isRecklessDriver, ref PathUnit.Position prevPos, ushort prevTargetNodeId, uint prevLaneID, ref PathUnit.Position position, ushort targetNodeId, uint laneID, ref PathUnit.Position nextPosition, ushort nextTargetNodeId, out float maxSpeed, bool debug = false)
        {
            debug = false;
            if (prevTargetNodeId != targetNodeId)
            {
                // method should only be called if targetNodeId == prevTargetNode
                maxSpeed = 0f;
                return(true);
            }

            bool         forceUpdatePos = false;
            VehicleState vehicleState   = null;

            try {
                vehicleState = VehicleStateManager.GetVehicleState(vehicleId);

                if (vehicleState == null)
                {
                    VehicleStateManager.OnPathFindReady(vehicleId, ref vehicleData);
                    vehicleState = VehicleStateManager.GetVehicleState(vehicleId);

                    if (vehicleState == null)
                    {
#if DEBUG
                        Log._Debug($"Could not get vehicle state of {vehicleId}!");
#endif
                    }
                    else
                    {
                        forceUpdatePos = true;
                    }
                }
            } catch (Exception e) {
                Log.Error("VehicleAI MayChangeSegment vehicle state error: " + e.ToString());
            }

            if (forceUpdatePos || Options.simAccuracy >= 2)
            {
                try {
                    VehicleStateManager.UpdateVehiclePos(vehicleId, ref vehicleData, ref prevPos, ref position);
                } catch (Exception e) {
                    Log.Error("VehicleAI MayChangeSegment Error: " + e.ToString());
                }
            }

            var netManager = Singleton <NetManager> .instance;

            uint currentFrameIndex        = Singleton <SimulationManager> .instance.m_currentFrameIndex;
            uint prevTargetNodeLower8Bits = (uint)((prevTargetNodeId << 8) / 32768);
            uint random = currentFrameIndex - prevTargetNodeLower8Bits & 255u;

            bool isRailVehicle = (vehicleData.Info.m_vehicleType & (VehicleInfo.VehicleType.Train | VehicleInfo.VehicleType.Metro)) != VehicleInfo.VehicleType.None;

            NetNode.Flags targetNodeFlags    = netManager.m_nodes.m_buffer[targetNodeId].m_flags;
            bool          hasTrafficLight    = (targetNodeFlags & NetNode.Flags.TrafficLights) != NetNode.Flags.None;
            bool          checkTrafficLights = false;
            if (!isRailVehicle)
            {
                // check if to check space

#if DEBUG
                if (debug)
                {
                    Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {vehicleId} is not a train.");
                }
#endif

                var  prevLaneFlags    = (NetLane.Flags)netManager.m_lanes.m_buffer[(int)((UIntPtr)prevLaneID)].m_flags;
                var  hasCrossing      = (targetNodeFlags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None;
                var  isJoinedJunction = (prevLaneFlags & NetLane.Flags.JoinedJunction) != NetLane.Flags.None;
                bool checkSpace       = !Flags.getEnterWhenBlockedAllowed(prevPos.m_segment, netManager.m_segments.m_buffer[prevPos.m_segment].m_startNode == targetNodeId) && !isRecklessDriver;
                //TrafficLightSimulation nodeSim = TrafficLightSimulation.GetNodeSimulation(destinationNodeId);
                //if (timedNode != null && timedNode.vehiclesMayEnterBlockedJunctions) {
                //	checkSpace = false;
                //}

                if (checkSpace)
                {
                    // check if there is enough space
                    if ((targetNodeFlags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction &&
                        netManager.m_nodes.m_buffer[targetNodeId].CountSegments() != 2)
                    {
                        var len = vehicleData.CalculateTotalLength(vehicleId) + 2f;
                        if (!netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].CheckSpace(len))
                        {
                            var sufficientSpace = false;
                            if (nextPosition.m_segment != 0 && netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_length < 30f)
                            {
                                NetNode.Flags nextTargetNodeFlags = netManager.m_nodes.m_buffer[nextTargetNodeId].m_flags;
                                if ((nextTargetNodeFlags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) != NetNode.Flags.Junction ||
                                    netManager.m_nodes.m_buffer[nextTargetNodeId].CountSegments() == 2)
                                {
                                    uint nextLaneId = PathManager.GetLaneID(nextPosition);
                                    if (nextLaneId != 0u)
                                    {
                                        sufficientSpace = netManager.m_lanes.m_buffer[(int)((UIntPtr)nextLaneId)].CheckSpace(len);
                                    }
                                }
                            }
                            if (!sufficientSpace)
                            {
                                maxSpeed = 0f;
                                try {
                                    if (vehicleState != null)
                                    {
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to BLOCKED");
                                        }
#endif

                                        vehicleState.JunctionTransitState = VehicleJunctionTransitState.Blocked;
                                    }
                                } catch (Exception e) {
                                    Log.Error("VehicleAI MayChangeSegment error while setting junction state to BLOCKED: " + e.ToString());
                                }
                                return(false);
                            }
                        }
                    }
                }

                checkTrafficLights = (!isJoinedJunction || hasCrossing);
            }
            else
            {
#if DEBUG
                if (debug)
                {
                    Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {vehicleId} is a train.");
                }
#endif

                checkTrafficLights = true;
            }

            try {
                if (vehicleState != null && vehicleState.JunctionTransitState == VehicleJunctionTransitState.Blocked)
                {
#if DEBUG
                    if (debug)
                    {
                        Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState from BLOCKED to ENTER");
                    }
#endif
                    vehicleState.JunctionTransitState = VehicleJunctionTransitState.Enter;
                }

                if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == 0)
                {
                    if (hasTrafficLight && checkTrafficLights)
                    {
#if DEBUG
                        if (debug)
                        {
                            Log._Debug($"CustomVehicleAI.MayChangeSegment: Node {targetNodeId} has a traffic light.");
                        }
#endif

                        var destinationInfo = netManager.m_nodes.m_buffer[targetNodeId].Info;

                        if (vehicleState != null && vehicleState.JunctionTransitState == VehicleJunctionTransitState.None)
                        {
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to ENTER (1)");
                            }
#endif
                            vehicleState.JunctionTransitState = VehicleJunctionTransitState.Enter;
                        }

                        RoadBaseAI.TrafficLightState vehicleLightState;
                        RoadBaseAI.TrafficLightState pedestrianLightState;
                        bool vehicles;
                        bool pedestrians;
                        CustomRoadAI.GetTrafficLightState(vehicleId, ref vehicleData, targetNodeId, prevPos.m_segment, prevPos.m_lane, position.m_segment, ref netManager.m_segments.m_buffer[prevPos.m_segment], currentFrameIndex - prevTargetNodeLower8Bits, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians);

                        if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car && isRecklessDriver)                           // no reckless driving at railroad crossings
                        {
                            vehicleLightState = RoadBaseAI.TrafficLightState.Green;
                        }

#if DEBUG
                        if (debug)
                        {
                            Log._Debug($"CustomVehicleAI.MayChangeSegment: Vehicle {vehicleId} has {vehicleLightState} at node {targetNodeId}");
                        }
#endif

                        if (!vehicles && random >= 196u)
                        {
                            vehicles = true;
                            RoadBaseAI.SetTrafficLightState(targetNodeId, ref netManager.m_segments.m_buffer[prevPos.m_segment], currentFrameIndex - prevTargetNodeLower8Bits, vehicleLightState, pedestrianLightState, vehicles, pedestrians);
                        }

                        var stopCar = false;
                        switch (vehicleLightState)
                        {
                        case RoadBaseAI.TrafficLightState.RedToGreen:
                            if (random < 60u)
                            {
                                stopCar = true;
                            }
                            else
                            {
#if DEBUG
                                if (debug)
                                {
                                    Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to LEAVE (RedToGreen)");
                                }
#endif
                                if (vehicleState != null)
                                {
                                    vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                }
                            }
                            break;

                        case RoadBaseAI.TrafficLightState.Red:
                            stopCar = true;
                            break;

                        case RoadBaseAI.TrafficLightState.GreenToRed:
                            if (random >= 30u)
                            {
                                stopCar = true;
                            }
                            else if (vehicleState != null)
                            {
#if DEBUG
                                if (debug)
                                {
                                    Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to LEAVE (GreenToRed)");
                                }
#endif
                                vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                            }
                            break;
                        }

                        /*if ((vehicleLightState == RoadBaseAI.TrafficLightState.Green || vehicleLightState == RoadBaseAI.TrafficLightState.RedToGreen) && !Flags.getEnterWhenBlockedAllowed(prevPos.m_segment, netManager.m_segments.m_buffer[prevPos.m_segment].m_startNode == targetNodeId)) {
                         *      var hasIncomingCars = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, targetNodeId);
                         *
                         *      if (hasIncomingCars) {
                         *              // green light but other cars are incoming and they have priority: stop
                         *              stopCar = true;
                         *      }
                         * }*/

                        if (stopCar)
                        {
                            if (vehicleState != null)
                            {
#if DEBUG
                                if (debug)
                                {
                                    Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to STOP");
                                }
#endif
                                vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop;
                            }
                            maxSpeed = 0f;
                            return(false);
                        }
                    }
                    else if (vehicleState != null)
                    {
#if DEBUG
                        //bool debug = destinationNodeId == 10864;
                        //bool debug = destinationNodeId == 13531;
                        //bool debug = false;// targetNodeId == 5027;
#endif
                        //bool debug = false;
#if DEBUG
                        if (debug)
                        {
                            Log._Debug($"Vehicle {vehicleId} is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light.");
                        }
#endif

                        var prioritySegment = TrafficPriority.GetPrioritySegment(targetNodeId, prevPos.m_segment);
                        if (prioritySegment != null)
                        {
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Vehicle {vehicleId} is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {targetNodeId} which is not a traffic light and is a priority segment.");
                            }
#endif
                            //if (prioritySegment.HasVehicle(vehicleId)) {
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Vehicle {vehicleId}: segment target position found");
                            }
#endif
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Vehicle {vehicleId}: global target position found. carState = {vehicleState.JunctionTransitState.ToString()}");
                            }
#endif
                            var   currentFrameIndex2 = Singleton <SimulationManager> .instance.m_currentFrameIndex;
                            var   frame = currentFrameIndex2 >> 4;
                            float speed = lastFrameData.m_velocity.magnitude;

                            if (vehicleState.JunctionTransitState == VehicleJunctionTransitState.None)
                            {
#if DEBUG
                                if (debug)
                                {
                                    Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to ENTER (prio)");
                                }
#endif
                                vehicleState.JunctionTransitState = VehicleJunctionTransitState.Enter;
                            }

                            if (vehicleState.JunctionTransitState != VehicleJunctionTransitState.Leave)
                            {
                                bool hasIncomingCars;
                                switch (prioritySegment.Type)
                                {
                                case SegmentEnd.PriorityType.Stop:
#if DEBUG
                                    if (debug)
                                    {
                                        Log._Debug($"Vehicle {vehicleId}: STOP sign. waittime={vehicleState.WaitTime}, vel={speed}");
                                    }
#endif

                                    if (Options.simAccuracy <= 2 || (Options.simAccuracy >= 3 && vehicleState.WaitTime < MaxPriorityWaitTime))
                                    {
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to STOP (wait)");
                                        }
#endif
                                        vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop;

                                        if (speed <= TrafficPriority.maxStopVelocity)
                                        {
                                            vehicleState.WaitTime++;

                                            float minStopWaitTime = UnityEngine.Random.Range(0f, 3f);
                                            if (vehicleState.WaitTime >= minStopWaitTime)
                                            {
                                                if (Options.simAccuracy >= 4)
                                                {
                                                    vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                                }
                                                else
                                                {
                                                    hasIncomingCars = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, ref vehicleData, ref prevPos, ref position);
#if DEBUG
                                                    if (debug)
                                                    {
                                                        Log._Debug($"hasIncomingCars: {hasIncomingCars}");
                                                    }
#endif

                                                    if (hasIncomingCars)
                                                    {
                                                        maxSpeed = 0f;
                                                        return(false);
                                                    }
#if DEBUG
                                                    if (debug)
                                                    {
                                                        Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to LEAVE (min wait timeout)");
                                                    }
#endif
                                                    vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                                }
                                            }
                                            else
                                            {
                                                maxSpeed = 0;
                                                return(false);
                                            }
                                        }
                                        else
                                        {
                                            vehicleState.WaitTime = 0;
                                            maxSpeed = 0f;
                                            return(false);
                                        }
                                    }
                                    else
                                    {
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to LEAVE (max wait timeout)");
                                        }
#endif
                                        vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                    }
                                    break;

                                case SegmentEnd.PriorityType.Yield:
#if DEBUG
                                    if (debug)
                                    {
                                        Log._Debug($"Vehicle {vehicleId}: YIELD sign. waittime={vehicleState.WaitTime}");
                                    }
#endif

                                    if (Options.simAccuracy <= 2 || (Options.simAccuracy >= 3 && vehicleState.WaitTime < MaxPriorityWaitTime))
                                    {
                                        vehicleState.WaitTime++;
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to STOP (wait)");
                                        }
#endif
                                        vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop;

                                        if (speed <= TrafficPriority.maxYieldVelocity || Options.simAccuracy <= 2)
                                        {
                                            if (Options.simAccuracy >= 4)
                                            {
                                                vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                            }
                                            else
                                            {
                                                hasIncomingCars = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, ref vehicleData, ref prevPos, ref position);
#if DEBUG
                                                if (debug)
                                                {
                                                    Log._Debug($"Vehicle {vehicleId}: hasIncomingCars: {hasIncomingCars}");
                                                }
#endif

                                                if (hasIncomingCars)
                                                {
                                                    maxSpeed = 0f;
                                                    return(false);
                                                }
                                                else
                                                {
#if DEBUG
                                                    if (debug)
                                                    {
                                                        Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to LEAVE (no incoming cars)");
                                                    }
#endif
                                                    vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                                }
                                            }
                                        }
                                        else
                                        {
#if DEBUG
                                            if (debug)
                                            {
                                                Log._Debug($"Vehicle {vehicleId}: Vehicle has not yet reached yield speed (reduce {speed} by {vehicleState.ReduceSpeedByValueToYield})");
                                            }
#endif

                                            // vehicle has not yet reached yield speed
                                            maxSpeed = TrafficPriority.maxYieldVelocity;
                                            return(false);
                                        }
                                    }
                                    else
                                    {
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to LEAVE (max wait timeout)");
                                        }
#endif
                                        vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                    }
                                    break;

                                case SegmentEnd.PriorityType.Main:
                                case SegmentEnd.PriorityType.None:
#if DEBUG
                                    if (debug)
                                    {
                                        Log._Debug($"Vehicle {vehicleId}: MAIN sign. waittime={vehicleState.WaitTime}");
                                    }
#endif
                                    maxSpeed = 0f;

                                    if (Options.simAccuracy == 4)
                                    {
                                        return(true);
                                    }

                                    if (Options.simAccuracy <= 2 || (Options.simAccuracy == 3 && vehicleState.WaitTime < MaxPriorityWaitTime))
                                    {
                                        vehicleState.WaitTime++;
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to STOP (wait)");
                                        }
#endif
                                        vehicleState.JunctionTransitState = VehicleJunctionTransitState.Stop;

                                        hasIncomingCars = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, ref vehicleData, ref prevPos, ref position);
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"hasIncomingCars: {hasIncomingCars}");
                                        }
#endif

                                        if (hasIncomingCars)
                                        {
                                            return(false);
                                        }
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState to LEAVE (no conflicting car)");
                                        }
#endif
                                        vehicleState.JunctionTransitState = VehicleJunctionTransitState.Leave;
                                    }
                                    return(true);
                                }
                            }
                            else if (speed <= TrafficPriority.maxStopVelocity)
                            {
                                // vehicle is not moving. reset allowance to leave junction
#if DEBUG
                                if (debug)
                                {
                                    Log._Debug($"Vehicle {vehicleId}: Setting JunctionTransitState from LEAVE to BLOCKED (speed to low)");
                                }
#endif
                                vehicleState.JunctionTransitState = VehicleJunctionTransitState.Blocked;

                                maxSpeed = 0f;
                                return(false);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                Log.Error($"Error occured in MayChangeSegment: {e.ToString()}");
            }
            maxSpeed = 0f;             // maxSpeed should be set by caller
            return(true);
        }
        /// <summary>
        /// Calculates the current metrics for flowing and waiting vehicles
        /// </summary>
        /// <param name="wait"></param>
        /// <param name="flow"></param>
        /// <returns>true if the values could be calculated, false otherwise</returns>
        public bool calcWaitFlow(out float wait, out float flow)
        {
#if TRACE
            Singleton <CodeProfiler> .instance.Start("TimedTrafficLightsStep.calcWaitFlow");
#endif

#if DEBUGMETRIC
            bool debug = timedNode.NodeId == 3201;
#else
            bool debug = false;
#endif

#if DEBUGMETRIC
            if (debug)
            {
                Log.Warning($"TimedTrafficLightsStep.calcWaitFlow: ***START*** @ node {timedNode.NodeId}");
            }
#endif

            uint numFlows    = 0;
            uint numWaits    = 0;
            uint curMeanFlow = 0;
            uint curMeanWait = 0;

            // we are the master node. calculate traffic data
            foreach (ushort timedNodeId in timedNode.NodeGroup)
            {
                TrafficLightSimulation sim = TrafficLightSimulation.GetNodeSimulation(timedNodeId);
                if (sim == null || !sim.IsTimedLight())
                {
                    continue;
                }
                TimedTrafficLights slaveTimedNode = sim.TimedLight;
                if (slaveTimedNode.NumSteps() <= timedNode.CurrentStep)
                {
                    for (int i = 0; i < slaveTimedNode.NumSteps(); ++i)
                    {
                        slaveTimedNode.GetStep(i).invalid = true;
                    }
                    continue;
                }
                TimedTrafficLightsStep slaveStep = slaveTimedNode.Steps[timedNode.CurrentStep];

                //List<int> segmentIdsToDelete = new List<int>();

                // minimum time reached. check traffic!
                foreach (KeyValuePair <ushort, CustomSegmentLights> e in slaveStep.segmentLights)
                {
                    var fromSegmentId = e.Key;
                    var segLights     = e.Value;

                    // one of the traffic lights at this segment is green: count minimum traffic flowing through
                    SegmentEnd fromSeg = TrafficPriority.GetPrioritySegment(timedNodeId, fromSegmentId);
                    if (fromSeg == null)
                    {
#if DEBUGMETRIC
                        if (debug)
                        {
                            Log.Warning($"TimedTrafficLightsStep.calcWaitFlow: No priority segment @ seg. {fromSegmentId} found!");
                        }
#endif
                        //Log.Warning("stepDone(): prioSeg is null");
                        //segmentIdsToDelete.Add(fromSegmentId);
                        continue;                         // skip invalid segment
                    }

                    //bool startPhase = getCurrentFrame() <= startFrame + minTime + 2; // during start phase all vehicles on "green" segments are counted as flowing
                    ExtVehicleType validVehicleTypes = VehicleRestrictionsManager.GetAllowedVehicleTypes(fromSegmentId, timedNode.NodeId);

                    foreach (KeyValuePair <byte, ExtVehicleType> e2 in segLights.VehicleTypeByLaneIndex)
                    {
                        byte           laneIndex   = e2.Key;
                        ExtVehicleType vehicleType = e2.Value;
                        if (vehicleType != ExtVehicleType.None && (validVehicleTypes & vehicleType) == ExtVehicleType.None)
                        {
                            continue;
                        }
                        CustomSegmentLight segLight = segLights.GetCustomLight(laneIndex);
                        if (segLight == null)
                        {
                            Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {fromSegmentId}, node {timedNode.NodeId}!");
                            continue;
                        }

#if DEBUGMETRIC
                        if (debug)
                        {
                            Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Checking lane {laneIndex} @ seg. {fromSegmentId}. Vehicle types: {vehicleType}");
                        }
#endif

                        Dictionary <ushort, uint> carsFlowingToSegmentMetric = null;
                        Dictionary <ushort, uint> allCarsToSegmentMetric     = null;
                        try {
                            carsFlowingToSegmentMetric = fromSeg.GetVehicleMetricGoingToSegment(false, laneIndex, debug);
                        } catch (Exception ex) {
                            Log.Warning("calcWaitFlow (1): " + ex.ToString());
                        }

                        try {
                            allCarsToSegmentMetric = fromSeg.GetVehicleMetricGoingToSegment(true, laneIndex, debug);
                        } catch (Exception ex) {
                            Log.Warning("calcWaitFlow (2): " + ex.ToString());
                        }

                        if (carsFlowingToSegmentMetric == null)
                        {
                            continue;
                        }

                        // build directions from toSegment to fromSegment
                        Dictionary <ushort, Direction> directions = new Dictionary <ushort, Direction>();
                        foreach (KeyValuePair <ushort, uint> f in allCarsToSegmentMetric)
                        {
                            var             toSegmentId = f.Key;
                            SegmentGeometry geometry    = SegmentGeometry.Get(fromSegmentId);
                            Direction       dir         = geometry.GetDirection(toSegmentId, timedNodeId == geometry.StartNodeId());
                            directions[toSegmentId] = dir;
#if DEBUGMETRIC
                            if (debug)
                            {
                                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Calculated direction for seg. {fromSegmentId} -> seg. {toSegmentId}: {dir}");
                            }
#endif
                        }

                        // calculate waiting/flowing traffic
                        foreach (KeyValuePair <ushort, uint> f in allCarsToSegmentMetric)
                        {
                            ushort toSegmentId               = f.Key;
                            uint   totalNormCarLength        = f.Value;
                            uint   totalFlowingNormCarLength = carsFlowingToSegmentMetric[f.Key];

#if DEBUGMETRIC
                            if (debug)
                            {
                                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Total norm. car length of vehicles on lane {laneIndex} going to seg. {toSegmentId}: {totalNormCarLength}");
                            }
#endif

                            bool addToFlow = false;
                            switch (directions[toSegmentId])
                            {
                            case Direction.Turn:
                                addToFlow = TrafficPriority.IsLeftHandDrive() ? segLight.isRightGreen() : segLight.isLeftGreen();
                                break;

                            case Direction.Left:
                                addToFlow = segLight.isLeftGreen();
                                break;

                            case Direction.Right:
                                addToFlow = segLight.isRightGreen();
                                break;

                            case Direction.Forward:
                            default:
                                addToFlow = segLight.isForwardGreen();
                                break;
                            }

                            if (addToFlow)
                            {
                                ++numFlows;
                                curMeanFlow += totalFlowingNormCarLength;
                            }
                            else
                            {
                                ++numWaits;
                                curMeanWait += totalNormCarLength;
                            }

#if DEBUGMETRIC
                            if (debug)
                            {
                                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: Vehicles on lane {laneIndex} on seg. {fromSegmentId} going to seg. {toSegmentId} flowing? {addToFlow} curMeanFlow={curMeanFlow}, curMeanWait={curMeanWait}");
                            }
#endif
                        }
                    }
                }

                // delete invalid segments from step

                /*foreach (int segmentId in segmentIdsToDelete) {
                 *      slaveStep.segmentLightStates.Remove(segmentId);
                 * }*/

                if (slaveStep.segmentLights.Count <= 0)
                {
                    invalid = true;
                    flow    = 0f;
                    wait    = 0f;
#if TRACE
                    Singleton <CodeProfiler> .instance.Stop("TimedTrafficLightsStep.calcWaitFlow");
#endif
                    return(false);
                }
            }

#if DEBUGMETRIC
            if (debug)
            {
                Log._Debug($"TimedTrafficLightsStep.calcWaitFlow: ### Calculation completed. numFlows={numFlows}, numWaits={numWaits}, curMeanFlow={curMeanFlow}, curMeanWait={curMeanWait}");
            }

            wait = curMeanWait;
            flow = curMeanFlow;
#else
            if (numFlows > 0)
            {
                curMeanFlow /= numFlows;
            }
            if (numWaits > 0)
            {
                curMeanWait /= numWaits;
            }

            float fCurMeanFlow = curMeanFlow;
            fCurMeanFlow /= waitFlowBalance;             // a value smaller than 1 rewards steady traffic currents

            wait = (float)curMeanWait;
            flow = fCurMeanFlow;
#endif
#if TRACE
            Singleton <CodeProfiler> .instance.Stop("TimedTrafficLightsStep.calcWaitFlow");
#endif
            return(true);
        }
        public void TmCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextPosition,
                                               PathUnit.Position position, uint laneID, byte offset, PathUnit.Position prevPos, uint prevLaneID,
                                               byte prevOffset, out Vector3 pos, out Vector3 dir, out float maxSpeed)
        {
            var netManager = Singleton <NetManager> .instance;

            //var vehicleManager = Singleton<VehicleManager>.instance;
            netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].CalculatePositionAndDirection(offset * 0.003921569f, out pos, out dir);

            var lastFrameData       = vehicleData.GetLastFrameData();
            var lastFrameVehiclePos = lastFrameData.m_position;

            if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car)
            {
                // add vehicle to our vehicle list
                if (!TrafficPriority.VehicleList.ContainsKey(vehicleId))
                {
                    TrafficPriority.VehicleList.Add(vehicleId, new PriorityCar());
                }
            }

            HandleVehicle(vehicleId, ref vehicleData);

            // I think this is supposed to be the lane position?
            // [VN, 12/23/2015] It's the 3D car position on the Bezier curve of the lane.
            // This crazy 0.003921569f equals to 1f/255 and prevOffset is the byte value (0..255) of the car position.
            var vehiclePosOnBezier = netManager.m_lanes.m_buffer[(int)((UIntPtr)prevLaneID)].CalculatePosition(prevOffset * 0.003921569f);
            //ushort currentSegmentId = netManager.m_lanes.m_buffer[(int)((UIntPtr)prevLaneID)].m_segment;

            ushort destinationNodeId;
            ushort sourceNodeId;

            if (offset < position.m_offset)
            {
                destinationNodeId = netManager.m_segments.m_buffer[position.m_segment].m_startNode;
                sourceNodeId      = netManager.m_segments.m_buffer[position.m_segment].m_endNode;
            }
            else
            {
                destinationNodeId = netManager.m_segments.m_buffer[position.m_segment].m_endNode;
                sourceNodeId      = netManager.m_segments.m_buffer[position.m_segment].m_startNode;
            }
            var interestingNodeId = prevOffset == 0 ? netManager.m_segments.m_buffer[prevPos.m_segment].m_startNode :
                                    netManager.m_segments.m_buffer[prevPos.m_segment].m_endNode;

            // this seems to be like the required braking force in order to stop the vehicle within its half length.
            var crazyValue = 0.5f * lastFrameData.m_velocity.sqrMagnitude / m_info.m_braking + m_info.m_generatedInfo.m_size.z * 0.5f;

            // Essentially, this is true if the car has enough time and space to brake (e.g. for a red traffic light)
            if (destinationNodeId == interestingNodeId)
            {
                if (Vector3.Distance(lastFrameVehiclePos, vehiclePosOnBezier) >= crazyValue - 1f)
                {
                    var currentFrameIndex = Singleton <SimulationManager> .instance.m_currentFrameIndex;
                    var num5 = (uint)((interestingNodeId << 8) / 32768);
                    var num6 = currentFrameIndex - num5 & 255u;

                    var nodeFlags        = netManager.m_nodes.m_buffer[destinationNodeId].m_flags;
                    var prevLaneFlags    = (NetLane.Flags)netManager.m_lanes.m_buffer[(int)((UIntPtr)prevLaneID)].m_flags;
                    var hasTrafficLight  = (nodeFlags & NetNode.Flags.TrafficLights) != NetNode.Flags.None;
                    var hasCrossing      = (nodeFlags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None;
                    var isJoinedJunction = (prevLaneFlags & NetLane.Flags.JoinedJunction) != NetLane.Flags.None;
                    if ((nodeFlags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) ==
                        NetNode.Flags.Junction && netManager.m_nodes.m_buffer[destinationNodeId].CountSegments() != 2)
                    {
                        var len = vehicleData.CalculateTotalLength(vehicleId) + 2f;
                        if (!netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].CheckSpace(len))
                        {
                            var sufficientSpace = false;
                            if (nextPosition.m_segment != 0 &&
                                netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_length < 30f)
                            {
                                var flags3 = netManager.m_nodes.m_buffer[sourceNodeId].m_flags;
                                if ((flags3 &
                                     (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) !=
                                    NetNode.Flags.Junction || netManager.m_nodes.m_buffer[sourceNodeId].CountSegments() == 2)
                                {
                                    var laneId2 = PathManager.GetLaneID(nextPosition);
                                    if (laneId2 != 0u)
                                    {
                                        sufficientSpace = netManager.m_lanes.m_buffer[(int)((UIntPtr)laneId2)].CheckSpace(len);
                                    }
                                }
                            }
                            if (!sufficientSpace)
                            {
                                maxSpeed = 0f;
                                return;
                            }
                        }
                    }

                    if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car)
                    {
                        if (hasTrafficLight && (!isJoinedJunction || hasCrossing))
                        {
                            var nodeSimulation = TrafficPriority.GetNodeSimulation(interestingNodeId);

                            var destinationInfo = netManager.m_nodes.m_buffer[destinationNodeId].Info;
                            RoadBaseAI.TrafficLightState vehicleLightState;
                            ManualSegmentLight           light = TrafficLightsManual.GetSegmentLight(interestingNodeId, prevPos.m_segment);                   // TODO rework

                            if (light == null || nodeSimulation == null ||
                                (nodeSimulation.FlagTimedTrafficLights && !nodeSimulation.TimedTrafficLightsActive))
                            {
                                RoadBaseAI.TrafficLightState pedestrianLightState;
                                bool flag5;
                                bool pedestrians;
                                RoadBaseAI.GetTrafficLightState(interestingNodeId,
                                                                ref netManager.m_segments.m_buffer[prevPos.m_segment],
                                                                currentFrameIndex - num5, out vehicleLightState, out pedestrianLightState, out flag5,
                                                                out pedestrians);
                                if (!flag5 && num6 >= 196u)
                                {
                                    flag5 = true;
                                    RoadBaseAI.SetTrafficLightState(interestingNodeId,
                                                                    ref netManager.m_segments.m_buffer[prevPos.m_segment], currentFrameIndex - num5,
                                                                    vehicleLightState, pedestrianLightState, flag5, pedestrians);
                                }

                                if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == Vehicle.Flags.None ||
                                    destinationInfo.m_class.m_service != ItemClass.Service.Road)
                                {
                                    switch (vehicleLightState)
                                    {
                                    case RoadBaseAI.TrafficLightState.RedToGreen:
                                        if (num6 < 60u)
                                        {
                                            maxSpeed = 0f;
                                            return;
                                        }
                                        break;

                                    case RoadBaseAI.TrafficLightState.Red:
                                        maxSpeed = 0f;
                                        return;

                                    case RoadBaseAI.TrafficLightState.GreenToRed:
                                        if (num6 >= 30u)
                                        {
                                            maxSpeed = 0f;
                                            return;
                                        }
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                // traffic light simulation is active
                                var stopCar = false;

                                // determine responsible traffic light (left, right or main)
                                if (TrafficPriority.IsLeftSegment(prevPos.m_segment, position.m_segment, destinationNodeId))
                                {
                                    vehicleLightState = light.GetLightLeft();
                                }
                                else if (TrafficPriority.IsRightSegment(prevPos.m_segment, position.m_segment, destinationNodeId))
                                {
                                    vehicleLightState = light.GetLightRight();
                                }
                                else
                                {
                                    vehicleLightState = light.GetLightMain();
                                }

                                if (vehicleLightState == RoadBaseAI.TrafficLightState.Green)
                                {
                                    var hasIncomingCars = TrafficPriority.HasIncomingVehicles(vehicleId, destinationNodeId);

                                    if (hasIncomingCars)
                                    {
                                        // green light but other cars are incoming: slow approach
                                        maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f) * 0.01f;
                                        //stopCar = true;
                                    }
                                }

                                if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == Vehicle.Flags.None ||
                                    destinationInfo.m_class.m_service != ItemClass.Service.Road)
                                {
                                    switch (vehicleLightState)
                                    {
                                    case RoadBaseAI.TrafficLightState.RedToGreen:
                                        if (num6 < 60u)
                                        {
                                            stopCar = true;
                                        }
                                        break;

                                    case RoadBaseAI.TrafficLightState.Red:
                                        stopCar = true;
                                        break;

                                    case RoadBaseAI.TrafficLightState.GreenToRed:
                                        if (num6 >= 30u)
                                        {
                                            stopCar = true;
                                        }
                                        break;
                                    }
                                }

                                if (stopCar)
                                {
                                    maxSpeed = 0f;
                                    return;
                                }
                            }
                        }
                        else
                        {
                            if (TrafficPriority.VehicleList.ContainsKey(vehicleId) &&
                                TrafficPriority.IsPrioritySegment(destinationNodeId, prevPos.m_segment))
                            {
                                var currentFrameIndex2 = Singleton <SimulationManager> .instance.m_currentFrameIndex;
                                var frame = currentFrameIndex2 >> 4;

                                var prioritySegment = TrafficPriority.GetPrioritySegment(destinationNodeId, prevPos.m_segment);

                                if (TrafficPriority.VehicleList[vehicleId].CarState == CarState.None)
                                {
                                    TrafficPriority.VehicleList[vehicleId].CarState = CarState.Enter;
                                }

                                if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == Vehicle.Flags.None &&
                                    TrafficPriority.VehicleList[vehicleId].CarState != CarState.Leave)
                                {
                                    bool hasIncomingCars;
                                    switch (prioritySegment.Type)
                                    {
                                    case PrioritySegment.PriorityType.Stop:
                                        if (TrafficPriority.VehicleList[vehicleId].WaitTime < 75)
                                        {
                                            TrafficPriority.VehicleList[vehicleId].CarState = CarState.Stop;

                                            if (lastFrameData.m_velocity.sqrMagnitude < 0.1f ||
                                                TrafficPriority.VehicleList[vehicleId].Stopped)
                                            {
                                                TrafficPriority.VehicleList[vehicleId].Stopped = true;
                                                TrafficPriority.VehicleList[vehicleId].WaitTime++;

                                                if (TrafficPriority.VehicleList[vehicleId].WaitTime > 2)
                                                {
                                                    hasIncomingCars = TrafficPriority.HasIncomingVehicles(vehicleId, destinationNodeId);

                                                    if (hasIncomingCars)
                                                    {
                                                        maxSpeed = 0f;
                                                        return;
                                                    }
                                                    TrafficPriority.VehicleList[vehicleId].CarState =
                                                        CarState.Leave;
                                                }
                                                else
                                                {
                                                    maxSpeed = 0f;
                                                    return;
                                                }
                                            }
                                            else
                                            {
                                                maxSpeed = 0f;
                                                return;
                                            }
                                        }
                                        else
                                        {
                                            TrafficPriority.VehicleList[vehicleId].CarState = CarState.Leave;
                                        }
                                        break;

                                    case PrioritySegment.PriorityType.Yield:
                                        if (TrafficPriority.VehicleList[vehicleId].WaitTime < 75)
                                        {
                                            TrafficPriority.VehicleList[vehicleId].WaitTime++;
                                            TrafficPriority.VehicleList[vehicleId].CarState = CarState.Stop;
                                            maxSpeed = 0f;

                                            if (lastFrameData.m_velocity.sqrMagnitude <
                                                TrafficPriority.VehicleList[vehicleId].ReduceSpeedByValueToYield)
                                            {
                                                hasIncomingCars = TrafficPriority.HasIncomingVehicles(vehicleId, destinationNodeId);

                                                if (hasIncomingCars)
                                                {
                                                    return;
                                                }
                                            }
                                            else
                                            {
                                                maxSpeed = lastFrameData.m_velocity.sqrMagnitude -
                                                           TrafficPriority.VehicleList[vehicleId]
                                                           .ReduceSpeedByValueToYield;
                                                return;
                                            }
                                        }
                                        else
                                        {
                                            TrafficPriority.VehicleList[vehicleId].CarState = CarState.Leave;
                                        }
                                        break;

                                    case PrioritySegment.PriorityType.Main:
                                        TrafficPriority.VehicleList[vehicleId].WaitTime++;
                                        TrafficPriority.VehicleList[vehicleId].CarState = CarState.Stop;
                                        maxSpeed = 0f;

                                        hasIncomingCars = TrafficPriority.HasIncomingVehicles(vehicleId, destinationNodeId);

                                        if (hasIncomingCars)
                                        {
                                            TrafficPriority.VehicleList[vehicleId].Stopped = true;
                                            return;
                                        }
                                        TrafficPriority.VehicleList[vehicleId].Stopped = false;

                                        var info3 = netManager.m_segments.m_buffer[position.m_segment].Info;
                                        if (info3.m_lanes != null && info3.m_lanes.Length > position.m_lane)
                                        {
                                            maxSpeed =
                                                CalculateTargetSpeed(vehicleId, ref vehicleData,
                                                                     info3.m_lanes[position.m_lane].m_speedLimit,
                                                                     netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_curve) * 0.8f;
                                        }
                                        else
                                        {
                                            maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f) *
                                                       0.8f;
                                        }
                                        return;
                                    }
                                }
                                else
                                {
                                    TrafficPriority.VehicleList[vehicleId].CarState = CarState.Transit;
                                }
                            }
                        }
                    }
                }
            }

            var info2 = netManager.m_segments.m_buffer[position.m_segment].Info;

            if (info2.m_lanes != null && info2.m_lanes.Length > position.m_lane)
            {
                var laneSpeedLimit = info2.m_lanes[position.m_lane].m_speedLimit;

                if (TrafficRoadRestrictions.IsSegment(position.m_segment))
                {
                    var restrictionSegment = TrafficRoadRestrictions.GetSegment(position.m_segment);

                    if (restrictionSegment.SpeedLimits[position.m_lane] > 0.1f)
                    {
                        laneSpeedLimit = restrictionSegment.SpeedLimits[position.m_lane];
                    }
                }

                maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit,
                                                netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_curve);
            }
            else
            {
                maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f);
            }
        }
        private static void LoadDataState()
        {
            Log.Info("Loading State from Config");
            if (_configuration == null)
            {
                Log.Warning("Configuration NULL, Couldn't load save data. Possibly a new game?");
                return;
            }

            // load priority segments
            if (_configuration.PrioritySegments != null)
            {
                Log.Info($"Loading {_configuration.PrioritySegments.Count()} priority segments");
                foreach (var segment in _configuration.PrioritySegments)
                {
                    try {
                        if (segment.Length < 3)
                        {
                            continue;
                        }
#if DEBUG
                        bool debug = segment[0] == 13630;
#endif

                        if ((SegmentEnd.PriorityType)segment[2] == SegmentEnd.PriorityType.None)
                        {
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Loading priority segment: Not adding 'None' priority segment: {segment[1]} @ node {segment[0]}");
                            }
#endif
                            continue;
                        }

                        if ((Singleton <NetManager> .instance.m_nodes.m_buffer[segment[0]].m_flags & NetNode.Flags.Created) == NetNode.Flags.None)
                        {
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Loading priority segment: node {segment[0]} is invalid");
                            }
#endif
                            continue;
                        }
                        if ((Singleton <NetManager> .instance.m_segments.m_buffer[segment[1]].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None)
                        {
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Loading priority segment: segment {segment[1]} @ node {segment[0]} is invalid");
                            }
#endif
                            continue;
                        }
                        if (TrafficPriority.IsPrioritySegment((ushort)segment[0], (ushort)segment[1]))
                        {
#if DEBUG
                            if (debug)
                            {
                                Log._Debug($"Loading priority segment: segment {segment[1]} @ node {segment[0]} is already a priority segment");
                            }
#endif
                            TrafficPriority.GetPrioritySegment((ushort)segment[0], (ushort)segment[1]).Type = (SegmentEnd.PriorityType)segment[2];
                            continue;
                        }
#if DEBUG
                        Log._Debug($"Adding Priority Segment of type: {segment[2].ToString()} to segment {segment[1]} @ node {segment[0]}");
#endif
                        TrafficPriority.AddPrioritySegment((ushort)segment[0], (ushort)segment[1], (SegmentEnd.PriorityType)segment[2]);
                    } catch (Exception e) {
                        // ignore, as it's probably corrupt save data. it'll be culled on next save
                        Log.Warning("Error loading data from Priority segments: " + e.ToString());
                    }
                }
            }
            else
            {
                Log.Warning("Priority segments data structure undefined!");
            }

            // load vehicle restrictions (warning: has to be done before loading timed lights!)
            if (_configuration.LaneAllowedVehicleTypes != null)
            {
                Log.Info($"Loading lane vehicle restriction data. {_configuration.LaneAllowedVehicleTypes.Count} elements");
                foreach (Configuration.LaneVehicleTypes laneVehicleTypes in _configuration.LaneAllowedVehicleTypes)
                {
                    try {
                        ExtVehicleType baseMask   = VehicleRestrictionsManager.GetBaseMask(laneVehicleTypes.laneId);
                        ExtVehicleType maskedType = laneVehicleTypes.vehicleTypes & baseMask;
                        Log._Debug($"Loading lane vehicle restriction: lane {laneVehicleTypes.laneId} = {laneVehicleTypes.vehicleTypes}, masked = {maskedType}");
                        if (maskedType != baseMask)
                        {
                            Flags.setLaneAllowedVehicleTypes(laneVehicleTypes.laneId, maskedType);
                        }
                        else
                        {
                            Log._Debug($"Masked type does not differ from base type. Ignoring.");
                        }
                    } catch (Exception e) {
                        // ignore, as it's probably corrupt save data. it'll be culled on next save
                        Log.Warning("Error loading data from vehicle restrictions: " + e.ToString());
                    }
                }
            }
            else
            {
                Log.Warning("Vehicle restrctions structure undefined!");
            }

            var timedStepCount        = 0;
            var timedStepSegmentCount = 0;

            NetManager netManager = Singleton <NetManager> .instance;

            if (_configuration.TimedLights != null)
            {
                Log.Info($"Loading {_configuration.TimedLights.Count()} timed traffic lights (new method)");

                foreach (Configuration.TimedTrafficLights cnfTimedLights in _configuration.TimedLights)
                {
                    try {
                        if ((Singleton <NetManager> .instance.m_nodes.m_buffer[cnfTimedLights.nodeId].m_flags & NetNode.Flags.Created) == NetNode.Flags.None)
                        {
                            continue;
                        }
                        Flags.setNodeTrafficLight(cnfTimedLights.nodeId, true);

                        Log._Debug($"Adding Timed Node at node {cnfTimedLights.nodeId}");

                        TrafficLightSimulation sim = TrafficLightSimulation.AddNodeToSimulation(cnfTimedLights.nodeId);
                        sim.SetupTimedTrafficLight(cnfTimedLights.nodeGroup);
                        var timedNode = sim.TimedLight;

                        int j = 0;
                        foreach (Configuration.TimedTrafficLightsStep cnfTimedStep in cnfTimedLights.timedSteps)
                        {
                            Log._Debug($"Loading timed step {j} at node {cnfTimedLights.nodeId}");
                            TimedTrafficLightsStep step = timedNode.AddStep(cnfTimedStep.minTime, cnfTimedStep.maxTime, cnfTimedStep.waitFlowBalance);

                            foreach (KeyValuePair <ushort, Configuration.CustomSegmentLights> e in cnfTimedStep.segmentLights)
                            {
                                Log._Debug($"Loading timed step {j}, segment {e.Key} at node {cnfTimedLights.nodeId}");
                                CustomSegmentLights lights = null;
                                if (!step.segmentLights.TryGetValue(e.Key, out lights))
                                {
                                    Log._Debug($"No segment lights found at timed step {j} for segment {e.Key}, node {cnfTimedLights.nodeId}");
                                    continue;
                                }
                                Configuration.CustomSegmentLights cnfLights = e.Value;

                                Log._Debug($"Loading pedestrian light @ seg. {e.Key}, step {j}: {cnfLights.pedestrianLightState} {cnfLights.manualPedestrianMode}");

                                lights.ManualPedestrianMode = cnfLights.manualPedestrianMode;
                                lights.PedestrianLightState = cnfLights.pedestrianLightState;

                                foreach (KeyValuePair <ExtVehicleType, Configuration.CustomSegmentLight> e2 in cnfLights.customLights)
                                {
                                    Log._Debug($"Loading timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}");
                                    CustomSegmentLight light = null;
                                    if (!lights.CustomLights.TryGetValue(e2.Key, out light))
                                    {
                                        Log._Debug($"No segment light found for timed step {j}, segment {e.Key}, vehicleType {e2.Key} at node {cnfTimedLights.nodeId}");
                                        continue;
                                    }
                                    Configuration.CustomSegmentLight cnfLight = e2.Value;

                                    light.CurrentMode = (CustomSegmentLight.Mode)cnfLight.currentMode;
                                    light.LightLeft   = cnfLight.leftLight;
                                    light.LightMain   = cnfLight.mainLight;
                                    light.LightRight  = cnfLight.rightLight;
                                }
                            }
                            ++j;
                        }

                        if (cnfTimedLights.started)
                        {
                            timedNode.Start();
                        }
                    } catch (Exception e) {
                        // ignore, as it's probably corrupt save data. it'll be culled on next save
                        Log.Warning("Error loading data from TimedNode (new method): " + e.ToString());
                    }
                }
            }
            else if (_configuration.TimedNodes != null && _configuration.TimedNodeGroups != null)
            {
                Log.Info($"Loading {_configuration.TimedNodes.Count()} timed traffic lights (old method)");
                for (var i = 0; i < _configuration.TimedNodes.Count; i++)
                {
                    try {
                        var nodeid = (ushort)_configuration.TimedNodes[i][0];
                        if ((Singleton <NetManager> .instance.m_nodes.m_buffer[nodeid].m_flags & NetNode.Flags.Created) == NetNode.Flags.None)
                        {
                            continue;
                        }
                        Flags.setNodeTrafficLight(nodeid, true);

                        Log._Debug($"Adding Timed Node {i} at node {nodeid}");

                        var nodeGroup = new List <ushort>();
                        for (var j = 0; j < _configuration.TimedNodeGroups[i].Length; j++)
                        {
                            nodeGroup.Add(_configuration.TimedNodeGroups[i][j]);
                        }

                        TrafficLightSimulation sim = TrafficLightSimulation.AddNodeToSimulation(nodeid);
                        sim.SetupTimedTrafficLight(nodeGroup);
                        var timedNode = sim.TimedLight;

                        timedNode.CurrentStep = _configuration.TimedNodes[i][1];

                        for (var j = 0; j < _configuration.TimedNodes[i][2]; j++)
                        {
                            var cfgstep = _configuration.TimedNodeSteps[timedStepCount];
                            // old (pre 1.3.0):
                            //   cfgstep[0]: time of step
                            //   cfgstep[1]: number of segments
                            // new (post 1.3.0):
                            //   cfgstep[0]: min. time of step
                            //   cfgstep[1]: max. time of step
                            //   cfgstep[2]: number of segments

                            int minTime = 1;
                            int maxTime = 1;
                            //int numSegments = 0;
                            float waitFlowBalance = 1f;

                            if (cfgstep.Length == 2)
                            {
                                minTime = cfgstep[0];
                                maxTime = cfgstep[0];
                                //numSegments = cfgstep[1];
                            }
                            else if (cfgstep.Length >= 3)
                            {
                                minTime = cfgstep[0];
                                maxTime = cfgstep[1];
                                //numSegments = cfgstep[2];
                                if (cfgstep.Length == 4)
                                {
                                    waitFlowBalance = Convert.ToSingle(cfgstep[3]) / 10f;
                                }
                                if (cfgstep.Length == 5)
                                {
                                    waitFlowBalance = Convert.ToSingle(cfgstep[4]) / 1000f;
                                }
                            }

                            Log._Debug($"Adding timed step to node {nodeid}: min/max: {minTime}/{maxTime}, waitFlowBalance: {waitFlowBalance}");

                            timedNode.AddStep(minTime, maxTime, waitFlowBalance);
                            var step = timedNode.Steps[j];

                            for (var s = 0; s < 8; s++)
                            {
                                var segmentId = netManager.m_nodes.m_buffer[nodeid].GetSegment(s);
                                if (segmentId <= 0)
                                {
                                    continue;
                                }

                                bool tooFewSegments = (timedStepSegmentCount >= _configuration.TimedNodeStepSegments.Count);

                                var leftLightState           = tooFewSegments ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)_configuration.TimedNodeStepSegments[timedStepSegmentCount][0];
                                var mainLightState           = tooFewSegments ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)_configuration.TimedNodeStepSegments[timedStepSegmentCount][1];
                                var rightLightState          = tooFewSegments ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)_configuration.TimedNodeStepSegments[timedStepSegmentCount][2];
                                var pedLightState            = tooFewSegments ? RoadBaseAI.TrafficLightState.Red : (RoadBaseAI.TrafficLightState)_configuration.TimedNodeStepSegments[timedStepSegmentCount][3];
                                CustomSegmentLight.Mode?mode = null;
                                if (_configuration.TimedNodeStepSegments[timedStepSegmentCount].Length >= 5)
                                {
                                    mode = (CustomSegmentLight.Mode)_configuration.TimedNodeStepSegments[timedStepSegmentCount][4];
                                }

                                foreach (KeyValuePair <ExtVehicleType, CustomSegmentLight> e in step.segmentLights[segmentId].CustomLights)
                                {
                                    //ManualSegmentLight segmentLight = new ManualSegmentLight(step.NodeId, step.segmentIds[k], mainLightState, leftLightState, rightLightState, pedLightState);
                                    e.Value.LightLeft  = leftLightState;
                                    e.Value.LightMain  = mainLightState;
                                    e.Value.LightRight = rightLightState;
                                    if (mode != null)
                                    {
                                        e.Value.CurrentMode = (CustomSegmentLight.Mode)mode;
                                    }
                                }

                                if (step.segmentLights[segmentId].PedestrianLightState != null)
                                {
                                    step.segmentLights[segmentId].PedestrianLightState = pedLightState;
                                }

                                timedStepSegmentCount++;
                            }
                            timedStepCount++;
                        }

                        if (Convert.ToBoolean(_configuration.TimedNodes[i][3]))
                        {
                            timedNode.Start();
                        }
                    } catch (Exception e) {
                        // ignore, as it's probably corrupt save data. it'll be culled on next save
                        Log.Warning("Error loading data from the TimedNodes: " + e.ToString());
                    }
                }
            }
            else
            {
                Log.Warning("Timed traffic lights data structure undefined!");
            }

            var trafficLightDefs = _configuration.NodeTrafficLights.Split(',');

            Log.Info($"Loading junction traffic light data");
            if (trafficLightDefs.Length <= 1)
            {
                // old method
                Log.Info($"Using old method to load traffic light data");

                var saveDataIndex = 0;
                for (var i = 0; i < Singleton <NetManager> .instance.m_nodes.m_buffer.Length; i++)
                {
                    //Log.Message($"Adding NodeTrafficLights iteration: {i1}");
                    try {
                        if ((Singleton <NetManager> .instance.m_nodes.m_buffer[i].Info.m_class.m_service != ItemClass.Service.Road &&
                             Singleton <NetManager> .instance.m_nodes.m_buffer[i].Info.m_class.m_service != ItemClass.Service.PublicTransport) ||
                            (Singleton <NetManager> .instance.m_nodes.m_buffer[i].m_flags & NetNode.Flags.Created) == NetNode.Flags.None)
                        {
                            continue;
                        }

                        // prevent overflow
                        if (_configuration.NodeTrafficLights.Length > saveDataIndex)
                        {
                            var trafficLight = _configuration.NodeTrafficLights[saveDataIndex];
#if DEBUG
                            Log._Debug("Setting traffic light flag for node " + i + ": " + (trafficLight == '1'));
#endif
                            Flags.setNodeTrafficLight((ushort)i, trafficLight == '1');
                        }
                        ++saveDataIndex;
                    } catch (Exception e) {
                        // ignore as it's probably bad save data.
                        Log.Warning("Error setting the NodeTrafficLights (old): " + e.Message);
                    }
                }
            }
            else
            {
                // new method
                foreach (var split in trafficLightDefs.Select(def => def.Split(':')).Where(split => split.Length > 1))
                {
                    try {
                        Log.Info($"Traffic light split data: {split[0]} , {split[1]}");
                        var  nodeId = Convert.ToUInt16(split[0]);
                        uint flag   = Convert.ToUInt16(split[1]);

                        Flags.setNodeTrafficLight(nodeId, flag > 0);
                    } catch (Exception e) {
                        // ignore as it's probably bad save data.
                        Log.Warning("Error setting the NodeTrafficLights (new): " + e.Message);
                    }
                }
            }

            if (_configuration.LaneFlags != null)
            {
                Log.Info($"Loading lane arrow data");
#if DEBUG
                Log._Debug($"LaneFlags: {_configuration.LaneFlags}");
#endif
                var lanes = _configuration.LaneFlags.Split(',');

                if (lanes.Length > 1)
                {
                    foreach (var split in lanes.Select(lane => lane.Split(':')).Where(split => split.Length > 1))
                    {
                        try {
                            Log.Info($"Split Data: {split[0]} , {split[1]}");
                            var  laneId = Convert.ToUInt32(split[0]);
                            uint flags  = Convert.ToUInt32(split[1]);

                            //make sure we don't cause any overflows because of bad save data.
                            if (Singleton <NetManager> .instance.m_lanes.m_buffer.Length <= laneId)
                            {
                                continue;
                            }

                            if (flags > ushort.MaxValue)
                            {
                                continue;
                            }

                            if ((Singleton <NetManager> .instance.m_lanes.m_buffer[laneId].m_flags & (ushort)NetLane.Flags.Created) == 0 || Singleton <NetManager> .instance.m_lanes.m_buffer[laneId].m_segment == 0)
                            {
                                continue;
                            }

                            //Singleton<NetManager>.instance.m_lanes.m_buffer[laneId].m_flags = fixLaneFlags(Singleton<NetManager>.instance.m_lanes.m_buffer[laneId].m_flags);

                            uint laneArrowFlags = flags & Flags.lfr;
                            uint origFlags      = (Singleton <NetManager> .instance.m_lanes.m_buffer[laneId].m_flags & Flags.lfr);
#if DEBUG
                            Log._Debug("Setting flags for lane " + laneId + " to " + flags + " (" + ((Flags.LaneArrows)(laneArrowFlags)).ToString() + ")");
                            if ((origFlags | laneArrowFlags) == origFlags)                               // only load if setting differs from default
                            {
                                Log._Debug("Flags for lane " + laneId + " are original (" + ((NetLane.Flags)(origFlags)).ToString() + ")");
                            }
#endif
                            Flags.setLaneArrowFlags(laneId, (Flags.LaneArrows)(laneArrowFlags));
                        } catch (Exception e) {
                            Log.Error($"Error loading Lane Split data. Length: {split.Length} value: {split}\nError: {e.Message}");
                        }
                    }
                }
            }
            else
            {
                Log.Warning("Lane arrow data structure undefined!");
            }

            // load lane connections
            if (_configuration.LaneConnections != null)
            {
                Log.Info($"Loading {_configuration.LaneConnections.Count()} lane connections");
                foreach (Configuration.LaneConnection conn in _configuration.LaneConnections)
                {
                    try {
                        Log._Debug($"Loading lane connection: lane {conn.lowerLaneId} -> {conn.higherLaneId}");
                        Singleton <LaneConnectionManager> .instance.AddLaneConnection(conn.lowerLaneId, conn.higherLaneId, conn.lowerStartNode);
                    } catch (Exception e) {
                        // ignore, as it's probably corrupt save data. it'll be culled on next save
                        Log.Warning("Error loading data from lane connection: " + e.ToString());
                    }
                }
            }
            else
            {
                Log.Warning("Lane connection data structure undefined!");
            }

            // load speed limits
            if (_configuration.LaneSpeedLimits != null)
            {
                Log.Info($"Loading lane speed limit data. {_configuration.LaneSpeedLimits.Count} elements");
                foreach (Configuration.LaneSpeedLimit laneSpeedLimit in _configuration.LaneSpeedLimits)
                {
                    try {
                        Log._Debug($"Loading lane speed limit: lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit}");
                        Flags.setLaneSpeedLimit(laneSpeedLimit.laneId, laneSpeedLimit.speedLimit);
                    } catch (Exception e) {
                        // ignore, as it's probably corrupt save data. it'll be culled on next save
                        Log.Warning("Error loading speed limits: " + e.ToString());
                    }
                }
            }
            else
            {
                Log.Warning("Lane speed limit structure undefined!");
            }

            // Load segment-at-node flags
            if (_configuration.SegmentNodeConfs != null)
            {
                Log.Info($"Loading segment-at-node data. {_configuration.SegmentNodeConfs.Count} elements");
                foreach (Configuration.SegmentNodeConf segNodeConf in _configuration.SegmentNodeConfs)
                {
                    try {
                        if ((Singleton <NetManager> .instance.m_segments.m_buffer[segNodeConf.segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None)
                        {
                            continue;
                        }
                        Flags.setSegmentNodeFlags(segNodeConf.segmentId, true, segNodeConf.startNodeFlags);
                        Flags.setSegmentNodeFlags(segNodeConf.segmentId, false, segNodeConf.endNodeFlags);
                    } catch (Exception e) {
                        // ignore, as it's probably corrupt save data. it'll be culled on next save
                        Log.Warning("Error loading segment-at-node config: " + e.ToString());
                    }
                }
            }
            else
            {
                Log.Warning("Segment-at-node structure undefined!");
            }
        }
        private bool HandleVehicle(ushort vehicleId, ref Vehicle vehicleData, bool handleWatched)
        {
            bool handledVehicle = false;

            if (handleWatched)
            {
                var vehManager = Singleton <VehicleManager> .instance;
                // handle watched vehicles
                HashSet <ushort> toDelete = new HashSet <ushort>();
                foreach (ushort otherVehicleId in watchedVehicleIds)
                {
                    Vehicle otherVehicle = vehManager.m_vehicles.m_buffer[otherVehicleId];
                    if (otherVehicle.m_flags != Vehicle.Flags.None)
                    {
                        if (HandleVehicle(otherVehicleId, ref otherVehicle, false))
                        {
                            toDelete.Add(otherVehicleId);
                        }
                    }
                    else
                    {
                        toDelete.Add(otherVehicleId);
                    }
                }

                foreach (ushort vehicleIdToDelete in toDelete)
                {
                    watchedVehicleIds.Remove(vehicleIdToDelete);
                }
            }

            var netManager    = Singleton <NetManager> .instance;
            var lastFrameData = vehicleData.GetLastFrameData();

#if DEBUG
            List <String> logBuffer = new List <String>();
            bool          logme     = false;
#endif

            if (vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Car)
            {
                return(false);
            }
#if DEBUG
            if (vehicleId % 5 == 0)
            {
                logBuffer.Add("Calculating prio info for vehicleId " + vehicleId);
            }
#endif

            // we extract the segment information directly from the vehicle
            var currentPathId = vehicleData.m_path;
            //ushort realTimeSourceNode = 0; // the car is currently moving from this node...
            ushort realTimeDestinationNode = 0;                                                     // ... to this node ...
            //ushort realTimeVeryNextDestinationNode = 0; // ... and then to this node ...
            PathUnit.Position?       realTimePosition             = null;                           // car position at current frame
            PathUnit.Position        veryNextRealTimePosition     = default(PathUnit.Position);
            List <PathUnit.Position> nextRealTimePositions        = new List <PathUnit.Position>(); // car position after passing the next nodes
            List <ushort>            nextRealTimeDestinationNodes = new List <ushort>();            // upcoming node ids

#if DEBUG
            if (vehicleId % 5 == 0)
            {
                logBuffer.Add("* vehicleId " + vehicleId + ". currentPathId: " + currentPathId);
            }
#endif

            if (currentPathId > 0)
            {
                // vehicle has a path...
                var vehiclePathUnit = Singleton <PathManager> .instance.m_pathUnits.m_buffer[currentPathId];
                if ((vehiclePathUnit.m_pathFindFlags & PathUnit.FLAG_READY) != 0)
                {
                    // The path(unit) is established and is ready for use: get the vehicle's current position in terms of segment and lane
                    realTimePosition = vehiclePathUnit.GetPosition(vehicleData.m_pathPositionIndex >> 1);
                    var currentSegment = netManager.m_segments.m_buffer[realTimePosition.Value.m_segment];
                    if (realTimePosition.Value.m_offset == 0)
                    {
                        realTimeDestinationNode = currentSegment.m_startNode;
                        //realTimeSourceNode = currentSegment.m_endNode;
                    }
                    else
                    {
                        realTimeDestinationNode = currentSegment.m_endNode;
                        //realTimeSourceNode = currentSegment.m_startNode;
                    }

                    // evaluate upcoming path units
                    bool first = true;
                    for (byte pathPos = (byte)((vehicleData.m_pathPositionIndex >> 1) + 1); pathPos < vehiclePathUnit.m_positionCount; ++pathPos)
                    {
                        PathUnit.Position nextRealTimePosition = default(PathUnit.Position);
                        if (!vehiclePathUnit.GetPosition(pathPos, out nextRealTimePosition))                         // if this returns false, there is no next path unit
                        {
                            break;
                        }

                        ushort destNodeId = 0;
                        if (nextRealTimePosition.m_segment > 0)
                        {
                            var nextSegment = netManager.m_segments.m_buffer[nextRealTimePosition.m_segment];
                            if (nextRealTimePosition.m_offset == 0)
                            {
                                destNodeId = nextSegment.m_startNode;
                            }
                            else
                            {
                                destNodeId = nextSegment.m_endNode;
                            }
                        }

                        // this is the very next path node, save it separately
                        if (first)
                        {
                            veryNextRealTimePosition = nextRealTimePosition;
                            //realTimeVeryNextDestinationNode = destNodeId;
                            first = false;
                            if (!handleWatched)
                            {
                                break;
                                // we only need the first path unit
                            }
                        }

                        nextRealTimePositions.Add(nextRealTimePosition);
                        nextRealTimeDestinationNodes.Add(destNodeId);
                    }

                    // please don't ask why we use "m_pathPositionIndex >> 1" (which equals to "m_pathPositionIndex / 2") here (Though it would
                    // be interesting to know why they used such an ugly indexing scheme!!). I assume m_pathPositionIndex relates to the car's position
                    // on the segment. If it is even the car might be in the segement's first half and if it is odd, it might be in the segment's
                    // second half.
#if DEBUG
                    if (vehicleId % 5 == 0)
                    {
                        logBuffer.Add("* vehicleId " + vehicleId + ". *INFO* rtPos.seg=" + realTimePosition.Value.m_segment + " nrtPos.seg=" + veryNextRealTimePosition.m_segment);
                    }
#endif
                }
            }

            // we have seen the car!
            TrafficPriority.VehicleList[vehicleId].LastFrame = Singleton <SimulationManager> .instance.m_currentFrameIndex >> 4;
            TrafficPriority.VehicleList[vehicleId].LastSpeed = lastFrameData.m_velocity.sqrMagnitude;

#if DEBUG
            if (vehicleId % 5 == 0)
            {
                logBuffer.Add("* vehicleId " + vehicleId + ". ToNode: " + TrafficPriority.VehicleList[vehicleId].ToNode + ". FromSegment: " + TrafficPriority.VehicleList[vehicleId].FromSegment + ". FromLaneId: " + TrafficPriority.VehicleList[vehicleId].FromLaneId);
            }
#endif

            if (realTimePosition != null)
            {
                // we found a valid path unit
                var sourceLaneId = PathManager.GetLaneID(realTimePosition.Value);
                if (TrafficPriority.VehicleList[vehicleId].ToNode != realTimeDestinationNode ||
                    TrafficPriority.VehicleList[vehicleId].FromSegment != realTimePosition.Value.m_segment ||
                    TrafficPriority.VehicleList[vehicleId].FromLaneId != sourceLaneId)
                {
                    // vehicle information is not up-to-date. remove the car from the old priority segment (if existing)...
                    var oldNode    = TrafficPriority.VehicleList[vehicleId].ToNode;
                    var oldSegment = TrafficPriority.VehicleList[vehicleId].FromSegment;

                    var oldPrioritySegment = TrafficPriority.GetPrioritySegment(oldNode, oldSegment);
                    if (oldPrioritySegment != null)
                    {
                        TrafficPriority.VehicleList[vehicleId].WaitTime = 0;
                        TrafficPriority.VehicleList[vehicleId].Stopped  = false;
                        if (oldPrioritySegment.RemoveCar(vehicleId))
                        {
#if DEBUG
                            if (vehicleId % 5 == 0)
                            {
                                logBuffer.Add("### REMOVING vehicle " + vehicleId);
                            }
#endif
                        }
                        else
                        {
#if DEBUG
                            if (vehicleId % 5 == 0)
                            {
                                logBuffer.Add("* vehicleId " + vehicleId + " was NOT REMOVED!");
                            }
#endif
                        }
                    }
                    else
                    {
#if DEBUG
                        if (vehicleId % 5 == 0)
                        {
                            logBuffer.Add("* vehicleId " + vehicleId + ". oldPrioSeg is null");
                        }
#endif
                    }

#if DEBUG
                    if (vehicleId % 5 == 0)
                    {
                        logBuffer.Add("* vehicleId " + vehicleId + ". *VEHINFO* ToNode=" + realTimeDestinationNode + " FromSegment=" + realTimePosition.Value.m_segment + " FromLaneId=" + PathManager.GetLaneID(realTimePosition.Value) + " ToSegment=" + veryNextRealTimePosition.m_segment + " ToLaneId=" + (veryNextRealTimePosition.m_segment > 0 ? "" + PathManager.GetLaneID(veryNextRealTimePosition) : "n/a"));
                    }
#endif

                    if (handleWatched)
                    {
                        watchedVehicleIds.Remove(vehicleId);
                    }
                    handledVehicle = true;

                    // ... and add it to the new priority segment
                    var prioritySegment = TrafficPriority.GetPrioritySegment(realTimeDestinationNode, realTimePosition.Value.m_segment);
                    if (prioritySegment != null)
                    {
#if DEBUG
                        logme = true;
#endif
                        if (handleWatched)
                        {
                            watchedVehicleIds.Add(vehicleId);
                        }

                        TrafficPriority.VehicleList[vehicleId].ToNode      = realTimeDestinationNode;
                        TrafficPriority.VehicleList[vehicleId].FromSegment = realTimePosition.Value.m_segment;
                        TrafficPriority.VehicleList[vehicleId].FromLaneId  = PathManager.GetLaneID(realTimePosition.Value);
                        TrafficPriority.VehicleList[vehicleId].ToSegment   = veryNextRealTimePosition.m_segment;
                        if (veryNextRealTimePosition.m_segment > 0)
                        {
                            TrafficPriority.VehicleList[vehicleId].ToLaneId = PathManager.GetLaneID(veryNextRealTimePosition);
                        }
                        else
                        {
                            TrafficPriority.VehicleList[vehicleId].ToLaneId = 0;
                        }
                        TrafficPriority.VehicleList[vehicleId].FromLaneFlags             = netManager.m_lanes.m_buffer[TrafficPriority.VehicleList[vehicleId].FromLaneId].m_flags;
                        TrafficPriority.VehicleList[vehicleId].ReduceSpeedByValueToYield = Random.Range(13f, 18f);

                        if (prioritySegment.AddCar(vehicleId))
                        {
#if DEBUG
                            if (vehicleId % 5 == 0)
                            {
                                logBuffer.Add("* vehicleId " + vehicleId + " was added!");
                            }
#endif

                            /*if (vehicleId % 16 == 0) {
                             *      Log.Message("query: vehicleId: " + vehicleId + ", prevLaneID: " + prevLaneID + ", laneID: " + laneID + ", prevOffset: " + prevOffset + ", offset: " + offset);
                             *      Log.Warning("*** ADDING vehicle " + vehicleId + " at (prev/cur/next) lane: (" + TrafficPriority.VehicleList[vehicleId].FromLaneId + "/" + TrafficPriority.VehicleList[vehicleId].ToLaneId + "/" + (nextPosition.m_segment > 0 ? "" + PathManager.GetLaneID(nextPosition) : "n/a") + "), (prev/cur/next) offset: (" + prevPos.m_offset + "/" + position.m_offset + "/" + nextPosition.m_offset + "), (prev/cur/next) segment: (" + prevPos.m_segment + "/" + position.m_segment + "/" + nextPosition.m_segment + ") at (src/dest/intrst) node (" + sourceNodeId + "/" + destinationNodeId + "/" + interestingNodeId + "), pathId: " + vehicleData.m_path + ", pathPosIndex: " + vehicleData.m_pathPositionIndex);
                             * }*/
                        }
                        else
                        {
#if DEBUG
                            if (vehicleId % 5 == 0)
                            {
                                logBuffer.Add("* vehicleId " + vehicleId + " was NOT added!");
                            }
#endif
                        }
                    }
                    else
                    {
#if DEBUG
                        if (vehicleId % 5 == 0)
                        {
                            logBuffer.Add("* vehicleId " + vehicleId + ". prioSeg is null");
                        }
#endif
                    }

                    // add to watchlist if any upcoming node is a priority node
                    if (handleWatched)
                    {
                        for (int i = 0; i < nextRealTimePositions.Count; ++i)
                        {
                            if (nextRealTimePositions[i].m_segment > 0)
                            {
                                var nextPrioritySegment = TrafficPriority.GetPrioritySegment(nextRealTimeDestinationNodes[i], nextRealTimePositions[i].m_segment);
                                if (nextPrioritySegment != null)
                                {
                                    watchedVehicleIds.Add(vehicleId);
                                }
                            }
                        }
                    }
                }
                else
                {
                    var prioritySegment = TrafficPriority.GetPrioritySegment(realTimeDestinationNode, realTimePosition.Value.m_segment);
                    if (prioritySegment != null)
                    {
                        // vehicle is still on priority segment
                        if (handleWatched)
                        {
                            watchedVehicleIds.Add(vehicleId);
                        }
                    }
                }
            }
            else
            {
                if (TrafficPriority.VehicleList.ContainsKey(vehicleId))
                {
                    var oldNode    = TrafficPriority.VehicleList[vehicleId].ToNode;
                    var oldSegment = TrafficPriority.VehicleList[vehicleId].FromSegment;

                    var oldPrioritySegment = TrafficPriority.GetPrioritySegment(oldNode, oldSegment);
                    if (oldPrioritySegment != null)
                    {
                        oldPrioritySegment.RemoveCar(vehicleId);
                    }
                }

                if (handleWatched)
                {
                    watchedVehicleIds.Remove(vehicleId);
                }
                handledVehicle = true;
            }

#if DEBUG
            if (false && vehicleId == (ushort)12000 /*logme || vehicleId % 100 == 0*/)
            {
                if (logme)
                {
                    Log.Error("vehicleId: " + vehicleId + " ============================================");
                }
                else
                {
                    Log.Warning("vehicleId: " + vehicleId + " ============================================");
                }
                foreach (String logBuf in logBuffer)
                {
                    Log.Message(logBuf);
                }
            }
#endif

            return(handledVehicle);
        }
Пример #8
0
        /// <summary>
        /// Handles vehicle path information in order to manage special nodes (nodes with priority signs or traffic lights).
        /// Data like "vehicle X is on segment S0 and is going to segment S1" is collected.
        /// </summary>
        /// <param name="vehicleId"></param>
        /// <param name="vehicleData"></param>
        internal static void HandleVehicle(ushort vehicleId, ref Vehicle vehicleData, bool addTraffic, bool realTraffic, byte maxUpcomingPathPositions, bool debug = false)
        {
            if (maxUpcomingPathPositions <= 0)
            {
                maxUpcomingPathPositions = 1;                 // we need at least one upcoming path position
            }
            var netManager          = Singleton <NetManager> .instance;
            var lastFrameData       = vehicleData.GetLastFrameData();
            var lastFrameVehiclePos = lastFrameData.m_position;

#if DEBUGV
            var camPos = Camera.main.transform.position;
            //debug = (lastFrameVehiclePos - camPos).sqrMagnitude < CloseLod;
            debug = false;
            List <String> logBuffer = new List <String>();
            bool          logme     = false;
#endif
            if ((vehicleData.m_flags & Vehicle.Flags.Created) == 0)
            {
                TrafficPriority.RemoveVehicleFromSegments(vehicleId);
                return;
            }

            if (vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Car &&
                vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Train &&
                vehicleData.Info.m_vehicleType != VehicleInfo.VehicleType.Tram)
            {
                //Log._Debug($"HandleVehicle does not handle vehicles of type {vehicleData.Info.m_vehicleType}");
                return;
            }
#if DEBUGV
            logBuffer.Add("Calculating prio info for vehicleId " + vehicleId);
#endif

            ExtVehicleType?vehicleType = CustomVehicleAI.DetermineVehicleTypeFromVehicle(vehicleId, ref vehicleData);
            if (vehicleType == null)
            {
                Log.Warning($"Could not determine vehicle type of vehicle {vehicleId}!");
            }

            if (vehicleType == null || vehicleType == ExtVehicleType.None)
            {
                return;
            }

            // add vehicle to our vehicle list
            VehiclePosition vehiclePos = TrafficPriority.GetVehiclePosition(vehicleId);

            // we extract the segment information directly from the vehicle
            var                      currentPathUnitId        = vehicleData.m_path;
            List <ushort>            realTimeDestinationNodes = new List <ushort>();            // current and upcoming node ids
            List <PathUnit.Position> realTimePositions        = new List <PathUnit.Position>(); // current and upcoming vehicle positions

#if DEBUGV
            logBuffer.Add("* vehicleId " + vehicleId + ". currentPathId: " + currentPathUnitId + " pathPositionIndex: " + vehicleData.m_pathPositionIndex);
#endif

            if (currentPathUnitId > 0)
            {
                // vehicle has a path...
                if ((Singleton <PathManager> .instance.m_pathUnits.m_buffer[currentPathUnitId].m_pathFindFlags & PathUnit.FLAG_READY) != 0)
                {
                    // The path(unit) is established and is ready for use: get the vehicle's current position in terms of segment and lane
                    realTimePositions.Add(Singleton <PathManager> .instance.m_pathUnits.m_buffer[currentPathUnitId].GetPosition(vehicleData.m_pathPositionIndex >> 1));
                    if (realTimePositions[0].m_offset == 0)
                    {
                        realTimeDestinationNodes.Add(netManager.m_segments.m_buffer[realTimePositions[0].m_segment].m_startNode);
                    }
                    else
                    {
                        realTimeDestinationNodes.Add(netManager.m_segments.m_buffer[realTimePositions[0].m_segment].m_endNode);
                    }

                    if (maxUpcomingPathPositions > 0)
                    {
                        // evaluate upcoming path units
                        byte i          = 0;
                        uint pathUnitId = currentPathUnitId;
                        int  pathPos    = (byte)((vehicleData.m_pathPositionIndex >> 1) + 1);
                        while (true)
                        {
                            if (pathPos > 11)
                            {
                                // go to next path unit
                                pathPos    = 0;
                                pathUnitId = Singleton <PathManager> .instance.m_pathUnits.m_buffer[pathUnitId].m_nextPathUnit;
#if DEBUGV
                                logBuffer.Add("* vehicleId " + vehicleId + ". Going to next path unit (1). pathUnitId=" + pathUnitId);
#endif
                                if (pathUnitId <= 0)
                                {
                                    break;
                                }
                            }

                            PathUnit.Position nextRealTimePosition = default(PathUnit.Position);
                            if (!Singleton <PathManager> .instance.m_pathUnits.m_buffer[pathUnitId].GetPosition(pathPos, out nextRealTimePosition))                             // if this returns false, there is no next path unit
                            {
#if DEBUGV
                                logBuffer.Add("* vehicleId " + vehicleId + ". No next path unit! pathPos=" + pathPos + ", pathUnitId=" + pathUnitId);
#endif
                                break;
                            }

                            ushort destNodeId = 0;
                            if (nextRealTimePosition.m_segment > 0)
                            {
                                if (nextRealTimePosition.m_offset == 0)
                                {
                                    destNodeId = netManager.m_segments.m_buffer[nextRealTimePosition.m_segment].m_startNode;
                                }
                                else
                                {
                                    destNodeId = netManager.m_segments.m_buffer[nextRealTimePosition.m_segment].m_endNode;
                                }
                            }

#if DEBUGV
                            logBuffer.Add("* vehicleId " + vehicleId + ". Next path unit! node " + destNodeId + ", seg. " + nextRealTimePosition.m_segment + ", pathUnitId=" + pathUnitId + ", pathPos: " + pathPos);
#endif

                            realTimePositions.Add(nextRealTimePosition);
                            realTimeDestinationNodes.Add(destNodeId);

                            if (i >= maxUpcomingPathPositions - 1)
                            {
                                break;                                 // we calculate up to 2 upcoming path units at the moment
                            }
                            ++pathPos;
                            ++i;
                        }
                    }

                    // please don't ask why we use "m_pathPositionIndex >> 1" (which equals to "m_pathPositionIndex / 2") here (Though it would
                    // be interesting to know why they used such an ugly indexing scheme!!). I assume the oddness of m_pathPositionIndex relates
                    // to the car's position on the segment. If it is even the car might be in the segment's first half and if it is odd, it might
                    // be in the segment's second half.
#if DEBUGV
                    logBuffer.Add("* vehicleId " + vehicleId + ". *INFO* rtPos.seg=" + realTimePositions[0].m_segment + " nrtPos.seg=" + (realTimePositions.Count > 1 ? "" + realTimePositions[1].m_segment : "n/a"));
#endif
                }
            }

            // we have seen the car!
            vehiclePos.LastFrame = Singleton <SimulationManager> .instance.m_currentFrameIndex;

#if DEBUGV
            logBuffer.Add("* vehicleId " + vehicleId + ". ToNode: " + vehiclePos.ToNode + ". FromSegment: " + vehiclePos.FromSegment /* + ". FromLaneId: " + TrafficPriority.Vehicles[vehicleId].FromLaneId*/);
#endif
            if (addTraffic && vehicleData.m_leadingVehicle == 0 && realTimePositions.Count > 0)
            {
                // add traffic to lane
                uint laneId = PathManager.GetLaneID(realTimePositions[0]);
                CustomRoadAI.AddTraffic(laneId, (ushort)Mathf.RoundToInt(vehicleData.CalculateTotalLength(vehicleId)), (ushort)Mathf.RoundToInt(lastFrameData.m_velocity.magnitude), realTraffic);
            }

#if DEBUGV
            logBuffer.Add("* vehicleId " + vehicleId + ". Real time positions: " + realTimePositions.Count + ", Destination nodes: " + realTimeDestinationNodes.Count);
#endif
            if (realTimePositions.Count >= 1)
            {
                // we found a valid path unit
                var sourceLaneIndex = realTimePositions[0].m_lane;

                if (
                    !vehiclePos.Valid ||
                    vehiclePos.ToNode != realTimeDestinationNodes[0] ||
                    vehiclePos.FromSegment != realTimePositions[0].m_segment ||
                    vehiclePos.FromLaneIndex != sourceLaneIndex)
                {
                    // vehicle information is not up-to-date. remove the car from old priority segments (if existing)...
                    TrafficPriority.RemoveVehicleFromSegments(vehicleId);

                    if (realTimePositions.Count >= 2)
                    {
                        // save vehicle information for priority rule handling
                        vehiclePos.Valid                     = true;
                        vehiclePos.CarState                  = VehicleJunctionTransitState.None;
                        vehiclePos.WaitTime                  = 0;
                        vehiclePos.Stopped                   = false;
                        vehiclePos.ToNode                    = realTimeDestinationNodes[0];
                        vehiclePos.FromSegment               = realTimePositions[0].m_segment;
                        vehiclePos.FromLaneIndex             = realTimePositions[0].m_lane;
                        vehiclePos.ToSegment                 = realTimePositions[1].m_segment;
                        vehiclePos.ToLaneIndex               = realTimePositions[1].m_lane;
                        vehiclePos.ReduceSpeedByValueToYield = UnityEngine.Random.Range(16f, 28f);
                        vehiclePos.OnEmergency               = (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0;
                        vehiclePos.VehicleType               = (ExtVehicleType)vehicleType;

#if DEBUGV
                        logBuffer.Add($"* vehicleId {vehicleId}. Setting current position to: from {vehiclePos.FromSegment} (lane {vehiclePos.FromLaneIndex}), going over {vehiclePos.ToNode}, to {vehiclePos.ToSegment} (lane {vehiclePos.ToLaneIndex})");
#endif

                        //if (!Options.disableSomething) {
                        // add the vehicle to upcoming priority segments that have timed traffic lights
                        for (int i = 0; i < realTimePositions.Count - 1; ++i)
                        {
                            var prioritySegment = TrafficPriority.GetPrioritySegment(realTimeDestinationNodes[i], realTimePositions[i].m_segment);
                            if (prioritySegment == null)
                            {
                                continue;
                            }

                            // add upcoming segments only if there is a timed traffic light
                            TrafficLightSimulation nodeSim = TrafficLightSimulation.GetNodeSimulation(realTimeDestinationNodes[i]);
                            if (i > 0 && (nodeSim == null || !nodeSim.IsTimedLight() || !nodeSim.IsTimedLightActive()))
                            {
                                continue;
                            }

                            VehiclePosition upcomingVehiclePos = new VehiclePosition();
                            upcomingVehiclePos.Valid                     = true;
                            upcomingVehiclePos.CarState                  = VehicleJunctionTransitState.None;
                            upcomingVehiclePos.LastFrame                 = vehiclePos.LastFrame;
                            upcomingVehiclePos.ToNode                    = realTimeDestinationNodes[i];
                            upcomingVehiclePos.FromSegment               = realTimePositions[i].m_segment;
                            upcomingVehiclePos.FromLaneIndex             = realTimePositions[i].m_lane;
                            upcomingVehiclePos.ToSegment                 = realTimePositions[i + 1].m_segment;
                            upcomingVehiclePos.ToLaneIndex               = realTimePositions[i + 1].m_lane;
                            upcomingVehiclePos.ReduceSpeedByValueToYield = UnityEngine.Random.Range(16f, 28f);
                            upcomingVehiclePos.OnEmergency               = (vehicleData.m_flags & Vehicle.Flags.Emergency2) != 0;
                            upcomingVehiclePos.VehicleType               = (ExtVehicleType)vehicleType;
#if DEBUGV
                            logBuffer.Add($"* vehicleId {vehicleId}. Adding future position: from {upcomingVehiclePos.FromSegment}  (lane {upcomingVehiclePos.FromLaneIndex}), going over {upcomingVehiclePos.ToNode}, to {upcomingVehiclePos.ToSegment} (lane {upcomingVehiclePos.ToLaneIndex})");
#endif

                            prioritySegment.AddVehicle(vehicleId, upcomingVehiclePos);
                        }
                    }
                    //}
                }
                else
                {
#if DEBUGV
                    logBuffer.Add($"* vehicleId {vehicleId}. Nothing has changed. from {vehiclePos.FromSegment} (lane {vehiclePos.FromLaneIndex}), going over {vehiclePos.ToNode}, to {vehiclePos.ToSegment} (lane {vehiclePos.ToLaneIndex})");
                    logme = false;
#endif
                }
            }
            else
            {
#if DEBUGV
                logBuffer.Add($"* vehicleId {vehicleId}. Insufficient path unit positions.");
#endif
                TrafficPriority.RemoveVehicleFromSegments(vehicleId);
            }

#if DEBUGV
            if (logme)
            {
                Log._Debug("vehicleId: " + vehicleId + " ============================================");
                foreach (String logBuf in logBuffer)
                {
                    Log._Debug(logBuf);
                }
                Log._Debug("vehicleId: " + vehicleId + " ============================================");
            }
#endif
        }
Пример #9
0
        public void CustomCalculateSegmentPosition(ushort vehicleId, ref Vehicle vehicleData, PathUnit.Position nextPosition,
                                                   PathUnit.Position position, uint laneID, byte offset, PathUnit.Position prevPos, uint prevLaneID,
                                                   byte prevOffset, int index, out Vector3 pos, out Vector3 dir, out float maxSpeed)
        {
            var netManager = Singleton <NetManager> .instance;

            //var vehicleManager = Singleton<VehicleManager>.instance;
            netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].CalculatePositionAndDirection(offset * 0.003921569f, out pos, out dir);
            bool isRecklessDriver = IsRecklessDriver(vehicleId, ref vehicleData);

            var lastFrameData       = vehicleData.GetLastFrameData();
            var lastFrameVehiclePos = lastFrameData.m_position;

            var  camPos = Camera.main.transform.position;
            bool simulatePrioritySigns = (lastFrameVehiclePos - camPos).sqrMagnitude < FarLod && !isRecklessDriver;

            if (Options.simAccuracy <= 0)
            {
                if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car)
                {
                    VehiclePosition vehiclePos = TrafficPriority.GetVehiclePosition(vehicleId);
                    if (vehiclePos.Valid && simulatePrioritySigns)                       // TODO check if this should be !vehiclePos.Valid
                    {
                        try {
                            CustomVehicleAI.HandleVehicle(vehicleId, ref Singleton <VehicleManager> .instance.m_vehicles.m_buffer[vehicleId], false, false);
                        } catch (Exception e) {
                            Log.Error("CarAI TmCalculateSegmentPosition Error: " + e.ToString());
                        }
                    }
                }
                else
                {
                    //Log._Debug($"TmCalculateSegmentPosition does not handle vehicles of type {vehicleData.Info.m_vehicleType}");
                }
            }

            // I think this is supposed to be the lane position?
            // [VN, 12/23/2015] It's the 3D car position on the Bezier curve of the lane.
            // This crazy 0.003921569f equals to 1f/255 and prevOffset is the byte value (0..255) of the car position.
            var vehiclePosOnBezier = netManager.m_lanes.m_buffer[(int)((UIntPtr)prevLaneID)].CalculatePosition(prevOffset * 0.003921569f);
            //ushort currentSegmentId = netManager.m_lanes.m_buffer[(int)((UIntPtr)prevLaneID)].m_segment;

            ushort destinationNodeId;
            ushort sourceNodeId;

            if (offset < position.m_offset)
            {
                destinationNodeId = netManager.m_segments.m_buffer[position.m_segment].m_startNode;
                sourceNodeId      = netManager.m_segments.m_buffer[position.m_segment].m_endNode;
            }
            else
            {
                destinationNodeId = netManager.m_segments.m_buffer[position.m_segment].m_endNode;
                sourceNodeId      = netManager.m_segments.m_buffer[position.m_segment].m_startNode;
            }
            var previousDestinationNode = prevOffset == 0 ? netManager.m_segments.m_buffer[prevPos.m_segment].m_startNode : netManager.m_segments.m_buffer[prevPos.m_segment].m_endNode;

            // this seems to be like the required braking force in order to stop the vehicle within its half length.
            var crazyValue = 0.5f * lastFrameData.m_velocity.sqrMagnitude / m_info.m_braking + m_info.m_generatedInfo.m_size.z * 0.5f;

            // Essentially, this is true if the car has enough time and space to brake (e.g. for a red traffic light)
            if (destinationNodeId == previousDestinationNode)
            {
                if (Vector3.Distance(lastFrameVehiclePos, vehiclePosOnBezier) >= crazyValue - 1f)
                {
                    var currentFrameIndex = Singleton <SimulationManager> .instance.m_currentFrameIndex;
                    var num5 = (uint)((previousDestinationNode << 8) / 32768);
                    var num6 = currentFrameIndex - num5 & 255u;

                    var  nodeFlags        = netManager.m_nodes.m_buffer[destinationNodeId].m_flags;
                    var  prevLaneFlags    = (NetLane.Flags)netManager.m_lanes.m_buffer[(int)((UIntPtr)prevLaneID)].m_flags;
                    var  hasTrafficLight  = (nodeFlags & NetNode.Flags.TrafficLights) != NetNode.Flags.None;
                    var  hasCrossing      = (nodeFlags & NetNode.Flags.LevelCrossing) != NetNode.Flags.None;
                    var  isJoinedJunction = (prevLaneFlags & NetLane.Flags.JoinedJunction) != NetLane.Flags.None;
                    bool checkSpace       = !Flags.getEnterWhenBlockedAllowed(prevPos.m_segment, netManager.m_segments.m_buffer[prevPos.m_segment].m_startNode == destinationNodeId) && !isRecklessDriver;
                    //TrafficLightSimulation nodeSim = TrafficLightSimulation.GetNodeSimulation(destinationNodeId);

                    /*if (timedNode != null && timedNode.vehiclesMayEnterBlockedJunctions) {
                     *      checkSpace = false;
                     * }*/

                    if (checkSpace)
                    {
                        // check if there is enough space
                        if ((nodeFlags & (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) == NetNode.Flags.Junction &&
                            netManager.m_nodes.m_buffer[destinationNodeId].CountSegments() != 2)
                        {
                            var len = vehicleData.CalculateTotalLength(vehicleId) + 2f;
                            if (!netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].CheckSpace(len))
                            {
                                var sufficientSpace = false;
                                if (nextPosition.m_segment != 0 &&
                                    netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_length < 30f)
                                {
                                    var flags3 = netManager.m_nodes.m_buffer[sourceNodeId].m_flags;
                                    if ((flags3 &
                                         (NetNode.Flags.Junction | NetNode.Flags.OneWayOut | NetNode.Flags.OneWayIn)) !=
                                        NetNode.Flags.Junction || netManager.m_nodes.m_buffer[sourceNodeId].CountSegments() == 2)
                                    {
                                        var laneId2 = PathManager.GetLaneID(nextPosition);
                                        if (laneId2 != 0u)
                                        {
                                            sufficientSpace = netManager.m_lanes.m_buffer[(int)((UIntPtr)laneId2)].CheckSpace(len);
                                        }
                                    }
                                }
                                if (!sufficientSpace)
                                {
                                    maxSpeed = 0f;
                                    return;
                                }
                            }
                        }
                    }

                    try {
                        VehiclePosition globalTargetPos = TrafficPriority.GetVehiclePosition(vehicleId);

                        if ((vehicleData.m_flags & Vehicle.Flags.Emergency2) == 0)
                        {
                            if (vehicleData.Info.m_vehicleType == VehicleInfo.VehicleType.Car)
                            {
                                if (hasTrafficLight && (!isJoinedJunction || hasCrossing))
                                {
                                    var destinationInfo = netManager.m_nodes.m_buffer[destinationNodeId].Info;

                                    if (globalTargetPos.CarState == VehicleJunctionTransitState.None)
                                    {
                                        globalTargetPos.CarState = VehicleJunctionTransitState.Enter;
                                    }

                                    RoadBaseAI.TrafficLightState vehicleLightState;
                                    RoadBaseAI.TrafficLightState pedestrianLightState;
                                    bool vehicles;
                                    bool pedestrians;
                                    CustomRoadAI.GetTrafficLightState(vehicleId, ref vehicleData, destinationNodeId, prevPos.m_segment, position.m_segment, ref netManager.m_segments.m_buffer[prevPos.m_segment], currentFrameIndex - num5, out vehicleLightState, out pedestrianLightState, out vehicles, out pedestrians);

                                    if (isRecklessDriver && (destinationInfo.GetConnectionClass().m_service & ItemClass.Service.PublicTransport) == ItemClass.Service.None)                                       // no reckless driving at railroad crossings
                                    {
                                        vehicleLightState = RoadBaseAI.TrafficLightState.Green;
                                    }

                                    if (!vehicles && num6 >= 196u)
                                    {
                                        vehicles = true;
                                        RoadBaseAI.SetTrafficLightState(destinationNodeId, ref netManager.m_segments.m_buffer[prevPos.m_segment], currentFrameIndex - num5, vehicleLightState, pedestrianLightState, vehicles, pedestrians);
                                    }

                                    var stopCar = false;
                                    switch (vehicleLightState)
                                    {
                                    case RoadBaseAI.TrafficLightState.RedToGreen:
                                        if (num6 < 60u)
                                        {
                                            stopCar = true;
                                        }
                                        else
                                        {
                                            globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                        }
                                        break;

                                    case RoadBaseAI.TrafficLightState.Red:
                                        stopCar = true;
                                        break;

                                    case RoadBaseAI.TrafficLightState.GreenToRed:
                                        if (num6 >= 30u)
                                        {
                                            stopCar = true;
                                        }
                                        else
                                        {
                                            globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                        }
                                        break;
                                    }

                                    if ((vehicleLightState == RoadBaseAI.TrafficLightState.Green || vehicleLightState == RoadBaseAI.TrafficLightState.RedToGreen) && !Flags.getEnterWhenBlockedAllowed(prevPos.m_segment, netManager.m_segments.m_buffer[prevPos.m_segment].m_startNode == destinationNodeId))
                                    {
                                        var hasIncomingCars = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, destinationNodeId);

                                        if (hasIncomingCars)
                                        {
                                            // green light but other cars are incoming and they have priority: stop
                                            stopCar = true;
                                        }
                                    }

                                    if (stopCar)
                                    {
                                        globalTargetPos.CarState = VehicleJunctionTransitState.Stop;
                                        maxSpeed = 0f;
                                        return;
                                    }
                                }
                                else if (simulatePrioritySigns)
                                {
#if DEBUG
                                    //bool debug = destinationNodeId == 10864;
                                    //bool debug = destinationNodeId == 13531;
                                    bool debug = false;
#endif
                                    //bool debug = false;
#if DEBUG
                                    if (debug)
                                    {
                                        Log._Debug($"Vehicle {vehicleId} is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {destinationNodeId} which is not a traffic light.");
                                    }
#endif

                                    var prioritySegment = TrafficPriority.GetPrioritySegment(destinationNodeId, prevPos.m_segment);
                                    if (prioritySegment != null)
                                    {
#if DEBUG
                                        if (debug)
                                        {
                                            Log._Debug($"Vehicle {vehicleId} is arriving @ seg. {prevPos.m_segment} ({position.m_segment}, {nextPosition.m_segment}), node {destinationNodeId} which is not a traffic light and is a priority segment.");
                                        }
#endif
                                        if (prioritySegment.HasVehicle(vehicleId))
                                        {
#if DEBUG
                                            if (debug)
                                            {
                                                Log._Debug($"Vehicle {vehicleId}: segment target position found");
                                            }
#endif
                                            if (globalTargetPos.Valid)
                                            {
#if DEBUG
                                                if (debug)
                                                {
                                                    Log._Debug($"Vehicle {vehicleId}: global target position found. carState = {globalTargetPos.CarState.ToString()}");
                                                }
#endif
                                                var currentFrameIndex2 = Singleton <SimulationManager> .instance.m_currentFrameIndex;
                                                var frame = currentFrameIndex2 >> 4;

                                                if (globalTargetPos.CarState == VehicleJunctionTransitState.None)
                                                {
                                                    globalTargetPos.CarState = VehicleJunctionTransitState.Enter;
                                                }

                                                if (globalTargetPos.CarState != VehicleJunctionTransitState.Leave)
                                                {
                                                    bool hasIncomingCars;
                                                    switch (prioritySegment.Type)
                                                    {
                                                    case SegmentEnd.PriorityType.Stop:
#if DEBUG
                                                        if (debug)
                                                        {
                                                            Log._Debug($"Vehicle {vehicleId}: STOP sign. waittime={globalTargetPos.WaitTime}, vel={lastFrameData.m_velocity.magnitude}");
                                                        }
#endif
                                                        if (globalTargetPos.WaitTime < MaxPriorityWaitTime)
                                                        {
                                                            globalTargetPos.CarState = VehicleJunctionTransitState.Stop;

                                                            if (lastFrameData.m_velocity.magnitude < 0.5f ||
                                                                globalTargetPos.Stopped)
                                                            {
                                                                globalTargetPos.Stopped = true;
                                                                globalTargetPos.WaitTime++;

                                                                float minStopWaitTime = Random.Range(0f, 3f);
                                                                if (globalTargetPos.WaitTime >= minStopWaitTime)
                                                                {
                                                                    hasIncomingCars = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, destinationNodeId);
#if DEBUG
                                                                    if (debug)
                                                                    {
                                                                        Log._Debug($"hasIncomingCars: {hasIncomingCars}");
                                                                    }
#endif

                                                                    if (hasIncomingCars)
                                                                    {
                                                                        maxSpeed = 0f;
                                                                        return;
                                                                    }
                                                                    globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                                                }
                                                                else
                                                                {
                                                                    maxSpeed = 0;
                                                                    return;
                                                                }
                                                            }
                                                            else
                                                            {
                                                                maxSpeed = 0f;
                                                                return;
                                                            }
                                                        }
                                                        else
                                                        {
                                                            globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                                        }
                                                        break;

                                                    case SegmentEnd.PriorityType.Yield:
#if DEBUG
                                                        if (debug)
                                                        {
                                                            Log._Debug($"Vehicle {vehicleId}: YIELD sign. waittime={globalTargetPos.WaitTime}");
                                                        }
#endif
                                                        if (globalTargetPos.WaitTime < MaxPriorityWaitTime)
                                                        {
                                                            globalTargetPos.WaitTime++;
                                                            globalTargetPos.CarState = VehicleJunctionTransitState.Stop;
                                                            hasIncomingCars          = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, destinationNodeId);
#if DEBUG
                                                            if (debug)
                                                            {
                                                                Log._Debug($"hasIncomingCars: {hasIncomingCars}");
                                                            }
#endif
                                                            if (hasIncomingCars)
                                                            {
                                                                if (lastFrameData.m_velocity.magnitude > 0)
                                                                {
                                                                    maxSpeed = Math.Max(0f, lastFrameData.m_velocity.magnitude - globalTargetPos.ReduceSpeedByValueToYield);
                                                                }
                                                                else
                                                                {
                                                                    maxSpeed = 0;
                                                                }
#if DEBUG
                                                                /*if (TrafficPriority.Vehicles[vehicleId].ToNode == 8621)
                                                                 *      Log.Message($"Vehicle {vehicleId} is yielding at node {destinationNodeId}. Speed: {maxSpeed}, Waiting time: {TrafficPriority.Vehicles[vehicleId].WaitTime}");*/
#endif
                                                                return;
                                                            }
                                                            else
                                                            {
#if DEBUG
                                                                /*if (TrafficPriority.Vehicles[vehicleId].ToNode == 8621)
                                                                 *      Log.Message($"Vehicle {vehicleId} is NOT yielding at node {destinationNodeId}.");*/
#endif
                                                                if (lastFrameData.m_velocity.magnitude > 0)
                                                                {
                                                                    maxSpeed = Math.Max(1f, lastFrameData.m_velocity.magnitude - globalTargetPos.ReduceSpeedByValueToYield * 0.5f);
                                                                }
                                                            }
                                                            globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                                        }
                                                        else
                                                        {
                                                            globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                                        }
                                                        break;

                                                    case SegmentEnd.PriorityType.Main:
#if DEBUG
                                                        if (debug)
                                                        {
                                                            Log._Debug($"Vehicle {vehicleId}: MAIN sign. waittime={globalTargetPos.WaitTime}");
                                                        }
#endif
                                                        if (globalTargetPos.WaitTime < MaxPriorityWaitTime)
                                                        {
                                                            globalTargetPos.WaitTime++;
                                                            globalTargetPos.CarState = VehicleJunctionTransitState.Stop;
                                                            maxSpeed = 0f;

                                                            hasIncomingCars = TrafficPriority.HasIncomingVehiclesWithHigherPriority(vehicleId, destinationNodeId);
#if DEBUG
                                                            if (debug)
                                                            {
                                                                Log._Debug($"hasIncomingCars: {hasIncomingCars}");
                                                            }
#endif

                                                            if (hasIncomingCars)
                                                            {
                                                                globalTargetPos.Stopped = true;
                                                                return;
                                                            }
                                                            globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                                            globalTargetPos.Stopped  = false;
                                                        }

                                                        var info3 = netManager.m_segments.m_buffer[position.m_segment].Info;
                                                        if (info3.m_lanes != null && info3.m_lanes.Length > position.m_lane)
                                                        {
                                                            //maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, info3.m_lanes[position.m_lane].m_speedLimit, netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_curve) * 0.8f;
                                                            maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, SpeedLimitManager.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info3.m_lanes[position.m_lane]), netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_curve);
                                                        }
                                                        else
                                                        {
                                                            maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f);
                                                        }
                                                        return;
                                                    }
                                                }
                                                else
                                                {
                                                    globalTargetPos.CarState = VehicleJunctionTransitState.Leave;
                                                }
                                            }
                                            else
                                            {
#if DEBUG
                                                if (debug)
                                                {
                                                    Log._Debug($"globalTargetPos is null! {vehicleId} @ seg. {prevPos.m_segment} @ node {destinationNodeId}");
                                                }
#endif
                                            }
                                        }
                                        else
                                        {
#if DEBUG
                                            if (debug)
                                            {
                                                Log._Debug($"targetPos is null! {vehicleId} @ seg. {prevPos.m_segment} @ node {destinationNodeId}");
                                            }
#endif
                                        }
                                    }
                                }
                            }
                        }
                    } catch (Exception e) {
                        Log.Error($"Error occured in TmCalculateSegmentPosition: {e.ToString()}");
                    }
                }
            }

            var info2 = netManager.m_segments.m_buffer[position.m_segment].Info;
            if (info2.m_lanes != null && info2.m_lanes.Length > position.m_lane)
            {
                var laneSpeedLimit = SpeedLimitManager.GetLockFreeGameSpeedLimit(position.m_segment, position.m_lane, laneID, info2.m_lanes[position.m_lane]);                 // info2.m_lanes[position.m_lane].m_speedLimit;

#if DEBUG
                /*if (position.m_segment == 275) {
                 *      Log._Debug($"Applying lane speed limit of {laneSpeedLimit} to lane {laneID} @ seg. {position.m_segment}");
                 * }*/
#endif

                /*if (TrafficRoadRestrictions.IsSegment(position.m_segment)) {
                 *      var restrictionSegment = TrafficRoadRestrictions.GetSegment(position.m_segment);
                 *
                 *      if (restrictionSegment.SpeedLimits[position.m_lane] > 0.1f) {
                 *              laneSpeedLimit = restrictionSegment.SpeedLimits[position.m_lane];
                 *      }
                 * }*/

                maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, laneSpeedLimit, netManager.m_lanes.m_buffer[(int)((UIntPtr)laneID)].m_curve);
            }
            else
            {
                maxSpeed = CalculateTargetSpeed(vehicleId, ref vehicleData, 1f, 0f);
            }

            maxSpeed = CalcMaxSpeed(vehicleId, ref vehicleData, position, pos, maxSpeed, isRecklessDriver);
        }
        public void ShowGUI(bool viewOnly)
        {
            try {
                if (viewOnly && !Options.prioritySignsOverlay)
                {
                    return;
                }

                bool clicked = !viewOnly?MainTool.CheckClicked() : false;

                var hoveredSegment = false;
                //Log.Message("_guiPrioritySigns called. num of prio segments: " + TrafficPriority.PrioritySegments.Count);

                HashSet <ushort> nodeIdsWithSigns = new HashSet <ushort>();
                for (ushort segmentId = 0; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId)
                {
                    var trafficSegment = TrafficPriority.TrafficSegments[segmentId];
                    if (trafficSegment == null)
                    {
                        continue;
                    }
                    SegmentGeometry geometry = SegmentGeometry.Get(segmentId);

                    List <SegmentEnd> prioritySegments = new List <SegmentEnd>();
                    if (TrafficLightSimulation.GetNodeSimulation(trafficSegment.Node1) == null)
                    {
                        SegmentEnd tmpSeg1   = TrafficPriority.GetPrioritySegment(trafficSegment.Node1, segmentId);
                        bool       startNode = geometry.StartNodeId() == trafficSegment.Node1;
                        if (tmpSeg1 != null && !geometry.IsOutgoingOneWay(startNode))
                        {
                            prioritySegments.Add(tmpSeg1);
                            nodeIdsWithSigns.Add(trafficSegment.Node1);
                        }
                    }
                    if (TrafficLightSimulation.GetNodeSimulation(trafficSegment.Node2) == null)
                    {
                        SegmentEnd tmpSeg2   = TrafficPriority.GetPrioritySegment(trafficSegment.Node2, segmentId);
                        bool       startNode = geometry.StartNodeId() == trafficSegment.Node2;
                        if (tmpSeg2 != null && !geometry.IsOutgoingOneWay(startNode))
                        {
                            prioritySegments.Add(tmpSeg2);
                            nodeIdsWithSigns.Add(trafficSegment.Node2);
                        }
                    }

                    //Log.Message("init ok");

                    foreach (var prioritySegment in prioritySegments)
                    {
                        var nodeId = prioritySegment.NodeId;
                        //Log.Message("_guiPrioritySigns: nodeId=" + nodeId);

                        var nodePositionVector3 = Singleton <NetManager> .instance.m_nodes.m_buffer[nodeId].m_position;
                        var camPos = Singleton <SimulationManager> .instance.m_simulationView.m_position;
                        var diff   = nodePositionVector3 - camPos;
                        if (diff.magnitude > TrafficManagerTool.PriorityCloseLod)
                        {
                            continue;                             // do not draw if too distant
                        }
                        if (Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startNode == (ushort)nodeId)
                        {
                            nodePositionVector3.x += Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startDirection.x * 10f;
                            nodePositionVector3.y += Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startDirection.y * 10f;
                            nodePositionVector3.z += Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_startDirection.z * 10f;
                        }
                        else
                        {
                            nodePositionVector3.x += Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endDirection.x * 10f;
                            nodePositionVector3.y += Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endDirection.y * 10f;
                            nodePositionVector3.z += Singleton <NetManager> .instance.m_segments.m_buffer[segmentId].m_endDirection.z * 10f;
                        }

                        var nodeScreenPosition = Camera.main.WorldToScreenPoint(nodePositionVector3);
                        nodeScreenPosition.y = Screen.height - nodeScreenPosition.y;
                        if (nodeScreenPosition.z < 0)
                        {
                            continue;
                        }
                        var zoom            = 1.0f / diff.magnitude * 100f * MainTool.GetBaseZoom();
                        var size            = 110f * zoom;
                        var guiColor        = GUI.color;
                        var nodeBoundingBox = new Rect(nodeScreenPosition.x - size / 2, nodeScreenPosition.y - size / 2, size, size);
                        hoveredSegment = !viewOnly && TrafficManagerTool.IsMouseOver(nodeBoundingBox);

                        if (hoveredSegment)
                        {
                            // mouse hovering over sign
                            guiColor.a = 0.8f;
                        }
                        else
                        {
                            guiColor.a = 0.5f;
                            size       = 90f * zoom;
                        }
                        var nodeDrawingBox = new Rect(nodeScreenPosition.x - size / 2, nodeScreenPosition.y - size / 2, size, size);

                        GUI.color = guiColor;

                        bool setUndefinedSignsToMainRoad = false;
                        switch (prioritySegment.Type)
                        {
                        case SegmentEnd.PriorityType.Main:
                            GUI.DrawTexture(nodeDrawingBox, TrafficLightToolTextureResources.SignPriorityTexture2D);
                            if (clicked && hoveredSegment)
                            {
                                //Log._Debug("Click on node " + nodeId + ", segment " + segmentId + " to change prio type (1)");
                                //Log.Message("PrioritySegment.Type = Yield");
                                prioritySegment.Type        = SegmentEnd.PriorityType.Yield;
                                setUndefinedSignsToMainRoad = true;
                                clicked = false;
                            }
                            break;

                        case SegmentEnd.PriorityType.Yield:
                            GUI.DrawTexture(nodeDrawingBox, TrafficLightToolTextureResources.SignYieldTexture2D);
                            if (clicked && hoveredSegment)
                            {
                                //Log._Debug("Click on node " + nodeId + ", segment " + segmentId + " to change prio type (2)");
                                prioritySegment.Type        = SegmentEnd.PriorityType.Stop;
                                setUndefinedSignsToMainRoad = true;
                                clicked = false;
                            }

                            break;

                        case SegmentEnd.PriorityType.Stop:
                            GUI.DrawTexture(nodeDrawingBox, TrafficLightToolTextureResources.SignStopTexture2D);
                            if (clicked && hoveredSegment)
                            {
                                //Log._Debug("Click on node " + nodeId + ", segment " + segmentId + " to change prio type (3)");
                                prioritySegment.Type = SegmentEnd.PriorityType.Main;
                                clicked = false;
                            }
                            break;

                        case SegmentEnd.PriorityType.None:
                            if (viewOnly)
                            {
                                break;
                            }
                            GUI.DrawTexture(nodeDrawingBox, TrafficLightToolTextureResources.SignNoneTexture2D);

                            if (clicked && hoveredSegment)
                            {
                                //Log._Debug("Click on node " + nodeId + ", segment " + segmentId + " to change prio type (4)");
                                //Log.Message("PrioritySegment.Type = None");
                                prioritySegment.Type = GetNumberOfMainRoads(nodeId, ref Singleton <NetManager> .instance.m_nodes.m_buffer[nodeId]) >= 2
                                                                                ? SegmentEnd.PriorityType.Yield
                                                                                : SegmentEnd.PriorityType.Main;
                                if (prioritySegment.Type == SegmentEnd.PriorityType.Yield)
                                {
                                    setUndefinedSignsToMainRoad = true;
                                }
                                clicked = false;
                            }
                            break;
                        }

                        if (setUndefinedSignsToMainRoad)
                        {
                            foreach (var otherPrioritySegment in TrafficPriority.GetPrioritySegments(nodeId))
                            {
                                if (otherPrioritySegment.SegmentId == prioritySegment.SegmentId)
                                {
                                    continue;
                                }
                                if (otherPrioritySegment.Type == SegmentEnd.PriorityType.None)
                                {
                                    otherPrioritySegment.Type = SegmentEnd.PriorityType.Main;
                                }
                            }
                        }
                    }
                }

                if (viewOnly)
                {
                    return;
                }

                ushort hoveredExistingNodeId = 0;
                foreach (ushort nodeId in nodeIdsWithSigns)
                {
                    var nodePositionVector3 = Singleton <NetManager> .instance.m_nodes.m_buffer[nodeId].m_position;
                    var camPos = Singleton <SimulationManager> .instance.m_simulationView.m_position;
                    var diff   = nodePositionVector3 - camPos;
                    if (diff.magnitude > TrafficManagerTool.PriorityCloseLod)
                    {
                        continue;
                    }

                    // draw deletion button
                    var nodeScreenPosition = Camera.main.WorldToScreenPoint(nodePositionVector3);
                    nodeScreenPosition.y = Screen.height - nodeScreenPosition.y;
                    if (nodeScreenPosition.z < 0)
                    {
                        continue;
                    }
                    var zoom            = 1.0f / diff.magnitude * 100f;
                    var size            = 100f * zoom;
                    var nodeBoundingBox = new Rect(nodeScreenPosition.x - size / 2, nodeScreenPosition.y - size / 2, size, size);

                    var guiColor          = GUI.color;
                    var nodeCenterHovered = TrafficManagerTool.IsMouseOver(nodeBoundingBox);
                    if (nodeCenterHovered)
                    {
                        hoveredExistingNodeId = nodeId;
                        guiColor.a            = 0.8f;
                    }
                    else
                    {
                        guiColor.a = 0.5f;
                    }
                    GUI.color = guiColor;

                    GUI.DrawTexture(nodeBoundingBox, TrafficLightToolTextureResources.SignRemoveTexture2D);
                }

                // add a new or delete a priority segment node
                if (HoveredNodeId != 0 || hoveredExistingNodeId != 0)
                {
                    bool delete = false;
                    if (hoveredExistingNodeId > 0)
                    {
                        delete = true;
                    }

                    // determine if we may add new priority signs to this node
                    bool ok = false;
                    TrafficLightSimulation nodeSim = TrafficLightSimulation.GetNodeSimulation(HoveredNodeId);
                    if ((Singleton <NetManager> .instance.m_nodes.m_buffer[HoveredNodeId].m_flags & NetNode.Flags.TrafficLights) == NetNode.Flags.None)
                    {
                        // no traffic light set
                        ok = true;
                    }
                    else if (nodeSim == null || !nodeSim.IsTimedLight())
                    {
                        ok = true;
                    }

                    if (!Flags.mayHaveTrafficLight(HoveredNodeId))
                    {
                        ok = false;
                    }

                    if (clicked)
                    {
                        //Log._Debug("_guiPrioritySigns: hovered+clicked @ nodeId=" + HoveredNodeId + "/" + hoveredExistingNodeId);

                        if (delete)
                        {
                            TrafficPriority.RemovePrioritySegments(hoveredExistingNodeId);
                        }
                        else if (ok)
                        {
                            if (!TrafficPriority.IsPriorityNode(HoveredNodeId))
                            {
                                //Log._Debug("_guiPrioritySigns: adding prio segments @ nodeId=" + HoveredNodeId);
                                TrafficLightSimulation.RemoveNodeFromSimulation(HoveredNodeId, false, true);
                                Flags.setNodeTrafficLight(HoveredNodeId, false);                                 // TODO refactor!
                                TrafficPriority.AddPriorityNode(HoveredNodeId);
                            }
                        }
                        else if (nodeSim != null && nodeSim.IsTimedLight())
                        {
                            MainTool.ShowTooltip(Translation.GetString("NODE_IS_TIMED_LIGHT"), Singleton <NetManager> .instance.m_nodes.m_buffer[HoveredNodeId].m_position);
                        }
                    }
                }
            } catch (Exception e) {
                Log.Error(e.ToString());
            }
        }
        /// <summary>
        /// Calculates the current metrics for flowing and waiting vehicles
        /// </summary>
        /// <param name="wait"></param>
        /// <param name="flow"></param>
        /// <returns>true if the values could be calculated, false otherwise</returns>
        public bool calcWaitFlow(out float wait, out float flow)
        {
#if DEBUG
            bool debug = timedNode.NodeId == 17857;
#else
            bool debug = false;
#endif

            uint numFlows    = 0;
            uint numWaits    = 0;
            uint curMeanFlow = 0;
            uint curMeanWait = 0;

            // we are the master node. calculate traffic data
            foreach (ushort timedNodeId in groupNodeIds)
            {
                TrafficLightSimulation sim = TrafficLightSimulation.GetNodeSimulation(timedNodeId);
                if (sim == null || !sim.IsTimedLight())
                {
                    continue;
                }
                TimedTrafficLights slaveTimedNode = sim.TimedLight;
                if (slaveTimedNode.NumSteps() <= timedNode.CurrentStep)
                {
                    for (int i = 0; i < slaveTimedNode.NumSteps(); ++i)
                    {
                        slaveTimedNode.GetStep(i).invalid = true;
                    }
                    continue;
                }
                TimedTrafficLightsStep slaveStep = slaveTimedNode.Steps[timedNode.CurrentStep];

                //List<int> segmentIdsToDelete = new List<int>();

                // minimum time reached. check traffic!
                foreach (KeyValuePair <ushort, CustomSegmentLights> e in slaveStep.segmentLights)
                {
                    var fromSegmentId = e.Key;
                    var segLights     = e.Value;

                    // one of the traffic lights at this segment is green: count minimum traffic flowing through
                    SegmentEnd fromSeg = TrafficPriority.GetPrioritySegment(timedNodeId, fromSegmentId);
                    if (fromSeg == null)
                    {
                        //Log.Warning("stepDone(): prioSeg is null");
                        //segmentIdsToDelete.Add(fromSegmentId);
                        continue;                         // skip invalid segment
                    }

                    bool           startPhase        = getCurrentFrame() <= startFrame + minTime + 2;    // during start phase all vehicles on "green" segments are counted as flowing
                    ExtVehicleType validVehicleTypes = VehicleRestrictionsManager.GetAllowedVehicleTypes(fromSegmentId, timedNode.NodeId);

                    foreach (ExtVehicleType vehicleType in segLights.VehicleTypes)
                    {
                        if (vehicleType != ExtVehicleType.None && (validVehicleTypes & vehicleType) == ExtVehicleType.None)
                        {
                            continue;
                        }
                        CustomSegmentLight segLight = segLights.GetCustomLight(vehicleType);
                        if (segLight == null)
                        {
                            Log.Warning($"Timed traffic light step: Failed to get custom light for vehicleType {vehicleType} @ seg. {fromSegmentId}, node {timedNode.NodeId}!");
                            continue;
                        }

                        Dictionary <ushort, uint>[] carsToSegmentMetrics = new Dictionary <ushort, uint> [startPhase ? 1: 2];
                        try {
                            carsToSegmentMetrics[0] = fromSeg.GetVehicleMetricGoingToSegment(null, vehicleType, segLights.SeparateVehicleTypes, debug);
                        } catch (Exception ex) {
                            Log.Warning("calcWaitFlow: " + ex.ToString());
                        }
                        if (!startPhase)
                        {
                            try {
                                carsToSegmentMetrics[1] = fromSeg.GetVehicleMetricGoingToSegment(0.1f, vehicleType, segLights.SeparateVehicleTypes, debug);
                            } catch (Exception ex) {
                                Log.Warning("calcWaitFlow: " + ex.ToString());
                            }
                        }

                        if (carsToSegmentMetrics[0] == null)
                        {
                            continue;
                        }

                        // build directions from toSegment to fromSegment
                        Dictionary <ushort, Direction> directions = new Dictionary <ushort, Direction>();
                        foreach (KeyValuePair <ushort, uint> f in carsToSegmentMetrics[0])
                        {
                            var             toSegmentId = f.Key;
                            SegmentGeometry geometry    = CustomRoadAI.GetSegmentGeometry(fromSegmentId);
                            Direction       dir         = geometry.GetDirection(toSegmentId, timedNodeId);
                            directions[toSegmentId] = dir;
                        }

                        // calculate waiting/flowing traffic
                        for (int i = 0; i < carsToSegmentMetrics.Length; ++i)
                        {
                            if (carsToSegmentMetrics[i] == null)
                            {
                                continue;
                            }

                            foreach (KeyValuePair <ushort, uint> f in carsToSegmentMetrics[i])
                            {
                                ushort toSegmentId        = f.Key;
                                uint   totalNormCarLength = f.Value;

                                bool addToFlow = false;
                                switch (directions[toSegmentId])
                                {
                                case Direction.Left:
                                    if (segLight.isLeftGreen())
                                    {
                                        addToFlow = true;
                                    }
                                    break;

                                case Direction.Right:
                                    if (segLight.isRightGreen())
                                    {
                                        addToFlow = true;
                                    }
                                    break;

                                case Direction.Forward:
                                default:
                                    if (segLight.isForwardGreen())
                                    {
                                        addToFlow = true;
                                    }
                                    break;
                                }

                                if (addToFlow)
                                {
                                    if (i > 0 || startPhase)
                                    {
                                        ++numFlows;
                                        curMeanFlow += totalNormCarLength;
                                    }
                                }
                                else if (i == 0)
                                {
                                    ++numWaits;
                                    curMeanWait += totalNormCarLength;
                                }
                            }
                        }
                    }
                }

                // delete invalid segments from step

                /*foreach (int segmentId in segmentIdsToDelete) {
                 *      slaveStep.segmentLightStates.Remove(segmentId);
                 * }*/

                if (slaveStep.segmentLights.Count <= 0)
                {
                    invalid = true;
                    flow    = 0f;
                    wait    = 0f;
                    return(false);
                }
            }

            if (numFlows > 0)
            {
                curMeanFlow /= numFlows;
            }
            if (numWaits > 0)
            {
                curMeanWait /= numWaits;
            }

            float fCurMeanFlow = curMeanFlow;
            fCurMeanFlow /= 100f * waitFlowBalance;             // a value smaller than 1 rewards steady traffic currents

            wait = (float)curMeanWait / 100f;
            flow = fCurMeanFlow;
            return(true);
        }