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.
**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
- [x] no_sliders_no_leniency (i.e. oppai)
- [x] no_leniency
- [ ] all included
- [x] no_leniency
- [x] no_sliders_no_leniency (i.e. oppai)
- [x] taiko sr
- [x] ctb 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] mania pp
---
- [ ] refactoring
- [x] refactoring
- [ ] 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 stars: f32,
pub max_combo: usize,
@@ -400,7 +402,7 @@ mod tests {
#[test]
#[ignore]
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,
Err(why) => panic!("Could not open file: {}", why),
};
@@ -410,9 +412,9 @@ mod tests {
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);
}
}
+26 -15
View File
@@ -1,23 +1,16 @@
use super::{stars, DifficultyAttributes};
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 pp: f32,
pub stars: f32,
pub attributes: DifficultyAttributes,
}
pub trait PpProvider {
fn pp(&self) -> PpCalculator;
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
/// Calculator for pp on osu!ctb maps.
pub struct FruitsPP<'m> {
map: &'m Beatmap,
attributes: Option<DifficultyAttributes>,
mods: u32,
@@ -31,7 +24,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>,
}
impl<'m> PpCalculator<'m> {
impl<'m> FruitsPP<'m> {
#[inline]
pub fn new(map: &'m Beatmap) -> 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]
pub fn attributes(mut self, attributes: DifficultyAttributes) -> Self {
self.attributes.replace(attributes);
@@ -56,6 +56,9 @@ impl<'m> PpCalculator<'m> {
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]
pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods;
@@ -63,6 +66,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the max combo of the play.
#[inline]
pub fn combo(mut self, combo: usize) -> Self {
self.combo.replace(combo);
@@ -70,6 +74,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of fruits of a play i.e. n300.
#[inline]
pub fn fruits(mut self, n_fruits: usize) -> Self {
self.n_fruits.replace(n_fruits);
@@ -77,6 +82,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of droplets of a play i.e. n100.
#[inline]
pub fn droplets(mut self, n_droplets: usize) -> Self {
self.n_droplets.replace(n_droplets);
@@ -84,6 +90,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of tiny droplets of a play.
#[inline]
pub fn tiny_droplets(mut self, n_tiny_droplets: usize) -> Self {
self.n_tiny_droplets.replace(n_tiny_droplets);
@@ -91,6 +98,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of tiny droplet misses of a play.
#[inline]
pub fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: usize) -> Self {
self.n_tiny_droplet_misses.replace(n_tiny_droplet_misses);
@@ -98,6 +106,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of fruit / droplet misses of the play.
#[inline]
pub fn misses(mut self, n_misses: usize) -> Self {
self.n_misses = n_misses;
@@ -152,6 +161,8 @@ impl<'m> PpCalculator<'m> {
self
}
/// Returns an object which contains the pp and [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
/// containing stars and other attributes.
pub fn calculate(mut self) -> PpResult {
let attributes = self
.attributes
@@ -216,7 +227,7 @@ impl<'m> PpCalculator<'m> {
pp *= 0.9;
}
PpResult { pp, stars }
PpResult { pp, attributes }
}
#[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 mania;
pub mod osu;
@@ -8,11 +104,55 @@ mod curve;
mod math_util;
mod mods;
pub use mods::Mods;
pub use parse::*;
pub use fruits::FruitsPP;
pub use mania::ManiaPP;
pub use osu::OsuPP;
pub use taiko::TaikoPP;
// TODO
pub trait BeatmapExt {}
pub use mods::Mods;
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]
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),
};
let result = PpCalculator::new(&map).mods(256).calculate();
let result = ManiaPP::new(&map).mods(256).calculate();
println!("Stars: {}", result.stars);
println!("PP: {}", result.pp);
+13 -14
View File
@@ -1,23 +1,15 @@
use super::stars;
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 pp: f32,
pub stars: f32,
}
pub trait PpProvider {
fn pp(&self) -> PpCalculator;
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
/// Calculator for pp on osu!mania maps.
pub struct ManiaPP<'m> {
map: &'m Beatmap,
stars: Option<f32>,
mods: u32,
@@ -25,7 +17,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>,
}
impl<'m> PpCalculator<'m> {
impl<'m> ManiaPP<'m> {
#[inline]
pub fn new(map: &'m Beatmap) -> 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]
pub fn stars(mut self, stars: f32) -> Self {
self.stars.replace(stars);
@@ -44,6 +39,9 @@ impl<'m> PpCalculator<'m> {
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]
pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods;
@@ -52,7 +50,7 @@ impl<'m> PpCalculator<'m> {
}
/// 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]
pub fn score(mut self, score: u32) -> Self {
self.score.replace(score as f32);
@@ -68,6 +66,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Returns an object which contains the pp and stars.
pub fn calculate(self) -> PpResult {
let stars = self
.stars
+3 -1
View File
@@ -4,7 +4,9 @@ mod versions;
pub use pp::*;
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 stars: f32,
pub ar: f32,
+25 -15
View File
@@ -1,23 +1,16 @@
use super::DifficultyAttributes as Attributes;
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 pp: f32,
pub attributes: Attributes,
}
pub trait PpProvider {
fn pp(&self) -> PpCalculator;
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
/// Calculator for pp on osu!standard maps.
pub struct OsuPP<'m> {
map: &'m Beatmap,
attributes: Option<Attributes>,
mods: u32,
@@ -31,7 +24,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>,
}
impl<'m> PpCalculator<'m> {
impl<'m> OsuPP<'m> {
#[inline]
pub fn new(map: &'m Beatmap) -> 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]
pub fn attributes(mut self, attributes: Attributes) -> Self {
self.attributes.replace(attributes);
@@ -56,6 +56,9 @@ impl<'m> PpCalculator<'m> {
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]
pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods;
@@ -63,6 +66,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the max combo of the play.
#[inline]
pub fn combo(mut self, combo: usize) -> Self {
self.combo.replace(combo);
@@ -70,6 +74,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of 300s of a play.
#[inline]
pub fn n300(mut self, n300: usize) -> Self {
self.n300.replace(n300);
@@ -77,6 +82,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of 100s of a play.
#[inline]
pub fn n100(mut self, n100: usize) -> Self {
self.n100.replace(n100);
@@ -84,6 +90,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of 50s of a play.
#[inline]
pub fn n50(mut self, n50: usize) -> Self {
self.n50.replace(n50);
@@ -91,6 +98,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of misses of a play.
#[inline]
pub fn misses(mut self, n_misses: usize) -> Self {
self.n_misses = n_misses;
@@ -142,10 +150,12 @@ impl<'m> PpCalculator<'m> {
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.
/// The default is suggested to be [`stars`](crate::osu::no_leniency::stars).
pub fn calculate(
mut self,
stars_func: impl FnOnce(&Beatmap, u32, Option<usize>) -> Attributes,
+4 -2
View File
@@ -1,3 +1,5 @@
#![allow(unused)]
mod difficulty_object;
mod osu_object;
mod skill;
@@ -130,7 +132,7 @@ pub fn stars(
#[cfg(test)]
mod tests {
use super::super::super::PpCalculator;
use super::super::super::OsuPP;
use super::stars;
use crate::Beatmap;
use std::fs::File;
@@ -148,7 +150,7 @@ mod tests {
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!("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_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;
mod control_point_iter;
@@ -151,7 +155,7 @@ pub fn stars(
#[cfg(test)]
mod tests {
use super::super::super::PpCalculator;
use super::super::super::OsuPP;
use super::stars;
use crate::Beatmap;
use std::fs::File;
@@ -169,7 +173,7 @@ mod tests {
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!("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;
mod control_point_iter;
@@ -171,7 +175,7 @@ pub fn stars(
#[cfg(test)]
mod tests {
use super::super::super::PpCalculator;
use super::super::super::OsuPP;
use super::stars;
use crate::Beatmap;
use std::fs::File;
@@ -189,7 +193,7 @@ mod tests {
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!("PP: {}", result.pp);
+4
View File
@@ -1,5 +1,6 @@
use crate::Mods;
/// Summary struct for a [`Beatmap`](crate::Beatmap)'s attributes.
#[derive(Clone, Debug)]
pub struct BeatmapAttributes {
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 {
if !mods.change_map() {
return self;
+2
View File
@@ -1,5 +1,6 @@
use std::cmp::Ordering;
/// New rhythm speed change.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct TimingPoint {
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)]
pub struct DifficultyPoint {
pub time: f32,
+3
View File
@@ -2,6 +2,8 @@ use super::{PathType, Pos2};
use std::cmp::Ordering;
/// "Intermediate" hitobject created through parsing.
/// Each mode will handle them differently.
#[derive(Clone, Debug, PartialEq)]
pub struct HitObject {
pub pos: Pos2,
@@ -43,6 +45,7 @@ impl PartialOrd for HitObject {
}
}
/// Further data related to specific object types.
#[derive(Clone, Debug, PartialEq)]
pub enum HitObjectKind {
Circle,
+1
View File
@@ -1,6 +1,7 @@
use std::fmt;
use std::ops;
/// Simple (x, y) coordinate
#[derive(Clone, Copy, Default, PartialEq)]
pub struct Pos2 {
pub x: f32,
+1 -1
View File
@@ -188,7 +188,7 @@ mod tests {
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!("PP: {}", result.pp);
+16 -15
View File
@@ -1,22 +1,14 @@
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 pp: f32,
pub stars: f32,
}
pub trait PpProvider {
fn pp(&self) -> PpCalculator;
}
impl PpProvider for Beatmap {
#[inline]
fn pp(&self) -> PpCalculator {
PpCalculator::new(self)
}
}
pub struct PpCalculator<'m> {
/// Calculator for pp on osu!taiko maps.
pub struct TaikoPP<'m> {
map: &'m Beatmap,
stars: Option<f32>,
mods: u32,
@@ -27,7 +19,7 @@ pub struct PpCalculator<'m> {
passed_objects: Option<usize>,
}
impl<'m> PpCalculator<'m> {
impl<'m> TaikoPP<'m> {
#[inline]
pub fn new(map: &'m Beatmap) -> Self {
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]
pub fn stars(mut self, stars: f32) -> Self {
self.stars.replace(stars);
@@ -51,6 +46,9 @@ impl<'m> PpCalculator<'m> {
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]
pub fn mods(mut self, mods: u32) -> Self {
self.mods = mods;
@@ -58,6 +56,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the max combo of the play.
#[inline]
pub fn combo(mut self, combo: usize) -> Self {
self.combo.replace(combo);
@@ -65,6 +64,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Specify the amount of misses of the play.
#[inline]
pub fn misses(mut self, n_misses: usize) -> Self {
self.n_misses = n_misses;
@@ -72,7 +72,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Set the accuracy between 0.0 and 100.0;
/// Set the accuracy between 0.0 and 100.0.
#[inline]
pub fn accuracy(mut self, acc: f32) -> Self {
self.acc = acc / 100.0;
@@ -88,6 +88,7 @@ impl<'m> PpCalculator<'m> {
self
}
/// Returns an object which contains the pp and stars.
pub fn calculate(self) -> PpResult {
let stars = self
.stars
@@ -160,6 +161,6 @@ const HITWINDOW_AVG: f32 = 35.0;
const HITWINDOW_MAX: f32 = 20.0;
#[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)
}
+4 -6
View File
@@ -33,19 +33,17 @@ fn fruits() {
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
};
let result = rosu_pp::fruits::PpCalculator::new(&map)
.mods(*mods)
.calculate();
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
assert!(
(result.stars - stars).abs() < star_margin * stars,
(result.attributes.stars - stars).abs() < star_margin * stars,
"\nStars:\n\
Calculated: {calculated} | Expected: {expected}\n \
=> {margin} margin ({allowed} allowed)\n\
[map {map} | mods {mods}]\n",
calculated = result.stars,
calculated = result.attributes.stars,
expected = stars,
margin = (result.stars - stars).abs(),
margin = (result.attributes.stars - stars).abs(),
allowed = star_margin * stars,
map = map_id,
mods = mods
+1 -3
View File
@@ -33,9 +33,7 @@ fn mania() {
Err(why) => panic!("Error while parsing map {}: {}", map_id, why),
};
let result = rosu_pp::mania::PpCalculator::new(&map)
.mods(*mods)
.calculate();
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
assert!(
(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),
};
let result = rosu_pp::osu::PpCalculator::new(&map)
let result = rosu_pp::OsuPP::new(&map)
.mods(*mods)
.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),
};
let result = rosu_pp::osu::PpCalculator::new(&map)
let result = rosu_pp::osu::OsuPP::new(&map)
.mods(*mods)
.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),
};
let result = rosu_pp::taiko::PpCalculator::new(&map)
.mods(*mods)
.calculate();
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
assert!(
(result.stars - stars).abs() < star_margin * stars,