apply new settings of Difficulty
This commit is contained in:
@@ -69,12 +69,12 @@ impl<M: IGameMode> ConvertedDifficulty<'_, M> {
|
|||||||
|
|
||||||
/// Create a gradual difficulty calculator for a [`Converted`] beatmap.
|
/// Create a gradual difficulty calculator for a [`Converted`] beatmap.
|
||||||
pub fn gradual_difficulty(self, map: &Converted<'_, M>) -> M::GradualDifficulty {
|
pub fn gradual_difficulty(self, map: &Converted<'_, M>) -> M::GradualDifficulty {
|
||||||
M::gradual_difficulty(self.inner, map)
|
M::gradual_difficulty(self.inner.to_owned(), map)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a gradual performance calculator for a [`Converted`] beatmap.
|
/// Create a gradual performance calculator for a [`Converted`] beatmap.
|
||||||
pub fn gradual_performance(self, map: &Converted<'_, M>) -> M::GradualPerformance {
|
pub fn gradual_performance(self, map: &Converted<'_, M>) -> M::GradualPerformance {
|
||||||
M::gradual_performance(self.inner, map)
|
M::gradual_performance(self.inner.to_owned(), map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub enum GradualDifficulty {
|
|||||||
macro_rules! from_converted {
|
macro_rules! from_converted {
|
||||||
( $fn:ident, $mode:ident, $converted:ident ) => {
|
( $fn:ident, $mode:ident, $converted:ident ) => {
|
||||||
#[doc = concat!("Create a [`GradualDifficulty`] for a [`", stringify!($converted), "`]")]
|
#[doc = concat!("Create a [`GradualDifficulty`] for a [`", stringify!($converted), "`]")]
|
||||||
pub fn $fn(difficulty: &Difficulty, converted: &$converted<'_>) -> Self {
|
pub fn $fn(difficulty: Difficulty, converted: &$converted<'_>) -> Self {
|
||||||
Self::$mode($mode::gradual_difficulty(difficulty, converted))
|
Self::$mode($mode::gradual_difficulty(difficulty, converted))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -62,7 +62,7 @@ macro_rules! from_converted {
|
|||||||
|
|
||||||
impl GradualDifficulty {
|
impl GradualDifficulty {
|
||||||
/// Create a [`GradualDifficulty`] for a map of any mode.
|
/// Create a [`GradualDifficulty`] for a map of any mode.
|
||||||
pub fn new(difficulty: &Difficulty, map: &Beatmap) -> Self {
|
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Self {
|
||||||
let map = Cow::Borrowed(map);
|
let map = Cow::Borrowed(map);
|
||||||
|
|
||||||
match map.mode {
|
match map.mode {
|
||||||
|
|||||||
+15
-43
@@ -1,7 +1,4 @@
|
|||||||
use std::{
|
use std::{borrow::Cow, num::NonZeroU32};
|
||||||
borrow::Cow,
|
|
||||||
num::{NonZeroU32, NonZeroU64},
|
|
||||||
};
|
|
||||||
|
|
||||||
use rosu_map::section::general::GameMode;
|
use rosu_map::section::general::GameMode;
|
||||||
|
|
||||||
@@ -56,22 +53,24 @@ pub struct Difficulty {
|
|||||||
cs: Option<ModsDependent>,
|
cs: Option<ModsDependent>,
|
||||||
hp: Option<ModsDependent>,
|
hp: Option<ModsDependent>,
|
||||||
od: Option<ModsDependent>,
|
od: Option<ModsDependent>,
|
||||||
/// The slider multiplier will be clamped internally between 0.05 and 50.0.
|
|
||||||
///
|
|
||||||
/// Since its minimum value is 0.05, its bits are never zero.
|
|
||||||
///
|
|
||||||
/// This allows for an optimization to reduce the struct size by storing its
|
|
||||||
/// bits as a [`NonZeroU64`].
|
|
||||||
slider_multiplier: Option<NonZeroU64>,
|
|
||||||
hardrock_offsets: Option<bool>,
|
hardrock_offsets: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
pub struct ModsDependent {
|
pub struct ModsDependent {
|
||||||
pub value: f32,
|
pub value: f32,
|
||||||
pub with_mods: bool,
|
pub with_mods: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ModsDependent {
|
||||||
|
pub const fn new(value: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
value,
|
||||||
|
with_mods: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Difficulty {
|
impl Difficulty {
|
||||||
/// Create a new difficulty calculator.
|
/// Create a new difficulty calculator.
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
@@ -83,7 +82,6 @@ impl Difficulty {
|
|||||||
cs: None,
|
cs: None,
|
||||||
hp: None,
|
hp: None,
|
||||||
od: None,
|
od: None,
|
||||||
slider_multiplier: None,
|
|
||||||
hardrock_offsets: None,
|
hardrock_offsets: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,26 +210,6 @@ impl Difficulty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust a beatmap's set scroll speed.
|
|
||||||
///
|
|
||||||
/// Only relevant for osu!taiko.
|
|
||||||
///
|
|
||||||
/// | Minimum | Maximum |
|
|
||||||
/// | :-----: | :-----: |
|
|
||||||
/// | 0.05 | 50 |
|
|
||||||
pub fn slider_multiplier(self, slider_multiplier: f64) -> Self {
|
|
||||||
let slider_multiplier = slider_multiplier.clamp(0.05, 50.0).to_bits();
|
|
||||||
|
|
||||||
// SAFETY: The minimum value is 0.05 so its bits can never be fully
|
|
||||||
// zero.
|
|
||||||
let non_zero = unsafe { NonZeroU64::new_unchecked(slider_multiplier) };
|
|
||||||
|
|
||||||
Self {
|
|
||||||
slider_multiplier: Some(non_zero),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adjust patterns as if the HR mod is enabled.
|
/// Adjust patterns as if the HR mod is enabled.
|
||||||
///
|
///
|
||||||
/// Only relevant for osu!catch.
|
/// Only relevant for osu!catch.
|
||||||
@@ -276,12 +254,12 @@ impl Difficulty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a gradual difficulty calculator for a [`Beatmap`].
|
/// Create a gradual difficulty calculator for a [`Beatmap`].
|
||||||
pub fn gradual_difficulty(&self, map: &Beatmap) -> GradualDifficulty {
|
pub fn gradual_difficulty(self, map: &Beatmap) -> GradualDifficulty {
|
||||||
GradualDifficulty::new(self, map)
|
GradualDifficulty::new(self, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a gradual performance calculator for a [`Beatmap`].
|
/// Create a gradual performance calculator for a [`Beatmap`].
|
||||||
pub fn gradual_performance(&self, map: &Beatmap) -> GradualPerformance {
|
pub fn gradual_performance(self, map: &Beatmap) -> GradualPerformance {
|
||||||
GradualPerformance::new(self, map)
|
GradualPerformance::new(self, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,14 +294,8 @@ impl Difficulty {
|
|||||||
self.od
|
self.od
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_slider_multiplier(&self) -> f64 {
|
pub(crate) fn get_hardrock_offsets(&self) -> bool {
|
||||||
self.slider_multiplier
|
self.hardrock_offsets.unwrap_or(self.mods.hr())
|
||||||
.map(NonZeroU64::get)
|
|
||||||
.map_or(1.0, f64::from_bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) const fn get_hardrock_offsets(&self) -> Option<bool> {
|
|
||||||
self.hardrock_offsets
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ pub enum GradualPerformance {
|
|||||||
macro_rules! from_converted {
|
macro_rules! from_converted {
|
||||||
( $fn:ident, $mode:ident, $converted:ident ) => {
|
( $fn:ident, $mode:ident, $converted:ident ) => {
|
||||||
#[doc = concat!("Create a [`GradualPerformance`] for a [`", stringify!($converted), "`]")]
|
#[doc = concat!("Create a [`GradualPerformance`] for a [`", stringify!($converted), "`]")]
|
||||||
pub fn $fn(difficulty: &Difficulty, converted: &$converted<'_>) -> Self {
|
pub fn $fn(difficulty: Difficulty, converted: &$converted<'_>) -> Self {
|
||||||
Self::$mode($mode::gradual_performance(difficulty, converted))
|
Self::$mode($mode::gradual_performance(difficulty, converted))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -111,7 +111,7 @@ macro_rules! from_converted {
|
|||||||
|
|
||||||
impl GradualPerformance {
|
impl GradualPerformance {
|
||||||
/// Create a [`GradualPerformance`] for a map of any mode.
|
/// Create a [`GradualPerformance`] for a map of any mode.
|
||||||
pub fn new(difficulty: &Difficulty, map: &Beatmap) -> Self {
|
pub fn new(difficulty: Difficulty, map: &Beatmap) -> Self {
|
||||||
let map = Cow::Borrowed(map);
|
let map = Cow::Borrowed(map);
|
||||||
|
|
||||||
match map.mode {
|
match map.mode {
|
||||||
|
|||||||
+111
-9
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
model::beatmap::{Beatmap, Converted},
|
model::beatmap::{Beatmap, Converted},
|
||||||
osu::{Osu, OsuPerformance},
|
osu::{Osu, OsuPerformance},
|
||||||
taiko::{Taiko, TaikoPerformance},
|
taiko::{Taiko, TaikoPerformance},
|
||||||
|
Difficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -45,8 +46,8 @@ impl<'map> Performance<'map> {
|
|||||||
/// Create a new performance calculator through previously calculated
|
/// Create a new performance calculator through previously calculated
|
||||||
/// attributes.
|
/// attributes.
|
||||||
///
|
///
|
||||||
/// Note that the map, mods, and passed object count should be the same
|
/// Note that the [`Beatmap`] and [`Difficulty`] should be the same as when
|
||||||
/// as when the attributes were calculated.
|
/// the attributes were calculated.
|
||||||
pub fn from_attributes(attributes: impl AttributeProvider) -> Self {
|
pub fn from_attributes(attributes: impl AttributeProvider) -> Self {
|
||||||
Self::from(attributes)
|
Self::from(attributes)
|
||||||
}
|
}
|
||||||
@@ -63,8 +64,10 @@ impl<'map> Performance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Provide the result of a previous difficulty or performance calculation.
|
/// Provide the result of a previous difficulty or performance calculation.
|
||||||
/// If you already calculated the attributes for the current map-mod combination,
|
///
|
||||||
/// be sure to put them in here so that they don't have to be recalculated.
|
/// If you already calculated the attributes for the current [`Beatmap`]
|
||||||
|
/// and [`Difficulty`], be sure to put them in here so that they don't have
|
||||||
|
/// to be recalculated.
|
||||||
pub fn attributes(self, attributes: impl AttributeProvider) -> Self {
|
pub fn attributes(self, attributes: impl AttributeProvider) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Osu(o) => Self::Osu(o.attributes(attributes.attributes())),
|
Self::Osu(o) => Self::Osu(o.attributes(attributes.attributes())),
|
||||||
@@ -124,6 +127,16 @@ impl<'map> Performance<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
|
pub fn difficulty(self, difficulty: Difficulty) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Osu(o) => Self::Osu(o.difficulty(difficulty)),
|
||||||
|
Self::Taiko(t) => Self::Taiko(t.difficulty(difficulty)),
|
||||||
|
Self::Catch(f) => Self::Catch(f.difficulty(difficulty)),
|
||||||
|
Self::Mania(m) => Self::Mania(m.difficulty(difficulty)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
///
|
///
|
||||||
/// If you want to calculate the performance after every few objects,
|
/// If you want to calculate the performance after every few objects,
|
||||||
@@ -141,8 +154,13 @@ impl<'map> Performance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the clock rate used in the calculation.
|
/// Adjust the clock rate used in the calculation.
|
||||||
|
///
|
||||||
/// If none is specified, it will take the clock rate based on the mods
|
/// If none is specified, it will take the clock rate based on the mods
|
||||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | 0.01 | 100 |
|
||||||
pub fn clock_rate(self, clock_rate: f64) -> Self {
|
pub fn clock_rate(self, clock_rate: f64) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Osu(o) => Self::Osu(o.clock_rate(clock_rate)),
|
Self::Osu(o) => Self::Osu(o.clock_rate(clock_rate)),
|
||||||
@@ -152,6 +170,91 @@ impl<'map> Performance<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set AR.
|
||||||
|
///
|
||||||
|
/// Only relevant for osu! and osu!catch.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn ar(self, ar: f32, with_mods: bool) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Osu(o) => Self::Osu(o.ar(ar, with_mods)),
|
||||||
|
Self::Catch(c) => Self::Catch(c.ar(ar, with_mods)),
|
||||||
|
Self::Taiko(_) | Self::Mania(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set CS.
|
||||||
|
///
|
||||||
|
/// Only relevant for osu! and osu!catch.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn cs(self, cs: f32, with_mods: bool) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Osu(o) => Self::Osu(o.cs(cs, with_mods)),
|
||||||
|
Self::Catch(c) => Self::Catch(c.cs(cs, with_mods)),
|
||||||
|
Self::Taiko(_) | Self::Mania(_) => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set HP.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn hp(self, hp: f32, with_mods: bool) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Osu(o) => Self::Osu(o.hp(hp, with_mods)),
|
||||||
|
Self::Taiko(t) => Self::Taiko(t.hp(hp, with_mods)),
|
||||||
|
Self::Catch(c) => Self::Catch(c.hp(hp, with_mods)),
|
||||||
|
Self::Mania(m) => Self::Mania(m.hp(hp, with_mods)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set OD.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn od(self, od: f32, with_mods: bool) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Osu(o) => Self::Osu(o.od(od, with_mods)),
|
||||||
|
Self::Taiko(t) => Self::Taiko(t.od(od, with_mods)),
|
||||||
|
Self::Catch(c) => Self::Catch(c.od(od, with_mods)),
|
||||||
|
Self::Mania(m) => Self::Mania(m.od(od, with_mods)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adjust patterns as if the HR mod is enabled.
|
||||||
|
///
|
||||||
|
/// Only relevant for osu!catch.
|
||||||
|
pub fn hardrock_offsets(self, hardrock_offsets: bool) -> Self {
|
||||||
|
if let Self::Catch(catch) = self {
|
||||||
|
Self::Catch(catch.hardrock_offsets(hardrock_offsets))
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Provide parameters through a [`ScoreState`].
|
/// Provide parameters through a [`ScoreState`].
|
||||||
pub fn state(self, state: ScoreState) -> Self {
|
pub fn state(self, state: ScoreState) -> Self {
|
||||||
match self {
|
match self {
|
||||||
@@ -240,9 +343,8 @@ impl<'map> Performance<'map> {
|
|||||||
|
|
||||||
/// Specify the amount of katus of a play.
|
/// Specify the amount of katus of a play.
|
||||||
///
|
///
|
||||||
/// This value is only relevant for osu!catch for which it represents
|
/// Only relevant for osu!catch for which it represents the amount of tiny
|
||||||
/// the amount of tiny droplet misses and osu!mania for which it.
|
/// droplet misses and osu!mania for which it repesents the amount of n200.
|
||||||
/// repesents the amount of n200.
|
|
||||||
pub fn n_katu(self, n_katu: u32) -> Self {
|
pub fn n_katu(self, n_katu: u32) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Osu(_) | Self::Taiko(_) => self,
|
Self::Osu(_) | Self::Taiko(_) => self,
|
||||||
@@ -253,8 +355,8 @@ impl<'map> Performance<'map> {
|
|||||||
|
|
||||||
/// Specify the amount of gekis of a play.
|
/// Specify the amount of gekis of a play.
|
||||||
///
|
///
|
||||||
/// This value is only relevant for osu!mania for which it.
|
/// Only relevant for osu!mania for which it repesents the
|
||||||
/// repesents the amount of n320.
|
/// amount of n320.
|
||||||
pub fn n_geki(self, n_geki: u32) -> Self {
|
pub fn n_geki(self, n_geki: u32) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Osu(_) | Self::Taiko(_) | Self::Catch(_) => self,
|
Self::Osu(_) | Self::Taiko(_) | Self::Catch(_) => self,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ pub fn try_convert(map: &mut Beatmap) -> ConvertStatus {
|
|||||||
pub fn convert_objects(
|
pub fn convert_objects(
|
||||||
converted: &CatchBeatmap<'_>,
|
converted: &CatchBeatmap<'_>,
|
||||||
count: &mut ObjectCountBuilder,
|
count: &mut ObjectCountBuilder,
|
||||||
hr: bool,
|
hr_offsets: bool,
|
||||||
cs: f32,
|
cs: f32,
|
||||||
) -> Vec<PalpableObject> {
|
) -> Vec<PalpableObject> {
|
||||||
// mean=686.54 | median=501
|
// mean=686.54 | median=501
|
||||||
@@ -73,7 +73,7 @@ pub fn convert_objects(
|
|||||||
|
|
||||||
apply_pos_offset(
|
apply_pos_offset(
|
||||||
&mut new_objects,
|
&mut new_objects,
|
||||||
hr,
|
hr_offsets,
|
||||||
&mut last_pos,
|
&mut last_pos,
|
||||||
&mut last_start_time,
|
&mut last_start_time,
|
||||||
&mut rng,
|
&mut rng,
|
||||||
@@ -174,14 +174,14 @@ impl ExactSizeIterator for ObjectIter<'_> {
|
|||||||
|
|
||||||
fn apply_pos_offset(
|
fn apply_pos_offset(
|
||||||
hit_object: &mut ObjectIter<'_>,
|
hit_object: &mut ObjectIter<'_>,
|
||||||
should_apply_hr_offset: bool,
|
hr_offsets: bool,
|
||||||
last_pos: &mut Option<f32>,
|
last_pos: &mut Option<f32>,
|
||||||
last_start_time: &mut f64,
|
last_start_time: &mut f64,
|
||||||
rng: &mut Random,
|
rng: &mut Random,
|
||||||
) {
|
) {
|
||||||
match hit_object.state {
|
match hit_object.state {
|
||||||
ObjectIterState::Fruit(Some(ref mut fruit)) => {
|
ObjectIterState::Fruit(Some(ref mut fruit)) => {
|
||||||
if should_apply_hr_offset {
|
if hr_offsets {
|
||||||
apply_hr_offset(
|
apply_hr_offset(
|
||||||
hit_object.x,
|
hit_object.x,
|
||||||
&mut fruit.x_offset,
|
&mut fruit.x_offset,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use crate::{
|
|||||||
convert::convert_objects,
|
convert::convert_objects,
|
||||||
CatchBeatmap, CatchDifficultyAttributes,
|
CatchBeatmap, CatchDifficultyAttributes,
|
||||||
},
|
},
|
||||||
util::mods::Mods,
|
|
||||||
Difficulty,
|
Difficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,8 +55,7 @@ use super::{
|
|||||||
/// [`CatchGradualPerformance`]: crate::catch::CatchGradualPerformance
|
/// [`CatchGradualPerformance`]: crate::catch::CatchGradualPerformance
|
||||||
pub struct CatchGradualDifficulty {
|
pub struct CatchGradualDifficulty {
|
||||||
pub(crate) idx: usize,
|
pub(crate) idx: usize,
|
||||||
pub(crate) mods: u32,
|
pub(crate) difficulty: Difficulty,
|
||||||
pub(crate) clock_rate: f64,
|
|
||||||
attrs: CatchDifficultyAttributes,
|
attrs: CatchDifficultyAttributes,
|
||||||
/// The delta of object counts after each palpable object
|
/// The delta of object counts after each palpable object
|
||||||
count: Vec<GradualObjectCount>,
|
count: Vec<GradualObjectCount>,
|
||||||
@@ -66,16 +64,16 @@ pub struct CatchGradualDifficulty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CatchGradualDifficulty {
|
impl CatchGradualDifficulty {
|
||||||
pub fn new(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||||
let mods = difficulty.get_mods();
|
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
let clock_rate = difficulty.get_clock_rate();
|
||||||
|
|
||||||
let CatchDifficultySetup { map_attrs, attrs } =
|
let CatchDifficultySetup { map_attrs, attrs } =
|
||||||
CatchDifficultySetup::new(difficulty, converted);
|
CatchDifficultySetup::new(&difficulty, converted);
|
||||||
|
|
||||||
let hr = mods.hr();
|
let hr_offsets = difficulty.get_hardrock_offsets();
|
||||||
let mut count = ObjectCountBuilder::new_gradual();
|
let mut count = ObjectCountBuilder::new_gradual();
|
||||||
let palpable_objects = convert_objects(converted, &mut count, hr, map_attrs.cs as f32);
|
let palpable_objects =
|
||||||
|
convert_objects(converted, &mut count, hr_offsets, map_attrs.cs as f32);
|
||||||
|
|
||||||
let diff_objects = DifficultyValues::create_difficulty_objects(
|
let diff_objects = DifficultyValues::create_difficulty_objects(
|
||||||
&map_attrs,
|
&map_attrs,
|
||||||
@@ -88,8 +86,7 @@ impl CatchGradualDifficulty {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
mods,
|
difficulty,
|
||||||
clock_rate,
|
|
||||||
attrs,
|
attrs,
|
||||||
count,
|
count,
|
||||||
diff_objects,
|
diff_objects,
|
||||||
@@ -171,8 +168,7 @@ mod tests {
|
|||||||
fn empty() {
|
fn empty() {
|
||||||
let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted();
|
let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted();
|
||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let mut gradual = CatchGradualDifficulty::new(Difficulty::new(), &converted);
|
||||||
let mut gradual = CatchGradualDifficulty::new(&difficulty, &converted);
|
|
||||||
|
|
||||||
assert!(gradual.next().is_none());
|
assert!(gradual.next().is_none());
|
||||||
}
|
}
|
||||||
@@ -185,9 +181,9 @@ mod tests {
|
|||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let difficulty = Difficulty::new();
|
||||||
|
|
||||||
let mut gradual = CatchGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual = CatchGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = CatchGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_2nd = CatchGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = CatchGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_3rd = CatchGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
for i in 1.. {
|
for i in 1.. {
|
||||||
let Some(next_gradual) = gradual.next() else {
|
let Some(next_gradual) = gradual.next() else {
|
||||||
@@ -207,7 +203,8 @@ mod tests {
|
|||||||
assert_eq!(next_gradual, next_gradual_3rd);
|
assert_eq!(next_gradual, next_gradual_3rd);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected = Difficulty::new()
|
let expected = difficulty
|
||||||
|
.clone()
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.with_mode()
|
.with_mode()
|
||||||
.calculate(&converted);
|
.calculate(&converted);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::{
|
|||||||
catcher::Catcher, convert::convert_objects, difficulty::object::CatchDifficultyObject,
|
catcher::Catcher, convert::convert_objects, difficulty::object::CatchDifficultyObject,
|
||||||
},
|
},
|
||||||
model::beatmap::BeatmapAttributes,
|
model::beatmap::BeatmapAttributes,
|
||||||
util::mods::Mods,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::skills::movement::Movement;
|
use self::skills::movement::Movement;
|
||||||
@@ -42,14 +41,7 @@ pub struct CatchDifficultySetup {
|
|||||||
|
|
||||||
impl CatchDifficultySetup {
|
impl CatchDifficultySetup {
|
||||||
pub fn new(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
pub fn new(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||||
let mods = difficulty.get_mods();
|
let map_attrs = converted.attributes().difficulty(difficulty).build();
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
|
||||||
|
|
||||||
let map_attrs = converted
|
|
||||||
.attributes()
|
|
||||||
.mods(mods)
|
|
||||||
.clock_rate(clock_rate)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let attrs = CatchDifficultyAttributes {
|
let attrs = CatchDifficultyAttributes {
|
||||||
ar: map_attrs.ar,
|
ar: map_attrs.ar,
|
||||||
@@ -69,7 +61,6 @@ pub struct DifficultyValues {
|
|||||||
impl DifficultyValues {
|
impl DifficultyValues {
|
||||||
pub fn calculate(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
pub fn calculate(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||||
let take = difficulty.get_passed_objects();
|
let take = difficulty.get_passed_objects();
|
||||||
let mods = difficulty.get_mods();
|
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
let clock_rate = difficulty.get_clock_rate();
|
||||||
|
|
||||||
let CatchDifficultySetup {
|
let CatchDifficultySetup {
|
||||||
@@ -77,10 +68,11 @@ impl DifficultyValues {
|
|||||||
mut attrs,
|
mut attrs,
|
||||||
} = CatchDifficultySetup::new(difficulty, converted);
|
} = CatchDifficultySetup::new(difficulty, converted);
|
||||||
|
|
||||||
let hr = mods.hr();
|
let hr_offsets = difficulty.get_hardrock_offsets();
|
||||||
let mut count = ObjectCountBuilder::new_regular(take);
|
let mut count = ObjectCountBuilder::new_regular(take);
|
||||||
|
|
||||||
let palpable_objects = convert_objects(converted, &mut count, hr, map_attrs.cs as f32);
|
let palpable_objects =
|
||||||
|
convert_objects(converted, &mut count, hr_offsets, map_attrs.cs as f32);
|
||||||
|
|
||||||
let diff_objects = Self::create_difficulty_objects(
|
let diff_objects = Self::create_difficulty_objects(
|
||||||
&map_attrs,
|
&map_attrs,
|
||||||
|
|||||||
+2
-2
@@ -62,14 +62,14 @@ impl IGameMode for Catch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_difficulty(
|
fn gradual_difficulty(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &CatchBeatmap<'_>,
|
map: &CatchBeatmap<'_>,
|
||||||
) -> Self::GradualDifficulty {
|
) -> Self::GradualDifficulty {
|
||||||
CatchGradualDifficulty::new(difficulty, map)
|
CatchGradualDifficulty::new(difficulty, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_performance(
|
fn gradual_performance(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &CatchBeatmap<'_>,
|
map: &CatchBeatmap<'_>,
|
||||||
) -> Self::GradualPerformance {
|
) -> Self::GradualPerformance {
|
||||||
CatchGradualPerformance::new(difficulty, map)
|
CatchGradualPerformance::new(difficulty, map)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ pub struct CatchGradualPerformance {
|
|||||||
|
|
||||||
impl CatchGradualPerformance {
|
impl CatchGradualPerformance {
|
||||||
/// Create a new gradual performance calculator for osu!catch maps.
|
/// Create a new gradual performance calculator for osu!catch maps.
|
||||||
pub fn new(difficulty: &Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &CatchBeatmap<'_>) -> Self {
|
||||||
let difficulty = CatchGradualDifficulty::new(difficulty, converted);
|
let difficulty = CatchGradualDifficulty::new(difficulty, converted);
|
||||||
|
|
||||||
Self { difficulty }
|
Self { difficulty }
|
||||||
@@ -122,8 +122,7 @@ impl CatchGradualPerformance {
|
|||||||
.nth(n)?
|
.nth(n)?
|
||||||
.performance()
|
.performance()
|
||||||
.state(state)
|
.state(state)
|
||||||
.mods(self.difficulty.mods)
|
.difficulty(self.difficulty.difficulty.clone())
|
||||||
.clock_rate(self.difficulty.clock_rate)
|
|
||||||
.passed_objects(self.difficulty.idx as u32)
|
.passed_objects(self.difficulty.idx as u32)
|
||||||
.calculate();
|
.calculate();
|
||||||
|
|
||||||
@@ -143,12 +142,11 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into_converted();
|
.unchecked_into_converted();
|
||||||
|
|
||||||
let mods = 88; // HDHRDT
|
let difficulty = Difficulty::new().mods(88); // HDHRDT
|
||||||
let difficulty = Difficulty::new().mods(88);
|
|
||||||
|
|
||||||
let mut gradual = CatchGradualPerformance::new(&difficulty, &converted);
|
let mut gradual = CatchGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = CatchGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_2nd = CatchGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = CatchGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_3rd = CatchGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
let mut state = CatchScoreState::default();
|
let mut state = CatchScoreState::default();
|
||||||
|
|
||||||
@@ -173,7 +171,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let regular_calc = CatchPerformance::new(converted.as_owned())
|
let regular_calc = CatchPerformance::new(converted.as_owned())
|
||||||
.mods(mods)
|
.difficulty(difficulty.clone())
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.state(state.clone());
|
.state(state.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,10 @@ impl<'map> CatchPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Provide the result of a previous difficulty or performance calculation.
|
/// Provide the result of a previous difficulty or performance calculation.
|
||||||
/// If you already calculated the attributes for the current map-mod combination,
|
///
|
||||||
/// be sure to put them in here so that they don't have to be recalculated.
|
/// If you already calculated the attributes for the current
|
||||||
|
/// [`CatchBeatmap`] and [`Difficulty`], be sure to put them in here so
|
||||||
|
/// that they don't have to be recalculated.
|
||||||
pub fn attributes(mut self, attributes: impl ModeAttributeProvider<Catch>) -> Self {
|
pub fn attributes(mut self, attributes: impl ModeAttributeProvider<Catch>) -> Self {
|
||||||
if let Some(attrs) = attributes.attributes() {
|
if let Some(attrs) = attributes.attributes() {
|
||||||
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
||||||
@@ -122,6 +124,13 @@ impl<'map> CatchPerformance<'map> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
|
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
///
|
///
|
||||||
/// If you want to calculate the performance after every few objects,
|
/// If you want to calculate the performance after every few objects,
|
||||||
@@ -136,14 +145,86 @@ impl<'map> CatchPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the clock rate used in the calculation.
|
/// Adjust the clock rate used in the calculation.
|
||||||
|
///
|
||||||
/// If none is specified, it will take the clock rate based on the mods
|
/// If none is specified, it will take the clock rate based on the mods
|
||||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | 0.01 | 100 |
|
||||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||||
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set AR.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn ar(mut self, ar: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.ar(ar, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set CS.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn cs(mut self, cs: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.cs(cs, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set HP.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn hp(mut self, hp: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.hp(hp, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set OD.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn od(mut self, od: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.od(od, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adjust patterns as if the HR mod is enabled.
|
||||||
|
pub const fn hardrock_offsets(mut self, hardrock_offsets: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.hardrock_offsets(hardrock_offsets);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Provide parameters through an [`CatchScoreState`].
|
/// Provide parameters through an [`CatchScoreState`].
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub const fn state(mut self, state: CatchScoreState) -> Self {
|
pub const fn state(mut self, state: CatchScoreState) -> Self {
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ use super::{
|
|||||||
/// [`ManiaGradualPerformance`]: crate::mania::ManiaGradualPerformance
|
/// [`ManiaGradualPerformance`]: crate::mania::ManiaGradualPerformance
|
||||||
pub struct ManiaGradualDifficulty {
|
pub struct ManiaGradualDifficulty {
|
||||||
pub(crate) idx: usize,
|
pub(crate) idx: usize,
|
||||||
pub(crate) mods: u32,
|
pub(crate) difficulty: Difficulty,
|
||||||
pub(crate) clock_rate: f64,
|
|
||||||
objects_is_circle: Box<[bool]>,
|
objects_is_circle: Box<[bool]>,
|
||||||
is_convert: bool,
|
is_convert: bool,
|
||||||
strain: Strain,
|
strain: Strain,
|
||||||
@@ -61,18 +60,14 @@ pub struct ManiaGradualDifficulty {
|
|||||||
|
|
||||||
impl ManiaGradualDifficulty {
|
impl ManiaGradualDifficulty {
|
||||||
/// Create a new difficulty attributes iterator for osu!mania maps.
|
/// Create a new difficulty attributes iterator for osu!mania maps.
|
||||||
pub fn new(difficulty: &Difficulty, converted: &ManiaBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &ManiaBeatmap<'_>) -> Self {
|
||||||
let take = difficulty.get_passed_objects();
|
let take = difficulty.get_passed_objects();
|
||||||
let mods = difficulty.get_mods();
|
|
||||||
let total_columns = converted.cs.round_even().max(1.0);
|
let total_columns = converted.cs.round_even().max(1.0);
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
let clock_rate = difficulty.get_clock_rate();
|
||||||
let mut params = ObjectParams::new(converted);
|
let mut params = ObjectParams::new(converted);
|
||||||
|
|
||||||
let HitWindows { od: hit_window, .. } = converted
|
let HitWindows { od: hit_window, .. } =
|
||||||
.attributes()
|
converted.attributes().difficulty(&difficulty).hit_windows();
|
||||||
.mods(mods)
|
|
||||||
.clock_rate(clock_rate)
|
|
||||||
.hit_windows();
|
|
||||||
|
|
||||||
let mania_objects = converted
|
let mania_objects = converted
|
||||||
.hit_objects
|
.hit_objects
|
||||||
@@ -105,8 +100,7 @@ impl ManiaGradualDifficulty {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
mods,
|
difficulty,
|
||||||
clock_rate,
|
|
||||||
objects_is_circle,
|
objects_is_circle,
|
||||||
is_convert: converted.is_convert,
|
is_convert: converted.is_convert,
|
||||||
strain,
|
strain,
|
||||||
@@ -130,7 +124,12 @@ impl Iterator for ManiaGradualDifficulty {
|
|||||||
Skill::new(&mut self.strain, &self.diff_objects).process(curr);
|
Skill::new(&mut self.strain, &self.diff_objects).process(curr);
|
||||||
|
|
||||||
let is_circle = self.objects_is_circle[self.idx];
|
let is_circle = self.objects_is_circle[self.idx];
|
||||||
increment_combo(is_circle, curr, &mut self.curr_combo, self.clock_rate);
|
increment_combo(
|
||||||
|
is_circle,
|
||||||
|
curr,
|
||||||
|
&mut self.curr_combo,
|
||||||
|
self.difficulty.get_clock_rate(),
|
||||||
|
);
|
||||||
} else if self.objects_is_circle.is_empty() {
|
} else if self.objects_is_circle.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -168,9 +167,10 @@ impl Iterator for ManiaGradualDifficulty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut strain = Skill::new(&mut self.strain, &self.diff_objects);
|
let mut strain = Skill::new(&mut self.strain, &self.diff_objects);
|
||||||
|
let clock_rate = self.difficulty.get_clock_rate();
|
||||||
|
|
||||||
for (curr, is_circle) in skip_iter.take(take) {
|
for (curr, is_circle) in skip_iter.take(take) {
|
||||||
increment_combo(*is_circle, curr, &mut self.curr_combo, self.clock_rate);
|
increment_combo(*is_circle, curr, &mut self.curr_combo, clock_rate);
|
||||||
strain.process(curr);
|
strain.process(curr);
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
}
|
}
|
||||||
@@ -219,8 +219,7 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into_converted::<Mania>();
|
.unchecked_into_converted::<Mania>();
|
||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let mut gradual = ManiaGradualDifficulty::new(Difficulty::new(), &converted);
|
||||||
let mut gradual = ManiaGradualDifficulty::new(&difficulty, &converted);
|
|
||||||
|
|
||||||
assert!(gradual.next().is_none());
|
assert!(gradual.next().is_none());
|
||||||
}
|
}
|
||||||
@@ -233,9 +232,9 @@ mod tests {
|
|||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let difficulty = Difficulty::new();
|
||||||
|
|
||||||
let mut gradual = ManiaGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual = ManiaGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = ManiaGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_2nd = ManiaGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = ManiaGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_3rd = ManiaGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
let hit_objects_len = converted.hit_objects.len();
|
let hit_objects_len = converted.hit_objects.len();
|
||||||
|
|
||||||
@@ -257,7 +256,8 @@ mod tests {
|
|||||||
assert_eq!(next_gradual, next_gradual_3rd);
|
assert_eq!(next_gradual, next_gradual_3rd);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected = Difficulty::new()
|
let expected = difficulty
|
||||||
|
.clone()
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.with_mode()
|
.with_mode()
|
||||||
.calculate(&converted);
|
.calculate(&converted);
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ pub fn difficulty(
|
|||||||
|
|
||||||
let hit_window = converted
|
let hit_window = converted
|
||||||
.attributes()
|
.attributes()
|
||||||
.mods(difficulty.get_mods())
|
.difficulty(difficulty)
|
||||||
.clock_rate(difficulty.get_clock_rate())
|
|
||||||
.hit_windows()
|
.hit_windows()
|
||||||
.od;
|
.od;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -59,14 +59,14 @@ impl IGameMode for Mania {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_difficulty(
|
fn gradual_difficulty(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &ManiaBeatmap<'_>,
|
map: &ManiaBeatmap<'_>,
|
||||||
) -> Self::GradualDifficulty {
|
) -> Self::GradualDifficulty {
|
||||||
ManiaGradualDifficulty::new(difficulty, map)
|
ManiaGradualDifficulty::new(difficulty, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_performance(
|
fn gradual_performance(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &ManiaBeatmap<'_>,
|
map: &ManiaBeatmap<'_>,
|
||||||
) -> Self::GradualPerformance {
|
) -> Self::GradualPerformance {
|
||||||
ManiaGradualPerformance::new(difficulty, map)
|
ManiaGradualPerformance::new(difficulty, map)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ pub struct ManiaGradualPerformance {
|
|||||||
|
|
||||||
impl ManiaGradualPerformance {
|
impl ManiaGradualPerformance {
|
||||||
/// Create a new gradual performance calculator for osu!mania maps.
|
/// Create a new gradual performance calculator for osu!mania maps.
|
||||||
pub fn new(difficulty: &Difficulty, converted: &ManiaBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &ManiaBeatmap<'_>) -> Self {
|
||||||
let difficulty = ManiaGradualDifficulty::new(difficulty, converted);
|
let difficulty = ManiaGradualDifficulty::new(difficulty, converted);
|
||||||
|
|
||||||
Self { difficulty }
|
Self { difficulty }
|
||||||
@@ -104,8 +104,7 @@ impl ManiaGradualPerformance {
|
|||||||
.nth(n)?
|
.nth(n)?
|
||||||
.performance()
|
.performance()
|
||||||
.state(state)
|
.state(state)
|
||||||
.mods(self.difficulty.mods)
|
.difficulty(self.difficulty.difficulty.clone())
|
||||||
.clock_rate(self.difficulty.clock_rate)
|
|
||||||
.passed_objects(self.difficulty.idx as u32)
|
.passed_objects(self.difficulty.idx as u32)
|
||||||
.calculate();
|
.calculate();
|
||||||
|
|
||||||
@@ -128,12 +127,11 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into_converted::<Mania>();
|
.unchecked_into_converted::<Mania>();
|
||||||
|
|
||||||
let mods = 88; // HDHRDT
|
let difficulty = Difficulty::new().mods(88); // HDHRDT
|
||||||
let difficulty = Difficulty::new().mods(88);
|
|
||||||
|
|
||||||
let mut gradual = ManiaGradualPerformance::new(&difficulty, &converted);
|
let mut gradual = ManiaGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = ManiaGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_2nd = ManiaGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = ManiaGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_3rd = ManiaGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
let mut state = ManiaScoreState::default();
|
let mut state = ManiaScoreState::default();
|
||||||
|
|
||||||
@@ -160,7 +158,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut regular_calc = ManiaPerformance::new(converted.as_owned())
|
let mut regular_calc = ManiaPerformance::new(converted.as_owned())
|
||||||
.mods(mods)
|
.difficulty(difficulty.clone())
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.state(state.clone());
|
.state(state.clone());
|
||||||
|
|
||||||
|
|||||||
@@ -64,8 +64,10 @@ impl<'map> ManiaPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Provide the result of a previous difficulty or performance calculation.
|
/// Provide the result of a previous difficulty or performance calculation.
|
||||||
/// If you already calculated the attributes for the current map-mod combination,
|
///
|
||||||
/// be sure to put them in here so that they don't have to be recalculated.
|
/// If you already calculated the attributes for the current
|
||||||
|
/// [`ManiaBeatmap`] and [`Difficulty`], be sure to put them in here so
|
||||||
|
/// that they don't have to be recalculated.
|
||||||
pub fn attributes(mut self, attrs: impl ModeAttributeProvider<Mania>) -> Self {
|
pub fn attributes(mut self, attrs: impl ModeAttributeProvider<Mania>) -> Self {
|
||||||
if let Some(attrs) = attrs.attributes() {
|
if let Some(attrs) = attrs.attributes() {
|
||||||
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
||||||
@@ -83,6 +85,13 @@ impl<'map> ManiaPerformance<'map> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
|
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
///
|
///
|
||||||
/// If you want to calculate the performance after every few objects,
|
/// If you want to calculate the performance after every few objects,
|
||||||
@@ -97,14 +106,49 @@ impl<'map> ManiaPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the clock rate used in the calculation.
|
/// Adjust the clock rate used in the calculation.
|
||||||
|
///
|
||||||
/// If none is specified, it will take the clock rate based on the mods
|
/// If none is specified, it will take the clock rate based on the mods
|
||||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | 0.01 | 100 |
|
||||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||||
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set HP.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn hp(mut self, hp: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.hp(hp, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set OD.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn od(mut self, od: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.od(od, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Specify the accuracy of a play between `0.0` and `100.0`.
|
/// Specify the accuracy of a play between `0.0` and `100.0`.
|
||||||
/// This will be used to generate matching hitresults.
|
/// This will be used to generate matching hitresults.
|
||||||
pub fn accuracy(mut self, acc: f64) -> Self {
|
pub fn accuracy(mut self, acc: f64) -> Self {
|
||||||
|
|||||||
+120
-49
@@ -1,6 +1,10 @@
|
|||||||
use rosu_map::section::general::GameMode;
|
use rosu_map::section::general::GameMode;
|
||||||
|
|
||||||
use crate::util::{float_ext::FloatExt, mods::Mods};
|
use crate::{
|
||||||
|
any::difficulty::ModsDependent,
|
||||||
|
util::{float_ext::FloatExt, mods::Mods},
|
||||||
|
Difficulty,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{converted::Converted, Beatmap};
|
use super::{converted::Converted, Beatmap};
|
||||||
|
|
||||||
@@ -32,13 +36,14 @@ pub struct HitWindows {
|
|||||||
|
|
||||||
/// A builder for [`BeatmapAttributes`] and [`HitWindows`].
|
/// A builder for [`BeatmapAttributes`] and [`HitWindows`].
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[must_use]
|
||||||
pub struct BeatmapAttributesBuilder {
|
pub struct BeatmapAttributesBuilder {
|
||||||
mode: GameMode,
|
mode: GameMode,
|
||||||
is_convert: bool,
|
is_convert: bool,
|
||||||
ar: f32,
|
ar: ModsDependent,
|
||||||
od: f32,
|
od: ModsDependent,
|
||||||
cs: f32,
|
cs: ModsDependent,
|
||||||
hp: f32,
|
hp: ModsDependent,
|
||||||
mods: u32,
|
mods: u32,
|
||||||
clock_rate: Option<f64>,
|
clock_rate: Option<f64>,
|
||||||
}
|
}
|
||||||
@@ -56,10 +61,10 @@ impl BeatmapAttributesBuilder {
|
|||||||
pub const fn new(map: &Beatmap) -> Self {
|
pub const fn new(map: &Beatmap) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: map.mode,
|
mode: map.mode,
|
||||||
ar: map.ar,
|
ar: ModsDependent::new(map.ar),
|
||||||
od: map.od,
|
od: ModsDependent::new(map.od),
|
||||||
cs: map.cs,
|
cs: ModsDependent::new(map.cs),
|
||||||
hp: map.hp,
|
hp: ModsDependent::new(map.hp),
|
||||||
mods: 0,
|
mods: 0,
|
||||||
clock_rate: None,
|
clock_rate: None,
|
||||||
is_convert: map.is_convert,
|
is_convert: map.is_convert,
|
||||||
@@ -67,56 +72,99 @@ impl BeatmapAttributesBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the approach rate.
|
/// Specify the approach rate.
|
||||||
pub fn ar(&mut self, ar: f32) -> &mut Self {
|
///
|
||||||
self.ar = ar;
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
self
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
pub const fn ar(self, ar: f32, with_mods: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
ar: ModsDependent {
|
||||||
|
value: ar,
|
||||||
|
with_mods,
|
||||||
|
},
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the overall difficulty.
|
/// Specify the overall difficulty.
|
||||||
pub fn od(&mut self, od: f32) -> &mut Self {
|
///
|
||||||
self.od = od;
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
self
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
pub const fn od(self, od: f32, with_mods: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
od: ModsDependent {
|
||||||
|
value: od,
|
||||||
|
with_mods,
|
||||||
|
},
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the circle size.
|
/// Specify the circle size.
|
||||||
pub fn cs(&mut self, cs: f32) -> &mut Self {
|
///
|
||||||
self.cs = cs;
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
self
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
pub const fn cs(self, cs: f32, with_mods: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
cs: ModsDependent {
|
||||||
|
value: cs,
|
||||||
|
with_mods,
|
||||||
|
},
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the drain rate.
|
/// Specify the drain rate.
|
||||||
pub fn hp(&mut self, hp: f32) -> &mut Self {
|
///
|
||||||
self.hp = hp;
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
self
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
pub const fn hp(self, hp: f32, with_mods: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
hp: ModsDependent {
|
||||||
|
value: hp,
|
||||||
|
with_mods,
|
||||||
|
},
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the mods.
|
/// Specify the mods.
|
||||||
pub fn mods(&mut self, mods: u32) -> &mut Self {
|
pub const fn mods(self, mods: u32) -> Self {
|
||||||
self.mods = mods;
|
Self { mods, ..self }
|
||||||
|
|
||||||
self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify a custom clock rate.
|
/// Specify a custom clock rate.
|
||||||
pub fn clock_rate(&mut self, clock_rate: f64) -> &mut Self {
|
pub const fn clock_rate(self, clock_rate: f64) -> Self {
|
||||||
self.clock_rate = Some(clock_rate);
|
Self {
|
||||||
|
clock_rate: Some(clock_rate),
|
||||||
self
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify a [`GameMode`] and whether it's a converted map.
|
/// Specify a [`GameMode`] and whether it's a converted map.
|
||||||
///
|
pub const fn mode(self, mode: GameMode, is_convert: bool) -> Self {
|
||||||
/// This should only be used if the [`BeatmapAttributesBuilder`] was
|
Self {
|
||||||
/// **not** created through a [`Beatmap`] or [`Converted`].
|
mode,
|
||||||
pub fn mode(&mut self, mode: GameMode, is_convert: bool) -> &mut Self {
|
is_convert,
|
||||||
self.mode = mode;
|
..self
|
||||||
self.is_convert = is_convert;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self
|
/// Specify all settings through [`Difficulty`].
|
||||||
|
pub fn difficulty(self, difficulty: &Difficulty) -> Self {
|
||||||
|
Self {
|
||||||
|
mode: self.mode,
|
||||||
|
is_convert: self.is_convert,
|
||||||
|
ar: difficulty.get_ar().unwrap_or(self.ar),
|
||||||
|
od: difficulty.get_od().unwrap_or(self.od),
|
||||||
|
cs: difficulty.get_cs().unwrap_or(self.cs),
|
||||||
|
hp: difficulty.get_hp().unwrap_or(self.hp),
|
||||||
|
mods: difficulty.get_mods(),
|
||||||
|
clock_rate: Some(difficulty.get_clock_rate()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the AR and OD hit windows.
|
/// Calculate the AR and OD hit windows.
|
||||||
@@ -134,13 +182,22 @@ impl BeatmapAttributesBuilder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_ar = mod_mult(self.ar);
|
let raw_ar = if self.ar.with_mods {
|
||||||
|
self.ar.value
|
||||||
|
} else {
|
||||||
|
mod_mult(self.ar.value)
|
||||||
|
};
|
||||||
|
|
||||||
let preempt = difficulty_range(f64::from(raw_ar), 1800.0, 1200.0, 450.0) / clock_rate;
|
let preempt = difficulty_range(f64::from(raw_ar), 1800.0, 1200.0, 450.0) / clock_rate;
|
||||||
|
|
||||||
// OD
|
// OD
|
||||||
let hit_window = match self.mode {
|
let hit_window = match self.mode {
|
||||||
GameMode::Osu | GameMode::Catch => {
|
GameMode::Osu | GameMode::Catch => {
|
||||||
let raw_od = mod_mult(self.od);
|
let raw_od = if self.od.with_mods {
|
||||||
|
self.od.value
|
||||||
|
} else {
|
||||||
|
mod_mult(self.od.value)
|
||||||
|
};
|
||||||
|
|
||||||
difficulty_range(
|
difficulty_range(
|
||||||
f64::from(raw_od),
|
f64::from(raw_od),
|
||||||
@@ -150,7 +207,11 @@ impl BeatmapAttributesBuilder {
|
|||||||
) / clock_rate
|
) / clock_rate
|
||||||
}
|
}
|
||||||
GameMode::Taiko => {
|
GameMode::Taiko => {
|
||||||
let raw_od = mod_mult(self.od);
|
let raw_od = if self.od.with_mods {
|
||||||
|
self.od.value
|
||||||
|
} else {
|
||||||
|
mod_mult(self.od.value)
|
||||||
|
};
|
||||||
|
|
||||||
let diff_range = difficulty_range(
|
let diff_range = difficulty_range(
|
||||||
f64::from(raw_od),
|
f64::from(raw_od),
|
||||||
@@ -163,18 +224,20 @@ impl BeatmapAttributesBuilder {
|
|||||||
}
|
}
|
||||||
GameMode::Mania => {
|
GameMode::Mania => {
|
||||||
let mut value = if !self.is_convert {
|
let mut value = if !self.is_convert {
|
||||||
34.0 + 3.0 * (10.0 - self.od).clamp(0.0, 10.0)
|
34.0 + 3.0 * (10.0 - self.od.value).clamp(0.0, 10.0)
|
||||||
} else if self.od.round_even() > 4.0 {
|
} else if self.od.value.round_even() > 4.0 {
|
||||||
34.0
|
34.0
|
||||||
} else {
|
} else {
|
||||||
47.0
|
47.0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !self.od.with_mods {
|
||||||
if mods.hr() {
|
if mods.hr() {
|
||||||
value /= 1.4;
|
value /= 1.4;
|
||||||
} else if mods.ez() {
|
} else if mods.ez() {
|
||||||
value *= 1.4;
|
value *= 1.4;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
((f64::from(value) * clock_rate).floor() / clock_rate).ceil()
|
((f64::from(value) * clock_rate).floor() / clock_rate).ceil()
|
||||||
}
|
}
|
||||||
@@ -192,16 +255,24 @@ impl BeatmapAttributesBuilder {
|
|||||||
let clock_rate = self.clock_rate.unwrap_or_else(|| mods.clock_rate());
|
let clock_rate = self.clock_rate.unwrap_or_else(|| mods.clock_rate());
|
||||||
|
|
||||||
// HP
|
// HP
|
||||||
let hp = (self.hp * mods.od_ar_hp_multiplier() as f32).min(10.0);
|
let mut hp = self.hp.value;
|
||||||
|
|
||||||
|
if !self.hp.with_mods {
|
||||||
|
hp *= mods.od_ar_hp_multiplier() as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
hp = hp.min(10.0);
|
||||||
|
|
||||||
// CS
|
// CS
|
||||||
let mut cs = self.cs;
|
let mut cs = self.cs.value;
|
||||||
|
|
||||||
|
if !self.cs.with_mods {
|
||||||
if mods.hr() {
|
if mods.hr() {
|
||||||
cs = (cs * 1.3).min(10.);
|
cs = (cs * 1.3).min(10.0);
|
||||||
} else if mods.ez() {
|
} else if mods.ez() {
|
||||||
cs *= 0.5;
|
cs *= 0.5;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let hit_windows = self.hit_windows();
|
let hit_windows = self.hit_windows();
|
||||||
let HitWindows { ar, od } = hit_windows;
|
let HitWindows { ar, od } = hit_windows;
|
||||||
@@ -217,7 +288,7 @@ impl BeatmapAttributesBuilder {
|
|||||||
let od = match self.mode {
|
let od = match self.mode {
|
||||||
GameMode::Osu => (Self::OSU_MIN - od) / 6.0,
|
GameMode::Osu => (Self::OSU_MIN - od) / 6.0,
|
||||||
GameMode::Taiko => (Self::TAIKO_MIN - od) / (Self::TAIKO_MIN - Self::TAIKO_AVG) * 5.0,
|
GameMode::Taiko => (Self::TAIKO_MIN - od) / (Self::TAIKO_MIN - Self::TAIKO_AVG) * 5.0,
|
||||||
GameMode::Catch | GameMode::Mania => f64::from(self.od),
|
GameMode::Catch | GameMode::Mania => f64::from(self.od.value),
|
||||||
};
|
};
|
||||||
|
|
||||||
BeatmapAttributes {
|
BeatmapAttributes {
|
||||||
|
|||||||
@@ -84,12 +84,12 @@ impl<M: IGameMode> Converted<'_, M> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a gradual difficulty calculator for the map.
|
/// Create a gradual difficulty calculator for the map.
|
||||||
pub fn gradual_difficulty(&self, difficulty: &Difficulty) -> M::GradualDifficulty {
|
pub fn gradual_difficulty(&self, difficulty: Difficulty) -> M::GradualDifficulty {
|
||||||
M::gradual_difficulty(difficulty, self)
|
M::gradual_difficulty(difficulty, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a gradual performance calculator for the map.
|
/// Create a gradual performance calculator for the map.
|
||||||
pub fn gradual_performance(&self, difficulty: &Difficulty) -> M::GradualPerformance {
|
pub fn gradual_performance(&self, difficulty: Difficulty) -> M::GradualPerformance {
|
||||||
M::gradual_performance(difficulty, self)
|
M::gradual_performance(difficulty, self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,12 +91,12 @@ impl Beatmap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a gradual difficulty calculator for this [`Beatmap`].
|
/// Create a gradual difficulty calculator for this [`Beatmap`].
|
||||||
pub fn gradual_difficulty(&self, difficulty: &Difficulty) -> GradualDifficulty {
|
pub fn gradual_difficulty(&self, difficulty: Difficulty) -> GradualDifficulty {
|
||||||
GradualDifficulty::new(difficulty, self)
|
GradualDifficulty::new(difficulty, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a gradual performance calculator for this [`Beatmap`].
|
/// Create a gradual performance calculator for this [`Beatmap`].
|
||||||
pub fn gradual_performance(&self, difficulty: &Difficulty) -> GradualPerformance {
|
pub fn gradual_performance(&self, difficulty: Difficulty) -> GradualPerformance {
|
||||||
GradualPerformance::new(difficulty, self)
|
GradualPerformance::new(difficulty, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -52,13 +52,13 @@ pub trait IGameMode: Sized {
|
|||||||
|
|
||||||
/// Create a gradual difficulty calculator for a [`Converted`] beatmap.
|
/// Create a gradual difficulty calculator for a [`Converted`] beatmap.
|
||||||
fn gradual_difficulty(
|
fn gradual_difficulty(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &Converted<'_, Self>,
|
map: &Converted<'_, Self>,
|
||||||
) -> Self::GradualDifficulty;
|
) -> Self::GradualDifficulty;
|
||||||
|
|
||||||
/// Create a gradual performance calculator for a [`Converted`] beatmap.
|
/// Create a gradual performance calculator for a [`Converted`] beatmap.
|
||||||
fn gradual_performance(
|
fn gradual_performance(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &Converted<'_, Self>,
|
map: &Converted<'_, Self>,
|
||||||
) -> Self::GradualPerformance;
|
) -> Self::GradualPerformance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,7 @@ use super::{
|
|||||||
/// [`OsuGradualPerformance`]: crate::osu::OsuGradualPerformance
|
/// [`OsuGradualPerformance`]: crate::osu::OsuGradualPerformance
|
||||||
pub struct OsuGradualDifficulty {
|
pub struct OsuGradualDifficulty {
|
||||||
pub(crate) idx: usize,
|
pub(crate) idx: usize,
|
||||||
pub(crate) mods: u32,
|
pub(crate) difficulty: Difficulty,
|
||||||
pub(crate) clock_rate: f64,
|
|
||||||
attrs: OsuDifficultyAttributes,
|
attrs: OsuDifficultyAttributes,
|
||||||
skills: OsuSkills,
|
skills: OsuSkills,
|
||||||
// Lifetimes actually depend on `osu_objects` so this type is
|
// Lifetimes actually depend on `osu_objects` so this type is
|
||||||
@@ -73,16 +72,15 @@ struct NotClonable;
|
|||||||
|
|
||||||
impl OsuGradualDifficulty {
|
impl OsuGradualDifficulty {
|
||||||
/// Create a new difficulty attributes iterator for osu!standard maps.
|
/// Create a new difficulty attributes iterator for osu!standard maps.
|
||||||
pub fn new(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &OsuBeatmap<'_>) -> Self {
|
||||||
let mods = difficulty.get_mods();
|
let mods = difficulty.get_mods();
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
|
||||||
|
|
||||||
let OsuDifficultySetup {
|
let OsuDifficultySetup {
|
||||||
scaling_factor,
|
scaling_factor,
|
||||||
map_attrs,
|
map_attrs,
|
||||||
mut attrs,
|
mut attrs,
|
||||||
time_preempt,
|
time_preempt,
|
||||||
} = OsuDifficultySetup::new(difficulty, converted);
|
} = OsuDifficultySetup::new(&difficulty, converted);
|
||||||
|
|
||||||
let osu_objects = convert_objects(
|
let osu_objects = convert_objects(
|
||||||
converted,
|
converted,
|
||||||
@@ -105,7 +103,7 @@ impl OsuGradualDifficulty {
|
|||||||
let mut osu_objects = OsuObjects::new(osu_objects);
|
let mut osu_objects = OsuObjects::new(osu_objects);
|
||||||
|
|
||||||
let diff_objects = DifficultyValues::create_difficulty_objects(
|
let diff_objects = DifficultyValues::create_difficulty_objects(
|
||||||
difficulty,
|
&difficulty,
|
||||||
&scaling_factor,
|
&scaling_factor,
|
||||||
osu_objects.iter_mut(),
|
osu_objects.iter_mut(),
|
||||||
);
|
);
|
||||||
@@ -115,8 +113,7 @@ impl OsuGradualDifficulty {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
mods,
|
difficulty,
|
||||||
clock_rate,
|
|
||||||
attrs,
|
attrs,
|
||||||
skills,
|
skills,
|
||||||
diff_objects,
|
diff_objects,
|
||||||
@@ -181,7 +178,7 @@ impl Iterator for OsuGradualDifficulty {
|
|||||||
|
|
||||||
DifficultyValues::eval(
|
DifficultyValues::eval(
|
||||||
&mut attrs,
|
&mut attrs,
|
||||||
self.mods,
|
self.difficulty.get_mods(),
|
||||||
aim_difficulty_value,
|
aim_difficulty_value,
|
||||||
aim_no_sliders_difficulty_value,
|
aim_no_sliders_difficulty_value,
|
||||||
speed_difficulty_value,
|
speed_difficulty_value,
|
||||||
@@ -271,8 +268,7 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into_converted::<Osu>();
|
.unchecked_into_converted::<Osu>();
|
||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let mut gradual = OsuGradualDifficulty::new(Difficulty::new(), &converted);
|
||||||
let mut gradual = OsuGradualDifficulty::new(&difficulty, &converted);
|
|
||||||
|
|
||||||
assert!(gradual.next().is_none());
|
assert!(gradual.next().is_none());
|
||||||
}
|
}
|
||||||
@@ -285,9 +281,9 @@ mod tests {
|
|||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let difficulty = Difficulty::new();
|
||||||
|
|
||||||
let mut gradual = OsuGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual = OsuGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = OsuGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_2nd = OsuGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = OsuGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_3rd = OsuGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
let hit_objects_len = converted.hit_objects.len();
|
let hit_objects_len = converted.hit_objects.len();
|
||||||
|
|
||||||
@@ -309,7 +305,8 @@ mod tests {
|
|||||||
assert_eq!(next_gradual, next_gradual_3rd);
|
assert_eq!(next_gradual, next_gradual_3rd);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected = Difficulty::new()
|
let expected = difficulty
|
||||||
|
.clone()
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.with_mode()
|
.with_mode()
|
||||||
.calculate(&converted);
|
.calculate(&converted);
|
||||||
|
|||||||
@@ -68,15 +68,8 @@ pub struct OsuDifficultySetup {
|
|||||||
|
|
||||||
impl OsuDifficultySetup {
|
impl OsuDifficultySetup {
|
||||||
pub fn new(difficulty: &Difficulty, converted: &OsuBeatmap) -> Self {
|
pub fn new(difficulty: &Difficulty, converted: &OsuBeatmap) -> Self {
|
||||||
let mods = difficulty.get_mods();
|
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
let clock_rate = difficulty.get_clock_rate();
|
||||||
|
let map_attrs = converted.attributes().difficulty(difficulty).build();
|
||||||
let map_attrs = converted
|
|
||||||
.attributes()
|
|
||||||
.mods(mods)
|
|
||||||
.clock_rate(clock_rate)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let scaling_factor = ScalingFactor::new(map_attrs.cs);
|
let scaling_factor = ScalingFactor::new(map_attrs.cs);
|
||||||
|
|
||||||
let attrs = OsuDifficultyAttributes {
|
let attrs = OsuDifficultyAttributes {
|
||||||
|
|||||||
+2
-5
@@ -62,15 +62,12 @@ impl IGameMode for Osu {
|
|||||||
OsuPerformance::new(map)
|
OsuPerformance::new(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_difficulty(
|
fn gradual_difficulty(difficulty: Difficulty, map: &OsuBeatmap<'_>) -> Self::GradualDifficulty {
|
||||||
difficulty: &Difficulty,
|
|
||||||
map: &OsuBeatmap<'_>,
|
|
||||||
) -> Self::GradualDifficulty {
|
|
||||||
OsuGradualDifficulty::new(difficulty, map)
|
OsuGradualDifficulty::new(difficulty, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_performance(
|
fn gradual_performance(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &OsuBeatmap<'_>,
|
map: &OsuBeatmap<'_>,
|
||||||
) -> Self::GradualPerformance {
|
) -> Self::GradualPerformance {
|
||||||
OsuGradualPerformance::new(difficulty, map)
|
OsuGradualPerformance::new(difficulty, map)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ pub struct OsuGradualPerformance {
|
|||||||
|
|
||||||
impl OsuGradualPerformance {
|
impl OsuGradualPerformance {
|
||||||
/// Create a new gradual performance calculator for osu!standard maps.
|
/// Create a new gradual performance calculator for osu!standard maps.
|
||||||
pub fn new(difficulty: &Difficulty, converted: &OsuBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &OsuBeatmap<'_>) -> Self {
|
||||||
let difficulty = OsuGradualDifficulty::new(difficulty, converted);
|
let difficulty = OsuGradualDifficulty::new(difficulty, converted);
|
||||||
|
|
||||||
Self { difficulty }
|
Self { difficulty }
|
||||||
@@ -114,8 +114,7 @@ impl OsuGradualPerformance {
|
|||||||
.nth(n)?
|
.nth(n)?
|
||||||
.performance()
|
.performance()
|
||||||
.state(state)
|
.state(state)
|
||||||
.mods(self.difficulty.mods)
|
.difficulty(self.difficulty.difficulty.clone())
|
||||||
.clock_rate(self.difficulty.clock_rate)
|
|
||||||
.passed_objects(self.difficulty.idx as u32)
|
.passed_objects(self.difficulty.idx as u32)
|
||||||
.calculate();
|
.calculate();
|
||||||
|
|
||||||
@@ -138,12 +137,11 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into_converted::<Osu>();
|
.unchecked_into_converted::<Osu>();
|
||||||
|
|
||||||
let mods = 88; // HDHRDT
|
let difficulty = Difficulty::new().mods(88); // HDHRDT
|
||||||
let difficulty = Difficulty::new().mods(88);
|
|
||||||
|
|
||||||
let mut gradual = OsuGradualPerformance::new(&difficulty, &converted);
|
let mut gradual = OsuGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = OsuGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_2nd = OsuGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = OsuGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_3rd = OsuGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
let mut state = OsuScoreState::default();
|
let mut state = OsuScoreState::default();
|
||||||
|
|
||||||
@@ -170,7 +168,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut regular_calc = OsuPerformance::new(converted.as_owned())
|
let mut regular_calc = OsuPerformance::new(converted.as_owned())
|
||||||
.mods(mods)
|
.difficulty(difficulty.clone())
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.state(state);
|
.state(state);
|
||||||
|
|
||||||
|
|||||||
@@ -114,8 +114,10 @@ impl<'map> OsuPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Provide the result of a previous difficulty or performance calculation.
|
/// Provide the result of a previous difficulty or performance calculation.
|
||||||
/// If you already calculated the attributes for the current map-mod combination,
|
///
|
||||||
/// be sure to put them in here so that they don't have to be recalculated.
|
/// If you already calculated the attributes for the current
|
||||||
|
/// [`OsuBeatmap`] and [`Difficulty`], be sure to put them in here so that
|
||||||
|
/// they don't have to be recalculated.
|
||||||
pub fn attributes(mut self, attributes: impl ModeAttributeProvider<Osu>) -> Self {
|
pub fn attributes(mut self, attributes: impl ModeAttributeProvider<Osu>) -> Self {
|
||||||
if let Some(attrs) = attributes.attributes() {
|
if let Some(attrs) = attributes.attributes() {
|
||||||
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
||||||
@@ -177,6 +179,13 @@ impl<'map> OsuPerformance<'map> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
|
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
///
|
///
|
||||||
/// If you want to calculate the performance after every few objects,
|
/// If you want to calculate the performance after every few objects,
|
||||||
@@ -191,14 +200,79 @@ impl<'map> OsuPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the clock rate used in the calculation.
|
/// Adjust the clock rate used in the calculation.
|
||||||
|
///
|
||||||
/// If none is specified, it will take the clock rate based on the mods
|
/// If none is specified, it will take the clock rate based on the mods
|
||||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | 0.01 | 100 |
|
||||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||||
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set AR.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn ar(mut self, ar: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.ar(ar, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set CS.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn cs(mut self, cs: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.cs(cs, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set HP.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn hp(mut self, hp: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.hp(hp, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set OD.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn od(mut self, od: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.od(od, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Provide parameters through an [`OsuScoreState`].
|
/// Provide parameters through an [`OsuScoreState`].
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub const fn state(mut self, state: OsuScoreState) -> Self {
|
pub const fn state(mut self, state: OsuScoreState) -> Self {
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ use super::{
|
|||||||
/// [`TaikoGradualPerformance`]: crate::taiko::TaikoGradualPerformance
|
/// [`TaikoGradualPerformance`]: crate::taiko::TaikoGradualPerformance
|
||||||
pub struct TaikoGradualDifficulty {
|
pub struct TaikoGradualDifficulty {
|
||||||
pub(crate) idx: usize,
|
pub(crate) idx: usize,
|
||||||
pub(crate) mods: u32,
|
pub(crate) difficulty: Difficulty,
|
||||||
pub(crate) clock_rate: f64,
|
|
||||||
attrs: TaikoDifficultyAttributes,
|
attrs: TaikoDifficultyAttributes,
|
||||||
diff_objects: TaikoDifficultyObjects,
|
diff_objects: TaikoDifficultyObjects,
|
||||||
diff_objects_iter: Iter<'static, RefCount<TaikoDifficultyObject>>,
|
diff_objects_iter: Iter<'static, RefCount<TaikoDifficultyObject>>,
|
||||||
@@ -69,9 +68,8 @@ enum FirstTwoCombos {
|
|||||||
|
|
||||||
impl TaikoGradualDifficulty {
|
impl TaikoGradualDifficulty {
|
||||||
/// Create a new difficulty attributes iterator for osu!taiko maps.
|
/// Create a new difficulty attributes iterator for osu!taiko maps.
|
||||||
pub fn new(difficulty: &Difficulty, converted: &TaikoBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &TaikoBeatmap<'_>) -> Self {
|
||||||
let take = difficulty.get_passed_objects();
|
let take = difficulty.get_passed_objects();
|
||||||
let mods = difficulty.get_mods();
|
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
let clock_rate = difficulty.get_clock_rate();
|
||||||
|
|
||||||
let first_combos = match (
|
let first_combos = match (
|
||||||
@@ -84,11 +82,8 @@ impl TaikoGradualDifficulty {
|
|||||||
(Some(true), Some(true)) => FirstTwoCombos::Both,
|
(Some(true), Some(true)) => FirstTwoCombos::Both,
|
||||||
};
|
};
|
||||||
|
|
||||||
let HitWindows { od: hit_window, .. } = converted
|
let HitWindows { od: hit_window, .. } =
|
||||||
.attributes()
|
converted.attributes().difficulty(&difficulty).hit_windows();
|
||||||
.mods(mods)
|
|
||||||
.clock_rate(clock_rate)
|
|
||||||
.hit_windows();
|
|
||||||
|
|
||||||
let mut n_diff_objects = 0;
|
let mut n_diff_objects = 0;
|
||||||
let mut max_combo = 0;
|
let mut max_combo = 0;
|
||||||
@@ -119,8 +114,7 @@ impl TaikoGradualDifficulty {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
idx: 0,
|
idx: 0,
|
||||||
mods,
|
difficulty,
|
||||||
clock_rate,
|
|
||||||
diff_objects,
|
diff_objects,
|
||||||
diff_objects_iter,
|
diff_objects_iter,
|
||||||
peaks,
|
peaks,
|
||||||
@@ -268,8 +262,7 @@ mod tests {
|
|||||||
fn empty() {
|
fn empty() {
|
||||||
let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted();
|
let converted = Beatmap::from_bytes(&[]).unwrap().unchecked_into_converted();
|
||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let mut gradual = TaikoGradualDifficulty::new(Difficulty::new(), &converted);
|
||||||
let mut gradual = TaikoGradualDifficulty::new(&difficulty, &converted);
|
|
||||||
|
|
||||||
assert!(gradual.next().is_none());
|
assert!(gradual.next().is_none());
|
||||||
}
|
}
|
||||||
@@ -282,9 +275,9 @@ mod tests {
|
|||||||
|
|
||||||
let difficulty = Difficulty::new();
|
let difficulty = Difficulty::new();
|
||||||
|
|
||||||
let mut gradual = TaikoGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual = TaikoGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = TaikoGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_2nd = TaikoGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = TaikoGradualDifficulty::new(&difficulty, &converted);
|
let mut gradual_3rd = TaikoGradualDifficulty::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
let hit_objects_len = converted.hit_objects.len();
|
let hit_objects_len = converted.hit_objects.len();
|
||||||
|
|
||||||
@@ -312,7 +305,8 @@ mod tests {
|
|||||||
assert_eq!(next_gradual, next_gradual_3rd);
|
assert_eq!(next_gradual, next_gradual_3rd);
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected = Difficulty::new()
|
let expected = difficulty
|
||||||
|
.clone()
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.with_mode()
|
.with_mode()
|
||||||
.calculate(&converted);
|
.calculate(&converted);
|
||||||
|
|||||||
@@ -26,12 +26,9 @@ pub fn difficulty(
|
|||||||
difficulty: &Difficulty,
|
difficulty: &Difficulty,
|
||||||
converted: &TaikoBeatmap<'_>,
|
converted: &TaikoBeatmap<'_>,
|
||||||
) -> TaikoDifficultyAttributes {
|
) -> TaikoDifficultyAttributes {
|
||||||
let clock_rate = difficulty.get_clock_rate();
|
|
||||||
|
|
||||||
let hit_window = converted
|
let hit_window = converted
|
||||||
.attributes()
|
.attributes()
|
||||||
.mods(difficulty.get_mods())
|
.difficulty(difficulty)
|
||||||
.clock_rate(clock_rate)
|
|
||||||
.hit_windows()
|
.hit_windows()
|
||||||
.od;
|
.od;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -59,14 +59,14 @@ impl IGameMode for Taiko {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_difficulty(
|
fn gradual_difficulty(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &TaikoBeatmap<'_>,
|
map: &TaikoBeatmap<'_>,
|
||||||
) -> Self::GradualDifficulty {
|
) -> Self::GradualDifficulty {
|
||||||
TaikoGradualDifficulty::new(difficulty, map)
|
TaikoGradualDifficulty::new(difficulty, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gradual_performance(
|
fn gradual_performance(
|
||||||
difficulty: &Difficulty,
|
difficulty: Difficulty,
|
||||||
map: &TaikoBeatmap<'_>,
|
map: &TaikoBeatmap<'_>,
|
||||||
) -> Self::GradualPerformance {
|
) -> Self::GradualPerformance {
|
||||||
TaikoGradualPerformance::new(difficulty, map)
|
TaikoGradualPerformance::new(difficulty, map)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ pub struct TaikoGradualPerformance {
|
|||||||
|
|
||||||
impl TaikoGradualPerformance {
|
impl TaikoGradualPerformance {
|
||||||
/// Create a new gradual performance calculator for osu!taiko maps.
|
/// Create a new gradual performance calculator for osu!taiko maps.
|
||||||
pub fn new(difficulty: &Difficulty, converted: &TaikoBeatmap<'_>) -> Self {
|
pub fn new(difficulty: Difficulty, converted: &TaikoBeatmap<'_>) -> Self {
|
||||||
let difficulty = TaikoGradualDifficulty::new(difficulty, converted);
|
let difficulty = TaikoGradualDifficulty::new(difficulty, converted);
|
||||||
|
|
||||||
Self { difficulty }
|
Self { difficulty }
|
||||||
@@ -113,8 +113,7 @@ impl TaikoGradualPerformance {
|
|||||||
.nth(n)?
|
.nth(n)?
|
||||||
.performance()
|
.performance()
|
||||||
.state(state)
|
.state(state)
|
||||||
.mods(self.difficulty.mods)
|
.difficulty(self.difficulty.difficulty.clone())
|
||||||
.clock_rate(self.difficulty.clock_rate)
|
|
||||||
.passed_objects(self.difficulty.idx as u32)
|
.passed_objects(self.difficulty.idx as u32)
|
||||||
.calculate();
|
.calculate();
|
||||||
|
|
||||||
@@ -134,12 +133,11 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unchecked_into_converted();
|
.unchecked_into_converted();
|
||||||
|
|
||||||
let mods = 88; // HDHRDT
|
let difficulty = Difficulty::new().mods(88); // HDHRDT
|
||||||
let difficulty = Difficulty::new().mods(88);
|
|
||||||
|
|
||||||
let mut gradual = TaikoGradualPerformance::new(&difficulty, &converted);
|
let mut gradual = TaikoGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_2nd = TaikoGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_2nd = TaikoGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
let mut gradual_3rd = TaikoGradualPerformance::new(&difficulty, &converted);
|
let mut gradual_3rd = TaikoGradualPerformance::new(difficulty.clone(), &converted);
|
||||||
|
|
||||||
let mut state = TaikoScoreState::default();
|
let mut state = TaikoScoreState::default();
|
||||||
|
|
||||||
@@ -172,7 +170,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut regular_calc = TaikoPerformance::new(converted.as_owned())
|
let mut regular_calc = TaikoPerformance::new(converted.as_owned())
|
||||||
.mods(mods)
|
.difficulty(difficulty.clone())
|
||||||
.passed_objects(i as u32)
|
.passed_objects(i as u32)
|
||||||
.state(state);
|
.state(state);
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,10 @@ impl<'map> TaikoPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Provide the result of a previous difficulty or performance calculation.
|
/// Provide the result of a previous difficulty or performance calculation.
|
||||||
/// If you already calculated the attributes for the current map-mod combination,
|
///
|
||||||
/// be sure to put them in here so that they don't have to be recalculated.
|
/// If you already calculated the attributes for the current
|
||||||
|
/// [`TaikoBeatmap`] and [`Difficulty`], be sure to put them in here so
|
||||||
|
/// that they don't have to be recalculated.
|
||||||
pub fn attributes(mut self, attrs: impl ModeAttributeProvider<Taiko>) -> Self {
|
pub fn attributes(mut self, attrs: impl ModeAttributeProvider<Taiko>) -> Self {
|
||||||
if let Some(attrs) = attrs.attributes() {
|
if let Some(attrs) = attrs.attributes() {
|
||||||
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
self.map_or_attrs = MapOrAttrs::Attrs(attrs);
|
||||||
@@ -122,6 +124,13 @@ impl<'map> TaikoPerformance<'map> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
|
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
///
|
///
|
||||||
/// If you want to calculate the performance after every few objects,
|
/// If you want to calculate the performance after every few objects,
|
||||||
@@ -136,14 +145,49 @@ impl<'map> TaikoPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the clock rate used in the calculation.
|
/// Adjust the clock rate used in the calculation.
|
||||||
|
///
|
||||||
/// If none is specified, it will take the clock rate based on the mods
|
/// If none is specified, it will take the clock rate based on the mods
|
||||||
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
/// i.e. 1.5 for DT, 0.75 for HT and 1.0 otherwise.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | 0.01 | 100 |
|
||||||
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
pub fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||||
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
self.difficulty = self.difficulty.clock_rate(clock_rate);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set HP.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn hp(mut self, hp: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.hp(hp, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override a beatmap's set OD.
|
||||||
|
///
|
||||||
|
/// `with_mods` determines if the given value should be used before
|
||||||
|
/// or after accounting for mods, e.g. on `true` the value will be
|
||||||
|
/// used as is and on `false` it will be modified based on the mods.
|
||||||
|
///
|
||||||
|
/// | Minimum | Maximum |
|
||||||
|
/// | :-----: | :-----: |
|
||||||
|
/// | -20 | 20 |
|
||||||
|
pub fn od(mut self, od: f32, with_mods: bool) -> Self {
|
||||||
|
self.difficulty = self.difficulty.od(od, with_mods);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Provide parameters through a [`TaikoScoreState`].
|
/// Provide parameters through a [`TaikoScoreState`].
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
pub const fn state(mut self, state: TaikoScoreState) -> Self {
|
pub const fn state(mut self, state: TaikoScoreState) -> Self {
|
||||||
|
|||||||
Reference in New Issue
Block a user