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