//! The advanced group lighting system events. pub mod color; pub mod fx; pub mod rotation; pub mod translation; #[doc(hidden)] pub use color::*; #[doc(hidden)] pub use fx::*; #[doc(hidden)] pub use rotation::*; #[doc(hidden)] pub use translation::*; use crate::difficulty::lightshow::filter::Filter; use crate::timing_traits::Timed; /// A collection of [`EventGroup`]s that share the same group ID and beat. pub trait EventBox: Timed { type Group: EventGroup; type Data: EventData; fn get_groups(&self) -> &Vec; } #[macro_export] #[doc(hidden)] macro_rules! impl_event_box { ($ident:ident, $group:ident, $data:ident) => { impl crate::difficulty::lightshow::group::EventBox for $ident { type Group = $group; type Data = $data; fn get_groups(&self) -> &Vec { &self.groups } } }; } /// A collection of [`EventData`] that share the same [`Filter`] and distribution. pub trait EventGroup { type Data: EventData; fn get_filter(&self) -> &Filter; fn get_data(&self) -> &Vec; /// Returns the number of beats that the event will be offset for a given light ID. /// # Panics /// Will panic if the light ID is greater than or equal to the group size. #[deprecated(note = "Experimental. Does not consider random in filter calculations.")] fn get_beat_offset(&self, light_id: i32, group_size: i32) -> f32; /// Returns the value (i.e. brightness) that the event will be offset for a given light ID. /// # Panics /// Will panic if the light ID is greater than or equal to the group size. #[deprecated(note = "Experimental. Does not consider random in filter calculations.")] fn get_value_offset(&self, light_id: i32, group_size: i32) -> f32; /// Returns the duration of the group in beats. #[deprecated(note = "Experimental. Does not consider random in filter calculations.")] fn get_duration(&self, group_size: i32) -> f32; } // Todo This macro could be a default trait implementation if other getters are added. #[macro_export] #[doc(hidden)] macro_rules! impl_event_group { ($ident:ident::$value_offset:ident, $data:ident) => { impl crate::difficulty::lightshow::group::EventGroup for $ident { type Data = $data; fn get_filter(&self) -> &Filter { &self.filter } fn get_data(&self) -> &Vec { &self.data } #[allow(deprecated)] fn get_beat_offset(&self, light_id: i32, group_size: i32) -> f32 { self.beat_dist_type.compute_beat_offset( light_id, group_size, &self.filter, self.beat_dist_value, self.data.last().map(|data| data.beat_offset), None, ) } #[allow(deprecated)] fn get_value_offset(&self, light_id: i32, group_size: i32) -> f32 { self.$value_offset(light_id, group_size) } #[allow(deprecated)] fn get_duration(&self, group_size: i32) -> f32 { let filtered_size = self.filter.count_filtered(group_size); if filtered_size == 0 { return 0.0; } let Some(data) = self.get_data().last() else { return 0.0; }; match self.beat_dist_type { DistributionType::Wave => { if let Some(limit_behaviour) = self.filter.limit_behaviour && !limit_behaviour.beat_enabled() && let Some(limit_percent) = self.filter.limit_percent && limit_percent > 0.0 { (self.beat_dist_value * limit_percent).max(data.beat_offset) } else { self.beat_dist_value.max(data.beat_offset) } } DistributionType::Step => { data.beat_offset + self.beat_dist_value * filtered_size as f32 } DistributionType::Undefined(_) => data.beat_offset, } } } }; } /// The lowest-level group event type, which determines the base value of the event. pub trait EventData { /// Returns the number of beats the event will be offset from the [`EventBox`]'s beat. fn get_beat_offset(&self) -> f32; } #[macro_export] #[doc(hidden)] macro_rules! impl_event_data { ($ident:ident) => { impl crate::difficulty::lightshow::group::EventData for $ident { fn get_beat_offset(&self) -> f32 { self.beat_offset } } }; } #[allow(deprecated)] #[cfg(test)] mod tests { use super::*; use crate::{DistributionType, LimitBehaviour}; #[test] fn get_duration_no_distribution() { assert_eq!(ColorEventGroup::default().get_duration(12), 0.0); } #[test] fn get_duration_wave() { let group = ColorEventGroup { beat_dist_type: DistributionType::Wave, beat_dist_value: 12.0, ..Default::default() }; assert_eq!(group.get_duration(12), 12.0); } #[test] fn get_duration_step() { let group = ColorEventGroup { beat_dist_type: DistributionType::Step, beat_dist_value: 1.0, ..Default::default() }; assert_eq!(group.get_duration(12), 12.0); } #[test] fn get_duration_wave_with_limit() { let group = ColorEventGroup { filter: Filter { limit_behaviour: Some(LimitBehaviour::None), limit_percent: Some(0.5), ..Default::default() }, beat_dist_type: DistributionType::Wave, beat_dist_value: 12.0, ..Default::default() }; assert_eq!(group.get_duration(12), 6.0); } #[test] fn get_duration_step_with_limit() { let group = ColorEventGroup { filter: Filter { limit_behaviour: Some(LimitBehaviour::None), limit_percent: Some(0.5), ..Default::default() }, beat_dist_type: DistributionType::Step, beat_dist_value: 1.0, ..Default::default() }; assert_eq!(group.get_duration(12), 6.0); } #[test] fn get_duration_wave_with_limit_adjusted() { let group = ColorEventGroup { filter: Filter { limit_behaviour: Some(LimitBehaviour::Beat), limit_percent: Some(0.5), ..Default::default() }, beat_dist_type: DistributionType::Wave, beat_dist_value: 12.0, ..Default::default() }; assert_eq!(group.get_duration(12), 12.0); } #[test] fn get_duration_step_with_limit_adjusted() { let group = ColorEventGroup { filter: Filter { limit_behaviour: Some(LimitBehaviour::Beat), limit_percent: Some(0.5), ..Default::default() }, beat_dist_type: DistributionType::Step, beat_dist_value: 1.0, ..Default::default() }; assert_eq!(group.get_duration(12), 6.0); } #[test] fn get_duration_step_with_limit_zero() { let group = ColorEventGroup { filter: Filter { limit_percent: Some(0.0), ..Default::default() }, beat_dist_type: DistributionType::Step, beat_dist_value: 1.0, ..Default::default() }; assert_eq!(group.get_duration(12), 12.0); } }