finished osu std + bunch of bug fixes
This commit is contained in:
@@ -1,27 +1,30 @@
|
||||
use super::mania_object::ManiaObject;
|
||||
|
||||
pub(crate) struct ManiaDifficultyObject<'h> {
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ManiaDifficultyObject {
|
||||
pub(crate) idx: usize,
|
||||
pub(crate) base: ManiaObject<'h>,
|
||||
pub(crate) base_column: usize,
|
||||
pub(crate) delta_time: f64,
|
||||
pub(crate) start_time: f64,
|
||||
pub(crate) end_time: f64,
|
||||
}
|
||||
|
||||
impl<'h> ManiaDifficultyObject<'h> {
|
||||
impl ManiaDifficultyObject {
|
||||
pub(crate) fn new(
|
||||
base: ManiaObject<'h>,
|
||||
last: ManiaObject<'h>,
|
||||
base: ManiaObject<'_>,
|
||||
last: ManiaObject<'_>,
|
||||
clock_rate: f64,
|
||||
total_columns: f32,
|
||||
idx: usize,
|
||||
) -> Self {
|
||||
let delta_time = (base.start_time() - last.start_time()) / clock_rate;
|
||||
let start_time = base.start_time() / clock_rate;
|
||||
let end_time = base.end_time() / clock_rate;
|
||||
let base_column = base.column(total_columns);
|
||||
|
||||
Self {
|
||||
idx,
|
||||
base,
|
||||
base_column,
|
||||
delta_time,
|
||||
start_time,
|
||||
end_time,
|
||||
|
||||
+47
-103
@@ -1,16 +1,11 @@
|
||||
use std::{
|
||||
iter::{self, Skip, Zip},
|
||||
slice::Iter,
|
||||
};
|
||||
use crate::{beatmap::BeatmapHitWindows, Beatmap, Mods};
|
||||
|
||||
use crate::{
|
||||
mania::{strain::Strain, SECTION_LEN},
|
||||
parse::HitObject,
|
||||
Beatmap, Mods,
|
||||
use super::{
|
||||
difficulty_object::ManiaDifficultyObject,
|
||||
skills::{Skill, Strain},
|
||||
ManiaDifficultyAttributes, ManiaObject, STAR_SCALING_FACTOR,
|
||||
};
|
||||
|
||||
use super::{DifficultyHitObject, ManiaDifficultyAttributes, STAR_SCALING_FACTOR};
|
||||
|
||||
/// Gradually calculate the difficulty attributes of an osu!mania map.
|
||||
///
|
||||
/// Note that this struct implements [`Iterator`](std::iter::Iterator).
|
||||
@@ -42,70 +37,65 @@ use super::{DifficultyHitObject, ManiaDifficultyAttributes, STAR_SCALING_FACTOR}
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ManiaGradualDifficultyAttributes<'map> {
|
||||
pub struct ManiaGradualDifficultyAttributes {
|
||||
pub(crate) idx: usize,
|
||||
difficulty_objects: ManiaObjectIter<'map>,
|
||||
hit_window: f64,
|
||||
strain: Strain,
|
||||
curr_section_end: f64,
|
||||
strain_peak_buf: Vec<f64>,
|
||||
diff_objects: Vec<ManiaDifficultyObject>,
|
||||
}
|
||||
|
||||
impl<'map> ManiaGradualDifficultyAttributes<'map> {
|
||||
impl ManiaGradualDifficultyAttributes {
|
||||
/// Create a new difficulty attributes iterator for osu!mania maps.
|
||||
pub fn new(map: &'map Beatmap, mods: impl Mods) -> Self {
|
||||
let columns = map.cs.round().max(1.0) as u8;
|
||||
pub fn new(map: &Beatmap, mods: u32) -> Self {
|
||||
let total_columns = map.cs.round().max(1.0);
|
||||
|
||||
let clock_rate = mods.clock_rate();
|
||||
let strain = Strain::new(columns);
|
||||
let columns = columns as f32;
|
||||
let difficulty_objects = ManiaObjectIter::new(&map.hit_objects, columns, clock_rate);
|
||||
let strain = Strain::new(total_columns as usize);
|
||||
|
||||
let BeatmapHitWindows { od: hit_window, .. } = map
|
||||
.attributes()
|
||||
.mods(mods)
|
||||
// TODO: allow converts
|
||||
// .converted(is_convert)
|
||||
.clock_rate(clock_rate)
|
||||
.hit_windows();
|
||||
|
||||
let diff_objects_iter = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(ManiaObject::new)
|
||||
.enumerate()
|
||||
.zip(map.hit_objects.iter().map(ManiaObject::new))
|
||||
.map(|((i, base), prev)| {
|
||||
ManiaDifficultyObject::new(base, prev, clock_rate, total_columns, i)
|
||||
});
|
||||
|
||||
let mut diff_objects = Vec::with_capacity(map.hit_objects.len().saturating_sub(1));
|
||||
diff_objects.extend(diff_objects_iter);
|
||||
|
||||
Self {
|
||||
idx: 0,
|
||||
difficulty_objects,
|
||||
hit_window,
|
||||
strain,
|
||||
curr_section_end: 0.0,
|
||||
strain_peak_buf: Vec::new(),
|
||||
diff_objects,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ManiaGradualDifficultyAttributes<'_> {
|
||||
impl Iterator for ManiaGradualDifficultyAttributes {
|
||||
type Item = ManiaDifficultyAttributes;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.idx = self.idx.saturating_add(1);
|
||||
let curr = self.diff_objects.get(self.idx)?;
|
||||
self.idx += 1;
|
||||
|
||||
if self.idx == 1 {
|
||||
return (!self.difficulty_objects.is_empty).then(ManiaDifficultyAttributes::default);
|
||||
}
|
||||
self.strain.process(curr, &self.diff_objects);
|
||||
|
||||
let h = self.difficulty_objects.next()?;
|
||||
|
||||
if self.idx == 2 {
|
||||
self.curr_section_end = (h.start_time / SECTION_LEN).ceil() * SECTION_LEN;
|
||||
} else {
|
||||
while h.start_time > self.curr_section_end {
|
||||
self.strain.save_current_peak();
|
||||
self.strain.start_new_section_from(self.curr_section_end);
|
||||
self.curr_section_end += SECTION_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
self.strain.process(&h);
|
||||
|
||||
let missing = self.strain.strain_peaks.len() + 1 - self.strain_peak_buf.len();
|
||||
self.strain_peak_buf.extend(iter::repeat(0.0).take(missing));
|
||||
|
||||
self.strain_peak_buf[..self.strain.strain_peaks.len()]
|
||||
.copy_from_slice(&self.strain.strain_peaks);
|
||||
|
||||
if let Some(last) = self.strain_peak_buf.last_mut() {
|
||||
*last = self.strain.curr_section_peak;
|
||||
}
|
||||
|
||||
let stars = Strain::difficulty_value(&mut self.strain_peak_buf) * STAR_SCALING_FACTOR;
|
||||
|
||||
Some(ManiaDifficultyAttributes { stars })
|
||||
Some(ManiaDifficultyAttributes {
|
||||
stars: self.strain.clone().difficulty_value() * STAR_SCALING_FACTOR,
|
||||
hit_window: self.hit_window,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -116,56 +106,10 @@ impl Iterator for ManiaGradualDifficultyAttributes<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for ManiaGradualDifficultyAttributes<'_> {
|
||||
impl ExactSizeIterator for ManiaGradualDifficultyAttributes {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.difficulty_objects.len() + (self.idx == 0) as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ManiaObjectIter<'map> {
|
||||
hit_objects: Zip<Skip<Iter<'map, HitObject>>, Iter<'map, HitObject>>,
|
||||
columns: f32,
|
||||
clock_rate: f64,
|
||||
is_empty: bool,
|
||||
}
|
||||
|
||||
impl<'map> ManiaObjectIter<'map> {
|
||||
fn new(hit_objects: &'map [HitObject], columns: f32, clock_rate: f64) -> Self {
|
||||
let is_empty = hit_objects.is_empty();
|
||||
let hit_objects = hit_objects.iter().skip(1).zip(hit_objects);
|
||||
|
||||
Self {
|
||||
hit_objects,
|
||||
columns,
|
||||
clock_rate,
|
||||
is_empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> Iterator for ManiaObjectIter<'map> {
|
||||
type Item = DifficultyHitObject<'map>;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let (base, prev) = self.hit_objects.next()?;
|
||||
let obj = DifficultyHitObject::new(base, prev, self.columns, self.clock_rate);
|
||||
|
||||
Some(obj)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.hit_objects.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for ManiaObjectIter<'_> {
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.hit_objects.len()
|
||||
self.diff_objects.len() - self.idx
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct ManiaScoreState {
|
||||
}
|
||||
|
||||
impl ManiaScoreState {
|
||||
/// Return the total amount of hits by adding everything up.
|
||||
pub fn total_hits(&self) -> usize {
|
||||
self.n320 + self.n300 + self.n200 + self.n100 + self.n50 + self.n_misses
|
||||
}
|
||||
@@ -100,7 +101,7 @@ impl ManiaScoreState {
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ManiaGradualPerformanceAttributes<'map> {
|
||||
difficulty: ManiaGradualDifficultyAttributes<'map>,
|
||||
difficulty: ManiaGradualDifficultyAttributes,
|
||||
performance: ManiaPP<'map>,
|
||||
}
|
||||
|
||||
@@ -118,8 +119,11 @@ impl<'map> ManiaGradualPerformanceAttributes<'map> {
|
||||
|
||||
/// Process the next hit object and calculate the
|
||||
/// performance attributes for the resulting score.
|
||||
pub fn process_next_object(&mut self, score: u32) -> Option<ManiaPerformanceAttributes> {
|
||||
self.process_next_n_objects(score, 1)
|
||||
pub fn process_next_object(
|
||||
&mut self,
|
||||
state: ManiaScoreState,
|
||||
) -> Option<ManiaPerformanceAttributes> {
|
||||
self.process_next_n_objects(state, 1)
|
||||
}
|
||||
|
||||
/// Same as [`process_next_object`](`ManiaGradualPerformanceAttributes::process_next_object`)
|
||||
@@ -130,13 +134,18 @@ impl<'map> ManiaGradualPerformanceAttributes<'map> {
|
||||
/// of remaining objects, `n` will be considered as the amount of remaining objects.
|
||||
pub fn process_next_n_objects(
|
||||
&mut self,
|
||||
score: u32,
|
||||
state: ManiaScoreState,
|
||||
n: usize,
|
||||
) -> Option<ManiaPerformanceAttributes> {
|
||||
let n = n.min(self.difficulty.len()).saturating_sub(1);
|
||||
let difficulty = self.difficulty.nth(n)?;
|
||||
|
||||
let _ = self.performance.score.insert(score as f64);
|
||||
self.performance.n320 = Some(state.n320);
|
||||
self.performance.n300 = Some(state.n300);
|
||||
self.performance.n200 = Some(state.n200);
|
||||
self.performance.n100 = Some(state.n100);
|
||||
self.performance.n50 = Some(state.n50);
|
||||
self.performance.n_misses = Some(state.n_misses);
|
||||
|
||||
let performance = self
|
||||
.performance
|
||||
@@ -161,10 +170,20 @@ mod tests {
|
||||
let mods = 64;
|
||||
|
||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, mods);
|
||||
let score = 0;
|
||||
|
||||
assert!(gradual.process_next_n_objects(score, usize::MAX).is_some());
|
||||
assert!(gradual.process_next_object(score).is_none());
|
||||
let state = ManiaScoreState {
|
||||
n320: 0,
|
||||
n300: 0,
|
||||
n200: 0,
|
||||
n100: 0,
|
||||
n50: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
assert!(gradual
|
||||
.process_next_n_objects(state.clone(), usize::MAX)
|
||||
.is_some());
|
||||
assert!(gradual.process_next_object(state).is_none());
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
@@ -172,25 +191,34 @@ mod tests {
|
||||
fn next_and_next_n() {
|
||||
let map = Beatmap::from_path("./maps/1974394.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
let score = 0;
|
||||
|
||||
let mut state = ManiaScoreState {
|
||||
n320: 0,
|
||||
n300: 0,
|
||||
n200: 0,
|
||||
n100: 0,
|
||||
n50: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let mut gradual1 = ManiaGradualPerformanceAttributes::new(&map, mods);
|
||||
let mut gradual2 = ManiaGradualPerformanceAttributes::new(&map, mods);
|
||||
|
||||
for _ in 0..20 {
|
||||
let _ = gradual1.process_next_object(score);
|
||||
let _ = gradual2.process_next_object(score);
|
||||
let _ = gradual1.process_next_object(state.clone());
|
||||
let _ = gradual2.process_next_object(state.clone());
|
||||
state.n320 += 1;
|
||||
}
|
||||
|
||||
let n = 80;
|
||||
|
||||
for _ in 1..n {
|
||||
let _ = gradual1.process_next_object(score);
|
||||
let _ = gradual1.process_next_object(state.clone());
|
||||
state.n320 += 1;
|
||||
}
|
||||
|
||||
let score = 100_000;
|
||||
let next = gradual1.process_next_object(score);
|
||||
let next_n = gradual2.process_next_n_objects(score, n);
|
||||
let next = gradual1.process_next_object(state.clone());
|
||||
let next_n = gradual2.process_next_n_objects(state, n);
|
||||
|
||||
assert_eq!(next_n, next);
|
||||
}
|
||||
@@ -204,8 +232,16 @@ mod tests {
|
||||
|
||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, mods);
|
||||
|
||||
let score = 1_000_000;
|
||||
let gradual_end = gradual.process_next_n_objects(score, usize::MAX).unwrap();
|
||||
let state = ManiaScoreState {
|
||||
n320: 3238,
|
||||
n300: 0,
|
||||
n200: 0,
|
||||
n100: 0,
|
||||
n50: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual_end);
|
||||
}
|
||||
@@ -216,16 +252,24 @@ mod tests {
|
||||
let map = Beatmap::from_path("./maps/1974394.osu").expect("failed to parse map");
|
||||
let mods = 64;
|
||||
let n = 100;
|
||||
let score = 100_000;
|
||||
|
||||
let state = ManiaScoreState {
|
||||
n320: 100,
|
||||
n300: 0,
|
||||
n200: 0,
|
||||
n100: 0,
|
||||
n50: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let regular = ManiaPP::new(&map)
|
||||
.mods(mods)
|
||||
.passed_objects(n)
|
||||
.score(score)
|
||||
.state(state.clone())
|
||||
.calculate();
|
||||
|
||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, mods);
|
||||
let gradual = gradual.process_next_n_objects(score, n).unwrap();
|
||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual);
|
||||
}
|
||||
|
||||
+14
-22
@@ -7,7 +7,7 @@ mod skills;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::{beatmap::BeatmapHitWindows, parse::HitObjectKind, Beatmap, GameMode, Mods, OsuStars};
|
||||
use crate::{beatmap::BeatmapHitWindows, Beatmap, GameMode, Mods, OsuStars};
|
||||
|
||||
pub use self::{gradual_difficulty::*, gradual_performance::*, pp::*};
|
||||
|
||||
@@ -105,12 +105,12 @@ impl<'map> ManiaStars<'map> {
|
||||
.clock_rate(clock_rate)
|
||||
.hit_windows();
|
||||
|
||||
let (strain, mut attrs) = calculate_strain(self);
|
||||
let strain = calculate_strain(self);
|
||||
|
||||
attrs.stars = strain.difficulty_value() * STAR_SCALING_FACTOR;
|
||||
attrs.hit_window = hit_window;
|
||||
|
||||
attrs
|
||||
ManiaDifficultyAttributes {
|
||||
stars: strain.difficulty_value() * STAR_SCALING_FACTOR,
|
||||
hit_window,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the skill strains.
|
||||
@@ -119,7 +119,7 @@ impl<'map> ManiaStars<'map> {
|
||||
#[inline]
|
||||
pub fn strains(self) -> ManiaStrains {
|
||||
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.clock_rate());
|
||||
let (strain, _) = calculate_strain(self);
|
||||
let strain = calculate_strain(self);
|
||||
|
||||
ManiaStrains {
|
||||
section_len: SECTION_LEN * clock_rate, // TODO: clock_rate correct here?
|
||||
@@ -147,7 +147,7 @@ impl ManiaStrains {
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_strain(params: ManiaStars<'_>) -> (Strain, ManiaDifficultyAttributes) {
|
||||
fn calculate_strain(params: ManiaStars<'_>) -> Strain {
|
||||
let ManiaStars {
|
||||
map,
|
||||
mods,
|
||||
@@ -156,28 +156,22 @@ fn calculate_strain(params: ManiaStars<'_>) -> (Strain, ManiaDifficultyAttribute
|
||||
} = params;
|
||||
|
||||
let take = passed_objects.unwrap_or(map.hit_objects.len());
|
||||
let total_columns = map.cs.round().max(1.0) as usize;
|
||||
let total_columns = map.cs.round().max(1.0);
|
||||
|
||||
let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate());
|
||||
let mut strain = Strain::new(total_columns);
|
||||
|
||||
let mut attrs = ManiaDifficultyAttributes::default();
|
||||
let mut strain = Strain::new(total_columns as usize);
|
||||
|
||||
let diff_objects_iter = map
|
||||
.hit_objects
|
||||
.iter()
|
||||
.take(take)
|
||||
.inspect(|h| match &h.kind {
|
||||
HitObjectKind::Hold { end_time } => {
|
||||
attrs.max_combo += 1 + ((*end_time - h.start_time) / 100.0) as usize
|
||||
}
|
||||
_ => attrs.max_combo += 1,
|
||||
})
|
||||
.skip(1)
|
||||
.map(ManiaObject::new)
|
||||
.enumerate()
|
||||
.zip(map.hit_objects.iter().map(ManiaObject::new))
|
||||
.map(|((i, base), prev)| ManiaDifficultyObject::new(base, prev, clock_rate, i));
|
||||
.map(|((i, base), prev)| {
|
||||
ManiaDifficultyObject::new(base, prev, clock_rate, total_columns, i)
|
||||
});
|
||||
|
||||
let mut diff_objects = Vec::with_capacity(map.hit_objects.len().min(take).saturating_sub(1));
|
||||
diff_objects.extend(diff_objects_iter);
|
||||
@@ -186,7 +180,7 @@ fn calculate_strain(params: ManiaStars<'_>) -> (Strain, ManiaDifficultyAttribute
|
||||
strain.process(curr, &diff_objects);
|
||||
}
|
||||
|
||||
(strain, attrs)
|
||||
strain
|
||||
}
|
||||
|
||||
/// The result of a difficulty calculation on an osu!mania map.
|
||||
@@ -194,8 +188,6 @@ fn calculate_strain(params: ManiaStars<'_>) -> (Strain, ManiaDifficultyAttribute
|
||||
pub struct ManiaDifficultyAttributes {
|
||||
/// The final star rating.
|
||||
pub stars: f64,
|
||||
/// The maximum achievable combo.
|
||||
pub max_combo: usize,
|
||||
/// The perceived hit window for an n300 inclusive of rate-adjusting mods (DT/HT/etc).
|
||||
pub hit_window: f64,
|
||||
}
|
||||
|
||||
+19
-9
@@ -40,12 +40,12 @@ pub struct ManiaPP<'map> {
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
|
||||
n320: Option<usize>,
|
||||
n300: Option<usize>,
|
||||
n200: Option<usize>,
|
||||
n100: Option<usize>,
|
||||
n50: Option<usize>,
|
||||
n_misses: Option<usize>,
|
||||
pub(crate) n320: Option<usize>,
|
||||
pub(crate) n300: Option<usize>,
|
||||
pub(crate) n200: Option<usize>,
|
||||
pub(crate) n100: Option<usize>,
|
||||
pub(crate) n50: Option<usize>,
|
||||
pub(crate) n_misses: Option<usize>,
|
||||
|
||||
acc: Option<f64>,
|
||||
hitresult_priority: Option<ManiaHitResultPriority>,
|
||||
@@ -120,6 +120,8 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the accuracy of a play.
|
||||
/// This will be used to generate matching hitresults.
|
||||
#[inline]
|
||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
||||
self.acc = Some(acc);
|
||||
@@ -127,6 +129,9 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify how hitresults should be generated.
|
||||
///
|
||||
/// Defauls to [`ManiaHitResultPriority::BestCase`].
|
||||
#[inline]
|
||||
pub fn hitresult_priority(mut self, priority: ManiaHitResultPriority) -> Self {
|
||||
self.hitresult_priority = Some(priority);
|
||||
@@ -134,6 +139,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of 320s of a play.
|
||||
#[inline]
|
||||
pub fn n320(mut self, n320: usize) -> Self {
|
||||
self.n320 = Some(n320);
|
||||
@@ -141,6 +147,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of 300s of a play.
|
||||
#[inline]
|
||||
pub fn n300(mut self, n300: usize) -> Self {
|
||||
self.n300 = Some(n300);
|
||||
@@ -148,6 +155,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of 200s of a play.
|
||||
#[inline]
|
||||
pub fn n200(mut self, n200: usize) -> Self {
|
||||
self.n200 = Some(n200);
|
||||
@@ -155,6 +163,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of 100s of a play.
|
||||
#[inline]
|
||||
pub fn n100(mut self, n100: usize) -> Self {
|
||||
self.n100 = Some(n100);
|
||||
@@ -162,6 +171,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of 50s of a play.
|
||||
#[inline]
|
||||
pub fn n50(mut self, n50: usize) -> Self {
|
||||
self.n50 = Some(n50);
|
||||
@@ -169,6 +179,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of misses of a play.
|
||||
#[inline]
|
||||
pub fn n_misses(mut self, n_misses: usize) -> Self {
|
||||
self.n_misses = Some(n_misses);
|
||||
@@ -176,6 +187,7 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide parameters through an [`ManiaScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: ManiaScoreState) -> Self {
|
||||
let ManiaScoreState {
|
||||
@@ -217,7 +229,6 @@ impl<'map> ManiaPP<'map> {
|
||||
let inner = ManiaPpInner {
|
||||
attrs,
|
||||
mods: self.mods,
|
||||
clock_rate: self.clock_rate.unwrap_or_else(|| self.mods.clock_rate()),
|
||||
state: self.generate_hitresults(),
|
||||
};
|
||||
|
||||
@@ -337,7 +348,6 @@ impl<'map> ManiaPP<'map> {
|
||||
struct ManiaPpInner {
|
||||
attrs: ManiaDifficultyAttributes,
|
||||
mods: u32,
|
||||
clock_rate: f64,
|
||||
state: ManiaScoreState,
|
||||
}
|
||||
|
||||
@@ -385,7 +395,7 @@ impl ManiaPpInner {
|
||||
n200,
|
||||
n100,
|
||||
n50,
|
||||
n_misses,
|
||||
n_misses: _,
|
||||
} = &self.state;
|
||||
|
||||
let numerator = *n320 * 320 + *n300 * 300 + *n200 * 200 + *n100 * 100 + *n50 * 50;
|
||||
|
||||
+14
-22
@@ -7,11 +7,7 @@ pub(crate) use self::strain::Strain;
|
||||
mod strain;
|
||||
|
||||
pub(crate) trait Skill {
|
||||
fn process(
|
||||
&mut self,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
);
|
||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]);
|
||||
fn difficulty_value(self) -> f64;
|
||||
}
|
||||
|
||||
@@ -26,13 +22,9 @@ pub(crate) trait StrainSkill: Sized + Skill {
|
||||
|
||||
fn strain_peaks_mut(&mut self) -> &mut Vec<f64>;
|
||||
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject<'_>) -> f64;
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64;
|
||||
|
||||
fn process(
|
||||
&mut self,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
) {
|
||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
||||
// The first object doesn't generate a strain, so we begin with an incremented section end
|
||||
if curr.idx == 0 {
|
||||
// currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength;
|
||||
@@ -56,8 +48,8 @@ pub(crate) trait StrainSkill: Sized + Skill {
|
||||
fn start_new_section_from(
|
||||
&mut self,
|
||||
time: f64,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
curr: &ManiaDifficultyObject,
|
||||
diff_objects: &[ManiaDifficultyObject],
|
||||
) {
|
||||
*self.curr_section_peak_mut() = self.calculate_initial_strain(time, curr, diff_objects);
|
||||
}
|
||||
@@ -65,8 +57,8 @@ pub(crate) trait StrainSkill: Sized + Skill {
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
time: f64,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
curr: &ManiaDifficultyObject,
|
||||
diff_objects: &[ManiaDifficultyObject],
|
||||
) -> f64;
|
||||
|
||||
fn get_curr_strain_peaks(mut self) -> Vec<f64> {
|
||||
@@ -104,16 +96,16 @@ pub(crate) trait StrainDecaySkill: StrainSkill {
|
||||
fn curr_strain(&self) -> f64;
|
||||
fn curr_strain_mut(&mut self) -> &mut f64;
|
||||
|
||||
fn strain_value_of(&mut self, curr: &ManiaDifficultyObject<'_>) -> f64;
|
||||
fn strain_value_of(&mut self, curr: &ManiaDifficultyObject) -> f64;
|
||||
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
time: f64,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
curr: &ManiaDifficultyObject,
|
||||
diff_objects: &[ManiaDifficultyObject],
|
||||
) -> f64;
|
||||
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject<'_>) -> f64 {
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64 {
|
||||
*self.curr_strain_mut() *= self.strain_decay(curr.delta_time);
|
||||
*self.curr_strain_mut() += self.strain_value_of(curr) * Self::SKILL_MULTIPLIER;
|
||||
|
||||
@@ -125,11 +117,11 @@ pub(crate) trait StrainDecaySkill: StrainSkill {
|
||||
}
|
||||
}
|
||||
|
||||
fn previous<'map, 'objects>(
|
||||
diff_objects: &'objects [ManiaDifficultyObject<'map>],
|
||||
fn previous(
|
||||
diff_objects: &[ManiaDifficultyObject],
|
||||
curr: usize,
|
||||
backwards_idx: usize,
|
||||
) -> Option<&'objects ManiaDifficultyObject<'map>> {
|
||||
) -> Option<&ManiaDifficultyObject> {
|
||||
curr.checked_sub(backwards_idx + 1)
|
||||
.and_then(|idx| diff_objects.get(idx))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::mania::difficulty_object::ManiaDifficultyObject;
|
||||
|
||||
use super::{previous, Skill, StrainDecaySkill, StrainSkill};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Strain {
|
||||
start_times: Vec<f64>,
|
||||
end_times: Vec<f64>,
|
||||
@@ -15,8 +16,6 @@ pub(crate) struct Strain {
|
||||
curr_section_end: f64,
|
||||
|
||||
pub(crate) strain_peaks: Vec<f64>,
|
||||
|
||||
total_columns: f32,
|
||||
}
|
||||
|
||||
impl Strain {
|
||||
@@ -35,7 +34,6 @@ impl Strain {
|
||||
curr_section_peak: 0.0,
|
||||
curr_section_end: 0.0,
|
||||
strain_peaks: Vec::new(),
|
||||
total_columns: total_columns as f32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +43,7 @@ impl Strain {
|
||||
}
|
||||
|
||||
impl Skill for Strain {
|
||||
fn process(
|
||||
&mut self,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
) {
|
||||
fn process(&mut self, curr: &ManiaDifficultyObject, diff_objects: &[ManiaDifficultyObject]) {
|
||||
<Self as StrainSkill>::process(self, curr, diff_objects)
|
||||
}
|
||||
|
||||
@@ -81,15 +75,15 @@ impl StrainSkill for Strain {
|
||||
&mut self.strain_peaks
|
||||
}
|
||||
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject<'_>) -> f64 {
|
||||
fn strain_value_at(&mut self, curr: &ManiaDifficultyObject) -> f64 {
|
||||
<Self as StrainDecaySkill>::strain_value_at(self, curr)
|
||||
}
|
||||
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
time: f64,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
curr: &ManiaDifficultyObject,
|
||||
diff_objects: &[ManiaDifficultyObject],
|
||||
) -> f64 {
|
||||
<Self as StrainDecaySkill>::calculate_initial_strain(self, time, curr, diff_objects)
|
||||
}
|
||||
@@ -107,11 +101,11 @@ impl StrainDecaySkill for Strain {
|
||||
&mut self.curr_strain
|
||||
}
|
||||
|
||||
fn strain_value_of(&mut self, curr: &ManiaDifficultyObject<'_>) -> f64 {
|
||||
fn strain_value_of(&mut self, curr: &ManiaDifficultyObject) -> f64 {
|
||||
let mania_curr = curr;
|
||||
let start_time = mania_curr.start_time;
|
||||
let end_time = mania_curr.end_time;
|
||||
let col = mania_curr.base.column(self.total_columns);
|
||||
let col = mania_curr.base_column;
|
||||
let mut is_overlapping = false;
|
||||
|
||||
// Lowest value we can assume with the current information
|
||||
@@ -183,8 +177,8 @@ impl StrainDecaySkill for Strain {
|
||||
fn calculate_initial_strain(
|
||||
&self,
|
||||
offset: f64,
|
||||
curr: &ManiaDifficultyObject<'_>,
|
||||
diff_objects: &[ManiaDifficultyObject<'_>],
|
||||
curr: &ManiaDifficultyObject,
|
||||
diff_objects: &[ManiaDifficultyObject],
|
||||
) -> f64 {
|
||||
let prev_start = previous(diff_objects, curr.idx, 0).map_or(0.0, |h| h.start_time);
|
||||
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
use super::DifficultyHitObject;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Strain {
|
||||
current_strain: f64,
|
||||
pub(crate) curr_section_peak: f64,
|
||||
|
||||
individual_strain: f64,
|
||||
overall_strain: f64,
|
||||
|
||||
hold_end_times: Vec<f64>,
|
||||
individual_strains: Vec<f64>,
|
||||
pub(crate) strain_peaks: Vec<f64>,
|
||||
|
||||
prev_time: Option<f64>,
|
||||
}
|
||||
|
||||
const INDIVISUAL_DECAY_BASE: f64 = 0.125;
|
||||
const OVERALL_DECAY_BASE: f64 = 0.3;
|
||||
const STRAIN_DECAY_BASE: f64 = 1.0;
|
||||
|
||||
const SKILL_MULTIPLIER: f64 = 1.0;
|
||||
const DECAY_WEIGHT: f64 = 0.9;
|
||||
|
||||
impl Strain {
|
||||
#[inline]
|
||||
pub(crate) fn new(column_count: u8) -> Self {
|
||||
Self {
|
||||
current_strain: 1.0,
|
||||
curr_section_peak: 1.0,
|
||||
|
||||
individual_strain: 0.0,
|
||||
overall_strain: 1.0,
|
||||
|
||||
hold_end_times: vec![0.0; column_count as usize],
|
||||
individual_strains: vec![0.0; column_count as usize],
|
||||
strain_peaks: Vec::with_capacity(128),
|
||||
|
||||
prev_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn save_current_peak(&mut self) {
|
||||
self.strain_peaks.push(self.curr_section_peak);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn start_new_section_from(&mut self, time: f64) {
|
||||
self.curr_section_peak = self.peak_strain(time - self.prev_time.unwrap());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn peak_strain(&self, delta_time: f64) -> f64 {
|
||||
apply_decay(self.individual_strain, delta_time, INDIVISUAL_DECAY_BASE)
|
||||
+ apply_decay(self.overall_strain, delta_time, OVERALL_DECAY_BASE)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn strain_decay(&self, ms: f64) -> f64 {
|
||||
STRAIN_DECAY_BASE.powf(ms / 1000.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn process(&mut self, current: &DifficultyHitObject<'_>) {
|
||||
self.current_strain *= self.strain_decay(current.delta);
|
||||
self.current_strain += self.strain_value_of(current) * SKILL_MULTIPLIER;
|
||||
self.curr_section_peak = self.current_strain.max(self.curr_section_peak);
|
||||
self.prev_time.replace(current.start_time);
|
||||
}
|
||||
|
||||
fn strain_value_of(&mut self, current: &DifficultyHitObject<'_>) -> f64 {
|
||||
let end_time = current.base.end_time();
|
||||
|
||||
let mut hold_factor = 1.0;
|
||||
let mut hold_addition = 0.0;
|
||||
|
||||
for col in 0..self.hold_end_times.len() {
|
||||
let hold_end_time = self.hold_end_times[col];
|
||||
|
||||
if end_time > hold_end_time + 1.0 {
|
||||
if hold_end_time > current.base.start_time + 1.0 {
|
||||
hold_addition = 1.0;
|
||||
}
|
||||
} else if (end_time - hold_end_time).abs() < 1.0 {
|
||||
hold_addition = 0.0;
|
||||
} else if end_time < hold_end_time - 1.0 {
|
||||
hold_factor = 1.25;
|
||||
}
|
||||
|
||||
self.individual_strains[col] = apply_decay(
|
||||
self.individual_strains[col],
|
||||
current.delta,
|
||||
INDIVISUAL_DECAY_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
self.hold_end_times[current.column] = end_time;
|
||||
self.individual_strains[current.column] += 2.0 * hold_factor;
|
||||
self.individual_strain = self.individual_strains[current.column];
|
||||
|
||||
self.overall_strain = apply_decay(self.overall_strain, current.delta, OVERALL_DECAY_BASE)
|
||||
+ (1.0 + hold_addition) * hold_factor;
|
||||
|
||||
self.individual_strain + self.overall_strain - self.current_strain
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn difficulty_value(strain_peaks: &mut [f64]) -> f64 {
|
||||
let mut difficulty = 0.0;
|
||||
let mut weight = 1.0;
|
||||
|
||||
strain_peaks.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
|
||||
|
||||
for &strain in strain_peaks.iter() {
|
||||
difficulty += strain * weight;
|
||||
weight *= DECAY_WEIGHT;
|
||||
}
|
||||
|
||||
difficulty
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply_decay(value: f64, delta_time: f64, decay_base: f64) -> f64 {
|
||||
value * decay_base.powf(delta_time / 1000.0)
|
||||
}
|
||||
Reference in New Issue
Block a user