// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; namespace osu.Game.Rulesets.Osu.Statistics { /// /// Displays the aim error statistic for a given play. /// public partial class AimError : SimpleStatisticItem { private readonly List hitPoints = new List(); /// /// Creates and computes an statistic. /// /// Sequence of s to calculate the aim error based on. /// The containing the radii of the circles, used to compute the variance of misses. public AimError(IEnumerable hitEvents, IBeatmap playableBeatmap) : base("Aim Error") { Value = calculateAimError(hitEvents, playableBeatmap); } private double? calculateAimError(IEnumerable hitEvents, IBeatmap playableBeatmap) { IEnumerable hitCircleEvents = hitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList(); double nonMissCount = hitCircleEvents.Count(e => e.Result.IsHit()); double missCount = hitCircleEvents.Count() - nonMissCount; if (nonMissCount == 0) return null; foreach (var e in hitCircleEvents) { if (e.Position == null) continue; hitPoints.Add((e.Position - ((OsuHitObject)e.HitObject).StackedEndPosition).Value); } double radius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(playableBeatmap.Difficulty.CircleSize, true); // We don't get data for miss locations, so we estimate the total variance using the Rayleigh distribution. // Deriving the Rayleigh distribution in this form results in a 2 in the denominator, // but it is removed to take the variance across both axes, instead of across just one. double variance = (missCount * Math.Pow(radius, 2) + hitPoints.Sum(point => point.LengthSquared)) / nonMissCount; return Math.Sqrt(variance) * 10; } protected override string DisplayValue(double? value) => value == null ? "(not available)" : value.Value.ToString(@"N2"); } }