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;
}
}