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);