Skip to main content

wowlab_engine_combat/state/defs/
drivers.rs

1use wowlab_engine_domain::rotation::WeaponImbueSlotHandle;
2
3use super::{BuffEffect, CastHookFn, HookView, LocalAuraIdx, LocalRppmIdx, LocalSpellIdx};
4
5/// Automatically casts `spell_local` after a primary-resource spend leaves the actor below `threshold`.
6#[derive(Clone, Copy, Debug, PartialEq)]
7#[must_use]
8pub(crate) struct ResourceThresholdTrigger {
9    pub resource_type: wowlab_types::combat::ResourceType,
10    pub threshold: f64,
11    pub spell_local: LocalSpellIdx,
12}
13
14/// One equipped weapon-enchant RPPM source and the aura it applies.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16#[must_use]
17pub struct WeaponEnchantBinding {
18    pub enchantment_id: u32,
19    pub buff_spell_id: u32,
20}
21
22/// Finalized dense coordinates for one equipped weapon-enchant proc.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24#[must_use]
25pub(crate) struct ResolvedWeaponEnchantBinding {
26    pub(crate) rppm: LocalRppmIdx,
27    pub(crate) buff_aura: LocalAuraIdx,
28}
29
30/// A temporary weapon-imbue action and its rotation-state key.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32#[must_use]
33pub(crate) struct WeaponImbueBinding {
34    pub spell_id: u32,
35    pub key: &'static str,
36}
37
38/// Finalized local spell and buffer coordinates for one temporary weapon imbue.
39#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40#[must_use]
41pub(crate) struct ResolvedWeaponImbueBinding {
42    pub spell_local: LocalSpellIdx,
43    pub slot: WeaponImbueSlotHandle,
44}
45
46/// Per-damage-event spell modifiers owned by a real spell or passive driver.
47#[derive(Clone, Copy, Debug)]
48#[must_use]
49pub struct DriverSpellModifier {
50    pub(crate) driver_spell_id: u32,
51    pub(crate) spells: &'static [u32],
52    pub(crate) resolve: DriverSpellModifierFn,
53}
54
55/// An always-present player effect owned by a real spell or passive driver.
56///
57/// Unlike an aura, this has no duration, stacks, dispel state, or buffer slot.
58#[derive(Clone, Copy, Debug)]
59#[must_use]
60pub struct PassiveDriverEffect {
61    pub(crate) driver_spell_id: u32,
62    pub(crate) effect: BuffEffect,
63}
64
65impl PassiveDriverEffect {
66    pub const fn new(driver_spell_id: u32, effect: BuffEffect) -> Self {
67        Self {
68            driver_spell_id,
69            effect,
70        }
71    }
72}
73
74/// Encounter-lifetime periodic work owned by a real spell or passive driver.
75///
76/// This is not an aura: it has no stacks, duration, dispel state, or buffer projection.
77#[derive(Clone, Copy, Debug)]
78#[must_use]
79pub struct PeriodicDriver {
80    pub(crate) driver_spell_id: u32,
81    pub(crate) tick_interval_ms: std::num::NonZeroU32,
82    pub(crate) triggered_duration_ms: Option<std::num::NonZeroU32>,
83    pub(crate) hasted_ticks: bool,
84    pub(crate) tick: CastHookFn,
85}
86
87impl PeriodicDriver {
88    /// Create an encounter-lifetime periodic driver.
89    ///
90    /// # Panics
91    ///
92    /// Panics when `tick_interval_ms` is zero.
93    pub const fn new(driver_spell_id: u32, tick_interval_ms: u32, tick: CastHookFn) -> Self {
94        let Some(tick_interval_ms) = std::num::NonZeroU32::new(tick_interval_ms) else {
95            panic!("periodic driver tick interval must be non-zero");
96        };
97
98        Self {
99            driver_spell_id,
100            tick_interval_ms,
101            triggered_duration_ms: None,
102            hasted_ticks: false,
103            tick,
104        }
105    }
106
107    pub const fn hasted(mut self) -> Self {
108        self.hasted_ticks = true;
109
110        self
111    }
112
113    /// Run only after explicit activation, stopping when the resolved duration elapses.
114    ///
115    /// # Panics
116    ///
117    /// Panics when `duration_ms` is zero.
118    pub const fn triggered_for(mut self, duration_ms: u32) -> Self {
119        let Some(duration_ms) = std::num::NonZeroU32::new(duration_ms) else {
120            panic!("triggered periodic driver duration must be non-zero");
121        };
122
123        self.triggered_duration_ms = Some(duration_ms);
124
125        self
126    }
127}
128
129pub(crate) type DriverSpellModifierFn = fn(HookView<'_>) -> DriverSpellModifierResult;
130
131/// Identity-based adjustments returned by a [`DriverSpellModifier`].
132#[derive(Clone, Copy, Debug, PartialEq)]
133#[must_use]
134pub struct DriverSpellModifierResult {
135    pub damage_mult: f64,
136    pub target_damage_mult: f64,
137    pub crit_chance_pct: f64,
138    pub crit_damage_mult: f64,
139}
140
141impl DriverSpellModifierResult {
142    pub const fn damage_mult(damage_mult: f64) -> Self {
143        Self {
144            damage_mult,
145            target_damage_mult: 1.0,
146            crit_chance_pct: 0.0,
147            crit_damage_mult: 1.0,
148        }
149    }
150
151    pub const fn crit_chance(crit_chance_pct: f64) -> Self {
152        Self {
153            damage_mult: 1.0,
154            target_damage_mult: 1.0,
155            crit_chance_pct,
156            crit_damage_mult: 1.0,
157        }
158    }
159}
160
161impl Default for DriverSpellModifierResult {
162    fn default() -> Self {
163        Self {
164            damage_mult: 1.0,
165            target_damage_mult: 1.0,
166            crit_chance_pct: 0.0,
167            crit_damage_mult: 1.0,
168        }
169    }
170}
171
172impl DriverSpellModifier {
173    pub const fn new(
174        driver_spell_id: u32,
175        spells: &'static [u32],
176        resolve: DriverSpellModifierFn,
177    ) -> Self {
178        Self {
179            driver_spell_id,
180            spells,
181            resolve,
182        }
183    }
184}