Skip to main content

wowlab_engine_content/
weapon_enchants.rs

1//! Weapon enchant procs registered post-build onto a [`BuiltCombatSystem`].
2
3use wowlab_engine_combat::{
4    AuraData, BuffEffect, BuiltCombatSystem, MAX_AURA_BUFF_EFFECTS, RppmTracker,
5    WeaponEnchantBinding, try_push_effect,
6};
7use wowlab_engine_gamedata::ResolvedGameData;
8use wowlab_engine_ports::{EngineError, WeaponEnchantProc, WeaponEnchantStat};
9use wowlab_types::game::RatingType;
10
11/// Register every `WeaponEnchantProc` onto `built` (RPPM tracker, buff aura, impact proc).
12///
13/// # Errors
14///
15/// Returns an engine construction error when a proc or its aura cannot be registered safely.
16pub(crate) fn register_weapon_enchant_procs(
17    built: &mut BuiltCombatSystem,
18    procs: &[WeaponEnchantProc],
19) -> Result<(), EngineError> {
20    for proc in procs {
21        register_one(built, proc)?;
22    }
23
24    Ok(())
25}
26
27fn register_one(
28    built: &mut BuiltCombatSystem,
29    proc: &WeaponEnchantProc,
30) -> Result<(), EngineError> {
31    let tracker = RppmTracker {
32        rppm: proc.rppm,
33        last_attempt_time: 0.0,
34        last_proc_time: 0.0,
35        accumulated_blp: 0.0,
36        haste_scales: proc.haste_scales,
37        crit_scales: proc.crit_scales,
38        auto_attack_speed_scales: false,
39        blp_enabled: true,
40    };
41    let registered = built.register_weapon_enchant_binding(
42        WeaponEnchantBinding {
43            enchantment_id: proc.enchantment_id,
44            buff_spell_id: proc.buff_spell_id,
45        },
46        tracker,
47    )?;
48
49    if registered && built.state.aura_local(proc.buff_spell_id).is_none() {
50        register_buff_aura(built, proc)?;
51    }
52
53    Ok(())
54}
55
56const DEFAULT_ENCHANT_BUFF_DURATION_MS: u32 = 15_000;
57
58fn register_buff_aura(
59    built: &mut BuiltCombatSystem,
60    proc: &WeaponEnchantProc,
61) -> Result<(), EngineError> {
62    let duration_ms = if proc.buff_duration_ms == 0 {
63        tracing::warn!(
64            enchantment_id = proc.enchantment_id,
65            buff_spell_id = proc.buff_spell_id,
66            "buff spell duration NULL/0; defaulting to 15000ms (will fix once DB populated)"
67        );
68
69        DEFAULT_ENCHANT_BUFF_DURATION_MS
70    } else {
71        proc.buff_duration_ms
72    };
73
74    let mut effects: [Option<BuffEffect>; MAX_AURA_BUFF_EFFECTS] = [None; MAX_AURA_BUFF_EFFECTS];
75
76    if let Some(effect) = stat_to_buff_effect(
77        &built.state.config.game_data,
78        proc.buff_stat,
79        proc.buff_amount,
80    )? {
81        // Single push into an empty array; the checked helper can never overflow here.
82        let _ = try_push_effect(&mut effects, effect);
83    } else {
84        tracing::warn!(
85            enchantment_id = proc.enchantment_id,
86            buff_spell_id = proc.buff_spell_id,
87            "buff stat not classified; aura registers inert (contributes zero stats)"
88        );
89    }
90
91    let aura = AuraData::simple_player_buff(proc.buff_spell_id, 0, duration_ms, effects);
92    let _ = built.register_aura(proc.name.clone(), aura)?;
93
94    Ok(())
95}
96
97fn stat_to_buff_effect(
98    data: &ResolvedGameData,
99    stat: WeaponEnchantStat,
100    amount: f64,
101) -> Result<Option<BuffEffect>, EngineError> {
102    if amount <= 0.0 {
103        return Ok(None);
104    }
105
106    let effect = match stat {
107        WeaponEnchantStat::Crit => Some(BuffEffect::Crit(
108            crate::raid_buffs::rating_percent_points(data, RatingType::Crit, amount)?,
109        )),
110        WeaponEnchantStat::Haste => Some(BuffEffect::Haste(
111            crate::raid_buffs::rating_percent_points(data, RatingType::Haste, amount)?,
112        )),
113        WeaponEnchantStat::Mastery => Some(BuffEffect::Mastery(
114            crate::raid_buffs::rating_percent_points(data, RatingType::Mastery, amount)?,
115        )),
116        WeaponEnchantStat::Versatility => Some(BuffEffect::Versatility(
117            crate::raid_buffs::rating_percent_points(data, RatingType::Versatility, amount)?,
118        )),
119        WeaponEnchantStat::PrimaryStat => Some(BuffEffect::PrimaryStat(amount)),
120        WeaponEnchantStat::PrimaryStatPercent => Some(BuffEffect::PrimaryStatPercent(amount)),
121        _ => None,
122    };
123
124    Ok(effect)
125}
126
127#[cfg(test)]
128mod tests;