initial rewrite
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
use rosu_pp::{Beatmap, CatchPP, GameMode, ManiaPP, OsuPP, TaikoPP};
|
||||
|
||||
#[test]
|
||||
fn osu() {
|
||||
let map = Beatmap::default();
|
||||
let _ = OsuPP::new(&map).calculate();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taiko() {
|
||||
let mut map = Beatmap::default();
|
||||
|
||||
// convert
|
||||
let _ = TaikoPP::new(&map).calculate();
|
||||
|
||||
// regular
|
||||
map.mode = GameMode::Taiko;
|
||||
let _ = TaikoPP::new(&map).calculate();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catch() {
|
||||
let mut map = Beatmap::default();
|
||||
|
||||
// convert
|
||||
let _ = CatchPP::new(&map).calculate();
|
||||
|
||||
// regular
|
||||
map.mode = GameMode::Catch;
|
||||
let _ = CatchPP::new(&map).calculate();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mania() {
|
||||
let mut map = Beatmap::default();
|
||||
|
||||
// convert
|
||||
let _ = ManiaPP::new(&map).calculate();
|
||||
|
||||
// regular
|
||||
map.mode = GameMode::Mania;
|
||||
let _ = ManiaPP::new(&map).calculate();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// False positive
|
||||
#[allow(unused)]
|
||||
pub use self::{mods::*, paths::*};
|
||||
|
||||
/// Paths to .osu files
|
||||
mod paths {
|
||||
pub const OSU: &str = "./resources/2785319.osu";
|
||||
pub const TAIKO: &str = "./resources/1028484.osu";
|
||||
pub const CATCH: &str = "./resources/2118524.osu";
|
||||
pub const MANIA: &str = "./resources/1638954.osu";
|
||||
}
|
||||
|
||||
/// Bit values for mods
|
||||
mod mods {
|
||||
#![allow(unused)]
|
||||
|
||||
pub const NM: u32 = 0;
|
||||
pub const NF: u32 = 1 << 0;
|
||||
pub const EZ: u32 = 1 << 1;
|
||||
pub const TD: u32 = 1 << 2;
|
||||
pub const HD: u32 = 1 << 3;
|
||||
pub const HR: u32 = 1 << 4;
|
||||
pub const DT: u32 = 1 << 6;
|
||||
pub const HT: u32 = 1 << 8;
|
||||
pub const FL: u32 = 1 << 10;
|
||||
pub const SO: u32 = 1 << 12;
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_eq_float<F: Float>(a: F, b: F) {
|
||||
assert!((a - b).abs() < F::EPSILON, "{a} != {b}")
|
||||
}
|
||||
|
||||
/// Trait to provide flexibility in the `assert_eq_float` function.
|
||||
pub trait Float:
|
||||
Copy + std::fmt::Display + std::ops::Sub<Output = Self> + PartialOrd + Sized
|
||||
{
|
||||
const EPSILON: Self;
|
||||
|
||||
fn abs(self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_float {
|
||||
( $( $ty:ty )* ) => {
|
||||
$(
|
||||
impl Float for $ty {
|
||||
const EPSILON: Self = Self::EPSILON;
|
||||
|
||||
fn abs(self) -> Self {
|
||||
self.abs()
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl_float!(f32 f64);
|
||||
|
||||
/// Trait to compare two instances and panic if they are not equal.
|
||||
pub trait AssertEq {
|
||||
fn assert_eq(&self, expected: &Self);
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
mod mode;
|
||||
|
||||
use rosu_pp::{Beatmap, GameMode};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub use self::mode::{Catch, Mania, Mode, Osu, Taiko};
|
||||
|
||||
static MAPS: OnceLock<[Beatmap; 4]> = OnceLock::new();
|
||||
|
||||
#[macro_export]
|
||||
#[rustfmt::skip]
|
||||
macro_rules! test_map {
|
||||
($mode:ident) => {{
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
{ common::test_map(rosu_pp::GameMode::$mode) }
|
||||
#[cfg(any(feature = "async_tokio", feature = "async_std"))]
|
||||
{ common::test_map(rosu_pp::GameMode::$mode).await }
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
pub fn test_map(mode: GameMode) -> &'static Beatmap {
|
||||
let maps = MAPS.get_or_init(|| {
|
||||
let osu = Beatmap::from_path(path(GameMode::Osu)).unwrap();
|
||||
let taiko = Beatmap::from_path(path(GameMode::Taiko)).unwrap();
|
||||
let catch = Beatmap::from_path(path(GameMode::Catch)).unwrap();
|
||||
let mania = Beatmap::from_path(path(GameMode::Mania)).unwrap();
|
||||
|
||||
[osu, taiko, catch, mania]
|
||||
});
|
||||
|
||||
&maps[mode as usize]
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_tokio")]
|
||||
pub async fn test_map(mode: GameMode) -> &'static Beatmap {
|
||||
let maps = MAPS.get_or_init(|| {
|
||||
let fut = async {
|
||||
let osu = Beatmap::from_path(path(GameMode::Osu)).await.unwrap();
|
||||
let taiko = Beatmap::from_path(path(GameMode::Taiko)).await.unwrap();
|
||||
let catch = Beatmap::from_path(path(GameMode::Catch)).await.unwrap();
|
||||
let mania = Beatmap::from_path(path(GameMode::Mania)).await.unwrap();
|
||||
|
||||
[osu, taiko, catch, mania]
|
||||
};
|
||||
|
||||
match tokio::runtime::Handle::try_current() {
|
||||
Ok(h) => std::thread::spawn(move || h.block_on(fut)).join().unwrap(),
|
||||
Err(_) => tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(fut),
|
||||
}
|
||||
});
|
||||
|
||||
&maps[mode as usize]
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_std")]
|
||||
pub async fn test_map(mode: GameMode) -> &'static Beatmap {
|
||||
let maps = MAPS.get_or_init(|| {
|
||||
async_std::task::block_on(async {
|
||||
let osu = Beatmap::from_path(path(GameMode::Osu)).await.unwrap();
|
||||
let taiko = Beatmap::from_path(path(GameMode::Taiko)).await.unwrap();
|
||||
let catch = Beatmap::from_path(path(GameMode::Catch)).await.unwrap();
|
||||
let mania = Beatmap::from_path(path(GameMode::Mania)).await.unwrap();
|
||||
|
||||
[osu, taiko, catch, mania]
|
||||
})
|
||||
});
|
||||
|
||||
&maps[mode as usize]
|
||||
}
|
||||
|
||||
fn path(mode: GameMode) -> String {
|
||||
let map_id = match mode {
|
||||
GameMode::Osu => <Osu as Mode>::TEST_MAP_ID,
|
||||
GameMode::Taiko => <Taiko as Mode>::TEST_MAP_ID,
|
||||
GameMode::Catch => <Catch as Mode>::TEST_MAP_ID,
|
||||
GameMode::Mania => <Mania as Mode>::TEST_MAP_ID,
|
||||
};
|
||||
|
||||
format!("./maps/{map_id}.osu")
|
||||
}
|
||||
@@ -1,71 +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,
|
||||
is_convert: false,
|
||||
};
|
||||
Catch: 2118524, CatchDifficultyAttributes {
|
||||
stars: 3.2502669316166624,
|
||||
ar: 8.0,
|
||||
n_fruits: 728,
|
||||
n_droplets: 2,
|
||||
n_tiny_droplets: 291,
|
||||
};
|
||||
Mania: 1638954, ManiaDifficultyAttributes {
|
||||
stars: 3.441830819988125,
|
||||
hit_window: 40.0,
|
||||
max_combo: 956,
|
||||
n_objects: 594,
|
||||
is_convert: false,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use rosu_pp::{model::mode::GameMode, Beatmap};
|
||||
|
||||
use crate::common::assert_eq_float;
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn osu() {
|
||||
let map = Beatmap::from_path(common::OSU).unwrap();
|
||||
|
||||
assert_eq!(map.mode, GameMode::Osu);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq_float(map.ar, 9.3);
|
||||
assert_eq_float(map.od, 8.8);
|
||||
assert_eq_float(map.cs, 4.5);
|
||||
assert_eq_float(map.hp, 5.0);
|
||||
assert_eq_float(map.slider_multiplier, 1.7);
|
||||
assert_eq_float(map.slider_tick_rate, 1.0);
|
||||
assert_eq!(map.hit_objects.len(), 601);
|
||||
assert_eq!(map.hit_sounds.len(), 601);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 50);
|
||||
assert_eq!(map.effect_points.len(), 0);
|
||||
assert_eq_float(map.stack_leniency, 0.5);
|
||||
assert_eq!(map.breaks.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taiko() {
|
||||
let map = Beatmap::from_path(common::TAIKO).unwrap();
|
||||
|
||||
assert_eq!(map.mode, GameMode::Taiko);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq_float(map.ar, 8.0);
|
||||
assert_eq_float(map.od, 5.0);
|
||||
assert_eq_float(map.cs, 2.0);
|
||||
assert_eq_float(map.hp, 6.0);
|
||||
assert_eq_float(map.slider_multiplier, 1.4);
|
||||
assert_eq_float(map.slider_tick_rate, 1.0);
|
||||
assert_eq!(map.hit_objects.len(), 295);
|
||||
assert_eq!(map.hit_sounds.len(), 295);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 3);
|
||||
// osu!lazer has 7 effect points because they store scroll speed
|
||||
assert_eq!(map.effect_points.len(), 4);
|
||||
assert_eq_float(map.stack_leniency, 0.7);
|
||||
assert_eq!(map.breaks.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catch() {
|
||||
let map = Beatmap::from_path(common::CATCH).unwrap();
|
||||
|
||||
assert_eq!(map.mode, GameMode::Catch);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq_float(map.ar, 8.0);
|
||||
assert_eq_float(map.od, 8.0);
|
||||
assert_eq_float(map.cs, 3.5);
|
||||
assert_eq_float(map.hp, 5.0);
|
||||
assert_eq_float(map.slider_multiplier, 1.45);
|
||||
assert_eq_float(map.slider_tick_rate, 1.0);
|
||||
assert_eq!(map.hit_objects.len(), 477);
|
||||
assert_eq!(map.hit_sounds.len(), 477);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 0);
|
||||
assert_eq!(map.effect_points.len(), 16);
|
||||
assert_eq_float(map.stack_leniency, 0.7);
|
||||
assert_eq!(map.breaks.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mania() {
|
||||
let map = Beatmap::from_path(common::MANIA).unwrap();
|
||||
|
||||
assert_eq!(map.mode, GameMode::Mania);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq_float(map.ar, 5.0);
|
||||
assert_eq_float(map.od, 8.0);
|
||||
assert_eq_float(map.cs, 4.0);
|
||||
assert_eq_float(map.hp, 8.0);
|
||||
assert_eq_float(map.slider_multiplier, 1.4);
|
||||
assert_eq_float(map.slider_tick_rate, 1.0);
|
||||
assert_eq!(map.hit_objects.len(), 594);
|
||||
assert_eq!(map.hit_sounds.len(), 594);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 0);
|
||||
assert_eq!(map.effect_points.len(), 0);
|
||||
assert_eq_float(map.stack_leniency, 0.7);
|
||||
assert_eq!(map.breaks.len(), 0);
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
use std::panic::{self, UnwindSafe};
|
||||
|
||||
use rosu_pp::{
|
||||
catch::{Catch, CatchDifficultyAttributes},
|
||||
mania::{Mania, ManiaDifficultyAttributes},
|
||||
osu::{Osu, OsuDifficultyAttributes},
|
||||
taiko::{Taiko, TaikoDifficultyAttributes},
|
||||
Beatmap, ModeDifficulty,
|
||||
};
|
||||
|
||||
use self::common::*;
|
||||
|
||||
mod common;
|
||||
|
||||
macro_rules! test_cases {
|
||||
( $mode:ident: $path:ident {
|
||||
$( $( $mods:ident )+ => {
|
||||
$( $key:ident: $value:literal $( , )? )*
|
||||
} $( ; )? )*
|
||||
} ) => {
|
||||
let map = Beatmap::from_path(common::$path)
|
||||
.unwrap()
|
||||
.unchecked_into_converted::<$mode>();
|
||||
|
||||
$(
|
||||
let mods = 0 $( + $mods )*;
|
||||
let expected = test_cases!(@$mode { $( $key: $value, )* });
|
||||
let actual = ModeDifficulty::new().mods(mods).calculate(&map);
|
||||
run(&actual, &expected, mods);
|
||||
)*
|
||||
};
|
||||
( @Osu {
|
||||
aim: $aim:literal,
|
||||
speed: $speed:literal,
|
||||
flashlight: $flashlight:literal,
|
||||
slider_factor: $slider_factor:literal,
|
||||
speed_note_count: $speed_note_count:literal,
|
||||
ar: $ar:literal,
|
||||
od: $od:literal,
|
||||
hp: $hp:literal,
|
||||
n_circles: $n_circles:literal,
|
||||
n_sliders: $n_sliders:literal,
|
||||
n_spinners: $n_spinners:literal,
|
||||
stars: $stars:literal,
|
||||
max_combo: $max_combo:literal,
|
||||
}) => {
|
||||
OsuDifficultyAttributes {
|
||||
aim: $aim,
|
||||
speed: $speed,
|
||||
flashlight: $flashlight,
|
||||
slider_factor: $slider_factor,
|
||||
speed_note_count: $speed_note_count,
|
||||
ar: $ar,
|
||||
od: $od,
|
||||
hp: $hp,
|
||||
n_circles: $n_circles,
|
||||
n_sliders: $n_sliders,
|
||||
n_spinners: $n_spinners,
|
||||
stars: $stars,
|
||||
max_combo: $max_combo,
|
||||
}
|
||||
};
|
||||
( @Taiko {
|
||||
stamina: $stamina:literal,
|
||||
rhythm: $rhythm:literal,
|
||||
color: $color:literal,
|
||||
peak: $peak:literal,
|
||||
hit_window: $hit_window:literal,
|
||||
stars: $stars:literal,
|
||||
max_combo: $max_combo:literal,
|
||||
is_convert: $is_convert:literal,
|
||||
}) => {
|
||||
TaikoDifficultyAttributes {
|
||||
stamina: $stamina,
|
||||
rhythm: $rhythm,
|
||||
color: $color,
|
||||
peak: $peak,
|
||||
hit_window: $hit_window,
|
||||
stars: $stars,
|
||||
max_combo: $max_combo,
|
||||
is_convert: $is_convert,
|
||||
}
|
||||
};
|
||||
( @Catch {
|
||||
stars: $stars:literal,
|
||||
ar: $ar:literal,
|
||||
n_fruits: $n_fruits:literal,
|
||||
n_droplets: $n_droplets:literal,
|
||||
n_tiny_droplets: $n_tiny_droplets:literal,
|
||||
is_convert: $is_convert:literal,
|
||||
}) => {
|
||||
CatchDifficultyAttributes {
|
||||
stars: $stars,
|
||||
ar: $ar,
|
||||
n_fruits: $n_fruits,
|
||||
n_droplets: $n_droplets,
|
||||
n_tiny_droplets: $n_tiny_droplets,
|
||||
is_convert: $is_convert,
|
||||
}
|
||||
};
|
||||
( @Mania {
|
||||
stars: $stars:literal,
|
||||
hit_window: $hit_window:literal,
|
||||
n_objects: $n_objects:literal,
|
||||
max_combo: $max_combo:literal,
|
||||
is_convert: $is_convert:literal,
|
||||
}) => {
|
||||
ManiaDifficultyAttributes {
|
||||
stars: $stars,
|
||||
hit_window: $hit_window,
|
||||
n_objects: $n_objects,
|
||||
max_combo: $max_combo,
|
||||
is_convert: $is_convert,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_osu() {
|
||||
test_cases! {
|
||||
Osu: OSU {
|
||||
NM => {
|
||||
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,
|
||||
};
|
||||
HD => {
|
||||
aim: 2.8693628443424104,
|
||||
speed: 2.533869745015772,
|
||||
flashlight: 2.606877929965889,
|
||||
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,
|
||||
};
|
||||
HR => {
|
||||
aim: 3.2385394176190507,
|
||||
speed: 2.7009854505234308,
|
||||
flashlight: 2.8549217213059936,
|
||||
slider_factor: 0.9690667605258665,
|
||||
speed_note_count: 184.01205359079387,
|
||||
ar: 10.0,
|
||||
od: 10.0,
|
||||
hp: 7.0,
|
||||
n_circles: 307,
|
||||
n_sliders: 293,
|
||||
n_spinners: 1,
|
||||
stars: 6.263576582906263,
|
||||
max_combo: 909,
|
||||
};
|
||||
DT => {
|
||||
aim: 4.041442573946681,
|
||||
speed: 3.6784866216272474,
|
||||
flashlight: 3.319522943625448,
|
||||
slider_factor: 0.9776943279272041,
|
||||
speed_note_count: 214.80421464205617,
|
||||
ar: 10.53333346048991,
|
||||
od: 10.311111238267687,
|
||||
hp: 5.0,
|
||||
n_circles: 307,
|
||||
n_sliders: 293,
|
||||
n_spinners: 1,
|
||||
stars: 8.085307648397622,
|
||||
max_combo: 909,
|
||||
};
|
||||
FL => {
|
||||
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: 6.8667780753884236,
|
||||
max_combo: 909,
|
||||
};
|
||||
HD FL => {
|
||||
aim: 2.8693628443424104,
|
||||
speed: 2.533869745015772,
|
||||
flashlight: 2.606877929965889,
|
||||
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: 7.17258038247615,
|
||||
max_combo: 909,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_taiko() {
|
||||
test_cases! {
|
||||
Taiko: TAIKO {
|
||||
NM => {
|
||||
stamina: 1.4528845068865617,
|
||||
rhythm: 0.20130047251681948,
|
||||
color: 1.0487315549761433,
|
||||
peak: 1.8881824429738323,
|
||||
hit_window: 35.0,
|
||||
stars: 2.9778030386845606,
|
||||
max_combo: 289,
|
||||
is_convert: false,
|
||||
};
|
||||
HR => {
|
||||
stamina: 1.4528845068865617,
|
||||
rhythm: 0.20130047251681948,
|
||||
color: 1.0487315549761433,
|
||||
peak: 1.8881824429738323,
|
||||
hit_window: 29.0,
|
||||
stars: 2.9778030386845606,
|
||||
max_combo: 289,
|
||||
is_convert: false,
|
||||
};
|
||||
DT => {
|
||||
stamina: 2.054617838297644,
|
||||
rhythm: 0.4448175371191029,
|
||||
color: 1.3637624960988888,
|
||||
peak: 2.6393434317991886,
|
||||
hit_window: 23.333333333333332,
|
||||
stars: 3.9605501866340607,
|
||||
max_combo: 289,
|
||||
is_convert: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_taiko() {
|
||||
test_cases! {
|
||||
Taiko: OSU {
|
||||
NM => {
|
||||
stamina: 2.947896529153566,
|
||||
rhythm: 1.4696991260446617,
|
||||
color: 2.3032281729649067,
|
||||
peak: 4.130240422926277,
|
||||
hit_window: 23.59999942779541,
|
||||
stars: 5.247857660585606,
|
||||
max_combo: 908,
|
||||
is_convert: true,
|
||||
};
|
||||
HR => {
|
||||
stamina: 2.947896529153566,
|
||||
rhythm: 1.4696991260446617,
|
||||
color: 2.3032281729649067,
|
||||
peak: 4.130240422926277,
|
||||
hit_window: 20.0,
|
||||
stars: 5.247857660585606,
|
||||
max_combo: 908,
|
||||
is_convert: true,
|
||||
};
|
||||
DT => {
|
||||
stamina: 4.412382708370478,
|
||||
rhythm: 2.002843919169095,
|
||||
color: 3.1864894777399986,
|
||||
peak: 6.107962386775966,
|
||||
hit_window: 15.733332951863607,
|
||||
stars: 7.0140481946324815,
|
||||
max_combo: 908,
|
||||
is_convert: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_catch() {
|
||||
test_cases! {
|
||||
Catch: CATCH {
|
||||
NM => {
|
||||
stars: 3.2502663133739844,
|
||||
ar: 8.0,
|
||||
n_fruits: 728,
|
||||
n_droplets: 2,
|
||||
n_tiny_droplets: 291,
|
||||
is_convert: false,
|
||||
};
|
||||
HR => {
|
||||
stars: 4.3148698646484265,
|
||||
ar: 10.0,
|
||||
n_fruits: 728,
|
||||
n_droplets: 2,
|
||||
n_tiny_droplets: 291,
|
||||
is_convert: false,
|
||||
};
|
||||
EZ => {
|
||||
stars: 4.021637906259923,
|
||||
ar: 4.0,
|
||||
n_fruits: 728,
|
||||
n_droplets: 2,
|
||||
n_tiny_droplets: 291,
|
||||
is_convert: false,
|
||||
};
|
||||
DT => {
|
||||
stars: 4.635262826575387,
|
||||
ar: 9.666666666666668,
|
||||
n_fruits: 728,
|
||||
n_droplets: 2,
|
||||
n_tiny_droplets: 291,
|
||||
is_convert: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_catch() {
|
||||
test_cases! {
|
||||
Catch: OSU {
|
||||
NM => {
|
||||
stars: 4.513884094512871,
|
||||
ar: 9.300000190734863,
|
||||
n_fruits: 908,
|
||||
n_droplets: 0,
|
||||
n_tiny_droplets: 159,
|
||||
is_convert: true,
|
||||
};
|
||||
HR => {
|
||||
stars: 5.082061773944862,
|
||||
ar: 10.0,
|
||||
n_fruits: 908,
|
||||
n_droplets: 0,
|
||||
n_tiny_droplets: 159,
|
||||
is_convert: true,
|
||||
};
|
||||
EZ => {
|
||||
stars: 3.598951063172104,
|
||||
ar: 4.650000095367432,
|
||||
n_fruits: 908,
|
||||
n_droplets: 0,
|
||||
n_tiny_droplets: 159,
|
||||
is_convert: true,
|
||||
};
|
||||
DT => {
|
||||
stars: 6.136837738350475,
|
||||
ar: 10.53333346048991,
|
||||
n_fruits: 908,
|
||||
n_droplets: 0,
|
||||
n_tiny_droplets: 159,
|
||||
is_convert: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_mania() {
|
||||
test_cases! {
|
||||
Mania: MANIA {
|
||||
NM => {
|
||||
stars: 3.441830819988125,
|
||||
hit_window: 40.0,
|
||||
n_objects: 594,
|
||||
max_combo: 956,
|
||||
is_convert: false,
|
||||
};
|
||||
DT => {
|
||||
stars: 4.70051326060948,
|
||||
hit_window: 40.0,
|
||||
n_objects: 594,
|
||||
max_combo: 956,
|
||||
is_convert: false,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_mania() {
|
||||
test_cases! {
|
||||
Mania: OSU {
|
||||
NM => {
|
||||
stars: 3.2033142085672255,
|
||||
hit_window: 34.0,
|
||||
n_objects: 1046,
|
||||
max_combo: 1381,
|
||||
is_convert: true,
|
||||
};
|
||||
DT => {
|
||||
stars: 4.2934063021960185,
|
||||
hit_window: 34.0,
|
||||
n_objects: 1046,
|
||||
max_combo: 1381,
|
||||
is_convert: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn run<A>(actual: &A, expected: &A, mods: u32)
|
||||
where
|
||||
A: AssertEq,
|
||||
for<'a> &'a A: UnwindSafe,
|
||||
{
|
||||
if panic::catch_unwind(|| actual.assert_eq(expected)).is_err() {
|
||||
panic!("Mods: {mods}");
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for OsuDifficultyAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.aim, expected.aim);
|
||||
assert_eq_float(self.speed, expected.speed);
|
||||
assert_eq_float(self.flashlight, expected.flashlight);
|
||||
assert_eq_float(self.slider_factor, expected.slider_factor);
|
||||
assert_eq_float(self.speed_note_count, expected.speed_note_count);
|
||||
assert_eq_float(self.ar, expected.ar);
|
||||
assert_eq_float(self.od, expected.od);
|
||||
assert_eq_float(self.hp, expected.hp);
|
||||
assert_eq!(self.n_circles, expected.n_circles);
|
||||
assert_eq!(self.n_sliders, expected.n_sliders);
|
||||
assert_eq!(self.n_spinners, expected.n_spinners);
|
||||
assert_eq_float(self.stars, expected.stars);
|
||||
assert_eq!(self.max_combo, expected.max_combo);
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for TaikoDifficultyAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.stamina, expected.stamina);
|
||||
assert_eq_float(self.rhythm, expected.rhythm);
|
||||
assert_eq_float(self.color, expected.color);
|
||||
assert_eq_float(self.peak, expected.peak);
|
||||
assert_eq_float(self.hit_window, expected.hit_window);
|
||||
assert_eq_float(self.stars, expected.stars);
|
||||
assert_eq!(self.max_combo, expected.max_combo);
|
||||
assert_eq!(self.is_convert, expected.is_convert);
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for CatchDifficultyAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.stars, expected.stars);
|
||||
assert_eq_float(self.ar, expected.ar);
|
||||
assert_eq!(self.n_fruits, expected.n_fruits);
|
||||
assert_eq!(self.n_droplets, expected.n_droplets);
|
||||
assert_eq!(self.n_tiny_droplets, expected.n_tiny_droplets);
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for ManiaDifficultyAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.stars, expected.stars);
|
||||
assert_eq_float(self.hit_window, expected.hit_window);
|
||||
assert_eq!(self.n_objects, expected.n_objects);
|
||||
assert_eq!(self.max_combo, expected.max_combo);
|
||||
assert_eq!(self.is_convert, expected.is_convert);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#![cfg(all(
|
||||
not(any(feature = "async_tokio", feature = "async_std")),
|
||||
feature = "gradual"
|
||||
))]
|
||||
|
||||
use rosu_pp::{
|
||||
catch::{
|
||||
CatchGradualDifficulty, CatchGradualPerformance, CatchOwnedGradualPerformance,
|
||||
CatchScoreState,
|
||||
},
|
||||
Beatmap, BeatmapExt, CatchPP,
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn empty_map() {
|
||||
let map = Beatmap::default();
|
||||
let mut attributes = CatchGradualDifficulty::new(&map, 0);
|
||||
|
||||
assert!(attributes.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_end_eq_regular() {
|
||||
let map = test_map!(Catch);
|
||||
let regular = CatchPP::new(map).calculate();
|
||||
|
||||
let mut gradual = CatchGradualPerformance::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.nth(state, usize::MAX).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual_end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_complete_next() {
|
||||
let map = test_map!(Catch);
|
||||
let mods = 88; // HDHRDT
|
||||
let n_objects = dbg!(map.catch_hitobjects(mods).len());
|
||||
|
||||
let mut gradual = CatchGradualPerformance::new(map, mods);
|
||||
let mut gradual_2nd = CatchGradualPerformance::new(map, mods);
|
||||
let mut gradual_3rd = CatchGradualPerformance::new(map, mods);
|
||||
let mut gradual_owned = CatchOwnedGradualPerformance::new(map.to_owned(), mods);
|
||||
|
||||
let mut state = CatchScoreState::default();
|
||||
|
||||
for i in 1.. {
|
||||
state.n_misses += 1;
|
||||
|
||||
let Some(next_gradual) = gradual.next(state.clone()) else {
|
||||
assert_eq!(i, n_objects + 1);
|
||||
assert!(gradual_2nd.last(state.clone()).is_some() || n_objects % 2 == 0);
|
||||
assert!(gradual_3rd.last(state.clone()).is_some() || n_objects % 3 == 0);
|
||||
assert!(gradual_owned.next(state.clone()).is_none());
|
||||
break;
|
||||
};
|
||||
|
||||
if i % 2 == 0 {
|
||||
let next_gradual_2nd = gradual_2nd.nth(state.clone(), 1).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_2nd, "i={i}");
|
||||
}
|
||||
|
||||
if i % 3 == 0 {
|
||||
let next_gradual_3rd = gradual_3rd.nth(state.clone(), 2).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_3rd, "i={i}");
|
||||
}
|
||||
|
||||
let next_gradual_owned = gradual_owned.next(state.clone()).unwrap();
|
||||
|
||||
let mut regular_calc = CatchPP::new(map)
|
||||
.mods(mods)
|
||||
.passed_objects(i)
|
||||
.state(state.clone());
|
||||
|
||||
let _regular_state = regular_calc.generate_state();
|
||||
// FIXME: juicestreams are currently added to the attributes in their entirety
|
||||
// so the state won't change while objects of the same juicestream are processed
|
||||
// assert_eq!(state, _regular_state);
|
||||
|
||||
let regular = regular_calc.calculate();
|
||||
|
||||
assert_eq!(next_gradual, next_gradual_owned, "i={i}");
|
||||
assert_eq!(next_gradual, regular, "i={i}");
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
#![cfg(all(
|
||||
not(any(feature = "async_tokio", feature = "async_std")),
|
||||
feature = "gradual"
|
||||
))]
|
||||
|
||||
use rosu_pp::{
|
||||
mania::{
|
||||
ManiaGradualDifficulty, ManiaGradualPerformance, ManiaOwnedGradualPerformance,
|
||||
ManiaScoreState,
|
||||
},
|
||||
Beatmap, ManiaPP,
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn empty_map() {
|
||||
let map = Beatmap::default();
|
||||
let mut attributes = ManiaGradualDifficulty::new(&map, 0);
|
||||
|
||||
assert!(attributes.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_end_eq_regular() {
|
||||
let map = test_map!(Mania);
|
||||
let regular = ManiaPP::new(map).calculate();
|
||||
|
||||
let state = ManiaScoreState {
|
||||
n320: map.hit_objects.len(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let gradual = ManiaGradualPerformance::new(map, 0).last(state).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_complete_next() {
|
||||
let map = test_map!(Mania);
|
||||
let mods = 67; // NFEZDT
|
||||
|
||||
let mut gradual = ManiaGradualPerformance::new(map, mods);
|
||||
let mut gradual_2nd = ManiaGradualPerformance::new(map, mods);
|
||||
let mut gradual_3rd = ManiaGradualPerformance::new(map, mods);
|
||||
let mut gradual_owned = ManiaOwnedGradualPerformance::new(map.to_owned(), mods);
|
||||
|
||||
let mut state = ManiaScoreState::default();
|
||||
|
||||
for i in 1.. {
|
||||
state.n320 += 1;
|
||||
|
||||
let Some(next_gradual) = gradual.next(state.clone()) else {
|
||||
assert_eq!(i, map.hit_objects.len() + 1);
|
||||
assert!(gradual_2nd.last(state.clone()).is_some() || map.hit_objects.len() % 2 == 0);
|
||||
assert!(gradual_3rd.last(state.clone()).is_some() || map.hit_objects.len() % 3 == 0);
|
||||
assert!(gradual_owned.next(state.clone()).is_none());
|
||||
break;
|
||||
};
|
||||
|
||||
if i % 2 == 0 {
|
||||
let next_gradual_2nd = gradual_2nd.nth(state.clone(), 1).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_2nd, "i={i}");
|
||||
}
|
||||
|
||||
if i % 3 == 0 {
|
||||
let next_gradual_3rd = gradual_3rd.nth(state.clone(), 2).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_3rd, "i={i}");
|
||||
}
|
||||
|
||||
let next_gradual_owned = gradual_owned.next(state.clone()).unwrap();
|
||||
|
||||
let mut regular_calc = ManiaPP::new(map)
|
||||
.mods(mods)
|
||||
.passed_objects(i)
|
||||
.state(state.clone());
|
||||
|
||||
let regular_state = regular_calc.generate_state();
|
||||
assert_eq!(state, regular_state);
|
||||
|
||||
let regular = regular_calc.calculate();
|
||||
|
||||
assert_eq!(next_gradual, next_gradual_owned, "i={i}");
|
||||
assert_eq!(next_gradual, regular, "i={i}");
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#![cfg(all(
|
||||
not(any(feature = "async_tokio", feature = "async_std")),
|
||||
feature = "gradual"
|
||||
))]
|
||||
|
||||
use rosu_pp::osu::OsuOwnedGradualPerformance;
|
||||
use rosu_pp::{
|
||||
osu::{OsuGradualDifficulty, OsuGradualPerformance, OsuScoreState},
|
||||
Beatmap, OsuPP,
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn empty_map() {
|
||||
let map = Beatmap::default();
|
||||
let mut attributes = OsuGradualDifficulty::new(&map, 0);
|
||||
|
||||
assert!(attributes.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_end_eq_regular() {
|
||||
let map = test_map!(Osu);
|
||||
let regular = OsuPP::new(map).calculate();
|
||||
let mut gradual = OsuGradualPerformance::new(map, 0);
|
||||
|
||||
let state = OsuScoreState {
|
||||
max_combo: 909,
|
||||
n300: 601,
|
||||
n100: 0,
|
||||
n50: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let gradual_end = gradual.last(state.clone()).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual_end);
|
||||
assert!(gradual.next(state).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_complete_next() {
|
||||
let map = test_map!(Osu);
|
||||
let mods = 88; // HDHRDT
|
||||
|
||||
let mut gradual = OsuGradualPerformance::new(map, mods);
|
||||
let mut gradual_2nd = OsuGradualPerformance::new(map, mods);
|
||||
let mut gradual_3rd = OsuGradualPerformance::new(map, mods);
|
||||
let mut gradual_owned = OsuOwnedGradualPerformance::new(map.to_owned(), mods);
|
||||
|
||||
let mut state = OsuScoreState::default();
|
||||
|
||||
for i in 1.. {
|
||||
state.n_misses += 1;
|
||||
|
||||
let Some(next_gradual) = gradual.next(state.clone()) else {
|
||||
assert_eq!(i, map.hit_objects.len() + 1);
|
||||
assert!(gradual_2nd.last(state.clone()).is_some() || map.hit_objects.len() % 2 == 0);
|
||||
assert!(gradual_3rd.last(state.clone()).is_some() || map.hit_objects.len() % 3 == 0);
|
||||
assert!(gradual_owned.next(state.clone()).is_none());
|
||||
break;
|
||||
};
|
||||
|
||||
if i % 2 == 0 {
|
||||
let next_gradual_2nd = gradual_2nd.nth(state.clone(), 1).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_2nd, "i={i}");
|
||||
}
|
||||
|
||||
if i % 3 == 0 {
|
||||
let next_gradual_3rd = gradual_3rd.nth(state.clone(), 2).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_3rd, "i={i}");
|
||||
}
|
||||
|
||||
let next_gradual_owned = gradual_owned.next(state.clone()).unwrap();
|
||||
|
||||
let mut regular_calc = OsuPP::new(map)
|
||||
.mods(mods)
|
||||
.passed_objects(i)
|
||||
.state(state.clone());
|
||||
|
||||
let regular_state = regular_calc.generate_state();
|
||||
assert_eq!(state, regular_state);
|
||||
|
||||
let regular = regular_calc.calculate();
|
||||
|
||||
assert_eq!(next_gradual, next_gradual_owned, "i={i}");
|
||||
assert_eq!(next_gradual, regular, "i={i}");
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#![cfg(all(
|
||||
not(any(feature = "async_tokio", feature = "async_std")),
|
||||
feature = "gradual"
|
||||
))]
|
||||
|
||||
use rosu_pp::{
|
||||
taiko::{
|
||||
TaikoGradualDifficulty, TaikoGradualPerformance, TaikoOwnedGradualPerformance,
|
||||
TaikoScoreState,
|
||||
},
|
||||
Beatmap, TaikoPP,
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn empty_map() {
|
||||
let map = Beatmap::default();
|
||||
let mut attrs = TaikoGradualDifficulty::new(&map, 0);
|
||||
|
||||
assert!(attrs.next().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_end_eq_regular() {
|
||||
let map = test_map!(Taiko);
|
||||
let regular = TaikoPP::new(map).calculate();
|
||||
let mut gradual = TaikoGradualPerformance::new(map, 0);
|
||||
|
||||
let state = TaikoScoreState {
|
||||
max_combo: 289,
|
||||
n300: 289,
|
||||
n100: 0,
|
||||
n_misses: 0,
|
||||
};
|
||||
|
||||
let gradual_end = gradual.last(state).unwrap();
|
||||
|
||||
assert_eq!(regular, gradual_end);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gradual_complete_next() {
|
||||
let map = test_map!(Taiko);
|
||||
let mods = 88; // HDHRDT
|
||||
|
||||
let mut gradual = TaikoGradualPerformance::new(map, mods);
|
||||
let mut gradual_2nd = TaikoGradualPerformance::new(map, mods);
|
||||
let mut gradual_3rd = TaikoGradualPerformance::new(map, mods);
|
||||
let mut gradual_owned = TaikoOwnedGradualPerformance::new(map.to_owned(), mods);
|
||||
|
||||
let mut state = TaikoScoreState::default();
|
||||
|
||||
for i in 1.. {
|
||||
state.n_misses += 1;
|
||||
|
||||
let Some(next_gradual) = gradual.next(state.clone()) else {
|
||||
assert_eq!(i, map.n_circles as usize + 1);
|
||||
assert!(gradual_2nd.last(state.clone()).is_some() || map.hit_objects.len() % 2 == 0);
|
||||
assert!(gradual_3rd.last(state.clone()).is_some() || map.hit_objects.len() % 3 == 0);
|
||||
assert!(gradual_owned.next(state.clone()).is_none());
|
||||
break;
|
||||
};
|
||||
|
||||
if i % 2 == 0 {
|
||||
let next_gradual_2nd = gradual_2nd.nth(state.clone(), 1).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_2nd, "i={i}");
|
||||
}
|
||||
|
||||
if i % 3 == 0 {
|
||||
let next_gradual_3rd = gradual_3rd.nth(state.clone(), 2).unwrap();
|
||||
assert_eq!(next_gradual, next_gradual_3rd, "i={i}");
|
||||
}
|
||||
|
||||
let next_gradual_owned = gradual_owned.next(state.clone()).unwrap();
|
||||
|
||||
let mut regular_calc = TaikoPP::new(map)
|
||||
.mods(mods)
|
||||
.passed_objects(i)
|
||||
.state(state.clone());
|
||||
|
||||
let regular_state = regular_calc.generate_state();
|
||||
assert_eq!(state, regular_state);
|
||||
|
||||
let regular = regular_calc.calculate();
|
||||
|
||||
assert_eq!(next_gradual, next_gradual_owned, "i={i}");
|
||||
assert_eq!(next_gradual, regular, "i={i}");
|
||||
}
|
||||
}
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
use rosu_pp::{Beatmap, GameMode};
|
||||
|
||||
mod common;
|
||||
|
||||
#[cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
mod sync {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_osu() {
|
||||
assert_osu(test_map!(Osu));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_taiko() {
|
||||
assert_taiko(test_map!(Taiko));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_catch() {
|
||||
assert_catch(test_map!(Catch));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mania() {
|
||||
assert_mania(test_map!(Mania));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_tokio")]
|
||||
mod async_tokio {
|
||||
use tokio::runtime::Builder as RuntimeBuilder;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_osu() {
|
||||
RuntimeBuilder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { assert_osu(test_map!(Osu)) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_taiko() {
|
||||
RuntimeBuilder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { assert_taiko(test_map!(Taiko)) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_catch() {
|
||||
RuntimeBuilder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { assert_catch(test_map!(Catch)) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mania() {
|
||||
RuntimeBuilder::new_current_thread()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { assert_mania(test_map!(Mania)) });
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async_std")]
|
||||
mod async_tokio {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_osu() {
|
||||
async_std::task::block_on(async { assert_osu(test_map!(Osu)) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_taiko() {
|
||||
async_std::task::block_on(async { assert_taiko(test_map!(Taiko)) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_catch() {
|
||||
async_std::task::block_on(async { assert_catch(test_map!(Catch)) });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_mania() {
|
||||
async_std::task::block_on(async { assert_mania(test_map!(Mania)) });
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_osu(map: &Beatmap) {
|
||||
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)
|
||||
}
|
||||
|
||||
fn assert_taiko(map: &Beatmap) {
|
||||
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)
|
||||
}
|
||||
|
||||
fn assert_catch(map: &Beatmap) {
|
||||
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)
|
||||
}
|
||||
|
||||
fn assert_mania(map: &Beatmap) {
|
||||
assert_eq!(map.mode, GameMode::Mania);
|
||||
assert_eq!(map.version, 14);
|
||||
assert_eq!(map.n_circles, 473);
|
||||
assert_eq!(map.n_sliders, 121);
|
||||
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 - 8.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(), 594);
|
||||
assert_eq!(map.sounds.len(), 594);
|
||||
assert_eq!(map.timing_points.len(), 1);
|
||||
assert_eq!(map.difficulty_points.len(), 0);
|
||||
assert_eq!(map.effect_points.len(), 1);
|
||||
assert!((map.stack_leniency - 0.7).abs() <= f32::EPSILON);
|
||||
assert_eq!(map.breaks.len(), 0)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
#![cfg(not(any(feature = "async_tokio", feature = "async_std")))]
|
||||
|
||||
use rosu_pp::{CatchPP, ManiaPP, OsuPP, TaikoPP};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn osu() {
|
||||
let map = test_map!(Osu);
|
||||
|
||||
let mods = 8 + 64;
|
||||
let misses = 2;
|
||||
|
||||
let regular = OsuPP::new(map).mods(mods).n_misses(misses).calculate();
|
||||
|
||||
let via_diff = OsuPP::from(regular.difficulty.clone())
|
||||
.mods(mods)
|
||||
.n_misses(misses)
|
||||
.calculate();
|
||||
|
||||
assert_eq!(regular, via_diff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taiko() {
|
||||
let map = test_map!(Taiko);
|
||||
|
||||
let mods = 8 + 64;
|
||||
let misses = 2;
|
||||
|
||||
let regular = TaikoPP::new(map).mods(mods).n_misses(misses).calculate();
|
||||
|
||||
let via_diff = TaikoPP::from(regular.difficulty.clone())
|
||||
.mods(mods)
|
||||
.n_misses(misses)
|
||||
.calculate();
|
||||
|
||||
assert_eq!(regular, via_diff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catch() {
|
||||
let map = test_map!(Catch);
|
||||
|
||||
let mods = 8 + 64;
|
||||
let misses = 2;
|
||||
|
||||
let regular = CatchPP::new(map).mods(mods).misses(misses).calculate();
|
||||
|
||||
let via_diff = CatchPP::from(regular.difficulty.clone())
|
||||
.mods(mods)
|
||||
.misses(misses)
|
||||
.calculate();
|
||||
|
||||
assert_eq!(regular, via_diff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mania() {
|
||||
let map = test_map!(Mania);
|
||||
|
||||
let mods = 8 + 64;
|
||||
let misses = 2;
|
||||
|
||||
let regular = ManiaPP::new(map).mods(mods).n_misses(misses).calculate();
|
||||
|
||||
let via_diff = ManiaPP::from(regular.difficulty.clone())
|
||||
.mods(mods)
|
||||
.n_misses(misses)
|
||||
.calculate();
|
||||
|
||||
assert_eq!(regular, via_diff);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
use std::panic::{self, UnwindSafe};
|
||||
|
||||
use rosu_pp::{
|
||||
catch::{CatchPerformance, CatchPerformanceAttributes},
|
||||
mania::{ManiaPerformance, ManiaPerformanceAttributes},
|
||||
osu::{OsuPerformance, OsuPerformanceAttributes},
|
||||
taiko::{TaikoPerformance, TaikoPerformanceAttributes},
|
||||
Beatmap,
|
||||
};
|
||||
|
||||
use self::common::*;
|
||||
|
||||
mod common;
|
||||
|
||||
macro_rules! test_cases {
|
||||
( $mode:ident: $path:ident {
|
||||
$( $( $mods:ident )+ => {
|
||||
$( $key:ident: $value:literal $( , )? )*
|
||||
} ;)*
|
||||
} ) => {
|
||||
let map = Beatmap::from_path(common::$path)
|
||||
.unwrap()
|
||||
.unchecked_into_converted();
|
||||
|
||||
$(
|
||||
let mods = 0 $( + $mods )*;
|
||||
let (calc, expected) = test_cases!(@$mode { map, $( $key: $value, )* });
|
||||
let actual = calc.mods(mods).calculate();
|
||||
run(&actual, &expected, mods);
|
||||
)*
|
||||
};
|
||||
( @Osu {
|
||||
$map:ident,
|
||||
pp: $pp:literal,
|
||||
pp_acc: $pp_acc:literal,
|
||||
pp_aim: $pp_aim:literal,
|
||||
pp_flashlight: $pp_flashlight:literal,
|
||||
pp_speed: $pp_speed:literal,
|
||||
effective_miss_count: $effective_miss_count:literal,
|
||||
}) => {
|
||||
(
|
||||
OsuPerformance::from($map.as_owned()),
|
||||
OsuPerformanceAttributes {
|
||||
pp: $pp,
|
||||
pp_acc: $pp_acc,
|
||||
pp_aim: $pp_aim,
|
||||
pp_flashlight: $pp_flashlight,
|
||||
pp_speed: $pp_speed,
|
||||
effective_miss_count: $effective_miss_count,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
};
|
||||
( @Taiko {
|
||||
$map: ident,
|
||||
pp: $pp:literal,
|
||||
pp_acc: $pp_acc:literal,
|
||||
pp_difficulty: $pp_difficulty:literal,
|
||||
effective_miss_count: $effective_miss_count:literal,
|
||||
}) => {
|
||||
(
|
||||
TaikoPerformance::from($map.as_owned()),
|
||||
TaikoPerformanceAttributes {
|
||||
pp: $pp,
|
||||
pp_acc: $pp_acc,
|
||||
pp_difficulty: $pp_difficulty,
|
||||
effective_miss_count: $effective_miss_count,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
};
|
||||
( @Catch {
|
||||
$map:ident,
|
||||
pp: $pp:literal,
|
||||
}) => {
|
||||
(
|
||||
CatchPerformance::from($map.as_owned()),
|
||||
CatchPerformanceAttributes {
|
||||
pp: $pp,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
};
|
||||
( @Mania {
|
||||
$map:ident,
|
||||
pp: $pp:literal,
|
||||
pp_difficulty: $pp_difficulty:literal,
|
||||
}) => {
|
||||
(
|
||||
ManiaPerformance::from($map.as_owned()),
|
||||
ManiaPerformanceAttributes {
|
||||
pp: $pp,
|
||||
pp_difficulty: $pp_difficulty,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_osu() {
|
||||
test_cases! {
|
||||
Osu: OSU {
|
||||
NM => {
|
||||
pp: 255.9419635475736,
|
||||
pp_acc: 79.84500076626814,
|
||||
pp_aim: 98.13131344235279,
|
||||
pp_flashlight: 0.0,
|
||||
pp_speed: 69.86876965478146,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
HD => {
|
||||
pp: 281.28736211196446,
|
||||
pp_acc: 86.2326008275696,
|
||||
pp_aim: 108.72949454544438,
|
||||
pp_flashlight: 0.0,
|
||||
pp_speed: 77.41459624444144,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
EZ HD => {
|
||||
pp: 185.64881287702838,
|
||||
pp_acc: 13.59914468151693,
|
||||
pp_aim: 96.88083530160195,
|
||||
pp_flashlight: 0.0,
|
||||
pp_speed: 65.96268917477774,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
HR => {
|
||||
pp: 375.0764291059058,
|
||||
pp_acc: 132.13521300659738,
|
||||
pp_aim: 143.28598037767793,
|
||||
pp_flashlight: 0.0,
|
||||
pp_speed: 87.39375701955078,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
DT => {
|
||||
pp: 716.3683237855254,
|
||||
pp_acc: 150.5694857734174,
|
||||
pp_aim: 300.39084638572484,
|
||||
pp_flashlight: 0.0,
|
||||
pp_speed: 240.8765306794618,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
FL => {
|
||||
pp: 384.8917879591265,
|
||||
pp_acc: 81.4419007815935,
|
||||
pp_aim: 98.13131344235279,
|
||||
pp_flashlight: 132.3991950960219,
|
||||
pp_speed: 69.86876965478146,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
HD FL => {
|
||||
pp: 450.3709760368082,
|
||||
pp_acc: 87.95725284412099,
|
||||
pp_aim: 108.72949454544438,
|
||||
pp_flashlight: 171.7600847331662,
|
||||
pp_speed: 77.41459624444144,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_taiko() {
|
||||
test_cases! {
|
||||
Taiko: TAIKO {
|
||||
NM => {
|
||||
pp: 98.47602106219567,
|
||||
pp_acc: 46.11642717726248,
|
||||
pp_difficulty: 46.69844233558799,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
HD => {
|
||||
pp: 107.19493857245885,
|
||||
pp_acc: 46.11642717726248,
|
||||
pp_difficulty: 47.86590339397769,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
HR => {
|
||||
pp: 112.22705475791287,
|
||||
pp_acc: 56.71431676265808,
|
||||
pp_difficulty: 49.033364452367394,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
DT => {
|
||||
pp: 181.5021832786881,
|
||||
pp_acc: 80.74206626516394,
|
||||
pp_difficulty: 90.29961105452931,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_taiko() {
|
||||
test_cases! {
|
||||
Taiko: OSU {
|
||||
NM => {
|
||||
pp: 324.23564627433217,
|
||||
pp_acc: 125.81086361861148,
|
||||
pp_difficulty: 179.31471072573842,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
HD => {
|
||||
pp: 353.7513933816713,
|
||||
pp_acc: 125.81086361861148,
|
||||
pp_difficulty: 183.79757849388187,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
HR => {
|
||||
pp: 360.12274556551137,
|
||||
pp_acc: 150.9344373000759,
|
||||
pp_difficulty: 188.28044626202535,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
DT => {
|
||||
pp: 604.8167434609272,
|
||||
pp_acc: 220.7055264311451,
|
||||
pp_difficulty: 347.90986552791844,
|
||||
effective_miss_count: 0.0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_catch() {
|
||||
test_cases! {
|
||||
Catch: CATCH {
|
||||
NM => { pp: 113.85903714373049 };
|
||||
HD => { pp: 136.63084457247658 };
|
||||
HD HR => { pp: 231.90266535529486 };
|
||||
DT => { pp: 247.18402249125862 };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_catch() {
|
||||
test_cases! {
|
||||
Catch: OSU {
|
||||
NM => { pp: 230.99937552589745 };
|
||||
HD => { pp: 254.6768082128294 };
|
||||
HD HR => { pp: 328.41201070443725 };
|
||||
DT => { pp: 500.4365349891725 };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_mania() {
|
||||
test_cases! {
|
||||
Mania: MANIA {
|
||||
NM => { pp: 114.37175184134917, pp_difficulty: 14.296468980168646 };
|
||||
EZ => { pp: 57.18587592067458, pp_difficulty: 14.296468980168646 };
|
||||
DT => { pp: 233.17882161546717, pp_difficulty: 29.147352701933396 };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_mania() {
|
||||
test_cases! {
|
||||
Mania: OSU {
|
||||
NM => { pp: 99.73849552661329, pp_difficulty: 12.467311940826661 };
|
||||
EZ => { pp: 49.869247763306646, pp_difficulty: 12.467311940826661 };
|
||||
DT => { pp: 195.23247718805612, pp_difficulty: 24.404059648507015 };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn run<A>(actual: &A, expected: &A, mods: u32)
|
||||
where
|
||||
A: AssertEq,
|
||||
for<'a> &'a A: UnwindSafe,
|
||||
{
|
||||
if panic::catch_unwind(|| actual.assert_eq(expected)).is_err() {
|
||||
panic!("Mods: {mods}");
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for OsuPerformanceAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.pp, expected.pp);
|
||||
assert_eq_float(self.pp_acc, expected.pp_acc);
|
||||
assert_eq_float(self.pp_aim, expected.pp_aim);
|
||||
assert_eq_float(self.pp_flashlight, expected.pp_flashlight);
|
||||
assert_eq_float(self.pp_speed, expected.pp_speed);
|
||||
assert_eq_float(self.effective_miss_count, expected.effective_miss_count);
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for TaikoPerformanceAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.pp, expected.pp);
|
||||
assert_eq_float(self.pp_acc, expected.pp_acc);
|
||||
assert_eq_float(self.pp_difficulty, expected.pp_difficulty);
|
||||
assert_eq_float(self.effective_miss_count, expected.effective_miss_count);
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for CatchPerformanceAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.pp, expected.pp);
|
||||
}
|
||||
}
|
||||
|
||||
impl AssertEq for ManiaPerformanceAttributes {
|
||||
fn assert_eq(&self, expected: &Self) {
|
||||
assert_eq_float(self.pp_difficulty, expected.pp_difficulty);
|
||||
assert_eq_float(self.pp, expected.pp);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user