Skip to main content

wowlab_engine_combat/state/
damage.rs

1/// DBC coordinates of an effect used as a damage payload or targeting geometry.
2#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3#[must_use]
4pub struct DamageEffectRef {
5    pub spell_id: u32,
6    pub effect_index: u8,
7}
8
9impl DamageEffectRef {
10    #[inline]
11    pub const fn new(spell_id: u32, effect_index: u8) -> Self {
12        Self {
13            spell_id,
14            effect_index,
15        }
16    }
17}
18
19/// Weapon-DPS composite used by an AP coefficient.
20#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
21#[non_exhaustive]
22#[must_use]
23pub enum WeaponApType {
24    #[default]
25    MainHand,
26    OffHand,
27    /// `AP + (mh_wdps + oh_wdps / 2) * 2/3 * 6` (e.g. Between the Eyes, Pistol Shot).
28    Both,
29    None,
30}
31
32/// Closed damage representation stored by the combat runtime.
33///
34/// ```compile_fail
35/// use wowlab_engine_combat::RuntimeDamageDef;
36/// use wowlab_engine_gamedata::ResolvedDamageDef;
37///
38/// fn runtime_only(_: RuntimeDamageDef) {}
39/// fn invalid(resolved: ResolvedDamageDef) {
40///     runtime_only(resolved);
41/// }
42/// ```
43#[derive(Clone, Copy, Debug, Default)]
44#[non_exhaustive]
45#[must_use]
46pub enum RuntimeDamageDef {
47    #[default]
48    None,
49    Flat(f64),
50    ApCoefficient {
51        coef: f64,
52        is_physical: bool,
53        ap_type: WeaponApType,
54    },
55    SpCoefficient {
56        coef: f64,
57        is_physical: bool,
58    },
59    Weapon {
60        multiplier: f64,
61        flat_bonus: f64,
62        normalized: bool,
63        is_physical: bool,
64        hand: WeaponApType,
65    },
66}
67
68impl RuntimeDamageDef {
69    /// Scale the direct damage by a cast-time multiplier (e.g. combo points spent).
70    #[inline]
71    pub fn scaled(self, mult: f64) -> Self {
72        match self {
73            Self::None => Self::None,
74            Self::Flat(amount) => Self::Flat(amount * mult),
75            Self::ApCoefficient {
76                coef,
77                is_physical,
78                ap_type,
79            } => Self::ApCoefficient {
80                coef: coef * mult,
81                is_physical,
82                ap_type,
83            },
84            Self::SpCoefficient { coef, is_physical } => Self::SpCoefficient {
85                coef: coef * mult,
86                is_physical,
87            },
88            Self::Weapon {
89                multiplier,
90                flat_bonus,
91                normalized,
92                is_physical,
93                hand,
94            } => Self::Weapon {
95                multiplier: multiplier * mult,
96                flat_bonus: flat_bonus * mult,
97                normalized,
98                is_physical,
99                hand,
100            },
101        }
102    }
103}
104
105pub(crate) fn resolved_damage_base_points(
106    def: wowlab_engine_gamedata::ResolvedDamageDef,
107    multiplier: f64,
108) -> f64 {
109    use wowlab_engine_gamedata::ResolvedDamageKind;
110
111    if matches!(def.kind, ResolvedDamageKind::Ap | ResolvedDamageKind::Sp) {
112        def.base_points * multiplier
113    } else {
114        0.0
115    }
116}
117
118pub(crate) const MAX_CDR_EFFECTS: usize = 4;
119
120/// Exclusive ownership category used by caster-scoped target modifiers.
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122#[non_exhaustive]
123pub enum DamageSource {
124    Player,
125    Pet,
126    Guardian,
127}
128
129/// One active-aura rule that copies resolved impact damage to selected targets.
130#[derive(Clone, Copy, Debug, PartialEq)]
131pub struct DamageReplication {
132    pub fraction: f64,
133    pub effect: DamageEffectRef,
134    pub source: DamageSource,
135    /// Restrict eligible source impacts by reported spell id; empty accepts every spell.
136    pub source_spells: &'static [u32],
137    /// Whether non-physical source impacts can be copied.
138    pub include_magic: bool,
139    /// Whether `AoE` and explicitly-targeted child impacts can be copied.
140    pub include_indirect: bool,
141    /// Whether the resolved source target also receives a copied hit.
142    pub include_primary: bool,
143    /// Maximum copied targets; zero means unlimited.
144    pub max_targets: u8,
145    /// Soft-cap target count; zero disables target-count reduction.
146    pub reduced_targets: u8,
147}
148
149/// Stage at which a target-side damage transfer is evaluated.
150#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151#[non_exhaustive]
152pub enum DamageTransferTiming {
153    /// Aura 81: remove the portion before target mitigation and damage absorbs.
154    PreMitigationSplit,
155    /// Aura 300: copy the resolved portion without reducing the original target's damage.
156    PostMitigationShare,
157}
158
159/// Target-side split/share rule whose recipient is the aura caster.
160#[derive(Clone, Copy, Debug, PartialEq)]
161pub struct DamageTransfer {
162    pub fraction: f64,
163    pub schools: wowlab_engine_domain::dbc::SpellSchoolMask,
164    pub timing: DamageTransferTiming,
165}
166
167/// Availability behavior contributed by an active DBC aura effect.
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
169#[non_exhaustive]
170pub enum ActiveSpellGate {
171    /// Waive aura-state requirements for affected spells while the aura is active (aura 262).
172    IgnoreAuraState,
173    /// Disable affected spells while the aura is active (aura 263).
174    DisableAbilities,
175}
176
177/// Direction used by aura 468 when an actor crosses a health threshold.
178#[derive(Clone, Copy, Debug, Eq, PartialEq)]
179#[non_exhaustive]
180pub enum HealthThresholdDirection {
181    Above,
182    Below,
183}
184
185/// Bitflag set carrying the boolean coordinates of a damage hit (physical/pet/periodic/crit).
186#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
187#[repr(transparent)]
188#[must_use]
189#[cfg(test)]
190pub(crate) struct HitFlags(u8);
191
192#[cfg(test)]
193impl HitFlags {
194    pub(crate) const PHYSICAL: u8 = 1 << 0;
195    pub(crate) const PET: u8 = 1 << 1;
196    pub(crate) const CRIT: u8 = 1 << 3;
197
198    #[inline]
199    pub(crate) const fn from_bits(b: u8) -> Self {
200        Self(b)
201    }
202
203    #[inline]
204    #[must_use]
205    pub(crate) const fn bits(self) -> u8 {
206        self.0
207    }
208
209    #[inline]
210    #[must_use]
211    pub(crate) const fn contains(self, mask: u8) -> bool {
212        (self.0 & mask) != 0
213    }
214}
215
216#[cfg(test)]
217#[path = "damage/tests.rs"]
218mod tests;