start base
This commit is contained in:
+5
-6
@@ -1,12 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rosu-pp"
|
name = "akatsuki-pp-rs"
|
||||||
version = "0.9.1"
|
version = "0.9.2"
|
||||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
authors = ["MaxOhn <ohn.m@hotmail.de>", "tsunyoku <tsunyoku@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/MaxOhn/rosu-pp"
|
repository = "https://github.com/osuAkatsuki/akatsuki-pp-rs"
|
||||||
documentation = "https://docs.rs/rosu-pp/"
|
|
||||||
description = "osu! difficulty and pp calculation for all modes"
|
description = "osu! difficulty and pp calculation for all modes"
|
||||||
keywords = ["osu", "pp", "stars", "async"]
|
keywords = ["osu", "pp", "stars", "async"]
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -138,13 +138,18 @@ impl<'map> OsuStars<'map> {
|
|||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let aim_difficult_strain_count = skills::OsuStrainSkill::count_difficult_strains(&mut aim);
|
||||||
|
|
||||||
|
// currently unused as relax pp doesn't include speed
|
||||||
|
let speed_difficult_strain_count =
|
||||||
|
skills::OsuStrainSkill::count_difficult_strains(&mut speed);
|
||||||
|
|
||||||
if mods.td() {
|
if mods.td() {
|
||||||
aim_rating = aim_rating.powf(0.8);
|
aim_rating = aim_rating.powf(0.8);
|
||||||
flashlight_rating = flashlight_rating.powf(0.8);
|
flashlight_rating = flashlight_rating.powf(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
if mods.rx() {
|
if mods.rx() {
|
||||||
aim_rating *= 0.9;
|
|
||||||
speed_rating = 0.0;
|
speed_rating = 0.0;
|
||||||
flashlight_rating *= 0.7;
|
flashlight_rating *= 0.7;
|
||||||
}
|
}
|
||||||
@@ -176,6 +181,8 @@ impl<'map> OsuStars<'map> {
|
|||||||
attrs.speed = speed_rating;
|
attrs.speed = speed_rating;
|
||||||
attrs.flashlight = flashlight_rating;
|
attrs.flashlight = flashlight_rating;
|
||||||
attrs.slider_factor = slider_factor;
|
attrs.slider_factor = slider_factor;
|
||||||
|
attrs.aim_difficult_strain_count = aim_difficult_strain_count;
|
||||||
|
attrs.speed_difficult_strain_count = speed_difficult_strain_count;
|
||||||
attrs.stars = star_rating;
|
attrs.stars = star_rating;
|
||||||
attrs.speed_note_count = speed_notes;
|
attrs.speed_note_count = speed_notes;
|
||||||
|
|
||||||
@@ -508,6 +515,10 @@ pub struct OsuDifficultyAttributes {
|
|||||||
pub flashlight: f64,
|
pub flashlight: f64,
|
||||||
/// The ratio of the aim strain with and without considering sliders
|
/// The ratio of the aim strain with and without considering sliders
|
||||||
pub slider_factor: f64,
|
pub slider_factor: f64,
|
||||||
|
/// The count of difficult aim strains.
|
||||||
|
pub aim_difficult_strain_count: f64,
|
||||||
|
/// The count of difficult speed strains.
|
||||||
|
pub speed_difficult_strain_count: f64,
|
||||||
/// The number of clickable objects weighted by difficulty.
|
/// The number of clickable objects weighted by difficulty.
|
||||||
pub speed_note_count: f64,
|
pub speed_note_count: f64,
|
||||||
/// The approach rate.
|
/// The approach rate.
|
||||||
|
|||||||
+14
-307
@@ -388,7 +388,7 @@ struct OsuPpInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OsuPpInner {
|
impl OsuPpInner {
|
||||||
fn calculate(mut self) -> OsuPerformanceAttributes {
|
fn calculate(self) -> OsuPerformanceAttributes {
|
||||||
let total_hits = self.state.total_hits();
|
let total_hits = self.state.total_hits();
|
||||||
|
|
||||||
if total_hits == 0 {
|
if total_hits == 0 {
|
||||||
@@ -410,28 +410,6 @@ impl OsuPpInner {
|
|||||||
multiplier *= 1.0 - (self.attrs.n_spinners as f64 / total_hits).powf(0.85);
|
multiplier *= 1.0 - (self.attrs.n_spinners as f64 / total_hits).powf(0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mods.rx() {
|
|
||||||
// * https://www.desmos.com/calculator/bc9eybdthb
|
|
||||||
// * we use OD13.3 as maximum since it's the value at which great hitwidow becomes 0
|
|
||||||
// * this is well beyond currently maximum achievable OD which is 12.17 (DTx2 + DA with OD11)
|
|
||||||
let (n100_mult, n50_mult) = if self.attrs.od > 0.0 {
|
|
||||||
(
|
|
||||||
1.0 - (self.attrs.od / 13.33).powf(1.8),
|
|
||||||
1.0 - (self.attrs.od / 13.33).powi(5),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(1.0, 1.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
// * As we're adding Oks and Mehs to an approximated number of combo breaks the result can be
|
|
||||||
// * higher than total hits in specific scenarios (which breaks some calculations) so we need to clamp it.
|
|
||||||
self.effective_miss_count = (self.effective_miss_count
|
|
||||||
+ self.state.n100 as f64
|
|
||||||
+ n100_mult
|
|
||||||
+ self.state.n50 as f64 * n50_mult)
|
|
||||||
.min(total_hits);
|
|
||||||
}
|
|
||||||
|
|
||||||
let aim_value = self.compute_aim_value();
|
let aim_value = self.compute_aim_value();
|
||||||
let speed_value = self.compute_speed_value();
|
let speed_value = self.compute_speed_value();
|
||||||
let acc_value = self.compute_accuracy_value();
|
let acc_value = self.compute_accuracy_value();
|
||||||
@@ -460,7 +438,13 @@ impl OsuPpInner {
|
|||||||
|
|
||||||
let total_hits = self.total_hits();
|
let total_hits = self.total_hits();
|
||||||
|
|
||||||
let len_bonus = 0.95
|
let mut len_bonus = 1.0;
|
||||||
|
if self.mods.rx() {
|
||||||
|
if self.effective_miss_count > 0.0 {
|
||||||
|
aim_value *= self.calculate_miss_penalty(self.attrs.aim_difficult_strain_count);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
len_bonus = 0.95
|
||||||
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
||||||
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
|
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
|
||||||
|
|
||||||
@@ -475,10 +459,9 @@ impl OsuPpInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
aim_value *= self.get_combo_scaling_factor();
|
aim_value *= self.get_combo_scaling_factor();
|
||||||
|
}
|
||||||
|
|
||||||
let ar_factor = if self.mods.rx() {
|
let ar_factor = if self.attrs.ar > 10.33 {
|
||||||
0.0
|
|
||||||
} else if self.attrs.ar > 10.33 {
|
|
||||||
0.3 * (self.attrs.ar - 10.33)
|
0.3 * (self.attrs.ar - 10.33)
|
||||||
} else if self.attrs.ar < 8.0 {
|
} else if self.attrs.ar < 8.0 {
|
||||||
0.05 * (8.0 - self.attrs.ar)
|
0.05 * (8.0 - self.attrs.ar)
|
||||||
@@ -671,6 +654,10 @@ impl OsuPpInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn calculate_miss_penalty(&self, strain_count: f64) -> f64 {
|
||||||
|
0.94 / ((self.effective_miss_count / (2.0 * strain_count.sqrt())) + 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
fn total_hits(&self) -> f64 {
|
fn total_hits(&self) -> f64 {
|
||||||
self.state.total_hits() as f64
|
self.state.total_hits() as f64
|
||||||
}
|
}
|
||||||
@@ -738,283 +725,3 @@ impl OsuAttributeProvider for PerformanceAttributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::Beatmap;
|
|
||||||
|
|
||||||
fn test_data() -> (Beatmap, OsuDifficultyAttributes) {
|
|
||||||
let path = "./maps/2785319.osu";
|
|
||||||
let map = Beatmap::from_path(path).unwrap();
|
|
||||||
|
|
||||||
let attrs = OsuDifficultyAttributes {
|
|
||||||
aim: 2.8693628443424104,
|
|
||||||
speed: 2.533869745015772,
|
|
||||||
flashlight: 2.288770487900865,
|
|
||||||
slider_factor: 0.9803052946037858,
|
|
||||||
speed_note_count: 210.36373973116545,
|
|
||||||
ar: 9.300000190734863,
|
|
||||||
od: 8.800000190734863,
|
|
||||||
hp: 5.0,
|
|
||||||
n_circles: 307,
|
|
||||||
n_sliders: 293,
|
|
||||||
n_spinners: 1,
|
|
||||||
stars: 5.669858729379631,
|
|
||||||
max_combo: 909,
|
|
||||||
};
|
|
||||||
|
|
||||||
(map, attrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_n300_n100_n_misses_best() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.n300(300)
|
|
||||||
.n100(20)
|
|
||||||
.n_misses(2)
|
|
||||||
.hitresult_priority(HitResultPriority::BestCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 300,
|
|
||||||
n100: 20,
|
|
||||||
n50: 279,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(state, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_n300_n50_n_misses_best() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.n300(300)
|
|
||||||
.n50(10)
|
|
||||||
.n_misses(2)
|
|
||||||
.hitresult_priority(HitResultPriority::BestCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 300,
|
|
||||||
n100: 289,
|
|
||||||
n50: 10,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(state, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_n50_n_misses_worst() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.n50(10)
|
|
||||||
.n_misses(2)
|
|
||||||
.hitresult_priority(HitResultPriority::WorstCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 0,
|
|
||||||
n100: 589,
|
|
||||||
n50: 10,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(state, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_n300_n100_n50_n_misses_worst() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.n300(300)
|
|
||||||
.n100(50)
|
|
||||||
.n50(10)
|
|
||||||
.n_misses(2)
|
|
||||||
.hitresult_priority(HitResultPriority::WorstCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 300,
|
|
||||||
n100: 50,
|
|
||||||
n50: 249,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(state, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_acc_n_misses_best() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.accuracy(98.0)
|
|
||||||
.n_misses(2)
|
|
||||||
.hitresult_priority(HitResultPriority::BestCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 584,
|
|
||||||
n100: 15,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
expected,
|
|
||||||
"{}% vs {}%",
|
|
||||||
state.accuracy(),
|
|
||||||
expected.accuracy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_acc_n100_n_misses_best() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.accuracy(95.0)
|
|
||||||
.n100(15)
|
|
||||||
.n_misses(2)
|
|
||||||
.hitresult_priority(HitResultPriority::BestCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 562,
|
|
||||||
n100: 15,
|
|
||||||
n50: 22,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
expected,
|
|
||||||
"{}% vs {}%",
|
|
||||||
state.accuracy(),
|
|
||||||
expected.accuracy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_acc_n50_n_misses_best() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.accuracy(95.0)
|
|
||||||
.n50(10)
|
|
||||||
.n_misses(2)
|
|
||||||
.hitresult_priority(HitResultPriority::BestCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 560,
|
|
||||||
n100: 29,
|
|
||||||
n50: 10,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
expected,
|
|
||||||
"{}% vs {}%",
|
|
||||||
state.accuracy(),
|
|
||||||
expected.accuracy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_acc_best() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.accuracy(90.0)
|
|
||||||
.hitresult_priority(HitResultPriority::BestCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 511,
|
|
||||||
n100: 89,
|
|
||||||
n50: 1,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
expected,
|
|
||||||
"{}% vs {}%",
|
|
||||||
state.accuracy(),
|
|
||||||
expected.accuracy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hitresults_acc_worst() {
|
|
||||||
let (map, attrs) = test_data();
|
|
||||||
let max_combo = attrs.max_combo();
|
|
||||||
|
|
||||||
let state = OsuPP::new(&map)
|
|
||||||
.attributes(attrs)
|
|
||||||
.combo(500)
|
|
||||||
.accuracy(90.0)
|
|
||||||
.hitresult_priority(HitResultPriority::WorstCase)
|
|
||||||
.generate_hitresults(max_combo);
|
|
||||||
|
|
||||||
let expected = OsuScoreState {
|
|
||||||
max_combo: 500,
|
|
||||||
n300: 528,
|
|
||||||
n100: 4,
|
|
||||||
n50: 69,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
state,
|
|
||||||
expected,
|
|
||||||
"{}% vs {}%",
|
|
||||||
state.accuracy(),
|
|
||||||
expected.accuracy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+17
-1
@@ -11,6 +11,7 @@ pub(crate) struct Aim {
|
|||||||
curr_section_end: f64,
|
curr_section_end: f64,
|
||||||
pub(crate) strain_peaks: Vec<f64>,
|
pub(crate) strain_peaks: Vec<f64>,
|
||||||
with_sliders: bool,
|
with_sliders: bool,
|
||||||
|
pub(crate) object_strains: Vec<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Aim {
|
impl Aim {
|
||||||
@@ -24,6 +25,7 @@ impl Aim {
|
|||||||
curr_section_end: 0.0,
|
curr_section_end: 0.0,
|
||||||
strain_peaks: Vec::new(),
|
strain_peaks: Vec::new(),
|
||||||
with_sliders,
|
with_sliders,
|
||||||
|
object_strains: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +76,8 @@ impl StrainSkill for Aim {
|
|||||||
self.curr_strain += AimEvaluator::evaluate_diff_of(curr, diff_objects, self.with_sliders)
|
self.curr_strain += AimEvaluator::evaluate_diff_of(curr, diff_objects, self.with_sliders)
|
||||||
* Self::SKILL_MULTIPLIER;
|
* Self::SKILL_MULTIPLIER;
|
||||||
|
|
||||||
|
self.object_strains.push(self.curr_strain);
|
||||||
|
|
||||||
self.curr_strain
|
self.curr_strain
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +97,19 @@ impl StrainSkill for Aim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsuStrainSkill for Aim {}
|
impl OsuStrainSkill for Aim {
|
||||||
|
fn count_difficult_strains(&mut self) -> f64 {
|
||||||
|
let top_strain = self
|
||||||
|
.object_strains
|
||||||
|
.iter()
|
||||||
|
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr));
|
||||||
|
|
||||||
|
self.object_strains
|
||||||
|
.iter()
|
||||||
|
.map(|strain| (strain / top_strain).powi(4))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct AimEvaluator;
|
struct AimEvaluator;
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,11 @@ impl StrainSkill for Flashlight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsuStrainSkill for Flashlight {}
|
impl OsuStrainSkill for Flashlight {
|
||||||
|
fn count_difficult_strains(&mut self) -> f64 {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct FlashlightEvaluator;
|
struct FlashlightEvaluator;
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,18 @@ impl StrainSkill for Speed {
|
|||||||
impl OsuStrainSkill for Speed {
|
impl OsuStrainSkill for Speed {
|
||||||
const REDUCED_SECTION_COUNT: usize = 5;
|
const REDUCED_SECTION_COUNT: usize = 5;
|
||||||
const DIFFICULTY_MULTIPLER: f64 = 1.04;
|
const DIFFICULTY_MULTIPLER: f64 = 1.04;
|
||||||
|
|
||||||
|
fn count_difficult_strains(&mut self) -> f64 {
|
||||||
|
let top_strain = self
|
||||||
|
.object_strains
|
||||||
|
.iter()
|
||||||
|
.fold(f64::NEG_INFINITY, |prev, curr| prev.max(*curr));
|
||||||
|
|
||||||
|
self.object_strains
|
||||||
|
.iter()
|
||||||
|
.map(|strain| (strain / top_strain).powi(4))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SpeedEvaluator;
|
struct SpeedEvaluator;
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
|
|||||||
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
|
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
|
||||||
const DIFFICULTY_MULTIPLER: f64 = 1.06;
|
const DIFFICULTY_MULTIPLER: f64 = 1.06;
|
||||||
|
|
||||||
|
fn count_difficult_strains(&mut self) -> f64;
|
||||||
|
|
||||||
fn difficulty_value(&mut self) -> f64 {
|
fn difficulty_value(&mut self) -> f64 {
|
||||||
let mut difficulty = 0.0;
|
let mut difficulty = 0.0;
|
||||||
let mut weight = 1.0;
|
let mut weight = 1.0;
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
mod mode;
|
|
||||||
|
|
||||||
use rosu_pp::Beatmap;
|
|
||||||
|
|
||||||
pub use self::mode::{Catch, Mania, Mode, Osu, Taiko};
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
#[rustfmt::skip]
|
|
||||||
macro_rules! test_map {
|
|
||||||
($mode:ident) => {{
|
|
||||||
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
|
|
||||||
{ common::test_map::<$mode>() }
|
|
||||||
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
|
|
||||||
{ common::test_map::<$mode>().await }
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "async_tokio", feature = "async_str")))]
|
|
||||||
pub fn test_map<M: Mode>() -> Beatmap {
|
|
||||||
let path = format!("./maps/{}.osu", M::TEST_MAP_ID);
|
|
||||||
|
|
||||||
Beatmap::from_path(path).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(feature = "async_tokio", feature = "async_str"))]
|
|
||||||
pub async fn test_map() -> Beatmap {
|
|
||||||
let path = format!("./maps/{}.osu", M::TEST_MAP_ID);
|
|
||||||
|
|
||||||
Beatmap::from_path(path).await.unwrap()
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
use rosu_pp::{
|
|
||||||
catch::CatchDifficultyAttributes, mania::ManiaDifficultyAttributes,
|
|
||||||
osu::OsuDifficultyAttributes, taiko::TaikoDifficultyAttributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Osu;
|
|
||||||
pub struct Taiko;
|
|
||||||
pub struct Catch;
|
|
||||||
pub struct Mania;
|
|
||||||
|
|
||||||
pub trait Mode {
|
|
||||||
type DifficultyAttributes;
|
|
||||||
|
|
||||||
const TEST_MAP_ID: u32;
|
|
||||||
const TEST_DIFF_ATTRS: Self::DifficultyAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_mode {
|
|
||||||
( $( $mode:ident: $map_id:literal, $diff_attrs:ident $fields:tt; )* ) => {
|
|
||||||
$(
|
|
||||||
impl Mode for $mode {
|
|
||||||
type DifficultyAttributes = $diff_attrs;
|
|
||||||
|
|
||||||
const TEST_MAP_ID: u32 = $map_id;
|
|
||||||
const TEST_DIFF_ATTRS: Self::DifficultyAttributes = $diff_attrs $fields;
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_mode! {
|
|
||||||
Osu: 2785319, OsuDifficultyAttributes {
|
|
||||||
aim: 2.8693628443424104,
|
|
||||||
speed: 2.533869745015772,
|
|
||||||
flashlight: 2.288770487900865,
|
|
||||||
slider_factor: 0.9803052946037858,
|
|
||||||
speed_note_count: 210.36373973116545,
|
|
||||||
ar: 9.300000190734863,
|
|
||||||
od: 8.800000190734863,
|
|
||||||
hp: 5.0,
|
|
||||||
n_circles: 307,
|
|
||||||
n_sliders: 293,
|
|
||||||
n_spinners: 1,
|
|
||||||
stars: 5.669858729379628,
|
|
||||||
max_combo: 909,
|
|
||||||
};
|
|
||||||
Taiko: 1028484, TaikoDifficultyAttributes {
|
|
||||||
stamina: 1.4528845068865617,
|
|
||||||
rhythm: 0.20130047251681948,
|
|
||||||
colour: 1.0487315549761433,
|
|
||||||
peak: 1.8881824429738323,
|
|
||||||
hit_window: 35.0,
|
|
||||||
stars: 2.9778030386845606,
|
|
||||||
max_combo: 289,
|
|
||||||
};
|
|
||||||
Catch: 2118524, CatchDifficultyAttributes {
|
|
||||||
stars: 3.2502669316166624,
|
|
||||||
ar: 8.0,
|
|
||||||
n_fruits: 728,
|
|
||||||
n_droplets: 2,
|
|
||||||
n_tiny_droplets: 291,
|
|
||||||
};
|
|
||||||
Mania: 1974394, ManiaDifficultyAttributes {
|
|
||||||
stars: 4.824631127426499,
|
|
||||||
hit_window: 40.0,
|
|
||||||
max_combo: 5064,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
|
||||||
|
|
||||||
use common::{test_map, Osu};
|
|
||||||
use rosu_pp::{CatchStars, ManiaStars, OsuStars, TaikoStars};
|
|
||||||
|
|
||||||
use crate::common::{Catch, Mania, Mode, Taiko};
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn difficulty_osu() {
|
|
||||||
let map = test_map::<Osu>();
|
|
||||||
let attrs = OsuStars::new(&map).calculate();
|
|
||||||
|
|
||||||
assert_eq!(Osu::TEST_DIFF_ATTRS, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn difficulty_taiko() {
|
|
||||||
let map = test_map::<Taiko>();
|
|
||||||
let attrs = TaikoStars::new(&map).calculate();
|
|
||||||
|
|
||||||
assert_eq!(Taiko::TEST_DIFF_ATTRS, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn difficulty_catch() {
|
|
||||||
let map = test_map::<Catch>();
|
|
||||||
let attrs = CatchStars::new(&map).calculate();
|
|
||||||
|
|
||||||
assert_eq!(Catch::TEST_DIFF_ATTRS, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn difficulty_mania() {
|
|
||||||
let map = test_map::<Mania>();
|
|
||||||
let attrs = ManiaStars::new(&map).calculate();
|
|
||||||
|
|
||||||
assert_eq!(Mania::TEST_DIFF_ATTRS, attrs);
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
|
||||||
|
|
||||||
use rosu_pp::{
|
|
||||||
catch::{CatchGradualDifficultyAttributes, CatchGradualPerformanceAttributes, CatchScoreState},
|
|
||||||
Beatmap, CatchPP, CatchStars,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::common::Catch;
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_map() {
|
|
||||||
let map = Beatmap::default();
|
|
||||||
let mut attributes = CatchGradualDifficultyAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
assert!(attributes.next().is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_end_eq_regular() {
|
|
||||||
let map = test_map!(Catch);
|
|
||||||
let regular = CatchStars::new(&map).calculate();
|
|
||||||
|
|
||||||
let iter_end = CatchGradualDifficultyAttributes::new(&map, 0)
|
|
||||||
.last()
|
|
||||||
.expect("empty iter");
|
|
||||||
|
|
||||||
assert_eq!(regular, iter_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn correct_empty() {
|
|
||||||
let map = test_map!(Catch);
|
|
||||||
let mut gradual = CatchGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let state = CatchScoreState::default();
|
|
||||||
|
|
||||||
let first_attrs = gradual.process_next_n_objects(state.clone(), usize::MAX);
|
|
||||||
|
|
||||||
assert!(first_attrs.is_some());
|
|
||||||
assert!(gradual.process_next_object(state).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn next_and_next_n() {
|
|
||||||
let map = test_map!(Catch);
|
|
||||||
let state = CatchScoreState::default();
|
|
||||||
|
|
||||||
let mut gradual1 = CatchGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let mut gradual2 = CatchGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
for _ in 0..20 {
|
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
|
||||||
let _ = gradual2.process_next_object(state.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let n = 80;
|
|
||||||
|
|
||||||
for _ in 1..n {
|
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = CatchScoreState {
|
|
||||||
max_combo: 101,
|
|
||||||
n_fruits: 99,
|
|
||||||
n_droplets: 2,
|
|
||||||
n_tiny_droplets: 68,
|
|
||||||
n_tiny_droplet_misses: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let next = gradual1.process_next_object(state.clone());
|
|
||||||
let next_n = gradual2.process_next_n_objects(state, n);
|
|
||||||
|
|
||||||
assert_eq!(next_n, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_end_eq_regular() {
|
|
||||||
let map = test_map!(Catch);
|
|
||||||
let regular = CatchPP::new(&map).calculate();
|
|
||||||
|
|
||||||
let mut gradual = CatchGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
let state = CatchScoreState {
|
|
||||||
max_combo: 730,
|
|
||||||
n_fruits: 728,
|
|
||||||
n_droplets: 2,
|
|
||||||
n_tiny_droplets: 291,
|
|
||||||
n_tiny_droplet_misses: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(regular, gradual_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_eq_regular_passed() {
|
|
||||||
let map = test_map!(Catch);
|
|
||||||
let n = 100;
|
|
||||||
|
|
||||||
let regular = CatchPP::new(&map).passed_objects(n).calculate();
|
|
||||||
let mut gradual = CatchGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
let state = CatchScoreState {
|
|
||||||
max_combo: 101,
|
|
||||||
n_fruits: 99,
|
|
||||||
n_droplets: 2,
|
|
||||||
n_tiny_droplets: 68,
|
|
||||||
n_tiny_droplet_misses: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(regular, gradual);
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
|
||||||
|
|
||||||
use rosu_pp::{
|
|
||||||
mania::{ManiaGradualDifficultyAttributes, ManiaGradualPerformanceAttributes, ManiaScoreState},
|
|
||||||
Beatmap, ManiaPP, ManiaStars,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::common::Mania;
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_map() {
|
|
||||||
let map = Beatmap::default();
|
|
||||||
let mut attributes = ManiaGradualDifficultyAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
assert!(attributes.next().is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_end_eq_regular() {
|
|
||||||
let map = test_map!(Mania);
|
|
||||||
let regular = ManiaStars::new(&map).calculate();
|
|
||||||
|
|
||||||
let iter_end = ManiaGradualDifficultyAttributes::new(&map, 0)
|
|
||||||
.last()
|
|
||||||
.expect("empty iter");
|
|
||||||
|
|
||||||
assert_eq!(regular, iter_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn correct_empty() {
|
|
||||||
let map = test_map!(Mania);
|
|
||||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
let state = ManiaScoreState {
|
|
||||||
n320: 0,
|
|
||||||
n300: 0,
|
|
||||||
n200: 0,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_attrs = gradual.process_next_n_objects(state.clone(), usize::MAX);
|
|
||||||
|
|
||||||
assert!(first_attrs.is_some());
|
|
||||||
assert!(gradual.process_next_object(state).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn next_and_next_n() {
|
|
||||||
let map = test_map!(Mania);
|
|
||||||
|
|
||||||
let mut state = ManiaScoreState {
|
|
||||||
n320: 0,
|
|
||||||
n300: 0,
|
|
||||||
n200: 0,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut gradual1 = ManiaGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let mut gradual2 = ManiaGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
for _ in 0..20 {
|
|
||||||
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(state.clone());
|
|
||||||
state.n320 += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let next = gradual1.process_next_object(state.clone());
|
|
||||||
let next_n = gradual2.process_next_n_objects(state, n);
|
|
||||||
|
|
||||||
assert_eq!(next_n, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_end_eq_regular() {
|
|
||||||
let map = test_map!(Mania);
|
|
||||||
let regular = ManiaPP::new(&map).calculate();
|
|
||||||
|
|
||||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_eq_regular_passed() {
|
|
||||||
let map = test_map!(Mania);
|
|
||||||
let n = 100;
|
|
||||||
|
|
||||||
let state = ManiaScoreState {
|
|
||||||
n320: 100,
|
|
||||||
n300: 0,
|
|
||||||
n200: 0,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let regular = ManiaPP::new(&map)
|
|
||||||
.passed_objects(n)
|
|
||||||
.state(state.clone())
|
|
||||||
.calculate();
|
|
||||||
|
|
||||||
let mut gradual = ManiaGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(regular, gradual);
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
|
||||||
|
|
||||||
use rosu_pp::{
|
|
||||||
osu::{OsuGradualDifficultyAttributes, OsuGradualPerformanceAttributes, OsuScoreState},
|
|
||||||
Beatmap, OsuPP, OsuStars,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::common::Osu;
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_map() {
|
|
||||||
let map = Beatmap::default();
|
|
||||||
let mut attributes = OsuGradualDifficultyAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
assert!(attributes.next().is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_end_eq_regular() {
|
|
||||||
let map = test_map!(Osu);
|
|
||||||
let regular = OsuStars::new(&map).calculate();
|
|
||||||
|
|
||||||
let iter_end = OsuGradualDifficultyAttributes::new(&map, 0)
|
|
||||||
.last()
|
|
||||||
.expect("empty iter");
|
|
||||||
|
|
||||||
assert_eq!(regular, iter_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn correct_empty() {
|
|
||||||
let map = test_map!(Osu);
|
|
||||||
let mut gradual = OsuGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let state = OsuScoreState::default();
|
|
||||||
|
|
||||||
let first_attrs = gradual.process_next_n_objects(state.clone(), usize::MAX);
|
|
||||||
|
|
||||||
assert!(first_attrs.is_some());
|
|
||||||
assert!(gradual.process_next_object(state).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn next_and_next_n() {
|
|
||||||
let map = test_map!(Osu);
|
|
||||||
let state = OsuScoreState::default();
|
|
||||||
|
|
||||||
let mut gradual1 = OsuGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let mut gradual2 = OsuGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
for _ in 0..20 {
|
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
|
||||||
let _ = gradual2.process_next_object(state.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let n = 80;
|
|
||||||
|
|
||||||
for _ in 1..n {
|
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = OsuScoreState {
|
|
||||||
max_combo: 122,
|
|
||||||
n300: 88,
|
|
||||||
n100: 8,
|
|
||||||
n50: 2,
|
|
||||||
n_misses: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
let next = gradual1.process_next_object(state.clone());
|
|
||||||
let next_n = gradual2.process_next_n_objects(state, n);
|
|
||||||
|
|
||||||
assert_eq!(next_n, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_end_eq_regular() {
|
|
||||||
let map = test_map!(Osu);
|
|
||||||
let regular = OsuPP::new(&map).calculate();
|
|
||||||
let mut gradual = OsuGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
let state = OsuScoreState {
|
|
||||||
max_combo: 909,
|
|
||||||
n300: 601,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(regular, gradual_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_eq_regular_passed() {
|
|
||||||
let map = test_map!(Osu);
|
|
||||||
let n = 100;
|
|
||||||
|
|
||||||
let regular = OsuPP::new(&map).passed_objects(n).calculate();
|
|
||||||
let mut gradual = OsuGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
let state = OsuScoreState {
|
|
||||||
max_combo: 122,
|
|
||||||
n300: 100,
|
|
||||||
n100: 0,
|
|
||||||
n50: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(regular, gradual);
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
|
||||||
|
|
||||||
use rosu_pp::{
|
|
||||||
taiko::{TaikoGradualDifficultyAttributes, TaikoGradualPerformanceAttributes, TaikoScoreState},
|
|
||||||
Beatmap, TaikoPP, TaikoStars,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::common::Taiko;
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_map() {
|
|
||||||
let map = Beatmap::default();
|
|
||||||
let mut attrs = TaikoGradualDifficultyAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
assert!(attrs.next().is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iter_end_eq_regular() {
|
|
||||||
let map = test_map!(Taiko);
|
|
||||||
let regular = TaikoStars::new(&map).calculate();
|
|
||||||
|
|
||||||
let iter_end = TaikoGradualDifficultyAttributes::new(&map, 0)
|
|
||||||
.last()
|
|
||||||
.expect("empty iter");
|
|
||||||
|
|
||||||
assert_eq!(regular, iter_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn correct_empty() {
|
|
||||||
let map = test_map!(Taiko);
|
|
||||||
let mut gradual = TaikoGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let state = TaikoScoreState::default();
|
|
||||||
|
|
||||||
let first_attrs = gradual.process_next_n_objects(state.clone(), usize::MAX);
|
|
||||||
|
|
||||||
assert!(first_attrs.is_some());
|
|
||||||
assert!(gradual.process_next_object(state).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn next_and_next_n() {
|
|
||||||
let map = test_map!(Taiko);
|
|
||||||
let state = TaikoScoreState::default();
|
|
||||||
|
|
||||||
let mut gradual1 = TaikoGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
let mut gradual2 = TaikoGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
for _ in 0..50 {
|
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
|
||||||
let _ = gradual2.process_next_object(state.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let n = 200;
|
|
||||||
|
|
||||||
for _ in 1..n {
|
|
||||||
let _ = gradual1.process_next_object(state.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = TaikoScoreState {
|
|
||||||
max_combo: 246,
|
|
||||||
n300: 200,
|
|
||||||
n100: 40,
|
|
||||||
n_misses: 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
let next = gradual1.process_next_object(state.clone());
|
|
||||||
let next_n = gradual2.process_next_n_objects(state, n);
|
|
||||||
|
|
||||||
assert_eq!(next_n, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_end_eq_regular() {
|
|
||||||
let map = test_map!(Taiko);
|
|
||||||
let regular = TaikoPP::new(&map).calculate();
|
|
||||||
let mut gradual = TaikoGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
let state = TaikoScoreState {
|
|
||||||
max_combo: 289,
|
|
||||||
n300: 289,
|
|
||||||
n100: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let gradual_end = gradual.process_next_n_objects(state, usize::MAX).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(regular, gradual_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn gradual_eq_regular_passed() {
|
|
||||||
let map = test_map!(Taiko);
|
|
||||||
let n = 250;
|
|
||||||
|
|
||||||
let regular = TaikoPP::new(&map).passed_objects(n).calculate();
|
|
||||||
let mut gradual = TaikoGradualPerformanceAttributes::new(&map, 0);
|
|
||||||
|
|
||||||
let state = TaikoScoreState {
|
|
||||||
max_combo: 246,
|
|
||||||
n300: 246,
|
|
||||||
n100: 0,
|
|
||||||
n_misses: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let gradual = gradual.process_next_n_objects(state, n).unwrap();
|
|
||||||
|
|
||||||
// Cyclic types in difficulty calculation prohibit values to coincide completely
|
|
||||||
assert_eq!(regular.difficulty.max_combo, gradual.difficulty.max_combo);
|
|
||||||
}
|
|
||||||
-103
@@ -1,103 +0,0 @@
|
|||||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
|
||||||
|
|
||||||
use rosu_pp::GameMode;
|
|
||||||
|
|
||||||
use crate::common::{Catch, Mania, Osu, Taiko};
|
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_osu() {
|
|
||||||
let map = test_map!(Osu);
|
|
||||||
|
|
||||||
assert_eq!(map.mode, GameMode::Osu);
|
|
||||||
assert_eq!(map.version, 14);
|
|
||||||
assert_eq!(map.n_circles, 307);
|
|
||||||
assert_eq!(map.n_sliders, 293);
|
|
||||||
assert_eq!(map.n_spinners, 1);
|
|
||||||
assert!((map.ar - 9.3).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.od - 8.8).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.cs - 4.5).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.hp - 5.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.slider_mult - 1.7).abs() <= f64::EPSILON);
|
|
||||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
|
||||||
assert_eq!(map.hit_objects.len(), 601);
|
|
||||||
assert_eq!(map.sounds.len(), 601);
|
|
||||||
assert_eq!(map.timing_points.len(), 1);
|
|
||||||
assert_eq!(map.difficulty_points.len(), 50);
|
|
||||||
assert_eq!(map.effect_points.len(), 131);
|
|
||||||
assert!((map.stack_leniency - 0.5).abs() <= f32::EPSILON);
|
|
||||||
assert_eq!(map.breaks.len(), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_taiko() {
|
|
||||||
let map = test_map!(Taiko);
|
|
||||||
|
|
||||||
assert_eq!(map.mode, GameMode::Taiko);
|
|
||||||
assert_eq!(map.version, 14);
|
|
||||||
assert_eq!(map.n_circles, 289);
|
|
||||||
assert_eq!(map.n_sliders, 4);
|
|
||||||
assert_eq!(map.n_spinners, 2);
|
|
||||||
assert!((map.ar - 8.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.od - 5.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.cs - 2.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.hp - 6.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.slider_mult - 1.4).abs() <= f64::EPSILON);
|
|
||||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
|
||||||
assert_eq!(map.hit_objects.len(), 295);
|
|
||||||
assert_eq!(map.sounds.len(), 295);
|
|
||||||
assert_eq!(map.timing_points.len(), 1);
|
|
||||||
assert_eq!(map.difficulty_points.len(), 3);
|
|
||||||
assert_eq!(map.effect_points.len(), 8);
|
|
||||||
assert!((map.stack_leniency - 0.7).abs() <= f32::EPSILON);
|
|
||||||
assert_eq!(map.breaks.len(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_catch() {
|
|
||||||
let map = test_map!(Catch);
|
|
||||||
|
|
||||||
assert_eq!(map.mode, GameMode::Catch);
|
|
||||||
assert_eq!(map.version, 14);
|
|
||||||
assert_eq!(map.n_circles, 249);
|
|
||||||
assert_eq!(map.n_sliders, 227);
|
|
||||||
assert_eq!(map.n_spinners, 1);
|
|
||||||
assert!((map.ar - 8.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.od - 8.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.cs - 3.5).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.hp - 5.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.slider_mult - 1.45).abs() <= f64::EPSILON);
|
|
||||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
|
||||||
assert_eq!(map.hit_objects.len(), 477);
|
|
||||||
assert_eq!(map.sounds.len(), 477);
|
|
||||||
assert_eq!(map.timing_points.len(), 1);
|
|
||||||
assert_eq!(map.difficulty_points.len(), 0);
|
|
||||||
assert_eq!(map.effect_points.len(), 57);
|
|
||||||
assert!((map.stack_leniency - 0.7).abs() <= f32::EPSILON);
|
|
||||||
assert_eq!(map.breaks.len(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_mania() {
|
|
||||||
let map = test_map!(Mania);
|
|
||||||
|
|
||||||
assert_eq!(map.mode, GameMode::Mania);
|
|
||||||
assert_eq!(map.version, 14);
|
|
||||||
assert_eq!(map.n_circles, 2815);
|
|
||||||
assert_eq!(map.n_sliders, 423);
|
|
||||||
assert_eq!(map.n_spinners, 0);
|
|
||||||
assert!((map.ar - 5.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.od - 8.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.cs - 4.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.hp - 9.0).abs() <= f32::EPSILON);
|
|
||||||
assert!((map.slider_mult - 1.4).abs() <= f64::EPSILON);
|
|
||||||
assert!((map.tick_rate - 1.0).abs() <= f64::EPSILON);
|
|
||||||
assert_eq!(map.hit_objects.len(), 3238);
|
|
||||||
assert_eq!(map.sounds.len(), 3238);
|
|
||||||
assert_eq!(map.timing_points.len(), 1);
|
|
||||||
assert_eq!(map.difficulty_points.len(), 1740);
|
|
||||||
assert_eq!(map.effect_points.len(), 1762);
|
|
||||||
assert!((map.stack_leniency - 0.7).abs() <= f32::EPSILON);
|
|
||||||
assert_eq!(map.breaks.len(), 0)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user