Skip to main content

wowlab_engine_combat/
travel.rs

1//! Shared projectile travel-time resolution.
2
3use 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    /// DBC projectile velocity in yards per second, or seconds when fixed travel is set.
9    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;