diff --git a/src/osu/mod.rs b/src/osu/mod.rs index 388f50a2..d29b2c8f 100644 --- a/src/osu/mod.rs +++ b/src/osu/mod.rs @@ -6,13 +6,15 @@ mod pp; mod scaling_factor; mod skills; +use skills::OsuStrainSkill; + use crate::{curve::CurveBuffers, parse::Pos2, AnyStars, Beatmap, GameMode, Mods}; use self::{ difficulty_object::{Distances, OsuDifficultyObject}, osu_object::{ObjectParameters, OsuObject}, scaling_factor::ScalingFactor, - skills::{Skill, Skills}, + skills::Skills, }; pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*}; @@ -183,6 +185,8 @@ impl<'map> OsuStars<'map> { attrs.slider_factor = slider_factor; attrs.stars = star_rating; attrs.speed_note_count = speed_notes; + attrs.aim_difficult_strain_count = aim.count_difficult_strains(); + attrs.speed = speed.count_difficult_strains(); attrs } @@ -513,6 +517,10 @@ pub struct OsuDifficultyAttributes { pub flashlight: f64, /// The ratio of the aim strain with and without considering sliders pub slider_factor: f64, + /// The number of difficult aim strains. + pub aim_difficult_strain_count: f64, + /// The number 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. diff --git a/src/osu/pp.rs b/src/osu/pp.rs index 57158195..db0af50f 100644 --- a/src/osu/pp.rs +++ b/src/osu/pp.rs @@ -470,16 +470,13 @@ impl OsuPpInner { 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); + aim_value *= calculate_miss_penalty( + self.effective_miss_count, + self.attrs.aim_difficult_strain_count, + ); } - aim_value *= self.get_combo_scaling_factor(); - let ar_factor = if self.mods.rx() { 0.0 } else if self.attrs.ar > 10.33 { @@ -536,16 +533,13 @@ impl OsuPpInner { speed_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 { - speed_value *= 0.97 - * (1.0 - (self.effective_miss_count / total_hits).powf(0.775)) - .powf(self.effective_miss_count.powf(0.875)); + speed_value *= calculate_miss_penalty( + self.effective_miss_count, + self.attrs.speed_difficult_strain_count, + ); } - speed_value *= self.get_combo_scaling_factor(); - let ar_factor = if self.mods.ap() { 0.0 } else if self.attrs.ar > 10.33 { @@ -701,6 +695,10 @@ fn calculate_effective_misses(attrs: &OsuDifficultyAttributes, state: &OsuScoreS combo_based_miss_count.max(state.n_misses as f64) } +fn calculate_miss_penalty(miss_count: f64, difficult_strain_count: f64) -> f64 { + 0.96 / ((miss_count / (4.0 * difficult_strain_count.ln().powf(0.94))) + 1.0) +} + /// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation. pub trait OsuAttributeProvider { /// Provide the actual difficulty attributes. diff --git a/src/osu/skills/aim.rs b/src/osu/skills/aim.rs index 2f9b337f..4851f8d0 100644 --- a/src/osu/skills/aim.rs +++ b/src/osu/skills/aim.rs @@ -11,6 +11,7 @@ pub(crate) struct Aim { curr_section_end: f64, pub(crate) strain_peaks: Vec, with_sliders: bool, + object_strains: Vec, } 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,11 @@ impl StrainSkill for Aim { } } -impl OsuStrainSkill for Aim {} +impl OsuStrainSkill for Aim { + fn strains(&self) -> &Vec { + &self.object_strains + } +} struct AimEvaluator; diff --git a/src/osu/skills/flashlight.rs b/src/osu/skills/flashlight.rs index 86606ee9..9861f1e6 100644 --- a/src/osu/skills/flashlight.rs +++ b/src/osu/skills/flashlight.rs @@ -108,7 +108,13 @@ impl StrainSkill for Flashlight { } } -impl OsuStrainSkill for Flashlight {} +const HOLDING_VEC: &Vec = &vec![]; + +impl OsuStrainSkill for Flashlight { + fn strains(&self) -> &Vec { + HOLDING_VEC + } +} struct FlashlightEvaluator; diff --git a/src/osu/skills/speed.rs b/src/osu/skills/speed.rs index be025f77..6af81b1b 100644 --- a/src/osu/skills/speed.rs +++ b/src/osu/skills/speed.rs @@ -121,6 +121,10 @@ impl StrainSkill for Speed { impl OsuStrainSkill for Speed { const REDUCED_SECTION_COUNT: usize = 5; const DIFFICULTY_MULTIPLER: f64 = 1.04; + + fn strains(&self) -> &Vec { + &self.object_strains + } } struct SpeedEvaluator; diff --git a/src/osu/skills/traits.rs b/src/osu/skills/traits.rs index 9b50f703..07526f34 100644 --- a/src/osu/skills/traits.rs +++ b/src/osu/skills/traits.rs @@ -125,4 +125,24 @@ pub(crate) trait OsuStrainSkill: StrainSkill + Sized { difficulty * Self::DIFFICULTY_MULTIPLER } + + fn strains(&self) -> &Vec; + + fn count_difficult_strains(&mut self) -> f64 { + let difficulty_value = OsuStrainSkill::difficulty_value(self); + if difficulty_value == 0.0 { + 0.0 + } else { + // * What would the top strain be if all strain values were identical + let consistent_top_strain = difficulty_value / 10.0; + + let strains = self.strains(); + + // Use a weighted sum of all strains. Constants are arbitrary and give nice values + strains + .iter() + .map(|&s| 1.1 / (1.0 + (-10.0 * (s / consistent_top_strain - 0.88)).exp())) + .sum() + } + } }