1use wowlab_engine_combat::PeriodicKind;
2use wowlab_engine_domain::dbc::ResolvedGameDataEffectExt as _;
3use wowlab_types::{constants::HUNDRED, sim::SpellIdx};
4
5use crate::{
6 generated::specs::marksmanship_hunter::{
7 AURA_EXPLOSIVE_SHOT, EFFECT, HERO, REPORTED_SPELL, SET_BONUS, SPELL_RAPID_FIRE,
8 TALENT,
9 },
10 hooks::gated,
11};
12
13crate::hooks::define_spec_config! {
14pub(super) struct MarksmanshipConfig {
16 black_arrow: bool { provenance: "Dark Ranger Black Arrow talent 466932.", gate: black_arrow, source: true, transform: |value| value, },
17 ba_coef: f64 { provenance: "Black Arrow 466930 e1 AP coefficient.", gate: true, source: ap(EFFECT::BLACK_ARROW_DAMAGE), transform: |value| value, },
18 ba_lower_pct: f64 { provenance: "Black Arrow 466930 e2 lower health threshold.", gate: true, source: base(EFFECT::BLACK_ARROW_LOWER_PCT), transform: |value| value / HUNDRED, },
19 ba_upper_pct: f64 { provenance: "Black Arrow 466930 e3 upper health threshold.", gate: true, source: base(EFFECT::BLACK_ARROW_UPPER_PCT), transform: |value| value / HUNDRED, },
20 ks_coef: f64 { provenance: "Kill Shot 53351 e1 AP coefficient.", gate: true, source: ap(EFFECT::KILL_SHOT_DAMAGE), transform: |value| value, },
21 ks_threshold: f64 { provenance: "Kill Shot 53351 e2 execute threshold.", gate: true, source: base(EFFECT::KILL_SHOT_THRESHOLD), transform: |value| value / HUNDRED, },
22 kill_shot: bool { provenance: "Kill Shot talent selection.", gate: params.talent_picked(TALENT::KILL_SHOT_TALENT), source: true, transform: |value| value, },
23 deathblow: bool { provenance: "Deathblow is enabled by its talent or Black Arrow.", gate: deathblow_talent || black_arrow, source: true, transform: |value| value, },
24 aimed_deathblow_chance: f64 { provenance: "Deathblow 343248 e1 plus Soul Drinker 469638 e3.", gate: deathblow_talent, source: aimed_chance, transform: |value| value, },
25 rf_deathblow_chance: f64 { provenance: "Deathblow 343248 e2 plus Soul Drinker 469638 e4.", gate: deathblow_talent, source: rf_chance, transform: |value| value, },
26 withering_fire: bool { provenance: "Withering Fire talent 466990.", gate: params.talent_picked(HERO::DARK_RANGER::SPELL::WITHERING_FIRE_TALENT), source: true, transform: |value| value, },
27 wf_count: i32 { provenance: "Withering Fire e3 plus Blighted Quiver e7 volley count.", gate: true, source: wf_count, transform: |value| value, },
28 wf_arrow_coef: f64 { provenance: "Withering Fire arrow AP coefficient.", gate: true, source: ap(EFFECT::WITHERING_FIRE_ARROW), transform: |value| value, },
29 unload: bool { provenance: "Unload 1277548 selection.", gate: params.talent_picked(TALENT::UNLOAD), source: true, transform: |value| value, },
30 no_scope: bool { provenance: "No Scope 473385 selection.", gate: params.talent_picked(TALENT::NO_SCOPE), source: true, transform: |value| value, },
31 precise_shots: bool { provenance: "Precise Shots 260240 selection.", gate: params.talent_picked(TALENT::PRECISE_SHOTS), source: true, transform: |value| value, },
32 arcane_coef: f64 { provenance: "Arcane Shot 185358 e1 AP coefficient.", gate: true, source: ap(EFFECT::ARCANE_SHOT_DAMAGE), transform: |value| value, },
33 bleak_arrows: bool { provenance: "Bleak Arrows 467749 selection.", gate: params.talent_picked(HERO::DARK_RANGER::SPELL::BLEAK_ARROWS_TALENT), source: true, transform: |value| value, },
34 headshot: bool { provenance: "Headshot 471363 selection.", gate: params.talent_picked(TALENT::HEADSHOT), source: true, transform: |value| value, },
35 focused_aim_cdr_ms: u32 { provenance: "Focused Aim 378767 e1 cooldown reduction milliseconds.", gate: params.talent_picked(TALENT::FOCUSED_AIM), source: base(EFFECT::FOCUSED_AIM_CDR_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc, },
36 windrunner_quiver_chance: f64 { provenance: "Windrunner Quiver 473523 e3 retrigger chance.", gate: params.talent_picked(TALENT::WINDRUNNER_QUIVER), source: base(EFFECT::WINDRUNNER_QUIVER_PCT), transform: |value| value / HUNDRED, },
37 spotters_mark_chance: f64 { provenance: "Spotter's Mark 1219616 e1 chance.", gate: true, source: base(EFFECT::SPOTTERS_MARK_CHANCE), transform: |value| value / HUNDRED, },
38 spotters_mark_mult: f64 { provenance: "Spotter's Mark 466872 e1 Aimed Shot multiplier.", gate: true, source: base(EFFECT::SPOTTERS_MARK_PCT), transform: |value| 1.0 + value / HUNDRED, },
39 feathered_frenzy_mult: f64 { provenance: "Feathered Frenzy 470943 e1 mark-chance multiplier.", gate: params.talent_picked(TALENT::FEATHERED_FRENZY), source: base(EFFECT::FEATHERED_FRENZY_PCT), transform: |value| 1.0 + value / HUNDRED, fallback: 1.0, },
40 aimed_coef: f64 { provenance: "Aimed Shot 19434 e1 AP coefficient.", gate: true, source: ap(EFFECT::AIMED_SHOT_DAMAGE), transform: |value| value, },
41 master_marksman: bool { provenance: "Master Marksman 260309 selection.", gate: params.talent_picked(TALENT::MASTER_MARKSMAN), source: true, transform: |value| value, },
42 master_marksman_fraction: f64 { provenance: "Master Marksman banked damage fraction.", gate: true, source: base(EFFECT::MASTER_MARKSMAN_PCT), transform: |value| value / HUNDRED, },
43 corpsecaller: bool { provenance: "Corpsecaller 1264289 RPPM-rolls a Dark Minion on Black Arrow `DoT` ticks.", gate: params.talent_picked(HERO::DARK_RANGER::SPELL::CORPSECALLER_TALENT), source: true, transform: |value| value, },
44 wailing_dead: bool { provenance: "Wailing Dead 1264290 makes Trueshot summon a Dark Minion.", gate: params.talent_picked(HERO::DARK_RANGER::SPELL::WAILING_DEAD_TALENT), source: true, transform: |value| value, },
45 pact_of_the_hollow: bool { provenance: "Pact of the Hollow 1264690 makes Aimed Shot command every Dark Minion to fire Blighted Arrow.", gate: params.talent_picked(HERO::DARK_RANGER::SPELL::PACT_OF_THE_HOLLOW_TALENT), source: true, transform: |value| value, },
46 dark_minion_duration_ms: u32 { provenance: "Dark Minion summon 1264345 supplies the duration.", gate: true, source: dark_minion_duration_ms, transform: |value| value, },
47 dark_minion_shoot_cast_ms: u32 { provenance: "Dark Minion Shoot 1264357 supplies the cast time; the pet inherits no haste.", gate: true, source: dark_minion_shoot_cast_ms, transform: |value| value, },
48 explosive_shot_cleave_mult: f64 { provenance: "Explosive Shot 212431 e3 cleave fraction.", gate: params.talent_picked(TALENT::EXPLOSIVE_SHOT), source: base(EFFECT::EXPLOSIVE_SHOT_CLEAVE_PCT), transform: |value| 1.0 - value / HUNDRED },
49 explosive_shot_reduced_targets: usize { provenance: "Explosive Shot 212431 e2 reduced-AoE target threshold.", gate: params.talent_picked(TALENT::EXPLOSIVE_SHOT), source: base(EFFECT::EXPLOSIVE_SHOT_REDUCED_TARGETS), transform: wowlab_types::numeric::f64_to_usize_saturating_trunc },
50 mid2_explosive_tick_cdr_ms: u32 { provenance: "MID2 Marksmanship four-piece cooldown reduction per Explosive Shot tick.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_MM_4PC), source: base(EFFECT::MID2_MM_4PC_CDR_MS), transform: wowlab_types::numeric::f64_to_u32_saturating_trunc },
51 mid2_explosive_damage_mult: f64 { provenance: "MID2 Marksmanship two-piece Explosive Shot damage bonus.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_MM_2PC), source: base(EFFECT::MID2_MM_2PC_DAMAGE), transform: |value| 1.0 + value / HUNDRED, fallback: 1.0 },
52 mid2_aimed_damage_mult: f64 { provenance: "MID2 Marksmanship four-piece Aimed Shot and Rapid Fire damage bonus.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_MM_4PC), source: base(EFFECT::MID2_MM_4PC_DAMAGE), transform: |value| 1.0 + value / HUNDRED, fallback: 1.0 },
53}
54accessor pub(super) fn marksmanship_config(ctx);
55register pub(super) fn derive_config(built, params) -> MarksmanshipConfig;
56prepare {
57 let data = ¶ms.game_data;
58 let base = |e: (u32, u8)| data.base_points(SpellIdx::from_raw(e.0), e.1);
59 let ap = |effect| data.effect_ap_coefficient(effect);
60
61 let black_arrow = params.talent_picked(HERO::DARK_RANGER::SPELL::BLACK_ARROW_TALENT);
62 let deathblow_talent = params.talent_picked(TALENT::DEATHBLOW);
63 let soul_drinker = params.talent_picked(HERO::DARK_RANGER::SPELL::SOUL_DRINKER_TALENT);
64
65 let (aimed_chance, rf_chance) = gated(deathblow_talent, || {
66 (
67 base(EFFECT::DEATHBLOW_AIMED_PCT) / HUNDRED
68 + gated(soul_drinker, || base(EFFECT::SOUL_DRINKER_AIMED_PCT) / HUNDRED),
69 base(EFFECT::DEATHBLOW_RF_PCT) / HUNDRED
70 + gated(soul_drinker, || base(EFFECT::SOUL_DRINKER_RF_PCT) / HUNDRED),
71 )
72 });
73
74 let wf_count = wowlab_types::numeric::f64_to_i32_saturating_trunc(base(EFFECT::WITHERING_FIRE_COUNT))
75 + gated(params.talent_picked(HERO::DARK_RANGER::SPELL::BLIGHTED_QUIVER_TALENT), || wowlab_types::numeric::f64_to_i32_saturating_trunc(base(EFFECT::BLIGHTED_QUIVER_EXTRA)));
76
77 let dark_minion_duration_ms = data
78 .aura_duration_ms(SpellIdx::from_raw(HERO::DARK_RANGER::SPELL::DARK_MINION_SUMMON))
79 .unwrap_or_default();
80 let dark_minion_shoot_cast_ms = data
81 .cast_time_ms(SpellIdx::from_raw(REPORTED_SPELL::SHOOT))
82 .unwrap_or_default();
83}
84auras {}
85wiring {
86 { provenance: "MID2 Marksmanship two-piece Explosive Shot duration and periodic damage.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_MM_2PC), apply: {
87 let duration_ms = wowlab_types::numeric::f64_to_u32_saturating_trunc(base(EFFECT::MID2_MM_2PC_DURATION_MS));
88 let multiplier = 1.0 + base(EFFECT::MID2_MM_2PC_DAMAGE) / HUNDRED;
89 built.patch_aura(AURA_EXPLOSIVE_SHOT, |aura| {
90 aura.base_duration_ms = aura.base_duration_ms.saturating_add(duration_ms);
91 if let Some(periodic) = aura.periodic.as_mut() {
92 periodic.effect = match periodic.effect {
93 PeriodicKind::DamageAp { coef, is_physical } => PeriodicKind::DamageAp {
94 coef: coef * multiplier,
95 is_physical,
96 },
97 other => other,
98 };
99 }
100 });
101 } },
102 { provenance: "MID2 Marksmanship four-piece Rapid Fire damage bonus.", gate: params.set_bonus_auras.contains(&SET_BONUS::MID2_MM_4PC), apply: {
103 let multiplier = 1.0 + base(EFFECT::MID2_MM_4PC_DAMAGE) / HUNDRED;
104 built.patch_spell(SPELL_RAPID_FIRE, |spell| {
105 spell.channel.tick_damage = spell.channel.tick_damage.scaled(multiplier);
106 });
107 } },
108}
109finish |cfg| {
110 let _ = built;
111 cfg
112}
113}