added more settings to Difficulty (unused)
This commit is contained in:
@@ -14,33 +14,26 @@ use crate::{
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use rosu_pp::{Beatmap, ConvertedDifficulty};
|
||||
/// use rosu_pp::{Beatmap, Difficulty};
|
||||
/// use rosu_pp::catch::{Catch, CatchDifficultyAttributes};
|
||||
///
|
||||
/// let converted = Beatmap::from_path("./resources/2118524.osu")
|
||||
/// .unwrap()
|
||||
/// .unchecked_into_converted();
|
||||
///
|
||||
/// let difficulty = ConvertedDifficulty::<Catch>::new();
|
||||
/// // Same as `Difficulty::new().with_mode::<Catch>();`
|
||||
///
|
||||
/// let attrs: CatchDifficultyAttributes = difficulty
|
||||
/// let attrs: CatchDifficultyAttributes = Difficulty::new()
|
||||
/// .mods(8 + 1024) // HDFL
|
||||
/// .with_mode::<Catch>() // -> `ConvertedDifficulty`
|
||||
/// .calculate(&converted);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub struct ConvertedDifficulty<M> {
|
||||
inner: Difficulty,
|
||||
pub struct ConvertedDifficulty<'a, M> {
|
||||
inner: &'a Difficulty,
|
||||
_mode: PhantomData<M>,
|
||||
}
|
||||
|
||||
impl<M> ConvertedDifficulty<M> {
|
||||
/// Create a new difficulty calculator for a generic mode.
|
||||
pub const fn new() -> Self {
|
||||
Self::from_difficulty(Difficulty::new())
|
||||
}
|
||||
|
||||
pub(crate) const fn from_difficulty(difficulty: Difficulty) -> Self {
|
||||
impl<'a, M> ConvertedDifficulty<'a, M> {
|
||||
pub(crate) const fn new(difficulty: &'a Difficulty) -> Self {
|
||||
Self {
|
||||
inner: difficulty,
|
||||
_mode: PhantomData,
|
||||
@@ -48,102 +41,73 @@ impl<M> ConvertedDifficulty<M> {
|
||||
}
|
||||
|
||||
/// Return the internal [`Difficulty`].
|
||||
pub const fn into_inner(self) -> Difficulty {
|
||||
pub const fn inner(self) -> &'a Difficulty {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Cast from generic mode `M` to `N`.
|
||||
pub const fn cast<N: IGameMode>(self) -> ConvertedDifficulty<N> {
|
||||
pub const fn cast<N: IGameMode>(self) -> ConvertedDifficulty<'a, N> {
|
||||
ConvertedDifficulty {
|
||||
inner: self.inner,
|
||||
_mode: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify mods through their bit values.
|
||||
///
|
||||
/// See [https://github.com/ppy/osu-api/wiki#mods](https://github.com/ppy/osu-api/wiki#mods)
|
||||
pub const fn mods(self, mods: u32) -> Self {
|
||||
Self {
|
||||
inner: self.inner.mods(mods),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||
pub const fn passed_objects(self, passed_objects: u32) -> Self {
|
||||
Self {
|
||||
inner: self.inner.passed_objects(passed_objects),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation between 0.01 and 100.0.
|
||||
///
|
||||
/// 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.
|
||||
pub fn clock_rate(self, clock_rate: f64) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clock_rate(clock_rate),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: IGameMode> ConvertedDifficulty<M> {
|
||||
impl<M: IGameMode> ConvertedDifficulty<'_, M> {
|
||||
/// Perform the difficulty calculation for a [`Converted`] beatmap and
|
||||
/// process the final skill values.
|
||||
pub fn calculate(&self, map: &Converted<'_, M>) -> M::DifficultyAttributes {
|
||||
M::difficulty(&self.inner, map)
|
||||
pub fn calculate(self, map: &Converted<'_, M>) -> M::DifficultyAttributes {
|
||||
M::difficulty(self.inner, map)
|
||||
}
|
||||
|
||||
/// Perform a difficulty calculation for a [`Converted`] beatmap without
|
||||
/// processing the final skill values.
|
||||
pub fn strains(&self, map: &Converted<'_, M>) -> M::Strains {
|
||||
M::strains(&self.inner, map)
|
||||
pub fn strains(self, map: &Converted<'_, M>) -> M::Strains {
|
||||
M::strains(self.inner, map)
|
||||
}
|
||||
|
||||
/// Create a gradual difficulty calculator for a [`Converted`] beatmap.
|
||||
pub fn gradual_difficulty(&self, map: &Converted<'_, M>) -> M::GradualDifficulty {
|
||||
M::gradual_difficulty(&self.inner, map)
|
||||
pub fn gradual_difficulty(self, map: &Converted<'_, M>) -> M::GradualDifficulty {
|
||||
M::gradual_difficulty(self.inner, map)
|
||||
}
|
||||
|
||||
/// Create a gradual performance calculator for a [`Converted`] beatmap.
|
||||
pub fn gradual_performance(&self, map: &Converted<'_, M>) -> M::GradualPerformance {
|
||||
M::gradual_performance(&self.inner, map)
|
||||
pub fn gradual_performance(self, map: &Converted<'_, M>) -> M::GradualPerformance {
|
||||
M::gradual_performance(self.inner, map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: IGameMode> From<Difficulty> for ConvertedDifficulty<M> {
|
||||
fn from(difficulty: Difficulty) -> Self {
|
||||
Self::from_difficulty(difficulty)
|
||||
impl<'a, M: IGameMode> From<&'a Difficulty> for ConvertedDifficulty<'a, M> {
|
||||
fn from(difficulty: &'a Difficulty) -> Self {
|
||||
Self::new(difficulty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> AsRef<Difficulty> for ConvertedDifficulty<M> {
|
||||
impl<M> AsRef<Difficulty> for ConvertedDifficulty<'_, M> {
|
||||
fn as_ref(&self) -> &Difficulty {
|
||||
&self.inner
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Copy for ConvertedDifficulty<M> {}
|
||||
impl<M> Copy for ConvertedDifficulty<'_, M> {}
|
||||
|
||||
impl<M> Clone for ConvertedDifficulty<M> {
|
||||
impl<M> Clone for ConvertedDifficulty<'_, M> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Debug for ConvertedDifficulty<M> {
|
||||
impl<M> Debug for ConvertedDifficulty<'_, M> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
f.debug_struct("ConvertedDifficulty")
|
||||
.field("inner", &self.inner)
|
||||
.field("inner", self.inner)
|
||||
.field("mode", &GenericFormatter::<M>::new())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> PartialEq for ConvertedDifficulty<M> {
|
||||
impl<M> PartialEq for ConvertedDifficulty<'_, M> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
|
||||
+181
-7
@@ -1,4 +1,7 @@
|
||||
use std::{borrow::Cow, num::NonZeroU32};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
num::{NonZeroU32, NonZeroU64},
|
||||
};
|
||||
|
||||
use rosu_map::section::general::GameMode;
|
||||
|
||||
@@ -35,7 +38,7 @@ use crate::{model::mode::IGameMode, util::mods::Mods};
|
||||
/// .mods(8 + 1024) // HDFL
|
||||
/// .calculate(&map);
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[must_use]
|
||||
pub struct Difficulty {
|
||||
mods: u32,
|
||||
@@ -49,6 +52,24 @@ pub struct Difficulty {
|
||||
/// This allows for an optimization to reduce the struct size by storing its
|
||||
/// bits as a [`NonZeroU32`].
|
||||
clock_rate: Option<NonZeroU32>,
|
||||
ar: Option<ModsDependent>,
|
||||
cs: Option<ModsDependent>,
|
||||
hp: 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>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct ModsDependent {
|
||||
pub value: f32,
|
||||
pub with_mods: bool,
|
||||
}
|
||||
|
||||
impl Difficulty {
|
||||
@@ -58,12 +79,21 @@ impl Difficulty {
|
||||
mods: 0,
|
||||
passed_objects: None,
|
||||
clock_rate: None,
|
||||
ar: None,
|
||||
cs: None,
|
||||
hp: None,
|
||||
od: None,
|
||||
slider_multiplier: None,
|
||||
hardrock_offsets: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this [`Difficulty`] as a calculator for a specific [`IGameMode`].
|
||||
pub const fn with_mode<M: IGameMode>(self) -> ConvertedDifficulty<M> {
|
||||
ConvertedDifficulty::from_difficulty(self)
|
||||
///
|
||||
/// Note that [`ConvertedDifficulty`] won't allow to further customize
|
||||
/// fields so be sure they're all set before converting to it.
|
||||
pub const fn with_mode<M: IGameMode>(&self) -> ConvertedDifficulty<'_, M> {
|
||||
ConvertedDifficulty::new(self)
|
||||
}
|
||||
|
||||
/// Specify mods through their bit values.
|
||||
@@ -81,10 +111,14 @@ impl Difficulty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjust the clock rate used in the calculation between 0.01 and 100.0.
|
||||
/// Adjust the clock rate used in the calculation.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// | Minimum | Maximum |
|
||||
/// | :-----: | :-----: |
|
||||
/// | 0.01 | 100 |
|
||||
pub fn clock_rate(self, clock_rate: f64) -> Self {
|
||||
let clock_rate = (clock_rate as f32).clamp(0.01, 100.0).to_bits();
|
||||
|
||||
@@ -98,9 +132,117 @@ impl Difficulty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the difficulty calculation.
|
||||
/// Override a beatmap's set AR.
|
||||
///
|
||||
/// The returned attributes depend on the map's mode.
|
||||
/// 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 {
|
||||
Self {
|
||||
ar: Some(ModsDependent {
|
||||
value: ar.clamp(-20.0, 20.0),
|
||||
with_mods,
|
||||
}),
|
||||
..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 {
|
||||
Self {
|
||||
cs: Some(ModsDependent {
|
||||
value: cs.clamp(-20.0, 20.0),
|
||||
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(self, hp: f32, with_mods: bool) -> Self {
|
||||
Self {
|
||||
hp: Some(ModsDependent {
|
||||
value: hp.clamp(-20.0, 20.0),
|
||||
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(self, od: f32, with_mods: bool) -> Self {
|
||||
Self {
|
||||
od: Some(ModsDependent {
|
||||
value: od.clamp(-20.0, 20.0),
|
||||
with_mods,
|
||||
}),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Only relevant for osu!catch.
|
||||
pub const fn hardrock_offsets(self, hardrock_offsets: bool) -> Self {
|
||||
Self {
|
||||
hardrock_offsets: Some(hardrock_offsets),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the difficulty calculation.
|
||||
pub fn calculate(&self, map: &Beatmap) -> DifficultyAttributes {
|
||||
let map = Cow::Borrowed(map);
|
||||
|
||||
@@ -157,4 +299,36 @@ impl Difficulty {
|
||||
pub(crate) fn get_passed_objects(&self) -> usize {
|
||||
self.passed_objects.map_or(usize::MAX, |n| n as usize)
|
||||
}
|
||||
|
||||
pub(crate) const fn get_ar(&self) -> Option<ModsDependent> {
|
||||
self.ar
|
||||
}
|
||||
|
||||
pub(crate) const fn get_cs(&self) -> Option<ModsDependent> {
|
||||
self.cs
|
||||
}
|
||||
|
||||
pub(crate) const fn get_hp(&self) -> Option<ModsDependent> {
|
||||
self.hp
|
||||
}
|
||||
|
||||
pub(crate) const fn get_od(&self) -> Option<ModsDependent> {
|
||||
self.od
|
||||
}
|
||||
|
||||
pub(crate) fn get_slider_multiplier(&self) -> f64 {
|
||||
self.slider_multiplier
|
||||
.map(NonZeroU64::get)
|
||||
.map_or(1.0, f64::from_bits)
|
||||
}
|
||||
|
||||
pub(crate) const fn get_hardrock_offsets(&self) -> Option<bool> {
|
||||
self.hardrock_offsets
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Difficulty {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user