fixed attributes on very short maps + fixed gradual attributes offset

This commit is contained in:
MaxOhn
2021-11-23 01:54:56 +01:00
parent 9f8c557d03
commit 55f0c4bfe3
6 changed files with 89 additions and 83 deletions
+1
View File
@@ -11,6 +11,7 @@
- [BREAKING] Renamed the `attributes` field to `difficulty` for all `{Mode}PerformanceAttributes` structs
- Added `OsuGradualDifficultyAttributes`. Suitable to calculate a map's difficulty after every or every few objects instead of calling the `stars` function over and over.
- Added `OsuGradualPerformanceAttributes`. Suitable to calculate the performance on a map after every or every few objects instead of using `OsuPP` over and over.
- osu: Fixed incorrect attributes on maps with only 1 or 2 hit objects
# v0.3.0
+38 -21
View File
@@ -75,9 +75,16 @@ impl OsuGradualDifficultyAttributes {
let time_preempt = difficulty_range_ar(raw_ar);
let scaling_factor = ScalingFactor::new(map_attributes.cs);
let mut attributes = OsuDifficultyAttributes {
ar: map_attributes.ar,
hp: map_attributes.hp,
od,
..Default::default()
};
let mut params = ObjectParameters {
map,
max_combo: 0,
attributes: &mut attributes,
slider_state: SliderState::new(map),
ticks: Vec::new(),
curve_bufs: CurveBuffers::default(),
@@ -91,6 +98,11 @@ impl OsuGradualDifficultyAttributes {
let mut hit_objects = Vec::with_capacity(map.hit_objects.len());
hit_objects.extend(hit_objects_iter);
attributes.n_circles = 0;
attributes.n_sliders = 0;
attributes.n_spinners = 0;
attributes.max_combo = 0;
let stack_threshold = time_preempt * map.stack_leniency as f64;
if map.version >= 6 {
@@ -99,13 +111,6 @@ impl OsuGradualDifficultyAttributes {
old_stacking(&mut hit_objects, stack_threshold);
}
let attributes = OsuDifficultyAttributes {
ar: map_attributes.ar,
hp: map_attributes.hp,
od,
..Default::default()
};
let skills = Skills::new(hit_window, mods.rx(), scaling_factor.radius(), mods.fl());
let hit_objects = OsuObjectIter {
@@ -122,16 +127,13 @@ impl OsuGradualDifficultyAttributes {
kind: OsuObjectKind::Circle,
};
let curr_section_end =
(prev.time / map_attributes.clock_rate / SECTION_LEN).ceil() * SECTION_LEN;
Self {
idx: 0,
attributes,
clock_rate: map_attributes.clock_rate,
hit_objects,
skills,
curr_section_end,
curr_section_end: 0.0,
prev_prev,
prev,
strain_peak_buf: Vec::new(),
@@ -159,6 +161,8 @@ impl Iterator for OsuGradualDifficultyAttributes {
if self.idx == 1 {
self.prev = curr;
self.curr_section_end =
(self.prev.time / self.clock_rate / SECTION_LEN).ceil() * SECTION_LEN;
return Some(self.attributes.clone());
}
@@ -189,24 +193,29 @@ impl Iterator for OsuGradualDifficultyAttributes {
self.skills.process(&h);
self.prev_prev = Some(mem::replace(&mut self.prev, curr));
if self.hit_objects.len() == 0 {
self.skills.save_current_peak();
}
let missing = self.skills.aim().strain_peaks.len() - self.strain_peak_buf.len();
let missing = self.skills.aim().strain_peaks.len() + 1 - self.strain_peak_buf.len();
self.strain_peak_buf.extend(iter::repeat(0.0).take(missing));
let aim_rating = {
let aim = self.skills.aim();
self.strain_peak_buf.copy_from_slice(&aim.strain_peaks);
self.strain_peak_buf[..aim.strain_peaks.len()].copy_from_slice(&aim.strain_peaks);
if let Some(last) = self.strain_peak_buf.last_mut() {
*last = aim.curr_section_peak;
}
Skill::difficulty_value(&mut self.strain_peak_buf, aim).sqrt() * DIFFICULTY_MULTIPLIER
};
let slider_factor = if aim_rating > 0.0 {
let aim_no_sliders = self.skills.aim_no_sliders();
self.strain_peak_buf
self.strain_peak_buf[..aim_no_sliders.strain_peaks.len()]
.copy_from_slice(&aim_no_sliders.strain_peaks);
if let Some(last) = self.strain_peak_buf.last_mut() {
*last = aim_no_sliders.curr_section_peak;
}
let aim_rating_no_sliders =
Skill::difficulty_value(&mut self.strain_peak_buf, aim_no_sliders).sqrt()
* DIFFICULTY_MULTIPLIER;
@@ -219,7 +228,11 @@ impl Iterator for OsuGradualDifficultyAttributes {
let (speed, flashlight) = self.skills.speed_flashlight();
let speed_rating = if let Some(speed) = speed {
self.strain_peak_buf.copy_from_slice(&speed.strain_peaks);
self.strain_peak_buf[..speed.strain_peaks.len()].copy_from_slice(&speed.strain_peaks);
if let Some(last) = self.strain_peak_buf.last_mut() {
*last = speed.curr_section_peak;
}
Skill::difficulty_value(&mut self.strain_peak_buf, speed).sqrt() * DIFFICULTY_MULTIPLIER
} else {
@@ -227,9 +240,13 @@ impl Iterator for OsuGradualDifficultyAttributes {
};
let flashlight_rating = if let Some(flashlight) = flashlight {
self.strain_peak_buf
self.strain_peak_buf[..flashlight.strain_peaks.len()]
.copy_from_slice(&flashlight.strain_peaks);
if let Some(last) = self.strain_peak_buf.last_mut() {
*last = flashlight.curr_section_peak;
}
Skill::difficulty_value(&mut self.strain_peak_buf, flashlight).sqrt()
* DIFFICULTY_MULTIPLIER
} else {
+2 -3
View File
@@ -247,7 +247,6 @@ mod tests {
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
#[test]
#[ignore = "currently broken due to incorrect object counts when the map is not fully processed"]
fn gradual_eq_regular_passed() {
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
let mods = 64;
@@ -257,8 +256,8 @@ mod tests {
let mut gradual = OsuGradualPerformanceAttributes::new(&map, mods);
let state = OsuScoreState {
max_combo: 110,
n300: 102,
max_combo: 122,
n300: 100,
n100: 0,
n50: 0,
misses: 0,
+18 -39
View File
@@ -43,21 +43,7 @@ pub fn stars(
mods: impl Mods,
passed_objects: Option<usize>,
) -> OsuDifficultyAttributes {
let (mut skills, mut attributes) = match calculate_skills(map, mods, passed_objects) {
Some(tuple) => tuple,
None => {
let map_attributes = map.attributes().mods(mods);
let hit_window = difficulty_range_od(map_attributes.od) / map_attributes.clock_rate;
let od = (80.0 - hit_window) / 6.0;
return OsuDifficultyAttributes {
ar: map_attributes.ar,
hp: map_attributes.hp,
od,
..Default::default()
};
}
};
let (mut skills, mut attributes) = calculate_skills(map, mods, passed_objects);
let aim_rating = {
let aim = skills.aim();
@@ -142,10 +128,7 @@ fn calculate_star_rating(aim_rating: f64, speed_rating: f64, flashlight_rating:
///
/// Suitable to plot the difficulty of a map over time.
pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
let mut skills = match calculate_skills(map, mods, None) {
Some((skills, _)) => skills,
None => return Strains::default(),
};
let (mut skills, _) = calculate_skills(map, mods, None);
let mut aim = mem::take(&mut skills.aim().strain_peaks);
let tuple = skills.speed_flashlight();
@@ -182,17 +165,13 @@ fn calculate_skills(
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> Option<(Skills, OsuDifficultyAttributes)> {
) -> (Skills, OsuDifficultyAttributes) {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
let map_attributes = map.attributes().mods(mods);
let hit_window = difficulty_range_od(map_attributes.od) / map_attributes.clock_rate;
let od = (80.0 - hit_window) / 6.0;
if take < 2 {
return None;
}
let mut raw_ar = map.ar as f64;
let hr = mods.hr();
@@ -205,9 +184,16 @@ fn calculate_skills(
let time_preempt = difficulty_range_ar(raw_ar);
let scaling_factor = ScalingFactor::new(map_attributes.cs);
let mut attributes = OsuDifficultyAttributes {
ar: map_attributes.ar,
hp: map_attributes.hp,
od,
..Default::default()
};
let mut params = ObjectParameters {
map,
max_combo: 0,
attributes: &mut attributes,
slider_state: SliderState::new(map),
ticks: Vec::new(),
curve_bufs: CurveBuffers::default(),
@@ -239,15 +225,19 @@ fn calculate_skills(
let mut skills = Skills::new(hit_window, mods.rx(), scaling_factor.radius(), mods.fl());
let (mut prev, curr) = match (hit_objects.next(), hit_objects.next()) {
(Some(prev), Some(curr)) => (prev, curr),
(Some(_), None) | (None, None) => return (skills, attributes),
(None, Some(_)) => unreachable!(),
};
let mut prev_prev = None;
let mut prev = hit_objects.next().unwrap();
// First object has no predecessor and thus no strain, handle distinctly
let mut curr_section_end =
(prev.time / map_attributes.clock_rate / SECTION_LEN).ceil() * SECTION_LEN;
// Handle second object separately to remove later if-branching
let curr = hit_objects.next().unwrap();
let h = DifficultyObject::new(
&curr,
&mut prev,
@@ -289,18 +279,7 @@ fn calculate_skills(
skills.save_current_peak();
let attributes = OsuDifficultyAttributes {
ar: map_attributes.ar,
hp: map_attributes.hp,
od,
n_circles: map.n_circles as usize,
n_sliders: map.n_sliders as usize,
n_spinners: map.n_spinners as usize,
max_combo: params.max_combo,
..Default::default()
};
Some((skills, attributes))
(skills, attributes)
}
fn stacking(hit_objects: &mut [OsuObject], stack_threshold: f64) {
+29 -19
View File
@@ -1,6 +1,6 @@
use std::{cmp::Ordering, convert::identity};
use super::slider_state::SliderState;
use super::{slider_state::SliderState, OsuDifficultyAttributes};
use crate::{
curve::{Curve, CurveBuffers},
@@ -49,7 +49,7 @@ pub(crate) enum NestedObjectKind {
pub(crate) struct ObjectParameters<'a> {
pub(crate) map: &'a Beatmap,
pub(crate) max_combo: usize,
pub(crate) attributes: &'a mut OsuDifficultyAttributes,
pub(crate) ticks: Vec<(Pos2, f64)>,
pub(crate) slider_state: SliderState<'a>,
pub(crate) curve_bufs: CurveBuffers,
@@ -60,13 +60,13 @@ impl OsuObject {
pub(crate) fn new(h: &HitObject, hr: bool, params: &mut ObjectParameters<'_>) -> Option<Self> {
let ObjectParameters {
map,
max_combo,
attributes,
ticks,
slider_state,
curve_bufs,
} = params;
*max_combo += 1; // hitcircle, slider head, or spinner
attributes.max_combo += 1; // hitcircle, slider head, or spinner
let mut pos = h.pos;
if hr {
@@ -74,17 +74,23 @@ impl OsuObject {
}
let obj = match &h.kind {
HitObjectKind::Circle => Self {
time: h.start_time,
pos,
stack_height: 0.0,
kind: OsuObjectKind::Circle,
},
HitObjectKind::Circle => {
attributes.n_circles += 1;
Self {
time: h.start_time,
pos,
stack_height: 0.0,
kind: OsuObjectKind::Circle,
}
}
HitObjectKind::Slider {
pixel_len,
repeats,
control_points,
} => {
attributes.n_sliders += 1;
// Responsible for timing point values
slider_state.update(h.start_time);
@@ -248,7 +254,7 @@ impl OsuObject {
_ => nested_objects.push(legacy_last_tick),
};
*max_combo += nested_objects.len();
attributes.max_combo += nested_objects.len();
let lazy_travel_time = final_span_end_time - h.start_time;
let mut end_time_min = lazy_travel_time / span_duration;
@@ -278,14 +284,18 @@ impl OsuObject {
},
}
}
HitObjectKind::Spinner { end_time } => Self {
time: h.start_time,
pos,
stack_height: 0.0,
kind: OsuObjectKind::Spinner {
end_time: *end_time,
},
},
HitObjectKind::Spinner { end_time } => {
attributes.n_spinners += 1;
Self {
time: h.start_time,
pos,
stack_height: 0.0,
kind: OsuObjectKind::Spinner {
end_time: *end_time,
},
}
}
HitObjectKind::Hold { .. } => return None,
};
+1 -1
View File
@@ -90,7 +90,7 @@ impl Skills {
#[derive(Clone)]
pub(crate) struct Skill {
curr_strain: f64,
curr_section_peak: f64,
pub(crate) curr_section_peak: f64,
kind: SkillKind,
pub(crate) strain_peaks: Vec<f64>,