diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 422b29601a..b0656e270e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; @@ -86,9 +87,22 @@ namespace osu.Game.Rulesets.Scoring /// private double maxBaseScore; + /// + /// The maximum number of basic (non-tick and non-bonus) hitobjects. + /// + private int maxBasicHitObjects; + + /// + /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. + /// Only populated via . + /// + private HitResult? maxBasicHitResult; + private double rollingMaxBaseScore; private double baseScore; + private int basicHitObjects; + private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly List hitEvents = new List(); private HitObject lastHitObject; @@ -122,8 +136,6 @@ namespace osu.Game.Rulesets.Scoring }; } - private readonly Dictionary scoreResultCounts = new Dictionary(); - protected sealed override void ApplyResultInternal(JudgementResult result) { result.ComboAtJudgement = Combo.Value; @@ -160,6 +172,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + if (result.Type.IsBasic()) + basicHitObjects++; + hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -195,6 +210,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + if (result.Type.IsBasic()) + basicHitObjects--; + Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -210,15 +228,30 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = GetScore(Mode.Value); } - public double GetScore(ScoringMode mode) => GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + /// + /// Computes the total score from judgements that have been applied to this + /// through and . + /// + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. + /// The total score in the given . + public double GetScore(ScoringMode mode) + { + return GetScore(mode, calculateAccuracyRatio(baseScore), calculateComboRatio(HighestCombo.Value), scoreResultCounts); + } /// - /// Given a minimal set of inputs, return the computed score for the tracked beatmap / mods combination, at the current point in time. + /// Computes the total score from judgements counts in a statistics dictionary. /// - /// The to compute the total score in. + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. /// The maximum combo achievable in the beatmap. - /// Statistics to be used for calculating accuracy, bonus score, etc. - /// The computed score for provided inputs. + /// The statistics to compute the score for. + /// The total score computed from judgements in the statistics dictionary. public double GetScore(ScoringMode mode, int maxCombo, Dictionary statistics) { // calculate base score from statistics pairs @@ -236,25 +269,34 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Computes the total score. + /// Computes the total score from given scoring component ratios. /// - /// The to compute the total score in. + /// + /// Requires an to have been applied via before use. + /// + /// The to represent the score as. /// The accuracy percentage achieved by the player. - /// The proportion of the max combo achieved by the player. - /// Any statistics to be factored in. - /// The total score. + /// The portion of the max combo achieved by the player. + /// Any additional statistics to be factored in. + /// The total score computed from the given scoring component ratios. public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics) { - int totalHitObjects = statistics.Where(k => k.Key >= HitResult.Miss && k.Key <= HitResult.Perfect).Sum(k => k.Value); - - // If there are no hitobjects then the beatmap can be composed of only ticks or spinners, so ensure we don't multiply by 0 at all times. - if (totalHitObjects == 0) - totalHitObjects = 1; - - return GetScore(mode, accuracyRatio, comboRatio, statistics, totalHitObjects); + return GetScore(mode, accuracyRatio, comboRatio, maxBasicHitObjects, statistics); } - public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, Dictionary statistics, int totalHitObjects) + /// + /// Computes the total score from given scoring component ratios. + /// + /// + /// Does not require an to have been applied via before use. + /// + /// The to represent the score as. + /// The accuracy percentage achieved by the player. + /// The portion of the max combo achieved by the player. + /// Any additional statistics to be factored in. + /// The total number of basic (non-tick and non-bonus) hitobjects in the beatmap. + /// The total score computed from the given scoring component ratios. + public double GetScore(ScoringMode mode, double accuracyRatio, double comboRatio, int totalBasicHitObjects, Dictionary statistics) { switch (mode) { @@ -268,7 +310,7 @@ namespace osu.Game.Rulesets.Scoring // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. double scaledStandardised = GetScore(ScoringMode.Standardised, accuracyRatio, comboRatio, statistics) / max_score; - return Math.Pow(scaledStandardised * totalHitObjects, 2) * 36; + return Math.Pow(scaledStandardised * totalBasicHitObjects, 2) * 36; } } @@ -326,10 +368,12 @@ namespace osu.Game.Rulesets.Scoring { maxAchievableCombo = HighestCombo.Value; maxBaseScore = baseScore; + maxBasicHitObjects = basicHitObjects; } baseScore = 0; rollingMaxBaseScore = 0; + basicHitObjects = 0; TotalScore.Value = 0; Accuracy.Value = 1; @@ -355,11 +399,6 @@ namespace osu.Game.Rulesets.Scoring score.HitEvents = hitEvents; } - /// - /// Maximum for a normal hit (i.e. not tick/bonus) for this ruleset. Only populated via . - /// - private HitResult? maxNormalResult; - public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) { base.ResetFromReplayFrame(ruleset, frame); @@ -394,7 +433,7 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxNormalResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + maxResult = maxBasicHitResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; break; }