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 AIMED_SHOT: u32 = 19434;
18 pub(crate) const RAPID_FIRE: u32 = 257_044;
20 pub(crate) const ARCANE_SHOT: u32 = 185_358;
22 pub(crate) const TRUESHOT: u32 = 288_613;
24 pub(crate) const KILL_SHOT: u32 = 53351;
26 pub(crate) const MULTI_SHOT: u32 = 257_620;
28 pub(crate) const STEADY_SHOT: u32 = 56641;
30 pub(crate) const VOLLEY: u32 = 260_243;
32 pub(crate) const RAPID_FIRE_TICK: u32 = 257_045;
34}
35pub(crate) mod AURA {
36 pub(crate) const UNNATURAL_CAUSES: u32 = 459_529;
38 pub(crate) const TRICK_SHOTS: u32 = 257_622;
40 pub(crate) const PRECISE_SHOTS: u32 = 260_242;
42 pub(crate) const TRUESHOT: u32 = 288_613;
44 pub(crate) const HEADSHOT: u32 = 1_277_558;
46 pub(crate) const SPOTTERS_MARK: u32 = 466_872;
48 pub(crate) const MASTER_MARKSMAN: u32 = 269_576;
50 pub(crate) const BLACK_ARROW: u32 = 466_932;
52 pub(crate) const BLACK_ARROW_DOT: u32 = 468_572;
54 pub(crate) const DEATHBLOW: u32 = 378_770;
56 pub(crate) const WITHERING_FIRE: u32 = 466_991;
58}
59pub(crate) mod TALENT {
60 pub(crate) const BLACK_ARROW: u32 = 466_932;
62 pub(crate) const BLEAK_ARROWS: u32 = 467_749;
64 pub(crate) const BLIGHTED_QUIVER: u32 = 1_264_291;
66 pub(crate) const DEATHBLOW: u32 = 343_248;
68 pub(crate) const FEATHERED_FRENZY: u32 = 470_943;
70 pub(crate) const FOCUSED_AIM: u32 = 378_767;
72 pub(crate) const HEADSHOT: u32 = 471_363;
74 pub(crate) const KILL_SHOT_TALENT: u32 = 53351;
76 pub(crate) const LONE_WOLF: u32 = 155_228;
78 pub(crate) const MASTER_MARKSMAN: u32 = 260_309;
80 pub(crate) const NO_SCOPE: u32 = 473_385;
82 pub(crate) const PRECISE_SHOTS: u32 = 260_240;
84 pub(crate) const SOUL_DRINKER: u32 = 469_638;
86 pub(crate) const THROUGH_THE_EYES: u32 = 1_277_565;
88 pub(crate) const UNLOAD: u32 = 1_277_548;
90 pub(crate) const UNNATURAL_CAUSES: u32 = 459_527;
92 pub(crate) const VOLLEY: u32 = 260_243;
94 pub(crate) const WINDRUNNER_QUIVER: u32 = 473_523;
96 pub(crate) const WITHERING_FIRE: u32 = 466_990;
98}
99pub(crate) mod REPORTED_SPELL {
100 pub(crate) const BLIGHTED_ARROW: u32 = 1_264_364;
102 pub(crate) const SHOOT: u32 = 1_264_357;
104}
105pub(crate) mod HERO {
106 pub(crate) mod DARK_RANGER {
107 pub(crate) mod SPELL {
108 pub(crate) const BLACK_ARROW: u32 = 466_930;
110 pub(crate) const BLACK_ARROW_DOT_SPELL: u32 = 468_572;
112 pub(crate) const BLACK_ARROW_TALENT: u32 = 466_932;
114 pub(crate) const BLACK_ARROW_WITHERING_FIRE: u32 = 468_037;
116 pub(crate) const BLEAK_ARROWS: u32 = 467_718;
118 pub(crate) const BLEAK_ARROWS_TALENT: u32 = 467_749;
120 pub(crate) const BLEAK_POWDER: u32 = 467_914;
122 pub(crate) const BLIGHTED_QUIVER_TALENT: u32 = 1_264_291;
124 pub(crate) const CORPSECALLER_TALENT: u32 = 1_264_289;
126 pub(crate) const DARK_MINION_SUMMON: u32 = 1_264_345;
128 pub(crate) const PACT_OF_THE_HOLLOW_TALENT: u32 = 1_264_690;
130 pub(crate) const SOUL_DRINKER_TALENT: u32 = 469_638;
132 pub(crate) const THROUGH_THE_EYES_TALENT: u32 = 1_277_565;
134 pub(crate) const WAILING_ARROW: u32 = 392_060;
136 pub(crate) const WAILING_ARROW_DAMAGE: u32 = 392_058;
138 pub(crate) const WAILING_DEAD_TALENT: u32 = 1_264_290;
140 pub(crate) const WITHERING_FIRE_TALENT: u32 = 466_990;
142 }
143 pub(crate) mod AURA {
144 pub(crate) const BLACK_ARROW_DOT: u32 = 468_572;
146 pub(crate) const DEATHBLOW: u32 = 378_770;
148 pub(crate) const PHANTOM_PAIN: u32 = 467_941;
150 pub(crate) const WAILING_ARROW_BUFF: u32 = 459_808;
152 pub(crate) const WITHERING_FIRE: u32 = 466_991;
154 }
155 }
156 pub(crate) mod SENTINEL {
157 pub(crate) mod SPELL {
158 pub(crate) const LUNAR_STORM_DAMAGE: u32 = 1_253_733;
160 pub(crate) const MOONLIGHT_CHAKRAM: u32 = 1_264_949;
162 pub(crate) const MOONLIGHT_CHAKRAM_DAMAGE: u32 = 1_266_081;
164 pub(crate) const TWILIGHT_REQUIEM_DAMAGE: u32 = 1_266_096;
166 }
167 pub(crate) mod AURA {
168 pub(crate) const MOONLIGHT_CHAKRAM_BUFF: u32 = 1_264_946;
170 pub(crate) const SANCTIFIED_ARMAMENTS_DOT: u32 = 1_253_836;
172 pub(crate) const SENTINELS_MARK: u32 = 1_253_601;
174 pub(crate) const STARGAZER: u32 = 1_253_750;
176 }
177 }
178}
179static HERO_TREE_DARK_RANGER_SPELLS: &[(&str, u32)] = &[
180 ("BLACK_ARROW", 466_930),
181 ("BLACK_ARROW_DOT_SPELL", 468_572),
182 ("BLACK_ARROW_TALENT", 466_932),
183 ("BLACK_ARROW_WITHERING_FIRE", 468_037),
184 ("BLEAK_ARROWS", 467_718),
185 ("BLEAK_ARROWS_TALENT", 467_749),
186 ("BLEAK_POWDER", 467_914),
187 ("BLIGHTED_QUIVER_TALENT", 1_264_291),
188 ("CORPSECALLER_TALENT", 1_264_289),
189 ("DARK_MINION_SUMMON", 1_264_345),
190 ("PACT_OF_THE_HOLLOW_TALENT", 1_264_690),
191 ("SOUL_DRINKER_TALENT", 469_638),
192 ("THROUGH_THE_EYES_TALENT", 1_277_565),
193 ("WAILING_ARROW", 392_060),
194 ("WAILING_ARROW_DAMAGE", 392_058),
195 ("WAILING_DEAD_TALENT", 1_264_290),
196 ("WITHERING_FIRE_TALENT", 466_990),
197];
198static HERO_TREE_DARK_RANGER_AURAS: &[(&str, u32)] = &[
199 ("BLACK_ARROW_DOT", 468_572),
200 ("DEATHBLOW", 378_770),
201 ("PHANTOM_PAIN", 467_941),
202 ("WAILING_ARROW_BUFF", 459_808),
203 ("WITHERING_FIRE", 466_991),
204];
205static HERO_TREE_SENTINEL_SPELLS: &[(&str, u32)] = &[
206 ("LUNAR_STORM_DAMAGE", 1_253_733),
207 ("MOONLIGHT_CHAKRAM", 1_264_949),
208 ("MOONLIGHT_CHAKRAM_DAMAGE", 1_266_081),
209 ("TWILIGHT_REQUIEM_DAMAGE", 1_266_096),
210];
211static HERO_TREE_SENTINEL_AURAS: &[(&str, u32)] = &[
212 ("MOONLIGHT_CHAKRAM_BUFF", 1_264_946),
213 ("SANCTIFIED_ARMAMENTS_DOT", 1_253_836),
214 ("SENTINELS_MARK", 1_253_601),
215 ("STARGAZER", 1_253_750),
216];
217pub(crate) const AURA_UNNATURAL_CAUSES: LocalAuraIdx = LocalAuraIdx::new(0);
218pub(crate) const AURA_TRICK_SHOTS: LocalAuraIdx = LocalAuraIdx::new(1);
219pub(crate) const AURA_PRECISE_SHOTS: LocalAuraIdx = LocalAuraIdx::new(2);
220pub(crate) const AURA_TRUESHOT: LocalAuraIdx = LocalAuraIdx::new(3);
221pub(crate) const AURA_HEADSHOT: LocalAuraIdx = LocalAuraIdx::new(4);
222pub(crate) const AURA_SPOTTERS_MARK: LocalAuraIdx = LocalAuraIdx::new(5);
223pub(crate) const AURA_MASTER_MARKSMAN: LocalAuraIdx = LocalAuraIdx::new(6);
224pub(crate) const AURA_BLACK_ARROW: LocalAuraIdx = LocalAuraIdx::new(7);
225pub(crate) const AURA_BLACK_ARROW_DOT: LocalAuraIdx = LocalAuraIdx::new(8);
226pub(crate) const AURA_DEATHBLOW: LocalAuraIdx = LocalAuraIdx::new(9);
227pub(crate) const AURA_WITHERING_FIRE: LocalAuraIdx = LocalAuraIdx::new(10);
228pub(crate) const AURA_COUNT: usize = 11;
229const _: () = assert!(
230 AURA_COUNT == 11, "AURA index ABI: count changed -- update positional hook consumers"
231);
232pub(crate) const SPELL_AIMED_SHOT: LocalSpellIdx = LocalSpellIdx::new(0);
233pub(crate) const SPELL_RAPID_FIRE: LocalSpellIdx = LocalSpellIdx::new(1);
234pub(crate) const SPELL_ARCANE_SHOT: LocalSpellIdx = LocalSpellIdx::new(2);
235pub(crate) const SPELL_TRUESHOT: LocalSpellIdx = LocalSpellIdx::new(3);
236pub(crate) const SPELL_KILL_SHOT: LocalSpellIdx = LocalSpellIdx::new(4);
237pub(crate) const SPELL_MULTI_SHOT: LocalSpellIdx = LocalSpellIdx::new(5);
238pub(crate) const SPELL_STEADY_SHOT: LocalSpellIdx = LocalSpellIdx::new(6);
239pub(crate) const SPELL_VOLLEY: LocalSpellIdx = LocalSpellIdx::new(7);
240pub(crate) const SPELL_RAPID_FIRE_TICK: LocalSpellIdx = LocalSpellIdx::new(8);
241pub(crate) const SPELL_COUNT: usize = 9;
242const _: () = assert!(
243 SPELL_COUNT == 9,
244 "SPELL index ABI: count changed -- update positional hook consumers"
245);
246pub(crate) mod EFFECT {
247 pub(crate) const MASTERY_ABILITY_DAMAGE: (u32, u8) = (193_468, 2);
249 pub(crate) const BLACK_ARROW_DAMAGE: (u32, u8) = (466_930, 1);
251 pub(crate) const BLACK_ARROW_LOWER_PCT: (u32, u8) = (466_930, 2);
253 pub(crate) const BLACK_ARROW_UPPER_PCT: (u32, u8) = (466_930, 3);
255 pub(crate) const KILL_SHOT_DAMAGE: (u32, u8) = (53351, 1);
257 pub(crate) const KILL_SHOT_THRESHOLD: (u32, u8) = (53351, 2);
259 pub(crate) const BLIGHTED_QUIVER_EXTRA: (u32, u8) = (1_264_291, 7);
261 pub(crate) const WITHERING_FIRE_ARROW: (u32, u8) = (468_037, 1);
263 pub(crate) const WITHERING_FIRE_COUNT: (u32, u8) = (466_990, 3);
265 pub(crate) const DEATHBLOW_AIMED_PCT: (u32, u8) = (343_248, 1);
267 pub(crate) const DEATHBLOW_RF_PCT: (u32, u8) = (343_248, 2);
269 pub(crate) const SOUL_DRINKER_AIMED_PCT: (u32, u8) = (469_638, 3);
271 pub(crate) const SOUL_DRINKER_RF_PCT: (u32, u8) = (469_638, 4);
273 pub(crate) const ARCANE_SHOT_DAMAGE: (u32, u8) = (185_358, 1);
275 pub(crate) const DARK_MINION_BLIGHTED_ARROW_DAMAGE: (u32, u8) = (1_264_364, 1);
277 pub(crate) const DARK_MINION_SHOOT_DAMAGE: (u32, u8) = (1_264_357, 1);
279 pub(crate) const FOCUSED_AIM_CDR_MS: (u32, u8) = (378_767, 1);
281 pub(crate) const FEATHERED_FRENZY_PCT: (u32, u8) = (470_943, 1);
283 pub(crate) const SPOTTERS_MARK_CHANCE: (u32, u8) = (1_219_616, 1);
285 pub(crate) const SPOTTERS_MARK_PCT: (u32, u8) = (466_872, 1);
287 pub(crate) const AIMED_SHOT_DAMAGE: (u32, u8) = (19434, 1);
289 pub(crate) const WINDRUNNER_QUIVER_PCT: (u32, u8) = (473_523, 3);
291 pub(crate) const MASTER_MARKSMAN_PCT: (u32, u8) = (260_309, 1);
293}
294static REPORTED_SPELLS: &[(&str, u32)] = &[
295 ("blighted_arrow", 1_264_364),
296 ("shoot", 1_264_357),
297];
298static DECLARED_SPELL_IDS: &[u32] = &[
299 19434, 257_044, 185_358, 288_613, 53351, 257_620, 56641, 260_243, 257_045, 459_529,
300 257_622, 260_242, 288_613, 1_277_558, 466_872, 269_576, 466_932, 468_572, 378_770,
301 466_991, 75, 19434, 53351, 56641, 77443, 185_358, 193_468, 257_044, 257_045, 260_309,
302 263_585, 269_576, 288_613, 343_248, 378_767, 466_872, 466_930, 466_990, 468_037,
303 469_638, 470_943, 473_523, 1_219_616, 1_264_291, 1_264_357, 1_264_364, 1_264_364,
304 1_264_357, 466_930, 468_572, 466_932, 468_037, 467_718, 467_749, 467_914, 1_264_291,
305 1_264_289, 1_264_345, 1_264_690, 469_638, 1_277_565, 392_060, 392_058, 1_264_290,
306 466_990, 468_572, 378_770, 467_941, 459_808, 466_991, 1_253_733, 1_264_949,
307 1_266_081, 1_266_096, 1_264_946, 1_253_836, 1_253_601, 1_253_750,
308];
309static DECLARED_AURA_IDS: &[u32] = &[
310 459_529, 257_622, 260_242, 288_613, 1_277_558, 466_872, 269_576, 466_932, 468_572,
311 378_770, 466_991, 468_572, 378_770, 467_941, 459_808, 466_991, 1_264_946, 1_253_836,
312 1_253_601, 1_253_750,
313];
314static TALENTS: &[(&str, u32)] = &[
315 ("BLACK_ARROW", TALENT::BLACK_ARROW),
316 ("BLEAK_ARROWS", TALENT::BLEAK_ARROWS),
317 ("BLIGHTED_QUIVER", TALENT::BLIGHTED_QUIVER),
318 ("DEATHBLOW", TALENT::DEATHBLOW),
319 ("FEATHERED_FRENZY", TALENT::FEATHERED_FRENZY),
320 ("FOCUSED_AIM", TALENT::FOCUSED_AIM),
321 ("HEADSHOT", TALENT::HEADSHOT),
322 ("KILL_SHOT_TALENT", TALENT::KILL_SHOT_TALENT),
323 ("LONE_WOLF", TALENT::LONE_WOLF),
324 ("MASTER_MARKSMAN", TALENT::MASTER_MARKSMAN),
325 ("NO_SCOPE", TALENT::NO_SCOPE),
326 ("PRECISE_SHOTS", TALENT::PRECISE_SHOTS),
327 ("SOUL_DRINKER", TALENT::SOUL_DRINKER),
328 ("THROUGH_THE_EYES", TALENT::THROUGH_THE_EYES),
329 ("UNLOAD", TALENT::UNLOAD),
330 ("UNNATURAL_CAUSES", TALENT::UNNATURAL_CAUSES),
331 ("VOLLEY", TALENT::VOLLEY),
332 ("WINDRUNNER_QUIVER", TALENT::WINDRUNNER_QUIVER),
333 ("WITHERING_FIRE", TALENT::WITHERING_FIRE),
334];
335fn aura_unnatural_causes(
336 a: AuraDefinitionDraft,
337 data: &ResolvedGameData,
338) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
339 Ok(
340 a
341 .on_target()
342 .apply_base_from_data(data, 459_529)?
343 .max_stacks(
344 data
345 .require_aura_max_stacks(
346 wowlab_types::sim::SpellIdx::from_raw(459_529),
347 )?,
348 ),
349 )
350}
351fn aura_trick_shots(
352 a: AuraDefinitionDraft,
353 data: &ResolvedGameData,
354) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
355 Ok(
356 a
357 .on_player()
358 .apply_base_from_data(data, 257_622)?
359 .max_stacks(
360 data
361 .require_aura_max_stacks(
362 wowlab_types::sim::SpellIdx::from_raw(257_622),
363 )?,
364 ),
365 )
366}
367fn aura_precise_shots(
368 a: AuraDefinitionDraft,
369 data: &ResolvedGameData,
370) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
371 Ok(
372 a
373 .on_player()
374 .apply_base_from_data(data, 260_242)?
375 .max_stacks(
376 data
377 .require_aura_max_stacks(
378 wowlab_types::sim::SpellIdx::from_raw(260_242),
379 )?,
380 ),
381 )
382}
383fn aura_trueshot(
384 a: AuraDefinitionDraft,
385 data: &ResolvedGameData,
386) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
387 Ok(
388 a
389 .on_player()
390 .apply_base_from_data(data, 288_613)?
391 .max_stacks(
392 data
393 .require_aura_max_stacks(
394 wowlab_types::sim::SpellIdx::from_raw(288_613),
395 )?,
396 ),
397 )
398}
399fn aura_headshot(
400 a: AuraDefinitionDraft,
401 data: &ResolvedGameData,
402) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
403 Ok(
404 a
405 .on_target()
406 .apply_base_from_data(data, 1_277_558)?
407 .max_stacks(
408 data
409 .require_aura_max_stacks(
410 wowlab_types::sim::SpellIdx::from_raw(1_277_558),
411 )?,
412 ),
413 )
414}
415fn aura_spotters_mark(
416 a: AuraDefinitionDraft,
417 data: &ResolvedGameData,
418) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
419 Ok(
420 a
421 .on_target()
422 .apply_base_from_data(data, 466_872)?
423 .max_stacks(
424 data
425 .require_aura_max_stacks(
426 wowlab_types::sim::SpellIdx::from_raw(466_872),
427 )?,
428 ),
429 )
430}
431fn aura_master_marksman(
432 a: AuraDefinitionDraft,
433 data: &ResolvedGameData,
434) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
435 a.on_target()
436 .apply_base_from_data(data, 269_576)?
437 .max_stacks(
438 data.require_aura_max_stacks(wowlab_types::sim::SpellIdx::from_raw(269_576))?,
439 )
440 .no_pandemic()
441 .periodic_residual_damage(
442 wowlab_engine_combat::validated_milliseconds(
443 data.period(wowlab_types::sim::SpellIdx::from_raw(269_576), 1),
444 269_576,
445 "periodic_residual_damage.tick_ms",
446 )?,
447 )
448 .apply_periodic_behavior_from_data(data, 269_576)
449}
450fn aura_black_arrow(
451 a: AuraDefinitionDraft,
452 data: &ResolvedGameData,
453) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
454 Ok(
455 a
456 .on_player()
457 .apply_base_from_data(data, 466_932)?
458 .max_stacks(
459 data
460 .require_aura_max_stacks(
461 wowlab_types::sim::SpellIdx::from_raw(466_932),
462 )?,
463 ),
464 )
465}
466fn aura_black_arrow_dot(
467 a: AuraDefinitionDraft,
468 data: &ResolvedGameData,
469) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
470 Ok(
471 a
472 .on_target()
473 .apply_base_from_data(data, 468_572)?
474 .max_stacks(
475 data
476 .require_aura_max_stacks(
477 wowlab_types::sim::SpellIdx::from_raw(468_572),
478 )?,
479 )
480 .unhasted_periodic(),
481 )
482}
483fn aura_deathblow(
484 a: AuraDefinitionDraft,
485 data: &ResolvedGameData,
486) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
487 Ok(
488 a
489 .on_player()
490 .apply_base_from_data(data, 378_770)?
491 .max_stacks(
492 data
493 .require_aura_max_stacks(
494 wowlab_types::sim::SpellIdx::from_raw(378_770),
495 )?,
496 ),
497 )
498}
499fn aura_withering_fire(
500 a: AuraDefinitionDraft,
501 data: &ResolvedGameData,
502) -> Result<AuraDefinitionDraft, wowlab_engine_combat::BuilderError> {
503 a.on_player()
504 .duration_ms(
505 wowlab_engine_combat::validated_milliseconds(
506 f64::from(
507 data
508 .require_aura_duration_ms(
509 wowlab_types::sim::SpellIdx::from_raw(288_613),
510 )?,
511 ),
512 466_991,
513 "aura.duration_ms",
514 )?,
515 )
516 .apply_periodic_effect_from_data(data, 466_991)?
517 .apply_duration_behavior_from_data(data, 288_613)?
518 .apply_timing_modifiers_from_data(data, 466_991)?
519 .max_stacks(
520 data.require_aura_max_stacks(wowlab_types::sim::SpellIdx::from_raw(466_991))?,
521 )
522 .apply_periodic_behavior_from_data(data, 288_613)
523}
524fn spell_aimed_shot(
525 s: SpellDefinitionDraft,
526 data: &ResolvedGameData,
527) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
528 Ok(
529 s
530 .apply_base_from_data(data, 19434)?
531 .damage_flat(0.0)
532 .apply_aoe_from_data(data)
533 .on_cast(crate::hooks::marksmanship_hunter::aimed_shot_hook),
534 )
535}
536fn spell_rapid_fire(
537 s: SpellDefinitionDraft,
538 data: &ResolvedGameData,
539) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
540 Ok(
541 s
542 .apply_base_from_data(data, 257_044)?
543 .apply_aoe_from_data(data)
544 .channel(
545 wowlab_engine_combat::validated_channel_tick_count(
546 data.base_points(wowlab_types::sim::SpellIdx::from_raw(257_044), 1),
547 257_044,
548 "channel.tick_count",
549 )?,
550 )
551 .channel_tick_direct_damage()
552 .channel_tick_timing(
553 wowlab_engine_combat::validated_milliseconds(
554 data.period(wowlab_types::sim::SpellIdx::from_raw(257_044), 2),
555 257_044,
556 "channel.tick_interval_ms",
557 )?,
558 true,
559 )
560 .channel_tick_damage_ap_from_data(
561 wowlab_engine_domain::dbc::EffectLookup::new(
562 data,
563 wowlab_types::sim::EffectRef::new(
564 wowlab_types::sim::SpellIdx::from_raw(257_045),
565 1,
566 ),
567 ),
568 )?
569 .channel_tick_gain(
570 data.base_points(wowlab_types::sim::SpellIdx::from_raw(263_585), 1),
571 )
572 .on_channel_tick(crate::hooks::marksmanship_hunter::rapid_fire_tick_hook)
573 .on_cast(crate::hooks::marksmanship_hunter::rapid_fire_hook),
574 )
575}
576fn spell_arcane_shot(
577 s: SpellDefinitionDraft,
578 data: &ResolvedGameData,
579) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
580 Ok(
581 s
582 .apply_base_from_data(data, 185_358)?
583 .damage_flat(0.0)
584 .apply_aoe_from_data(data)
585 .on_cast(crate::hooks::marksmanship_hunter::arcane_shot_hook),
586 )
587}
588fn spell_trueshot(
589 s: SpellDefinitionDraft,
590 data: &ResolvedGameData,
591) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
592 Ok(
593 s
594 .apply_base_from_data(data, 288_613)?
595 .applies_inferred_aura(AURA_TRUESHOT, 288_613)
596 .apply_aoe_from_data(data)
597 .on_cast(crate::hooks::marksmanship_hunter::trueshot_hook),
598 )
599}
600fn spell_kill_shot(
601 s: SpellDefinitionDraft,
602 data: &ResolvedGameData,
603) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
604 Ok(
605 s
606 .apply_base_from_data(data, 53351)?
607 .damage_flat(0.0)
608 .apply_aoe_from_data(data)
609 .on_cast(crate::hooks::marksmanship_hunter::kill_shot_hook)
610 .cost_free_when_aura(378_770),
611 )
612}
613fn spell_multi_shot(
614 s: SpellDefinitionDraft,
615 data: &ResolvedGameData,
616) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
617 Ok(s.apply_base_from_data(data, 257_620)?.apply_aoe_from_data(data))
618}
619fn spell_steady_shot(
620 s: SpellDefinitionDraft,
621 data: &ResolvedGameData,
622) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
623 Ok(
624 s
625 .apply_base_from_data(data, 56641)?
626 .gain(data.base_points(wowlab_types::sim::SpellIdx::from_raw(77443), 1))
627 .reduces_cd(
628 SPELL_AIMED_SHOT,
629 wowlab_engine_combat::validated_milliseconds(
630 data.base_points(wowlab_types::sim::SpellIdx::from_raw(56641), 2),
631 56641,
632 "spell.reduces_cd.amount_ms",
633 )?,
634 )
635 .apply_aoe_from_data(data),
636 )
637}
638fn spell_volley(
639 s: SpellDefinitionDraft,
640 data: &ResolvedGameData,
641) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
642 Ok(s.apply_base_from_data(data, 260_243)?.apply_aoe_from_data(data))
643}
644fn spell_rapid_fire_tick(
645 s: SpellDefinitionDraft,
646 data: &ResolvedGameData,
647) -> Result<SpellDefinitionDraft, wowlab_engine_combat::BuilderError> {
648 Ok(s.apply_base_from_data(data, 257_045)?.damage_flat(0.0).apply_aoe_from_data(data))
649}
650pub(crate) fn build_combat_system(
651 params: &wowlab_engine_ports::HandlerParams<'_>,
652) -> Result<BuiltCombatSystem, wowlab_engine_ports::EngineError> {
653 let builder = BuiltCombatSystem::builder(params.stats.clone())
654 .spec_id(SpecId::Marksmanship)
655 .hero_talent_trees(DECLARED_METADATA.hero_talent_trees)
656 .mastery_spell(193_468)
657 .mastery_hook(crate::hooks::marksmanship_hunter::mastery)
658 .resource(
659 "focus",
660 params
661 .game_data
662 .power_max(2)
663 .ok_or_else(|| {
664 wowlab_engine_ports::EngineError::spec_construction(
665 "missing power-type power_max for type_id 2",
666 )
667 })?,
668 params
669 .game_data
670 .power_regen_for_max(
671 2,
672 params
673 .game_data
674 .power_max(2)
675 .ok_or_else(|| {
676 wowlab_engine_ports::EngineError::spec_construction(
677 "missing power-type power_max for type_id 2",
678 )
679 })?,
680 )
681 .ok_or_else(|| {
682 wowlab_engine_ports::EngineError::spec_construction(
683 "missing power-type power_regen_for_max for type_id 2",
684 )
685 })?,
686 )
687 .resource_type_id(2)
688 .resource_start(
689 params
690 .game_data
691 .power_default(2)
692 .ok_or_else(|| {
693 wowlab_engine_ports::EngineError::spec_construction(
694 "missing power-type power_default for type_id 2",
695 )
696 })?,
697 )
698 .aura(
699 "unnatural_causes",
700 AURA::UNNATURAL_CAUSES,
701 |a| aura_unnatural_causes(a, ¶ms.game_data),
702 )
703 .aura(
704 "trick_shots",
705 AURA::TRICK_SHOTS,
706 |a| aura_trick_shots(a, ¶ms.game_data),
707 )
708 .aura(
709 "precise_shots",
710 AURA::PRECISE_SHOTS,
711 |a| aura_precise_shots(a, ¶ms.game_data),
712 )
713 .aura("trueshot", AURA::TRUESHOT, |a| aura_trueshot(a, ¶ms.game_data))
714 .aura("headshot", AURA::HEADSHOT, |a| aura_headshot(a, ¶ms.game_data))
715 .aura(
716 "spotters_mark",
717 AURA::SPOTTERS_MARK,
718 |a| aura_spotters_mark(a, ¶ms.game_data),
719 )
720 .aura(
721 "master_marksman",
722 AURA::MASTER_MARKSMAN,
723 |a| aura_master_marksman(a, ¶ms.game_data),
724 )
725 .aura(
726 "black_arrow",
727 AURA::BLACK_ARROW,
728 |a| aura_black_arrow(a, ¶ms.game_data),
729 )
730 .aura(
731 "black_arrow_dot",
732 AURA::BLACK_ARROW_DOT,
733 |a| aura_black_arrow_dot(a, ¶ms.game_data),
734 )
735 .aura("deathblow", AURA::DEATHBLOW, |a| aura_deathblow(a, ¶ms.game_data))
736 .aura(
737 "withering_fire",
738 AURA::WITHERING_FIRE,
739 |a| aura_withering_fire(a, ¶ms.game_data),
740 )
741 .spell(
742 "aimed_shot",
743 SPELL::AIMED_SHOT,
744 |s| spell_aimed_shot(s, ¶ms.game_data),
745 )
746 .spell(
747 "rapid_fire",
748 SPELL::RAPID_FIRE,
749 |s| spell_rapid_fire(s, ¶ms.game_data),
750 )
751 .spell(
752 "arcane_shot",
753 SPELL::ARCANE_SHOT,
754 |s| spell_arcane_shot(s, ¶ms.game_data),
755 )
756 .spell("trueshot", SPELL::TRUESHOT, |s| spell_trueshot(s, ¶ms.game_data))
757 .spell("kill_shot", SPELL::KILL_SHOT, |s| spell_kill_shot(s, ¶ms.game_data))
758 .spell(
759 "multi_shot",
760 SPELL::MULTI_SHOT,
761 |s| spell_multi_shot(s, ¶ms.game_data),
762 )
763 .spell(
764 "steady_shot",
765 SPELL::STEADY_SHOT,
766 |s| spell_steady_shot(s, ¶ms.game_data),
767 )
768 .spell("volley", SPELL::VOLLEY, |s| spell_volley(s, ¶ms.game_data))
769 .spell(
770 "rapid_fire_tick",
771 SPELL::RAPID_FIRE_TICK,
772 |s| spell_rapid_fire_tick(s, ¶ms.game_data),
773 )
774 .auto_attack(|a| {
775 a.spell_id(75).swing_ms(3000).apply_base_from_data(¶ms.game_data)
776 })
777 .game_data(params.game_data.clone())
778 .talent_spell_ids(TALENTS)
779 .talent_selections(params.talent_selections)
780 .talent_companion_aura(TALENT::BLACK_ARROW, AURA_BLACK_ARROW)
781 .talent_companion_aura(TALENT::UNNATURAL_CAUSES, AURA_UNNATURAL_CAUSES)
782 .set_bonus_auras(params.set_bonus_auras)
783 .encounter(params.encounter.clone());
784 let builder = crate::generated::items::register_equipped(
785 builder,
786 ¶ms.game_data,
787 params.equipped_items,
788 )?;
789
790 builder
791 .build(params.rotation)
792 .map_err(wowlab_engine_ports::EngineError::rotation_compile)
793}
794fn new_handler(
795 params: wowlab_engine_ports::HandlerParams<'_>,
796) -> Result<Box<dyn SpecHandler>, wowlab_engine_ports::EngineError> {
797 let handler = crate::composition::compose_handler(
798 build_combat_system,
799 ¶ms,
800 crate::hooks::marksmanship_hunter::MarksmanshipHandler::try_new,
801 )?;
802
803 Ok(Box::new(handler))
804}
805static DECLARED_METADATA: DeclaredSpecMetadata = DeclaredSpecMetadata {
806 primary_resource: ResourceType::Focus,
807 secondary_resource: None,
808 spell_ids: DECLARED_SPELL_IDS,
809 aura_ids: DECLARED_AURA_IDS,
810 server_proc_entries: &[
811 DeclaredServerProcEntry {
812 spell_id: 260_242,
813 attributes: ProcAttributes::REQUIRE_SPELL_MODIFIER.bits()
814 | ProcAttributes::USE_STACKS_FOR_CHARGES.bits(),
815 },
816 ],
817 talents: TALENTS,
818 rotation_field_schema: &[],
819 metrics_plugin_id: None,
820 hero_talent_trees: &[
821 HeroTalentTreeDesc {
822 name: "Dark Ranger",
823 spells: HERO_TREE_DARK_RANGER_SPELLS,
824 auras: HERO_TREE_DARK_RANGER_AURAS,
825 },
826 HeroTalentTreeDesc {
827 name: "Sentinel",
828 spells: HERO_TREE_SENTINEL_SPELLS,
829 auras: HERO_TREE_SENTINEL_AURAS,
830 },
831 ],
832 reported_spells: REPORTED_SPELLS,
833 masked_passive_effects: &[],
834 passive_effect_overrides: &[],
835};
836pub(super) const SPEC_DESCRIPTOR: SpecDescriptor = SpecDescriptor {
837 spec_id: SpecId::Marksmanship,
838 display_name: "Marksmanship Hunter",
839 metadata: DECLARED_METADATA,
840 handler_factory: new_handler,
841};
842wowlab_engine_combat::smoke_test!(build_combat_system);