use crate::rulesets::structs::{StrainSolverData, Hand};
pub fn get_coefficient_value(
duration: f32,
x_min: f32,
x_max: f32,
strain_max: f32,
exp: f32,
average_note_density: f32,
) -> f32 {
const LOWEST_DIFFICULTY: f32 = 1.0;
const DENSITY_MULTIPLIER: f32 = 0.266;
const DENSITY_DIFFICULTY_MIN: f32 = 0.4;
let ratio = (1.0 - (duration - x_min) / (x_max - x_min)).max(0.0);
if ratio == 0.0 && average_note_density < 4.0 {
if average_note_density < 1.0 {
return DENSITY_DIFFICULTY_MIN;
}
return average_note_density * DENSITY_MULTIPLIER + 0.134;
}
LOWEST_DIFFICULTY + (strain_max - LOWEST_DIFFICULTY) * ratio.powf(exp)
}
pub fn calculate_overall_difficulty(
strain_solver_data: &mut [StrainSolverData],
map_start: f32,
map_end: f32,
use_fallback: bool,
) -> f32 {
if strain_solver_data.is_empty() {
return 0.0;
}
for data in strain_solver_data.iter_mut() {
data.calculate_strain_value();
}
let calculated_diff = strain_solver_data
.iter()
.filter(|s| matches!(s.hand, Hand::Left | Hand::Right))
.map(|s| s.total_strain_value)
.sum::<f32>()
/ strain_solver_data
.iter()
.filter(|s| matches!(s.hand, Hand::Left | Hand::Right))
.count() as f32;
let bins = create_difficulty_bins(strain_solver_data, map_start, map_end, use_fallback);
if !bins.iter().any(|&strain| strain > 0.0) {
return 0.0;
}
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 create_difficulty_bins(
strain_solver_data: &[StrainSolverData],
map_start: f32,
map_end: f32,
use_fallback: bool,
) -> Vec<f32> {
let mut bins = Vec::new();
const BIN_SIZE: f32 = 1000.0;
let mut left_index = 0;
let mut right_index = 0;
while left_index < strain_solver_data.len() && strain_solver_data[left_index].start_time < map_start {
left_index += 1;
}
for i in (map_start as i32..map_end as i32).step_by(BIN_SIZE as usize) {
let bin_start = i as f32;
let bin_end = bin_start + BIN_SIZE;
let values_in_bin: Vec<&StrainSolverData> = if use_fallback {
strain_solver_data
.iter()
.filter(|s| s.start_time >= bin_start && s.start_time < bin_end)
.collect()
} else {
while right_index < strain_solver_data.len() - 1
&& strain_solver_data[right_index + 1].start_time < bin_end {
right_index += 1;
}
if left_index >= strain_solver_data.len() {
bins.push(0.0);
continue;
}
strain_solver_data[left_index..=right_index].iter().collect()
};
let average_rating = if values_in_bin.is_empty() {
0.0
} else {
values_in_bin.iter().map(|s| s.total_strain_value).sum::<f32>() / values_in_bin.len() as f32
};
bins.push(average_rating);
left_index = right_index + 1;
}
bins
}
pub fn calculate_continuity_adjustment(bins: &[f32]) -> (f32, f32) {
let cutoff_pos = (bins.len() as f32 * 0.4).floor() as usize;
let mut sorted_bins = bins.to_vec();
sorted_bins.sort_by(|a, b| b.partial_cmp(a).unwrap());
let top_40 = &sorted_bins[..cutoff_pos];
let easy_rating_cutoff = if top_40.is_empty() {
0.0
} else {
top_40.iter().sum::<f32>() / top_40.len() as f32
};
let continuity = if easy_rating_cutoff > 0.0 {
let non_zero_bins: Vec<f32> = bins.iter()
.filter(|&&strain| strain > 0.0)
.map(|&strain| (strain / easy_rating_cutoff).sqrt())
.collect();
if !non_zero_bins.is_empty() {
non_zero_bins.iter().sum::<f32>() / non_zero_bins.len() as f32
} else {
0.0
}
} else {
0.0
};
const MAX_CONTINUITY: f32 = 1.00;
const AVG_CONTINUITY: f32 = 0.85;
const MIN_CONTINUITY: f32 = 0.60;
const MAX_ADJUSTMENT: f32 = 1.05;
const AVG_ADJUSTMENT: f32 = 1.00;
const MIN_ADJUSTMENT: f32 = 0.90;
let continuity_adjustment = if continuity > AVG_CONTINUITY {
let continuity_factor = 1.0 - (continuity - AVG_CONTINUITY) / (MAX_CONTINUITY - AVG_CONTINUITY);
(continuity_factor * (AVG_ADJUSTMENT - MIN_ADJUSTMENT) + MIN_ADJUSTMENT)
.min(AVG_ADJUSTMENT)
.max(MIN_ADJUSTMENT)
} else {
let continuity_factor = 1.0 - (continuity - MIN_CONTINUITY) / (AVG_CONTINUITY - MIN_CONTINUITY);
(continuity_factor * (MAX_ADJUSTMENT - AVG_ADJUSTMENT) + AVG_ADJUSTMENT)
.min(MAX_ADJUSTMENT)
.max(AVG_ADJUSTMENT)
};
(continuity_adjustment, continuity)
}
pub fn calculate_short_map_adjustment(bins: &[f32], continuity: f32) -> f32 {
const MAX_SHORT_MAP_ADJUSTMENT: f32 = 0.75;
const SHORT_MAP_THRESHOLD: f32 = 60.0 * 1000.0; const BIN_SIZE: f32 = 1000.0;
let true_drain_time = bins.len() as f32 * continuity * BIN_SIZE;
let short_map_adjustment = (0.25 * (true_drain_time / SHORT_MAP_THRESHOLD).sqrt() + 0.75)
.min(1.0)
.max(MAX_SHORT_MAP_ADJUSTMENT);
short_map_adjustment
}