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 KILL_COMMAND: u32 = 259_489;
18 pub(crate) const WILDFIRE_BOMB: u32 = 259_495;
20 pub(crate) const MONGOOSE_BITE: u32 = 259_387;
22 pub(crate) const RAPTOR_STRIKE: u32 = 186_270;
24 pub(crate) const ARCANE_SHOT: u32 = 185_358;
26 pub(crate) const STEADY_SHOT: u32 = 56641;
28 pub(crate) const REVIVE_PET: u32 = 982;
30 pub(crate) const CALL_PET_1: u32 = 883;
32 pub(crate) const KILL_COMMAND_PET: u32 = 259_277;
34 pub(crate) const WILDFIRE_BOMB_DAMAGE: u32 = 265_157;
36 pub(crate) const WILDFIRE_BOMB_BLEED: u32 = 1_253_171;
38 pub(crate) const TAKEDOWN: u32 = 1_250_646;
40 pub(crate) const BOOMSTICK: u32 = 1_261_193;
42 pub(crate) const FLAMEFANG_PITCH: u32 = 1_251_592;
44 pub(crate) const HATCHET_TOSS: u32 = 193_265;
46 pub(crate) const RAPTOR_SWIPE: u32 = 1_262_293;
48 pub(crate) const BOOMSTICK_DAMAGE: u32 = 1_261_215;
50 pub(crate) const TAKEDOWN_DAMAGE: u32 = 1_253_859;
52 pub(crate) const TAKEDOWN_PET: u32 = 1_253_862;
54 pub(crate) const FLAMEFANG_PITCH_DAMAGE: u32 = 1_251_595;
56 pub(crate) const FLAMEFANG_PITCH_AOE: u32 = 1_251_614;
58 pub(crate) const CLAW: u32 = 16827;
60 pub(crate) const STRIKE_AS_ONE: u32 = 1_251_779;
62 pub(crate) const BOAR_CHARGE: u32 = 471_936;
64 pub(crate) const BOAR_CHARGE_CLEAVE: u32 = 471_938;
66 pub(crate) const STAMPEDE_TICK: u32 = 201_594;
68}
69pub(crate) mod AURA {
70 pub(crate) const UNNATURAL_CAUSES: u32 = 459_529;
72 pub(crate) const TIP_OF_THE_SPEAR: u32 = 260_286;
74 pub(crate) const TRACK_PETS: u32 = 462_599;
76 pub(crate) const RAPTOR_SWIPE: u32 = 1_273_155;
78 pub(crate) const TAKEDOWN: u32 = 1_250_646;
80 pub(crate) const HOWL_COOLDOWN: u32 = 471_877;
82 pub(crate) const WYVERN_READY: u32 = 471_878;
84 pub(crate) const BOAR_READY: u32 = 472_324;
86 pub(crate) const BEAR_READY: u32 = 472_325;
88 pub(crate) const REND_FLESH: u32 = 471_999;
90 pub(crate) const WYVERNS_CRY: u32 = 471_881;
92 pub(crate) const SIC_EM: u32 = 1_253_138;
94}
95pub(crate) mod TALENT {
96 pub(crate) const FLAMEFANG_PITCH: u32 = 1_251_592;
98 pub(crate) const RAPTOR_SWIPE_2: u32 = 1_259_017;
100 pub(crate) const STRIKE_AS_ONE: u32 = 1_251_717;
102 pub(crate) const TIP_OF_THE_SPEAR: u32 = 260_285;
104 pub(crate) const UNNATURAL_CAUSES: u32 = 459_527;
106}
107pub(crate) mod HERO {
108 pub(crate) mod PACK_LEADER {
109 pub(crate) mod SPELL {
110 pub(crate) const BOAR_CHARGE: u32 = 471_936;
112 pub(crate) const BOAR_CHARGE_CLEAVE: u32 = 471_938;
114 pub(crate) const HOWL_OF_THE_PACK_LEADER: u32 = 471_878;
116 }
117 pub(crate) mod AURA {
118 pub(crate) const BEAR_READY: u32 = 472_325;
120 pub(crate) const BOAR_READY: u32 = 472_324;
122 pub(crate) const ENVENOMED_FANGS: u32 = 471_944;
124 pub(crate) const MONGOOSE_FURY: u32 = 471_939;
126 pub(crate) const PACK_MENTALITY: u32 = 321_014;
128 pub(crate) const WYVERN_READY: u32 = 471_878;
130 }
131 }
132 pub(crate) mod SENTINEL {
133 pub(crate) mod SPELL {
134 pub(crate) const LUNAR_STORM_DAMAGE: u32 = 1_253_733;
136 pub(crate) const MOONLIGHT_CHAKRAM: u32 = 1_264_949;
138 pub(crate) const MOONLIGHT_CHAKRAM_DAMAGE: u32 = 1_266_081;
140 pub(crate) const TWILIGHT_REQUIEM_DAMAGE: u32 = 1_266_096;
142 }
143 pub(crate) mod AURA {
144 pub(crate) const MOONLIGHT_CHAKRAM_BUFF: u32 = 1_264_946;
146 pub(crate) const SANCTIFIED_ARMAMENTS_DOT: u32 = 1_253_836;
148 pub(crate) const SENTINELS_MARK: u32 = 1_253_601;
150 pub(crate) const STARGAZER: u32 = 1_253_750;
152 }
153 }
154}
155static HERO_TREE_PACK_LEADER_SPELLS: &[(&str, u32)] = &[
156 ("BOAR_CHARGE", 471_936),
157 ("BOAR_CHARGE_CLEAVE", 471_938),
158 ("HOWL_OF_THE_PACK_LEADER", 471_878),
159];
160static HERO_TREE_PACK_LEADER_AURAS: &[(&str, u32)] = &[
161 ("BEAR_READY", 472_325),
162 ("BOAR_READY", 472_324),
163 ("ENVENOMED_FANGS", 471_944),
164 ("MONGOOSE_FURY", 471_939),
165 ("PACK_MENTALITY", 321_014),
166 ("WYVERN_READY", 471_878),
167];
168static HERO_TREE_SENTINEL_SPELLS: &[(&str, u32)] = &[
169 ("LUNAR_STORM_DAMAGE", 1_253_733),
170 ("MOONLIGHT_CHAKRAM", 1_264_949),
171 ("MOONLIGHT_CHAKRAM_DAMAGE", 1_266_081),
172 ("TWILIGHT_REQUIEM_DAMAGE", 1_266_096),
173];
174static HERO_TREE_SENTINEL_AURAS: &[(&str, u32)] = &[
175 ("MOONLIGHT_CHAKRAM_BUFF", 1_264_946),
176 ("SANCTIFIED_ARMAMENTS_DOT", 1_253_836),
177 ("SENTINELS_MARK", 1_253_601),
178 ("STARGAZER", 1_253_750),
179];
180pub(crate) const AURA_UNNATURAL_CAUSES: LocalAuraIdx = LocalAuraIdx::new(0);
181pub(crate) const AURA_TIP_OF_THE_SPEAR: LocalAuraIdx = LocalAuraIdx::new(1);
182pub(crate) const AURA_TRACK_PETS: LocalAuraIdx = LocalAuraIdx::new(2);
183pub(crate) const AURA_RAPTOR_SWIPE: LocalAuraIdx = LocalAuraIdx::new(3);
184pub(crate) const AURA_TAKEDOWN: LocalAuraIdx = LocalAuraIdx::new(4);
185pub(crate) const AURA_HOWL_COOLDOWN: LocalAuraIdx = LocalAuraIdx::new(5);
186pub(crate) const AURA_WYVERN_READY: LocalAuraIdx = LocalAuraIdx::new(6);
187pub(crate) const AURA_BOAR_READY: LocalAuraIdx = LocalAuraIdx::new(7);
188pub(crate) const AURA_BEAR_READY: LocalAuraIdx = LocalAuraIdx::new(8);
189pub(crate) const AURA_REND_FLESH: LocalAuraIdx = LocalAuraIdx::new(9);
190pub(crate) const AURA_WYVERNS_CRY: LocalAuraIdx = LocalAuraIdx::new(10);
191pub(crate) const AURA_SIC_EM: LocalAuraIdx = LocalAuraIdx::new(11);
192pub(crate) const AURA_COUNT: usize = 12;
193const _: () = assert!(
194 AURA_COUNT == 12, "AURA index ABI: count changed -- update positional hook consumers"
195);
196pub(crate) const SPELL_KILL_COMMAND: LocalSpellIdx = LocalSpellIdx::new(0);
197pub(crate) const SPELL_WILDFIRE_BOMB: LocalSpellIdx = LocalSpellIdx::new(1);
198pub(crate) const SPELL_MONGOOSE_BITE: LocalSpellIdx = LocalSpellIdx::new(2);
199pub(crate) const SPELL_RAPTOR_STRIKE: LocalSpellIdx = LocalSpellIdx::new(3);
200pub(crate) const SPELL_ARCANE_SHOT: LocalSpellIdx = LocalSpellIdx::new(4);
201pub(crate) const SPELL_STEADY_SHOT: LocalSpellIdx = LocalSpellIdx::new(5);
202pub(crate) const SPELL_REVIVE_PET: LocalSpellIdx = LocalSpellIdx::new(6);
203pub(crate) const SPELL_CALL_PET_1: LocalSpellIdx = LocalSpellIdx::new(7);
204pub(crate) const SPELL_KILL_COMMAND_PET: LocalSpellIdx = LocalSpellIdx::new(8);
205pub(crate) const SPELL_WILDFIRE_BOMB_DAMAGE: LocalSpellIdx = LocalSpellIdx::new(9);
206pub(crate) const SPELL_WILDFIRE_BOMB_BLEED: LocalSpellIdx = LocalSpellIdx::new(10);
207pub(crate) const SPELL_TAKEDOWN: LocalSpellIdx = LocalSpellIdx::new(11);
208pub(crate) const SPELL_BOOMSTICK: LocalSpellIdx = LocalSpellIdx::new(12);
209pub(crate) const SPELL_FLAMEFANG_PITCH: LocalSpellIdx = LocalSpellIdx::new(13);
210pub(crate) const SPELL_HATCHET_TOSS: LocalSpellIdx = LocalSpellIdx::new(14);
211pub(crate) const SPELL_RAPTOR_SWIPE: LocalSpellIdx = LocalSpellIdx::new(15);
212pub(crate) const SPELL_BOOMSTICK_DAMAGE: LocalSpellIdx = LocalSpellIdx::new(16);
213pub(crate) const SPELL_TAKEDOWN_DAMAGE: LocalSpellIdx = LocalSpellIdx::new(17);
214pub(crate) const SPELL_TAKEDOWN_PET: LocalSpellIdx = LocalSpellIdx::new(18);
215pub(crate) const SPELL_FLAMEFANG_PITCH_DAMAGE: LocalSpellIdx = LocalSpellIdx::new(19);
216pub(crate) const SPELL_FLAMEFANG_PITCH_AOE: LocalSpellIdx = LocalSpellIdx::new(20);
217pub(crate) const SPELL_CLAW: LocalSpellIdx = LocalSpellIdx::new(21);
218pub(crate) const SPELL_STRIKE_AS_ONE: LocalSpellIdx = LocalSpellIdx::new(22);
219pub(crate) const SPELL_BOAR_CHARGE: LocalSpellIdx = LocalSpellIdx::new(23);
220pub(crate) const SPELL_BOAR_CHARGE_CLEAVE: LocalSpellIdx = LocalSpellIdx::new(24);
221pub(crate) const SPELL_STAMPEDE_TICK: LocalSpellIdx = LocalSpellIdx::new(25);
222pub(crate) const SPELL_COUNT: usize = 26;
223const _: () = assert!(
224 SPELL_COUNT == 26,
225 "SPELL index ABI: count changed -- update positional hook consumers"
226);
227pub(crate) mod EFFECT {
228 pub(crate) const BOAR_CHARGE: (u32, u8) = (471_936, 1);
230 pub(crate) const BOAR_CHARGE_CLEAVE: (u32, u8) = (471_938, 1);
232 pub(crate) const BOOMSTICK_DAMAGE: (u32, u8) = (1_261_215, 1);
234 pub(crate) const FLAMEFANG_PITCH_AOE: (u32, u8) = (1_251_614, 1);
236 pub(crate) const FLAMEFANG_PITCH_DAMAGE: (u32, u8) = (1_251_595, 1);
238 pub(crate) const HOWL_WYVERN_STACKS: (u32, u8) = (471_876, 3);
240 pub(crate) const KILL_COMMAND_PET_DAMAGE: (u32, u8) = (259_277, 1);
242 pub(crate) const RAPTOR_STRIKE_DAMAGE: (u32, u8) = (186_270, 1);
244 pub(crate) const RAPTOR_SWIPE_CHANCE: (u32, u8) = (1_259_019, 1);
246 pub(crate) const RAPTOR_SWIPE_DAMAGE: (u32, u8) = (1_262_293, 1);
248 pub(crate) const RAPTOR_SWIPE_STRIKE_AS_ONE: (u32, u8) = (1_259_019, 2);
250 pub(crate) const RAPTOR_SWIPE_TWO_PRIMARY: (u32, u8) = (1_259_017, 2);
252 pub(crate) const SPIRIT_BOND_DIRECT: (u32, u8) = (263_135, 1);
254 pub(crate) const STAMPEDE_DAMAGE: (u32, u8) = (201_594, 1);
256 pub(crate) const STRIKE_AS_ONE_DAMAGE: (u32, u8) = (1_251_779, 1);
258 pub(crate) const TAKEDOWN_DAMAGE: (u32, u8) = (1_253_859, 1);
260 pub(crate) const TAKEDOWN_FOCUS: (u32, u8) = (1_258_571, 1);
262 pub(crate) const TAKEDOWN_PET_DAMAGE: (u32, u8) = (1_253_862, 1);
264 pub(crate) const TIP_OF_THE_SPEAR_BONUS: (u32, u8) = (260_285, 1);
266 pub(crate) const WILDFIRE_BOMB_DAMAGE: (u32, u8) = (265_157, 1);
268 pub(crate) const WILDFIRE_BOMB_DOT: (u32, u8) = (1_253_171, 1);
270}
271static REPORTED_SPELLS: &[(&str, u32)] = &[];
272static DECLARED_SPELL_IDS: &[u32] = &[
273 259_489, 259_495, 259_387, 186_270, 185_358, 56641, 982, 883, 259_277, 265_157,
274 1_253_171, 1_250_646, 1_261_193, 1_251_592, 193_265, 1_262_293, 1_261_215, 1_253_859,
275 1_253_862, 1_251_595, 1_251_614, 16827, 1_251_779, 471_936, 471_938, 201_594,
276 459_529, 260_286, 462_599, 1_273_155, 1_250_646, 471_877, 471_878, 472_324, 472_325,
277 471_999, 471_881, 1_253_138, 6603, 6603, 186_270, 201_594, 259_277, 260_285, 263_135,
278 265_157, 471_876, 471_936, 471_938, 471_993, 471_999, 1_251_595, 1_251_614,
279 1_251_779, 1_253_138, 1_253_171, 1_253_859, 1_253_862, 1_258_571, 1_259_017,
280 1_259_019, 1_261_215, 1_262_293, 471_936, 471_938, 471_878, 472_325, 472_324,
281 471_944, 471_939, 321_014, 471_878, 1_253_733, 1_264_949, 1_266_081, 1_266_096,
282 1_264_946, 1_253_836, 1_253_601, 1_253_750,
283];
284static DECLARED_AURA_IDS: &[u32] = &[
285 459_529, 260_286, 462_599, 1_273_155, 1_250_646, 471_877, 471_878, 472_324, 472_325,
286 471_999, 471_881, 1_253_138, 472_325, 472_324, 471_944, 471_939, 321_014, 471_878,
287 1_264_946, 1_253_836, 1_253_601, 1_253_750,
288];
289static TALENTS: &[(&str, u32)] = &[
290 ("FLAMEFANG_PITCH", TALENT::FLAMEFANG_PITCH),
291 ("RAPTOR_SWIPE_2", TALENT::RAPTOR_SWIPE_2),
292 ("STRIKE_AS_ONE", TALENT::STRIKE_AS_ONE),
293 ("TIP_OF_THE_SPEAR", TALENT::TIP_OF_THE_SPEAR),
294 ("UNNATURAL_CAUSES", TALENT::UNNATURAL_CAUSES),
295];
296fn aura_unnatural_causes(
297 a: AuraDefinitionDraft,
298 data: &ResolvedGameData,
299) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
300 Ok(
301 a
302 .on_target()
303 .apply_base_from_data(data, 459_529)?
304 .max_stacks(
305 data
306 .require_aura_max_stacks(
307 wowlab_types::sim::SpellIdx::from_raw(459_529),
308 )?,
309 ),
310 )
311}
312fn aura_tip_of_the_spear(
313 a: AuraDefinitionDraft,
314 data: &ResolvedGameData,
315) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
316 Ok(
317 a
318 .on_player()
319 .apply_base_from_data(data, 260_286)?
320 .max_stacks(
321 data
322 .require_aura_max_stacks(
323 wowlab_types::sim::SpellIdx::from_raw(260_286),
324 )?,
325 ),
326 )
327}
328fn aura_track_pets(
329 a: AuraDefinitionDraft,
330 data: &ResolvedGameData,
331) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
332 Ok(
333 a
334 .on_player()
335 .apply_base_from_data(data, 462_599)?
336 .max_stacks(
337 data
338 .require_aura_max_stacks(
339 wowlab_types::sim::SpellIdx::from_raw(462_599),
340 )?,
341 ),
342 )
343}
344fn aura_raptor_swipe(
345 a: AuraDefinitionDraft,
346 data: &ResolvedGameData,
347) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
348 Ok(
349 a
350 .on_player()
351 .apply_base_from_data(data, 1_273_155)?
352 .max_stacks(
353 data
354 .require_aura_max_stacks(
355 wowlab_types::sim::SpellIdx::from_raw(1_273_155),
356 )?,
357 ),
358 )
359}
360fn aura_takedown(
361 a: AuraDefinitionDraft,
362 data: &ResolvedGameData,
363) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
364 Ok(
365 a
366 .on_player()
367 .apply_base_from_data(data, 1_250_646)?
368 .max_stacks(
369 data
370 .require_aura_max_stacks(
371 wowlab_types::sim::SpellIdx::from_raw(1_250_646),
372 )?,
373 ),
374 )
375}
376fn aura_howl_cooldown(
377 a: AuraDefinitionDraft,
378 data: &ResolvedGameData,
379) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
380 Ok(
381 a
382 .on_player()
383 .apply_base_from_data(data, 471_877)?
384 .max_stacks(
385 data
386 .require_aura_max_stacks(
387 wowlab_types::sim::SpellIdx::from_raw(471_877),
388 )?,
389 )
390 .on_expire(crate::hooks::survival_hunter::howl_cooldown_expire_hook),
391 )
392}
393fn aura_wyvern_ready(
394 a: AuraDefinitionDraft,
395 data: &ResolvedGameData,
396) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
397 Ok(
398 a
399 .on_player()
400 .apply_base_from_data(data, 471_878)?
401 .max_stacks(
402 data
403 .require_aura_max_stacks(
404 wowlab_types::sim::SpellIdx::from_raw(471_878),
405 )?,
406 ),
407 )
408}
409fn aura_boar_ready(
410 a: AuraDefinitionDraft,
411 data: &ResolvedGameData,
412) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
413 Ok(
414 a
415 .on_player()
416 .apply_base_from_data(data, 472_324)?
417 .max_stacks(
418 data
419 .require_aura_max_stacks(
420 wowlab_types::sim::SpellIdx::from_raw(472_324),
421 )?,
422 ),
423 )
424}
425fn aura_bear_ready(
426 a: AuraDefinitionDraft,
427 data: &ResolvedGameData,
428) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
429 Ok(
430 a
431 .on_player()
432 .apply_base_from_data(data, 472_325)?
433 .max_stacks(
434 data
435 .require_aura_max_stacks(
436 wowlab_types::sim::SpellIdx::from_raw(472_325),
437 )?,
438 ),
439 )
440}
441fn aura_rend_flesh(
442 a: AuraDefinitionDraft,
443 data: &ResolvedGameData,
444) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
445 a.on_target()
446 .apply_base_from_data(data, 471_999)?
447 .max_stacks(
448 data.require_aura_max_stacks(wowlab_types::sim::SpellIdx::from_raw(471_999))?,
449 )
450 .periodic_damage(
451 wowlab_engine_combat::validated_milliseconds(
452 data.period(wowlab_types::sim::SpellIdx::from_raw(471_999), 1),
453 471_999,
454 "periodic_damage.tick_ms",
455 )?,
456 (((data.ap_coef(wowlab_types::sim::SpellIdx::from_raw(471_999), 1)) * 0.6)
457 * 100.0)
458 .round() / 100.0,
459 wowlab_engine_combat::DamageFlags::PHYSICAL,
460 )
461 .apply_periodic_behavior_from_data(data, 471_999)
462}
463fn aura_wyverns_cry(
464 a: AuraDefinitionDraft,
465 data: &ResolvedGameData,
466) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
467 a.on_player()
468 .duration_ms(
469 wowlab_engine_combat::validated_milliseconds(
470 f64::from(
471 data
472 .require_aura_duration_ms(
473 wowlab_types::sim::SpellIdx::from_raw(471_993),
474 )?,
475 ),
476 471_881,
477 "aura.duration_ms",
478 )?,
479 )
480 .apply_periodic_effect_from_data(data, 471_881)?
481 .apply_duration_behavior_from_data(data, 471_993)?
482 .apply_timing_modifiers_from_data(data, 471_881)?
483 .max_stacks(
484 data.require_aura_max_stacks(wowlab_types::sim::SpellIdx::from_raw(471_881))?,
485 )
486 .apply_periodic_behavior_from_data(data, 471_993)
487}
488fn aura_sic_em(
489 a: AuraDefinitionDraft,
490 data: &ResolvedGameData,
491) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
492 a.on_target()
493 .apply_base_from_data(data, 1_253_138)?
494 .max_stacks(
495 data
496 .require_aura_max_stacks(
497 wowlab_types::sim::SpellIdx::from_raw(1_253_138),
498 )?,
499 )
500 .periodic_damage(
501 wowlab_engine_combat::validated_milliseconds(
502 data.period(wowlab_types::sim::SpellIdx::from_raw(1_253_138), 1),
503 1_253_138,
504 "periodic_damage.tick_ms",
505 )?,
506 (((data.ap_coef(wowlab_types::sim::SpellIdx::from_raw(1_253_138), 1)) * 0.6)
507 * 1000.0)
508 .round() / 1000.0,
509 wowlab_engine_combat::DamageFlags::PHYSICAL,
510 )
511 .apply_periodic_behavior_from_data(data, 1_253_138)
512}
513fn spell_kill_command(
514 s: SpellDefinitionDraft,
515 data: &ResolvedGameData,
516) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
517 Ok(
518 s
519 .apply_base_from_data(data, 259_489)?
520 .gain(15.0)
521 .applies_aura(AURA_TIP_OF_THE_SPEAR)
522 .apply_aoe_from_data(data)
523 .on_cast(crate::hooks::survival_hunter::kill_command_hook),
524 )
525}
526fn spell_wildfire_bomb(
527 s: SpellDefinitionDraft,
528 data: &ResolvedGameData,
529) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
530 Ok(
531 s
532 .apply_base_from_data(data, 259_495)?
533 .apply_aoe_from_data(data)
534 .on_cast(crate::hooks::survival_hunter::wildfire_bomb_hook),
535 )
536}
537fn spell_mongoose_bite(
538 s: SpellDefinitionDraft,
539 data: &ResolvedGameData,
540) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
541 Ok(s.apply_base_from_data(data, 259_387)?.apply_aoe_from_data(data))
542}
543fn spell_raptor_strike(
544 s: SpellDefinitionDraft,
545 data: &ResolvedGameData,
546) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
547 Ok(
548 s
549 .apply_base_from_data(data, 186_270)?
550 .damage_flat(0.0)
551 .apply_aoe_from_data(data)
552 .on_cast(crate::hooks::survival_hunter::raptor_strike_hook),
553 )
554}
555fn spell_arcane_shot(
556 s: SpellDefinitionDraft,
557 data: &ResolvedGameData,
558) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
559 Ok(s.apply_base_from_data(data, 185_358)?.apply_aoe_from_data(data))
560}
561fn spell_steady_shot(
562 s: SpellDefinitionDraft,
563 data: &ResolvedGameData,
564) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
565 Ok(s.apply_base_from_data(data, 56641)?.apply_aoe_from_data(data))
566}
567fn spell_revive_pet(
568 s: SpellDefinitionDraft,
569 data: &ResolvedGameData,
570) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
571 Ok(s.apply_base_from_data(data, 982)?.apply_aoe_from_data(data))
572}
573fn spell_call_pet_1(
574 s: SpellDefinitionDraft,
575 data: &ResolvedGameData,
576) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
577 Ok(s.apply_base_from_data(data, 883)?.apply_aoe_from_data(data))
578}
579fn spell_kill_command_pet(
580 s: SpellDefinitionDraft,
581 data: &ResolvedGameData,
582) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
583 Ok(s.apply_base_from_data(data, 259_277)?.apply_aoe_from_data(data))
584}
585fn spell_wildfire_bomb_damage(
586 s: SpellDefinitionDraft,
587 data: &ResolvedGameData,
588) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
589 Ok(s.apply_base_from_data(data, 265_157)?.apply_aoe_from_data(data))
590}
591fn spell_wildfire_bomb_bleed(
592 s: SpellDefinitionDraft,
593 data: &ResolvedGameData,
594) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
595 Ok(s.apply_base_from_data(data, 1_253_171)?.apply_aoe_from_data(data))
596}
597fn spell_takedown(
598 s: SpellDefinitionDraft,
599 data: &ResolvedGameData,
600) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
601 Ok(
602 s
603 .apply_base_from_data(data, 1_250_646)?
604 .applies_inferred_aura(AURA_TAKEDOWN, 1_250_646)
605 .apply_aoe_from_data(data)
606 .on_cast(crate::hooks::survival_hunter::takedown_hook),
607 )
608}
609fn spell_boomstick(
610 s: SpellDefinitionDraft,
611 data: &ResolvedGameData,
612) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
613 Ok(
614 s
615 .apply_base_from_data(data, 1_261_193)?
616 .damage_flat(0.0)
617 .apply_aoe_from_data(data)
618 .on_cast(crate::hooks::survival_hunter::boomstick_hook),
619 )
620}
621fn spell_flamefang_pitch(
622 s: SpellDefinitionDraft,
623 data: &ResolvedGameData,
624) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
625 Ok(
626 s
627 .apply_base_from_data(data, 1_251_592)?
628 .apply_aoe_from_data(data)
629 .on_cast(crate::hooks::survival_hunter::flamefang_pitch_hook),
630 )
631}
632fn spell_hatchet_toss(
633 s: SpellDefinitionDraft,
634 data: &ResolvedGameData,
635) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
636 Ok(s.apply_base_from_data(data, 193_265)?.apply_aoe_from_data(data))
637}
638fn spell_raptor_swipe(
639 s: SpellDefinitionDraft,
640 data: &ResolvedGameData,
641) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
642 Ok(
643 s
644 .apply_base_from_data(data, 1_262_293)?
645 .applies_inferred_aura(AURA_RAPTOR_SWIPE, 1_273_155)
646 .apply_aoe_from_data(data),
647 )
648}
649fn spell_boomstick_damage(
650 s: SpellDefinitionDraft,
651 data: &ResolvedGameData,
652) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
653 Ok(s.apply_base_from_data(data, 1_261_215)?.apply_aoe_from_data(data))
654}
655fn spell_takedown_damage(
656 s: SpellDefinitionDraft,
657 data: &ResolvedGameData,
658) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
659 Ok(s.apply_base_from_data(data, 1_253_859)?.apply_aoe_from_data(data))
660}
661fn spell_takedown_pet(
662 s: SpellDefinitionDraft,
663 data: &ResolvedGameData,
664) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
665 Ok(s.apply_base_from_data(data, 1_253_862)?.apply_aoe_from_data(data))
666}
667fn spell_flamefang_pitch_damage(
668 s: SpellDefinitionDraft,
669 data: &ResolvedGameData,
670) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
671 Ok(s.apply_base_from_data(data, 1_251_595)?.apply_aoe_from_data(data))
672}
673fn spell_flamefang_pitch_aoe(
674 s: SpellDefinitionDraft,
675 data: &ResolvedGameData,
676) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
677 Ok(s.apply_base_from_data(data, 1_251_614)?.apply_aoe_from_data(data))
678}
679fn spell_claw(
680 s: SpellDefinitionDraft,
681 data: &ResolvedGameData,
682) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
683 Ok(s.apply_base_from_data(data, 16827)?.apply_aoe_from_data(data))
684}
685fn spell_strike_as_one(
686 s: SpellDefinitionDraft,
687 data: &ResolvedGameData,
688) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
689 Ok(s.apply_base_from_data(data, 1_251_779)?.apply_aoe_from_data(data))
690}
691fn spell_boar_charge(
692 s: SpellDefinitionDraft,
693 data: &ResolvedGameData,
694) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
695 Ok(s.apply_base_from_data(data, 471_936)?.apply_aoe_from_data(data))
696}
697fn spell_boar_charge_cleave(
698 s: SpellDefinitionDraft,
699 data: &ResolvedGameData,
700) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
701 Ok(s.apply_base_from_data(data, 471_938)?.apply_aoe_from_data(data))
702}
703fn spell_stampede_tick(
704 s: SpellDefinitionDraft,
705 data: &ResolvedGameData,
706) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
707 Ok(s.apply_base_from_data(data, 201_594)?.apply_aoe_from_data(data))
708}
709pub(crate) fn build_combat_system(
710 params: &wowlab_engine_ports::HandlerParams<'_>,
711) -> Result<BuiltCombatSystem, wowlab_engine_ports::EngineError> {
712 let builder = BuiltCombatSystem::builder(params.stats.clone())
713 .spec_id(SpecId::Survival)
714 .hero_talent_trees(DECLARED_METADATA.hero_talent_trees)
715 .mastery_spell(263_135)
716 .mastery_hook(crate::hooks::survival_hunter::mastery)
717 .resource(
718 "focus",
719 params
720 .game_data
721 .power_max(2)
722 .ok_or_else(|| {
723 wowlab_engine_ports::EngineError::spec_construction(
724 "missing power-type power_max for type_id 2",
725 )
726 })?,
727 params
728 .game_data
729 .power_regen_for_max(
730 2,
731 params
732 .game_data
733 .power_max(2)
734 .ok_or_else(|| {
735 wowlab_engine_ports::EngineError::spec_construction(
736 "missing power-type power_max for type_id 2",
737 )
738 })?,
739 )
740 .ok_or_else(|| {
741 wowlab_engine_ports::EngineError::spec_construction(
742 "missing power-type power_regen_for_max for type_id 2",
743 )
744 })?,
745 )
746 .resource_type_id(2)
747 .resource_start(
748 params
749 .game_data
750 .power_default(2)
751 .ok_or_else(|| {
752 wowlab_engine_ports::EngineError::spec_construction(
753 "missing power-type power_default for type_id 2",
754 )
755 })?,
756 )
757 .has_pet(true)
758 .aura(
759 "unnatural_causes",
760 AURA::UNNATURAL_CAUSES,
761 |a| aura_unnatural_causes(a, ¶ms.game_data),
762 )
763 .aura(
764 "tip_of_the_spear",
765 AURA::TIP_OF_THE_SPEAR,
766 |a| aura_tip_of_the_spear(a, ¶ms.game_data),
767 )
768 .aura("track_pets", AURA::TRACK_PETS, |a| aura_track_pets(a, ¶ms.game_data))
769 .aura(
770 "raptor_swipe",
771 AURA::RAPTOR_SWIPE,
772 |a| aura_raptor_swipe(a, ¶ms.game_data),
773 )
774 .aura("takedown", AURA::TAKEDOWN, |a| aura_takedown(a, ¶ms.game_data))
775 .aura(
776 "howl_cooldown",
777 AURA::HOWL_COOLDOWN,
778 |a| aura_howl_cooldown(a, ¶ms.game_data),
779 )
780 .aura(
781 "wyvern_ready",
782 AURA::WYVERN_READY,
783 |a| aura_wyvern_ready(a, ¶ms.game_data),
784 )
785 .aura("boar_ready", AURA::BOAR_READY, |a| aura_boar_ready(a, ¶ms.game_data))
786 .aura("bear_ready", AURA::BEAR_READY, |a| aura_bear_ready(a, ¶ms.game_data))
787 .aura("rend_flesh", AURA::REND_FLESH, |a| aura_rend_flesh(a, ¶ms.game_data))
788 .aura(
789 "wyverns_cry",
790 AURA::WYVERNS_CRY,
791 |a| aura_wyverns_cry(a, ¶ms.game_data),
792 )
793 .aura("sic_em", AURA::SIC_EM, |a| aura_sic_em(a, ¶ms.game_data))
794 .spell(
795 "kill_command",
796 SPELL::KILL_COMMAND,
797 |s| spell_kill_command(s, ¶ms.game_data),
798 )
799 .spell(
800 "wildfire_bomb",
801 SPELL::WILDFIRE_BOMB,
802 |s| spell_wildfire_bomb(s, ¶ms.game_data),
803 )
804 .spell(
805 "mongoose_bite",
806 SPELL::MONGOOSE_BITE,
807 |s| spell_mongoose_bite(s, ¶ms.game_data),
808 )
809 .spell(
810 "raptor_strike",
811 SPELL::RAPTOR_STRIKE,
812 |s| spell_raptor_strike(s, ¶ms.game_data),
813 )
814 .spell(
815 "arcane_shot",
816 SPELL::ARCANE_SHOT,
817 |s| spell_arcane_shot(s, ¶ms.game_data),
818 )
819 .spell(
820 "steady_shot",
821 SPELL::STEADY_SHOT,
822 |s| spell_steady_shot(s, ¶ms.game_data),
823 )
824 .spell(
825 "revive_pet",
826 SPELL::REVIVE_PET,
827 |s| spell_revive_pet(s, ¶ms.game_data),
828 )
829 .spell(
830 "call_pet_1",
831 SPELL::CALL_PET_1,
832 |s| spell_call_pet_1(s, ¶ms.game_data),
833 )
834 .spell(
835 "kill_command_pet",
836 SPELL::KILL_COMMAND_PET,
837 |s| spell_kill_command_pet(s, ¶ms.game_data),
838 )
839 .spell(
840 "wildfire_bomb_damage",
841 SPELL::WILDFIRE_BOMB_DAMAGE,
842 |s| spell_wildfire_bomb_damage(s, ¶ms.game_data),
843 )
844 .spell(
845 "wildfire_bomb_bleed",
846 SPELL::WILDFIRE_BOMB_BLEED,
847 |s| spell_wildfire_bomb_bleed(s, ¶ms.game_data),
848 )
849 .spell("takedown", SPELL::TAKEDOWN, |s| spell_takedown(s, ¶ms.game_data))
850 .spell("boomstick", SPELL::BOOMSTICK, |s| spell_boomstick(s, ¶ms.game_data))
851 .spell(
852 "flamefang_pitch",
853 SPELL::FLAMEFANG_PITCH,
854 |s| spell_flamefang_pitch(s, ¶ms.game_data),
855 )
856 .spell(
857 "hatchet_toss",
858 SPELL::HATCHET_TOSS,
859 |s| spell_hatchet_toss(s, ¶ms.game_data),
860 )
861 .spell(
862 "raptor_swipe",
863 SPELL::RAPTOR_SWIPE,
864 |s| spell_raptor_swipe(s, ¶ms.game_data),
865 )
866 .spell(
867 "boomstick_damage",
868 SPELL::BOOMSTICK_DAMAGE,
869 |s| spell_boomstick_damage(s, ¶ms.game_data),
870 )
871 .spell(
872 "takedown_damage",
873 SPELL::TAKEDOWN_DAMAGE,
874 |s| spell_takedown_damage(s, ¶ms.game_data),
875 )
876 .spell(
877 "takedown_pet",
878 SPELL::TAKEDOWN_PET,
879 |s| spell_takedown_pet(s, ¶ms.game_data),
880 )
881 .spell(
882 "flamefang_pitch_damage",
883 SPELL::FLAMEFANG_PITCH_DAMAGE,
884 |s| spell_flamefang_pitch_damage(s, ¶ms.game_data),
885 )
886 .spell(
887 "flamefang_pitch_aoe",
888 SPELL::FLAMEFANG_PITCH_AOE,
889 |s| spell_flamefang_pitch_aoe(s, ¶ms.game_data),
890 )
891 .spell("claw", SPELL::CLAW, |s| spell_claw(s, ¶ms.game_data))
892 .spell(
893 "strike_as_one",
894 SPELL::STRIKE_AS_ONE,
895 |s| spell_strike_as_one(s, ¶ms.game_data),
896 )
897 .spell(
898 "boar_charge",
899 SPELL::BOAR_CHARGE,
900 |s| spell_boar_charge(s, ¶ms.game_data),
901 )
902 .spell(
903 "boar_charge_cleave",
904 SPELL::BOAR_CHARGE_CLEAVE,
905 |s| spell_boar_charge_cleave(s, ¶ms.game_data),
906 )
907 .spell(
908 "stampede_tick",
909 SPELL::STAMPEDE_TICK,
910 |s| spell_stampede_tick(s, ¶ms.game_data),
911 )
912 .auto_attack(|a| {
913 a.spell_id(6603).swing_ms(3000).apply_base_from_data(¶ms.game_data)
914 })
915 .auto_attack(|a| {
916 a
917 .spell_id(6603)
918 .swing_ms(2000)
919 .apply_base_from_data(¶ms.game_data)
920 .ap_coef(0.2)
921 .is_pet()
922 .on_swing(crate::hooks::survival_hunter::pet_swing)
923 })
924 .game_data(params.game_data.clone())
925 .talent_spell_ids(TALENTS)
926 .talent_selections(params.talent_selections)
927 .talent_companion_aura(TALENT::UNNATURAL_CAUSES, AURA_UNNATURAL_CAUSES)
928 .set_bonus_auras(params.set_bonus_auras)
929 .encounter(params.encounter.clone());
930 let builder = crate::generated::items::register_equipped(
931 builder,
932 ¶ms.game_data,
933 params.equipped_items,
934 )?;
935
936 builder
937 .build(params.rotation)
938 .map_err(wowlab_engine_ports::EngineError::rotation_compile)
939}
940fn new_handler(
941 params: wowlab_engine_ports::HandlerParams<'_>,
942) -> Result<Box<dyn SpecHandler>, wowlab_engine_ports::EngineError> {
943 let handler = crate::composition::compose_handler(
944 build_combat_system,
945 ¶ms,
946 |parts, _| Ok(parts.into_handler()),
947 )?;
948
949 Ok(Box::new(handler))
950}
951static DECLARED_METADATA: DeclaredSpecMetadata = DeclaredSpecMetadata {
952 primary_resource: ResourceType::Focus,
953 secondary_resource: None,
954 spell_ids: DECLARED_SPELL_IDS,
955 aura_ids: DECLARED_AURA_IDS,
956 server_proc_entries: &[
957 DeclaredServerProcEntry {
958 spell_id: 260_286,
959 attributes: ProcAttributes::REQUIRE_SPELL_MODIFIER.bits()
960 | ProcAttributes::USE_STACKS_FOR_CHARGES.bits(),
961 },
962 ],
963 talents: TALENTS,
964 rotation_field_schema: &[],
965 metrics_plugin_id: None,
966 hero_talent_trees: &[
967 HeroTalentTreeDesc {
968 name: "Pack Leader",
969 spells: HERO_TREE_PACK_LEADER_SPELLS,
970 auras: HERO_TREE_PACK_LEADER_AURAS,
971 },
972 HeroTalentTreeDesc {
973 name: "Sentinel",
974 spells: HERO_TREE_SENTINEL_SPELLS,
975 auras: HERO_TREE_SENTINEL_AURAS,
976 },
977 ],
978 reported_spells: REPORTED_SPELLS,
979 masked_passive_effects: &[],
980 passive_effect_overrides: &[],
981};
982pub(super) const SPEC_DESCRIPTOR: SpecDescriptor = SpecDescriptor {
983 spec_id: SpecId::Survival,
984 display_name: "Survival Hunter",
985 metadata: DECLARED_METADATA,
986 handler_factory: new_handler,
987};
988wowlab_engine_combat::smoke_test!(build_combat_system);