From 587cf09d2a1e91fb51339730404f85d86c861bdd Mon Sep 17 00:00:00 2001 From: Xexxar Date: Sat, 25 Sep 2021 03:02:33 +0000 Subject: [PATCH 01/21] base change of aim refactor, isolated --- .../Preprocessing/OsuDifficultyHitObject.cs | 22 +++-- .../Difficulty/Skills/Aim.cs | 96 ++++++++++++++----- .../Difficulty/Skills/Speed.cs | 4 +- 3 files changed, 89 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 8e8f9bc06e..52f18539c4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -12,20 +12,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { public class OsuDifficultyHitObject : DifficultyHitObject { - private const int normalized_radius = 52; + private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. 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 . /// public double JumpDistance { get; private set; } + /// + /// Normalized Vector from the end position of the previous to the start position of this . + /// + public Vector2 JumpVector { get; private set; } + /// /// Normalized distance between the start and end position of the previous . /// @@ -37,6 +37,11 @@ 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; @@ -73,7 +78,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // Don't need to jump to reach spinners if (!(BaseObject is Spinner)) - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + { + JumpVector = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor); + JumpDistance = JumpVector.Length; + } if (lastLastObject != null) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 16a18cbcb9..677d3c06aa 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -6,6 +6,8 @@ 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; +using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -14,51 +16,97 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Aim : OsuStrainSkill { - private const double angle_bonus_begin = Math.PI / 3; - private const double timing_threshold = 107; - public Aim(Mod[] mods) : base(mods) { } - protected override double SkillMultiplier => 26.25; + protected override int HistoryLength => 2; + + protected override double SkillMultiplier => 24.75; protected override double StrainDecayBase => 0.15; + private const double wide_angle_multiplier = 1.0; + private const double acute_angle_multiplier = 1.0; + private const double rhythm_variance_multiplier = 1.0; + protected override double StrainValueOf(DifficultyHitObject current) { - if (current.BaseObject is Spinner) + if (current.BaseObject is Spinner || Previous.Count <= 1) return 0; - var osuCurrent = (OsuDifficultyHitObject)current; + var osuCurrObj = (OsuDifficultyHitObject)current; + var osuPrevObj = (OsuDifficultyHitObject)Previous[0]; + var osuLastObj = (OsuDifficultyHitObject)Previous[1]; - double result = 0; + var currVector = Vector2.Divide(osuCurrObj.JumpVector, (float)osuCurrObj.StrainTime); + var prevVector = Vector2.Divide(osuPrevObj.JumpVector, (float)osuPrevObj.StrainTime); - if (Previous.Count > 0) + // Start with regular velocity. + double aimStrain = currVector.Length; + + if (Precision.AlmostEquals(osuCurrObj.StrainTime, osuPrevObj.StrainTime, 10)) // Rhythms are the same. { - var osuPrevious = (OsuDifficultyHitObject)Previous[0]; - - if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin) + if (osuCurrObj.Angle != null) { - const double scale = 90; + double angle = osuCurrObj.Angle.Value; - var angleBonus = Math.Sqrt( - Math.Max(osuPrevious.JumpDistance - scale, 0) - * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2) - * Math.Max(osuCurrent.JumpDistance - scale, 0)); - result = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime); + // Rewarding angles, take the smaller velocity as base. + double angleBonus = Math.Min(currVector.Length, prevVector.Length); + + double wideAngleBonus = calcWideAngleBonus(angle); + double acuteAngleBonus = calcAcuteAngleBonus(angle); + + if (osuCurrObj.StrainTime > 100) + acuteAngleBonus = 0; + else + { + acuteAngleBonus *= Math.Min(2, Math.Pow((100 - osuCurrObj.StrainTime) / 15, 1.5)); + wideAngleBonus *= Math.Pow(osuCurrObj.StrainTime / 100, 6); + } + + if (acuteAngleBonus > wideAngleBonus) + angleBonus = Math.Min(angleBonus, 150 / osuCurrObj.StrainTime) * Math.Min(1, Math.Pow(Math.Min(osuCurrObj.JumpDistance, osuPrevObj.JumpDistance) / 150, 2)); + + angleBonus *= Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier); + + // add in angle velocity. + aimStrain += angleBonus; } } + else // There is a rhythm change + { + // Rewarding rhythm, take the smaller velocity as base. + double rhythmBonus = Math.Min(currVector.Length, prevVector.Length); - double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance); - double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); + if (osuCurrObj.StrainTime + 10 < osuPrevObj.StrainTime && osuPrevObj.StrainTime > osuLastObj.StrainTime + 10) + // Don't want to reward for a rhythm change back to back (unless its a double, which is why this only checks for fast -> slow -> fast). + rhythmBonus = 0; - return Math.Max( - result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), - (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime - ); + aimStrain += rhythmBonus * rhythm_variance_multiplier; // add in rhythm velocity. + } + + return aimStrain; } - private double applyDiminishingExp(double val) => Math.Pow(val, 0.99); + private double calcWideAngleBonus(double angle) + { + if (angle < Math.PI / 3) + return 0; + if (angle < 2 * Math.PI / 3) + return Math.Pow(Math.Sin(1.5 * (angle - Math.PI / 3)), 2); + + return 0.25 + 0.75 * Math.Pow(Math.Sin(1.5 * (Math.PI - angle)), 2); + } + + private double calcAcuteAngleBonus(double angle) + { + if (angle < Math.PI / 3) + return 0.5 + 0.5 * Math.Pow(Math.Sin(1.5 * angle), 2); + if (angle < 2 * Math.PI / 3) + return Math.Pow(Math.Sin(1.5 * (2 * Math.PI / 3 - angle)), 2); + + return 0; + } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 9364b11048..434074da17 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - private const double single_spacing_threshold = 125; + private const double single_spacing_threshold = 135; private const double angle_bonus_begin = 5 * Math.PI / 6; private const double pi_over_4 = Math.PI / 4; @@ -81,7 +81,7 @@ 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)) + * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.0)) / strainTime; } } From 4099726d594b00f02456131bb7ca3cf76632fd79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 25 Sep 2021 15:41:06 +0200 Subject: [PATCH 02/21] Update SR values in tests --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 19881b5c33..c85ce49ef6 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.6634445062299665d, "diffcalc-test")] - [TestCase(1.0414203870195022d, "zero-length-sliders")] + [TestCase(6.4766744062777439d, "diffcalc-test")] + [TestCase(0.63190213974653298d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.3858089051603368d, "diffcalc-test")] - [TestCase(1.2723279173428435d, "zero-length-sliders")] + [TestCase(8.9926029916156764d, "diffcalc-test")] + [TestCase(0.78116445583636385d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From e4fb5a01c93ce43fc8fd3993b9a1adf50b00a965 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Sun, 26 Sep 2021 21:58:26 +0000 Subject: [PATCH 03/21] readded missing aim nerf for low acc --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index bf4d92652c..e936fa14d9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= approachRateBonus; - // Scale the aim value with accuracy _slightly_. - aimValue *= 0.5 + accuracy / 2.0; + // Scale the aim value with accuracy + aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; From 7001578045e509ba12c06ca9e3afa50e60182cb2 Mon Sep 17 00:00:00 2001 From: Xexxar <38889014+Xexxar@users.noreply.github.com> Date: Sun, 26 Sep 2021 20:39:57 -0500 Subject: [PATCH 04/21] Update osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 52f18539c4..b9ba6364af 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -38,7 +38,7 @@ 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. + /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. /// public readonly double StrainTime; From 200149c9d7203084a08d77303b8091a3634aea8d Mon Sep 17 00:00:00 2001 From: Xexxar Date: Wed, 13 Oct 2021 15:41:24 +0000 Subject: [PATCH 05/21] updated to newly refactored aim --- .../Preprocessing/OsuDifficultyHitObject.cs | 33 +++++-- .../Difficulty/Skills/Aim.cs | 98 ++++++++----------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++ 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index b9ba6364af..89ba73ba51 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double JumpDistance { get; private set; } /// - /// Normalized Vector from the end position of the previous to the start position of this . + /// Minimum distance from the end position of the previous to the start position of this . /// - public Vector2 JumpVector { get; private set; } + public double MovementDistance { get; private set; } - /// + /// JumpTravel /// Normalized distance between the start and end position of the previous . /// public double TravelDistance { get; private set; } @@ -37,6 +37,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double? Angle { get; private set; } + /// + /// Milliseconds elapsed since the end time of the Previous , with a minimum of 25ms. + /// + public double MovementTime { get; private set; } + + /// + /// Milliseconds elapsed since from the start time of the Previous to the end time of the same Previous , with a minimum of 25ms. + /// + public double TravelTime { get; private set; } + /// /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. /// @@ -51,13 +61,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; - setDistances(); - // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects. StrainTime = Math.Max(DeltaTime, 25); + + setDistances(clockRate); } - private void setDistances() + private void setDistances(double clockRate) { // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. float scalingFactor = normalized_radius / (float)BaseObject.Radius; @@ -72,6 +82,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { computeSliderCursorPosition(lastSlider); TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; + TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, 0); + MovementTime = Math.Max(StrainTime - TravelTime, 0); + MovementDistance = Math.Max(0, Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length - 0) * scalingFactor; } Vector2 lastCursorPosition = getEndCursorPosition(lastObject); @@ -79,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // Don't need to jump to reach spinners if (!(BaseObject is Spinner)) { - JumpVector = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor); - JumpDistance = JumpVector.Length; + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + MovementDistance = Math.Min(JumpDistance, MovementDistance); } if (lastLastObject != null) @@ -104,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing slider.LazyEndPosition = slider.StackedPosition; - float approxFollowCircleRadius = (float)(slider.Radius * 3); + float approxFollowCircleRadius = (float)(slider.Radius * 2.4); var computeVertex = new Action(t => { double progress = (t - slider.StartTime) / slider.SpanDuration; @@ -117,6 +130,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value; float dist = diff.Length; + slider.LazyTravelTime = t - slider.StartTime; + if (dist > approxFollowCircleRadius) { // The cursor would be outside the follow circle, we need to move it diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 0101dcaee2..e162b25f10 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; -using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -23,13 +22,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override int HistoryLength => 2; - private const double wide_angle_multiplier = 1.0; - private const double acute_angle_multiplier = 1.0; - private const double rhythm_variance_multiplier = 1.0; + private const double wide_angle_multiplier = 1.5; + private const double acute_angle_multiplier = 2.0; private double currentStrain = 1; - private double skillMultiplier => 26.25; + private double skillMultiplier => 23.25; private double strainDecayBase => 0.15; private double strainValueOf(DifficultyHitObject current) @@ -41,75 +39,65 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuPrevObj = (OsuDifficultyHitObject)Previous[0]; var osuLastObj = (OsuDifficultyHitObject)Previous[1]; - var currVector = Vector2.Divide(osuCurrObj.JumpVector, (float)osuCurrObj.StrainTime); - var prevVector = Vector2.Divide(osuPrevObj.JumpVector, (float)osuPrevObj.StrainTime); + double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; // Start iwth the base distance / time - // Start with regular velocity. - double aimStrain = currVector.Length; - - if (Precision.AlmostEquals(osuCurrObj.StrainTime, osuPrevObj.StrainTime, 10)) // Rhythms are the same. + if (osuPrevObj.BaseObject is Slider) // If object is a slider { - if (osuCurrObj.Angle != null) + double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to next note + double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to lazy end. + + currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. + } + + double prevVelocity = osuPrevObj.JumpDistance / osuPrevObj.StrainTime; // do the same for the previous velocity. + + if (osuLastObj.BaseObject is Slider) + { + double movementVelocity = osuPrevObj.MovementDistance / osuPrevObj.MovementTime; + double travelVelocity = osuPrevObj.TravelDistance / osuPrevObj.TravelTime; + + prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); + } + + double angleBonus = 0; + + double aimStrain = currVelocity; // Start strain with regular velocity. + + if (Precision.AlmostEquals(osuCurrObj.StrainTime, osuPrevObj.StrainTime, 10)) // If rhythms are the same. + { + if (osuCurrObj.Angle != null && osuPrevObj.Angle != null) { - double angle = osuCurrObj.Angle.Value; + double currAngle = osuCurrObj.Angle.Value; + double prevAngle = osuPrevObj.Angle.Value; // Rewarding angles, take the smaller velocity as base. - double angleBonus = Math.Min(currVector.Length, prevVector.Length); + angleBonus = Math.Min(currVelocity, prevVelocity); - double wideAngleBonus = calcWideAngleBonus(angle); - double acuteAngleBonus = calcAcuteAngleBonus(angle); + double wideAngleBonus = calcWideAngleBonus(currAngle); + double acuteAngleBonus = calcAcuteAngleBonus(currAngle); - if (osuCurrObj.StrainTime > 100) + if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2. acuteAngleBonus = 0; else - { - acuteAngleBonus *= Math.Min(2, Math.Pow((100 - osuCurrObj.StrainTime) / 15, 1.5)); - wideAngleBonus *= Math.Pow(osuCurrObj.StrainTime / 100, 6); - } + acuteAngleBonus *= calcAcuteAngleBonus(prevAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. + * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime + * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 + * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Min(100, osuCurrObj.JumpDistance) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). - if (acuteAngleBonus > wideAngleBonus) - angleBonus = Math.Min(angleBonus, 150 / osuCurrObj.StrainTime) * Math.Min(1, Math.Pow(Math.Min(osuCurrObj.JumpDistance, osuPrevObj.JumpDistance) / 150, 2)); + wideAngleBonus *= angleBonus * (1 - Math.Pow(calcWideAngleBonus(prevAngle), 3)); // Penalize wide angles if they're repeated, reducing the penalty as the prevAngle gets more acute. - angleBonus *= Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier); - - // add in angle velocity. - aimStrain += angleBonus; + angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the anglebuffs together. } } - else // There is a rhythm change - { - // Rewarding rhythm, take the smaller velocity as base. - double rhythmBonus = Math.Min(currVector.Length, prevVector.Length); - if (osuCurrObj.StrainTime + 10 < osuPrevObj.StrainTime && osuPrevObj.StrainTime > osuLastObj.StrainTime + 10) - // Don't want to reward for a rhythm change back to back (unless its a double, which is why this only checks for fast -> slow -> fast). - rhythmBonus = 0; - - aimStrain += rhythmBonus * rhythm_variance_multiplier; // add in rhythm velocity. - } + aimStrain += angleBonus; // Add in angle bonus. return aimStrain; } - private double calcWideAngleBonus(double angle) - { - if (angle < Math.PI / 3) - return 0; - if (angle < 2 * Math.PI / 3) - return Math.Pow(Math.Sin(1.5 * (angle - Math.PI / 3)), 2); + private double calcWideAngleBonus(double angle) => Math.Pow(Math.Sin(3.0 / 4 * (Math.Min(5.0 / 6 * Math.PI, Math.Max(Math.PI / 6, angle)) - Math.PI / 6)), 2); - return 0.25 + 0.75 * Math.Pow(Math.Sin(1.5 * (Math.PI - angle)), 2); - } - - private double calcAcuteAngleBonus(double angle) - { - if (angle < Math.PI / 3) - return 0.5 + 0.5 * Math.Pow(Math.Sin(1.5 * angle), 2); - if (angle < 2 * Math.PI / 3) - return Math.Pow(Math.Sin(1.5 * (2 * Math.PI / 3 - angle)), 2); - - return 0; - } + private double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle); private double applyDiminishingExp(double val) => Math.Pow(val, 0.99); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1d2666f46b..1d494c2917 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -79,6 +79,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; + /// + /// The time taken by the cursor upon completion of this if it was hit + /// with as few movements as possible. This is set and used by difficulty calculation. + /// + internal double LazyTravelTime; + public List> NodeSamples { get; set; } = new List>(); [JsonIgnore] From 0f248d55107e574876d139b2d030f98575f62704 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Wed, 13 Oct 2021 15:45:42 +0000 Subject: [PATCH 06/21] removed the merge conflict markers --- .../OsuDifficultyCalculatorTest.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 1096e9a777..15675e74d1 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,15 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; -<<<<<<< HEAD - [TestCase(6.4766744062777439d, "diffcalc-test")] - [TestCase(0.63190213974653298d, "zero-length-sliders")] - public void Test(double expected, string name) - => base.Test(expected, name); - - [TestCase(8.9926029916156764d, "diffcalc-test")] - [TestCase(0.78116445583636385d, "zero-length-sliders")] -======= [TestCase(6.5867229481955389d, "diffcalc-test")] [TestCase(1.0416315570967911d, "zero-length-sliders")] public void Test(double expected, string name) @@ -31,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(8.2730989071947896d, "diffcalc-test")] [TestCase(1.2726413186221039d, "zero-length-sliders")] ->>>>>>> ppy/master public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From 0292fe1c834ebf68f22f85832a776b6740030865 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Wed, 13 Oct 2021 16:04:39 +0000 Subject: [PATCH 07/21] removed residual code leftover --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 89ba73ba51..c2a4dc741f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, 0); MovementTime = Math.Max(StrainTime - TravelTime, 0); - MovementDistance = Math.Max(0, Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length - 0) * scalingFactor; + MovementDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; } Vector2 lastCursorPosition = getEndCursorPosition(lastObject); From 4edf559408e2cfe48bcc805d61cf914d032bf839 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Wed, 13 Oct 2021 16:45:58 +0000 Subject: [PATCH 08/21] adjusted constant to 1.5 from 2.0 for acute --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index e162b25f10..3d27e13bc1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override int HistoryLength => 2; private const double wide_angle_multiplier = 1.5; - private const double acute_angle_multiplier = 2.0; + private const double acute_angle_multiplier = 1.5; private double currentStrain = 1; From 14d405786ee50ac4a641717dec83a5a11e092b2c Mon Sep 17 00:00:00 2001 From: Xexxar Date: Sun, 17 Oct 2021 03:56:53 +0000 Subject: [PATCH 09/21] resolved nans and added stacked slider fix --- .../Preprocessing/OsuDifficultyHitObject.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 5aa249a616..d86358fb57 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -82,19 +82,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } + double sliderAbuseIndex = 1; + if (lastObject is Slider lastSlider) { computeSliderCursorPosition(lastSlider); - TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; - TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, 0); - MovementTime = Math.Max(StrainTime - TravelTime, 0); + sliderAbuseIndex = Math.Clamp(Vector2.Subtract(lastSlider.StackedPosition * scalingFactor, BaseObject.StackedPosition * scalingFactor).Length - 100, 0, 25) / 25; + TravelDistance = lastSlider.LazyTravelDistance * scalingFactor * sliderAbuseIndex; + TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, 25); + MovementTime = Math.Max(StrainTime - TravelTime, 25); MovementDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; } Vector2 lastCursorPosition = getEndCursorPosition(lastObject); - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; - MovementDistance = Math.Min(JumpDistance, MovementDistance); + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length * sliderAbuseIndex; + MovementDistance = Math.Min(JumpDistance, MovementDistance) * sliderAbuseIndex; if (lastLastObject != null && !(lastLastObject is Spinner)) { From cb605f91568272a5e0b30b18980661e4795d8d72 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Thu, 21 Oct 2021 16:00:57 +0000 Subject: [PATCH 10/21] removed ppCalc changes and sliderabuseChecks --- .../Difficulty/OsuPerformanceCalculator.cs | 77 ++++--------------- .../Preprocessing/OsuDifficultyHitObject.cs | 24 +++--- .../Difficulty/Skills/Aim.cs | 2 +- 3 files changed, 27 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index f97f4899de..bf4d92652c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMeh; private int countMiss; - private int effectiveMissCount; - public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) : base(ruleset, attributes, score) { @@ -41,23 +39,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - effectiveMissCount = calculateEffectiveMissCount(); - - double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. // Custom multipliers for NoFail and SpunOut. + double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + if (mods.Any(m => m is OsuModNoFail)) - multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); + multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss); if (mods.Any(m => m is OsuModSpunOut)) multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); - if (mods.Any(h => h is OsuModRelax)) - { - effectiveMissCount += countOk + countMeh; - multiplier *= 0.6; - } - double aimValue = computeAimValue(); double speedValue = computeSpeedValue(); double accuracyValue = computeAccuracyValue(); @@ -100,8 +91,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. - if (effectiveMissCount > 0) - aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount); + if (countMiss > 0) + aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -117,18 +108,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty double approachRateBonus = 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor; - if (mods.Any(m => m is OsuModBlinds)) - aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate); - else if (mods.Any(h => h is OsuModHidden)) - { - // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + if (mods.Any(h => h is OsuModHidden)) aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); - } aimValue *= approachRateBonus; - // Scale the aim value with accuracy - aimValue *= accuracy; + // Scale the aim value with accuracy _slightly_. + aimValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; @@ -145,8 +132,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= lengthBonus; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. - if (effectiveMissCount > 0) - speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); + if (countMiss > 0) + speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875)); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -160,20 +147,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor; - if (mods.Any(m => m is OsuModBlinds)) - { - // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. - speedValue *= 1.12; - } - else if (mods.Any(m => m is OsuModHidden)) - { - // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. + if (mods.Any(m => m is OsuModHidden)) speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); - } // Scale the speed value with accuracy and OD. speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); - // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); @@ -182,9 +160,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAccuracyValue() { - if (mods.Any(h => h is OsuModRelax)) - return 0.0; - // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; int amountHitObjectsWithAccuracy = Attributes.HitCircleCount; @@ -205,12 +180,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Bonus for many hitcircles - it's harder to keep good accuracy up for longer. accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); - // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. - if (mods.Any(m => m is OsuModBlinds)) - accuracyValue *= 1.14; - else if (mods.Any(m => m is OsuModHidden)) + if (mods.Any(m => m is OsuModHidden)) accuracyValue *= 1.08; - if (mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; @@ -234,8 +205,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightValue *= 1.3; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. - if (effectiveMissCount > 0) - flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); + if (countMiss > 0) + flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875)); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -253,24 +224,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty return flashlightValue; } - private int calculateEffectiveMissCount() - { - // guess the number of misses + slider breaks from combo - double comboBasedMissCount = 0.0; - - if (Attributes.SliderCount > 0) - { - double fullComboThreshold = Attributes.MaxCombo - 0.1 * Attributes.SliderCount; - if (scoreMaxCombo < fullComboThreshold) - comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); - } - - // we're clamping misscount because since its derived from combo it can be higher than total hits and that breaks some calculations - comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits); - - return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); - } - private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d86358fb57..5d85c4338c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public class OsuDifficultyHitObject : DifficultyHitObject { private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. + private const int min_delta_time = 25; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; @@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double MovementDistance { get; private set; } - /// JumpTravel + /// /// Normalized distance between the start and end position of the previous . /// public double TravelDistance { get; private set; } @@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastObject = (OsuHitObject)lastObject; // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects. - StrainTime = Math.Max(DeltaTime, 25); + StrainTime = Math.Max(DeltaTime, min_delta_time); setDistances(clockRate); } @@ -82,22 +83,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } - double sliderAbuseIndex = 1; - if (lastObject is Slider lastSlider) { computeSliderCursorPosition(lastSlider); - sliderAbuseIndex = Math.Clamp(Vector2.Subtract(lastSlider.StackedPosition * scalingFactor, BaseObject.StackedPosition * scalingFactor).Length - 100, 0, 25) / 25; - TravelDistance = lastSlider.LazyTravelDistance * scalingFactor * sliderAbuseIndex; - TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, 25); - MovementTime = Math.Max(StrainTime - TravelTime, 25); + TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; + TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); + MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time); MovementDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; } Vector2 lastCursorPosition = getEndCursorPosition(lastObject); - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length * sliderAbuseIndex; - MovementDistance = Math.Min(JumpDistance, MovementDistance) * sliderAbuseIndex; + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + MovementDistance = Math.Min(JumpDistance, MovementDistance); if (lastLastObject != null && !(lastLastObject is Spinner)) { @@ -120,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing slider.LazyEndPosition = slider.StackedPosition; - float approxFollowCircleRadius = (float)(slider.Radius * 2.4); + float followCircleRadius = (float)(slider.Radius * 2.4); var computeVertex = new Action(t => { double progress = (t - slider.StartTime) / slider.SpanDuration; @@ -135,11 +133,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing slider.LazyTravelTime = t - slider.StartTime; - if (dist > approxFollowCircleRadius) + if (dist > followCircleRadius) { // The cursor would be outside the follow circle, we need to move it diff.Normalize(); // Obtain direction of diff - dist -= approxFollowCircleRadius; + dist -= followCircleRadius; slider.LazyEndPosition += diff * dist; slider.LazyTravelDistance += dist; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 3d27e13bc1..966e7eb221 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuPrevObj = (OsuDifficultyHitObject)Previous[0]; var osuLastObj = (OsuDifficultyHitObject)Previous[1]; - double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; // Start iwth the base distance / time + double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; // Start with the base distance / time if (osuPrevObj.BaseObject is Slider) // If object is a slider { From d6aa2fe6e48e5804bca01c9be29debdaadd884fb Mon Sep 17 00:00:00 2001 From: Xexxar Date: Thu, 21 Oct 2021 17:21:34 +0000 Subject: [PATCH 11/21] identified case with spinner where / 0 could theoretically be possible --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 966e7eb221..94f02c2d9f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double strainValueOf(DifficultyHitObject current) { - if (current.BaseObject is Spinner || Previous.Count <= 1) + if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner) return 0; var osuCurrObj = (OsuDifficultyHitObject)current; From 2f8972b529c58398dc0ac14b0f552c9e65aba126 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Thu, 21 Oct 2021 18:37:06 +0000 Subject: [PATCH 12/21] reverted overwritten file --- .../Difficulty/OsuPerformanceCalculator.cs | 71 +++++++++++++++---- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index bf4d92652c..4bca87204a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMeh; private int countMiss; + private int effectiveMissCount; + public OsuPerformanceCalculator(Ruleset ruleset, DifficultyAttributes attributes, ScoreInfo score) : base(ruleset, attributes, score) { @@ -39,16 +41,23 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = Score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); + effectiveMissCount = calculateEffectiveMissCount(); - // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + // Custom multipliers for NoFail and SpunOut. if (mods.Any(m => m is OsuModNoFail)) - multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss); + multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); if (mods.Any(m => m is OsuModSpunOut)) multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); + if (mods.Any(h => h is OsuModRelax)) + { + effectiveMissCount += countOk + countMeh; + multiplier *= 0.6; + } + double aimValue = computeAimValue(); double speedValue = computeSpeedValue(); double accuracyValue = computeAccuracyValue(); @@ -91,8 +100,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. - if (countMiss > 0) - aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss); + if (effectiveMissCount > 0) + aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), effectiveMissCount); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -108,9 +117,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty double approachRateBonus = 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor; - // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. - if (mods.Any(h => h is OsuModHidden)) + if (mods.Any(m => m is OsuModBlinds)) + aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * Attributes.DrainRate * Attributes.DrainRate); + else if (mods.Any(h => h is OsuModHidden)) + { + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); + } aimValue *= approachRateBonus; @@ -132,8 +145,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= lengthBonus; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. - if (countMiss > 0) - speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875)); + if (effectiveMissCount > 0) + speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -147,11 +160,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + (0.03 + 0.37 * approachRateTotalHitsFactor) * approachRateFactor; - if (mods.Any(m => m is OsuModHidden)) + if (mods.Any(m => m is OsuModBlinds)) + { + // Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given. + speedValue *= 1.12; + } + else if (mods.Any(m => m is OsuModHidden)) + { + // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); + } // Scale the speed value with accuracy and OD. speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2); + // Scale the speed value with # of 50s to punish doubletapping. speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); @@ -160,6 +182,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAccuracyValue() { + if (mods.Any(h => h is OsuModRelax)) + return 0.0; + // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; int amountHitObjectsWithAccuracy = Attributes.HitCircleCount; @@ -180,8 +205,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Bonus for many hitcircles - it's harder to keep good accuracy up for longer. accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); - if (mods.Any(m => m is OsuModHidden)) + // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. + if (mods.Any(m => m is OsuModBlinds)) + accuracyValue *= 1.14; + else if (mods.Any(m => m is OsuModHidden)) accuracyValue *= 1.08; + if (mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02; @@ -205,8 +234,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightValue *= 1.3; // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. - if (countMiss > 0) - flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875)); + if (effectiveMissCount > 0) + flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); // Combo scaling. if (Attributes.MaxCombo > 0) @@ -224,6 +253,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty return flashlightValue; } + private int calculateEffectiveMissCount() + { + // guess the number of misses + slider breaks from combo + double comboBasedMissCount = 0.0; + + if (Attributes.SliderCount > 0) + { + double fullComboThreshold = Attributes.MaxCombo - 0.1 * Attributes.SliderCount; + if (scoreMaxCombo < fullComboThreshold) + comboBasedMissCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo); + } + + // we're clamping misscount because since its derived from combo it can be higher than total hits and that breaks some calculations + comboBasedMissCount = Math.Min(comboBasedMissCount, totalHits); + + return Math.Max(countMiss, (int)Math.Floor(comboBasedMissCount)); + } + private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; } From ddf87316dfcdf775e4f8baa972b95413f1fdd5f2 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Wed, 27 Oct 2021 16:30:17 +0000 Subject: [PATCH 13/21] updated to add nerf for repeated acute angles --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 94f02c2d9f..7ccb5158db 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override int HistoryLength => 2; private const double wide_angle_multiplier = 1.5; - private const double acute_angle_multiplier = 1.5; + private const double acute_angle_multiplier = 2.0; private double currentStrain = 1; @@ -69,6 +69,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { double currAngle = osuCurrObj.Angle.Value; double prevAngle = osuPrevObj.Angle.Value; + double lastAngle = osuLastObj.Angle.Value; // Rewarding angles, take the smaller velocity as base. angleBonus = Math.Min(currVelocity, prevVelocity); @@ -84,7 +85,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Min(100, osuCurrObj.JumpDistance) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). - wideAngleBonus *= angleBonus * (1 - Math.Pow(calcWideAngleBonus(prevAngle), 3)); // Penalize wide angles if they're repeated, reducing the penalty as the prevAngle gets more acute. + wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(prevAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the prevAngle gets more acute. + acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastAngle gets more obtuse. angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the anglebuffs together. } From 219880d719c8a34d7cd87f86ceb4ab4b01a160a7 Mon Sep 17 00:00:00 2001 From: Xexxar Date: Thu, 28 Oct 2021 14:45:45 +0000 Subject: [PATCH 14/21] add in stanrs review --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 7ccb5158db..f706646cdf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -63,9 +63,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double aimStrain = currVelocity; // Start strain with regular velocity. - if (Precision.AlmostEquals(osuCurrObj.StrainTime, osuPrevObj.StrainTime, 10)) // If rhythms are the same. + if (Math.Max(osuCurrObj.StrainTime, osuPrevObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuPrevObj.StrainTime)) // If rhythms are the same. { - if (osuCurrObj.Angle != null && osuPrevObj.Angle != null) + if (osuCurrObj.Angle != null && osuPrevObj.Angle != null && osuLastObj.Angle != null) { double currAngle = osuCurrObj.Angle.Value; double prevAngle = osuPrevObj.Angle.Value; @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills acuteAngleBonus *= calcAcuteAngleBonus(prevAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 - * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Min(100, osuCurrObj.JumpDistance) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(prevAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the prevAngle gets more acute. acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastAngle gets more obtuse. From 5486ca084e66e6a8bb751a153837b72e534769ae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Nov 2021 23:32:58 +0900 Subject: [PATCH 15/21] Resolve tests --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 15675e74d1..7cd06c5225 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.5867229481955389d, "diffcalc-test")] - [TestCase(1.0416315570967911d, "zero-length-sliders")] + [TestCase(6.5295339534769958d, "diffcalc-test")] + [TestCase(1.1514260533755143d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.2730989071947896d, "diffcalc-test")] - [TestCase(1.2726413186221039d, "zero-length-sliders")] + [TestCase(9.047752485219954d, "diffcalc-test")] + [TestCase(1.3985711787077566d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From be8a1f60c03d89e66a94fec9e8d3801e2c2a0031 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Nov 2021 23:33:51 +0900 Subject: [PATCH 16/21] Apply styling changes --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index f706646cdf..f1eb464a29 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -6,7 +6,6 @@ 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 { @@ -80,10 +79,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2. acuteAngleBonus = 0; else + { acuteAngleBonus *= calcAcuteAngleBonus(prevAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. - * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime - * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 - * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime + * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 + * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + } wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(prevAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the prevAngle gets more acute. acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastAngle gets more obtuse. From 5454de7ae876fee195d2e03feab8cab88516ebdd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Nov 2021 23:47:20 +0900 Subject: [PATCH 17/21] Refactor xmldocs --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index e5bb80d9a5..c274d08a1b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double? Angle { get; private set; } /// - /// Milliseconds elapsed since the end time of the Previous , with a minimum of 25ms. + /// Milliseconds elapsed since the end time of the previous , with a minimum of 25ms. /// public double MovementTime { get; private set; } /// - /// Milliseconds elapsed since from the start time of the Previous to the end time of the same Previous , with a minimum of 25ms. + /// Milliseconds elapsed since the start time of the previous to the end time of the same previous , with a minimum of 25ms. /// public double TravelTime { get; private set; } @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; - // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects. + // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. StrainTime = Math.Max(DeltaTime, min_delta_time); setDistances(clockRate); From 7e0629774b1421c076d60da42a10ff3ff57c6ff0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Nov 2021 23:51:09 +0900 Subject: [PATCH 18/21] Rename variables for readability In particular, "last" vs "prev" can get confusing because they mean the same thing. --- .../Difficulty/Skills/Aim.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index f1eb464a29..d18d177515 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -35,12 +35,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return 0; var osuCurrObj = (OsuDifficultyHitObject)current; - var osuPrevObj = (OsuDifficultyHitObject)Previous[0]; - var osuLastObj = (OsuDifficultyHitObject)Previous[1]; + var osuLastObj = (OsuDifficultyHitObject)Previous[0]; + var osuLastLastObj = (OsuDifficultyHitObject)Previous[1]; double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; // Start with the base distance / time - if (osuPrevObj.BaseObject is Slider) // If object is a slider + if (osuLastObj.BaseObject is Slider) // If object is a slider { double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to next note double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to lazy end. @@ -48,12 +48,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } - double prevVelocity = osuPrevObj.JumpDistance / osuPrevObj.StrainTime; // do the same for the previous velocity. + double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; // do the same for the previous velocity. - if (osuLastObj.BaseObject is Slider) + if (osuLastLastObj.BaseObject is Slider) { - double movementVelocity = osuPrevObj.MovementDistance / osuPrevObj.MovementTime; - double travelVelocity = osuPrevObj.TravelDistance / osuPrevObj.TravelTime; + double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; + double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } @@ -62,13 +62,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double aimStrain = currVelocity; // Start strain with regular velocity. - if (Math.Max(osuCurrObj.StrainTime, osuPrevObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuPrevObj.StrainTime)) // If rhythms are the same. + if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same. { - if (osuCurrObj.Angle != null && osuPrevObj.Angle != null && osuLastObj.Angle != null) + if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null) { double currAngle = osuCurrObj.Angle.Value; - double prevAngle = osuPrevObj.Angle.Value; - double lastAngle = osuLastObj.Angle.Value; + double prevAngle = osuLastObj.Angle.Value; + double lastAngle = osuLastLastObj.Angle.Value; // Rewarding angles, take the smaller velocity as base. angleBonus = Math.Min(currVelocity, prevVelocity); From 93c03b9d37a25b8c187a124c6a90152a155187ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Nov 2021 00:04:19 +0900 Subject: [PATCH 19/21] Refactor documentation around velocity calculations --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index d18d177515..ccf69e30a2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -38,17 +38,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuLastObj = (OsuDifficultyHitObject)Previous[0]; var osuLastLastObj = (OsuDifficultyHitObject)Previous[1]; - double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; // Start with the base distance / time + // Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle. + double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; - if (osuLastObj.BaseObject is Slider) // If object is a slider + // 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) { - double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to next note - double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to lazy end. + double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object + double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end. currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } - double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; // do the same for the previous velocity. + // As above, do the same for the previous hitobject. + double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; if (osuLastLastObj.BaseObject is Slider) { @@ -59,7 +62,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } double angleBonus = 0; - double aimStrain = currVelocity; // Start strain with regular velocity. if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same. From b0d9c0eca3709d22b2bae21cc89b36d1b42a2cb3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Nov 2021 00:16:33 +0900 Subject: [PATCH 20/21] Refactor variables for readability in angle calculations --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index ccf69e30a2..a054b46366 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null) { double currAngle = osuCurrObj.Angle.Value; - double prevAngle = osuLastObj.Angle.Value; - double lastAngle = osuLastLastObj.Angle.Value; + double lastAngle = osuLastObj.Angle.Value; + double lastLastAngle = osuLastLastObj.Angle.Value; // Rewarding angles, take the smaller velocity as base. angleBonus = Math.Min(currVelocity, prevVelocity); @@ -82,16 +82,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills acuteAngleBonus = 0; else { - acuteAngleBonus *= calcAcuteAngleBonus(prevAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. + acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). } - wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(prevAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the prevAngle gets more acute. - acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastAngle gets more obtuse. + wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute. + acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse. - angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the anglebuffs together. + angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the angle buffs together. } } From 133218ecbb41f87f8d6975380ccff5805da1a010 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Nov 2021 01:04:07 +0900 Subject: [PATCH 21/21] Document special MovementDistance for sliders, ensure always has a value --- .../Preprocessing/OsuDifficultyHitObject.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index c274d08a1b..4b90285fd4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -83,19 +83,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } + Vector2 lastCursorPosition = getEndCursorPosition(lastObject); + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + if (lastObject is Slider lastSlider) { computeSliderCursorPosition(lastSlider); TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time); - MovementDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; + + // Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance. + float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; + + // For hitobjects which continue in the direction of the slider, the player will normally follow through the slider, + // such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider. + // In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance. + MovementDistance = Math.Min(JumpDistance, tailJumpDistance); + } + else + { + MovementTime = StrainTime; + MovementDistance = JumpDistance; } - - Vector2 lastCursorPosition = getEndCursorPosition(lastObject); - - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; - MovementDistance = Math.Min(JumpDistance, MovementDistance); if (lastLastObject != null && !(lastLastObject is Spinner)) {