rosu_map/section/general/
decode.rs

1use crate::{
2    decode::{DecodeBeatmap, DecodeState},
3    section::hit_objects::hit_samples::{ParseSampleBankError, SampleBank},
4    util::{KeyValue, ParseNumber, ParseNumberError, StrExt},
5    Beatmap,
6};
7
8use super::{CountdownType, GameMode, ParseCountdownTypeError, ParseGameModeError};
9
10/// Struct containing all data from a `.osu` file's `[General]` section.
11#[derive(Clone, Debug, PartialEq)]
12pub struct General {
13    pub audio_file: String,
14    pub audio_lead_in: f64,
15    pub preview_time: i32,
16    pub default_sample_bank: SampleBank,
17    pub default_sample_volume: i32,
18    pub stack_leniency: f32,
19    pub mode: GameMode,
20    pub letterbox_in_breaks: bool,
21    pub special_style: bool,
22    pub widescreen_storyboard: bool,
23    pub epilepsy_warning: bool,
24    pub samples_match_playback_rate: bool,
25    pub countdown: CountdownType,
26    pub countdown_offset: i32,
27}
28
29impl Default for General {
30    #[allow(clippy::default_trait_access)]
31    fn default() -> Self {
32        Self {
33            audio_file: Default::default(),
34            audio_lead_in: Default::default(),
35            preview_time: -1,
36            default_sample_bank: Default::default(),
37            default_sample_volume: 100,
38            stack_leniency: 0.7,
39            mode: Default::default(),
40            letterbox_in_breaks: Default::default(),
41            special_style: Default::default(),
42            widescreen_storyboard: false,
43            epilepsy_warning: Default::default(),
44            samples_match_playback_rate: false,
45            countdown: CountdownType::Normal,
46            countdown_offset: Default::default(),
47        }
48    }
49}
50
51impl From<General> for Beatmap {
52    fn from(general: General) -> Self {
53        Self {
54            audio_file: general.audio_file,
55            audio_lead_in: general.audio_lead_in,
56            preview_time: general.preview_time,
57            default_sample_bank: general.default_sample_bank,
58            default_sample_volume: general.default_sample_volume,
59            stack_leniency: general.stack_leniency,
60            mode: general.mode,
61            letterbox_in_breaks: general.letterbox_in_breaks,
62            special_style: general.special_style,
63            widescreen_storyboard: general.widescreen_storyboard,
64            epilepsy_warning: general.epilepsy_warning,
65            samples_match_playback_rate: general.samples_match_playback_rate,
66            countdown: general.countdown,
67            countdown_offset: general.countdown_offset,
68            ..Self::default()
69        }
70    }
71}
72
73section_keys! {
74    /// All valid keys within a `.osu` file's `[General]` section.
75    pub enum GeneralKey {
76        AudioFilename,
77        AudioLeadIn,
78        PreviewTime,
79        SampleSet,
80        SampleVolume,
81        StackLeniency,
82        Mode,
83        LetterboxInBreaks,
84        SpecialStyle,
85        WidescreenStoryboard,
86        EpilepsyWarning,
87        SamplesMatchPlaybackRate,
88        Countdown,
89        CountdownOffset,
90    }
91}
92
93thiserror! {
94    /// All the ways that parsing a `.osu` file into [`General`] can fail.
95    #[derive(Debug)]
96    pub enum ParseGeneralError {
97        #[error("failed to parse countdown type")]
98        CountdownType(#[from] ParseCountdownTypeError),
99        #[error("failed to parse mode")]
100        Mode(#[from] ParseGameModeError),
101        #[error("failed to parse number")]
102        Number(#[from] ParseNumberError),
103        #[error("failed to parse sample bank")]
104        SampleBank(#[from] ParseSampleBankError),
105    }
106}
107
108/// The parsing state for [`General`] in [`DecodeBeatmap`].
109pub type GeneralState = General;
110
111impl DecodeState for GeneralState {
112    fn create(_: i32) -> Self {
113        Self::default()
114    }
115}
116
117impl DecodeBeatmap for General {
118    type Error = ParseGeneralError;
119    type State = GeneralState;
120
121    fn parse_general(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
122        let Ok(KeyValue { key, value }) = KeyValue::parse(line.trim_comment()) else {
123            return Ok(());
124        };
125
126        match key {
127            GeneralKey::AudioFilename => state.audio_file = value.to_standardized_path(),
128            GeneralKey::AudioLeadIn => state.audio_lead_in = f64::from(i32::parse(value)?),
129            GeneralKey::PreviewTime => state.preview_time = i32::parse(value)?,
130            GeneralKey::SampleSet => state.default_sample_bank = value.parse()?,
131            GeneralKey::SampleVolume => state.default_sample_volume = value.parse_num()?,
132            GeneralKey::StackLeniency => state.stack_leniency = value.parse_num()?,
133            GeneralKey::Mode => state.mode = value.parse()?,
134            GeneralKey::LetterboxInBreaks => state.letterbox_in_breaks = i32::parse(value)? == 1,
135            GeneralKey::SpecialStyle => state.special_style = i32::parse(value)? == 1,
136            GeneralKey::WidescreenStoryboard => {
137                state.widescreen_storyboard = i32::parse(value)? == 1;
138            }
139            GeneralKey::EpilepsyWarning => state.epilepsy_warning = i32::parse(value)? == 1,
140            GeneralKey::SamplesMatchPlaybackRate => {
141                state.samples_match_playback_rate = i32::parse(value)? == 1;
142            }
143            GeneralKey::Countdown => state.countdown = value.parse()?,
144            GeneralKey::CountdownOffset => state.countdown_offset = value.parse_num()?,
145        }
146
147        Ok(())
148    }
149
150    fn parse_editor(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
151        Ok(())
152    }
153
154    fn parse_metadata(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
155        Ok(())
156    }
157
158    fn parse_difficulty(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
159        Ok(())
160    }
161
162    fn parse_events(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
163        Ok(())
164    }
165
166    fn parse_timing_points(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
167        Ok(())
168    }
169
170    fn parse_colors(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
171        Ok(())
172    }
173
174    fn parse_hit_objects(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
175        Ok(())
176    }
177
178    fn parse_variables(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
179        Ok(())
180    }
181
182    fn parse_catch_the_beat(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
183        Ok(())
184    }
185
186    fn parse_mania(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
187        Ok(())
188    }
189}