From aefe31d315ce7bd5dce11cf4df6fdb0a03ecc2bd Mon Sep 17 00:00:00 2001 From: Givy120 <89256026+Givikap120@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:23:22 +0200 Subject: [PATCH] Make sliderless aim in fact sliderless (#29993) Part of this PR - https://github.com/ppy/osu/pull/27303 Current aim calculation have a flaw of sliderless aim still accounting for sliders. This happens because of usage of `LazyJumpDistance` as a main distance metric. This PR is fixing this by adding `JumpDistance` as true sliderless metric, using it instead of `LazyJumpDistance`. This can introduce very rare cases where sliderless aim is worth more than normal aim (because of velocity change bonus). The effect of this is minimal on most of the maps. It can be seen the best on this map - https://osu.ppy.sh/beatmapsets/594751#osu/1257904 Before: ![image](https://github.com/user-attachments/assets/e96773e6-3274-4ed6-8293-ed9bdfa213d0) After: ![image](https://github.com/user-attachments/assets/126e861b-c28d-4ed4-a0db-f5305225a749) --------- Co-authored-by: James Wilson Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com> --- .../Difficulty/Evaluators/AimEvaluator.cs | 27 +++++++++++-------- .../Preprocessing/OsuDifficultyHitObject.cs | 8 +++++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index 4aa45f4529..24db5ef525 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -40,7 +40,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER; // Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle. - double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.AdjustedDeltaTime; + double currDistance = withSliderTravelDistance ? osuCurrObj.LazyJumpDistance : osuCurrObj.JumpDistance; + double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime; // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. if (osuLastObj.BaseObject is Slider && withSliderTravelDistance) @@ -52,7 +53,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } // As above, do the same for the previous hitobject. - double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.AdjustedDeltaTime; + double prevDistance = withSliderTravelDistance ? osuLastObj.LazyJumpDistance : osuLastObj.JumpDistance; + double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime; if (osuLastLastObj.BaseObject is Slider && withSliderTravelDistance) { @@ -88,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Apply acute angle bonus for BPM above 300 1/2 and distance more than one diameter acuteAngleBonus *= angleBonus * DifficultyCalculationUtils.Smootherstep(DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.AdjustedDeltaTime, 2), 300, 400) * - DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, diameter, diameter * 2); + DifficultyCalculationUtils.Smootherstep(currDistance, diameter, diameter * 2); } wideAngleBonus = calcWideAngleBonus(currAngle); @@ -97,16 +99,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators wideAngleBonus *= 1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3)); // Apply full wide angle bonus for distance more than one diameter - wideAngleBonus *= angleBonus * DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, 0, diameter); + wideAngleBonus *= angleBonus * DifficultyCalculationUtils.Smootherstep(currDistance, 0, diameter); // Apply wiggle bonus for jumps that are [radius, 3*diameter] in distance, with < 110 angle // https://www.desmos.com/calculator/dp0v0nvowc wiggleBonus = angleBonus - * DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, radius, diameter) - * Math.Pow(DifficultyCalculationUtils.ReverseLerp(osuCurrObj.LazyJumpDistance, diameter * 3, diameter), 1.8) + * DifficultyCalculationUtils.Smootherstep(currDistance, radius, diameter) + * Math.Pow(DifficultyCalculationUtils.ReverseLerp(currDistance, diameter * 3, diameter), 1.8) * DifficultyCalculationUtils.Smootherstep(currAngle, double.DegreesToRadians(110), double.DegreesToRadians(60)) - * DifficultyCalculationUtils.Smootherstep(osuLastObj.LazyJumpDistance, radius, diameter) - * Math.Pow(DifficultyCalculationUtils.ReverseLerp(osuLastObj.LazyJumpDistance, diameter * 3, diameter), 1.8) + * DifficultyCalculationUtils.Smootherstep(prevDistance, radius, diameter) + * Math.Pow(DifficultyCalculationUtils.ReverseLerp(prevDistance, diameter * 3, diameter), 1.8) * DifficultyCalculationUtils.Smootherstep(lastAngle, double.DegreesToRadians(110), double.DegreesToRadians(60)); if (osuLast2Obj != null) @@ -127,9 +129,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (Math.Max(prevVelocity, currVelocity) != 0) { - // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities. - prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.AdjustedDeltaTime; - currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.AdjustedDeltaTime; + if (withSliderTravelDistance) + { + // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities. + prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.AdjustedDeltaTime; + currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.AdjustedDeltaTime; + } // Scale with ratio of difference compared to 0.5 * max dist. double distRatio = DifficultyCalculationUtils.Smoothstep(Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity), 0, 1); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 00bb4908c6..727b07c4af 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -46,6 +46,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public readonly double ClockRate; + /// + /// Normalised distance from the start position of the previous to the start position of this . + /// + public double JumpDistance { get; private set; } + /// /// Normalised distance from the "lazy" end position of the previous to the start position of this . /// @@ -232,7 +237,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing Vector2 lastCursorPosition = lastDifficultyObject != null ? getEndCursorPosition(lastDifficultyObject) : LastObject.StackedPosition; - LazyJumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + JumpDistance = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor; + LazyJumpDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor; MinimumJumpTime = AdjustedDeltaTime; MinimumJumpDistance = LazyJumpDistance;