Beatsaber Rust Utilities: A Beatsaber V3 parsing library.
beatsaber
beatmap
1//! Events that control animations unique to each environment.
2//!
3//! Unlike the other V3 group event types, FX events use a template-like JSON syntax.
4//! In order to have standardized structure across all V3 events, custom serialization has been written in [`FxEventContainer`].
5//! Because of this, neither [`FxEventBox`] nor [`FxEventGroup`] implement [`Serialize`] nor [`Deserialize`] directly.
6
7use crate::difficulty::lightshow::DistributionType;
8use crate::difficulty::lightshow::easing::Easing;
9use crate::difficulty::lightshow::filter::Filter;
10use crate::loose_bool::LooseBool;
11use crate::{TransitionType, impl_event_box, impl_event_data, impl_event_group, impl_timed};
12use indexmap::IndexSet;
13use ordered_float::OrderedFloat;
14use serde::ser::SerializeStruct;
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16use std::ops::{Deref, DerefMut};
17
18/// Contains a list of [`FxEventBox`] as well as the [`Serialize`] and [`Deserialize`] implementations for FX events.
19#[derive(Debug, Clone, PartialEq, Default)]
20#[cfg_attr(
21 feature = "bevy_reflect",
22 derive(bevy_reflect::Reflect),
23 reflect(Debug, Clone, PartialEq)
24)]
25pub struct FxEventContainer {
26 pub event_boxes: Vec<FxEventBox>,
27}
28
29impl Deref for FxEventContainer {
30 type Target = Vec<FxEventBox>;
31
32 fn deref(&self) -> &Self::Target {
33 &self.event_boxes
34 }
35}
36
37impl DerefMut for FxEventContainer {
38 fn deref_mut(&mut self) -> &mut Self::Target {
39 &mut self.event_boxes
40 }
41}
42
43/// The format that is actually stored in JSON.
44#[derive(Deserialize)]
45struct FxEventInput {
46 #[serde(rename = "vfxEventBoxGroups")]
47 event_boxes: Vec<FxEventBoxRaw>,
48 #[serde(rename = "_fxEventsCollection")]
49 arrays: FxEventArrays,
50}
51
52#[derive(Deserialize, Serialize)]
53struct FxEventArrays {
54 #[serde(rename = "_fl")]
55 event_data: Vec<FxEventData>,
56}
57
58impl<'de> Deserialize<'de> for FxEventContainer {
59 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60 where
61 D: Deserializer<'de>,
62 {
63 let FxEventInput {
64 event_boxes,
65 arrays,
66 } = FxEventInput::deserialize(deserializer)?;
67
68 let event_boxes = event_boxes
69 .into_iter()
70 .map(|raw_box| {
71 let groups = raw_box
72 .groups
73 .into_iter()
74 .map(|raw_group| {
75 let data = raw_group
76 .data_ids
77 .into_iter()
78 .map(|id| {
79 arrays.event_data.get(id).cloned().ok_or_else(|| {
80 serde::de::Error::custom(format!(
81 "Missing FxEventData with id {}",
82 id
83 ))
84 })
85 })
86 .collect::<Result<Vec<FxEventData>, _>>()?;
87
88 Ok(FxEventGroup {
89 filter: raw_group.filter,
90 beat_dist_type: raw_group.beat_dist_type,
91 beat_dist_value: raw_group.beat_dist_value,
92 fx_dist_type: raw_group.fx_dist_type,
93 fx_dist_value: raw_group.fx_dist_value,
94 fx_dist_effect_first: raw_group.fx_dist_effect_first,
95 fx_dist_easing: raw_group.fx_dist_easing,
96 data,
97 })
98 })
99 .collect::<Result<Vec<FxEventGroup>, _>>()?;
100
101 Ok(FxEventBox {
102 beat: raw_box.beat,
103 group_id: raw_box.group_id,
104 groups,
105 })
106 })
107 .collect::<Result<Vec<FxEventBox>, _>>()?;
108
109 Ok(FxEventContainer { event_boxes })
110 }
111}
112
113// Todo avoid allocations.
114impl Serialize for FxEventContainer {
115 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
116 where
117 S: Serializer,
118 {
119 let mut event_data: IndexSet<FxEventDataKey> = IndexSet::new();
120
121 // Todo Deduplicate.
122 let raw_boxes = self
123 .event_boxes
124 .iter()
125 .map(|event_box| {
126 let groups_raw = event_box
127 .groups
128 .iter()
129 .map(|event_group| {
130 let mut ids = Vec::new();
131
132 for data in &event_group.data {
133 let data_key: FxEventDataKey = data.into();
134
135 let index = event_data.get_index_of(&data_key).unwrap_or_else(|| {
136 let index = event_data.len();
137 event_data.insert(data_key);
138 index
139 });
140
141 ids.push(index);
142 }
143
144 FxEventGroupRaw {
145 filter: event_group.filter.clone(),
146 beat_dist_type: event_group.beat_dist_type.clone(),
147 beat_dist_value: event_group.beat_dist_value,
148 fx_dist_type: event_group.fx_dist_type.clone(),
149 fx_dist_value: event_group.fx_dist_value,
150 fx_dist_effect_first: event_group.fx_dist_effect_first,
151 fx_dist_easing: event_group.fx_dist_easing.clone(),
152 data_ids: ids,
153 }
154 })
155 .collect::<Vec<FxEventGroupRaw>>();
156
157 FxEventBoxRaw {
158 beat: event_box.beat,
159 group_id: event_box.group_id,
160 groups: groups_raw,
161 }
162 })
163 .collect::<Vec<FxEventBoxRaw>>();
164
165 let mut state = serializer.serialize_struct("FxEventContainer", 2)?;
166 state.serialize_field("vfxEventBoxGroups", &raw_boxes)?;
167 state.serialize_field(
168 "_fxEventsCollection",
169 &FxEventArrays {
170 event_data: event_data.into_iter().map(FxEventData::from).collect(),
171 },
172 )?;
173 state.end()
174 }
175}
176
177/// A collection of [`FxEventGroup`]s that share the same group ID and beat.
178///
179/// Does not implement [`Serialize`] nor [`Deserialize`]. For more info, see the [module docs](super::fx).
180#[derive(Debug, Clone, PartialEq)]
181#[cfg_attr(
182 feature = "bevy_reflect",
183 derive(bevy_reflect::Reflect),
184 reflect(Debug, Clone, PartialEq)
185)]
186pub struct FxEventBox {
187 /// The time the event takes place.
188 pub beat: f32,
189 /// The ID of the collection of objects that this event effects.
190 pub group_id: i32,
191 pub groups: Vec<FxEventGroup>,
192}
193
194/// The raw JSON structure that uses [data IDs](FxEventGroupRaw::data_ids) rather than actual [event data](FxEventData).
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196struct FxEventBoxRaw {
197 #[serde(rename = "b")]
198 beat: f32,
199 #[serde(rename = "g")]
200 group_id: i32,
201 #[serde(rename = "e")]
202 groups: Vec<FxEventGroupRaw>,
203}
204
205impl Default for FxEventBox {
206 fn default() -> Self {
207 Self {
208 beat: 0.0,
209 group_id: 0,
210 groups: vec![FxEventGroup::default()],
211 }
212 }
213}
214
215impl_timed!(FxEventBox::beat);
216impl_event_box!(FxEventBox, FxEventGroup, FxEventData);
217
218/// A collection of [`FxEventData`] that share the same [`Filter`] and distribution.
219///
220/// Does not implement [`Serialize`] nor [`Deserialize`]. For more info, see the [module docs](super::fx).
221#[derive(Debug, Clone, PartialEq)]
222#[cfg_attr(
223 feature = "bevy_reflect",
224 derive(bevy_reflect::Reflect),
225 reflect(Debug, Clone, PartialEq)
226)]
227pub struct FxEventGroup {
228 pub filter: Filter,
229 pub beat_dist_type: DistributionType,
230 /// The strength of the beat distribution. Dependent on the [distribution type](Self::beat_dist_type).
231 ///
232 /// A value of zero will have no effect.
233 pub beat_dist_value: f32,
234 pub fx_dist_type: DistributionType,
235 /// The strength of the brightness distribution. Dependent on the [distribution type](Self::fx_dist_type).
236 ///
237 /// A value of zero will have no effect.
238 pub fx_dist_value: f32,
239 /// Whether the first [`FxEventData`] of the group will be effected by brightness distribution.
240 pub fx_dist_effect_first: LooseBool,
241 pub fx_dist_easing: Option<Easing>,
242 /// In the actual JSON structure, this is a list of indexes to a separate list of event data.
243 /// For consistency, this is merged during parsing.
244 pub data: Vec<FxEventData>,
245}
246
247/// The raw JSON structure that uses [data IDs](self::data_ids) rather than actual [event data](FxEventData).
248#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249struct FxEventGroupRaw {
250 #[serde(rename = "f")]
251 filter: Filter,
252 #[serde(rename = "d")]
253 beat_dist_type: DistributionType,
254 #[serde(rename = "w")]
255 beat_dist_value: f32,
256 #[serde(rename = "t")]
257 fx_dist_type: DistributionType,
258 #[serde(rename = "s")]
259 fx_dist_value: f32,
260 #[serde(rename = "b")]
261 fx_dist_effect_first: LooseBool,
262 #[serde(rename = "i")]
263 fx_dist_easing: Option<Easing>,
264 #[serde(rename = "l")]
265 data_ids: Vec<usize>,
266}
267
268impl Default for FxEventGroup {
269 fn default() -> Self {
270 Self {
271 filter: Default::default(),
272 beat_dist_type: Default::default(),
273 beat_dist_value: 0.0,
274 fx_dist_type: Default::default(),
275 fx_dist_value: 0.0,
276 fx_dist_effect_first: Default::default(),
277 fx_dist_easing: Some(Easing::Linear),
278 data: vec![FxEventData::default()],
279 }
280 }
281}
282
283impl_event_group!(FxEventGroup::get_fx_offset, FxEventData);
284
285impl FxEventGroup {
286 /// Returns the FX value that the event will be offset for a given light ID.
287 /// # Panics
288 /// Will panic if the light ID is greater than or equal to the group size.
289 #[deprecated(note = "Experimental. Does not consider random in filter calculations.")]
290 #[allow(deprecated)]
291 pub fn get_fx_offset(&self, light_id: i32, group_size: i32) -> f32 {
292 self.fx_dist_type.compute_value_offset(
293 light_id,
294 group_size,
295 &self.filter,
296 self.fx_dist_value,
297 self.data.last().map(|data| data.beat_offset),
298 self.fx_dist_easing,
299 )
300 }
301}
302
303/// The lowest-level group event type, which determines the base value of the event.
304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305#[cfg_attr(
306 feature = "bevy_reflect",
307 derive(bevy_reflect::Reflect),
308 reflect(Debug, Clone, PartialEq)
309)]
310pub struct FxEventData {
311 /// The number of beats the event will be offset from the [`FxEventBox`]'s beat.
312 #[serde(rename = "b")]
313 pub beat_offset: f32,
314 #[serde(rename = "p")]
315 pub transition_type: TransitionType,
316 #[serde(rename = "i")]
317 pub easing: Easing,
318 /// The base value of the effect.
319 #[serde(rename = "v")]
320 pub value: f32,
321}
322
323/// A `PartialEq` and `Hash` version of [`FxEventData`], allowing for deduplication.
324#[derive(Debug, Clone, Eq, PartialEq, Hash)]
325struct FxEventDataKey {
326 beat_offset: OrderedFloat<f32>,
327 transition_type: TransitionType,
328 easing: Easing,
329 value: OrderedFloat<f32>,
330}
331
332impl From<&FxEventData> for FxEventDataKey {
333 fn from(value: &FxEventData) -> Self {
334 Self {
335 beat_offset: value.beat_offset.into(),
336 transition_type: value.transition_type,
337 easing: value.easing,
338 value: value.value.into(),
339 }
340 }
341}
342
343impl From<FxEventDataKey> for FxEventData {
344 fn from(value: FxEventDataKey) -> Self {
345 Self {
346 beat_offset: value.beat_offset.into(),
347 transition_type: value.transition_type,
348 easing: value.easing,
349 value: value.value.into(),
350 }
351 }
352}
353
354impl Default for FxEventData {
355 fn default() -> Self {
356 Self {
357 beat_offset: 0.0,
358 transition_type: Default::default(),
359 easing: Easing::default(),
360 value: 1.0,
361 }
362 }
363}
364
365impl_event_data!(FxEventData);
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use crate::loose_bool::LooseBool;
371 use serde_json::{Value, json};
372
373 fn get_test_container() -> FxEventContainer {
374 FxEventContainer {
375 event_boxes: vec![FxEventBox {
376 beat: 2.0,
377 group_id: 0,
378 groups: vec![get_test_group(), get_test_group()],
379 }],
380 }
381 }
382
383 fn get_test_group() -> FxEventGroup {
384 FxEventGroup {
385 filter: Default::default(),
386 beat_dist_type: DistributionType::Wave,
387 beat_dist_value: 1.0,
388 fx_dist_type: DistributionType::Wave,
389 fx_dist_value: 1.0,
390 fx_dist_effect_first: LooseBool::True,
391 fx_dist_easing: Some(Easing::None),
392 data: vec![FxEventData {
393 beat_offset: 0.0,
394 transition_type: TransitionType::Transition,
395 easing: Easing::Linear,
396 value: 100.0,
397 }],
398 }
399 }
400
401 fn get_test_json() -> Value {
402 json!(
403 {
404 "vfxEventBoxGroups":
405 [
406 {
407 "b": 2.0,
408 "g": 0,
409 "e":
410 [
411 {
412 "f":
413 {
414 "c": 0,
415 "f": 1,
416 "p": 1,
417 "t": 0,
418 "r": 0,
419 "n": 0,
420 "s": 0,
421 "l": 1.0,
422 "d": 0
423 },
424 "w": 1.0,
425 "d": 1,
426 "s": 1.0,
427 "t": 1,
428 "b": 1,
429 "i": -1,
430 "l":
431 [
432 0
433 ]
434 },
435 {
436 "f":
437 {
438 "c": 0,
439 "f": 1,
440 "p": 1,
441 "t": 0,
442 "r": 0,
443 "n": 0,
444 "s": 0,
445 "l": 1.0,
446 "d": 0
447 },
448 "w": 1.0,
449 "d": 1,
450 "s": 1.0,
451 "t": 1,
452 "b": 1,
453 "i": -1,
454 "l":
455 [
456 0
457 ]
458 }
459 ]
460 }
461 ],
462 "_fxEventsCollection":
463 {
464 "_fl":
465 [
466 {
467 "b": 0.0,
468 "p": 0,
469 "i": 0,
470 "v": 100.0
471 }
472 ]
473 }
474 }
475 )
476 }
477
478 #[test]
479 fn test_deserialize() {
480 let container: FxEventContainer = serde_json::from_value(get_test_json()).unwrap();
481
482 assert_eq!(container, get_test_container());
483 }
484
485 #[test]
486 fn test_serialize() {
487 let out_json = serde_json::to_value(&get_test_container()).unwrap();
488
489 assert_eq!(out_json, get_test_json());
490 }
491
492 #[test]
493 fn test_round_trip() {
494 let container: FxEventContainer = serde_json::from_value(get_test_json()).unwrap();
495
496 let out_json = serde_json::to_string_pretty(&container).unwrap();
497
498 let round_trip: FxEventContainer = serde_json::from_str(&out_json).unwrap();
499
500 assert_eq!(container, round_trip);
501 }
502}