Beatsaber Rust Utilities: A Beatsaber V3 parsing library.
beatsaber
beatmap
1//! The interactable objects of a difficulty.
2
3use crate::{impl_duration, impl_timed};
4use loose_enum::loose_enum;
5use serde::{Deserialize, Serialize};
6
7/// The standard block/note that a player cuts.
8#[doc(alias = "Block")]
9#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[cfg_attr(
11 feature = "bevy_reflect",
12 derive(bevy_reflect::Reflect),
13 reflect(Debug, Clone, PartialEq)
14)]
15pub struct Note {
16 /// The position of the object in time.
17 #[serde(rename = "b")]
18 pub beat: f32,
19 /// A value representing the vertical position of the object.
20 /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
21 #[serde(rename = "y")]
22 pub row: i32,
23 /// A value representing the horizontal position of the object.
24 /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
25 #[serde(rename = "x")]
26 pub col: i32,
27 /// The color that determines which saber should be used to cut the note.
28 #[serde(rename = "c")]
29 pub color: NoteColor,
30 /// The direction the note should be cut.
31 #[serde(rename = "d")]
32 pub direction: CutDirection,
33 /// The number of degrees counter-clockwise to offset the object by.
34 #[serde(rename = "a")]
35 pub angle_offset: f32,
36}
37
38impl_timed!(Note::beat);
39
40loose_enum! {
41 /// The color of a note, which determines which saber should be used to cut it.
42 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
43 #[cfg_attr(
44 feature = "bevy_reflect",
45 derive(bevy_reflect::Reflect),
46 reflect(Debug, Clone, PartialEq)
47 )]
48 pub enum NoteColor: i32 {
49 #[default]
50 Left = 0,
51 Right = 1,
52 }
53}
54
55loose_enum! {
56 /// The direction a note should be cut.
57 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
58 #[cfg_attr(
59 feature = "bevy_reflect",
60 derive(bevy_reflect::Reflect),
61 reflect(Debug, Clone, PartialEq)
62 )]
63 pub enum CutDirection: i32 {
64 #[default]
65 Up = 0,
66 Down = 1,
67 Left = 2,
68 Right = 3,
69 UpLeft = 4,
70 UpRight = 5,
71 DownLeft = 6,
72 DownRight = 7,
73 #[doc(alias = "Dot")]
74 Any = 8,
75 }
76}
77
78impl CutDirection {
79 /// Returns the number of degrees a note is rotated, with zero degrees being a downward note.
80 ///
81 /// Returns zero if the cut direction is undefined/any.
82 pub fn get_degrees(&self) -> f32 {
83 match self {
84 CutDirection::Up => 180.0,
85 CutDirection::Down => 0.0,
86 CutDirection::Left => -90.0,
87 CutDirection::Right => 90.0,
88 CutDirection::UpLeft => -135.0,
89 CutDirection::UpRight => 135.0,
90 CutDirection::DownLeft => -45.0,
91 CutDirection::DownRight => 45.0,
92 CutDirection::Any => 0.0,
93 CutDirection::Undefined(_) => 0.0,
94 }
95 }
96}
97
98/// The spiked bombs that players avoid hitting with their sabers.
99#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
100#[cfg_attr(
101 feature = "bevy_reflect",
102 derive(bevy_reflect::Reflect),
103 reflect(Debug, Clone, PartialEq)
104)]
105pub struct Bomb {
106 /// The position of the object in time.
107 #[serde(rename = "b")]
108 pub beat: f32,
109 /// A value representing the vertical position of the object.
110 /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
111 #[serde(rename = "y")]
112 pub row: i32,
113 /// A value representing the horizontal position of the object.
114 /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
115 #[serde(rename = "x")]
116 pub col: i32,
117}
118
119impl_timed!(Bomb::beat);
120
121/// A wall/obstacle that players avoid running into.
122#[doc(alias = "Obstacle")]
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124#[cfg_attr(
125 feature = "bevy_reflect",
126 derive(bevy_reflect::Reflect),
127 reflect(Debug, Clone, PartialEq)
128)]
129pub struct Wall {
130 /// The start position of the object in time.
131 #[serde(rename = "b")]
132 pub beat: f32,
133 /// The length (in beats) that an object takes place.
134 #[serde(rename = "d")]
135 pub duration: f32,
136 /// A value representing the vertical position of the object.
137 /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
138 #[serde(rename = "y")]
139 pub row: i32,
140 /// A value representing the horizontal position of the object.
141 /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
142 #[serde(rename = "x")]
143 pub col: i32,
144 /// The number of columns that the wall will take up.
145 #[serde(rename = "w")]
146 pub width: i32,
147 /// The number of rows that the wall will take up.
148 ///
149 /// A standard wall has a height of five while a crouch wall has a height of three.
150 #[serde(rename = "h")]
151 pub height: i32,
152}
153
154impl Default for Wall {
155 fn default() -> Self {
156 Self {
157 beat: 0.0,
158 duration: 1.0,
159 row: 0,
160 col: 0,
161 width: 1,
162 height: 5,
163 }
164 }
165}
166
167impl_duration!(Wall::beat, duration: duration);
168
169/// A glowing line that guides the player's saber.
170#[doc(alias = "Slider")]
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
172#[cfg_attr(
173 feature = "bevy_reflect",
174 derive(bevy_reflect::Reflect),
175 reflect(Debug, Clone, PartialEq)
176)]
177pub struct Arc {
178 /// The start position of the object in time.
179 #[serde(rename = "b")]
180 pub beat: f32,
181 /// A value representing the vertical starting position of the object.
182 /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
183 #[serde(rename = "y")]
184 pub row: i32,
185 /// A value representing the horizontal starting position of the object.
186 /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
187 #[serde(rename = "x")]
188 pub col: i32,
189 /// The color of the arc.
190 #[serde(rename = "c")]
191 pub color: NoteColor,
192 /// The direction the arc moves in at the start.
193 #[serde(rename = "d")]
194 pub direction: CutDirection,
195 /// Controls how far away the starting bezier control point is in [cut direction](Self::direction).
196 #[serde(rename = "mu")]
197 pub control_point: f32,
198
199 /// The end position of the object in time.
200 #[serde(rename = "tb")]
201 pub tail_beat: f32,
202 /// A value representing the vertical ending position of the object.
203 /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
204 #[serde(rename = "ty")]
205 pub tail_row: i32,
206 /// A value representing the horizontal ending position of the object.
207 /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
208 #[serde(rename = "tx")]
209 pub tail_col: i32,
210 /// The direction the arc moves in at the end.
211 #[serde(rename = "tc")]
212 pub tail_direction: CutDirection,
213 /// Controls how far away the ending bezier control point is in [tail cut direction](Self::tail_direction).
214 #[serde(rename = "tmu")]
215 pub tail_control_point: f32,
216
217 /// Controls how the arc curves from its head to its tail.
218 #[serde(rename = "m")]
219 pub mid_anchor_mode: MidAnchorMode,
220}
221
222impl Default for Arc {
223 fn default() -> Self {
224 Self {
225 beat: 0.0,
226 row: 0,
227 col: 0,
228 color: Default::default(),
229 direction: Default::default(),
230 control_point: 1.0,
231 tail_beat: 1.0,
232 tail_row: 0,
233 tail_col: 0,
234 tail_direction: Default::default(),
235 tail_control_point: 1.0,
236 mid_anchor_mode: Default::default(),
237 }
238 }
239}
240
241impl_duration!(Arc::beat, end: tail_beat);
242
243loose_enum! {
244 /// Controls how an arc curves from its head to its tail.
245 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
246 #[cfg_attr(
247 feature = "bevy_reflect",
248 derive(bevy_reflect::Reflect),
249 reflect(Debug, Clone, PartialEq)
250 )]
251 pub enum MidAnchorMode: i32 {
252 #[default]
253 Straight = 0,
254 Clockwise = 1,
255 CounterClockwise = 2,
256 }
257}
258
259/// A chain/burst of mini-notes.
260#[doc(alias = "BurstSlider")]
261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
262#[cfg_attr(
263 feature = "bevy_reflect",
264 derive(bevy_reflect::Reflect),
265 reflect(Debug, Clone, PartialEq)
266)]
267pub struct Chain {
268 /// The start position of the object in time.
269 #[serde(rename = "b")]
270 pub beat: f32,
271 /// A value representing the vertical starting position of the object.
272 /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
273 #[serde(rename = "y")]
274 pub row: i32,
275 /// A value representing the horizontal starting position of the object.
276 /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
277 #[serde(rename = "x")]
278 pub col: i32,
279 /// The color that determines which saber should be used to cut the chain links.
280 #[serde(rename = "c")]
281 pub color: NoteColor,
282 /// The direction the start of the chain should be cut.
283 #[serde(rename = "d")]
284 pub direction: CutDirection,
285
286 /// The end position of the object in time.
287 #[serde(rename = "tb")]
288 pub tail_beat: f32,
289 /// A value representing the vertical ending position of the object.
290 /// In the range 0..2 inclusive, with zero being the bottom and two being the top row.
291 #[serde(rename = "ty")]
292 pub tail_row: i32,
293 /// A value representing the horizontal ending position of the object.
294 /// In the range 0..3 inclusive, with zero being the far left and three being the far right column.
295 #[serde(rename = "tx")]
296 pub tail_col: i32,
297
298 /// The number of links the chain has, including the connected [`Note`].
299 #[serde(rename = "sc")]
300 pub link_count: i32,
301 /// The percent of the path (from head to tail) that will be used, usually in the range 0..1 inclusive.
302 /// Smaller values will result in the chain links bunching up near the head.
303 ///
304 /// Setting this to zero will crash the game.
305 #[serde(rename = "s")]
306 pub link_squish: f32,
307}
308
309impl Default for Chain {
310 fn default() -> Self {
311 Self {
312 beat: 0.0,
313 row: 1,
314 col: 0,
315 color: Default::default(),
316 direction: Default::default(),
317 tail_beat: 0.0,
318 tail_row: 0,
319 tail_col: 0,
320 link_count: 3,
321 link_squish: 1.0,
322 }
323 }
324}
325
326impl_duration!(Chain::beat, end: tail_beat);