diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs new file mode 100644 index 0000000000..273d8613c5 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdown.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Rulesets.Difficulty +{ + /// + /// Data for generating a performance breakdown by comparing performance to a perfect play. + /// + public class PerformanceBreakdown + { + /// + /// Actual gameplay performance. + /// + public PerformanceAttributes Performance { get; set; } + + /// + /// Performance of a perfect play for comparison. + /// + public PerformanceAttributes PerfectPerformance { get; set; } + } +} diff --git a/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs new file mode 100644 index 0000000000..fad7937a99 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Extensions; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Rulesets.Difficulty +{ + public class PerformanceBreakdownCalculator + { + private readonly BeatmapManager beatmapManager; + private readonly BeatmapDifficultyCache difficultyCache; + private readonly ScorePerformanceCache performanceCache; + + public PerformanceBreakdownCalculator(BeatmapManager beatmapManager, BeatmapDifficultyCache difficultyCache, ScorePerformanceCache performanceCache) + { + this.beatmapManager = beatmapManager; + this.difficultyCache = difficultyCache; + this.performanceCache = performanceCache; + } + + [ItemCanBeNull] + public async Task CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default) + { + PerformanceAttributes performance = await performanceCache.CalculatePerformanceAsync(score, cancellationToken).ConfigureAwait(false); + + ScoreInfo perfectScore = await getPerfectScore(score, cancellationToken).ConfigureAwait(false); + if (perfectScore == null) + return null; + + PerformanceAttributes perfectPerformance = await performanceCache.CalculatePerformanceAsync(perfectScore, cancellationToken).ConfigureAwait(false); + + return new PerformanceBreakdown { Performance = performance, PerfectPerformance = perfectPerformance }; + } + + [ItemCanBeNull] + private Task getPerfectScore(ScoreInfo score, CancellationToken cancellationToken = default) + { + return Task.Factory.StartNew(() => + { + IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(score.BeatmapInfo).GetPlayableBeatmap(score.Ruleset, score.Mods); + ScoreInfo perfectPlay = score.DeepClone(); + perfectPlay.Accuracy = 1; + perfectPlay.Passed = true; + + // calculate max combo + var difficulty = difficultyCache.GetDifficultyAsync( + beatmap.BeatmapInfo, + score.Ruleset, + score.Mods, + cancellationToken + ).GetResultSafely(); + + if (difficulty == null) + return null; + + perfectPlay.MaxCombo = difficulty.Value.MaxCombo; + + // 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 total score + ScoreProcessor scoreProcessor = score.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; + }, cancellationToken); + } + } +} diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs index f00eb9d71f..f934dd1836 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatistic.cs @@ -2,9 +2,7 @@ // 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; @@ -13,13 +11,11 @@ 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; @@ -47,64 +43,9 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load() { - 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 async Task getPerfectPerformance(ScoreInfo originalScore) - { - 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(() => - { - 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); + new PerformanceBreakdownCalculator(beatmapManager, difficultyCache, performanceCache) + .CalculateAsync(score, cancellationTokenSource.Token) + .ContinueWith(t => setPerformanceValue(t.GetResultSafely())); } private void setPerformanceValue(PerformanceBreakdown breakdown) @@ -137,18 +78,5 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public ITooltip GetCustomTooltip() => new PerformanceStatisticTooltip(); public PerformanceBreakdown TooltipContent { get; private set; } - - public class PerformanceBreakdown - { - /// - /// Actual gameplay performance. - /// - public PerformanceAttributes Performance { get; set; } - - /// - /// Performance of a perfect play for comparison. - /// - 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 528ccafd41..930bfba96a 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/PerformanceStatisticTooltip.cs @@ -19,8 +19,6 @@ using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Expanded.Statistics { - using static PerformanceStatistic; - public class PerformanceStatisticTooltip : VisibilityContainer, ITooltip { private readonly Box background;