use builder pattern to calculate difficulty attrs
This commit is contained in:
@@ -324,7 +324,7 @@ mod tests {
|
||||
fn iter_end_eq_regular() {
|
||||
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
let regular = crate::osu::stars(&map, mods, None);
|
||||
let regular = crate::OsuStars::new(&map).mods(mods).calculate();
|
||||
|
||||
let iter_end = OsuGradualDifficultyAttributes::new(&map, mods)
|
||||
.last()
|
||||
|
||||
+146
-92
@@ -29,71 +29,161 @@ const DIFFICULTY_MULTIPLIER: f64 = 0.0675;
|
||||
const NORMALIZED_RADIUS: f32 = 50.0; // * diameter of 100; easier mental maths.
|
||||
const STACK_DISTANCE: f32 = 3.0;
|
||||
|
||||
/// Difficulty calculation for osu!standard maps.
|
||||
/// Difficulty calculator on osu!standard maps.
|
||||
///
|
||||
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
|
||||
/// # Example
|
||||
///
|
||||
/// If you want to calculate the difficulty after every few objects, instead of
|
||||
/// calling this function multiple times with different `passed_objects`, you should use
|
||||
/// [`OsuGradualDifficultyAttributes`](crate::osu::OsuGradualDifficultyAttributes).
|
||||
pub fn stars(
|
||||
map: &Beatmap,
|
||||
mods: impl Mods,
|
||||
/// ```
|
||||
/// use rosu_pp::{OsuStars, Beatmap};
|
||||
///
|
||||
/// # /*
|
||||
/// let map: Beatmap = ...
|
||||
/// # */
|
||||
/// # let map = Beatmap::default();
|
||||
///
|
||||
/// let difficulty_attrs = OsuStars::new(&map)
|
||||
/// .mods(8 + 64) // HDDT
|
||||
/// .calculate();
|
||||
///
|
||||
/// println!("Stars: {}", difficulty_attrs.stars);
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OsuStars<'map> {
|
||||
map: &'map Beatmap,
|
||||
mods: u32,
|
||||
passed_objects: Option<usize>,
|
||||
) -> OsuDifficultyAttributes {
|
||||
let (mut skills, mut attributes) = calculate_skills(map, mods, passed_objects);
|
||||
}
|
||||
|
||||
let aim_rating = {
|
||||
let aim = skills.aim();
|
||||
let mut aim_strains = mem::take(&mut aim.strain_peaks);
|
||||
impl<'map> OsuStars<'map> {
|
||||
/// Create a new difficulty calculator for osu!standard maps.
|
||||
#[inline]
|
||||
pub fn new(map: &'map Beatmap) -> Self {
|
||||
Self {
|
||||
map,
|
||||
mods: 0,
|
||||
passed_objects: None,
|
||||
}
|
||||
}
|
||||
|
||||
Skill::difficulty_value(&mut aim_strains, aim).sqrt() * DIFFICULTY_MULTIPLIER
|
||||
};
|
||||
/// Specify mods through their bit values.
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
#[inline]
|
||||
pub fn mods(mut self, mods: u32) -> Self {
|
||||
self.mods = mods;
|
||||
|
||||
let slider_factor = if aim_rating > 0.0 {
|
||||
let aim_no_sliders = skills.aim_no_sliders();
|
||||
self
|
||||
}
|
||||
|
||||
let mut aim_strains_no_sliders = mem::take(&mut aim_no_sliders.strain_peaks);
|
||||
let aim_rating_no_sliders =
|
||||
Skill::difficulty_value(&mut aim_strains_no_sliders, aim_no_sliders).sqrt()
|
||||
* DIFFICULTY_MULTIPLIER;
|
||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||
///
|
||||
/// If you want to calculate the difficulty after every few objects, instead of
|
||||
/// using [`OsuStars`] multiple times with different `passed_objects`, you should use
|
||||
/// [`OsuGradualDifficultyAttributes`](crate::osu::OsuGradualDifficultyAttributes).
|
||||
#[inline]
|
||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||
self.passed_objects = Some(passed_objects);
|
||||
|
||||
aim_rating_no_sliders / aim_rating
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
let (speed, flashlight) = skills.speed_flashlight();
|
||||
/// Calculate all difficulty related values, including stars.
|
||||
#[inline]
|
||||
pub fn calculate(self) -> OsuDifficultyAttributes {
|
||||
let (mut skills, mut attributes) = calculate_skills(self);
|
||||
|
||||
let speed_rating = if let Some(speed) = speed {
|
||||
let mut speed_strains = mem::take(&mut speed.strain_peaks);
|
||||
let aim_rating = {
|
||||
let aim = skills.aim();
|
||||
let mut aim_strains = mem::take(&mut aim.strain_peaks);
|
||||
|
||||
Skill::difficulty_value(&mut speed_strains, speed).sqrt() * DIFFICULTY_MULTIPLIER
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
Skill::difficulty_value(&mut aim_strains, aim).sqrt() * DIFFICULTY_MULTIPLIER
|
||||
};
|
||||
|
||||
let flashlight_rating = if let Some(flashlight) = flashlight {
|
||||
let mut flashlight_strains = mem::take(&mut flashlight.strain_peaks);
|
||||
let slider_factor = if aim_rating > 0.0 {
|
||||
let aim_no_sliders = skills.aim_no_sliders();
|
||||
|
||||
Skill::difficulty_value(&mut flashlight_strains, flashlight).sqrt() * DIFFICULTY_MULTIPLIER
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let mut aim_strains_no_sliders = mem::take(&mut aim_no_sliders.strain_peaks);
|
||||
let aim_rating_no_sliders =
|
||||
Skill::difficulty_value(&mut aim_strains_no_sliders, aim_no_sliders).sqrt()
|
||||
* DIFFICULTY_MULTIPLIER;
|
||||
|
||||
let star_rating = if attributes.max_combo == 0 {
|
||||
0.0
|
||||
} else {
|
||||
calculate_star_rating(aim_rating, speed_rating, flashlight_rating)
|
||||
};
|
||||
aim_rating_no_sliders / aim_rating
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
attributes.aim_strain = aim_rating;
|
||||
attributes.speed_strain = speed_rating;
|
||||
attributes.flashlight_rating = flashlight_rating;
|
||||
attributes.slider_factor = slider_factor;
|
||||
attributes.stars = star_rating;
|
||||
let (speed, flashlight) = skills.speed_flashlight();
|
||||
|
||||
attributes
|
||||
let speed_rating = if let Some(speed) = speed {
|
||||
let mut speed_strains = mem::take(&mut speed.strain_peaks);
|
||||
|
||||
Skill::difficulty_value(&mut speed_strains, speed).sqrt() * DIFFICULTY_MULTIPLIER
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let flashlight_rating = if let Some(flashlight) = flashlight {
|
||||
let mut flashlight_strains = mem::take(&mut flashlight.strain_peaks);
|
||||
|
||||
Skill::difficulty_value(&mut flashlight_strains, flashlight).sqrt()
|
||||
* DIFFICULTY_MULTIPLIER
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let star_rating = if attributes.max_combo == 0 {
|
||||
0.0
|
||||
} else {
|
||||
calculate_star_rating(aim_rating, speed_rating, flashlight_rating)
|
||||
};
|
||||
|
||||
attributes.aim_strain = aim_rating;
|
||||
attributes.speed_strain = speed_rating;
|
||||
attributes.flashlight_rating = flashlight_rating;
|
||||
attributes.slider_factor = slider_factor;
|
||||
attributes.stars = star_rating;
|
||||
|
||||
attributes
|
||||
}
|
||||
|
||||
/// Calculate the skill strains.
|
||||
///
|
||||
/// Suitable to plot the difficulty of a map over time.
|
||||
#[inline]
|
||||
pub fn strains(self) -> Strains {
|
||||
let mods = self.mods;
|
||||
let (mut skills, _) = calculate_skills(self);
|
||||
|
||||
let mut aim = mem::take(&mut skills.aim().strain_peaks);
|
||||
let tuple = skills.speed_flashlight();
|
||||
|
||||
let strains = match tuple {
|
||||
(Some(speed), Some(flashlight)) => {
|
||||
for ((aim, speed), flashlight) in aim
|
||||
.iter_mut()
|
||||
.zip(&speed.strain_peaks)
|
||||
.zip(&flashlight.strain_peaks)
|
||||
{
|
||||
*aim += speed + flashlight;
|
||||
}
|
||||
|
||||
aim
|
||||
}
|
||||
(Some(strains), None) | (None, Some(strains)) => {
|
||||
for (aim, strain) in aim.iter_mut().zip(&strains.strain_peaks) {
|
||||
*aim += strain;
|
||||
}
|
||||
|
||||
aim
|
||||
}
|
||||
(None, None) => aim,
|
||||
};
|
||||
|
||||
Strains {
|
||||
section_length: SECTION_LEN * mods.speed(),
|
||||
strains,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_star_rating(aim_rating: f64, speed_rating: f64, flashlight_rating: f64) -> f64 {
|
||||
@@ -125,49 +215,13 @@ fn calculate_star_rating(aim_rating: f64, speed_rating: f64, flashlight_rating:
|
||||
}
|
||||
}
|
||||
|
||||
/// Essentially the same as the [`stars`] function but instead of
|
||||
/// evaluating the final strains, it just returns them as is.
|
||||
///
|
||||
/// Suitable to plot the difficulty of a map over time.
|
||||
pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
||||
let (mut skills, _) = calculate_skills(map, mods, None);
|
||||
fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
let OsuStars {
|
||||
map,
|
||||
mods,
|
||||
passed_objects,
|
||||
} = params;
|
||||
|
||||
let mut aim = mem::take(&mut skills.aim().strain_peaks);
|
||||
let tuple = skills.speed_flashlight();
|
||||
|
||||
let strains = match tuple {
|
||||
(Some(speed), Some(flashlight)) => {
|
||||
for ((aim, speed), flashlight) in aim
|
||||
.iter_mut()
|
||||
.zip(&speed.strain_peaks)
|
||||
.zip(&flashlight.strain_peaks)
|
||||
{
|
||||
*aim += speed + flashlight;
|
||||
}
|
||||
|
||||
aim
|
||||
}
|
||||
(Some(strains), None) | (None, Some(strains)) => {
|
||||
for (aim, strain) in aim.iter_mut().zip(&strains.strain_peaks) {
|
||||
*aim += strain;
|
||||
}
|
||||
|
||||
aim
|
||||
}
|
||||
(None, None) => aim,
|
||||
};
|
||||
|
||||
Strains {
|
||||
section_length: SECTION_LEN * mods.speed(),
|
||||
strains,
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_skills(
|
||||
map: &Beatmap,
|
||||
mods: impl Mods,
|
||||
passed_objects: Option<usize>,
|
||||
) -> (Skills, OsuDifficultyAttributes) {
|
||||
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
|
||||
|
||||
let map_attributes = map.attributes().mods(mods);
|
||||
@@ -207,7 +261,7 @@ fn calculate_skills(
|
||||
.take(take)
|
||||
.filter_map(|h| OsuObject::new(h, hr, &mut params));
|
||||
|
||||
let mut hit_objects = Vec::with_capacity(take);
|
||||
let mut hit_objects = Vec::with_capacity(take.min(map.hit_objects.len()));
|
||||
hit_objects.extend(hit_objects_iter);
|
||||
|
||||
let stack_threshold = time_preempt * map.stack_leniency as f64;
|
||||
|
||||
+7
-5
@@ -1,5 +1,5 @@
|
||||
use super::{OsuDifficultyAttributes, OsuPerformanceAttributes, OsuScoreState};
|
||||
use crate::{Beatmap, DifficultyAttributes, Mods, PerformanceAttributes};
|
||||
use crate::{Beatmap, DifficultyAttributes, Mods, OsuStars, PerformanceAttributes};
|
||||
|
||||
/// Performance calculator on osu!standard maps.
|
||||
///
|
||||
@@ -312,10 +312,12 @@ impl<'map> OsuPP<'map> {
|
||||
|
||||
/// Calculate all performance related values, including pp and stars.
|
||||
pub fn calculate(mut self) -> OsuPerformanceAttributes {
|
||||
let attributes = self
|
||||
.attributes
|
||||
.take()
|
||||
.unwrap_or_else(|| super::stars(self.map, self.mods, self.passed_objects));
|
||||
let attributes = self.attributes.take().unwrap_or_else(|| {
|
||||
OsuStars::new(self.map)
|
||||
.mods(self.mods)
|
||||
.passed_objects(self.passed_objects.unwrap_or(usize::MAX))
|
||||
.calculate()
|
||||
});
|
||||
|
||||
self.assert_hitresults(attributes).calculate()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user