added AnyPP enum to calculate over 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.
|
||||
|
||||
### Usage
|
||||
#### Usage
|
||||
```rust
|
||||
use std::fs::File;
|
||||
use rosu_pp::{Beatmap, BeatmapExt, GameMode, OsuPP, TaikoPP};
|
||||
@@ -20,63 +20,47 @@ let map = match Beatmap::parse(file) {
|
||||
Err(why) => panic!("Error while parsing map: {}", why),
|
||||
};
|
||||
|
||||
// The different modes make things annoying because their
|
||||
// pp calculations require different parameters.
|
||||
// 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)
|
||||
.calculate();
|
||||
// If `BeatmapExt` is included, you can make use of
|
||||
// some methods on `Beatmap` to make your life simpler.
|
||||
// However, to calculate specific pp values, it is recommended
|
||||
// to match on the map's mode yourself and modify the mode's
|
||||
// pp calculator, e.g. `TaikoPP`, manually.
|
||||
let result = map.pp()
|
||||
.mods(24) // HDHR
|
||||
.combo(1234)
|
||||
.misses(2)
|
||||
.accuracy(99.2)
|
||||
.calculate();
|
||||
|
||||
println!("PP: {}", result.pp());
|
||||
println!("PP: {}", result.pp());
|
||||
|
||||
// If you intend to reuse the current map-mod combination,
|
||||
// make use of the previous 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)
|
||||
.combo(543)
|
||||
.misses(5)
|
||||
.n50(3)
|
||||
.accuracy(97.5)
|
||||
.calculate();
|
||||
// If you intend to reuse the current map-mod combination,
|
||||
// make use of the previous result!
|
||||
// If attributes are given, then stars & co don't have to be recalculated.
|
||||
let next_result = map.pp()
|
||||
.mods(24) // HDHR
|
||||
.attributes(result) // recycle
|
||||
.combo(543)
|
||||
.misses(5)
|
||||
.n50(3)
|
||||
.passed_objects(600)
|
||||
.accuracy(96.5)
|
||||
.calculate();
|
||||
|
||||
println!("Next PP: {}", next_result.pp());
|
||||
},
|
||||
GameMode::TKO => {
|
||||
let result = TaikoPP::new(&map)
|
||||
.mods(64) // DT
|
||||
.combo(555)
|
||||
.misses(10)
|
||||
.passed_objects(600)
|
||||
.accuracy(95.12345)
|
||||
.calculate();
|
||||
println!("Next PP: {}", next_result.pp());
|
||||
|
||||
println!("Stars: {} | PP: {}", result.stars(), result.pp());
|
||||
}
|
||||
GameMode::MNA | GameMode::CTB => panic!("do your thing"),
|
||||
}
|
||||
|
||||
// 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).stars(); // HR
|
||||
let max_pp = map.max_pp(16).pp();
|
||||
|
||||
println!("Stars: {} | Max PP: {}", stars, max_pp);
|
||||
```
|
||||
|
||||
### osu!standard versions
|
||||
- `all_included`: WIP
|
||||
#### osu!standard versions
|
||||
- `all_included`: Both stack leniency & slider paths are considered so that the difficulty and pp calculation immitates osu! as close as possible. Pro: Most precise; Con: Least performant.
|
||||
- `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](https://github.com/Francesco149/oppai-ng)): 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`.
|
||||
|
||||
### Features
|
||||
#### Features
|
||||
|
||||
| Flag | Description |
|
||||
|-----|-----|
|
||||
@@ -87,11 +71,11 @@ println!("Stars: {} | Max PP: {}", stars, max_pp);
|
||||
| `osu` | Enable osu!standard. Requires to also enable exactly one of the features `no_leniency`, `no_sliders_no_leniency`, or `all_included`. |
|
||||
| `no_leniency` | When calculating difficulty attributes in osu!standard, ignore stack leniency but consider sliders. Solid middleground between performance and precision, suggested default version. |
|
||||
| `no_sliders_no_leniency` | When calculating difficulty attributes in osu!standard, ignore stack leniency and sliders. Best performance but slightly less precision than `no_leniency`. |
|
||||
| `all_included` | When calculating difficulty attributes in osu!standard, consider both stack leniency and sliders. Best precision but significantly worse performance than `no_leniency`. |
|
||||
| `all_included` | When calculating difficulty attributes in osu!standard, consider both stack leniency and sliders. Best precision but significantly worse performance than `no_leniency`. |
|
||||
|
||||
### Roadmap
|
||||
#### Roadmap
|
||||
- osu sr versions
|
||||
- [ ] all included
|
||||
- [x] all included
|
||||
- [x] no_leniency
|
||||
- [x] no_sliders_no_leniency (i.e. [oppai](https://github.com/Francesco149/oppai-ng))
|
||||
- [x] taiko sr
|
||||
|
||||
+43
-44
@@ -5,7 +5,7 @@
|
||||
//! ### Usage
|
||||
//! ```rust,no_run
|
||||
//! use std::fs::File;
|
||||
//! use rosu_pp::{Beatmap, BeatmapExt, GameMode, OsuPP, TaikoPP};
|
||||
//! use rosu_pp::{Beatmap, BeatmapExt};
|
||||
//!
|
||||
//! let file = match File::open("/path/to/file.osu") {
|
||||
//! Ok(file) => file,
|
||||
@@ -18,51 +18,35 @@
|
||||
//! Err(why) => panic!("Error while parsing map: {}", why),
|
||||
//! };
|
||||
//!
|
||||
//! // The different modes make things annoying because their
|
||||
//! // pp calculations require different parameters.
|
||||
//! // 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)
|
||||
//! .calculate();
|
||||
//! // If `BeatmapExt` is included, you can make use of
|
||||
//! // some methods on `Beatmap` to make your life simpler.
|
||||
//! // However, to calculate specific pp values, it is recommended
|
||||
//! // to match on the map's mode yourself and modify the mode's
|
||||
//! // pp calculator, e.g. `TaikoPP`, manually.
|
||||
//! let result = map.pp()
|
||||
//! .mods(24) // HDHR
|
||||
//! .combo(1234)
|
||||
//! .misses(2)
|
||||
//! .accuracy(99.2)
|
||||
//! .calculate();
|
||||
//!
|
||||
//! println!("PP: {}", result.pp());
|
||||
//! println!("PP: {}", result.pp());
|
||||
//!
|
||||
//! // If you intend to reuse the current map-mod combination,
|
||||
//! // make use of the previous 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)
|
||||
//! .combo(543)
|
||||
//! .misses(5)
|
||||
//! .n50(3)
|
||||
//! .accuracy(97.5)
|
||||
//! .calculate();
|
||||
//! // If you intend to reuse the current map-mod combination,
|
||||
//! // make use of the previous result!
|
||||
//! // If attributes are given, then stars & co don't have to be recalculated.
|
||||
//! let next_result = map.pp()
|
||||
//! .mods(24) // HDHR
|
||||
//! .attributes(result) // recycle
|
||||
//! .combo(543)
|
||||
//! .misses(5)
|
||||
//! .n50(3)
|
||||
//! .passed_objects(600)
|
||||
//! .accuracy(96.5)
|
||||
//! .calculate();
|
||||
//!
|
||||
//! println!("Next PP: {}", next_result.pp());
|
||||
//! },
|
||||
//! GameMode::TKO => {
|
||||
//! let result = TaikoPP::new(&map)
|
||||
//! .mods(64) // DT
|
||||
//! .combo(555)
|
||||
//! .misses(10)
|
||||
//! .passed_objects(600)
|
||||
//! .accuracy(95.12345)
|
||||
//! .calculate();
|
||||
//! println!("Next PP: {}", next_result.pp());
|
||||
//!
|
||||
//! println!("Stars: {} | PP: {}", result.stars(), result.pp());
|
||||
//! }
|
||||
//! GameMode::MNA | GameMode::CTB => panic!("do your thing"),
|
||||
//! }
|
||||
//!
|
||||
//! // 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).stars(); // HR
|
||||
//! let max_pp = map.max_pp(16).pp();
|
||||
//!
|
||||
@@ -70,7 +54,7 @@
|
||||
//! ```
|
||||
//!
|
||||
//! ### osu!standard versions
|
||||
//! - `all_included`: WIP
|
||||
//! - `all_included`: Both stack leniency & slider paths are considered so that the difficulty and pp calculation immitates osu! as close as possible. Pro: Most precise; Con: Least performant.
|
||||
//! - `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](https://github.com/Francesco149/oppai-ng)): 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`.
|
||||
//!
|
||||
@@ -89,7 +73,7 @@
|
||||
//!
|
||||
//! ### Roadmap
|
||||
//! - osu sr versions
|
||||
//! - [ ] all included
|
||||
//! - [x] all included
|
||||
//! - [x] no_leniency
|
||||
//! - [x] no_sliders_no_leniency (i.e. [oppai](https://github.com/Francesco149/oppai-ng))
|
||||
//! - [x] taiko sr
|
||||
@@ -110,6 +94,9 @@ pub mod osu;
|
||||
pub mod parse;
|
||||
pub mod taiko;
|
||||
|
||||
mod pp;
|
||||
pub use pp::{AnyPP, AttributeProvider};
|
||||
|
||||
mod curve;
|
||||
mod math_util;
|
||||
mod mods;
|
||||
@@ -152,6 +139,12 @@ pub trait BeatmapExt {
|
||||
/// mode and use the mode's corresponding calculator, e.g. [`TaikoPP`](crate::TaikoPP) for taiko.
|
||||
fn max_pp(&self, mods: u32) -> PpResult;
|
||||
|
||||
/// Returns a builder to calculate a pp value.
|
||||
///
|
||||
/// Although this method is not terribly bad, it is recommended to match on the
|
||||
/// map's mode yourself and construct the pp builder accordingly.
|
||||
fn pp(&self) -> AnyPP;
|
||||
|
||||
/// Calculate the strains of a map.
|
||||
/// This essentially performs the same calculation as a `stars` function but
|
||||
/// instead of evaluating the final strains, they are just returned as is.
|
||||
@@ -221,6 +214,7 @@ impl BeatmapExt for Beatmap {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn max_pp(&self, mods: u32) -> PpResult {
|
||||
match self.mode {
|
||||
GameMode::STD => {
|
||||
@@ -253,6 +247,11 @@ impl BeatmapExt for Beatmap {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pp(&self) -> AnyPP {
|
||||
AnyPP::new(self)
|
||||
}
|
||||
|
||||
fn strains(&self, mods: impl Mods) -> Strains {
|
||||
match self.mode {
|
||||
GameMode::STD => {
|
||||
|
||||
+2
-2
@@ -47,8 +47,8 @@ impl<'m> ManiaPP<'m> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Score of a play.
|
||||
/// On `NoMod` its between 0 and 1,000,000, on `Easy` between 0 and 500,000, etc
|
||||
/// Specify the score of a play.
|
||||
/// 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);
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
use crate::{Beatmap, GameMode, PpResult, StarResult};
|
||||
|
||||
#[cfg(feature = "fruits")]
|
||||
use crate::FruitsPP;
|
||||
|
||||
#[cfg(feature = "mania")]
|
||||
use crate::ManiaPP;
|
||||
|
||||
#[cfg(feature = "osu")]
|
||||
use crate::OsuPP;
|
||||
|
||||
#[cfg(feature = "taiko")]
|
||||
use crate::TaikoPP;
|
||||
|
||||
/// Calculator for pp on maps of any mode.
|
||||
pub enum AnyPP<'m> {
|
||||
#[cfg(feature = "fruits")]
|
||||
Fruits(FruitsPP<'m>),
|
||||
#[cfg(feature = "mania")]
|
||||
Mania(ManiaPP<'m>),
|
||||
#[cfg(feature = "osu")]
|
||||
Osu(OsuPP<'m>),
|
||||
#[cfg(feature = "taiko")]
|
||||
Taiko(TaikoPP<'m>),
|
||||
}
|
||||
|
||||
impl<'m> AnyPP<'m> {
|
||||
#[inline]
|
||||
pub fn new(map: &'m Beatmap) -> Self {
|
||||
match map.mode {
|
||||
#[cfg(feature = "fruits")]
|
||||
GameMode::CTB => Self::Fruits(FruitsPP::new(map)),
|
||||
#[cfg(feature = "mania")]
|
||||
GameMode::MNA => Self::Mania(ManiaPP::new(map)),
|
||||
#[cfg(feature = "osu")]
|
||||
GameMode::STD => Self::Osu(OsuPP::new(map)),
|
||||
#[cfg(feature = "taiko")]
|
||||
GameMode::TKO => Self::Taiko(TaikoPP::new(map)),
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => panic!("feature for mode {:?} is not enabled", map.mode),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn calculate(self) -> PpResult {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => f.calculate(),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(m) => m.calculate(),
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => o.calculate(),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => t.calculate(),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`AttributeProvider`] is implemented by [`StarResult`](crate::StarResult)
|
||||
/// and [`PpResult`](crate::PpResult) meaning you can give the result of a \
|
||||
/// star calculation or the result of 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]
|
||||
pub fn attributes(self, attributes: impl AttributeProvider) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.attributes(attributes.attributes())),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(m) => Self::Mania(m.attributes(attributes.attributes())),
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.attributes(attributes.attributes())),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.attributes(attributes.attributes())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(self, mods: u32) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.mods(mods)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(m) => Self::Mania(m.mods(mods)),
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.mods(mods)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.mods(mods)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||
#[inline]
|
||||
pub fn passed_objects(self, passed_objects: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.passed_objects(passed_objects)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(m) => Self::Mania(m.passed_objects(passed_objects)),
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.passed_objects(passed_objects)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.passed_objects(passed_objects)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the accuracy between 0.0 and 100.0.
|
||||
///
|
||||
/// For some modes this method depends on previously set values
|
||||
/// be sure to call this last before calling `calculate`.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn accuracy(self, acc: f32) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.accuracy(acc)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.accuracy(acc)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.accuracy(acc)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the amount of misses of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn misses(self, misses: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.misses(misses)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.misses(misses)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.misses(misses)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the max combo of the play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn combo(self, combo: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.combo(combo)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.combo(combo)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.combo(combo)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the amount of 300s of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn n300(self, n300: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.fruits(n300)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.n300(n300)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.n300(n300)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the amount of 100s of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania.
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu", feature = "taiko"))]
|
||||
pub fn n100(self, n100: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.droplets(n100)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.n100(n100)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(t) => Self::Taiko(t.n100(n100)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the amount of 50s of a play.
|
||||
///
|
||||
/// Irrelevant for osu!mania and osu!taiko.
|
||||
#[inline]
|
||||
#[cfg(any(feature = "fruits", feature = "osu"))]
|
||||
pub fn n50(self, n50: usize) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(f) => Self::Fruits(f.tiny_droplets(n50)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(o) => Self::Osu(o.n50(n50)),
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(_) => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the amount of katus of a play.
|
||||
///
|
||||
/// This value is only relevant for osu!ctb for which it represent
|
||||
/// the amount of tiny droplet misses.
|
||||
#[inline]
|
||||
#[cfg(feature = "fruits")]
|
||||
pub fn n_katu(self, n_katu: usize) -> Self {
|
||||
match self {
|
||||
Self::Fruits(f) => Self::Fruits(f.tiny_droplet_misses(n_katu)),
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(_) => self,
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(_) => self,
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(_) => self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify the score of a play.
|
||||
///
|
||||
/// This value is only relevant for osu!mania.
|
||||
///
|
||||
/// On `NoMod` its between 0 and 1,000,000, on `Easy` between 0 and 500,000, etc.
|
||||
#[inline]
|
||||
#[cfg(feature = "mania")]
|
||||
pub fn score(self, score: u32) -> Self {
|
||||
match self {
|
||||
#[cfg(feature = "fruits")]
|
||||
Self::Fruits(_) => self,
|
||||
#[cfg(feature = "mania")]
|
||||
Self::Mania(m) => Self::Mania(m.score(score)),
|
||||
#[cfg(feature = "osu")]
|
||||
Self::Osu(_) => self,
|
||||
#[cfg(feature = "taiko")]
|
||||
Self::Taiko(_) => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AttributeProvider {
|
||||
fn attributes(self) -> StarResult;
|
||||
}
|
||||
|
||||
impl AttributeProvider for StarResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> StarResult {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl AttributeProvider for PpResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> StarResult {
|
||||
self.attributes
|
||||
}
|
||||
}
|
||||
+39
-10
@@ -12,6 +12,9 @@ pub struct TaikoPP<'m> {
|
||||
acc: f32,
|
||||
n_misses: usize,
|
||||
passed_objects: Option<usize>,
|
||||
|
||||
n300: Option<usize>,
|
||||
n100: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'m> TaikoPP<'m> {
|
||||
@@ -26,6 +29,8 @@ impl<'m> TaikoPP<'m> {
|
||||
acc: 1.0,
|
||||
n_misses: 0,
|
||||
passed_objects: None,
|
||||
n300: None,
|
||||
n100: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,16 +66,18 @@ impl<'m> TaikoPP<'m> {
|
||||
self
|
||||
}
|
||||
|
||||
/// osu!taiko pp calculation does not require specific hit results, but only accuracy.
|
||||
/// If the accuracy is already available, provide it through the `accuracy` method,
|
||||
/// otherwise use this method to calculate and set the accuracy through the hit results.
|
||||
/// Specify the amount of 300s of a play.
|
||||
#[inline]
|
||||
pub fn hit_results(mut self, n300: usize, n100: usize, misses: usize) -> Self {
|
||||
let hits = 2 * n300 + n100;
|
||||
let acc = hits as f32 / (2 * (n300 + n100 + misses)) as f32;
|
||||
pub fn n300(mut self, n300: usize) -> Self {
|
||||
self.n300.replace(n300);
|
||||
|
||||
self.acc = acc;
|
||||
self.n_misses = misses;
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of 100s of a play.
|
||||
#[inline]
|
||||
pub fn n100(mut self, n100: usize) -> Self {
|
||||
self.n100.replace(n100);
|
||||
|
||||
self
|
||||
}
|
||||
@@ -78,7 +85,7 @@ impl<'m> TaikoPP<'m> {
|
||||
/// Specify the amount of misses of the play.
|
||||
#[inline]
|
||||
pub fn misses(mut self, n_misses: usize) -> Self {
|
||||
self.n_misses = n_misses;
|
||||
self.n_misses = n_misses.min(self.map.n_circles as usize);
|
||||
|
||||
self
|
||||
}
|
||||
@@ -87,6 +94,8 @@ impl<'m> TaikoPP<'m> {
|
||||
#[inline]
|
||||
pub fn accuracy(mut self, acc: f32) -> Self {
|
||||
self.acc = acc / 100.0;
|
||||
self.n300.take();
|
||||
self.n100.take();
|
||||
|
||||
self
|
||||
}
|
||||
@@ -100,11 +109,31 @@ impl<'m> TaikoPP<'m> {
|
||||
}
|
||||
|
||||
/// Returns an object which contains the pp and stars.
|
||||
pub fn calculate(self) -> PpResult {
|
||||
pub fn calculate(mut self) -> PpResult {
|
||||
let stars = self
|
||||
.stars
|
||||
.unwrap_or_else(|| stars(self.map, self.mods, self.passed_objects).stars());
|
||||
|
||||
if self.n300.or(self.n100).is_some() {
|
||||
let total = self.map.n_circles as usize;
|
||||
let misses = self.n_misses;
|
||||
|
||||
let mut n300 = self.n300.unwrap_or(0).min(total - misses);
|
||||
let mut n100 = self.n100.unwrap_or(0).min(total - n300 - misses);
|
||||
|
||||
let given = n300 + n100 + misses;
|
||||
let missing = total - given;
|
||||
|
||||
match (self.n300, self.n100) {
|
||||
(Some(_), Some(_)) => n300 += missing,
|
||||
(Some(_), None) => n100 += missing,
|
||||
(None, Some(_)) => n300 += missing,
|
||||
(None, None) => unreachable!(),
|
||||
};
|
||||
|
||||
self.acc = (2 * n300 + n100) as f32 / (2 * (n300 + n100 + misses)) as f32;
|
||||
}
|
||||
|
||||
let mut multiplier = 1.1;
|
||||
|
||||
if self.mods.nf() {
|
||||
|
||||
Reference in New Issue
Block a user