diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
index e8fb4a8469..ed7c60ccf6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
@@ -165,94 +165,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
return Math.Max(0, Math.Pow(difficulty, 1.5) - 1);
}
- // Returns value from 0 to 1, where 0 is very predictable and 1 is very unpredictable
- public static double EvaluateInpredictabilityOf(DifficultyHitObject current)
- {
- // make the sum equal to 1
- const double velocity_change_part = 0.8;
- const double angle_change_part = 0.1;
- const double rhythm_change_part = 0.1;
-
- if (current.BaseObject is Spinner || current.Index == 0 || current.Previous(0).BaseObject is Spinner)
- return 0;
-
- var osuCurrObj = (OsuDifficultyHitObject)current;
- var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
-
- // Rhythm difference punishment for velocity and angle bonuses
- double rhythmSimilarity = 1 - getRhythmDifference(osuCurrObj.StrainTime, osuLastObj.StrainTime);
-
- // Make differentiation going from 1/4 to 1/2 and bigger difference
- // To 1/3 to 1/2 and smaller difference
- rhythmSimilarity = Math.Clamp(rhythmSimilarity, 0.5, 0.75);
- rhythmSimilarity = 4 * (rhythmSimilarity - 0.5);
-
- double velocityChangeBonus = getVelocityChangeFactor(osuCurrObj, osuLastObj) * rhythmSimilarity;
-
- double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
- double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
-
- double angleChangeBonus = 0;
-
- if (osuCurrObj.Angle != null && osuLastObj.Angle != null && currVelocity > 0 && prevVelocity > 0)
- {
- angleChangeBonus = 1 - osuCurrObj.AnglePredictability;
- angleChangeBonus *= Math.Min(currVelocity, prevVelocity) / Math.Max(currVelocity, prevVelocity); // Prevent cheesing
- }
-
- angleChangeBonus *= rhythmSimilarity;
-
- // This bonus only awards rhythm changes if they're not filled with sliderends
- double rhythmChangeBonus = 0;
-
- if (current.Index > 1)
- {
- var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
-
- double currDelta = osuCurrObj.StrainTime;
- double lastDelta = osuLastObj.StrainTime;
-
- if (osuLastObj.BaseObject is Slider sliderCurr)
- {
- currDelta -= sliderCurr.Duration / osuCurrObj.ClockRate;
- currDelta = Math.Max(0, currDelta);
- }
-
- if (osuLastLastObj.BaseObject is Slider sliderLast)
- {
- lastDelta -= sliderLast.Duration / osuLastObj.ClockRate;
- lastDelta = Math.Max(0, lastDelta);
- }
-
- rhythmChangeBonus = getRhythmDifference(currDelta, lastDelta);
- }
-
- double result = velocity_change_part * velocityChangeBonus + angle_change_part * angleChangeBonus + rhythm_change_part * rhythmChangeBonus;
- return result;
- }
-
- private static double getVelocityChangeFactor(OsuDifficultyHitObject osuCurrObj, OsuDifficultyHitObject osuLastObj)
- {
- double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
- double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
-
- double velocityChangeFactor = 0;
-
- // https://www.desmos.com/calculator/kqxmqc8pkg
- if (currVelocity > 0 || prevVelocity > 0)
- {
- double velocityChange = Math.Max(0,
- Math.Min(
- Math.Abs(prevVelocity - currVelocity) - 0.5 * Math.Min(currVelocity, prevVelocity),
- Math.Max(((OsuHitObject)osuCurrObj.BaseObject).Radius / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Min(currVelocity, prevVelocity))
- )); // Stealed from xexxar
- velocityChangeFactor = velocityChange / Math.Max(currVelocity, prevVelocity); // maxiumum is 0.4
- velocityChangeFactor /= 0.4;
- }
-
- return velocityChangeFactor;
- }
-
private static double getTimeNerfFactor(double deltaTime) => Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1);
private static double getRhythmDifference(double t1, double t2) => 1 - Math.Min(t1, t2) / Math.Max(t1, t2);
private static double logistic(double x) => 1 / (1 + Math.Exp(-x));
@@ -282,73 +194,4 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
return arr[result].Overlapness;
}
}
-
- public static class ReadingHiddenEvaluator
- {
- public static double EvaluateDifficultyOf(DifficultyHitObject current)
- {
- var currObj = (OsuDifficultyHitObject)current;
-
- double density = ReadingEvaluator.EvaluateDensityOf(current, false);
- double preempt = currObj.Preempt / 1000;
-
- double densityFactor = Math.Pow(density / 6.2, 1.5);
-
- double invisibilityFactor;
-
- // AR11+DT and faster = 0 HD pp unless density is big
- if (preempt < 0.2) invisibilityFactor = 0;
-
- // Else accelerating growth until around ART0, then linear, and starting from AR5 is 3 times faster again to buff AR0 +HD
- else invisibilityFactor = Math.Min(Math.Pow(preempt * 2.4 - 0.2, 5), Math.Max(preempt, preempt * 3 - 2.4));
-
-
- double hdDifficulty = invisibilityFactor + densityFactor;
-
- // Scale by inpredictability slightly
- hdDifficulty *= 0.96 + 0.1 * ReadingEvaluator.EvaluateInpredictabilityOf(current); // Max multiplier is 1.1
-
- return hdDifficulty;
- }
- }
-
- public static class ReadingHighAREvaluator
- {
- public static double EvaluateDifficultyOf(DifficultyHitObject current, bool applyAdjust = false)
- {
- var currObj = (OsuDifficultyHitObject)current;
-
- double result = GetDifficulty(currObj.Preempt);
-
- if (applyAdjust)
- {
- double inpredictability = ReadingEvaluator.EvaluateInpredictabilityOf(current);
-
- // follow lines make high AR easier, so apply nerf if object isn't new combo
- inpredictability *= 1 + 0.1 * (800 - currObj.FollowLineTime) / 800;
-
- result *= 0.98 + 0.6 * inpredictability;
- }
-
- return result;
- }
-
- // High AR curve
- // https://www.desmos.com/calculator/srzbeumngi
- public static double GetDifficulty(double preempt)
- {
- // Get preempt in seconds
- preempt /= 1000;
- double value;
-
- if (preempt < 0.375) // We have stop in the point of AR10.5, the value here = 0.396875, derivative = -10.5833,
- value = 0.63 * Math.Pow(8 - 20 * preempt, 2.0 / 3); // This function is matching live high AR bonus
- else
- value = Math.Exp(9.07583 - 80.0 * preempt / 3);
-
- // The power is 2 times higher to compensate sqrt in high AR skill
- // EDIT: looks like AR11 getting a bit overnerfed in comparison to other ARs, so i will increase the difference
- return Math.Pow(value, 2.2);
- }
- }
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 4ee92c7b8f..2a2a0e1dc2 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -38,24 +38,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
[JsonProperty("reading_low_ar_difficulty")]
public double ReadingDifficultyLowAR { get; set; }
- ///
- /// The difficulty corresponding to the reading skill. High AR branch.
- ///
- [JsonProperty("reading_high_ar_difficulty")]
- public double ReadingDifficultyHighAR { get; set; }
-
- ///
- /// The difficulty corresponding to the reading skill. Sliders branch.
- ///
- [JsonProperty("reading_sliders_difficulty")]
- public double ReadingDifficultySliders { get; set; }
-
- ///
- /// The difficulty corresponding to the reading skill. Hidden mod branch.
- ///
- [JsonProperty("reading_hidden_difficulty")]
- public double HiddenDifficulty { get; set; }
-
///
/// The difficulty corresponding to the flashlight skill.
///
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index dde58f59c7..1cc31cf2e4 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -44,17 +44,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double hiddenFlashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
double readingLowARRating = Math.Sqrt(skills[4].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
- double readingHighARRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
-
- double hiddenRating = 0;
- double flashlightRating = 0;
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
+ double flashlightRating = 0;
+ double baseFlashlightPerformance = 0.0;
+ if (mods.Any(h => h is OsuModFlashlight))
+ {
+ flashlightRating = Math.Sqrt(skills[5].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
+ baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
+ }
+
if (mods.Any(m => m is OsuModTouchDevice))
{
aimRating = Math.Pow(aimRating, 0.8);
flashlightRating = Math.Pow(flashlightRating, 0.8);
+ readingLowARRating = Math.Pow(readingLowARRating, 0.9);
}
if (mods.Any(h => h is OsuModRelax))
@@ -62,32 +67,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimRating *= 0.9;
speedRating = 0.0;
flashlightRating *= 0.7;
+ readingLowARRating *= 0.95;
}
double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating);
double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating);
// Cognition
- int flIndex = 6;
-
- double baseReadingHiddenPerformance = 0;
- if (mods.Any(h => h is OsuModHidden))
- {
- hiddenRating = Math.Sqrt(skills[6].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
- baseReadingHiddenPerformance = ReadingHidden.DifficultyToPerformance(hiddenRating);
- flIndex++;
- }
-
- double baseFlashlightPerformance = 0.0;
- if (mods.Any(h => h is OsuModFlashlight))
- {
- flashlightRating = Math.Sqrt(skills[flIndex].DifficultyValue()) * DIFFICULTY_MULTIPLIER;
- baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
- }
-
double baseReadingLowARPerformance = ReadingLowAR.DifficultyToPerformance(readingLowARRating);
- double baseReadingHighARPerformance = OsuStrainSkill.DifficultyToPerformance(readingHighARRating);
- double baseReadingARPerformance = Math.Pow(Math.Pow(baseReadingLowARPerformance, SUM_POWER) + Math.Pow(baseReadingHighARPerformance, SUM_POWER), 1.0 / SUM_POWER);
+ double baseReadingARPerformance = baseReadingLowARPerformance;
double baseFlashlightARPerformance = Math.Pow(Math.Pow(baseFlashlightPerformance, FL_SUM_POWER) + Math.Pow(baseReadingARPerformance, FL_SUM_POWER), 1.0 / FL_SUM_POWER);
@@ -100,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
- double cognitionPerformance = baseFlashlightARPerformance + baseReadingHiddenPerformance;
+ double cognitionPerformance = baseFlashlightARPerformance;
double mechanicalPerformance = Math.Pow(Math.Pow(baseAimPerformance, SUM_POWER) + Math.Pow(baseSpeedPerformance, SUM_POWER), 1.0 / SUM_POWER);
// Limit cognition by full memorisation difficulty
@@ -126,8 +114,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
SpeedDifficulty = speedRating,
SpeedNoteCount = speedNotes,
ReadingDifficultyLowAR = readingLowARRating,
- ReadingDifficultyHighAR = readingHighARRating,
- HiddenDifficulty = hiddenRating,
FlashlightDifficulty = flashlightRating,
HiddenFlashlightDifficulty = hiddenFlashlightRating,
SliderFactor = sliderFactor,
@@ -167,12 +153,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
new Speed(mods),
new HiddenFlashlight(mods),
new ReadingLowAR(mods),
- new ReadingHighAR(mods),
};
- if (mods.Any(h => h is OsuModHidden))
- skills.Add(new ReadingHidden(mods));
-
if (mods.Any(h => h is OsuModFlashlight))
skills.Add(new Flashlight(mods));
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index fe4cea02c2..b708d35db6 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -77,23 +77,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double potentialHiddenFlashlightValue = computeFlashlightValue(score, osuAttributes, true);
double lowARValue = computeReadingLowARValue(score, osuAttributes);
- double highARValue = computeReadingHighARValue(score, osuAttributes);
- double readingARValue = Math.Pow(Math.Pow(lowARValue, power) + Math.Pow(highARValue, power), 1.0 / power);
+ double readingARValue = lowARValue;
double flashlightValue = 0;
if (score.Mods.Any(h => h is OsuModFlashlight))
flashlightValue = computeFlashlightValue(score, osuAttributes);
- double readingHDValue = 0;
- if (score.Mods.Any(h => h is OsuModHidden))
- readingHDValue = computeReadingHiddenValue(score, osuAttributes);
-
// Reduce AR reading bonus if FL is present
double flPower = OsuDifficultyCalculator.FL_SUM_POWER;
double flashlightARValue = Math.Pow(Math.Pow(flashlightValue, flPower) + Math.Pow(readingARValue, flPower), 1.0 / flPower);
- double cognitionValue = flashlightARValue + readingHDValue;
+ double cognitionValue = flashlightARValue;
cognitionValue = AdjustCognitionPerformance(cognitionValue, mechanicalValue, potentialHiddenFlashlightValue);
double accuracyValue = computeAccuracyValue(score, osuAttributes);
@@ -129,9 +124,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= getComboScalingFactor(attributes);
+ double approachRateFactor = 0.0;
+ if (attributes.ApproachRate > 10.33)
+ approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
+
+ if (score.Mods.Any(h => h is OsuModRelax))
+ approachRateFactor = 0.0;
+
+ aimValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
+
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);
- else if (score.Mods.Any(m => m is OsuModTraceable))
+ else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
{
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
aimValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
@@ -170,12 +174,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
speedValue *= getComboScalingFactor(attributes);
+ double approachRateFactor = 0.0;
+ if (attributes.ApproachRate > 10.33)
+ approachRateFactor = 0.3 * (attributes.ApproachRate - 10.33);
+
+ speedValue *= 1.0 + approachRateFactor * lengthBonus; // Buff for longer maps with high AR.
+
if (score.Mods.Any(m => m is OsuModBlinds))
{
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
speedValue *= 1.12;
}
- else if (score.Mods.Any(m => m is OsuModTraceable))
+ else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
{
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
speedValue *= 1.0 + 0.04 * (12.0 - attributes.ApproachRate);
@@ -225,6 +235,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// 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))
accuracyValue *= 1.14;
+ else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
+ accuracyValue *= 1.08;
if (score.Mods.Any(m => m is OsuModFlashlight))
accuracyValue *= 1.02;
@@ -239,8 +251,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double deltaBonus = (1 - Math.Pow(0.95, Math.Pow(ARODDelta, 4)));
accuracyValue *= 1 + visualBonus * (1 + 2 * deltaBonus);
- if (score.Mods.Any(h => h is OsuModHidden || h is OsuModTraceable))
- accuracyValue *= 1 + visualBonus * (1 + deltaBonus);
return accuracyValue;
}
@@ -299,86 +309,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return readingValue;
}
-
- private double computeReadingHighARValue(ScoreInfo score, OsuDifficultyAttributes attributes)
- {
- double highARValue = OsuStrainSkill.DifficultyToPerformance(attributes.ReadingDifficultyHighAR);
-
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
- if (effectiveMissCount > 0)
- highARValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), effectiveMissCount);
-
- highARValue *= getComboScalingFactor(attributes);
-
- // Approximate how much of high AR difficulty is aim
- double aimPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
- double speedPerformance = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
-
- double aimRatio = aimPerformance / (aimPerformance + speedPerformance);
-
- // Aim part calculation
- double aimPartValue = highARValue * aimRatio;
- {
- // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator.
- double estimateDifficultSliders = attributes.SliderCount * 0.15;
-
- if (attributes.SliderCount > 0)
- {
- 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;
- aimPartValue *= sliderNerfFactor;
- }
-
- aimPartValue *= accuracy;
- // It is important to consider accuracy difficulty when scaling with accuracy.
- aimPartValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
- }
-
- // Speed part calculation
- double speedPartValue = highARValue * (1 - aimRatio);
- {
- // 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.
- speedPartValue *= (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.
- speedPartValue *= Math.Pow(0.99, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
- }
-
- return aimPartValue + speedPartValue;
- }
-
- private double computeReadingHiddenValue(ScoreInfo score, OsuDifficultyAttributes attributes)
- {
- if (!score.Mods.Any(h => h is OsuModHidden))
- return 0.0;
-
- double rawReading = attributes.HiddenDifficulty;
- double hiddenValue = ReadingHidden.DifficultyToPerformance(attributes.HiddenDifficulty);
-
- double lengthBonus = CalculateDefaultLengthBonus(totalHits);
- hiddenValue *= lengthBonus;
-
- // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
- if (effectiveMissCount > 0)
- hiddenValue *= 0.97 * Math.Pow(1 - Math.Pow(effectiveMissCount / totalHits, 0.775), Math.Pow(effectiveMissCount, .875));
-
- hiddenValue *= getComboScalingFactor(attributes);
-
- // Scale the reading value with accuracy _harshly_. Additional note: it would have it's own curve in Statistical Accuracy rework.
- hiddenValue *= accuracy * accuracy;
- // It is important to also consider accuracy difficulty when doing that.
- hiddenValue *= 0.98 + Math.Pow(attributes.OverallDifficulty, 2) / 2500;
-
- return hiddenValue;
- }
-
private double calculateEffectiveMissCount(OsuDifficultyAttributes attributes)
{
// Guess the number of misses + slider breaks from combo
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index bf968e2145..64d12c6d9f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -98,11 +98,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
///
public double HitWindowGreat { get; private set; }
- ///
- /// Rhythm difficulty of the object. Saved for optimization, rhythm calculation is very expensive.
- ///
- public double RhythmDifficulty { get; private set; }
-
///
/// Density of the object for given preempt. Saved for optimization, density calculation is expensive.
///
@@ -128,18 +123,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
///
public readonly double Preempt;
- ///
- /// Preempt of follow line for this adjusted by clockrate.
- /// Will be equal to 0 if object is New Combo.
- ///
- public readonly double FollowLineTime;
-
- ///
- /// Playback rate of beatmap.
- /// Will be equal 1.5 on DT and 0.75 on HT.
- ///
- public readonly double ClockRate;
-
private readonly OsuHitObject? lastLastObject;
private readonly OsuHitObject lastObject;
@@ -152,9 +135,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
StackedPosition = currObject.StackedPosition;
Preempt = BaseObject.TimePreempt / clockRate;
- FollowLineTime = 800 / clockRate; // 800ms is follow line appear time
- FollowLineTime *= (currObject.NewCombo ? 0 : 1); // no follow lines when NC
- ClockRate = clockRate;
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
StrainTime = Math.Max(DeltaTime, min_delta_time);
@@ -174,7 +154,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
(ReadingObjects, OverlapValues) = getReadingObjects();
- RhythmDifficulty = RhythmEvaluator.EvaluateDifficultyOf(this);
Density = ReadingEvaluator.EvaluateDensityOf(this);
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 6929095f21..3f6b22bbb1 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -21,17 +21,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
private readonly bool withSliders;
- protected double CurrentStrain;
- protected double SkillMultiplier => 23.55;
+ private double currentStrain;
- protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => CurrentStrain * StrainDecay(time - current.Previous(0).StartTime);
+ private double skillMultiplier => 23.55;
+ private double strainDecayBase => 0.15;
+
+ private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
+
+ protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
protected override double StrainValueAt(DifficultyHitObject current)
{
- CurrentStrain *= StrainDecay(current.DeltaTime);
- CurrentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * SkillMultiplier;
+ currentStrain *= strainDecay(current.DeltaTime);
+ currentStrain += AimEvaluator.EvaluateDifficultyOf(current, withSliders) * skillMultiplier;
- return CurrentStrain;
+ return currentStrain;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
index 9d2719449a..9fafeacb9c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
hasHiddenMod = mods.Any(m => m is OsuModHidden);
}
- private double skillMultiplier => 0.05;
+ private double skillMultiplier => 0.052;
private double strainDecayBase => 0.15;
private double currentStrain;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
index 103ca5571f..93c21d33ad 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs
@@ -34,10 +34,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
protected virtual double DifficultyMultiplier => DEFAULT_DIFFICULTY_MULTIPLIER;
- protected virtual double StrainDecayBase => 0.15;
-
- protected double StrainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000);
-
protected OsuStrainSkill(Mod[] mods)
: base(mods)
{
@@ -76,10 +72,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// Converts difficulty value from to base performance.
///
public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0;
-
- ///
- /// Converts base performance to difficulty value.s
- ///
- public static double PerformanceToDifficulty(double performance) => (Math.Pow(100000.0 * performance, 1.0 / 3.0) + 4.0) / 5.0 * 0.0675;
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs
index ac3e9e64e9..4835d5d817 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs
@@ -9,13 +9,11 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
-using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
-using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
- public class ReadingLowAR : GraphSkill
+ public class ReadingLowAR : Skill
{
private readonly List difficulties = new List();
private double skillMultiplier => 1.26;
@@ -42,18 +40,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier;
difficulties.Add(totalDensityDifficulty);
-
- if (current.Index == 0)
- CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
-
- while (current.StartTime > CurrentSectionEnd)
- {
- StrainPeaks.Add(CurrentSectionPeak);
- CurrentSectionPeak = 0;
- CurrentSectionEnd += SectionLength;
- }
-
- CurrentSectionPeak = Math.Max(totalDensityDifficulty, CurrentSectionPeak);
}
private double reducedNoteCount => 5;
@@ -89,145 +75,4 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
Math.Max(Math.Pow(difficulty, 1.5) * 20, Math.Pow(difficulty, 2) * 17.0),
Math.Max(Math.Pow(difficulty, 3) * 10.5, Math.Pow(difficulty, 4) * 6.00));
}
-
- public class ReadingHidden : Aim
- {
- public ReadingHidden(Mod[] mods)
- : base(mods, false)
- {
- }
- protected new double SkillMultiplier => 7.2;
-
- protected override double StrainValueAt(DifficultyHitObject current)
- {
- CurrentStrain *= StrainDecay(current.DeltaTime);
-
- // We're not using slider aim because we assuming that HD doesn't makes sliders harder (what is not true, but we will ignore this for now)
- double hiddenDifficulty = AimEvaluator.EvaluateDifficultyOf(current, false);
- hiddenDifficulty *= ReadingHiddenEvaluator.EvaluateDifficultyOf(current);
- hiddenDifficulty *= SkillMultiplier;
-
- CurrentStrain += hiddenDifficulty;
-
- return CurrentStrain;
- }
-
- public new static double DifficultyToPerformance(double difficulty) => Math.Max(
- Math.Max(difficulty * 16, Math.Pow(difficulty, 2) * 10), Math.Pow(difficulty, 3) * 4);
- }
-
- public class ReadingHighAR : GraphSkill
- {
-
- private const double component_multiplier = 0.135;
- private const double component_default_value_multiplier = 60;
- public ReadingHighAR(Mod[] mods)
- : base(mods)
- {
- aimComponent = new HighARAimComponent(mods);
- speedComponent = new HighARSpeedComponent(mods);
- }
-
- private HighARAimComponent aimComponent;
- private HighARSpeedComponent speedComponent;
-
- private readonly List difficulties = new List();
- private int objectsCount = 0;
-
- public override void Process(DifficultyHitObject current)
- {
- aimComponent.Process(current);
- speedComponent.Process(current);
-
- if (current.BaseObject is not Spinner)
- objectsCount++;
-
- double power = OsuDifficultyCalculator.SUM_POWER;
- double mergedDifficulty = Math.Pow(
- Math.Pow(aimComponent.CurrentSectionPeak, power) +
- Math.Pow(speedComponent.CurrentSectionPeak, power), 1.0 / power);
-
- difficulties.Add(mergedDifficulty);
-
- if (current.Index == 0)
- CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
-
- while (current.StartTime > CurrentSectionEnd)
- {
- StrainPeaks.Add(CurrentSectionPeak);
- CurrentSectionPeak = 0;
- CurrentSectionEnd += SectionLength;
- }
-
- CurrentSectionPeak = Math.Max(mergedDifficulty, CurrentSectionPeak);
- }
- public override double DifficultyValue()
- {
- // Simulating summing to get the most correct value possible
- double aimValue = Math.Sqrt(aimComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER;
- double speedValue = Math.Sqrt(speedComponent.DifficultyValue()) * OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER;
-
- double aimPerformance = OsuStrainSkill.DifficultyToPerformance(aimValue);
- double speedPerformance = OsuStrainSkill.DifficultyToPerformance(speedValue);
-
- double power = OsuDifficultyCalculator.SUM_POWER;
- double totalPerformance = Math.Pow(Math.Pow(aimPerformance, power) + Math.Pow(speedPerformance, power), 1.0 / power);
-
- // Length bonus is in SR to not inflate Star Rating short AR11 maps
- double lengthBonus = OsuPerformanceCalculator.CalculateDefaultLengthBonus(objectsCount);
- totalPerformance *= Math.Pow(lengthBonus, 4); // make it bypass sqrt
-
- double adjustedDifficulty = OsuStrainSkill.PerformanceToDifficulty(totalPerformance);
- double difficultyValue = Math.Pow(adjustedDifficulty / OsuDifficultyCalculator.DIFFICULTY_MULTIPLIER, 2.0);
-
- return 53.2 * Math.Sqrt(difficultyValue);
- }
-
- public class HighARAimComponent : Aim
- {
- public HighARAimComponent(Mod[] mods)
- : base(mods, true)
- {
- }
-
- protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier;
-
- protected override double StrainValueAt(DifficultyHitObject current)
- {
- CurrentStrain *= StrainDecay(current.DeltaTime);
-
- double aimDifficulty = AimEvaluator.EvaluateDifficultyOf(current, true) * SkillMultiplier;
- aimDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current, true);
-
- CurrentStrain += aimDifficulty;
-
- return CurrentStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current, true);
- }
- }
-
- public class HighARSpeedComponent : Speed
- {
- public HighARSpeedComponent(Mod[] mods)
- : base(mods)
- {
- }
-
- protected new double SkillMultiplier => base.SkillMultiplier * component_multiplier;
-
- protected override double StrainValueAt(DifficultyHitObject current)
- {
- OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current;
-
- CurrentStrain *= StrainDecay(currObj.StrainTime);
-
- double speedDifficulty = SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier;
- speedDifficulty *= ReadingHighAREvaluator.EvaluateDifficultyOf(current);
- CurrentStrain += speedDifficulty;
-
- CurrentRhythm = currObj.RhythmDifficulty;
- double totalStrain = CurrentStrain * CurrentRhythm;
- return totalStrain + component_default_value_multiplier * ReadingHighAREvaluator.EvaluateDifficultyOf(current);
- }
- }
- }
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index c360d21bb7..40aac013ab 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
///
public class Speed : OsuStrainSkill
{
- protected double SkillMultiplier => 1375;
- protected override double StrainDecayBase => 0.3;
+ private double skillMultiplier => 1375;
+ private double strainDecayBase => 0.3;
- protected double CurrentStrain;
- protected double CurrentRhythm;
+ private double currentStrain;
+ private double currentRhythm;
protected override int ReducedSectionCount => 5;
protected override double DifficultyMultiplier => 1.04;
@@ -32,17 +32,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
}
- protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (CurrentStrain * CurrentRhythm) * StrainDecay(time - current.Previous(0).StartTime);
+ private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
+
+ protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime);
protected override double StrainValueAt(DifficultyHitObject current)
{
- OsuDifficultyHitObject currODHO = (OsuDifficultyHitObject)current;
+ currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
+ currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * skillMultiplier;
- CurrentStrain *= StrainDecay(currODHO.StrainTime);
- CurrentStrain += SpeedEvaluator.EvaluateDifficultyOf(current) * SkillMultiplier;
+ currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
- CurrentRhythm = currODHO.RhythmDifficulty;
- double totalStrain = CurrentStrain * CurrentRhythm;
+ double totalStrain = currentStrain * currentRhythm;
objectStrains.Add(totalStrain);
diff --git a/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs
deleted file mode 100644
index 953abd61fe..0000000000
--- a/osu.Game/Rulesets/Difficulty/Skills/GraphSkill.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using System.Linq;
-using osu.Game.Rulesets.Mods;
-
-namespace osu.Game.Rulesets.Difficulty.Skills
-{
- ///
- /// A abstract skill with available per objet difficulty.
- ///
- ///
- /// This class should be considered a "processing" class and not persisted.
- ///
- public abstract class GraphSkill : Skill
- {
- protected GraphSkill(Mod[] mods)
- : base(mods)
- {
- }
-
- ///
- /// The length of each section.
- ///
- protected virtual int SectionLength => 400;
-
- public double CurrentSectionPeak { get; protected set; } // We also keep track of the peak level in the current section.
-
- protected double CurrentSectionEnd;
-
- protected readonly List StrainPeaks = new List();
-
- ///
- /// Returns a live enumerable of the difficulties
- ///
- public virtual IEnumerable GetCurrentStrainPeaks() => StrainPeaks.Append(CurrentSectionPeak);
- }
-}
diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
index ace40b07b2..b07e8399c0 100644
--- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
+++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
@@ -12,13 +13,24 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// Used to processes strain values of s, keep track of strain levels caused by the processed objects
/// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects.
///
- public abstract class StrainSkill : GraphSkill
+ public abstract class StrainSkill : Skill
{
///
/// The weight by which each strain value decays.
///
protected virtual double DecayWeight => 0.9;
+ ///
+ /// The length of each strain section.
+ ///
+ protected virtual int SectionLength => 400;
+
+ private double currentSectionPeak; // We also keep track of the peak strain level in the current section.
+
+ private double currentSectionEnd;
+
+ private readonly List strainPeaks = new List();
+
protected StrainSkill(Mod[] mods)
: base(mods)
{
@@ -36,16 +48,16 @@ namespace osu.Game.Rulesets.Difficulty.Skills
{
// The first object doesn't generate a strain, so we begin with an incremented section end
if (current.Index == 0)
- CurrentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
+ currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
- while (current.StartTime > CurrentSectionEnd)
+ while (current.StartTime > currentSectionEnd)
{
saveCurrentPeak();
- startNewSectionFrom(CurrentSectionEnd, current);
- CurrentSectionEnd += SectionLength;
+ startNewSectionFrom(currentSectionEnd, current);
+ currentSectionEnd += SectionLength;
}
- CurrentSectionPeak = Math.Max(StrainValueAt(current), CurrentSectionPeak);
+ currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak);
}
///
@@ -53,7 +65,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
///
private void saveCurrentPeak()
{
- StrainPeaks.Add(CurrentSectionPeak);
+ strainPeaks.Add(currentSectionPeak);
}
///
@@ -65,7 +77,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills
{
// The maximum strain of the new section is not zero by default
// This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level.
- CurrentSectionPeak = CalculateInitialStrain(time, current);
+ currentSectionPeak = CalculateInitialStrain(time, current);
}
///
@@ -76,6 +88,12 @@ namespace osu.Game.Rulesets.Difficulty.Skills
/// The peak strain.
protected abstract double CalculateInitialStrain(double time, DifficultyHitObject current);
+ ///
+ /// Returns a live enumerable of the peak strains for each section of the beatmap,
+ /// including the peak of the current section.
+ ///
+ public IEnumerable GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak);
+
///
/// Returns the calculated difficulty value representing all s that have been processed up to this point.
///