use the same PpResult type for all modes
This commit is contained in:
@@ -4,7 +4,7 @@ A standalone crate to calculate star ratings and performance points for all [osu
|
|||||||
|
|
||||||
Conversions are generally not supported.
|
Conversions are generally not supported.
|
||||||
|
|
||||||
### Usage
|
#### Usage
|
||||||
```rust
|
```rust
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use rosu_pp::{Beatmap, BeatmapExt, GameMode, OsuPP, TaikoPP};
|
use rosu_pp::{Beatmap, BeatmapExt, GameMode, OsuPP, TaikoPP};
|
||||||
@@ -35,32 +35,32 @@ fn main() {
|
|||||||
// `no_leniency` is the suggested default
|
// `no_leniency` is the suggested default
|
||||||
.calculate(rosu_pp::osu::no_leniency::stars);
|
.calculate(rosu_pp::osu::no_leniency::stars);
|
||||||
|
|
||||||
println!("PP: {}", result.pp);
|
println!("PP: {}", result.pp());
|
||||||
|
|
||||||
// If you intend to reuse the current map-mod combination,
|
// If you intend to reuse the current map-mod combination,
|
||||||
// make use of the attributes in the result.
|
// make use of the previous result!
|
||||||
// If attributes are given, then stars & co don't have to be recalculated.
|
// If attributes are given, then stars & co don't have to be recalculated.
|
||||||
let next_result = OsuPP::new(&map)
|
let next_result = OsuPP::new(&map)
|
||||||
.mods(24) // HDHR
|
.mods(24) // HDHR
|
||||||
.attributes(result.attributes)
|
.attributes(result)
|
||||||
.combo(543)
|
.combo(543)
|
||||||
.misses(5)
|
.misses(5)
|
||||||
.n50(3)
|
.n50(3)
|
||||||
.passed_objects(612)
|
|
||||||
.accuracy(97.5)
|
.accuracy(97.5)
|
||||||
.calculate(rosu_pp::osu::no_leniency::stars);
|
.calculate(rosu_pp::osu::no_leniency::stars);
|
||||||
|
|
||||||
println!("Next PP: {}", next_result.pp);
|
println!("Next PP: {}", next_result.pp());
|
||||||
},
|
},
|
||||||
GameMode::TKO => {
|
GameMode::TKO => {
|
||||||
let result = TaikoPP::new(&map)
|
let result = TaikoPP::new(&map)
|
||||||
.mods(64) // DT
|
.mods(64) // DT
|
||||||
.combo(555)
|
.combo(555)
|
||||||
.misses(10)
|
.misses(10)
|
||||||
|
.passed_objects(600)
|
||||||
.accuracy(95.12345)
|
.accuracy(95.12345)
|
||||||
.calculate();
|
.calculate();
|
||||||
|
|
||||||
println!("Stars: {} | PP: {}", result.stars, result.pp);
|
println!("Stars: {} | PP: {}", result.stars(), result.pp());
|
||||||
}
|
}
|
||||||
GameMode::MNA | GameMode::CTB => panic!("do your thing"),
|
GameMode::MNA | GameMode::CTB => panic!("do your thing"),
|
||||||
}
|
}
|
||||||
@@ -74,12 +74,12 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### osu!standard versions
|
#### osu!standard versions
|
||||||
- `all_included`: WIP
|
- `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_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`.
|
- `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
|
#### Roadmap
|
||||||
- osu sr versions
|
- osu sr versions
|
||||||
- [ ] all included
|
- [ ] all included
|
||||||
- [x] no_leniency
|
- [x] no_leniency
|
||||||
|
|||||||
+2
-2
@@ -414,7 +414,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = FruitsPP::new(&map).mods(0).calculate();
|
let result = FruitsPP::new(&map).mods(0).calculate();
|
||||||
|
|
||||||
println!("Stars: {}", result.attributes.stars);
|
println!("Stars: {}", result.stars());
|
||||||
println!("PP: {}", result.pp);
|
println!("PP: {}", result.pp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-15
@@ -1,12 +1,24 @@
|
|||||||
use super::{stars, DifficultyAttributes};
|
use super::{stars, DifficultyAttributes};
|
||||||
use crate::{Beatmap, Mods};
|
use crate::{Beatmap, Mods, PpResult};
|
||||||
|
|
||||||
/// Basic struct containing the result of a PP calculation.
|
pub trait FruitsAttributeProvider {
|
||||||
/// In osu!ctb's case, this will be the pp value and the
|
fn attributes(self) -> Option<DifficultyAttributes>;
|
||||||
/// difficulty attributes created by star calculation.
|
}
|
||||||
pub struct PpResult {
|
|
||||||
pub pp: f32,
|
impl FruitsAttributeProvider for DifficultyAttributes {
|
||||||
pub attributes: DifficultyAttributes,
|
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FruitsAttributeProvider for PpResult {
|
||||||
|
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||||
|
if let PpResult::Fruits { attributes, .. } = self {
|
||||||
|
Some(attributes)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculator for pp on osu!ctb maps.
|
/// Calculator for pp on osu!ctb maps.
|
||||||
@@ -42,13 +54,11 @@ impl<'m> FruitsPP<'m> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
|
/// [`FruitsAttributeProvider`] is implemented by [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
|
||||||
/// stay the same for each map-mod combination.
|
/// and by [`PpResult`](crate::PpResult) meaning you can give the
|
||||||
/// If you already calculated them, be sure to put them in here so
|
/// result of a star calculation or a pp calculation.
|
||||||
/// that they don't have to be recalculated.
|
/// If you already calculated the attributes for the current map-mod combination,
|
||||||
///
|
/// 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);
|
||||||
@@ -227,7 +237,7 @@ impl<'m> FruitsPP<'m> {
|
|||||||
pp *= 0.9;
|
pp *= 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
PpResult { pp, attributes }
|
PpResult::Fruits { pp, attributes }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
+53
-13
@@ -33,34 +33,34 @@
|
|||||||
//! // `no_leniency` is the suggested default
|
//! // `no_leniency` is the suggested default
|
||||||
//! .calculate(rosu_pp::osu::no_leniency::stars);
|
//! .calculate(rosu_pp::osu::no_leniency::stars);
|
||||||
//!
|
//!
|
||||||
//! println!("PP: {}", result.pp);
|
//! println!("PP: {}", result.pp());
|
||||||
//!
|
//!
|
||||||
//! // If you intend to reuse the current map-mod combination,
|
//! // If you intend to reuse the current map-mod combination,
|
||||||
//! // make use of the attributes in the result.
|
//! // make use of the previous result!
|
||||||
//! // If attributes are given, then stars & co don't have to be recalculated.
|
//! // If attributes are given, then stars & co don't have to be recalculated.
|
||||||
//! let next_result = OsuPP::new(&map)
|
//! let next_result = OsuPP::new(&map)
|
||||||
//! .mods(24) // HDHR
|
//! .mods(24) // HDHR
|
||||||
//! .attributes(result.attributes)
|
//! .attributes(result)
|
||||||
//! .combo(543)
|
//! .combo(543)
|
||||||
//! .misses(5)
|
//! .misses(5)
|
||||||
//! .n50(3)
|
//! .n50(3)
|
||||||
//! .passed_objects(612)
|
|
||||||
//! .accuracy(97.5)
|
//! .accuracy(97.5)
|
||||||
//! .calculate(rosu_pp::osu::no_leniency::stars);
|
//! .calculate(rosu_pp::osu::no_leniency::stars);
|
||||||
//!
|
//!
|
||||||
//! println!("Next PP: {}", next_result.pp);
|
//! println!("Next PP: {}", next_result.pp());
|
||||||
//! },
|
//! },
|
||||||
//! GameMode::TKO => {
|
//! GameMode::TKO => {
|
||||||
//! let result = TaikoPP::new(&map)
|
//! let result = TaikoPP::new(&map)
|
||||||
//! .mods(64) // DT
|
//! .mods(64) // DT
|
||||||
//! .combo(555)
|
//! .combo(555)
|
||||||
//! .misses(10)
|
//! .misses(10)
|
||||||
|
//! .passed_objects(600)
|
||||||
//! .accuracy(95.12345)
|
//! .accuracy(95.12345)
|
||||||
//! .calculate();
|
//! .calculate();
|
||||||
//!
|
//!
|
||||||
//! println!("Stars: {} | PP: {}", result.stars, result.pp);
|
//! println!("Stars: {} | PP: {}", result.stars(), result.pp());
|
||||||
//! }
|
//! }
|
||||||
//! GameMode::MNA | GameMode::CTB => unimplemented!(),
|
//! GameMode::MNA | GameMode::CTB => panic!("do your thing"),
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! // If all you want is the map's stars or max pp,
|
//! // If all you want is the map's stars or max pp,
|
||||||
@@ -141,15 +141,55 @@ impl BeatmapExt for Beatmap {
|
|||||||
}
|
}
|
||||||
fn max_pp(&self, mods: u32) -> f32 {
|
fn max_pp(&self, mods: u32) -> f32 {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
GameMode::STD => {
|
GameMode::STD => OsuPP::new(self)
|
||||||
OsuPP::new(self)
|
|
||||||
.mods(mods)
|
.mods(mods)
|
||||||
.calculate(osu::no_leniency::stars)
|
.calculate(osu::no_leniency::stars)
|
||||||
.pp
|
.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(),
|
||||||
}
|
}
|
||||||
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,
|
|
||||||
|
/// Basic enum containing the result of a PP calculation depending on the mode.
|
||||||
|
pub enum PpResult {
|
||||||
|
Fruits {
|
||||||
|
pp: f32,
|
||||||
|
attributes: fruits::DifficultyAttributes,
|
||||||
|
},
|
||||||
|
Mania {
|
||||||
|
pp: f32,
|
||||||
|
stars: f32,
|
||||||
|
},
|
||||||
|
Osu {
|
||||||
|
pp: f32,
|
||||||
|
attributes: osu::DifficultyAttributes,
|
||||||
|
},
|
||||||
|
Taiko {
|
||||||
|
pp: f32,
|
||||||
|
stars: f32,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PpResult {
|
||||||
|
/// The final pp value.
|
||||||
|
pub fn pp(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Self::Fruits { pp, .. } => *pp,
|
||||||
|
Self::Mania { pp, .. } => *pp,
|
||||||
|
Self::Osu { pp, .. } => *pp,
|
||||||
|
Self::Taiko { pp, .. } => *pp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The final star value.
|
||||||
|
pub fn stars(&self) -> f32 {
|
||||||
|
match self {
|
||||||
|
Self::Fruits { attributes, .. } => attributes.stars,
|
||||||
|
Self::Mania { stars, .. } => *stars,
|
||||||
|
Self::Osu { attributes, .. } => attributes.stars,
|
||||||
|
Self::Taiko { stars, .. } => *stars,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -101,7 +101,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = ManiaPP::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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-11
@@ -1,11 +1,24 @@
|
|||||||
use super::stars;
|
use super::stars;
|
||||||
use crate::{Beatmap, Mods};
|
use crate::{Beatmap, Mods, PpResult};
|
||||||
|
|
||||||
/// Basic struct containing the result of a PP calculation.
|
pub trait ManiaStarProvider {
|
||||||
/// In osu!mania's case, this will be just the pp value and the star value.
|
fn attributes(self) -> Option<f32>;
|
||||||
pub struct PpResult {
|
}
|
||||||
pub pp: f32,
|
|
||||||
pub stars: f32,
|
impl ManiaStarProvider for f32 {
|
||||||
|
fn attributes(self) -> Option<f32> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManiaStarProvider for PpResult {
|
||||||
|
fn attributes(self) -> Option<f32> {
|
||||||
|
if let PpResult::Mania { stars, .. } = self {
|
||||||
|
Some(stars)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculator for pp on osu!mania maps.
|
/// Calculator for pp on osu!mania maps.
|
||||||
@@ -29,12 +42,16 @@ impl<'m> ManiaPP<'m> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If you already know the stars of the map with the current mods,
|
/// [`ManiaStarsProvider`] is implemented by `f32`
|
||||||
/// you should specify them so that they don't have to be calculated
|
/// and by [`PpResult`](crate::PpResult) meaning you can give the
|
||||||
/// again while calculating PP.
|
/// result of a star calculation or a pp calculation.
|
||||||
|
/// If you already calculated the attributes for the current map-mod combination,
|
||||||
|
/// be sure to put them in here so that they don't have to be recalculated.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn stars(mut self, stars: f32) -> Self {
|
pub fn stars(mut self, stars: impl ManiaStarProvider) -> Self {
|
||||||
|
if let Some(stars) = stars.attributes() {
|
||||||
self.stars.replace(stars);
|
self.stars.replace(stars);
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -116,7 +133,7 @@ impl<'m> ManiaPP<'m> {
|
|||||||
|
|
||||||
let pp = (strain_value.powf(1.1) + acc_value.powf(1.1)).powf(1.0 / 1.1) * multiplier;
|
let pp = (strain_value.powf(1.1) + acc_value.powf(1.1)).powf(1.0 / 1.1) * multiplier;
|
||||||
|
|
||||||
PpResult { pp, stars }
|
PpResult::Mania { pp, stars }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_strain(&self, score: f32, stars: f32) -> f32 {
|
fn compute_strain(&self, score: f32, stars: f32) -> f32 {
|
||||||
|
|||||||
+31
-19
@@ -1,18 +1,30 @@
|
|||||||
use super::DifficultyAttributes as Attributes;
|
use super::DifficultyAttributes;
|
||||||
use crate::{Beatmap, Mods};
|
use crate::{Beatmap, Mods, PpResult};
|
||||||
|
|
||||||
/// Basic struct containing the result of a PP calculation.
|
pub trait OsuAttributeProvider {
|
||||||
/// In osu!standard's case, this will be the pp value and the
|
fn attributes(self) -> Option<DifficultyAttributes>;
|
||||||
/// difficulty attributes created by star calculation.
|
}
|
||||||
pub struct PpResult {
|
|
||||||
pub pp: f32,
|
impl OsuAttributeProvider for DifficultyAttributes {
|
||||||
pub attributes: Attributes,
|
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OsuAttributeProvider for PpResult {
|
||||||
|
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||||
|
if let PpResult::Osu { attributes, .. } = self {
|
||||||
|
Some(attributes)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculator for pp on osu!standard maps.
|
/// Calculator for pp on osu!standard maps.
|
||||||
pub struct OsuPP<'m> {
|
pub struct OsuPP<'m> {
|
||||||
map: &'m Beatmap,
|
map: &'m Beatmap,
|
||||||
attributes: Option<Attributes>,
|
attributes: Option<DifficultyAttributes>,
|
||||||
mods: u32,
|
mods: u32,
|
||||||
combo: Option<usize>,
|
combo: Option<usize>,
|
||||||
acc: Option<f32>,
|
acc: Option<f32>,
|
||||||
@@ -42,16 +54,16 @@ impl<'m> OsuPP<'m> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
|
/// [`OsuAttributeProvider`] is implemented by [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
|
||||||
/// stay the same for each map-mod combination.
|
/// and by [`PpResult`](crate::PpResult) meaning you can give the
|
||||||
/// If you already calculated them, be sure to put them in here so
|
/// result of a star calculation or a pp calculation.
|
||||||
/// that they don't have to be recalculated.
|
/// If you already calculated the attributes for the current map-mod combination,
|
||||||
///
|
/// 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: impl OsuAttributeProvider) -> Self {
|
||||||
|
if let Some(attributes) = attributes.attributes() {
|
||||||
self.attributes.replace(attributes);
|
self.attributes.replace(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -158,7 +170,7 @@ impl<'m> OsuPP<'m> {
|
|||||||
/// The default is suggested to be [`stars`](crate::osu::no_leniency::stars).
|
/// 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>) -> DifficultyAttributes,
|
||||||
) -> PpResult {
|
) -> PpResult {
|
||||||
if self.attributes.is_none() {
|
if self.attributes.is_none() {
|
||||||
let attributes = stars_func(self.map, self.mods, self.passed_objects);
|
let attributes = stars_func(self.map, self.mods, self.passed_objects);
|
||||||
@@ -215,7 +227,7 @@ impl<'m> OsuPP<'m> {
|
|||||||
.powf(1.0 / 1.1)
|
.powf(1.0 / 1.1)
|
||||||
* multiplier;
|
* multiplier;
|
||||||
|
|
||||||
PpResult {
|
PpResult::Osu {
|
||||||
pp,
|
pp,
|
||||||
attributes: self.attributes.unwrap(),
|
attributes: self.attributes.unwrap(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = OsuPP::new(&map).mods(64).calculate(stars);
|
let result = OsuPP::new(&map).mods(64).calculate(stars);
|
||||||
|
|
||||||
println!("Stars: {}", result.attributes.stars);
|
println!("Stars: {}", result.stars());
|
||||||
println!("PP: {}", result.pp);
|
println!("PP: {}", result.pp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = OsuPP::new(&map).mods(256).calculate(stars);
|
let result = OsuPP::new(&map).mods(256).calculate(stars);
|
||||||
|
|
||||||
println!("Stars: {}", result.attributes.stars);
|
println!("Stars: {}", result.stars());
|
||||||
println!("PP: {}", result.pp);
|
println!("PP: {}", result.pp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = OsuPP::new(&map).mods(2).calculate(stars);
|
let result = OsuPP::new(&map).mods(2).calculate(stars);
|
||||||
|
|
||||||
println!("Stars: {}", result.attributes.stars);
|
println!("Stars: {}", result.stars());
|
||||||
println!("PP: {}", result.pp);
|
println!("PP: {}", result.pp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -190,7 +190,7 @@ mod tests {
|
|||||||
|
|
||||||
let result = TaikoPP::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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-11
@@ -1,10 +1,23 @@
|
|||||||
use crate::{Beatmap, Mods};
|
use crate::{Beatmap, Mods, PpResult};
|
||||||
|
|
||||||
/// Basic struct containing the result of a PP calculation.
|
pub trait TaikoStarProvider {
|
||||||
/// In osu!taiko's case, this will be just the pp value and the star value.
|
fn attributes(self) -> Option<f32>;
|
||||||
pub struct PpResult {
|
}
|
||||||
pub pp: f32,
|
|
||||||
pub stars: f32,
|
impl TaikoStarProvider for f32 {
|
||||||
|
fn attributes(self) -> Option<f32> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaikoStarProvider for PpResult {
|
||||||
|
fn attributes(self) -> Option<f32> {
|
||||||
|
if let PpResult::Taiko { stars, .. } = self {
|
||||||
|
Some(stars)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculator for pp on osu!taiko maps.
|
/// Calculator for pp on osu!taiko maps.
|
||||||
@@ -36,12 +49,16 @@ impl<'m> TaikoPP<'m> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If you already know the stars of the map with the current mods,
|
/// [`TaikoStarProvider`] is implemented by `f32`
|
||||||
/// you should specify them so that they don't have to be calculated
|
/// and by [`PpResult`](crate::PpResult) meaning you can give the
|
||||||
/// again while calculating PP.
|
/// result of a star calculation or a pp calculation.
|
||||||
|
/// If you already calculated the stars for the current map-mod combination,
|
||||||
|
/// be sure to put them in here so that they don't have to be recalculated.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn stars(mut self, stars: f32) -> Self {
|
pub fn stars(mut self, stars: impl TaikoStarProvider) -> Self {
|
||||||
|
if let Some(stars) = stars.attributes() {
|
||||||
self.stars.replace(stars);
|
self.stars.replace(stars);
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -109,7 +126,7 @@ impl<'m> TaikoPP<'m> {
|
|||||||
|
|
||||||
let pp = (strain_value.powf(1.1) + acc_value.powf(1.1)).powf(1.0 / 1.1) * multiplier;
|
let pp = (strain_value.powf(1.1) + acc_value.powf(1.1)).powf(1.0 / 1.1) * multiplier;
|
||||||
|
|
||||||
PpResult { stars, pp }
|
PpResult::Taiko { stars, pp }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_strain_value(&self, stars: f32) -> f32 {
|
fn compute_strain_value(&self, stars: f32) -> f32 {
|
||||||
|
|||||||
+6
-6
@@ -36,28 +36,28 @@ fn fruits() {
|
|||||||
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::FruitsPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.attributes.stars - stars).abs() < star_margin * stars,
|
(result.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.attributes.stars,
|
calculated = result.stars(),
|
||||||
expected = stars,
|
expected = stars,
|
||||||
margin = (result.attributes.stars - stars).abs(),
|
margin = (result.stars() - stars).abs(),
|
||||||
allowed = star_margin * stars,
|
allowed = star_margin * stars,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.pp - pp).abs() < pp_margin * pp,
|
(result.pp() - pp).abs() < pp_margin * pp,
|
||||||
"\nPP:\n\
|
"\nPP:\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.pp,
|
calculated = result.pp(),
|
||||||
expected = pp,
|
expected = pp,
|
||||||
margin = (result.pp - pp).abs(),
|
margin = (result.pp() - pp).abs(),
|
||||||
allowed = pp_margin * pp,
|
allowed = pp_margin * pp,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
|
|||||||
+6
-6
@@ -36,28 +36,28 @@ fn mania() {
|
|||||||
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::ManiaPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.stars - stars).abs() < star_margin * stars,
|
(result.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.stars(),
|
||||||
expected = stars,
|
expected = stars,
|
||||||
margin = (result.stars - stars).abs(),
|
margin = (result.stars() - stars).abs(),
|
||||||
allowed = star_margin * stars,
|
allowed = star_margin * stars,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.pp - pp).abs() < pp_margin * pp,
|
(result.pp() - pp).abs() < pp_margin * pp,
|
||||||
"\nPP:\n\
|
"\nPP:\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.pp,
|
calculated = result.pp(),
|
||||||
expected = pp,
|
expected = pp,
|
||||||
margin = (result.pp - pp).abs(),
|
margin = (result.pp() - pp).abs(),
|
||||||
allowed = pp_margin * pp,
|
allowed = pp_margin * pp,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
|
|||||||
+12
-12
@@ -38,28 +38,28 @@ fn osu_no_sliders() {
|
|||||||
.calculate(osu::no_sliders_no_leniency::stars);
|
.calculate(osu::no_sliders_no_leniency::stars);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.attributes.stars - stars).abs() < star_margin * stars,
|
(result.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.attributes.stars,
|
calculated = result.stars(),
|
||||||
expected = stars,
|
expected = stars,
|
||||||
margin = (result.attributes.stars - stars).abs(),
|
margin = (result.stars() - stars).abs(),
|
||||||
allowed = star_margin * stars,
|
allowed = star_margin * stars,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.pp - pp).abs() < pp_margin * pp,
|
(result.pp() - pp).abs() < pp_margin * pp,
|
||||||
"\nPP:\n\
|
"\nPP:\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.pp,
|
calculated = result.pp(),
|
||||||
expected = pp,
|
expected = pp,
|
||||||
margin = (result.pp - pp).abs(),
|
margin = (result.pp() - pp).abs(),
|
||||||
allowed = pp_margin * pp,
|
allowed = pp_margin * pp,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
@@ -95,28 +95,28 @@ fn osu_no_leniency() {
|
|||||||
.calculate(osu::no_leniency::stars);
|
.calculate(osu::no_leniency::stars);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.attributes.stars - stars).abs() < star_margin * stars,
|
(result.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.attributes.stars,
|
calculated = result.stars(),
|
||||||
expected = stars,
|
expected = stars,
|
||||||
margin = (result.attributes.stars - stars).abs(),
|
margin = (result.stars() - stars).abs(),
|
||||||
allowed = star_margin * stars,
|
allowed = star_margin * stars,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.pp - pp).abs() < pp_margin * pp,
|
(result.pp() - pp).abs() < pp_margin * pp,
|
||||||
"\nPP:\n\
|
"\nPP:\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.pp,
|
calculated = result.pp(),
|
||||||
expected = pp,
|
expected = pp,
|
||||||
margin = (result.pp - pp).abs(),
|
margin = (result.pp() - pp).abs(),
|
||||||
allowed = pp_margin * pp,
|
allowed = pp_margin * pp,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
|
|||||||
+6
-6
@@ -36,28 +36,28 @@ fn taiko() {
|
|||||||
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
let result = rosu_pp::TaikoPP::new(&map).mods(*mods).calculate();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.stars - stars).abs() < star_margin * stars,
|
(result.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.stars(),
|
||||||
expected = stars,
|
expected = stars,
|
||||||
margin = (result.stars - stars).abs(),
|
margin = (result.stars() - stars).abs(),
|
||||||
allowed = star_margin * stars,
|
allowed = star_margin * stars,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
(result.pp - pp).abs() < pp_margin * pp,
|
(result.pp() - pp).abs() < pp_margin * pp,
|
||||||
"\nPP:\n\
|
"\nPP:\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.pp,
|
calculated = result.pp(),
|
||||||
expected = pp,
|
expected = pp,
|
||||||
margin = (result.pp - pp).abs(),
|
margin = (result.pp() - pp).abs(),
|
||||||
allowed = pp_margin * pp,
|
allowed = pp_margin * pp,
|
||||||
map = map_id,
|
map = map_id,
|
||||||
mods = mods
|
mods = mods
|
||||||
|
|||||||
Reference in New Issue
Block a user