rosu_map/section/general/
decode.rs1use 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#[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 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 #[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
108pub 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}