Skip to main content

wowlab_engine_combat/systems/damage_pipeline/
weapon.rs

1//! Weapon and dynamic power inputs for outgoing damage.
2
3use wowlab_engine_domain::rotation::DenseBuffer;
4use wowlab_types::constants::{HUNDRED, MS_PER_SECOND};
5
6use super::dynamic_base_powers;
7use crate::state::{BuffTotals, CombatState, SwingHand, WeaponApType};
8
9/// Unarmed weapon DPS.
10const UNARMED_WDPS: f64 = 0.5;
11/// Dual-weapon AP composite scale.
12const WEAPON_BOTH_SCALE: f64 = 2.0 / 3.0;
13const HALF: f64 = 2.0;
14
15fn weapon_dps(weapon: &wowlab_types::data::WeaponStats) -> f64 {
16    if weapon.speed_ms == 0 || weapon.max_damage <= 0.0 {
17        return UNARMED_WDPS;
18    }
19
20    (weapon.min_damage + weapon.max_damage) / HALF / (f64::from(weapon.speed_ms) / MS_PER_SECOND)
21}
22
23/// Returns the attack-power composite for a weapon type.
24pub(crate) fn typed_attack_power(
25    state: &CombatState,
26    buf: &DenseBuffer,
27    ap_type: WeaponApType,
28) -> f64 {
29    let view = crate::CombatView::new(state, buf);
30    let totals = view.buff_totals();
31
32    typed_attack_power_with(state, buf, ap_type, totals)
33}
34
35pub(crate) fn typed_attack_power_with(
36    state: &CombatState,
37    buf: &DenseBuffer,
38    ap_type: WeaponApType,
39    totals: &BuffTotals,
40) -> f64 {
41    let (ap, _) = dynamic_base_powers(state, buf, totals);
42    let ap_mult = 1.0 + totals.attack_power_pct / HUNDRED;
43    let data = &state.config.game_data;
44    let wdps = match ap_type {
45        WeaponApType::None => return ap * ap_mult,
46        WeaponApType::MainHand => weapon_dps(&data.main_hand()),
47        WeaponApType::OffHand => data
48            .off_hand()
49            .map_or(UNARMED_WDPS, |weapon| weapon_dps(&weapon)),
50        WeaponApType::Both => {
51            let main_hand = weapon_dps(&data.main_hand());
52            let off_hand = data
53                .off_hand()
54                .map_or(UNARMED_WDPS, |weapon| weapon_dps(&weapon));
55
56            (main_hand + off_hand / HALF) * WEAPON_BOTH_SCALE
57        }
58    };
59
60    (ap + (wdps * wowlab_engine_domain::damage::WEAPON_POWER_COEFFICIENT).trunc()) * ap_mult
61}
62
63/// Returns dynamic spell power including active stat buffs.
64pub(crate) fn current_spell_power(state: &CombatState, buf: &DenseBuffer) -> f64 {
65    let view = crate::CombatView::new(state, buf);
66    let totals = view.buff_totals();
67
68    dynamic_base_powers(state, buf, totals).1
69}
70
71/// Weapon-damage inputs for one damage request.
72#[derive(Clone, Copy, Debug, Default)]
73pub(in crate::systems) struct WeaponDamageInput {
74    pub min: f64,
75    pub max: f64,
76    pub multiplier: f64,
77    pub ap_coefficient: f64,
78    pub is_auto_attack: bool,
79    pub swing_hand: Option<SwingHand>,
80    pub swing_speed_ms: u32,
81    pub miss_chance: f64,
82}
83
84impl WeaponDamageInput {
85    pub(in crate::systems) const fn none() -> Self {
86        Self {
87            min: 0.0,
88            max: 0.0,
89            multiplier: 0.0,
90            ap_coefficient: 0.0,
91            is_auto_attack: false,
92            swing_hand: None,
93            swing_speed_ms: 0,
94            miss_chance: 0.0,
95        }
96    }
97}
98
99pub(super) fn weapon_damage_input(
100    state: &CombatState,
101    hand: WeaponApType,
102    multiplier: f64,
103    normalized: bool,
104) -> WeaponDamageInput {
105    let data = &state.config.game_data;
106    let (weapon, swing_hand) = match hand {
107        WeaponApType::MainHand => (Some(data.main_hand()), SwingHand::MainHand),
108        WeaponApType::OffHand => (data.off_hand(), SwingHand::OffHand),
109        WeaponApType::Both | WeaponApType::None => (None, SwingHand::MainHand),
110    };
111    let Some(weapon) = weapon.filter(|weapon| weapon.speed_ms > 0 && weapon.max_damage > 0.0)
112    else {
113        return WeaponDamageInput::none();
114    };
115    let hand_multiplier = if swing_hand == SwingHand::OffHand {
116        wowlab_engine_domain::constants::OFF_HAND_DAMAGE_MULT
117    } else {
118        1.0
119    };
120    let multiplier = multiplier * hand_multiplier;
121    let speed_s = if normalized {
122        weapon.normalized_speed_s
123    } else {
124        f64::from(weapon.speed_ms) / MS_PER_SECOND
125    };
126
127    WeaponDamageInput {
128        min: weapon.min_damage,
129        max: weapon.max_damage,
130        multiplier,
131        ap_coefficient: speed_s / wowlab_engine_domain::damage::WEAPON_POWER_COEFFICIENT
132            * multiplier,
133        is_auto_attack: false,
134        swing_hand: Some(swing_hand),
135        swing_speed_ms: weapon.speed_ms,
136        miss_chance: 0.0,
137    }
138}