Skip to main content

wowlab_engine_content/
weapon_enchants.rs

1//! Weapon-enchant procs composed into a [`CombatSystemDraft`] before finalization.
2
3use wowlab_engine_combat::{
4    AuraData, BuffEffect, CombatSystemDraft, 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 resolved enchant proc, tracker, and buff aura on the mutable draft.
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 CombatSystemDraft,
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 CombatSystemDraft,
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.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 CombatSystemDraft,
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(built.game_data(), proc.buff_stat, proc.buff_amount)?
77    {
78        // Single push into an empty array; the checked helper can never overflow here.
79        let _ = try_push_effect(&mut effects, effect);
80    } else {
81        tracing::warn!(
82            enchantment_id = proc.enchantment_id,
83            buff_spell_id = proc.buff_spell_id,
84            "buff stat not classified; aura registers inert (contributes zero stats)"
85        );
86    }
87
88    let aura = AuraData::simple_player_buff(proc.buff_spell_id, 0, duration_ms, effects);
89    let _ = built.register_aura(proc.name.clone(), aura)?;
90
91    Ok(())
92}
93
94fn stat_to_buff_effect(
95    data: &ResolvedGameData,
96    stat: WeaponEnchantStat,
97    amount: f64,
98) -> Result<Option<BuffEffect>, EngineError> {
99    if amount <= 0.0 {
100        return Ok(None);
101    }
102
103    let effect = match stat {
104        WeaponEnchantStat::Crit => Some(BuffEffect::Crit(
105            crate::raid_buffs::rating_percent_points(data, RatingType::Crit, amount)?,
106        )),
107        WeaponEnchantStat::Haste => Some(BuffEffect::Haste(
108            crate::raid_buffs::rating_percent_points(data, RatingType::Haste, amount)?,
109        )),
110        WeaponEnchantStat::Mastery => Some(BuffEffect::Mastery(
111            crate::raid_buffs::rating_percent_points(data, RatingType::Mastery, amount)?,
112        )),
113        WeaponEnchantStat::Versatility => Some(BuffEffect::Versatility(
114            crate::raid_buffs::rating_percent_points(data, RatingType::Versatility, amount)?,
115        )),
116        WeaponEnchantStat::PrimaryStat => Some(BuffEffect::PrimaryStat(amount)),
117        WeaponEnchantStat::PrimaryStatPercent => Some(BuffEffect::PrimaryStatPercent(amount)),
118        _ => None,
119    };
120
121    Ok(effect)
122}
123
124#[cfg(test)]
125mod tests;