1use wowlab_engine_domain::dbc::SpellEffectAttributes;
5
6use crate::{
7 context::ActorView,
8 state::{BuffEffect, EffectFamily},
9};
10
11pub(crate) fn dbc_effect_points(
12 program: &crate::state::CombatProgram,
13 _state: &crate::state::CombatInstance,
14 source: crate::state::program::LocalDamageEffectIdx,
15 stacks: f64,
16) -> f64 {
17 let attributes = SpellEffectAttributes::from_dbc(program.damage.effect_at(source).attributes);
18
19 if attributes.contains(SpellEffectAttributes::SUPPRESS_POINTS_STACKING) {
20 1.0
21 } else {
22 stacks
23 }
24}
25
26pub(crate) fn for_each_planned_actor_effect(
28 view: ActorView<'_>,
29 family: EffectFamily,
30 mut visit: impl FnMut(f64, &BuffEffect),
31) {
32 if family.bits().is_power_of_two() {
33 if view.actor == wowlab_types::sim::ActorId::Player {
34 for &index in view.program.effects.passive_family_indexes(family) {
35 visit(1.0, &view.program.passive_driver_effects[index].effect);
36 }
37 }
38
39 for_each_planned_actor_aura_effect_single_family(
40 view,
41 family,
42 |_, _, _, stacks, effect| {
43 visit(stacks, effect);
44 },
45 );
46
47 return;
48 }
49
50 if view.actor == wowlab_types::sim::ActorId::Player {
51 for_each_planned_player_passive_effect(view.program, family, &mut visit);
52 }
53
54 for_each_planned_actor_aura_effect(view, family, |_, _, stacks, effect| {
55 visit(stacks, effect);
56 });
57}
58
59pub(crate) fn for_each_planned_actor_effect_damage_binding(
61 view: ActorView<'_>,
62 family: EffectFamily,
63 mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
64) {
65 if family.bits().is_power_of_two() {
66 if view.actor == wowlab_types::sim::ActorId::Player {
67 for &index in view.program.effects.passive_family_indexes(family) {
68 let effect = &view.program.passive_driver_effects[index].effect;
69 let binding = view
70 .program
71 .damage
72 .passive_buff_binding(index)
73 .expect("every finalized passive effect has a damage binding record");
74
75 visit(1.0, effect, binding);
76 }
77 }
78
79 for_each_planned_actor_aura_effect_single_family(
80 view,
81 family,
82 |_, local, position, stacks, effect| {
83 let binding = view
84 .program
85 .damage
86 .aura_buff_binding(local, position)
87 .expect("every finalized aura effect has a damage binding record");
88
89 visit(stacks, effect, binding);
90 },
91 );
92
93 return;
94 }
95
96 if view.actor == wowlab_types::sim::ActorId::Player {
97 for_each_planned_player_passive_effect_binding(view.program, family, &mut visit);
98 }
99
100 for_each_planned_actor_aura_effect_inner(view, family, |_, local, position, stacks, effect| {
101 let binding = view
102 .program
103 .damage
104 .aura_buff_binding(local, position)
105 .expect("every finalized aura effect has a damage binding record");
106
107 visit(stacks, effect, binding);
108 });
109}
110
111#[cfg(feature = "rotation-optimizations")]
112fn for_each_active_bound_action_effect(
113 view: ActorView<'_>,
114 passive_indexes: &[usize],
115 aura_plan: &crate::state::program::ActiveAuraEffectPlan,
116 mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
117) {
118 if view.actor == wowlab_types::sim::ActorId::Player {
119 for &index in passive_indexes {
120 let effect = &view.program.passive_driver_effects[index].effect;
121 let binding = view
122 .program
123 .damage
124 .passive_buff_binding(index)
125 .expect("every finalized passive effect has a damage binding record");
126
127 visit(1.0, effect, binding);
128 }
129 }
130
131 for_each_active_aura_effect_plan(view, aura_plan, aura_plan, &mut visit);
132}
133
134#[cfg(not(feature = "rotation-optimizations"))]
135fn for_each_active_bound_action_effect(
136 view: ActorView<'_>,
137 passive_indexes: &[usize],
138 aura_entries: &[crate::state::program::PlannedAuraEffect],
139 mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
140) {
141 if view.actor == wowlab_types::sim::ActorId::Player {
142 for &index in passive_indexes {
143 let effect = &view.program.passive_driver_effects[index].effect;
144 let binding = view
145 .program
146 .damage
147 .passive_buff_binding(index)
148 .expect("every finalized passive effect has a damage binding record");
149
150 visit(1.0, effect, binding);
151 }
152 }
153
154 for entry in aura_entries {
155 wowlab_types::bench_metrics::record_aura_slot_visit();
156 let slot = view.instance.buffer.aura_at(entry.handle);
157
158 if !slot.is_occupied() {
159 continue;
160 }
161
162 let effect = view.program.aura(entry.local).effects[usize::from(entry.position)]
163 .as_ref()
164 .expect("planned effect position remains occupied after finalization");
165 let binding = view
166 .program
167 .damage
168 .aura_buff_binding(entry.local, entry.position)
169 .expect("every finalized aura effect has a damage binding record");
170
171 visit(f64::from(slot.stacks), effect, binding);
172 }
173}
174
175#[cfg(feature = "rotation-optimizations")]
176fn for_each_active_bound_action_effect_union(
177 view: ActorView<'_>,
178 left_passives: &[usize],
179 right_passives: &[usize],
180 left_auras: &crate::state::program::ActiveAuraEffectPlan,
181 right_auras: &crate::state::program::ActiveAuraEffectPlan,
182 mut visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
183) {
184 if view.actor == wowlab_types::sim::ActorId::Player {
185 let mut left = 0;
186 let mut right = 0;
187
188 while left < left_passives.len() || right < right_passives.len() {
189 let index = match (left_passives.get(left), right_passives.get(right)) {
190 (Some(&left_index), Some(&right_index)) if left_index < right_index => {
191 left += 1;
192
193 left_index
194 }
195 (Some(&left_index), Some(&right_index)) if right_index < left_index => {
196 right += 1;
197
198 right_index
199 }
200 (Some(&index), Some(_)) => {
201 left += 1;
202 right += 1;
203
204 index
205 }
206 (Some(&index), None) => {
207 left += 1;
208
209 index
210 }
211 (None, Some(&index)) => {
212 right += 1;
213
214 index
215 }
216 (None, None) => break,
217 };
218 let effect = &view.program.passive_driver_effects[index].effect;
219 let binding = view
220 .program
221 .damage
222 .passive_buff_binding(index)
223 .expect("every finalized passive effect has a damage binding record");
224
225 visit(1.0, effect, binding);
226 }
227 }
228
229 for_each_active_aura_effect_plan(view, left_auras, right_auras, &mut visit);
230}
231
232#[cfg(feature = "rotation-optimizations")]
233fn for_each_active_aura_effect_plan(
234 view: ActorView<'_>,
235 left: &crate::state::program::ActiveAuraEffectPlan,
236 right: &crate::state::program::ActiveAuraEffectPlan,
237 visit: &mut impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
238) {
239 let mut visit_binding = |local: crate::state::LocalAuraIdx,
240 handle: wowlab_engine_domain::rotation::AuraSlotHandle,
241 mut mask: u16| {
242 let slot = view.instance.buffer.aura_at(handle);
243
244 debug_assert!(
245 slot.is_occupied(),
246 "active aura intersection contains only occupied concrete bindings"
247 );
248
249 let aura = view.program.aura(local);
250
251 while mask != 0 {
252 let position =
253 u8::try_from(mask.trailing_zeros()).expect("u16 effect mask positions fit in u8");
254
255 mask &= mask - 1;
256 wowlab_types::bench_metrics::record_aura_slot_visit();
257 let effect = aura.effects[usize::from(position)]
258 .as_ref()
259 .expect("active-aura effect plan references an occupied effect");
260 let damage_binding = view
261 .program
262 .damage
263 .aura_buff_binding(local, position)
264 .expect("every finalized aura effect has a damage binding record");
265
266 visit(f64::from(slot.stacks), effect, damage_binding);
267 }
268 };
269
270 if std::ptr::eq(left, right) {
271 for &(local, mask) in left.masks() {
272 view.instance
273 .for_each_active_aura_local(view.actor, local, |binding| {
274 visit_binding(local, binding.handle, mask);
275 });
276 }
277
278 return;
279 }
280
281 view.instance.for_each_active_aura_intersection(
282 view.actor,
283 (left.relevant_locals(), right.relevant_locals()),
284 |local, binding| {
285 visit_binding(local, binding.handle, left.mask(local) | right.mask(local));
286 },
287 );
288}
289
290pub(crate) fn for_each_active_spell_modifier_for_action(
291 view: ActorView<'_>,
292 action: crate::state::program::LocalDamageActionIdx,
293 property: wowlab_engine_domain::dbc::ModifierPropertyKind,
294 visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
295) {
296 debug_assert_eq!(
297 view.actor,
298 wowlab_types::sim::ActorId::Player,
299 "affected-spell property modifiers resolve through the canonical player owner"
300 );
301
302 #[cfg(feature = "rotation-optimizations")]
303 for_each_active_bound_action_effect(
304 view,
305 view.program
306 .effects
307 .spell_modifier_passive_indexes(action, property),
308 view.program
309 .effects
310 .spell_modifier_player_aura_plan(action, property),
311 visit,
312 );
313
314 #[cfg(not(feature = "rotation-optimizations"))]
315 for_each_active_bound_action_effect(
316 view,
317 view.program
318 .effects
319 .spell_modifier_passive_indexes(action, property),
320 view.program
321 .effects
322 .spell_modifier_player_auras(action, property),
323 visit,
324 );
325}
326
327pub(crate) fn for_each_active_scoped_spell_modifier_for_action(
328 view: ActorView<'_>,
329 action: crate::state::program::LocalDamageActionIdx,
330 visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
331) {
332 #[cfg(feature = "rotation-optimizations")]
333 for_each_active_bound_action_effect(
334 view,
335 view.program
336 .effects
337 .scoped_spell_modifier_passive_indexes(action),
338 view.program.effects.scoped_spell_modifier_aura_plan(action),
339 visit,
340 );
341
342 #[cfg(not(feature = "rotation-optimizations"))]
343 for_each_active_bound_action_effect(
344 view,
345 view.program
346 .effects
347 .scoped_spell_modifier_passive_indexes(action),
348 view.program
349 .effects
350 .scoped_spell_modifier_actor_auras(view.actor, action),
351 visit,
352 );
353}
354
355#[cfg(feature = "rotation-optimizations")]
356pub(crate) fn for_each_active_damage_modifier_for_actions(
357 view: ActorView<'_>,
358 profile_action: crate::state::program::LocalDamageActionIdx,
359 effect_action: crate::state::program::LocalDamageActionIdx,
360 visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
361) {
362 for_each_active_bound_action_effect_union(
363 view,
364 view.program
365 .effects
366 .damage_spell_modifier_passive_indexes(profile_action),
367 view.program
368 .effects
369 .damage_spell_modifier_passive_indexes(effect_action),
370 view.program
371 .effects
372 .damage_spell_modifier_aura_plan(profile_action),
373 view.program
374 .effects
375 .damage_spell_modifier_aura_plan(effect_action),
376 visit,
377 );
378}
379
380#[cfg(not(feature = "rotation-optimizations"))]
381pub(crate) fn for_each_active_damage_modifier_for_action(
382 view: ActorView<'_>,
383 action: crate::state::program::LocalDamageActionIdx,
384 visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
385) {
386 for_each_active_bound_action_effect(
387 view,
388 view.program
389 .effects
390 .damage_spell_modifier_passive_indexes(action),
391 view.program
392 .effects
393 .damage_spell_modifier_actor_auras(view.actor, action),
394 visit,
395 );
396}
397
398pub(crate) fn for_each_active_cost_modifier_for_action(
399 view: ActorView<'_>,
400 action: crate::state::program::LocalDamageActionIdx,
401 visit: impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
402) {
403 #[cfg(feature = "rotation-optimizations")]
404 for_each_active_bound_action_effect(
405 view,
406 view.program.effects.cost_modifier_passive_indexes(action),
407 view.program.effects.cost_modifier_aura_plan(action),
408 visit,
409 );
410
411 #[cfg(not(feature = "rotation-optimizations"))]
412 for_each_active_bound_action_effect(
413 view,
414 view.program.effects.cost_modifier_passive_indexes(action),
415 view.program
416 .effects
417 .cost_modifier_actor_auras(view.actor, action),
418 visit,
419 );
420}
421
422fn for_each_planned_player_passive_effect_binding(
423 program: &crate::state::CombatProgram,
424 family: EffectFamily,
425 visit: &mut impl FnMut(f64, &BuffEffect, &crate::state::program::FinalizedBuffDamageBinding),
426) {
427 let mut cursors = [0_usize; u128::BITS as usize];
428
429 loop {
430 let mut selected_entry = None::<(usize, usize)>;
431 let mut bits = family.bits();
432
433 while bits != 0 {
434 let bit = bits.trailing_zeros() as usize;
435 let selected = EffectFamily::from_bits_retain(1_u128 << bit);
436 let indexes = program.effects.passive_family_indexes(selected);
437 let cursor = &mut cursors[bit];
439
440 bits &= bits - 1;
441
442 while let Some(&index) = indexes.get(*cursor) {
443 let effect = &program.passive_driver_effects[index].effect;
445 let matching = effect.classification().families().intersection(family);
446
447 if matching.bits().trailing_zeros() as usize == bit {
448 if selected_entry.is_none_or(|(current, _)| index < current) {
449 selected_entry = Some((index, bit));
450 }
451
452 break;
453 }
454
455 *cursor += 1;
456 }
457 }
458
459 let Some((index, bit)) = selected_entry else {
460 break;
461 };
462
463 cursors[bit] += 1;
466 let effect = &program.passive_driver_effects[index].effect;
468 let binding = program
469 .damage
470 .passive_buff_binding(index)
471 .expect("every finalized passive effect has a damage binding record");
472
473 visit(1.0, effect, binding);
474 }
475}
476
477pub(crate) fn for_each_planned_actor_aura_effect_binding(
479 view: ActorView<'_>,
480 family: EffectFamily,
481 mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, u8, f64, &BuffEffect),
482) {
483 for_each_planned_actor_aura_effect_inner(
484 view,
485 family,
486 |key, local, position, stacks, effect| {
487 visit(key, local, position, stacks, effect);
488 },
489 );
490}
491
492fn for_each_planned_player_passive_effect(
493 program: &crate::state::CombatProgram,
494 family: EffectFamily,
495 visit: &mut impl FnMut(f64, &BuffEffect),
496) {
497 let mut cursors = [0_usize; u128::BITS as usize];
498
499 loop {
500 let mut selected_entry = None::<(usize, usize)>;
501 let mut bits = family.bits();
502
503 while bits != 0 {
504 let bit = bits.trailing_zeros() as usize;
505 let selected = EffectFamily::from_bits_retain(1_u128 << bit);
506 let indexes = program.effects.passive_family_indexes(selected);
507 let cursor = &mut cursors[bit];
509
510 bits &= bits - 1;
511
512 while let Some(&index) = indexes.get(*cursor) {
513 let effect = &program.passive_driver_effects[index].effect;
515 let matching = effect.classification().families().intersection(family);
516
517 if matching.bits().trailing_zeros() as usize == bit {
518 if selected_entry.is_none_or(|(current, _)| index < current) {
519 selected_entry = Some((index, bit));
520 }
521
522 break;
523 }
524
525 *cursor += 1;
526 }
527 }
528
529 let Some((index, bit)) = selected_entry else {
530 break;
531 };
532
533 cursors[bit] += 1;
536 visit(1.0, &program.passive_driver_effects[index].effect);
538 }
539}
540
541pub(crate) fn for_each_planned_actor_aura_effect(
542 view: ActorView<'_>,
543 family: EffectFamily,
544 mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, f64, &BuffEffect),
545) {
546 for_each_planned_actor_aura_effect_inner(view, family, |key, local, _, stacks, effect| {
547 visit(key, local, stacks, effect);
548 });
549}
550
551fn for_each_planned_actor_aura_effect_inner(
552 view: ActorView<'_>,
553 family: EffectFamily,
554 mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, u8, f64, &BuffEffect),
555) {
556 if family.bits().is_power_of_two() {
557 for_each_planned_actor_aura_effect_single_family(view, family, visit);
558
559 return;
560 }
561
562 let mut cursors = [0_usize; u128::BITS as usize];
563
564 loop {
565 let mut selected_entry = None::<(&crate::state::program::PlannedAuraEffect, usize)>;
566 let mut bits = family.bits();
567
568 while bits != 0 {
569 let bit = bits.trailing_zeros() as usize;
570 let selected = EffectFamily::from_bits_retain(1_u128 << bit);
571 let entries = view
572 .program
573 .effects
574 .actor_family_effects(view.actor, selected);
575 let cursor = &mut cursors[bit];
577
578 bits &= bits - 1;
579
580 while let Some(entry) = entries.get(*cursor) {
581 if entry.is_canonical_for(family, selected) {
582 if selected_entry.is_none_or(|(current, _)| entry.order < current.order) {
583 selected_entry = Some((entry, bit));
584 }
585
586 break;
587 }
588
589 *cursor += 1;
590 }
591 }
592
593 let Some((entry, bit)) = selected_entry else {
594 break;
595 };
596
597 cursors[bit] += 1;
600
601 wowlab_types::bench_metrics::record_aura_slot_visit();
602 let slot = view.instance.buffer.aura_at(entry.handle);
603
604 if !slot.is_occupied() {
605 continue;
606 }
607
608 let effect = view.program.aura(entry.local).effects[usize::from(entry.position)]
611 .as_ref()
612 .expect("planned effect position remains occupied after finalization");
613
614 visit(
615 entry.key,
616 entry.local,
617 entry.position,
618 f64::from(slot.stacks),
619 effect,
620 );
621 }
622}
623
624fn for_each_planned_actor_aura_effect_single_family(
625 view: ActorView<'_>,
626 family: EffectFamily,
627 mut visit: impl FnMut(wowlab_types::sim::AuraKey, crate::state::LocalAuraIdx, u8, f64, &BuffEffect),
628) {
629 #[cfg(feature = "rotation-optimizations")]
630 {
631 let plan = view.program.effects.family_aura_plan(family);
632
633 view.instance.for_each_active_aura_intersection(
634 view.actor,
635 (plan.relevant_locals(), &[]),
636 |local, binding| {
637 let slot = view.instance.buffer.aura_at(binding.handle);
638 let aura = view.program.aura(local);
639 let mut mask = plan.mask(local);
640
641 debug_assert!(
642 slot.is_occupied(),
643 "active aura intersection contains only occupied concrete bindings"
644 );
645
646 while mask != 0 {
647 let position = u8::try_from(mask.trailing_zeros())
648 .expect("u16 effect mask positions fit in u8");
649
650 mask &= mask - 1;
651 wowlab_types::bench_metrics::record_aura_slot_visit();
652 let effect = aura.effects[usize::from(position)]
653 .as_ref()
654 .expect("family aura plan references an occupied effect");
655
656 visit(binding.key, local, position, f64::from(slot.stacks), effect);
657 }
658 },
659 );
660 }
661
662 #[cfg(not(feature = "rotation-optimizations"))]
663 for entry in view
664 .program
665 .effects
666 .actor_family_effects(view.actor, family)
667 {
668 wowlab_types::bench_metrics::record_aura_slot_visit();
669 let slot = view.instance.buffer.aura_at(entry.handle);
670
671 if !slot.is_occupied() {
672 continue;
673 }
674
675 let effect = view.program.aura(entry.local).effects[usize::from(entry.position)]
678 .as_ref()
679 .expect("planned effect position remains occupied after finalization");
680
681 visit(
682 entry.key,
683 entry.local,
684 entry.position,
685 f64::from(slot.stacks),
686 effect,
687 );
688 }
689}
690
691pub(crate) fn for_each_planned_player_effect(
692 program: &crate::state::CombatProgram,
693 instance: &crate::state::CombatInstance,
694 family: EffectFamily,
695 visit: impl FnMut(f64, &BuffEffect),
696) {
697 for_each_planned_actor_effect(
698 ActorView::new(program, instance, wowlab_types::sim::ActorId::Player),
699 family,
700 visit,
701 );
702}
703
704pub(crate) fn for_each_planned_target_effect(
705 program: &crate::state::CombatProgram,
706 instance: &crate::state::CombatInstance,
707 target: wowlab_types::sim::EnemyIdx,
708 family: EffectFamily,
709 visit: impl FnMut(f64, &BuffEffect),
710) {
711 for_each_planned_actor_effect(
712 ActorView::new(program, instance, wowlab_types::sim::ActorId::Enemy(target)),
713 family,
714 visit,
715 );
716}