diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index a3c0209a08..718e502178 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -52,6 +52,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty [JsonProperty("speed_difficult_strain_count")] public double SpeedDifficultStrainCount { get; set; } + // DEV ATTRIBUTE - DO NOT STORE + [JsonProperty("aim_relevant_object_count")] + public double AimRelevantObjectCount { get; set; } + + // DEV ATTRIBUTE - DO NOTE STORE + [JsonProperty("speed_relevant_object_count")] + public double SpeedRelevantObjectCount { get; set; } + /// /// The perceived approach rate inclusive of rate-adjusting mods (DT/HT/etc). /// diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 575e03051c..ce182d8560 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -46,24 +46,41 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(h => h is OsuModFlashlight)) flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; - double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - double aimDifficultyStrainCount = ((OsuStrainSkill)skills[0]).CountTopWeightedStrains(); double speedDifficultyStrainCount = ((OsuStrainSkill)skills[2]).CountTopWeightedStrains(); if (mods.Any(m => m is OsuModTouchDevice)) { aimRating = Math.Pow(aimRating, 0.8); + aimRatingNoSliders = Math.Pow(aimRatingNoSliders, 0.8); flashlightRating = Math.Pow(flashlightRating, 0.8); } if (mods.Any(h => h is OsuModRelax)) { aimRating *= 0.9; + aimRatingNoSliders *= 0.9; speedRating = 0.0; flashlightRating *= 0.7; } + + double aimRelevantObjectCount = ((OsuStrainSkill)skills[0]).CountRelevantObjects(); + double aimNoSlidersRelevantObjectCount = ((OsuStrainSkill)skills[1]).CountRelevantObjects(); + double speedRelevantObjectCount = ((OsuStrainSkill)skills[2]).CountRelevantObjects(); + + double aimLengthBonus = (aimRelevantObjectCount < 25 ? 0.8 + aimRelevantObjectCount / 150.0 : 0.9 + Math.Min(1.5, aimRelevantObjectCount / 375.0) + + (aimRelevantObjectCount > 562.5 ? Math.Log10(aimRelevantObjectCount / 562.5) : 0)); + aimRating *= Math.Cbrt(aimLengthBonus); + double aimNoSlidersLengthBonus = (aimNoSlidersRelevantObjectCount < 25 ? 0.8 + aimNoSlidersRelevantObjectCount / 150.0 : 0.9 + aimNoSlidersRelevantObjectCount / 375.0); + aimRatingNoSliders *= Math.Cbrt(aimNoSlidersLengthBonus); + + double speedLengthBonus = 0.9 + 0.5 * Math.Min(1.0, speedRelevantObjectCount / 500.0) + + (speedRelevantObjectCount > 500 ? Math.Log10(speedRelevantObjectCount / 500.0) * 0.3 : 0.0); + speedRating *= Math.Cbrt(speedLengthBonus); + + double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; + double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating); double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); double baseFlashlightPerformance = 0.0; @@ -79,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty ); double starRating = basePerformance > 0.00001 - ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) + ? Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * 0.026 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; @@ -105,6 +122,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty SliderFactor = sliderFactor, AimDifficultStrainCount = aimDifficultyStrainCount, SpeedDifficultStrainCount = speedDifficultyStrainCount, + AimRelevantObjectCount = aimRelevantObjectCount, + SpeedRelevantObjectCount = speedRelevantObjectCount, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 31b00dba2b..bed658559f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuPerformanceCalculator : PerformanceCalculator { - public const double PERFORMANCE_BASE_MULTIPLIER = 1.15; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. + public const double PERFORMANCE_BASE_MULTIPLIER = 1.152; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. private bool usingClassicSliderAccuracy; @@ -137,10 +137,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty); - 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; - if (effectiveMissCount > 0) aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount); @@ -153,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (score.Mods.Any(h => h is OsuModRelax)) approachRateFactor = 0.0; - aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. + aimValue *= 1.0 + approachRateFactor; if (score.Mods.Any(m => m is OsuModBlinds)) aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate); @@ -201,10 +197,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); - 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; - if (effectiveMissCount > 0) speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount); @@ -212,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (attributes.ApproachRate > 10.33) approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33); - speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR. + speedValue *= 1.0 + approachRateFactor; if (score.Mods.Any(m => m is OsuModBlinds)) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 6823512cef..d4a85b9856 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -57,6 +57,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return difficulty; } + /// + /// Returns the number of relevant objects weighted against the top strain. + /// + public double CountRelevantObjects() + { + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical + if (consistentTopStrain == 0) + return 0.0; + + //Being consistently difficult for 1000 notes should be worth more than being consistently difficult for 100. + double totalStrains = ObjectStrains.Count; + double lengthFactor = 0.74 * Math.Pow(0.9987, totalStrains); + //// Use a weighted sum of all strains. Constants are arbitrary and give nice values + return ObjectStrains.Sum(s => (1.1 - lengthFactor)/ (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88 - lengthFactor / 4.0)))); + } + public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0; } }