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}