Skip to main content

wowlab_engine_combat/context/
aura.rs

1//! Aura lifecycle operations exposed to combat hooks.
2
3use wowlab_types::sim::{ActorId, EnemyIdx, PetIdx, SimTime};
4
5use super::{AuraOps, HookCtx};
6use crate::{state::LocalAuraIdx, systems};
7
8impl HookCtx<'_> {
9    pub(crate) fn with_source_damage_flags(mut self, flags: systems::DamageFlags) -> Self {
10        self.source_damage_flags = flags;
11
12        self
13    }
14
15    pub(crate) fn with_effect_target(mut self, target: ActorId) -> Self {
16        self.effect_target = Some(target);
17
18        self
19    }
20
21    pub(crate) fn with_source_npc_id(mut self, npc_id: Option<u32>) -> Self {
22        self.source_npc_id = npc_id;
23
24        self
25    }
26
27    pub(super) fn source_flags(&self, flags: systems::DamageFlags) -> systems::DamageFlags {
28        flags | self.source_damage_flags
29    }
30
31    pub(super) fn resolved_pet_coefficient(
32        &self,
33        flags: systems::DamageFlags,
34        stat: wowlab_engine_domain::dbc::PetStatKind,
35        coefficient: f64,
36    ) -> f64 {
37        if !flags.contains(systems::DamageFlags::PET) {
38            return coefficient;
39        }
40
41        let totals = systems::accumulate_buffs(self.state, self.buf, None);
42        let (owner_attack_power, owner_spell_power) =
43            systems::dynamic_base_powers(self.state, self.buf, &totals);
44        let snapshot = systems::pet_stat_snapshot(self.state, self.source, self.source_npc_id);
45        let snapshot_ratio = match stat {
46            wowlab_engine_domain::dbc::PetStatKind::AttackPowerInheritance => {
47                snapshot.map_or(1.0, |snapshot| {
48                    if owner_attack_power.abs() <= f64::EPSILON {
49                        0.0
50                    } else {
51                        snapshot.attack_power / owner_attack_power
52                    }
53                })
54            }
55            wowlab_engine_domain::dbc::PetStatKind::SpellPowerInheritance => {
56                snapshot.map_or(1.0, |snapshot| {
57                    if owner_spell_power.abs() <= f64::EPSILON {
58                        0.0
59                    } else {
60                        snapshot.spell_power / owner_spell_power
61                    }
62                })
63            }
64            _ => 1.0,
65        };
66        let dbc_multiplier = self.source_npc_id.map_or(1.0, |npc_id| {
67            let data = &self.state.config.game_data;
68
69            match stat {
70                wowlab_engine_domain::dbc::PetStatKind::AttackPowerInheritance => {
71                    data.pet_attack_power_inheritance_mult(npc_id)
72                }
73                wowlab_engine_domain::dbc::PetStatKind::SpellPowerInheritance => {
74                    data.pet_spell_power_inheritance_mult(npc_id)
75                }
76                _ => 1.0,
77            }
78        });
79
80        coefficient * snapshot_ratio * dbc_multiplier
81    }
82
83    fn aura_local(&self, aura_id: u32) -> Option<LocalAuraIdx> {
84        self.state.aura_local(aura_id)
85    }
86}
87
88impl AuraOps for HookCtx<'_> {
89    /// Run target-dependent hook effects against one identity from a resolved hit set.
90    fn with_resolved_target(&mut self, target: EnemyIdx, apply: impl FnOnce(&mut Self)) {
91        if !self.state.is_valid_target(target) {
92            return;
93        }
94
95        let previous = self.target.replace(target);
96
97        apply(self);
98        self.target = previous;
99    }
100
101    #[inline]
102    fn now(&self) -> SimTime {
103        self.now
104    }
105
106    fn apply_aura(&mut self, local: u8) {
107        systems::apply_aura(self, LocalAuraIdx::new(local));
108    }
109
110    /// Apply a player-owned aura from a pet or guardian callback.
111    fn apply_owner_aura(&mut self, local: u8) {
112        let source = std::mem::replace(&mut self.source, ActorId::Player);
113
114        systems::apply_aura(self, LocalAuraIdx::new(local));
115        self.source = source;
116    }
117
118    /// Apply an aura using a duration computed by the current cast.
119    fn apply_aura_with_duration(&mut self, local: u8, duration_ms: u32) {
120        systems::apply_aura_with_duration(self, LocalAuraIdx::new(local), Some(duration_ms));
121    }
122
123    /// Apply a rolling periodic aura while scaling the damage contributed by this application.
124    fn apply_aura_with_rolling_multiplier(&mut self, local: u8, multiplier: f64) {
125        systems::apply_aura_with_rolling_multiplier(self, LocalAuraIdx::new(local), multiplier);
126    }
127
128    /// Apply an aura to the primary persistent pet from an owner callback.
129    fn apply_pet_aura_with_duration(&mut self, local: u8, duration_ms: u32) {
130        let source = std::mem::replace(&mut self.source, ActorId::Pet(PetIdx::PRIMARY));
131
132        systems::apply_aura_with_duration(self, LocalAuraIdx::new(local), Some(duration_ms));
133        self.source = source;
134    }
135
136    fn apply_aura_n(&mut self, local: u8, n: i32) {
137        for _ in 0..n.max(0) {
138            self.apply_aura(local);
139        }
140    }
141
142    /// Add one stack without refreshing an active aura's duration.
143    fn add_aura_stack(&mut self, local: u8) {
144        systems::add_aura_stack(self, LocalAuraIdx::new(local));
145    }
146
147    /// Add a stack to a player-owned aura from a pet or guardian callback.
148    fn add_owner_aura_stack(&mut self, local: u8) {
149        let source = std::mem::replace(&mut self.source, ActorId::Player);
150
151        systems::add_aura_stack(self, LocalAuraIdx::new(local));
152        self.source = source;
153    }
154
155    /// Add post-mitigation damage to an aura-local residual pool, applying or refreshing its draining periodic aura.
156    fn add_residual_damage(&mut self, local: u8, amount: f64) {
157        if amount <= 0.0 {
158            return;
159        }
160
161        let local = LocalAuraIdx::new(local);
162        let Some(key) = systems::aura_key_for(self.state, local, self.source, self.target) else {
163            return;
164        };
165
166        self.state
167            .runtime
168            .pools
169            .residual_damage_pools
170            .add(key, amount);
171        systems::apply_aura(self, local);
172    }
173
174    fn residual_damage_remaining(&self, local: u8) -> f64 {
175        let local = LocalAuraIdx::new(local);
176        let Some(key) = systems::aura_key_for(self.state, local, self.source, self.target) else {
177            return 0.0;
178        };
179
180        self.state
181            .runtime
182            .pools
183            .residual_damage_pools
184            .remaining(&key)
185    }
186
187    /// Add damage to an aura-local delayed accumulator.
188    fn accumulate_damage(&mut self, local: u8, amount: f64) {
189        if amount <= 0.0 {
190            return;
191        }
192
193        let local = LocalAuraIdx::new(local);
194        let Some(key) = systems::aura_key_for(self.state, local, self.source, self.target) else {
195            return;
196        };
197
198        self.state
199            .runtime
200            .pools
201            .accumulated_damage_pools
202            .add(key, amount);
203    }
204
205    /// Add companion damage to a player-owned delayed accumulator.
206    fn accumulate_owner_damage(&mut self, local: u8, amount: f64) {
207        let source = std::mem::replace(&mut self.source, ActorId::Player);
208
209        self.accumulate_damage(local, amount);
210        self.source = source;
211    }
212
213    /// Drain an aura-local delayed accumulator.
214    fn take_accumulated_damage(&mut self, local: u8) -> f64 {
215        let local = LocalAuraIdx::new(local);
216        let Some(key) = systems::aura_key_for(self.state, local, self.source, self.target) else {
217            return 0.0;
218        };
219
220        self.state.runtime.pools.accumulated_damage_pools.take(&key)
221    }
222
223    fn expire_aura(&mut self, local: u8) {
224        systems::expire_aura(self, LocalAuraIdx::new(local));
225    }
226
227    /// Expire a player-owned aura from a pet or guardian callback.
228    fn expire_owner_aura(&mut self, local: u8) {
229        let source = std::mem::replace(&mut self.source, ActorId::Player);
230
231        systems::expire_aura(self, LocalAuraIdx::new(local));
232        self.source = source;
233    }
234
235    fn consume_aura(&mut self, local: u8) -> bool {
236        systems::consume_aura(self, LocalAuraIdx::new(local))
237    }
238
239    fn consume_aura_stack(&mut self, local: u8) -> bool {
240        systems::consume_aura_stack(self, LocalAuraIdx::new(local))
241    }
242
243    /// Consume a stack from a player-owned aura from a pet or guardian callback.
244    fn consume_owner_aura_stack(&mut self, local: u8) -> bool {
245        let source = std::mem::replace(&mut self.source, ActorId::Player);
246        let consumed = systems::consume_aura_stack(self, LocalAuraIdx::new(local));
247
248        self.source = source;
249
250        consumed
251    }
252
253    fn consume_aura_stacks(&mut self, local: u8, count: i32) -> i32 {
254        systems::consume_aura_stacks(self, LocalAuraIdx::new(local), count)
255    }
256
257    fn refresh_aura_snapshot_multiplier(&mut self, local: u8, multiplier: f64) -> bool {
258        systems::refresh_aura_snapshot_multiplier(self, LocalAuraIdx::new(local), multiplier)
259    }
260
261    /// Extend an active aura's expiry by a flat `ms` (no pandemic recompute).
262    fn extend_aura(&mut self, local: u8, ms: u32) {
263        systems::extend_aura(self, LocalAuraIdx::new(local), ms);
264    }
265
266    /// Shorten an active aura's expiry by a flat `ms`, expiring it immediately when exhausted.
267    fn reduce_aura(&mut self, local: u8, ms: u32) {
268        systems::reduce_aura(self, LocalAuraIdx::new(local), ms);
269    }
270
271    fn is_aura_active(&self, local: u8) -> bool {
272        systems::is_aura_active(
273            &self.view().combat(),
274            LocalAuraIdx::new(local),
275            self.source,
276            self.target,
277        )
278    }
279
280    /// Query a player-applied aura from a pet or guardian callback.
281    fn is_owner_aura_active(&self, local: u8) -> bool {
282        systems::is_aura_active(
283            &self.view().combat(),
284            LocalAuraIdx::new(local),
285            ActorId::Player,
286            self.target,
287        )
288    }
289
290    fn aura_stacks(&self, local: u8) -> i32 {
291        systems::aura_stacks(
292            self.state,
293            self.buf,
294            LocalAuraIdx::new(local),
295            self.source,
296            self.target,
297        )
298    }
299
300    /// Remaining lifetime of the exact aura instance in this hook context.
301    ///
302    /// Returns `None` when inactive and `u32::MAX` for a permanent aura.
303    fn aura_remaining_ms(&self, local: u8) -> Option<u32> {
304        systems::aura_remaining_ms(
305            self.state,
306            self.buf,
307            LocalAuraIdx::new(local),
308            self.source,
309            self.target,
310            self.now,
311        )
312    }
313
314    /// Remaining pre-modifier damage in an active flat-damage periodic.
315    fn flat_periodic_damage_remaining(&self, local: u8) -> Option<f64> {
316        let local = LocalAuraIdx::new(local);
317        let aura = self.state.defs.auras.get(local.as_usize())?;
318        let periodic = aura.periodic?;
319
320        let amount = match periodic.effect {
321            crate::state::PeriodicKind::FlatDamage { amount, .. } => amount,
322            crate::state::PeriodicKind::RollingFlatDamage { amount, .. } => {
323                let key = systems::aura_key_for(self.state, local, self.source, self.target)?;
324
325                amount * self.state.runtime.pools.rolling_tick_mult.mult(&key)
326            }
327            _ => return None,
328        };
329
330        let key = systems::aura_key_for(self.state, local, self.source, self.target)?;
331        let slot = self.buf.aura(key)?;
332
333        if !slot.is_active(self.now.as_secs_f64()) {
334            return None;
335        }
336
337        let remaining_s = (slot.expires_at - self.now.as_secs_f64()).max(0.0);
338        let tick_interval_s = slot.tick_interval.max(f64::EPSILON);
339        let stack_multiplier = if aura.periodic_damage_scales_with_stacks {
340            f64::from(slot.stacks.max(1))
341        } else {
342            1.0
343        };
344
345        Some(amount * remaining_s / tick_interval_s * stack_multiplier)
346    }
347
348    fn aura_local_index(&self, aura_id: u32) -> Option<u8> {
349        self.aura_local(aura_id).map(|local| local.0)
350    }
351
352    fn apply_aura_id(&mut self, aura_id: u32) {
353        if let Some(local) = self.aura_local(aura_id) {
354            self.apply_aura(local.0);
355        }
356    }
357
358    fn apply_aura_id_n(&mut self, aura_id: u32, n: i32) {
359        if let Some(local) = self.aura_local(aura_id) {
360            self.apply_aura_n(local.0, n);
361        }
362    }
363
364    fn add_aura_stack_id(&mut self, aura_id: u32) {
365        if let Some(local) = self.aura_local(aura_id) {
366            self.add_aura_stack(local.0);
367        }
368    }
369
370    fn expire_aura_id(&mut self, aura_id: u32) {
371        if let Some(local) = self.aura_local(aura_id) {
372            self.expire_aura(local.0);
373        }
374    }
375
376    fn is_aura_active_id(&self, aura_id: u32) -> bool {
377        self.aura_local(aura_id)
378            .is_some_and(|local| self.is_aura_active(local.0))
379    }
380
381    fn aura_stacks_id(&self, aura_id: u32) -> i32 {
382        self.aura_local(aura_id)
383            .map_or(0, |local| self.aura_stacks(local.0))
384    }
385
386    fn flat_periodic_damage_remaining_id(&self, aura_id: u32) -> Option<f64> {
387        self.aura_local(aura_id)
388            .and_then(|local| self.flat_periodic_damage_remaining(local.0))
389    }
390
391    fn take_aura_stacks_id(&mut self, aura_id: u32) -> i32 {
392        let Some(local) = self.aura_local(aura_id) else {
393            return 0;
394        };
395
396        systems::take_aura_stacks(self, local)
397    }
398}