示例#1
0
        public override IEnumerable <Issue> GetIssues(Beatmap beatmap)
        {
            HitObject nextObject;

            double deltaTime;

            List <ObservedDistance> observedDistances = new List <ObservedDistance>();
            ObservedDistance?       observedIssue     = null;

            double distanceExpected;
            double distance;

            double mleniencyPercent = 0.15;
            double leniencyAbsolute = 10;

            double snapLeniencyPercent = 0.1;

            double ratioLeniencyPercent  = 0.2;
            double ratioLeniencyAbsolute = 0.1;

            foreach (HitObject hitObject in beatmap.hitObjects)
            {
                nextObject = hitObject.Next();

                // Ignore spinners, since they have no clear start or end.
                if (hitObject is Spinner || nextObject is Spinner || nextObject == null)
                {
                    continue;
                }

                deltaTime = nextObject.GetPrevDeltaTime();

                // Ignore objects 2 beats or more apart (assuming 200 bpm), since they don't really hang together context-wise.
                if (deltaTime > 600)
                {
                    continue;
                }

                distance = nextObject.GetPrevDistance();

                // Ignore stacks and half-stacks, since these are relatively normal.
                if (distance < 8)
                {
                    continue;
                }

                double closeDistanceSum =
                    observedDistances.Sum(observedDistance =>
                                          observedDistance.hitObject.time > hitObject.time - 4000 ?
                                          observedDistance.distance / observedDistance.deltaTime : 0);
                int closeDistanceCount =
                    observedDistances.Count(observedDistance =>
                                            observedDistance.hitObject.time > hitObject.time - 4000);

                double avrRatio = closeDistanceCount > 0 ? closeDistanceSum / closeDistanceCount : -1;

                // Checks whether a similar snapping has already been observed and uses that as
                // reference for determining if the current is too different.
                int index =
                    observedDistances
                    .FindLastIndex(observedDistance =>
                                   deltaTime <= observedDistance.deltaTime * (1 + snapLeniencyPercent) &&
                                   deltaTime >= observedDistance.deltaTime * (1 - snapLeniencyPercent) &&
                                   observedDistance.hitObject.time > hitObject.time - 4000);

                if (index != -1)
                {
                    distanceExpected = observedDistances[index].distance;

                    if ((Math.Abs(distanceExpected - distance) - leniencyAbsolute) / distance > mleniencyPercent)
                    {
                        // Prevents issues from duplicating due to error being different compared to both before and after.
                        // (e.g. if 1 -> 2 is too large, and 2 -> 3 is only too small because of 1 -> 2 being an issue, we
                        // only mention 1 -> 2 rather than both, since they stem from the same issue)
                        double distanceExpectedAlternate = observedIssue?.distance ?? 0;

                        if (observedIssue != null &&
                            Math.Abs(distanceExpectedAlternate - distance) / distance <= mleniencyPercent)
                        {
                            observedDistances[index] = new ObservedDistance(deltaTime, distance, hitObject);
                            observedIssue            = null;
                        }
                        else
                        {
                            HitObject prevObject     = observedDistances[index].hitObject;
                            HitObject prevNextObject = prevObject.Next();

                            yield return(new Issue(GetTemplate("Distance"), beatmap,
                                                   Timestamp.Get(hitObject, nextObject),
                                                   (int)Math.Round(distance), (int)Math.Round(distanceExpected),
                                                   Timestamp.Get(prevObject, prevNextObject)));

                            observedIssue = new ObservedDistance(deltaTime, distance, hitObject);
                        }
                    }
                    else
                    {
                        observedDistances[index] = new ObservedDistance(deltaTime, distance, hitObject);
                        observedIssue            = null;
                    }
                }
                else
                {
                    if (avrRatio != -1 && (
                            distance / deltaTime - ratioLeniencyAbsolute > avrRatio * (1 + ratioLeniencyPercent) ||
                            distance / deltaTime + ratioLeniencyAbsolute < avrRatio * (1 - ratioLeniencyPercent)))
                    {
                        string ratio         = $"{distance / deltaTime:0.##}";
                        string ratioExpected = $"{avrRatio:0.##}";

                        yield return(new Issue(GetTemplate("Ratio"), beatmap,
                                               Timestamp.Get(hitObject, nextObject),
                                               ratio, ratioExpected));
                    }
                    else
                    {
                        observedDistances.Add(new ObservedDistance(deltaTime, distance, hitObject));
                        observedIssue = null;
                    }
                }
            }
        }