Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compute total score without mods during standardised score conversion #28277

Merged
merged 2 commits into from
May 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 34 additions & 33 deletions osu.Game/Database/StandardisedScoreMigrationTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;

namespace osu.Game.Database
{
Expand Down Expand Up @@ -248,8 +247,7 @@ public static void UpdateFromLegacy(ScoreInfo score, WorkingBeatmap beatmap)
// 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>
Expand All @@ -273,7 +271,7 @@ public static void UpdateFromLegacy(ScoreInfo score, Ruleset ruleset, LegacyBeat
// 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>
Expand All @@ -283,17 +281,13 @@ public static void UpdateFromLegacy(ScoreInfo score, Ruleset ruleset, LegacyBeat
/// <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);

Expand All @@ -302,8 +296,13 @@ private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset

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>
Expand All @@ -314,15 +313,15 @@ private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset
/// <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;
Expand Down Expand Up @@ -354,28 +353,30 @@ private static long convertFromLegacyTotalScore(ScoreInfo score, Ruleset ruleset

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.
// if there is no legacy combo score, all combo conversion operations below
// 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:
Expand Down Expand Up @@ -472,17 +473,17 @@ double scoreBasedEstimateOfComboPortionInStandardisedScore

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:
Expand All @@ -507,28 +508,28 @@ double scoreBasedEstimateOfComboPortionInStandardisedScore
? 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>
Expand Down
Loading