From cba409d84adeb358b74a3f25305d445ca64a1075 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 21 Oct 2024 02:25:15 +0300 Subject: [PATCH] numerous simplifications --- .../Difficulty/Evaluators/ReadingEvaluator.cs | 136 +++++------------- .../Preprocessing/OsuDifficultyHitObject.cs | 8 +- .../Difficulty/Skills/Reading.cs | 34 ++--- .../Preprocessing/DifficultyHitObject.cs | 5 - 4 files changed, 50 insertions(+), 133 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index cfb7817cbc..cc8696a81f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators @@ -18,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private const double overlap_multiplier = 1; + private const double slider_body_length_multiplier = 1.3; + public static double EvaluateDensityOf(DifficultyHitObject current, bool applyDistanceNerf = true, bool applySliderbodyDensity = true, double angleNerfMultiplier = 1.0) { var currObj = (OsuDifficultyHitObject)current; @@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false); // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly. - if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.MinimumJumpDistance - 80) / 10) + 0.2) / 1.2; + if (applyDistanceNerf) loopDifficulty *= (logistic((loopObj.LazyJumpDistance - 80) / 10) + 0.2) / 1.2; // Additional buff for long sliderbodies. OVERBUFFED ON PURPOSE if (applySliderbodyDensity && loopObj.BaseObject is Slider slider) @@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (i > 0) maxBuff += 1; if (i < readingObjects.Count - 1) maxBuff += 1; - loopDifficulty *= 1 + 1.5 * Math.Min(sliderBodyBuff, maxBuff); + loopDifficulty *= 1 + slider_body_length_multiplier * Math.Min(sliderBodyBuff, maxBuff); } // Reduce density bonus for this object if they're too apart in time @@ -85,6 +86,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators density += loopDifficulty; // Angles nerf + // Why it's /2 + 0.5? + // Because there was a bug initially that made angle predictability to be from 0.5 to 1 + // And removing this bug caused balance to be destroyed double angleNerf = (loopObj.AnglePredictability / 2) + 0.5; densityAnglesNerf += angleNerf * loopDifficulty * angleNerfMultiplier; @@ -189,71 +193,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable public static double EvaluateInpredictabilityOf(DifficultyHitObject current) { - // make the sum equal to 1 - const double velocity_change_part = 0.8; - const double angle_change_part = 0.1; - const double rhythm_change_part = 0.1; - if (current.BaseObject is Spinner || current.Index == 0 || current.Previous(0).BaseObject is Spinner) return 0; var osuCurrObj = (OsuDifficultyHitObject)current; var osuLastObj = (OsuDifficultyHitObject)current.Previous(0); - // Rhythm difference punishment for velocity and angle bonuses - double rhythmSimilarity = 1 - getRhythmDifference(osuCurrObj.StrainTime, osuLastObj.StrainTime); - - // Make differentiation going from 1/4 to 1/2 and bigger difference - // To 1/3 to 1/2 and smaller difference - rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75); - rhythmSimilarity = 4 * (rhythmSimilarity - 0.5); - - double velocityChangeBonus = getVelocityChangeFactor(osuCurrObj, osuLastObj) * rhythmSimilarity; - - double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; - double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; - - double angleChangeBonus = 0; - - if (osuCurrObj.Angle != null && osuLastObj.Angle != null && currVelocity > 0 && prevVelocity > 0) - { - angleChangeBonus = 1 - osuCurrObj.AnglePredictability; - angleChangeBonus *= Math.Min(currVelocity, prevVelocity) / Math.Max(currVelocity, prevVelocity); // Prevent cheesing - } - - angleChangeBonus *= rhythmSimilarity; - - // This bonus only awards rhythm changes if they're not filled with sliderends - double rhythmChangeBonus = 0; - - if (current.Index > 1) - { - var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1); - - double currDelta = osuCurrObj.StrainTime; - double lastDelta = osuLastObj.StrainTime; - - if (osuLastObj.BaseObject is Slider sliderCurr) - { - currDelta -= sliderCurr.Duration / osuCurrObj.ClockRate; - currDelta = Math.Max(0, currDelta); - } - - if (osuLastLastObj.BaseObject is Slider sliderLast) - { - lastDelta -= sliderLast.Duration / osuLastObj.ClockRate; - lastDelta = Math.Max(0, lastDelta); - } - - rhythmChangeBonus = getRhythmDifference(currDelta, lastDelta); - } - - double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus; - return result; - } - - private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, OsuDifficultyHitObject osuLastObj) - { double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; @@ -271,12 +216,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators velocityChangeFactor /= 0.4; } - return velocityChangeFactor; + // Rhythm difference punishment for velocity and angle bonuses + double rhythmSimilarity = 1 - getRhythmDifference(osuCurrObj.StrainTime, osuLastObj.StrainTime); + + // Make differentiation going from 1/4 to 1/2 and bigger difference + // To 1/3 to 1/2 and smaller difference + rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75); + rhythmSimilarity = 4 * (rhythmSimilarity - 0.5); + + return velocityChangeFactor * rhythmSimilarity; } private static double getTimeNerfFactor(double deltaTime) => Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2); - private static double logistic(double x) => 1 / (1 + Math.Exp(-x)); + private static double logistic(double x) => 1.0 / (1 + Math.Exp(-x)); // Finds the overlapness of the last object for which StartTime lower than target private static double boundBinarySearch(List arr, double target) @@ -315,59 +268,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators double densityFactor = Math.Pow(density / 6.2, 1.5); - double invisibilityFactor; + // High AR curve + double invisibilityFactor = Math.Pow(preempt * 2.4 - 0.2, 5); - // AR11+DT and faster = 0 HD pp unless density is big - if (preempt < 0.2) invisibilityFactor = 0; + // Mid and Low AR curves + invisibilityFactor = Math.Min(invisibilityFactor, Math.Max(preempt, preempt * 3 - 2.4)); - // Else accelerating growth until around ART0, then linear, and starting from AR5 is 3 times faster again to buff AR0 +HD - else invisibilityFactor = Math.Min(Math.Pow(preempt * 2.4 - 0.2, 5), Math.Max(preempt, preempt * 3 - 2.4)); + // On 250ms invisibility factor will be 0 + invisibilityFactor *= invLerp(preempt, 0.25, 0.3); - - double hdDifficulty = invisibilityFactor + densityFactor; - - // Scale by inpredictability slightly - hdDifficulty *= 0.96 + 0.1 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1 - - return hdDifficulty; + return invisibilityFactor + densityFactor; } + + private static double invLerp(double value, double min, double max) => Math.Clamp((value - min) / (max - min), 0, 1); } public static class ReadingHighAREvaluator { + // https://www.desmos.com/calculator/mtlyw84ncw public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false) { var currObj = (OsuDifficultyHitObject)current; - double result = GetDifficulty(currObj.Preempt); + // Get preempt in seconds + double preempt = currObj.Preempt / 1000; + + // This function is matching live high AR bonus + double value = 0.63 * Math.Pow(Math.Max(8 - 20 * preempt, 0), 2.0 / 3); + + // Use different curve for preempt < 375ms + value = Math.Max(value, Math.Exp(9.075 - 80.0 * Math.Max(preempt, 0.375) / 3)); if (applyAdjust) { double inpredictability = ReadingEvaluator.EvaluateInpredictabilityOf(current); - - // follow lines make high AR easier, so apply nerf if object isn't new combo - inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800; - - result *= 0.98 + 0.6 * inpredictability; + value *= 0.98 + 0.6 * inpredictability; } - return result; - } - - // High AR curve (this curve is without Math.Pow(value, 2)) - // https://www.desmos.com/calculator/xuuwd77cbq - public static double GetDifficulty(double preempt) - { - // Get preempt in seconds - preempt /= 1000; - double value; - - if (preempt < 0.375) // We have stop in the point of AR10.5, the value here = 0.396875, derivative = -10.5833, - value = 0.63 * Math.Pow(8 - 20 * preempt, 2.0 / 3); // This function is matching live high AR bonus - else - value = Math.Exp(9.07583 - 80.0 * preempt / 3); - - return Math.Pow(value, 1.0 / ReadingHighAR.MECHANICAL_PP_POWER); + return value; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index c4957bc895..3b213112d9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing var visibleObjects = new List(); - for (int i = 0; i < current.Count; i++) + for (int i = 0; i < current.Index; i++) { OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i); @@ -421,21 +421,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double similarity3_2 = getGeneralSimilarity(prevObj0, prevObj3); double similarity3_3 = getGeneralSimilarity(prevObj1, prevObj4); - double similarity3_max = Math.Max(Math.Max(similarity3_1, similarity3_2), similarity3_3); double similarity3_total = similarity3_1 * similarity3_2 * similarity3_3; - double similarity3 = Math.Max(Math.Pow(similarity3_max, 3) * 0.5, similarity3_total); - // 4-4 repeat double similarity4_1 = getGeneralSimilarity(this, prevObj3); double similarity4_2 = getGeneralSimilarity(prevObj0, prevObj4); double similarity4_3 = getGeneralSimilarity(prevObj1, prevObj5); - double similarity4_max = Math.Pow(Math.Max(Math.Max(similarity4_1, similarity4_2), similarity4_3), 2); double similarity4_total = similarity4_1 * similarity4_2 * similarity4_3; - double similarity4 = Math.Max(Math.Pow(similarity4_max, 3) * 0.5, similarity4_total); - // Bandaid to fix Rubik's Cube +EZ double wideness = 0; if (Angle!.Value > Math.PI * 0.5) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 05ac06c9ac..5f09cedd40 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { public class ReadingLowAR : GraphSkill { - private double skillMultiplier => 1.23; + private double skillMultiplier => 1.22; private double aimComponentMultiplier => 0.4; public ReadingLowAR(Mod[] mods) @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills : base(mods, false) { } - protected new double SkillMultiplier => 7.632; + protected new double SkillMultiplier => 7.3; protected override double StrainValueAt(DifficultyHitObject current) { @@ -131,7 +131,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private HighARSpeedComponent speedComponent; private int objectsCount = 0; - private double objectsPreemptSum = 0; public override void Process(DifficultyHitObject current) { @@ -141,7 +140,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (current.BaseObject is not Spinner) { objectsCount++; - objectsPreemptSum += ((OsuDifficultyHitObject)current).Preempt; } double power = OsuDifficultyCalculator.SUM_POWER; @@ -164,8 +162,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } // Coefs for curve similar to difficulty to performance curve - private static double power => 3.0369; - private static double multiplier => 3.69656; + private static double power => 3; + private static double multiplier => 3.7; public static double DifficultyToPerformance(double difficulty) => Math.Pow(difficulty, power) * multiplier; private static double performanceToDifficulty(double performance) => Math.Pow(performance / multiplier, 1.0 / power); @@ -185,18 +183,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Length bonus is in SR to not inflate Star Rating of short AR11 maps double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount); lengthBonus = Math.Pow(lengthBonus, 0.5 / MECHANICAL_PP_POWER); - - // Get average preempt of objects - double averagePreempt = objectsPreemptSum / objectsCount / 1000; - - // Increase length bonus for long maps with very high AR - // https://www.desmos.com/calculator/wz9wckqgzu - double lengthBonusPower = 1 + 0.75 * Math.Pow(0.1, Math.Pow(2.3 * averagePreempt, 8)); - - // Be sure that increasing AR won't decrease pp - if (lengthBonus < 1) lengthBonusPower = 2; - - totalPerformance *= Math.Pow(lengthBonus, lengthBonusPower); + totalPerformance *= lengthBonus; double adjustedDifficulty = performanceToDifficulty(totalPerformance); double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0); @@ -217,12 +204,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { CurrentStrain *= StrainDecay(current.DeltaTime); + double highARDifficulty = Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, true), 1.0 / MECHANICAL_PP_POWER); double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true) * SkillMultiplier; - aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, true); + aimDifficulty *= highARDifficulty; CurrentStrain += aimDifficulty; - return CurrentStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current, true); + return CurrentStrain + component_default_value_multiplier * highARDifficulty; } } @@ -239,13 +227,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills CurrentStrain *= StrainDecay(currObj.StrainTime); + double highARDifficulty = Math.Pow(ReadingHighAREvaluator.EvaluateDifficultyOf(current, false), 1.0 / MECHANICAL_PP_POWER); double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier; - speedDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current); + + speedDifficulty *= highARDifficulty; CurrentStrain += speedDifficulty; CurrentRhythm = currObj.RhythmDifficulty; double totalStrain = CurrentStrain * CurrentRhythm; - return totalStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current); + return totalStrain + component_default_value_multiplier * highARDifficulty; } } } diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index cd9dd3572c..9785865192 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -15,11 +15,6 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing { private readonly IReadOnlyList difficultyHitObjects; - /// - /// The index of this in the list of all s. - /// - public int Count => difficultyHitObjects.Count; - /// /// The index of this in the list of all s. ///