wowlab_engine_combat/systems/damage_pipeline/
weapon.rs1use 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
9const UNARMED_WDPS: f64 = 0.5;
11const 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
23pub(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
63pub(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#[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}