Beatsaber Rust Utilities: A Beatsaber V3 parsing library.
beatsaber
beatmap
1//! Events that have no effect on gameplay.
2
3pub mod basic;
4pub mod easing;
5pub mod filter;
6pub mod group;
7
8#[doc(hidden)]
9pub use basic::*;
10#[doc(hidden)]
11pub use easing::*;
12#[doc(hidden)]
13pub use filter::*;
14#[doc(hidden)]
15pub use group::*;
16
17use loose_enum::loose_enum;
18
19loose_enum! {
20 /// The way that the distribution value is used.
21 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
22 #[cfg_attr(
23 feature = "bevy_reflect",
24 derive(bevy_reflect::Reflect),
25 reflect(Debug, Clone, PartialEq)
26 )]
27 pub enum DistributionType: i32 {
28 /// The distribution value represents the difference between *the last and first step*.
29 #[default]
30 Wave = 1,
31 /// The distribution value represents the difference between *each step*.
32 Step = 2,
33 }
34}
35
36impl DistributionType {
37 #[deprecated(note = "Experimental. Does not consider random in filter calculations.")]
38 #[allow(deprecated)]
39 fn compute_beat_offset(
40 &self,
41 light_id: i32,
42 group_size: i32,
43 filter: &Filter,
44 dist_value: f32,
45 last_data_offset: Option<f32>,
46 easing: Option<Easing>,
47 ) -> f32 {
48 let filtered_id = filter.get_relative_index(light_id, group_size);
49
50 let filtered_size = if let Some(limit_behaviour) = filter.limit_behaviour
51 && limit_behaviour.beat_enabled()
52 {
53 filter.count_filtered(group_size)
54 } else {
55 filter.count_filtered_without_limit(group_size)
56 };
57
58 self.compute_offset(
59 filtered_id,
60 filtered_size,
61 dist_value,
62 last_data_offset,
63 easing,
64 )
65 }
66
67 #[deprecated(note = "Experimental. Does not consider random in filter calculations.")]
68 #[allow(deprecated)]
69 fn compute_value_offset(
70 &self,
71 light_id: i32,
72 group_size: i32,
73 filter: &Filter,
74 dist_value: f32,
75 last_data_offset: Option<f32>,
76 easing: Option<Easing>,
77 ) -> f32 {
78 let filtered_id = filter.get_relative_index(light_id, group_size);
79
80 let filtered_size = if let Some(limit_behaviour) = filter.limit_behaviour
81 && limit_behaviour.value_enabled()
82 {
83 filter.count_filtered(group_size)
84 } else {
85 filter.count_filtered_without_limit(group_size)
86 };
87
88 self.compute_offset(
89 filtered_id,
90 filtered_size,
91 dist_value,
92 last_data_offset,
93 easing,
94 )
95 }
96
97 #[deprecated(note = "Experimental. Does not consider random in filter calculations.")]
98 #[allow(deprecated)]
99 #[inline(always)]
100 fn compute_offset(
101 &self,
102 filtered_id: i32,
103 filtered_size: i32,
104 dist_value: f32,
105 last_data_offset: Option<f32>,
106 easing: Option<Easing>,
107 ) -> f32 {
108 let filtered_id = filtered_id as f32;
109 let filtered_size = filtered_size as f32;
110
111 if dist_value == 0.0 {
112 return 0.0;
113 }
114
115 match self {
116 DistributionType::Wave => {
117 let mut modified_value = dist_value;
118 if let Some(offset) = last_data_offset {
119 modified_value = (modified_value - offset).max(0.0);
120 }
121
122 let mut fraction = filtered_id / filtered_size;
123 if let Some(easing) = easing {
124 fraction = easing.ease(fraction);
125 }
126
127 fraction * modified_value
128 }
129 DistributionType::Step => dist_value * filtered_id,
130 DistributionType::Undefined(_) => 0.0,
131 }
132 }
133
134 /// Tests that both [`self.compute_beat_offset`] and [`self.compute_value_offset`] have the same return value, returning that value.
135 ///
136 /// This only makes sense if the [`LimitBehaviour`] is either `None` or `Both`.
137 #[cfg(test)]
138 #[allow(deprecated)]
139 fn compute_both(
140 &self,
141 light_id: i32,
142 group_size: i32,
143 filter: &Filter,
144 dist_value: f32,
145 last_data_offset: Option<f32>,
146 easing: Option<Easing>,
147 ) -> f32 {
148 assert!(
149 matches!(
150 filter.limit_behaviour,
151 None | Some(LimitBehaviour::None) | Some(LimitBehaviour::Both)
152 ),
153 "This test method only makes sense if LimitBehaviour is `None` or `Both`"
154 );
155
156 let beat = self.compute_beat_offset(
157 light_id,
158 group_size,
159 filter,
160 dist_value,
161 last_data_offset,
162 easing,
163 );
164 let value = self.compute_value_offset(
165 light_id,
166 group_size,
167 filter,
168 dist_value,
169 last_data_offset,
170 easing,
171 );
172 assert_eq!(beat, value);
173 beat
174 }
175}
176
177loose_enum! {
178 /// Controls how the state is changed relative to the previous event.
179 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
180 #[cfg_attr(
181 feature = "bevy_reflect",
182 derive(bevy_reflect::Reflect),
183 reflect(Debug, Clone, PartialEq)
184 )]
185 pub enum TransitionType: i32 {
186 /// The state will blend from the previous event's state, using the events [easing](Easing).
187 #[default]
188 Transition = 0,
189 /// The event's state will be ignored, replaced with the state from the previous event.
190 Extend = 1,
191 }
192}
193
194loose_enum! {
195 /// The axis that a rotation/translation event effects.
196 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
197 #[cfg_attr(
198 feature = "bevy_reflect",
199 derive(bevy_reflect::Reflect),
200 reflect(Debug, Clone, PartialEq)
201 )]
202 pub enum EventAxis: i32 {
203 #[default]
204 X = 0,
205 Y = 1,
206 Z = 2,
207 }
208}
209
210// More readable concrete tests are available in the `color` module.
211#[allow(deprecated)]
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use crate::difficulty::lightshow::filter::FilterType;
216 use crate::loose_bool::LooseBool;
217
218 #[test]
219 fn wave() {
220 for i in 0..12 {
221 assert_eq!(
222 DistributionType::Wave.compute_both(i, 12, &Filter::default(), 12.0, None, None),
223 i as f32
224 );
225 }
226 }
227
228 #[test]
229 fn step() {
230 for i in 0..12 {
231 assert_eq!(
232 DistributionType::Step.compute_both(i, 12, &Filter::default(), 1.0, None, None),
233 i as f32
234 );
235 }
236 }
237
238 #[test]
239 fn wave_negative() {
240 for i in 0..12 {
241 assert_eq!(
242 DistributionType::Wave.compute_both(i, 12, &Filter::default(), -12.0, None, None),
243 -i as f32
244 );
245 }
246 }
247
248 #[test]
249 fn step_negative() {
250 for i in 0..12 {
251 assert_eq!(
252 DistributionType::Step.compute_both(i, 12, &Filter::default(), -1.0, None, None),
253 -i as f32
254 );
255 }
256 }
257
258 #[test]
259 fn wave_zero() {
260 for i in 0..12 {
261 assert_eq!(
262 DistributionType::Wave.compute_both(i, 12, &Filter::default(), 0.0, None, None),
263 0.0
264 );
265 }
266 }
267
268 #[test]
269 fn step_zero() {
270 for i in 0..12 {
271 assert_eq!(
272 DistributionType::Step.compute_both(i, 12, &Filter::default(), 0.0, None, None),
273 0.0
274 );
275 }
276 }
277
278 #[test]
279 fn wave_with_division_filter() {
280 for i in 0..6 {
281 assert_eq!(
282 DistributionType::Wave.compute_both(
283 i + 6,
284 12,
285 &Filter {
286 filter_type: FilterType::Division,
287 parameter1: 2,
288 parameter2: 1,
289 ..Default::default()
290 },
291 6.0,
292 None,
293 None
294 ),
295 i as f32
296 );
297 }
298 }
299
300 #[test]
301 fn step_with_division_filter() {
302 for i in 0..6 {
303 assert_eq!(
304 DistributionType::Step.compute_both(
305 i + 6,
306 12,
307 &Filter {
308 filter_type: FilterType::Division,
309 parameter1: 2,
310 parameter2: 1,
311 ..Default::default()
312 },
313 1.0,
314 None,
315 None
316 ),
317 i as f32
318 );
319 }
320 }
321
322 #[test]
323 fn wave_with_step_filter() {
324 for i in 0..6 {
325 assert_eq!(
326 DistributionType::Wave.compute_both(
327 i * 2,
328 12,
329 &Filter {
330 filter_type: FilterType::StepAndOffset,
331 parameter1: 0,
332 parameter2: 2,
333 ..Default::default()
334 },
335 6.0,
336 None,
337 None
338 ),
339 i as f32
340 );
341 }
342 }
343
344 #[test]
345 fn step_with_step_filter() {
346 for i in 0..6 {
347 assert_eq!(
348 DistributionType::Step.compute_both(
349 i * 2,
350 12,
351 &Filter {
352 filter_type: FilterType::StepAndOffset,
353 parameter1: 0,
354 parameter2: 2,
355 ..Default::default()
356 },
357 1.0,
358 None,
359 None
360 ),
361 i as f32
362 );
363 }
364 }
365
366 #[test]
367 fn wave_with_reverse_filter() {
368 for i in 0..12 {
369 assert_eq!(
370 DistributionType::Wave.compute_both(
371 i,
372 12,
373 &Filter {
374 reverse: LooseBool::True,
375 ..Default::default()
376 },
377 12.0,
378 None,
379 None
380 ),
381 12.0 - i as f32
382 );
383 }
384 }
385
386 #[test]
387 fn step_with_reverse_filter() {
388 for i in 0..12 {
389 assert_eq!(
390 DistributionType::Step.compute_both(
391 i,
392 12,
393 &Filter {
394 reverse: LooseBool::True,
395 ..Default::default()
396 },
397 1.0,
398 None,
399 None
400 ),
401 12.0 - i as f32
402 );
403 }
404 }
405
406 #[test]
407 fn wave_with_chunks_of_size_two() {
408 for i in 0..6 {
409 let filter = Filter {
410 chunks: Some(6),
411 ..Default::default()
412 };
413 assert_eq!(
414 DistributionType::Wave.compute_both(i * 2, 12, &filter, 6.0, None, None),
415 i as f32
416 );
417 assert_eq!(
418 DistributionType::Wave.compute_both(i * 2 + 1, 12, &filter, 6.0, None, None),
419 i as f32
420 );
421 }
422 }
423
424 #[test]
425 fn step_with_chunks_of_size_two() {
426 for i in 0..6 {
427 let filter = Filter {
428 chunks: Some(6),
429 ..Default::default()
430 };
431 assert_eq!(
432 DistributionType::Step.compute_both(i * 2, 12, &filter, 1.0, None, None),
433 i as f32
434 );
435 assert_eq!(
436 DistributionType::Step.compute_both(i * 2 + 1, 12, &filter, 1.0, None, None),
437 i as f32
438 );
439 }
440 }
441
442 #[test]
443 fn wave_with_chunks_of_size_six() {
444 for i in 0..6 {
445 let filter = Filter {
446 chunks: Some(2),
447 ..Default::default()
448 };
449 assert_eq!(
450 DistributionType::Wave.compute_both(i, 12, &filter, 2.0, None, None),
451 0.0
452 );
453 assert_eq!(
454 DistributionType::Wave.compute_both(i + 6, 12, &filter, 2.0, None, None),
455 1.0
456 );
457 }
458 }
459
460 #[test]
461 fn step_with_chunks_of_size_six() {
462 for i in 0..6 {
463 let filter = Filter {
464 chunks: Some(2),
465 ..Default::default()
466 };
467 assert_eq!(
468 DistributionType::Step.compute_both(i, 12, &filter, 1.0, None, None),
469 0.0
470 );
471 assert_eq!(
472 DistributionType::Step.compute_both(i + 6, 12, &filter, 1.0, None, None),
473 1.0
474 );
475 }
476 }
477
478 #[test]
479 fn wave_with_chunks_out_of_bounds() {
480 let filter = Filter {
481 chunks: Some(24),
482 ..Default::default()
483 };
484
485 for i in 0..12 {
486 assert_eq!(
487 DistributionType::Wave.compute_both(i, 12, &filter, 12.0, None, None),
488 i as f32
489 );
490 }
491 }
492
493 #[test]
494 fn step_with_chunks_out_of_bounds() {
495 let filter = Filter {
496 chunks: Some(24),
497 ..Default::default()
498 };
499
500 for i in 0..12 {
501 assert_eq!(
502 DistributionType::Step.compute_both(i, 12, &filter, 1.0, None, None),
503 i as f32
504 );
505 }
506 }
507
508 #[test]
509 fn wave_with_limit() {
510 let filter = Filter {
511 limit_percent: Some(0.5),
512 ..Default::default()
513 };
514
515 for i in 0..6 {
516 assert_eq!(
517 DistributionType::Wave.compute_both(i, 12, &filter, 12.0, None, None),
518 i as f32
519 );
520 }
521 }
522
523 #[test]
524 fn step_with_limit() {
525 let filter = Filter {
526 limit_percent: Some(0.5),
527 ..Default::default()
528 };
529
530 for i in 0..6 {
531 assert_eq!(
532 DistributionType::Step.compute_both(i, 12, &filter, 1.0, None, None),
533 i as f32
534 );
535 }
536 }
537
538 #[test]
539 fn wave_with_enabled_limit() {
540 let filter = Filter {
541 limit_behaviour: Some(LimitBehaviour::Both),
542 limit_percent: Some(0.5),
543 ..Default::default()
544 };
545
546 for i in 0..6 {
547 assert_eq!(
548 DistributionType::Wave.compute_both(i, 12, &filter, 12.0, None, None),
549 (i * 2) as f32
550 );
551 }
552 }
553
554 #[test]
555 fn step_with_enabled_limit() {
556 let filter = Filter {
557 limit_behaviour: Some(LimitBehaviour::Both),
558 limit_percent: Some(0.5),
559 ..Default::default()
560 };
561
562 for i in 0..6 {
563 assert_eq!(
564 DistributionType::Step.compute_both(i, 12, &filter, 1.0, None, None),
565 i as f32
566 );
567 }
568 }
569
570 #[test]
571 fn wave_with_value_less_than_data_offset() {
572 for i in 0..12 {
573 assert_eq!(
574 DistributionType::Wave.compute_both(
575 i,
576 12,
577 &Filter::default(),
578 1.0,
579 Some(2.0),
580 None
581 ),
582 0.0
583 );
584 }
585 }
586}