Skip to main content

wowlab_engine_content/hooks/fire_mage/
config.rs

1use wowlab_engine_combat::{
2    BuffEffect, CombatSystemDraft, DamageFlags, DriverSpellModifier, GroundEffectDefinition,
3    ImpactProc,
4};
5use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
6use wowlab_engine_ports::HandlerParams;
7use wowlab_types::constants::{HUNDRED, TEN_THOUSAND};
8
9use crate::generated::specs::fire_mage::{
10    AURA_COMBUSTION, AURA_HYPERTHERMIA, AURA_PYROCLASM, DAMAGE_EFFECT, EFFECT, SET_BONUS, SPELL,
11    TALENT,
12};
13
14static MID2_PYROCLASM_CRIT_SPELLS: [u32; 2] = [SPELL::PYROBLAST, SPELL::FLAMESTRIKE];
15
16use super::hooks::{
17    FIRESTARTER_CRIT_SPELLS, HYPERTHERMIA_CRIT_SPELLS, combustion_dynamic_mult,
18    controlled_destruction_impact, controlled_destruction_spell_filter, fevered_incantation_impact,
19    firestarter_crit_modifier, hot_streak_impact, hot_streak_spell_filter, ignite_fire,
20    ignite_spell_filter, intensifying_flame_fire, is_fireball, is_ignite, pyrotechnics_impact,
21};
22
23#[derive(Clone, Copy, Debug, Default)]
24pub(super) struct FireRuntime {
25    pub(super) glorious_incandescence: bool,
26    pub(super) heating_up_applied_ms: u32,
27    pub(super) hot_streak_pyro_in_flight: bool,
28    pub(super) fired_up_count: usize,
29    pub(super) spellfire_spheres: u8,
30    pub(super) combustion_spheres: u8,
31    pub(super) phoenix_exceptional_remaining: u8,
32}
33
34crate::hooks::define_spec_config! {
35    pub(super) struct FireConfig {
36        meteor_impact_ground: Option<wowlab_engine_combat::GroundEffectId> { provenance: "Finalized Meteor impact ground-effect plan.", gate: params.talent_picked(TALENT::METEOR), source: Some(built.register_ground_effect(GroundEffectDefinition::new(DAMAGE_EFFECT::METEOR_IMPACT_DAMAGE, DAMAGE_EFFECT::METEOR_IMPACT_DAMAGE).first_pulse_after(meteor_impact_delay_ms))?), transform: |value| value },
37        meteor_burn_ground: Option<wowlab_engine_combat::GroundEffectId> { provenance: "Finalized Meteor burn ground-effect plan.", gate: params.talent_picked(TALENT::METEOR), source: Some(built.register_ground_effect(GroundEffectDefinition::new(DAMAGE_EFFECT::METEOR_IMPACT_DAMAGE, DAMAGE_EFFECT::METEOR_BURN_DAMAGE).pulses(meteor_burn_pulses, meteor_burn_period_ms).first_pulse_after(meteor_impact_delay_ms.saturating_add(meteor_burn_period_ms)).flags(DamageFlags::PERIODIC))?), transform: |value| value },
38        burnout_fraction: f64 { provenance: "Burnout effect damage fraction.", gate: params.talent_picked(TALENT::BURNOUT), source: effect_fraction(EFFECT::BURNOUT_PCT), transform: |value| value },
39        firestarter_health_fraction: f64 { provenance: "Firestarter target-health threshold.", gate: params.talent_picked(TALENT::FIRESTARTER), source: params.game_data.effect_base_points(EFFECT::FIRESTARTER_THRESHOLD), transform: |value| value / HUNDRED },
40        intensifying_flame_fraction: f64 { provenance: "Intensifying Flame effect damage fraction.", gate: params.talent_picked(TALENT::INTENSIFYING_FLAME), source: effect_fraction(EFFECT::INTENSIFYING_FLAME_PCT), transform: |value| value },
41        fired_up: bool { provenance: "Fired Up rank one enables accumulated proc rolls.", gate: params.talent_picked(TALENT::FIRED_UP_1), source: true, transform: |value| value },
42        fired_up_chance: f64 { provenance: "Fired Up effect proc chance.", gate: true, source: effect_fraction(EFFECT::FIRED_UP_CHANCE), transform: |value| value },
43        fired_up_fire_blast_cdr_ms: u32 { provenance: "Fired Up effect Fire Blast cooldown reduction milliseconds.", gate: true, source: params.game_data.effect_base_points(EFFECT::FIRED_UP_FIRE_BLAST_CDR_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
44        fired_up_combustion_extend_ms: u32 { provenance: "Fired Up effect Combustion extension milliseconds.", gate: true, source: params.game_data.effect_base_points(EFFECT::FIRED_UP_COMBUSTION_EXTEND_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
45        combustion_base_pct: f64 { provenance: "Burn It All base Combustion percentage.", gate: params.talent_picked(TALENT::BURN_IT_ALL), source: params.game_data.effect_base_points(EFFECT::BURN_IT_ALL_BASE_PCT), transform: |value| value },
46        combustion_crit_fraction: f64 { provenance: "Burn It All critical-strike contribution fraction.", gate: params.talent_picked(TALENT::BURN_IT_ALL), source: effect_fraction(EFFECT::BURN_IT_ALL_CRIT_PCT), transform: |value| value },
47        combustion_pct_per_sphere: f64 { provenance: "Codex of the Sunstriders Combustion percentage per sphere.", gate: params.talent_picked(TALENT::CODEX_OF_THE_SUNSTRIDERS), source: params.game_data.effect_base_points(EFFECT::CODEX_COMBUSTION_PCT), transform: |value| value },
48        combustion_ms_per_sphere: u32 { provenance: "Savor the Moment Combustion milliseconds per sphere.", gate: params.talent_picked(TALENT::SAVOR_THE_MOMENT), source: params.game_data.effect_base_points(EFFECT::SAVOR_PER_SPHERE_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
49        combustion_extension_cap_ms: u32 { provenance: "Savor the Moment Combustion extension cap milliseconds.", gate: params.talent_picked(TALENT::SAVOR_THE_MOMENT), source: params.game_data.effect_base_points(EFFECT::SAVOR_CAP_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
50    controlled_destruction_fraction: f64 { provenance: "Controlled Destruction hundredth-percent value converted to a fraction.", gate: params.talent_picked(TALENT::CONTROLLED_DESTRUCTION), source: params.game_data.effect_base_points(EFFECT::CONTROLLED_DESTRUCTION_PCT_X100), transform: |value| value / TEN_THOUSAND },
51        invocation_arcane_phoenix: bool { provenance: "Invocation: Arcane Phoenix enables guardian summoning.", gate: params.talent_picked(TALENT::INVOCATION_ARCANE_PHOENIX), source: true, transform: |value| value },
52        memory_of_alar: bool { provenance: "Memory of Al'ar enables its sphere rider.", gate: params.talent_picked(TALENT::MEMORY_OF_ALAR), source: true, transform: |value| value },
53        codex_of_the_sunstriders: bool { provenance: "Codex of the Sunstriders enables sphere scaling.", gate: params.talent_picked(TALENT::CODEX_OF_THE_SUNSTRIDERS), source: true, transform: |value| value },
54        master_of_flame: bool { provenance: "Master of Flame enables its Ignite modifier.", gate: params.talent_picked(TALENT::MASTER_OF_FLAME), source: true, transform: |value| value },
55        pyromaniac: bool { provenance: "Pyromaniac enables its duplicate Pyroblast proc.", gate: params.talent_picked(TALENT::PYROMANIAC), source: true, transform: |value| value },
56        pyroclasm_chance: f64 { provenance: "Pyroclasm 269650 e1 chance after consuming Hot Streak.", gate: params.talent_picked(TALENT::PYROCLASM), source: params.game_data.effect_base_points(EFFECT::PYROCLASM_CHANCE), transform: |value| value / HUNDRED },
57    }
58    register fn register_fire_config(built, params) -> Result<(), wowlab_engine_ports::EngineError>;
59    prepare {
60        let effect_fraction = |effect| params.game_data.effect_percent(effect);
61        let meteor_selected = params.talent_picked(TALENT::METEOR);
62        let meteor_impact_delay_ms = meteor_selected
63            .then(|| params.game_data.require_aura_duration_ms(wowlab_types::sim::SpellIdx::from_raw(EFFECT::METEOR_DELAY.0)))
64            .transpose()?
65            .unwrap_or(0);
66        let meteor_burn_duration_ms = meteor_selected
67            .then(|| params.game_data.require_aura_duration_ms(wowlab_types::sim::SpellIdx::from_raw(EFFECT::METEOR_BURN_DURATION.0)))
68            .transpose()?
69            .unwrap_or(0);
70        let meteor_burn_period_ms = if meteor_selected {
71            wowlab_types::numeric::f64_to_u32_saturating_round(
72                params
73                    .game_data
74                    .effect_lookup(EFFECT::METEOR_BURN_DAMAGE)
75                    .period(),
76            )
77        } else {
78            1
79        };
80        if meteor_burn_period_ms == 0 {
81            return Err(wowlab_engine_ports::EngineError::spec_construction("Meteor Burn has an invalid pulse period"));
82        }
83        let meteor_burn_pulses = u8::try_from(meteor_burn_duration_ms.div_ceil(meteor_burn_period_ms.max(1))).unwrap_or(u8::MAX);
84    }
85    auras {}
86    wiring {
87        { provenance: "Firestarter evaluates the exact damage target's live health.", gate: params.talent_picked(TALENT::FIRESTARTER), apply: {
88            built.register_driver_spell_modifier(DriverSpellModifier::new(
89                TALENT::FIRESTARTER,
90                &FIRESTARTER_CRIT_SPELLS,
91                firestarter_crit_modifier,
92            ));
93        } },
94    }
95    finish |cfg| {
96        built.set_spec_config(cfg);
97        built.set_spec_runtime(FireRuntime::default());
98        Ok(())
99    }
100}
101
102pub(super) fn wire_fire(
103    built: &mut CombatSystemDraft,
104    params: &HandlerParams<'_>,
105) -> Result<(), wowlab_engine_ports::EngineError> {
106    register_fire_config(built, params)?;
107
108    if params.set_bonus_auras.contains(&SET_BONUS::MID2_FIRE_2PC) {
109        built.set_aura_effect(
110            AURA_PYROCLASM,
111            BuffEffect::CritChanceSpells(HUNDRED, &MID2_PYROCLASM_CRIT_SPELLS),
112        );
113    }
114
115    built.set_aura_effect(
116        AURA_COMBUSTION,
117        BuffEffect::DynamicDamageMult(combustion_dynamic_mult),
118    );
119    built.set_aura_effect(
120        AURA_HYPERTHERMIA,
121        BuffEffect::CritChanceSpells(HUNDRED, &HYPERTHERMIA_CRIT_SPELLS),
122    );
123    built.register_impact_proc(
124        ImpactProc::new(ignite_fire)
125            .with_spell_filter(ignite_spell_filter)
126            .skip_periodic(),
127    );
128    built.register_impact_proc(
129        ImpactProc::new(intensifying_flame_fire)
130            .with_spell_filter(is_ignite)
131            .periodic_only(),
132    );
133    built.register_impact_proc(
134        ImpactProc::new(hot_streak_impact)
135            .with_spell_filter(hot_streak_spell_filter)
136            .skip_periodic(),
137    );
138    built.register_impact_proc(
139        ImpactProc::new(fevered_incantation_impact)
140            .with_spell_filter(ignite_spell_filter)
141            .skip_periodic(),
142    );
143    built.register_impact_proc(
144        ImpactProc::new(pyrotechnics_impact)
145            .with_spell_filter(is_fireball)
146            .skip_periodic(),
147    );
148    built.register_impact_proc(
149        ImpactProc::new(controlled_destruction_impact)
150            .with_spell_filter(controlled_destruction_spell_filter)
151            .skip_periodic(),
152    );
153
154    Ok(())
155}