From 43e5bd731cbfa0fff4d2a0825e10ceaefb56d599 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 18 Jan 2022 21:57:12 +0800 Subject: [PATCH] Compare performance to a perfect play --- .../Statistics/PerformanceStatistic.cs | 97 +++++++++++++++++-- .../Statistics/PerformanceStatisticTooltip.cs | 38 ++++---- 2 files changed, 107 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index 4fd6964a68..493f6a2dc9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,19 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip + public class PerformanceStatistic : StatisticDisplay, IHasCustomTooltip { private readonly ScoreInfo score; @@ -24,6 +29,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics private RollingCounter counter; + [Resolved] + private ScorePerformanceCache performanceCache { get; set; } + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public PerformanceStatistic(ScoreInfo score) : base("PP") { @@ -31,18 +45,74 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics } [BackgroundDependencyLoader] - private void load(ScorePerformanceCache performanceCache) + private void load() { - performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token) - .ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely())), cancellationTokenSource.Token); + Task.WhenAll( + // actual performance + performanceCache.CalculatePerformanceAsync(score, cancellationTokenSource.Token), + // performance for a perfect play + getPerfectPerformance(score) + ).ContinueWith(attr => + { + PerformanceAttributes[] result = attr.GetResultSafely(); + setPerformanceValue(new PerformanceBreakdown { Performance = result[0], PerfectPerformance = result[1] }); + }); } - private void setPerformanceValue(PerformanceAttributes pp) + private async Task getPerfectPerformance(ScoreInfo originalScore) { - if (pp != null) + ScoreInfo perfectScore = await getPerfectScore(originalScore).ConfigureAwait(false); + return await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationTokenSource.Token).ConfigureAwait(false); + } + + private Task getPerfectScore(ScoreInfo originalScore) + { + return Task.Factory.StartNew(() => { - TooltipContent = pp; - performance.Value = (int)Math.Round(pp.Total, MidpointRounding.AwayFromZero); + var beatmap = beatmapManager.GetWorkingBeatmap(originalScore.BeatmapInfo).GetPlayableBeatmap(originalScore.Ruleset, originalScore.Mods); + ScoreInfo perfectPlay = originalScore.DeepClone(); + perfectPlay.Accuracy = 1; + perfectPlay.Passed = true; + + // create statistics assuming all hit objects have perfect hit result + var statistics = beatmap.HitObjects + .Select(ho => ho.CreateJudgement().MaxResult) + .GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count())) + .ToDictionary(pair => pair.hitResult, pair => pair.count); + perfectPlay.Statistics = statistics; + + // calculate max combo + var difficulty = difficultyCache.GetDifficultyAsync( + beatmap.BeatmapInfo, + originalScore.Ruleset, + originalScore.Mods, + cancellationTokenSource.Token + ).GetResultSafely(); + perfectPlay.MaxCombo = difficulty?.MaxCombo ?? originalScore.MaxCombo; + + // calculate total score + ScoreProcessor scoreProcessor = originalScore.Ruleset.CreateInstance().CreateScoreProcessor(); + perfectPlay.TotalScore = (long)scoreProcessor.GetImmediateScore(ScoringMode.Standardised, perfectPlay.MaxCombo, statistics); + + // compute rank achieved + // default to SS, then adjust the rank with mods + perfectPlay.Rank = ScoreRank.X; + + foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType()) + { + perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1); + } + + return perfectPlay; + }, cancellationTokenSource.Token); + } + + private void setPerformanceValue(PerformanceBreakdown breakdown) + { + if (breakdown != null) + { + TooltipContent = breakdown; + performance.Value = (int)Math.Round(breakdown.Performance.Total, MidpointRounding.AwayFromZero); } } @@ -64,8 +134,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.TopCentre }; - public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); + public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); - public PerformanceAttributes TooltipContent { get; private set; } + public PerformanceBreakdown TooltipContent { get; private set; } + + public class PerformanceBreakdown + { + public PerformanceAttributes Performance { get; set; } + + public PerformanceAttributes PerfectPerformance { get; set; } + } } } diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs index deef30124c..564c195a3d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -1,11 +1,10 @@ // 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.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -19,7 +18,9 @@ using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip + using static PerformanceStatistic; + + public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background; private Colour4 totalColour; @@ -59,27 +60,30 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics protected override void PopIn() { // Don't display the tooltip if "Total" is the only item - if (lastAttributes.GetAttributesForDisplay().Count() > 1) + if (currentPerformance.Performance.GetAttributesForDisplay().Count() > 1) this.FadeIn(200, Easing.OutQuint); } protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - private PerformanceAttributes lastAttributes; + private PerformanceBreakdown currentPerformance; - public void SetContent(PerformanceAttributes attributes) + public void SetContent(PerformanceBreakdown performance) { - if (attributes == lastAttributes) + if (performance == currentPerformance) return; - lastAttributes = attributes; + currentPerformance = performance; - UpdateDisplay(attributes); + UpdateDisplay(performance); } - private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, double attributeSum) + private Drawable createAttributeItem(PerformanceDisplayAttribute attribute, PerformanceDisplayAttribute perfectAttribute) { bool isTotal = attribute.PropertyName == nameof(PerformanceAttributes.Total); + float fraction = (float)(attribute.Value / perfectAttribute.Value); + if (float.IsNaN(fraction)) + fraction = 0; return new GridContainer { AutoSizeAxes = Axes.Both, @@ -113,7 +117,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Width = 130, Height = 5, BackgroundColour = Color4.White.Opacity(0.5f), - Length = (float)(attribute.Value / attributeSum), + Length = fraction, Margin = new MarginPadding { Left = 5, Right = 5 } }, new OsuSpriteText @@ -121,7 +125,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.SemiBold), - Text = ((int)Math.Round(attribute.Value, MidpointRounding.AwayFromZero)).ToString(CultureInfo.CurrentCulture), + Text = fraction.ToLocalisableString("0%"), Colour = isTotal ? totalColour : textColour } } @@ -129,19 +133,17 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics }; } - protected virtual void UpdateDisplay(PerformanceAttributes attributes) + protected virtual void UpdateDisplay(PerformanceBreakdown performance) { Content.Clear(); - var displayAttributes = attributes.GetAttributesForDisplay(); + var displayAttributes = performance.Performance.GetAttributesForDisplay(); - double attributeSum = displayAttributes - .Where(attr => attr.PropertyName != nameof(PerformanceAttributes.Total)) - .Sum(attr => attr.Value); + var perfectDisplayAttributes = performance.PerfectPerformance.GetAttributesForDisplay(); foreach (PerformanceDisplayAttribute attr in displayAttributes) { - Content.Add(createAttributeItem(attr, attributeSum)); + Content.Add(createAttributeItem(attr, perfectDisplayAttributes.First(a => a.PropertyName == attr.PropertyName))); } }