diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
index 906ccc1b46..1f37990196 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
private const int history_time_max = 5 * 1000; // 5 seconds
private const int history_objects_max = 32;
private const double rhythm_overall_multiplier = 1.0;
- private const double rhythm_ratio_multiplier = 17.0;
+ private const double rhythm_ratio_multiplier = 30.0;
///
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current .
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedAimEvaluator.cs
new file mode 100644
index 0000000000..c34fdb3236
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedAimEvaluator.cs
@@ -0,0 +1,50 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
+{
+ public static class SpeedAimEvaluator
+ {
+ private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers
+
+ ///
+ /// Evaluates the difficulty of aiming the current object, based on:
+ ///
+ /// - distance between the previous and current object
+ ///
+ ///
+ public static double EvaluateDifficultyOf(DifficultyHitObject current)
+ {
+ if (current.BaseObject is Spinner)
+ return 0;
+
+ var osuCurrObj = (OsuDifficultyHitObject)current;
+ var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
+
+ double travelDistance = osuPrevObj?.LazyTravelDistance ?? 0;
+ double distance = travelDistance + osuCurrObj.LazyJumpDistance;
+
+ // Cap distance at single_spacing_threshold
+ distance = Math.Min(distance, single_spacing_threshold);
+
+ // Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
+ double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.95);
+
+ // Apply reduced small circle bonus because flow aim difficulty on small circles doesn't scale as hard as jumps
+ distanceBonus *= Math.Sqrt(osuCurrObj.SmallCircleBonus);
+
+ double strain = distanceBonus * 1000 / osuCurrObj.AdjustedDeltaTime;
+
+ strain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime);
+
+ return strain;
+ }
+
+ private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.3, ms / 1000));
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
index a35d24d7a8..32bf36b0eb 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
@@ -2,40 +2,31 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
-using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Utils;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
-using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
{
public static class SpeedEvaluator
{
- private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers
private const double min_speed_bonus = 200; // 200 BPM 1/4th
private const double speed_balancing_factor = 40;
- private const double distance_multiplier = 0.8;
///
/// Evaluates the difficulty of tapping the current object, based on:
///
/// - time between pressing the previous and current object,
- /// - distance between those objects,
/// - and how easily they can be cheesed.
///
///
- public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnlyList mods)
+ public static double EvaluateDifficultyOf(DifficultyHitObject current)
{
if (current.BaseObject is Spinner)
return 0;
- // derive strainTime for calculation
var osuCurrObj = (OsuDifficultyHitObject)current;
- var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
double strainTime = osuCurrObj.AdjustedDeltaTime;
double doubletapness = 1.0 - osuCurrObj.GetDoubletapness((OsuDifficultyHitObject?)osuCurrObj.Next(0));
@@ -51,23 +42,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
if (DifficultyCalculationUtils.MillisecondsToBPM(strainTime) > min_speed_bonus)
speedBonus = 0.75 * Math.Pow((DifficultyCalculationUtils.BPMToMilliseconds(min_speed_bonus) - strainTime) / speed_balancing_factor, 2);
- double travelDistance = osuPrevObj?.LazyTravelDistance ?? 0;
- double distance = travelDistance + osuCurrObj.LazyJumpDistance;
-
- // Cap distance at single_spacing_threshold
- distance = Math.Min(distance, single_spacing_threshold);
-
- // Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
- double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.95) * distance_multiplier;
-
- // Apply reduced small circle bonus because flow aim difficulty on small circles doesn't scale as hard as jumps
- distanceBonus *= Math.Sqrt(osuCurrObj.SmallCircleBonus);
-
- if (mods.OfType().Any())
- distanceBonus = 0;
-
// Base difficulty with all bonuses
- double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime;
+ double difficulty = (1 + speedBonus) * 1000 / strainTime;
difficulty *= highBpmBonus(osuCurrObj.AdjustedDeltaTime);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 87982dfaaa..95bbbd9310 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimValue = OsuStrainSkill.DifficultyToPerformance(aimDifficulty);
- double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
+ double lengthBonus = 0.95 + 0.3 * Math.Min(1.0, totalHits / 2000.0) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
aimValue *= lengthBonus;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 11b84f2d34..6a131511ef 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Difficulty.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
@@ -26,28 +27,41 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
IncludeSliders = includeSliders;
}
- private double currentStrain;
+ private double currentAimStrain;
+ private double currentSpeedStrain;
- private double skillMultiplier => 26.6;
- private double strainDecayBase => 0.15;
+ private double skillMultiplierAim => 26.0;
+ private double skillMultiplierSpeed => 1.3;
+ private double skillMultiplierTotal => 1.02;
+ private double meanExponent => 1.2;
private readonly List sliderStrains = new List();
- private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
+ private double strainDecayAim(double ms) => Math.Pow(0.15, ms / 1000);
+ private double strainDecaySpeed(double ms) => Math.Pow(0.3, ms / 1000);
- protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
+ protected override double CalculateInitialStrain(double time, DifficultyHitObject current) =>
+ DifficultyCalculationUtils.Norm(meanExponent,
+ currentAimStrain * strainDecayAim(time - current.Previous(0).StartTime),
+ currentSpeedStrain * strainDecaySpeed(time - current.Previous(0).StartTime));
protected override double StrainValueAt(DifficultyHitObject current)
{
- double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
+ double decayAim = strainDecayAim(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
+ double decaySpeed = strainDecaySpeed(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
- currentStrain *= decay;
- currentStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * (1 - decay) * skillMultiplier;
+ currentAimStrain *= decayAim;
+ currentAimStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * (1 - decayAim) * skillMultiplierAim;
+
+ currentSpeedStrain *= decaySpeed;
+ currentSpeedStrain += SpeedAimEvaluator.EvaluateDifficultyOf(current) * (1 - decaySpeed) * skillMultiplierSpeed;
+
+ double totalStrain = DifficultyCalculationUtils.Norm(meanExponent, currentAimStrain, currentSpeedStrain);
if (current.BaseObject is Slider)
- sliderStrains.Add(currentStrain);
+ sliderStrains.Add(totalStrain);
- return currentStrain;
+ return totalStrain * skillMultiplierTotal;
}
public double GetDifficultSliders()
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 8911b32f42..474091045d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public class Speed : HarmonicSkill
{
- private double skillMultiplier => 0.93;
+ private double skillMultiplier => 0.95;
private readonly List sliderStrains = new List();
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
currentDifficulty *= decay;
- currentDifficulty += SpeedEvaluator.EvaluateDifficultyOf(current, Mods) * (1 - decay) * skillMultiplier;
+ currentDifficulty += SpeedEvaluator.EvaluateDifficultyOf(current) * (1 - decay) * skillMultiplier;
double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);