use std::cmp;
use crate::{
GameMods,
osu::{OsuDifficultyAttributes, OsuScoreState},
};
pub struct OsuLegacyScoreMissCalculator<'a> {
state: &'a OsuScoreState,
acc: f64,
mods: &'a GameMods,
attrs: &'a OsuDifficultyAttributes,
}
impl<'a> OsuLegacyScoreMissCalculator<'a> {
pub const fn new(
state: &'a OsuScoreState,
acc: f64,
mods: &'a GameMods,
attrs: &'a OsuDifficultyAttributes,
) -> Self {
Self {
state,
acc,
mods,
attrs,
}
}
pub fn calculate(self) -> f64 {
let Self { state, attrs, .. } = self;
if attrs.max_combo == 0 {
return 0.0;
}
let Some(legacy_total_score) = state.legacy_total_score else {
return 0.0;
};
let score_v1_multiplier =
self.attrs.legacy_score_base_multiplier * self.get_legacy_score_multiplier();
let relevant_combo_per_object = self.calculate_relevant_score_combo_per_object();
let maximum_miss_count = self.calculate_maximum_combo_based_miss_count();
let score_obtained_during_max_combo = self.calculate_score_at_combo(
state.max_combo,
relevant_combo_per_object,
score_v1_multiplier,
);
let remaining_score = f64::from(legacy_total_score) - score_obtained_during_max_combo;
if remaining_score <= 0.0 {
return maximum_miss_count;
}
let remaining_combo = attrs.max_combo - state.max_combo;
let expected_remaining_score = self.calculate_score_at_combo(
remaining_combo,
relevant_combo_per_object,
score_v1_multiplier,
);
let mut score_based_miss_count = expected_remaining_score / remaining_score;
score_based_miss_count = score_based_miss_count.max(1.0);
score_based_miss_count.min(maximum_miss_count)
}
fn calculate_score_at_combo(
&self,
combo: u32,
relevant_combo_per_object: f64,
score_v1_multiplier: f64,
) -> f64 {
let Self {
state, acc, attrs, ..
} = self;
let total_hits = state.hitresults.total_hits();
let estimated_objects = f64::from(combo) / relevant_combo_per_object - 1.0;
let mut combo_score = if relevant_combo_per_object > 0.0 {
(2.0 * (relevant_combo_per_object - 1.0)
+ (estimated_objects - 1.0) * relevant_combo_per_object)
* estimated_objects
/ 2.0
} else {
0.0
};
combo_score *= acc * 300.0 / 25.0 * score_v1_multiplier;
let objects_hit = f64::from(total_hits - state.hitresults.misses) * f64::from(combo)
/ f64::from(attrs.max_combo);
let non_combo_score = (300.0 + attrs.nested_score_per_object) * acc * objects_hit;
combo_score + non_combo_score
}
fn calculate_relevant_score_combo_per_object(&self) -> f64 {
let attrs = self.attrs;
let mut combo_score = attrs.maximum_legacy_combo_score;
combo_score /= 300.0 / 25.0 * attrs.legacy_score_base_multiplier;
let mut result = f64::from((attrs.max_combo as i32 - 2) * attrs.max_combo as i32);
result /= (f64::from(attrs.max_combo) + 2.0 * (combo_score - 1.0)).max(1.0);
result
}
fn calculate_maximum_combo_based_miss_count(&self) -> f64 {
let Self { state, attrs, .. } = self;
if attrs.n_sliders == 0 {
return f64::from(state.hitresults.misses);
}
let total_imperfect_hits =
state.hitresults.n100 + state.hitresults.n50 + state.hitresults.misses;
let mut miss_count = 0.0;
let full_combo_threshold = f64::from(attrs.max_combo) - 0.1 * f64::from(attrs.n_sliders);
if f64::from(state.max_combo) < full_combo_threshold {
miss_count = (full_combo_threshold / f64::from(state.max_combo).max(1.0)).powf(2.5);
}
miss_count = miss_count.min(f64::from(total_imperfect_hits));
let max_possible_slider_breaks =
cmp::min((attrs.max_combo - state.max_combo) / 2, attrs.n_sliders);
let slider_breaks = miss_count - f64::from(state.hitresults.misses);
if slider_breaks > f64::from(max_possible_slider_breaks) {
miss_count = f64::from(state.hitresults.misses + max_possible_slider_breaks);
}
miss_count
}
fn get_legacy_score_multiplier(&self) -> f64 {
let mods = self.mods;
let score_v2 = mods.sv2();
let mut multiplier = 1.0;
if mods.nf() {
multiplier *= if score_v2 { 1.0 } else { 0.5 };
}
if mods.ez() {
multiplier *= 0.5;
}
if mods.clock_rate() < 1.0 {
multiplier *= 0.3;
}
if mods.hd() {
multiplier *= 1.06;
}
if mods.hr() {
multiplier *= if score_v2 { 1.10 } else { 1.06 };
}
if mods.clock_rate() > 1.0 {
multiplier *= if score_v2 { 1.20 } else { 1.12 };
}
if mods.fl() {
multiplier *= 1.12;
}
if mods.so() {
multiplier *= 0.9;
}
if mods.rx() || mods.ap() {
multiplier *= 0.0;
}
multiplier
}
}