wowlab_engine_content/hooks/enhancement_shaman/
config.rs1use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
2use wowlab_types::{
3 constants::{HUNDRED, THOUSAND},
4 sim::SpellIdx,
5};
6
7use crate::generated::specs::enhancement_shaman::{EFFECT, HERO, SPELL, TALENT};
8
9const ELEMENTAL_WEAPONS_TENTHS_DIVISOR: f64 = 10.0;
10pub(super) const WINDFURY_BASE_HITS: u32 = 2;
11#[derive(Clone, Copy, Debug, Default)]
12pub(super) struct EnhancementRuntime {
13 pub(super) imbuement_mastery_failures: u32,
14 pub(super) lively_totems_failures: u32,
15 pub(super) primordial_storm_mw_stacks: i32,
16}
17
18crate::hooks::define_spec_config! {
19 pub(super) struct EnhancementConfig {
20 splitstream_mult: f64 { provenance: "Splitstream talent e1 damage fraction.", gate: params.talent_picked(TALENT::SPLITSTREAM), source: params.game_data.effect_base_points(EFFECT::SPLITSTREAM_DAMAGE), transform: |value| value / HUNDRED },
21 feral_spirit: bool { provenance: "Feral Spirit enables guardian summons.", gate: params.talent_picked(TALENT::FERAL_SPIRIT), source: true, transform: |value| value },
22 imbuement_mastery: bool { provenance: "Imbuement Mastery enables its weapon-imbue riders.", gate: params.talent_picked(TALENT::IMBUEMENT_MASTERY), source: true, transform: |value| value },
23 lively_totems: bool { provenance: "Lively Totems enables its accumulated trigger.", gate: params.talent_picked(TALENT::LIVELY_TOTEMS), source: true, transform: |value| value },
24 elemental_weapons_per_imbue: f64 { provenance: "Elemental Weapons e1 bonus per imbue in tenths of a percent.", gate: params.talent_picked(TALENT::ELEMENTAL_WEAPONS), source: params.game_data.effect_base_points(EFFECT::ELEMENTAL_WEAPONS_BONUS), transform: |value| value / (HUNDRED * ELEMENTAL_WEAPONS_TENTHS_DIVISOR) },
25 windfury_hits: u32 { provenance: "Windfury base hits plus one with Unruly Winds.", gate: true, source: WINDFURY_BASE_HITS + u32::from(params.talent_picked(TALENT::UNRULY_WINDS)), transform: |value| value },
26 stormblast_mult: f64 { provenance: "Stormblast effect damage fraction.", gate: params.talent_picked(TALENT::STORMBLAST), source: params.game_data.effect_base_points(EFFECT::STORMBLAST_BONUS), transform: |value| value / HUNDRED },
27 elemental_tempo_cdr_per_stack_ms: u32 { provenance: "Elemental Tempo effect cooldown reduction per stack.", gate: params.talent_picked(TALENT::ELEMENTAL_TEMPO), source: params.game_data.effect_base_points(EFFECT::ELEMENTAL_TEMPO_CDR), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
28 totemic_momentum_hot_hand_ms: u32 { provenance: "Totemic Momentum effect Hot Hand extension milliseconds.", gate: params.talent_picked(TALENT::TOTEMIC_MOMENTUM), source: params.game_data.effect_base_points(EFFECT::TOTEMIC_MOMENTUM_BONUS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
29 surging_totem_duration_ms: u32 { provenance: "Surging Totem's summon-spell duration owns the guardian lifetime.", gate: params.talent_selected(HERO::TOTEMIC::SPELL::SURGING_TOTEM), source: params.game_data.require_aura_duration_ms(SpellIdx::from_raw(SPELL::SURGING_TOTEM))?, transform: |value| value },
30 surging_totem_npc_id: u32 { provenance: "Surging Totem's summon effect provides the guardian creature identity.", gate: params.talent_selected(HERO::TOTEMIC::SPELL::SURGING_TOTEM), source: params.game_data.effect_misc_value_0(SpellIdx::from_raw(SPELL::SURGING_TOTEM), 1), transform: wowlab_types::numeric::i32_to_u32_nonnegative },
31 storm_unleashed_repeats: bool { provenance: "Storm Unleashed repeat binding enables repeats.", gate: params.talent_picked(TALENT::STORM_UNLEASHED_REPEATS), source: true, transform: |value| value },
32 storm_unleashed_chance_per_stack: f64 { provenance: "Storm Unleashed effect chance per stack.", gate: params.talent_picked(TALENT::STORM_UNLEASHED), source: params.game_data.effect_base_points(EFFECT::STORM_UNLEASHED_CHANCE), transform: |value| value / THOUSAND },
33 ashen_catalyst_cdr_ms: u32 { provenance: "Ashen Catalyst effect cooldown reduction milliseconds.", gate: params.talent_picked(TALENT::ASHEN_CATALYST), source: params.game_data.effect_base_points(EFFECT::ASHEN_CATALYST_CDR), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
34 voltaic_blaze_maelstrom_stacks: i32 { provenance: "Voltaic Blaze 470057 e2 resolved Maelstrom Weapon stacks.", gate: params.talent_picked(TALENT::VOLTAIC_BLAZE), source: params.game_data.effect_base_points(EFFECT::VOLTAIC_BLAZE_MAELSTROM_STACKS), transform: wowlab_types::numeric::f64_to_i32_saturating_trunc },
35 }
36 accessor pub(super) fn enhancement_config(ctx);
37 register pub(super) fn register_enhancement(built, params) -> Result<(), wowlab_engine_ports::EngineError>;
38 prepare {}
39 auras {}
40 wiring {
41 { provenance: "Totemic Rebound uses its DBC RPPM driver and proc-category recovery.", gate: params.talent_picked(EFFECT::TOTEMIC_REBOUND_RPPM_DRIVER.0), apply: {
42 let _ = built.register_system_rppm_from_data(EFFECT::TOTEMIC_REBOUND_RPPM_DRIVER.0)?;
43 } },
44 }
45 finish |cfg| {
46 built.register_weapon_imbue(SPELL::WINDFURY_WEAPON, "windfury_weapon", true);
47 built.register_weapon_imbue(SPELL::FLAMETONGUE_WEAPON, "flametongue_weapon", true);
48 built.state.set_spec_config(cfg);
49 built.state.set_spec_runtime(EnhancementRuntime::default());
50 Ok(())
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use googletest::prelude::*;
57 use wowlab_engine_ports::{CombatStats, ConsumableFlags, TalentSelection};
58use wowlab_engine_gamedata::{ResolvedGameData};
59 use wowlab_types::game::RaceId;
60
61 use super::*;
62
63 const UNUSUAL_VOLTAIC_BLAZE_STACKS: f64 = 4.0;
64
65 fn registered_config(selected: bool) -> Result<EnhancementConfig> {
66 let encounter = wowlab_engine_ports::test_support::introspection_fixture(60.0).or_fail()?;
67 let environment = wowlab_engine_ports::test_support::introspection_game_data(&encounter);
68 let mut data = ResolvedGameData::builder();
69
70 data.set_armor_constant(environment.armor_constant());
71 data.set_creature_armor(environment.creature_armor());
72 data.set_armor_constant_mod(environment.armor_constant_mod());
73 data.insert_base_points(
74 SpellIdx::from_raw(EFFECT::VOLTAIC_BLAZE_MAELSTROM_STACKS.0),
75 EFFECT::VOLTAIC_BLAZE_MAELSTROM_STACKS.1,
76 UNUSUAL_VOLTAIC_BLAZE_STACKS,
77 );
78 let data = data.build();
79 let stats = CombatStats::default();
80 let selected = selected
81 .then(|| TalentSelection {
82 spell_id: TALENT::VOLTAIC_BLAZE,
83 override_spell_id: 0,
84 replaces_spell_id: 0,
85 ranks: 1,
86 precombat_aura: false,
87 hero_tree: None,
88 effect_overrides: Vec::new(),
89 })
90 .into_iter()
91 .collect::<Vec<_>>();
92 let rotation = crate::test_support::wait_rotation();
93 let params =
94 crate::test_support::handler_params(crate::test_support::HandlerParamsFixture {
95 game_data: data.clone(),
96 rotation: &rotation,
97 stats: &stats,
98 talent_selections: &selected,
99 encounter: &encounter,
100 consumables: ConsumableFlags::default(),
101 race: RaceId::Human,
102 })
103 .or_fail()?;
104 let mut built = crate::test_combat_builder(CombatStats::default())
105 .game_data(data)
106 .build(crate::test_support::wait_rotation())
107 .or_fail()?;
108
109 register_enhancement(&mut built, ¶ms)?;
110
111 built
112 .state
113 .spec_config::<EnhancementConfig>()
114 .copied()
115 .or_fail()
116 }
117
118 #[gtest]
119 fn voltaic_blaze_stacks_use_resolved_effect_and_talent_gate() -> Result<()> {
120 verify_that!(
121 registered_config(true)?.voltaic_blaze_maelstrom_stacks,
122 eq(4)
123 )?;
124
125 verify_that!(
126 registered_config(false)?.voltaic_blaze_maelstrom_stacks,
127 eq(0)
128 )
129 }
130}