added documentation

This commit is contained in:
MaxOhn
2021-01-17 22:11:27 +01:00
parent bbc6698fc7
commit 5e01f867ce
22 changed files with 350 additions and 96 deletions
+80 -5
View File
@@ -2,13 +2,88 @@
A standalone crate to calculate star ratings and performance points for all [osu!](https://osu.ppy.sh/home) gamemodes. A standalone crate to calculate star ratings and performance points for all [osu!](https://osu.ppy.sh/home) gamemodes.
**Most of it is still a WIP.** Conversions are generally not supported.
### Roadmap #### Usage
```rust
use std::fs::File;
use rosu_pp::{Beatmap, BeatmapExt, GameMode, OsuPP, TaikoPP};
fn main() {
let file = match File::open("/path/to/file.osu") {
Ok(file) => file,
Err(why) => panic!("Could not open file: {}", why),
};
// Parse the map yourself
let map = match Beatmap::parse(file) {
Ok(map) => map,
Err(why) => panic!("Error while parsing map: {}", why),
};
// The different modes make things annoying because their
// pp calculations require different parameters.
// For now, you will have to match on the mode yourself
// to be able to set all options for pp calculation.
match map.mode {
GameMode::STD => {
let result = OsuPP::new(&map)
.mods(24) // HDHR
.combo(1234)
.misses(2)
.accuracy(99.2)
// `no_leniency` is the suggested default
.calculate(rosu_pp::osu::no_leniency::stars);
println!("PP: {}", result.pp);
// If you intend to reuse the current map-mod combination,
// make use of the attributes in the result.
// If attributes are given, then stars & co don't have to be recalculated.
let next_result = OsuPP::new(&map)
.mods(24) // HDHR
.attributes(result.attributes)
.combo(543)
.misses(5)
.n50(3)
.passed_objects(612)
.accuracy(97.5)
.calculate(rosu_pp::osu::no_leniency::stars);
println!("Next PP: {}", next_result.pp);
},
GameMode::TKO => {
let result = TaikoPP::new(&map)
.mods(64) // DT
.combo(555)
.misses(10)
.accuracy(95.12345)
.calculate();
println!("Stars: {} | PP: {}", result.stars, result.pp);
}
GameMode::MNA | GameMode::CTB => unimplemented!(),
}
// If all you want is the map's stars or max pp,
// you can make use of the BeatmapExt trait.
let stars = map.stars(16, None); // HR
let max_pp = map.max_pp(16);
println!("Stars: {} | Max PP: {}", stars, max_pp);
}
```
#### osu!standard versions
- `all_included`: WIP
- `no_leniency`: The positional offset of notes created by stack leniency is not considered. This means the jump distance inbetween notes might be slightly off, resulting in small inaccuracies. Since calculating these offsets is relatively expensive though, this version is considerably faster than `all_included`.
- `no_slider_no_leniency` (i.e. oppai): In addtion to not considering the positional offset caused by stack leniency, slider paths are also ignored. This means the travel distance of notes is completely omitted which may cause further inaccuracies. Since the slider paths don't have to be computed though, it should generally be faster than `no_leniency`.
#### Roadmap
- osu sr versions - osu sr versions
- [x] no_sliders_no_leniency (i.e. oppai)
- [x] no_leniency
- [ ] all included - [ ] all included
- [x] no_leniency
- [x] no_sliders_no_leniency (i.e. oppai)
- [x] taiko sr - [x] taiko sr
- [x] ctb sr - [x] ctb sr
- [x] mania sr - [x] mania sr
@@ -18,5 +93,5 @@ A standalone crate to calculate star ratings and performance points for all [osu
- [x] ctb pp - [x] ctb pp
- [x] mania pp - [x] mania pp
--- ---
- [ ] refactoring - [x] refactoring
- [ ] benchmarking - [ ] benchmarking
+6 -4
View File
@@ -382,7 +382,9 @@ impl<I: Iterator<Item = CatchObject>> Iterator for FruitOrJuice<I> {
} }
} }
#[derive(Default)] /// Various data created through the star calculation.
/// This data is necessary to calculate PP.
#[derive(Clone, Debug, Default)]
pub struct DifficultyAttributes { pub struct DifficultyAttributes {
pub stars: f32, pub stars: f32,
pub max_combo: usize, pub max_combo: usize,
@@ -400,7 +402,7 @@ mod tests {
#[test] #[test]
#[ignore] #[ignore]
fn fruits_single() { fn fruits_single() {
let file = match File::open("E:/Games/osu!/beatmaps/1974968.osu") { let file = match File::open("./maps/1974968.osu") {
Ok(file) => file, Ok(file) => file,
Err(why) => panic!("Could not open file: {}", why), Err(why) => panic!("Could not open file: {}", why),
}; };
@@ -410,9 +412,9 @@ mod tests {
Err(why) => panic!("Error while parsing map: {}", why), Err(why) => panic!("Error while parsing map: {}", why),
}; };
let result = PpCalculator::new(&map).mods(0).calculate(); let result = FruitsPP::new(&map).mods(0).calculate();
println!("Stars: {}", result.stars); println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp); println!("PP: {}", result.pp);
} }
} }
+26 -15
View File
@@ -1,23 +1,16 @@
use super::{stars, DifficultyAttributes}; use super::{stars, DifficultyAttributes};
use crate::{Beatmap, Mods}; use crate::{Beatmap, Mods};
/// Basic struct containing the result of a PP calculation.
/// In osu!ctb's case, this will be the pp value and the
/// difficulty attributes created by star calculation.
pub struct PpResult { pub struct PpResult {
pub pp: f32, pub pp: f32,
pub stars: f32, pub attributes: DifficultyAttributes,
} }
pub trait PpProvider { /// Calculator for pp on osu!ctb maps.
fn pp(&self) -> PpCalculator; pub struct FruitsPP<'m> {
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
map: &'m Beatmap, map: &'m Beatmap,
attributes: Option<DifficultyAttributes>, attributes: Option<DifficultyAttributes>,
mods: u32, mods: u32,
@@ -31,7 +24,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>, passed_objects: Option<usize>,
} }
impl<'m> PpCalculator<'m> { impl<'m> FruitsPP<'m> {
#[inline] #[inline]
pub fn new(map: &'m Beatmap) -> Self { pub fn new(map: &'m Beatmap) -> Self {
Self { Self {
@@ -49,6 +42,13 @@ impl<'m> PpCalculator<'m> {
} }
} }
/// [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
/// stay the same for each map-mod combination.
/// If you already calculated them, be sure to put them in here so
/// that they don't have to be recalculated.
///
/// The final object after calling `calculation` will contain the
/// attributes again for later reuse.
#[inline] #[inline]
pub fn attributes(mut self, attributes: DifficultyAttributes) -> Self { pub fn attributes(mut self, attributes: DifficultyAttributes) -> Self {
self.attributes.replace(attributes); self.attributes.replace(attributes);
@@ -56,6 +56,9 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify mods through their bit values.
///
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
#[inline] #[inline]
pub fn mods(mut self, mods: u32) -> Self { pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods; self.mods = mods;
@@ -63,6 +66,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the max combo of the play.
#[inline] #[inline]
pub fn combo(mut self, combo: usize) -> Self { pub fn combo(mut self, combo: usize) -> Self {
self.combo.replace(combo); self.combo.replace(combo);
@@ -70,6 +74,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of fruits of a play i.e. n300.
#[inline] #[inline]
pub fn fruits(mut self, n_fruits: usize) -> Self { pub fn fruits(mut self, n_fruits: usize) -> Self {
self.n_fruits.replace(n_fruits); self.n_fruits.replace(n_fruits);
@@ -77,6 +82,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of droplets of a play i.e. n100.
#[inline] #[inline]
pub fn droplets(mut self, n_droplets: usize) -> Self { pub fn droplets(mut self, n_droplets: usize) -> Self {
self.n_droplets.replace(n_droplets); self.n_droplets.replace(n_droplets);
@@ -84,6 +90,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of tiny droplets of a play.
#[inline] #[inline]
pub fn tiny_droplets(mut self, n_tiny_droplets: usize) -> Self { pub fn tiny_droplets(mut self, n_tiny_droplets: usize) -> Self {
self.n_tiny_droplets.replace(n_tiny_droplets); self.n_tiny_droplets.replace(n_tiny_droplets);
@@ -91,6 +98,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of tiny droplet misses of a play.
#[inline] #[inline]
pub fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: usize) -> Self { pub fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: usize) -> Self {
self.n_tiny_droplet_misses.replace(n_tiny_droplet_misses); self.n_tiny_droplet_misses.replace(n_tiny_droplet_misses);
@@ -98,6 +106,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of fruit / droplet misses of the play.
#[inline] #[inline]
pub fn misses(mut self, n_misses: usize) -> Self { pub fn misses(mut self, n_misses: usize) -> Self {
self.n_misses = n_misses; self.n_misses = n_misses;
@@ -152,6 +161,8 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Returns an object which contains the pp and [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
/// containing stars and other attributes.
pub fn calculate(mut self) -> PpResult { pub fn calculate(mut self) -> PpResult {
let attributes = self let attributes = self
.attributes .attributes
@@ -216,7 +227,7 @@ impl<'m> PpCalculator<'m> {
pp *= 0.9; pp *= 0.9;
} }
PpResult { pp, stars } PpResult { pp, attributes }
} }
#[inline] #[inline]
+144 -4
View File
@@ -1,3 +1,99 @@
//! A standalone crate to calculate star ratings and performance points for all [osu!](https://osu.ppy.sh/home) gamemodes.
//!
//! Conversions are generally not supported.
//!
//! ### Usage
//! ```rust,no_run
//! use std::fs::File;
//! use rosu_pp::{Beatmap, BeatmapExt, GameMode, OsuPP, TaikoPP};
//!
//! fn main() {
//! let file = match File::open("/path/to/file.osu") {
//! Ok(file) => file,
//! Err(why) => panic!("Could not open file: {}", why),
//! };
//!
//! // Parse the map yourself
//! let map = match Beatmap::parse(file) {
//! Ok(map) => map,
//! Err(why) => panic!("Error while parsing map: {}", why),
//! };
//!
//! // The different modes make things annoying because their
//! // pp calculations require different parameters.
//! // For now, you will have to match on the mode yourself
//! // to be able to set all options for pp calculation.
//! match map.mode {
//! GameMode::STD => {
//! let result = OsuPP::new(&map)
//! .mods(24) // HDHR
//! .combo(1234)
//! .misses(2)
//! .accuracy(99.2)
//! // `no_leniency` is the suggested default
//! .calculate(rosu_pp::osu::no_leniency::stars);
//!
//! println!("PP: {}", result.pp);
//!
//! // If you intend to reuse the current map-mod combination,
//! // make use of the attributes in the result.
//! // If attributes are given, then stars & co don't have to be recalculated.
//! let next_result = OsuPP::new(&map)
//! .mods(24) // HDHR
//! .attributes(result.attributes)
//! .combo(543)
//! .misses(5)
//! .n50(3)
//! .passed_objects(612)
//! .accuracy(97.5)
//! .calculate(rosu_pp::osu::no_leniency::stars);
//!
//! println!("Next PP: {}", next_result.pp);
//! },
//! GameMode::TKO => {
//! let result = TaikoPP::new(&map)
//! .mods(64) // DT
//! .combo(555)
//! .misses(10)
//! .accuracy(95.12345)
//! .calculate();
//!
//! println!("Stars: {} | PP: {}", result.stars, result.pp);
//! }
//! GameMode::MNA | GameMode::CTB => unimplemented!(),
//! }
//!
//! // If all you want is the map's stars or max pp,
//! // you can make use of the BeatmapExt trait.
//! let stars = map.stars(16, None); // HR
//! let max_pp = map.max_pp(16);
//!
//! println!("Stars: {} | Max PP: {}", stars, max_pp);
//! }
//! ```
//!
//! ### osu!standard versions
//! - `all_included`: WIP
//! - `no_leniency`: The positional offset of notes created by stack leniency is not considered. This means the jump distance inbetween notes might be slightly off, resulting in small inaccuracies. Since calculating these offsets is relatively expensive though, this version is considerably faster than `all_included`.
//! - `no_slider_no_leniency` (i.e. oppai): In addtion to not considering the positional offset caused by stack leniency, slider paths are also ignored. This means the travel distance of notes is completely omitted which may cause further inaccuracies. Since the slider paths don't have to be computed though, it should generally be faster than `no_leniency`.
//!
//! ### Roadmap
//! - osu sr versions
//! - [ ] all included
//! - [x] no_leniency
//! - [x] no_sliders_no_leniency (i.e. oppai)
//! - [x] taiko sr
//! - [x] ctb sr
//! - [x] mania sr
//! ---
//! - [x] osu pp
//! - [x] taiko pp
//! - [x] ctb pp
//! - [x] mania pp
//! ---
//! - [x] refactoring
//! - [ ] benchmarking
pub mod fruits; pub mod fruits;
pub mod mania; pub mod mania;
pub mod osu; pub mod osu;
@@ -8,11 +104,55 @@ mod curve;
mod math_util; mod math_util;
mod mods; mod mods;
pub use mods::Mods; pub use fruits::FruitsPP;
pub use parse::*; pub use mania::ManiaPP;
pub use osu::OsuPP;
pub use taiko::TaikoPP;
// TODO pub use mods::Mods;
pub trait BeatmapExt {} pub use parse::{
Beatmap, BeatmapAttributes, DifficultyPoint, GameMode, HitObject, HitObjectKind, HitSound,
PathType, Pos2, TimingPoint,
};
pub trait BeatmapExt {
fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> f32;
fn max_pp(&self, mods: u32) -> f32;
}
impl BeatmapExt for Beatmap {
/// Calculate the stars of a beatmap.
///
/// For osu!standard maps, the `no_leniency` version will be used.
fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> f32 {
match self.mode {
GameMode::STD => osu::no_leniency::stars(self, mods, passed_objects).stars,
GameMode::MNA => mania::stars(self, mods, passed_objects),
GameMode::TKO => taiko::stars(self, mods, passed_objects),
GameMode::CTB => fruits::stars(self, mods, passed_objects).stars,
}
}
/// Calculate the max pp of a beatmap if that is all you want.
///
/// For osu!standard maps, the `no_leniency` version will be used.
///
/// If you seek more fine-tuning and options you need to match on the map's
/// mode and use the mode's corresponding calculator, e.g. [`TaikoPP`](crate::TaikoPP).
fn max_pp(&self, mods: u32) -> f32 {
match self.mode {
GameMode::STD => {
OsuPP::new(self)
.mods(mods)
.calculate(osu::no_leniency::stars)
.pp
}
GameMode::MNA => ManiaPP::new(self).mods(mods).calculate().pp,
GameMode::TKO => TaikoPP::new(self).mods(mods).calculate().pp,
GameMode::CTB => FruitsPP::new(self).mods(mods).calculate().pp,
}
}
}
#[inline] #[inline]
fn difficulty_range(val: f32, max: f32, avg: f32, min: f32) -> f32 { fn difficulty_range(val: f32, max: f32, avg: f32, min: f32) -> f32 {
+1 -1
View File
@@ -99,7 +99,7 @@ mod tests {
Err(why) => panic!("Error while parsing map: {}", why), Err(why) => panic!("Error while parsing map: {}", why),
}; };
let result = PpCalculator::new(&map).mods(256).calculate(); let result = ManiaPP::new(&map).mods(256).calculate();
println!("Stars: {}", result.stars); println!("Stars: {}", result.stars);
println!("PP: {}", result.pp); println!("PP: {}", result.pp);
+13 -14
View File
@@ -1,23 +1,15 @@
use super::stars; use super::stars;
use crate::{Beatmap, Mods}; use crate::{Beatmap, Mods};
/// Basic struct containing the result of a PP calculation.
/// In osu!mania's case, this will be just the pp value and the star value.
pub struct PpResult { pub struct PpResult {
pub pp: f32, pub pp: f32,
pub stars: f32, pub stars: f32,
} }
pub trait PpProvider { /// Calculator for pp on osu!mania maps.
fn pp(&self) -> PpCalculator; pub struct ManiaPP<'m> {
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
map: &'m Beatmap, map: &'m Beatmap,
stars: Option<f32>, stars: Option<f32>,
mods: u32, mods: u32,
@@ -25,7 +17,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>, passed_objects: Option<usize>,
} }
impl<'m> PpCalculator<'m> { impl<'m> ManiaPP<'m> {
#[inline] #[inline]
pub fn new(map: &'m Beatmap) -> Self { pub fn new(map: &'m Beatmap) -> Self {
Self { Self {
@@ -37,6 +29,9 @@ impl<'m> PpCalculator<'m> {
} }
} }
/// If you already know the stars of the map with the current mods,
/// you should specify them so that they don't have to be calculated
/// again while calculating PP.
#[inline] #[inline]
pub fn stars(mut self, stars: f32) -> Self { pub fn stars(mut self, stars: f32) -> Self {
self.stars.replace(stars); self.stars.replace(stars);
@@ -44,6 +39,9 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify mods through their bit values.
///
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
#[inline] #[inline]
pub fn mods(mut self, mods: u32) -> Self { pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods; self.mods = mods;
@@ -52,7 +50,7 @@ impl<'m> PpCalculator<'m> {
} }
/// Score of a play. /// Score of a play.
/// On NM its between 0 and 1,000,000, on EZ between 0 and 500,000, etc /// On `NoMod` its between 0 and 1,000,000, on `Easy` between 0 and 500,000, etc
#[inline] #[inline]
pub fn score(mut self, score: u32) -> Self { pub fn score(mut self, score: u32) -> Self {
self.score.replace(score as f32); self.score.replace(score as f32);
@@ -68,6 +66,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Returns an object which contains the pp and stars.
pub fn calculate(self) -> PpResult { pub fn calculate(self) -> PpResult {
let stars = self let stars = self
.stars .stars
+3 -1
View File
@@ -4,7 +4,9 @@ mod versions;
pub use pp::*; pub use pp::*;
pub use versions::*; pub use versions::*;
#[derive(Clone, Default, Debug)] /// Various data created through the star calculation.
/// This data is necessary to calculate PP.
#[derive(Clone, Debug, Default)]
pub struct DifficultyAttributes { pub struct DifficultyAttributes {
pub stars: f32, pub stars: f32,
pub ar: f32, pub ar: f32,
+25 -15
View File
@@ -1,23 +1,16 @@
use super::DifficultyAttributes as Attributes; use super::DifficultyAttributes as Attributes;
use crate::{Beatmap, Mods}; use crate::{Beatmap, Mods};
/// Basic struct containing the result of a PP calculation.
/// In osu!standard's case, this will be the pp value and the
/// difficulty attributes created by star calculation.
pub struct PpResult { pub struct PpResult {
pub pp: f32, pub pp: f32,
pub attributes: Attributes, pub attributes: Attributes,
} }
pub trait PpProvider { /// Calculator for pp on osu!standard maps.
fn pp(&self) -> PpCalculator; pub struct OsuPP<'m> {
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
map: &'m Beatmap, map: &'m Beatmap,
attributes: Option<Attributes>, attributes: Option<Attributes>,
mods: u32, mods: u32,
@@ -31,7 +24,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>, passed_objects: Option<usize>,
} }
impl<'m> PpCalculator<'m> { impl<'m> OsuPP<'m> {
#[inline] #[inline]
pub fn new(map: &'m Beatmap) -> Self { pub fn new(map: &'m Beatmap) -> Self {
Self { Self {
@@ -49,6 +42,13 @@ impl<'m> PpCalculator<'m> {
} }
} }
/// [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
/// stay the same for each map-mod combination.
/// If you already calculated them, be sure to put them in here so
/// that they don't have to be recalculated.
///
/// The final object after calling `calculation` will contain the
/// attributes again for later reuse.
#[inline] #[inline]
pub fn attributes(mut self, attributes: Attributes) -> Self { pub fn attributes(mut self, attributes: Attributes) -> Self {
self.attributes.replace(attributes); self.attributes.replace(attributes);
@@ -56,6 +56,9 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify mods through their bit values.
///
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
#[inline] #[inline]
pub fn mods(mut self, mods: u32) -> Self { pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods; self.mods = mods;
@@ -63,6 +66,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the max combo of the play.
#[inline] #[inline]
pub fn combo(mut self, combo: usize) -> Self { pub fn combo(mut self, combo: usize) -> Self {
self.combo.replace(combo); self.combo.replace(combo);
@@ -70,6 +74,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of 300s of a play.
#[inline] #[inline]
pub fn n300(mut self, n300: usize) -> Self { pub fn n300(mut self, n300: usize) -> Self {
self.n300.replace(n300); self.n300.replace(n300);
@@ -77,6 +82,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of 100s of a play.
#[inline] #[inline]
pub fn n100(mut self, n100: usize) -> Self { pub fn n100(mut self, n100: usize) -> Self {
self.n100.replace(n100); self.n100.replace(n100);
@@ -84,6 +90,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of 50s of a play.
#[inline] #[inline]
pub fn n50(mut self, n50: usize) -> Self { pub fn n50(mut self, n50: usize) -> Self {
self.n50.replace(n50); self.n50.replace(n50);
@@ -91,6 +98,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of misses of a play.
#[inline] #[inline]
pub fn misses(mut self, n_misses: usize) -> Self { pub fn misses(mut self, n_misses: usize) -> Self {
self.n_misses = n_misses; self.n_misses = n_misses;
@@ -142,10 +150,12 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Consume the calculator and calculate the pp. /// Returns an object which contains the pp and [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
/// containing stars and other attributes.
/// ///
/// `stars_func` will be used to calculate stars and other necessary attributes if not already given. /// `stars_func` will be used to calculate the difficulty attributes if not already given.
/// Only called if `attributes` was not set. /// Only called if `attributes` was not set.
/// The default is suggested to be [`stars`](crate::osu::no_leniency::stars).
pub fn calculate( pub fn calculate(
mut self, mut self,
stars_func: impl FnOnce(&Beatmap, u32, Option<usize>) -> Attributes, stars_func: impl FnOnce(&Beatmap, u32, Option<usize>) -> Attributes,
+4 -2
View File
@@ -1,3 +1,5 @@
#![allow(unused)]
mod difficulty_object; mod difficulty_object;
mod osu_object; mod osu_object;
mod skill; mod skill;
@@ -130,7 +132,7 @@ pub fn stars(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::super::PpCalculator; use super::super::super::OsuPP;
use super::stars; use super::stars;
use crate::Beatmap; use crate::Beatmap;
use std::fs::File; use std::fs::File;
@@ -148,7 +150,7 @@ mod tests {
Err(why) => panic!("Error while parsing map: {}", why), Err(why) => panic!("Error while parsing map: {}", why),
}; };
let result = PpCalculator::new(&map).mods(64).calculate(stars); let result = OsuPP::new(&map).mods(64).calculate(stars);
println!("Stars: {}", result.attributes.stars); println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp); println!("PP: {}", result.pp);
+1 -1
View File
@@ -1,4 +1,4 @@
pub mod all_included; mod all_included;
pub mod no_leniency; pub mod no_leniency;
pub mod no_sliders_no_leniency; pub mod no_sliders_no_leniency;
+6 -2
View File
@@ -1,3 +1,7 @@
//! The positional offset of notes created by stack leniency is not considered.
//! This means the jump distance inbetween notes might be slightly off, resulting in small inaccuracies.
//! Since calculating these offsets is relatively expensive though, this version is considerably faster than `all_included`.
use super::super::DifficultyAttributes; use super::super::DifficultyAttributes;
mod control_point_iter; mod control_point_iter;
@@ -151,7 +155,7 @@ pub fn stars(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::super::PpCalculator; use super::super::super::OsuPP;
use super::stars; use super::stars;
use crate::Beatmap; use crate::Beatmap;
use std::fs::File; use std::fs::File;
@@ -169,7 +173,7 @@ mod tests {
Err(why) => panic!("Error while parsing map: {}", why), Err(why) => panic!("Error while parsing map: {}", why),
}; };
let result = PpCalculator::new(&map).mods(256).calculate(stars); let result = OsuPP::new(&map).mods(256).calculate(stars);
println!("Stars: {}", result.attributes.stars); println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp); println!("PP: {}", result.pp);
@@ -1,3 +1,7 @@
//! In addtion to not considering the positional offset caused by stack leniency, slider paths are also ignored.
//! This means the travel distance of notes is completely omitted which may cause further inaccuracies.
//! Since the slider paths don't have to be computed though, it should generally be faster than `no_leniency`.
use super::super::DifficultyAttributes; use super::super::DifficultyAttributes;
mod control_point_iter; mod control_point_iter;
@@ -171,7 +175,7 @@ pub fn stars(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::super::PpCalculator; use super::super::super::OsuPP;
use super::stars; use super::stars;
use crate::Beatmap; use crate::Beatmap;
use std::fs::File; use std::fs::File;
@@ -189,7 +193,7 @@ mod tests {
Err(why) => panic!("Error while parsing map: {}", why), Err(why) => panic!("Error while parsing map: {}", why),
}; };
let result = PpCalculator::new(&map).mods(2).calculate(stars); let result = OsuPP::new(&map).mods(2).calculate(stars);
println!("Stars: {}", result.attributes.stars); println!("Stars: {}", result.attributes.stars);
println!("PP: {}", result.pp); println!("PP: {}", result.pp);
+4
View File
@@ -1,5 +1,6 @@
use crate::Mods; use crate::Mods;
/// Summary struct for a [`Beatmap`](crate::Beatmap)'s attributes.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct BeatmapAttributes { pub struct BeatmapAttributes {
pub ar: f32, pub ar: f32,
@@ -27,6 +28,9 @@ impl BeatmapAttributes {
} }
} }
/// Adjusts attributes w.r.t. mods.
/// AR is further adjusted by the its hitwindow.
/// OD is __not__ adjusted by its hitwindow.
pub fn mods(self, mods: impl Mods) -> Self { pub fn mods(self, mods: impl Mods) -> Self {
if !mods.change_map() { if !mods.change_map() {
return self; return self;
+2
View File
@@ -1,5 +1,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
/// New rhythm speed change.
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct TimingPoint { pub struct TimingPoint {
pub beat_len: f32, pub beat_len: f32,
@@ -13,6 +14,7 @@ impl PartialOrd for TimingPoint {
} }
} }
/// [`TimingPoint`](crate::parse::TimingPoint) that depends on a previous one.
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
pub struct DifficultyPoint { pub struct DifficultyPoint {
pub time: f32, pub time: f32,
+3
View File
@@ -2,6 +2,8 @@ use super::{PathType, Pos2};
use std::cmp::Ordering; use std::cmp::Ordering;
/// "Intermediate" hitobject created through parsing.
/// Each mode will handle them differently.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct HitObject { pub struct HitObject {
pub pos: Pos2, pub pos: Pos2,
@@ -43,6 +45,7 @@ impl PartialOrd for HitObject {
} }
} }
/// Further data related to specific object types.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum HitObjectKind { pub enum HitObjectKind {
Circle, Circle,
+1
View File
@@ -1,6 +1,7 @@
use std::fmt; use std::fmt;
use std::ops; use std::ops;
/// Simple (x, y) coordinate
#[derive(Clone, Copy, Default, PartialEq)] #[derive(Clone, Copy, Default, PartialEq)]
pub struct Pos2 { pub struct Pos2 {
pub x: f32, pub x: f32,
+1 -1
View File
@@ -188,7 +188,7 @@ mod tests {
Err(why) => panic!("Error while parsing map: {}", why), Err(why) => panic!("Error while parsing map: {}", why),
}; };
let result = PpCalculator::new(&map).mods(64).calculate(); let result = TaikoPP::new(&map).mods(64).calculate();
println!("Stars: {}", result.stars); println!("Stars: {}", result.stars);
println!("PP: {}", result.pp); println!("PP: {}", result.pp);
+16 -15
View File
@@ -1,22 +1,14 @@
use crate::{Beatmap, Mods}; use crate::{Beatmap, Mods};
/// Basic struct containing the result of a PP calculation.
/// In osu!taiko's case, this will be just the pp value and the star value.
pub struct PpResult { pub struct PpResult {
pub pp: f32, pub pp: f32,
pub stars: f32, pub stars: f32,
} }
pub trait PpProvider { /// Calculator for pp on osu!taiko maps.
fn pp(&self) -> PpCalculator; pub struct TaikoPP<'m> {
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
map: &'m Beatmap, map: &'m Beatmap,
stars: Option<f32>, stars: Option<f32>,
mods: u32, mods: u32,
@@ -27,7 +19,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>, passed_objects: Option<usize>,
} }
impl<'m> PpCalculator<'m> { impl<'m> TaikoPP<'m> {
#[inline] #[inline]
pub fn new(map: &'m Beatmap) -> Self { pub fn new(map: &'m Beatmap) -> Self {
let max_combo = map.hit_objects.iter().filter(|h| h.is_circle()).count(); let max_combo = map.hit_objects.iter().filter(|h| h.is_circle()).count();
@@ -44,6 +36,9 @@ impl<'m> PpCalculator<'m> {
} }
} }
/// If you already know the stars of the map with the current mods,
/// you should specify them so that they don't have to be calculated
/// again while calculating PP.
#[inline] #[inline]
pub fn stars(mut self, stars: f32) -> Self { pub fn stars(mut self, stars: f32) -> Self {
self.stars.replace(stars); self.stars.replace(stars);
@@ -51,6 +46,9 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify mods through their bit values.
///
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
#[inline] #[inline]
pub fn mods(mut self, mods: u32) -> Self { pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods; self.mods = mods;
@@ -58,6 +56,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the max combo of the play.
#[inline] #[inline]
pub fn combo(mut self, combo: usize) -> Self { pub fn combo(mut self, combo: usize) -> Self {
self.combo.replace(combo); self.combo.replace(combo);
@@ -65,6 +64,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Specify the amount of misses of the play.
#[inline] #[inline]
pub fn misses(mut self, n_misses: usize) -> Self { pub fn misses(mut self, n_misses: usize) -> Self {
self.n_misses = n_misses; self.n_misses = n_misses;
@@ -72,7 +72,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Set the accuracy between 0.0 and 100.0; /// Set the accuracy between 0.0 and 100.0.
#[inline] #[inline]
pub fn accuracy(mut self, acc: f32) -> Self { pub fn accuracy(mut self, acc: f32) -> Self {
self.acc = acc / 100.0; self.acc = acc / 100.0;
@@ -88,6 +88,7 @@ impl<'m> PpCalculator<'m> {
self self
} }
/// Returns an object which contains the pp and stars.
pub fn calculate(self) -> PpResult { pub fn calculate(self) -> PpResult {
let stars = self let stars = self
.stars .stars
@@ -160,6 +161,6 @@ const HITWINDOW_AVG: f32 = 35.0;
const HITWINDOW_MAX: f32 = 20.0; const HITWINDOW_MAX: f32 = 20.0;
#[inline] #[inline]
pub(crate) fn difficulty_range_od(ar: f32) -> f32 { fn difficulty_range_od(ar: f32) -> f32 {
crate::difficulty_range(ar, HITWINDOW_MAX, HITWINDOW_AVG, HITWINDOW_MIN) crate::difficulty_range(ar, HITWINDOW_MAX, HITWINDOW_AVG, HITWINDOW_MIN)
} }
+4 -6
View File
@@ -33,19 +33,17 @@ fn fruits() {
Err(why) => panic!("Error while parsing map {}: {}", map_id, why), Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
}; };
let result = rosu_pp::fruits::PpCalculator::new(&map) let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
.mods(*mods)
.calculate();
assert!( assert!(
(result.stars - stars).abs() < star_margin * stars, (result.attributes.stars - stars).abs() < star_margin * stars,
"\nStars:\n\ "\nStars:\n\
Calculated: {calculated} | Expected: {expected}\n \ Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\ => {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n", [map {map} | mods {mods}]\n",
calculated = result.stars, calculated = result.attributes.stars,
expected = stars, expected = stars,
margin = (result.stars - stars).abs(), margin = (result.attributes.stars - stars).abs(),
allowed = star_margin * stars, allowed = star_margin * stars,
map = map_id, map = map_id,
mods = mods mods = mods
+1 -3
View File
@@ -33,9 +33,7 @@ fn mania() {
Err(why) => panic!("Error while parsing map {}: {}", map_id, why), Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
}; };
let result = rosu_pp::mania::PpCalculator::new(&map) let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
.mods(*mods)
.calculate();
assert!( assert!(
(result.stars - stars).abs() < star_margin * stars, (result.stars - stars).abs() < star_margin * stars,
+2 -2
View File
@@ -33,7 +33,7 @@ fn osu_no_sliders() {
Err(why) => panic!("Error while parsing map {}: {}", map_id, why), Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
}; };
let result = rosu_pp::osu::PpCalculator::new(&map) let result = rosu_pp::OsuPP::new(&map)
.mods(*mods) .mods(*mods)
.calculate(osu::no_sliders_no_leniency::stars); .calculate(osu::no_sliders_no_leniency::stars);
@@ -90,7 +90,7 @@ fn osu_no_leniency() {
Err(why) => panic!("Error while parsing map {}: {}", map_id, why), Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
}; };
let result = rosu_pp::osu::PpCalculator::new(&map) let result = rosu_pp::osu::OsuPP::new(&map)
.mods(*mods) .mods(*mods)
.calculate(osu::no_leniency::stars); .calculate(osu::no_leniency::stars);
+1 -3
View File
@@ -33,9 +33,7 @@ fn taiko() {
Err(why) => panic!("Error while parsing map {}: {}", map_id, why), Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
}; };
let result = rosu_pp::taiko::PpCalculator::new(&map) let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
.mods(*mods)
.calculate();
assert!( assert!(
(result.stars - stars).abs() < star_margin * stars, (result.stars - stars).abs() < star_margin * stars,