1use crate::enums::GameMode;
2use bitflags::bitflags;
3use serde::{Deserialize, Serialize};
4
5fn constant_1() -> f32 {
6 1.0
7}
8
9#[derive(Serialize, Deserialize, Default, Clone, Debug)]
10#[serde(rename_all = "PascalCase")]
11#[serde(default)]
12pub struct QuaverMap {
13 pub audio_file: String,
14 pub song_preview_time: i32,
15 pub background_file: String,
16 pub banner_file: String,
17 pub map_id: i32,
18 pub map_set_id: i32,
19 pub mode: GameMode,
20 pub title: String,
21 pub artist: String,
22 pub source: String,
23 pub tags: String,
24 pub creator: String,
25 pub difficulty_name: String,
26 pub description: String,
27 pub genre: String,
28 pub bpm_does_not_affect_scroll_velocity: bool,
29 #[serde(default = "constant_1")]
30 pub initial_scroll_velocity: f32,
31 pub has_scratch_key: bool,
32 pub editor_layers: Vec<EditorLayerInfo>,
33 pub custom_audio_samples: Vec<CustomAudioSampleInfo>,
34 pub sound_effects: Vec<SoundEffectInfo>,
35 pub timing_points: Vec<TimingPointInfo>,
36 pub slider_velocities: Vec<SliderVelocityInfo>,
37 pub hit_objects: Vec<HitObjectInfo>,
38 pub file_path: String,
39}
40
41impl QuaverMap {
42 pub fn from_path(path: &str) -> Self {
43 let file = std::fs::File::open(path).unwrap();
44 serde_yaml::from_reader(file).unwrap()
45 }
46
47 pub fn from_string(input: &str) -> Self {
48 serde_yaml::from_str(input).unwrap()
49 }
50
51 pub fn sort(&mut self) {
52 self.hit_objects.sort_by_key(|x| x.start_time);
53 self.timing_points.sort_by_key(|x| x.start_time as i32);
54 self.slider_velocities.sort_by_key(|x| x.start_time as i32);
55 self.sound_effects.sort_by_key(|x| x.start_time as i32);
56 }
57
58 pub fn to_file(&self) -> Result<(), serde_yaml::Error> {
59 let w = std::fs::OpenOptions::new()
60 .write(true)
61 .create(true)
62 .open(format!("{}.QuaverMap", self.title))
63 .expect("unable to open file");
64
65 serde_yaml::to_writer(w, self)
66 }
67
68 pub fn get_actions_per_second(&self, rate: Option<f32>) -> f32 {
69 let rate = rate.unwrap_or(1.);
70 let mut actions: Vec<i32> = Vec::new();
71
72 for &info in self.hit_objects.iter() {
73 actions.push(info.start_time);
74
75 if info.end_time > 0 {
76 actions.push(info.end_time);
77 }
78 }
79
80 if actions.is_empty() {
81 return 0.;
82 }
83
84 actions.sort();
85
86 let mut length = actions.last().unwrap() - actions.first().unwrap();
87
88 for i in 0..actions.len() {
89 if i == 0 {
90 continue;
91 }
92
93 let action = actions[i];
94 let previous_action = actions[i - 1];
95 let difference = action - previous_action;
96
97 if difference >= 1000 {
98 length -= difference;
99 }
100 }
101
102 actions.len() as f32 / (length as f32 / (1000. * rate))
103 }
104}
105
106#[derive(Serialize, Deserialize, Default, Clone, Debug)]
107#[serde(rename_all = "PascalCase")]
108#[serde(default)]
109pub struct EditorLayerInfo {
110 pub name: String,
111 pub hidden: bool,
112 pub color_rgb: String,
113}
114
115#[derive(Serialize, Deserialize, Clone, Debug)]
116#[serde(rename_all = "PascalCase")]
117pub struct CustomAudioSampleInfo {
118 pub path: String,
119 pub unaffected_by_rate: bool,
120}
121
122#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug)]
123#[serde(rename_all = "PascalCase")]
124#[serde(default)]
125pub struct SoundEffectInfo {
126 pub start_time: f32,
127 pub sample: i32,
128 pub volume: i32,
129}
130
131#[derive(Serialize, Deserialize, Default, Clone, Debug)]
132#[serde(rename_all = "PascalCase")]
133#[serde(default)]
134pub struct TimingPointInfo {
135 pub start_time: f32,
136 pub bpm: f32,
137 pub hidden: bool,
139}
140
141#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)]
142#[serde(rename_all = "PascalCase")]
143#[serde(default)]
144pub struct SliderVelocityInfo {
145 pub start_time: f32,
146 pub multiplier: f32,
147}
148
149#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq)]
150#[serde(rename_all = "PascalCase")]
151#[serde(default)]
152pub struct HitObjectInfo {
153 pub start_time: i32,
154 pub lane: i32,
155 pub end_time: i32,
156 }
158
159bitflags! {
160 #[derive(Serialize, Deserialize, Default)]
161 #[serde(rename_all = "PascalCase")]
162 #[serde(default)]
163 pub struct HitSounds: i32 {
164 const NORMAL = 1 << 0;
165 const WHISTLE = 1 << 1;
166 const FINISH = 1 << 2;
167 const CLAP = 1 << 3;
168 }
169}