diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 031c14a091..63ad97376f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -430,6 +430,7 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; + Rank.Disabled = false; Rank.Value = ScoreRank.X; HighestCombo.Value = 0; } @@ -452,6 +453,32 @@ namespace osu.Game.Rulesets.Scoring score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } + /// + /// Populates the given score with remaining statistics as "missed" and marks it with rank. + /// + public void FailScore(ScoreInfo score) + { + if (Rank.Value == ScoreRank.F) + return; + + Rank.Value = ScoreRank.F; + Rank.Disabled = true; + + Debug.Assert(maximumResultCounts != null); + + if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick)) + scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit); + + if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) + scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + + int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; + + PopulateScore(score); + } + public override void ResetFromReplayFrame(ReplayFrame frame) { base.ResetFromReplayFrame(frame); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 516364e13a..490252ff2b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -267,12 +267,7 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - SaveReplay = () => - { - Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; - return prepareAndImportScore(); - }, + SaveReplay = prepareAndImportScore, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -831,7 +826,6 @@ namespace osu.Game.Screens.Play return false; GameplayState.HasFailed = true; - Score.ScoreInfo.Passed = false; updateGameplayState(); @@ -849,9 +843,17 @@ namespace osu.Game.Screens.Play return true; } - // Called back when the transform finishes + /// + /// Invoked when the fail animation has finished. + /// private void onFailComplete() { + // fail completion is a good point to mark a score as failed, + // since the last judgement that caused the fail only applies to score processor after onFail. + // todo: this should probably be handled better. + Score.ScoreInfo.Passed = false; + ScoreProcessor.FailScore(Score.ScoreInfo); + GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; @@ -1030,7 +1032,7 @@ namespace osu.Game.Screens.Play if (prepareScoreForDisplayTask == null) { Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; + ScoreProcessor.FailScore(Score.ScoreInfo); } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.