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"));
|
||||
}
|
||||
|
||||
[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 class TestRuleset : Ruleset
|
||||
|
@ -4,12 +4,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
@ -18,7 +21,11 @@ using osu.Game.Database;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -237,10 +244,37 @@ namespace osu.Game.Beatmaps
|
||||
var ruleset = rulesetInfo.CreateInstance();
|
||||
Debug.Assert(ruleset != null);
|
||||
|
||||
var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo));
|
||||
var attributes = calculator.Calculate(key.OrderedMods, cancellationToken);
|
||||
PlayableCachedWorkingBeatmap workingBeatmap = new PlayableCachedWorkingBeatmap(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo));
|
||||
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)
|
||||
{
|
||||
@ -276,7 +310,6 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public readonly BeatmapInfo BeatmapInfo;
|
||||
public readonly RulesetInfo Ruleset;
|
||||
|
||||
public readonly Mod[] OrderedMods;
|
||||
|
||||
public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable<Mod>? mods)
|
||||
@ -317,5 +350,42 @@ namespace osu.Game.Beatmaps
|
||||
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.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
@ -25,30 +22,34 @@ namespace osu.Game.Beatmaps
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
public readonly DifficultyAttributes Attributes;
|
||||
public readonly DifficultyAttributes? DifficultyAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="StarDifficulty"/> structure based on <see cref="DifficultyAttributes"/> computed
|
||||
/// by a <see cref="DifficultyCalculator"/>.
|
||||
/// The performance attributes computed for a perfect score on the given beatmap.
|
||||
/// Might not be available if the star difficulty is associated with a beatmap that's not locally available.
|
||||
/// </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;
|
||||
MaxCombo = attributes.MaxCombo;
|
||||
Attributes = attributes;
|
||||
Stars = double.IsFinite(difficulty.StarRating) ? difficulty.StarRating : 0;
|
||||
MaxCombo = difficulty.MaxCombo;
|
||||
DifficultyAttributes = difficulty;
|
||||
PerformanceAttributes = performance;
|
||||
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public StarDifficulty(double starDifficulty, int maxCombo)
|
||||
{
|
||||
Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0;
|
||||
MaxCombo = maxCombo;
|
||||
Attributes = null;
|
||||
}
|
||||
|
||||
public DifficultyRating DifficultyRating => GetDifficultyRating(Stars);
|
||||
|
@ -12,23 +12,28 @@ namespace osu.Game.Localisation.SkinComponents
|
||||
/// <summary>
|
||||
/// "Attribute"
|
||||
/// </summary>
|
||||
public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), "Attribute");
|
||||
public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), @"Attribute");
|
||||
|
||||
/// <summary>
|
||||
/// "The attribute to be displayed."
|
||||
/// </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>
|
||||
/// "Template"
|
||||
/// </summary>
|
||||
public static LocalisableString Template => new TranslatableString(getKey(@"template"), "Template");
|
||||
public static LocalisableString Template => new TranslatableString(getKey(@"template"), @"Template");
|
||||
|
||||
/// <summary>
|
||||
/// "Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values)."
|
||||
/// </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).");
|
||||
|
||||
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>
|
||||
public long MaximumTotalScore { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum achievable combo.
|
||||
/// </summary>
|
||||
public int MaximumCombo { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum sum of accuracy-affecting judgements at the current point in time.
|
||||
/// </summary>
|
||||
@ -423,6 +428,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
MaximumResultCounts.AddRange(ScoreResultCounts);
|
||||
|
||||
MaximumTotalScore = TotalScore.Value;
|
||||
MaximumCombo = HighestCombo.Value;
|
||||
}
|
||||
|
||||
ScoreResultCounts.Clear();
|
||||
|
@ -53,10 +53,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
|
||||
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)
|
||||
if (attributes?.DifficultyAttributes == null || performanceCalculator == null)
|
||||
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));
|
||||
}, cancellationToken ?? default);
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -26,7 +27,6 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
public partial class PerformanceBreakdownChart : Container
|
||||
{
|
||||
private readonly ScoreInfo score;
|
||||
private readonly IBeatmap playableBeatmap;
|
||||
|
||||
private Drawable spinner = null!;
|
||||
private Drawable content = null!;
|
||||
@ -42,7 +42,6 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
public PerformanceBreakdownChart(ScoreInfo score, IBeatmap playableBeatmap)
|
||||
{
|
||||
this.score = score;
|
||||
this.playableBeatmap = playableBeatmap;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -142,12 +141,33 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
|
||||
spinner.Show();
|
||||
|
||||
new PerformanceBreakdownCalculator(playableBeatmap, difficultyCache)
|
||||
.CalculateAsync(score, cancellationTokenSource.Token)
|
||||
.ContinueWith(t => Schedule(() => setPerformanceValue(t.GetResultSafely()!)));
|
||||
computePerformance(cancellationTokenSource.Token)
|
||||
.ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
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();
|
||||
content.FadeIn(200);
|
||||
@ -236,6 +256,8 @@ namespace osu.Game.Screens.Ranking.Statistics
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
cancellationTokenSource.Dispose();
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
@ -177,6 +177,9 @@ namespace osu.Game.Skinning.Components
|
||||
case BeatmapAttribute.BPM:
|
||||
return BeatmapsetsStrings.ShowStatsBpm;
|
||||
|
||||
case BeatmapAttribute.MaxPP:
|
||||
return BeatmapAttributeTextStrings.MaxPP;
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
@ -225,6 +228,9 @@ namespace osu.Game.Skinning.Components
|
||||
case BeatmapAttribute.StarRating:
|
||||
return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2");
|
||||
|
||||
case BeatmapAttribute.MaxPP:
|
||||
return Math.Round(starDifficulty?.PerformanceAttributes?.Total ?? 0, MidpointRounding.AwayFromZero).ToLocalisableString();
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
@ -279,5 +285,6 @@ namespace osu.Game.Skinning.Components
|
||||
RankedStatus,
|
||||
BPM,
|
||||
Source,
|
||||
MaxPP
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user