diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs
index 78beda6298..3c31acca01 100644
--- a/osu.Game/Online/Spectator/SpectatorClient.cs
+++ b/osu.Game/Online/Spectator/SpectatorClient.cs
@@ -172,6 +172,9 @@ namespace osu.Game.Online.Spectator
currentState.RulesetID = score.ScoreInfo.RulesetID;
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
currentState.State = SpectatedUserState.Playing;
+ currentState.MaxAchievableCombo = state.ScoreProcessor.MaxAchievableCombo;
+ currentState.MaxAchievableBaseScore = state.ScoreProcessor.MaxAchievableBaseScore;
+ currentState.TotalBasicHitObjects = state.ScoreProcessor.TotalBasicHitObjects;
currentBeatmap = state.Beatmap;
currentScore = score;
diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs
index 77686d12da..8b2e90ead0 100644
--- a/osu.Game/Online/Spectator/SpectatorState.cs
+++ b/osu.Game/Online/Spectator/SpectatorState.cs
@@ -27,6 +27,24 @@ namespace osu.Game.Online.Spectator
[Key(3)]
public SpectatedUserState State { get; set; }
+ ///
+ /// The maximum achievable combo, if everything is hit perfectly.
+ ///
+ [Key(4)]
+ public int MaxAchievableCombo { get; set; }
+
+ ///
+ /// The maximum achievable base score, if everything is hit perfectly.
+ ///
+ [Key(5)]
+ public double MaxAchievableBaseScore { get; set; }
+
+ ///
+ /// The total number of basic (non-tick and non-bonus) hitobjects that can be hit.
+ ///
+ [Key(6)]
+ public int TotalBasicHitObjects { get; set; }
+
public bool Equals(SpectatorState other)
{
if (ReferenceEquals(null, other)) return false;
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index 1dd1d1aeb6..ec09bfcfa3 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -88,17 +88,20 @@ namespace osu.Game.Rulesets.Scoring
private readonly double accuracyPortion;
private readonly double comboPortion;
- private int maxAchievableCombo;
+ ///
+ /// The maximum achievable combo, if everything is hit perfectly.
+ ///
+ internal int MaxAchievableCombo;
///
- /// The maximum achievable base score.
+ /// The maximum achievable base score, if everything is hit perfectly.
///
- private double maxBaseScore;
+ internal double MaxAchievableBaseScore;
///
- /// The maximum number of basic (non-tick and non-bonus) hitobjects.
+ /// The total number of basic (non-tick and non-bonus) hitobjects that can be hit.
///
- private int maxBasicHitObjects;
+ internal int TotalBasicHitObjects;
///
/// The maximum of a basic (non-tick and non-bonus) hitobject.
@@ -106,9 +109,9 @@ namespace osu.Game.Rulesets.Scoring
///
private HitResult? maxBasicResult;
- private double rollingMaxBaseScore;
- private double baseScore;
- private int basicHitObjects;
+ private double rollingMaxAchievableBaseScore;
+ private double rollingBaseScore;
+ private int rollingBasicHitObjects;
private bool beatmapApplied;
private readonly Dictionary scoreResultCounts = new Dictionary();
@@ -175,12 +178,12 @@ namespace osu.Game.Rulesets.Scoring
if (!result.Type.IsBonus())
{
- baseScore += scoreIncrease;
- rollingMaxBaseScore += result.Judgement.MaxNumericResult;
+ rollingBaseScore += scoreIncrease;
+ rollingMaxAchievableBaseScore += result.Judgement.MaxNumericResult;
}
if (result.Type.IsBasic())
- basicHitObjects++;
+ rollingBasicHitObjects++;
hitEvents.Add(CreateHitEvent(result));
lastHitObject = result.HitObject;
@@ -213,12 +216,12 @@ namespace osu.Game.Rulesets.Scoring
if (!result.Type.IsBonus())
{
- baseScore -= scoreIncrease;
- rollingMaxBaseScore -= result.Judgement.MaxNumericResult;
+ rollingBaseScore -= scoreIncrease;
+ rollingMaxAchievableBaseScore -= result.Judgement.MaxNumericResult;
}
if (result.Type.IsBasic())
- basicHitObjects--;
+ rollingBasicHitObjects--;
Debug.Assert(hitEvents.Count > 0);
lastHitObject = hitEvents[^1].LastHitObject;
@@ -229,12 +232,12 @@ namespace osu.Game.Rulesets.Scoring
private void updateScore()
{
- double rollingAccuracyRatio = rollingMaxBaseScore > 0 ? baseScore / rollingMaxBaseScore : 1;
- double accuracyRatio = maxBaseScore > 0 ? baseScore / maxBaseScore : 1;
- double comboRatio = maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1;
+ double rollingAccuracyRatio = rollingMaxAchievableBaseScore > 0 ? rollingBaseScore / rollingMaxAchievableBaseScore : 1;
+ double accuracyRatio = MaxAchievableBaseScore > 0 ? rollingBaseScore / MaxAchievableBaseScore : 1;
+ double comboRatio = MaxAchievableCombo > 0 ? (double)HighestCombo.Value / MaxAchievableCombo : 1;
Accuracy.Value = rollingAccuracyRatio;
- TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maxBasicHitObjects);
+ TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), TotalBasicHitObjects);
}
///
@@ -288,10 +291,10 @@ namespace osu.Game.Rulesets.Scoring
out _,
out _);
- double accuracyRatio = maxBaseScore > 0 ? extractedBaseScore / maxBaseScore : 1;
- double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1;
+ double accuracyRatio = MaxAchievableBaseScore > 0 ? extractedBaseScore / MaxAchievableBaseScore : 1;
+ double comboRatio = MaxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / MaxAchievableCombo : 1;
- return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maxBasicHitObjects);
+ return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), TotalBasicHitObjects);
}
///
@@ -403,14 +406,14 @@ namespace osu.Game.Rulesets.Scoring
if (storeResults)
{
- maxAchievableCombo = HighestCombo.Value;
- maxBaseScore = baseScore;
- maxBasicHitObjects = basicHitObjects;
+ MaxAchievableCombo = HighestCombo.Value;
+ MaxAchievableBaseScore = rollingBaseScore;
+ TotalBasicHitObjects = rollingBasicHitObjects;
}
- baseScore = 0;
- rollingMaxBaseScore = 0;
- basicHitObjects = 0;
+ rollingBaseScore = 0;
+ rollingMaxAchievableBaseScore = 0;
+ rollingBasicHitObjects = 0;
TotalScore.Value = 0;
Accuracy.Value = 1;
@@ -444,7 +447,7 @@ namespace osu.Game.Rulesets.Scoring
if (frame.Header == null)
return;
- extractFromStatistics(ruleset, frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _);
+ extractFromStatistics(ruleset, frame.Header.Statistics, out rollingBaseScore, out rollingMaxAchievableBaseScore, out _, out _);
HighestCombo.Value = frame.Header.MaxCombo;
scoreResultCounts.Clear();
diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs
index c6a072da74..586cdcda51 100644
--- a/osu.Game/Screens/Play/GameplayState.cs
+++ b/osu.Game/Screens/Play/GameplayState.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
using System;
using System.Collections.Generic;
using osu.Framework.Bindables;
@@ -8,10 +10,9 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
-#nullable enable
-
namespace osu.Game.Screens.Play
{
///
@@ -39,6 +40,8 @@ namespace osu.Game.Screens.Play
///
public readonly Score Score;
+ public readonly ScoreProcessor ScoreProcessor;
+
///
/// Whether gameplay completed without the user failing.
///
@@ -61,7 +64,7 @@ namespace osu.Game.Screens.Play
private readonly Bindable lastJudgementResult = new Bindable();
- public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null)
+ public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null, ScoreProcessor? scoreProcessor = null)
{
Beatmap = beatmap;
Ruleset = ruleset;
@@ -72,7 +75,8 @@ namespace osu.Game.Screens.Play
Ruleset = ruleset.RulesetInfo
}
};
- Mods = mods ?? ArraySegment.Empty;
+ Mods = mods ?? Array.Empty();
+ ScoreProcessor = scoreProcessor ?? ruleset.CreateScoreProcessor();
}
///
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index b68ac3cb05..dfc0fa1d1d 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -237,7 +237,7 @@ namespace osu.Game.Screens.Play
Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
Score.ScoreInfo.Mods = gameplayMods;
- dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score));
+ dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor));
var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin);