evtc\effect/
effect51.rs

1use crate::extract::transmute_field;
2use crate::AgentId;
3use crate::{extract::Extract, Event, Position, StateChange, TryExtract};
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8/// Effect information from an [`Event`] with [`StateChange::Effect51`].
9#[derive(Debug, Clone)]
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11pub struct Effect51 {
12    /// Time of registering the effect.
13    pub time: u64,
14
15    /// Source of the effect.
16    pub source: AgentId,
17
18    /// Id of the effect.
19    ///
20    /// Use to map to a GUID using [`StateChange::IdToGUID`] events.
21    pub effect_id: u32,
22
23    /// Whether the effect is on a moving platform.
24    pub moving_platform: u8,
25
26    /// Location of the effect.
27    pub location: EffectLocation,
28
29    /// Duration of the effect in milliseconds.
30    pub duration: u32,
31
32    /// Trackable id for effect end.
33    pub tracking_id: u32,
34
35    /// Effect orientation.
36    pub orientation: EffectOrientation,
37}
38
39impl Effect51 {
40    /// Checks whether this is the end of an effect.
41    #[inline]
42    pub fn is_end(&self) -> bool {
43        self.effect_id == 0
44    }
45}
46
47impl Extract for Effect51 {
48    #[inline]
49    unsafe fn extract(event: &Event) -> Self {
50        let effect_id = event.skill_id;
51        let duration = transmute_field!(event.affinity as u32);
52        let tracking_id = transmute_field!(event.is_buffremove as u32);
53        let orientation = transmute_field!(event.is_shields as [i16; 3]);
54
55        Self {
56            time: event.time,
57            effect_id,
58            source: AgentId::from_src(event),
59            moving_platform: event.is_flanking,
60            location: EffectLocation::extract(event),
61            duration,
62            tracking_id,
63            orientation: orientation.into(),
64        }
65    }
66}
67
68impl TryExtract for Effect51 {
69    #[inline]
70    fn can_extract(event: &Event) -> bool {
71        event.get_statechange() == StateChange::Effect51
72    }
73}
74
75/// Location of an effect.
76#[derive(Debug, Clone, PartialEq)]
77#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
78pub enum EffectLocation {
79    Agent(u64),
80    Position(Position),
81}
82
83impl Extract for EffectLocation {
84    #[inline]
85    unsafe fn extract(event: &Event) -> Self {
86        if event.dst_agent != 0 {
87            Self::Agent(event.dst_agent)
88        } else {
89            let pos = transmute_field!(event.value as [f32; 3]);
90            Self::Position(pos.into())
91        }
92    }
93}
94
95/// Orientation of an effect.
96///
97/// Values represent rotation along each axis multiplied by `1000` or [`i16::MIN`]/[`i16::MAX`] if out of range.
98#[derive(Debug, Clone, PartialEq, Eq)]
99#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100pub struct EffectOrientation {
101    pub x: i16,
102    pub y: i16,
103    pub z: i16,
104}
105
106impl EffectOrientation {
107    /// Ratio between [`i16`] and [`f32`] representation.
108    pub const RATIO: f32 = 1000.0;
109
110    /// Maximum value in [`f32`] representation.
111    ///
112    /// For [`i16`] representation use [`i16::MAX`].
113    pub const MAX: f32 = i16::MAX as f32 / Self::RATIO;
114
115    /// Minimum value in [`f32`] representation.
116    ///
117    /// For [`i16`] representation use [`i16::MIN`].
118    pub const MIN: f32 = i16::MIN as f32 / Self::RATIO;
119
120    /// Creates a new effect orientation from radians in [`i16`] representation.
121    #[inline]
122    pub const fn new(x: i16, y: i16, z: i16) -> Self {
123        Self { x, y, z }
124    }
125
126    /// Creates a new effect orientation from radians in [`f32`] representation.
127    #[inline]
128    pub fn from_floats(x: f32, y: f32, z: f32) -> Self {
129        Self::new(Self::to_int(x), Self::to_int(y), Self::to_int(z))
130    }
131
132    /// Converts int to float.
133    #[inline]
134    pub fn to_float(int: i16) -> f32 {
135        int as f32 / Self::RATIO
136    }
137
138    /// Converts int to float.
139    #[inline]
140    pub fn to_int(float: f32) -> i16 {
141        (float * Self::RATIO).round() as i16
142    }
143
144    /// Converts the orientation to a [`Position`].
145    #[inline]
146    pub fn as_position(&self) -> Position {
147        Position::new(
148            Self::to_float(self.x),
149            Self::to_float(self.y),
150            Self::to_float(self.z),
151        )
152    }
153
154    /// Converts the orientation to a rotation matrix.
155    #[inline]
156    pub fn as_rotation_matrix(&self) -> [[f32; 3]; 3] {
157        self.as_position().as_rotation_matrix()
158    }
159
160    /// Rotates the [`Position`] vector.
161    #[inline]
162    pub fn rotate(&self, vector: Position) -> Position {
163        self.as_position().rotate(vector)
164    }
165}
166
167impl From<[i16; 3]> for EffectOrientation {
168    #[inline]
169    fn from(value: [i16; 3]) -> Self {
170        let [x, y, z] = value;
171        Self::new(x, y, z)
172    }
173}
174
175impl From<EffectOrientation> for [i16; 3] {
176    #[inline]
177    fn from(orientation: EffectOrientation) -> Self {
178        [orientation.x, orientation.y, orientation.z]
179    }
180}
181
182impl From<EffectOrientation> for Position {
183    #[inline]
184    fn from(orientation: EffectOrientation) -> Self {
185        orientation.as_position()
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn orientation() {
195        let orient = EffectOrientation::from_floats(12.345, 6.789, 0.0);
196        assert_eq!(orient, EffectOrientation::new(12345, 6789, 0));
197    }
198
199    #[test]
200    fn orientation_round() {
201        assert_eq!(EffectOrientation::to_int(30.9999), 31000);
202    }
203
204    #[test]
205    fn orientation_saturate() {
206        let orient = EffectOrientation::from_floats(12345.0, -6789.0, 0.0);
207        assert_eq!(orient, EffectOrientation::new(i16::MAX, i16::MIN, 0));
208    }
209}