reworked attribute calculation (#15)
This commit is contained in:
+250
-87
@@ -1,87 +1,250 @@
|
|||||||
use crate::Mods;
|
use crate::{Beatmap, GameMode, Mods};
|
||||||
|
|
||||||
/// Summary struct for a [`Beatmap`](crate::Beatmap)'s attributes.
|
/// Summary struct for a [`Beatmap`]'s attributes.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct BeatmapAttributes {
|
pub struct BeatmapAttributes {
|
||||||
/// The approach rate.
|
/// The approach rate.
|
||||||
pub ar: f64,
|
pub ar: f64,
|
||||||
/// The overall difficulty.
|
/// The overall difficulty.
|
||||||
pub od: f64,
|
pub od: f64,
|
||||||
/// The circle size.
|
/// The circle size.
|
||||||
pub cs: f64,
|
pub cs: f64,
|
||||||
/// The health drain rate
|
/// The health drain rate
|
||||||
pub hp: f64,
|
pub hp: f64,
|
||||||
/// The clock rate with respect to mods.
|
/// The clock rate with respect to mods.
|
||||||
pub clock_rate: f64,
|
pub clock_rate: f64,
|
||||||
}
|
/// The hit windows for approach rate and overall difficulty.
|
||||||
|
pub hit_windows: BeatmapHitWindows,
|
||||||
impl BeatmapAttributes {
|
}
|
||||||
const AR0_MS: f64 = 1800.0;
|
|
||||||
const AR5_MS: f64 = 1200.0;
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
const AR10_MS: f64 = 450.0;
|
/// AR and OD hit windows
|
||||||
const AR_MS_STEP_1: f64 = (Self::AR0_MS - Self::AR5_MS) / 5.0;
|
pub struct BeatmapHitWindows {
|
||||||
const AR_MS_STEP_2: f64 = (Self::AR5_MS - Self::AR10_MS) / 5.0;
|
/// Hit window for approach rate i.e. TimePreempt in milliseconds.
|
||||||
|
pub ar: f64,
|
||||||
#[inline]
|
/// Hit window for overall difficulty i.e. time to hit a 300 ("Great") in milliseconds.
|
||||||
pub(crate) fn new(ar: f32, od: f32, cs: f32, hp: f32) -> Self {
|
pub od: f64,
|
||||||
Self {
|
}
|
||||||
ar: ar as f64,
|
|
||||||
od: od as f64,
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
cs: cs as f64,
|
/// Specify values for this builder to get [`BeatmapAttributes`] or [`BeatmapHitWindows`] based on
|
||||||
hp: hp as f64,
|
/// mods & co.
|
||||||
clock_rate: 1.0,
|
pub struct BeatmapAttributesBuilder {
|
||||||
}
|
mode: GameMode,
|
||||||
}
|
ar: f64,
|
||||||
|
od: f64,
|
||||||
/// Adjusts attributes w.r.t. mods.
|
cs: f64,
|
||||||
/// AR is further adjusted by its hitwindow.
|
hp: f64,
|
||||||
/// OD is __not__ adjusted by its hitwindow.
|
mods: Option<u32>,
|
||||||
pub fn mods(self, mods: impl Mods) -> Self {
|
clock_rate: Option<f64>,
|
||||||
if !mods.change_map() {
|
converted: bool,
|
||||||
return self;
|
}
|
||||||
}
|
|
||||||
|
impl BeatmapAttributesBuilder {
|
||||||
let clock_rate = mods.clock_rate();
|
const OSU_MIN: f64 = 80.0;
|
||||||
let multiplier = mods.od_ar_hp_multiplier();
|
const OSU_AVG: f64 = 50.0;
|
||||||
|
const OSU_MAX: f64 = 20.0;
|
||||||
// AR
|
|
||||||
let mut ar = (self.ar * multiplier) as f64;
|
const TAIKO_MIN: f64 = 50.0;
|
||||||
let mut ar_ms = if ar <= 5.0 {
|
const TAIKO_AVG: f64 = 35.0;
|
||||||
Self::AR0_MS - Self::AR_MS_STEP_1 * ar
|
const TAIKO_MAX: f64 = 20.0;
|
||||||
} else {
|
|
||||||
Self::AR5_MS - Self::AR_MS_STEP_2 * (ar - 5.0)
|
#[inline]
|
||||||
};
|
/// Create a new [`BeatmapAttributesBuilder`].
|
||||||
|
pub fn new(map: &Beatmap) -> Self {
|
||||||
ar_ms = ar_ms.max(Self::AR10_MS).min(Self::AR0_MS);
|
Self::from(map)
|
||||||
ar_ms /= clock_rate;
|
}
|
||||||
|
|
||||||
ar = if ar_ms > Self::AR5_MS {
|
#[inline]
|
||||||
(Self::AR0_MS - ar_ms) / Self::AR_MS_STEP_1
|
/// Specify the mode.
|
||||||
} else {
|
pub fn mode(&mut self, mode: GameMode) -> &mut Self {
|
||||||
5.0 + (Self::AR5_MS - ar_ms) / Self::AR_MS_STEP_2
|
self.mode = mode;
|
||||||
};
|
|
||||||
|
self
|
||||||
// OD
|
}
|
||||||
let od = (self.od * multiplier).min(10.0);
|
|
||||||
|
#[inline]
|
||||||
// CS
|
/// Specify the approach rate.
|
||||||
let mut cs = self.cs;
|
pub fn ar(&mut self, ar: f64) -> &mut Self {
|
||||||
if mods.hr() {
|
self.ar = ar;
|
||||||
cs *= 1.3;
|
|
||||||
} else if mods.ez() {
|
self
|
||||||
cs *= 0.5;
|
}
|
||||||
}
|
|
||||||
cs = cs.min(10.0);
|
#[inline]
|
||||||
|
/// Specify the overall difficulty.
|
||||||
// HP
|
pub fn od(&mut self, od: f64) -> &mut Self {
|
||||||
let hp = (self.hp * multiplier).min(10.0);
|
self.od = od;
|
||||||
|
|
||||||
Self {
|
self
|
||||||
ar,
|
}
|
||||||
od,
|
|
||||||
cs,
|
#[inline]
|
||||||
hp,
|
/// Specify the circle size.
|
||||||
clock_rate,
|
pub fn cs(&mut self, cs: f64) -> &mut Self {
|
||||||
}
|
self.cs = cs;
|
||||||
}
|
|
||||||
}
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Specify the drain rate.
|
||||||
|
pub fn hp(&mut self, hp: f64) -> &mut Self {
|
||||||
|
self.hp = hp;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Specify the mods.
|
||||||
|
pub fn mods(&mut self, mods: u32) -> &mut Self {
|
||||||
|
self.mods = Some(mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Specify a custom clock rate.
|
||||||
|
pub fn clock_rate(&mut self, clock_rate: f64) -> &mut Self {
|
||||||
|
self.clock_rate = Some(clock_rate);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Specify whether it's a converted map.
|
||||||
|
/// Only relevant for mania.
|
||||||
|
pub fn converted(&mut self, converted: bool) -> &mut Self {
|
||||||
|
self.converted = converted;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Calculate the AR and OD hit windows.
|
||||||
|
pub fn hit_windows(&self) -> BeatmapHitWindows {
|
||||||
|
let mods = self.mods.unwrap_or(0);
|
||||||
|
let clock_rate = self.clock_rate.unwrap_or_else(|| mods.clock_rate());
|
||||||
|
|
||||||
|
let mod_mult = |val: f64| {
|
||||||
|
if mods.hr() {
|
||||||
|
(val * 1.4).min(10.0)
|
||||||
|
} else if mods.ez() {
|
||||||
|
val * 0.5
|
||||||
|
} else {
|
||||||
|
val
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let raw_ar = mod_mult(self.ar);
|
||||||
|
let preempt = difficulty_range(raw_ar, 1800.0, 1200.0, 450.0) / clock_rate;
|
||||||
|
|
||||||
|
// OD
|
||||||
|
let hit_window = match self.mode {
|
||||||
|
GameMode::Osu | GameMode::Catch => {
|
||||||
|
let raw_od = mod_mult(self.od);
|
||||||
|
|
||||||
|
difficulty_range(raw_od, Self::OSU_MIN, Self::OSU_AVG, Self::OSU_MAX) / clock_rate
|
||||||
|
}
|
||||||
|
GameMode::Taiko => {
|
||||||
|
let raw_od = mod_mult(self.od);
|
||||||
|
|
||||||
|
difficulty_range(raw_od, Self::TAIKO_MIN, Self::TAIKO_AVG, Self::TAIKO_MAX).floor()
|
||||||
|
/ clock_rate
|
||||||
|
}
|
||||||
|
GameMode::Mania => {
|
||||||
|
let mut value = if !self.converted {
|
||||||
|
34.0 + 3.0 * (10.0 - self.od).clamp(0.0, 10.0)
|
||||||
|
} else if self.od > 4.0 {
|
||||||
|
34.0
|
||||||
|
} else {
|
||||||
|
47.0
|
||||||
|
};
|
||||||
|
|
||||||
|
if mods.hr() {
|
||||||
|
value /= 1.4;
|
||||||
|
} else if mods.ez() {
|
||||||
|
value *= 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
((value * clock_rate).floor() / clock_rate).ceil()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BeatmapHitWindows {
|
||||||
|
ar: preempt,
|
||||||
|
od: hit_window,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the [`BeatmapAttributes`].
|
||||||
|
pub fn build(&self) -> BeatmapAttributes {
|
||||||
|
let mods = self.mods.unwrap_or(0);
|
||||||
|
let clock_rate = self.clock_rate.unwrap_or_else(|| mods.clock_rate());
|
||||||
|
let multiplier = mods.od_ar_hp_multiplier();
|
||||||
|
|
||||||
|
// HP
|
||||||
|
let hp = (self.hp * multiplier).min(10.0);
|
||||||
|
|
||||||
|
// CS
|
||||||
|
let mut cs = self.cs;
|
||||||
|
|
||||||
|
if mods.hr() {
|
||||||
|
cs = (cs * 1.3).min(10.);
|
||||||
|
} else if mods.ez() {
|
||||||
|
cs *= 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hit_windows = self.hit_windows();
|
||||||
|
let BeatmapHitWindows { ar, od } = hit_windows;
|
||||||
|
|
||||||
|
// AR
|
||||||
|
let ar = if ar > 1200.0 {
|
||||||
|
(1800.0 - ar) / 120.0
|
||||||
|
} else {
|
||||||
|
(1200.0 - ar) / 150.0 + 5.0
|
||||||
|
};
|
||||||
|
|
||||||
|
// OD
|
||||||
|
let od = match self.mode {
|
||||||
|
GameMode::Osu => (Self::OSU_MIN - od) / (Self::OSU_MIN - Self::OSU_AVG) * 5.0,
|
||||||
|
GameMode::Taiko => (Self::TAIKO_MIN - od) / (Self::TAIKO_MIN - Self::TAIKO_AVG) * 5.0,
|
||||||
|
GameMode::Catch | GameMode::Mania => self.od,
|
||||||
|
};
|
||||||
|
|
||||||
|
BeatmapAttributes {
|
||||||
|
ar,
|
||||||
|
od,
|
||||||
|
cs,
|
||||||
|
hp,
|
||||||
|
clock_rate,
|
||||||
|
hit_windows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Beatmap> for BeatmapAttributesBuilder {
|
||||||
|
#[inline]
|
||||||
|
fn from(map: &Beatmap) -> Self {
|
||||||
|
Self {
|
||||||
|
mode: map.mode,
|
||||||
|
ar: map.ar as f64,
|
||||||
|
od: map.od as f64,
|
||||||
|
cs: map.cs as f64,
|
||||||
|
hp: map.hp as f64,
|
||||||
|
mods: None,
|
||||||
|
clock_rate: None,
|
||||||
|
converted: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn difficulty_range(difficulty: f64, min: f64, mid: f64, max: f64) -> f64 {
|
||||||
|
if difficulty > 5.0 {
|
||||||
|
mid + (max - mid) * (difficulty - 5.0) / 5.0
|
||||||
|
} else if difficulty < 5.0 {
|
||||||
|
mid - (mid - min) * (5.0 - difficulty) / 5.0
|
||||||
|
} else {
|
||||||
|
mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -3,7 +3,7 @@ use std::{borrow::Cow, cmp::Ordering};
|
|||||||
use crate::parse::HitObject;
|
use crate::parse::HitObject;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
attributes::BeatmapAttributes,
|
attributes::{BeatmapAttributes, BeatmapAttributesBuilder, BeatmapHitWindows},
|
||||||
breaks::Break,
|
breaks::Break,
|
||||||
control_points::{ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint},
|
control_points::{ControlPoint, ControlPointIter, DifficultyPoint, TimingPoint},
|
||||||
mode::GameMode,
|
mode::GameMode,
|
||||||
@@ -66,8 +66,8 @@ pub struct Beatmap {
|
|||||||
impl Beatmap {
|
impl Beatmap {
|
||||||
/// Extract a beatmap's attributes into their own type.
|
/// Extract a beatmap's attributes into their own type.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn attributes(&self) -> BeatmapAttributes {
|
pub fn attributes(&self) -> BeatmapAttributesBuilder {
|
||||||
BeatmapAttributes::new(self.ar, self.od, self.cs, self.hp)
|
BeatmapAttributesBuilder::new(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The beats per minute of the map.
|
/// The beats per minute of the map.
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ pub struct CatchGradualDifficultyAttributes<'map> {
|
|||||||
|
|
||||||
impl<'map> CatchGradualDifficultyAttributes<'map> {
|
impl<'map> CatchGradualDifficultyAttributes<'map> {
|
||||||
/// Create a new difficulty attributes iterator for osu!catch maps.
|
/// Create a new difficulty attributes iterator for osu!catch maps.
|
||||||
pub fn new(map: &'map Beatmap, mods: impl Mods) -> Self {
|
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
|
||||||
let map_attributes = map.attributes().mods(mods);
|
let map_attributes = map.attributes().mods(mods).build();
|
||||||
|
|
||||||
let attributes = CatchDifficultyAttributes {
|
let attributes = CatchDifficultyAttributes {
|
||||||
ar: map_attributes.ar,
|
ar: map_attributes.ar,
|
||||||
|
|||||||
+2
-2
@@ -147,8 +147,8 @@ fn calculate_movement(params: CatchStars<'_>) -> (Movement, CatchDifficultyAttri
|
|||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let take = passed_objects.unwrap_or(usize::MAX);
|
let take = passed_objects.unwrap_or(usize::MAX);
|
||||||
let map_attributes = map.attributes().mods(mods);
|
let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate());
|
||||||
let clock_rate = clock_rate.unwrap_or(map_attributes.clock_rate);
|
let map_attributes = map.attributes().mods(mods).clock_rate(clock_rate).build();
|
||||||
|
|
||||||
let attributes = CatchDifficultyAttributes {
|
let attributes = CatchDifficultyAttributes {
|
||||||
ar: map_attributes.ar,
|
ar: map_attributes.ar,
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
mania::{ManiaGradualDifficultyAttributes, ManiaGradualPerformanceAttributes},
|
mania::{ManiaGradualDifficultyAttributes, ManiaGradualPerformanceAttributes},
|
||||||
osu::{OsuGradualDifficultyAttributes, OsuGradualPerformanceAttributes, OsuScoreState},
|
osu::{OsuGradualDifficultyAttributes, OsuGradualPerformanceAttributes, OsuScoreState},
|
||||||
taiko::{TaikoGradualDifficultyAttributes, TaikoGradualPerformanceAttributes, TaikoScoreState},
|
taiko::{TaikoGradualDifficultyAttributes, TaikoGradualPerformanceAttributes, TaikoScoreState},
|
||||||
Beatmap, DifficultyAttributes, GameMode, Mods, PerformanceAttributes,
|
Beatmap, DifficultyAttributes, GameMode, PerformanceAttributes,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Gradually calculate the difficulty attributes on maps of any mode.
|
/// Gradually calculate the difficulty attributes on maps of any mode.
|
||||||
@@ -51,7 +51,7 @@ pub enum GradualDifficultyAttributes<'map> {
|
|||||||
|
|
||||||
impl<'map> GradualDifficultyAttributes<'map> {
|
impl<'map> GradualDifficultyAttributes<'map> {
|
||||||
/// Create a new gradual difficulty calculator for maps of any mode.
|
/// Create a new gradual difficulty calculator for maps of any mode.
|
||||||
pub fn new(map: &'map Beatmap, mods: impl Mods) -> Self {
|
pub fn new(map: &'map Beatmap, mods: u32) -> Self {
|
||||||
match map.mode {
|
match map.mode {
|
||||||
GameMode::Osu => Self::Osu(OsuGradualDifficultyAttributes::new(map, mods)),
|
GameMode::Osu => Self::Osu(OsuGradualDifficultyAttributes::new(map, mods)),
|
||||||
GameMode::Taiko => Self::Taiko(TaikoGradualDifficultyAttributes::new(map, mods)),
|
GameMode::Taiko => Self::Taiko(TaikoGradualDifficultyAttributes::new(map, mods)),
|
||||||
|
|||||||
+2
-13
@@ -242,7 +242,7 @@ pub trait BeatmapExt {
|
|||||||
/// Return an iterator that gives you the [`DifficultyAttributes`] after each hit object.
|
/// Return an iterator that gives you the [`DifficultyAttributes`] after each hit object.
|
||||||
///
|
///
|
||||||
/// Suitable to efficiently get the map's star rating after multiple different locations.
|
/// Suitable to efficiently get the map's star rating after multiple different locations.
|
||||||
fn gradual_difficulty(&self, mods: impl Mods) -> GradualDifficultyAttributes<'_>;
|
fn gradual_difficulty(&self, mods: u32) -> GradualDifficultyAttributes<'_>;
|
||||||
|
|
||||||
/// Return a struct that gives you the [`PerformanceAttributes`] after every (few) hit object(s).
|
/// Return a struct that gives you the [`PerformanceAttributes`] after every (few) hit object(s).
|
||||||
///
|
///
|
||||||
@@ -294,7 +294,7 @@ impl BeatmapExt for Beatmap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn gradual_difficulty(&self, mods: impl Mods) -> GradualDifficultyAttributes<'_> {
|
fn gradual_difficulty(&self, mods: u32) -> GradualDifficultyAttributes<'_> {
|
||||||
GradualDifficultyAttributes::new(self, mods)
|
GradualDifficultyAttributes::new(self, mods)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,16 +511,5 @@ impl From<taiko::TaikoPerformanceAttributes> for PerformanceAttributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn difficulty_range(val: f64, max: f64, avg: f64, min: f64) -> f64 {
|
|
||||||
if val > 5.0 {
|
|
||||||
avg + (max - avg) * (val - 5.0) / 5.0
|
|
||||||
} else if val < 5.0 {
|
|
||||||
avg - (avg - min) * (5.0 - val) / 5.0
|
|
||||||
} else {
|
|
||||||
avg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(feature = "async_tokio", feature = "async_std"))]
|
#[cfg(all(feature = "async_tokio", feature = "async_std"))]
|
||||||
compile_error!("Only one of the features `async_tokio` and `async_std` should be enabled");
|
compile_error!("Only one of the features `async_tokio` and `async_std` should be enabled");
|
||||||
|
|||||||
+12
-17
@@ -1,7 +1,10 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use super::{ManiaDifficultyAttributes, ManiaPerformanceAttributes, ManiaStars};
|
use super::{ManiaDifficultyAttributes, ManiaPerformanceAttributes, ManiaStars};
|
||||||
use crate::{Beatmap, DifficultyAttributes, GameMode, Mods, OsuPP, PerformanceAttributes};
|
use crate::{
|
||||||
|
beatmap::BeatmapHitWindows, Beatmap, DifficultyAttributes, GameMode, Mods, OsuPP,
|
||||||
|
PerformanceAttributes,
|
||||||
|
};
|
||||||
|
|
||||||
/// Performance calculator on osu!mania maps.
|
/// Performance calculator on osu!mania maps.
|
||||||
///
|
///
|
||||||
@@ -142,9 +145,16 @@ impl<'map> ManiaPP<'map> {
|
|||||||
scaled_score /= percent_passed;
|
scaled_score /= percent_passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut od = 34.0 + 3.0 * (10.0 - self.map.od as f64).max(0.0).min(10.0);
|
|
||||||
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.clock_rate());
|
let clock_rate = self.clock_rate.unwrap_or_else(|| self.mods.clock_rate());
|
||||||
|
|
||||||
|
let BeatmapHitWindows { od: hit_window, .. } = self
|
||||||
|
.map
|
||||||
|
.attributes()
|
||||||
|
.mods(self.mods)
|
||||||
|
.clock_rate(clock_rate)
|
||||||
|
.converted(matches!(self.map, Cow::Owned(_)))
|
||||||
|
.hit_windows();
|
||||||
|
|
||||||
let mut multiplier = 0.8;
|
let mut multiplier = 0.8;
|
||||||
|
|
||||||
if nf {
|
if nf {
|
||||||
@@ -153,23 +163,8 @@ impl<'map> ManiaPP<'map> {
|
|||||||
|
|
||||||
if ez {
|
if ez {
|
||||||
multiplier *= 0.5;
|
multiplier *= 0.5;
|
||||||
od *= 1.4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let hit_window = {
|
|
||||||
let not_converted = matches!(self.map, Cow::Borrowed(_));
|
|
||||||
|
|
||||||
let value = if not_converted {
|
|
||||||
od
|
|
||||||
} else if self.map.od > 4.0 {
|
|
||||||
34.0
|
|
||||||
} else {
|
|
||||||
47.0
|
|
||||||
};
|
|
||||||
|
|
||||||
((value * clock_rate).floor() / clock_rate).ceil()
|
|
||||||
};
|
|
||||||
|
|
||||||
let strain_value = self.compute_strain(scaled_score, stars);
|
let strain_value = self.compute_strain(scaled_score, stars);
|
||||||
let acc_value = self.compute_accuracy_value(scaled_score, strain_value, hit_window);
|
let acc_value = self.compute_accuracy_value(scaled_score, strain_value, hit_window);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
calculate_star_rating, difficulty_range_ar, difficulty_range_od, old_stacking,
|
calculate_star_rating, old_stacking,
|
||||||
osu_object::{ObjectParameters, OsuObject, OsuObjectKind},
|
osu_object::{ObjectParameters, OsuObject, OsuObjectKind},
|
||||||
scaling_factor::ScalingFactor,
|
scaling_factor::ScalingFactor,
|
||||||
skill::{Skill, Skills},
|
skill::{Skill, Skills},
|
||||||
@@ -58,27 +58,17 @@ pub struct OsuGradualDifficultyAttributes {
|
|||||||
|
|
||||||
impl OsuGradualDifficultyAttributes {
|
impl OsuGradualDifficultyAttributes {
|
||||||
/// Create a new difficulty attributes iterator for osu!standard maps.
|
/// Create a new difficulty attributes iterator for osu!standard maps.
|
||||||
pub fn new(map: &Beatmap, mods: impl Mods) -> Self {
|
pub fn new(map: &Beatmap, mods: u32) -> Self {
|
||||||
let map_attributes = map.attributes().mods(mods);
|
let map_attributes = map.attributes().mods(mods).build();
|
||||||
let hit_window = difficulty_range_od(map_attributes.od) / map_attributes.clock_rate;
|
let hit_window = map_attributes.hit_windows.od;
|
||||||
let od = (80.0 - hit_window) / 6.0;
|
let time_preempt = map_attributes.hit_windows.ar;
|
||||||
|
|
||||||
let mut raw_ar = map.ar as f64;
|
|
||||||
let hr = mods.hr();
|
let hr = mods.hr();
|
||||||
|
|
||||||
if hr {
|
|
||||||
raw_ar = (raw_ar * 1.4).min(10.0);
|
|
||||||
} else if mods.ez() {
|
|
||||||
raw_ar *= 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
let time_preempt = difficulty_range_ar(raw_ar);
|
|
||||||
let scaling_factor = ScalingFactor::new(map_attributes.cs);
|
let scaling_factor = ScalingFactor::new(map_attributes.cs);
|
||||||
|
|
||||||
let mut attributes = OsuDifficultyAttributes {
|
let mut attributes = OsuDifficultyAttributes {
|
||||||
ar: map_attributes.ar,
|
ar: map_attributes.ar,
|
||||||
hp: map_attributes.hp,
|
hp: map_attributes.hp,
|
||||||
od,
|
od: map_attributes.od,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+5
-25
@@ -265,26 +265,16 @@ fn calculate_skills(params: OsuStars<'_>) -> (Skills, OsuDifficultyAttributes) {
|
|||||||
let take = passed_objects.unwrap_or(map.hit_objects.len());
|
let take = passed_objects.unwrap_or(map.hit_objects.len());
|
||||||
let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate());
|
let clock_rate = clock_rate.unwrap_or_else(|| mods.clock_rate());
|
||||||
|
|
||||||
let map_attributes = map.attributes().mods(mods);
|
let map_attributes = map.attributes().mods(mods).clock_rate(clock_rate).build();
|
||||||
let hit_window = difficulty_range_od(map_attributes.od) / clock_rate;
|
|
||||||
let od = (80.0 - hit_window) / 6.0;
|
|
||||||
|
|
||||||
let mut raw_ar = map.ar as f64;
|
|
||||||
let hr = mods.hr();
|
|
||||||
|
|
||||||
if hr {
|
|
||||||
raw_ar = (raw_ar * 1.4).min(10.0);
|
|
||||||
} else if mods.ez() {
|
|
||||||
raw_ar *= 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
let time_preempt = difficulty_range_ar(raw_ar);
|
|
||||||
let scaling_factor = ScalingFactor::new(map_attributes.cs);
|
let scaling_factor = ScalingFactor::new(map_attributes.cs);
|
||||||
|
let hr = mods.hr();
|
||||||
|
let time_preempt = map_attributes.hit_windows.ar;
|
||||||
|
let hit_window = map_attributes.hit_windows.od;
|
||||||
|
|
||||||
let mut attributes = OsuDifficultyAttributes {
|
let mut attributes = OsuDifficultyAttributes {
|
||||||
ar: map_attributes.ar,
|
ar: map_attributes.ar,
|
||||||
hp: map_attributes.hp,
|
hp: map_attributes.hp,
|
||||||
od,
|
od: map_attributes.od,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -521,11 +511,6 @@ fn old_stacking(hit_objects: &mut [OsuObject], stack_threshold: f64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn difficulty_range_ar(ar: f64) -> f64 {
|
|
||||||
crate::difficulty_range(ar, 450.0, 1200.0, 1800.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lerp(start: f64, end: f64, percent: f64) -> f64 {
|
fn lerp(start: f64, end: f64, percent: f64) -> f64 {
|
||||||
start + (end - start) * percent
|
start + (end - start) * percent
|
||||||
}
|
}
|
||||||
@@ -610,8 +595,3 @@ impl From<OsuPerformanceAttributes> for OsuDifficultyAttributes {
|
|||||||
attributes.difficulty
|
attributes.difficulty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn difficulty_range_od(od: f64) -> f64 {
|
|
||||||
super::difficulty_range(od, 20.0, 50.0, 80.0)
|
|
||||||
}
|
|
||||||
|
|||||||
+10
-14
@@ -1,7 +1,10 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use super::{TaikoDifficultyAttributes, TaikoPerformanceAttributes, TaikoScoreState, TaikoStars};
|
use super::{TaikoDifficultyAttributes, TaikoPerformanceAttributes, TaikoScoreState, TaikoStars};
|
||||||
use crate::{Beatmap, DifficultyAttributes, GameMode, Mods, OsuPP, PerformanceAttributes};
|
use crate::{
|
||||||
|
beatmap::BeatmapHitWindows, Beatmap, DifficultyAttributes, GameMode, Mods, OsuPP,
|
||||||
|
PerformanceAttributes,
|
||||||
|
};
|
||||||
|
|
||||||
/// Performance calculator on osu!taiko maps.
|
/// Performance calculator on osu!taiko maps.
|
||||||
///
|
///
|
||||||
@@ -281,15 +284,13 @@ impl<'map> TaikoPPInner<'map> {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn compute_accuracy_value(&self) -> f64 {
|
fn compute_accuracy_value(&self) -> f64 {
|
||||||
let mut od = self.map.od as f64;
|
let BeatmapHitWindows { od: hit_window, .. } = self
|
||||||
|
.map
|
||||||
|
.attributes()
|
||||||
|
.mods(self.mods)
|
||||||
|
.clock_rate(self.clock_rate)
|
||||||
|
.hit_windows();
|
||||||
|
|
||||||
if self.mods.hr() {
|
|
||||||
od *= 1.4;
|
|
||||||
} else if self.mods.ez() {
|
|
||||||
od *= 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hit_window = difficulty_range_od(od).floor() / self.clock_rate;
|
|
||||||
let max_combo = self.attributes.max_combo;
|
let max_combo = self.attributes.max_combo;
|
||||||
|
|
||||||
(150.0 / hit_window).powf(1.1)
|
(150.0 / hit_window).powf(1.1)
|
||||||
@@ -299,11 +300,6 @@ impl<'map> TaikoPPInner<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn difficulty_range_od(od: f64) -> f64 {
|
|
||||||
crate::difficulty_range(od, 20.0, 35.0, 50.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'map> From<OsuPP<'map>> for TaikoPP<'map> {
|
impl<'map> From<OsuPP<'map>> for TaikoPP<'map> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(osu: OsuPP<'map>) -> Self {
|
fn from(osu: OsuPP<'map>) -> Self {
|
||||||
|
|||||||
Reference in New Issue
Block a user