start base

This commit is contained in:
osu!Akatsuki Developer
2022-11-03 18:36:51 +00:00
parent 4137520bbb
commit ceb91025fa
15 changed files with 78 additions and 1046 deletions
+12 -1
View File
@@ -138,13 +138,18 @@ impl<'map> OsuStars<'map> {
1.0
};
let aim_difficult_strain_count = skills::OsuStrainSkill::count_difficult_strains(&mut aim);
// currently unused as relax pp doesn't include speed
let speed_difficult_strain_count =
skills::OsuStrainSkill::count_difficult_strains(&mut speed);
if mods.td() {
aim_rating = aim_rating.powf(0.8);
flashlight_rating = flashlight_rating.powf(0.8);
}
if mods.rx() {
aim_rating *= 0.9;
speed_rating = 0.0;
flashlight_rating *= 0.7;
}
@@ -176,6 +181,8 @@ impl<'map> OsuStars<'map> {
attrs.speed = speed_rating;
attrs.flashlight = flashlight_rating;
attrs.slider_factor = slider_factor;
attrs.aim_difficult_strain_count = aim_difficult_strain_count;
attrs.speed_difficult_strain_count = speed_difficult_strain_count;
attrs.stars = star_rating;
attrs.speed_note_count = speed_notes;
@@ -508,6 +515,10 @@ pub struct OsuDifficultyAttributes {
pub flashlight: f64,
/// The ratio of the aim strain with and without considering sliders
pub slider_factor: f64,
/// The count of difficult aim strains.
pub aim_difficult_strain_count: f64,
/// The count of difficult speed strains.
pub speed_difficult_strain_count: f64,
/// The number of clickable objects weighted by difficulty.
pub speed_note_count: f64,
/// The approach rate.
+25 -318
View File
@@ -388,7 +388,7 @@ struct OsuPpInner {
}
impl OsuPpInner {
fn calculate(mut self) -> OsuPerformanceAttributes {
fn calculate(self) -> OsuPerformanceAttributes {
let total_hits = self.state.total_hits();
if total_hits == 0 {
@@ -410,28 +410,6 @@ impl OsuPpInner {
multiplier *= 1.0 - (self.attrs.n_spinners as f64 / total_hits).powf(0.85);
}
if self.mods.rx() {
// * https://www.desmos.com/calculator/bc9eybdthb
// * we use OD13.3 as maximum since it's the value at which great hitwidow becomes 0
// * this is well beyond currently maximum achievable OD which is 12.17 (DTx2 + DA with OD11)
let (n100_mult, n50_mult) = if self.attrs.od > 0.0 {
(
1.0 - (self.attrs.od / 13.33).powf(1.8),
1.0 - (self.attrs.od / 13.33).powi(5),
)
} else {
(1.0, 1.0)
};
// * As we're adding Oks and Mehs to an approximated number of combo breaks the result can be
// * higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
self.effective_miss_count = (self.effective_miss_count
+ self.state.n100 as f64
+ n100_mult
+ self.state.n50 as f64 * n50_mult)
.min(total_hits);
}
let aim_value = self.compute_aim_value();
let speed_value = self.compute_speed_value();
let acc_value = self.compute_accuracy_value();
@@ -460,25 +438,30 @@ impl OsuPpInner {
let total_hits = self.total_hits();
let len_bonus = 0.95
+ 0.4 * (total_hits / 2000.0).min(1.0)
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
let mut len_bonus = 1.0;
if self.mods.rx() {
if self.effective_miss_count > 0.0 {
aim_value *= self.calculate_miss_penalty(self.attrs.aim_difficult_strain_count);
}
} else {
len_bonus = 0.95
+ 0.4 * (total_hits / 2000.0).min(1.0)
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
aim_value *= len_bonus;
aim_value *= len_bonus;
// * Penalize misses by assessing # of misses relative to the total # of objects.
// * Default a 3% reduction for any # of misses.
if self.effective_miss_count > 0.0 {
aim_value *= 0.97
* (1.0 - (self.effective_miss_count / total_hits).powf(0.775))
.powf(self.effective_miss_count);
// * Penalize misses by assessing # of misses relative to the total # of objects.
// * Default a 3% reduction for any # of misses.
if self.effective_miss_count > 0.0 {
aim_value *= 0.97
* (1.0 - (self.effective_miss_count / total_hits).powf(0.775))
.powf(self.effective_miss_count);
}
aim_value *= self.get_combo_scaling_factor();
}
aim_value *= self.get_combo_scaling_factor();
let ar_factor = if self.mods.rx() {
0.0
} else if self.attrs.ar > 10.33 {
let ar_factor = if self.attrs.ar > 10.33 {
0.3 * (self.attrs.ar - 10.33)
} else if self.attrs.ar < 8.0 {
0.05 * (8.0 - self.attrs.ar)
@@ -671,6 +654,10 @@ impl OsuPpInner {
}
}
fn calculate_miss_penalty(&self, strain_count: f64) -> f64 {
0.94 / ((self.effective_miss_count / (2.0 * strain_count.sqrt())) + 1.0)
}
fn total_hits(&self) -> f64 {
self.state.total_hits() as f64
}
@@ -738,283 +725,3 @@ impl OsuAttributeProvider for PerformanceAttributes {
}
}
}
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
#[cfg(test)]
mod test {
use super::*;
use crate::Beatmap;
fn test_data() -> (Beatmap, OsuDifficultyAttributes) {
let path = "./maps/2785319.osu";
let map = Beatmap::from_path(path).unwrap();
let attrs = OsuDifficultyAttributes {
aim: 2.8693628443424104,
speed: 2.533869745015772,
flashlight: 2.288770487900865,
slider_factor: 0.9803052946037858,
speed_note_count: 210.36373973116545,
ar: 9.300000190734863,
od: 8.800000190734863,
hp: 5.0,
n_circles: 307,
n_sliders: 293,
n_spinners: 1,
stars: 5.669858729379631,
max_combo: 909,
};
(map, attrs)
}
#[test]
fn hitresults_n300_n100_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.n300(300)
.n100(20)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 300,
n100: 20,
n50: 279,
n_misses: 2,
};
assert_eq!(state, expected);
}
#[test]
fn hitresults_n300_n50_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.n300(300)
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 300,
n100: 289,
n50: 10,
n_misses: 2,
};
assert_eq!(state, expected);
}
#[test]
fn hitresults_n50_n_misses_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 0,
n100: 589,
n50: 10,
n_misses: 2,
};
assert_eq!(state, expected);
}
#[test]
fn hitresults_n300_n100_n50_n_misses_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.n300(300)
.n100(50)
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 300,
n100: 50,
n50: 249,
n_misses: 2,
};
assert_eq!(state, expected);
}
#[test]
fn hitresults_acc_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.accuracy(98.0)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 584,
n100: 15,
n50: 0,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
}
#[test]
fn hitresults_acc_n100_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.accuracy(95.0)
.n100(15)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 562,
n100: 15,
n50: 22,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
}
#[test]
fn hitresults_acc_n50_n_misses_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.accuracy(95.0)
.n50(10)
.n_misses(2)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 560,
n100: 29,
n50: 10,
n_misses: 2,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
}
#[test]
fn hitresults_acc_best() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.accuracy(90.0)
.hitresult_priority(HitResultPriority::BestCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 511,
n100: 89,
n50: 1,
n_misses: 0,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
}
#[test]
fn hitresults_acc_worst() {
let (map, attrs) = test_data();
let max_combo = attrs.max_combo();
let state = OsuPP::new(&map)
.attributes(attrs)
.combo(500)
.accuracy(90.0)
.hitresult_priority(HitResultPriority::WorstCase)
.generate_hitresults(max_combo);
let expected = OsuScoreState {
max_combo: 500,
n300: 528,
n100: 4,
n50: 69,
n_misses: 0,
};
assert_eq!(
state,
expected,
"{}% vs {}%",
state.accuracy(),
expected.accuracy()
);
}
}
+17 -1
View File
@@ -11,6 +11,7 @@ pub(crate) struct Aim {
curr_section_end: f64,
pub(crate) strain_peaks: Vec<f64>,
with_sliders: bool,
pub(crate) object_strains: Vec<f64>,
}
impl Aim {
@@ -24,6 +25,7 @@ impl Aim {
curr_section_end: 0.0,
strain_peaks: Vec::new(),
with_sliders,
object_strains: Vec::new(),
}
}
@@ -74,6 +76,8 @@ impl StrainSkill for Aim {
self.curr_strain += AimEvaluator::evaluate_diff_of(curr, diff_objects, self.with_sliders)
* Self::SKILL_MULTIPLIER;
self.object_strains.push(self.curr_strain);
self.curr_strain
}
@@ -93,7 +97,19 @@ impl StrainSkill for Aim {
}
}
impl OsuStrainSkill for Aim {}
impl OsuStrainSkill for Aim {
fn count_difficult_strains(&mut self) -> f64 {
let top_strain = self
.object_strains
.iter()
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr));
self.object_strains
.iter()
.map(|strain| (strain / top_strain).powi(4))
.sum()
}
}
struct AimEvaluator;
+5 -1
View File
@@ -108,7 +108,11 @@ impl StrainSkill for Flashlight {
}
}
impl OsuStrainSkill for Flashlight {}
impl OsuStrainSkill for Flashlight {
fn count_difficult_strains(&mut self) -> f64 {
0.0
}
}
struct FlashlightEvaluator;
+12
View File
@@ -118,6 +118,18 @@ impl StrainSkill for Speed {
impl OsuStrainSkill for Speed {
const REDUCED_SECTION_COUNT: usize = 5;
const DIFFICULTY_MULTIPLER: f64 = 1.04;
fn count_difficult_strains(&mut self) -> f64 {
let top_strain = self
.object_strains
.iter()
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr));
self.object_strains
.iter()
.map(|strain| (strain / top_strain).powi(4))
.sum()
}
}
struct SpeedEvaluator;
+2
View File
@@ -90,6 +90,8 @@ pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
const DIFFICULTY_MULTIPLER: f64 = 1.06;
fn count_difficult_strains(&mut self) -> f64;
fn difficulty_value(&mut self) -> f64 {
let mut difficulty = 0.0;
let mut weight = 1.0;