diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 5ef670c739..ca88701c07 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -18,6 +18,16 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class Spinner : OsuHitObject, IHasDuration
{
+ ///
+ /// The RPM required to clear the spinner at ODs [ 0, 5, 10 ].
+ ///
+ private static readonly (int min, int mid, int max) clear_rpm_range = (90, 150, 225);
+
+ ///
+ /// The RPM required to complete the spinner and receive full score at ODs [ 0, 5, 10 ].
+ ///
+ private static readonly (int min, int mid, int max) complete_rpm_range = (250, 380, 430);
+
public double EndTime
{
get => StartTime + Duration;
@@ -52,13 +62,16 @@ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, I
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
- const double maximum_rotations_per_second = 477f / 60f;
+ // The average RPS required over the length of the spinner to clear the spinner.
+ double minRps = IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, clear_rpm_range) / 60;
+
+ // The RPS required over the length of the spinner to receive full score (all normal + bonus ticks).
+ double maxRps = IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, complete_rpm_range) / 60;
double secondsDuration = Duration / 1000;
- double minimumRotationsPerSecond = IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, 1.5, 2.5, 3.75);
- SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond);
- MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration) - bonus_spins_gap;
+ SpinsRequired = (int)(minRps * secondsDuration);
+ MaximumBonusSpins = (int)(maxRps * secondsDuration) - bonus_spins_gap;
}
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)