Beatsaber Rust Utilities: A Beatsaber V3 parsing library.
beatsaber
beatmap
1//! Events that control the rotation of objects.
2
3use crate::difficulty::lightshow::easing::Easing;
4use crate::difficulty::lightshow::filter::Filter;
5use crate::difficulty::lightshow::group::EventData;
6use crate::difficulty::lightshow::{DistributionType, EventAxis, TransitionType};
7use crate::loose_bool::LooseBool;
8use crate::{impl_event_box, impl_event_group, impl_timed};
9use loose_enum::loose_enum;
10use serde::{Deserialize, Serialize};
11
12/// A collection of [`RotationEventGroup`]s that share the same group ID and beat.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[cfg_attr(
15 feature = "bevy_reflect",
16 derive(bevy_reflect::Reflect),
17 reflect(Debug, Clone, PartialEq)
18)]
19pub struct RotationEventBox {
20 /// The time the event takes place.
21 #[serde(rename = "b")]
22 pub beat: f32,
23 /// The ID of the collection of objects that this event effects.
24 #[serde(rename = "g")]
25 pub group_id: i32,
26 #[serde(rename = "e")]
27 pub groups: Vec<RotationEventGroup>,
28}
29
30impl Default for RotationEventBox {
31 fn default() -> Self {
32 Self {
33 beat: 0.0,
34 group_id: 0,
35 groups: vec![RotationEventGroup::default()],
36 }
37 }
38}
39
40impl_timed!(RotationEventBox::beat);
41impl_event_box!(RotationEventBox, RotationEventGroup, RotationEventData);
42
43/// A collection of [`RotationEventData`] that share the same [`EventAxis`], [`Filter`], and distribution.
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45#[cfg_attr(
46 feature = "bevy_reflect",
47 derive(bevy_reflect::Reflect),
48 reflect(Debug, Clone, PartialEq)
49)]
50pub struct RotationEventGroup {
51 #[serde(rename = "f")]
52 pub filter: Filter,
53 #[serde(rename = "d")]
54 pub beat_dist_type: DistributionType,
55 /// The strength of the beat distribution. Dependent on the [distribution type](Self::beat_dist_type).
56 ///
57 /// A value of zero will have no effect.
58 #[serde(rename = "w")]
59 pub beat_dist_value: f32,
60 #[serde(rename = "t")]
61 pub rotation_dist_type: DistributionType,
62 /// The strength of the rotation distribution. Dependent on the [distribution type](Self::rotation_dist_type).
63 ///
64 /// A value of zero will have no effect.
65 #[serde(rename = "s")]
66 pub rotation_dist_value: f32,
67 /// Whether the first [`RotationEventData`] of the group will be effected by rotation distribution.
68 #[serde(rename = "b")]
69 pub rotation_dist_effect_first: LooseBool,
70 /// > Only present in difficulty file V3.2 or higher.
71 #[serde(rename = "i")]
72 pub rotation_dist_easing: Option<Easing>,
73 #[serde(rename = "a")]
74 pub axis: EventAxis,
75 /// If true, the rotation will be mirrored.
76 #[serde(rename = "r")]
77 pub invert_axis: LooseBool,
78 #[serde(rename = "l")]
79 pub data: Vec<RotationEventData>,
80}
81
82impl Default for RotationEventGroup {
83 fn default() -> Self {
84 Self {
85 filter: Default::default(),
86 beat_dist_type: Default::default(),
87 beat_dist_value: 0.0,
88 rotation_dist_type: Default::default(),
89 rotation_dist_value: 0.0,
90 rotation_dist_effect_first: LooseBool::True,
91 rotation_dist_easing: Some(Easing::Linear),
92 axis: Default::default(),
93 invert_axis: Default::default(),
94 data: vec![RotationEventData::default()],
95 }
96 }
97}
98
99impl_event_group!(RotationEventGroup::get_rotation_offset, RotationEventData);
100
101impl RotationEventGroup {
102 /// Returns the number of degrees that the event will be offset for a given light ID.
103 /// # Panics
104 /// Will panic if the light ID is greater than or equal to the group size.
105 #[deprecated(note = "Experimental. Does not consider random in filter calculations.")]
106 #[allow(deprecated)]
107 pub fn get_rotation_offset(&self, light_id: i32, group_size: i32) -> f32 {
108 self.rotation_dist_type.compute_value_offset(
109 light_id,
110 group_size,
111 &self.filter,
112 self.rotation_dist_value,
113 self.data.last().map(|data| data.beat_offset),
114 self.rotation_dist_easing,
115 )
116 }
117}
118
119/// The lowest-level group event type, which determines the base rotation of the event.
120#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
121#[cfg_attr(
122 feature = "bevy_reflect",
123 derive(bevy_reflect::Reflect),
124 reflect(Debug, Clone, PartialEq)
125)]
126pub struct RotationEventData {
127 /// The number of beats the event will be offset from the [`RotationEventBox`]'s beat.
128 #[serde(rename = "b")]
129 pub beat_offset: f32,
130 #[serde(rename = "p")]
131 pub transition_type: TransitionType,
132 #[serde(rename = "e")]
133 pub easing: Easing,
134 /// The base number of degrees the event will rotate objects by.
135 #[serde(rename = "r")]
136 pub degrees: f32,
137 #[serde(rename = "o")]
138 pub direction: RotationDirection,
139 /// Extends the rotation by 360 degrees in the [`RotationDirection`].
140 #[serde(rename = "l")]
141 pub loops: i32,
142}
143
144impl EventData for RotationEventData {
145 fn get_beat_offset(&self) -> f32 {
146 self.beat_offset
147 }
148}
149
150loose_enum! {
151 /// Determines the direction that the rotation event will rotate.
152 /// Automatic will choose the shortest distance.
153 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
154 #[cfg_attr(
155 feature = "bevy_reflect",
156 derive(bevy_reflect::Reflect),
157 reflect(Debug, Clone, PartialEq)
158 )]
159 pub enum RotationDirection: i32 {
160 #[default]
161 Automatic = 0,
162 Clockwise = 1,
163 CounterClockwise = 2,
164 }
165}