Skip to content

Relocate numeric HitResult values, add accuracy conversion #25993

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

Merged
merged 4 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
Expand All @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
{
private readonly ScoreProcessor scoreProcessor = new CatchScoreProcessor();

private int legacyBonusScore;
private int standardisedBonusScore;
private int combo;
Expand Down Expand Up @@ -134,7 +136,7 @@ private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attribut
if (isBonus)
{
legacyBonusScore += scoreIncrease;
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
}
else
attributes.AccuracyScore += scoreIncrease;
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ protected override double ComputeTotalScore(double comboProgress, double accurac
}

protected override double GetComboScoreChange(JudgementResult result)
=> GetNumericResultFor(result) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
=> GetBaseScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));

public override ScoreRank RankFromAccuracy(double accuracy)
{
Expand Down
31 changes: 9 additions & 22 deletions osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,44 +31,31 @@ protected override double ComputeTotalScore(double comboProgress, double accurac
+ bonusPortion;
}

protected override double GetNumericResultFor(JudgementResult result)
protected override double GetComboScoreChange(JudgementResult result)
{
switch (result.Type)
{
case HitResult.Perfect:
return 305;
}

return base.GetNumericResultFor(result);
return getBaseComboScoreForResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
}

protected override double GetMaxNumericResultFor(JudgementResult result)
public override int GetBaseScoreForResult(HitResult result)
{
switch (result.Judgement.MaxResult)
switch (result)
{
case HitResult.Perfect:
return 305;
}

return base.GetMaxNumericResultFor(result);
return base.GetBaseScoreForResult(result);
}

protected override double GetComboScoreChange(JudgementResult result)
private int getBaseComboScoreForResult(HitResult result)
{
double numericResult;

switch (result.Type)
switch (result)
{
case HitResult.Perfect:
numericResult = 300;
break;

default:
numericResult = GetNumericResultFor(result);
break;
return 300;
}

return numericResult * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
return GetBaseScoreForResult(result);
}

private class JudgementOrderComparer : IComparer<HitObject>
Expand Down
11 changes: 5 additions & 6 deletions osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ public void TestSpinnerRewindingRotation()
double trackerRotationTolerance = 0;

addSeekStep(5000);
AddStep("calculate rotation tolerance", () =>
{
trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f);
});
AddStep("calculate rotation tolerance", () => { trackerRotationTolerance = Math.Abs(drawableSpinner.RotationTracker.Rotation * 0.1f); });
AddAssert("is disc rotation not almost 0", () => drawableSpinner.RotationTracker.Rotation, () => Is.Not.EqualTo(0).Within(100));
AddAssert("is disc rotation absolute not almost 0", () => drawableSpinner.Result.TotalRotation, () => Is.Not.EqualTo(0).Within(100));

Expand Down Expand Up @@ -133,9 +130,11 @@ public void TestSpinnerNormalBonusRewinding()

AddAssert("player score matching expected bonus score", () =>
{
var scoreProcessor = ((ScoreExposedPlayer)Player).ScoreProcessor;

// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
long totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
long totalScore = scoreProcessor.TotalScore.Value * 2;
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult);
});

addSeekStep(0);
Expand Down
6 changes: 4 additions & 2 deletions osu.Game.Rulesets.Osu/Difficulty/OsuLegacyScoreSimulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;

namespace osu.Game.Rulesets.Osu.Difficulty
{
internal class OsuLegacyScoreSimulator : ILegacyScoreSimulator
{
private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor();

private int legacyBonusScore;
private int standardisedBonusScore;
private int combo;
Expand Down Expand Up @@ -171,7 +173,7 @@ private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attribut
if (isBonus)
{
legacyBonusScore += scoreIncrease;
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
}
else
attributes.AccuracyScore += scoreIncrease;
Expand Down
3 changes: 2 additions & 1 deletion osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
Expand Down Expand Up @@ -312,7 +313,7 @@ protected override void UpdateAfterChildren()
updateBonusScore();
}

private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult;
private static readonly int score_per_tick = new OsuScoreProcessor().GetBaseScoreForResult(new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxResult);

private void updateBonusScore()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring;

namespace osu.Game.Rulesets.Taiko.Difficulty
{
internal class TaikoLegacyScoreSimulator : ILegacyScoreSimulator
{
private readonly ScoreProcessor scoreProcessor = new TaikoScoreProcessor();

private int legacyBonusScore;
private int standardisedBonusScore;
private int combo;
Expand Down Expand Up @@ -191,7 +193,7 @@ private void simulateHit(HitObject hitObject, ref LegacyScoreAttributes attribut
if (isBonus)
{
legacyBonusScore += scoreIncrease;
standardisedBonusScore += Judgement.ToNumericResult(bonusResult);
standardisedBonusScore += scoreProcessor.GetBaseScoreForResult(bonusResult);
}
else
attributes.AccuracyScore += scoreIncrease;
Expand Down
8 changes: 4 additions & 4 deletions osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ protected override double ComputeTotalScore(double comboProgress, double accurac

protected override double GetComboScoreChange(JudgementResult result)
{
return GetNumericResultFor(result)
return GetBaseScoreForResult(result.Type)
* Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base))
* strongScaleValue(result);
}

protected override double GetNumericResultFor(JudgementResult result)
public override int GetBaseScoreForResult(HitResult result)
{
switch (result.Type)
switch (result)
{
case HitResult.Ok:
return 150;
}

return base.GetNumericResultFor(result);
return base.GetBaseScoreForResult(result);
}

private double strongScaleValue(JudgementResult result)
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void TestOnlyBonusScore()
// Apply a judgement
scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus });

Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE));
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(scoreProcessor.GetBaseScoreForResult(HitResult.LargeBonus)));
}

[Test]
Expand Down
5 changes: 1 addition & 4 deletions osu.Game/BackgroundDataStoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,15 +340,12 @@ private void convertLegacyTotalScoreToStandardised()

try
{
var score = scoreManager.Query(s => s.ID == id);
long newTotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(score, beatmapManager);

// Can't use async overload because we're not on the update thread.
// ReSharper disable once MethodHasAsyncOverload
realmAccess.Write(r =>
{
ScoreInfo s = r.Find<ScoreInfo>(id)!;
s.TotalScore = newTotalScore;
StandardisedScoreMigrationTools.UpdateFromLegacy(s, beatmapManager);
s.TotalScoreVersion = LegacyScoreEncoder.LATEST_VERSION;
});

Expand Down
88 changes: 80 additions & 8 deletions osu.Game/Database/StandardisedScoreMigrationTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ public static long GetNewStandardised(ScoreInfo score)
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
List<HitResult> sortedHits = score.Statistics
.Where(kvp => kvp.Key.AffectsCombo())
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
.OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key))
.SelectMany(kvp => Enumerable.Repeat(kvp.Key, kvp.Value))
.ToList();

// Attempt to use maximum statistics from the database.
var maximumJudgements = score.MaximumStatistics
.Where(kvp => kvp.Key.AffectsCombo())
.OrderByDescending(kvp => Judgement.ToNumericResult(kvp.Key))
.OrderByDescending(kvp => processor.GetBaseScoreForResult(kvp.Key))
.SelectMany(kvp => Enumerable.Repeat(new FakeJudgement(kvp.Key), kvp.Value))
.ToList();

Expand Down Expand Up @@ -169,10 +169,10 @@ private static HitResult getMaxJudgementFor(HitResult hitResult, HitResult max)
public static long GetOldStandardised(ScoreInfo score)
{
double accuracyScore =
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value)
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
(double)score.Statistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value)
/ score.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value);
double comboScore = (double)score.MaxCombo / score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Sum(kvp => kvp.Value);
double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value);
double bonusScore = score.Statistics.Where(kvp => kvp.Key.IsBonus()).Sum(kvp => numericScoreFor(kvp.Key) * kvp.Value);

double accuracyPortion = 0.3;

Expand All @@ -193,6 +193,65 @@ public static long GetOldStandardised(ScoreInfo score)
modMultiplier *= mod.ScoreMultiplier;

return (long)Math.Round((1000000 * (accuracyPortion * accuracyScore + (1 - accuracyPortion) * comboScore) + bonusScore) * modMultiplier);

static int numericScoreFor(HitResult result)
{
switch (result)
{
default:
return 0;

case HitResult.SmallTickHit:
return 10;

case HitResult.LargeTickHit:
return 30;

case HitResult.Meh:
return 50;

case HitResult.Ok:
return 100;

case HitResult.Good:
return 200;

case HitResult.Great:
return 300;

case HitResult.Perfect:
return 315;

case HitResult.SmallBonus:
return 10;

case HitResult.LargeBonus:
return 50;
}
}
}

/// <summary>
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
/// </summary>
/// <param name="score">The score to update.</param>
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
public static void UpdateFromLegacy(ScoreInfo score, BeatmapManager beatmaps)
{
score.TotalScore = convertFromLegacyTotalScore(score, beatmaps);
score.Accuracy = ComputeAccuracy(score);
}

/// <summary>
/// Updates a legacy <see cref="ScoreInfo"/> to standardised scoring.
/// </summary>
/// <param name="score">The score to update.</param>
/// <param name="difficulty">The beatmap difficulty.</param>
/// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
public static void UpdateFromLegacy(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
score.TotalScore = convertFromLegacyTotalScore(score, difficulty, attributes);
score.Accuracy = ComputeAccuracy(score);
}

/// <summary>
Expand All @@ -201,7 +260,7 @@ public static long GetOldStandardised(ScoreInfo score)
/// <param name="score">The score to convert the total score of.</param>
/// <param name="beatmaps">A <see cref="BeatmapManager"/> used for <see cref="WorkingBeatmap"/> lookups.</param>
/// <returns>The standardised total score.</returns>
public static long ConvertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
private static long convertFromLegacyTotalScore(ScoreInfo score, BeatmapManager beatmaps)
{
if (!score.IsLegacyScore)
return score.TotalScore;
Expand All @@ -224,7 +283,7 @@ public static long ConvertFromLegacyTotalScore(ScoreInfo score, BeatmapManager b
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);

return ConvertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
return convertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
}

/// <summary>
Expand All @@ -234,7 +293,7 @@ public static long ConvertFromLegacyTotalScore(ScoreInfo score, BeatmapManager b
/// <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>
public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
private static long convertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{
if (!score.IsLegacyScore)
return score.TotalScore;
Expand Down Expand Up @@ -386,6 +445,19 @@ double lowerEstimateOfComboPortionInStandardisedScore
}
}

public static double ComputeAccuracy(ScoreInfo scoreInfo)
{
Ruleset ruleset = scoreInfo.Ruleset.CreateInstance();
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();

int baseScore = scoreInfo.Statistics.Where(kvp => kvp.Key.AffectsAccuracy())
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));
int maxBaseScore = scoreInfo.MaximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy())
.Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key));

return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore;
}

/// <summary>
/// Used to populate the <paramref name="score"/> model using data parsed from its corresponding replay file.
/// </summary>
Expand Down
Loading