diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs
index 380bf63e63..66c816e796 100644
--- a/osu.Game/Database/StandardisedScoreMigrationTools.cs
+++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs
@@ -312,8 +312,12 @@ namespace osu.Game.Database
double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy;
// We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio.
- double comboProportion =
- ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore);
+ // Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0
+ // when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot).
+ // In such cases, just assume 0.
+ double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0
+ ? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore)
+ : 0;
// We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore.
long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore;
@@ -321,6 +325,8 @@ namespace osu.Game.Database
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
+ long convertedTotalScore;
+
switch (score.Ruleset.OnlineID)
{
case 0:
@@ -417,32 +423,42 @@ namespace osu.Game.Database
double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore;
- return (long)Math.Round((
+ convertedTotalScore = (long)Math.Round((
500000 * newComboScoreProportion * score.Accuracy
+ 500000 * Math.Pow(score.Accuracy, 5)
+ bonusProportion) * modMultiplier);
+ break;
case 1:
- return (long)Math.Round((
+ convertedTotalScore = (long)Math.Round((
250000 * comboProportion
+ 750000 * Math.Pow(score.Accuracy, 3.6)
+ bonusProportion) * modMultiplier);
+ break;
case 2:
- return (long)Math.Round((
+ convertedTotalScore = (long)Math.Round((
600000 * comboProportion
+ 400000 * score.Accuracy
+ bonusProportion) * modMultiplier);
+ break;
case 3:
- return (long)Math.Round((
+ convertedTotalScore = (long)Math.Round((
850000 * comboProportion
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
+ bonusProportion) * modMultiplier);
+ break;
default:
- return score.TotalScore;
+ convertedTotalScore = score.TotalScore;
+ break;
}
+
+ if (convertedTotalScore < 0)
+ throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}");
+
+ return convertedTotalScore;
}
public static double ComputeAccuracy(ScoreInfo scoreInfo)
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
index dbd9a106df..cf0a7bd54f 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
@@ -35,9 +35,10 @@ namespace osu.Game.Scoring.Legacy
/// - 30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores.
/// - 30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores.
/// - 30000008: Add accuracy conversion. Reconvert all scores.
+ /// - 30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores.
///
///
- public const int LATEST_VERSION = 30000008;
+ public const int LATEST_VERSION = 30000009;
///
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.