Skip to content

Commit d539c4b

Browse files
authored
Merge pull request #34227 from bdach/display-weird-numbers-for-mania-so-that-people-stop-complaining
Attempt to properly quantify the impact of mania Hard Rock / Easy mod application on overall difficulty
2 parents 2a15ffd + 131f828 commit d539c4b

File tree

16 files changed

+68
-41
lines changed

16 files changed

+68
-41
lines changed

osu.Game.Rulesets.Catch.Tests/CatchRateAdjustedDisplayDifficultyTest.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using NUnit.Framework;
66
using osu.Game.Beatmaps;
7+
using osu.Game.Rulesets.Catch.Mods;
78

89
namespace osu.Game.Rulesets.Catch.Tests
910
{
@@ -22,7 +23,7 @@ public void TestApproachRateIsUnchangedWithRateEqualToOne(float originalApproach
2223
var ruleset = new CatchRuleset();
2324
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
2425

25-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
26+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
2627

2728
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
2829
}
@@ -33,7 +34,7 @@ public void TestRateBelowOne()
3334
var ruleset = new CatchRuleset();
3435
var difficulty = new BeatmapDifficulty();
3536

36-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
37+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new CatchModHalfTime()]);
3738

3839
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
3940
}
@@ -44,7 +45,7 @@ public void TestRateAboveOne()
4445
var ruleset = new CatchRuleset();
4546
var difficulty = new BeatmapDifficulty();
4647

47-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
48+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new CatchModDoubleTime()]);
4849

4950
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
5051
}

osu.Game.Rulesets.Catch/CatchRuleset.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
using osu.Game.Screens.Edit.Setup;
3434
using osu.Game.Screens.Ranking.Statistics;
3535
using osu.Game.Skinning;
36+
using osu.Game.Utils;
3637
using osuTK;
3738

3839
namespace osu.Game.Rulesets.Catch
@@ -265,9 +266,10 @@ public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatm
265266
}
266267

267268
/// <seealso cref="CatchHitObject.ApplyDefaultsToSelf"/>
268-
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
269+
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
269270
{
270271
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
272+
double rate = ModUtils.CalculateRateWithMods(mods);
271273

272274
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN);
273275
preempt /= rate;

osu.Game.Rulesets.Mania/ManiaRuleset.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,32 @@ public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatm
414414
}), true)
415415
};
416416

417+
/// <seealso cref="ManiaHitWindows"/>
418+
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
419+
{
420+
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
421+
422+
// notably, in mania, hit windows are designed to be independent of track playback rate (see `ManiaHitWindows.SpeedMultiplier`).
423+
// *however*, to not make matters *too* simple, mania Hard Rock and Easy differ from all other rulesets
424+
// in that they apply multipliers *to hit window durations directly* rather than to the Overall Difficulty attribute itself.
425+
// because the duration of hit window durations as a function of OD is not a linear function,
426+
// this means that multiplying the OD is *not* the same thing as multiplying the hit window duration.
427+
// in fact, the second operation is *much* harsher and will produce values much farther outside of normal operating range
428+
// (even negative in the case of Easy).
429+
// stable handles this wrong on song select and just assumes that it can handle mania EZ / HR the same way as all other rulesets.
430+
431+
double perfectHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, ManiaHitWindows.PERFECT_WINDOW_RANGE);
432+
433+
if (mods.Any(m => m is ManiaModHardRock))
434+
perfectHitWindow /= ManiaModHardRock.HIT_WINDOW_DIFFICULTY_MULTIPLIER;
435+
else if (mods.Any(m => m is ManiaModEasy))
436+
perfectHitWindow /= ManiaModEasy.HIT_WINDOW_DIFFICULTY_MULTIPLIER;
437+
438+
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(perfectHitWindow, ManiaHitWindows.PERFECT_WINDOW_RANGE);
439+
440+
return adjustedDifficulty;
441+
}
442+
417443
public override IRulesetFilterCriteria CreateRulesetFilterCriteria()
418444
{
419445
return new ManiaFilterCriteria();

osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ public class ManiaModEasy : ModEasyWithExtraLives, IApplicableToHitObject
1313
{
1414
public override LocalisableString Description => @"More forgiving HP drain, less accuracy required, and extra lives!";
1515

16+
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1 / 1.4;
17+
1618
void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
1719
{
18-
const double multiplier = 1 / 1.4;
19-
2020
switch (hitObject)
2121
{
2222
case Note:
23-
((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = multiplier;
23+
((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
2424
break;
2525

2626
case HoldNote hold:
27-
((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = multiplier;
28-
((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = multiplier;
27+
((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
28+
((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
2929
break;
3030
}
3131
}

osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
1313
public override double ScoreMultiplier => 1;
1414
public override bool Ranked => false;
1515

16+
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1.4;
17+
1618
void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
1719
{
18-
const double multiplier = 1.4;
19-
2020
switch (hitObject)
2121
{
2222
case Note:
23-
((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = multiplier;
23+
((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
2424
break;
2525

2626
case HoldNote hold:
27-
((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = multiplier;
28-
((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = multiplier;
27+
((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
28+
((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
2929
break;
3030
}
3131
}

osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
99
{
1010
public class ManiaHitWindows : HitWindows
1111
{
12-
private static readonly DifficultyRange perfect_window_range = new DifficultyRange(22.4D, 19.4D, 13.9D);
12+
public static readonly DifficultyRange PERFECT_WINDOW_RANGE = new DifficultyRange(22.4D, 19.4D, 13.9D);
1313
private static readonly DifficultyRange great_window_range = new DifficultyRange(64, 49, 34);
1414
private static readonly DifficultyRange good_window_range = new DifficultyRange(97, 82, 67);
1515
private static readonly DifficultyRange ok_window_range = new DifficultyRange(127, 112, 97);
@@ -151,7 +151,7 @@ private void updateWindows()
151151
}
152152
else
153153
{
154-
perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, perfect_window_range) * totalMultiplier) + 0.5;
154+
perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, PERFECT_WINDOW_RANGE) * totalMultiplier) + 0.5;
155155
great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, great_window_range) * totalMultiplier) + 0.5;
156156
good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, good_window_range) * totalMultiplier) + 0.5;
157157
ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, ok_window_range) * totalMultiplier) + 0.5;

osu.Game.Rulesets.Osu.Tests/OsuRateAdjustedDisplayDifficultyTest.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using NUnit.Framework;
66
using osu.Game.Beatmaps;
7+
using osu.Game.Rulesets.Osu.Mods;
78

89
namespace osu.Game.Rulesets.Osu.Tests
910
{
@@ -22,7 +23,7 @@ public void TestApproachRateIsUnchangedWithRateEqualToOne(float originalApproach
2223
var ruleset = new OsuRuleset();
2324
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
2425

25-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
26+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
2627

2728
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
2829
}
@@ -33,7 +34,7 @@ public void TestOverallDifficultyIsUnchangedWithRateEqualToOne(float originalOve
3334
var ruleset = new OsuRuleset();
3435
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
3536

36-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
37+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
3738

3839
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
3940
}
@@ -44,7 +45,7 @@ public void TestRateBelowOne()
4445
var ruleset = new OsuRuleset();
4546
var difficulty = new BeatmapDifficulty();
4647

47-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
48+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new OsuModHalfTime()]);
4849

4950
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
5051
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(2.22).Within(0.01));
@@ -56,7 +57,7 @@ public void TestRateAboveOne()
5657
var ruleset = new OsuRuleset();
5758
var difficulty = new BeatmapDifficulty();
5859

59-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
60+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new OsuModDoubleTime()]);
6061

6162
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
6263
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(7.77).Within(0.01));

osu.Game.Rulesets.Osu/OsuRuleset.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
using osu.Game.Screens.Edit.Setup;
4141
using osu.Game.Screens.Ranking.Statistics;
4242
using osu.Game.Skinning;
43+
using osu.Game.Utils;
4344
using osuTK;
4445

4546
namespace osu.Game.Rulesets.Osu
@@ -365,9 +366,10 @@ public override IEnumerable<Drawable> CreateEditorSetupSections() =>
365366

366367
/// <seealso cref="OsuHitObject.ApplyDefaultsToSelf"/>
367368
/// <seealso cref="OsuHitWindows"/>
368-
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
369+
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
369370
{
370371
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
372+
double rate = ModUtils.CalculateRateWithMods(mods);
371373

372374
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN);
373375
preempt /= rate;

osu.Game.Rulesets.Taiko.Tests/TaikoRateAdjustedDisplayDifficultyTest.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using NUnit.Framework;
66
using osu.Game.Beatmaps;
7+
using osu.Game.Rulesets.Taiko.Mods;
78

89
namespace osu.Game.Rulesets.Taiko.Tests
910
{
@@ -22,7 +23,7 @@ public void TestOverallDifficultyIsUnchangedWithRateEqualToOne(float originalOve
2223
var ruleset = new TaikoRuleset();
2324
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
2425

25-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
26+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
2627

2728
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
2829
}
@@ -33,7 +34,7 @@ public void TestRateBelowOne()
3334
var ruleset = new TaikoRuleset();
3435
var difficulty = new BeatmapDifficulty();
3536

36-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
37+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new TaikoModHalfTime()]);
3738

3839
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(1.11).Within(0.01));
3940
}
@@ -44,7 +45,7 @@ public void TestRateAboveOne()
4445
var ruleset = new TaikoRuleset();
4546
var difficulty = new BeatmapDifficulty();
4647

47-
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
48+
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new TaikoModDoubleTime()]);
4849

4950
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(8.89).Within(0.01));
5051
}

osu.Game.Rulesets.Taiko/TaikoRuleset.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
using osu.Game.Rulesets.Taiko.Edit.Setup;
3939
using osu.Game.Rulesets.Taiko.Skinning.Default;
4040
using osu.Game.Screens.Edit.Setup;
41+
using osu.Game.Utils;
4142

4243
namespace osu.Game.Rulesets.Taiko
4344
{
@@ -270,9 +271,10 @@ public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatm
270271
}
271272

272273
/// <seealso cref="TaikoHitWindows"/>
273-
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
274+
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
274275
{
275276
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
277+
double rate = ModUtils.CalculateRateWithMods(mods);
276278

277279
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, TaikoHitWindows.GREAT_WINDOW_RANGE);
278280
greatHitWindow /= rate;

0 commit comments

Comments
 (0)