osu_file_parser/osu_file/general/
mod.rs

1pub mod error;
2pub mod types;
3
4use std::fmt::Debug;
5use std::num::{IntErrorKind, ParseIntError};
6use std::path::PathBuf;
7
8use crate::osu_file::types::Decimal;
9use crate::parsers::comma;
10use nom::bytes::complete::take_till;
11use nom::combinator::map_res;
12use nom::multi::separated_list0;
13use nom::Finish;
14use rust_decimal_macros::dec;
15
16use crate::helper;
17use crate::helper::macros::*;
18
19use crate::osu_file::Integer;
20
21pub use error::*;
22pub use types::*;
23
24versioned_field!(AudioFilename, PathBuf, no_versions, |s| { Ok(PathBuf::from(s)) } -> (), |v| { v.display().to_string() }, PathBuf::from(""));
25versioned_field!(AudioLeadIn, Integer, no_versions, |s| { s.parse() } -> ParseIntError,, 0);
26versioned_field!(AudioHash, String, no_versions, |s| { Ok(s.to_string()) } -> (),
27    |v, version| { if version > 13 { None } else { Some(v.to_string()) } },
28    |version| { if version > 13 { None } else { Some(String::new())}
29});
30versioned_field!(PreviewTime, Integer, no_versions, |s| { s.parse() } -> ParseIntError,, -1);
31versioned_field!(StackLeniency, Decimal, no_versions, |s| { s.parse() } -> (),, Decimal::from(dec!(0.7)));
32versioned_field!(LetterboxInBreaks, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, false);
33versioned_field!(StoryFireInFront, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, true);
34versioned_field!(UseSkinSprites, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, false);
35versioned_field!(AlwaysShowPlayfield, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, false);
36versioned_field!(SkinPreference, String, no_versions, |s| { Ok(s.to_string()) } -> (),, String::new());
37versioned_field!(EpilepsyWarning, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, false);
38versioned_field!(CountdownOffset, Integer, no_versions, |s| { s.parse() } -> ParseIntError,, 0);
39versioned_field!(SpecialStyle, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, false);
40versioned_field!(WidescreenStoryboard, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, false);
41versioned_field!(SamplesMatchPlaybackRate, bool, no_versions, |s| { helper::parse_zero_one_bool(s) } -> helper::ParseZeroOneBoolError, boolean, false);
42versioned_field!(EditorBookmarks, Vec<Integer>, no_versions, |s| {
43    let bookmark = map_res(take_till(|c| c == ','), |s: &str| s.parse::<Integer>());
44    let mut bookmarks = separated_list0(comma::<nom::error::Error<_>>(), bookmark);
45    let input_len = s.len();
46
47    let (s, bookmarks) = bookmarks(s).finish().unwrap();
48
49    if s.is_empty() {
50        Ok(bookmarks)
51    } else {
52        let (_, s) = {
53            let s = if s.len() < input_len {
54                match s.strip_prefix(',') {
55                    Some(s) => s,
56                    None => s,
57                }
58            } else {
59                s
60            };
61
62            take_till::<_, _, nom::error::Error<_>>(|c| c == ',')(s).unwrap()
63        };
64
65        // re-parse to get error
66        let err = s.parse::<Integer>().unwrap_err();
67
68        let err = if let IntErrorKind::Empty = err.kind() {
69            ParseError::InvalidCommaList
70        } else {
71            ParseError::ParseIntError(err)
72        };
73
74        Err(err)
75    }
76} -> ParseError,
77|v, version| {
78    if version > 5 {
79        return None;
80    }
81
82    Some(v.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(","))
83},
84);
85versioned_field!(EditorDistanceSpacing, Decimal, no_versions, |s| { s.parse() } -> (),
86|v, version| {
87    if version > 5 {
88        return None;
89    }
90
91    Some(v.to_string())
92}
93,);
94
95general_section!(
96    /// A struct representing the general section of an osu file.
97    pub struct General {
98        /// The name of the beatmap.
99        pub audio_filename: AudioFilename,
100        /// Milliseconds of silence before the audio starts playing.
101        pub audio_lead_in: AudioLeadIn,
102        /// Deprecated.
103        pub audio_hash: AudioHash,
104        /// Time in milliseconds when the audio preview should start.
105        /// - Defaults to `-1`.
106        pub preview_time: PreviewTime,
107        /// Speed of the countdown before the first hit object.
108        /// - Defaults to `Normal`.
109        pub countdown: Countdown,
110        /// Sample set that will be used if timing points do not override it.
111        /// - Defaults to `Normal`.
112        pub sample_set: SampleSet,
113        /// Multiplier for the threshold in time where hit objects placed close together stack.
114        /// - Defaults to `0.7`.
115        pub stack_leniency: StackLeniency,
116        /// Game mode.
117        /// - Defaults to `osu`.
118        pub mode: Mode,
119        /// Whether or not breaks have a letterboxing effect.
120        /// - Defaults to `false`.
121        pub letterbox_in_breaks: LetterboxInBreaks,
122        /// Deprecated.
123        /// - Defaults to `true`.
124        pub story_fire_in_front: StoryFireInFront,
125        /// Whether or not the storyboard can use the user's skin images.
126        /// - Defaults to `false`.
127        pub use_skin_sprites: UseSkinSprites,
128        /// Deprecated.
129        /// - Defaults to `false`.
130        pub always_show_playfield: AlwaysShowPlayfield,
131        /// Draw order of hit circle overlays compared to hit numbers.
132        /// - Defaults to `NoChange`.
133        pub overlay_position: OverlayPosition,
134        /// Preferred skin to use during gameplay.
135        pub skin_preference: SkinPreference,
136        /// Whether or not a warning about flashing colours should be shown at the beginning of the map.
137        /// - Defaults to `false`.
138        pub epilepsy_warning: EpilepsyWarning,
139        /// Time in beats that the countdown starts before the first hit object.
140        /// - Defaults to `0`.
141        pub countdown_offset: CountdownOffset,
142        /// Whether or not the "N+1" style key layout is used for osu!mania.
143        /// - Defaults to `false`.
144        pub special_style: SpecialStyle,
145        /// Whether or not the storyboard allows widescreen viewing.
146        /// - Defaults to `false`.
147        pub widescreen_storyboard: WidescreenStoryboard,
148        /// Whether or not sound samples will change rate when playing with speed-changing mods.
149        /// - Defaults to `false`.
150        pub samples_match_playback_rate: SamplesMatchPlaybackRate,
151        /// Deprecated.
152        pub editor_bookmarks: EditorBookmarks,
153        /// Deprecated.
154        pub editor_distance_spacing: EditorDistanceSpacing,
155    },
156    ParseError,
157    " ",
158);