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