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, SET_BONUS, SPELL, TALENT};
8
9const ELEMENTAL_WEAPONS_TENTHS_DIVISOR: f64 = 10.0;
10const MID2_CRASH_SNAPSHOT_CAPACITY: usize = 16;
11pub(super) const WINDFURY_BASE_HITS: u32 = 2;
12#[derive(Clone, Copy, Debug, Default)]
13pub(super) struct EnhancementRuntime {
14 pub(super) imbuement_mastery_failures: u32,
15 pub(super) lively_totems_failures: u32,
16 pub(super) primordial_storm_mw_stacks: i32,
17 pub(super) mid2_crash_snapshots: [f64; MID2_CRASH_SNAPSHOT_CAPACITY],
18 pub(super) mid2_crash_snapshot_head: usize,
19 pub(super) mid2_crash_snapshot_count: usize,
20 pub(super) mid2_storm_unleashed_snapshot: f64,
21}
22
23crate::hooks::define_spec_config! {
24 pub(super) struct EnhancementConfig {
25 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 },
26 feral_spirit: bool { provenance: "Feral Spirit enables guardian summons.", gate: params.talent_picked(TALENT::FERAL_SPIRIT), source: true, transform: |value| value },
27 imbuement_mastery: bool { provenance: "Imbuement Mastery enables its weapon-imbue riders.", gate: params.talent_picked(TALENT::IMBUEMENT_MASTERY), source: true, transform: |value| value },
28 lively_totems: bool { provenance: "Lively Totems enables its accumulated trigger.", gate: params.talent_picked(TALENT::LIVELY_TOTEMS), source: true, transform: |value| value },
29 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) },
30 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 },
31 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 },
32 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 },
33 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 },
34 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 },
35 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 },
36 surging_totem_ap_inheritance: f64 { provenance: "Surging Totem's creature attack-power inheritance is frozen during composition.", gate: params.talent_selected(HERO::TOTEMIC::SPELL::SURGING_TOTEM), source: params.game_data.pet_attack_power_inheritance_mult(wowlab_types::numeric::i32_to_u32_nonnegative(params.game_data.effect_misc_value_0(SpellIdx::from_raw(SPELL::SURGING_TOTEM), 1))), transform: |value| value },
37 surging_totem_sp_inheritance: f64 { provenance: "Surging Totem's creature spell-power inheritance is frozen during composition.", gate: params.talent_selected(HERO::TOTEMIC::SPELL::SURGING_TOTEM), source: params.game_data.pet_spell_power_inheritance_mult(wowlab_types::numeric::i32_to_u32_nonnegative(params.game_data.effect_misc_value_0(SpellIdx::from_raw(SPELL::SURGING_TOTEM), 1))), transform: |value| value },
38 storm_unleashed_repeats: bool { provenance: "Storm Unleashed repeat binding enables repeats.", gate: params.talent_picked(TALENT::STORM_UNLEASHED_REPEATS), source: true, transform: |value| value },
39 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 },
40 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 },
41 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 },
42 mid2_2pc: bool { provenance: "MID2 Enhancement two-piece applies Burning Core from Voltaic Blaze.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_ENHANCEMENT_2PC), source: true, transform: |value| value },
43 mid2_fire_nova_primary_mult: f64 { provenance: "MID2 Enhancement two-piece Fire Nova bonus against Voltaic Blaze's primary target.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_ENHANCEMENT_2PC), source: params.game_data.effect_base_points(EFFECT::MID2_FIRE_NOVA_PRIMARY_BONUS), transform: |value| 1.0 + value / HUNDRED },
44 mid2_fire_nova_crash_cdr_ms: u32 { provenance: "MID2 Enhancement four-piece Crash Lightning cooldown reduction per Fire Nova.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_ENHANCEMENT_4PC), source: params.game_data.effect_base_points(EFFECT::MID2_FIRE_NOVA_CRASH_CDR_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
45 mid2_short_circuit_per_stack: f64 { provenance: "Short Circuit 1299991 e1 damage fraction snapshotted into Crash Lightning proc windows.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_ENHANCEMENT_4PC), source: params.game_data.effect_base_points(EFFECT::MID2_SHORT_CIRCUIT_DAMAGE), transform: |value| value / HUNDRED },
46 }
47 accessor pub(super) fn enhancement_config(ctx);
48 register pub(super) fn register_enhancement(built, params) -> Result<(), wowlab_engine_ports::EngineError>;
49 prepare {}
50 auras {}
51 wiring {
52 { provenance: "Totemic Rebound uses its DBC RPPM driver and proc-category recovery.", gate: params.talent_picked(EFFECT::TOTEMIC_REBOUND_RPPM_DRIVER.0), apply: {
53 built.bind_system_rppm(wowlab_engine_combat::generated_identities::system_rppm::TOTEMIC_REBOUND, EFFECT::TOTEMIC_REBOUND_RPPM_DRIVER.0)?;
54 } },
55 }
56 finish |cfg| {
57 built.register_weapon_imbue(SPELL::WINDFURY_WEAPON, "windfury_weapon", true);
58 built.register_weapon_imbue(SPELL::FLAMETONGUE_WEAPON, "flametongue_weapon", true);
59 built.set_spec_config(cfg);
60 built.set_spec_runtime(EnhancementRuntime::default());
61 Ok(())
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use googletest::prelude::*;
68 use wowlab_engine_gamedata::ResolvedGameData;
69 use wowlab_engine_ports::{CombatStats, ConsumableFlags, TalentSelection};
70 use wowlab_types::game::RaceId;
71
72 use super::*;
73
74 const UNUSUAL_VOLTAIC_BLAZE_STACKS: f64 = 4.0;
75
76 fn registered_config(selected: bool) -> Result<EnhancementConfig> {
77 let encounter = wowlab_engine_ports::test_support::introspection_fixture(60.0).or_fail()?;
78 let environment = wowlab_engine_ports::test_support::introspection_game_data(&encounter);
79 let mut data = ResolvedGameData::builder();
80
81 data.set_armor_constant(environment.armor_constant());
82 data.set_creature_armor(environment.creature_armor());
83 data.set_armor_constant_mod(environment.armor_constant_mod());
84 data.insert_base_points(
85 SpellIdx::from_raw(EFFECT::VOLTAIC_BLAZE_MAELSTROM_STACKS.0),
86 EFFECT::VOLTAIC_BLAZE_MAELSTROM_STACKS.1,
87 UNUSUAL_VOLTAIC_BLAZE_STACKS,
88 );
89 let data = data.build();
90 let stats = CombatStats::default();
91 let selected = selected
92 .then(|| TalentSelection {
93 spell_id: TALENT::VOLTAIC_BLAZE,
94 override_spell_id: 0,
95 replaces_spell_id: 0,
96 ranks: 1,
97 precombat_aura: false,
98 hero_tree: None,
99 effect_overrides: Vec::new(),
100 })
101 .into_iter()
102 .collect::<Vec<_>>();
103 let rotation = crate::test_support::wait_rotation();
104 let params =
105 crate::test_support::handler_params(crate::test_support::HandlerParamsFixture {
106 game_data: data.clone(),
107 rotation: &rotation,
108 stats: &stats,
109 talent_selections: &selected,
110 encounter: &encounter,
111 consumables: ConsumableFlags::default(),
112 race: RaceId::Human,
113 })
114 .or_fail()?;
115 let mut draft = crate::test_combat_builder(CombatStats::default())
116 .game_data(data)
117 .into_draft()
118 .or_fail()?;
119
120 register_enhancement(&mut draft, ¶ms)?;
121
122 draft.spec_config::<EnhancementConfig>().copied().or_fail()
123 }
124
125 #[gtest]
126 fn voltaic_blaze_stacks_use_resolved_effect_and_talent_gate() -> Result<()> {
127 verify_that!(
128 registered_config(true)?.voltaic_blaze_maelstrom_stacks,
129 eq(4)
130 )?;
131
132 verify_that!(
133 registered_config(false)?.voltaic_blaze_maelstrom_stacks,
134 eq(0)
135 )
136 }
137}