use crate::rulesets::structs::{StrainSolverData, Hand, FingerAction, StrainSolverHitObject, FingerState, LnLayerType};
use crate::difficulty_processor::constants::{StrainConstants, StrainConstantsKeys};
use crate::difficulty_processor::helpers::{lane_to_hand, lane_to_finger, get_rate_from_mods, mode_to_key_count};
use crate::difficulty_processor::calculations::get_coefficient_value;
use crate::qua::Qua;
use crate::enums::{ModIdentifier, QssPatternFlags};
const SECONDS_TO_MILLISECONDS: f32 = 1000.0;
#[derive(Debug, Clone)]
pub struct DifficultyProcessor {
pub map: Qua,
pub overall_difficulty: f32,
pub qss_pattern_flags: QssPatternFlags,
pub strain_constants: StrainConstantsKeys,
pub average_note_density: f32,
pub strain_solver_data: Vec<StrainSolverData>,
pub vibro_inaccuracy_confidence: f32,
pub roll_inaccuracy_confidence: f32,
}
impl DifficultyProcessor {
pub fn new(map: Qua, _constants: StrainConstants, _mods: ModIdentifier) -> Self {
let strain_constants = StrainConstantsKeys::new();
Self {
map,
overall_difficulty: 0.0,
qss_pattern_flags: QssPatternFlags::UNKNOWN,
strain_constants,
average_note_density: 0.0,
strain_solver_data: Vec::new(),
vibro_inaccuracy_confidence: 0.0,
roll_inaccuracy_confidence: 0.0,
}
}
pub fn calculate_difficulty(&mut self, mods: ModIdentifier) {
if self.map.hit_objects.len() < 2 {
return;
}
let rate = get_rate_from_mods(mods);
let key_count = mode_to_key_count(self.map.mode());
if key_count % 2 == 0 {
self.overall_difficulty = self.compute_for_overall_difficulty(rate);
} else {
let left_diff = self.compute_for_overall_difficulty_with_hand(rate, Hand::Left);
let right_diff = self.compute_for_overall_difficulty_with_hand(rate, Hand::Right);
self.overall_difficulty = (left_diff + right_diff) / 2.0;
}
}
fn compute_for_overall_difficulty(&mut self, rate: f32) -> f32 {
self.compute_for_overall_difficulty_with_hand(rate, Hand::Right)
}
fn compute_for_overall_difficulty_with_hand(&mut self, rate: f32, assume_hand: Hand) -> f32 {
self.compute_note_density_data(rate);
self.compute_base_strain_states(rate, assume_hand);
self.compute_for_chords();
self.compute_for_finger_actions();
self.compute_for_roll_manipulation();
self.compute_for_jack_manipulation();
self.compute_for_ln_multiplier();
self.calculate_overall_difficulty()
}
fn calculate_overall_difficulty(&mut self) -> f32 {
if self.strain_solver_data.is_empty() {
return 0.0;
}
for i in 0..self.strain_solver_data.len() {
self.strain_solver_data[i].calculate_strain_value();
}
let calculated_diff = self.strain_solver_data
.iter()
.filter(|s| matches!(s.hand, Hand::Left | Hand::Right))
.map(|s| s.total_strain_value)
.sum::<f32>()
/ self.strain_solver_data
.iter()
.filter(|s| matches!(s.hand, Hand::Left | Hand::Right))
.count() as f32;
let mut bins = Vec::new();
let map_start = self.strain_solver_data
.iter()
.map(|s| s.start_time as i32)
.min()
.unwrap_or(0) as f32;
let map_end = self.strain_solver_data
.iter()
.map(|s| (s.start_time.max(s.end_time) as i32))
.max()
.unwrap_or(0) as f32;
let use_fallback = self.map.get_key_count(false) % 2 == 1;
if use_fallback {
let mut current_time = map_start as i32;
let map_end_int = map_end as i32;
while current_time < map_end_int {
let values_in_bin: Vec<&StrainSolverData> = self.strain_solver_data
.iter()
.filter(|s| s.start_time >= current_time as f32 && s.start_time < (current_time + 1000) as f32)
.collect();
let average_rating = if !values_in_bin.is_empty() {
values_in_bin.iter().map(|s| s.total_strain_value).sum::<f32>() / values_in_bin.len() as f32
} else {
0.0
};
bins.push(average_rating);
current_time += 1000;
}
} else {
let mut left_index = 0;
let mut right_index = 0;
while left_index < self.strain_solver_data.len() && self.strain_solver_data[left_index].start_time < map_start {
left_index += 1;
}
let mut current_time = map_start as i32;
let map_end_int = map_end as i32;
while current_time < map_end_int {
while right_index < self.strain_solver_data.len() - 1
&& self.strain_solver_data[right_index + 1].start_time < (current_time + 1000) as f32 {
right_index += 1;
}
if left_index >= self.strain_solver_data.len() {
bins.push(0.0);
current_time += 1000;
continue;
}
let values_in_bin = &self.strain_solver_data[left_index..=right_index];
let average_rating = if !values_in_bin.is_empty() {
values_in_bin.iter().map(|s| s.total_strain_value).sum::<f32>() / values_in_bin.len() as f32
} else {
0.0
};
bins.push(average_rating);
left_index = right_index + 1;
current_time += 1000;
}
}
if bins.iter().all(|&strain| strain <= 0.0) {
return 0.0;
}
use crate::difficulty_processor::calculations::{calculate_continuity_adjustment, calculate_short_map_adjustment};
let (continuity_adjustment, continuity) = calculate_continuity_adjustment(&bins);
let short_map_adjustment = calculate_short_map_adjustment(&bins, continuity);
calculated_diff * continuity_adjustment * short_map_adjustment
}
fn compute_base_strain_states(&mut self, rate: f32, assume_hand: Hand) {
let key_count = mode_to_key_count(self.map.mode());
for hit_object in &self.map.hit_objects {
if self.map.has_scratch_key() && hit_object.lane == key_count {
continue;
}
let strain_hit_object = StrainSolverHitObject::new(hit_object.clone());
let mut strain_data = StrainSolverData::new(strain_hit_object, rate);
if let Ok(finger_state) = lane_to_finger(hit_object.lane, key_count) {
strain_data.hit_objects[0].finger_state = finger_state;
}
if let Ok(hand) = lane_to_hand(hit_object.lane, key_count) {
strain_data.hand = match hand {
Hand::Ambiguous => assume_hand,
_ => hand,
};
}
self.strain_solver_data.push(strain_data);
}
}
fn compute_for_chords(&mut self) {
let mut i = 0;
while i < self.strain_solver_data.len() - 1 {
let mut j = i + 1;
while j < self.strain_solver_data.len() {
let ms_diff = self.strain_solver_data[j].start_time - self.strain_solver_data[i].start_time;
if ms_diff > self.strain_constants.chord_clump_tolerance_ms {
break;
}
if ms_diff.abs() <= self.strain_constants.chord_clump_tolerance_ms {
if self.strain_solver_data[i].hand == self.strain_solver_data[j].hand {
let mut hit_objects_to_add = Vec::new();
for hit_obj in &self.strain_solver_data[j].hit_objects {
let same_state_found = self.strain_solver_data[i].hit_objects
.iter()
.any(|existing| existing.finger_state == hit_obj.finger_state);
if !same_state_found {
hit_objects_to_add.push(hit_obj.clone());
}
}
self.strain_solver_data[i].hit_objects.extend(hit_objects_to_add);
self.strain_solver_data.remove(j);
continue;
}
}
j += 1;
}
i += 1;
}
for i in 0..self.strain_solver_data.len() {
self.strain_solver_data[i].solve_finger_state();
}
}
fn compute_for_finger_actions(&mut self) {
for i in 0..self.strain_solver_data.len() - 1 {
for j in i + 1..self.strain_solver_data.len() {
if self.strain_solver_data[i].hand == self.strain_solver_data[j].hand
&& self.strain_solver_data[j].start_time > self.strain_solver_data[i].start_time {
let action_jack_found = (self.strain_solver_data[i].finger_state & self.strain_solver_data[j].finger_state) != FingerState::NONE;
let action_chord_found = self.strain_solver_data[i].hand_chord() || self.strain_solver_data[j].hand_chord();
let action_same_state = self.strain_solver_data[i].finger_state == self.strain_solver_data[j].finger_state;
let action_duration = self.strain_solver_data[j].start_time - self.strain_solver_data[i].start_time;
self.strain_solver_data[i].next_strain_solver_data_on_current_hand = Some(Box::new(self.strain_solver_data[j].clone()));
self.strain_solver_data[i].finger_action_duration_ms = action_duration;
if !action_chord_found && !action_same_state {
self.strain_solver_data[i].finger_action = FingerAction::Roll;
self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
action_duration,
self.strain_constants.roll_lower_boundary_ms,
self.strain_constants.roll_upper_boundary_ms,
self.strain_constants.roll_max_strain_value,
self.strain_constants.roll_curve_exponential,
self.average_note_density,
);
} else if action_same_state {
self.strain_solver_data[i].finger_action = FingerAction::SimpleJack;
self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
action_duration,
self.strain_constants.s_jack_lower_boundary_ms,
self.strain_constants.s_jack_upper_boundary_ms,
self.strain_constants.s_jack_max_strain_value,
self.strain_constants.s_jack_curve_exponential,
self.average_note_density,
);
} else if action_jack_found {
self.strain_solver_data[i].finger_action = FingerAction::TechnicalJack;
self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
action_duration,
self.strain_constants.t_jack_lower_boundary_ms,
self.strain_constants.t_jack_upper_boundary_ms,
self.strain_constants.t_jack_max_strain_value,
self.strain_constants.t_jack_curve_exponential,
self.average_note_density,
);
} else {
self.strain_solver_data[i].finger_action = FingerAction::Bracket;
self.strain_solver_data[i].action_strain_coefficient = get_coefficient_value(
action_duration,
self.strain_constants.bracket_lower_boundary_ms,
self.strain_constants.bracket_upper_boundary_ms,
self.strain_constants.bracket_max_strain_value,
self.strain_constants.bracket_curve_exponential,
self.average_note_density,
);
}
break;
}
}
}
}
fn compute_for_roll_manipulation(&mut self) {
let mut manipulation_index = 0;
for data in &mut self.strain_solver_data {
let mut manipulation_found = false;
if let Some(ref next) = data.next_strain_solver_data_on_current_hand {
if let Some(ref next_next) = next.next_strain_solver_data_on_current_hand {
if data.finger_action == FingerAction::Roll && next.finger_action == FingerAction::Roll {
if data.finger_state == next_next.finger_state {
let duration_ratio = (data.finger_action_duration_ms / next.finger_action_duration_ms)
.max(next.finger_action_duration_ms / data.finger_action_duration_ms);
if duration_ratio >= self.strain_constants.roll_ratio_tolerance_ms {
let duration_multiplier = 1.0 / (1.0 + (duration_ratio - 1.0) * self.strain_constants.roll_ratio_multiplier);
let manipulation_found_ratio = 1.0 - manipulation_index as f32 / self.strain_constants.roll_max_length * (1.0 - self.strain_constants.roll_length_multiplier);
data.roll_manipulation_strain_multiplier = duration_multiplier * manipulation_found_ratio;
manipulation_found = true;
self.roll_inaccuracy_confidence += 1.0;
if manipulation_index < self.strain_constants.roll_max_length as usize {
manipulation_index += 1;
}
}
}
}
}
}
if !manipulation_found && manipulation_index > 0 {
manipulation_index -= 1;
}
}
}
fn compute_for_jack_manipulation(&mut self) {
let mut long_jack_size = 0;
for data in &mut self.strain_solver_data {
let mut manipulation_found = false;
if let Some(ref next) = data.next_strain_solver_data_on_current_hand {
if data.finger_action == FingerAction::SimpleJack && next.finger_action == FingerAction::SimpleJack {
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)
.min(1.0)
.max(0.0);
let duration_multiplier = 1.0 - duration_value * (1.0 - self.strain_constants.vibro_multiplier);
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);
data.roll_manipulation_strain_multiplier = duration_multiplier * manipulation_found_ratio;
manipulation_found = true;
self.vibro_inaccuracy_confidence += 1.0;
if long_jack_size < self.strain_constants.vibro_max_length as usize {
long_jack_size += 1;
}
}
}
if !manipulation_found {
long_jack_size = 0;
}
}
}
fn compute_for_ln_multiplier(&mut self) {
for data in &mut self.strain_solver_data {
if data.end_time > data.start_time {
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)
.min(1.0)
.max(0.0);
let base_multiplier = 1.0 + duration_value * self.strain_constants.ln_base_multiplier;
for hit_object in &mut data.hit_objects {
hit_object.ln_strain_multiplier = base_multiplier;
}
if let Some(ref next) = data.next_strain_solver_data_on_current_hand {
if next.start_time < data.end_time - self.strain_constants.ln_end_threshold_ms {
if next.start_time >= data.start_time + self.strain_constants.ln_end_threshold_ms {
if next.end_time > data.end_time + self.strain_constants.ln_end_threshold_ms {
for hit_object in &mut data.hit_objects {
hit_object.ln_layer_type = LnLayerType::OutsideRelease;
hit_object.ln_strain_multiplier *= self.strain_constants.ln_release_after_multiplier;
}
} else if next.end_time > 0.0 {
for hit_object in &mut data.hit_objects {
hit_object.ln_layer_type = LnLayerType::InsideRelease;
hit_object.ln_strain_multiplier *= self.strain_constants.ln_release_before_multiplier;
}
} else {
for hit_object in &mut data.hit_objects {
hit_object.ln_layer_type = LnLayerType::InsideTap;
hit_object.ln_strain_multiplier *= self.strain_constants.ln_tap_multiplier;
}
}
}
}
}
}
}
}
fn compute_note_density_data(&mut self, rate: f32) {
self.average_note_density = SECONDS_TO_MILLISECONDS * self.map.hit_objects.len() as f32
/ (self.map.length() * (-0.5 * rate + 1.5));
}
}