// 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.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 AverageAimError : SimpleStatisticItem { private readonly List hitPoints = new List(); /// /// Creates and computes an statistic. /// /// Sequence of s to calculate the aim error based on. public AverageAimError(IEnumerable hitEvents) : base("Average Aim Error") { Value = calculateAverageAimError(hitEvents); } private double? calculateAverageAimError(IEnumerable hitEvents) { IEnumerable hitCircleEvents = hitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle) && e.Result.IsHit()).ToList(); double nonMissCount = hitCircleEvents.Count(e => e.Result.IsHit()); if (nonMissCount == 0) return null; foreach (var e in hitCircleEvents) { if (e.LastHitObject == null || e.Position == null) continue; addAngleAdjustedPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.Position.Value); } Vector2 averagePosition = new Vector2(hitPoints.Sum(x => x[0]), hitPoints.Sum(x => x[1])) / (float)nonMissCount; return averagePosition.Length; } private void addAngleAdjustedPoint(Vector2 start, Vector2 end, Vector2 hitPoint) { double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. double distanceFromCenter = (hitPoint - end).Length; Vector2 angleAdjustedPoint = new Vector2((float)(Math.Cos(finalAngle) * distanceFromCenter), (float)(Math.Sin(finalAngle) * distanceFromCenter)); hitPoints.Add(angleAdjustedPoint); } protected override string DisplayValue(double? value) => value == null ? "(not available)" : $"{Math.Abs(value.Value):N2} osu! pixels from center"; } }