diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs new file mode 100644 index 0000000000..687cd03152 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Difficulty +{ + public class CatchDifficultyAttributes : DifficultyAttributes + { + public double AimRating; + public double ApproachRate; + public int MaxCombo; + + public CatchDifficultyAttributes(Mod[] mods, double starRating) + : base(mods, starRating) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index acdec851d8..520a980d26 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -2,6 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Internal; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -13,31 +15,39 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyCalculator : DifficultyCalculator { + + /// + /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. + /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. + /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. + /// + private const double strain_step = 750; + + /// + /// The weighting of each strain value decays to this number * it's previous value + /// + private const double decay_weight = 0.94; + private const double star_scaling_factor = 0.145; - private const float playfield_width = CatchPlayfield.BASE_WIDTH; - private readonly List difficultyHitObjects = new List(); - - public CatchDifficultyCalculator(IBeatmap beatmap) - : base(beatmap) + public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) { } - public CatchDifficultyCalculator(IBeatmap beatmap, Mod[] mods) - : base(beatmap, mods) + protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) { - } + if (!beatmap.HitObjects.Any()) + return new CatchDifficultyAttributes(mods, 0); - public override double Calculate(Dictionary categoryDifficulty = null) - { - difficultyHitObjects.Clear(); - - float circleSize = Beatmap.BeatmapInfo.BaseDifficulty.CircleSize; + float circleSize = beatmap.BeatmapInfo.BaseDifficulty.CircleSize; float catcherWidth = (1.0f - 0.7f * (circleSize - 5) / 5) * 0.62064f * CatcherArea.CATCHER_SIZE; float catcherWidthHalf = catcherWidth / 2; catcherWidthHalf *= 0.8f; - foreach (var hitObject in Beatmap.HitObjects) + var difficultyHitObjects = new List(); + + foreach (var hitObject in beatmap.HitObjects) { // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. if (hitObject is Fruit) @@ -60,28 +70,26 @@ namespace osu.Game.Rulesets.Catch.Difficulty difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - if (!CalculateStrainValues()) return 0; + if (!calculateStrainValues(difficultyHitObjects, timeRate)) + return new CatchDifficultyAttributes(mods, 0); - double starRating = Math.Sqrt(CalculateDifficulty()) * star_scaling_factor; + double ar = beatmap.BeatmapInfo.BaseDifficulty.ApproachRate; + double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / timeRate; - if (categoryDifficulty != null) + double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor; + + return new CatchDifficultyAttributes(mods, starRating) { - categoryDifficulty["Aim"] = starRating; - - double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate; - double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate; - - categoryDifficulty["AR"] = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0; - categoryDifficulty["Max combo"] = difficultyHitObjects.Count; - } - - return starRating; + AimRating = starRating, + ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0, + MaxCombo = difficultyHitObjects.Count + }; } - protected bool CalculateStrainValues() + private bool calculateStrainValues(List objects, double timeRate) { // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) + using (var hitObjectsEnumerator = objects.GetEnumerator()) { if (!hitObjectsEnumerator.MoveNext()) return false; @@ -91,7 +99,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty while (hitObjectsEnumerator.MoveNext()) { CatchDifficultyHitObject nextHitObject = hitObjectsEnumerator.Current; - nextHitObject?.CalculateStrains(currentHitObject, TimeRate); + nextHitObject?.CalculateStrains(currentHitObject, timeRate); currentHitObject = nextHitObject; } @@ -99,22 +107,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty } } - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 750; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.94; - - protected double CalculateDifficulty() + private double calculateDifficulty(List objects, double timeRate) { // The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods - double actualStrainStep = strain_step * TimeRate; + double actualStrainStep = strain_step * timeRate; // Find the highest strain value within each strain step List highestStrains = new List(); @@ -122,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval CatchDifficultyHitObject previousHitObject = null; - foreach (CatchDifficultyHitObject hitObject in difficultyHitObjects) + foreach (CatchDifficultyHitObject hitObject in objects) { // While we are beyond the current interval push the currently available maximum to our strain list while (hitObject.BaseHitObject.StartTime > intervalEndTime) @@ -151,8 +147,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty previousHitObject = hitObject; } - // calculate maximun strain difficulty - double difficulty = StrainCalculator(highestStrains, decay_weight); + // Build the weighted sum over the highest strains for each interval + double difficulty = 0; + double weight = 1; + highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + foreach (double strain in highestStrains) + { + difficulty += weight * strain; + weight *= decay_weight; + } return difficulty; } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs index 72fc6d211b..720c1d8653 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs @@ -8,7 +8,7 @@ using OpenTK; namespace osu.Game.Rulesets.Catch.Difficulty { - internal class CatchDifficultyHitObject + public class CatchDifficultyHitObject { internal static readonly double DECAY_BASE = 0.20; private const float normalized_hitobject_radius = 41.0f; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 446dcfd0b4..473c205293 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -110,8 +110,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty previousHitObject = hitObject; } - // calculate maximun strain difficulty - double difficulty = StrainCalculator(highestStrains, decay_weight); + // Build the weighted sum over the highest strains for each interval + double difficulty = 0; + double weight = 1; + highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + + foreach (double strain in highestStrains) + { + difficulty += weight * strain; + weight *= decay_weight; + } return difficulty; }