Skip to main content

wowlab_engine_content/hooks/marksmanship_hunter/
dark_minion.rs

1//! Dark Ranger's Dark Minion guardian: Corpsecaller / Wailing Dead summons and Pact of the Hollow.
2
3use wowlab_engine_combat::DamageOps as _;
4use wowlab_engine_combat::GuardianOps as _;
5use wowlab_engine_combat::{DamageFlags, GuardianAbility, GuardianEvent, GuardianSpec, HookCtx};
6use wowlab_engine_rng::roll_range;
7use wowlab_types::constants::MS_PER_SECOND;
8
9use crate::generated::specs::marksmanship_hunter::{EFFECT, HERO};
10
11use super::config::{MarksmanshipConfig, marksmanship_config};
12
13/// sc_hunter.cpp:1822-1825 — Dark Minions do not cast Shoot for ~1.0-1.5s after they spawn.
14const SPAWN_SHOOT_DELAY_MIN_MS: f64 = MS_PER_SECOND;
15const SPAWN_SHOOT_DELAY_MAX_MS: f64 = MS_PER_SECOND * 1.5;
16/// sc_hunter.cpp:3358-3360 — the pet idles 350-650ms between Shoot casts.
17const SHOOT_IDLE_MIN_MS: f64 = 350.0;
18const SHOOT_IDLE_MAX_MS: f64 = 650.0;
19
20/// Ability slot commanded by Pact of the Hollow; Shoot occupies slot 0 and self-schedules.
21const BLIGHTED_ARROW_ABILITY: usize = 1;
22
23fn shoot(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
24    ctx.deal_effect_damage_ap(
25        EFFECT::DARK_MINION_SHOOT_DAMAGE,
26        1.0,
27        DamageFlags::PHYSICAL,
28    );
29}
30
31fn blighted_arrow(ctx: &mut HookCtx<'_>, _event: GuardianEvent) {
32    ctx.deal_effect_damage_ap(
33        EFFECT::DARK_MINION_BLIGHTED_ARROW_DAMAGE,
34        1.0,
35        DamageFlags::empty(),
36    );
37}
38
39fn delay_ms(rng: &mut dyn FnMut() -> f64, cast_ms: u32, low: f64, high: f64) -> u32 {
40    cast_ms.saturating_add(wowlab_types::numeric::f64_to_u32_saturating_round(roll_range(
41        rng, low, high,
42    )))
43}
44
45/// Spawns one Dark Minion for 1264345's duration (`sc_hunter.cpp:1783-1788`).
46pub(super) fn summon_dark_minion(ctx: &mut HookCtx<'_>) {
47    let cfg = marksmanship_config(ctx);
48
49    if cfg.dark_minion_duration_ms == 0 {
50        return;
51    }
52
53    let cast_ms = cfg.dark_minion_shoot_cast_ms;
54    let first_shoot_ms = delay_ms(ctx.rng(), cast_ms, SPAWN_SHOOT_DELAY_MIN_MS, SPAWN_SHOOT_DELAY_MAX_MS);
55    let shoot_interval_ms = delay_ms(ctx.rng(), cast_ms, SHOOT_IDLE_MIN_MS, SHOOT_IDLE_MAX_MS);
56
57    let _ = ctx.summon_guardian(
58        GuardianSpec::new_inheriting_owner(
59            HERO::DARK_RANGER::SPELL::DARK_MINION_SUMMON,
60            cfg.dark_minion_duration_ms,
61        )
62        .ability(GuardianAbility::new(shoot_interval_ms, shoot).first_action_delay(first_shoot_ms))
63        .ability(GuardianAbility::on_demand(blighted_arrow)),
64    );
65}
66
67/// Wailing Dead: Trueshot summons a Dark Minion (`sc_hunter.cpp:6967-6970`).
68pub(super) fn trueshot_summon(
69    ctx: &mut HookCtx<'_>,
70    cfg: &MarksmanshipConfig,
71) {
72    if !cfg.wailing_dead {
73        return;
74    }
75
76    summon_dark_minion(ctx);
77}
78
79/// Pact of the Hollow: Aimed Shot fires Blighted Arrow from every Dark Minion (`sc_hunter.cpp:5682`).
80pub(super) fn pact_of_the_hollow(
81    ctx: &mut HookCtx<'_>,
82    cfg: &MarksmanshipConfig,
83) {
84    if !cfg.pact_of_the_hollow {
85        return;
86    }
87
88    let _ = ctx.command_guardians(
89        HERO::DARK_RANGER::SPELL::DARK_MINION_SUMMON,
90        BLIGHTED_ARROW_ABILITY,
91    );
92}