wowlab_engine_combat/
travel.rs1use wowlab_types::constants::MS_PER_SECOND;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
6pub(crate) struct SpellTravelData {
7 pub(crate) explicit_fixed_ms: Option<u32>,
8 pub(crate) projectile_speed: f64,
10 pub(crate) launch_delay_s: f64,
11 pub(crate) dbc_fixed_travel_time: bool,
12}
13
14impl SpellTravelData {
15 #[must_use]
16 pub(crate) fn has_projectile_timing(self) -> bool {
17 self.explicit_fixed_ms.is_some()
18 || self.dbc_fixed_travel_time
19 || self.projectile_speed > 0.0
20 || self.launch_delay_s > 0.0
21 }
22
23 #[must_use]
24 pub(crate) const fn is_fixed(self) -> bool {
25 self.explicit_fixed_ms.is_some() || self.dbc_fixed_travel_time
26 }
27}
28
29#[must_use]
30pub(crate) fn launch_offset_ms(travel: SpellTravelData) -> u32 {
31 if travel.is_fixed() {
32 return 0;
33 }
34
35 ceil_seconds_to_millis(nonnegative_finite(travel.launch_delay_s))
36}
37
38#[must_use]
39pub(crate) fn impact_offset_ms(travel: SpellTravelData, locked_distance: f64) -> u32 {
40 if let Some(explicit_fixed_ms) = travel.explicit_fixed_ms {
41 return explicit_fixed_ms;
42 }
43
44 if travel.dbc_fixed_travel_time {
45 return ceil_seconds_to_millis(nonnegative_finite(travel.projectile_speed));
46 }
47
48 let launch_delay_s = nonnegative_finite(travel.launch_delay_s);
49 let distance_s = if travel.projectile_speed > 0.0 && travel.projectile_speed.is_finite() {
50 nonnegative_finite(locked_distance) / travel.projectile_speed
51 } else {
52 0.0
53 };
54
55 ceil_seconds_to_millis(launch_delay_s + distance_s)
56}
57
58fn nonnegative_finite(value: f64) -> f64 {
59 if value.is_finite() && value > 0.0 {
60 value
61 } else {
62 0.0
63 }
64}
65
66fn ceil_seconds_to_millis(seconds: f64) -> u32 {
67 let millis = (seconds * MS_PER_SECOND).ceil();
68
69 if millis >= f64::from(u32::MAX) {
70 u32::MAX
71 } else {
72 wowlab_types::numeric::f64_to_u32_saturating_trunc(millis)
73 }
74}
75
76#[cfg(test)]
77mod tests;