fix: improve osu hitresult generation

This commit is contained in:
MaxOhn
2023-10-29 17:12:55 +01:00
parent 5393d36782
commit 99b12376c3
+112 -77
View File
@@ -212,13 +212,15 @@ impl<'map> OsuPP<'map> {
let n_objects = self.passed_objects.unwrap_or(self.map.hit_objects.len()); let n_objects = self.passed_objects.unwrap_or(self.map.hit_objects.len());
let priority = self.hitresult_priority.unwrap_or_default(); let priority = self.hitresult_priority.unwrap_or_default();
let mut n300 = self.n300.unwrap_or(0); let n_misses = self.n_misses.map_or(0, |n| n.min(n_objects));
let mut n100 = self.n100.unwrap_or(0); let n_remaining = n_objects - n_misses;
let mut n50 = self.n50.unwrap_or(0);
let n_misses = self.n_misses.unwrap_or(0); let mut n300 = self.n300.map_or(0, |n| n.min(n_remaining));
let mut n100 = self.n100.map_or(0, |n| n.min(n_remaining));
let mut n50 = self.n50.map_or(0, |n| n.min(n_remaining));
if let Some(acc) = self.acc { if let Some(acc) = self.acc {
let target_total = (acc * (n_objects * 6) as f64).round() as usize; let target_total = acc * (6 * n_objects) as f64;
match (self.n300, self.n100, self.n50) { match (self.n300, self.n100, self.n50) {
(Some(_), Some(_), Some(_)) => { (Some(_), Some(_), Some(_)) => {
@@ -233,114 +235,140 @@ impl<'map> OsuPP<'map> {
(Some(_), None, Some(_)) => n100 = n_objects.saturating_sub(n300 + n50 + n_misses), (Some(_), None, Some(_)) => n100 = n_objects.saturating_sub(n300 + n50 + n_misses),
(None, Some(_), Some(_)) => n300 = n_objects.saturating_sub(n100 + n50 + n_misses), (None, Some(_), Some(_)) => n300 = n_objects.saturating_sub(n100 + n50 + n_misses),
(Some(_), None, None) => { (Some(_), None, None) => {
let delta = (target_total - n_objects.saturating_sub(n_misses)) let mut best_dist = f64::MAX;
.saturating_sub(n300 * 5);
n100 = delta % 5; n300 = n300.min(n_remaining);
n50 = n_objects.saturating_sub(n300 + n100 + n_misses); let n_remaining = n_remaining - n300;
let curr_total = 6 * n300 + 2 * n100 + n50; let raw_n100 = target_total - (n_remaining + 6 * n300) as f64;
let min_n100 = n_remaining.min(raw_n100.floor() as usize);
let max_n100 = n_remaining.min(raw_n100.ceil() as usize);
if curr_total < target_total { for new100 in min_n100..=max_n100 {
let n = (target_total - curr_total).min(n50); let new50 = n_remaining - new100;
n50 -= n; let dist = (acc - accuracy(n300, new100, new50, n_misses)).abs();
n100 += n;
} else { if dist < best_dist {
let n = (curr_total - target_total).min(n100); best_dist = dist;
n100 -= n; n100 = new100;
n50 += n; n50 = new50;
}
} }
} }
(None, Some(_), None) => { (None, Some(_), None) => {
let delta = let mut best_dist = f64::MAX;
(target_total - n_objects.saturating_sub(n_misses)).saturating_sub(n100);
n300 = delta / 5; n100 = n100.min(n_remaining);
let n_remaining = n_remaining - n100;
if n300 + n100 + n_misses > n_objects { let raw_n300 = (target_total - (n_remaining + 2 * n100) as f64) / 5.0;
n300 -= (n300 + n100 + n_misses) - n_objects; let min_n300 = n_remaining.min(raw_n300.floor() as usize);
let max_n300 = n_remaining.min(raw_n300.ceil() as usize);
for new300 in min_n300..=max_n300 {
let new50 = n_remaining - new300;
let curr_dist = (acc - accuracy(new300, n100, new50, n_misses)).abs();
if curr_dist < best_dist {
best_dist = curr_dist;
n300 = new300;
n50 = new50;
}
} }
n50 = n_objects - n300 - n100 - n_misses;
} }
(None, None, Some(_)) => { (None, None, Some(_)) => {
let delta = target_total - n_objects.saturating_sub(n_misses); let mut best_dist = f64::MAX;
n300 = delta / 5; n50 = n50.min(n_remaining);
n100 = delta % 5; let n_remaining = n_remaining - n50;
if n300 + n100 + n50 + n_misses > n_objects { let raw_n300 =
let too_many = n300 + n100 + n50 + n_misses - n_objects; (target_total + (2 * n_misses + n50) as f64 - (2 * n_objects) as f64) / 4.0;
if too_many > n100 { let min_n300 = n_remaining.min(raw_n300.floor() as usize);
n300 -= too_many - n100; let max_n300 = n_remaining.min(raw_n300.ceil() as usize);
n100 = 0;
} else { for new300 in min_n300..=max_n300 {
n100 -= too_many; let new100 = n_remaining - new300;
let curr_dist = (acc - accuracy(new300, new100, n50, n_misses)).abs();
if curr_dist < best_dist {
best_dist = curr_dist;
n300 = new300;
n100 = new100;
} }
} }
n100 += n_objects.saturating_sub(n300 + n100 + n50 + n_misses);
let curr_total = 6 * n300 + 2 * n100 + n50;
if curr_total < target_total {
let n = n100.min((target_total - curr_total) / 4);
n100 -= n;
n300 += n;
} else {
let n = n300.min((curr_total - target_total) / 4);
n300 -= n;
n100 += n;
}
} }
(None, None, None) => { (None, None, None) => {
let delta = target_total - n_objects.saturating_sub(n_misses); let mut best_dist = f64::MAX;
n300 = delta / 5; let raw_n300 = (target_total - n_remaining as f64) / 5.0;
n100 = delta % 5; let min_n300 = n_remaining.min(raw_n300.floor() as usize);
n50 = n_objects.saturating_sub(n300 + n100 + n_misses); let max_n300 = n_remaining.min(raw_n300.ceil() as usize);
if let HitResultPriority::BestCase = priority { for new300 in min_n300..=max_n300 {
let raw_n100 = target_total - (n_remaining + 5 * new300) as f64;
let min_n100 = (raw_n100.floor() as usize).min(n_remaining - new300);
let max_n100 = (raw_n100.ceil() as usize).min(n_remaining - new300);
for new100 in min_n100..=max_n100 {
let new50 = n_remaining - new300 - new100;
let curr_dist = (acc - accuracy(new300, new100, new50, n_misses)).abs();
if curr_dist < best_dist {
best_dist = curr_dist;
n300 = new300;
n100 = new100;
n50 = new50;
}
}
}
match priority {
HitResultPriority::BestCase => {
// Shift n50 to n100 by sacrificing n300 // Shift n50 to n100 by sacrificing n300
let n = n300.min(n50 / 4); let n = n300.min(n50 / 4);
n300 -= n; n300 -= n;
n100 += 5 * n; n100 += 5 * n;
n50 -= 4 * n; n50 -= 4 * n;
} }
HitResultPriority::WorstCase => {
// Shift n100 to n50 by gaining n300
let n = n100 / 5;
n300 += n;
n100 -= 5 * n;
n50 += 4 * n;
}
}
} }
} }
} else { } else {
let remaining = n_objects.saturating_sub(n300 + n100 + n50 + n_misses); let remaining = n_objects.saturating_sub(n300 + n100 + n50 + n_misses);
match priority { match priority {
HitResultPriority::BestCase => { HitResultPriority::BestCase => match (self.n300, self.n100, self.n50) {
if self.n300.is_none() { (None, ..) => n300 = remaining,
n300 = remaining; (_, None, _) => n100 = remaining,
} else if self.n100.is_none() { (.., None) => n50 = remaining,
n100 = remaining; _ => n300 += remaining,
} else if self.n50.is_none() { },
n50 = remaining; HitResultPriority::WorstCase => match (self.n50, self.n100, self.n300) {
} else { (None, ..) => n50 = remaining,
n300 += remaining; (_, None, _) => n100 = remaining,
} (.., None) => n300 = remaining,
} _ => n50 += remaining,
HitResultPriority::WorstCase => { },
if self.n50.is_none() {
n50 = remaining;
} else if self.n100.is_none() {
n100 = remaining;
} else if self.n300.is_none() {
n300 = remaining;
} else {
n50 += remaining;
}
}
} }
} }
let max_possible_combo = max_combo.saturating_sub(n_misses);
let max_combo = self
.combo
.map_or(max_possible_combo, |combo| combo.min(max_possible_combo));
OsuScoreState { OsuScoreState {
max_combo: self.combo.unwrap_or(max_combo), max_combo,
n300, n300,
n100, n100,
n50, n50,
@@ -696,6 +724,13 @@ fn calculate_effective_misses(attrs: &OsuDifficultyAttributes, state: &OsuScoreS
combo_based_miss_count.max(state.n_misses as f64) combo_based_miss_count.max(state.n_misses as f64)
} }
fn accuracy(n300: usize, n100: usize, n50: usize, n_misses: usize) -> f64 {
let numerator = 6 * n300 + 2 * n100 + n50;
let denominator = 6 * (n300 + n100 + n50 + n_misses);
numerator as f64 / denominator as f64
}
/// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation. /// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation.
pub trait OsuAttributeProvider { pub trait OsuAttributeProvider {
/// Provide the actual difficulty attributes. /// Provide the actual difficulty attributes.