diff --git a/engine/class_modules/warlock/sc_warlock.cpp b/engine/class_modules/warlock/sc_warlock.cpp index 4e5ab68dab1..e43a9ee1db0 100644 --- a/engine/class_modules/warlock/sc_warlock.cpp +++ b/engine/class_modules/warlock/sc_warlock.cpp @@ -212,7 +212,8 @@ warlock_t::warlock_t( sim_t* sim, util::string_view name, race_e r ) normalize_destruction_mastery( false ), eye_explosion_instanced_bug_cb( true ), eye_explosion_instanced_bug_sb( true ), - eye_explosion_instanced_bug_rof( false ) + eye_explosion_instanced_bug_rof( false ), + tyrant_antoran_armaments_target_mul( 1.0 ) { cooldowns.haunt = get_cooldown( "haunt" ); cooldowns.dark_harvest = get_cooldown( "dark_harvest" ); @@ -482,15 +483,18 @@ std::string warlock_t::create_profile( save_e stype ) if ( normalize_destruction_mastery ) profile_str += "normalize_destruction_mastery=" + util::to_string( as( normalize_destruction_mastery ) ) + "\n"; - if ( eye_explosion_instanced_bug_cb ) + if ( !eye_explosion_instanced_bug_cb ) profile_str += "eye_explosion_instanced_bug_cb=" + util::to_string( as( eye_explosion_instanced_bug_cb ) ) + "\n"; - if ( eye_explosion_instanced_bug_sb ) + if ( !eye_explosion_instanced_bug_sb ) profile_str += "eye_explosion_instanced_bug_sb=" + util::to_string( as( eye_explosion_instanced_bug_sb ) ) + "\n"; if ( eye_explosion_instanced_bug_rof ) profile_str += "eye_explosion_instanced_bug_rof=" + util::to_string( as( eye_explosion_instanced_bug_rof ) ) + "\n"; + if ( tyrant_antoran_armaments_target_mul < 1.0 ) + profile_str += + "tyrant_antoran_armaments_target_mul=" + util::to_string( tyrant_antoran_armaments_target_mul ) + "\n"; rng_settings.for_each( [ &profile_str ]( auto& setting ) { profile_str += append_rng_option( setting ); } ); } @@ -510,6 +514,7 @@ void warlock_t::copy_from( player_t* source ) eye_explosion_instanced_bug_cb = p->eye_explosion_instanced_bug_cb; eye_explosion_instanced_bug_sb = p->eye_explosion_instanced_bug_sb; eye_explosion_instanced_bug_rof = p->eye_explosion_instanced_bug_rof; + tyrant_antoran_armaments_target_mul = p->tyrant_antoran_armaments_target_mul; rng_settings = p->rng_settings; } diff --git a/engine/class_modules/warlock/sc_warlock.hpp b/engine/class_modules/warlock/sc_warlock.hpp index 9e07bf75e5b..fda0564925d 100644 --- a/engine/class_modules/warlock/sc_warlock.hpp +++ b/engine/class_modules/warlock/sc_warlock.hpp @@ -459,6 +459,7 @@ struct warlock_t : public parse_player_effects_t player_talent_t rune_of_shadows; player_talent_t carnivorous_stalkers; // Chance for Dreadstalkers to perform additional Dreadbites player_talent_t fel_armaments; + const spell_data_t* fel_armaments_2; // Another effect of Fel Armaments that, due to a bug, is always active player_talent_t imp_gang_boss; const spell_data_t* imp_gang_boss_buff; // Buff on Wild Imps @@ -1071,6 +1072,7 @@ struct warlock_t : public parse_player_effects_t bool eye_explosion_instanced_bug_cb; bool eye_explosion_instanced_bug_sb; bool eye_explosion_instanced_bug_rof; + double tyrant_antoran_armaments_target_mul; warlock_t( sim_t* sim, util::string_view name, race_e r ); diff --git a/engine/class_modules/warlock/sc_warlock_actions.cpp b/engine/class_modules/warlock/sc_warlock_actions.cpp index 46d3841efca..2053b5161e0 100644 --- a/engine/class_modules/warlock/sc_warlock_actions.cpp +++ b/engine/class_modules/warlock/sc_warlock_actions.cpp @@ -352,6 +352,7 @@ using namespace helpers; // It seems that having the GoSac buff prevents these bugs if ( p()->bugs && !p()->buffs.grimoire_of_sacrifice->check() && ( ( this->id == p()->talents.chaos_bolt->id() && p()->eye_explosion_instanced_bug_cb ) + || ( this->id == p()->hero.ruination_cast->id() && p()->eye_explosion_instanced_bug_sb ) || ( this->id == p()->talents.shadowburn->id() && p()->eye_explosion_instanced_bug_sb ) || ( this->id == p()->talents.rain_of_fire->id() && p()->eye_explosion_instanced_bug_rof ) ) ) { @@ -2054,7 +2055,8 @@ using namespace helpers; if ( soul_harvester() && p()->buffs.nightfall->check() ) { - if ( p()->hero.wicked_reaping.ok() ) + // NOTE: 2026-03-21 Malefic Grasp consumes Nightfall without triggering Wicked Reaping (bug) + if ( !p()->bugs && p()->hero.wicked_reaping.ok() ) p()->proc_actions.wicked_reaping->execute_on_target( target ); if ( p()->hero.quietus.ok() && p()->hero.shared_fate.ok() ) @@ -4635,6 +4637,10 @@ using namespace helpers; ruination_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Ruination", p, p->hero.ruination_cast, options_str ) { + // Ruination triggers Demonic Art, but not Diabolic Ritual. + // On top of that, it does so even if the cast starts before gaining the Demonic Art buff. + triggers.demonic_art = triggers.demonic_art_buff = p->hero.diabolic_ritual.ok(); + impact_action = new ruination_impact_t( p ); add_child( impact_action ); } @@ -4652,6 +4658,15 @@ using namespace helpers; warlock_spell_t::execute(); p()->buffs.ruination->decrement(); + + // Ruination triggers Dominion of Argus + if ( p()->talents.dominion_of_argus_1.ok() && p()->buffs.dominion_of_argus->check() ) + p()->buffs.dominion_of_argus->trigger(); + + // Ruination "refunds" one shard even though it doesn't actually consume any + if ( p()->talents.dominion_of_argus_3.ok() && p()->buffs.dominion_of_argus->check() ) + p()->resource_gain( RESOURCE_SOUL_SHARD, p()->talents.dominion_of_argus_3_gain->effectN( 1 ).resource(), + p()->gains.dominion_of_argus ); } }; @@ -4684,14 +4699,8 @@ using namespace helpers; p()->buffs.minds_eyes->trigger( p()->buffs.demonic_oculi->check() ); warlock_spell_t::execute(); - } - void impact( action_state_t* s ) override - { - warlock_spell_t::impact( s ); - - if ( s->chain_target == 0 ) - p()->buffs.demonic_oculi->expire(); + p()->buffs.demonic_oculi->expire(); } }; @@ -4827,12 +4836,13 @@ using namespace helpers; if ( p->cooldowns.echo_of_sargeras->down() ) return; + // NOTE: 2026-03-17 RoF does not proc Embers of Sargeras out of combat (bug?) + if ( p->bugs && !p->in_combat && proc == p->procs.echo_of_sargeras_rof ) + return; + // If no valid target provided, find a random target with Immolate or Wither ticking if ( !target || target->is_sleeping() ) { - // NOTE: 2026-03-17 RoF does not proc Embers of Sargeras unless you are targeting an enemy (bug) - if ( p->bugs && proc == p->procs.echo_of_sargeras_rof ) - return; std::vector candidates; @@ -4854,11 +4864,7 @@ using namespace helpers; echo_action->execute_on_target( target ); proc->occur(); - - // NOTE: 2026-03-17 Vision of Nihilam buff does not proc from RoF (bug) - if ( !p->bugs || proc != p->procs.echo_of_sargeras_rof ) - p->buffs.vision_of_nihilam->trigger(); - + p->buffs.vision_of_nihilam->trigger(); p->cooldowns.echo_of_sargeras->start(); } @@ -5186,7 +5192,7 @@ using namespace helpers; // NOTE: 2026-03-17 Echo of Sargeras is not scaled as stated for any of the spenders (bug) proc_actions.echo_of_sargeras_cb = new echo_of_sargeras_t( this, "echo_of_sargeras_cb", bugs ? 1.0 : talents.embers_of_nihilam_3->effectN( 1 ).percent() ); proc_actions.echo_of_sargeras_sb = new echo_of_sargeras_t( this, "echo_of_sargeras_sb", bugs ? 1.0 : talents.embers_of_nihilam_3->effectN( 2 ).percent() ); - proc_actions.echo_of_sargeras_rof = new echo_of_sargeras_t( this, "echo_of_sargeras_rof", bugs ? 0.5 : talents.embers_of_nihilam_3->effectN( 3 ).percent() ); + proc_actions.echo_of_sargeras_rof = new echo_of_sargeras_t( this, "echo_of_sargeras_rof", bugs ? 1.0 : talents.embers_of_nihilam_3->effectN( 3 ).percent() ); } if ( talents.embers_of_nihilam_1.ok() || talents.embers_of_nihilam_3.ok() ) diff --git a/engine/class_modules/warlock/sc_warlock_init.cpp b/engine/class_modules/warlock/sc_warlock_init.cpp index c8b38ae6568..69079bed6a4 100644 --- a/engine/class_modules/warlock/sc_warlock_init.cpp +++ b/engine/class_modules/warlock/sc_warlock_init.cpp @@ -121,6 +121,10 @@ namespace warlock // NOTE: 2026-02-17 Mark of Perotharn is being applied twice in what appears to be a bug if ( bugs ) parse_passive_effects( hero.mark_of_perotharn, true ); + + // NOTE: 2026-03-21 An additional effect of Fel Armaments talent is applied even if the talent is not selected (bug) + if ( demonology() && bugs ) + parse_passive_effects( talents.fel_armaments_2, true ); } void warlock_t::init_spells_affliction() @@ -274,6 +278,7 @@ namespace warlock talents.carnivorous_stalkers = find_talent_spell( talent_tree::SPECIALIZATION, "Carnivorous Stalkers" ); // Should be ID 386194; talents.fel_armaments = find_talent_spell( talent_tree::SPECIALIZATION, "Fel Armaments" ); // Should be ID 1263935 + talents.fel_armaments_2 = conditional_spell_lookup( warlock_base.demonology_warlock->ok() && bugs, 1263938 ); // Always active due to a bug talents.imp_gang_boss = find_talent_spell( talent_tree::SPECIALIZATION, "Imp Gang Boss" ); // Should be ID 1250768 @@ -1599,6 +1604,7 @@ namespace warlock add_option( opt_bool( "eye_explosion_instanced_bug_cb", eye_explosion_instanced_bug_cb ) ); add_option( opt_bool( "eye_explosion_instanced_bug_sb", eye_explosion_instanced_bug_sb ) ); add_option( opt_bool( "eye_explosion_instanced_bug_rof", eye_explosion_instanced_bug_rof ) ); + add_option( opt_float( "tyrant_antoran_armaments_target_mul", tyrant_antoran_armaments_target_mul, 0.0, 1.0 )); rng_settings.for_each( [ this ]( auto& setting ) { diff --git a/engine/class_modules/warlock/sc_warlock_pets.cpp b/engine/class_modules/warlock/sc_warlock_pets.cpp index 7c7fcf8f022..79c3e83f512 100644 --- a/engine/class_modules/warlock/sc_warlock_pets.cpp +++ b/engine/class_modules/warlock/sc_warlock_pets.cpp @@ -537,7 +537,7 @@ struct felstorm_t : public warlock_pet_melee_attack_t { return action_t::composite_haste() * player->cache.spell_cast_speed(); } timespan_t composite_dot_duration( const action_state_t* s ) const override - { return s->action->tick_time( s ) * 5.0; } + { return s->action->tick_time( s ) * data().duration().total_seconds(); } void execute() override { @@ -596,7 +596,7 @@ void felguard_pet_t::init_base_stats() melee_attack = new felguard_melee_t( this, 1.0, "melee" ); // 2026-02-17: Validated coefficients - owner_coeff.ap_from_sp = 1.521; + owner_coeff.ap_from_sp = 1.5201; owner_coeff.sp_from_sp = 1.4519; // not validated owner_coeff.health = 0.75; @@ -1364,6 +1364,21 @@ struct burning_cleave_t : public warlock_pet_spell_t reduced_aoe_targets = as( data().effectN( 2 ).base_value() ); } + int n_targets() const override + { + // Tyrant with Antoran Armaments (Burning Cleave) has a very narrow arc, so it often misses some targets. + // This behavior is replicated through a configurable option that controls the target ratio affected by Burning Cleave. + if ( p()->o()->talents.antoran_armaments.ok() && p()->o()->tyrant_antoran_armaments_target_mul < 1.0 ) + { + assert( warlock_pet_spell_t::n_targets() == -1 ); + const int cur_n_targets = target_list().size(); + return std::max( 1, as( std::lround( cur_n_targets * p()->o()->tyrant_antoran_armaments_target_mul ) ) ); + } + else + { + return warlock_pet_spell_t::n_targets(); + } + } }; struct demonic_tyrant_leap_t : warlock_pet_t::travel_t