allow custom clock rates
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
- Fixed handling .osu files with missing difficulty attributes
|
||||
- Fixed huge memory allocations caused by incorrectly parsed .osu files by clamping beat length
|
||||
- Added `AttributeProvider` impl for `{Mode}PerformanceAttributes`
|
||||
- Added the method `clock_rate` to `{Mode}PP` and `{Mode}Stars` to consider a custom clock rate instead of the one dictated by mods.
|
||||
- [BREAKING] The `stars` and `strains` functions for all modes were removed. Instead use the `{Mode}Stars` builder pattern which is similar to `{Mode}PP`.
|
||||
- [BREAKING] `BeatmapExt::stars`'s definition was adjusted to use the `AnyStars` builder struct
|
||||
- [BREAKING] Store `HitObject::sound` in `Beatmap::sounds` instead to reduce the struct size
|
||||
|
||||
+20
-17
@@ -47,6 +47,7 @@ pub struct FruitsStars<'map> {
|
||||
map: &'map Beatmap,
|
||||
mods: u32,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'map> FruitsStars<'map> {
|
||||
@@ -57,6 +58,7 @@ impl<'map> FruitsStars<'map> {
|
||||
map,
|
||||
mods: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +84,16 @@ impl<'map> FruitsStars<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculate all difficulty related values, including stars.
|
||||
#[inline]
|
||||
pub fn calculate(self) -> FruitsDifficultyAttributes {
|
||||
@@ -97,11 +109,11 @@ impl<'map> FruitsStars<'map> {
|
||||
/// Suitable to plot the difficulty of a map over time.
|
||||
#[inline]
|
||||
pub fn strains(self) -> Strains {
|
||||
let mods = self.mods;
|
||||
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.speed());
|
||||
let (movement, _) = calculate_movement(self);
|
||||
|
||||
Strains {
|
||||
section_length: SECTION_LENGTH * mods.speed(),
|
||||
section_length: SECTION_LENGTH * clock_rate,
|
||||
strains: movement.strain_peaks,
|
||||
}
|
||||
}
|
||||
@@ -112,11 +124,13 @@ fn calculate_movement(params: FruitsStars<'_>) -> (Movement, FruitsDifficultyAtt
|
||||
map,
|
||||
mods,
|
||||
passed_objects,
|
||||
clock_rate,
|
||||
} = params;
|
||||
|
||||
let take = passed_objects.unwrap_or(usize::MAX);
|
||||
|
||||
let map_attributes = map.attributes().mods(mods);
|
||||
let clock_rate = clock_rate.unwrap_or(map_attributes.clock_rate);
|
||||
|
||||
let attributes = FruitsDifficultyAttributes {
|
||||
ar: map_attributes.ar,
|
||||
@@ -157,8 +171,7 @@ fn calculate_movement(params: FruitsStars<'_>) -> (Movement, FruitsDifficultyAtt
|
||||
(None, Some(_)) => unreachable!(),
|
||||
};
|
||||
|
||||
let mut curr_section_end =
|
||||
(curr.time / map_attributes.clock_rate / SECTION_LENGTH).ceil() * SECTION_LENGTH;
|
||||
let mut curr_section_end = (curr.time / clock_rate / SECTION_LENGTH).ceil() * SECTION_LENGTH;
|
||||
|
||||
prev.init_hyper_dash(
|
||||
half_catcher_width,
|
||||
@@ -168,12 +181,7 @@ fn calculate_movement(params: FruitsStars<'_>) -> (Movement, FruitsDifficultyAtt
|
||||
);
|
||||
|
||||
// Handle first object distinctly
|
||||
let h = DifficultyObject::new(
|
||||
&curr,
|
||||
&prev,
|
||||
movement.half_catcher_width,
|
||||
map_attributes.clock_rate,
|
||||
);
|
||||
let h = DifficultyObject::new(&curr, &prev, movement.half_catcher_width, clock_rate);
|
||||
|
||||
movement.process(&h);
|
||||
prev = curr;
|
||||
@@ -187,14 +195,9 @@ fn calculate_movement(params: FruitsStars<'_>) -> (Movement, FruitsDifficultyAtt
|
||||
&mut last_excess,
|
||||
);
|
||||
|
||||
let h = DifficultyObject::new(
|
||||
&curr,
|
||||
&prev,
|
||||
movement.half_catcher_width,
|
||||
map_attributes.clock_rate,
|
||||
);
|
||||
let h = DifficultyObject::new(&curr, &prev, movement.half_catcher_width, clock_rate);
|
||||
|
||||
let base_time = h.base.time / map_attributes.clock_rate;
|
||||
let base_time = h.base.time / clock_rate;
|
||||
|
||||
while base_time > curr_section_end {
|
||||
movement.save_current_peak();
|
||||
|
||||
+33
-9
@@ -46,6 +46,7 @@ pub struct FruitsPP<'map> {
|
||||
pub(crate) n_tiny_droplet_misses: Option<usize>,
|
||||
pub(crate) n_misses: usize,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'map> FruitsPP<'map> {
|
||||
@@ -64,6 +65,7 @@ impl<'map> FruitsPP<'map> {
|
||||
n_tiny_droplet_misses: None,
|
||||
n_misses: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +151,16 @@ impl<'map> FruitsPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide parameters through an [`FruitsScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: FruitsScoreState) -> Self {
|
||||
@@ -176,12 +188,17 @@ impl<'map> FruitsPP<'map> {
|
||||
/// Be sure to set `misses` beforehand! Also, if available, set `attributes` beforehand.
|
||||
pub fn accuracy(mut self, mut acc: f64) -> Self {
|
||||
if self.attributes.is_none() {
|
||||
let attrs = FruitsStars::new(self.map)
|
||||
.mods(self.mods)
|
||||
.passed_objects(self.passed_objects.unwrap_or(usize::MAX))
|
||||
.calculate();
|
||||
let mut calculator = FruitsStars::new(self.map).mods(self.mods);
|
||||
|
||||
self.attributes = Some(attrs);
|
||||
if let Some(passed_objects) = self.passed_objects {
|
||||
calculator = calculator.passed_objects(passed_objects);
|
||||
}
|
||||
|
||||
if let Some(clock_rate) = self.clock_rate {
|
||||
calculator = calculator.clock_rate(clock_rate);
|
||||
}
|
||||
|
||||
self.attributes = Some(calculator.calculate());
|
||||
}
|
||||
|
||||
let attributes = self.attributes.as_ref().unwrap();
|
||||
@@ -291,10 +308,17 @@ impl<'map> FruitsPP<'map> {
|
||||
/// Calculate all performance related values, including pp and stars.
|
||||
pub fn calculate(mut self) -> FruitsPerformanceAttributes {
|
||||
let attributes = self.attributes.take().unwrap_or_else(|| {
|
||||
FruitsStars::new(self.map)
|
||||
.mods(self.mods)
|
||||
.passed_objects(self.passed_objects.unwrap_or(usize::MAX))
|
||||
.calculate()
|
||||
let mut calculator = FruitsStars::new(self.map).mods(self.mods);
|
||||
|
||||
if let Some(passed_objects) = self.passed_objects {
|
||||
calculator = calculator.passed_objects(passed_objects);
|
||||
}
|
||||
|
||||
if let Some(clock_rate) = self.clock_rate {
|
||||
calculator = calculator.clock_rate(clock_rate);
|
||||
}
|
||||
|
||||
calculator.calculate()
|
||||
});
|
||||
|
||||
self.assert_hitresults(attributes).calculate()
|
||||
|
||||
+16
-3
@@ -36,6 +36,7 @@ pub struct ManiaStars<'map> {
|
||||
map: &'map Beatmap,
|
||||
mods: u32,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'map> ManiaStars<'map> {
|
||||
@@ -46,6 +47,7 @@ impl<'map> ManiaStars<'map> {
|
||||
map,
|
||||
mods: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +73,16 @@ impl<'map> ManiaStars<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculate all difficulty related values, including stars.
|
||||
#[inline]
|
||||
pub fn calculate(self) -> ManiaDifficultyAttributes {
|
||||
@@ -86,11 +98,11 @@ impl<'map> ManiaStars<'map> {
|
||||
/// Suitable to plot the difficulty of a map over time.
|
||||
#[inline]
|
||||
pub fn strains(self) -> Strains {
|
||||
let mods = self.mods;
|
||||
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.speed());
|
||||
let strain = calculate_strain(self);
|
||||
|
||||
Strains {
|
||||
section_length: SECTION_LEN * mods.speed(),
|
||||
section_length: SECTION_LEN * clock_rate,
|
||||
strains: strain.strain_peaks,
|
||||
}
|
||||
}
|
||||
@@ -101,6 +113,7 @@ fn calculate_strain(params: ManiaStars<'_>) -> Strain {
|
||||
map,
|
||||
mods,
|
||||
passed_objects,
|
||||
clock_rate,
|
||||
} = params;
|
||||
|
||||
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
|
||||
@@ -127,7 +140,7 @@ fn calculate_strain(params: ManiaStars<'_>) -> Strain {
|
||||
other => panic!("can not calculate mania difficulty on a {:?} map", other),
|
||||
};
|
||||
|
||||
let clock_rate = mods.speed();
|
||||
let clock_rate = clock_rate.unwrap_or_else(|| mods.speed());
|
||||
let mut strain = Strain::new(columns);
|
||||
let columns = columns as f32;
|
||||
|
||||
|
||||
+23
-5
@@ -36,6 +36,7 @@ pub struct ManiaPP<'map> {
|
||||
mods: u32,
|
||||
pub(crate) score: Option<f64>,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'map> ManiaPP<'map> {
|
||||
@@ -48,6 +49,7 @@ impl<'map> ManiaPP<'map> {
|
||||
mods: 0,
|
||||
score: None,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,14 +99,30 @@ impl<'map> ManiaPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculate all performance related values, including pp and stars.
|
||||
pub fn calculate(self) -> ManiaPerformanceAttributes {
|
||||
let stars = self.stars.unwrap_or_else(|| {
|
||||
ManiaStars::new(self.map)
|
||||
.mods(self.mods)
|
||||
.passed_objects(self.passed_objects.unwrap_or(usize::MAX))
|
||||
.calculate()
|
||||
.stars
|
||||
let mut calculator = ManiaStars::new(self.map).mods(self.mods);
|
||||
|
||||
if let Some(passed_objects) = self.passed_objects {
|
||||
calculator = calculator.passed_objects(passed_objects);
|
||||
}
|
||||
|
||||
if let Some(clock_rate) = self.clock_rate {
|
||||
calculator = calculator.clock_rate(clock_rate);
|
||||
}
|
||||
|
||||
calculator.calculate().stars
|
||||
});
|
||||
|
||||
let ez = self.mods.ez();
|
||||
|
||||
+22
-9
@@ -52,6 +52,7 @@ pub struct OsuStars<'map> {
|
||||
map: &'map Beatmap,
|
||||
mods: u32,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'map> OsuStars<'map> {
|
||||
@@ -62,6 +63,7 @@ impl<'map> OsuStars<'map> {
|
||||
map,
|
||||
mods: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +89,16 @@ impl<'map> OsuStars<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculate all difficulty related values, including stars.
|
||||
#[inline]
|
||||
pub fn calculate(self) -> OsuDifficultyAttributes {
|
||||
@@ -151,7 +163,7 @@ impl<'map> OsuStars<'map> {
|
||||
/// Suitable to plot the difficulty of a map over time.
|
||||
#[inline]
|
||||
pub fn strains(self) -> Strains {
|
||||
let mods = self.mods;
|
||||
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.speed());
|
||||
let (mut skills, _) = calculate_skills(self);
|
||||
|
||||
let mut aim = mem::take(&mut skills.aim().strain_peaks);
|
||||
@@ -180,7 +192,7 @@ impl<'map> OsuStars<'map> {
|
||||
};
|
||||
|
||||
Strains {
|
||||
section_length: SECTION_LEN * mods.speed(),
|
||||
section_length: SECTION_LEN * clock_rate,
|
||||
strains,
|
||||
}
|
||||
}
|
||||
@@ -220,12 +232,14 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
map,
|
||||
mods,
|
||||
passed_objects,
|
||||
clock_rate,
|
||||
} = params;
|
||||
|
||||
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
|
||||
let clock_rate = clock_rate.unwrap_or_else(|| mods.speed());
|
||||
|
||||
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) / clock_rate;
|
||||
let od = (80.0 - hit_window) / 6.0;
|
||||
|
||||
let mut raw_ar = map.ar as f64;
|
||||
@@ -290,8 +304,7 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
let mut prev_prev = None;
|
||||
|
||||
// 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;
|
||||
let mut curr_section_end = (prev.time / clock_rate / SECTION_LEN).ceil() * SECTION_LEN;
|
||||
|
||||
// Handle second object separately to remove later if-branching
|
||||
let h = DifficultyObject::new(
|
||||
@@ -299,10 +312,10 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
&mut prev,
|
||||
prev_prev.as_ref(),
|
||||
&scaling_factor,
|
||||
map_attributes.clock_rate,
|
||||
clock_rate,
|
||||
);
|
||||
|
||||
let base_time = h.base.time / map_attributes.clock_rate;
|
||||
let base_time = h.base.time / clock_rate;
|
||||
|
||||
while base_time > curr_section_end {
|
||||
skills.start_new_section_from(curr_section_end);
|
||||
@@ -319,10 +332,10 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
||||
&mut prev,
|
||||
prev_prev.as_ref(),
|
||||
&scaling_factor,
|
||||
map_attributes.clock_rate,
|
||||
clock_rate,
|
||||
);
|
||||
|
||||
let base_time = h.base.time / map_attributes.clock_rate;
|
||||
let base_time = h.base.time / clock_rate;
|
||||
|
||||
while base_time > curr_section_end {
|
||||
skills.save_peak_and_start_new_section(curr_section_end);
|
||||
|
||||
+23
-4
@@ -44,6 +44,7 @@ pub struct OsuPP<'map> {
|
||||
pub(crate) n50: Option<usize>,
|
||||
pub(crate) n_misses: usize,
|
||||
pub(crate) passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'map> OsuPP<'map> {
|
||||
@@ -62,6 +63,7 @@ impl<'map> OsuPP<'map> {
|
||||
n50: None,
|
||||
n_misses: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +141,16 @@ impl<'map> OsuPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide parameters through an [`OsuScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: OsuScoreState) -> Self {
|
||||
@@ -313,10 +325,17 @@ 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(|| {
|
||||
OsuStars::new(self.map)
|
||||
.mods(self.mods)
|
||||
.passed_objects(self.passed_objects.unwrap_or(usize::MAX))
|
||||
.calculate()
|
||||
let mut calculator = OsuStars::new(self.map).mods(self.mods);
|
||||
|
||||
if let Some(passed_objects) = self.passed_objects {
|
||||
calculator = calculator.passed_objects(passed_objects);
|
||||
}
|
||||
|
||||
if let Some(clock_rate) = self.clock_rate {
|
||||
calculator = calculator.clock_rate(clock_rate);
|
||||
}
|
||||
|
||||
calculator.calculate()
|
||||
});
|
||||
|
||||
self.assert_hitresults(attributes).calculate()
|
||||
|
||||
@@ -113,6 +113,19 @@ impl<'map> AnyPP<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(self, clock_rate: f64) -> Self {
|
||||
match self {
|
||||
Self::Fruits(f) => Self::Fruits(f.clock_rate(clock_rate)),
|
||||
Self::Mania(m) => Self::Mania(m.clock_rate(clock_rate)),
|
||||
Self::Osu(o) => Self::Osu(o.clock_rate(clock_rate)),
|
||||
Self::Taiko(t) => Self::Taiko(t.clock_rate(clock_rate)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide parameters through a [`ScoreState`].
|
||||
#[inline]
|
||||
pub fn state(self, state: ScoreState) -> Self {
|
||||
|
||||
@@ -72,6 +72,19 @@ impl<'map> AnyStars<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(self, clock_rate: f64) -> Self {
|
||||
match self {
|
||||
Self::Fruits(f) => Self::Fruits(f.clock_rate(clock_rate)),
|
||||
Self::Mania(m) => Self::Mania(m.clock_rate(clock_rate)),
|
||||
Self::Osu(o) => Self::Osu(o.clock_rate(clock_rate)),
|
||||
Self::Taiko(t) => Self::Taiko(t.clock_rate(clock_rate)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the difficulty calculator and calculate
|
||||
/// difficulty attributes for the given parameters.
|
||||
#[inline]
|
||||
|
||||
+16
-3
@@ -56,6 +56,7 @@ pub struct TaikoStars<'map> {
|
||||
map: &'map Beatmap,
|
||||
mods: u32,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
}
|
||||
|
||||
impl<'map> TaikoStars<'map> {
|
||||
@@ -66,6 +67,7 @@ impl<'map> TaikoStars<'map> {
|
||||
map,
|
||||
mods: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +93,16 @@ impl<'map> TaikoStars<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculate all difficulty related values, including stars.
|
||||
#[inline]
|
||||
pub fn calculate(self) -> TaikoDifficultyAttributes {
|
||||
@@ -127,7 +139,7 @@ impl<'map> TaikoStars<'map> {
|
||||
/// Suitable to plot the difficulty of a map over time.
|
||||
#[inline]
|
||||
pub fn strains(self) -> Strains {
|
||||
let mods = self.mods;
|
||||
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.speed());
|
||||
let (skills, _) = calculate_skills(self);
|
||||
|
||||
let strains = skills
|
||||
@@ -143,7 +155,7 @@ impl<'map> TaikoStars<'map> {
|
||||
.collect();
|
||||
|
||||
Strains {
|
||||
section_length: SECTION_LEN * mods.speed(),
|
||||
section_length: SECTION_LEN * clock_rate,
|
||||
strains,
|
||||
}
|
||||
}
|
||||
@@ -154,14 +166,15 @@ fn calculate_skills(params: TaikoStars<'_>) -> (Skills, usize) {
|
||||
map,
|
||||
mods,
|
||||
passed_objects,
|
||||
clock_rate,
|
||||
} = params;
|
||||
|
||||
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
|
||||
let clock_rate = clock_rate.unwrap_or_else(|| mods.speed());
|
||||
|
||||
// True if the object at that index is stamina cheese
|
||||
let cheese = map.find_cheese();
|
||||
let mut skills = Skills::new();
|
||||
let clock_rate = mods.speed();
|
||||
let mut max_combo = 0;
|
||||
|
||||
match map.hit_objects.get(0) {
|
||||
|
||||
+23
-4
@@ -39,6 +39,7 @@ pub struct TaikoPP<'map> {
|
||||
combo: Option<usize>,
|
||||
acc: f64,
|
||||
passed_objects: Option<usize>,
|
||||
clock_rate: Option<f64>,
|
||||
|
||||
pub(crate) n300: Option<usize>,
|
||||
pub(crate) n100: Option<usize>,
|
||||
@@ -57,6 +58,7 @@ impl<'map> TaikoPP<'map> {
|
||||
acc: 1.0,
|
||||
n_misses: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
}
|
||||
@@ -138,6 +140,16 @@ impl<'map> TaikoPP<'map> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
/// If none is specified, it will take the clock rate based on the mods
|
||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||
#[inline]
|
||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||
self.clock_rate = Some(clock_rate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Provide parameters through a [`TaikoScoreState`].
|
||||
#[inline]
|
||||
pub fn state(mut self, state: TaikoScoreState) -> Self {
|
||||
@@ -159,10 +171,17 @@ impl<'map> TaikoPP<'map> {
|
||||
/// Calculate all performance related values, including pp and stars.
|
||||
pub fn calculate(mut self) -> TaikoPerformanceAttributes {
|
||||
let attributes = self.attributes.take().unwrap_or_else(|| {
|
||||
TaikoStars::new(self.map)
|
||||
.mods(self.mods)
|
||||
.passed_objects(self.passed_objects.unwrap_or(usize::MAX))
|
||||
.calculate()
|
||||
let mut calculator = TaikoStars::new(self.map).mods(self.mods);
|
||||
|
||||
if let Some(passed_objects) = self.passed_objects {
|
||||
calculator = calculator.passed_objects(passed_objects);
|
||||
}
|
||||
|
||||
if let Some(clock_rate) = self.clock_rate {
|
||||
calculator = calculator.clock_rate(clock_rate);
|
||||
}
|
||||
|
||||
calculator.calculate()
|
||||
});
|
||||
|
||||
if self.n300.or(self.n100).is_some() {
|
||||
|
||||
Reference in New Issue
Block a user