osu & fruits: added unit tests for acc in pp calculators
This commit is contained in:
+180
-18
@@ -6,12 +6,14 @@ pub trait FruitsAttributeProvider {
|
||||
}
|
||||
|
||||
impl FruitsAttributeProvider for DifficultyAttributes {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FruitsAttributeProvider for StarResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Self::Fruits(attributes) = self {
|
||||
@@ -23,12 +25,14 @@ impl FruitsAttributeProvider for StarResult {
|
||||
}
|
||||
|
||||
impl FruitsAttributeProvider for PpResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||
self.attributes.attributes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculator for pp on osu!ctb maps.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FruitsPP<'m> {
|
||||
map: &'m Beatmap,
|
||||
attributes: Option<DifficultyAttributes>,
|
||||
@@ -109,7 +113,7 @@ impl<'m> FruitsPP<'m> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of tiny droplets of a play.
|
||||
/// Specify the amount of tiny droplets of a play i.e. n50.
|
||||
#[inline]
|
||||
pub fn tiny_droplets(mut self, n_tiny_droplets: usize) -> Self {
|
||||
self.n_tiny_droplets.replace(n_tiny_droplets);
|
||||
@@ -117,7 +121,7 @@ impl<'m> FruitsPP<'m> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify the amount of tiny droplet misses of a play.
|
||||
/// Specify the amount of tiny droplet misses of a play i.e. n_katu.
|
||||
#[inline]
|
||||
pub fn tiny_droplet_misses(mut self, n_tiny_droplet_misses: usize) -> Self {
|
||||
self.n_tiny_droplet_misses.replace(n_tiny_droplet_misses);
|
||||
@@ -144,7 +148,7 @@ impl<'m> FruitsPP<'m> {
|
||||
/// Generate the hit results with respect to the given accuracy between `0` and `100`.
|
||||
///
|
||||
/// Be sure to set `misses` beforehand! Also, if available, set `attributes` beforehand.
|
||||
pub fn accuracy(mut self, acc: f32) -> Self {
|
||||
pub fn accuracy(mut self, mut acc: f32) -> Self {
|
||||
if self.attributes.is_none() {
|
||||
self.attributes.replace(
|
||||
stars(self.map, self.mods, self.passed_objects)
|
||||
@@ -162,10 +166,12 @@ impl<'m> FruitsPP<'m> {
|
||||
let n_fruits = self.n_fruits.unwrap_or_else(|| {
|
||||
attributes
|
||||
.max_combo
|
||||
.saturating_sub(self.n_misses.saturating_sub(n_droplets))
|
||||
.saturating_sub(self.n_misses)
|
||||
.saturating_sub(n_droplets)
|
||||
});
|
||||
|
||||
let max_tiny_droplets = attributes.n_tiny_droplets;
|
||||
acc /= 100.0;
|
||||
|
||||
let n_tiny_droplets = self.n_tiny_droplets.unwrap_or_else(|| {
|
||||
((acc * (attributes.max_combo + max_tiny_droplets) as f32).round() as usize)
|
||||
@@ -183,21 +189,29 @@ impl<'m> FruitsPP<'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.take().unwrap_or_else(|| {
|
||||
stars(self.map, self.mods, self.passed_objects)
|
||||
.attributes()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
// Make sure all objects are set
|
||||
if self
|
||||
fn assert_hitresults(&mut self, attributes: &DifficultyAttributes) {
|
||||
let correct_combo_hits = self
|
||||
.n_fruits
|
||||
.and(self.n_droplets)
|
||||
.and(self.n_tiny_droplets)
|
||||
.and(self.n_tiny_droplet_misses)
|
||||
.and_then(|f| self.n_droplets.map(|d| f + d + self.n_misses))
|
||||
.filter(|h| *h == attributes.max_combo);
|
||||
|
||||
let correct_fruits = self
|
||||
.n_fruits
|
||||
.filter(|f| *f >= attributes.n_fruits.saturating_sub(self.n_misses));
|
||||
|
||||
let correct_droplets = self
|
||||
.n_droplets
|
||||
.filter(|d| *d >= attributes.n_droplets.saturating_sub(self.n_misses));
|
||||
|
||||
let correct_tinies = self
|
||||
.n_tiny_droplets
|
||||
.and_then(|t| self.n_tiny_droplet_misses.map(|m| t + m))
|
||||
.filter(|h| *h == attributes.n_tiny_droplets);
|
||||
|
||||
if correct_combo_hits
|
||||
.and(correct_fruits)
|
||||
.and(correct_droplets)
|
||||
.and(correct_tinies)
|
||||
.is_none()
|
||||
{
|
||||
let mut n_fruits = self.n_fruits.unwrap_or(0);
|
||||
@@ -226,6 +240,19 @@ impl<'m> FruitsPP<'m> {
|
||||
self.n_tiny_droplets.replace(n_tiny_droplets);
|
||||
self.n_tiny_droplet_misses.replace(n_tiny_droplet_misses);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.take().unwrap_or_else(|| {
|
||||
stars(self.map, self.mods, self.passed_objects)
|
||||
.attributes()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
// Make sure all objects are set
|
||||
self.assert_hitresults(&attributes);
|
||||
|
||||
let stars = attributes.stars;
|
||||
|
||||
@@ -322,3 +349,138 @@ impl<'m> FruitsPP<'m> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Beatmap;
|
||||
|
||||
fn attributes() -> DifficultyAttributes {
|
||||
DifficultyAttributes {
|
||||
n_fruits: 1234,
|
||||
n_droplets: 567,
|
||||
n_tiny_droplets: 2345,
|
||||
max_combo: 1234 + 567,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fruits_only_accuracy() {
|
||||
let map = Beatmap::default();
|
||||
let attributes = attributes();
|
||||
|
||||
let total_objects = attributes.n_fruits + attributes.n_droplets;
|
||||
let target_acc = 97.5;
|
||||
|
||||
let calculator = FruitsPP::new(&map)
|
||||
.attributes(attributes)
|
||||
.passed_objects(total_objects)
|
||||
.accuracy(target_acc);
|
||||
|
||||
let numerator = calculator.n_fruits.unwrap_or(0)
|
||||
+ calculator.n_droplets.unwrap_or(0)
|
||||
+ calculator.n_tiny_droplets.unwrap_or(0);
|
||||
let denominator =
|
||||
numerator + calculator.n_tiny_droplet_misses.unwrap_or(0) + calculator.n_misses;
|
||||
let acc = 100.0 * numerator as f32 / denominator as f32;
|
||||
|
||||
assert!(
|
||||
(target_acc - acc).abs() < 1.0,
|
||||
"Expected: {} | Actual: {}",
|
||||
target_acc,
|
||||
acc
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fruits_accuracy_droplets_and_tiny_droplets() {
|
||||
let map = Beatmap::default();
|
||||
let attributes = attributes();
|
||||
|
||||
let total_objects = attributes.n_fruits + attributes.n_droplets;
|
||||
let target_acc = 97.5;
|
||||
let n_droplets = 550;
|
||||
let n_tiny_droplets = 2222;
|
||||
|
||||
let calculator = FruitsPP::new(&map)
|
||||
.attributes(attributes)
|
||||
.passed_objects(total_objects)
|
||||
.droplets(n_droplets)
|
||||
.tiny_droplets(n_tiny_droplets)
|
||||
.accuracy(target_acc);
|
||||
|
||||
assert_eq!(
|
||||
n_droplets,
|
||||
calculator.n_droplets.unwrap(),
|
||||
"Expected: {} | Actual: {}",
|
||||
n_droplets,
|
||||
calculator.n_droplets.unwrap()
|
||||
);
|
||||
|
||||
let numerator = calculator.n_fruits.unwrap_or(0)
|
||||
+ calculator.n_droplets.unwrap_or(0)
|
||||
+ calculator.n_tiny_droplets.unwrap_or(0);
|
||||
let denominator =
|
||||
numerator + calculator.n_tiny_droplet_misses.unwrap_or(0) + calculator.n_misses;
|
||||
let acc = 100.0 * numerator as f32 / denominator as f32;
|
||||
|
||||
assert!(
|
||||
(target_acc - acc).abs() < 1.0,
|
||||
"Expected: {} | Actual: {}",
|
||||
target_acc,
|
||||
acc
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fruits_missing_objects() {
|
||||
let map = Beatmap::default();
|
||||
let attributes = attributes();
|
||||
|
||||
let total_objects = attributes.n_fruits + attributes.n_droplets;
|
||||
let n_fruits = attributes.n_fruits - 10;
|
||||
let n_droplets = attributes.n_droplets - 5;
|
||||
let n_tiny_droplets = attributes.n_tiny_droplets - 50;
|
||||
let n_tiny_droplet_misses = 20;
|
||||
let n_misses = 2;
|
||||
|
||||
let mut calculator = FruitsPP::new(&map)
|
||||
.attributes(attributes.clone())
|
||||
.passed_objects(total_objects)
|
||||
.fruits(n_fruits)
|
||||
.droplets(n_droplets)
|
||||
.tiny_droplets(n_tiny_droplets)
|
||||
.tiny_droplet_misses(n_tiny_droplet_misses)
|
||||
.misses(n_misses);
|
||||
|
||||
calculator.assert_hitresults(&attributes);
|
||||
|
||||
assert!(
|
||||
(attributes.n_fruits as i32 - calculator.n_fruits.unwrap() as i32).abs()
|
||||
<= n_misses as i32,
|
||||
"Expected: {} | Actual: {} [+/- {} misses]",
|
||||
attributes.n_fruits,
|
||||
calculator.n_fruits.unwrap(),
|
||||
n_misses
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
attributes.n_droplets,
|
||||
calculator.n_droplets.unwrap()
|
||||
- (n_misses - (attributes.n_fruits - calculator.n_fruits.unwrap())),
|
||||
"Expected: {} | Actual: {}",
|
||||
attributes.n_droplets,
|
||||
calculator.n_droplets.unwrap()
|
||||
- (n_misses - (attributes.n_fruits - calculator.n_fruits.unwrap())),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
attributes.n_tiny_droplets,
|
||||
calculator.n_tiny_droplets.unwrap() + calculator.n_tiny_droplet_misses.unwrap(),
|
||||
"Expected: {} | Actual: {}",
|
||||
attributes.n_tiny_droplets,
|
||||
calculator.n_tiny_droplets.unwrap() + calculator.n_tiny_droplet_misses.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -1,23 +1,26 @@
|
||||
use super::{stars, DifficultyAttributes};
|
||||
use crate::{Beatmap, Mods, PpResult, StarResult};
|
||||
|
||||
pub trait ManiaStarProvider {
|
||||
pub trait ManiaAttributeProvider {
|
||||
fn attributes(self) -> Option<f32>;
|
||||
}
|
||||
|
||||
impl ManiaStarProvider for f32 {
|
||||
impl ManiaAttributeProvider for f32 {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<f32> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ManiaStarProvider for DifficultyAttributes {
|
||||
impl ManiaAttributeProvider for DifficultyAttributes {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<f32> {
|
||||
Some(self.stars)
|
||||
}
|
||||
}
|
||||
|
||||
impl ManiaStarProvider for StarResult {
|
||||
impl ManiaAttributeProvider for StarResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<f32> {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Self::Mania(attributes) = self {
|
||||
@@ -28,13 +31,14 @@ impl ManiaStarProvider for StarResult {
|
||||
}
|
||||
}
|
||||
|
||||
impl ManiaStarProvider for PpResult {
|
||||
impl ManiaAttributeProvider for PpResult {
|
||||
fn attributes(self) -> Option<f32> {
|
||||
self.attributes.attributes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculator for pp on osu!mania maps.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ManiaPP<'m> {
|
||||
map: &'m Beatmap,
|
||||
stars: Option<f32>,
|
||||
@@ -55,13 +59,13 @@ impl<'m> ManiaPP<'m> {
|
||||
}
|
||||
}
|
||||
|
||||
/// [`ManiaStarsProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
|
||||
/// [`ManiaAttributeProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
|
||||
/// and by [`PpResult`](crate::PpResult) meaning you can give the star rating,
|
||||
/// the result of a star calculation, or the result of 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]
|
||||
pub fn attributes(mut self, attributes: impl ManiaStarProvider) -> Self {
|
||||
pub fn attributes(mut self, attributes: impl ManiaAttributeProvider) -> Self {
|
||||
if let Some(stars) = attributes.attributes() {
|
||||
self.stars.replace(stars);
|
||||
}
|
||||
|
||||
+131
-29
@@ -6,12 +6,14 @@ pub trait OsuAttributeProvider {
|
||||
}
|
||||
|
||||
impl OsuAttributeProvider for DifficultyAttributes {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl OsuAttributeProvider for StarResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Self::Osu(attributes) = self {
|
||||
@@ -23,12 +25,14 @@ impl OsuAttributeProvider for StarResult {
|
||||
}
|
||||
|
||||
impl OsuAttributeProvider for PpResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<DifficultyAttributes> {
|
||||
self.attributes.attributes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculator for pp on osu!standard maps.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OsuPP<'m> {
|
||||
map: &'m Beatmap,
|
||||
attributes: Option<DifficultyAttributes>,
|
||||
@@ -196,6 +200,38 @@ impl<'m> OsuPP<'m> {
|
||||
self
|
||||
}
|
||||
|
||||
fn assert_hitresults(&mut self) {
|
||||
if self.acc.is_none() {
|
||||
let n_objects = self
|
||||
.passed_objects
|
||||
.unwrap_or_else(|| self.map.hit_objects.len());
|
||||
|
||||
let remaining = n_objects
|
||||
.saturating_sub(self.n300.unwrap_or(0))
|
||||
.saturating_sub(self.n100.unwrap_or(0))
|
||||
.saturating_sub(self.n50.unwrap_or(0))
|
||||
.saturating_sub(self.n_misses);
|
||||
|
||||
if remaining > 0 {
|
||||
if self.n300.is_none() {
|
||||
self.n300.replace(remaining);
|
||||
self.n100.get_or_insert(0);
|
||||
self.n50.get_or_insert(0);
|
||||
} else if self.n100.is_none() {
|
||||
self.n100.replace(remaining);
|
||||
self.n50.get_or_insert(0);
|
||||
} else if self.n50.is_none() {
|
||||
self.n50.replace(remaining);
|
||||
} else {
|
||||
*self.n300.as_mut().unwrap() += remaining;
|
||||
}
|
||||
}
|
||||
|
||||
let numerator = self.n50.unwrap() + self.n100.unwrap() * 2 + self.n300.unwrap() * 6;
|
||||
self.acc.replace(numerator as f32 / n_objects as f32 / 6.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an object which contains the pp and [`DifficultyAttributes`](crate::osu::DifficultyAttributes)
|
||||
/// containing stars and other attributes.
|
||||
#[cfg(feature = "no_leniency")]
|
||||
@@ -232,35 +268,7 @@ impl<'m> OsuPP<'m> {
|
||||
self.attributes.replace(attributes);
|
||||
}
|
||||
|
||||
if self.acc.is_none() {
|
||||
let n_objects = self
|
||||
.passed_objects
|
||||
.unwrap_or_else(|| self.map.hit_objects.len());
|
||||
|
||||
let remaining = n_objects
|
||||
.saturating_sub(self.n300.unwrap_or(0))
|
||||
.saturating_sub(self.n100.unwrap_or(0))
|
||||
.saturating_sub(self.n50.unwrap_or(0))
|
||||
.saturating_sub(self.n_misses);
|
||||
|
||||
if remaining > 0 {
|
||||
if self.n300.is_none() {
|
||||
self.n300.replace(remaining);
|
||||
self.n100.get_or_insert(0);
|
||||
self.n50.get_or_insert(0);
|
||||
} else if self.n100.is_none() {
|
||||
self.n100.replace(remaining);
|
||||
self.n50.get_or_insert(0);
|
||||
} else if self.n50.is_none() {
|
||||
self.n50.replace(remaining);
|
||||
} else {
|
||||
*self.n300.as_mut().unwrap() += remaining;
|
||||
}
|
||||
}
|
||||
|
||||
let numerator = self.n50.unwrap() + self.n100.unwrap() * 2 + self.n300.unwrap() * 6;
|
||||
self.acc.replace(numerator as f32 / n_objects as f32 / 6.0);
|
||||
}
|
||||
self.assert_hitresults();
|
||||
|
||||
let total_hits = self.total_hits();
|
||||
let mut multiplier = 1.12;
|
||||
@@ -432,3 +440,97 @@ impl<'m> OsuPP<'m> {
|
||||
self.n300.unwrap_or(0) + self.n100.unwrap_or(0) + self.n50.unwrap_or(0) + self.n_misses
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::Beatmap;
|
||||
|
||||
#[test]
|
||||
fn osu_only_accuracy() {
|
||||
let map = Beatmap::default();
|
||||
|
||||
let total_objects = 1234;
|
||||
let target_acc = 97.5;
|
||||
|
||||
let calculator = OsuPP::new(&map)
|
||||
.passed_objects(total_objects)
|
||||
.accuracy(target_acc);
|
||||
|
||||
let numerator = 6 * calculator.n300.unwrap_or(0)
|
||||
+ 2 * calculator.n100.unwrap_or(0)
|
||||
+ calculator.n50.unwrap_or(0);
|
||||
let denominator = 6 * total_objects;
|
||||
let acc = 100.0 * numerator as f32 / denominator as f32;
|
||||
|
||||
assert!(
|
||||
(target_acc - acc).abs() < 1.0,
|
||||
"Expected: {} | Actual: {}",
|
||||
target_acc,
|
||||
acc
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osu_accuracy_and_n50() {
|
||||
let map = Beatmap::default();
|
||||
|
||||
let total_objects = 1234;
|
||||
let target_acc = 97.5;
|
||||
let n50 = 30;
|
||||
|
||||
let calculator = OsuPP::new(&map)
|
||||
.passed_objects(total_objects)
|
||||
.n50(n50)
|
||||
.accuracy(target_acc);
|
||||
|
||||
assert!(
|
||||
(calculator.n50.unwrap() as i32 - n50 as i32).abs() <= 4,
|
||||
"Expected: {} | Actual: {}",
|
||||
n50,
|
||||
calculator.n50.unwrap()
|
||||
);
|
||||
|
||||
let numerator = 6 * calculator.n300.unwrap_or(0)
|
||||
+ 2 * calculator.n100.unwrap_or(0)
|
||||
+ calculator.n50.unwrap_or(0);
|
||||
let denominator = 6 * total_objects;
|
||||
let acc = 100.0 * numerator as f32 / denominator as f32;
|
||||
|
||||
assert!(
|
||||
(target_acc - acc).abs() < 1.0,
|
||||
"Expected: {} | Actual: {}",
|
||||
target_acc,
|
||||
acc
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osu_missing_objects() {
|
||||
let map = Beatmap::default();
|
||||
|
||||
let total_objects = 1234;
|
||||
let n300 = 1000;
|
||||
let n100 = 200;
|
||||
let n50 = 30;
|
||||
|
||||
let mut calculator = OsuPP::new(&map)
|
||||
.passed_objects(total_objects)
|
||||
.n300(n300)
|
||||
.n100(n100)
|
||||
.n50(n50);
|
||||
|
||||
calculator.assert_hitresults();
|
||||
|
||||
let n_objects = calculator.n300.unwrap()
|
||||
+ calculator.n100.unwrap()
|
||||
+ calculator.n50.unwrap()
|
||||
+ calculator.n_misses;
|
||||
|
||||
assert_eq!(
|
||||
total_objects, n_objects,
|
||||
"Expected: {} | Actual: {}",
|
||||
total_objects, n_objects
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -1,23 +1,26 @@
|
||||
use super::{stars, DifficultyAttributes};
|
||||
use crate::{Beatmap, Mods, PpResult, StarResult};
|
||||
|
||||
pub trait TaikoStarProvider {
|
||||
pub trait TaikoAttributeProvider {
|
||||
fn attributes(self) -> Option<f32>;
|
||||
}
|
||||
|
||||
impl TaikoStarProvider for f32 {
|
||||
impl TaikoAttributeProvider for f32 {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<f32> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaikoStarProvider for DifficultyAttributes {
|
||||
impl TaikoAttributeProvider for DifficultyAttributes {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<f32> {
|
||||
Some(self.stars)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaikoStarProvider for StarResult {
|
||||
impl TaikoAttributeProvider for StarResult {
|
||||
#[inline]
|
||||
fn attributes(self) -> Option<f32> {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let StarResult::Taiko(attributes) = self {
|
||||
@@ -28,13 +31,14 @@ impl TaikoStarProvider for StarResult {
|
||||
}
|
||||
}
|
||||
|
||||
impl TaikoStarProvider for PpResult {
|
||||
impl TaikoAttributeProvider for PpResult {
|
||||
fn attributes(self) -> Option<f32> {
|
||||
self.attributes.attributes()
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculator for pp on osu!taiko maps.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TaikoPP<'m> {
|
||||
map: &'m Beatmap,
|
||||
stars: Option<f32>,
|
||||
@@ -61,13 +65,13 @@ impl<'m> TaikoPP<'m> {
|
||||
}
|
||||
}
|
||||
|
||||
/// [`TaikoStarProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
|
||||
/// [`TaikoAttributeProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
|
||||
/// and by [`PpResult`](crate::PpResult) meaning you can give the star rating,
|
||||
/// 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(mut self, attributes: impl TaikoStarProvider) -> Self {
|
||||
pub fn attributes(mut self, attributes: impl TaikoAttributeProvider) -> Self {
|
||||
if let Some(stars) = attributes.attributes() {
|
||||
self.stars.replace(stars);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user