wowlab_engine_combat/handler/spec_handler/
auras.rs1use super::{
2 ActorId, AuraEventRef, CombatCtx, CombatHandler, Event, HookCtxRequest, SimContext, SimTime,
3 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 Some((local, key, target)) = self.program.validate_aura_event(event.binding()) else {
9 return;
10 };
11 let now = ctx.state.current_time;
12
13 let resolved_target =
14 crate::systems::periodic_tick_target(&self.program, &self.instance, local, key, target);
15 let deferred_target = target.or_else(|| match key.affected() {
16 ActorId::Enemy(enemy) => Some(enemy),
17 _ => None,
18 });
19
20 self.with_hook_ctx(
21 &mut *ctx.telemetry,
22 HookCtxRequest::for_optional_target(now, deferred_target).with_source(key.source()),
23 |hook| {
24 let processed = CombatCtx::for_optional_target(hook, resolved_target, |combat| {
25 process_single_aura_tick_for(combat, local, key);
26 });
27
28 if processed.is_none() {
29 drain_deferred_work(hook);
30 }
31 },
32 );
33 }
34
35 pub(super) fn on_aura_expire_impl(&mut self, event: AuraEventRef, ctx: &mut SimContext) {
36 let Some((local, key, target)) = self.program.validate_aura_event(event.binding()) else {
37 return;
38 };
39 let now = ctx.state.current_time;
40
41 if self.program.auras[local.as_usize()].async_stacks {
43 self.with_hook_ctx(
44 &mut *ctx.telemetry,
45 HookCtxRequest::for_optional_target(now, target)
46 .with_source(key.source())
47 .with_effect_target(key.affected()),
48 |hook| {
49 crate::systems::expire_async_stacks_due(hook, local, key);
50 drain_deferred_work(hook);
51 },
52 );
53
54 return;
55 }
56
57 let should_expire = self
58 .program
59 .buffer_aura(&self.instance, local, key)
60 .is_some_and(|a| a.expires_at > 0.0 && now >= SimTime::from_secs_f64(a.expires_at));
61
62 if should_expire {
63 let aura = self
64 .program
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 let Some(spell_local) = self
93 .program
94 .aura_expire_trigger_spells
95 .get(local.as_usize())
96 .copied()
97 .flatten()
98 {
99 let source = if aura.expire_trigger_from_caster {
100 key.source()
101 } else {
102 key.affected()
103 };
104
105 self.instance.schedule(Event::TriggeredSpell {
106 t: now,
107 spell: self.program.scheduled_spell(spell_local),
108 source,
109 target: key.affected(),
110 });
111 }
112 }
113 }
114
115 pub(super) fn on_external_buff_impl(
116 &mut self,
117 buff: wowlab_engine_ports::ExternalBuffBinding,
118 ctx: &mut SimContext,
119 ) {
120 let Some(index) = self
121 .program
122 .runtime_events
123 .validate_external_buff(buff)
124 .map(usize::from)
125 .filter(|index| *index < self.program.config.external_buffs.len())
126 else {
127 return;
128 };
129
130 self.handle_external_buff(index, ctx);
131 }
132}