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.