diff --git a/osu.Game.Rulesets.Osu/Skinning/GameplayAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Skinning/GameplayAccuracyHeatmap.cs new file mode 100644 index 000000000000..485cc243ca5d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/GameplayAccuracyHeatmap.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Scoring; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Screens.Play; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public partial class GameplayAccuracyHeatmap : Container, ISerialisableDrawable + { + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; + + [Resolved(canBeNull: true)] + private GameplayClockContainer? gameplayClockContainer { get; set; } + + public bool UsesFixedAnchor { get; set; } + + private float radius; + private AccuracyHeatmap heatmap = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + AutoSizeAxes = Axes.Both; + + radius = OsuHitObject.OBJECT_RADIUS * LegacyRulesetExtensions.CalculateScaleFromCircleSize(beatmap.Value.Beatmap.Difficulty.CircleSize, true); + + initHeatmap(); + + if (gameplayClockContainer != null) + gameplayClockContainer.OnSeek += initHeatmap; + + scoreProcessor.NewJudgement += updateHeatmap; + } + + private void initHeatmap() + { + ScoreInfo scoreInfo = new ScoreInfo { BeatmapInfo = beatmap.Value.BeatmapInfo, HitEvents = (List)scoreProcessor.HitEvents }; + Child = new Container + { + Height = 200, + Width = 200, + Child = heatmap = new AccuracyHeatmap(scoreInfo, beatmap.Value.Beatmap, false) + { + RelativeSizeAxes = Axes.Both + } + }; + } + + private void updateHeatmap(JudgementResult j) + { + if (j is not OsuHitCircleJudgementResult circleJudgementResult || circleJudgementResult.CursorPositionAtHit == null) + return; + + heatmap.AddPoint(((OsuHitObject)j.HitObject).StackedEndPosition, ((OsuHitObject)j.HitObject).StackedEndPosition, circleJudgementResult.CursorPositionAtHit.Value, radius); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 41620bc3d879..c6f6bc242f3f 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -39,22 +39,25 @@ public partial class AccuracyHeatmap : CompositeDrawable private const float rotation = 45; private BufferedContainer bufferedGrid = null!; - private GridContainer pointGrid = null!; + private GridContainer? pointGrid; private readonly ScoreInfo score; private readonly IBeatmap playableBeatmap; private const float line_thickness = 2; + private readonly bool showsLabel; + /// /// The highest count of any point currently being displayed. /// protected float PeakValue { get; private set; } - public AccuracyHeatmap(ScoreInfo score, IBeatmap playableBeatmap) + public AccuracyHeatmap(ScoreInfo score, IBeatmap playableBeatmap, bool showsLabel = true) { this.score = score; this.playableBeatmap = playableBeatmap; + this.showsLabel = showsLabel; } [BackgroundDependencyLoader] @@ -123,6 +126,7 @@ private void load() new OsuSpriteText { Text = "Overshoot", + Alpha = showsLabel ? 1 : 0, Font = OsuFont.GetFont(size: 12), Anchor = Anchor.Centre, Origin = Anchor.BottomLeft, @@ -134,6 +138,7 @@ private void load() new OsuSpriteText { Text = "Undershoot", + Alpha = showsLabel ? 1 : 0, Font = OsuFont.GetFont(size: 12), Anchor = Anchor.Centre, Origin = Anchor.TopRight, @@ -227,9 +232,9 @@ private void load() } } - protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) { - if (pointGrid.Content.Count == 0) + if (pointGrid == null || pointGrid.Content.Count == 0) return; double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point.