diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs index ec7e17f38e..03a81dfbf5 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators OsuDifficultyHitObject prevObj0 = currObj; var readingObjects = currObj.ReadingObjects; + for (int i = 0; i < readingObjects.Count; i++) { var loopObj = readingObjects[i].HitObject; @@ -40,10 +41,10 @@ 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. + // Small distances means objects may be cheesed, so it doesn't matter whether they are arranged confusingly if (applyDistanceNerf) loopDifficulty *= (DifficultyCalculationUtils.Logistic(-(loopObj.LazyJumpDistance - 80) / 10) + 0.2) / 1.2; - // Additional buff for long sliderbodies. OVERBUFFED ON PURPOSE + // Additional buff for long sliderbodies if (applySliderbodyDensity && loopObj.BaseObject is Slider slider) { // In radiuses, with minimal of 1 @@ -144,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators var easierObject = sortedDifficulties[j]; // Get the overlap value - double overlapValue = 0; + double overlapValue; // OverlapValues dict only contains prev objects, so be sure to use right object if (harderObject.HitObject.Index > easierObject.HitObject.Index) @@ -171,6 +172,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators return overlap_multiplier * Math.Max(0, screenOverlapDifficulty); } + public static double EvaluateDifficultyOf(DifficultyHitObject current) { if (current.BaseObject is Spinner || current.Index == 0) @@ -191,43 +193,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators return Math.Max(0, Math.Pow(difficulty, 1.37) - 1); } - // Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable - public static double EvaluateInpredictabilityOf(DifficultyHitObject current) - { - 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); - - double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; - double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; - - double velocityChangeFactor = 0; - - // https://www.desmos.com/calculator/kqxmqc8pkg - if (currVelocity > 0 || prevVelocity > 0) - { - double velocityChange = Math.Max(0, - Math.Min( - Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity), - Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity)) - )); // Stealed from xexxar - velocityChangeFactor = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4 - velocityChangeFactor /= 0.4; - } - - // Rhythm difference punishment for velocity and angle bonuses - double rhythmSimilarity = DifficultyCalculationUtils.GetRatio(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; - } - // This factor nerfs AR below 0 as extra safety measure private static double getTimeNerfFactor(double deltaTime) => Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1); @@ -235,14 +200,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators private static double boundBinarySearch(List arr, double target) { int low = 0; - int mid; int high = arr.Count; int result = -1; while (low < high) { - mid = low + (high - low) / 2; + int mid = low + (high - low) / 2; if (arr[mid].HitObject.StartTime >= target) { @@ -253,6 +217,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators } if (result == -1) return 0; + return arr[result].Overlapness; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index dc803af46f..6dd29a5466 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty /// The difficulty corresponding to the reading skill. Low AR branch. /// [JsonProperty("reading_low_ar_difficulty")] - public double ReadingDifficultyLowAR { get; set; } + public double ReadingDifficultyLowAr { get; set; } /// /// The difficulty corresponding to the flashlight skill. diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 223855890a..8e7e8e2166 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -43,13 +43,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedNotes = skills.OfType().First().RelevantNoteCount(); double flashlightRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * difficulty_multiplier; - double readingLowARRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * difficulty_multiplier; + double readingLowARRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; double aimDifficultyStrainCount = skills.OfType().First().CountTopWeightedStrains(); double speedDifficultyStrainCount = skills.OfType().First().CountTopWeightedStrains(); - double lowArDifficultyStrainCount = skills.OfType().First().CountTopWeightedStrains(); + double lowArDifficultyStrainCount = skills.OfType().First().CountTopWeightedStrains(); if (mods.Any(m => m is OsuModTouchDevice)) { @@ -70,15 +70,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); // Cognition - double readingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating); - double readingARPerformance = readingLowARPerformance; + double readingLowArPerformance = ReadingLowAr.DifficultyToPerformance(readingLowARRating); + double readingArPerformance = readingLowArPerformance; double potentialFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); double flashlightPerformance = mods.Any(h => h is OsuModFlashlight) ? potentialFlashlightPerformance : 0; - double flashlightARPerformance = Math.Pow(Math.Pow(flashlightPerformance, FL_SUM_POWER) + Math.Pow(readingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); + double flashlightArPerformance = Math.Pow(Math.Pow(flashlightPerformance, FL_SUM_POWER) + Math.Pow(readingArPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER); - double cognitionPerformance = flashlightARPerformance; + double cognitionPerformance = flashlightArPerformance; double mechanicalPerformance = Math.Pow(Math.Pow(aimPerformance, SUM_POWER) + Math.Pow(speedPerformance, SUM_POWER), 1.0 / SUM_POWER); // Limit cognition by full memorisation difficulty, what is assumed to be mechanicalPerformance + flashlightPerformance @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty AimDifficulty = aimRating, SpeedDifficulty = speedRating, SpeedNoteCount = speedNotes, - ReadingDifficultyLowAR = readingLowARRating, + ReadingDifficultyLowAr = readingLowARRating, FlashlightDifficulty = flashlightRating, SliderFactor = sliderFactor, AimDifficultStrainCount = aimDifficultyStrainCount, @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty new Aim(mods, false), new Speed(mods), new Flashlight(mods), - new ReadingLowAR(mods), + new ReadingLowAr(mods), }; return skills.ToArray(); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 3e7394b66a..492740deba 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -111,34 +111,31 @@ namespace osu.Game.Rulesets.Osu.Difficulty effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); } - double power = OsuDifficultyCalculator.SUM_POWER; + const double power = OsuDifficultyCalculator.SUM_POWER; double aimValue = computeAimValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes); double mechanicalValue = Math.Pow(Math.Pow(aimValue, power) + Math.Pow(speedValue, power), 1.0 / power); // Cognition - - double lowARValue = computeReadingLowARValue(score, osuAttributes); - - double readingARValue = lowARValue; + double lowArValue = computeReadingLowArValue(score, osuAttributes); double flashlightValue = computeFlashlightValue(score, osuAttributes); // Reduce AR reading bonus if FL is present - double flPower = OsuDifficultyCalculator.FL_SUM_POWER; - double flashlightARValue = score.Mods.Any(h => h is OsuModFlashlight) ? - Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower) : readingARValue; + const double fl_power = OsuDifficultyCalculator.FL_SUM_POWER; - double cognitionValue = flashlightARValue; + double flashlightArValue = score.Mods.Any(h => h is OsuModFlashlight) + ? Math.Pow(Math.Pow(flashlightValue, fl_power) + Math.Pow(lowArValue, fl_power), 1.0 / fl_power) + : lowArValue; + + double cognitionValue = flashlightArValue; cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, flashlightValue); double accuracyValue = computeAccuracyValue(score, osuAttributes); // Add cognition value without LP-sum cuz otherwise it makes balancing harder - double totalValue = - (Math.Pow(Math.Pow(mechanicalValue, power) + Math.Pow(accuracyValue, power), 1.0 / power) - + cognitionValue) * multiplier; + double totalValue = (Math.Pow(Math.Pow(mechanicalValue, power) + Math.Pow(accuracyValue, power), 1.0 / power) + cognitionValue) * multiplier; return new OsuPerformanceAttributes { @@ -297,10 +294,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty double visualBonus = 0.1 * DifficultyCalculationUtils.Logistic(attributes.ApproachRate - 8.0); // Buff if OD is way lower than AR - double ARODDelta = Math.Max(0, attributes.OverallDifficulty - attributes.ApproachRate); + double ArOdDelta = Math.Max(0, attributes.OverallDifficulty - attributes.ApproachRate); // This one is goes from 0.0 on delta=0 to 1.0 somewhere around delta=3.4 - double deltaBonus = (1 - Math.Pow(0.95, Math.Pow(ARODDelta, 4))); + double deltaBonus = (1 - Math.Pow(0.95, Math.Pow(ArOdDelta, 4))); // Nerf delta bonus on OD lower than 10 and 9 if (attributes.OverallDifficulty < 10) @@ -335,9 +332,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty return flashlightValue; } - private double computeReadingLowARValue(ScoreInfo score, OsuDifficultyAttributes attributes) + private double computeReadingLowArValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double readingValue = ReadingLowAR.DifficultyToPerformance(attributes.ReadingDifficultyLowAR); + double readingValue = ReadingLowAr.DifficultyToPerformance(attributes.ReadingDifficultyLowAr); // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 415b49d1ec..76ea6d6f9b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // Overlapness between current and prev to make streams have 0 buff double instantOverlapness = 0; - prevObject.OverlapValues?.TryGetValue(loopObj.Index, out instantOverlapness); + prevObject.OverlapValues.TryGetValue(loopObj.Index, out instantOverlapness); // Nerf overlaps on wide angles double angleFactor = 1; @@ -230,6 +230,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing if (cumulativeTimeWithoutCurrent >= cumulativeTimeWithCurrent) break; } + cumulativeTimeWithCurrent += historicTimes[i]; } @@ -284,9 +285,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private static double getAngleSimilarity(double angle1, double angle2) { double difference = Math.Abs(angle1 - angle2); - double threeshold = Math.PI / 12; + const double threeshold = Math.PI / 12; if (difference > threeshold) return 0; + return 1 - difference / threeshold; } @@ -297,18 +299,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double distance = Vector2.Distance(odho1.StackedPosition, odho2.StackedPosition); // Distance func is kinda slow for some reason double radius = odho1.BaseObject.Radius; - double distance_sqr = distance * distance; - double radius_sqr = radius * radius; + double distanceSqr = distance * distance; + double radiusSqr = radius * radius; if (distance > radius * 2) return 0; - double s1 = Math.Acos(distance / (2 * radius)) * radius_sqr; // Area of sector - double s2 = distance * Math.Sqrt(radius_sqr - distance_sqr / 4) / 2; // Area of triangle + double s1 = Math.Acos(distance / (2 * radius)) * radiusSqr; // Area of sector + double s2 = distance * Math.Sqrt(radiusSqr - distanceSqr / 4) / 2; // Area of triangle - double overlappingAreaNormalized = (s1 - s2) * 2 / (Math.PI * radius_sqr); + double overlappingAreaNormalized = (s1 - s2) * 2 / (Math.PI * radiusSqr); - // don't ask me how i get this value, looks oddly similar to PI - 3 + // Don't ask me how I got this value, looks oddly similar to PI - 3 const double stack_distance_ratio = 0.1414213562373; double perfectStackBuff = (stack_distance_ratio - distance / radius) / stack_distance_ratio; // scale from 0 on normal stack to 1 on perfect stack @@ -397,21 +399,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing OsuDifficultyHitObject? prevObj5 = (OsuDifficultyHitObject?)Previous(5); // 3-3 repeat - double similarity3_1 = getGeneralSimilarity(this, prevObj2); - double similarity3_2 = getGeneralSimilarity(prevObj0, prevObj3); - double similarity3_3 = getGeneralSimilarity(prevObj1, prevObj4); + double similarityBy3 = getGeneralSimilarity(this, prevObj2) * getGeneralSimilarity(prevObj0, prevObj3) * getGeneralSimilarity(prevObj1, prevObj4); - double similarity3_total = similarity3_1 * similarity3_2 * similarity3_3; - - // 4-4 repeat - double similarity4_1 = getGeneralSimilarity(this, prevObj3); - double similarity4_2 = getGeneralSimilarity(prevObj0, prevObj4); - double similarity4_3 = getGeneralSimilarity(prevObj1, prevObj5); - - double similarity4_total = similarity4_1 * similarity4_2 * similarity4_3; + // 4-4 repeat, only first 3 are checked, this is enough + double similarityBy4 = getGeneralSimilarity(this, prevObj3) * getGeneralSimilarity(prevObj0, prevObj4) * getGeneralSimilarity(prevObj1, prevObj5); // Bandaid to fix Rubik's Cube +EZ double wideness = 0; + if (Angle!.Value > Math.PI * 0.5) { // Goes from 0 to 1 as angle increasing from 90 degrees to 180 @@ -429,7 +424,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double predictability = Math.Cos(Math.Min(Math.PI / 2, 6 * adjustedAngleDifference)) * rhythmFactor; // Punish for big pattern similarity - return 1 - (1 - predictability) * (1 - Math.Max(similarity3_total, similarity4_total)); + return 1 - (1 - predictability) * (1 - Math.Max(similarityBy3, similarityBy4)); } private double getGeneralSimilarity(OsuDifficultyHitObject? o1, OsuDifficultyHitObject? o2) @@ -440,7 +435,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing if (o1.AngleSigned == null || o2.AngleSigned == null) return o1.AngleSigned == o2.AngleSigned ? 1 : 0; - double timeSimilarity = 1 - getTimeDifference(o1.StrainTime, o2.StrainTime); double angleDelta = Math.Abs((double)o1.AngleSigned - (double)o2.AngleSigned); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index b385ac8950..7d4e26868c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -12,12 +12,12 @@ using osu.Game.Rulesets.Osu.Difficulty.Evaluators; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { - public class ReadingLowAR : StrainSkill + public class ReadingLowAr : StrainSkill { private double skillMultiplier => 1.22; private double aimComponentMultiplier => 0.4; - public ReadingLowAR(Mod[] mods) + public ReadingLowAr(Mod[] mods) : base(mods) { } @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double strainDecayBase => 0.15; private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); - private double currentDensityAimStrain = 0; + private double currentDensityAimStrain; protected override double StrainValueAt(DifficultyHitObject current) { @@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double reducedNoteCount => 5; private double reducedNoteBaseline => 0.7; + public override double DifficultyValue() { // Sections with 0 difficulty are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871). @@ -70,6 +71,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return difficulty; } + public static double DifficultyToPerformance(double difficulty) => Math.Max( Math.Max(Math.Pow(difficulty, 1.5) * 20, Math.Pow(difficulty, 2) * 17.0), Math.Max(Math.Pow(difficulty, 3) * 10.5, Math.Pow(difficulty, 4) * 6.00));