Skip to main content

wowlab_engine_combat/handler/spec_handler/
auras.rs

1use 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        // #t(rust_unchecked_indexing) local is carried by the finalized aura event.
42        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}