Skip to main content

rosu_pp/any/difficulty/
gradual.rs

1use rosu_map::section::general::GameMode;
2
3use crate::{
4    Beatmap, Difficulty,
5    any::DifficultyAttributes,
6    catch::{Catch, CatchGradualDifficulty},
7    mania::{Mania, ManiaGradualDifficulty},
8    model::{
9        beatmap::TooSuspicious,
10        mode::{ConvertError, IGameMode},
11    },
12    osu::{Osu, OsuGradualDifficulty},
13    taiko::{Taiko, TaikoGradualDifficulty},
14};
15
16/// Gradually calculate the difficulty attributes on maps of any mode.
17///
18/// Note that this type implements [`Iterator`]. On every call of
19/// [`Iterator::next`], the next object will be processed and the
20/// [`DifficultyAttributes`] will be updated and returned.
21///
22/// If you want to calculate performance attributes, use [`GradualPerformance`] instead.
23///
24/// # Example
25///
26/// ```
27/// use rosu_pp::{Beatmap, GradualDifficulty, Difficulty};
28///
29/// let map = Beatmap::from_path("./resources/2785319.osu").unwrap();
30/// let difficulty = Difficulty::new().mods(64); // DT
31/// let mut iter = GradualDifficulty::new(difficulty, &map);
32///
33/// // the difficulty of the map after the first object
34/// let attrs1 = iter.next();
35/// // ... after the second object
36/// let attrs2 = iter.next();
37///
38/// // Remaining objects
39/// for difficulty in iter {
40///     // ...
41/// }
42/// ```
43///
44/// [`GradualPerformance`]: crate::GradualPerformance
45pub enum GradualDifficulty {
46    Osu(OsuGradualDifficulty),
47    Taiko(TaikoGradualDifficulty),
48    Catch(CatchGradualDifficulty),
49    Mania(ManiaGradualDifficulty),
50}
51
52impl GradualDifficulty {
53    /// Create a [`GradualDifficulty`] for a map of any mode.
54    #[expect(clippy::missing_panics_doc, reason = "unreachable")]
55    pub fn new(difficulty: Difficulty, map: &Beatmap) -> Self {
56        Self::new_with_mode(difficulty, map, map.mode).expect("no conversion required")
57    }
58
59    /// Same as [`GradualDifficulty::new`] but verifies that the map is not
60    /// suspicious.
61    pub fn checked_new(difficulty: Difficulty, map: &Beatmap) -> Result<Self, TooSuspicious> {
62        // This is fine because `Self::new` will use the map's mode so it won't
63        // be converted.
64        map.check_suspicion()?;
65
66        Ok(Self::new(difficulty, map))
67    }
68
69    /// Create a [`GradualDifficulty`] for a [`Beatmap`] on a specific
70    /// [`GameMode`].
71    pub fn new_with_mode(
72        difficulty: Difficulty,
73        map: &Beatmap,
74        mode: GameMode,
75    ) -> Result<Self, ConvertError> {
76        match mode {
77            GameMode::Osu => Osu::gradual_difficulty(difficulty, map).map(Self::Osu),
78            GameMode::Taiko => Taiko::gradual_difficulty(difficulty, map).map(Self::Taiko),
79            GameMode::Catch => Catch::gradual_difficulty(difficulty, map).map(Self::Catch),
80            GameMode::Mania => Mania::gradual_difficulty(difficulty, map).map(Self::Mania),
81        }
82    }
83}
84
85impl Iterator for GradualDifficulty {
86    type Item = DifficultyAttributes;
87
88    fn next(&mut self) -> Option<Self::Item> {
89        match self {
90            GradualDifficulty::Osu(gradual) => gradual.next().map(DifficultyAttributes::Osu),
91            GradualDifficulty::Taiko(gradual) => gradual.next().map(DifficultyAttributes::Taiko),
92            GradualDifficulty::Catch(gradual) => gradual.next().map(DifficultyAttributes::Catch),
93            GradualDifficulty::Mania(gradual) => gradual.next().map(DifficultyAttributes::Mania),
94        }
95    }
96
97    fn size_hint(&self) -> (usize, Option<usize>) {
98        match self {
99            GradualDifficulty::Osu(gradual) => gradual.size_hint(),
100            GradualDifficulty::Taiko(gradual) => gradual.size_hint(),
101            GradualDifficulty::Catch(gradual) => gradual.size_hint(),
102            GradualDifficulty::Mania(gradual) => gradual.size_hint(),
103        }
104    }
105
106    fn nth(&mut self, n: usize) -> Option<Self::Item> {
107        match self {
108            GradualDifficulty::Osu(gradual) => gradual.nth(n).map(DifficultyAttributes::Osu),
109            GradualDifficulty::Taiko(gradual) => gradual.nth(n).map(DifficultyAttributes::Taiko),
110            GradualDifficulty::Catch(gradual) => gradual.nth(n).map(DifficultyAttributes::Catch),
111            GradualDifficulty::Mania(gradual) => gradual.nth(n).map(DifficultyAttributes::Mania),
112        }
113    }
114}
115
116impl ExactSizeIterator for GradualDifficulty {
117    fn len(&self) -> usize {
118        match self {
119            GradualDifficulty::Osu(gradual) => gradual.len(),
120            GradualDifficulty::Taiko(gradual) => gradual.len(),
121            GradualDifficulty::Catch(gradual) => gradual.len(),
122            GradualDifficulty::Mania(gradual) => gradual.len(),
123        }
124    }
125}