mirror of
https://github.com/ppy/osu.git
synced 2025-02-05 20:42:55 +08:00
Merge pull request #30322 from smoogipoo/bat-max-performance
Implement "max pp" beatmap difficulty attribute text
This commit is contained in:
commit
5cc1cbe880
@ -159,6 +159,23 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddUntilStep("check star rating is 2", getText, () => Is.EqualTo("Star Rating: 2.00"));
|
AddUntilStep("check star rating is 2", getText, () => Is.EqualTo("Star Rating: 2.00"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMaxPp()
|
||||||
|
{
|
||||||
|
AddStep("set test ruleset", () => Ruleset.Value = new TestRuleset().RulesetInfo);
|
||||||
|
AddStep("set max pp attribute", () => text.Attribute.Value = BeatmapAttribute.MaxPP);
|
||||||
|
AddAssert("check max pp is 0", getText, () => Is.EqualTo("Max PP: 0"));
|
||||||
|
|
||||||
|
// Adding mod
|
||||||
|
TestMod mod = null!;
|
||||||
|
AddStep("add mod with pp 1", () => SelectedMods.Value = new[] { mod = new TestMod { Performance = { Value = 1 } } });
|
||||||
|
AddUntilStep("check max pp is 1", getText, () => Is.EqualTo("Max PP: 1"));
|
||||||
|
|
||||||
|
// Changing mod setting
|
||||||
|
AddStep("change mod pp to 2", () => mod.Performance.Value = 2);
|
||||||
|
AddUntilStep("check max pp is 2", getText, () => Is.EqualTo("Max PP: 2"));
|
||||||
|
}
|
||||||
|
|
||||||
private string getText() => text.ChildrenOfType<SpriteText>().Single().Text.ToString();
|
private string getText() => text.ChildrenOfType<SpriteText>().Single().Text.ToString();
|
||||||
|
|
||||||
private class TestRuleset : Ruleset
|
private class TestRuleset : Ruleset
|
||||||
|
@ -4,12 +4,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Lists;
|
using osu.Framework.Lists;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -18,7 +21,11 @@ using osu.Game.Database;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
@ -237,10 +244,37 @@ namespace osu.Game.Beatmaps
|
|||||||
var ruleset = rulesetInfo.CreateInstance();
|
var ruleset = rulesetInfo.CreateInstance();
|
||||||
Debug.Assert(ruleset != null);
|
Debug.Assert(ruleset != null);
|
||||||
|
|
||||||
var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo));
|
PlayableCachedWorkingBeatmap workingBeatmap = new PlayableCachedWorkingBeatmap(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo));
|
||||||
var attributes = calculator.Calculate(key.OrderedMods, cancellationToken);
|
IBeatmap playableBeatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, key.OrderedMods, cancellationToken);
|
||||||
|
|
||||||
return new StarDifficulty(attributes);
|
var difficulty = ruleset.CreateDifficultyCalculator(workingBeatmap).Calculate(key.OrderedMods, cancellationToken);
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var performanceCalculator = ruleset.CreatePerformanceCalculator();
|
||||||
|
if (performanceCalculator == null)
|
||||||
|
return new StarDifficulty(difficulty, new PerformanceAttributes());
|
||||||
|
|
||||||
|
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
|
scoreProcessor.Mods.Value = key.OrderedMods;
|
||||||
|
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
ScoreInfo perfectScore = new ScoreInfo(key.BeatmapInfo, ruleset.RulesetInfo)
|
||||||
|
{
|
||||||
|
Passed = true,
|
||||||
|
Accuracy = 1,
|
||||||
|
Mods = key.OrderedMods,
|
||||||
|
MaxCombo = scoreProcessor.MaximumCombo,
|
||||||
|
Combo = scoreProcessor.MaximumCombo,
|
||||||
|
TotalScore = scoreProcessor.MaximumTotalScore,
|
||||||
|
Statistics = scoreProcessor.MaximumStatistics,
|
||||||
|
MaximumStatistics = scoreProcessor.MaximumStatistics
|
||||||
|
};
|
||||||
|
|
||||||
|
var performance = performanceCalculator.Calculate(perfectScore, difficulty);
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
return new StarDifficulty(difficulty, performance);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@ -276,7 +310,6 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
public readonly BeatmapInfo BeatmapInfo;
|
public readonly BeatmapInfo BeatmapInfo;
|
||||||
public readonly RulesetInfo Ruleset;
|
public readonly RulesetInfo Ruleset;
|
||||||
|
|
||||||
public readonly Mod[] OrderedMods;
|
public readonly Mod[] OrderedMods;
|
||||||
|
|
||||||
public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable<Mod>? mods)
|
public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable<Mod>? mods)
|
||||||
@ -317,5 +350,42 @@ namespace osu.Game.Beatmaps
|
|||||||
CancellationToken = cancellationToken;
|
CancellationToken = cancellationToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A working beatmap that caches its playable representation.
|
||||||
|
/// This is intended as single-use for when it is guaranteed that the playable beatmap can be reused.
|
||||||
|
/// </summary>
|
||||||
|
private class PlayableCachedWorkingBeatmap : IWorkingBeatmap
|
||||||
|
{
|
||||||
|
private readonly IWorkingBeatmap working;
|
||||||
|
private IBeatmap? playable;
|
||||||
|
|
||||||
|
public PlayableCachedWorkingBeatmap(IWorkingBeatmap working)
|
||||||
|
{
|
||||||
|
this.working = working;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods)
|
||||||
|
=> playable ??= working.GetPlayableBeatmap(ruleset, mods);
|
||||||
|
|
||||||
|
public IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods, CancellationToken cancellationToken)
|
||||||
|
=> playable ??= working.GetPlayableBeatmap(ruleset, mods, cancellationToken);
|
||||||
|
|
||||||
|
IBeatmapInfo IWorkingBeatmap.BeatmapInfo => working.BeatmapInfo;
|
||||||
|
bool IWorkingBeatmap.BeatmapLoaded => working.BeatmapLoaded;
|
||||||
|
bool IWorkingBeatmap.TrackLoaded => working.TrackLoaded;
|
||||||
|
IBeatmap IWorkingBeatmap.Beatmap => working.Beatmap;
|
||||||
|
Texture IWorkingBeatmap.GetBackground() => working.GetBackground();
|
||||||
|
Texture IWorkingBeatmap.GetPanelBackground() => working.GetPanelBackground();
|
||||||
|
Waveform IWorkingBeatmap.Waveform => working.Waveform;
|
||||||
|
Storyboard IWorkingBeatmap.Storyboard => working.Storyboard;
|
||||||
|
ISkin IWorkingBeatmap.Skin => working.Skin;
|
||||||
|
Track IWorkingBeatmap.Track => working.Track;
|
||||||
|
Track IWorkingBeatmap.LoadTrack() => working.LoadTrack();
|
||||||
|
Stream IWorkingBeatmap.GetStream(string storagePath) => working.GetStream(storagePath);
|
||||||
|
void IWorkingBeatmap.BeginAsyncLoad() => working.BeginAsyncLoad();
|
||||||
|
void IWorkingBeatmap.CancelAsyncLoad() => working.CancelAsyncLoad();
|
||||||
|
void IWorkingBeatmap.PrepareTrackForPreview(bool looping, double offsetFromPreviewPoint) => working.PrepareTrackForPreview(looping, offsetFromPreviewPoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
|
||||||
@ -25,30 +22,34 @@ namespace osu.Game.Beatmaps
|
|||||||
/// The difficulty attributes computed for the given beatmap.
|
/// The difficulty attributes computed for the given beatmap.
|
||||||
/// Might not be available if the star difficulty is associated with a beatmap that's not locally available.
|
/// Might not be available if the star difficulty is associated with a beatmap that's not locally available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[CanBeNull]
|
public readonly DifficultyAttributes? DifficultyAttributes;
|
||||||
public readonly DifficultyAttributes Attributes;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="StarDifficulty"/> structure based on <see cref="DifficultyAttributes"/> computed
|
/// The performance attributes computed for a perfect score on the given beatmap.
|
||||||
/// by a <see cref="DifficultyCalculator"/>.
|
/// Might not be available if the star difficulty is associated with a beatmap that's not locally available.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StarDifficulty([NotNull] DifficultyAttributes attributes)
|
public readonly PerformanceAttributes? PerformanceAttributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="StarDifficulty"/> structure.
|
||||||
|
/// </summary>
|
||||||
|
public StarDifficulty(DifficultyAttributes difficulty, PerformanceAttributes performance)
|
||||||
{
|
{
|
||||||
Stars = double.IsFinite(attributes.StarRating) ? attributes.StarRating : 0;
|
Stars = double.IsFinite(difficulty.StarRating) ? difficulty.StarRating : 0;
|
||||||
MaxCombo = attributes.MaxCombo;
|
MaxCombo = difficulty.MaxCombo;
|
||||||
Attributes = attributes;
|
DifficultyAttributes = difficulty;
|
||||||
|
PerformanceAttributes = performance;
|
||||||
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
|
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="StarDifficulty"/> structure with a pre-populated star difficulty and max combo
|
/// Creates a <see cref="StarDifficulty"/> structure with a pre-populated star difficulty and max combo
|
||||||
/// in scenarios where computing <see cref="DifficultyAttributes"/> is not feasible (i.e. when working with online sources).
|
/// in scenarios where computing <see cref="Rulesets.Difficulty.DifficultyAttributes"/> is not feasible (i.e. when working with online sources).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public StarDifficulty(double starDifficulty, int maxCombo)
|
public StarDifficulty(double starDifficulty, int maxCombo)
|
||||||
{
|
{
|
||||||
Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0;
|
Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0;
|
||||||
MaxCombo = maxCombo;
|
MaxCombo = maxCombo;
|
||||||
Attributes = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DifficultyRating DifficultyRating => GetDifficultyRating(Stars);
|
public DifficultyRating DifficultyRating => GetDifficultyRating(Stars);
|
||||||
|
@ -12,23 +12,28 @@ namespace osu.Game.Localisation.SkinComponents
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Attribute"
|
/// "Attribute"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), "Attribute");
|
public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), @"Attribute");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The attribute to be displayed."
|
/// "The attribute to be displayed."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), "The attribute to be displayed.");
|
public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), @"The attribute to be displayed.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Template"
|
/// "Template"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Template => new TranslatableString(getKey(@"template"), "Template");
|
public static LocalisableString Template => new TranslatableString(getKey(@"template"), @"Template");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values)."
|
/// "Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values)."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString TemplateDescription => new TranslatableString(getKey(@"template_description"), @"Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values).");
|
public static LocalisableString TemplateDescription => new TranslatableString(getKey(@"template_description"), @"Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values).");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
/// <summary>
|
||||||
|
/// "Max PP"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString MaxPP => new TranslatableString(getKey(@"max_pp"), @"Max PP");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Difficulty
|
|
||||||
{
|
|
||||||
public class PerformanceBreakdownCalculator
|
|
||||||
{
|
|
||||||
private readonly IBeatmap playableBeatmap;
|
|
||||||
private readonly BeatmapDifficultyCache difficultyCache;
|
|
||||||
|
|
||||||
public PerformanceBreakdownCalculator(IBeatmap playableBeatmap, BeatmapDifficultyCache difficultyCache)
|
|
||||||
{
|
|
||||||
this.playableBeatmap = playableBeatmap;
|
|
||||||
this.difficultyCache = difficultyCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
[ItemCanBeNull]
|
|
||||||
public async Task<PerformanceBreakdown> CalculateAsync(ScoreInfo score, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
|
|
||||||
|
|
||||||
// Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value.
|
|
||||||
if (attributes?.Attributes == null || performanceCalculator == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
PerformanceAttributes[] performanceArray = await Task.WhenAll(
|
|
||||||
// compute actual performance
|
|
||||||
performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken),
|
|
||||||
// compute performance for perfect play
|
|
||||||
getPerfectPerformance(score, cancellationToken)
|
|
||||||
).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return new PerformanceBreakdown(performanceArray[0] ?? new PerformanceAttributes(), performanceArray[1] ?? new PerformanceAttributes());
|
|
||||||
}
|
|
||||||
|
|
||||||
[ItemCanBeNull]
|
|
||||||
private Task<PerformanceAttributes> getPerfectPerformance(ScoreInfo score, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return Task.Run(async () =>
|
|
||||||
{
|
|
||||||
Ruleset ruleset = score.Ruleset.CreateInstance();
|
|
||||||
ScoreInfo perfectPlay = score.DeepClone();
|
|
||||||
perfectPlay.Accuracy = 1;
|
|
||||||
perfectPlay.Passed = true;
|
|
||||||
|
|
||||||
// calculate max combo
|
|
||||||
// todo: Get max combo from difficulty calculator instead when diffcalc properly supports lazer-first scores
|
|
||||||
perfectPlay.MaxCombo = calculateMaxCombo(playableBeatmap);
|
|
||||||
|
|
||||||
// create statistics assuming all hit objects have perfect hit result
|
|
||||||
var statistics = playableBeatmap.HitObjects
|
|
||||||
.SelectMany(getPerfectHitResults)
|
|
||||||
.GroupBy(hr => hr, (hr, list) => (hitResult: hr, count: list.Count()))
|
|
||||||
.ToDictionary(pair => pair.hitResult, pair => pair.count);
|
|
||||||
perfectPlay.Statistics = statistics;
|
|
||||||
perfectPlay.MaximumStatistics = statistics;
|
|
||||||
|
|
||||||
// calculate total score
|
|
||||||
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
|
|
||||||
scoreProcessor.Mods.Value = perfectPlay.Mods;
|
|
||||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
|
||||||
perfectPlay.TotalScore = scoreProcessor.MaximumTotalScore;
|
|
||||||
|
|
||||||
// compute rank achieved
|
|
||||||
// default to SS, then adjust the rank with mods
|
|
||||||
perfectPlay.Rank = ScoreRank.X;
|
|
||||||
|
|
||||||
foreach (IApplicableToScoreProcessor mod in perfectPlay.Mods.OfType<IApplicableToScoreProcessor>())
|
|
||||||
{
|
|
||||||
perfectPlay.Rank = mod.AdjustRank(perfectPlay.Rank, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate performance for this perfect score
|
|
||||||
var difficulty = await difficultyCache.GetDifficultyAsync(
|
|
||||||
playableBeatmap.BeatmapInfo,
|
|
||||||
score.Ruleset,
|
|
||||||
score.Mods,
|
|
||||||
cancellationToken
|
|
||||||
).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var performanceCalculator = ruleset.CreatePerformanceCalculator();
|
|
||||||
|
|
||||||
if (performanceCalculator == null || difficulty == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return await performanceCalculator.CalculateAsync(perfectPlay, difficulty.Value.Attributes.AsNonNull(), cancellationToken).ConfigureAwait(false);
|
|
||||||
}, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calculateMaxCombo(IBeatmap beatmap)
|
|
||||||
{
|
|
||||||
return beatmap.HitObjects.SelectMany(getPerfectHitResults).Count(r => r.AffectsCombo());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<HitResult> getPerfectHitResults(HitObject hitObject)
|
|
||||||
{
|
|
||||||
foreach (HitObject nested in hitObject.NestedHitObjects)
|
|
||||||
yield return nested.Judgement.MaxResult;
|
|
||||||
|
|
||||||
yield return hitObject.Judgement.MaxResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -119,6 +119,11 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long MaximumTotalScore { get; private set; }
|
public long MaximumTotalScore { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum achievable combo.
|
||||||
|
/// </summary>
|
||||||
|
public int MaximumCombo { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum sum of accuracy-affecting judgements at the current point in time.
|
/// The maximum sum of accuracy-affecting judgements at the current point in time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -423,6 +428,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
MaximumResultCounts.AddRange(ScoreResultCounts);
|
MaximumResultCounts.AddRange(ScoreResultCounts);
|
||||||
|
|
||||||
MaximumTotalScore = TotalScore.Value;
|
MaximumTotalScore = TotalScore.Value;
|
||||||
|
MaximumCombo = HighestCombo.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScoreResultCounts.Clear();
|
ScoreResultCounts.Clear();
|
||||||
|
@ -53,10 +53,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
|||||||
var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
|
var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
|
||||||
|
|
||||||
// Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value.
|
// Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value.
|
||||||
if (attributes?.Attributes == null || performanceCalculator == null)
|
if (attributes?.DifficultyAttributes == null || performanceCalculator == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var result = await performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken ?? default).ConfigureAwait(false);
|
var result = await performanceCalculator.CalculateAsync(score, attributes.Value.DifficultyAttributes, cancellationToken ?? default).ConfigureAwait(false);
|
||||||
|
|
||||||
Schedule(() => setPerformanceValue(score, result.Total));
|
Schedule(() => setPerformanceValue(score, result.Total));
|
||||||
}, cancellationToken ?? default);
|
}, cancellationToken ?? default);
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
@ -26,7 +27,6 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
public partial class PerformanceBreakdownChart : Container
|
public partial class PerformanceBreakdownChart : Container
|
||||||
{
|
{
|
||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
private readonly IBeatmap playableBeatmap;
|
|
||||||
|
|
||||||
private Drawable spinner = null!;
|
private Drawable spinner = null!;
|
||||||
private Drawable content = null!;
|
private Drawable content = null!;
|
||||||
@ -42,7 +42,6 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap)
|
public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap)
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
this.playableBeatmap = playableBeatmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -142,12 +141,33 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
|
|
||||||
spinner.Show();
|
spinner.Show();
|
||||||
|
|
||||||
new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache)
|
computePerformance(cancellationTokenSource.Token)
|
||||||
.CalculateAsync(score, cancellationTokenSource.Token)
|
.ContinueWith(t => Schedule(() =>
|
||||||
.ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()!)));
|
{
|
||||||
|
if (t.GetResultSafely() is PerformanceBreakdown breakdown)
|
||||||
|
setPerformance(breakdown);
|
||||||
|
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPerformanceValue(PerformanceBreakdown breakdown)
|
private async Task<PerformanceBreakdown?> computePerformance(CancellationToken token)
|
||||||
|
{
|
||||||
|
var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
|
||||||
|
if (performanceCalculator == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var starsTask = difficultyCache.GetDifficultyAsync(score.BeatmapInfo!, score.Ruleset, score.Mods, token).ConfigureAwait(false);
|
||||||
|
if (await starsTask is not StarDifficulty stars)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (stars.DifficultyAttributes == null || stars.PerformanceAttributes == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new PerformanceBreakdown(
|
||||||
|
await performanceCalculator.CalculateAsync(score, stars.DifficultyAttributes, token).ConfigureAwait(false),
|
||||||
|
stars.PerformanceAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPerformance(PerformanceBreakdown breakdown)
|
||||||
{
|
{
|
||||||
spinner.Hide();
|
spinner.Hide();
|
||||||
content.FadeIn(200);
|
content.FadeIn(200);
|
||||||
@ -236,6 +256,8 @@ namespace osu.Game.Screens.Ranking.Statistics
|
|||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
cancellationTokenSource.Cancel();
|
cancellationTokenSource.Cancel();
|
||||||
|
cancellationTokenSource.Dispose();
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,9 @@ namespace osu.Game.Skinning.Components
|
|||||||
case BeatmapAttribute.BPM:
|
case BeatmapAttribute.BPM:
|
||||||
return BeatmapsetsStrings.ShowStatsBpm;
|
return BeatmapsetsStrings.ShowStatsBpm;
|
||||||
|
|
||||||
|
case BeatmapAttribute.MaxPP:
|
||||||
|
return BeatmapAttributeTextStrings.MaxPP;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
@ -225,6 +228,9 @@ namespace osu.Game.Skinning.Components
|
|||||||
case BeatmapAttribute.StarRating:
|
case BeatmapAttribute.StarRating:
|
||||||
return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2");
|
return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2");
|
||||||
|
|
||||||
|
case BeatmapAttribute.MaxPP:
|
||||||
|
return Math.Round(starDifficulty?.PerformanceAttributes?.Total ?? 0, MidpointRounding.AwayFromZero).ToLocalisableString();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
@ -279,5 +285,6 @@ namespace osu.Game.Skinning.Components
|
|||||||
RankedStatus,
|
RankedStatus,
|
||||||
BPM,
|
BPM,
|
||||||
Source,
|
Source,
|
||||||
|
MaxPP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user