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

Add difficulty attributes to facilitate conversion from legacy score, and convert existing scores #24072

Merged
merged 44 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
02111e3
Implement ScoreV1 calculation for OsuRuleset
smoogipoo Jun 1, 2023
e402c6d
Write max combo attribute from base class
smoogipoo Jun 2, 2023
77c745c
"TotalScoreV1" -> "LegacyTotalScore"
smoogipoo Jun 6, 2023
d10c63e
Fix difficulty calculation when mods are involved
smoogipoo Jun 8, 2023
446807e
Add combo score / bonus score attributes
smoogipoo Jun 12, 2023
b9f485b
Merge classes + split out
smoogipoo Jun 12, 2023
aa64483
Add ScoreV1 calculation for TaikoRuleset
smoogipoo Jun 12, 2023
3ec9712
Add ScoreV1 calculation for CatchRuleset
smoogipoo Jun 13, 2023
13d1f9c
Add ScoreV1 calculation for ManiaRuleset
smoogipoo Jun 13, 2023
0844a21
Merge branch 'master' into diffcalc-total-scorev1
smoogipoo Jun 15, 2023
975e9ba
Fix exception with no matching mods
smoogipoo Jun 15, 2023
bfa449e
Adjust attribute data
smoogipoo Jun 19, 2023
87447f4
Fix incorrect calculation of difficulty
smoogipoo Jun 23, 2023
0656587
Add flag to disable computing legacy scoring values
smoogipoo Jun 23, 2023
5fadadc
Merge branch 'master' into diffcalc-total-scorev1
smoogipoo Jun 23, 2023
e1d723a
Merge branch 'master' into diffcalc-total-scorev1
smoogipoo Jun 26, 2023
8e79510
Add migration for total score conversion
smoogipoo Jun 26, 2023
a9c65d2
Initial conversion of scores
smoogipoo Jun 26, 2023
0c5c095
Store old total score as LegacyTotalScore
smoogipoo Jun 27, 2023
5f350aa
Fix float division
smoogipoo Jun 27, 2023
6e2369e
Add xmldoc on LegacyTotalScore
smoogipoo Jun 27, 2023
e291dff
Fix imported scores not getting LegacyTotalScore
smoogipoo Jun 28, 2023
09bc8e4
Refactoring
smoogipoo Jun 28, 2023
af25ffb
Remove JSON output
smoogipoo Jun 28, 2023
1ca4e39
Allow legacy scores to be displayed in "classic" scoring mode
smoogipoo Jun 28, 2023
829044d
Revert unintented change
smoogipoo Jun 29, 2023
c816281
Make BackgroundBeatmapProcessor task long-running
smoogipoo Jun 29, 2023
ddd870e
Make LegacyTotalScore nullable
smoogipoo Jun 29, 2023
6822871
Move population of LegacyTotalScore to ScoreImporter
smoogipoo Jun 29, 2023
c6ad184
Move Ruleset method to ILegacyRuleset interface
smoogipoo Jun 29, 2023
426f11b
Apply a few other code reviews
smoogipoo Jun 29, 2023
4203e21
Merge branch 'master' into diffcalc-total-scorev1
peppy Jul 4, 2023
6765083
Remove unnecessary null check
peppy Jul 4, 2023
1a6381b
Reduce code repetition for sleep logic
peppy Jul 4, 2023
3b5f3b6
Tidy up and improve messaging on completion notification
peppy Jul 4, 2023
1629024
`ILegacyScoreProcessor` -> `ILegacyScoreSimulator`
peppy Jul 4, 2023
a0c3fa9
Move preconditions to realm migration step to simplify marker version…
peppy Jul 4, 2023
4de15f9
Fix realm silly business
peppy Jul 4, 2023
257a96e
Fix background beatmap processor thread not correctly exiting
peppy Jul 4, 2023
56bfb92
Allow user cancellation
peppy Jul 4, 2023
d3eb065
Improve messaging around failed scores
peppy Jul 4, 2023
dd99981
Count missing beatmaps as errored items
peppy Jul 4, 2023
aee89e5
Rewrite comment regarding `LegacyTotalScore`
peppy Jul 4, 2023
f2aa80f
Rename and adjust xmldoc on `TotalScoreVersion`
peppy Jul 4, 2023
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
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Catch/CatchRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ public override LocalisableString GetDisplayNameForHitResult(HitResult result)

public int LegacyID => 2;

public ILegacyScoreSimulator CreateLegacyScoreSimulator() => new CatchLegacyScoreSimulator();

public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();

public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public class CatchDifficultyAttributes : DifficultyAttributes
// Todo: osu!catch should not output star rating in the 'aim' attribute.
yield return (ATTRIB_ID_AIM, StarRating);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attribute hasn't been removed, just moved from each ruleset down to the base DifficultyAttributes class.

The reasoning for why it was this way is that, historically, some rulesets (e.g. mania) didn't store max combo. This has changed over the years and we now store max combo for all rulesets, but this code hadn't been adjusted in line with that.

}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
Expand All @@ -36,7 +35,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val

StarRating = values[ATTRIB_ID_AIM];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
}
}
}
16 changes: 15 additions & 1 deletion osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ public class CatchDifficultyCalculator : DifficultyCalculator

public override int Version => 20220701;

private readonly IWorkingBeatmap workingBeatmap;

public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
workingBeatmap = beatmap;
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
Expand All @@ -38,13 +41,24 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;

return new CatchDifficultyAttributes
CatchDifficultyAttributes attributes = new CatchDifficultyAttributes
{
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
};

if (ComputeLegacyScoringValues)
{
CatchLegacyScoreSimulator sv1Simulator = new CatchLegacyScoreSimulator();
sv1Simulator.Simulate(workingBeatmap, beatmap, mods);
attributes.LegacyAccuracyScore = sv1Simulator.AccuracyScore;
attributes.LegacyComboScore = sv1Simulator.ComboScore;
attributes.LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio;
}

return attributes;
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
Expand Down
142 changes: 142 additions & 0 deletions osu.Game.Rulesets.Catch/Difficulty/CatchLegacyScoreSimulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
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;

namespace osu.Game.Rulesets.Catch.Difficulty
{
internal class CatchLegacyScoreSimulator : ILegacyScoreSimulator
{
public int AccuracyScore { get; private set; }

public int ComboScore { get; private set; }

public double BonusScoreRatio => legacyBonusScore == 0 ? 0 : (double)modernBonusScore / legacyBonusScore;

private int legacyBonusScore;
private int modernBonusScore;
private int combo;

private double scoreMultiplier;

public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
{
IBeatmap baseBeatmap = workingBeatmap.Beatmap;

int countNormal = 0;
int countSlider = 0;
int countSpinner = 0;

foreach (HitObject obj in baseBeatmap.HitObjects)
{
switch (obj)
{
case IHasPath:
countSlider++;
break;

case IHasDuration:
countSpinner++;
break;

default:
countNormal++;
break;
}
}

int objectCount = countNormal + countSlider + countSpinner;

int drainLength = 0;

if (baseBeatmap.HitObjects.Count > 0)
{
int breakLength = baseBeatmap.Breaks.Select(b => (int)Math.Round(b.EndTime) - (int)Math.Round(b.StartTime)).Sum();
drainLength = ((int)Math.Round(baseBeatmap.HitObjects[^1].StartTime) - (int)Math.Round(baseBeatmap.HitObjects[0].StartTime) - breakLength) / 1000;
}

int difficultyPeppyStars = (int)Math.Round(
(baseBeatmap.Difficulty.DrainRate
+ baseBeatmap.Difficulty.OverallDifficulty
+ baseBeatmap.Difficulty.CircleSize
+ Math.Clamp((float)objectCount / drainLength * 8, 0, 16)) / 38 * 5);

scoreMultiplier = difficultyPeppyStars * mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);

foreach (var obj in playableBeatmap.HitObjects)
simulateHit(obj);
}

private void simulateHit(HitObject hitObject)
{
bool increaseCombo = true;
bool addScoreComboMultiplier = false;

bool isBonus = false;
HitResult bonusResult = HitResult.None;

int scoreIncrease = 0;

switch (hitObject)
{
case TinyDroplet:
scoreIncrease = 10;
increaseCombo = false;
break;

case Droplet:
scoreIncrease = 100;
break;

case Fruit:
scoreIncrease = 300;
addScoreComboMultiplier = true;
increaseCombo = true;
break;

case Banana:
scoreIncrease = 1100;
increaseCombo = false;
isBonus = true;
bonusResult = HitResult.LargeBonus;
break;

case JuiceStream:
foreach (var nested in hitObject.NestedHitObjects)
simulateHit(nested);
return;

case BananaShower:
foreach (var nested in hitObject.NestedHitObjects)
simulateHit(nested);
return;
}

if (addScoreComboMultiplier)
{
// ReSharper disable once PossibleLossOfFraction (intentional to match osu-stable...)
ComboScore += (int)(Math.Max(0, combo - 1) * (scoreIncrease / 25 * scoreMultiplier));
}

if (isBonus)
{
legacyBonusScore += scoreIncrease;
modernBonusScore += Judgement.ToNumericResult(bonusResult);
}
else
AccuracyScore += scoreIncrease;

if (increaseCombo)
combo++;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public class ManiaDifficultyAttributes : DifficultyAttributes
foreach (var v in base.ToDatabaseAttributes())
yield return v;

yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}
Expand All @@ -33,7 +32,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val
{
base.FromDatabaseAttributes(values, onlineInfo);

MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
}
Expand Down
19 changes: 17 additions & 2 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ public class ManiaDifficultyCalculator : DifficultyCalculator

public override int Version => 20220902;

private readonly IWorkingBeatmap workingBeatmap;

public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
workingBeatmap = beatmap;

isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
originalOverallDifficulty = beatmap.BeatmapInfo.Difficulty.OverallDifficulty;
}
Expand All @@ -46,15 +50,26 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
HitWindows hitWindows = new ManiaHitWindows();
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);

return new ManiaDifficultyAttributes
ManiaDifficultyAttributes attributes = new ManiaDifficultyAttributes
{
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
Mods = mods,
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject),
};

if (ComputeLegacyScoringValues)
{
ManiaLegacyScoreSimulator sv1Simulator = new ManiaLegacyScoreSimulator();
sv1Simulator.Simulate(workingBeatmap, beatmap, mods);
attributes.LegacyAccuracyScore = sv1Simulator.AccuracyScore;
attributes.LegacyComboScore = sv1Simulator.ComboScore;
attributes.LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio;
}

return attributes;
}

private static int maxComboForObject(HitObject hitObject)
Expand Down
28 changes: 28 additions & 0 deletions osu.Game.Rulesets.Mania/Difficulty/ManiaLegacyScoreSimulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;

namespace osu.Game.Rulesets.Mania.Difficulty
{
internal class ManiaLegacyScoreSimulator : ILegacyScoreSimulator
{
public int AccuracyScore => 0;
public int ComboScore { get; private set; }
public double BonusScoreRatio => 0;

public void Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap, IReadOnlyList<Mod> mods)
{
double multiplier = mods.Where(m => m is not (ModHidden or ModHardRock or ModDoubleTime or ModFlashlight or ManiaModFadeIn))
.Select(m => m.ScoreMultiplier)
.Aggregate(1.0, (c, n) => c * n);

ComboScore = (int)(1000000 * multiplier);
}
}
}
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Mania/ManiaRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ public override IEnumerable<Mod> GetModsFor(ModType type)

public int LegacyID => 3;

public ILegacyScoreSimulator CreateLegacyScoreSimulator() => new ManiaLegacyScoreSimulator();

public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();

public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new ManiaRulesetConfigManager(settings, RulesetInfo);
Expand Down
2 changes: 0 additions & 2 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ public class OsuDifficultyAttributes : DifficultyAttributes
yield return (ATTRIB_ID_SPEED, SpeedDifficulty);
yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
yield return (ATTRIB_ID_DIFFICULTY, StarRating);

if (ShouldSerializeFlashlightRating())
Expand All @@ -111,7 +110,6 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val
SpeedDifficulty = values[ATTRIB_ID_SPEED];
OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_DIFFICULTY];
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
Expand Down
20 changes: 18 additions & 2 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ public class OsuDifficultyCalculator : DifficultyCalculator

public override int Version => 20220902;

private readonly IWorkingBeatmap workingBeatmap;

public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
workingBeatmap = beatmap;
}

protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
Expand Down Expand Up @@ -71,7 +74,9 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
);

double starRating = basePerformance > 0.00001 ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
double starRating = basePerformance > 0.00001
? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4)
: 0;

double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
double drainRate = beatmap.Difficulty.DrainRate;
Expand All @@ -86,7 +91,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat

double hitWindowGreat = hitWindows.WindowFor(HitResult.Great) / clockRate;

return new OsuDifficultyAttributes
OsuDifficultyAttributes attributes = new OsuDifficultyAttributes
{
StarRating = starRating,
Mods = mods,
Expand All @@ -103,6 +108,17 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
SliderCount = sliderCount,
SpinnerCount = spinnerCount,
};

if (ComputeLegacyScoringValues)
{
OsuLegacyScoreSimulator sv1Simulator = new OsuLegacyScoreSimulator();
sv1Simulator.Simulate(workingBeatmap, beatmap, mods);
attributes.LegacyAccuracyScore = sv1Simulator.AccuracyScore;
attributes.LegacyComboScore = sv1Simulator.ComboScore;
attributes.LegacyBonusScoreRatio = sv1Simulator.BonusScoreRatio;
}

return attributes;
}

protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
Expand Down
Loading