@@ -115,8 +115,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
115115 double monoStaminaSkill = singleColourStamina . DifficultyValue ( ) * stamina_skill_multiplier ;
116116 double monoStaminaFactor = staminaSkill == 0 ? 1 : Math . Pow ( monoStaminaSkill / staminaSkill , 5 ) ;
117117
118- double colourDifficultStrains = colour . CountTopWeightedStrains ( ) ;
119- double rhythmDifficultStrains = rhythm . CountTopWeightedStrains ( ) ;
120118 double staminaDifficultStrains = stamina . CountTopWeightedStrains ( ) ;
121119
122120 // As we don't have pattern integration in osu!taiko, we apply the other two skills relative to rhythm.
@@ -126,7 +124,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
126124 + Math . Min ( Math . Max ( ( staminaDifficultStrains - 1000 ) / 3700 , 0 ) , 0.15 )
127125 + Math . Min ( Math . Max ( ( staminaSkill - 7.0 ) / 1.0 , 0 ) , 0.05 ) ;
128126
129- double combinedRating = combinedDifficultyValue ( rhythm , reading , colour , stamina , isRelax , isConvert ) ;
127+ double combinedRating = combinedDifficultyValue ( rhythm , reading , colour , stamina , isRelax , isConvert , out double consistencyFactor ) ;
130128 double starRating = rescale ( combinedRating * 1.4 ) ;
131129
132130 // Calculate proportional contribution of each skill to the combinedRating.
@@ -136,19 +134,20 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
136134 double readingDifficulty = readingSkill * skillRating ;
137135 double colourDifficulty = colourSkill * skillRating ;
138136 double staminaDifficulty = staminaSkill * skillRating ;
137+ double mechanicalDifficulty = colourDifficulty + staminaDifficulty ; // Mechanical difficulty is the sum of colour and stamina difficulties.
139138
140139 TaikoDifficultyAttributes attributes = new TaikoDifficultyAttributes
141140 {
142141 StarRating = starRating ,
143142 Mods = mods ,
143+ MechanicalDifficulty = mechanicalDifficulty ,
144144 RhythmDifficulty = rhythmDifficulty ,
145145 ReadingDifficulty = readingDifficulty ,
146146 ColourDifficulty = colourDifficulty ,
147147 StaminaDifficulty = staminaDifficulty ,
148148 MonoStaminaFactor = monoStaminaFactor ,
149- RhythmTopStrains = rhythmDifficultStrains ,
150- ColourTopStrains = colourDifficultStrains ,
151149 StaminaTopStrains = staminaDifficultStrains ,
150+ ConsistencyFactor = consistencyFactor ,
152151 MaxCombo = beatmap . GetMaxCombo ( ) ,
153152 } ;
154153
@@ -162,7 +161,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
162161 /// For each section, the peak strains of all separate skills are combined into a single peak strain for the section.
163162 /// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more).
164163 /// </remarks>
165- private double combinedDifficultyValue ( Rhythm rhythm , Reading reading , Colour colour , Stamina stamina , bool isRelax , bool isConvert )
164+ private double combinedDifficultyValue ( Rhythm rhythm , Reading reading , Colour colour , Stamina stamina , bool isRelax , bool isConvert , out double consistencyFactor )
166165 {
167166 List < double > peaks = new List < double > ( ) ;
168167
@@ -196,9 +195,35 @@ private double combinedDifficultyValue(Rhythm rhythm, Reading reading, Colour co
196195 weight *= 0.9 ;
197196 }
198197
198+ consistencyFactor = calculateConsistencyFactor ( peaks ) ;
199+
199200 return difficulty ;
200201 }
201202
203+ /// <summary>
204+ /// Calculates a consistency factor based on how 'spiked' the strain peaks are.
205+ /// Higher values indicate more consistent difficulty, lower values indicate diff-spike heavy maps.
206+ /// </summary>
207+ private double calculateConsistencyFactor ( List < double > peaks )
208+ {
209+ // If there are too few sections in a map, assume it is consistent.
210+ if ( peaks . Count < 3 )
211+ return 1.0 ;
212+
213+ List < double > sorted = peaks . OrderDescending ( ) . ToList ( ) ;
214+
215+ double topPeak = sorted [ 0 ] ;
216+ double secondTopPeak = sorted . Count > 1 ? sorted [ 1 ] : topPeak ;
217+
218+ // Compute the average of the middle 50% of strain values.
219+ double midAvg = sorted . Skip ( sorted . Count / 4 ) . Take ( sorted . Count / 2 ) . Average ( ) ;
220+
221+ // A higher ratio means the top sections are much harder than the average, indicating inconsistency.
222+ double spikeSeverity = ( topPeak + secondTopPeak ) / 2.0 / midAvg ;
223+
224+ return 1.0 / spikeSeverity ;
225+ }
226+
202227 /// <summary>
203228 /// Applies a final re-scaling of the star rating.
204229 /// </summary>
0 commit comments