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:
parent
20a539bc4b
commit
45fcbea182
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user