1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 08:32:57 +08:00

Update classic scoring algorithm to closer match stable score V1

This commit is contained in:
Bartłomiej Dach 2023-09-15 14:11:14 +02:00
parent 81130eacd1
commit 57c00e728b
No known key found for this signature in database

View File

@ -27,44 +27,37 @@ namespace osu.Game.Scoring.Legacy
.DefaultIfEmpty(0)
.Sum();
// 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 scaledRawScore = score / ScoreProcessor.MAX_SCORE;
return (long)Math.Round(Math.Pow(scaledRawScore * Math.Max(1, maxBasicJudgements), 2) * getStandardisedToClassicMultiplier(rulesetId));
return convertStandardisedToClassic(rulesetId, score, maxBasicJudgements);
}
/// <summary>
/// Returns a ballpark multiplier which gives a similar "feel" for how large scores should get when displayed in "classic" mode.
/// Returns a ballpark "classic" score which gives a similar "feel" to stable.
/// This is different per ruleset to match the different algorithms used in the scoring implementation.
/// </summary>
private static double getStandardisedToClassicMultiplier(int rulesetId)
/// <remarks>
/// The coefficients chosen here were determined by a least-squares fit performed over all beatmaps
/// with the goal of minimising the relative error of maximum possible base score (without bonus).
/// The constant coefficients (100000, 1 / 10d) - while being detrimental to the least-squares fit - are forced,
/// so that every 10 points in standardised mode converts to at least 1 point in classic mode.
/// This is done to account for bonus judgements in a way that does not reorder scores.
/// </remarks>
private static long convertStandardisedToClassic(int rulesetId, long standardisedTotalScore, int objectCount)
{
double multiplier;
switch (rulesetId)
{
// For non-legacy rulesets, just go with the same as the osu! ruleset.
// This is arbitrary, but at least allows the setting to do something to the score.
default:
case 0:
multiplier = 36;
break;
return (long)Math.Round((objectCount * objectCount * 32.57 + 100000) * standardisedTotalScore / ScoreProcessor.MAX_SCORE);
case 1:
multiplier = 22;
break;
return (long)Math.Round((objectCount * 1109 + 100000) * standardisedTotalScore / ScoreProcessor.MAX_SCORE);
case 2:
multiplier = 28;
break;
return (long)Math.Round(Math.Pow(standardisedTotalScore / ScoreProcessor.MAX_SCORE * objectCount, 2) * 21.62 + standardisedTotalScore / 10d);
case 3:
multiplier = 16;
break;
default:
return standardisedTotalScore;
}
return multiplier;
}
public static int? GetCountGeki(this ScoreInfo scoreInfo)