internal static void StartEventCoroutine(CustomEventData customEventData, EventType eventType)
        {
            NoodleCoroutineEventData noodleData = (NoodleCoroutineEventData)NoodleEventDatas[customEventData];

            if (noodleData == null)
            {
                return;
            }

            float duration = noodleData.Duration;

            duration = 60f * duration / Instance.BeatmapObjectSpawnController.currentBpm; // Convert to real time;

            Functions easing = noodleData.Easing;

            foreach (NoodleCoroutineEventData.CoroutineInfo coroutineInfo in noodleData.CoroutineInfos)
            {
                Property        property  = coroutineInfo.Property;
                PointDefinition pointData = coroutineInfo.PointDefinition;

                if (property.Coroutine != null)
                {
                    Instance.StopCoroutine(property.Coroutine);
                }

                if (pointData == null)
                {
                    switch (eventType)
                    {
                    case EventType.AnimateTrack:
                        property.Value = null;
                        break;

                    case EventType.AssignPathAnimation:
                        ((PointDefinitionInterpolation)property.Value).Init(null);
                        break;
                    }
                }
                else
                {
                    switch (eventType)
                    {
                    case EventType.AnimateTrack:
                        property.Coroutine = Instance.StartCoroutine(AnimateTrack.AnimateTrackCoroutine(pointData, property, duration, customEventData.time, easing));
                        break;

                    case EventType.AssignPathAnimation:
                        ((PointDefinitionInterpolation)property.Value).Init(pointData);
                        property.Coroutine = Instance.StartCoroutine(AssignPathAnimation.AssignPathAnimationCoroutine(property, duration, customEventData.time, easing));
                        break;
                    }
                }
            }
        }
        private static NoodleCoroutineEventData ProcessCoroutineEvent(CustomEventData customEventData, IReadonlyBeatmapData beatmapData)
        {
            NoodleCoroutineEventData noodleEventData = new NoodleCoroutineEventData();

            string easingString = (string)Trees.at(customEventData.data, EASING);

            noodleEventData.Easing = Functions.easeLinear;
            if (easingString != null)
            {
                noodleEventData.Easing = (Functions)Enum.Parse(typeof(Functions), easingString);
            }

            noodleEventData.Duration = (float?)Trees.at(customEventData.data, DURATION) ?? 0f;

            Track track = GetTrackPreload(customEventData.data, beatmapData);

            if (track == null)
            {
                return(null);
            }

            EventType eventType;

            switch (customEventData.type)
            {
            case ANIMATETRACK:
                eventType = EventType.AnimateTrack;
                break;

            case ASSIGNPATHANIMATION:
                eventType = EventType.AssignPathAnimation;
                break;

            default:
                return(null);
            }

            List <string> excludedStrings = new List <string> {
                TRACK, DURATION, EASING
            };
            IDictionary <string, object>   eventData  = new Dictionary <string, object>(customEventData.data as IDictionary <string, object>); // Shallow copy
            IDictionary <string, Property> properties = null;

            switch (eventType)
            {
            case EventType.AnimateTrack:
                properties = track.Properties;
                break;

            case EventType.AssignPathAnimation:
                properties = track.PathProperties;
                break;
            }

            foreach (KeyValuePair <string, object> valuePair in eventData)
            {
                if (!excludedStrings.Any(n => n == valuePair.Key))
                {
                    if (!properties.TryGetValue(valuePair.Key, out Property property))
                    {
                        NoodleLogger.Log($"Could not find property {valuePair.Key}!", IPA.Logging.Logger.Level.Error);
                        continue;
                    }

                    Dictionary <string, PointDefinition> pointDefinitions = Trees.at(((CustomBeatmapData)beatmapData).customData, "pointDefinitions");
                    TryGetPointData(customEventData.data, valuePair.Key, out PointDefinition pointData, pointDefinitions);

                    NoodleCoroutineEventData.CoroutineInfo coroutineInfo = new NoodleCoroutineEventData.CoroutineInfo()
                    {
                        PointDefinition = pointData,
                        Property        = property,
                    };

                    noodleEventData.CoroutineInfos.Add(coroutineInfo);
                }
            }

            return(noodleEventData);
        }