start base
This commit is contained in:
+5
-6
@@ -1,12 +1,11 @@
|
||||
[package]
|
||||
name = "rosu-pp"
|
||||
version = "0.9.1"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>"]
|
||||
edition = "2018"
|
||||
name = "akatsuki-pp-rs"
|
||||
version = "0.9.2"
|
||||
authors = ["MaxOhn <ohn.m@hotmail.de>", "tsunyoku <tsunyoku@gmail.com>"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/MaxOhn/rosu-pp"
|
||||
documentation = "https://docs.rs/rosu-pp/"
|
||||
repository = "https://github.com/osuAkatsuki/akatsuki-pp-rs"
|
||||
description = "osu! difficulty and pp calculation for all modes"
|
||||
keywords = ["osu", "pp", "stars", "async"]
|
||||
|
||||
|
||||
+12
-1
@@ -138,13 +138,18 @@ impl<'map> OsuStars<'map> {
|
||||
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() {
|
||||
aim_rating = aim_rating.powf(0.8);
|
||||
flashlight_rating = flashlight_rating.powf(0.8);
|
||||
}
|
||||
|
||||
if mods.rx() {
|
||||
aim_rating *= 0.9;
|
||||
speed_rating = 0.0;
|
||||
flashlight_rating *= 0.7;
|
||||
}
|
||||
@@ -176,6 +181,8 @@ impl<'map> OsuStars<'map> {
|
||||
attrs.speed = speed_rating;
|
||||
attrs.flashlight = flashlight_rating;
|
||||
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.speed_note_count = speed_notes;
|
||||
|
||||
@@ -508,6 +515,10 @@ pub struct OsuDifficultyAttributes {
|
||||
pub flashlight: f64,
|
||||
/// The ratio of the aim strain with and without considering sliders
|
||||
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.
|
||||
pub speed_note_count: f64,
|
||||
/// The approach rate.
|
||||
|
||||
+25
-318
@@ -388,7 +388,7 @@ struct OsuPpInner {
|
||||
}
|
||||
|
||||
impl OsuPpInner {
|
||||
fn calculate(mut self) -> OsuPerformanceAttributes {
|
||||
fn calculate(self) -> OsuPerformanceAttributes {
|
||||
let total_hits = self.state.total_hits();
|
||||
|
||||
if total_hits == 0 {
|
||||
@@ -410,28 +410,6 @@ impl OsuPpInner {
|
||||
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 speed_value = self.compute_speed_value();
|
||||
let acc_value = self.compute_accuracy_value();
|
||||
@@ -460,25 +438,30 @@ impl OsuPpInner {
|
||||
|
||||
let total_hits = self.total_hits();
|
||||
|
||||
let len_bonus = 0.95
|
||||
+ 0.4 * (total_hits / 2000.0).min(1.0)
|
||||
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
|
||||
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)
|
||||
+ (total_hits > 2000.0) as u8 as f64 * (total_hits / 2000.0).log10() * 0.5;
|
||||
|
||||
aim_value *= len_bonus;
|
||||
aim_value *= len_bonus;
|
||||
|
||||
// * Penalize misses by assessing # of misses relative to the total # of objects.
|
||||
// * Default a 3% reduction for any # of misses.
|
||||
if self.effective_miss_count > 0.0 {
|
||||
aim_value *= 0.97
|
||||
* (1.0 - (self.effective_miss_count / total_hits).powf(0.775))
|
||||
.powf(self.effective_miss_count);
|
||||
// * Penalize misses by assessing # of misses relative to the total # of objects.
|
||||
// * Default a 3% reduction for any # of misses.
|
||||
if self.effective_miss_count > 0.0 {
|
||||
aim_value *= 0.97
|
||||
* (1.0 - (self.effective_miss_count / total_hits).powf(0.775))
|
||||
.powf(self.effective_miss_count);
|
||||
}
|
||||
|
||||
aim_value *= self.get_combo_scaling_factor();
|
||||
}
|
||||
|
||||
aim_value *= self.get_combo_scaling_factor();
|
||||
|
||||
let ar_factor = if self.mods.rx() {
|
||||
0.0
|
||||
} else if self.attrs.ar > 10.33 {
|
||||
let ar_factor = if self.attrs.ar > 10.33 {
|
||||
0.3 * (self.attrs.ar - 10.33)
|
||||
} else if self.attrs.ar < 8.0 {
|
||||
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 {
|
||||
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,
|
||||
pub(crate) strain_peaks: Vec<f64>,
|
||||
with_sliders: bool,
|
||||
pub(crate) object_strains: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Aim {
|
||||
@@ -24,6 +25,7 @@ impl Aim {
|
||||
curr_section_end: 0.0,
|
||||
strain_peaks: Vec::new(),
|
||||
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::SKILL_MULTIPLIER;
|
||||
|
||||
self.object_strains.push(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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -118,6 +118,18 @@ impl StrainSkill for Speed {
|
||||
impl OsuStrainSkill for Speed {
|
||||
const REDUCED_SECTION_COUNT: usize = 5;
|
||||
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;
|
||||
|
||||
@@ -90,6 +90,8 @@ pub(crate) trait OsuStrainSkill: StrainSkill + Sized {
|
||||
const REDUCED_STRAIN_BASELINE: f64 = 0.75;
|
||||
const DIFFICULTY_MULTIPLER: f64 = 1.06;
|
||||
|
||||
fn count_difficult_strains(&mut self) -> f64;
|
||||
|
||||
fn difficulty_value(&mut self) -> f64 {
|
||||
let mut difficulty = 0.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