diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 00bb02a937..81b624f908 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -133,7 +133,6 @@ namespace osu.Game.Tests.Resources
StarRating = diff,
Length = length,
BPM = bpm,
- MaxCombo = 1000,
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = rulesetInfo,
Metadata = metadata,
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index c6f69286cd..f90208d0c0 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -9,6 +9,7 @@ using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Models;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays.BeatmapSet.Scores;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using Realms;
@@ -169,7 +170,12 @@ namespace osu.Game.Beatmaps
[Ignored]
public APIBeatmap? OnlineInfo { get; set; }
+ ///
+ /// The maximum achievable combo on this beatmap, populated for online info purposes only.
+ /// Todo: This should never be used nor exist, but is still relied on in since can't be used yet. For now this is obsoleted until it is removed.
+ ///
[Ignored]
+ [Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")]
public int? MaxCombo { get; set; }
[Ignored]
diff --git a/osu.Game/Beatmaps/EFBeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs
index 8daeaa7030..740adfd1c7 100644
--- a/osu.Game/Beatmaps/EFBeatmapInfo.cs
+++ b/osu.Game/Beatmaps/EFBeatmapInfo.cs
@@ -53,9 +53,6 @@ namespace osu.Game.Beatmaps
[NotMapped]
public APIBeatmap OnlineInfo { get; set; }
- [NotMapped]
- public int? MaxCombo { get; set; }
-
///
/// The playable length in milliseconds of this beatmap.
///
diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs
index c9deee19fe..cbf5c5ffe9 100644
--- a/osu.Game/Database/EFToRealmMigrator.cs
+++ b/osu.Game/Database/EFToRealmMigrator.cs
@@ -295,7 +295,6 @@ namespace osu.Game.Database
TimelineZoom = beatmap.TimelineZoom,
Countdown = beatmap.Countdown,
CountdownOffset = beatmap.CountdownOffset,
- MaxCombo = beatmap.MaxCombo,
Bookmarks = beatmap.Bookmarks,
BeatmapSet = realmBeatmapSet,
};
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
index 5ef434c427..86e72e9faa 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
@@ -173,7 +173,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
Text = score.MaxCombo.ToLocalisableString(@"0\x"),
Font = OsuFont.GetFont(size: text_size),
+#pragma warning disable 618
Colour = score.MaxCombo == score.BeatmapInfo.MaxCombo ? highAccuracyColour : Color4.White
+#pragma warning restore 618
}
};
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
index 6f07b20049..7d59c95396 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
@@ -78,7 +78,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
// TODO: temporary. should be removed once `OrderByTotalScore` can accept `IScoreInfo`.
var beatmapInfo = new BeatmapInfo
{
+#pragma warning disable 618
MaxCombo = apiBeatmap.MaxCombo,
+#pragma warning restore 618
Status = apiBeatmap.Status,
MD5Hash = apiBeatmap.MD5Hash
};
diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index 4de1d580dc..d7185a1677 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -157,7 +157,7 @@ namespace osu.Game.Scoring
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
///
- /// Whether this represents a legacy (osu!stable) score.
+ /// Whether this represents a legacy (osu!stable) score.
///
[Ignored]
public bool IsLegacyScore => Mods.OfType().Any();
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index 02a7d9a39f..83359838aa 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -134,35 +134,9 @@ namespace osu.Game.Scoring
if (string.IsNullOrEmpty(score.BeatmapInfo.MD5Hash))
return score.TotalScore;
- int beatmapMaxCombo;
-
- if (score.IsLegacyScore)
- {
- // This score is guaranteed to be an osu!stable score.
- // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
- if (score.BeatmapInfo.MaxCombo != null)
- beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value;
- else
- {
- if (difficulties == null)
- return score.TotalScore;
-
- // We can compute the max combo locally after the async beatmap difficulty computation.
- var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
-
- // Something failed during difficulty calculation. Fall back to provided score.
- if (difficulty == null)
- return score.TotalScore;
-
- beatmapMaxCombo = difficulty.Value.MaxCombo;
- }
- }
- else
- {
- // This is guaranteed to be a non-legacy score.
- // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
- beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum();
- }
+ int? beatmapMaxCombo = await GetMaximumAchievableComboAsync(score, cancellationToken).ConfigureAwait(false);
+ if (beatmapMaxCombo == null)
+ return score.TotalScore;
if (beatmapMaxCombo == 0)
return 0;
@@ -171,7 +145,37 @@ namespace osu.Game.Scoring
var scoreProcessor = ruleset.CreateScoreProcessor();
scoreProcessor.Mods.Value = score.Mods;
- return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo));
+ return (long)Math.Round(scoreProcessor.ComputeFinalLegacyScore(mode, score, beatmapMaxCombo.Value));
+ }
+
+ ///
+ /// Retrieves the maximum achievable combo for the provided score.
+ ///
+ /// The to compute the maximum achievable combo for.
+ /// A to cancel the process.
+ /// The maximum achievable combo. A return value indicates the difficulty cache has failed to retrieve the combo.
+ public async Task GetMaximumAchievableComboAsync([NotNull] ScoreInfo score, CancellationToken cancellationToken = default)
+ {
+ if (score.IsLegacyScore)
+ {
+ // This score is guaranteed to be an osu!stable score.
+ // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
+#pragma warning disable CS0618
+ if (score.BeatmapInfo.MaxCombo != null)
+ return score.BeatmapInfo.MaxCombo.Value;
+#pragma warning restore CS0618
+
+ if (difficulties == null)
+ return null;
+
+ // We can compute the max combo locally after the async beatmap difficulty computation.
+ var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
+ return difficulty?.MaxCombo;
+ }
+
+ // This is guaranteed to be a non-legacy score.
+ // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
+ return Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetValueOrDefault(r)).Sum();
}
///
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index 1b1aa3a684..5b3129dad6 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -65,10 +65,12 @@ namespace osu.Game.Screens.Ranking.Expanded
var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata;
string creator = metadata.Author.Username;
+ int? beatmapMaxCombo = scoreManager.GetMaximumAchievableComboAsync(score).GetResultSafely();
+
var topStatistics = new List
{
new AccuracyStatistic(score.Accuracy),
- new ComboStatistic(score.MaxCombo, beatmap.MaxCombo, score.Statistics.All(stat => !stat.Key.BreaksCombo() || stat.Value == 0)),
+ new ComboStatistic(score.MaxCombo, beatmapMaxCombo),
new PerformanceStatistic(score),
};
@@ -80,8 +82,6 @@ namespace osu.Game.Screens.Ranking.Expanded
statisticDisplays.AddRange(topStatistics);
statisticDisplays.AddRange(bottomStatistics);
- var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely();
-
AddInternal(new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
@@ -224,6 +224,8 @@ namespace osu.Game.Screens.Ranking.Expanded
if (score.Date != default)
AddInternal(new PlayedOnText(score.Date));
+ var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).GetResultSafely();
+
if (starDifficulty != null)
{
starAndModDisplay.Add(new StarRatingDisplay(starDifficulty.Value)
diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs
index 67d580270d..0e42ec026a 100644
--- a/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Statistics/ComboStatistic.cs
@@ -26,11 +26,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
///
/// The combo to be displayed.
/// The maximum value of .
- /// Whether this is a perfect combo.
- public ComboStatistic(int combo, int? maxCombo, bool isPerfect)
+ public ComboStatistic(int combo, int? maxCombo)
: base("combo", combo, maxCombo)
{
- this.isPerfect = isPerfect;
+ isPerfect = combo == maxCombo;
}
public override void Appear()