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

Compute total score without mods during standardised score conversion

This is going to be used by server-side flows. Note that the server-side
overload of `UpdateFromLegacy()` was not calling
`LegacyScoreDecoder.PopulateTotalScoreWithoutMods()`.

Computing the score without mods inline reduces reflection overheads
from constructing mod instances, which feels pretty important for
server-side flows.

There is one weird kink in the treatment of stable scores with score V2
active - they get the *legacy* multipliers unapplied for them because
that made the most sense. For all intents and purposes this matters
mostly for client-side replays with score V2. I'm not sure whether
scores with SV2 ever make it to submission in stable.

There may be minute differences in converted score due to rounding
shenanigans but I don't think it's worth doing a reverify for this.
This commit is contained in:
Bartłomiej Dach 2024-05-17 14:43:06 +02:00
parent 20a539bc4b
commit 45fcbea182
No known key found for this signature in database

View File

@ -16,7 +16,6 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
namespace osu.Game.Database
{
@ -248,8 +247,7 @@ namespace osu.Game.Database
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
score.Accuracy = computeAccuracy(score, scoreProcessor);
score.Rank = computeRank(score, scoreProcessor);
score.TotalScore = convertFromLegacyTotalScore(score, ruleset, beatmap);
LegacyScoreDecoder.PopulateTotalScoreWithoutMods(score);
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap);
}
/// <summary>
@ -273,7 +271,7 @@ namespace osu.Game.Database
// warning: ordering is important here - both total score and ranks are dependent on accuracy!
score.Accuracy = computeAccuracy(score, scoreProcessor);
score.Rank = computeRank(score, scoreProcessor);
score.TotalScore = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
(score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes);
}
/// <summary>
@ -283,17 +281,13 @@ namespace osu.Game.Database
/// <param name="ruleset">The <see cref="Ruleset"/> in which the score was set.</param>
/// <param name="beatmap">The <see cref="WorkingBeatmap"/> applicable for this score.</param>
/// <returns>The standardised total score.</returns>
private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap)
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, WorkingBeatmap beatmap)
{
if (!score.IsLegacyScore)
return score.TotalScore;
return (score.TotalScoreWithoutMods, score.TotalScore);
if (ruleset is not ILegacyRuleset legacyRuleset)
return score.TotalScore;
var mods = score.Mods;
if (mods.Any(mod => mod is ModScoreV2))
return score.TotalScore;
return (score.TotalScoreWithoutMods, score.TotalScore);
var playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods);
@ -302,8 +296,13 @@ namespace osu.Game.Database
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
var legacyBeatmapConversionDifficultyInfo = LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap);
return convertFromLegacyTotalScore(score, ruleset, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
var mods = score.Mods;
if (mods.Any(mod => mod is ModScoreV2))
return ((long)Math.Round(score.TotalScore / sv1Simulator.GetLegacyScoreMultiplier(mods, legacyBeatmapConversionDifficultyInfo)), score.TotalScore);
return convertFromLegacyTotalScore(score, ruleset, legacyBeatmapConversionDifficultyInfo, attributes);
}
/// <summary>
@ -314,15 +313,15 @@ namespace osu.Game.Database
/// <param name="difficulty">The beatmap difficulty.</param>
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
/// <returns>The standardised total score.</returns>
private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
private static (long withoutMods, long withMods) convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
if (!score.IsLegacyScore)
return score.TotalScore;
return (score.TotalScoreWithoutMods, score.TotalScore);
Debug.Assert(score.LegacyTotalScore != null);
if (ruleset is not ILegacyRuleset legacyRuleset)
return score.TotalScore;
return (score.TotalScoreWithoutMods, score.TotalScore);
double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty);
int maximumLegacyAccuracyScore = attributes.AccuracyScore;
@ -354,17 +353,18 @@ namespace osu.Game.Database
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
long convertedTotalScore;
long convertedTotalScoreWithoutMods;
switch (score.Ruleset.OnlineID)
{
case 0:
if (score.MaxCombo == 0 || score.Accuracy == 0)
{
return (long)Math.Round((
convertedTotalScoreWithoutMods = (long)Math.Round(
0
+ 500000 * Math.Pow(score.Accuracy, 5)
+ bonusProportion) * modMultiplier);
+ bonusProportion);
break;
}
// see similar check above.
@ -372,10 +372,11 @@ namespace osu.Game.Database
// are either pointless or wildly wrong.
if (maximumLegacyComboScore + maximumLegacyBonusScore == 0)
{
return (long)Math.Round((
convertedTotalScoreWithoutMods = (long)Math.Round(
500000 * comboProportion // as above, zero if mods result in zero multiplier, one otherwise
+ 500000 * Math.Pow(score.Accuracy, 5)
+ bonusProportion) * modMultiplier);
+ bonusProportion);
break;
}
// Assumptions:
@ -472,17 +473,17 @@ namespace osu.Game.Database
double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore;
convertedTotalScore = (long)Math.Round((
convertedTotalScoreWithoutMods = (long)Math.Round(
500000 * newComboScoreProportion * score.Accuracy
+ 500000 * Math.Pow(score.Accuracy, 5)
+ bonusProportion) * modMultiplier);
+ bonusProportion);
break;
case 1:
convertedTotalScore = (long)Math.Round((
convertedTotalScoreWithoutMods = (long)Math.Round(
250000 * comboProportion
+ 750000 * Math.Pow(score.Accuracy, 3.6)
+ bonusProportion) * modMultiplier);
+ bonusProportion);
break;
case 2:
@ -507,28 +508,28 @@ namespace osu.Game.Database
? 0
: (double)score.Statistics.GetValueOrDefault(HitResult.SmallTickHit) / score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit);
convertedTotalScore = (long)Math.Round((
convertedTotalScoreWithoutMods = (long)Math.Round(
comboPortion * estimateComboProportionForCatch(attributes.MaxCombo, score.MaxCombo, score.Statistics.GetValueOrDefault(HitResult.Miss))
+ dropletsPortion * dropletsHit
+ bonusProportion) * modMultiplier);
+ bonusProportion);
break;
case 3:
convertedTotalScore = (long)Math.Round((
convertedTotalScoreWithoutMods = (long)Math.Round(
850000 * comboProportion
+ 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
+ bonusProportion) * modMultiplier);
+ bonusProportion);
break;
default:
convertedTotalScore = score.TotalScore;
break;
return (score.TotalScoreWithoutMods, score.TotalScore);
}
if (convertedTotalScore < 0)
throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}");
if (convertedTotalScoreWithoutMods < 0)
throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScoreWithoutMods}");
return convertedTotalScore;
long convertedTotalScore = (long)Math.Round(convertedTotalScoreWithoutMods * modMultiplier);
return (convertedTotalScoreWithoutMods, convertedTotalScore);
}
/// <summary>