1#![allow(non_snake_case, dead_code)]
4use wowlab_types::{combat::ResourceType, game::SpecId};
5use wowlab_engine_domain::dbc::ProcAttributes;
6use wowlab_engine_combat::{
7 BuiltCombatSystem, AuraDefinitionDraft, LocalAuraIdx, LocalSpellIdx,
8 SpellDefinitionDraft,
9};
10use wowlab_engine_ports::{
11 DeclaredSpecMetadata, SpecDescriptor, SpecHandler, HeroTalentTreeDesc,
12 DeclaredServerProcEntry,
13};
14use wowlab_engine_gamedata::ResolvedGameData;
15pub(crate) mod SPELL {
16 pub(crate) const CONSUME: u32 = 473_662;
18 pub(crate) const DEVOUR: u32 = 1_217_610;
20 pub(crate) const VOIDBLADE: u32 = 1_245_412;
22 pub(crate) const VOID_RAY: u32 = 473_728;
24 pub(crate) const VOID_RAY_TICK: u32 = 1_213_649;
26 pub(crate) const VOID_RAY_TICK_META: u32 = 1_214_595;
28 pub(crate) const REAP: u32 = 1_226_019;
30 pub(crate) const VOID_METAMORPHOSIS: u32 = 1_217_607;
32 pub(crate) const IMMOLATION_AURA: u32 = 258_920;
34 pub(crate) const THE_HUNT: u32 = 370_965;
36 pub(crate) const CULL: u32 = 1_245_453;
38 pub(crate) const ERADICATE: u32 = 1_225_826;
40 pub(crate) const COLLAPSING_STAR_SPELL: u32 = 1_221_150;
42 pub(crate) const SOUL_IMMOLATION_SPELL: u32 = 1_241_937;
44 pub(crate) const HUNGERING_SLASH: u32 = 1_239_123;
46 pub(crate) const PREDATORS_WAKE: u32 = 1_246_160;
48 pub(crate) const PIERCE_THE_VEIL: u32 = 1_246_158;
50 pub(crate) const STUN: u32 = 179_057;
52}
53pub(crate) mod AURA {
54 pub(crate) const VOID_METAMORPHOSIS: u32 = 1_217_607;
56 pub(crate) const VOID_METAMORPHOSIS_STACK: u32 = 1_225_789;
58 pub(crate) const COLLAPSING_STAR: u32 = 1_221_171;
60 pub(crate) const MOMENT_OF_CRAVING: u32 = 1_238_495;
62 pub(crate) const SOUL_IMMOLATION: u32 = 1_241_937;
64 pub(crate) const SPONTANEOUS_IMMOLATION: u32 = 1_246_556;
66 pub(crate) const ENTROPY_TICKER: u32 = 1_261_684;
68 pub(crate) const SOUL_FRAGMENTS: u32 = 1_245_577;
70 pub(crate) const FEAST_OF_SOULS: u32 = 1_232_310;
72 pub(crate) const FOCUSED_RAY: u32 = 1_240_203;
74 pub(crate) const VOID_RAY: u32 = 473_728;
76 pub(crate) const VOIDFALL_BUILDING: u32 = 1_256_301;
78 pub(crate) const VOIDFALL_SPENDING: u32 = 1_256_302;
80 pub(crate) const VOIDFALL_FINAL_HOUR: u32 = 1_256_322;
82 pub(crate) const CATASTROPHE: u32 = 1_256_676;
84}
85pub(crate) mod TALENT {
86 pub(crate) const COLLAPSING_STAR: u32 = 1_221_150;
88 pub(crate) const FEAST_OF_SOULS: u32 = 1_237_270;
90 pub(crate) const SOULSHAPER: u32 = 1_238_739;
92 pub(crate) const THE_HUNT: u32 = 370_965;
94 pub(crate) const VOID_RAY: u32 = 473_728;
96}
97pub(crate) mod REPORTED_SPELL {
98 pub(crate) const REAP_DAMAGE: u32 = 1_225_823;
100 pub(crate) const VOIDBLADE_DAMAGE: u32 = 1_245_414;
102}
103pub(crate) mod HERO {
104 pub(crate) mod ANNIHILATOR {
105 pub(crate) mod SPELL {
106 pub(crate) const CATASTROPHE: u32 = 1_253_769;
108 pub(crate) const HARNESS_THE_COSMOS: u32 = 1_279_247;
110 pub(crate) const MASS_ACCELERATION: u32 = 1_256_295;
112 pub(crate) const METEORIC_FALL: u32 = 1_253_391;
114 pub(crate) const METEORIC_RISE: u32 = 1_253_377;
116 pub(crate) const OTHERWORLDLY_FOCUS: u32 = 1_253_817;
118 pub(crate) const VOIDFALL: u32 = 1_253_304;
120 pub(crate) const VOIDFALL_METEOR_DAMAGE: u32 = 1_256_305;
122 pub(crate) const WORLD_KILLER: u32 = 1_256_353;
124 pub(crate) const WORLD_KILLER_DAMAGE: u32 = 1_256_619;
126 }
127 pub(crate) mod AURA {
128 pub(crate) const VOIDFALL_BUILDING: u32 = 1_256_301;
130 pub(crate) const VOIDFALL_FINAL_HOUR: u32 = 1_256_322;
132 pub(crate) const VOIDFALL_SPENDING: u32 = 1_256_302;
134 }
135 }
136}
137static HERO_TREE_ANNIHILATOR_SPELLS: &[(&str, u32)] = &[
138 ("CATASTROPHE", 1_253_769),
139 ("HARNESS_THE_COSMOS", 1_279_247),
140 ("MASS_ACCELERATION", 1_256_295),
141 ("METEORIC_FALL", 1_253_391),
142 ("METEORIC_RISE", 1_253_377),
143 ("OTHERWORLDLY_FOCUS", 1_253_817),
144 ("VOIDFALL", 1_253_304),
145 ("VOIDFALL_METEOR_DAMAGE", 1_256_305),
146 ("WORLD_KILLER", 1_256_353),
147 ("WORLD_KILLER_DAMAGE", 1_256_619),
148];
149static HERO_TREE_ANNIHILATOR_AURAS: &[(&str, u32)] = &[
150 ("VOIDFALL_BUILDING", 1_256_301),
151 ("VOIDFALL_FINAL_HOUR", 1_256_322),
152 ("VOIDFALL_SPENDING", 1_256_302),
153];
154pub(crate) const AURA_VOID_METAMORPHOSIS: LocalAuraIdx = LocalAuraIdx::new(0);
155pub(crate) const AURA_VOID_METAMORPHOSIS_STACK: LocalAuraIdx = LocalAuraIdx::new(1);
156pub(crate) const AURA_COLLAPSING_STAR: LocalAuraIdx = LocalAuraIdx::new(2);
157pub(crate) const AURA_MOMENT_OF_CRAVING: LocalAuraIdx = LocalAuraIdx::new(3);
158pub(crate) const AURA_SOUL_IMMOLATION: LocalAuraIdx = LocalAuraIdx::new(4);
159pub(crate) const AURA_SPONTANEOUS_IMMOLATION: LocalAuraIdx = LocalAuraIdx::new(5);
160pub(crate) const AURA_ENTROPY_TICKER: LocalAuraIdx = LocalAuraIdx::new(6);
161pub(crate) const AURA_SOUL_FRAGMENTS: LocalAuraIdx = LocalAuraIdx::new(7);
162pub(crate) const AURA_FEAST_OF_SOULS: LocalAuraIdx = LocalAuraIdx::new(8);
163pub(crate) const AURA_FOCUSED_RAY: LocalAuraIdx = LocalAuraIdx::new(9);
164pub(crate) const AURA_VOID_RAY: LocalAuraIdx = LocalAuraIdx::new(10);
165pub(crate) const AURA_VOIDFALL_BUILDING: LocalAuraIdx = LocalAuraIdx::new(11);
166pub(crate) const AURA_VOIDFALL_SPENDING: LocalAuraIdx = LocalAuraIdx::new(12);
167pub(crate) const AURA_VOIDFALL_FINAL_HOUR: LocalAuraIdx = LocalAuraIdx::new(13);
168pub(crate) const AURA_CATASTROPHE: LocalAuraIdx = LocalAuraIdx::new(14);
169pub(crate) const AURA_COUNT: usize = 15;
170const _: () = assert!(
171 AURA_COUNT == 15, "AURA index ABI: count changed -- update positional hook consumers"
172);
173pub(crate) const SPELL_CONSUME: LocalSpellIdx = LocalSpellIdx::new(0);
174pub(crate) const SPELL_DEVOUR: LocalSpellIdx = LocalSpellIdx::new(1);
175pub(crate) const SPELL_VOIDBLADE: LocalSpellIdx = LocalSpellIdx::new(2);
176pub(crate) const SPELL_VOID_RAY: LocalSpellIdx = LocalSpellIdx::new(3);
177pub(crate) const SPELL_VOID_RAY_TICK: LocalSpellIdx = LocalSpellIdx::new(4);
178pub(crate) const SPELL_VOID_RAY_TICK_META: LocalSpellIdx = LocalSpellIdx::new(5);
179pub(crate) const SPELL_REAP: LocalSpellIdx = LocalSpellIdx::new(6);
180pub(crate) const SPELL_VOID_METAMORPHOSIS: LocalSpellIdx = LocalSpellIdx::new(7);
181pub(crate) const SPELL_IMMOLATION_AURA: LocalSpellIdx = LocalSpellIdx::new(8);
182pub(crate) const SPELL_THE_HUNT: LocalSpellIdx = LocalSpellIdx::new(9);
183pub(crate) const SPELL_CULL: LocalSpellIdx = LocalSpellIdx::new(10);
184pub(crate) const SPELL_ERADICATE: LocalSpellIdx = LocalSpellIdx::new(11);
185pub(crate) const SPELL_COLLAPSING_STAR_SPELL: LocalSpellIdx = LocalSpellIdx::new(12);
186pub(crate) const SPELL_SOUL_IMMOLATION_SPELL: LocalSpellIdx = LocalSpellIdx::new(13);
187pub(crate) const SPELL_HUNGERING_SLASH: LocalSpellIdx = LocalSpellIdx::new(14);
188pub(crate) const SPELL_PREDATORS_WAKE: LocalSpellIdx = LocalSpellIdx::new(15);
189pub(crate) const SPELL_PIERCE_THE_VEIL: LocalSpellIdx = LocalSpellIdx::new(16);
190pub(crate) const SPELL_STUN: LocalSpellIdx = LocalSpellIdx::new(17);
191pub(crate) const SPELL_COUNT: usize = 18;
192const _: () = assert!(
193 SPELL_COUNT == 18,
194 "SPELL index ABI: count changed -- update positional hook consumers"
195);
196pub(crate) mod EFFECT {
197 pub(crate) const ENTROPY_STARTING_SOULS: (u32, u8) = (1_261_684, 2);
199 pub(crate) const MONSTER_WITHIN_DIRECT: (u32, u8) = (1_238_855, 1);
201 pub(crate) const MONSTER_WITHIN_PERIODIC: (u32, u8) = (1_238_855, 2);
203 pub(crate) const MOMENT_OF_CRAVING_EXTRA_SOULS: (u32, u8) = (1_238_495, 1);
205 pub(crate) const REAP_DAMAGE: (u32, u8) = (1_225_823, 1);
207 pub(crate) const REAP_SOUL_LIMIT: (u32, u8) = (1_227_619, 2);
209 pub(crate) const SHATTERED_SOULS_CHANCE: (u32, u8) = (1_227_619, 1);
211 pub(crate) const SOULSHAPER_PER_FRAGMENT: (u32, u8) = (1_238_739, 1);
213 pub(crate) const SOUL_SPLITTER_CHANCE: (u32, u8) = (1_266_330, 1);
215 pub(crate) const CATASTROPHE_PCT: (u32, u8) = (1_253_769, 1);
217 pub(crate) const HARNESS_THE_COSMOS_PCT: (u32, u8) = (1_279_247, 1);
219 pub(crate) const MASS_ACCELERATION_STACKS: (u32, u8) = (1_256_295, 1);
221 pub(crate) const OTHERWORLDLY_FOCUS_PCT: (u32, u8) = (1_253_817, 1);
223 pub(crate) const VOIDFALL_CHANCE: (u32, u8) = (1_253_304, 3);
225 pub(crate) const VOIDFALL_METEOR_COEF: (u32, u8) = (1_256_305, 1);
227 pub(crate) const VOIDFALL_STACKS: (u32, u8) = (1_253_304, 1);
229 pub(crate) const WORLD_KILLER_COEF: (u32, u8) = (1_256_619, 1);
231 pub(crate) const WORLD_KILLER_SOULS: (u32, u8) = (1_256_353, 4);
233}
234static REPORTED_SPELLS: &[(&str, u32)] = &[
235 ("reap_damage", 1_225_823),
236 ("voidblade_damage", 1_245_414),
237];
238static DECLARED_SPELL_IDS: &[u32] = &[
239 473_662, 1_217_610, 1_245_412, 473_728, 1_213_649, 1_214_595, 1_226_019, 1_217_607,
240 258_920, 370_965, 1_245_453, 1_225_826, 1_221_150, 1_241_937, 1_239_123, 1_246_160,
241 1_246_158, 179_057, 1_217_607, 1_225_789, 1_221_171, 1_238_495, 1_241_937, 1_246_556,
242 1_261_684, 1_245_577, 1_232_310, 1_240_203, 473_728, 1_256_301, 1_256_302, 1_256_322,
243 1_256_676, 1_213_649, 1_225_823, 1_227_619, 1_238_495, 1_238_739, 1_238_855,
244 1_240_203, 1_245_414, 1_253_304, 1_253_769, 1_253_817, 1_256_295, 1_256_305,
245 1_256_353, 1_256_619, 1_256_676, 1_261_684, 1_266_330, 1_279_247, 1_225_823,
246 1_245_414, 1_253_769, 1_279_247, 1_256_295, 1_253_391, 1_253_377, 1_253_817,
247 1_253_304, 1_256_305, 1_256_353, 1_256_619, 1_256_301, 1_256_322, 1_256_302,
248];
249static DECLARED_AURA_IDS: &[u32] = &[
250 1_217_607, 1_225_789, 1_221_171, 1_238_495, 1_241_937, 1_246_556, 1_261_684,
251 1_245_577, 1_232_310, 1_240_203, 473_728, 1_256_301, 1_256_302, 1_256_322, 1_256_676,
252 1_256_301, 1_256_322, 1_256_302,
253];
254static TALENTS: &[(&str, u32)] = &[
255 ("COLLAPSING_STAR", TALENT::COLLAPSING_STAR),
256 ("FEAST_OF_SOULS", TALENT::FEAST_OF_SOULS),
257 ("SOULSHAPER", TALENT::SOULSHAPER),
258 ("THE_HUNT", TALENT::THE_HUNT),
259 ("VOID_RAY", TALENT::VOID_RAY),
260];
261fn aura_void_metamorphosis(
262 a: AuraDefinitionDraft,
263 data: &ResolvedGameData,
264) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
265 a.on_player()
266 .apply_base_from_data(data, 1_217_607)?
267 .max_stacks(
268 data
269 .require_aura_max_stacks(
270 wowlab_types::sim::SpellIdx::from_raw(1_217_607),
271 )?,
272 )
273 .periodic_resource_drain(
274 wowlab_engine_combat::validated_milliseconds(
275 2000.0,
276 1_217_607,
277 "periodic_resource_drain.tick_ms",
278 )?,
279 30.0,
280 )
281 .apply_periodic_behavior_from_data(data, 1_217_607)
282}
283fn aura_void_metamorphosis_stack(
284 a: AuraDefinitionDraft,
285 data: &ResolvedGameData,
286) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
287 Ok(
288 a
289 .on_player()
290 .apply_base_from_data(data, 1_225_789)?
291 .max_stacks(
292 data
293 .require_aura_max_stacks(
294 wowlab_types::sim::SpellIdx::from_raw(1_225_789),
295 )?,
296 ),
297 )
298}
299fn aura_collapsing_star(
300 a: AuraDefinitionDraft,
301 data: &ResolvedGameData,
302) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
303 Ok(
304 a
305 .on_player()
306 .apply_base_from_data(data, 1_221_171)?
307 .max_stacks(
308 data
309 .require_aura_max_stacks(
310 wowlab_types::sim::SpellIdx::from_raw(1_221_171),
311 )?,
312 ),
313 )
314}
315fn aura_moment_of_craving(
316 a: AuraDefinitionDraft,
317 data: &ResolvedGameData,
318) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
319 Ok(
320 a
321 .on_player()
322 .apply_base_from_data(data, 1_238_495)?
323 .max_stacks(
324 data
325 .require_aura_max_stacks(
326 wowlab_types::sim::SpellIdx::from_raw(1_238_495),
327 )?,
328 ),
329 )
330}
331fn aura_soul_immolation(
332 a: AuraDefinitionDraft,
333 data: &ResolvedGameData,
334) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
335 a.on_player()
336 .apply_base_from_data(data, 1_241_937)?
337 .max_stacks(
338 data
339 .require_aura_max_stacks(
340 wowlab_types::sim::SpellIdx::from_raw(1_241_937),
341 )?,
342 )
343 .periodic_damage(
344 wowlab_engine_combat::validated_milliseconds(
345 1000.0,
346 1_241_937,
347 "periodic_damage.tick_ms",
348 )?,
349 0.0,
350 wowlab_engine_combat::DamageFlags::empty(),
351 )
352 .apply_periodic_behavior_from_data(data, 1_241_937)
353}
354fn aura_spontaneous_immolation(
355 a: AuraDefinitionDraft,
356 data: &ResolvedGameData,
357) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
358 Ok(
359 a
360 .on_player()
361 .apply_base_from_data(data, 1_246_556)?
362 .max_stacks(
363 data
364 .require_aura_max_stacks(
365 wowlab_types::sim::SpellIdx::from_raw(1_246_556),
366 )?,
367 ),
368 )
369}
370fn aura_entropy_ticker(
371 a: AuraDefinitionDraft,
372 data: &ResolvedGameData,
373) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
374 a.on_player()
375 .apply_base_from_data(data, 1_261_684)?
376 .max_stacks(
377 data
378 .require_aura_max_stacks(
379 wowlab_types::sim::SpellIdx::from_raw(1_261_684),
380 )?,
381 )
382 .periodic_apply_aura(
383 wowlab_engine_combat::validated_milliseconds(
384 data.period(wowlab_types::sim::SpellIdx::from_raw(1_261_684), 1),
385 1_261_684,
386 "periodic_apply_aura.tick_ms",
387 )?,
388 1,
389 )
390 .apply_periodic_behavior_from_data(data, 1_261_684)
391}
392fn aura_soul_fragments(
393 a: AuraDefinitionDraft,
394 data: &ResolvedGameData,
395) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
396 Ok(
397 a
398 .on_player()
399 .apply_base_from_data(data, 1_245_577)?
400 .max_stacks(
401 data
402 .require_aura_max_stacks(
403 wowlab_types::sim::SpellIdx::from_raw(1_245_577),
404 )?,
405 ),
406 )
407}
408fn aura_feast_of_souls(
409 a: AuraDefinitionDraft,
410 data: &ResolvedGameData,
411) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
412 Ok(
413 a
414 .on_player()
415 .apply_base_from_data(data, 1_232_310)?
416 .max_stacks(
417 data
418 .require_aura_max_stacks(
419 wowlab_types::sim::SpellIdx::from_raw(1_232_310),
420 )?,
421 )
422 .async_stacks(),
423 )
424}
425fn aura_focused_ray(
426 a: AuraDefinitionDraft,
427 data: &ResolvedGameData,
428) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
429 a.on_player()
430 .apply_base_from_data(data, 1_240_203)?
431 .max_stacks(
432 data
433 .require_aura_max_stacks(
434 wowlab_types::sim::SpellIdx::from_raw(1_240_203),
435 )?,
436 )
437 .damage_percent_spells(
438 data.base_points(wowlab_types::sim::SpellIdx::from_raw(1_240_203), 1),
439 &[473_728],
440 )
441}
442fn aura_void_ray(
443 a: AuraDefinitionDraft,
444 data: &ResolvedGameData,
445) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
446 Ok(
447 a
448 .on_player()
449 .apply_base_from_data(data, 473_728)?
450 .max_stacks(
451 data
452 .require_aura_max_stacks(
453 wowlab_types::sim::SpellIdx::from_raw(473_728),
454 )?,
455 ),
456 )
457}
458fn aura_voidfall_building(
459 a: AuraDefinitionDraft,
460 data: &ResolvedGameData,
461) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
462 Ok(
463 a
464 .on_player()
465 .apply_base_from_data(data, 1_256_301)?
466 .max_stacks(
467 data
468 .require_aura_max_stacks(
469 wowlab_types::sim::SpellIdx::from_raw(1_256_301),
470 )?,
471 ),
472 )
473}
474fn aura_voidfall_spending(
475 a: AuraDefinitionDraft,
476 data: &ResolvedGameData,
477) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
478 Ok(
479 a
480 .on_player()
481 .apply_base_from_data(data, 1_256_302)?
482 .max_stacks(
483 data
484 .require_aura_max_stacks(
485 wowlab_types::sim::SpellIdx::from_raw(1_256_302),
486 )?,
487 ),
488 )
489}
490fn aura_voidfall_final_hour(
491 a: AuraDefinitionDraft,
492 data: &ResolvedGameData,
493) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
494 Ok(
495 a
496 .on_player()
497 .apply_base_from_data(data, 1_256_322)?
498 .max_stacks(
499 data
500 .require_aura_max_stacks(
501 wowlab_types::sim::SpellIdx::from_raw(1_256_322),
502 )?,
503 ),
504 )
505}
506fn aura_catastrophe(
507 a: AuraDefinitionDraft,
508 data: &ResolvedGameData,
509) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
510 a.on_target()
511 .apply_base_from_data(data, 1_256_676)?
512 .max_stacks(
513 data
514 .require_aura_max_stacks(
515 wowlab_types::sim::SpellIdx::from_raw(1_256_676),
516 )?,
517 )
518 .periodic_residual_damage(
519 wowlab_engine_combat::validated_milliseconds(
520 data.period(wowlab_types::sim::SpellIdx::from_raw(1_256_676), 1),
521 1_256_676,
522 "periodic_residual_damage.tick_ms",
523 )?,
524 )
525 .apply_periodic_behavior_from_data(data, 1_256_676)
526}
527fn spell_consume(
528 s: SpellDefinitionDraft,
529 data: &ResolvedGameData,
530) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
531 Ok(
532 s
533 .apply_base_from_data(data, 473_662)?
534 .gain(8.0)
535 .apply_aoe_from_data(data)
536 .on_cast(crate::hooks::devourer_demon_hunter::consume_hook),
537 )
538}
539fn spell_devour(
540 s: SpellDefinitionDraft,
541 data: &ResolvedGameData,
542) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
543 Ok(
544 s
545 .apply_base_from_data(data, 1_217_610)?
546 .gain(40.0)
547 .apply_aoe_from_data(data)
548 .on_cast(crate::hooks::devourer_demon_hunter::consume_hook),
549 )
550}
551fn spell_voidblade(
552 s: SpellDefinitionDraft,
553 data: &ResolvedGameData,
554) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
555 Ok(
556 s
557 .apply_base_from_data(data, 1_245_412)?
558 .gain(20.0)
559 .damage_sp_from_data(
560 wowlab_engine_domain::dbc::EffectLookup::new(
561 data,
562 wowlab_types::sim::EffectRef::new(
563 wowlab_types::sim::SpellIdx::from_raw(1_245_414),
564 2,
565 ),
566 ),
567 )?
568 .apply_aoe_from_data(data),
569 )
570}
571fn spell_void_ray(
572 s: SpellDefinitionDraft,
573 data: &ResolvedGameData,
574) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
575 Ok(
576 s
577 .apply_base_from_data(data, 473_728)?
578 .applies_inferred_aura(AURA_VOID_RAY, 473_728)
579 .apply_aoe_from_data(data)
580 .channel(
581 wowlab_engine_combat::validated_channel_tick_count(
582 20.0,
583 473_728,
584 "channel.tick_count",
585 )?,
586 )
587 .channel_tick_direct_damage()
588 .channel_tick_damage_sp_from_data(
589 wowlab_engine_domain::dbc::EffectLookup::new(
590 data,
591 wowlab_types::sim::EffectRef::new(
592 wowlab_types::sim::SpellIdx::from_raw(1_213_649),
593 1,
594 ),
595 ),
596 )?
597 .channel_tick_damage_alt_sp_from_data(
598 wowlab_engine_domain::dbc::EffectLookup::new(
599 data,
600 wowlab_types::sim::EffectRef::new(
601 wowlab_types::sim::SpellIdx::from_raw(1_214_595),
602 1,
603 ),
604 ),
605 AURA::VOID_METAMORPHOSIS,
606 )?
607 .channel_tick_cost(
608 data.require_cost(wowlab_types::sim::SpellIdx::from_raw(1_213_649))?,
609 )
610 .channel_tick_cost_alt(
611 data.require_cost(wowlab_types::sim::SpellIdx::from_raw(1_214_595))?,
612 AURA::VOID_METAMORPHOSIS,
613 )
614 .on_cast(crate::hooks::devourer_demon_hunter::void_ray_hook)
615 .cost_free_when_aura(1_217_607),
616 )
617}
618fn spell_void_ray_tick(
619 s: SpellDefinitionDraft,
620 data: &ResolvedGameData,
621) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
622 Ok(s.apply_base_from_data(data, 1_213_649)?.apply_aoe_from_data(data))
623}
624fn spell_void_ray_tick_meta(
625 s: SpellDefinitionDraft,
626 data: &ResolvedGameData,
627) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
628 Ok(s.apply_base_from_data(data, 1_214_595)?.apply_aoe_from_data(data))
629}
630fn spell_reap(
631 s: SpellDefinitionDraft,
632 data: &ResolvedGameData,
633) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
634 Ok(
635 s
636 .apply_base_from_data(data, 1_226_019)?
637 .apply_aoe_from_data(data)
638 .on_cast(crate::hooks::devourer_demon_hunter::reap_hook),
639 )
640}
641fn spell_void_metamorphosis(
642 s: SpellDefinitionDraft,
643 data: &ResolvedGameData,
644) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
645 Ok(
646 s
647 .apply_base_from_data(data, 1_217_607)?
648 .gain(200.0)
649 .applies_inferred_aura(AURA_VOID_METAMORPHOSIS, 1_217_607)
650 .apply_aoe_from_data(data)
651 .on_cast(crate::hooks::devourer_demon_hunter::void_metamorphosis_hook)
652 .requires_aura_at_min_stacks(1_225_789, 50),
653 )
654}
655fn spell_immolation_aura(
656 s: SpellDefinitionDraft,
657 data: &ResolvedGameData,
658) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
659 Ok(s.apply_base_from_data(data, 258_920)?.apply_aoe_from_data(data))
660}
661fn spell_the_hunt(
662 s: SpellDefinitionDraft,
663 data: &ResolvedGameData,
664) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
665 Ok(s.apply_base_from_data(data, 370_965)?.apply_aoe_from_data(data))
666}
667fn spell_cull(
668 s: SpellDefinitionDraft,
669 data: &ResolvedGameData,
670) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
671 Ok(s.apply_base_from_data(data, 1_245_453)?.apply_aoe_from_data(data))
672}
673fn spell_eradicate(
674 s: SpellDefinitionDraft,
675 data: &ResolvedGameData,
676) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
677 Ok(
678 s
679 .apply_base_from_data(data, 1_225_826)?
680 .apply_aoe_from_data(data)
681 .on_cast(crate::hooks::devourer_demon_hunter::eradicate_hook),
682 )
683}
684fn spell_collapsing_star_spell(
685 s: SpellDefinitionDraft,
686 data: &ResolvedGameData,
687) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
688 Ok(s.apply_base_from_data(data, 1_221_150)?.apply_aoe_from_data(data))
689}
690fn spell_soul_immolation_spell(
691 s: SpellDefinitionDraft,
692 data: &ResolvedGameData,
693) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
694 Ok(
695 s
696 .apply_base_from_data(data, 1_241_937)?
697 .applies_inferred_aura(AURA_SOUL_IMMOLATION, 1_241_937)
698 .apply_aoe_from_data(data),
699 )
700}
701fn spell_hungering_slash(
702 s: SpellDefinitionDraft,
703 data: &ResolvedGameData,
704) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
705 Ok(s.apply_base_from_data(data, 1_239_123)?.apply_aoe_from_data(data))
706}
707fn spell_predators_wake(
708 s: SpellDefinitionDraft,
709 data: &ResolvedGameData,
710) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
711 Ok(s.apply_base_from_data(data, 1_246_160)?.apply_aoe_from_data(data))
712}
713fn spell_pierce_the_veil(
714 s: SpellDefinitionDraft,
715 data: &ResolvedGameData,
716) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
717 Ok(s.apply_base_from_data(data, 1_246_158)?.apply_aoe_from_data(data))
718}
719fn spell_stun(
720 s: SpellDefinitionDraft,
721 data: &ResolvedGameData,
722) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
723 Ok(s.apply_base_from_data(data, 179_057)?.apply_aoe_from_data(data))
724}
725pub(crate) fn build_combat_system(
726 params: &wowlab_engine_ports::HandlerParams<'_>,
727) -> Result<BuiltCombatSystem, wowlab_engine_ports::EngineError> {
728 let builder = BuiltCombatSystem::builder(params.stats.clone())
729 .spec_id(SpecId::Devourer)
730 .hero_talent_trees(DECLARED_METADATA.hero_talent_trees)
731 .mastery_spell(1_238_855)
732 .mastery_hook(crate::hooks::devourer_demon_hunter::mastery)
733 .resource(
734 "fury",
735 120.0,
736 params
737 .game_data
738 .power_regen_for_max(17, 120.0)
739 .ok_or_else(|| {
740 wowlab_engine_ports::EngineError::spec_construction(
741 "missing power-type power_regen_for_max for type_id 17",
742 )
743 })?,
744 )
745 .resource_type_id(17)
746 .resource_start(
747 params
748 .game_data
749 .power_default(17)
750 .ok_or_else(|| {
751 wowlab_engine_ports::EngineError::spec_construction(
752 "missing power-type power_default for type_id 17",
753 )
754 })?,
755 )
756 .aura(
757 "void_metamorphosis",
758 AURA::VOID_METAMORPHOSIS,
759 |a| aura_void_metamorphosis(a, ¶ms.game_data),
760 )
761 .aura(
762 "void_metamorphosis_stack",
763 AURA::VOID_METAMORPHOSIS_STACK,
764 |a| aura_void_metamorphosis_stack(a, ¶ms.game_data),
765 )
766 .aura(
767 "collapsing_star",
768 AURA::COLLAPSING_STAR,
769 |a| aura_collapsing_star(a, ¶ms.game_data),
770 )
771 .aura(
772 "moment_of_craving",
773 AURA::MOMENT_OF_CRAVING,
774 |a| aura_moment_of_craving(a, ¶ms.game_data),
775 )
776 .aura(
777 "soul_immolation",
778 AURA::SOUL_IMMOLATION,
779 |a| aura_soul_immolation(a, ¶ms.game_data),
780 )
781 .aura(
782 "spontaneous_immolation",
783 AURA::SPONTANEOUS_IMMOLATION,
784 |a| aura_spontaneous_immolation(a, ¶ms.game_data),
785 )
786 .aura(
787 "entropy_ticker",
788 AURA::ENTROPY_TICKER,
789 |a| aura_entropy_ticker(a, ¶ms.game_data),
790 )
791 .aura(
792 "soul_fragments",
793 AURA::SOUL_FRAGMENTS,
794 |a| aura_soul_fragments(a, ¶ms.game_data),
795 )
796 .aura(
797 "feast_of_souls",
798 AURA::FEAST_OF_SOULS,
799 |a| aura_feast_of_souls(a, ¶ms.game_data),
800 )
801 .aura(
802 "focused_ray",
803 AURA::FOCUSED_RAY,
804 |a| aura_focused_ray(a, ¶ms.game_data),
805 )
806 .aura("void_ray", AURA::VOID_RAY, |a| aura_void_ray(a, ¶ms.game_data))
807 .aura(
808 "voidfall_building",
809 AURA::VOIDFALL_BUILDING,
810 |a| aura_voidfall_building(a, ¶ms.game_data),
811 )
812 .aura(
813 "voidfall_spending",
814 AURA::VOIDFALL_SPENDING,
815 |a| aura_voidfall_spending(a, ¶ms.game_data),
816 )
817 .aura(
818 "voidfall_final_hour",
819 AURA::VOIDFALL_FINAL_HOUR,
820 |a| aura_voidfall_final_hour(a, ¶ms.game_data),
821 )
822 .aura(
823 "catastrophe",
824 AURA::CATASTROPHE,
825 |a| aura_catastrophe(a, ¶ms.game_data),
826 )
827 .spell("consume", SPELL::CONSUME, |s| spell_consume(s, ¶ms.game_data))
828 .spell("devour", SPELL::DEVOUR, |s| spell_devour(s, ¶ms.game_data))
829 .spell("voidblade", SPELL::VOIDBLADE, |s| spell_voidblade(s, ¶ms.game_data))
830 .spell("void_ray", SPELL::VOID_RAY, |s| spell_void_ray(s, ¶ms.game_data))
831 .spell(
832 "void_ray_tick",
833 SPELL::VOID_RAY_TICK,
834 |s| spell_void_ray_tick(s, ¶ms.game_data),
835 )
836 .spell(
837 "void_ray_tick_meta",
838 SPELL::VOID_RAY_TICK_META,
839 |s| spell_void_ray_tick_meta(s, ¶ms.game_data),
840 )
841 .spell("reap", SPELL::REAP, |s| spell_reap(s, ¶ms.game_data))
842 .spell(
843 "void_metamorphosis",
844 SPELL::VOID_METAMORPHOSIS,
845 |s| spell_void_metamorphosis(s, ¶ms.game_data),
846 )
847 .spell(
848 "immolation_aura",
849 SPELL::IMMOLATION_AURA,
850 |s| spell_immolation_aura(s, ¶ms.game_data),
851 )
852 .spell("the_hunt", SPELL::THE_HUNT, |s| spell_the_hunt(s, ¶ms.game_data))
853 .spell("cull", SPELL::CULL, |s| spell_cull(s, ¶ms.game_data))
854 .spell("eradicate", SPELL::ERADICATE, |s| spell_eradicate(s, ¶ms.game_data))
855 .spell(
856 "collapsing_star_spell",
857 SPELL::COLLAPSING_STAR_SPELL,
858 |s| spell_collapsing_star_spell(s, ¶ms.game_data),
859 )
860 .spell(
861 "soul_immolation_spell",
862 SPELL::SOUL_IMMOLATION_SPELL,
863 |s| spell_soul_immolation_spell(s, ¶ms.game_data),
864 )
865 .spell(
866 "hungering_slash",
867 SPELL::HUNGERING_SLASH,
868 |s| spell_hungering_slash(s, ¶ms.game_data),
869 )
870 .spell(
871 "predators_wake",
872 SPELL::PREDATORS_WAKE,
873 |s| spell_predators_wake(s, ¶ms.game_data),
874 )
875 .spell(
876 "pierce_the_veil",
877 SPELL::PIERCE_THE_VEIL,
878 |s| spell_pierce_the_veil(s, ¶ms.game_data),
879 )
880 .spell("stun", SPELL::STUN, |s| spell_stun(s, ¶ms.game_data))
881 .game_data(params.game_data.clone())
882 .talent_spell_ids(TALENTS)
883 .talent_selections(params.talent_selections)
884 .set_bonus_auras(params.set_bonus_auras)
885 .encounter(params.encounter.clone());
886 let builder = crate::generated::items::register_equipped(
887 builder,
888 ¶ms.game_data,
889 params.equipped_items,
890 )?;
891
892 builder
893 .build(params.rotation)
894 .map_err(wowlab_engine_ports::EngineError::rotation_compile)
895}
896fn new_handler(
897 params: wowlab_engine_ports::HandlerParams<'_>,
898) -> Result<Box<dyn SpecHandler>, wowlab_engine_ports::EngineError> {
899 let handler = crate::composition::compose_handler(
900 build_combat_system,
901 ¶ms,
902 crate::hooks::devourer_demon_hunter::DevourerHandler::try_new,
903 )?;
904
905 Ok(Box::new(handler))
906}
907static DECLARED_METADATA: DeclaredSpecMetadata = DeclaredSpecMetadata {
908 primary_resource: ResourceType::Fury,
909 secondary_resource: None,
910 spell_ids: DECLARED_SPELL_IDS,
911 aura_ids: DECLARED_AURA_IDS,
912 server_proc_entries: &[
913 DeclaredServerProcEntry {
914 spell_id: 1_238_495,
915 attributes: ProcAttributes::TRIGGERED_CAN_PROC.bits()
916 | ProcAttributes::USE_STACKS_FOR_CHARGES.bits(),
917 },
918 ],
919 talents: TALENTS,
920 rotation_field_schema: &[],
921 metrics_plugin_id: None,
922 hero_talent_trees: &[
923 HeroTalentTreeDesc {
924 name: "Annihilator",
925 spells: HERO_TREE_ANNIHILATOR_SPELLS,
926 auras: HERO_TREE_ANNIHILATOR_AURAS,
927 },
928 ],
929 reported_spells: REPORTED_SPELLS,
930 masked_passive_effects: &[],
931 passive_effect_overrides: &[],
932};
933pub(super) const SPEC_DESCRIPTOR: SpecDescriptor = SpecDescriptor {
934 spec_id: SpecId::Devourer,
935 display_name: "Devourer Demon Hunter",
936 metadata: DECLARED_METADATA,
937 handler_factory: new_handler,
938};
939wowlab_engine_combat::smoke_test!(build_combat_system);