wowlab_engine_combat/systems/
aoe.rs1use wowlab_engine_domain::dbc::ModifierPropertyKind;
4
5use crate::state::{BuffEffect, CombatInstance, DamageSource};
6
7pub(crate) type AoeConfig = crate::state::program::DamageProfileAoe;
8
9pub(crate) fn read_spell_aoe_config(
10 program: &crate::state::CombatProgram,
11 state: &CombatInstance,
12 source: wowlab_types::sim::ActorId,
13 profile: crate::state::program::LocalDamageActionIdx,
14 effect: crate::state::program::LocalDamageEffectIdx,
15) -> AoeConfig {
16 let derived = effect_aoe_config(program, state, source, effect);
17 let profile = program.damage.action_at(profile).profile_aoe;
18
19 AoeConfig {
20 is_aoe: profile.is_aoe || derived.is_aoe,
21 split: profile.split,
22 chain_targeting: profile.chain_targeting || derived.chain_targeting,
23 chain_multiplier: if profile.chain_targeting {
24 profile.chain_multiplier
25 } else {
26 derived.chain_multiplier
27 },
28 base_mult: profile.base_mult,
29 reduced_targets: profile.reduced_targets,
30 full_targets: profile.full_targets,
31 max_targets: if profile.max_targets > 0 {
32 profile.max_targets
33 } else {
34 derived.max_targets
35 },
36 }
37}
38
39fn effect_aoe_config(
40 program: &crate::state::CombatProgram,
41 state: &CombatInstance,
42 source: wowlab_types::sim::ActorId,
43 effect: crate::state::program::LocalDamageEffectIdx,
44) -> AoeConfig {
45 let effect_plan = program.damage.effect_at(effect);
46 let chain_targets = effect_plan.aoe.chain_targets.clamp(0, i32::from(u8::MAX));
47 let chain_targets = wowlab_types::numeric::i32_to_u8_saturating(chain_targets);
48 let spell_cap = super::buffs::active_targeting_value(
49 crate::context::ActorView::new(program, state, source),
50 effect_plan.action,
51 ModifierPropertyKind::MaxTargets,
52 f64::from(effect_plan.aoe.max_affected_targets),
53 )
54 .round()
55 .clamp(0.0, f64::from(u8::MAX));
56 let spell_cap = wowlab_types::numeric::f64_to_u8_saturating_trunc(spell_cap);
57 let max_targets = match (chain_targets, spell_cap) {
58 (0, cap) | (cap, 0) => cap,
59 (chain, cap) => chain.min(cap),
60 };
61
62 AoeConfig {
63 is_aoe: effect_plan.aoe.treat_as_area_effect
64 || effect_plan
65 .target
66 .is_some_and(|target| target.is_multi_target),
67 chain_targeting: chain_targets > 0,
68 chain_multiplier: effect_plan.aoe.chain_multiplier,
69 base_mult: 1.0,
70 max_targets,
71 ..AoeConfig::default()
72 }
73}
74
75pub(crate) fn resolve_hit_count(candidates: usize, aoe: &AoeConfig) -> u8 {
76 let candidates_u8 = u8::try_from(candidates.min(usize::from(u8::MAX)))
77 .expect("candidate count is clamped to u8::MAX");
78
79 if aoe.max_targets == 0 {
80 candidates_u8
81 } else {
82 aoe.max_targets.min(candidates_u8)
83 }
84}
85
86pub(crate) fn aoe_falloff_multiplier(k: u8, hits: u8, aoe: &AoeConfig) -> f64 {
87 if aoe.split {
88 return if hits == 0 {
89 aoe.base_mult
90 } else {
91 aoe.base_mult / f64::from(hits)
92 };
93 }
94
95 if aoe.chain_targeting {
96 return aoe.base_mult * aoe.chain_multiplier.powi(i32::from(k));
97 }
98
99 let full_targets = usize::from(aoe.full_targets);
100 let reduced_targets = usize::from(aoe.reduced_targets);
101 let reduction = if reduced_targets == 0
102 || usize::from(k) < full_targets
103 || usize::from(hits) <= reduced_targets
104 {
105 1.0
106 } else {
107 (f64::from(aoe.reduced_targets) / f64::from(hits)).sqrt()
108 };
109
110 aoe.base_mult * reduction
111}
112
113pub(crate) fn active_damage_replication<'a>(
114 program: &'a crate::state::CombatProgram,
115 state: &CombatInstance,
116 source: DamageSource,
117) -> Option<&'a crate::state::program::FinalizedDamageReplication> {
118 let mut replication = None;
119
120 for &index in program
121 .effects
122 .passive_family_indexes(crate::state::EffectFamily::DAMAGE_REPLICATION)
123 {
124 if let Some(candidate) = program.damage.passive_replication(index)
125 && candidate.definition.source == source
126 {
127 replication.get_or_insert(candidate);
128 }
129 }
130
131 crate::systems::buffs::for_each_planned_actor_aura_effect_binding(
132 crate::context::ActorView::new(program, state, wowlab_types::sim::ActorId::Player),
133 crate::state::EffectFamily::DAMAGE_REPLICATION,
134 |_key, local, position, _stacks, effect| {
135 if let BuffEffect::DamageReplication(candidate) = effect
136 && candidate.source == source
137 && let Some(candidate) = program.damage.aura_replication(local, position)
138 {
139 replication.get_or_insert(candidate);
140 }
141 },
142 );
143
144 replication
145}