wowlab_engine_combat/handler/spec_handler/
auras.rs1use super::{
2 ActorId, AuraEventRef, CombatCtx, CombatHandler, Event, HookCtxRequest, SimContext, SimTime,
3 SpellIdx, drain_deferred_work, process_single_aura_tick_for,
4};
5
6impl CombatHandler {
7 pub(super) fn on_aura_tick_impl(&mut self, event: AuraEventRef, ctx: &mut SimContext) {
8 let AuraEventRef { key, target } = event;
9 let now = ctx.state.current_time;
10
11 let resolved_target = crate::systems::periodic_tick_target(&self.state, key, target);
12 let deferred_target = target.or_else(|| match key.affected() {
13 ActorId::Enemy(enemy) => Some(enemy),
14 _ => None,
15 });
16
17 self.with_hook_ctx(
18 &mut *ctx.telemetry,
19 HookCtxRequest::for_optional_target(now, deferred_target).with_source(key.source()),
20 |hook| {
21 let processed = CombatCtx::for_optional_target(hook, resolved_target, |combat| {
22 process_single_aura_tick_for(combat, key);
23 });
24
25 if processed.is_none() {
26 drain_deferred_work(hook);
27 }
28 },
29 );
30 }
31
32 pub(super) fn on_aura_expire_impl(&mut self, event: AuraEventRef, ctx: &mut SimContext) {
33 let AuraEventRef { key, target } = event;
34 let now = ctx.state.current_time;
35
36 let Some(local) = self.state.aura_local_for_key(key) else {
37 return;
38 };
39
40 if self.state.defs.auras[local.as_usize()].async_stacks {
42 self.with_hook_ctx(
43 &mut *ctx.telemetry,
44 HookCtxRequest::for_optional_target(now, target)
45 .with_source(key.source())
46 .with_effect_target(key.affected()),
47 |hook| {
48 crate::systems::expire_async_stacks_due(hook, local, key);
49 drain_deferred_work(hook);
50 },
51 );
52
53 return;
54 }
55
56 let should_expire = self
57 .buf
58 .aura(key)
59 .is_some_and(|a| a.expires_at > 0.0 && now >= SimTime::from_secs_f64(a.expires_at));
60
61 if should_expire {
62 let aura = self
63 .state
64 .defs
65 .auras
66 .get(local.as_usize())
67 .copied()
68 .expect("aura expiry carries a registered local index");
69
70 self.with_hook_ctx(
71 &mut *ctx.telemetry,
72 HookCtxRequest::for_optional_target(now, target)
73 .with_source(key.source())
74 .with_effect_target(key.affected()),
75 |hook| {
76 let fired = CombatCtx::for_optional_target(hook, target, |combat| {
77 crate::systems::fire_partial_tick_on_expiry_for(combat, local, key);
78 });
79
80 if fired.is_none() {
81 crate::systems::fire_partial_tick_on_expiry_without_target(
82 hook, local, key,
83 );
84 }
85
86 crate::systems::expire_aura(hook, local);
87 crate::systems::reapply_if_flagged(hook, local);
88 drain_deferred_work(hook);
89 },
90 );
91
92 if aura.expire_trigger_spell_id != 0 {
93 let source = if aura.expire_trigger_from_caster {
94 key.source()
95 } else {
96 key.affected()
97 };
98
99 self.state.schedule(Event::TriggeredSpell {
100 t: now,
101 spell_id: SpellIdx::from_raw(aura.expire_trigger_spell_id),
102 source,
103 target: key.affected(),
104 });
105 }
106 }
107 }
108
109 pub(super) fn on_external_buff_impl(&mut self, index: usize, ctx: &mut SimContext) {
110 self.handle_external_buff(index, ctx);
111 }
112}