wowlab_engine_combat/systems/damage_pipeline/setup/
armor.rs1#[cfg(test)]
2use wowlab_engine_domain::rotation::DenseBuffer;
3#[cfg(test)]
4use wowlab_types::sim::EnemyIdx;
5use wowlab_types::{constants::HUNDRED, sim::ActorId};
6
7use super::DamageFlags;
8use crate::state::BuffEffect;
9#[cfg(test)]
10use crate::state::CombatState;
11
12pub(super) fn for_target(
13 view: crate::context::HookView<'_>,
14 source: ActorId,
15 flags: DamageFlags,
16) -> f64 {
17 let crate::context::HookView {
18 state, buf, target, ..
19 } = view;
20
21 let Some(target) = target else {
22 return 0.0;
23 };
24
25 if !flags.contains(DamageFlags::PHYSICAL) || flags.contains(DamageFlags::PERIODIC) {
26 return 0.0;
27 }
28
29 let armor = state.enemy_definition(target).map_or(
30 0.0,
31 wowlab_engine_domain::encounter::EnemyActorDefinition::armor,
32 );
33
34 if armor <= 0.0 {
35 tracing::warn!("creature armor is non-positive; armor mitigation skipped");
36 }
37
38 let mut bypass_pct = 0.0;
39
40 for key in buf.aura_keys() {
41 if key.source() != source || key.affected() != ActorId::Enemy(target) {
42 continue;
43 }
44
45 let Some(slot) = buf.aura(key).filter(|slot| slot.is_occupied()) else {
46 continue;
47 };
48 let Some(local) = state.aura_local_for_key(key) else {
49 continue;
50 };
51 let Some(aura) = state.defs.auras.get(local.as_usize()) else {
52 continue;
53 };
54
55 for effect in aura.effects.iter().flatten() {
56 if let BuffEffect::ArmorBypassFromCaster(percent) = effect {
57 bypass_pct += percent * f64::from(slot.stacks);
58 }
59 }
60 }
61
62 armor
63 * state.config.game_data.target_armor_mult()
64 * (1.0 - bypass_pct.clamp(0.0, HUNDRED) / HUNDRED)
65}
66
67#[cfg(test)]
68pub(super) fn for_fixture(state: &CombatState, buf: &DenseBuffer, flags: DamageFlags) -> f64 {
69 for_target(
70 crate::HookView {
71 state,
72 buf,
73 target: state.current_target(),
74 now: wowlab_types::sim::SimTime::ZERO,
75 },
76 ActorId::Player,
77 flags,
78 )
79}
80
81#[cfg(test)]
82mod tests;