From d4144c68223997ae48db35bd143914d6e8ff4820 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Wed, 1 Jul 2026 15:08:26 +0100 Subject: [PATCH 01/10] [Hunter] Incendiary Ammunition rework new name & effect, autoparsed --- engine/class_modules/sc_hunter.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index c51475f2899..2f2df5e9627 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -786,7 +786,7 @@ struct hunter_t final : public player_t spell_data_ptr_t take_aim_3; spell_data_ptr_t windrunner_quiver; - spell_data_ptr_t incendiary_ammunition; + spell_data_ptr_t accuracy_by_volume; spell_data_ptr_t double_tap; spell_data_ptr_t double_tap_buff; spell_data_ptr_t salvo; @@ -5449,9 +5449,6 @@ struct aimed_shot_base_t : public hunter_ranged_attack_t { double cm = hunter_ranged_attack_t::composite_crit_damage_bonus_multiplier(); - if ( p()->talents.incendiary_ammunition.ok() ) - cm *= 1 + p()->buffs.bulletstorm->check() * p()->talents.bulletstorm_buff->effectN( 2 ).percent(); - if ( p()->talents.take_aim_2.ok() ) cm *= 1 + p()->talents.take_aim_2->effectN( 4 ).percent() * p()->cache.attack_crit_chance(); @@ -7722,7 +7719,7 @@ void hunter_t::init_spells() talents.focus_fire_buff = talents.focus_fire.ok() ? find_spell( 1277549 ) : spell_data_t::not_found(); talents.windrunner_quiver = find_talent_spell( talent_tree::SPECIALIZATION, "Windrunner Quiver", HUNTER_MARKSMANSHIP ); - talents.incendiary_ammunition = find_talent_spell( talent_tree::SPECIALIZATION, "Incendiary Ammunition", HUNTER_MARKSMANSHIP ); + talents.accuracy_by_volume = find_talent_spell( talent_tree::SPECIALIZATION, "Accuracy By Volume", HUNTER_MARKSMANSHIP ); talents.double_tap = find_talent_spell( talent_tree::SPECIALIZATION, "Double Tap", HUNTER_MARKSMANSHIP ); talents.double_tap_buff = talents.double_tap.ok() ? find_spell( 260402 ) : spell_data_t::not_found(); talents.salvo = find_talent_spell( talent_tree::SPECIALIZATION, "Salvo", HUNTER_MARKSMANSHIP ); From 9e34f4e46477a428b382a84b064ac165798c3b94 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Wed, 1 Jul 2026 15:48:33 +0100 Subject: [PATCH 02/10] [Hunter] Double Tap removal it's really gone this time, right? --- engine/class_modules/sc_hunter.cpp | 68 ------------------------------ 1 file changed, 68 deletions(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index 2f2df5e9627..2e0475c8851 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -448,7 +448,6 @@ struct hunter_t final : public player_t buff_t* bullseye; buff_t* bulletstorm; buff_t* volley; - buff_t* double_tap; buff_t* focus_fire; buff_t* precision_detonation_hidden; @@ -787,8 +786,6 @@ struct hunter_t final : public player_t spell_data_ptr_t take_aim_3; spell_data_ptr_t windrunner_quiver; spell_data_ptr_t accuracy_by_volume; - spell_data_ptr_t double_tap; - spell_data_ptr_t double_tap_buff; spell_data_ptr_t salvo; spell_data_ptr_t shrapnel_shot; spell_data_ptr_t unload; @@ -5535,32 +5532,6 @@ struct aimed_shot_t : public aimed_shot_base_t } }; - struct aimed_shot_double_tap_t : aimed_shot_base_t - { - aimed_shot_double_tap_t( util::string_view n, hunter_t* p ) : aimed_shot_base_t( n, p, p->talents.aimed_shot ) - { - background = dual = true; - base_costs[ RESOURCE_FOCUS ] = 0; - base_multiplier *= p->talents.double_tap->effectN( 3 ).percent(); - } - - void execute() override - { - aimed_shot_base_t::execute(); - - // Consumes Lock and Load without a benefit - if ( p()->buffs.lock_and_load->check() ) - { - p()->buffs.lock_and_load->decrement(); - } - - // 2026-04-21: Double Tap Aimed Shots can trigger Blighted Arrow casts - if ( p()->talents.pact_of_the_hollow.ok() ) - for ( auto pet : p()->pets.dark_minion.active_pets() ) - pet->actions.blighted_arrow->execute(); - } - }; - struct { double chance = 0; proc_t* proc; @@ -5571,7 +5542,6 @@ struct aimed_shot_t : public aimed_shot_base_t } deathblow; aimed_shot_aspect_of_the_hydra_t* aspect_of_the_hydra = nullptr; - aimed_shot_double_tap_t* double_tap = nullptr; bool lock_and_loaded = false; aimed_shot_t( hunter_t* p, util::string_view options_str ) : @@ -5585,12 +5555,6 @@ struct aimed_shot_t : public aimed_shot_base_t add_child( aspect_of_the_hydra ); } - if ( p->talents.double_tap.ok() ) - { - double_tap = p->get_background_action( "aimed_shot_double_tap" ); - add_child( double_tap ); - } - if ( p->talents.surging_shots.ok() ) { surging_shots.chance = p->talents.surging_shots->effectN( 3 ).percent(); @@ -5667,15 +5631,6 @@ struct aimed_shot_t : public aimed_shot_base_t if ( aspect_of_the_hydra && tl.size() > 1 ) make_event( p()->sim, 10_ms, [ this, tl ]() { aspect_of_the_hydra->execute_on_target( tl[ 1 ] ); } ); - if ( double_tap && p()->buffs.double_tap->up() ) - { - make_event( p()->sim, 10_ms, [ this ]() { double_tap->execute_on_target( target ); } ); - if ( aspect_of_the_hydra && tl.size() > 1 ) - make_event( p()->sim, 10_ms, [ this, tl ]() { aspect_of_the_hydra->execute_on_target( tl[ 1 ] ); } ); - - p()->buffs.double_tap->expire(); - } - if ( p()->talents.pact_of_the_hollow.ok() ) for ( auto pet : p()->pets.dark_minion.active_pets() ) pet->actions.blighted_arrow->execute(); @@ -5951,7 +5906,6 @@ struct rapid_fire_t: public hunter_ranged_attack_t hunter_ranged_attack_t::last_tick( d ); p()->consume_trick_shots(); - p()->buffs.double_tap->expire(); p()->buffs.focus_fire->expire(); execute_unload(); @@ -5962,23 +5916,11 @@ struct rapid_fire_t: public hunter_ranged_attack_t // substract 1 here because RF has a tick at zero double num_ticks = base_num_ticks - 1; - if ( p()->buffs.double_tap->check() ) - num_ticks *= 1 + p()->talents.double_tap_buff->effectN( 3 ).percent(); - timespan_t base_duration = num_ticks * tick_time( s ); return base_duration; } - double tick_time_pct_multiplier( const action_state_t* s ) const override - { - double m = hunter_ranged_attack_t::tick_time_pct_multiplier( s ); - - m *= 1 + p()->buffs.double_tap->check_value(); - - return m; - } - double energize_cast_regen( const action_state_t* ) const override { return base_num_ticks * damage -> composite_energize_amount( nullptr ); @@ -6972,8 +6914,6 @@ struct trueshot_t : public hunter_spell_t if ( p()->talents.moonlight_chakram.ok() ) p()->buffs.moonlight_chakram->trigger(); - - p()->buffs.double_tap->trigger(); } }; @@ -7064,8 +7004,6 @@ struct volley_t : public hunter_spell_t } } ) ); - - p()->buffs.double_tap->trigger(); } }; @@ -7720,8 +7658,6 @@ void hunter_t::init_spells() talents.windrunner_quiver = find_talent_spell( talent_tree::SPECIALIZATION, "Windrunner Quiver", HUNTER_MARKSMANSHIP ); talents.accuracy_by_volume = find_talent_spell( talent_tree::SPECIALIZATION, "Accuracy By Volume", HUNTER_MARKSMANSHIP ); - talents.double_tap = find_talent_spell( talent_tree::SPECIALIZATION, "Double Tap", HUNTER_MARKSMANSHIP ); - talents.double_tap_buff = talents.double_tap.ok() ? find_spell( 260402 ) : spell_data_t::not_found(); talents.salvo = find_talent_spell( talent_tree::SPECIALIZATION, "Salvo", HUNTER_MARKSMANSHIP ); talents.shrapnel_shot = find_talent_spell( talent_tree::SPECIALIZATION, "Shrapnel Shot", HUNTER_MARKSMANSHIP ); talents.unload = find_talent_spell( talent_tree::SPECIALIZATION, "Unload", HUNTER_MARKSMANSHIP ); @@ -8106,10 +8042,6 @@ void hunter_t::create_buffs() ->set_default_value_from_effect( 1 ) ->set_refresh_behavior( buff_refresh_behavior::DISABLED ); - buffs.double_tap = - make_buff( this, "double_tap", talents.double_tap_buff ) - ->set_default_value_from_effect( 1 ); - buffs.volley = make_buff( this, "volley", talents.volley_data ) -> set_cooldown( 0_ms ) From 5e018bbf5f5f6aadb69e6ebe2278bd0f07e0ddb4 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Wed, 1 Jul 2026 15:50:35 +0100 Subject: [PATCH 03/10] [Hunter] Headshot removal --- engine/class_modules/sc_hunter.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index 2e0475c8851..b9e3bb4875f 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -353,10 +353,7 @@ struct hunter_td_t: public actor_target_data_t buff_t* outland_venom; buff_t* spotters_mark; - buff_t* sentinels_mark; - - buff_t* headshot; } debuffs; struct dots_t @@ -758,8 +755,6 @@ struct hunter_t final : public player_t spell_data_ptr_t critical_precision; spell_data_ptr_t no_scope; spell_data_ptr_t feathered_frenzy; - spell_data_ptr_t headshot; - spell_data_ptr_t headshot_debuff; spell_data_ptr_t deadeye; spell_data_ptr_t deathblow; @@ -1223,7 +1218,6 @@ struct hunter_action_t: public Base bool bullseye_crit_chance = false; damage_affected_by lone_wolf; damage_affected_by sniper_training; - damage_affected_by headshot; // Survival bool outland_venom = false; @@ -1255,7 +1249,6 @@ struct hunter_action_t: public Base affected_by.unnatural_causes = parse_damage_affecting_aura( this, p->talents.unnatural_causes_debuff ); affected_by.sniper_training = parse_damage_affecting_aura( this, p->mastery.sniper_training ); - affected_by.headshot = parse_damage_affecting_aura( this, p->talents.headshot_debuff ); affected_by.trueshot_crit_damage_bonus = check_affected_by( this, p->talents.trueshot->effectN( 4 ) ); affected_by.bullseye_crit_chance = check_affected_by( this, p->talents.bullseye->effectN( 1 ).trigger()->effectN( 1 ) ); @@ -1487,9 +1480,6 @@ struct hunter_action_t: public Base if ( affected_by.sentinels_mark.direct ) da *= 1 + td( target )->debuffs.sentinels_mark->check_value(); - if ( affected_by.headshot.direct && td( target )->debuffs.headshot->check() ) - da *= 1 + td( target )->debuffs.headshot->stack_value(); - if ( p()->specialization() == HUNTER_BEAST_MASTERY && affected_by.through_the_eyes.direct && td( target )->dots.black_arrow->is_ticking() ) @@ -1510,9 +1500,6 @@ struct hunter_action_t: public Base ta *= 1.0476; } - if ( affected_by.headshot.tick && td( target )->debuffs.headshot->check() ) - ta *= 1 + td( target )->debuffs.headshot->stack_value(); - if ( p()->specialization() == HUNTER_BEAST_MASTERY && affected_by.through_the_eyes.tick && td( target )->dots.black_arrow->is_ticking() ) @@ -4504,9 +4491,6 @@ struct kill_shot_base_t : hunter_ranged_attack_t if ( debug_cast( s )->empowered_by_precise_shots ) p()->trigger_eagles_mark( s->target, p()->talents.sentinel.ok() ); - - if ( p()->talents.headshot.ok() ) - td( s->target )->debuffs.headshot->trigger(); } bool target_ready( player_t* candidate_target ) override @@ -7267,9 +7251,6 @@ hunter_td_t::hunter_td_t( player_t* t, hunter_t* p ) : actor_target_data_t( t, p debuffs.sentinels_mark = make_buff( *this, "sentinels_mark", p->talents.sentinels_mark ) ->set_default_value_from_effect( p->specialization() == HUNTER_MARKSMANSHIP ? 1 : 2 ); - debuffs.headshot = make_buff( *this, "headshot", p->talents.headshot_debuff ) - -> set_default_value_from_effect( 1 ); - dots.wildfire_bomb = t->get_dot( p->talents.shrapnel_bomb ? "wildfire_bomb_bleed" : "wildfire_bomb_dot", p ); dots.sanctified_armaments = t->get_dot( "sanctified_armaments", p ); dots.black_arrow = t -> get_dot( "black_arrow_dot", p ); @@ -7633,8 +7614,6 @@ void hunter_t::init_spells() talents.critical_precision = find_talent_spell( talent_tree::SPECIALIZATION, "Critical Precision", HUNTER_MARKSMANSHIP ); talents.no_scope = find_talent_spell( talent_tree::SPECIALIZATION, "No Scope", HUNTER_MARKSMANSHIP ); talents.feathered_frenzy = find_talent_spell( talent_tree::SPECIALIZATION, "Feathered Frenzy", HUNTER_MARKSMANSHIP ); - talents.headshot = find_talent_spell( talent_tree::SPECIALIZATION, "Headshot", HUNTER_MARKSMANSHIP ); - talents.headshot_debuff = talents.headshot.ok() ? find_spell( 1277558 ) : spell_data_t::not_found(); talents.deadeye = find_talent_spell( talent_tree::SPECIALIZATION, "Deadeye", HUNTER_MARKSMANSHIP ); talents.deathblow = find_talent_spell( talent_tree::SPECIALIZATION, "Deathblow", HUNTER_MARKSMANSHIP ); From 51c9fcfbccc1f8471700e04d1fe8bdd92ad26041 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Fri, 10 Jul 2026 19:29:16 +0100 Subject: [PATCH 04/10] [Hunter] Razor Sharp replacing heart of the pack --- engine/class_modules/sc_hunter.cpp | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index b9e3bb4875f..e4b1c06a4e6 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -455,7 +455,6 @@ struct hunter_t final : public player_t buff_t* huntmasters_call; buff_t* summon_fenryr; buff_t* summon_hati; - buff_t* heart_of_the_pack; buff_t* natures_ally_3; buff_t* bloody_frenzy; @@ -691,8 +690,7 @@ struct hunter_t final : public player_t spell_data_ptr_t brutal_companion; spell_data_ptr_t huntmasters_call; - spell_data_ptr_t heart_of_the_pack; - spell_data_ptr_t heart_of_the_pack_buff; + spell_data_ptr_t razor_sharp; spell_data_ptr_t bloodshed; spell_data_ptr_t bloodshed_dot; spell_data_ptr_t savagery_bm; @@ -1830,8 +1828,6 @@ struct dire_critter_t : public hunter_pet_t action_t* kill_command = nullptr; } actions; - bool triggers_heart_of_the_pack = false; - dire_critter_t( hunter_t* owner, util::string_view n = "dire_beast" ) : hunter_pet_t( owner, n, PET_HUNTER, true /* GUARDIAN */, true /* dynamic */ ) { @@ -1865,9 +1861,6 @@ struct dire_critter_t : public hunter_pet_t if ( o()->talents.wildspeaker.ok() && o()->buffs.bestial_wrath->check() ) buffs.bestial_wrath->trigger( o()->buffs.bestial_wrath->remains() ); - if ( triggers_heart_of_the_pack && o()->talents.heart_of_the_pack.ok() ) - o()->buffs.heart_of_the_pack->trigger(); - buffs.pet_damage->trigger(); additional_summon_behavior(); @@ -1911,7 +1904,6 @@ struct dark_hound_t final : public dire_critter_t resource_regeneration = regen_type::DISABLED; owner_coeff.ap_from_ap = 1.5; auto_attack_multiplier = 4; - triggers_heart_of_the_pack = true; } void summon( timespan_t duration = 0_ms ) override @@ -1936,7 +1928,6 @@ struct dire_beast_t final : public dire_critter_t // 13-10-22 Dire Beast damage increased by 50%. (60% -> 90%) // 22-7-24 Dire Beast damage increased by 10% (90% -> 100%) owner_coeff.ap_from_ap = 1; - triggers_heart_of_the_pack = true; } void summon( timespan_t duration = 0_ms ) override @@ -1975,7 +1966,6 @@ struct fenryr_t final : public dire_critter_t { // 9-7-25 Hati and Fenryr base damage increased to about 2x of a normal Dire Beast's damage. owner_coeff.ap_from_ap = 2; - triggers_heart_of_the_pack = true; } void summon( timespan_t duration = 0_ms ) override @@ -2011,7 +2001,6 @@ struct hati_t final : public dire_critter_t { // 9-7-25 Hati and Fenryr base damage increased to about 2x of a normal Dire Beast's damage. owner_coeff.ap_from_ap = 2; - triggers_heart_of_the_pack = true; } }; @@ -2049,7 +2038,6 @@ struct bear_t final : public dire_critter_t owner_coeff.ap_from_ap = 0.6; auto_attack_multiplier = 7; main_hand_weapon.swing_time = 1.5_s; - triggers_heart_of_the_pack = true; } void additional_summon_behavior() override @@ -7543,8 +7531,7 @@ void hunter_t::init_spells() talents.brutal_companion = find_talent_spell( talent_tree::SPECIALIZATION, "Brutal Companion", HUNTER_BEAST_MASTERY ); talents.huntmasters_call = find_talent_spell( talent_tree::SPECIALIZATION, "Huntmaster's Call", HUNTER_BEAST_MASTERY ); - talents.heart_of_the_pack = find_talent_spell( talent_tree::SPECIALIZATION, "Heart of the Pack", HUNTER_BEAST_MASTERY ); - talents.heart_of_the_pack_buff = talents.heart_of_the_pack.ok() ? find_spell( 1282747 ) : spell_data_t::not_found(); + talents.razor_sharp = find_talent_spell( talent_tree::SPECIALIZATION, "Razor Sharp", HUNTER_BEAST_MASTERY ); talents.bloodshed = find_talent_spell( talent_tree::SPECIALIZATION, "Bloodshed", HUNTER_BEAST_MASTERY ); talents.bloodshed_dot = talents.bloodshed.ok() ? find_spell( 321538 ) : spell_data_t::not_found(); talents.savagery_bm = find_talent_spell( talent_tree::SPECIALIZATION, "Savagery", HUNTER_BEAST_MASTERY ); @@ -8064,12 +8051,6 @@ void hunter_t::create_buffs() -> add_invalidate( CACHE_PET_DAMAGE_MULTIPLIER ) -> set_default_value_from_effect( 2 ); - buffs.heart_of_the_pack = - make_buff( this, "heart_of_the_pack", talents.heart_of_the_pack_buff ) - -> set_stack_behavior( buff_stack_behavior::ASYNCHRONOUS ) - -> set_default_value( talents.heart_of_the_pack->effectN( 1 ).percent() / 10 ) // Spelldata is scuffed as of 2026-01-08 - -> set_pct_buff_type( STAT_PCT_BUFF_HASTE ); - buffs.natures_ally_3 = make_buff( this, "natures_ally", talents.natures_ally_3_buff ) -> set_default_value( talents.natures_ally_3_buff->effectN( 1 ).percent() ); From 222345549cc60ec3c1f5972ac086de1919847041 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Fri, 10 Jul 2026 19:38:03 +0100 Subject: [PATCH 05/10] [Hunter] Piercing Fangs rework fully autoparsed now --- engine/class_modules/sc_hunter.cpp | 44 ++---------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index e4b1c06a4e6..8a445a99a44 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -2147,7 +2147,6 @@ struct hunter_main_pet_base_t : public stable_pet_t struct buffs_t { buff_t* bestial_wrath = nullptr; - buff_t* piercing_fangs = nullptr; } buffs; target_specific_t target_data; @@ -2161,21 +2160,7 @@ struct hunter_main_pet_base_t : public stable_pet_t buffs.bestial_wrath = make_buff( this, "bestial_wrath", find_spell( 186254 ) ) -> set_default_value_from_effect( 1 ) - -> set_cooldown( 0_ms ) - -> set_stack_change_callback( [ this ]( buff_t*, int old, int cur ) { - if ( cur == 0 ) - { - buffs.piercing_fangs -> expire(); - } - else if (old == 0) { - buffs.piercing_fangs -> trigger(); - } - } ); - - buffs.piercing_fangs = - make_buff( this, "piercing_fangs", o() -> find_spell( 392054 ) ) - -> set_default_value_from_effect( 1 ) - -> set_chance( o() -> talents.piercing_fangs.ok() ); + -> set_cooldown( 0_ms ); } double composite_melee_auto_attack_speed() const override @@ -2198,16 +2183,6 @@ struct hunter_main_pet_base_t : public stable_pet_t return m; } - double composite_player_critical_damage_multiplier( const action_state_t* s, school_e school ) const override - { - double m = stable_pet_t::composite_player_critical_damage_multiplier( s, school ); - - if ( buffs.piercing_fangs -> data().effectN( 1 ).has_common_school( school ) ) - m *= 1 + buffs.piercing_fangs -> check_value(); - - return m; - } - const hunter_main_pet_base_td_t* find_target_data( const player_t* target ) const override { return target_data[ target ]; @@ -2260,22 +2235,7 @@ struct natures_ally_pet_t final : public hunter_main_pet_base_t buffs.bestial_wrath = make_buff( this, "bestial_wrath_apex", find_spell( 1285912 ) ) ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_DONE ) - ->set_cooldown( 0_ms ) - ->set_stack_change_callback( [ this ]( buff_t*, int old, int cur ) { - if ( cur == 0 ) - { - buffs.piercing_fangs->expire(); - } - else if ( old == 0 ) - { - buffs.piercing_fangs->trigger(); - } - } ); - - buffs.piercing_fangs = - make_buff( this, "piercing_fangs", o()->find_spell( 392054 ) ) - ->set_default_value_from_effect( 1 ) - ->set_chance( o()->talents.piercing_fangs.ok() ); + ->set_cooldown( 0_ms ); } void summon( timespan_t duration = 0_ms ) override From 3ea8914b5de21890afba16b9a7b664af49c40a36 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Fri, 10 Jul 2026 22:32:02 +0100 Subject: [PATCH 06/10] [Hunter] Wild Instincts removal --- engine/class_modules/sc_hunter.cpp | 58 ++---------------------------- 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index 8a445a99a44..ee301e6b65b 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -700,7 +700,6 @@ struct hunter_t final : public player_t spell_data_ptr_t wildspeaker; spell_data_ptr_t wildspeaker_kill_command; spell_data_ptr_t wildspeaker_bestial_wrath; - spell_data_ptr_t wild_instincts; spell_data_ptr_t bloody_frenzy; spell_data_ptr_t bloody_frenzy_buff; spell_data_ptr_t piercing_fangs; @@ -1016,7 +1015,6 @@ struct hunter_t final : public player_t action_t* lunar_storm = nullptr; action_t* stampede = nullptr; - action_t* wild_instincts = nullptr; action_t* let_fly = nullptr; } actions; @@ -3100,50 +3098,13 @@ struct takedown_t : public hunter_pet_attack_t struct stomp_t : public hunter_pet_attack_t { - bool thundering_hooves = false; - - stomp_t( hunter_pet_t* p, util::string_view n = "stomp", bool is_thundering_hooves = false ) + stomp_t( hunter_pet_t* p, util::string_view n = "stomp", double dd = 1.0 ) : hunter_pet_attack_t( n, p, p->o()->talents.stomp_dmg ) { background = true; aoe = -1; - thundering_hooves = is_thundering_hooves; - base_dd_multiplier *= thundering_hooves ? o()->talents.thundering_hooves->effectN( 1 ).percent() : 1.0; + base_dd_multiplier *= dd; }; - - void execute() override - { - hunter_pet_attack_t::execute(); - - if ( o()->talents.wild_instincts.ok() ) - { - auto tl = target_list(); - - if ( p() == o()->pets.main ) - { - // Prioritise targets without Barbed Shot ticking. - // Thundering Hooves stomps can trigger Wild Instincts on the primary target. - range::erase_remove( tl, [ this ]( player_t* t ) { - return ( !thundering_hooves && t == target ) || o()->get_target_data( t )->dots.barbed_shot->is_ticking(); - } ); - target_cache.is_valid = false; - - if ( !tl.empty() ) - o()->actions.wild_instincts->execute_on_target( tl.front() ); - } - } - } - - void impact( action_state_t* s ) override - { - hunter_pet_attack_t::impact( s ); - - /* Wild Instincts edge case in ST where the target, if unaffected by - Barbed Shot, can receive a double Barbed Shot. */ - if ( o()->talents.wild_instincts.ok() && target_list().size() == 1 ) - if ( p() == o()->pets.main && !o()->get_target_data( s->target )->dots.barbed_shot->is_ticking() ) - o()->actions.wild_instincts->execute_on_target( s->target ); - } }; // Bloodshed =============================================================== @@ -3358,7 +3319,7 @@ void stable_pet_t::init_spells() hunter_pet_t::init_spells(); if ( o()->talents.thundering_hooves.ok() ) - actions.thundering_hooves = new actions::stomp_t( this, "thundering_hooves", o()->talents.thundering_hooves.ok() ); + actions.thundering_hooves = new actions::stomp_t( this, "thundering_hooves", o()->talents.thundering_hooves->effectN( 1 ).percent() ); } void hunter_main_pet_base_t::init_spells() @@ -5201,15 +5162,6 @@ struct barbed_shot_t : public barbed_shot_base_t } }; -struct barbed_shot_wild_instincts_t : public barbed_shot_base_t -{ - barbed_shot_wild_instincts_t( hunter_t* p ) - : barbed_shot_base_t( p, "barbed_shot", p->talents.barbed_shot ) - { - background = dual = true; - } -}; - // Laceration (Beast Mastery Talent) ============================================== struct laceration_t : public residual_bleed_base_t @@ -7501,7 +7453,6 @@ void hunter_t::init_spells() talents.wildspeaker = find_talent_spell( talent_tree::SPECIALIZATION, "Wildspeaker", HUNTER_BEAST_MASTERY ); talents.wildspeaker_bestial_wrath = talents.wildspeaker.ok() ? find_spell( 1235388 ) : spell_data_t::not_found(); talents.wildspeaker_kill_command = talents.wildspeaker.ok() ? find_spell( 1232922 ) : spell_data_t::not_found(); - talents.wild_instincts = find_talent_spell( talent_tree::SPECIALIZATION, "Wild Instincts", HUNTER_BEAST_MASTERY ); talents.bloody_frenzy = find_talent_spell( talent_tree::SPECIALIZATION, "Bloody Frenzy", HUNTER_BEAST_MASTERY ); talents.bloody_frenzy_buff = talents.bloody_frenzy.ok() ? find_spell( 1265063 ) : spell_data_t::not_found(); talents.piercing_fangs = find_talent_spell( talent_tree::SPECIALIZATION, "Piercing Fangs", HUNTER_BEAST_MASTERY ); @@ -7906,9 +7857,6 @@ void hunter_t::create_actions() if ( talents.stampede.ok() ) actions.stampede = new attacks::stampede_t( this ); - if ( talents.wild_instincts.ok() ) - actions.wild_instincts = new attacks::barbed_shot_wild_instincts_t( this ); - if ( tier_set.mid_s1_mm_4pc.ok() ) actions.let_fly = new attacks::let_fly_t( this ); } From a36d5e061de2516da456f1a747271b4b5e10ff44 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Fri, 10 Jul 2026 22:52:36 +0100 Subject: [PATCH 07/10] [Hunter] Beast Mastery 12.1 2pc --- engine/class_modules/sc_hunter.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index ee301e6b65b..85ca42215f4 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -2094,6 +2094,7 @@ struct stable_pet_t : public hunter_pet_t struct actions_t { action_t* stomp = nullptr; + action_t* stomp_2pc = nullptr; action_t* thundering_hooves = nullptr; } actions; @@ -3320,6 +3321,9 @@ void stable_pet_t::init_spells() if ( o()->talents.thundering_hooves.ok() ) actions.thundering_hooves = new actions::stomp_t( this, "thundering_hooves", o()->talents.thundering_hooves->effectN( 1 ).percent() ); + + if ( o()->tier_set.mid_s2_bm_2pc.ok() ) + actions.stomp_2pc = new actions::stomp_t( this, "stomp_2pc", o()->tier_set.mid_s2_bm_2pc->effectN( 1 ).percent() ); } void hunter_main_pet_base_t::init_spells() @@ -5152,6 +5156,9 @@ struct barbed_shot_t : public barbed_shot_base_t { if ( p()->talents.stomp.ok() ) pet->stable_pet_t::actions.stomp->execute_on_target( target ); + + if ( p()->tier_set.mid_s2_bm_2pc.ok() ) + pet->stable_pet_t::actions.stomp_2pc->execute_on_target( target ); } if ( p()->talents.soul_drinker.ok() ) From 0b134f710c7524ed5095f122906df6fb53856938 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Sat, 11 Jul 2026 15:14:20 +0100 Subject: [PATCH 08/10] [Hunter] Beast Mastery 12.1 4pc --- engine/class_modules/sc_hunter.cpp | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index 85ca42215f4..7e0267d3ac1 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -424,6 +424,8 @@ struct hunter_t final : public player_t // Midnight Season 2 - Curse of Ula’tek spell_data_ptr_t mid_s2_bm_2pc; spell_data_ptr_t mid_s2_bm_4pc; + spell_data_ptr_t mid_s2_bm_4pc_buff; + spell_data_ptr_t mid_s2_bm_4pc_damage; spell_data_ptr_t mid_s2_mm_2pc; spell_data_ptr_t mid_s2_mm_4pc; @@ -477,6 +479,7 @@ struct hunter_t final : public player_t buff_t* predators_thirst; // Tier Set Bonuses + buff_t* cobra_fang; // Hero Talents @@ -1017,6 +1020,7 @@ struct hunter_t final : public player_t action_t* stampede = nullptr; action_t* let_fly = nullptr; + action_t* cobra_cleave = nullptr; } actions; cdwaste::player_data_t cd_waste; @@ -3106,6 +3110,16 @@ struct stomp_t : public hunter_pet_attack_t aoe = -1; base_dd_multiplier *= dd; }; + + void impact( action_state_t* s ) override + { + hunter_pet_attack_t::impact( s ); + + if ( o()->tier_set.mid_s2_bm_4pc.ok() ) + { + o()->buffs.cobra_fang->increment(); + } + } }; // Bloodshed =============================================================== @@ -5036,6 +5050,8 @@ struct cobra_shot_base_t: public hunter_ranged_attack_t p()->buffs.hogstrider->expire(); + p()->buffs.cobra_fang->expire(); + p()->trigger_natures_ally_3(); } @@ -5046,6 +5062,9 @@ struct cobra_shot_base_t: public hunter_ranged_attack_t if ( p()->buffs.hogstrider->up() ) m *= 1 + p()->talents.hogstrider_buff->effectN( 1 ).percent(); + if ( p()->buffs.cobra_fang->up() && !p()->buffs.beast_cleave->check() && s->chain_target == 0 ) + m *= 1 + p()->tier_set.mid_s2_bm_4pc_buff->effectN( 2 ).percent() * p()->buffs.cobra_fang->check(); + return m; } @@ -5060,6 +5079,16 @@ struct cobra_shot_base_t: public hunter_ranged_attack_t { hunter_ranged_attack_t::impact( s ); + if ( p()->buffs.cobra_fang->up() && p()->buffs.beast_cleave->check() && s->chain_target == 0 ) + { + // 2026-07-11: It seems like Cobra Cleave's target multipliers replicate to other targets, unlike all other Beast Cleaves + // TODO fully confirm the above + const double effectiveness = p()->tier_set.mid_s2_bm_4pc_buff->effectN( 1 ).percent() * p()->buffs.cobra_fang->check(); + const double amount = s->result_total * effectiveness; + + p()->actions.cobra_cleave->execute_on_target( s->target, amount ); + } + if ( s->result == RESULT_CRIT && p()->talents.serpentine_strikes.ok() ) p()->resource_gain( RESOURCE_FOCUS, serpentine_strikes_amount, p()->gains.serpentine_strikes, this ); } @@ -5084,6 +5113,26 @@ struct cobra_shot_snakeskin_quiver_t : public cobra_shot_base_t } }; +// Cobra Cleave (BM MID2 4pc) ================================================ + +struct cobra_cleave_t final : hunter_ranged_attack_t +{ + cobra_cleave_t( hunter_t* p ) : hunter_ranged_attack_t( "cobra_cleave", p, p->tier_set.mid_s2_bm_4pc_damage ) + { + background = dual = true; + aoe = -1; + reduced_aoe_targets = data().effectN( 2 ).base_value(); + target_filter_callback = secondary_targets_only(); + } + + void init() override + { + hunter_ranged_attack_t::init(); + + snapshot_flags |= STATE_TGT_MUL_DA; + } +}; + // Barbed Shot =============================================================== struct barbed_shot_base_t : public hunter_ranged_attack_t @@ -7780,6 +7829,8 @@ void hunter_t::init_spells() tier_set.mid_s2_bm_2pc = sets->set( HUNTER_BEAST_MASTERY, MID2, B2 ); tier_set.mid_s2_bm_4pc = sets->set( HUNTER_BEAST_MASTERY, MID2, B4 ); + tier_set.mid_s2_bm_4pc_buff = tier_set.mid_s2_bm_4pc.ok() ? find_spell( 1299389 ) : spell_data_t::not_found(); + tier_set.mid_s2_bm_4pc_damage = tier_set.mid_s2_bm_4pc.ok() ? find_spell( 1299409 ) : spell_data_t::not_found(); tier_set.mid_s2_mm_2pc = sets->set( HUNTER_MARKSMANSHIP, MID2, B2 ); tier_set.mid_s2_mm_4pc = sets->set( HUNTER_MARKSMANSHIP, MID2, B4 ); @@ -7866,6 +7917,9 @@ void hunter_t::create_actions() if ( tier_set.mid_s1_mm_4pc.ok() ) actions.let_fly = new attacks::let_fly_t( this ); + + if ( tier_set.mid_s2_bm_4pc.ok() ) + actions.cobra_cleave = new attacks::cobra_cleave_t( this ); } void hunter_t::create_buffs() @@ -8059,6 +8113,10 @@ void hunter_t::create_buffs() -> add_invalidate( CACHE_LEECH ); // Tier Set Bonuses + + buffs.cobra_fang = + make_buff( this, "cobra_fang", tier_set.mid_s2_bm_4pc_buff ) + ->set_chance( tier_set.mid_s2_bm_4pc.ok() ); // Hero Talents From d67832e60badd16ef248e63c89db2f0a53c1daa8 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Sat, 11 Jul 2026 15:22:35 +0100 Subject: [PATCH 09/10] [Hunter] Through the Eyes rework fully autoparsed now --- engine/class_modules/sc_hunter.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index 7e0267d3ac1..31665da04c6 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -1235,7 +1235,6 @@ struct hunter_action_t: public Base damage_affected_by wyverns_cry; // Dark Ranger - damage_affected_by through_the_eyes; } affected_by; cdwaste::action_data_t* cd_waste = nullptr; @@ -1266,8 +1265,6 @@ struct hunter_action_t: public Base affected_by.stargazer = check_affected_by( this, p->talents.stargazer_buff->effectN( 1 ) ); affected_by.wyverns_cry = parse_damage_affecting_aura( this, p->talents.howl_of_the_pack_leader_wyvern_buff ); - - affected_by.through_the_eyes = parse_damage_affecting_aura( this, p->talents.black_arrow_dot ); } hunter_t* p() { return static_cast( ab::player ); } @@ -1480,11 +1477,6 @@ struct hunter_action_t: public Base if ( affected_by.sentinels_mark.direct ) da *= 1 + td( target )->debuffs.sentinels_mark->check_value(); - if ( p()->specialization() == HUNTER_BEAST_MASTERY - && affected_by.through_the_eyes.direct - && td( target )->dots.black_arrow->is_ticking() ) - da *= 1 + p()->talents.black_arrow_dot->effectN( 2 ).percent(); - return da; } @@ -1500,11 +1492,6 @@ struct hunter_action_t: public Base ta *= 1.0476; } - if ( p()->specialization() == HUNTER_BEAST_MASTERY - && affected_by.through_the_eyes.tick - && td( target )->dots.black_arrow->is_ticking() ) - ta *= 1 + p()->talents.black_arrow_dot->effectN( 2 ).percent(); - return ta; } From c9812f57e1175187fc769d6de00e039d1daf4286 Mon Sep 17 00:00:00 2001 From: ZingusWoW Date: Sat, 11 Jul 2026 15:39:13 +0100 Subject: [PATCH 10/10] [Hunter] Improve Apex Pet realism --- engine/class_modules/sc_hunter.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index 31665da04c6..fa2e182e8f7 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -6646,7 +6646,7 @@ struct bestial_wrath_t: public hunter_ranged_attack_t if ( p()->tier_set.mid_s1_bm_4pc->ok() ) p()->spawn_dire_beast( p()->tier_set.mid_s1_bm_4pc->effectN( 1 ).time_value() ); - for ( auto pet : pets::active( p()->pets.main, p()->pets.animal_companion, p()->pets.natures_ally_pet.active_pet() ) ) + for ( auto pet : pets::active( p()->pets.main, p()->pets.animal_companion ) ) { trigger_buff( pet->buffs.bestial_wrath, precast_time ); @@ -6655,6 +6655,16 @@ struct bestial_wrath_t: public hunter_ranged_attack_t pet -> actions.bestial_wrath -> execute_on_target( target ); } + // 2026-07-11: Apex pets have funky delays on Bestial Wrath's buff trigger and damage event + // Using timings based on log data + if ( auto pet = p()->pets.natures_ally_pet.active_pet() ) + { + make_event( sim, 450_ms, [ this, pet ]() { trigger_buff( pet->buffs.bestial_wrath, precast_time ); } ); + + if ( !is_precombat ) + make_event( sim, 1.5_s, [ this, pet ]() { pet->actions.bestial_wrath->execute_on_target( target ); } ); + } + if ( p()->talents.wildspeaker.ok() ) { for ( auto pet : p()->active_pets )