mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 08:32:57 +08:00
Merge pull request #24924 from bdach/update-classic-scoring
Update classic scoring formula to closer match stable score V1
This commit is contained in:
commit
db9113b38e
@ -45,9 +45,9 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Meh, 116_667)]
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Ok, 233_338)]
|
||||
[TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Meh, 0)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Ok, 2)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Great, 36)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Meh, 11_670)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Ok, 23_341)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Great, 100_033)]
|
||||
public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore)
|
||||
{
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
@ -84,17 +84,17 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
[TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)]
|
||||
[TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 4)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 15)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 53)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 140)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 140)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 7_975)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 15_949)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 30_398)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 49_546)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 49_546)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 11)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 54_189)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 9)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 36)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 36)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 49_289)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 100_003)]
|
||||
[TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 100_015)]
|
||||
public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore)
|
||||
{
|
||||
var minResult = new TestJudgement(hitResult).MinResult;
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user