quaver_rs/difficulty_processor/
processor.rs1use crate::rulesets::structs::{StrainSolverData, Hand, FingerAction, StrainSolverHitObject, FingerState, LnLayerType};
2use crate::difficulty_processor::constants::{StrainConstants, StrainConstantsKeys};
3use crate::difficulty_processor::helpers::{lane_to_hand, lane_to_finger, get_rate_from_mods, mode_to_key_count};
4use crate::difficulty_processor::calculations::get_coefficient_value;
5use crate::qua::Qua;
6use crate::enums::{ModIdentifier, QssPatternFlags};
7
8const SECONDS_TO_MILLISECONDS: f32 = 1000.0;
10
11#[derive(Debug, Clone)]
13pub struct DifficultyProcessor {
14 pub map: Qua,
16
17 pub overall_difficulty: f32,
19
20 pub qss_pattern_flags: QssPatternFlags,
22
23 pub strain_constants: StrainConstantsKeys,
25
26 pub average_note_density: f32,
28
29 pub strain_solver_data: Vec<StrainSolverData>,
31
32 pub vibro_inaccuracy_confidence: f32,
34
35 pub roll_inaccuracy_confidence: f32,
37}
38
39
40impl DifficultyProcessor {
41 pub fn new(map: Qua, _constants: StrainConstants, _mods: ModIdentifier) -> Self {
43 let strain_constants = StrainConstantsKeys::new();
44
45 Self {
46 map,
47 overall_difficulty: 0.0,
48 qss_pattern_flags: QssPatternFlags::UNKNOWN,
49 strain_constants,
50 average_note_density: 0.0,
51 strain_solver_data: Vec::new(),
52 vibro_inaccuracy_confidence: 0.0,
53 roll_inaccuracy_confidence: 0.0,
54 }
55 }
56
57 pub fn calculate_difficulty(&mut self, mods: ModIdentifier) {
59 if self.map.hit_objects.len() < 2 {
61 return;
62 }
63
64 let rate = get_rate_from_mods(mods);
66
67 let key_count = mode_to_key_count(self.map.mode());
69 if key_count % 2 == 0 {
70 self.overall_difficulty = self.compute_for_overall_difficulty(rate);
71 } else {
72 let left_diff = self.compute_for_overall_difficulty_with_hand(rate, Hand::Left);
73 let right_diff = self.compute_for_overall_difficulty_with_hand(rate, Hand::Right);
74 self.overall_difficulty = (left_diff + right_diff) / 2.0;
75 }
76 }
77
78 fn compute_for_overall_difficulty(&mut self, rate: f32) -> f32 {
80 self.compute_for_overall_difficulty_with_hand(rate, Hand::Right)
81 }
82
83 fn compute_for_overall_difficulty_with_hand(&mut self, rate: f32, assume_hand: Hand) -> f32 {
85 self.compute_note_density_data(rate);
86 self.compute_base_strain_states(rate, assume_hand);
87 self.compute_for_chords();
88 self.compute_for_finger_actions();
89 self.compute_for_roll_manipulation();
90 self.compute_for_jack_manipulation();
91 self.compute_for_ln_multiplier();
92
93 self.calculate_overall_difficulty()
95 }
96
97 fn calculate_overall_difficulty(&mut self) -> f32 {
99 if self.strain_solver_data.is_empty() {
101 return 0.0;
102 }
103
104 for i in 0..self.strain_solver_data.len() {
106 self.strain_solver_data[i].calculate_strain_value();
107 }
108
109 let calculated_diff = self.strain_solver_data
110 .iter()
111 .filter(|s| matches!(s.hand, Hand::Left | Hand::Right))
112 .map(|s| s.total_strain_value)
113 .sum::<f32>()
114 / self.strain_solver_data
115 .iter()
116 .filter(|s| matches!(s.hand, Hand::Left | Hand::Right))
117 .count() as f32;
118
119 let mut bins = Vec::new();
121
122 let map_start = self.strain_solver_data
123 .iter()
124 .map(|s| s.start_time as i32)
125 .min()
126 .unwrap_or(0) as f32;
127 let map_end = self.strain_solver_data
128 .iter()
129 .map(|s| (s.start_time.max(s.end_time) as i32))
130 .max()
131 .unwrap_or(0) as f32;
132
133 let use_fallback = self.map.get_key_count(false) % 2 == 1;
134
135 if use_fallback {
136 let mut current_time = map_start as i32;
138 let map_end_int = map_end as i32;
139 while current_time < map_end_int {
140 let values_in_bin: Vec<&StrainSolverData> = self.strain_solver_data
141 .iter()
142 .filter(|s| s.start_time >= current_time as f32 && s.start_time < (current_time + 1000) as f32)
143 .collect();
144
145 let average_rating = if !values_in_bin.is_empty() {
146 values_in_bin.iter().map(|s| s.total_strain_value).sum::<f32>() / values_in_bin.len() as f32
147 } else {
148 0.0
149 };
150
151 bins.push(average_rating);
152 current_time += 1000;
153 }
154 } else {
155 let mut left_index = 0;
157 let mut right_index = 0;
158
159 while left_index < self.strain_solver_data.len() && self.strain_solver_data[left_index].start_time < map_start {
161 left_index += 1;
162 }
163
164 let mut current_time = map_start as i32;
165 let map_end_int = map_end as i32;
166 while current_time < map_end_int {
167 while right_index < self.strain_solver_data.len() - 1
169 && self.strain_solver_data[right_index + 1].start_time < (current_time + 1000) as f32 {
170 right_index += 1;
171 }
172
173 if left_index >= self.strain_solver_data.len() {
174 bins.push(0.0);
175 current_time += 1000;
176 continue;
177 }
178
179 let values_in_bin = &self.strain_solver_data[left_index..=right_index];
180 let average_rating = if !values_in_bin.is_empty() {
181 values_in_bin.iter().map(|s| s.total_strain_value).sum::<f32>() / values_in_bin.len() as f32
182 } else {
183 0.0
184 };
185
186 bins.push(average_rating);
187 left_index = right_index + 1;
188 current_time += 1000;
189 }
190 }
191
192 if bins.iter().all(|&strain| strain <= 0.0) {
193 return 0.0;
194 }
195
196 use crate::difficulty_processor::calculations::{calculate_continuity_adjustment, calculate_short_map_adjustment};
198
199 let (continuity_adjustment, continuity) = calculate_continuity_adjustment(&bins);
200 let short_map_adjustment = calculate_short_map_adjustment(&bins, continuity);
201
202 calculated_diff * continuity_adjustment * short_map_adjustment
203 }
204
205 fn compute_base_strain_states(&mut self, rate: f32, assume_hand: Hand) {
207 let key_count = mode_to_key_count(self.map.mode());
208
209 for hit_object in &self.map.hit_objects {
210 if self.map.has_scratch_key() && hit_object.lane == key_count {
211 continue;
212 }
213
214 let strain_hit_object = StrainSolverHitObject::new(hit_object.clone());
215 let mut strain_data = StrainSolverData::new(strain_hit_object, rate);
216
217 if let Ok(finger_state) = lane_to_finger(hit_object.lane, key_count) {
219 strain_data.hit_objects[0].finger_state = finger_state;
220 }
221
222 if let Ok(hand) = lane_to_hand(hit_object.lane, key_count) {
223 strain_data.hand = match hand {
224 Hand::Ambiguous => assume_hand,
225 _ => hand,
226 };
227 }
228
229 self.strain_solver_data.push(strain_data);
230 }
231 }
232
233 fn compute_for_chords(&mut self) {
235 let mut i = 0;
236 while i < self.strain_solver_data.len() - 1 {
237 let mut j = i + 1;
238 while j < self.strain_solver_data.len() {
239 let ms_diff = self.strain_solver_data[j].start_time - self.strain_solver_data[i].start_time;
240
241 if ms_diff > self.strain_constants.chord_clump_tolerance_ms {
243 break;
244 }
245
246 if ms_diff.abs() <= self.strain_constants.chord_clump_tolerance_ms {
248 if self.strain_solver_data[i].hand == self.strain_solver_data[j].hand {
249 let mut hit_objects_to_add = Vec::new();
251 for hit_obj in &self.strain_solver_data[j].hit_objects {
252 let same_state_found = self.strain_solver_data[i].hit_objects
253 .iter()
254 .any(|existing| existing.finger_state == hit_obj.finger_state);
255
256 if !same_state_found {
257 hit_objects_to_add.push(hit_obj.clone());
258 }
259 }
260
261 self.strain_solver_data[i].hit_objects.extend(hit_objects_to_add);
262 self.strain_solver_data.remove(j);
263 continue;
264 }
265 }
266 j += 1;
267 }
268 i += 1;
269 }
270
271 for i in 0..self.strain_solver_data.len() {
273 self.strain_solver_data[i].solve_finger_state();
274 }
275 }
276
277 fn compute_for_finger_actions(&mut self) {
279 for i in 0..self.strain_solver_data.len() - 1 {
280 for j in i + 1..self.strain_solver_data.len() {
282 if self.strain_solver_data[i].hand == self.strain_solver_data[j].hand
283 && self.strain_solver_data[j].start_time > self.strain_solver_data[i].start_time {
284 let action_jack_found = (self.strain_solver_data[i].finger_state & self.strain_solver_data[j].finger_state) != FingerState::NONE;
286 let action_chord_found = self.strain_solver_data[i].hand_chord() || self.strain_solver_data[j].hand_chord();
287 let action_same_state = self.strain_solver_data[i].finger_state == self.strain_solver_data[j].finger_state;
288 let action_duration = self.strain_solver_data[j].start_time - self.strain_solver_data[i].start_time;
289
290 self.strain_solver_data[i].next_strain_solver_data_on_current_hand = Some(Box::new(self.strain_solver_data[j].clone()));
292 self.strain_solver_data[i].finger_action_duration_ms = action_duration;
293
294 if !action_chord_found && !action_same_state {
296 self.strain_solver_data[i].finger_action = FingerAction::Roll;
297 self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
298 action_duration,
299 self.strain_constants.roll_lower_boundary_ms,
300 self.strain_constants.roll_upper_boundary_ms,
301 self.strain_constants.roll_max_strain_value,
302 self.strain_constants.roll_curve_exponential,
303 self.average_note_density,
304 );
305 } else if action_same_state {
306 self.strain_solver_data[i].finger_action = FingerAction::SimpleJack;
307 self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
308 action_duration,
309 self.strain_constants.s_jack_lower_boundary_ms,
310 self.strain_constants.s_jack_upper_boundary_ms,
311 self.strain_constants.s_jack_max_strain_value,
312 self.strain_constants.s_jack_curve_exponential,
313 self.average_note_density,
314 );
315 } else if action_jack_found {
316 self.strain_solver_data[i].finger_action = FingerAction::TechnicalJack;
317 self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
318 action_duration,
319 self.strain_constants.t_jack_lower_boundary_ms,
320 self.strain_constants.t_jack_upper_boundary_ms,
321 self.strain_constants.t_jack_max_strain_value,
322 self.strain_constants.t_jack_curve_exponential,
323 self.average_note_density,
324 );
325 } else {
326 self.strain_solver_data[i].finger_action = FingerAction::Bracket;
327 self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
328 action_duration,
329 self.strain_constants.bracket_lower_boundary_ms,
330 self.strain_constants.bracket_upper_boundary_ms,
331 self.strain_constants.bracket_max_strain_value,
332 self.strain_constants.bracket_curve_exponential,
333 self.average_note_density,
334 );
335 }
336 break;
337 }
338 }
339 }
340 }
341
342 fn compute_for_roll_manipulation(&mut self) {
344 let mut manipulation_index = 0;
345
346 for data in &mut self.strain_solver_data {
347 let mut manipulation_found = false;
348
349 if let Some(ref next) = data.next_strain_solver_data_on_current_hand {
350 if let Some(ref next_next) = next.next_strain_solver_data_on_current_hand {
351 if data.finger_action == FingerAction::Roll && next.finger_action == FingerAction::Roll {
352 if data.finger_state == next_next.finger_state {
353 let duration_ratio = (data.finger_action_duration_ms / next.finger_action_duration_ms)
354 .max(next.finger_action_duration_ms / data.finger_action_duration_ms);
355
356 if duration_ratio >= self.strain_constants.roll_ratio_tolerance_ms {
357 let duration_multiplier = 1.0 / (1.0 + (duration_ratio - 1.0) * self.strain_constants.roll_ratio_multiplier);
358 let manipulation_found_ratio = 1.0 - manipulation_index as f32 / self.strain_constants.roll_max_length * (1.0 - self.strain_constants.roll_length_multiplier);
359
360 data.roll_manipulation_strain_multiplier = duration_multiplier * manipulation_found_ratio;
361
362 manipulation_found = true;
363 self.roll_inaccuracy_confidence += 1.0;
364
365 if manipulation_index < self.strain_constants.roll_max_length as usize {
366 manipulation_index += 1;
367 }
368 }
369 }
370 }
371 }
372 }
373
374 if !manipulation_found && manipulation_index > 0 {
375 manipulation_index -= 1;
376 }
377 }
378 }
379
380 fn compute_for_jack_manipulation(&mut self) {
382 let mut long_jack_size = 0;
383
384 for data in &mut self.strain_solver_data {
385 let mut manipulation_found = false;
386
387 if let Some(ref next) = data.next_strain_solver_data_on_current_hand {
388 if data.finger_action == FingerAction::SimpleJack && next.finger_action == FingerAction::SimpleJack {
389 let duration_value = ((self.strain_constants.vibro_action_duration_ms + self.strain_constants.vibro_action_tolerance_ms - data.finger_action_duration_ms) / self.strain_constants.vibro_action_tolerance_ms)
390 .min(1.0)
391 .max(0.0);
392
393 let duration_multiplier = 1.0 - duration_value * (1.0 - self.strain_constants.vibro_multiplier);
394 let manipulation_found_ratio = 1.0 - long_jack_size as f32 / self.strain_constants.vibro_max_length * (1.0 - self.strain_constants.vibro_length_multiplier);
395
396 data.roll_manipulation_strain_multiplier = duration_multiplier * manipulation_found_ratio;
397
398 manipulation_found = true;
399 self.vibro_inaccuracy_confidence += 1.0;
400
401 if long_jack_size < self.strain_constants.vibro_max_length as usize {
402 long_jack_size += 1;
403 }
404 }
405 }
406
407 if !manipulation_found {
408 long_jack_size = 0;
409 }
410 }
411 }
412
413 fn compute_for_ln_multiplier(&mut self) {
415 for data in &mut self.strain_solver_data {
416 if data.end_time > data.start_time {
417 let duration_value = 1.0 - ((self.strain_constants.ln_layer_threshold_ms + self.strain_constants.ln_layer_tolerance_ms - (data.end_time - data.start_time)) / self.strain_constants.ln_layer_tolerance_ms)
418 .min(1.0)
419 .max(0.0);
420
421 let base_multiplier = 1.0 + duration_value * self.strain_constants.ln_base_multiplier;
422
423 for hit_object in &mut data.hit_objects {
424 hit_object.ln_strain_multiplier = base_multiplier;
425 }
426
427 if let Some(ref next) = data.next_strain_solver_data_on_current_hand {
428 if next.start_time < data.end_time - self.strain_constants.ln_end_threshold_ms {
429 if next.start_time >= data.start_time + self.strain_constants.ln_end_threshold_ms {
430 if next.end_time > data.end_time + self.strain_constants.ln_end_threshold_ms {
431 for hit_object in &mut data.hit_objects {
432 hit_object.ln_layer_type = LnLayerType::OutsideRelease;
433 hit_object.ln_strain_multiplier *= self.strain_constants.ln_release_after_multiplier;
434 }
435 } else if next.end_time > 0.0 {
436 for hit_object in &mut data.hit_objects {
437 hit_object.ln_layer_type = LnLayerType::InsideRelease;
438 hit_object.ln_strain_multiplier *= self.strain_constants.ln_release_before_multiplier;
439 }
440 } else {
441 for hit_object in &mut data.hit_objects {
442 hit_object.ln_layer_type = LnLayerType::InsideTap;
443 hit_object.ln_strain_multiplier *= self.strain_constants.ln_tap_multiplier;
444 }
445 }
446 }
447 }
448 }
449 }
450 }
451 }
452
453 fn compute_note_density_data(&mut self, rate: f32) {
455 self.average_note_density = SECONDS_TO_MILLISECONDS * self.map.hit_objects.len() as f32
456 / (self.map.length() * (-0.5 * rate + 1.5));
457 }
458
459}