wowlab_engine_combat/systems/
auras.rs1use wowlab_engine_domain::{dbc::SpellAuraInterruptFlags, rotation::DenseBuffer};
4use wowlab_engine_ports::Event;
5use wowlab_types::{
6 constants::HUNDRED,
7 sim::{ActorId, AuraKey, EnemyIdx, SimTime, SpellIdx},
8};
9
10use super::{
11 EffectExecution, async_stacks, attempt_weapon_enchants, execute_effect_range_for_actor,
12 execute_proc_effect_range_for_actor, fire_aura_application_procs,
13 process_health_threshold_application, procs, refresh_pet_actions_after_aura_change,
14};
15use crate::{
16 context::HookCtx,
17 state::{ActiveAbsorb, BuffEffect, CombatState, LocalAuraIdx, SpellGroup, SpellGroupRule},
18};
19
20pub(crate) mod apply;
21mod breaks;
22mod copy;
23mod deferred;
24pub(crate) mod expire;
25pub(crate) mod stacks;
26
27use apply::enqueue_aura_application_proc;
28#[cfg(test)]
29use apply::fixed_tick_periodic_duration_ms;
30#[cfg(test)]
31use apply::schedule_aura_expiry;
32pub(crate) use apply::{apply_aura, apply_aura_with_duration, apply_aura_with_rolling_multiplier};
33pub(crate) use breaks::{
34 break_auras_on_actor, break_auras_without_telemetry, break_stealth_if_active,
35};
36pub(crate) use copy::aura_remaining_ms;
37#[cfg(test)]
38pub(crate) use copy::copy_target_aura;
39pub(crate) use deferred::drain_deferred_work;
40#[cfg(test)]
41pub(crate) use expire::expire_aura_by_id;
42pub(crate) use expire::{expire_aura, expire_auras_on_actor};
43use expire::{expire_aura_instance, refresh_absorbs};
44pub(crate) use stacks::{
45 add_aura_stack, aura_stacks, aura_stacks_by_id, bypass_aura_active, consume_aura,
46 consume_aura_stack, consume_aura_stack_by_id, consume_aura_stacks, extend_aura, is_aura_active,
47 reduce_aura, take_aura_stacks,
48};
49
50mod application_state;
51
52#[cfg(test)]
53pub(crate) use application_state::capture_snapshot_fixture;
54#[cfg(test)]
55use application_state::pandemic_carry;
56#[cfg(test)]
57use application_state::pandemic_carry_ms;
58pub(crate) use application_state::{
59 aura_key_for, aura_keys_for_actor_application, aura_keys_for_application, capture_snapshot_for,
60 emit_aura_apply_event, emit_aura_refresh_event, refresh_aura_projections,
61 refresh_aura_snapshot_multiplier,
62};
63use application_state::{
64 aura_query_key_for, combine_rolling_multiplier, context_aura_key, emit_aura_expire_event,
65 expire_conflicting_auras, initialize_or_revive_periodic, is_rolling_periodic, refresh_snapshot,
66 refreshed_stacks, restore_active_shapeshift_form, revive_tick_chain, rolling_ticks_left,
67 sync_exclusive_group_enabled, synchronize_aura_dependents, update_aura_slot,
68};
69
70const MAX_DEFERRED_WORK_ITEMS: usize = 1_024;
71const MAX_AURA_APPLICATION_ACTORS: usize = 2;
72const PERMANENT_AURA_EXPIRES_AT_S: f64 = 1.0e12;
73
74#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76#[cfg(test)]
77#[non_exhaustive]
78pub(crate) enum DotCopyBehavior {
79 Start,
81 CloneNoRefresh,
83}
84
85pub(super) fn reproject_aura_time_rates(ctx: &mut HookCtx<'_>) {
86 let keys: Vec<_> = ctx.buf.aura_keys().collect();
87
88 for key in keys {
89 let Some(slot) = ctx
90 .buf
91 .aura(key)
92 .copied()
93 .filter(|slot| slot.is_active(ctx.now.as_secs_f64()))
94 else {
95 continue;
96 };
97 let Some(local) = ctx.state.aura_local_for_key(key) else {
98 continue;
99 };
100 let aura = ctx.state.defs.auras[local.as_usize()];
102 let desired = crate::systems::buffs::aura_time_rate_multiplier(
103 ctx.view().for_actor(key.affected()),
104 aura.aura_id,
105 );
106 let current = if slot.time_rate_multiplier > 0.0 {
107 slot.time_rate_multiplier
108 } else {
109 1.0
110 };
111 let ratio = desired / current;
112
113 if (ratio - 1.0).abs() <= f64::EPSILON {
114 continue;
115 }
116
117 let now_s = ctx.now.as_secs_f64();
118
119 if let Some(active) = ctx.buf.aura_mut(key) {
120 if active.expires_at < PERMANENT_AURA_EXPIRES_AT_S {
121 active.expires_at = now_s + (active.expires_at - now_s).max(0.0) * ratio;
122 }
123
124 if active.tick_interval > 0.0 {
125 active.next_tick = now_s + (active.next_tick - now_s).max(0.0) * ratio;
126 active.tick_interval *= ratio;
127 }
128
129 active.time_rate_multiplier = desired;
130 }
131
132 if let Some(active) = ctx.buf.aura(key) {
133 if active.expires_at < PERMANENT_AURA_EXPIRES_AT_S {
134 ctx.state.schedule(Event::AuraExpire {
135 t: SimTime::from_secs_f64(active.expires_at),
136 key,
137 target: match key.affected() {
138 ActorId::Enemy(enemy) => Some(enemy),
139 _ => None,
140 },
141 });
142 }
143
144 if active.tick_interval > 0.0 && active.next_tick <= active.expires_at {
145 ctx.state.schedule(Event::AuraTick {
146 t: SimTime::from_secs_f64(active.next_tick),
147 key,
148 target: match key.affected() {
149 ActorId::Enemy(enemy) => Some(enemy),
150 _ => None,
151 },
152 });
153 }
154 }
155 }
156}
157
158#[derive(Clone, Copy)]
159pub(crate) enum AuraBreakTrigger {
160 Action,
161 Damage { periodic: bool },
162}
163
164#[cfg(test)]
165#[path = "auras/tests.rs"]
166mod tests;