use the same StarResult type for all modes

This commit is contained in:
MaxOhn
2021-01-18 12:55:46 +01:00
parent aae601a91e
commit 44538a7022
11 changed files with 170 additions and 120 deletions
+9 -9
View File
@@ -11,7 +11,7 @@ use movement::Movement;
pub use pp::*; pub use pp::*;
use slider_state::SliderState; use slider_state::SliderState;
use crate::{curve::Curve, Beatmap, HitObjectKind, Mods, PathType, Pos2}; use crate::{curve::Curve, Beatmap, HitObjectKind, Mods, PathType, Pos2, StarResult};
use std::convert::identity; use std::convert::identity;
@@ -27,15 +27,13 @@ const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
/// ///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects. /// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
// Slider parsing based on https://github.com/osufx/catch-the-pp // Slider parsing based on https://github.com/osufx/catch-the-pp
pub fn stars( pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> StarResult {
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> DifficultyAttributes {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
if take < 2 { if take < 2 {
return DifficultyAttributes::default(); return StarResult::Fruits {
attributes: DifficultyAttributes::default(),
};
} }
let attributes = map.attributes().mods(mods); let attributes = map.attributes().mods(mods);
@@ -268,14 +266,16 @@ pub fn stars(
let stars = movement.difficulty_value().sqrt() * STAR_SCALING_FACTOR; let stars = movement.difficulty_value().sqrt() * STAR_SCALING_FACTOR;
DifficultyAttributes { let attributes = DifficultyAttributes {
stars, stars,
ar: attributes.ar, ar: attributes.ar,
n_fruits: fruits, n_fruits: fruits,
n_droplets: droplets, n_droplets: droplets,
n_tiny_droplets: tiny_droplets, n_tiny_droplets: tiny_droplets,
max_combo: fruits + droplets, max_combo: fruits + droplets,
} };
StarResult::Fruits { attributes }
} }
fn tiny_droplet_count( fn tiny_droplet_count(
+26 -13
View File
@@ -1,5 +1,5 @@
use super::{stars, DifficultyAttributes}; use super::{stars, DifficultyAttributes};
use crate::{Beatmap, Mods, PpResult}; use crate::{Beatmap, Mods, PpResult, StarResult};
pub trait FruitsAttributeProvider { pub trait FruitsAttributeProvider {
fn attributes(self) -> Option<DifficultyAttributes>; fn attributes(self) -> Option<DifficultyAttributes>;
@@ -11,9 +11,9 @@ impl FruitsAttributeProvider for DifficultyAttributes {
} }
} }
impl FruitsAttributeProvider for PpResult { impl FruitsAttributeProvider for StarResult {
fn attributes(self) -> Option<DifficultyAttributes> { fn attributes(self) -> Option<DifficultyAttributes> {
if let PpResult::Fruits { attributes, .. } = self { if let Self::Fruits { attributes } = self {
Some(attributes) Some(attributes)
} else { } else {
None None
@@ -21,6 +21,12 @@ impl FruitsAttributeProvider for PpResult {
} }
} }
impl FruitsAttributeProvider for PpResult {
fn attributes(self) -> Option<DifficultyAttributes> {
self.attributes.attributes()
}
}
/// Calculator for pp on osu!ctb maps. /// Calculator for pp on osu!ctb maps.
pub struct FruitsPP<'m> { pub struct FruitsPP<'m> {
map: &'m Beatmap, map: &'m Beatmap,
@@ -54,8 +60,8 @@ impl<'m> FruitsPP<'m> {
} }
} }
/// [`FruitsAttributeProvider`] is implemented by [`DifficultyAttributes`](crate::fruits::DifficultyAttributes) /// [`FruitsAttributeProvider`] is implemented by [`DifficultyAttributes`](crate::fruits::DifficultyAttributes),
/// and by [`PpResult`](crate::PpResult) meaning you can give the /// [`StarResult`](crate::StarResult), and by [`PpResult`](crate::PpResult) meaning you can give the
/// result of a star calculation or a pp calculation. /// result of a star calculation or a pp calculation.
/// If you already calculated the attributes for the current map-mod combination, /// 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. /// be sure to put them in here so that they don't have to be recalculated.
@@ -139,8 +145,11 @@ impl<'m> FruitsPP<'m> {
/// Be sure to set `misses` beforehand! Also, if available, set `attributes` beforehand. /// 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, acc: f32) -> Self {
if self.attributes.is_none() { if self.attributes.is_none() {
self.attributes self.attributes.replace(
.replace(stars(self.map, self.mods, self.passed_objects)); stars(self.map, self.mods, self.passed_objects)
.attributes()
.unwrap(),
);
} }
let attributes = self.attributes.as_ref().unwrap(); let attributes = self.attributes.as_ref().unwrap();
@@ -163,7 +172,7 @@ impl<'m> FruitsPP<'m> {
.saturating_sub(n_droplets) .saturating_sub(n_droplets)
}); });
let n_tiny_droplet_misses = max_tiny_droplets - n_tiny_droplets; let n_tiny_droplet_misses = max_tiny_droplets.saturating_sub(n_tiny_droplets);
self.n_fruits.replace(n_fruits); self.n_fruits.replace(n_fruits);
self.n_droplets.replace(n_droplets); self.n_droplets.replace(n_droplets);
@@ -176,10 +185,11 @@ impl<'m> FruitsPP<'m> {
/// Returns an object which contains the pp and [`DifficultyAttributes`](crate::fruits::DifficultyAttributes) /// Returns an object which contains the pp and [`DifficultyAttributes`](crate::fruits::DifficultyAttributes)
/// containing stars and other attributes. /// containing stars and other attributes.
pub fn calculate(mut self) -> PpResult { pub fn calculate(mut self) -> PpResult {
let attributes = self let attributes = self.attributes.take().unwrap_or_else(|| {
.attributes stars(self.map, self.mods, self.passed_objects)
.take() .attributes()
.unwrap_or_else(|| stars(self.map, self.mods, self.passed_objects)); .unwrap()
});
let stars = attributes.stars; let stars = attributes.stars;
@@ -239,7 +249,10 @@ impl<'m> FruitsPP<'m> {
pp *= 0.9; pp *= 0.9;
} }
PpResult::Fruits { pp, attributes } PpResult {
pp,
attributes: StarResult::Fruits { attributes },
}
} }
#[inline] #[inline]
+37 -31
View File
@@ -64,8 +64,8 @@
//! //!
//! // If all you want is the map's stars or max pp, //! // If all you want is the map's stars or max pp,
//! // you can make use of the BeatmapExt trait. //! // you can make use of the BeatmapExt trait.
//! let stars = map.stars(16, None); // HR //! let stars = map.stars(16, None).stars(); // HR
//! let max_pp = map.max_pp(16); //! let max_pp = map.max_pp(16).pp();
//! //!
//! println!("Stars: {} | Max PP: {}", stars, max_pp); //! println!("Stars: {} | Max PP: {}", stars, max_pp);
//! ``` //! ```
@@ -117,7 +117,7 @@ pub trait BeatmapExt {
/// Calculate the stars of a beatmap. /// Calculate the stars of a beatmap.
/// ///
/// For osu!standard maps, the `no_leniency` version will be used. /// For osu!standard maps, the `no_leniency` version will be used.
fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> f32; fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> StarResult;
/// Calculate the max pp of a beatmap if that is all you want. /// Calculate the max pp of a beatmap if that is all you want.
/// ///
@@ -125,70 +125,76 @@ pub trait BeatmapExt {
/// ///
/// If you seek more fine-tuning and options you need to match on the map's /// 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) for taiko. /// mode and use the mode's corresponding calculator, e.g. [`TaikoPP`](crate::TaikoPP) for taiko.
fn max_pp(&self, mods: u32) -> f32; fn max_pp(&self, mods: u32) -> PpResult;
} }
impl BeatmapExt for Beatmap { impl BeatmapExt for Beatmap {
fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> f32 { fn stars(&self, mods: impl Mods, passed_objects: Option<usize>) -> StarResult {
match self.mode { match self.mode {
GameMode::STD => osu::no_leniency::stars(self, mods, passed_objects).stars, GameMode::STD => osu::no_leniency::stars(self, mods, passed_objects),
GameMode::MNA => mania::stars(self, mods, passed_objects), GameMode::MNA => mania::stars(self, mods, passed_objects),
GameMode::TKO => taiko::stars(self, mods, passed_objects), GameMode::TKO => taiko::stars(self, mods, passed_objects),
GameMode::CTB => fruits::stars(self, mods, passed_objects).stars, GameMode::CTB => fruits::stars(self, mods, passed_objects),
} }
} }
fn max_pp(&self, mods: u32) -> f32 { fn max_pp(&self, mods: u32) -> PpResult {
match self.mode { match self.mode {
GameMode::STD => OsuPP::new(self) GameMode::STD => OsuPP::new(self)
.mods(mods) .mods(mods)
.calculate(osu::no_leniency::stars) .calculate(osu::no_leniency::stars),
.pp(), GameMode::MNA => ManiaPP::new(self).mods(mods).calculate(),
GameMode::MNA => ManiaPP::new(self).mods(mods).calculate().pp(), GameMode::TKO => TaikoPP::new(self).mods(mods).calculate(),
GameMode::TKO => TaikoPP::new(self).mods(mods).calculate().pp(), GameMode::CTB => FruitsPP::new(self).mods(mods).calculate(),
GameMode::CTB => FruitsPP::new(self).mods(mods).calculate().pp(),
} }
} }
} }
/// Basic enum containing the result of a PP calculation depending on the mode. /// Basic enum containing the result of a star calculation based on the mode.
pub enum PpResult { pub enum StarResult {
Fruits { Fruits {
pp: f32,
attributes: fruits::DifficultyAttributes, attributes: fruits::DifficultyAttributes,
}, },
Mania { Mania {
pp: f32,
stars: f32, stars: f32,
}, },
Osu { Osu {
pp: f32,
attributes: osu::DifficultyAttributes, attributes: osu::DifficultyAttributes,
}, },
Taiko { Taiko {
pp: f32,
stars: f32, stars: f32,
}, },
} }
impl StarResult {
/// The final star value.
#[inline]
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,
}
}
}
/// Basic struct containing the result of a PP calculation.
pub struct PpResult {
pub pp: f32,
pub attributes: StarResult,
}
impl PpResult { impl PpResult {
/// The final pp value. /// The final pp value.
#[inline]
pub fn pp(&self) -> f32 { pub fn pp(&self) -> f32 {
match self { self.pp
Self::Fruits { pp, .. } => *pp,
Self::Mania { pp, .. } => *pp,
Self::Osu { pp, .. } => *pp,
Self::Taiko { pp, .. } => *pp,
}
} }
/// The final star value. /// The final star value.
#[inline]
pub fn stars(&self) -> f32 { pub fn stars(&self) -> f32 {
match self { self.attributes.stars()
Self::Fruits { attributes, .. } => attributes.stars,
Self::Mania { stars, .. } => *stars,
Self::Osu { attributes, .. } => attributes.stars,
Self::Taiko { stars, .. } => *stars,
}
} }
} }
+6 -4
View File
@@ -4,7 +4,7 @@ mod strain;
pub use pp::*; pub use pp::*;
use strain::Strain; use strain::Strain;
use crate::{Beatmap, HitObject, Mods}; use crate::{Beatmap, HitObject, Mods, StarResult};
const SECTION_LEN: f32 = 400.0; const SECTION_LEN: f32 = 400.0;
const STAR_SCALING_FACTOR: f32 = 0.018; const STAR_SCALING_FACTOR: f32 = 0.018;
@@ -12,11 +12,11 @@ const STAR_SCALING_FACTOR: f32 = 0.018;
/// Star calculation for osu!mania maps /// Star calculation for osu!mania maps
/// ///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects. /// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> f32 { pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> StarResult {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
if take < 2 { if take < 2 {
return 0.0; return StarResult::Mania { stars: 0.0 };
} }
let clock_rate = mods.speed(); let clock_rate = mods.speed();
@@ -58,7 +58,9 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> f
strain.save_current_peak(); strain.save_current_peak();
strain.difficulty_value() * STAR_SCALING_FACTOR let stars = strain.difficulty_value() * STAR_SCALING_FACTOR;
StarResult::Mania { stars }
} }
#[derive(Debug)] #[derive(Debug)]
+15 -6
View File
@@ -1,5 +1,5 @@
use super::stars; use super::stars;
use crate::{Beatmap, Mods, PpResult}; use crate::{Beatmap, Mods, PpResult, StarResult};
pub trait ManiaStarProvider { pub trait ManiaStarProvider {
fn attributes(self) -> Option<f32>; fn attributes(self) -> Option<f32>;
@@ -11,9 +11,9 @@ impl ManiaStarProvider for f32 {
} }
} }
impl ManiaStarProvider for PpResult { impl ManiaStarProvider for StarResult {
fn attributes(self) -> Option<f32> { fn attributes(self) -> Option<f32> {
if let PpResult::Mania { stars, .. } = self { if let Self::Mania { stars } = self {
Some(stars) Some(stars)
} else { } else {
None None
@@ -21,6 +21,12 @@ impl ManiaStarProvider for PpResult {
} }
} }
impl ManiaStarProvider for PpResult {
fn attributes(self) -> Option<f32> {
self.attributes.attributes()
}
}
/// Calculator for pp on osu!mania maps. /// Calculator for pp on osu!mania maps.
pub struct ManiaPP<'m> { pub struct ManiaPP<'m> {
map: &'m Beatmap, map: &'m Beatmap,
@@ -42,7 +48,7 @@ impl<'m> ManiaPP<'m> {
} }
} }
/// [`ManiaStarsProvider`] is implemented by `f32` /// [`ManiaStarsProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
/// and by [`PpResult`](crate::PpResult) meaning you can give the /// and by [`PpResult`](crate::PpResult) meaning you can give the
/// result of a star calculation or a pp calculation. /// result of a star calculation or a pp calculation.
/// If you already calculated the attributes for the current map-mod combination, /// If you already calculated the attributes for the current map-mod combination,
@@ -87,7 +93,7 @@ impl<'m> ManiaPP<'m> {
pub fn calculate(self) -> PpResult { pub fn calculate(self) -> PpResult {
let stars = self let stars = self
.stars .stars
.unwrap_or_else(|| stars(self.map, self.mods, self.passed_objects)); .unwrap_or_else(|| stars(self.map, self.mods, self.passed_objects).stars());
let ez = self.mods.ez(); let ez = self.mods.ez();
let nf = self.mods.nf(); let nf = self.mods.nf();
@@ -133,7 +139,10 @@ 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::Mania { pp, stars } PpResult {
pp,
attributes: StarResult::Mania { stars },
}
} }
fn compute_strain(&self, score: f32, stars: f32) -> f32 { fn compute_strain(&self, score: f32, stars: f32) -> f32 {
+17 -8
View File
@@ -1,5 +1,5 @@
use super::DifficultyAttributes; use super::DifficultyAttributes;
use crate::{Beatmap, Mods, PpResult}; use crate::{Beatmap, Mods, PpResult, StarResult};
pub trait OsuAttributeProvider { pub trait OsuAttributeProvider {
fn attributes(self) -> Option<DifficultyAttributes>; fn attributes(self) -> Option<DifficultyAttributes>;
@@ -11,9 +11,9 @@ impl OsuAttributeProvider for DifficultyAttributes {
} }
} }
impl OsuAttributeProvider for PpResult { impl OsuAttributeProvider for StarResult {
fn attributes(self) -> Option<DifficultyAttributes> { fn attributes(self) -> Option<DifficultyAttributes> {
if let PpResult::Osu { attributes, .. } = self { if let Self::Osu { attributes } = self {
Some(attributes) Some(attributes)
} else { } else {
None None
@@ -21,6 +21,12 @@ impl OsuAttributeProvider for PpResult {
} }
} }
impl OsuAttributeProvider for PpResult {
fn attributes(self) -> Option<DifficultyAttributes> {
self.attributes.attributes()
}
}
/// 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,
@@ -170,10 +176,12 @@ 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>) -> DifficultyAttributes, stars_func: impl FnOnce(&Beatmap, u32, Option<usize>) -> StarResult,
) -> 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)
.attributes()
.unwrap();
self.attributes.replace(attributes); self.attributes.replace(attributes);
} }
@@ -227,10 +235,11 @@ impl<'m> OsuPP<'m> {
.powf(1.0 / 1.1) .powf(1.0 / 1.1)
* multiplier; * multiplier;
PpResult::Osu { let attributes = StarResult::Osu {
pp,
attributes: self.attributes.unwrap(), attributes: self.attributes.unwrap(),
} };
PpResult { pp, attributes }
} }
fn compute_aim_value(&self, total_hits: f32) -> f32 { fn compute_aim_value(&self, total_hits: f32) -> f32 {
+9 -9
View File
@@ -11,7 +11,7 @@ use skill::Skill;
use skill_kind::SkillKind; use skill_kind::SkillKind;
use super::super::DifficultyAttributes; use super::super::DifficultyAttributes;
use crate::{Beatmap, Mods}; use crate::{Beatmap, Mods, StarResult};
const SECTION_LEN: f32 = 400.0; const SECTION_LEN: f32 = 400.0;
const DIFFICULTY_MULTIPLIER: f32 = 0.0675; const DIFFICULTY_MULTIPLIER: f32 = 0.0675;
@@ -19,11 +19,7 @@ const DIFFICULTY_MULTIPLIER: f32 = 0.0675;
/// Star calculation for osu!standard maps /// Star calculation for osu!standard maps
/// ///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects. /// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars( pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> StarResult {
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> DifficultyAttributes {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
let attributes = map.attributes().mods(mods); let attributes = map.attributes().mods(mods);
@@ -31,10 +27,12 @@ pub fn stars(
let od = (80.0 - hitwindow) / 6.0; let od = (80.0 - hitwindow) / 6.0;
if take < 2 { if take < 2 {
return DifficultyAttributes { return StarResult::Osu {
attributes: DifficultyAttributes {
ar: attributes.ar, ar: attributes.ar,
od, od,
..Default::default() ..Default::default()
},
}; };
} }
@@ -118,7 +116,7 @@ pub fn stars(
let stars = aim_rating + speed_rating + (aim_rating - speed_rating).abs() / 2.0; let stars = aim_rating + speed_rating + (aim_rating - speed_rating).abs() / 2.0;
DifficultyAttributes { let attributes = DifficultyAttributes {
stars, stars,
ar: attributes.ar, ar: attributes.ar,
od, od,
@@ -127,7 +125,9 @@ pub fn stars(
max_combo: 0, // TODO max_combo: 0, // TODO
n_circles: 0, // TODO n_circles: 0, // TODO
n_spinners: 0, // TODO n_spinners: 0, // TODO
} };
StarResult::Osu { attributes }
} }
#[cfg(test)] #[cfg(test)]
+8 -8
View File
@@ -17,7 +17,7 @@ use skill::Skill;
use skill_kind::SkillKind; use skill_kind::SkillKind;
use slider_state::SliderState; use slider_state::SliderState;
use crate::{Beatmap, Mods}; use crate::{Beatmap, Mods, StarResult};
const OBJECT_RADIUS: f32 = 64.0; const OBJECT_RADIUS: f32 = 64.0;
const SECTION_LEN: f32 = 400.0; const SECTION_LEN: f32 = 400.0;
@@ -33,11 +33,7 @@ const NORMALIZED_RADIUS: f32 = 52.0;
/// processing stack leniency is relatively expensive. /// processing stack leniency is relatively expensive.
/// ///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects. /// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars( pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> StarResult {
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> DifficultyAttributes {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
let map_attributes = map.attributes().mods(mods); let map_attributes = map.attributes().mods(mods);
@@ -51,7 +47,9 @@ pub fn stars(
}; };
if take < 2 { if take < 2 {
return diff_attributes; return StarResult::Osu {
attributes: diff_attributes,
};
} }
let section_len = SECTION_LEN * map_attributes.clock_rate; let section_len = SECTION_LEN * map_attributes.clock_rate;
@@ -150,7 +148,9 @@ pub fn stars(
diff_attributes.speed_strain = speed_strain; diff_attributes.speed_strain = speed_strain;
diff_attributes.aim_strain = aim_strain; diff_attributes.aim_strain = aim_strain;
diff_attributes StarResult::Osu {
attributes: diff_attributes,
}
} }
#[cfg(test)] #[cfg(test)]
@@ -15,7 +15,7 @@ use skill::Skill;
use skill_kind::SkillKind; use skill_kind::SkillKind;
use slider_state::SliderState; use slider_state::SliderState;
use crate::{Beatmap, HitObject, HitObjectKind, Mods}; use crate::{Beatmap, HitObject, HitObjectKind, Mods, StarResult};
use std::borrow::Cow; use std::borrow::Cow;
@@ -31,11 +31,7 @@ const NORMALIZED_RADIUS: f32 = 52.0;
/// However, this is the most efficient one. /// However, this is the most efficient one.
/// ///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects. /// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars( pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> StarResult {
map: &Beatmap,
mods: impl Mods,
passed_objects: Option<usize>,
) -> DifficultyAttributes {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
let attributes = map.attributes().mods(mods); let attributes = map.attributes().mods(mods);
@@ -43,10 +39,12 @@ pub fn stars(
let od = (80.0 - hitwindow) / 6.0; let od = (80.0 - hitwindow) / 6.0;
if take < 2 { if take < 2 {
return DifficultyAttributes { return StarResult::Osu {
attributes: DifficultyAttributes {
ar: attributes.ar, ar: attributes.ar,
od, od,
..Default::default() ..Default::default()
},
}; };
} }
@@ -165,7 +163,8 @@ pub fn stars(
let stars = aim_strain + speed_strain + (aim_strain - speed_strain).abs() / 2.0; let stars = aim_strain + speed_strain + (aim_strain - speed_strain).abs() / 2.0;
DifficultyAttributes { StarResult::Osu {
attributes: DifficultyAttributes {
stars, stars,
ar: attributes.ar, ar: attributes.ar,
od, od,
@@ -174,6 +173,7 @@ pub fn stars(
max_combo, max_combo,
n_circles, n_circles,
n_spinners, n_spinners,
},
} }
} }
+6 -4
View File
@@ -16,7 +16,7 @@ use skill::Skill;
use skill_kind::SkillKind; use skill_kind::SkillKind;
use stamina_cheese::StaminaCheeseDetector; use stamina_cheese::StaminaCheeseDetector;
use crate::{Beatmap, Mods}; use crate::{Beatmap, Mods, StarResult};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::f32::consts::PI; use std::f32::consts::PI;
@@ -30,11 +30,11 @@ const STAMINA_SKILL_MULTIPLIER: f32 = 0.02;
/// Star calculation for osu!taiko maps. /// Star calculation for osu!taiko maps.
/// ///
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects. /// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> f32 { pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> StarResult {
let take = passed_objects.unwrap_or_else(|| map.hit_objects.len()); let take = passed_objects.unwrap_or_else(|| map.hit_objects.len());
if take < 2 { if take < 2 {
return 0.0; return StarResult::Taiko { stars: 0.0 };
} }
// True if the object at that index is stamina cheese // True if the object at that index is stamina cheese
@@ -112,7 +112,9 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> f
let combined_rating = locally_combined_difficulty(&skills, stamina_penalty); let combined_rating = locally_combined_difficulty(&skills, stamina_penalty);
let separate_rating = norm(1.5, color_rating, rhythm_rating, stamina_rating); let separate_rating = norm(1.5, color_rating, rhythm_rating, stamina_rating);
rescale(1.4 * separate_rating + 0.5 * combined_rating) let stars = rescale(1.4 * separate_rating + 0.5 * combined_rating);
StarResult::Taiko { stars }
} }
#[inline] #[inline]
+15 -6
View File
@@ -1,4 +1,4 @@
use crate::{Beatmap, Mods, PpResult}; use crate::{Beatmap, Mods, PpResult, StarResult};
pub trait TaikoStarProvider { pub trait TaikoStarProvider {
fn attributes(self) -> Option<f32>; fn attributes(self) -> Option<f32>;
@@ -10,9 +10,9 @@ impl TaikoStarProvider for f32 {
} }
} }
impl TaikoStarProvider for PpResult { impl TaikoStarProvider for StarResult {
fn attributes(self) -> Option<f32> { fn attributes(self) -> Option<f32> {
if let PpResult::Taiko { stars, .. } = self { if let StarResult::Taiko { stars } = self {
Some(stars) Some(stars)
} else { } else {
None None
@@ -20,6 +20,12 @@ impl TaikoStarProvider for PpResult {
} }
} }
impl TaikoStarProvider for PpResult {
fn attributes(self) -> Option<f32> {
self.attributes.attributes()
}
}
/// Calculator for pp on osu!taiko maps. /// Calculator for pp on osu!taiko maps.
pub struct TaikoPP<'m> { pub struct TaikoPP<'m> {
map: &'m Beatmap, map: &'m Beatmap,
@@ -50,7 +56,7 @@ impl<'m> TaikoPP<'m> {
} }
} }
/// [`TaikoStarProvider`] is implemented by `f32` /// [`TaikoStarProvider`] is implemented by `f32`, [`StarResult`](crate::StarResult),
/// and by [`PpResult`](crate::PpResult) meaning you can give the /// and by [`PpResult`](crate::PpResult) meaning you can give the
/// result of a star calculation or a pp calculation. /// result of a star calculation or a pp calculation.
/// If you already calculated the stars for the current map-mod combination, /// If you already calculated the stars for the current map-mod combination,
@@ -110,7 +116,7 @@ impl<'m> TaikoPP<'m> {
pub fn calculate(self) -> PpResult { pub fn calculate(self) -> PpResult {
let stars = self let stars = self
.stars .stars
.unwrap_or_else(|| super::stars(self.map, self.mods, self.passed_objects)); .unwrap_or_else(|| super::stars(self.map, self.mods, self.passed_objects).stars());
let mut multiplier = 1.1; let mut multiplier = 1.1;
@@ -127,7 +133,10 @@ 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::Taiko { stars, pp } PpResult {
pp,
attributes: StarResult::Taiko { stars },
}
} }
fn compute_strain_value(&self, stars: f32) -> f32 { fn compute_strain_value(&self, stars: f32) -> f32 {