diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index cb3338126c..4c8d0b2ce6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public class OsuDifficultyCalculator : DifficultyCalculator
{
private const double difficulty_multiplier = 0.0675;
+ private double hitWindowGreat;
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
@@ -52,11 +53,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
- HitWindows hitWindows = new OsuHitWindows();
- hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
-
- // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
@@ -96,12 +92,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
}
}
- protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
{
- new Aim(mods),
- new Speed(mods),
- new Flashlight(mods)
- };
+ HitWindows hitWindows = new OsuHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
+ // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
+ hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
+
+ return new Skill[]
+ {
+ new Aim(mods),
+ new Speed(mods, hitWindowGreat),
+ new Flashlight(mods)
+ };
+ }
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index fa6c5c4d9c..8e8f9bc06e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
+ ///
+ /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms to account for simultaneous s.
+ ///
+ public double StrainTime { get; private set; }
+
///
/// Normalized distance from the end position of the previous to the start position of this .
///
@@ -32,11 +37,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
///
public double? Angle { get; private set; }
- ///
- /// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms.
- ///
- public readonly double StrainTime;
-
private readonly OsuHitObject lastLastObject;
private readonly OsuHitObject lastObject;
@@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
setDistances();
- // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
- StrainTime = Math.Max(50, DeltaTime);
+ // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects.
+ StrainTime = Math.Max(DeltaTime, 25);
}
private void setDistances()
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index f0eb199e5f..9364b11048 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -6,6 +6,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Framework.Utils;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
@@ -26,12 +27,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double DifficultyMultiplier => 1.04;
private const double min_speed_bonus = 75; // ~200BPM
- private const double max_speed_bonus = 45; // ~330BPM
private const double speed_balancing_factor = 40;
- public Speed(Mod[] mods)
+ private readonly double greatWindow;
+
+ public Speed(Mod[] mods, double hitWindowGreat)
: base(mods)
{
+ greatWindow = hitWindowGreat;
}
protected override double StrainValueOf(DifficultyHitObject current)
@@ -40,13 +43,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
return 0;
var osuCurrent = (OsuDifficultyHitObject)current;
+ var osuPrevious = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null;
double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance);
- double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime);
+ double strainTime = osuCurrent.StrainTime;
+
+ double greatWindowFull = greatWindow * 2;
+ double speedWindowRatio = strainTime / greatWindowFull;
+
+ // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between)
+ if (osuPrevious != null && strainTime < greatWindowFull && osuPrevious.StrainTime > strainTime)
+ strainTime = Interpolation.Lerp(osuPrevious.StrainTime, strainTime, speedWindowRatio);
+
+ // Cap deltatime to the OD 300 hitwindow.
+ // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
+ strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1);
double speedBonus = 1.0;
- if (deltaTime < min_speed_bonus)
- speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2);
+ if (strainTime < min_speed_bonus)
+ speedBonus = 1 + Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
double angleBonus = 1.0;
@@ -64,7 +79,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
}
}
- return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime;
+ return (1 + (speedBonus - 1) * 0.75)
+ * angleBonus
+ * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5))
+ / strainTime;
}
}
}