diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SnapAimEvaluator.cs index 58bf9dc48e..15d8ef033e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SnapAimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SnapAimEvaluator.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; @@ -11,11 +12,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators { public static class SnapAimEvaluator { - private const double wide_angle_multiplier = 1.3; - private const double acute_angle_multiplier = 2.5; - private const double slider_multiplier = 1.9; - private const double velocity_change_multiplier = 1.0; + private const double wide_angle_multiplier = 1.05; + private const double acute_angle_multiplier = 2.41; + private const double slider_multiplier = 1.5; + private const double velocity_change_multiplier = 0.9; private const double wiggle_multiplier = 1.02; // WARNING: Increasing this multiplier beyond 1.02 reduces difficulty as distance increases. Refer to the desmos link above the wiggle bonus calculation + private const double maximum_repetition_nerf = 0.15; + private const double maximum_vector_influence = 0.5; /// /// Evaluates the difficulty of aiming the current object, based on: @@ -117,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (distance < 1) { - wideAngleBonus *= 1 - 0.35 * (1 - distance); + wideAngleBonus *= 1 - 0.55 * (1 - distance); } } } @@ -149,6 +152,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; } + // Penalize angle repetition. + aimStrain *= vectorAngleRepetition(osuCurrObj, osuLastObj); + aimStrain += wiggleBonus * wiggle_multiplier; aimStrain += velocityChangeBonus * velocity_change_multiplier; @@ -173,6 +179,49 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private static double highBpmBonus(double ms, double distance) => 1 / (1 - Math.Pow(0.03, Math.Pow(ms / 1000, 0.65))) * DifficultyCalculationUtils.Smootherstep(distance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS); + private static double vectorAngleRepetition(OsuDifficultyHitObject current, OsuDifficultyHitObject previous) + { + if (current.Angle == null || previous.Angle == null) + return 1; + + const double note_limit = 6; + + double constantAngleCount = 0; + + for (int index = 0; index < note_limit; index++) + { + var loopObj = (OsuDifficultyHitObject)current.Previous(index); + + if (loopObj.IsNull()) + break; + + // Only consider vectors in the same jump section, stopping to change rhythm ruins momentum + if (Math.Max(current.AdjustedDeltaTime, loopObj.AdjustedDeltaTime) > 1.1 * Math.Min(current.AdjustedDeltaTime, loopObj.AdjustedDeltaTime)) + break; + + if (loopObj.NormalisedVectorAngle.IsNotNull() && current.NormalisedVectorAngle.IsNotNull()) + { + double angleDifference = Math.Abs(current.NormalisedVectorAngle.Value - loopObj.NormalisedVectorAngle.Value); + // Refer to this desmos for tuning, constants need to be precise so that values stay within the range of 0 and 1. + // https://www.desmos.com/calculator/a8jesv5sv2 + constantAngleCount += Math.Cos(8 * Math.Min(double.DegreesToRadians(11.25), angleDifference)); + } + } + + double vectorRepetition = Math.Pow(Math.Min(0.5 / constantAngleCount, 1), 2); + + double stackFactor = DifficultyCalculationUtils.Smootherstep(current.LazyJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_DIAMETER); + + double currAngle = current.Angle.Value; + double lastAngle = previous.Angle.Value; + + double angleDifferenceAdjusted = Math.Cos(2 * Math.Min(double.DegreesToRadians(45), Math.Abs(currAngle - lastAngle) * stackFactor)); + + double baseNerf = 1 - maximum_repetition_nerf * CalcAcuteAngleBonus(lastAngle) * angleDifferenceAdjusted; + + return Math.Pow(baseNerf + (1 - baseNerf) * vectorRepetition * maximum_vector_influence * stackFactor, 2); + } + private static double calcWideAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(40), double.DegreesToRadians(140)); public static double CalcAcuteAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(140), double.DegreesToRadians(40)); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 848816503d..68950829f3 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -118,6 +118,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double? AngularVelocity { get; private set; } + /// + /// Angle of the vector created between current and current-1 + /// normalised to consider symmetrical vectors in any axis to be the same angle. + /// + public double? NormalisedVectorAngle { get; private set; } + /// /// Selective bonus for maps with higher circle size. /// @@ -261,6 +267,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double angle = calculateAngle(BaseObject.StackedPosition, lastCursorPosition, lastLastCursorPosition); double sliderAngle = calculateSliderAngle(lastDifficultyObject!, lastLastCursorPosition); + Vector2 v = BaseObject.StackedPosition - lastCursorPosition; + NormalisedVectorAngle = Math.Atan2(Math.Abs(v.Y), Math.Abs(v.X)); + Angle = Math.Min(angle, sliderAngle); if (lastLastDifficultyObject.Angle != null) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 101dd2250a..dec6785193 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double currentStrain; - private double skillMultiplierSnap => 65.2; - private double skillMultiplierAgility => 2.7; - private double skillMultiplierFlow => 262.0; - private double skillMultiplierTotal => 1.0; + private double skillMultiplierSnap => 71.0; + private double skillMultiplierAgility => 2.0; + private double skillMultiplierFlow => 238.0; + private double skillMultiplierTotal => 1.1; private double meanExponent => 1.2; private readonly List sliderStrains = new List();