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);