use the same StarResult type for all modes
This commit is contained in:
+9
-9
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user