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