diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 12f4540924..dc803af46f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -61,9 +61,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty [JsonProperty("low_ar_difficult_strain_count")] public double LowArDifficultStrainCount { get; set; } - [JsonProperty("hidden_difficult_strain_count")] - public double HiddenDifficultStrainCount { 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 4922002ec7..3847ed6a8e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { - public const double DIFFICULTY_MULTIPLIER = 0.0675; + private const double difficulty_multiplier = 0.0675; public const double SUM_POWER = 1.1; public const double FL_SUM_POWER = 1.5; public override int Version => 20241007; @@ -37,17 +37,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (beatmap.HitObjects.Count == 0) return new OsuDifficultyAttributes { Mods = mods }; - double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double speedRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; + double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * difficulty_multiplier; double speedNotes = skills.OfType().First().RelevantNoteCount(); - double flashlightRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * DIFFICULTY_MULTIPLIER; - double readingLowARRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * DIFFICULTY_MULTIPLIER; + double flashlightRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * difficulty_multiplier; + double readingLowARRating = Math.Sqrt(skills.OfType().First().DifficultyValue()) * difficulty_multiplier; double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; - double aimDifficultyStrainCount = skills[0].CountTopWeightedStrains(); + double aimDifficultyStrainCount = skills.OfType().First().CountTopWeightedStrains(); double speedDifficultyStrainCount = skills.OfType().First().CountTopWeightedStrains(); double lowArDifficultyStrainCount = skills.OfType().First().CountTopWeightedStrains(); @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { aimRating *= 0.9; speedRating = 0.0; - readingLowARRating *= 0.95; flashlightRating *= 0.7; readingLowARRating *= 0.95; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index cfb8dec2b8..5ace17bdbc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; @@ -20,17 +21,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly bool withSliders; - protected double CurrentStrain; - protected double SkillMultiplier => 25.3; + private double currentStrain; - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => CurrentStrain * StrainDecay(time - current.Previous(0).StartTime); + private double skillMultiplier => 25.3; + 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/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 367f3bee18..6823512cef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -23,10 +23,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// protected virtual double ReducedStrainBaseline => 0.75; - protected virtual double StrainDecayBase => 0.15; - - protected double StrainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); - protected OsuStrainSkill(Mod[] mods) : base(mods) { @@ -61,9 +57,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return difficulty; } - /// - /// 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; } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs index 3a60f5843c..d51d5fdbb9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Reading.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private double currentDensityAimStrain = 0; - public override void Process(DifficultyHitObject current) + protected override double StrainValueAt(DifficultyHitObject current) { double densityReadingDifficulty = ReadingEvaluator.EvaluateDifficultyOf(current); double densityAimingFactor = ReadingEvaluator.EvaluateAimingDensityFactorOf(current); @@ -37,22 +37,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills currentDensityAimStrain += densityAimingFactor * AimEvaluator.EvaluateDifficultyOf(current, true) * aimComponentMultiplier; double totalDensityDifficulty = (currentDensityAimStrain + densityReadingDifficulty) * skillMultiplier; - - ObjectStrains.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); + return totalDensityDifficulty; } + protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => 0; + private double reducedNoteCount => 5; private double reducedNoteBaseline => 0.7; public override double DifficultyValue() @@ -85,15 +74,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public static double DifficultyToPerformance(double difficulty) => Math.Max( Math.Max(Math.Pow(difficulty, 1.5) * 20, Math.Pow(difficulty, 2) * 17.0), Math.Max(Math.Pow(difficulty, 3) * 10.5, Math.Pow(difficulty, 4) * 6.00)); - - protected override double StrainValueAt(DifficultyHitObject current) - { - throw new NotImplementedException(); - } - - protected override double CalculateInitialStrain(double time, DifficultyHitObject current) - { - throw new NotImplementedException(); - } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 56b51a915e..d2c4bbb618 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -15,11 +15,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : OsuStrainSkill { - protected double SkillMultiplier => 1.430; - protected override double StrainDecayBase => 0.3; + private double skillMultiplier => 1.430; + private double strainDecayBase => 0.3; - protected double CurrentStrain; - protected double CurrentRhythm; + private double currentStrain; + private double currentRhythm; protected override int ReducedSectionCount => 5; @@ -28,17 +28,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 = RhythmEvaluator.EvaluateDifficultyOf(current); - double totalStrain = CurrentStrain * CurrentRhythm; + double totalStrain = currentStrain * currentRhythm; return totalStrain; } diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 6b48fa041f..8b8892113b 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -1,9 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // 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; @@ -29,8 +27,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills this.mods = mods; } - protected List ObjectStrains = new List(); - /// /// Process a . /// @@ -41,23 +37,5 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Returns the calculated difficulty value representing all s that have been processed up to this point. /// public abstract double DifficultyValue(); - - /// - /// Calculates the number of strains weighted against the top strain. - /// The result is scaled by clock rate as it affects the total number of strains. - /// - public virtual double CountTopWeightedStrains() - { - if (ObjectStrains.Count == 0) - return 0.0; - - double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical - - if (consistentTopStrain == 0) - return ObjectStrains.Count; - - // Use a weighted sum of all strains. Constants are arbitrary and give nice values - return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); - } } } diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 0d71ba0680..a76a0a5526 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -20,21 +20,21 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected virtual double DecayWeight => 0.9; - protected StrainSkill(Mod[] mods) - : base(mods) - { - } - /// /// The length of each strain section. /// protected virtual int SectionLength => 400; - public double CurrentSectionPeak { get; protected set; } // We also keep track of the peak level in the current section. + private double currentSectionPeak; // We also keep track of the peak strain level in the current section. + private double currentSectionEnd; - protected double CurrentSectionEnd; + private readonly List strainPeaks = new List(); + protected readonly List ObjectStrains = new List(); // Store individual strains - protected readonly List StrainPeaks = new List(); + protected StrainSkill(Mod[] mods) + : base(mods) + { + } /// /// Returns the strain value at . This value is calculated with or without respect to previous objects. @@ -48,28 +48,46 @@ 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; } double strain = StrainValueAt(current); - CurrentSectionPeak = Math.Max(strain, CurrentSectionPeak); + currentSectionPeak = Math.Max(strain, currentSectionPeak); // Store the strain value for the object ObjectStrains.Add(strain); } + /// + /// Calculates the number of strains weighted against the top strain. + /// The result is scaled by clock rate as it affects the total number of strains. + /// + public virtual double CountTopWeightedStrains() + { + if (ObjectStrains.Count == 0) + return 0.0; + + double consistentTopStrain = DifficultyValue() / 10; // What would the top strain be if all strain values were identical + + if (consistentTopStrain == 0) + return ObjectStrains.Count; + + // Use a weighted sum of all strains. Constants are arbitrary and give nice values + return ObjectStrains.Sum(s => 1.1 / (1 + Math.Exp(-10 * (s / consistentTopStrain - 0.88)))); + } + /// /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. /// private void saveCurrentPeak() { - StrainPeaks.Add(CurrentSectionPeak); + strainPeaks.Add(currentSectionPeak); } /// @@ -81,7 +99,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); } /// @@ -93,9 +111,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills protected abstract double CalculateInitialStrain(double time, DifficultyHitObject current); /// - /// Returns a live enumerable of the difficulties + /// Returns a live enumerable of the peak strains for each section of the beatmap, + /// including the peak of the current section. /// - public virtual IEnumerable GetCurrentStrainPeaks() => StrainPeaks.Append(CurrentSectionPeak); + public IEnumerable GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak); /// /// Returns the calculated difficulty value representing all s that have been processed up to this point.