From 198f35dc63463e5184daba871ea944935043493d Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 27 Jun 2024 15:45:58 +0300 Subject: [PATCH 01/32] ported my stat acc branch --- .../Difficulty/OsuPerformanceAttributes.cs | 8 + .../Difficulty/OsuPerformanceCalculator.cs | 302 +++++++++++++++--- .../Difficulty/Skills/Speed.cs | 24 +- .../osu.Game.Rulesets.Osu.csproj | 4 + 4 files changed, 294 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 0aeaf7669f..6ff8d97285 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -24,6 +24,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } + [JsonProperty("deviation")] + public double Deviation { get; set; } + + [JsonProperty("speed_deviation")] + public double SpeedDeviation { get; set; } + public override IEnumerable GetAttributesForDisplay() { foreach (var attribute in base.GetAttributesForDisplay()) @@ -33,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed); yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight); + yield return new PerformanceDisplayAttribute(nameof(Deviation), "Estimated UR", Deviation * 10); + yield return new PerformanceDisplayAttribute(nameof(SpeedDeviation), "Estimated UR (speed)", SpeedDeviation * 10); } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 18a4b8be0c..2408688501 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -4,7 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using MathNet.Numerics; +using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -17,13 +21,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double accuracy; private int scoreMaxCombo; + private int countGreat; private int countOk; private int countMeh; private int countMiss; + private int countSliderBreaks; + private int countSliderEndsDropped; private double effectiveMissCount; + private bool usingSliderAccuracy; + private double hitWindow300, hitWindow100, hitWindow50; + private double deviation, speedDeviation; + private double deviationARadjust; + public OsuPerformanceCalculator() : base(new OsuRuleset()) { @@ -39,7 +51,20 @@ 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(osuAttributes); + countSliderBreaks = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); + + usingSliderAccuracy = !score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); + + if (usingSliderAccuracy) + { + effectiveMissCount = countMiss; + countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); + } + else + { + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); + countSliderEndsDropped = Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo); + } double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -61,6 +86,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); } + var track = new TrackVirtual(1); + score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); + double clockRate = track.Rate; + + hitWindow300 = 80 - 6 * osuAttributes.OverallDifficulty; + hitWindow100 = (140 - 8 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; + hitWindow50 = (200 - 10 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; + + deviation = calculateDeviation(score, osuAttributes); + speedDeviation = calculateSpeedDeviation(score, osuAttributes); + + // Bonus for low AR to account for the fact that it's more difficult to get low UR on low AR + deviationARadjust = 0.475 + 0.7 / (1.0 + Math.Pow(1.73, 7.9 - osuAttributes.ApproachRate)); + double aimValue = computeAimValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes); double accuracyValue = computeAccuracyValue(score, osuAttributes); @@ -80,14 +119,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty Accuracy = accuracyValue, Flashlight = flashlightValue, EffectiveMissCount = effectiveMissCount, + Deviation = deviation, + SpeedDeviation = speedDeviation, Total = totalValue }; } private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { + if (deviation == double.PositiveInfinity) + return 0.0; + double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + // Rake tapping nerf + aimValue = adjustPerformanceWithUR(aimValue, deviation); + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); aimValue *= lengthBonus; @@ -122,25 +169,28 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + double estimateSliderEndsDropped = Math.Clamp(countSliderEndsDropped + countSliderBreaks * 2, 0, estimateDifficultSliders); double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } - aimValue *= accuracy; - // It is important to consider accuracy difficulty when scaling with accuracy. - aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + // Scale the aim value with deviation + aimValue *= SpecialFunctions.Erf(30 / (Math.Sqrt(2) * deviation * deviationARadjust)); + aimValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return aimValue; } private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (score.Mods.Any(h => h is OsuModRelax)) + if (score.Mods.Any(h => h is OsuModRelax) || speedDeviation == double.PositiveInfinity) return 0.0; double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + // Rake tapping nerf + speedValue = adjustSpeedWithUR(speedValue, speedDeviation); + double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); speedValue *= lengthBonus; @@ -168,46 +218,33 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } - // Calculate accuracy assuming the worst case scenario - double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; - double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); - double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); - double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); - double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); - - // Scale the speed value with accuracy and OD. - speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); - - // Scale the speed value with # of 50s to punish doubletapping. - speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); + // Scale the speed value with speed deviation + speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * speedDeviation * deviationARadjust)); + speedValue *= 0.95 + Math.Pow(100.0 / 9, 2) / 750; // OD 11 SS stays the same. return speedValue; } private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (score.Mods.Any(h => h is OsuModRelax)) + if (deviation == double.PositiveInfinity || score.Mods.Any(h => h is OsuModRelax) || deviation == double.PositiveInfinity) 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; + if (usingSliderAccuracy) amountHitObjectsWithAccuracy += attributes.SliderCount; - if (amountHitObjectsWithAccuracy > 0) - betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); - else - betterAccuracyPercentage = 0; + double liveLengthBonus = Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); + double threshold = 1000 * Math.Pow(1.15, 1 / 0.3); // Number of objects until length bonus caps. - // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points. - if (betterAccuracyPercentage < 0) - betterAccuracyPercentage = 0; + // Some fancy stuff to make curve similar to live + double scaling = Math.Sqrt(2) * Math.Log(1.52163) * SpecialFunctions.ErfInv(1 / (1 + 1 / Math.Min(amountHitObjectsWithAccuracy, threshold))) / 6; - // Lots of arbitrary values from testing. - // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution. - double accuracyValue = Math.Pow(1.52163, attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; + // Accuracy pp formula that's roughly the same as live. + double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * deviation * deviationARadjust); - // 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)); + // Punish very low amount of hits additionally to prevent big pp values right at the start of the map + if (amountHitObjectsWithAccuracy < 30) + accuracyValue *= Math.Sqrt((double)amountHitObjectsWithAccuracy / 30); // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) @@ -223,11 +260,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (!score.Mods.Any(h => h is OsuModFlashlight)) + if (!score.Mods.Any(h => h is OsuModFlashlight) || deviation == double.PositiveInfinity) return 0.0; double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; + // Rake tapping nerf + flashlightValue = adjustPerformanceWithUR(flashlightValue, deviation); + // 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(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); @@ -238,10 +278,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); - // Scale the flashlight value with accuracy _slightly_. - flashlightValue *= 0.5 + accuracy / 2.0; - // It is important to also consider accuracy difficulty when doing that. - flashlightValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; + // Scale the flashlight value with deviation + flashlightValue *= SpecialFunctions.Erf(50 / (Math.Sqrt(2) * deviation * deviationARadjust)); + flashlightValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return flashlightValue; } @@ -264,7 +303,194 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, comboBasedMissCount); } + /// + /// Estimates the player's tap deviation based on the OD, number of circles and sliders, and number of 300s, 100s, 50s, and misses, + /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings + /// will always return the same deviation. Sliders are treated as circles with a 50 hit window. Misses are ignored because they are usually due to misaiming. + /// 300s and 100s are assumed to follow a normal distribution, whereas 50s are assumed to follow a uniform distribution. + /// + private double calculateDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) + { + if (totalSuccessfulHits == 0) + return double.PositiveInfinity; + + int accuracyObjectCount = attributes.HitCircleCount; + if (usingSliderAccuracy) accuracyObjectCount += attributes.SliderCount; + + int missCountCircles = Math.Min(countMiss, accuracyObjectCount); + int mehCountCircles = Math.Min(countMeh, accuracyObjectCount - missCountCircles); + int okCountCircles = Math.Min(countOk, accuracyObjectCount - missCountCircles - mehCountCircles); + int greatCountCircles = Math.Max(0, accuracyObjectCount - missCountCircles - mehCountCircles - okCountCircles); + + // Assume 100s, 50s, and misses happen on circles. If there are less non-300s on circles than 300s, + // compute the deviation on circles. + if (usingSliderAccuracy || greatCountCircles > 0) + { + //// The probability that a player hits a circle is unknown, but we can estimate it to be + //// the number of greats on circles divided by the number of circles, and then add one + //// to the number of circles as a bias correction. + double n = Math.Max(1, accuracyObjectCount - missCountCircles - mehCountCircles); + const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). + + // Proportion of greats hit on circles, ignoring misses and 50s. + double p = greatCountCircles / n; + + // We can be 99% confident that p is at least this value. + double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); + + // Compute the deviation assuming 300s and 100s are normally distributed, and 50s are uniformly distributed. + // Begin with 300s and 100s first. Ignoring 50s, we can be 99% confident that the deviation is not higher than: + double estimatedDeviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); + + double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / estimatedDeviation, 2)) + / (estimatedDeviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * estimatedDeviation))); + + estimatedDeviation *= Math.Sqrt(1 - randomValue); + + // If precision is not enough - use limit value + if (pLowerBound == 0 || randomValue >= 1) + estimatedDeviation = hitWindow100 / Math.Sqrt(3); + + // Then compute the variance for 50s. + double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; + + // Find the total deviation. + estimatedDeviation = Math.Sqrt(((greatCountCircles + okCountCircles) * Math.Pow(estimatedDeviation, 2) + mehCountCircles * mehVariance) / (greatCountCircles + okCountCircles + mehCountCircles)); + + // Adjust by 0.9 to account for the fact that it's higher bound UR value + return estimatedDeviation * 0.9; + } + + // If there are more non-300s than there are circles, compute the deviation on sliders instead. + // Here, all that matters is whether or not the slider was missed, since it is impossible + // to get a 100 or 50 on a slider by mis-tapping it. + int sliderCount = attributes.SliderCount; + int missCountSliders = Math.Min(sliderCount, countMiss - missCountCircles); + int greatCountSliders = sliderCount - missCountSliders; + + // We only get here if nothing was hit. In this case, there is no estimate for deviation. + // Note that this is never negative, so checking if this is only equal to 0 makes sense. + if (greatCountSliders == 0) + { + return double.PositiveInfinity; + } + + double greatProbabilitySlider = greatCountSliders / (sliderCount + 1.0); + double deviationOnSliders = hitWindow50 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbabilitySlider)); + + return deviationOnSliders; + } + + /// + /// Does the same as , but only for notes and inaccuracies that are relevant to speed difficulty. + /// Treats all difficult speed notes as circles, so this method can sometimes return a lower deviation than . + /// This is fine though, since this method is only used to scale speed pp. + /// + private double calculateSpeedDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) + { + if (totalSuccessfulHits == 0) + return double.PositiveInfinity; + + // Calculate accuracy assuming the worst case scenario + double speedNoteCount = attributes.SpeedNoteCount; + + double relevantTotalDiff = totalHits - speedNoteCount; + + double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); + double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); + double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); + double relevantCountMiss = Math.Max(0, countMiss - Math.Max(0, relevantTotalDiff - countGreat - countOk - countMeh)); + + if (usingSliderAccuracy) + { + double greatsRatio = relevantCountGreat / speedNoteCount; + double mistapRatio = 1 - greatsRatio; + + // Assume sliders are 2 times easier to acc than streams + double hitcircleRatio = attributes.HitCircleCount / (attributes.HitCircleCount + attributes.SliderCount / 2.0); + mistapRatio *= hitcircleRatio; + + // This can't get higher than total value + double adjustedGreatsRatio = Math.Min(1 - mistapRatio, (double)countGreat / totalHits); + + double mistapsMultiplier = (greatsRatio == 1) ? 0 : (1 - adjustedGreatsRatio) / (1 - greatsRatio); + + relevantCountGreat = speedNoteCount * adjustedGreatsRatio; + relevantCountOk *= mistapsMultiplier; + relevantCountMeh *= mistapsMultiplier; + relevantCountMiss *= mistapsMultiplier; + } + + if (relevantCountMiss < speedNoteCount) + { + double n = Math.Max(1, speedNoteCount - relevantCountMiss - relevantCountMeh); + const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). + + // Proportion of greats hit on circles, ignoring misses and 50s. + double p = relevantCountGreat / n; + + // We can be 99% confident that p is at least this value. + double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); + + // Compute the deviation assuming 300s and 100s are normally distributed, and 50s are uniformly distributed. + // Begin with 300s and 100s first. Ignoring 50s, we can be 99% confident that the deviation is not higher than: + double estimatedDeviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); + + double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / estimatedDeviation, 2)) + / (estimatedDeviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * estimatedDeviation))); + + estimatedDeviation *= Math.Sqrt(1 - randomValue); + + // If precision is not enough - use limit value + if (pLowerBound == 0 || randomValue >= 1) + estimatedDeviation = hitWindow100 / Math.Sqrt(3); + + // Then compute the variance for 50s. + double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; + + // Find the total deviation. + estimatedDeviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(estimatedDeviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh)); + + // Adjust by 0.9 to account for the fact that it's higher bound UR value + return estimatedDeviation * 0.9; + } + + return double.PositiveInfinity; + } + + // https://www.desmos.com/calculator/no1pv5gv5g + private double adjustSpeedWithUR(double speedValue, double UR) + { + // Starting from this pp amount - penalty will be applied + double abusePoint = Math.Pow(400 / UR, 2); + + if (speedValue <= abusePoint) + return speedValue; + + // Descale values to make log curve look correctly + const double scale = 50; + speedValue = scale * (Math.Log(speedValue / scale + 1 - abusePoint / scale) + abusePoint / scale); + + return speedValue; + } + + private double adjustPerformanceWithUR(double performanceValue, double UR) + { + // Starting from this pp amount - penalty will be applied + double abusePoint = Math.Pow(600 / UR, 2); + + if (performanceValue <= abusePoint) + return performanceValue; + + // Descale value to make log curve look correctly + const double scale = 50; + performanceValue = scale * (Math.Log(performanceValue / scale + 1 - abusePoint / scale) + abusePoint / scale); + + return performanceValue; + } + private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); private int totalHits => countGreat + countOk + countMeh + countMiss; + private int totalSuccessfulHits => countGreat + countOk + countMeh; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 40aac013ab..840d9cb368 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -6,8 +6,10 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using System.Collections.Generic; using System.Linq; +using osu.Game.Rulesets.Osu.Mods; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -20,12 +22,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double strainDecayBase => 0.3; private double currentStrain; + private double currentStrainNoDistance; private double currentRhythm; protected override int ReducedSectionCount => 5; protected override double DifficultyMultiplier => 1.04; - private readonly List objectStrains = new List(); + // Used to calculate acc punishment + private readonly List objectStrainsNoDistance = new List(); public Speed(Mod[] mods) : base(mods) @@ -41,26 +45,34 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; + // Disregard distance when computing the number of speed notes. + double travelDistance = current.Index > 0 ? ((OsuDifficultyHitObject)current.Previous(0)).TravelDistance : 0; + double distance = Math.Min(125, travelDistance + ((OsuDifficultyHitObject)current).MinimumJumpDistance); + + currentStrainNoDistance *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); + currentStrainNoDistance += SpeedEvaluator.EvaluateDifficultyOf(current) / (1 + Math.Pow(distance / 125, 3.5)) * skillMultiplier; + currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; + double totalStrainNoDistance = currentStrainNoDistance * currentRhythm; - objectStrains.Add(totalStrain); + objectStrainsNoDistance.Add(totalStrainNoDistance); return totalStrain; } public double RelevantNoteCount() { - if (objectStrains.Count == 0) + if (objectStrainsNoDistance.Count == 0) return 0; - double maxStrain = objectStrains.Max(); + double maxStrain = objectStrainsNoDistance.Max(); if (maxStrain == 0) return 0; - return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); + return objectStrainsNoDistance.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); } } } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 7817d55f57..a3ea45c208 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -12,6 +12,10 @@ true + + + + From 0788f18406be11baa9518e2eefd09cf00f808b01 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 27 Jun 2024 16:44:09 +0300 Subject: [PATCH 02/32] fixed NaN issue in deviation calc --- .../Difficulty/OsuPerformanceCalculator.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 2408688501..87eee3ae22 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -322,6 +322,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty int okCountCircles = Math.Min(countOk, accuracyObjectCount - missCountCircles - mehCountCircles); int greatCountCircles = Math.Max(0, accuracyObjectCount - missCountCircles - mehCountCircles - okCountCircles); + if (greatCountCircles + okCountCircles + mehCountCircles <= 0) + return double.PositiveInfinity; + // Assume 100s, 50s, and misses happen on circles. If there are less non-300s on circles than 300s, // compute the deviation on circles. if (usingSliderAccuracy || greatCountCircles > 0) @@ -421,41 +424,39 @@ namespace osu.Game.Rulesets.Osu.Difficulty relevantCountMiss *= mistapsMultiplier; } - if (relevantCountMiss < speedNoteCount) - { - double n = Math.Max(1, speedNoteCount - relevantCountMiss - relevantCountMeh); - const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). + if (relevantCountGreat + relevantCountOk + relevantCountMeh <= 0) + return double.PositiveInfinity; - // Proportion of greats hit on circles, ignoring misses and 50s. - double p = relevantCountGreat / n; + double n = Math.Max(1, speedNoteCount - relevantCountMiss - relevantCountMeh); + const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). - // We can be 99% confident that p is at least this value. - double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); + // Proportion of greats hit on circles, ignoring misses and 50s. + double p = relevantCountGreat / n; - // Compute the deviation assuming 300s and 100s are normally distributed, and 50s are uniformly distributed. - // Begin with 300s and 100s first. Ignoring 50s, we can be 99% confident that the deviation is not higher than: - double estimatedDeviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); + // We can be 99% confident that p is at least this value. + double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); - double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / estimatedDeviation, 2)) - / (estimatedDeviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * estimatedDeviation))); + // Compute the deviation assuming 300s and 100s are normally distributed, and 50s are uniformly distributed. + // Begin with 300s and 100s first. Ignoring 50s, we can be 99% confident that the deviation is not higher than: + double estimatedDeviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); - estimatedDeviation *= Math.Sqrt(1 - randomValue); + double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / estimatedDeviation, 2)) + / (estimatedDeviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * estimatedDeviation))); - // If precision is not enough - use limit value - if (pLowerBound == 0 || randomValue >= 1) - estimatedDeviation = hitWindow100 / Math.Sqrt(3); + estimatedDeviation *= Math.Sqrt(1 - randomValue); - // Then compute the variance for 50s. - double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; + // If precision is not enough - use limit value + if (pLowerBound == 0 || randomValue >= 1) + estimatedDeviation = hitWindow100 / Math.Sqrt(3); - // Find the total deviation. - estimatedDeviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(estimatedDeviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh)); + // Then compute the variance for 50s. + double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; - // Adjust by 0.9 to account for the fact that it's higher bound UR value - return estimatedDeviation * 0.9; - } + // Find the total deviation. + estimatedDeviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(estimatedDeviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh)); - return double.PositiveInfinity; + // Adjust by 0.9 to account for the fact that it's higher bound UR value + return estimatedDeviation * 0.9; } // https://www.desmos.com/calculator/no1pv5gv5g From 91e377de45975cae0ff8ba6c92779331ff9a20b7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 27 Jun 2024 21:16:48 +0300 Subject: [PATCH 03/32] added bandaid for huis --- .../Difficulty/OsuPerformanceCalculator.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 87eee3ae22..e00b9b4012 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -55,6 +55,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty usingSliderAccuracy = !score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); + // isLegacy == true on stable scores + bool isLegacy = !score.MaximumStatistics.ContainsKey(HitResult.LargeTickMiss); + + // Make stable scores always CL + usingSliderAccuracy &= !isLegacy; + if (usingSliderAccuracy) { effectiveMissCount = countMiss; From 588a98335b74696a7bdda58ecfc0ec89c2e580d1 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 27 Jun 2024 21:20:16 +0300 Subject: [PATCH 04/32] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index e00b9b4012..6cbe873467 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty bool isLegacy = !score.MaximumStatistics.ContainsKey(HitResult.LargeTickMiss); // Make stable scores always CL - usingSliderAccuracy &= !isLegacy; + if (isLegacy) usingSliderAccuracy = false; if (usingSliderAccuracy) { From 2f94eea9505e9a4d6894c5c53154a0e06a60bded Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 28 Jun 2024 00:14:30 +0300 Subject: [PATCH 05/32] removed speed object count independene from distance --- .../Difficulty/Skills/Speed.cs | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 840d9cb368..88021a5dda 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -22,14 +22,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double strainDecayBase => 0.3; private double currentStrain; - private double currentStrainNoDistance; private double currentRhythm; protected override int ReducedSectionCount => 5; protected override double DifficultyMultiplier => 1.04; // Used to calculate acc punishment - private readonly List objectStrainsNoDistance = new List(); + private readonly List objectStrains = new List(); public Speed(Mod[] mods) : base(mods) @@ -45,34 +44,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier; - // Disregard distance when computing the number of speed notes. - double travelDistance = current.Index > 0 ? ((OsuDifficultyHitObject)current.Previous(0)).TravelDistance : 0; - double distance = Math.Min(125, travelDistance + ((OsuDifficultyHitObject)current).MinimumJumpDistance); - - currentStrainNoDistance *= strainDecay(((OsuDifficultyHitObject)current).StrainTime); - currentStrainNoDistance += SpeedEvaluator.EvaluateDifficultyOf(current) / (1 + Math.Pow(distance / 125, 3.5)) * skillMultiplier; - currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; - double totalStrainNoDistance = currentStrainNoDistance * currentRhythm; - - objectStrainsNoDistance.Add(totalStrainNoDistance); + objectStrains.Add(totalStrain); return totalStrain; } public double RelevantNoteCount() { - if (objectStrainsNoDistance.Count == 0) + if (objectStrains.Count == 0) return 0; - double maxStrain = objectStrainsNoDistance.Max(); + double maxStrain = objectStrains.Max(); if (maxStrain == 0) return 0; - return objectStrainsNoDistance.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); + return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); } } } From 9aba0f6bb87c21128985b2e3ca7d5ba0b9a3adda Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 28 Jun 2024 01:52:47 +0300 Subject: [PATCH 06/32] changed rake nerf and made UR on sliders more stable --- .../Difficulty/OsuPerformanceCalculator.cs | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6cbe873467..f6184affee 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -100,12 +100,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty hitWindow100 = (140 - 8 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; hitWindow50 = (200 - 10 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; - deviation = calculateDeviation(score, osuAttributes); - speedDeviation = calculateSpeedDeviation(score, osuAttributes); - // Bonus for low AR to account for the fact that it's more difficult to get low UR on low AR deviationARadjust = 0.475 + 0.7 / (1.0 + Math.Pow(1.73, 7.9 - osuAttributes.ApproachRate)); + deviation = calculateDeviation(score, osuAttributes) * deviationARadjust; + speedDeviation = calculateSpeedDeviation(score, osuAttributes) * deviationARadjust; + double aimValue = computeAimValue(score, osuAttributes); double speedValue = computeSpeedValue(score, osuAttributes); double accuracyValue = computeAccuracyValue(score, osuAttributes); @@ -125,8 +125,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty Accuracy = accuracyValue, Flashlight = flashlightValue, EffectiveMissCount = effectiveMissCount, - Deviation = deviation, - SpeedDeviation = speedDeviation, + Deviation = deviation / deviationARadjust, + SpeedDeviation = speedDeviation / deviationARadjust, Total = totalValue }; } @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } // Scale the aim value with deviation - aimValue *= SpecialFunctions.Erf(30 / (Math.Sqrt(2) * deviation * deviationARadjust)); + aimValue *= SpecialFunctions.Erf(30 / (Math.Sqrt(2) * deviation)); aimValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return aimValue; @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; // Rake tapping nerf - speedValue = adjustSpeedWithUR(speedValue, speedDeviation); + speedValue = adjustSpeedWithUR(speedValue, speedDeviation / deviationARadjust); double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } // Scale the speed value with speed deviation - speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * speedDeviation * deviationARadjust)); + speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * speedDeviation)); speedValue *= 0.95 + Math.Pow(100.0 / 9, 2) / 750; // OD 11 SS stays the same. return speedValue; @@ -246,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double scaling = Math.Sqrt(2) * Math.Log(1.52163) * SpecialFunctions.ErfInv(1 / (1 + 1 / Math.Min(amountHitObjectsWithAccuracy, threshold))) / 6; // Accuracy pp formula that's roughly the same as live. - double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * deviation * deviationARadjust); + double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * deviation); // Punish very low amount of hits additionally to prevent big pp values right at the start of the map if (amountHitObjectsWithAccuracy < 30) @@ -285,7 +285,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); // Scale the flashlight value with deviation - flashlightValue *= SpecialFunctions.Erf(50 / (Math.Sqrt(2) * deviation * deviationARadjust)); + flashlightValue *= SpecialFunctions.Erf(50 / (Math.Sqrt(2) * deviation)); flashlightValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return flashlightValue; @@ -331,9 +331,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (greatCountCircles + okCountCircles + mehCountCircles <= 0) return double.PositiveInfinity; + double deviation = double.PositiveInfinity; + // Assume 100s, 50s, and misses happen on circles. If there are less non-300s on circles than 300s, // compute the deviation on circles. - if (usingSliderAccuracy || greatCountCircles > 0) + if (accuracyObjectCount > 0) { //// The probability that a player hits a circle is unknown, but we can estimate it to be //// the number of greats on circles divided by the number of circles, and then add one @@ -349,45 +351,46 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Compute the deviation assuming 300s and 100s are normally distributed, and 50s are uniformly distributed. // Begin with 300s and 100s first. Ignoring 50s, we can be 99% confident that the deviation is not higher than: - double estimatedDeviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); + deviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); - double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / estimatedDeviation, 2)) - / (estimatedDeviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * estimatedDeviation))); + double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / deviation, 2)) + / (deviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * deviation))); - estimatedDeviation *= Math.Sqrt(1 - randomValue); + deviation *= Math.Sqrt(1 - randomValue); // If precision is not enough - use limit value if (pLowerBound == 0 || randomValue >= 1) - estimatedDeviation = hitWindow100 / Math.Sqrt(3); + deviation = hitWindow100 / Math.Sqrt(3); // Then compute the variance for 50s. double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; // Find the total deviation. - estimatedDeviation = Math.Sqrt(((greatCountCircles + okCountCircles) * Math.Pow(estimatedDeviation, 2) + mehCountCircles * mehVariance) / (greatCountCircles + okCountCircles + mehCountCircles)); + deviation = Math.Sqrt(((greatCountCircles + okCountCircles) * Math.Pow(deviation, 2) + mehCountCircles * mehVariance) / (greatCountCircles + okCountCircles + mehCountCircles)); // Adjust by 0.9 to account for the fact that it's higher bound UR value - return estimatedDeviation * 0.9; + deviation *= 0.9; } + if (usingSliderAccuracy) return deviation; + // If there are more non-300s than there are circles, compute the deviation on sliders instead. // Here, all that matters is whether or not the slider was missed, since it is impossible // to get a 100 or 50 on a slider by mis-tapping it. + double deviationOnSliders = double.PositiveInfinity; int sliderCount = attributes.SliderCount; int missCountSliders = Math.Min(sliderCount, countMiss - missCountCircles); int greatCountSliders = sliderCount - missCountSliders; // We only get here if nothing was hit. In this case, there is no estimate for deviation. // Note that this is never negative, so checking if this is only equal to 0 makes sense. - if (greatCountSliders == 0) + if (greatCountSliders > 0) { - return double.PositiveInfinity; + double greatProbabilitySlider = greatCountSliders / (sliderCount + 1.0); + deviationOnSliders = hitWindow50 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbabilitySlider)); } - double greatProbabilitySlider = greatCountSliders / (sliderCount + 1.0); - double deviationOnSliders = hitWindow50 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbabilitySlider)); - - return deviationOnSliders; + return Math.Min(deviation, deviationOnSliders); } /// @@ -444,32 +447,32 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Compute the deviation assuming 300s and 100s are normally distributed, and 50s are uniformly distributed. // Begin with 300s and 100s first. Ignoring 50s, we can be 99% confident that the deviation is not higher than: - double estimatedDeviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); + double deviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); - double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / estimatedDeviation, 2)) - / (estimatedDeviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * estimatedDeviation))); + double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / deviation, 2)) + / (deviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * deviation))); - estimatedDeviation *= Math.Sqrt(1 - randomValue); + deviation *= Math.Sqrt(1 - randomValue); // If precision is not enough - use limit value if (pLowerBound == 0 || randomValue >= 1) - estimatedDeviation = hitWindow100 / Math.Sqrt(3); + deviation = hitWindow100 / Math.Sqrt(3); // Then compute the variance for 50s. double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; // Find the total deviation. - estimatedDeviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(estimatedDeviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh)); + deviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(deviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh)); // Adjust by 0.9 to account for the fact that it's higher bound UR value - return estimatedDeviation * 0.9; + return deviation * 0.9; } // https://www.desmos.com/calculator/no1pv5gv5g private double adjustSpeedWithUR(double speedValue, double UR) { // Starting from this pp amount - penalty will be applied - double abusePoint = Math.Pow(400 / UR, 2); + double abusePoint = 100 + 220 * Math.Pow(20 / UR, 4.5); if (speedValue <= abusePoint) return speedValue; @@ -484,7 +487,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double adjustPerformanceWithUR(double performanceValue, double UR) { // Starting from this pp amount - penalty will be applied - double abusePoint = Math.Pow(600 / UR, 2); + double abusePoint = 200 + 260 * Math.Pow(20 / UR, 4.5); if (performanceValue <= abusePoint) return performanceValue; From 5196891553bf31f1ad3226472711619b254414ca Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 28 Jun 2024 14:23:10 +0300 Subject: [PATCH 07/32] removed CL huis bandaid --- .../Difficulty/OsuPerformanceCalculator.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index f6184affee..7ccb715c34 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -55,12 +55,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty usingSliderAccuracy = !score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); - // isLegacy == true on stable scores - bool isLegacy = !score.MaximumStatistics.ContainsKey(HitResult.LargeTickMiss); - - // Make stable scores always CL - if (isLegacy) usingSliderAccuracy = false; - if (usingSliderAccuracy) { effectiveMissCount = countMiss; From 907b63866303f963e57066c8b98827e7ffc340e7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 28 Jun 2024 14:44:26 +0300 Subject: [PATCH 08/32] refactoring --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 88021a5dda..cdab3789f8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -6,10 +6,8 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using System.Linq; -using osu.Game.Rulesets.Osu.Mods; using System.Collections.Generic; -using osu.Game.Rulesets.Osu.Objects; +using System.Linq; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -28,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DifficultyMultiplier => 1.04; // Used to calculate acc punishment - private readonly List objectStrains = new List(); + private readonly List objectStrains = []; public Speed(Mod[] mods) : base(mods) @@ -47,6 +45,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current); double totalStrain = currentStrain * currentRhythm; + objectStrains.Add(totalStrain); return totalStrain; From ce41fc58e965ea27e9ca816e57663dd9b8892b2e Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 28 Jun 2024 20:04:53 +0300 Subject: [PATCH 09/32] anti-rake curve update --- 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 7ccb715c34..881dea721d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double adjustSpeedWithUR(double speedValue, double UR) { // Starting from this pp amount - penalty will be applied - double abusePoint = 100 + 220 * Math.Pow(20 / UR, 4.5); + double abusePoint = 100 + 260 * Math.Pow(20 / UR, 5.8); if (speedValue <= abusePoint) return speedValue; @@ -481,7 +481,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double adjustPerformanceWithUR(double performanceValue, double UR) { // Starting from this pp amount - penalty will be applied - double abusePoint = 200 + 260 * Math.Pow(20 / UR, 4.5); + double abusePoint = 200 + 260 * Math.Pow(20 / UR, 5.8); if (performanceValue <= abusePoint) return performanceValue; From 41a3a9b15b8460dfd6b9932816c4a55c842657fa Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Fri, 28 Jun 2024 22:58:15 +0300 Subject: [PATCH 10/32] made aim rake nerf more lenient --- 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 881dea721d..b30d831d55 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -481,13 +481,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double adjustPerformanceWithUR(double performanceValue, double UR) { // Starting from this pp amount - penalty will be applied - double abusePoint = 200 + 260 * Math.Pow(20 / UR, 5.8); + double abusePoint = 150 + 360 * Math.Pow(20 / UR, 4.2); if (performanceValue <= abusePoint) return performanceValue; // Descale value to make log curve look correctly - const double scale = 50; + const double scale = 150; performanceValue = scale * (Math.Log(performanceValue / scale + 1 - abusePoint / scale) + abusePoint / scale); return performanceValue; From 92807d58f09af46f7095ac3cfa5f881780b57c52 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 30 Jun 2024 20:41:22 +0300 Subject: [PATCH 11/32] reworked anti-rake, again --- .../Difficulty/OsuPerformanceCalculator.cs | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b30d831d55..79dd856746 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -100,10 +100,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty deviation = calculateDeviation(score, osuAttributes) * deviationARadjust; speedDeviation = calculateSpeedDeviation(score, osuAttributes) * deviationARadjust; - double aimValue = computeAimValue(score, osuAttributes); - double speedValue = computeSpeedValue(score, osuAttributes); + // Use adjusted deviation to not nerf EZHT aim maps + double totalAntiRakeMultiplier = calculateTotalRakeNerf(osuAttributes, deviation); + + // Use raw speed deviation to prevent abuse of deviation AR adjust with EZDT + double speedAntiRakeMultiplier = calculateSpeedRakeNerf(osuAttributes, speedDeviation / deviationARadjust); + speedAntiRakeMultiplier = Math.Min(speedAntiRakeMultiplier, totalAntiRakeMultiplier); + + double aimValue = computeAimValue(score, osuAttributes) * totalAntiRakeMultiplier; + double speedValue = computeSpeedValue(score, osuAttributes) * speedAntiRakeMultiplier; double accuracyValue = computeAccuracyValue(score, osuAttributes); - double flashlightValue = computeFlashlightValue(score, osuAttributes); + double flashlightValue = computeFlashlightValue(score, osuAttributes) * totalAntiRakeMultiplier; + double totalValue = Math.Pow( Math.Pow(aimValue, 1.1) + @@ -132,9 +140,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; - // Rake tapping nerf - aimValue = adjustPerformanceWithUR(aimValue, deviation); - double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); aimValue *= lengthBonus; @@ -188,9 +193,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; - // Rake tapping nerf - speedValue = adjustSpeedWithUR(speedValue, speedDeviation / deviationARadjust); - double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); speedValue *= lengthBonus; @@ -265,9 +267,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; - // Rake tapping nerf - flashlightValue = adjustPerformanceWithUR(flashlightValue, deviation); - // 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(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875)); @@ -462,35 +461,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty return deviation * 0.9; } - // https://www.desmos.com/calculator/no1pv5gv5g - private double adjustSpeedWithUR(double speedValue, double UR) + // Calculates multiplier for speed accounting for rake based on the deviation and speed difficulty + // https://www.desmos.com/calculator/puc1mzdtfv + private double calculateSpeedRakeNerf(OsuDifficultyAttributes attributes, double rawSpeedDeviation) { + // Base speed value + double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + // Starting from this pp amount - penalty will be applied - double abusePoint = 100 + 260 * Math.Pow(20 / UR, 5.8); + double abusePoint = 100 + 260 * Math.Pow(20 / rawSpeedDeviation, 5.8); if (speedValue <= abusePoint) - return speedValue; + return 1.0; - // Descale values to make log curve look correctly + // Use log curve to make additional rise in difficulty unimpactful. Rescale values to make curve have correct steepness const double scale = 50; - speedValue = scale * (Math.Log(speedValue / scale + 1 - abusePoint / scale) + abusePoint / scale); + double adjustedSpeedValue = scale * (Math.Log((speedValue - abusePoint) / scale + 1) + abusePoint / scale); - return speedValue; + return adjustedSpeedValue / speedValue; } - private double adjustPerformanceWithUR(double performanceValue, double UR) + // Calculates multiplier for total pp accounting for rake based on the deviation and sliderless aim and speed difficulty + private double calculateTotalRakeNerf(OsuDifficultyAttributes attributes, double deviation) { + // Base values + double aimNoSlidersValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty * attributes.SliderFactor / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double totalValue = Math.Pow(Math.Pow(aimNoSlidersValue, 1.1) + Math.Pow(speedValue, 1.1), 1 / 1.1); + // Starting from this pp amount - penalty will be applied - double abusePoint = 150 + 360 * Math.Pow(20 / UR, 4.2); + double abusePoint = 200 + 600 * Math.Pow(20 / deviation, 4.2); - if (performanceValue <= abusePoint) - return performanceValue; + if (totalValue <= abusePoint) + return 1.0; - // Descale value to make log curve look correctly - const double scale = 150; - performanceValue = scale * (Math.Log(performanceValue / scale + 1 - abusePoint / scale) + abusePoint / scale); + // Use relax penalty after the point to make values grow slower but still noticeably + double adjustedTotalValue = abusePoint + Math.Pow(0.9, 3) * (totalValue - abusePoint); - return performanceValue; + return adjustedTotalValue / totalValue; } private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); From e90d79babd4e031ca6fee2364c5acd9865c21a05 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 1 Jul 2024 15:30:24 +0300 Subject: [PATCH 12/32] simplified difficulty to performance formulas --- .../Difficulty/OsuPerformanceCalculator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 79dd856746..92b20b5dd1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -466,7 +466,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double calculateSpeedRakeNerf(OsuDifficultyAttributes attributes, double rawSpeedDeviation) { // Base speed value - double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = 4 * Math.Pow(attributes.SpeedDifficulty, 3); // Starting from this pp amount - penalty will be applied double abusePoint = 100 + 260 * Math.Pow(20 / rawSpeedDeviation, 5.8); @@ -485,8 +485,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double calculateTotalRakeNerf(OsuDifficultyAttributes attributes, double deviation) { // Base values - double aimNoSlidersValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty * attributes.SliderFactor / 0.0675) - 4.0, 3.0) / 100000.0; - double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double aimNoSlidersValue = 4 * Math.Pow(attributes.AimDifficulty * attributes.SliderFactor, 3); + double speedValue = 4 * Math.Pow(attributes.SpeedDifficulty, 3); double totalValue = Math.Pow(Math.Pow(aimNoSlidersValue, 1.1) + Math.Pow(speedValue, 1.1), 1 / 1.1); // Starting from this pp amount - penalty will be applied From 7f57226bdb012613146a1fdb1e771e0b480d4f8d Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 1 Jul 2024 17:21:58 +0300 Subject: [PATCH 13/32] big amount of refactoring - put shared logic in one function - improved sanity checks - improved estimation of UR on sliders --- .../Difficulty/OsuPerformanceCalculator.cs | 154 ++++++++---------- 1 file changed, 65 insertions(+), 89 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 92b20b5dd1..e5ee0715aa 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Bonus for low AR to account for the fact that it's more difficult to get low UR on low AR deviationARadjust = 0.475 + 0.7 / (1.0 + Math.Pow(1.73, 7.9 - osuAttributes.ApproachRate)); - deviation = calculateDeviation(score, osuAttributes) * deviationARadjust; + deviation = calculateTotalDeviation(score, osuAttributes) * deviationARadjust; speedDeviation = calculateSpeedDeviation(score, osuAttributes) * deviationARadjust; // Use adjusted deviation to not nerf EZHT aim maps @@ -245,8 +245,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * deviation); // Punish very low amount of hits additionally to prevent big pp values right at the start of the map - if (amountHitObjectsWithAccuracy < 30) - accuracyValue *= Math.Sqrt((double)amountHitObjectsWithAccuracy / 30); + double amountOfHits = Math.Clamp(totalSuccessfulHits - attributes.SpinnerCount, 0, amountHitObjectsWithAccuracy); + if (amountOfHits < 30) + accuracyValue *= Math.Sqrt(amountOfHits / 30); // Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given. if (score.Mods.Any(m => m is OsuModBlinds)) @@ -303,12 +304,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty } /// - /// Estimates the player's tap deviation based on the OD, number of circles and sliders, and number of 300s, 100s, 50s, and misses, - /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings - /// will always return the same deviation. Sliders are treated as circles with a 50 hit window. Misses are ignored because they are usually due to misaiming. - /// 300s and 100s are assumed to follow a normal distribution, whereas 50s are assumed to follow a uniform distribution. + /// Using estimates player's deviation on accuracy objects. + /// Returns deviation for circles and sliders if score was set with slideracc. + /// Returns the min between deviation of circles and deviation on circles and sliders (assuming slider hits are 50s), if score was set without slideracc. /// - private double calculateDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) + private double calculateTotalDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) { if (totalSuccessfulHits == 0) return double.PositiveInfinity; @@ -316,80 +316,34 @@ namespace osu.Game.Rulesets.Osu.Difficulty int accuracyObjectCount = attributes.HitCircleCount; if (usingSliderAccuracy) accuracyObjectCount += attributes.SliderCount; - int missCountCircles = Math.Min(countMiss, accuracyObjectCount); - int mehCountCircles = Math.Min(countMeh, accuracyObjectCount - missCountCircles); - int okCountCircles = Math.Min(countOk, accuracyObjectCount - missCountCircles - mehCountCircles); - int greatCountCircles = Math.Max(0, accuracyObjectCount - missCountCircles - mehCountCircles - okCountCircles); + // Assume worst case: all mistakes was on accuracy objects + int relevantCountMiss = Math.Min(countMiss, accuracyObjectCount); + int relevantCountMeh = Math.Min(countMeh, accuracyObjectCount - relevantCountMiss); + int relevantCountOk = Math.Min(countOk, accuracyObjectCount - relevantCountMiss - relevantCountMeh); + int relevantCountGreat = Math.Max(0, accuracyObjectCount - relevantCountMiss - relevantCountMeh - relevantCountOk); - if (greatCountCircles + okCountCircles + mehCountCircles <= 0) - return double.PositiveInfinity; - - double deviation = double.PositiveInfinity; - - // Assume 100s, 50s, and misses happen on circles. If there are less non-300s on circles than 300s, - // compute the deviation on circles. - if (accuracyObjectCount > 0) - { - //// The probability that a player hits a circle is unknown, but we can estimate it to be - //// the number of greats on circles divided by the number of circles, and then add one - //// to the number of circles as a bias correction. - double n = Math.Max(1, accuracyObjectCount - missCountCircles - mehCountCircles); - const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). - - // Proportion of greats hit on circles, ignoring misses and 50s. - double p = greatCountCircles / n; - - // We can be 99% confident that p is at least this value. - double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4); - - // Compute the deviation assuming 300s and 100s are normally distributed, and 50s are uniformly distributed. - // Begin with 300s and 100s first. Ignoring 50s, we can be 99% confident that the deviation is not higher than: - deviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); - - double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / deviation, 2)) - / (deviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * deviation))); - - deviation *= Math.Sqrt(1 - randomValue); - - // If precision is not enough - use limit value - if (pLowerBound == 0 || randomValue >= 1) - deviation = hitWindow100 / Math.Sqrt(3); - - // Then compute the variance for 50s. - double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; - - // Find the total deviation. - deviation = Math.Sqrt(((greatCountCircles + okCountCircles) * Math.Pow(deviation, 2) + mehCountCircles * mehVariance) / (greatCountCircles + okCountCircles + mehCountCircles)); - - // Adjust by 0.9 to account for the fact that it's higher bound UR value - deviation *= 0.9; - } + // Calculate deviation on accuracy objects + double deviation = calculateDeviation(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss); if (usingSliderAccuracy) return deviation; - // If there are more non-300s than there are circles, compute the deviation on sliders instead. - // Here, all that matters is whether or not the slider was missed, since it is impossible - // to get a 100 or 50 on a slider by mis-tapping it. - double deviationOnSliders = double.PositiveInfinity; - int sliderCount = attributes.SliderCount; - int missCountSliders = Math.Min(sliderCount, countMiss - missCountCircles); - int greatCountSliders = sliderCount - missCountSliders; + // If score was set without slider accuracy - also compute deviation with sliders + // Assume that all hits was 50s + int totalCountWithSliders = attributes.HitCircleCount + attributes.SliderCount; + int missCountWithSliders = Math.Min(totalCountWithSliders, countMiss); + int hitCountWithSliders = totalCountWithSliders - missCountWithSliders; - // We only get here if nothing was hit. In this case, there is no estimate for deviation. - // Note that this is never negative, so checking if this is only equal to 0 makes sense. - if (greatCountSliders > 0) - { - double greatProbabilitySlider = greatCountSliders / (sliderCount + 1.0); - deviationOnSliders = hitWindow50 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(greatProbabilitySlider)); - } + double hitProbabilityWithSliders = hitCountWithSliders / (totalCountWithSliders + 1.0); + double deviationWithSliders = hitWindow50 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(hitProbabilityWithSliders)); - return Math.Min(deviation, deviationOnSliders); + // Min is needed for edgecase maps with 1 circle and 999 sliders, as deviation on sliders can be lower in this case + return Math.Min(deviation, deviationWithSliders); } /// - /// Does the same as , but only for notes and inaccuracies that are relevant to speed difficulty. - /// Treats all difficult speed notes as circles, so this method can sometimes return a lower deviation than . - /// This is fine though, since this method is only used to scale speed pp. + /// Using estimates player's deviation on speed notes, assuming worst-case. + /// Treats all speed notes as hit circles. This is not good way to do this, but fixing this is impossible under the limitation of current speed pp. + /// If score was set with slideracc - tries to remove mistaps on sliders from total mistaps. /// private double calculateSpeedDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) { @@ -399,37 +353,56 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Calculate accuracy assuming the worst case scenario double speedNoteCount = attributes.SpeedNoteCount; - double relevantTotalDiff = totalHits - speedNoteCount; - - double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); - double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); - double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); - double relevantCountMiss = Math.Max(0, countMiss - Math.Max(0, relevantTotalDiff - countGreat - countOk - countMeh)); + // Assume worst case: all mistakes was on speed notes + double relevantCountMiss = Math.Min(countMiss, speedNoteCount); + double relevantCountMeh = Math.Min(countMeh, speedNoteCount - relevantCountMiss); + double relevantCountOk = Math.Min(countOk, speedNoteCount - relevantCountMiss - relevantCountMeh); + double relevantCountGreat = Math.Max(0, speedNoteCount - relevantCountMiss - relevantCountMeh - relevantCountOk); + // Adjust amount of mistaps if score was set with slider accuracy if (usingSliderAccuracy) { - double greatsRatio = relevantCountGreat / speedNoteCount; - double mistapRatio = 1 - greatsRatio; + double greatRatio = relevantCountGreat / speedNoteCount; + double mistapRatio = 1 - greatRatio; // Assume sliders are 2 times easier to acc than streams double hitcircleRatio = attributes.HitCircleCount / (attributes.HitCircleCount + attributes.SliderCount / 2.0); mistapRatio *= hitcircleRatio; - // This can't get higher than total value - double adjustedGreatsRatio = Math.Min(1 - mistapRatio, (double)countGreat / totalHits); + // This can't get higher than normal great value + double accuracyObjectsCount = attributes.HitCircleCount + attributes.SliderCount; + double accuracyObjectsGreat = Math.Max(0, countGreat - (totalHits - accuracyObjectsCount)); + double adjustedGreatRatio = Math.Min(1 - mistapRatio, accuracyObjectsGreat / accuracyObjectsCount); - double mistapsMultiplier = (greatsRatio == 1) ? 0 : (1 - adjustedGreatsRatio) / (1 - greatsRatio); + double mistapsMultiplier = (greatRatio == 1) ? 0 : (1 - adjustedGreatRatio) / (1 - greatRatio); - relevantCountGreat = speedNoteCount * adjustedGreatsRatio; + relevantCountGreat = speedNoteCount * adjustedGreatRatio; relevantCountOk *= mistapsMultiplier; relevantCountMeh *= mistapsMultiplier; relevantCountMiss *= mistapsMultiplier; } + // Calculate and return deviation on speed notes + return calculateDeviation(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss); + } + + /// + /// Estimates the player's tap deviation based on the OD, given number of 300s, 100s, 50s and misses, + /// assuming the player's mean hit error is 0. The estimation is consistent in that two SS scores on the same map with the same settings + /// will always return the same deviation. Misses are ignored because they are usually due to misaiming. + /// 300s and 100s are assumed to follow a normal distribution, whereas 50s are assumed to follow a uniform distribution. + /// + private double calculateDeviation(double relevantCountGreat, double relevantCountOk, double relevantCountMeh, double relevantCountMiss) + { if (relevantCountGreat + relevantCountOk + relevantCountMeh <= 0) return double.PositiveInfinity; - double n = Math.Max(1, speedNoteCount - relevantCountMiss - relevantCountMeh); + double objectCount = relevantCountGreat + relevantCountOk + relevantCountMeh + relevantCountMiss; + + //// The probability that a player hits a circle is unknown, but we can estimate it to be + //// the number of greats on circles divided by the number of circles, and then add one + //// to the number of circles as a bias correction. + double n = Math.Max(1, objectCount - relevantCountMiss - relevantCountMeh); const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed). // Proportion of greats hit on circles, ignoring misses and 50s. @@ -447,9 +420,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty deviation *= Math.Sqrt(1 - randomValue); - // If precision is not enough - use limit value - if (pLowerBound == 0 || randomValue >= 1) - deviation = hitWindow100 / Math.Sqrt(3); + // Value deviation approach as greatCount approaches 0 + double limitValue = hitWindow100 / Math.Sqrt(3); + + // If precision is not enough to compute true deviation - use limit value + if (pLowerBound == 0 || randomValue >= 1 || deviation > limitValue) + deviation = limitValue; // Then compute the variance for 50s. double mehVariance = (hitWindow50 * hitWindow50 + hitWindow100 * hitWindow50 + hitWindow100 * hitWindow100) / 3; From 3c52296d1ccb62f31e2b114f7cb1c2a22ae244ac Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 1 Jul 2024 20:31:23 +0300 Subject: [PATCH 14/32] removed [] array literal --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index cdab3789f8..4fb08a75fd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DifficultyMultiplier => 1.04; // Used to calculate acc punishment - private readonly List objectStrains = []; + private readonly List objectStrains = new(); public Speed(Mod[] mods) : base(mods) From 42d6464d6487ce7425731361c3eec16985c99632 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 17 Jul 2024 15:44:05 +0300 Subject: [PATCH 15/32] removed lazer stuff --- .../Difficulty/OsuPerformanceCalculator.cs | 47 +------------------ 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index e5ee0715aa..4ea1482d2f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -26,12 +26,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countOk; private int countMeh; private int countMiss; - private int countSliderBreaks; - private int countSliderEndsDropped; private double effectiveMissCount; - private bool usingSliderAccuracy; private double hitWindow300, hitWindow100, hitWindow50; private double deviation, speedDeviation; private double deviationARadjust; @@ -51,20 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty countOk = score.Statistics.GetValueOrDefault(HitResult.Ok); countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - countSliderBreaks = score.Statistics.GetValueOrDefault(HitResult.LargeTickMiss); - - usingSliderAccuracy = !score.Mods.Any(h => h is OsuModClassic cl && cl.NoSliderHeadAccuracy.Value); - - if (usingSliderAccuracy) - { - effectiveMissCount = countMiss; - countSliderEndsDropped = osuAttributes.SliderCount - score.Statistics.GetValueOrDefault(HitResult.SliderTailHit); - } - else - { - effectiveMissCount = calculateEffectiveMissCount(osuAttributes); - countSliderEndsDropped = Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo); - } + effectiveMissCount = calculateEffectiveMissCount(osuAttributes); double multiplier = PERFORMANCE_BASE_MULTIPLIER; @@ -174,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.SliderCount > 0) { - double estimateSliderEndsDropped = Math.Clamp(countSliderEndsDropped + countSliderBreaks * 2, 0, estimateDifficultSliders); + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + attributes.SliderFactor; aimValue *= sliderNerfFactor; } @@ -233,7 +217,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty return 0.0; int amountHitObjectsWithAccuracy = attributes.HitCircleCount; - if (usingSliderAccuracy) amountHitObjectsWithAccuracy += attributes.SliderCount; double liveLengthBonus = Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); double threshold = 1000 * Math.Pow(1.15, 1 / 0.3); // Number of objects until length bonus caps. @@ -314,7 +297,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty return double.PositiveInfinity; int accuracyObjectCount = attributes.HitCircleCount; - if (usingSliderAccuracy) accuracyObjectCount += attributes.SliderCount; // Assume worst case: all mistakes was on accuracy objects int relevantCountMiss = Math.Min(countMiss, accuracyObjectCount); @@ -325,8 +307,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Calculate deviation on accuracy objects double deviation = calculateDeviation(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss); - if (usingSliderAccuracy) return deviation; - // If score was set without slider accuracy - also compute deviation with sliders // Assume that all hits was 50s int totalCountWithSliders = attributes.HitCircleCount + attributes.SliderCount; @@ -359,29 +339,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double relevantCountOk = Math.Min(countOk, speedNoteCount - relevantCountMiss - relevantCountMeh); double relevantCountGreat = Math.Max(0, speedNoteCount - relevantCountMiss - relevantCountMeh - relevantCountOk); - // Adjust amount of mistaps if score was set with slider accuracy - if (usingSliderAccuracy) - { - double greatRatio = relevantCountGreat / speedNoteCount; - double mistapRatio = 1 - greatRatio; - - // Assume sliders are 2 times easier to acc than streams - double hitcircleRatio = attributes.HitCircleCount / (attributes.HitCircleCount + attributes.SliderCount / 2.0); - mistapRatio *= hitcircleRatio; - - // This can't get higher than normal great value - double accuracyObjectsCount = attributes.HitCircleCount + attributes.SliderCount; - double accuracyObjectsGreat = Math.Max(0, countGreat - (totalHits - accuracyObjectsCount)); - double adjustedGreatRatio = Math.Min(1 - mistapRatio, accuracyObjectsGreat / accuracyObjectsCount); - - double mistapsMultiplier = (greatRatio == 1) ? 0 : (1 - adjustedGreatRatio) / (1 - greatRatio); - - relevantCountGreat = speedNoteCount * adjustedGreatRatio; - relevantCountOk *= mistapsMultiplier; - relevantCountMeh *= mistapsMultiplier; - relevantCountMiss *= mistapsMultiplier; - } - // Calculate and return deviation on speed notes return calculateDeviation(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss); } From c0dc878698b022de51fa5be46d43d61da116138f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 17 Jul 2024 15:45:34 +0300 Subject: [PATCH 16/32] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 4ea1482d2f..a29f9818e1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double accuracy; private int scoreMaxCombo; - private int countGreat; private int countOk; private int countMeh; From a0e1da87753ae22b5a179d0ddd85a3b772c420ad Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 18 Jul 2024 19:19:21 +0300 Subject: [PATCH 17/32] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a29f9818e1..67fd76b5d2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (deviation == double.PositiveInfinity || score.Mods.Any(h => h is OsuModRelax) || deviation == double.PositiveInfinity) + if (score.Mods.Any(h => h is OsuModRelax) || deviation == double.PositiveInfinity) return 0.0; int amountHitObjectsWithAccuracy = attributes.HitCircleCount; From c4d5ac7008c3beda91ad651fcd135a63f11031b4 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 18 Jul 2024 19:40:50 +0300 Subject: [PATCH 18/32] refactored multiplier stuff out of main func --- .../Difficulty/OsuPerformanceCalculator.cs | 78 ++++++++++++------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 67fd76b5d2..eaaa9df328 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double hitWindow300, hitWindow100, hitWindow50; private double deviation, speedDeviation; - private double deviationARadjust; public OsuPerformanceCalculator() : base(new OsuRuleset()) @@ -69,31 +68,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty effectiveMissCount = Math.Min(effectiveMissCount + countOk * okMultiplier + countMeh * mehMultiplier, totalHits); } - var track = new TrackVirtual(1); - score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); - double clockRate = track.Rate; - + double clockRate = getClockRate(score); hitWindow300 = 80 - 6 * osuAttributes.OverallDifficulty; hitWindow100 = (140 - 8 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; hitWindow50 = (200 - 10 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; - // Bonus for low AR to account for the fact that it's more difficult to get low UR on low AR - deviationARadjust = 0.475 + 0.7 / (1.0 + Math.Pow(1.73, 7.9 - osuAttributes.ApproachRate)); + deviation = calculateTotalDeviation(score, osuAttributes); + speedDeviation = calculateSpeedDeviation(score, osuAttributes); - deviation = calculateTotalDeviation(score, osuAttributes) * deviationARadjust; - speedDeviation = calculateSpeedDeviation(score, osuAttributes) * deviationARadjust; - - // Use adjusted deviation to not nerf EZHT aim maps - double totalAntiRakeMultiplier = calculateTotalRakeNerf(osuAttributes, deviation); - - // Use raw speed deviation to prevent abuse of deviation AR adjust with EZDT - double speedAntiRakeMultiplier = calculateSpeedRakeNerf(osuAttributes, speedDeviation / deviationARadjust); - speedAntiRakeMultiplier = Math.Min(speedAntiRakeMultiplier, totalAntiRakeMultiplier); - - double aimValue = computeAimValue(score, osuAttributes) * totalAntiRakeMultiplier; - double speedValue = computeSpeedValue(score, osuAttributes) * speedAntiRakeMultiplier; + double aimValue = computeAimValue(score, osuAttributes); + double speedValue = computeSpeedValue(score, osuAttributes); double accuracyValue = computeAccuracyValue(score, osuAttributes); - double flashlightValue = computeFlashlightValue(score, osuAttributes) * totalAntiRakeMultiplier; + double flashlightValue = computeFlashlightValue(score, osuAttributes); double totalValue = Math.Pow( @@ -110,8 +96,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty Accuracy = accuracyValue, Flashlight = flashlightValue, EffectiveMissCount = effectiveMissCount, - Deviation = deviation / deviationARadjust, - SpeedDeviation = speedDeviation / deviationARadjust, + Deviation = deviation, + SpeedDeviation = speedDeviation, Total = totalValue }; } @@ -162,8 +148,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= sliderNerfFactor; } - // Scale the aim value with deviation - aimValue *= SpecialFunctions.Erf(30 / (Math.Sqrt(2) * deviation)); + // Apply antirake nerf + double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes, deviation); + aimValue *= totalAntiRakeMultiplier; + + // Scale the aim value with adjusted deviation + double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); + aimValue *= SpecialFunctions.Erf(30 / (Math.Sqrt(2) * adjustedDeviation)); aimValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return aimValue; @@ -203,8 +194,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } - // Scale the speed value with speed deviation - speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * speedDeviation)); + // Apply antirake nerf, while taking the bigger nerf between speed nerf and total nerf + double speedAntiRakeMultiplier = calculateSpeedRakeNerf(attributes, speedDeviation); + double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes, deviation); + speedAntiRakeMultiplier = Math.Min(speedAntiRakeMultiplier, totalAntiRakeMultiplier); + speedValue *= speedAntiRakeMultiplier; + + // Scale the speed value with adjusted speed deviation + double adjustedSpeedDeviation = speedDeviation * calculateDeviationArAdjust(attributes.ApproachRate); + speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * adjustedSpeedDeviation)); speedValue *= 0.95 + Math.Pow(100.0 / 9, 2) / 750; // OD 11 SS stays the same. return speedValue; @@ -220,11 +218,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty double liveLengthBonus = Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); double threshold = 1000 * Math.Pow(1.15, 1 / 0.3); // Number of objects until length bonus caps. + // Adjust deviation as it's harder to get good acc on lower AR + double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); + // Some fancy stuff to make curve similar to live double scaling = Math.Sqrt(2) * Math.Log(1.52163) * SpecialFunctions.ErfInv(1 / (1 + 1 / Math.Min(amountHitObjectsWithAccuracy, threshold))) / 6; // Accuracy pp formula that's roughly the same as live. - double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * deviation); + double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * adjustedDeviation); // Punish very low amount of hits additionally to prevent big pp values right at the start of the map double amountOfHits = Math.Clamp(totalSuccessfulHits - attributes.SpinnerCount, 0, amountHitObjectsWithAccuracy); @@ -260,8 +261,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); - // Scale the flashlight value with deviation - flashlightValue *= SpecialFunctions.Erf(50 / (Math.Sqrt(2) * deviation)); + // Apply antirake nerf + double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes, deviation); + flashlightValue *= totalAntiRakeMultiplier; + + // Scale the flashlight value with adjusted deviation + double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); + flashlightValue *= SpecialFunctions.Erf(50 / (Math.Sqrt(2) * adjustedDeviation)); flashlightValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return flashlightValue; @@ -416,6 +422,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Calculates multiplier for total pp accounting for rake based on the deviation and sliderless aim and speed difficulty private double calculateTotalRakeNerf(OsuDifficultyAttributes attributes, double deviation) { + // Use adjusted deviation to not nerf EZHT aim maps + deviation *= calculateDeviationArAdjust(attributes.ApproachRate); + // Base values double aimNoSlidersValue = 4 * Math.Pow(attributes.AimDifficulty * attributes.SliderFactor, 3); double speedValue = 4 * Math.Pow(attributes.SpeedDifficulty, 3); @@ -433,8 +442,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty return adjustedTotalValue / totalValue; } + private static double getClockRate(ScoreInfo score) + { + var track = new TrackVirtual(1); + score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); + return track.Rate; + } private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); + + // Bonus for low AR to account for the fact that it's more difficult to get low UR on low AR + private static double calculateDeviationArAdjust(double AR) => 0.475 + 0.7 / (1.0 + Math.Pow(1.73, 7.9 - AR)); + private int totalHits => countGreat + countOk + countMeh + countMiss; + private int totalSuccessfulHits => countGreat + countOk + countMeh; } } From d3ceb1b5204f9056d4ed8c6130e53c77db239dd7 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 18 Jul 2024 19:43:55 +0300 Subject: [PATCH 19/32] reverted speed change --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 4fb08a75fd..40aac013ab 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -25,8 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override int ReducedSectionCount => 5; protected override double DifficultyMultiplier => 1.04; - // Used to calculate acc punishment - private readonly List objectStrains = new(); + private readonly List objectStrains = new List(); public Speed(Mod[] mods) : base(mods) From 07dd3249225a3f4c23a08020ffb007457458c2bf Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 18 Jul 2024 19:44:50 +0300 Subject: [PATCH 20/32] removed display attributes --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 6ff8d97285..5d27aa718a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -39,8 +39,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed); yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy); yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight); - yield return new PerformanceDisplayAttribute(nameof(Deviation), "Estimated UR", Deviation * 10); - yield return new PerformanceDisplayAttribute(nameof(SpeedDeviation), "Estimated UR (speed)", SpeedDeviation * 10); } } } From ecf1dcfcf74ea78cad06fac32b0874fdaccc1925 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 18 Jul 2024 19:58:34 +0300 Subject: [PATCH 21/32] changed antirake functions logic --- .../Difficulty/OsuPerformanceCalculator.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index eaaa9df328..c25fa18eb4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty } // Apply antirake nerf - double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes, deviation); + double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes); aimValue *= totalAntiRakeMultiplier; // Scale the aim value with adjusted deviation @@ -194,10 +194,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } - // Apply antirake nerf, while taking the bigger nerf between speed nerf and total nerf - double speedAntiRakeMultiplier = calculateSpeedRakeNerf(attributes, speedDeviation); - double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes, deviation); - speedAntiRakeMultiplier = Math.Min(speedAntiRakeMultiplier, totalAntiRakeMultiplier); + // Apply antirake nerf + double speedAntiRakeMultiplier = calculateSpeedRakeNerf(attributes); speedValue *= speedAntiRakeMultiplier; // Scale the speed value with adjusted speed deviation @@ -262,7 +260,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); // Apply antirake nerf - double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes, deviation); + double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes); flashlightValue *= totalAntiRakeMultiplier; // Scale the flashlight value with adjusted deviation @@ -401,13 +399,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Calculates multiplier for speed accounting for rake based on the deviation and speed difficulty // https://www.desmos.com/calculator/puc1mzdtfv - private double calculateSpeedRakeNerf(OsuDifficultyAttributes attributes, double rawSpeedDeviation) + private double calculateSpeedRakeNerf(OsuDifficultyAttributes attributes) { // Base speed value double speedValue = 4 * Math.Pow(attributes.SpeedDifficulty, 3); // Starting from this pp amount - penalty will be applied - double abusePoint = 100 + 260 * Math.Pow(20 / rawSpeedDeviation, 5.8); + double abusePoint = 100 + 260 * Math.Pow(20 / speedDeviation, 5.8); if (speedValue <= abusePoint) return 1.0; @@ -416,14 +414,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty const double scale = 50; double adjustedSpeedValue = scale * (Math.Log((speedValue - abusePoint) / scale + 1) + abusePoint / scale); - return adjustedSpeedValue / speedValue; + double speedRakeNerf = adjustedSpeedValue / speedValue; + + return Math.Min(speedRakeNerf, calculateTotalRakeNerf(attributes)); } // Calculates multiplier for total pp accounting for rake based on the deviation and sliderless aim and speed difficulty - private double calculateTotalRakeNerf(OsuDifficultyAttributes attributes, double deviation) + private double calculateTotalRakeNerf(OsuDifficultyAttributes attributes) { // Use adjusted deviation to not nerf EZHT aim maps - deviation *= calculateDeviationArAdjust(attributes.ApproachRate); + double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); // Base values double aimNoSlidersValue = 4 * Math.Pow(attributes.AimDifficulty * attributes.SliderFactor, 3); @@ -431,7 +431,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double totalValue = Math.Pow(Math.Pow(aimNoSlidersValue, 1.1) + Math.Pow(speedValue, 1.1), 1 / 1.1); // Starting from this pp amount - penalty will be applied - double abusePoint = 200 + 600 * Math.Pow(20 / deviation, 4.2); + double abusePoint = 200 + 600 * Math.Pow(20 / adjustedDeviation, 4.2); if (totalValue <= abusePoint) return 1.0; From c72adc69ac0999e8013b9bb782a7bc1b6112f8cc Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 26 Aug 2024 22:46:55 +0300 Subject: [PATCH 22/32] ported things from main branch slight buff for slidermaps and high-end fix --- .../Difficulty/OsuPerformanceCalculator.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index c25fa18eb4..ba27284942 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -198,9 +198,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedAntiRakeMultiplier = calculateSpeedRakeNerf(attributes); speedValue *= speedAntiRakeMultiplier; - // Scale the speed value with adjusted speed deviation + // Scale the speed value with speed deviation. + // Use additional bad UR penalty for high speed difficulty + // (WARNING: potentially unstable, but instability detected in playable difficulty range). double adjustedSpeedDeviation = speedDeviation * calculateDeviationArAdjust(attributes.ApproachRate); - speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * adjustedSpeedDeviation)); + speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * adjustedSpeedDeviation * Math.Max(1, Math.Pow(attributes.SpeedDifficulty / 4.5, 1.2)))); speedValue *= 0.95 + Math.Pow(100.0 / 9, 2) / 750; // OD 11 SS stays the same. return speedValue; @@ -211,7 +213,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (score.Mods.Any(h => h is OsuModRelax) || deviation == double.PositiveInfinity) return 0.0; - int amountHitObjectsWithAccuracy = attributes.HitCircleCount; + double amountHitObjectsWithAccuracy = attributes.HitCircleCount; + + // If amount of circles is too small - also add sliders, because UR is calculated accounting to sliders too + double circlesRatio = (double)attributes.HitCircleCount / (attributes.HitCircleCount + attributes.SliderCount); + if (circlesRatio < 0.25) + amountHitObjectsWithAccuracy += attributes.SliderCount * (1 - circlesRatio / 0.25); double liveLengthBonus = Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); double threshold = 1000 * Math.Pow(1.15, 1 / 0.3); // Number of objects until length bonus caps. From b21afb4c544c35a4fc66a4e44792c6705dda973f Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 28 Aug 2024 02:26:22 +0300 Subject: [PATCH 23/32] removed *0.9 and changed relevant note count --- .../Difficulty/OsuPerformanceCalculator.cs | 16 ++++++++-------- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index ba27284942..96435f2b40 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Scale the aim value with adjusted deviation double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); - aimValue *= SpecialFunctions.Erf(30 / (Math.Sqrt(2) * adjustedDeviation)); + aimValue *= SpecialFunctions.Erf(33 / (Math.Sqrt(2) * adjustedDeviation)); aimValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return aimValue; @@ -202,7 +202,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Use additional bad UR penalty for high speed difficulty // (WARNING: potentially unstable, but instability detected in playable difficulty range). double adjustedSpeedDeviation = speedDeviation * calculateDeviationArAdjust(attributes.ApproachRate); - speedValue *= SpecialFunctions.Erf(20 / (Math.Sqrt(2) * adjustedSpeedDeviation * Math.Max(1, Math.Pow(attributes.SpeedDifficulty / 4.5, 1.2)))); + speedValue *= SpecialFunctions.Erf(22 / (Math.Sqrt(2) * adjustedSpeedDeviation * Math.Max(1, Math.Pow(attributes.SpeedDifficulty / 4.5, 1.2)))); speedValue *= 0.95 + Math.Pow(100.0 / 9, 2) / 750; // OD 11 SS stays the same. return speedValue; @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); // Some fancy stuff to make curve similar to live - double scaling = Math.Sqrt(2) * Math.Log(1.52163) * SpecialFunctions.ErfInv(1 / (1 + 1 / Math.Min(amountHitObjectsWithAccuracy, threshold))) / 6; + double scaling = 0.9 * Math.Sqrt(2) * Math.Log(1.52163) * SpecialFunctions.ErfInv(1 / (1 + 1 / Math.Min(amountHitObjectsWithAccuracy, threshold))) / 6; // Accuracy pp formula that's roughly the same as live. double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * adjustedDeviation); @@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Scale the flashlight value with adjusted deviation double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); - flashlightValue *= SpecialFunctions.Erf(50 / (Math.Sqrt(2) * adjustedDeviation)); + flashlightValue *= SpecialFunctions.Erf(55 / (Math.Sqrt(2) * adjustedDeviation)); flashlightValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. return flashlightValue; @@ -400,8 +400,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Find the total deviation. deviation = Math.Sqrt(((relevantCountGreat + relevantCountOk) * Math.Pow(deviation, 2) + relevantCountMeh * mehVariance) / (relevantCountGreat + relevantCountOk + relevantCountMeh)); - // Adjust by 0.9 to account for the fact that it's higher bound UR value - return deviation * 0.9; + return deviation; } // Calculates multiplier for speed accounting for rake based on the deviation and speed difficulty @@ -412,7 +411,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedValue = 4 * Math.Pow(attributes.SpeedDifficulty, 3); // Starting from this pp amount - penalty will be applied - double abusePoint = 100 + 260 * Math.Pow(20 / speedDeviation, 5.8); + double abusePoint = 100 + 260 * Math.Pow(22 / speedDeviation, 5.8); if (speedValue <= abusePoint) return 1.0; @@ -438,7 +437,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double totalValue = Math.Pow(Math.Pow(aimNoSlidersValue, 1.1) + Math.Pow(speedValue, 1.1), 1 / 1.1); // Starting from this pp amount - penalty will be applied - double abusePoint = 200 + 600 * Math.Pow(20 / adjustedDeviation, 4.2); + double abusePoint = 200 + 600 * Math.Pow(22 / adjustedDeviation, 4.2); if (totalValue <= abusePoint) return 1.0; @@ -455,6 +454,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty score.Mods.OfType().ForEach(m => m.ApplyToTrack(track)); return track.Rate; } + private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); // Bonus for low AR to account for the fact that it's more difficult to get low UR on low AR diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 40aac013ab..8f0c9b9ea6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (maxStrain == 0) return 0; - return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); + return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 8.0 - 4.0)))); } } } From 67219631630f44e8c5bdbc851bd6d0b91e1e30ab Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 9 Sep 2024 13:02:54 +0300 Subject: [PATCH 24/32] reverted acc to live --- .../Difficulty/OsuPerformanceCalculator.cs | 69 ++++++++++--------- .../Difficulty/Skills/Speed.cs | 2 +- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 96435f2b40..e444ddede6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -152,10 +152,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes); aimValue *= totalAntiRakeMultiplier; - // Scale the aim value with adjusted deviation - double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); - aimValue *= SpecialFunctions.Erf(33 / (Math.Sqrt(2) * adjustedDeviation)); - aimValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. + aimValue *= accuracy; + // It is important to consider accuracy difficulty when scaling with accuracy. + aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; return aimValue; } @@ -198,44 +197,46 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedAntiRakeMultiplier = calculateSpeedRakeNerf(attributes); speedValue *= speedAntiRakeMultiplier; - // Scale the speed value with speed deviation. - // Use additional bad UR penalty for high speed difficulty - // (WARNING: potentially unstable, but instability detected in playable difficulty range). - double adjustedSpeedDeviation = speedDeviation * calculateDeviationArAdjust(attributes.ApproachRate); - speedValue *= SpecialFunctions.Erf(22 / (Math.Sqrt(2) * adjustedSpeedDeviation * Math.Max(1, Math.Pow(attributes.SpeedDifficulty / 4.5, 1.2)))); - speedValue *= 0.95 + Math.Pow(100.0 / 9, 2) / 750; // OD 11 SS stays the same. + // Calculate accuracy assuming the worst case scenario + double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; + double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff); + double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat)); + double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk)); + double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0); + + // Scale the speed value with accuracy and OD. + speedValue *= (0.95 + Math.Pow(attributes.OverallDifficulty, 2) / 750) * Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - Math.Max(attributes.OverallDifficulty, 8)) / 2); + + // Scale the speed value with # of 50s to punish doubletapping. + speedValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0); return speedValue; } private double computeAccuracyValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (score.Mods.Any(h => h is OsuModRelax) || deviation == double.PositiveInfinity) + if (score.Mods.Any(h => h is OsuModRelax)) return 0.0; - double amountHitObjectsWithAccuracy = attributes.HitCircleCount; + // 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; - // If amount of circles is too small - also add sliders, because UR is calculated accounting to sliders too - double circlesRatio = (double)attributes.HitCircleCount / (attributes.HitCircleCount + attributes.SliderCount); - if (circlesRatio < 0.25) - amountHitObjectsWithAccuracy += attributes.SliderCount * (1 - circlesRatio / 0.25); + if (amountHitObjectsWithAccuracy > 0) + betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); + else + betterAccuracyPercentage = 0; - double liveLengthBonus = Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)); - double threshold = 1000 * Math.Pow(1.15, 1 / 0.3); // Number of objects until length bonus caps. + // It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points. + if (betterAccuracyPercentage < 0) + betterAccuracyPercentage = 0; - // Adjust deviation as it's harder to get good acc on lower AR - double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); + // Lots of arbitrary values from testing. + // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution. + double accuracyValue = Math.Pow(1.52163, attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83; - // Some fancy stuff to make curve similar to live - double scaling = 0.9 * Math.Sqrt(2) * Math.Log(1.52163) * SpecialFunctions.ErfInv(1 / (1 + 1 / Math.Min(amountHitObjectsWithAccuracy, threshold))) / 6; - - // Accuracy pp formula that's roughly the same as live. - double accuracyValue = 2.83 * Math.Pow(1.52163, 40.0 / 3) * liveLengthBonus * Math.Exp(-scaling * adjustedDeviation); - - // Punish very low amount of hits additionally to prevent big pp values right at the start of the map - double amountOfHits = Math.Clamp(totalSuccessfulHits - attributes.SpinnerCount, 0, amountHitObjectsWithAccuracy); - if (amountOfHits < 30) - accuracyValue *= Math.Sqrt(amountOfHits / 30); + // 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 (score.Mods.Any(m => m is OsuModBlinds)) @@ -270,10 +271,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes); flashlightValue *= totalAntiRakeMultiplier; - // Scale the flashlight value with adjusted deviation - double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); - flashlightValue *= SpecialFunctions.Erf(55 / (Math.Sqrt(2) * adjustedDeviation)); - flashlightValue *= 0.98 + Math.Pow(100.0 / 9, 2) / 2500; // OD 11 SS stays the same. + // Scale the flashlight value with accuracy _slightly_. + flashlightValue *= 0.5 + accuracy / 2.0; + // It is important to also consider accuracy difficulty when doing that. + flashlightValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; return flashlightValue; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 8f0c9b9ea6..40aac013ab 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (maxStrain == 0) return 0; - return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 8.0 - 4.0)))); + return objectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0)))); } } } From ea954002d2a1e701181612da8fac4bc8b6986505 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 9 Sep 2024 13:41:32 +0300 Subject: [PATCH 25/32] tried to bandaid the issue --- .../Difficulty/OsuPerformanceCalculator.cs | 81 ++----------------- 1 file changed, 5 insertions(+), 76 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index e444ddede6..94e77e6bf1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double effectiveMissCount; private double hitWindow300, hitWindow100, hitWindow50; - private double deviation, speedDeviation; + private double speedDeviation; public OsuPerformanceCalculator() : base(new OsuRuleset()) @@ -73,7 +73,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty hitWindow100 = (140 - 8 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; hitWindow50 = (200 - 10 * ((80 - hitWindow300 * clockRate) / 6)) / clockRate; - deviation = calculateTotalDeviation(score, osuAttributes); speedDeviation = calculateSpeedDeviation(score, osuAttributes); double aimValue = computeAimValue(score, osuAttributes); @@ -96,7 +95,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty Accuracy = accuracyValue, Flashlight = flashlightValue, EffectiveMissCount = effectiveMissCount, - Deviation = deviation, SpeedDeviation = speedDeviation, Total = totalValue }; @@ -104,9 +102,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (deviation == double.PositiveInfinity) - return 0.0; - double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + @@ -148,10 +143,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= sliderNerfFactor; } - // Apply antirake nerf - double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes); - aimValue *= totalAntiRakeMultiplier; - aimValue *= accuracy; // It is important to consider accuracy difficulty when scaling with accuracy. aimValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500; @@ -252,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (!score.Mods.Any(h => h is OsuModFlashlight) || deviation == double.PositiveInfinity) + if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; @@ -267,10 +258,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightValue *= 0.7 + 0.1 * Math.Min(1.0, totalHits / 200.0) + (totalHits > 200 ? 0.2 * Math.Min(1.0, (totalHits - 200) / 200.0) : 0.0); - // Apply antirake nerf - double totalAntiRakeMultiplier = calculateTotalRakeNerf(attributes); - flashlightValue *= totalAntiRakeMultiplier; - // Scale the flashlight value with accuracy _slightly_. flashlightValue *= 0.5 + accuracy / 2.0; // It is important to also consider accuracy difficulty when doing that. @@ -297,40 +284,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty return Math.Max(countMiss, comboBasedMissCount); } - /// - /// Using estimates player's deviation on accuracy objects. - /// Returns deviation for circles and sliders if score was set with slideracc. - /// Returns the min between deviation of circles and deviation on circles and sliders (assuming slider hits are 50s), if score was set without slideracc. - /// - private double calculateTotalDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) - { - if (totalSuccessfulHits == 0) - return double.PositiveInfinity; - - int accuracyObjectCount = attributes.HitCircleCount; - - // Assume worst case: all mistakes was on accuracy objects - int relevantCountMiss = Math.Min(countMiss, accuracyObjectCount); - int relevantCountMeh = Math.Min(countMeh, accuracyObjectCount - relevantCountMiss); - int relevantCountOk = Math.Min(countOk, accuracyObjectCount - relevantCountMiss - relevantCountMeh); - int relevantCountGreat = Math.Max(0, accuracyObjectCount - relevantCountMiss - relevantCountMeh - relevantCountOk); - - // Calculate deviation on accuracy objects - double deviation = calculateDeviation(relevantCountGreat, relevantCountOk, relevantCountMeh, relevantCountMiss); - - // If score was set without slider accuracy - also compute deviation with sliders - // Assume that all hits was 50s - int totalCountWithSliders = attributes.HitCircleCount + attributes.SliderCount; - int missCountWithSliders = Math.Min(totalCountWithSliders, countMiss); - int hitCountWithSliders = totalCountWithSliders - missCountWithSliders; - - double hitProbabilityWithSliders = hitCountWithSliders / (totalCountWithSliders + 1.0); - double deviationWithSliders = hitWindow50 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(hitProbabilityWithSliders)); - - // Min is needed for edgecase maps with 1 circle and 999 sliders, as deviation on sliders can be lower in this case - return Math.Min(deviation, deviationWithSliders); - } - /// /// Using estimates player's deviation on speed notes, assuming worst-case. /// Treats all speed notes as hit circles. This is not good way to do this, but fixing this is impossible under the limitation of current speed pp. @@ -344,6 +297,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Calculate accuracy assuming the worst case scenario double speedNoteCount = attributes.SpeedNoteCount; + speedNoteCount += (totalHits - attributes.SpeedNoteCount) * 0.1; + // Assume worst case: all mistakes was on speed notes double relevantCountMiss = Math.Min(countMiss, speedNoteCount); double relevantCountMeh = Math.Min(countMeh, speedNoteCount - relevantCountMiss); @@ -423,30 +378,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedRakeNerf = adjustedSpeedValue / speedValue; - return Math.Min(speedRakeNerf, calculateTotalRakeNerf(attributes)); - } - - // Calculates multiplier for total pp accounting for rake based on the deviation and sliderless aim and speed difficulty - private double calculateTotalRakeNerf(OsuDifficultyAttributes attributes) - { - // Use adjusted deviation to not nerf EZHT aim maps - double adjustedDeviation = deviation * calculateDeviationArAdjust(attributes.ApproachRate); - - // Base values - double aimNoSlidersValue = 4 * Math.Pow(attributes.AimDifficulty * attributes.SliderFactor, 3); - double speedValue = 4 * Math.Pow(attributes.SpeedDifficulty, 3); - double totalValue = Math.Pow(Math.Pow(aimNoSlidersValue, 1.1) + Math.Pow(speedValue, 1.1), 1 / 1.1); - - // Starting from this pp amount - penalty will be applied - double abusePoint = 200 + 600 * Math.Pow(22 / adjustedDeviation, 4.2); - - if (totalValue <= abusePoint) - return 1.0; - - // Use relax penalty after the point to make values grow slower but still noticeably - double adjustedTotalValue = abusePoint + Math.Pow(0.9, 3) * (totalValue - abusePoint); - - return adjustedTotalValue / totalValue; + return speedRakeNerf; } private static double getClockRate(ScoreInfo score) @@ -458,9 +390,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0); - // Bonus for low AR to account for the fact that it's more difficult to get low UR on low AR - private static double calculateDeviationArAdjust(double AR) => 0.475 + 0.7 / (1.0 + Math.Pow(1.73, 7.9 - AR)); - private int totalHits => countGreat + countOk + countMeh + countMiss; private int totalSuccessfulHits => countGreat + countOk + countMeh; From bba244981a9ae3fb1f0b5dd82becb76641a66540 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 16 Sep 2024 20:28:38 +0300 Subject: [PATCH 26/32] adjusted rake nerf to usage of new function --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs | 3 --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 5d27aa718a..08836d9f25 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -24,9 +24,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty [JsonProperty("effective_miss_count")] public double EffectiveMissCount { get; set; } - [JsonProperty("deviation")] - public double Deviation { get; set; } - [JsonProperty("speed_deviation")] public double SpeedDeviation { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 14dfba7aaa..432327fa44 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -365,7 +365,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double calculateSpeedRakeNerf(OsuDifficultyAttributes attributes) { // Base speed value - double speedValue = 4 * Math.Pow(attributes.SpeedDifficulty, 3); + double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); // Starting from this pp amount - penalty will be applied double abusePoint = 100 + 260 * Math.Pow(22 / speedDeviation, 5.8); From b8898d61f24ec3a23502496de77e69d66c9f23ce Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 15 Oct 2024 15:02:18 +0300 Subject: [PATCH 27/32] updated antirake --- .../Difficulty/OsuPerformanceCalculator.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 432327fa44..bdf006ac45 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -361,14 +361,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty } // Calculates multiplier for speed accounting for rake based on the deviation and speed difficulty - // https://www.desmos.com/calculator/puc1mzdtfv + // https://www.desmos.com/calculator/dmogdhzofn private double calculateSpeedRakeNerf(OsuDifficultyAttributes attributes) { // Base speed value double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); // Starting from this pp amount - penalty will be applied - double abusePoint = 100 + 260 * Math.Pow(22 / speedDeviation, 5.8); + double abusePoint = 100 + 220 * Math.Pow(22 / speedDeviation, 6.5); if (speedValue <= abusePoint) return 1.0; @@ -377,9 +377,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty const double scale = 50; double adjustedSpeedValue = scale * (Math.Log((speedValue - abusePoint) / scale + 1) + abusePoint / scale); - double speedRakeNerf = adjustedSpeedValue / speedValue; + // 200 UR and less are considered not raked and will be punished only by normal acc scaling + double lerp = 1 - Math.Clamp((speedDeviation - 20) / (24 - 20), 0, 1); + adjustedSpeedValue = double.Lerp(adjustedSpeedValue, speedValue, lerp); - return speedRakeNerf; + return adjustedSpeedValue / speedValue; } private static double getClockRate(ScoreInfo score) From 821158bfba6ed62e78afe700c1d5294af793891c Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 28 Nov 2024 17:35:59 +0200 Subject: [PATCH 28/32] change to nullable and rename some stuff --- .../Difficulty/OsuPerformanceAttributes.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 31 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs index 08836d9f25..de4491a31b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceAttributes.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double EffectiveMissCount { get; set; } [JsonProperty("speed_deviation")] - public double SpeedDeviation { get; set; } + public double? SpeedDeviation { get; set; } public override IEnumerable GetAttributesForDisplay() { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 7d7859f871..33da76b99c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double effectiveMissCount; private double hitWindow300, hitWindow100, hitWindow50; - private double speedDeviation; + private double? speedDeviation; public OsuPerformanceCalculator() : base(new OsuRuleset()) @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - if (score.Mods.Any(h => h is OsuModRelax) || speedDeviation == double.PositiveInfinity) + if (score.Mods.Any(h => h is OsuModRelax) || speedDeviation == null) return 0.0; double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); @@ -241,9 +241,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate); } - // Apply antirake nerf - double speedAntiRakeMultiplier = calculateSpeedRakeNerf(attributes); - speedValue *= speedAntiRakeMultiplier; + // Apply improper tapping nerf for too high deviation values + double speedHighDeviationMultiplier = calculateSpeedHighDeviationNerf(attributes); + speedValue *= speedHighDeviationMultiplier; // Calculate accuracy assuming the worst case scenario double relevantTotalDiff = totalHits - attributes.SpeedNoteCount; @@ -348,10 +348,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty /// Treats all speed notes as hit circles. This is not good way to do this, but fixing this is impossible under the limitation of current speed pp. /// If score was set with slideracc - tries to remove mistaps on sliders from total mistaps. /// - private double calculateSpeedDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) + private double? calculateSpeedDeviation(ScoreInfo score, OsuDifficultyAttributes attributes) { if (totalSuccessfulHits == 0) - return double.PositiveInfinity; + return null; // Calculate accuracy assuming the worst case scenario double speedNoteCount = attributes.SpeedNoteCount; @@ -374,10 +374,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty /// will always return the same deviation. Misses are ignored because they are usually due to misaiming. /// 300s and 100s are assumed to follow a normal distribution, whereas 50s are assumed to follow a uniform distribution. /// - private double calculateDeviation(double relevantCountGreat, double relevantCountOk, double relevantCountMeh, double relevantCountMiss) + private double? calculateDeviation(double relevantCountGreat, double relevantCountOk, double relevantCountMeh, double relevantCountMiss) { if (relevantCountGreat + relevantCountOk + relevantCountMeh <= 0) - return double.PositiveInfinity; + return null; double objectCount = relevantCountGreat + relevantCountOk + relevantCountMeh + relevantCountMiss; @@ -418,15 +418,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty return deviation; } - // Calculates multiplier for speed accounting for rake based on the deviation and speed difficulty + // Calculates multiplier for speed accounting for improper tapping based on the deviation and speed difficulty // https://www.desmos.com/calculator/dmogdhzofn - private double calculateSpeedRakeNerf(OsuDifficultyAttributes attributes) + private double calculateSpeedHighDeviationNerf(OsuDifficultyAttributes attributes) { + if (speedDeviation == null) + return 0; + // Base speed value double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); // Starting from this pp amount - penalty will be applied - double abusePoint = 100 + 220 * Math.Pow(22 / speedDeviation, 6.5); + double abusePoint = 100 + 220 * Math.Pow(22 / speedDeviation.Value, 6.5); if (speedValue <= abusePoint) return 1.0; @@ -435,8 +438,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty const double scale = 50; double adjustedSpeedValue = scale * (Math.Log((speedValue - abusePoint) / scale + 1) + abusePoint / scale); - // 200 UR and less are considered not raked and will be punished only by normal acc scaling - double lerp = 1 - Math.Clamp((speedDeviation - 20) / (24 - 20), 0, 1); + // 200 UR and less are considered tapped correctly to ensure that normal scores would be punished as little as possible + double lerp = 1 - Math.Clamp((speedDeviation.Value - 20) / (24 - 20), 0, 1); adjustedSpeedValue = double.Lerp(adjustedSpeedValue, speedValue, lerp); return adjustedSpeedValue / speedValue; From eef605ead2ffd10d59c375b253d41692ef60b71d Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 28 Nov 2024 17:39:43 +0200 Subject: [PATCH 29/32] remove math net --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 33da76b99c..6c387e830a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using MathNet.Numerics; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Difficulty; @@ -13,6 +12,7 @@ using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Difficulty { diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index a3ea45c208..7817d55f57 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -12,10 +12,6 @@ true - - - - From 4a5f2f9236a11da0dd6eec92aed5db7c17db49ca Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 28 Nov 2024 17:52:34 +0200 Subject: [PATCH 30/32] fix CI --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6c387e830a..a7a11d7a85 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double deviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / deviation, 2)) - / (deviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * deviation))); + / (deviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * deviation))); deviation *= Math.Sqrt(1 - randomValue); From f1a4fbc05336b86f35a87647d46ddd20fda556e2 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 28 Nov 2024 18:06:06 +0200 Subject: [PATCH 31/32] Update OsuPerformanceCalculator.cs --- osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a7a11d7a85..aa4f2719a4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double deviation = hitWindow300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound)); double randomValue = Math.Sqrt(2 / Math.PI) * hitWindow100 * Math.Exp(-0.5 * Math.Pow(hitWindow100 / deviation, 2)) - / (deviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * deviation))); + / (deviation * SpecialFunctions.Erf(hitWindow100 / (Math.Sqrt(2) * deviation))); deviation *= Math.Sqrt(1 - randomValue); From 0ff35eb475592d8013d5551fec39991457f9c70b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 28 Nov 2024 21:23:43 +0200 Subject: [PATCH 32/32] remove leftover effective misscount func --- .../Difficulty/OsuPerformanceCalculator.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index aa4f2719a4..5045a2954b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -325,24 +325,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty return flashlightValue; } - private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes) - { - // 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); - } - - // Clamp miss count to maximum amount of possible breaks - comboBasedMissCount = Math.Min(comboBasedMissCount, countOk + countMeh + countMiss); - - return Math.Max(countMiss, comboBasedMissCount); - } - /// /// Using estimates player's deviation on speed notes, assuming worst-case. /// Treats all speed notes as hit circles. This is not good way to do this, but fixing this is impossible under the limitation of current speed pp.