wowlab_engine_combat/systems/auras/
copy.rs1use super::{
2 ActorId, CombatState, DenseBuffer, EnemyIdx, LocalAuraIdx, PERMANENT_AURA_EXPIRES_AT_S,
3 SimTime, aura_key_for,
4};
5#[cfg(test)]
6use super::{
7 DotCopyBehavior, Event, HookCtx, emit_aura_apply_event, enqueue_aura_application_proc,
8 schedule_aura_expiry, synchronize_aura_dependents,
9};
10
11#[cfg(test)]
16pub(crate) fn copy_target_aura(
17 ctx: &mut HookCtx<'_>,
18 local: LocalAuraIdx,
19 source_target: EnemyIdx,
20 destination_target: EnemyIdx,
21 behavior: DotCopyBehavior,
22) -> bool {
23 let aura = ctx.state.defs.auras[local.as_usize()];
25
26 if aura.on != wowlab_types::sim::AuraOn::Target {
27 return false;
28 }
29
30 let Some(source_key) = aura_key_for(ctx.state, local, ctx.source, Some(source_target)) else {
31 return false;
32 };
33 let Some(source_slot) = ctx.buf.aura(source_key).copied() else {
34 return false;
35 };
36
37 if !source_slot.is_active(ctx.now.as_secs_f64()) {
38 return false;
39 }
40
41 let Some(destination_key) =
42 aura_key_for(ctx.state, local, ctx.source, Some(destination_target))
43 else {
44 return false;
45 };
46
47 ctx.buf.ensure_aura_slot(destination_key);
48
49 let now_s = ctx.now.as_secs_f64();
50 let source_remaining = source_slot.expires_at - now_s;
51 let cloned_duration = match behavior {
52 DotCopyBehavior::Start | DotCopyBehavior::CloneNoRefresh => source_remaining,
53 };
54
55 let mut destination_slot = source_slot;
56
57 destination_slot.expires_at = now_s + cloned_duration;
58
59 if behavior == DotCopyBehavior::Start && destination_slot.tick_interval > 0.0 {
60 destination_slot.next_tick = now_s + destination_slot.tick_interval;
61 destination_slot.remaining_ticks = wowlab_types::numeric::f64_to_i32_saturating_ceil(
62 cloned_duration / destination_slot.tick_interval,
63 );
64 }
65
66 if let Some(slot) = ctx.buf.aura_mut(destination_key) {
67 *slot = destination_slot;
68 }
69
70 let stacks = wowlab_types::numeric::i32_to_u8_saturating(destination_slot.stacks);
71
72 emit_aura_apply_event(
73 ctx.state,
74 ctx.sink,
75 destination_key,
76 aura.aura_id,
77 stacks,
78 ctx.now,
79 );
80 schedule_aura_expiry(ctx, destination_key);
81
82 if destination_slot.tick_interval > 0.0
83 && destination_slot.next_tick <= destination_slot.expires_at
84 {
85 ctx.state.schedule(Event::AuraTick {
86 t: SimTime::from_secs_f64(destination_slot.next_tick),
87 key: destination_key,
88 target: Some(destination_target),
89 });
90 }
91
92 synchronize_aura_dependents(ctx);
93 enqueue_aura_application_proc(ctx, &aura, destination_key);
94
95 true
96}
97
98pub(crate) fn aura_remaining_ms(
99 state: &CombatState,
100 buf: &DenseBuffer,
101 local: LocalAuraIdx,
102 source: ActorId,
103 target: Option<EnemyIdx>,
104 now: SimTime,
105) -> Option<u32> {
106 let key = aura_key_for(state, local, source, target)?;
107 let slot = buf.aura(key)?;
108
109 if !slot.is_active(now.as_secs_f64()) {
110 return None;
111 }
112
113 if slot.expires_at >= PERMANENT_AURA_EXPIRES_AT_S {
114 return Some(u32::MAX);
115 }
116
117 Some(wowlab_types::numeric::f64_to_u32_saturating_ceil(
118 (slot.expires_at - now.as_secs_f64()) * wowlab_types::constants::MS_PER_SECOND,
119 ))
120}