From 8eba94e8c9a31300877fd7b5b8aa895571a35369 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:46:32 +0900 Subject: [PATCH 01/10] Implement new difficulty calculator for Rulesets.Catch --- .../CatchDifficultyCalculatorTest.cs | 2 +- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../Difficulty/CatchDifficultyAttributes.cs | 4 +- .../Difficulty/CatchDifficultyCalculator.cs | 81 ++++++++++ .../Difficulty/CatchDifficultyHitObject.cs | 130 --------------- .../CatchLegacyDifficultyCalculator.cs | 148 ------------------ .../Preprocessing/CatchDifficultyHitObject.cs | 68 ++++++++ .../Difficulty/Skills/Movement.cs | 63 ++++++++ .../Objects/CatchHitObject.cs | 5 + 9 files changed, 221 insertions(+), 282 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs delete mode 100644 osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs delete mode 100644 osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs create mode 100644 osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs create mode 100644 osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 84f4fd9c99..61fb4ca5d1 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests public void Test(double expected, string name) => base.Test(expected, name); - protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(new CatchRuleset(), beatmap); + protected override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); protected override Ruleset CreateRuleset() => new CatchRuleset(); } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9b2bbc9bf7..c539a47897 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Catch public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; - public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchLegacyDifficultyCalculator(this, beatmap); + public override LegacyDifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); public override int? LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index c6518cbf8a..8669543841 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty public double ApproachRate; public int MaxCombo; - public CatchDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) + public CatchDifficultyAttributes(Mod[] mods) + : base(mods) { } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs new file mode 100644 index 0000000000..99702235d5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -0,0 +1,81 @@ +// 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.Beatmaps; +using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.Difficulty.Skills; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Difficulty +{ + public class CatchDifficultyCalculator : DifficultyCalculator + { + private const double star_scaling_factor = 0.145; + + protected override int SectionLength => 750; + + private readonly float halfCatchWidth; + + public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); + halfCatchWidth = catcher.CatchWidth * 0.5f; + } + + protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + { + var catchAttributes = (CatchDifficultyAttributes)attributes; + + // this is the same as osu!, so there's potential to share the implementation... maybe + double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; + + catchAttributes.StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor; + catchAttributes.ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0; + catchAttributes.MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + { + CatchHitObject lastObject = null; + + foreach (var hitObject in beatmap.HitObjects.OfType()) + { + if (lastObject == null) + { + lastObject = hitObject; + continue; + } + + switch (hitObject) + { + // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. + case Fruit fruit: + yield return new CatchDifficultyHitObject(fruit, lastObject, timeRate, halfCatchWidth); + break; + case JuiceStream _: + foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) + yield return new CatchDifficultyHitObject(nested, lastObject, timeRate, halfCatchWidth); + break; + } + + lastObject = hitObject; + } + } + + protected override Skill[] CreateSkills() => new Skill[] + { + new Movement(), + }; + + protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new CatchDifficultyAttributes(mods); + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs deleted file mode 100644 index fb16e117b1..0000000000 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs +++ /dev/null @@ -1,130 +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; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; -using osuTK; - -namespace osu.Game.Rulesets.Catch.Difficulty -{ - public class CatchDifficultyHitObject - { - internal static readonly double DECAY_BASE = 0.20; - private const float normalized_hitobject_radius = 41.0f; - private const float absolute_player_positioning_error = 16f; - private readonly float playerPositioningError; - - internal CatchHitObject BaseHitObject; - - /// - /// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy - /// - internal double Strain = 1; - - /// - /// This is required to keep track of lazy player movement (always moving only as far as necessary) - /// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated - /// - internal float PlayerPositionOffset; - internal float LastMovement; - - internal float NormalizedPosition; - internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset; - - internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf) - { - BaseHitObject = baseHitObject; - - // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. - float scalingFactor = normalized_hitobject_radius / catcherWidthHalf; - - playerPositioningError = absolute_player_positioning_error; // * scalingFactor; - NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; - } - - private const double direction_change_bonus = 12.5; - internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate) - { - // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. - // See Taiko feedback thread. - double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); - - // Update new position with lazy movement. - PlayerPositionOffset = - MathHelper.Clamp( - previousHitObject.ActualNormalizedPosition, - NormalizedPosition - (normalized_hitobject_radius - playerPositioningError), - NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player. - - NormalizedPosition; // Subtract HitObject position to obtain offset - - LastMovement = DistanceTo(previousHitObject); - double addition = spacingWeight(LastMovement); - - if (NormalizedPosition < previousHitObject.NormalizedPosition) - { - LastMovement = -LastMovement; - } - - CatchHitObject previousHitCircle = previousHitObject.BaseHitObject; - - double additionBonus = 0; - double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25)); - - // Direction changes give an extra point! - if (Math.Abs(LastMovement) > 0.1) - { - if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement)) - { - double bonus = direction_change_bonus / sqrtTime; - - // Weight bonus by how - double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError; - - // We want time to play a role twice here! - addition += bonus * bonusFactor; - - // Bonus for tougher direction switches and "almost" hyperdashes at this point - if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) - { - additionBonus += 0.3 * bonusFactor; - } - } - - // Base bonus for every movement, giving some weight to streams. - addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime; - } - - // Bonus for "almost" hyperdashes at corner points - if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) - { - if (!previousHitCircle.HyperDash) - { - additionBonus += 1.0; - } - else - { - // After a hyperdash we ARE in the correct position. Always! - PlayerPositionOffset = 0; - } - - addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); - } - - addition *= 850.0 / Math.Max(timeElapsed, 25); - - Strain = previousHitObject.Strain * decay + addition; - } - - private static double spacingWeight(float distance) - { - return Math.Pow(distance, 1.3) / 500; - } - - internal float DistanceTo(CatchDifficultyHitObject other) - { - return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition); - } - } -} diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs deleted file mode 100644 index 0a0897d97b..0000000000 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchLegacyDifficultyCalculator.cs +++ /dev/null @@ -1,148 +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; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; - -namespace osu.Game.Rulesets.Catch.Difficulty -{ - public class CatchLegacyDifficultyCalculator : LegacyDifficultyCalculator - { - /// - /// 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; - - public CatchLegacyDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) - { - if (!beatmap.HitObjects.Any()) - return new CatchDifficultyAttributes(mods, 0); - - var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); - float halfCatchWidth = catcher.CatchWidth * 0.5f; - - var difficultyHitObjects = new List(); - - foreach (var hitObject in beatmap.HitObjects) - { - switch (hitObject) - { - // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. - case Fruit fruit: - difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth)); - break; - case JuiceStream _: - difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth))); - break; - } - } - - difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - - if (!calculateStrainValues(difficultyHitObjects, timeRate)) - return new CatchDifficultyAttributes(mods, 0); - - // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; - double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor; - - return new CatchDifficultyAttributes(mods, starRating) - { - ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, - MaxCombo = difficultyHitObjects.Count - }; - } - - private bool calculateStrainValues(List objects, double timeRate) - { - CatchDifficultyHitObject lastObject = null; - - if (!objects.Any()) return false; - - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - foreach (var currentObject in objects) - { - if (lastObject != null) - currentObject.CalculateStrains(lastObject, timeRate); - - lastObject = currentObject; - } - - return true; - } - - 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; - - // Find the highest strain value within each strain step - var highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - CatchDifficultyHitObject previousHitObject = null; - 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) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.Strain * decay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - maximumStrain = Math.Max(hitObject.Strain, maximumStrain); - - previousHitObject = hitObject; - } - - // 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/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs new file mode 100644 index 0000000000..5dbd60d700 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -0,0 +1,68 @@ +// 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.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing +{ + public class CatchDifficultyHitObject : DifficultyHitObject + { + private const float normalized_hitobject_radius = 41.0f; + private const float absolute_player_positioning_error = 16f; + + public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject; + + public float MovementDistance { get; private set; } + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// + public readonly double StrainTime; + + private readonly float scalingFactor; + + private readonly CatchHitObject lastObject; + + public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate, float halfCatcherWidth) + : base(hitObject, lastObject, timeRate) + { + this.lastObject = (CatchHitObject)lastObject; + + // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. + scalingFactor = normalized_hitobject_radius / halfCatcherWidth; + + setDistances(); + + // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure + StrainTime = Math.Max(25, DeltaTime); + } + + private void setDistances() + { + float lastPosition = getNormalizedPosition(lastObject); + float currentPosition = getNormalizedPosition(BaseObject); + + // After a hyperdash the player is assumed to be in the correct position + if (lastObject.LazyMovementDistance != null && !lastObject.HyperDash) + lastPosition += lastObject.LazyMovementDistance.Value; + + BaseObject.LazyMovementDistance = + MathHelper.Clamp( + lastPosition, + currentPosition - (normalized_hitobject_radius - absolute_player_positioning_error), + currentPosition + (normalized_hitobject_radius - absolute_player_positioning_error)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player. + - currentPosition; // Subtract HitObject position to obtain offset + + MovementDistance = Math.Abs(currentPosition - lastPosition + BaseObject.LazyMovementDistance.Value); + + if (currentPosition < lastPosition) + MovementDistance *= -1; + } + + private float getNormalizedPosition(CatchHitObject hitObject) => hitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs new file mode 100644 index 0000000000..1626a9be1d --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -0,0 +1,63 @@ +// 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.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; + +namespace osu.Game.Rulesets.Catch.Difficulty.Skills +{ + public class Movement : Skill + { + private const float absolute_player_positioning_error = 16f; + private const float normalized_hitobject_radius = 41.0f; + private const double direction_change_bonus = 12.5; + + protected override double SkillMultiplier => 850; + protected override double StrainDecayBase => 0.2; + + protected override double DecayWeight => 0.94; + + protected override double StrainValueOf(DifficultyHitObject current) + { + var catchCurrent = (CatchDifficultyHitObject)current; + var catchPrevious = (CatchDifficultyHitObject)Previous[0]; + + var sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); + + double distanceAddition = Math.Pow(Math.Abs(catchCurrent.MovementDistance), 1.3) / 500; + + double bonus = 0; + + if (Math.Abs(catchCurrent.MovementDistance) > 0.1) + { + if (catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) + { + double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(catchCurrent.MovementDistance)) / absolute_player_positioning_error; + + distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; + + // Bonus for tougher direction switches and "almost" hyperdashes at this point + if (catchPrevious.BaseObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) + bonus = 0.3 * bonusFactor; + } + + // Base bonus for every movement, giving some weight to streams. + distanceAddition += 7.5 * Math.Min(Math.Abs(catchCurrent.MovementDistance), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; + } + + // Bonus for "almost" hyperdashes at corner points + if (catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + { + if (!catchPrevious.BaseObject.HyperDash) + bonus += 1.0; + + distanceAddition *= 1.0 + bonus * ((10 - catchPrevious.BaseObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); + } + + return distanceAddition / catchCurrent.StrainTime; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 2153b8dc85..aff18619fb 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -50,6 +50,11 @@ namespace osu.Game.Rulesets.Catch.Objects /// public CatchHitObject HyperDashTarget; + /// + /// The minimum distance to be moved from the last to hit this . + /// + internal float? LazyMovementDistance; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); From e02ae927b31f6eccbe673164cb7367d4515e8175 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:47:31 +0900 Subject: [PATCH 02/10] Fix nullrefs --- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 1626a9be1d..8137437959 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -3,6 +3,7 @@ using System; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var catchCurrent = (CatchDifficultyHitObject)current; - var catchPrevious = (CatchDifficultyHitObject)Previous[0]; + var catchPrevious = Previous.Count > 0 ? (CatchDifficultyHitObject)Previous[0] : null; var sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills if (Math.Abs(catchCurrent.MovementDistance) > 0.1) { - if (catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) + if (catchPrevious != null && catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) { double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(catchCurrent.MovementDistance)) / absolute_player_positioning_error; @@ -49,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills } // Bonus for "almost" hyperdashes at corner points - if (catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + if (catchPrevious?.BaseObject is Fruit && catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) { if (!catchPrevious.BaseObject.HyperDash) bonus += 1.0; From f6b13ca79dd271141ba6de8d18ec1fd1b69a599b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 Feb 2019 11:11:31 +0900 Subject: [PATCH 03/10] Rewrite catch diffcalc for readability + attempt to fix --- .../Preprocessing/CatchDifficultyHitObject.cs | 41 +++--------------- .../Difficulty/Skills/Movement.cs | 42 +++++++++++++------ 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 5dbd60d700..6ce45bbbde 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -6,63 +6,34 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; -using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing { public class CatchDifficultyHitObject : DifficultyHitObject { private const float normalized_hitobject_radius = 41.0f; - private const float absolute_player_positioning_error = 16f; public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject; - public float MovementDistance { get; private set; } + public new CatchHitObject LastObject => (CatchHitObject)base.LastObject; + + public readonly float NormalizedPosition; + /// /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. /// public readonly double StrainTime; - private readonly float scalingFactor; - - private readonly CatchHitObject lastObject; - public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate, float halfCatcherWidth) : base(hitObject, lastObject, timeRate) { - this.lastObject = (CatchHitObject)lastObject; - // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. - scalingFactor = normalized_hitobject_radius / halfCatcherWidth; + var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; - setDistances(); + NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure StrainTime = Math.Max(25, DeltaTime); } - - private void setDistances() - { - float lastPosition = getNormalizedPosition(lastObject); - float currentPosition = getNormalizedPosition(BaseObject); - - // After a hyperdash the player is assumed to be in the correct position - if (lastObject.LazyMovementDistance != null && !lastObject.HyperDash) - lastPosition += lastObject.LazyMovementDistance.Value; - - BaseObject.LazyMovementDistance = - MathHelper.Clamp( - lastPosition, - currentPosition - (normalized_hitobject_radius - absolute_player_positioning_error), - currentPosition + (normalized_hitobject_radius - absolute_player_positioning_error)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player. - - currentPosition; // Subtract HitObject position to obtain offset - - MovementDistance = Math.Abs(currentPosition - lastPosition + BaseObject.LazyMovementDistance.Value); - - if (currentPosition < lastPosition) - MovementDistance *= -1; - } - - private float getNormalizedPosition(CatchHitObject hitObject) => hitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 8137437959..e06ca08fbe 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -3,10 +3,10 @@ using System; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; -using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osuTK; namespace osu.Game.Rulesets.Catch.Difficulty.Skills { @@ -21,43 +21,61 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills protected override double DecayWeight => 0.94; + private float lastPlayerPosition; + private float lastDistanceMoved; + protected override double StrainValueOf(DifficultyHitObject current) { var catchCurrent = (CatchDifficultyHitObject)current; - var catchPrevious = Previous.Count > 0 ? (CatchDifficultyHitObject)Previous[0] : null; - var sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); + float playerPosition = MathHelper.Clamp( + lastPlayerPosition, + catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error), + catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error) + ); - double distanceAddition = Math.Pow(Math.Abs(catchCurrent.MovementDistance), 1.3) / 500; + float distanceMoved = playerPosition - lastPlayerPosition; + + double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500; + double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); double bonus = 0; - if (Math.Abs(catchCurrent.MovementDistance) > 0.1) + // Direction changes give an extra point! + if (Math.Abs(distanceMoved) > 0.1) { - if (catchPrevious != null && catchPrevious.MovementDistance > 0.1 && Math.Sign(catchCurrent.MovementDistance) != Math.Sign(catchPrevious.MovementDistance)) + if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) { - double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(catchCurrent.MovementDistance)) / absolute_player_positioning_error; + double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error; distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; // Bonus for tougher direction switches and "almost" hyperdashes at this point - if (catchPrevious.BaseObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) + if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) bonus = 0.3 * bonusFactor; } // Base bonus for every movement, giving some weight to streams. - distanceAddition += 7.5 * Math.Min(Math.Abs(catchCurrent.MovementDistance), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; + distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; } // Bonus for "almost" hyperdashes at corner points - if (catchPrevious?.BaseObject is Fruit && catchPrevious.BaseObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) { - if (!catchPrevious.BaseObject.HyperDash) + if (!catchCurrent.LastObject.HyperDash) bonus += 1.0; + else + { + // After a hyperdash we ARE in the correct position. Always! + playerPosition = catchCurrent.NormalizedPosition; + } - distanceAddition *= 1.0 + bonus * ((10 - catchPrevious.BaseObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); + distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); } + lastPlayerPosition = playerPosition; + lastDistanceMoved = distanceMoved; + return distanceAddition / catchCurrent.StrainTime; } } From 83cab2ba8ab23a476132993afb21c41253bd95f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:48:19 +0900 Subject: [PATCH 04/10] Fix incorrect hitobject being used as the last hitobject --- .../Difficulty/CatchDifficultyCalculator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 99702235d5..d4110ab1e1 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -60,14 +60,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. case Fruit fruit: yield return new CatchDifficultyHitObject(fruit, lastObject, timeRate, halfCatchWidth); + lastObject = hitObject; break; case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) + { yield return new CatchDifficultyHitObject(nested, lastObject, timeRate, halfCatchWidth); + lastObject = nested; + } break; } - - lastObject = hitObject; } } From 9463475202be48b011e955532de1596bcd1eb2b9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 14:51:30 +0900 Subject: [PATCH 05/10] Remove now unused member --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index aff18619fb..2153b8dc85 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -50,11 +50,6 @@ namespace osu.Game.Rulesets.Catch.Objects /// public CatchHitObject HyperDashTarget; - /// - /// The minimum distance to be moved from the last to hit this . - /// - internal float? LazyMovementDistance; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); From 20f91106d9988154252a681d15841e3683594f47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Feb 2019 15:02:46 +0900 Subject: [PATCH 06/10] Fix failing test --- osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index 61fb4ca5d1..c9831aad6d 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; - [TestCase(3.8664391043534758, "diffcalc-test")] + [TestCase(3.8701854263563118d, "diffcalc-test")] public void Test(double expected, string name) => base.Test(expected, name); From 3abb281ad570ceebdd58a5d53457e031f24fd239 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 16:41:53 +0900 Subject: [PATCH 07/10] Fix post-merge errors --- .../Difficulty/CatchDifficultyAttributes.cs | 6 ------ .../Difficulty/CatchDifficultyCalculator.cs | 3 +-- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index 8669543841..75f5b18607 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public double ApproachRate; public int MaxCombo; - - public CatchDifficultyAttributes(Mod[] mods) - : base(mods) - { - } } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index d4110ab1e1..f8ca066a4f 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -78,6 +77,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty new Movement(), }; - protected override DifficultyAttributes CreateDifficultyAttributes(Mod[] mods) => new CatchDifficultyAttributes(mods); + protected override DifficultyAttributes CreateDifficultyAttributes() => new CatchDifficultyAttributes(); } } From 2765ffa19093b796b38e8bf0efe3bcd2d7ebb486 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:42:24 +0900 Subject: [PATCH 08/10] Update with PopulateAttributes() removal --- .../Difficulty/CatchDifficultyCalculator.cs | 25 +++++++++++-------- .../Preprocessing/CatchDifficultyHitObject.cs | 4 +-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index f8ca066a4f..d29dd4591e 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -30,19 +31,23 @@ namespace osu.Game.Rulesets.Catch.Difficulty halfCatchWidth = catcher.CatchWidth * 0.5f; } - protected override void PopulateAttributes(DifficultyAttributes attributes, IBeatmap beatmap, Skill[] skills, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - var catchAttributes = (CatchDifficultyAttributes)attributes; + if (beatmap.HitObjects.Count == 0) + return new CatchDifficultyAttributes(); // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; + double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; - catchAttributes.StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor; - catchAttributes.ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0; - catchAttributes.MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)); + return new CatchDifficultyAttributes + { + StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor, + ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, + MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)) + }; } - protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double timeRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { CatchHitObject lastObject = null; @@ -58,13 +63,13 @@ namespace osu.Game.Rulesets.Catch.Difficulty { // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. case Fruit fruit: - yield return new CatchDifficultyHitObject(fruit, lastObject, timeRate, halfCatchWidth); + yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth); lastObject = hitObject; break; case JuiceStream _: foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet))) { - yield return new CatchDifficultyHitObject(nested, lastObject, timeRate, halfCatchWidth); + yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth); lastObject = nested; } break; @@ -76,7 +81,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty { new Movement(), }; - - protected override DifficultyAttributes CreateDifficultyAttributes() => new CatchDifficultyAttributes(); } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 6ce45bbbde..6d00bb27b5 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -24,8 +24,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing /// public readonly double StrainTime; - public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double timeRate, float halfCatcherWidth) - : base(hitObject, lastObject, timeRate) + public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) + : base(hitObject, lastObject, clockRate) { // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; From 1a645b511592f6a1b275b69d24e874c4c939369b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:45:52 +0900 Subject: [PATCH 09/10] Fix mods not being populated --- .../Difficulty/CatchDifficultyCalculator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index d29dd4591e..9e6511b362 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { if (beatmap.HitObjects.Count == 0) - return new CatchDifficultyAttributes(); + return new CatchDifficultyAttributes { Mods = mods }; // this is the same as osu!, so there's potential to share the implementation... maybe double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return new CatchDifficultyAttributes { StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor, + Mods = mods, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)) }; From ea281e8596e51028a6e24b71cd5feca905dd5a57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Feb 2019 17:54:00 +0900 Subject: [PATCH 10/10] Add beatmap argument --- osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 9e6511b362..89f3a8c25e 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty } } - protected override Skill[] CreateSkills() => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Movement(), };