From a42c03cea457b9e6786983d77d966a461d1a10ed Mon Sep 17 00:00:00 2001 From: Jay Lawton Date: Fri, 17 Jan 2025 21:15:22 +1000 Subject: [PATCH] osu!taiko further considerations for rhythm (#31339) * further considerations for rhythm * new rhythm balancing * fix license header * use isNormal to validate ratio * adjust tests --------- Co-authored-by: StanR --- .../TaikoDifficultyCalculatorTest.cs | 8 ++-- .../Difficulty/Evaluators/RhythmEvaluator.cs | 48 +++++++++++++------ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index b4cbe03511..d760b9aef6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(3.3172381854905493d, 200, "diffcalc-test")] - [TestCase(3.3172381854905493d, 200, "diffcalc-test-strong")] + [TestCase(3.3167800835687551d, 200, "diffcalc-test")] + [TestCase(3.3167800835687551d, 200, "diffcalc-test-strong")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(4.4640702427013101d, 200, "diffcalc-test")] - [TestCase(4.4640702427013101d, 200, "diffcalc-test-strong")] + [TestCase(4.4631326105105122d, 200, "diffcalc-test")] + [TestCase(4.4631326105105122d, 200, "diffcalc-test-strong")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new TaikoModDoubleTime()); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs index 3a294f7123..7d58eada5e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs @@ -21,27 +21,39 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators return -multiplier * Math.Pow(Math.Cos(denominator * Math.PI * ratio), power); } + /// + /// Validates the ratio by ensuring it is a normal number in cases where maps breach regular mapping conditions. + /// + private static double validateRatio(double ratio) + { + return double.IsNormal(ratio) ? ratio : 0; + } + /// /// Calculates the difficulty of a given ratio using a combination of periodic penalties and bonuses. /// private static double ratioDifficulty(double ratio, int terms = 8) { double difficulty = 0; + ratio = validateRatio(ratio); for (int i = 1; i <= terms; ++i) { - difficulty += termPenalty(ratio, i, 2, 1); + difficulty += termPenalty(ratio, i, 4, 1); } - difficulty += terms; + difficulty += terms / (1 + ratio); // Give bonus to near-1 ratios - difficulty += DifficultyCalculationUtils.BellCurve(ratio, 1, 0.7); + difficulty += DifficultyCalculationUtils.BellCurve(ratio, 1, 0.5); // Penalize ratios that are VERY near 1 - difficulty -= DifficultyCalculationUtils.BellCurve(ratio, 1, 0.5); + difficulty -= DifficultyCalculationUtils.BellCurve(ratio, 1, 0.3); - return difficulty / Math.Sqrt(8); + difficulty = Math.Max(difficulty, 0); + difficulty /= Math.Sqrt(8); + + return difficulty; } /// @@ -55,10 +67,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators ? sameInterval(sameRhythmHitObjects, 4) : 1.0; // Returns a non-penalty if there are 6 or more notes within an interval. - // Scale penalties dynamically based on hit object duration relative to hitWindow. - double penaltyScaling = Math.Max(1 - sameRhythmHitObjects.Duration / (hitWindow * 2), 0.5); + // The duration penalty is based on hit object duration relative to hitWindow. + double durationPenalty = Math.Max(1 - sameRhythmHitObjects.Duration * 2 / hitWindow, 0.5); - return Math.Min(longIntervalPenalty, shortIntervalPenalty) * penaltyScaling; + return Math.Min(longIntervalPenalty, shortIntervalPenalty) * durationPenalty; double sameInterval(SameRhythmHitObjects startObject, int intervalCount) { @@ -82,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators { double ratio = intervals[i]!.Value / intervals[j]!.Value; if (Math.Abs(1 - ratio) <= threshold) // If any two intervals are similar, apply a penalty. - return 0.3; + return 0.80; } } @@ -95,6 +107,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators double intervalDifficulty = ratioDifficulty(sameRhythmHitObjects.HitObjectIntervalRatio); double? previousInterval = sameRhythmHitObjects.Previous?.HitObjectInterval; + intervalDifficulty *= repeatedIntervalPenalty(sameRhythmHitObjects, hitWindow); + // If a previous interval exists and there are multiple hit objects in the sequence: if (previousInterval != null && sameRhythmHitObjects.Children.Count > 1) { @@ -111,9 +125,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators } } - // Apply consistency penalty. - intervalDifficulty *= repeatedIntervalPenalty(sameRhythmHitObjects, hitWindow); - // Penalise patterns that can be hit within a single hit window. intervalDifficulty *= DifficultyCalculationUtils.Logistic( sameRhythmHitObjects.Duration / hitWindow, @@ -137,11 +148,20 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators TaikoDifficultyHitObjectRhythm rhythm = ((TaikoDifficultyHitObject)hitObject).Rhythm; double difficulty = 0.0d; + double sameRhythm = 0; + double samePattern = 0; + double intervalPenalty = 0; + if (rhythm.SameRhythmHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmHitObjects - difficulty += evaluateDifficultyOf(rhythm.SameRhythmHitObjects, hitWindow); + { + sameRhythm += 10.0 * evaluateDifficultyOf(rhythm.SameRhythmHitObjects, hitWindow); + intervalPenalty = repeatedIntervalPenalty(rhythm.SameRhythmHitObjects, hitWindow); + } if (rhythm.SamePatterns?.FirstHitObject == hitObject) // Difficulty for SamePatterns - difficulty += 0.5 * evaluateDifficultyOf(rhythm.SamePatterns); + samePattern += 1.15 * evaluateDifficultyOf(rhythm.SamePatterns); + + difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty; return difficulty; }