added OsuGradualPerformanceAttributes
This commit is contained in:
+2
-1
@@ -9,7 +9,8 @@
|
|||||||
- Added method `Beatmap::bpm`
|
- Added method `Beatmap::bpm`
|
||||||
- Added method `max_combo` for `DifficultyAttributes`, `PerformanceAttributes`, and all `{Mode}PerformanceAttributes`
|
- Added method `max_combo` for `DifficultyAttributes`, `PerformanceAttributes`, and all `{Mode}PerformanceAttributes`
|
||||||
- [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 `OsuDifficultyAttributesIter`. 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.
|
||||||
|
|
||||||
# v0.3.0
|
# v0.3.0
|
||||||
|
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ use super::{
|
|||||||
stacking, OsuDifficultyAttributes, DIFFICULTY_MULTIPLIER, SECTION_LEN,
|
stacking, OsuDifficultyAttributes, DIFFICULTY_MULTIPLIER, SECTION_LEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Iterate over a map's hit objects and update the difficulty attributes each time.
|
/// Gradually calculate the difficulty attributes of an osu!standard map.
|
||||||
///
|
///
|
||||||
/// Note that this struct implements [`Iterator`](std::iter::Iterator).
|
/// Note that this struct implements [`Iterator`](std::iter::Iterator).
|
||||||
/// On every call of [`Iterator::next`](std::iter::Iterator::next), the map's next hit object will
|
/// On every call of [`Iterator::next`](std::iter::Iterator::next), the map's next hit object will
|
||||||
/// be processed and the [`OsuDifficultyAttributes`](`crate::osu::OsuDifficultyAttributes`)
|
/// be processed and the [`OsuDifficultyAttributes`] will be updated and returned.
|
||||||
/// will be updated and returned.
|
|
||||||
///
|
///
|
||||||
/// TODO: Mention struct that does the same for performance
|
/// If you want to calculate performance attributes, use
|
||||||
|
/// [`OsuGradualPerformanceAttributes`](crate::osu::OsuGradualPerformanceAttributes) instead.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use rosu_pp::{Beatmap, osu::OsuDifficultyAttributesIter};
|
/// use rosu_pp::{Beatmap, osu::OsuGradualDifficultyAttributes};
|
||||||
///
|
///
|
||||||
/// # /*
|
/// # /*
|
||||||
/// let map: Beatmap = ...
|
/// let map: Beatmap = ...
|
||||||
@@ -33,7 +33,7 @@ use super::{
|
|||||||
/// # let map = Beatmap::default();
|
/// # let map = Beatmap::default();
|
||||||
///
|
///
|
||||||
/// let mods = 64; // DT
|
/// let mods = 64; // DT
|
||||||
/// let mut iter = OsuDifficultyAttributesIter::new(&map, mods);
|
/// let mut iter = OsuGradualDifficultyAttributes::new(&map, mods);
|
||||||
///
|
///
|
||||||
/// let attrs1 = iter.next(); // the difficulty of the map after the first hit object
|
/// let attrs1 = iter.next(); // the difficulty of the map after the first hit object
|
||||||
/// let attrs2 = iter.next(); // after the second hit object
|
/// let attrs2 = iter.next(); // after the second hit object
|
||||||
@@ -43,9 +43,9 @@ use super::{
|
|||||||
/// // ...
|
/// // ...
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OsuDifficultyAttributesIter {
|
pub struct OsuGradualDifficultyAttributes {
|
||||||
idx: usize,
|
pub(crate) idx: usize,
|
||||||
attributes: OsuDifficultyAttributes,
|
attributes: OsuDifficultyAttributes,
|
||||||
clock_rate: f64,
|
clock_rate: f64,
|
||||||
hit_objects: OsuObjectIter,
|
hit_objects: OsuObjectIter,
|
||||||
@@ -56,7 +56,7 @@ pub struct OsuDifficultyAttributesIter {
|
|||||||
strain_peak_buf: Vec<f64>,
|
strain_peak_buf: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsuDifficultyAttributesIter {
|
impl OsuGradualDifficultyAttributes {
|
||||||
/// Create a new difficulty attributes iterator for osu!standard maps.
|
/// Create a new difficulty attributes iterator for osu!standard maps.
|
||||||
pub fn new(map: &Beatmap, mods: impl Mods) -> Self {
|
pub fn new(map: &Beatmap, mods: impl Mods) -> Self {
|
||||||
let map_attributes = map.attributes().mods(mods);
|
let map_attributes = map.attributes().mods(mods);
|
||||||
@@ -139,7 +139,7 @@ impl OsuDifficultyAttributesIter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for OsuDifficultyAttributesIter {
|
impl Iterator for OsuGradualDifficultyAttributes {
|
||||||
type Item = OsuDifficultyAttributes;
|
type Item = OsuDifficultyAttributes;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
@@ -253,14 +253,14 @@ impl Iterator for OsuDifficultyAttributesIter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExactSizeIterator for OsuDifficultyAttributesIter {
|
impl ExactSizeIterator for OsuGradualDifficultyAttributes {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.hit_objects.len()
|
self.hit_objects.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct OsuObjectIter {
|
struct OsuObjectIter {
|
||||||
hit_objects: IntoIter<OsuObject>,
|
hit_objects: IntoIter<OsuObject>,
|
||||||
scaling_factor: ScalingFactor,
|
scaling_factor: ScalingFactor,
|
||||||
@@ -298,7 +298,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn empty_map() {
|
fn empty_map() {
|
||||||
let map = Beatmap::default();
|
let map = Beatmap::default();
|
||||||
assert!(OsuDifficultyAttributesIter::new(&map, 0).next().is_none());
|
assert!(OsuGradualDifficultyAttributes::new(&map, 0)
|
||||||
|
.next()
|
||||||
|
.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||||
@@ -308,8 +310,8 @@ mod tests {
|
|||||||
let mods = 64;
|
let mods = 64;
|
||||||
let regular = crate::osu::stars(&map, mods, None);
|
let regular = crate::osu::stars(&map, mods, None);
|
||||||
|
|
||||||
let iter_end = OsuDifficultyAttributesIter::new(&map, mods)
|
let iter_end = OsuGradualDifficultyAttributes::new(&map, mods)
|
||||||
.reduce(|_, next| next)
|
.last()
|
||||||
.expect("empty iter");
|
.expect("empty iter");
|
||||||
|
|
||||||
assert_eq!(regular, iter_end);
|
assert_eq!(regular, iter_end);
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
use crate::{Beatmap, OsuPP};
|
||||||
|
|
||||||
|
use super::{OsuGradualDifficultyAttributes, OsuPerformanceAttributes};
|
||||||
|
|
||||||
|
// TODO: Benchmark if Copy is faster than Clone
|
||||||
|
/// Aggregation for a score's current state i.e. what was the
|
||||||
|
/// maximum combo so far and what are the current hitresults.
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct OsuScoreState {
|
||||||
|
/// Maximum combo that the score has had so far.
|
||||||
|
/// **Not** the maximum possible combo of the map so far.
|
||||||
|
pub max_combo: usize,
|
||||||
|
/// Amount of current 300s.
|
||||||
|
pub n300: usize,
|
||||||
|
/// Amount of current 100s.
|
||||||
|
pub n100: usize,
|
||||||
|
/// Amount of current 50s.
|
||||||
|
pub n50: usize,
|
||||||
|
/// Amount of current misses.
|
||||||
|
pub misses: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OsuScoreState {
|
||||||
|
/// Create a new empty score state.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gradually calculate the performance attributes of an osu!standard map.
|
||||||
|
///
|
||||||
|
/// After each hit object you can call
|
||||||
|
/// [`process_next_object`](`OsuGradualPerformanceAttributes::process_next_object`)
|
||||||
|
/// and it will return the resulting current [`OsuPerformanceAttributes`].
|
||||||
|
/// To process multiple objects at once, use
|
||||||
|
/// [`process_next_n_objects`](`OsuGradualPerformanceAttributes::process_next_n_objects`) instead.
|
||||||
|
///
|
||||||
|
/// Both methods require an [`OsuScoreState`] that contains the current
|
||||||
|
/// hitresults as well as the maximum combo so far.
|
||||||
|
///
|
||||||
|
/// If you only want to calculate difficulty attributes use
|
||||||
|
/// [`OsuGradualDifficultyAttributes`](crate::osu::OsuGradualDifficultyAttributes) instead.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosu_pp::{Beatmap, osu::{OsuGradualPerformanceAttributes, OsuScoreState}};
|
||||||
|
///
|
||||||
|
/// # /*
|
||||||
|
/// let map: Beatmap = ...
|
||||||
|
/// # */
|
||||||
|
/// # let map = Beatmap::default();
|
||||||
|
///
|
||||||
|
/// let mods = 64; // DT
|
||||||
|
/// let mut gradual_perf = OsuGradualPerformanceAttributes::new(&map, mods);
|
||||||
|
/// let mut state = OsuScoreState::new(); // empty state, everything is on 0.
|
||||||
|
///
|
||||||
|
/// // The first 10 hitresults are 300s and there are no sliders for additional combo
|
||||||
|
/// for _ in 0..10 {
|
||||||
|
/// state.n300 += 1;
|
||||||
|
/// state.max_combo += 1;
|
||||||
|
///
|
||||||
|
/// # /*
|
||||||
|
/// let performance = gradual_perf.process_next_object(state).unwrap();
|
||||||
|
/// println!("PP: {}", performance.pp);
|
||||||
|
/// # */
|
||||||
|
/// # let _ = gradual_perf.process_next_object(state);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Then comes a miss.
|
||||||
|
/// // Note that state's max combo won't be incremented for
|
||||||
|
/// // the next few objects because the combo is reset.
|
||||||
|
/// state.misses += 1;
|
||||||
|
/// # /*
|
||||||
|
/// let performance = gradual_perf.process_next_object(state).unwrap();
|
||||||
|
/// println!("PP: {}", performance.pp);
|
||||||
|
/// # */
|
||||||
|
/// # let _ = gradual_perf.process_next_object(state);
|
||||||
|
///
|
||||||
|
/// // The next 10 objects will be a mixture of 300s, 100s, and 50s.
|
||||||
|
/// // Notice how all 10 objects will be processed in one go.
|
||||||
|
/// state.n300 += 2;
|
||||||
|
/// state.n100 += 7;
|
||||||
|
/// state.n50 += 1;
|
||||||
|
/// # /*
|
||||||
|
/// let performance = gradual_perf.process_next_n_objects(state, 10).unwrap();
|
||||||
|
/// println!("PP: {}", performance.pp);
|
||||||
|
/// # */
|
||||||
|
/// # let _ = gradual_perf.process_next_n_objects(state, 10);
|
||||||
|
///
|
||||||
|
/// // Now comes another 300. Note that the max combo gets incremented again.
|
||||||
|
/// state.n300 += 1;
|
||||||
|
/// state.max_combo += 1;
|
||||||
|
/// # /*
|
||||||
|
/// let performance = gradual_perf.process_next_object(state).unwrap();
|
||||||
|
/// println!("PP: {}", performance.pp);
|
||||||
|
/// # */
|
||||||
|
/// # let _ = gradual_perf.process_next_object(state);
|
||||||
|
///
|
||||||
|
/// // Skip to the end
|
||||||
|
/// # /*
|
||||||
|
/// state.max_combo = ...
|
||||||
|
/// state.n300 = ...
|
||||||
|
/// state.n100 = ...
|
||||||
|
/// state.n50 = ...
|
||||||
|
/// state.misses = ...
|
||||||
|
/// let final_performance = gradual_perf.process_next_n_objects(state, usize::MAX).unwrap();
|
||||||
|
/// println!("PP: {}", performance.pp);
|
||||||
|
/// # */
|
||||||
|
/// # let _ = gradual_perf.process_next_n_objects(state, usize::MAX);
|
||||||
|
///
|
||||||
|
/// // Once the final performance was calculated,
|
||||||
|
/// // attempting to process further objects will return `None`.
|
||||||
|
/// assert!(gradual_perf.process_next_object(state).is_none());
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct OsuGradualPerformanceAttributes<'map> {
|
||||||
|
difficulty: OsuGradualDifficultyAttributes,
|
||||||
|
performance: OsuPP<'map>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'map> OsuGradualPerformanceAttributes<'map> {
|
||||||
|
/// Create a new gradual performance calculator for osu!standard maps.
|
||||||
|
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
|
||||||
|
let difficulty = OsuGradualDifficultyAttributes::new(map, mods);
|
||||||
|
let performance = OsuPP::new(map).mods(mods).passed_objects(0);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
difficulty,
|
||||||
|
performance,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process the next hit object and calculate the
|
||||||
|
/// performance attributes for the resulting score state.
|
||||||
|
pub fn process_next_object(
|
||||||
|
&mut self,
|
||||||
|
state: OsuScoreState,
|
||||||
|
) -> Option<OsuPerformanceAttributes> {
|
||||||
|
self.process_next_n_objects(state, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as [`process_next_object`](`OsuGradualPerformanceAttributes::process_next_object`)
|
||||||
|
/// but instead of processing only one object it process `n` many.
|
||||||
|
///
|
||||||
|
/// If `n` is 0 it will be considered as 1.
|
||||||
|
/// If there are still objects to be processed but `n` is larger than the amount
|
||||||
|
/// of remaining objects, `n` will be considered as the amount of remaining objects.
|
||||||
|
pub fn process_next_n_objects(
|
||||||
|
&mut self,
|
||||||
|
state: OsuScoreState,
|
||||||
|
n: usize,
|
||||||
|
) -> Option<OsuPerformanceAttributes> {
|
||||||
|
let n = n.min(self.difficulty.len()).saturating_sub(1);
|
||||||
|
let difficulty = self.difficulty.nth(n)?;
|
||||||
|
|
||||||
|
let _ = self.performance.n300.insert(state.n300);
|
||||||
|
let _ = self.performance.n100.insert(state.n100);
|
||||||
|
let _ = self.performance.n50.insert(state.n50);
|
||||||
|
self.performance.n_misses = state.misses;
|
||||||
|
|
||||||
|
let performance = self
|
||||||
|
.performance
|
||||||
|
.clone()
|
||||||
|
.attributes(difficulty)
|
||||||
|
.combo(state.max_combo)
|
||||||
|
.passed_objects(self.difficulty.idx)
|
||||||
|
.calculate();
|
||||||
|
|
||||||
|
Some(performance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||||
|
#[test]
|
||||||
|
fn correct_empty() {
|
||||||
|
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
|
||||||
|
let mods = 64;
|
||||||
|
|
||||||
|
let mut gradual = OsuGradualPerformanceAttributes::new(&map, mods);
|
||||||
|
let state = OsuScoreState::default();
|
||||||
|
|
||||||
|
assert!(gradual.process_next_n_objects(state, usize::MAX).is_some());
|
||||||
|
assert!(gradual.process_next_object(state).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||||
|
#[test]
|
||||||
|
fn next_and_next_n() {
|
||||||
|
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
|
||||||
|
let mods = 64;
|
||||||
|
let state = OsuScoreState::default();
|
||||||
|
|
||||||
|
let mut gradual1 = OsuGradualPerformanceAttributes::new(&map, mods);
|
||||||
|
let mut gradual2 = OsuGradualPerformanceAttributes::new(&map, mods);
|
||||||
|
|
||||||
|
for _ in 0..20 {
|
||||||
|
let _ = gradual1.process_next_object(state);
|
||||||
|
let _ = gradual2.process_next_object(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = 80;
|
||||||
|
|
||||||
|
for _ in 1..n {
|
||||||
|
let _ = gradual1.process_next_object(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = OsuScoreState {
|
||||||
|
max_combo: 110,
|
||||||
|
n300: 90,
|
||||||
|
n100: 8,
|
||||||
|
n50: 2,
|
||||||
|
misses: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let next = gradual1.process_next_object(state);
|
||||||
|
let next_n = gradual2.process_next_n_objects(state, n);
|
||||||
|
|
||||||
|
assert_eq!(next_n, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||||
|
#[test]
|
||||||
|
fn gradual_end_eq_regular() {
|
||||||
|
let map = Beatmap::from_path("./maps/2785319.osu").expect("failed to parse map");
|
||||||
|
let mods = 64;
|
||||||
|
let regular = OsuPP::new(&map).mods(mods).calculate();
|
||||||
|
|
||||||
|
let mut gradual = OsuGradualPerformanceAttributes::new(&map, mods);
|
||||||
|
|
||||||
|
let state = OsuScoreState {
|
||||||
|
max_combo: 909,
|
||||||
|
n300: 601,
|
||||||
|
n100: 0,
|
||||||
|
n50: 0,
|
||||||
|
misses: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(regular, gradual_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
let n = 100;
|
||||||
|
let regular = OsuPP::new(&map).mods(mods).passed_objects(n).calculate();
|
||||||
|
|
||||||
|
let mut gradual = OsuGradualPerformanceAttributes::new(&map, mods);
|
||||||
|
|
||||||
|
let state = OsuScoreState {
|
||||||
|
max_combo: 110,
|
||||||
|
n300: 102,
|
||||||
|
n100: 0,
|
||||||
|
n50: 0,
|
||||||
|
misses: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(regular, gradual);
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-3
@@ -1,7 +1,8 @@
|
|||||||
#![cfg(feature = "osu")]
|
#![cfg(feature = "osu")]
|
||||||
|
|
||||||
mod difficulty_iter;
|
|
||||||
mod difficulty_object;
|
mod difficulty_object;
|
||||||
|
mod gradual_difficulty;
|
||||||
|
mod gradual_performance;
|
||||||
mod osu_object;
|
mod osu_object;
|
||||||
mod pp;
|
mod pp;
|
||||||
mod scaling_factor;
|
mod scaling_factor;
|
||||||
@@ -11,8 +12,9 @@ mod slider_state;
|
|||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
pub use difficulty_iter::OsuDifficultyAttributesIter;
|
|
||||||
use difficulty_object::DifficultyObject;
|
use difficulty_object::DifficultyObject;
|
||||||
|
pub use gradual_difficulty::*;
|
||||||
|
pub use gradual_performance::*;
|
||||||
use osu_object::{ObjectParameters, OsuObject};
|
use osu_object::{ObjectParameters, OsuObject};
|
||||||
pub use pp::*;
|
pub use pp::*;
|
||||||
use scaling_factor::ScalingFactor;
|
use scaling_factor::ScalingFactor;
|
||||||
@@ -32,6 +34,10 @@ const STACK_DISTANCE: f32 = 3.0;
|
|||||||
/// Difficulty calculation for osu!standard maps.
|
/// Difficulty calculation for osu!standard maps.
|
||||||
///
|
///
|
||||||
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
|
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
|
||||||
|
///
|
||||||
|
/// 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(
|
pub fn stars(
|
||||||
map: &Beatmap,
|
map: &Beatmap,
|
||||||
mods: impl Mods,
|
mods: impl Mods,
|
||||||
@@ -475,7 +481,7 @@ pub struct OsuDifficultyAttributes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a performance calculation on an osu!standard map.
|
/// The result of a performance calculation on an osu!standard map.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct OsuPerformanceAttributes {
|
pub struct OsuPerformanceAttributes {
|
||||||
/// The difficulty attributes that were used for the performance calculation
|
/// The difficulty attributes that were used for the performance calculation
|
||||||
pub difficulty: OsuDifficultyAttributes,
|
pub difficulty: OsuDifficultyAttributes,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
const LEGACY_LAST_TICK_OFFSET: f64 = 36.0;
|
const LEGACY_LAST_TICK_OFFSET: f64 = 36.0;
|
||||||
const BASE_SCORING_DISTANCE: f64 = 100.0;
|
const BASE_SCORING_DISTANCE: f64 = 100.0;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct OsuObject {
|
pub(crate) struct OsuObject {
|
||||||
pub(crate) time: f64,
|
pub(crate) time: f64,
|
||||||
pub(crate) pos: Pos2,
|
pub(crate) pos: Pos2,
|
||||||
@@ -19,7 +19,7 @@ pub(crate) struct OsuObject {
|
|||||||
pub(crate) kind: OsuObjectKind,
|
pub(crate) kind: OsuObjectKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) enum OsuObjectKind {
|
pub(crate) enum OsuObjectKind {
|
||||||
Circle,
|
Circle,
|
||||||
Slider {
|
Slider {
|
||||||
@@ -33,7 +33,7 @@ pub(crate) enum OsuObjectKind {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct NestedObject {
|
pub(crate) struct NestedObject {
|
||||||
pub(crate) pos: Pos2,
|
pub(crate) pos: Pos2,
|
||||||
pub(crate) time: f64,
|
pub(crate) time: f64,
|
||||||
|
|||||||
+18
-9
@@ -36,14 +36,14 @@ pub struct OsuPP<'map> {
|
|||||||
map: &'map Beatmap,
|
map: &'map Beatmap,
|
||||||
attributes: Option<OsuDifficultyAttributes>,
|
attributes: Option<OsuDifficultyAttributes>,
|
||||||
mods: u32,
|
mods: u32,
|
||||||
combo: Option<usize>,
|
|
||||||
acc: Option<f64>,
|
acc: Option<f64>,
|
||||||
|
pub(crate) combo: Option<usize>,
|
||||||
|
|
||||||
n300: Option<usize>,
|
pub(crate) n300: Option<usize>,
|
||||||
n100: Option<usize>,
|
pub(crate) n100: Option<usize>,
|
||||||
n50: Option<usize>,
|
pub(crate) n50: Option<usize>,
|
||||||
n_misses: usize,
|
pub(crate) n_misses: usize,
|
||||||
passed_objects: Option<usize>,
|
pub(crate) passed_objects: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'map> OsuPP<'map> {
|
impl<'map> OsuPP<'map> {
|
||||||
@@ -54,8 +54,8 @@ impl<'map> OsuPP<'map> {
|
|||||||
map,
|
map,
|
||||||
attributes: None,
|
attributes: None,
|
||||||
mods: 0,
|
mods: 0,
|
||||||
combo: None,
|
|
||||||
acc: None,
|
acc: None,
|
||||||
|
combo: None,
|
||||||
|
|
||||||
n300: None,
|
n300: None,
|
||||||
n100: None,
|
n100: None,
|
||||||
@@ -128,6 +128,10 @@ impl<'map> OsuPP<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
|
///
|
||||||
|
/// If you want to calculate the performance after every few objects, instead of
|
||||||
|
/// using [`OsuPP`] multiple times with different `passed_objects`, you should use
|
||||||
|
/// [`OsuGradualPerformanceAttributes`](crate::osu::OsuGradualDifficultyAttributes).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
pub fn passed_objects(mut self, passed_objects: usize) -> Self {
|
||||||
self.passed_objects.replace(passed_objects);
|
self.passed_objects.replace(passed_objects);
|
||||||
@@ -260,7 +264,12 @@ impl<'map> OsuPP<'map> {
|
|||||||
let n50 = n50.unwrap_or(0);
|
let n50 = n50.unwrap_or(0);
|
||||||
|
|
||||||
let numerator = n300 * 6 + n100 * 2 + n50;
|
let numerator = n300 * 6 + n100 * 2 + n50;
|
||||||
let acc = numerator as f64 / n_objects as f64 / 6.0;
|
|
||||||
|
let acc = if n_objects > 0 {
|
||||||
|
numerator as f64 / n_objects as f64 / 6.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
let total_hits = (n300 + n100 + n50 + self.n_misses).min(n_objects) as f64;
|
let total_hits = (n300 + n100 + n50 + self.n_misses).min(n_objects) as f64;
|
||||||
|
|
||||||
@@ -295,8 +304,8 @@ impl<'map> OsuPP<'map> {
|
|||||||
struct OsuPPInner {
|
struct OsuPPInner {
|
||||||
attributes: OsuDifficultyAttributes,
|
attributes: OsuDifficultyAttributes,
|
||||||
mods: u32,
|
mods: u32,
|
||||||
combo: Option<usize>,
|
|
||||||
acc: f64,
|
acc: f64,
|
||||||
|
combo: Option<usize>,
|
||||||
|
|
||||||
n300: usize,
|
n300: usize,
|
||||||
n100: usize,
|
n100: usize,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use super::NORMALIZED_RADIUS;
|
|||||||
|
|
||||||
const OBJECT_RADIUS: f32 = 64.0;
|
const OBJECT_RADIUS: f32 = 64.0;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub(crate) struct ScalingFactor {
|
pub(crate) struct ScalingFactor {
|
||||||
adjusted_factor: f32,
|
adjusted_factor: f32,
|
||||||
factor: f32,
|
factor: f32,
|
||||||
|
|||||||
+2
-1
@@ -4,7 +4,7 @@ use std::{cmp::Ordering, fmt};
|
|||||||
|
|
||||||
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
|
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Skills {
|
pub(crate) struct Skills {
|
||||||
skills: Box<[Skill]>,
|
skills: Box<[Skill]>,
|
||||||
mask: u8,
|
mask: u8,
|
||||||
@@ -87,6 +87,7 @@ impl Skills {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) struct Skill {
|
pub(crate) struct Skill {
|
||||||
curr_strain: f64,
|
curr_strain: f64,
|
||||||
curr_section_peak: f64,
|
curr_section_peak: f64,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const FLASHLIGHT_REDUCED_SECTION_COUNT: usize = 10;
|
|||||||
|
|
||||||
const FLASHLIGHT_HISTORY_LENGTH: usize = 10;
|
const FLASHLIGHT_HISTORY_LENGTH: usize = 10;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) struct AimHistoryEntry {
|
pub(crate) struct AimHistoryEntry {
|
||||||
angle: Option<f64>,
|
angle: Option<f64>,
|
||||||
is_slider: bool,
|
is_slider: bool,
|
||||||
@@ -71,6 +72,7 @@ impl From<&DifficultyObject<'_>> for AimHistoryEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) struct FlashlightHistoryEntry {
|
pub(crate) struct FlashlightHistoryEntry {
|
||||||
end_pos: Pos2,
|
end_pos: Pos2,
|
||||||
is_spinner: bool,
|
is_spinner: bool,
|
||||||
@@ -89,7 +91,7 @@ impl From<&DifficultyObject<'_>> for FlashlightHistoryEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct SpeedHistoryEntry {
|
pub(crate) struct SpeedHistoryEntry {
|
||||||
is_slider: bool,
|
is_slider: bool,
|
||||||
start_time: f64,
|
start_time: f64,
|
||||||
@@ -106,6 +108,7 @@ impl From<&DifficultyObject<'_>> for SpeedHistoryEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub(crate) enum SkillKind {
|
pub(crate) enum SkillKind {
|
||||||
Aim {
|
Aim {
|
||||||
history: VecDeque<AimHistoryEntry>,
|
history: VecDeque<AimHistoryEntry>,
|
||||||
|
|||||||
Reference in New Issue
Block a user