Merge branch 'main' of https://github.com/Yentis/rosu-pp into Yentis-main
This commit is contained in:
+1
-1
@@ -407,7 +407,7 @@ impl PerformanceAttributes {
|
|||||||
#[cfg(feature = "osu")]
|
#[cfg(feature = "osu")]
|
||||||
Self::Osu(attributes) => DifficultyAttributes::Osu(attributes.attributes.clone()),
|
Self::Osu(attributes) => DifficultyAttributes::Osu(attributes.attributes.clone()),
|
||||||
#[cfg(feature = "taiko")]
|
#[cfg(feature = "taiko")]
|
||||||
Self::Taiko(attributes) => DifficultyAttributes::Taiko(attributes.attributes),
|
Self::Taiko(attributes) => DifficultyAttributes::Taiko(attributes.attributes.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -37,9 +37,16 @@ pub fn stars(
|
|||||||
mods: impl Mods,
|
mods: impl Mods,
|
||||||
passed_objects: Option<usize>,
|
passed_objects: Option<usize>,
|
||||||
) -> TaikoDifficultyAttributes {
|
) -> TaikoDifficultyAttributes {
|
||||||
|
let max_combo = map.n_circles as usize;
|
||||||
|
|
||||||
let skills = match calculate_skills(map, mods, passed_objects) {
|
let skills = match calculate_skills(map, mods, passed_objects) {
|
||||||
Some(skills) => skills,
|
Some(skills) => skills,
|
||||||
None => return TaikoDifficultyAttributes { stars: 0.0 },
|
None => {
|
||||||
|
return TaikoDifficultyAttributes {
|
||||||
|
max_combo,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut buf = vec![0.0; skills[0].strain_peaks.len()];
|
let mut buf = vec![0.0; skills[0].strain_peaks.len()];
|
||||||
@@ -59,7 +66,7 @@ pub fn stars(
|
|||||||
|
|
||||||
let stars = rescale(1.4 * separate_rating + 0.5 * combined_rating);
|
let stars = rescale(1.4 * separate_rating + 0.5 * combined_rating);
|
||||||
|
|
||||||
TaikoDifficultyAttributes { stars }
|
TaikoDifficultyAttributes { stars, max_combo }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Essentially the same as the [`stars`] function but instead of
|
/// Essentially the same as the [`stars`] function but instead of
|
||||||
@@ -219,14 +226,16 @@ fn norm(p: f64, a: f64, b: f64, c: f64) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a difficulty calculation on an osu!taiko map.
|
/// The result of a difficulty calculation on an osu!taiko map.
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct TaikoDifficultyAttributes {
|
pub struct TaikoDifficultyAttributes {
|
||||||
/// The final star rating.
|
/// The final star rating.
|
||||||
pub stars: f64,
|
pub stars: f64,
|
||||||
|
/// The maximum combo.
|
||||||
|
pub max_combo: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of a performance calculation on an osu!taiko map.
|
/// The result of a performance calculation on an osu!taiko map.
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct TaikoPerformanceAttributes {
|
pub struct TaikoPerformanceAttributes {
|
||||||
/// The difficulty attributes that were used for the performance calculation
|
/// The difficulty attributes that were used for the performance calculation
|
||||||
pub attributes: TaikoDifficultyAttributes,
|
pub attributes: TaikoDifficultyAttributes,
|
||||||
|
|||||||
+28
-36
@@ -34,9 +34,8 @@ use crate::{Beatmap, DifficultyAttributes, Mods, PerformanceAttributes};
|
|||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub struct TaikoPP<'map> {
|
pub struct TaikoPP<'map> {
|
||||||
map: &'map Beatmap,
|
map: &'map Beatmap,
|
||||||
stars: Option<f64>,
|
attributes: Option<TaikoDifficultyAttributes>,
|
||||||
mods: u32,
|
mods: u32,
|
||||||
max_combo: usize,
|
|
||||||
combo: Option<usize>,
|
combo: Option<usize>,
|
||||||
acc: f64,
|
acc: f64,
|
||||||
n_misses: usize,
|
n_misses: usize,
|
||||||
@@ -52,9 +51,8 @@ impl<'map> TaikoPP<'map> {
|
|||||||
pub fn new(map: &'map Beatmap) -> Self {
|
pub fn new(map: &'map Beatmap) -> Self {
|
||||||
Self {
|
Self {
|
||||||
map,
|
map,
|
||||||
stars: None,
|
attributes: None,
|
||||||
mods: 0,
|
mods: 0,
|
||||||
max_combo: map.n_circles as usize,
|
|
||||||
combo: None,
|
combo: None,
|
||||||
acc: 1.0,
|
acc: 1.0,
|
||||||
n_misses: 0,
|
n_misses: 0,
|
||||||
@@ -69,8 +67,8 @@ impl<'map> TaikoPP<'map> {
|
|||||||
/// 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.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn attributes(mut self, attributes: impl TaikoAttributeProvider) -> Self {
|
pub fn attributes(mut self, attributes: impl TaikoAttributeProvider) -> Self {
|
||||||
if let Some(stars) = attributes.attributes() {
|
if let Some(attributes) = attributes.attributes() {
|
||||||
self.stars = Some(stars);
|
self.attributes.replace(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -138,9 +136,10 @@ impl<'map> TaikoPP<'map> {
|
|||||||
|
|
||||||
/// Calculate all performance related values, including pp and stars.
|
/// Calculate all performance related values, including pp and stars.
|
||||||
pub fn calculate(mut self) -> TaikoPerformanceAttributes {
|
pub fn calculate(mut self) -> TaikoPerformanceAttributes {
|
||||||
let stars = self
|
let attributes = self
|
||||||
.stars
|
.attributes
|
||||||
.unwrap_or_else(|| stars(self.map, self.mods, self.passed_objects).stars);
|
.take()
|
||||||
|
.unwrap_or_else(|| stars(self.map, self.mods, self.passed_objects));
|
||||||
|
|
||||||
if self.n300.or(self.n100).is_some() {
|
if self.n300.or(self.n100).is_some() {
|
||||||
let total = self.map.n_circles as usize;
|
let total = self.map.n_circles as usize;
|
||||||
@@ -164,9 +163,8 @@ impl<'map> TaikoPP<'map> {
|
|||||||
|
|
||||||
let inner = TaikoPPInner {
|
let inner = TaikoPPInner {
|
||||||
map: self.map,
|
map: self.map,
|
||||||
stars,
|
attributes,
|
||||||
mods: self.mods,
|
mods: self.mods,
|
||||||
max_combo: self.max_combo,
|
|
||||||
acc: self.acc,
|
acc: self.acc,
|
||||||
n_misses: self.n_misses,
|
n_misses: self.n_misses,
|
||||||
};
|
};
|
||||||
@@ -177,9 +175,8 @@ impl<'map> TaikoPP<'map> {
|
|||||||
|
|
||||||
struct TaikoPPInner<'map> {
|
struct TaikoPPInner<'map> {
|
||||||
map: &'map Beatmap,
|
map: &'map Beatmap,
|
||||||
stars: f64,
|
attributes: TaikoDifficultyAttributes,
|
||||||
mods: u32,
|
mods: u32,
|
||||||
max_combo: usize,
|
|
||||||
acc: f64,
|
acc: f64,
|
||||||
n_misses: usize,
|
n_misses: usize,
|
||||||
}
|
}
|
||||||
@@ -196,25 +193,26 @@ impl<'map> TaikoPPInner<'map> {
|
|||||||
multiplier *= 1.1;
|
multiplier *= 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let strain_value = self.compute_strain_value(self.stars);
|
let strain_value = self.compute_strain_value();
|
||||||
let acc_value = self.compute_accuracy_value();
|
let acc_value = self.compute_accuracy_value();
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
TaikoPerformanceAttributes {
|
TaikoPerformanceAttributes {
|
||||||
attributes: TaikoDifficultyAttributes { stars: self.stars },
|
attributes: self.attributes,
|
||||||
pp,
|
pp,
|
||||||
pp_acc: acc_value,
|
pp_acc: acc_value,
|
||||||
pp_strain: strain_value,
|
pp_strain: strain_value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_strain_value(&self, stars: f64) -> f64 {
|
fn compute_strain_value(&self) -> f64 {
|
||||||
let exp_base = 5.0 * (stars / 0.0075).max(1.0) - 4.0;
|
let attributes = &self.attributes;
|
||||||
|
let exp_base = 5.0 * (attributes.stars / 0.0075).max(1.0) - 4.0;
|
||||||
let mut strain = exp_base * exp_base / 100_000.0;
|
let mut strain = exp_base * exp_base / 100_000.0;
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
let len_bonus = 1.0 + 0.1 * (self.max_combo as f64 / 1500.0).min(1.0);
|
let len_bonus = 1.0 + 0.1 * (attributes.max_combo as f64 / 1500.0).min(1.0);
|
||||||
strain *= len_bonus;
|
strain *= len_bonus;
|
||||||
|
|
||||||
// Penalize misses exponentially
|
// Penalize misses exponentially
|
||||||
@@ -245,11 +243,12 @@ impl<'map> TaikoPPInner<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hit_window = difficulty_range_od(od).floor() / self.mods.speed();
|
let hit_window = difficulty_range_od(od).floor() / self.mods.speed();
|
||||||
|
let max_combo = self.attributes.max_combo;
|
||||||
|
|
||||||
(150.0 / hit_window).powf(1.1)
|
(150.0 / hit_window).powf(1.1)
|
||||||
* self.acc.powi(15)
|
* self.acc.powi(15)
|
||||||
* 22.0
|
* 22.0
|
||||||
* (self.max_combo as f64 / 1500.0).powf(0.3).min(1.15)
|
* (max_combo as f64 / 1500.0).powf(0.3).min(1.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,37 +259,30 @@ fn difficulty_range_od(od: f64) -> f64 {
|
|||||||
|
|
||||||
/// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation.
|
/// Abstract type to provide flexibility when passing difficulty attributes to a performance calculation.
|
||||||
pub trait TaikoAttributeProvider {
|
pub trait TaikoAttributeProvider {
|
||||||
/// Provide the star rating (only difficulty attribute for osu!taiko).
|
/// Provide the actual difficulty attributes.
|
||||||
fn attributes(self) -> Option<f64>;
|
fn attributes(self) -> Option<TaikoDifficultyAttributes>;
|
||||||
}
|
|
||||||
|
|
||||||
impl TaikoAttributeProvider for f64 {
|
|
||||||
#[inline]
|
|
||||||
fn attributes(self) -> Option<f64> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaikoAttributeProvider for TaikoDifficultyAttributes {
|
impl TaikoAttributeProvider for TaikoDifficultyAttributes {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn attributes(self) -> Option<f64> {
|
fn attributes(self) -> Option<TaikoDifficultyAttributes> {
|
||||||
Some(self.stars)
|
Some(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaikoAttributeProvider for TaikoPerformanceAttributes {
|
impl TaikoAttributeProvider for TaikoPerformanceAttributes {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn attributes(self) -> Option<f64> {
|
fn attributes(self) -> Option<TaikoDifficultyAttributes> {
|
||||||
Some(self.attributes.stars)
|
Some(self.attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaikoAttributeProvider for DifficultyAttributes {
|
impl TaikoAttributeProvider for DifficultyAttributes {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn attributes(self) -> Option<f64> {
|
fn attributes(self) -> Option<TaikoDifficultyAttributes> {
|
||||||
#[allow(irrefutable_let_patterns)]
|
#[allow(irrefutable_let_patterns)]
|
||||||
if let Self::Taiko(attributes) = self {
|
if let Self::Taiko(attributes) = self {
|
||||||
Some(attributes.stars)
|
Some(attributes)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -299,10 +291,10 @@ impl TaikoAttributeProvider for DifficultyAttributes {
|
|||||||
|
|
||||||
impl TaikoAttributeProvider for PerformanceAttributes {
|
impl TaikoAttributeProvider for PerformanceAttributes {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn attributes(self) -> Option<f64> {
|
fn attributes(self) -> Option<TaikoDifficultyAttributes> {
|
||||||
#[allow(irrefutable_let_patterns)]
|
#[allow(irrefutable_let_patterns)]
|
||||||
if let Self::Taiko(attributes) = self {
|
if let Self::Taiko(attributes) = self {
|
||||||
Some(attributes.attributes.stars)
|
Some(attributes.attributes)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user