rosu_map/section/
difficulty.rs

1use crate::{
2    decode::{DecodeBeatmap, DecodeState},
3    util::{KeyValue, ParseNumber, ParseNumberError, StrExt},
4    Beatmap,
5};
6
7/// Struct containing all data from a `.osu` file's `[Difficulty]` section.
8#[derive(Clone, Debug, PartialEq)]
9pub struct Difficulty {
10    pub hp_drain_rate: f32,
11    pub circle_size: f32,
12    pub overall_difficulty: f32,
13    pub approach_rate: f32,
14    pub slider_multiplier: f64,
15    pub slider_tick_rate: f64,
16}
17
18impl Default for Difficulty {
19    fn default() -> Self {
20        Self {
21            hp_drain_rate: 5.0,
22            circle_size: 5.0,
23            overall_difficulty: 5.0,
24            approach_rate: 5.0,
25            slider_multiplier: 1.4,
26            slider_tick_rate: 1.0,
27        }
28    }
29}
30
31impl From<Difficulty> for Beatmap {
32    fn from(difficulty: Difficulty) -> Self {
33        Self {
34            hp_drain_rate: difficulty.hp_drain_rate,
35            circle_size: difficulty.circle_size,
36            overall_difficulty: difficulty.overall_difficulty,
37            approach_rate: difficulty.approach_rate,
38            slider_multiplier: difficulty.slider_multiplier,
39            slider_tick_rate: difficulty.slider_tick_rate,
40            ..Self::default()
41        }
42    }
43}
44
45section_keys! {
46    /// All valid keys within a `.osu` file's `[Difficulty]` section
47    pub enum DifficultyKey {
48        HPDrainRate,
49        CircleSize,
50        OverallDifficulty,
51        ApproachRate,
52        SliderMultiplier,
53        SliderTickRate,
54    }
55}
56
57thiserror! {
58    /// All the ways that parsing a `.osu` file into [`Difficulty`] can fail.
59    #[derive(Debug)]
60    pub enum ParseDifficultyError {
61        #[error("failed to parse number")]
62        Number(#[from] ParseNumberError),
63    }
64}
65
66/// The parsing state for [`Difficulty`] in [`DecodeBeatmap`].
67pub struct DifficultyState {
68    pub has_approach_rate: bool,
69    pub difficulty: Difficulty,
70}
71
72impl DecodeState for DifficultyState {
73    fn create(_: i32) -> Self {
74        Self {
75            has_approach_rate: false,
76            difficulty: Difficulty::default(),
77        }
78    }
79}
80
81impl From<DifficultyState> for Difficulty {
82    fn from(state: DifficultyState) -> Self {
83        state.difficulty
84    }
85}
86
87impl DecodeBeatmap for Difficulty {
88    type Error = ParseDifficultyError;
89    type State = DifficultyState;
90
91    fn parse_general(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
92        Ok(())
93    }
94
95    fn parse_editor(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
96        Ok(())
97    }
98
99    fn parse_metadata(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
100        Ok(())
101    }
102
103    fn parse_difficulty(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
104        let Ok(KeyValue { key, value }) = KeyValue::parse(line.trim_comment()) else {
105            return Ok(());
106        };
107
108        match key {
109            DifficultyKey::HPDrainRate => state.difficulty.hp_drain_rate = value.parse_num()?,
110            DifficultyKey::CircleSize => state.difficulty.circle_size = value.parse_num()?,
111            DifficultyKey::OverallDifficulty => {
112                state.difficulty.overall_difficulty = value.parse_num()?;
113
114                if !state.has_approach_rate {
115                    state.difficulty.approach_rate = state.difficulty.overall_difficulty;
116                }
117            }
118            DifficultyKey::ApproachRate => {
119                state.difficulty.approach_rate = value.parse_num()?;
120                state.has_approach_rate = true;
121            }
122            DifficultyKey::SliderMultiplier => {
123                state.difficulty.slider_multiplier = f64::parse(value)?.clamp(0.4, 3.6);
124            }
125            DifficultyKey::SliderTickRate => {
126                state.difficulty.slider_tick_rate = f64::parse(value)?.clamp(0.5, 8.0);
127            }
128        }
129
130        Ok(())
131    }
132
133    fn parse_events(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
134        Ok(())
135    }
136
137    fn parse_timing_points(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
138        Ok(())
139    }
140
141    fn parse_colors(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
142        Ok(())
143    }
144
145    fn parse_hit_objects(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
146        Ok(())
147    }
148
149    fn parse_variables(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
150        Ok(())
151    }
152
153    fn parse_catch_the_beat(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
154        Ok(())
155    }
156
157    fn parse_mania(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
158        Ok(())
159    }
160}