feat: use rosu-mods (#36)
* use rosu-mods * apply values from DifficultyAdjust mod * minor doc addition * consider Blinds mod in perf calc
This commit is contained in:
@@ -18,6 +18,7 @@ tracing = ["rosu-map/tracing"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rosu-map = { version = "0.1.1" }
|
rosu-map = { version = "0.1.1" }
|
||||||
|
rosu-mods = { version = "0.1.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = "1.4.0"
|
proptest = "1.4.0"
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
use crate::Difficulty;
|
use crate::{model::mods::GameMods, Difficulty};
|
||||||
|
|
||||||
use super::ModsDependent;
|
use super::ModsDependent;
|
||||||
|
|
||||||
/// [`Difficulty`] but all fields are public for inspection.
|
/// [`Difficulty`] but all fields are public for inspection.
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct InspectDifficulty {
|
pub struct InspectDifficulty {
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods.
|
||||||
///
|
pub mods: GameMods,
|
||||||
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
|
||||||
pub mods: u32,
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
pub passed_objects: Option<u32>,
|
pub passed_objects: Option<u32>,
|
||||||
/// Adjust the clock rate used in the calculation.
|
/// Adjust the clock rate used in the calculation.
|
||||||
|
|||||||
+41
-26
@@ -9,7 +9,10 @@ use rosu_map::section::general::GameMode;
|
|||||||
use crate::{
|
use crate::{
|
||||||
catch::Catch,
|
catch::Catch,
|
||||||
mania::Mania,
|
mania::Mania,
|
||||||
model::beatmap::{Beatmap, Converted},
|
model::{
|
||||||
|
beatmap::{Beatmap, Converted},
|
||||||
|
mods::GameMods,
|
||||||
|
},
|
||||||
osu::Osu,
|
osu::Osu,
|
||||||
taiko::Taiko,
|
taiko::Taiko,
|
||||||
GradualDifficulty, GradualPerformance,
|
GradualDifficulty, GradualPerformance,
|
||||||
@@ -25,7 +28,7 @@ pub mod inspect;
|
|||||||
pub mod object;
|
pub mod object;
|
||||||
pub mod skills;
|
pub mod skills;
|
||||||
|
|
||||||
use crate::{model::mode::IGameMode, util::mods::Mods};
|
use crate::model::mode::IGameMode;
|
||||||
|
|
||||||
/// Difficulty calculator on maps of any mode.
|
/// Difficulty calculator on maps of any mode.
|
||||||
///
|
///
|
||||||
@@ -43,7 +46,7 @@ use crate::{model::mode::IGameMode, util::mods::Mods};
|
|||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Difficulty {
|
pub struct Difficulty {
|
||||||
mods: u32,
|
mods: GameMods,
|
||||||
passed_objects: Option<u32>,
|
passed_objects: Option<u32>,
|
||||||
/// Clock rate will be clamped internally between 0.01 and 100.0.
|
/// Clock rate will be clamped internally between 0.01 and 100.0.
|
||||||
///
|
///
|
||||||
@@ -86,7 +89,7 @@ impl Difficulty {
|
|||||||
/// Create a new difficulty calculator.
|
/// Create a new difficulty calculator.
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
mods: 0,
|
mods: GameMods::DEFAULT,
|
||||||
passed_objects: None,
|
passed_objects: None,
|
||||||
clock_rate: None,
|
clock_rate: None,
|
||||||
ar: None,
|
ar: None,
|
||||||
@@ -122,7 +125,7 @@ impl Difficulty {
|
|||||||
InspectDifficulty {
|
InspectDifficulty {
|
||||||
mods,
|
mods,
|
||||||
passed_objects,
|
passed_objects,
|
||||||
clock_rate: clock_rate.map(non_zero_u32_to_f64),
|
clock_rate: clock_rate.map(non_zero_u32_to_f32).map(f64::from),
|
||||||
ar,
|
ar,
|
||||||
cs,
|
cs,
|
||||||
hp,
|
hp,
|
||||||
@@ -131,19 +134,28 @@ impl Difficulty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods.
|
||||||
|
///
|
||||||
|
/// Accepted types are
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
///
|
///
|
||||||
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
||||||
pub const fn mods(self, mods: u32) -> Self {
|
pub fn mods(self, mods: impl Into<GameMods>) -> Self {
|
||||||
Self { mods, ..self }
|
Self {
|
||||||
|
mods: mods.into(),
|
||||||
|
..self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Amount of passed objects for partial plays, e.g. a fail.
|
/// Amount of passed objects for partial plays, e.g. a fail.
|
||||||
pub const fn passed_objects(self, passed_objects: u32) -> Self {
|
pub const fn passed_objects(mut self, passed_objects: u32) -> Self {
|
||||||
Self {
|
self.passed_objects = Some(passed_objects);
|
||||||
passed_objects: Some(passed_objects),
|
|
||||||
..self
|
self
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the clock rate used in the calculation.
|
/// Adjust the clock rate used in the calculation.
|
||||||
@@ -250,11 +262,10 @@ impl Difficulty {
|
|||||||
/// 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.
|
||||||
pub const fn hardrock_offsets(self, hardrock_offsets: bool) -> Self {
|
pub const fn hardrock_offsets(mut self, hardrock_offsets: bool) -> Self {
|
||||||
Self {
|
self.hardrock_offsets = Some(hardrock_offsets);
|
||||||
hardrock_offsets: Some(hardrock_offsets),
|
|
||||||
..self
|
self
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the difficulty calculation.
|
/// Perform the difficulty calculation.
|
||||||
@@ -300,13 +311,16 @@ impl Difficulty {
|
|||||||
GradualPerformance::new(self, map)
|
GradualPerformance::new(self, map)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn get_mods(&self) -> u32 {
|
pub(crate) const fn get_mods(&self) -> &GameMods {
|
||||||
self.mods
|
&self.mods
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_clock_rate(&self) -> f64 {
|
pub(crate) fn get_clock_rate(&self) -> f64 {
|
||||||
self.clock_rate
|
let clock_rate = self
|
||||||
.map_or(self.mods.clock_rate(), non_zero_u32_to_f64)
|
.clock_rate
|
||||||
|
.map_or(self.mods.clock_rate(), non_zero_u32_to_f32);
|
||||||
|
|
||||||
|
f64::from(clock_rate)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_passed_objects(&self) -> usize {
|
pub(crate) fn get_passed_objects(&self) -> usize {
|
||||||
@@ -330,12 +344,13 @@ impl Difficulty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_hardrock_offsets(&self) -> bool {
|
pub(crate) fn get_hardrock_offsets(&self) -> bool {
|
||||||
self.hardrock_offsets.unwrap_or(self.mods.hr())
|
self.hardrock_offsets
|
||||||
|
.unwrap_or_else(|| self.mods.hardrock_offsets())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn non_zero_u32_to_f64(n: NonZeroU32) -> f64 {
|
fn non_zero_u32_to_f32(n: NonZeroU32) -> f32 {
|
||||||
f64::from(f32::from_bits(n.get()))
|
f32::from_bits(n.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Difficulty {
|
impl Debug for Difficulty {
|
||||||
@@ -354,7 +369,7 @@ impl Debug for Difficulty {
|
|||||||
f.debug_struct("Difficulty")
|
f.debug_struct("Difficulty")
|
||||||
.field("mods", mods)
|
.field("mods", mods)
|
||||||
.field("passed_objects", passed_objects)
|
.field("passed_objects", passed_objects)
|
||||||
.field("clock_rate", &clock_rate.map(non_zero_u32_to_f64))
|
.field("clock_rate", &clock_rate.map(non_zero_u32_to_f32))
|
||||||
.field("ar", ar)
|
.field("ar", ar)
|
||||||
.field("cs", cs)
|
.field("cs", cs)
|
||||||
.field("hp", hp)
|
.field("hp", hp)
|
||||||
|
|||||||
@@ -98,7 +98,14 @@ impl<'map> Performance<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods.
|
||||||
|
///
|
||||||
|
/// Accepted types are
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
///
|
///
|
||||||
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
||||||
pub fn mods(self, mods: u32) -> Self {
|
pub fn mods(self, mods: u32) -> Self {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use std::cmp::{self, Ordering};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any::{Difficulty, IntoModePerformance, IntoPerformance},
|
any::{Difficulty, IntoModePerformance, IntoPerformance},
|
||||||
|
model::mods::GameMods,
|
||||||
osu::OsuPerformance,
|
osu::OsuPerformance,
|
||||||
util::{map_or_attrs::MapOrAttrs, mods::Mods},
|
util::map_or_attrs::MapOrAttrs,
|
||||||
Performance,
|
Performance,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,10 +71,17 @@ impl<'map> CatchPerformance<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods.
|
||||||
|
///
|
||||||
|
/// Accepted types are
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
///
|
///
|
||||||
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
||||||
pub const fn mods(mut self, mods: u32) -> Self {
|
pub fn mods(mut self, mods: impl Into<GameMods>) -> Self {
|
||||||
self.difficulty = self.difficulty.mods(mods);
|
self.difficulty = self.difficulty.mods(mods);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -122,7 +130,7 @@ impl<'map> CatchPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Use the specified settings of the given [`Difficulty`].
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
pub fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
self.difficulty = difficulty;
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -135,7 +143,7 @@ impl<'map> CatchPerformance<'map> {
|
|||||||
/// `passed_objects`, you should use [`CatchGradualPerformance`].
|
/// `passed_objects`, you should use [`CatchGradualPerformance`].
|
||||||
///
|
///
|
||||||
/// [`CatchGradualPerformance`]: crate::catch::CatchGradualPerformance
|
/// [`CatchGradualPerformance`]: crate::catch::CatchGradualPerformance
|
||||||
pub const fn passed_objects(mut self, passed_objects: u32) -> Self {
|
pub fn passed_objects(mut self, passed_objects: u32) -> Self {
|
||||||
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -216,7 +224,7 @@ impl<'map> CatchPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust patterns as if the HR mod is enabled.
|
/// Adjust patterns as if the HR mod is enabled.
|
||||||
pub const fn hardrock_offsets(mut self, hardrock_offsets: bool) -> Self {
|
pub fn hardrock_offsets(mut self, hardrock_offsets: bool) -> Self {
|
||||||
self.difficulty = self.difficulty.hardrock_offsets(hardrock_offsets);
|
self.difficulty = self.difficulty.hardrock_offsets(hardrock_offsets);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -486,13 +494,13 @@ impl<'map, T: IntoModePerformance<'map, Catch>> From<T> for CatchPerformance<'ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CatchPerformanceInner {
|
struct CatchPerformanceInner<'mods> {
|
||||||
attrs: CatchDifficultyAttributes,
|
attrs: CatchDifficultyAttributes,
|
||||||
mods: u32,
|
mods: &'mods GameMods,
|
||||||
state: CatchScoreState,
|
state: CatchScoreState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CatchPerformanceInner {
|
impl CatchPerformanceInner<'_> {
|
||||||
fn calculate(self) -> CatchPerformanceAttributes {
|
fn calculate(self) -> CatchPerformanceAttributes {
|
||||||
let attributes = &self.attrs;
|
let attributes = &self.attrs;
|
||||||
let stars = attributes.stars;
|
let stars = attributes.stars;
|
||||||
|
|||||||
+4
-1
@@ -163,7 +163,10 @@
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use self::{
|
pub use self::{
|
||||||
any::{Difficulty, GradualDifficulty, GradualPerformance, Performance},
|
any::{Difficulty, GradualDifficulty, GradualPerformance, Performance},
|
||||||
model::beatmap::{Beatmap, Converted},
|
model::{
|
||||||
|
beatmap::{Beatmap, Converted},
|
||||||
|
mods::GameMods,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Types for calculations of any mode.
|
/// Types for calculations of any mode.
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use std::cmp;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance},
|
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance},
|
||||||
|
model::mods::GameMods,
|
||||||
osu::OsuPerformance,
|
osu::OsuPerformance,
|
||||||
util::{map_or_attrs::MapOrAttrs, mods::Mods},
|
util::map_or_attrs::MapOrAttrs,
|
||||||
Performance,
|
Performance,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,17 +72,24 @@ impl<'map> ManiaPerformance<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods.
|
||||||
|
///
|
||||||
|
/// Accepted types are
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
///
|
///
|
||||||
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
||||||
pub const fn mods(mut self, mods: u32) -> Self {
|
pub fn mods(mut self, mods: impl Into<GameMods>) -> Self {
|
||||||
self.difficulty = self.difficulty.mods(mods);
|
self.difficulty = self.difficulty.mods(mods);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use the specified settings of the given [`Difficulty`].
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
pub fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
self.difficulty = difficulty;
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -94,7 +102,7 @@ impl<'map> ManiaPerformance<'map> {
|
|||||||
/// `passed_objects`, you should use [`ManiaGradualPerformance`].
|
/// `passed_objects`, you should use [`ManiaGradualPerformance`].
|
||||||
///
|
///
|
||||||
/// [`ManiaGradualPerformance`]: crate::mania::ManiaGradualPerformance
|
/// [`ManiaGradualPerformance`]: crate::mania::ManiaGradualPerformance
|
||||||
pub const fn passed_objects(mut self, passed_objects: u32) -> Self {
|
pub fn passed_objects(mut self, passed_objects: u32) -> Self {
|
||||||
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -840,13 +848,13 @@ impl<'map, T: IntoModePerformance<'map, Mania>> From<T> for ManiaPerformance<'ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ManiaPerformanceInner {
|
struct ManiaPerformanceInner<'mods> {
|
||||||
attrs: ManiaDifficultyAttributes,
|
attrs: ManiaDifficultyAttributes,
|
||||||
mods: u32,
|
mods: &'mods GameMods,
|
||||||
state: ManiaScoreState,
|
state: ManiaScoreState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManiaPerformanceInner {
|
impl ManiaPerformanceInner<'_> {
|
||||||
fn calculate(self) -> ManiaPerformanceAttributes {
|
fn calculate(self) -> ManiaPerformanceAttributes {
|
||||||
// * Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
// * Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
|
||||||
// * The specific number has no intrinsic meaning and can be adjusted as needed.
|
// * The specific number has no intrinsic meaning and can be adjusted as needed.
|
||||||
|
|||||||
+191
-97
@@ -1,6 +1,6 @@
|
|||||||
use rosu_map::section::general::GameMode;
|
use rosu_map::section::general::GameMode;
|
||||||
|
|
||||||
use crate::{any::difficulty::ModsDependent, util::mods::Mods, Difficulty};
|
use crate::{any::difficulty::ModsDependent, model::mods::GameMods, Difficulty};
|
||||||
|
|
||||||
use super::{converted::Converted, Beatmap};
|
use super::{converted::Converted, Beatmap};
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ pub struct HitWindows {
|
|||||||
pub struct BeatmapAttributesBuilder {
|
pub struct BeatmapAttributesBuilder {
|
||||||
mode: GameMode,
|
mode: GameMode,
|
||||||
is_convert: bool,
|
is_convert: bool,
|
||||||
ar: ModsDependent,
|
ar: ModsDependentKind,
|
||||||
od: ModsDependent,
|
od: ModsDependentKind,
|
||||||
cs: ModsDependent,
|
cs: ModsDependentKind,
|
||||||
hp: ModsDependent,
|
hp: ModsDependentKind,
|
||||||
mods: u32,
|
mods: GameMods,
|
||||||
clock_rate: Option<f64>,
|
clock_rate: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,24 +60,24 @@ impl BeatmapAttributesBuilder {
|
|||||||
Self {
|
Self {
|
||||||
mode: GameMode::Osu,
|
mode: GameMode::Osu,
|
||||||
is_convert: false,
|
is_convert: false,
|
||||||
ar: ModsDependent::new(5.0),
|
ar: ModsDependentKind::DEFAULT,
|
||||||
od: ModsDependent::new(5.0),
|
od: ModsDependentKind::DEFAULT,
|
||||||
cs: ModsDependent::new(5.0),
|
cs: ModsDependentKind::DEFAULT,
|
||||||
hp: ModsDependent::new(5.0),
|
hp: ModsDependentKind::DEFAULT,
|
||||||
mods: 0,
|
mods: GameMods::DEFAULT,
|
||||||
clock_rate: None,
|
clock_rate: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use the given [`Beatmap`]'s attributes, mode, and convert status.
|
/// Use the given [`Beatmap`]'s attributes, mode, and convert status.
|
||||||
pub const fn map(self, map: &Beatmap) -> Self {
|
pub fn map(self, map: &Beatmap) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: map.mode,
|
mode: map.mode,
|
||||||
ar: ModsDependent::new(map.ar),
|
ar: ModsDependentKind::Default(ModsDependent::new(map.ar)),
|
||||||
od: ModsDependent::new(map.od),
|
od: ModsDependentKind::Default(ModsDependent::new(map.od)),
|
||||||
cs: ModsDependent::new(map.cs),
|
cs: ModsDependentKind::Default(ModsDependent::new(map.cs)),
|
||||||
hp: ModsDependent::new(map.hp),
|
hp: ModsDependentKind::Default(ModsDependent::new(map.hp)),
|
||||||
mods: 0,
|
mods: GameMods::DEFAULT,
|
||||||
clock_rate: None,
|
clock_rate: None,
|
||||||
is_convert: map.is_convert,
|
is_convert: map.is_convert,
|
||||||
}
|
}
|
||||||
@@ -88,14 +88,13 @@ impl BeatmapAttributesBuilder {
|
|||||||
/// `with_mods` determines if the given value should be used before
|
/// `with_mods` determines if the given value should be used before
|
||||||
/// or after accounting for mods, e.g. on `true` the value will be
|
/// 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.
|
/// 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 {
|
pub const fn ar(mut self, ar: f32, with_mods: bool) -> Self {
|
||||||
Self {
|
self.ar = ModsDependentKind::Custom(ModsDependent {
|
||||||
ar: ModsDependent {
|
|
||||||
value: ar,
|
value: ar,
|
||||||
with_mods,
|
with_mods,
|
||||||
},
|
});
|
||||||
..self
|
|
||||||
}
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the overall difficulty.
|
/// Specify the overall difficulty.
|
||||||
@@ -103,14 +102,13 @@ impl BeatmapAttributesBuilder {
|
|||||||
/// `with_mods` determines if the given value should be used before
|
/// `with_mods` determines if the given value should be used before
|
||||||
/// or after accounting for mods, e.g. on `true` the value will be
|
/// 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.
|
/// 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 {
|
pub const fn od(mut self, od: f32, with_mods: bool) -> Self {
|
||||||
Self {
|
self.od = ModsDependentKind::Custom(ModsDependent {
|
||||||
od: ModsDependent {
|
|
||||||
value: od,
|
value: od,
|
||||||
with_mods,
|
with_mods,
|
||||||
},
|
});
|
||||||
..self
|
|
||||||
}
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the circle size.
|
/// Specify the circle size.
|
||||||
@@ -118,14 +116,13 @@ impl BeatmapAttributesBuilder {
|
|||||||
/// `with_mods` determines if the given value should be used before
|
/// `with_mods` determines if the given value should be used before
|
||||||
/// or after accounting for mods, e.g. on `true` the value will be
|
/// 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.
|
/// 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 {
|
pub const fn cs(mut self, cs: f32, with_mods: bool) -> Self {
|
||||||
Self {
|
self.cs = ModsDependentKind::Custom(ModsDependent {
|
||||||
cs: ModsDependent {
|
|
||||||
value: cs,
|
value: cs,
|
||||||
with_mods,
|
with_mods,
|
||||||
},
|
});
|
||||||
..self
|
|
||||||
}
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the drain rate.
|
/// Specify the drain rate.
|
||||||
@@ -133,36 +130,44 @@ impl BeatmapAttributesBuilder {
|
|||||||
/// `with_mods` determines if the given value should be used before
|
/// `with_mods` determines if the given value should be used before
|
||||||
/// or after accounting for mods, e.g. on `true` the value will be
|
/// 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.
|
/// 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 {
|
pub const fn hp(mut self, hp: f32, with_mods: bool) -> Self {
|
||||||
Self {
|
self.hp = ModsDependentKind::Custom(ModsDependent {
|
||||||
hp: ModsDependent {
|
|
||||||
value: hp,
|
value: hp,
|
||||||
with_mods,
|
with_mods,
|
||||||
},
|
});
|
||||||
..self
|
|
||||||
}
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the mods.
|
/// Specify mods.
|
||||||
pub const fn mods(self, mods: u32) -> Self {
|
///
|
||||||
Self { mods, ..self }
|
/// Accepted types are
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
|
///
|
||||||
|
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
||||||
|
pub fn mods(mut self, mods: impl Into<GameMods>) -> Self {
|
||||||
|
self.mods = mods.into();
|
||||||
|
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify a custom clock rate.
|
/// Specify a custom clock rate.
|
||||||
pub const fn clock_rate(self, clock_rate: f64) -> Self {
|
pub const fn clock_rate(mut self, clock_rate: f64) -> Self {
|
||||||
Self {
|
self.clock_rate = Some(clock_rate);
|
||||||
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 {
|
pub const fn mode(mut self, mode: GameMode, is_convert: bool) -> Self {
|
||||||
Self {
|
self.mode = mode;
|
||||||
mode,
|
self.is_convert = is_convert;
|
||||||
is_convert,
|
|
||||||
..self
|
self
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify all settings through [`Difficulty`].
|
/// Specify all settings through [`Difficulty`].
|
||||||
@@ -170,22 +175,33 @@ impl BeatmapAttributesBuilder {
|
|||||||
Self {
|
Self {
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
is_convert: self.is_convert,
|
is_convert: self.is_convert,
|
||||||
ar: difficulty.get_ar().unwrap_or(self.ar),
|
ar: difficulty
|
||||||
od: difficulty.get_od().unwrap_or(self.od),
|
.get_ar()
|
||||||
cs: difficulty.get_cs().unwrap_or(self.cs),
|
.map_or(self.ar, ModsDependentKind::Custom),
|
||||||
hp: difficulty.get_hp().unwrap_or(self.hp),
|
od: difficulty
|
||||||
mods: difficulty.get_mods(),
|
.get_od()
|
||||||
|
.map_or(self.od, ModsDependentKind::Custom),
|
||||||
|
cs: difficulty
|
||||||
|
.get_cs()
|
||||||
|
.map_or(self.cs, ModsDependentKind::Custom),
|
||||||
|
hp: difficulty
|
||||||
|
.get_hp()
|
||||||
|
.map_or(self.hp, ModsDependentKind::Custom),
|
||||||
|
mods: difficulty.get_mods().clone(),
|
||||||
clock_rate: Some(difficulty.get_clock_rate()),
|
clock_rate: Some(difficulty.get_clock_rate()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the AR and OD hit windows.
|
/// Calculate the AR and OD hit windows.
|
||||||
pub fn hit_windows(&self) -> HitWindows {
|
pub fn hit_windows(&self) -> HitWindows {
|
||||||
let mods = self.mods;
|
let mods = &self.mods;
|
||||||
|
|
||||||
let clock_rate = self.clock_rate.unwrap_or(mods.clock_rate());
|
let clock_rate = self
|
||||||
let ar_clock_rate = if self.ar.with_mods { 1.0 } else { clock_rate };
|
.clock_rate
|
||||||
let od_clock_rate = if self.od.with_mods { 1.0 } else { clock_rate };
|
.unwrap_or_else(|| f64::from(mods.clock_rate()));
|
||||||
|
|
||||||
|
let ar_clock_rate = if self.ar.with_mods() { 1.0 } else { clock_rate };
|
||||||
|
let od_clock_rate = if self.od.with_mods() { 1.0 } else { clock_rate };
|
||||||
|
|
||||||
let mod_mult = |val: f32| {
|
let mod_mult = |val: f32| {
|
||||||
if mods.hr() {
|
if mods.hr() {
|
||||||
@@ -197,10 +213,10 @@ impl BeatmapAttributesBuilder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_ar = if self.ar.with_mods {
|
let raw_ar = if self.ar.with_mods() {
|
||||||
self.ar.value
|
self.ar.value(mods, GameMods::ar)
|
||||||
} else {
|
} else {
|
||||||
mod_mult(self.ar.value)
|
mod_mult(self.ar.value(mods, GameMods::ar))
|
||||||
};
|
};
|
||||||
|
|
||||||
let preempt = difficulty_range(f64::from(raw_ar), 1800.0, 1200.0, 450.0) / ar_clock_rate;
|
let preempt = difficulty_range(f64::from(raw_ar), 1800.0, 1200.0, 450.0) / ar_clock_rate;
|
||||||
@@ -208,10 +224,10 @@ impl BeatmapAttributesBuilder {
|
|||||||
// 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 = if self.od.with_mods {
|
let raw_od = if self.od.with_mods() {
|
||||||
self.od.value
|
self.od.value(mods, GameMods::od)
|
||||||
} else {
|
} else {
|
||||||
mod_mult(self.od.value)
|
mod_mult(self.od.value(mods, GameMods::od))
|
||||||
};
|
};
|
||||||
|
|
||||||
difficulty_range(
|
difficulty_range(
|
||||||
@@ -222,10 +238,10 @@ impl BeatmapAttributesBuilder {
|
|||||||
) / od_clock_rate
|
) / od_clock_rate
|
||||||
}
|
}
|
||||||
GameMode::Taiko => {
|
GameMode::Taiko => {
|
||||||
let raw_od = if self.od.with_mods {
|
let raw_od = if self.od.with_mods() {
|
||||||
self.od.value
|
self.od.value(mods, GameMods::od)
|
||||||
} else {
|
} else {
|
||||||
mod_mult(self.od.value)
|
mod_mult(self.od.value(mods, GameMods::od))
|
||||||
};
|
};
|
||||||
|
|
||||||
let diff_range = difficulty_range(
|
let diff_range = difficulty_range(
|
||||||
@@ -239,14 +255,14 @@ 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.value).clamp(0.0, 10.0)
|
34.0 + 3.0 * (10.0 - self.od.value(mods, GameMods::od)).clamp(0.0, 10.0)
|
||||||
} else if self.od.value.round_ties_even() > 4.0 {
|
} else if self.od.value(mods, GameMods::od).round_ties_even() > 4.0 {
|
||||||
34.0
|
34.0
|
||||||
} else {
|
} else {
|
||||||
47.0
|
47.0
|
||||||
};
|
};
|
||||||
|
|
||||||
if !self.od.with_mods {
|
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() {
|
||||||
@@ -266,22 +282,24 @@ impl BeatmapAttributesBuilder {
|
|||||||
|
|
||||||
/// Calculate the [`BeatmapAttributes`].
|
/// Calculate the [`BeatmapAttributes`].
|
||||||
pub fn build(&self) -> BeatmapAttributes {
|
pub fn build(&self) -> BeatmapAttributes {
|
||||||
let mods = self.mods;
|
let mods = &self.mods;
|
||||||
let clock_rate = self.clock_rate.unwrap_or_else(|| mods.clock_rate());
|
let clock_rate = self
|
||||||
|
.clock_rate
|
||||||
|
.unwrap_or_else(|| f64::from(mods.clock_rate()));
|
||||||
|
|
||||||
// HP
|
// HP
|
||||||
let mut hp = self.hp.value;
|
let mut hp = self.hp.value(mods, GameMods::hp);
|
||||||
|
|
||||||
if !self.hp.with_mods {
|
if !self.hp.with_mods() {
|
||||||
hp *= mods.od_ar_hp_multiplier() as f32;
|
hp *= mods.od_ar_hp_multiplier() as f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
hp = hp.min(10.0);
|
hp = hp.min(10.0);
|
||||||
|
|
||||||
// CS
|
// CS
|
||||||
let mut cs = self.cs.value;
|
let mut cs = self.cs.value(mods, GameMods::cs);
|
||||||
|
|
||||||
if !self.cs.with_mods {
|
if !self.cs.with_mods() {
|
||||||
if mods.hr() {
|
if mods.hr() {
|
||||||
cs = (cs * 1.3).min(10.0);
|
cs = (cs * 1.3).min(10.0);
|
||||||
} else if mods.ez() {
|
} else if mods.ez() {
|
||||||
@@ -303,7 +321,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.value),
|
GameMode::Catch | GameMode::Mania => f64::from(self.od.value(mods, GameMods::od)),
|
||||||
};
|
};
|
||||||
|
|
||||||
BeatmapAttributes {
|
BeatmapAttributes {
|
||||||
@@ -345,33 +363,109 @@ impl Default for BeatmapAttributesBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum ModsDependentKind {
|
||||||
|
Default(ModsDependent),
|
||||||
|
Custom(ModsDependent),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModsDependentKind {
|
||||||
|
const DEFAULT: Self = Self::Default(ModsDependent::new(5.0));
|
||||||
|
|
||||||
|
const fn with_mods(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
ModsDependentKind::Default(inner) | ModsDependentKind::Custom(inner) => inner.with_mods,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self, mods: &GameMods, mods_fn: impl Fn(&GameMods) -> Option<f32>) -> f32 {
|
||||||
|
match self {
|
||||||
|
ModsDependentKind::Default(inner) => mods_fn(mods).unwrap_or(inner.value),
|
||||||
|
ModsDependentKind::Custom(inner) => inner.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::util::float_ext::FloatExt;
|
use rosu_mods::{generated_mods::DifficultyAdjustOsu, GameMod, GameMods};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn consider_mods() {
|
fn default_ar() {
|
||||||
let attrs = BeatmapAttributesBuilder::new()
|
let gamemod = GameMod::HiddenOsu(Default::default());
|
||||||
.ar(8.5, false)
|
let diff = Difficulty::new().mods(GameMods::from(gamemod));
|
||||||
.mods(64)
|
let attrs = BeatmapAttributesBuilder::new().difficulty(&diff).build();
|
||||||
.build();
|
|
||||||
|
|
||||||
let expected = 10.0;
|
assert_eq!(attrs.ar, 5.0);
|
||||||
|
|
||||||
assert!(attrs.ar.eq(expected), "{} != {expected}", attrs.ar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn skip_mods() {
|
fn custom_ar_without_mods() {
|
||||||
|
let gamemod = GameMod::DoubleTimeOsu(Default::default());
|
||||||
|
let diff = Difficulty::new().mods(GameMods::from(gamemod));
|
||||||
let attrs = BeatmapAttributesBuilder::new()
|
let attrs = BeatmapAttributesBuilder::new()
|
||||||
.ar(8.5, true)
|
.ar(8.5, false)
|
||||||
.mods(64)
|
.difficulty(&diff)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let expected = 8.5;
|
assert_eq!(attrs.ar, 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
assert!(attrs.ar.eq(expected), "{} != {expected}", attrs.ar);
|
#[test]
|
||||||
|
fn custom_ar_with_mods() {
|
||||||
|
let gamemod = GameMod::DoubleTimeOsu(Default::default());
|
||||||
|
let diff = Difficulty::new().mods(GameMods::from(gamemod));
|
||||||
|
let attrs = BeatmapAttributesBuilder::new()
|
||||||
|
.ar(8.5, true)
|
||||||
|
.difficulty(&diff)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assert_eq!(attrs.ar, 8.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_mods_ar() {
|
||||||
|
let mut mods = GameMods::new();
|
||||||
|
mods.insert(GameMod::DoubleTimeCatch(Default::default()));
|
||||||
|
mods.insert(GameMod::DifficultyAdjustOsu(DifficultyAdjustOsu {
|
||||||
|
approach_rate: Some(7.0),
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
let diff = Difficulty::new().mods(mods);
|
||||||
|
let attrs = BeatmapAttributesBuilder::new().difficulty(&diff).build();
|
||||||
|
|
||||||
|
assert_eq!(attrs.ar, 9.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_ar_custom_mods_ar_without_mods() {
|
||||||
|
let mut mods = GameMods::new();
|
||||||
|
mods.insert(GameMod::DoubleTimeCatch(Default::default()));
|
||||||
|
mods.insert(GameMod::DifficultyAdjustOsu(DifficultyAdjustOsu {
|
||||||
|
approach_rate: Some(9.0),
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let diff = Difficulty::new().mods(mods).ar(8.5, false);
|
||||||
|
let attrs = BeatmapAttributesBuilder::new().difficulty(&diff).build();
|
||||||
|
|
||||||
|
assert_eq!(attrs.ar, 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_ar_custom_mods_ar_with_mods() {
|
||||||
|
let mut mods = GameMods::new();
|
||||||
|
mods.insert(GameMod::DoubleTimeCatch(Default::default()));
|
||||||
|
mods.insert(GameMod::DifficultyAdjustOsu(DifficultyAdjustOsu {
|
||||||
|
approach_rate: Some(9.0),
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let diff = Difficulty::new().mods(mods).ar(8.5, true);
|
||||||
|
let attrs = BeatmapAttributesBuilder::new().difficulty(&diff).build();
|
||||||
|
|
||||||
|
assert_eq!(attrs.ar, 8.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ impl Beatmap {
|
|||||||
|
|
||||||
/// Returns a [`BeatmapAttributesBuilder`] to calculate modified beatmap
|
/// Returns a [`BeatmapAttributesBuilder`] to calculate modified beatmap
|
||||||
/// attributes.
|
/// attributes.
|
||||||
pub const fn attributes(&self) -> BeatmapAttributesBuilder {
|
pub fn attributes(&self) -> BeatmapAttributesBuilder {
|
||||||
BeatmapAttributesBuilder::new().map(self)
|
BeatmapAttributesBuilder::new().map(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,5 @@ pub mod hit_object;
|
|||||||
|
|
||||||
/// Gamemode related types.
|
/// Gamemode related types.
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
|
|
||||||
|
pub(crate) mod mods;
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
use std::fmt::{Debug, Formatter, Result as FmtResult};
|
||||||
|
|
||||||
|
use rosu_mods::{
|
||||||
|
generated_mods::{
|
||||||
|
DifficultyAdjustCatch, DifficultyAdjustMania, DifficultyAdjustOsu, DifficultyAdjustTaiko,
|
||||||
|
},
|
||||||
|
GameMod, GameModIntermode, GameMods as GameModsLazer, GameModsIntermode, GameModsLegacy,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Collection of game mods.
|
||||||
|
///
|
||||||
|
/// This type can be created through its `From<T>` implementations where `T`
|
||||||
|
/// can be
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rosu_pp::GameMods;
|
||||||
|
/// use rosu_mods::{GameModsIntermode, GameModsLegacy, GameMods as GameModsLazer};
|
||||||
|
///
|
||||||
|
/// let int = GameMods::from(64 + 8);
|
||||||
|
/// let legacy = GameMods::from(GameModsLegacy::Hidden | GameModsLegacy::Easy);
|
||||||
|
/// let lazer = GameMods::from(GameModsLazer::new());
|
||||||
|
/// let intermode = GameMods::from(GameModsIntermode::new());
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct GameMods {
|
||||||
|
inner: GameModsInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for GameMods {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
match self.inner {
|
||||||
|
GameModsInner::Lazer(ref mods) => Debug::fmt(mods, f),
|
||||||
|
GameModsInner::Intermode(ref mods) => Debug::fmt(mods, f),
|
||||||
|
GameModsInner::Legacy(ref mods) => Debug::fmt(mods, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inner type of [`GameMods`] so that remote types contained in variants don't
|
||||||
|
/// need to be re-exported.
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
enum GameModsInner {
|
||||||
|
Lazer(GameModsLazer),
|
||||||
|
Intermode(GameModsIntermode),
|
||||||
|
Legacy(GameModsLegacy),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameMods {
|
||||||
|
pub(crate) const DEFAULT: Self = Self {
|
||||||
|
inner: GameModsInner::Legacy(GameModsLegacy::NoMod),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Returns the mods' clock rate.
|
||||||
|
///
|
||||||
|
/// In case of variable clock rates like for `WindUp`, this will return
|
||||||
|
/// `1.0`.
|
||||||
|
pub(crate) fn clock_rate(&self) -> f32 {
|
||||||
|
match self.inner {
|
||||||
|
GameModsInner::Lazer(ref mods) => mods.clock_rate().unwrap_or(1.0),
|
||||||
|
GameModsInner::Intermode(ref mods) => mods.legacy_clock_rate(),
|
||||||
|
GameModsInner::Legacy(mods) => mods.clock_rate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn od_ar_hp_multiplier(&self) -> f64 {
|
||||||
|
if self.hr() {
|
||||||
|
1.4
|
||||||
|
} else if self.ez() {
|
||||||
|
0.5
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether the mods enable `hardrock_offsets`.
|
||||||
|
pub(crate) fn hardrock_offsets(&self) -> bool {
|
||||||
|
fn custom_hardrock_offsets(mods: &GameMods) -> Option<bool> {
|
||||||
|
match mods.inner {
|
||||||
|
GameModsInner::Lazer(ref mods) => mods.iter().find_map(|gamemod| match gamemod {
|
||||||
|
GameMod::DifficultyAdjustCatch(DifficultyAdjustCatch {
|
||||||
|
hard_rock_offsets,
|
||||||
|
..
|
||||||
|
}) => *hard_rock_offsets,
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
GameModsInner::Intermode(_) | GameModsInner::Legacy(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
custom_hardrock_offsets(self).unwrap_or_else(|| self.hr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_map_attr {
|
||||||
|
( $( $fn:ident: $field:ident [ $( $mode:ident ),* ] [$s:literal] ;)* ) => {
|
||||||
|
impl GameMods {
|
||||||
|
$(
|
||||||
|
#[doc = "Check whether the mods specify a custom "]
|
||||||
|
#[doc = $s]
|
||||||
|
#[doc = "value."]
|
||||||
|
pub(crate) fn $fn(&self) -> Option<f32> {
|
||||||
|
match self.inner {
|
||||||
|
GameModsInner::Lazer(ref mods) => mods.iter().find_map(|gamemod| match gamemod {
|
||||||
|
$( impl_map_attr!( @ $mode $field) => *$field, )*
|
||||||
|
_ => None,
|
||||||
|
}),
|
||||||
|
GameModsInner::Intermode(_) | GameModsInner::Legacy(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
( @ Osu $field:ident) => { GameMod::DifficultyAdjustOsu(DifficultyAdjustOsu { $field, .. }) };
|
||||||
|
( @ Taiko $field:ident) => { GameMod::DifficultyAdjustTaiko(DifficultyAdjustTaiko { $field, .. }) };
|
||||||
|
( @ Catch $field:ident) => { GameMod::DifficultyAdjustCatch(DifficultyAdjustCatch { $field, .. }) };
|
||||||
|
( @ Mania $field:ident) => { GameMod::DifficultyAdjustMania(DifficultyAdjustMania { $field, .. }) };
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_map_attr! {
|
||||||
|
ar: approach_rate [Osu, Catch] ["ar"];
|
||||||
|
cs: circle_size [Osu, Catch] ["cs"];
|
||||||
|
hp: drain_rate [Osu, Taiko, Catch, Mania] ["hp"];
|
||||||
|
od: overall_difficulty [Osu, Taiko, Catch, Mania] ["od"];
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_has_mod {
|
||||||
|
( $( $fn:ident: $sign:tt $name:ident [ $s:literal ], )* ) => {
|
||||||
|
impl GameMods {
|
||||||
|
$(
|
||||||
|
// workaround for <https://github.com/rust-lang/rust-analyzer/issues/8092>
|
||||||
|
#[doc = "Check whether [`GameMods`] contain `"]
|
||||||
|
#[doc = $s]
|
||||||
|
#[doc = "`."]
|
||||||
|
pub(crate) fn $fn(&self) -> bool {
|
||||||
|
match self.inner {
|
||||||
|
GameModsInner::Lazer(ref mods) => {
|
||||||
|
mods.contains_intermode(GameModIntermode::$name)
|
||||||
|
},
|
||||||
|
GameModsInner::Intermode(ref mods) => {
|
||||||
|
mods.contains(GameModIntermode::$name)
|
||||||
|
},
|
||||||
|
GameModsInner::Legacy(_mods) => {
|
||||||
|
impl_has_mod!(LEGACY $sign $name _mods)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
( LEGACY + $name:ident $mods:ident ) => {
|
||||||
|
$mods.contains(GameModsLegacy::$name)
|
||||||
|
};
|
||||||
|
|
||||||
|
( LEGACY - $name:ident $mods:ident ) => {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_has_mod! {
|
||||||
|
nf: + NoFail ["NoFail"],
|
||||||
|
ez: + Easy ["Easy"],
|
||||||
|
td: + TouchDevice ["TouchDevice"],
|
||||||
|
hd: + Hidden ["Hidden"],
|
||||||
|
hr: + HardRock ["HardRock"],
|
||||||
|
rx: + Relax ["Relax"],
|
||||||
|
fl: + Flashlight ["Flashlight"],
|
||||||
|
so: + SpunOut ["SpunOut"],
|
||||||
|
bl: - Blinds ["Blinds"],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GameMods {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GameModsLazer> for GameMods {
|
||||||
|
fn from(mods: GameModsLazer) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: GameModsInner::Lazer(mods),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GameModsIntermode> for GameMods {
|
||||||
|
fn from(mods: GameModsIntermode) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: GameModsInner::Intermode(mods),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&GameModsIntermode> for GameMods {
|
||||||
|
fn from(mods: &GameModsIntermode) -> Self {
|
||||||
|
// If only legacy mods are set, use `GameModsLegacy` and thus avoid
|
||||||
|
// allocating an owned `GameModsIntermode` instance.
|
||||||
|
match mods.checked_bits() {
|
||||||
|
Some(bits) => bits.into(),
|
||||||
|
None => mods.to_owned().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GameModsLegacy> for GameMods {
|
||||||
|
fn from(mods: GameModsLegacy) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: GameModsInner::Legacy(mods),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for GameMods {
|
||||||
|
fn from(bits: u32) -> Self {
|
||||||
|
GameModsLegacy::from_bits(bits).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ use crate::{
|
|||||||
object::{OsuObject, OsuObjectKind},
|
object::{OsuObject, OsuObjectKind},
|
||||||
OsuBeatmap,
|
OsuBeatmap,
|
||||||
},
|
},
|
||||||
util::mods::Mods,
|
|
||||||
Difficulty,
|
Difficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ use std::{cmp, pin::Pin};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any::difficulty::{skills::Skill, Difficulty},
|
any::difficulty::{skills::Skill, Difficulty},
|
||||||
model::beatmap::BeatmapAttributes,
|
model::{beatmap::BeatmapAttributes, mods::GameMods},
|
||||||
osu::{
|
osu::{
|
||||||
convert::convert_objects,
|
convert::convert_objects,
|
||||||
difficulty::{object::OsuDifficultyObject, scaling_factor::ScalingFactor},
|
difficulty::{object::OsuDifficultyObject, scaling_factor::ScalingFactor},
|
||||||
object::OsuObject,
|
object::OsuObject,
|
||||||
performance::PERFORMANCE_BASE_MULTIPLIER,
|
performance::PERFORMANCE_BASE_MULTIPLIER,
|
||||||
},
|
},
|
||||||
util::mods::Mods,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::skills::OsuSkills;
|
use self::skills::OsuSkills;
|
||||||
@@ -146,7 +145,7 @@ impl DifficultyValues {
|
|||||||
/// Process the difficulty values and store the results in `attrs`.
|
/// Process the difficulty values and store the results in `attrs`.
|
||||||
pub fn eval(
|
pub fn eval(
|
||||||
attrs: &mut OsuDifficultyAttributes,
|
attrs: &mut OsuDifficultyAttributes,
|
||||||
mods: u32,
|
mods: &GameMods,
|
||||||
aim_difficulty_value: f64,
|
aim_difficulty_value: f64,
|
||||||
aim_no_sliders_difficulty_value: f64,
|
aim_no_sliders_difficulty_value: f64,
|
||||||
speed_difficulty_value: f64,
|
speed_difficulty_value: f64,
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ use crate::{
|
|||||||
object::IDifficultyObject,
|
object::IDifficultyObject,
|
||||||
skills::{strain_decay, ISkill, Skill, StrainSkill},
|
skills::{strain_decay, ISkill, Skill, StrainSkill},
|
||||||
},
|
},
|
||||||
|
model::mods::GameMods,
|
||||||
osu::{difficulty::object::OsuDifficultyObject, object::OsuObjectKind},
|
osu::{difficulty::object::OsuDifficultyObject, object::OsuObjectKind},
|
||||||
util::{mods::Mods, strains_vec::StrainsVec},
|
util::strains_vec::StrainsVec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::strain::OsuStrainSkill;
|
use super::strain::OsuStrainSkill;
|
||||||
@@ -22,7 +23,7 @@ pub struct Flashlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Flashlight {
|
impl Flashlight {
|
||||||
pub fn new(mods: u32, radius: f64, time_preempt: f64, time_fade_in: f64) -> Self {
|
pub fn new(mods: &GameMods, radius: f64, time_preempt: f64, time_fade_in: f64) -> Self {
|
||||||
let scaling_factor = 52.0 / radius;
|
let scaling_factor = 52.0 / radius;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::{model::beatmap::BeatmapAttributes, osu::object::OsuObject, util::mods::Mods};
|
use crate::{
|
||||||
|
model::{beatmap::BeatmapAttributes, mods::GameMods},
|
||||||
|
osu::object::OsuObject,
|
||||||
|
};
|
||||||
|
|
||||||
use self::{aim::Aim, flashlight::Flashlight, speed::Speed};
|
use self::{aim::Aim, flashlight::Flashlight, speed::Speed};
|
||||||
|
|
||||||
@@ -18,7 +21,7 @@ pub struct OsuSkills {
|
|||||||
|
|
||||||
impl OsuSkills {
|
impl OsuSkills {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
mods: u32,
|
mods: &GameMods,
|
||||||
scaling_factor: &ScalingFactor,
|
scaling_factor: &ScalingFactor,
|
||||||
map_attrs: &BeatmapAttributes,
|
map_attrs: &BeatmapAttributes,
|
||||||
time_preempt: f64,
|
time_preempt: f64,
|
||||||
|
|||||||
+33
-12
@@ -6,8 +6,9 @@ use crate::{
|
|||||||
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance, Performance},
|
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance, Performance},
|
||||||
catch::CatchPerformance,
|
catch::CatchPerformance,
|
||||||
mania::ManiaPerformance,
|
mania::ManiaPerformance,
|
||||||
|
model::mods::GameMods,
|
||||||
taiko::TaikoPerformance,
|
taiko::TaikoPerformance,
|
||||||
util::{float_ext::FloatExt, map_or_attrs::MapOrAttrs, mods::Mods},
|
util::{float_ext::FloatExt, map_or_attrs::MapOrAttrs},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -118,10 +119,17 @@ impl<'map> OsuPerformance<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods.
|
||||||
|
///
|
||||||
|
/// Accepted types are
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
///
|
///
|
||||||
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
||||||
pub const fn mods(mut self, mods: u32) -> Self {
|
pub fn mods(mut self, mods: impl Into<GameMods>) -> Self {
|
||||||
self.difficulty = self.difficulty.mods(mods);
|
self.difficulty = self.difficulty.mods(mods);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -172,7 +180,7 @@ impl<'map> OsuPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Use the specified settings of the given [`Difficulty`].
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
pub fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
self.difficulty = difficulty;
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -185,7 +193,7 @@ impl<'map> OsuPerformance<'map> {
|
|||||||
/// `passed_objects`, you should use [`OsuGradualPerformance`].
|
/// `passed_objects`, you should use [`OsuGradualPerformance`].
|
||||||
///
|
///
|
||||||
/// [`OsuGradualPerformance`]: crate::osu::OsuGradualPerformance
|
/// [`OsuGradualPerformance`]: crate::osu::OsuGradualPerformance
|
||||||
pub const fn passed_objects(mut self, passed_objects: u32) -> Self {
|
pub fn passed_objects(mut self, passed_objects: u32) -> Self {
|
||||||
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -522,15 +530,15 @@ impl<'map, T: IntoModePerformance<'map, Osu>> From<T> for OsuPerformance<'map> {
|
|||||||
|
|
||||||
pub const PERFORMANCE_BASE_MULTIPLIER: f64 = 1.14;
|
pub const PERFORMANCE_BASE_MULTIPLIER: f64 = 1.14;
|
||||||
|
|
||||||
struct OsuPerformanceInner {
|
struct OsuPerformanceInner<'mods> {
|
||||||
attrs: OsuDifficultyAttributes,
|
attrs: OsuDifficultyAttributes,
|
||||||
mods: u32,
|
mods: &'mods GameMods,
|
||||||
acc: f64,
|
acc: f64,
|
||||||
state: OsuScoreState,
|
state: OsuScoreState,
|
||||||
effective_miss_count: f64,
|
effective_miss_count: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OsuPerformanceInner {
|
impl OsuPerformanceInner<'_> {
|
||||||
fn calculate(mut self) -> OsuPerformanceAttributes {
|
fn calculate(mut self) -> OsuPerformanceAttributes {
|
||||||
let total_hits = self.state.total_hits();
|
let total_hits = self.state.total_hits();
|
||||||
|
|
||||||
@@ -632,7 +640,13 @@ impl OsuPerformanceInner {
|
|||||||
// * Buff for longer maps with high AR.
|
// * Buff for longer maps with high AR.
|
||||||
aim_value *= 1.0 + ar_factor * len_bonus;
|
aim_value *= 1.0 + ar_factor * len_bonus;
|
||||||
|
|
||||||
if self.mods.hd() {
|
if self.mods.bl() {
|
||||||
|
aim_value *= 1.3
|
||||||
|
+ (total_hits
|
||||||
|
* (0.0016 / (1.0 + 2.0 * self.effective_miss_count))
|
||||||
|
* self.acc.powf(16.0))
|
||||||
|
* (1.0 - 0.003 * self.attrs.hp * self.attrs.hp);
|
||||||
|
} else if self.mods.hd() {
|
||||||
// * We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
// * We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
|
||||||
aim_value *= 1.0 + 0.04 * (12.0 - self.attrs.ar);
|
aim_value *= 1.0 + 0.04 * (12.0 - self.attrs.ar);
|
||||||
}
|
}
|
||||||
@@ -695,7 +709,11 @@ impl OsuPerformanceInner {
|
|||||||
// * Buff for longer maps with high AR.
|
// * Buff for longer maps with high AR.
|
||||||
speed_value *= 1.0 + ar_factor * len_bonus;
|
speed_value *= 1.0 + ar_factor * len_bonus;
|
||||||
|
|
||||||
if self.mods.hd() {
|
if self.mods.bl() {
|
||||||
|
// * Increasing the speed value by object count for Blinds isn't
|
||||||
|
// * ideal, so the minimum buff is given.
|
||||||
|
speed_value *= 1.12;
|
||||||
|
} else if self.mods.hd() {
|
||||||
// * We want to give more reward for lower AR when it comes to aim and HD.
|
// * We want to give more reward for lower AR when it comes to aim and HD.
|
||||||
// * This nerfs high AR and buffs lower AR.
|
// * This nerfs high AR and buffs lower AR.
|
||||||
speed_value *= 1.0 + 0.04 * (12.0 - self.attrs.ar);
|
speed_value *= 1.0 + 0.04 * (12.0 - self.attrs.ar);
|
||||||
@@ -764,8 +782,11 @@ impl OsuPerformanceInner {
|
|||||||
.powf(0.3)
|
.powf(0.3)
|
||||||
.min(1.15);
|
.min(1.15);
|
||||||
|
|
||||||
// * Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
|
// * Increasing the accuracy value by object count for Blinds isn't
|
||||||
if self.mods.hd() {
|
// * ideal, so the minimum buff is given.
|
||||||
|
if self.mods.bl() {
|
||||||
|
acc_value *= 1.14;
|
||||||
|
} else if self.mods.hd() {
|
||||||
acc_value *= 1.08;
|
acc_value *= 1.08;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use std::cmp;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance},
|
any::{Difficulty, HitResultPriority, IntoModePerformance, IntoPerformance},
|
||||||
|
model::mods::GameMods,
|
||||||
osu::OsuPerformance,
|
osu::OsuPerformance,
|
||||||
util::{map_or_attrs::MapOrAttrs, mods::Mods},
|
util::map_or_attrs::MapOrAttrs,
|
||||||
Performance,
|
Performance,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,10 +70,17 @@ impl<'map> TaikoPerformance<'map> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify mods through their bit values.
|
/// Specify mods.
|
||||||
|
///
|
||||||
|
/// Accepted types are
|
||||||
|
/// - `u32`
|
||||||
|
/// - [`rosu_mods::GameModsLegacy`]
|
||||||
|
/// - [`rosu_mods::GameMods`]
|
||||||
|
/// - [`rosu_mods::GameModsIntermode`]
|
||||||
|
/// - [`&rosu_mods::GameModsIntermode`](rosu_mods::GameModsIntermode)
|
||||||
///
|
///
|
||||||
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
/// See <https://github.com/ppy/osu-api/wiki#mods>
|
||||||
pub const fn mods(mut self, mods: u32) -> Self {
|
pub fn mods(mut self, mods: impl Into<GameMods>) -> Self {
|
||||||
self.difficulty = self.difficulty.mods(mods);
|
self.difficulty = self.difficulty.mods(mods);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -124,7 +132,7 @@ impl<'map> TaikoPerformance<'map> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Use the specified settings of the given [`Difficulty`].
|
/// Use the specified settings of the given [`Difficulty`].
|
||||||
pub const fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
pub fn difficulty(mut self, difficulty: Difficulty) -> Self {
|
||||||
self.difficulty = difficulty;
|
self.difficulty = difficulty;
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -137,7 +145,7 @@ impl<'map> TaikoPerformance<'map> {
|
|||||||
/// `passed_objects`, you should use [`TaikoGradualPerformance`].
|
/// `passed_objects`, you should use [`TaikoGradualPerformance`].
|
||||||
///
|
///
|
||||||
/// [`TaikoGradualPerformance`]: crate::taiko::TaikoGradualPerformance
|
/// [`TaikoGradualPerformance`]: crate::taiko::TaikoGradualPerformance
|
||||||
pub const fn passed_objects(mut self, passed_objects: u32) -> Self {
|
pub fn passed_objects(mut self, passed_objects: u32) -> Self {
|
||||||
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
self.difficulty = self.difficulty.passed_objects(passed_objects);
|
||||||
|
|
||||||
self
|
self
|
||||||
@@ -377,13 +385,13 @@ impl<'map, T: IntoModePerformance<'map, Taiko>> From<T> for TaikoPerformance<'ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TaikoPerformanceInner {
|
struct TaikoPerformanceInner<'mods> {
|
||||||
attrs: TaikoDifficultyAttributes,
|
attrs: TaikoDifficultyAttributes,
|
||||||
mods: u32,
|
mods: &'mods GameMods,
|
||||||
state: TaikoScoreState,
|
state: TaikoScoreState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaikoPerformanceInner {
|
impl TaikoPerformanceInner<'_> {
|
||||||
fn calculate(self) -> TaikoPerformanceAttributes {
|
fn calculate(self) -> TaikoPerformanceAttributes {
|
||||||
// * The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits
|
// * The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits
|
||||||
// * and increasing the miss penalty for shorter object counts lower than 1000.
|
// * and increasing the miss penalty for shorter object counts lower than 1000.
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ pub mod float_ext;
|
|||||||
pub mod generic_fmt;
|
pub mod generic_fmt;
|
||||||
pub mod limited_queue;
|
pub mod limited_queue;
|
||||||
pub mod map_or_attrs;
|
pub mod map_or_attrs;
|
||||||
pub mod mods;
|
|
||||||
pub mod random;
|
pub mod random;
|
||||||
pub mod sort;
|
pub mod sort;
|
||||||
pub mod strains_vec;
|
pub mod strains_vec;
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
pub trait Mods: Copy {
|
|
||||||
fn nf(self) -> bool;
|
|
||||||
fn ez(self) -> bool;
|
|
||||||
fn td(self) -> bool;
|
|
||||||
fn hd(self) -> bool;
|
|
||||||
fn hr(self) -> bool;
|
|
||||||
fn dt(self) -> bool;
|
|
||||||
fn rx(self) -> bool;
|
|
||||||
fn ht(self) -> bool;
|
|
||||||
fn fl(self) -> bool;
|
|
||||||
fn so(self) -> bool;
|
|
||||||
|
|
||||||
fn clock_rate(self) -> f64 {
|
|
||||||
if self.dt() {
|
|
||||||
1.5
|
|
||||||
} else if self.ht() {
|
|
||||||
0.75
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn od_ar_hp_multiplier(self) -> f64 {
|
|
||||||
if self.hr() {
|
|
||||||
1.4
|
|
||||||
} else if self.ez() {
|
|
||||||
0.5
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_mods_fn {
|
|
||||||
( $fn_name:ident, $bits:expr ) => {
|
|
||||||
fn $fn_name(self) -> bool {
|
|
||||||
self & ($bits) != 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mods for u32 {
|
|
||||||
impl_mods_fn!(nf, 1 << 0);
|
|
||||||
impl_mods_fn!(ez, 1 << 1);
|
|
||||||
impl_mods_fn!(td, 1 << 2);
|
|
||||||
impl_mods_fn!(hd, 1 << 3);
|
|
||||||
impl_mods_fn!(hr, 1 << 4);
|
|
||||||
impl_mods_fn!(dt, 1 << 6);
|
|
||||||
impl_mods_fn!(rx, 1 << 7);
|
|
||||||
impl_mods_fn!(ht, 1 << 8);
|
|
||||||
impl_mods_fn!(fl, 1 << 10);
|
|
||||||
impl_mods_fn!(so, 1 << 12);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user