diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs
index 9cbc5bf2de..8e73744b53 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/Evaluators/RhythmEvaluator.cs
@@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
double sameRhythm = 0;
double samePattern = 0;
double intervalPenalty = 0;
+ double gapPenalty = 0;
double hitWindow = hitObject.HitWindow(HitResult.Great);
@@ -36,12 +37,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
{
sameRhythm += 10.0 * evaluateDifficultyOf(rhythmData.SameRhythmGroupedHitObjects, hitWindow);
intervalPenalty = repeatedIntervalPenalty(rhythmData.SameRhythmGroupedHitObjects, hitWindow);
+ gapPenalty = longGapPenalty(rhythmData.SameRhythmGroupedHitObjects.Previous);
}
if (rhythmData.SamePatternsGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SamePatternsGroupedHitObjects
samePattern += 1.15 * ratioDifficulty(rhythmData.SamePatternsGroupedHitObjects.IntervalRatio);
- difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty;
+ difficulty += Math.Max(sameRhythm, samePattern) * intervalPenalty * gapPenalty;
return difficulty;
}
@@ -125,6 +127,32 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
}
}
+ ///
+ /// Frequent rhythm changes containing long gaps (i.e. 1/4 + 1/6 with 1/2 gaps) award more difficulty than expected.
+ /// Due to limitations of the current rhythm evaluation, these cases are targeted and penalised.
+ /// The previous hit object grouping is used as often the rhythm change *two* rhythms after a long gap awards the unexpected difficulty.
+ ///
+ private static double longGapPenalty(SameRhythmHitObjectGrouping? previous)
+ {
+ if (previous == null)
+ return 1.0;
+
+ double gapInterval = previous.FirstHitObject.DeltaTime;
+ double rhythmInterval = previous.HitObjectInterval ?? gapInterval;
+ double rhythmLength = previous.HitObjects.Count;
+
+ // The ratio of the gap before this rhythm to the rhythm itself.
+ double gapRatio = gapInterval / Math.Max(rhythmInterval, 1);
+
+ // The gap ratio normalised to represent if the gap is long.
+ double gapFactor = DifficultyCalculationUtils.Logistic(gapRatio, 1.75, 20);
+
+ // The length in objects of this rhythm normalised to represent if the rhythm change is frequent enough to be penalised.
+ double lengthFactor = DifficultyCalculationUtils.ReverseLerp(rhythmLength, 8, 2);
+
+ return 1.0 - 0.75 * gapFactor * lengthFactor;
+ }
+
///
/// Calculates the difficulty of a given ratio using a combination of periodic penalties and bonuses.
///
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 64af2861ec..43edbfb151 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public class TaikoDifficultyCalculator : DifficultyCalculator
{
private const double difficulty_multiplier = 0.084375;
- private const double rhythm_skill_multiplier = 0.750 * difficulty_multiplier;
+ private const double rhythm_skill_multiplier = 0.770 * difficulty_multiplier;
private const double reading_skill_multiplier = 0.100 * difficulty_multiplier;
private const double colour_skill_multiplier = 0.375 * difficulty_multiplier;
private const double stamina_skill_multiplier = 0.445 * difficulty_multiplier;