Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions engine/class_modules/warlock/sc_warlock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,7 @@ struct warlock_t : public parse_player_effects_t
{
threshold_rng_t* agony_energize;
threshold_rng_t* nightfall;
threshold_rng_t* demonfire_infusion;
threshold_rng_t* seeds_of_their_demise;
} progress_rng;

Expand Down Expand Up @@ -1052,8 +1053,6 @@ struct warlock_t : public parse_player_effects_t
simple_proc_t* immolate_crit_energize; // TODO: Need to check the type of rng
simple_proc_t* demoniac_imp_implosion;
simple_proc_t* carnivorous_stalkers;
simple_proc_t* demonfire_infusion_dot; // TODO: Need to check the type of rng
simple_proc_t* demonfire_infusion_inc; // TODO: Need to check the type of rng
simple_proc_t* alythesss_ire_shift;
simple_proc_t* wither_crit_energize; // TODO: Need to check the type of rng
simple_proc_t* blackened_soul;
Expand Down Expand Up @@ -1330,6 +1329,8 @@ namespace helpers
virtual void execute() override;
};

unsigned incinerate_state_target_count( const action_state_t* state );

void consume_succulent_soul( warlock_t* p, player_t* target );

void trigger_blackened_soul( warlock_t* p, bool malevolence, player_t* bs_target = nullptr );
Expand Down
226 changes: 161 additions & 65 deletions engine/class_modules/warlock/sc_warlock_actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ using namespace helpers;
p()->procs.ravenous_afflictions->occur();
}

if ( destruction() && p()->talents.reverse_entropy.ok() )
if ( destruction() && p()->talents.reverse_entropy.ok() && result_is_hit( d->state->result ) )
{
if ( p()->buffs.reverse_entropy->trigger() )
p()->procs.reverse_entropy->occur();
Expand Down Expand Up @@ -1105,37 +1105,43 @@ using namespace helpers;

if ( affliction() )
{
if ( result_is_hit( d->state->result ) && p()->talents.nightfall.ok() && p()->progress_rng.nightfall->trigger( d->state ) )
{
p()->procs.nightfall->occur();
p()->buffs.nightfall->trigger();
}
if ( p()->talents.siphon_life.ok() || ( p()->hero.seeds_of_their_demise.ok() && d->target->health_percentage() <= p()->hero.seeds_of_their_demise->effectN( 2 ).base_value() ) )
if ( result_is_hit( d->state->result ) )
{
// Affliction Wither DoT ticks trigger procs when talented into Siphon Life
// Affliction Wither DoT ticks also trigger procs when attempting to start a collapse via Seeds of Their Demise
p()->trigger_aura_applied_callbacks( proc_data, p() );
if ( p()->talents.nightfall.ok() && p()->progress_rng.nightfall->trigger( d->state ) )
{
p()->procs.nightfall->occur();
p()->buffs.nightfall->trigger();
}
if ( p()->talents.siphon_life.ok() || ( p()->hero.seeds_of_their_demise.ok() && d->target->health_percentage() <= p()->hero.seeds_of_their_demise->effectN( 2 ).base_value() ) )
{
// Affliction Wither DoT ticks trigger procs when talented into Siphon Life
// Affliction Wither DoT ticks also trigger procs when attempting to start a collapse via Seeds of Their Demise
p()->trigger_aura_applied_callbacks( proc_data, p() );
}
}
}

if ( destruction() )
{
if ( d->state->result == RESULT_CRIT && p()->flat_rng.wither_crit_energize->trigger() )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.wither_crits );
if ( result_is_hit( d->state->result ) )
{
if ( d->state->result == RESULT_CRIT && p()->flat_rng.wither_crit_energize->trigger() )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.wither_crits );

p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.wither );
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.wither );

if ( p()->talents.flashpoint.ok() && d->target->health_percentage() >= p()->talents.flashpoint->effectN( 2 ).base_value() )
p()->buffs.flashpoint->trigger();
if ( p()->talents.flashpoint.ok() && d->target->health_percentage() >= p()->talents.flashpoint->effectN( 2 ).base_value() )
p()->buffs.flashpoint->trigger();

if ( p()->talents.demonfire_infusion.ok() && p()->flat_rng.demonfire_infusion_dot->trigger() )
{
p()->proc_actions.demonfire_infusion->execute_on_target( d->target );
p()->procs.demonfire_infusion_dot->occur();
}
if ( p()->talents.demonfire_infusion.ok() && p()->progress_rng.demonfire_infusion->trigger( d->state ) )
{
p()->proc_actions.demonfire_infusion->execute_on_target( d->target );
p()->procs.demonfire_infusion_dot->occur();
}

// Destruction Wither DoT ticks trigger procs through some hidden trigger
p()->trigger_aura_applied_callbacks( proc_data, p() );
// Destruction Wither DoT ticks trigger procs through some hidden trigger
p()->trigger_aura_applied_callbacks( proc_data, p() );
}
}

// Seeds of their Demise collapse conditions must be checked periodically for every Wither tick
Expand Down Expand Up @@ -1518,17 +1524,20 @@ using namespace helpers;

void tick( dot_t* d ) override
{
if ( p()->progress_rng.agony_energize->trigger( d->state ) )
{
p()->resource_gain( RESOURCE_SOUL_SHARD, 1.0, p()->gains.agony );
warlock_spell_t::tick( d );

// Agony energize spell triggers procs
p()->trigger_aura_applied_callbacks( p()->proc_data_entries.agony_energize, p() );
}
if ( result_is_hit( d->state->result ) )
{
if ( p()->progress_rng.agony_energize->trigger( d->state ) )
{
p()->resource_gain( RESOURCE_SOUL_SHARD, 1.0, p()->gains.agony );

warlock_spell_t::tick( d );
// Agony energize spell triggers procs
p()->trigger_aura_applied_callbacks( p()->proc_data_entries.agony_energize, p() );
}

d->increment( 1 );
d->increment( 1 );
}
}
};

Expand Down Expand Up @@ -3886,8 +3895,39 @@ using namespace helpers;

struct incinerate_t : public warlock_spell_t
{
struct incinerate_state_t : public action_state_t
{
unsigned real_total_target_count;

incinerate_state_t( action_t* action, player_t* target )
: action_state_t( action, target ),
real_total_target_count( 1 )
{ }

void initialize() override
{
action_state_t::initialize();
real_total_target_count = 1;
}

std::ostringstream& debug_str( std::ostringstream& s ) override
{
action_state_t::debug_str( s );
s << " real_total_target_count=" << real_total_target_count;
return s;
}

void copy_state( const action_state_t* s ) override
{
action_state_t::copy_state( s );
real_total_target_count = debug_cast<const incinerate_state_t*>( s )->real_total_target_count;
}
};

struct incinerate_fnb_t : public warlock_spell_t
{
unsigned real_total_target_count = 1;

incinerate_fnb_t( warlock_t* p )
: warlock_spell_t( "Incinerate (Fire and Brimstone)", p, p->warlock_base.incinerate )
{
Expand All @@ -3912,6 +3952,15 @@ using namespace helpers;
double cost() const override
{ return 0.0; }

action_state_t* new_state() override
{ return new incinerate_state_t( this, target ); }

void snapshot_state( action_state_t* s, result_amount_type rt ) override
{
warlock_spell_t::snapshot_state( s, rt );
debug_cast<incinerate_state_t*>( s )->real_total_target_count = real_total_target_count;
}

size_t available_targets( std::vector<player_t*>& tl ) const override
{
warlock_spell_t::available_targets( tl );
Expand All @@ -3931,13 +3980,23 @@ using namespace helpers;
{
warlock_spell_t::impact( s );

if ( p()->bugs && p()->talents.diabolic_embers.ok() && s->result == RESULT_CRIT )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.incinerate_crits );
if ( result_is_hit( s->result ) )
{
if ( p()->bugs && p()->talents.diabolic_embers.ok() && s->result == RESULT_CRIT )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.incinerate_crits );

if ( p()->talents.demonfire_infusion.ok() && p()->progress_rng.demonfire_infusion->trigger( s ) )
{
p()->proc_actions.demonfire_infusion->execute_on_target( s->target );
p()->procs.demonfire_infusion_inc->occur();
}
}
}
};

double energize_mult;
incinerate_fnb_t* fnb_action;
unsigned real_total_target_count = 1;

incinerate_t( warlock_t* p, util::string_view options_str )
: warlock_spell_t( "Incinerate", p, p->warlock_base.incinerate, options_str ),
Expand All @@ -3959,6 +4018,15 @@ using namespace helpers;
add_child( fnb_action );
}

action_state_t* new_state() override
{ return new incinerate_state_t( this, target ); }

void snapshot_state( action_state_t* s, result_amount_type rt ) override
{
warlock_spell_t::snapshot_state( s, rt );
debug_cast<incinerate_state_t*>( s )->real_total_target_count = real_total_target_count;
}

// Custom init() to combine Havoc+FnB coefficients instead of using the generic warlock_spell_t::init() Havoc multiplier
void init() override
{
Expand All @@ -3985,16 +4053,23 @@ using namespace helpers;
{
player_t* cast_target = target;

warlock_spell_t::execute();
real_total_target_count = 1;
if ( use_havoc() )
real_total_target_count = std::min( 2u, as<unsigned>( target_list().size() ) );

if ( p()->talents.fire_and_brimstone.ok() )
fnb_action->execute_on_target( cast_target );

if ( p()->talents.demonfire_infusion.ok() && p()->flat_rng.demonfire_infusion_inc->trigger() )
{
p()->proc_actions.demonfire_infusion->execute_on_target( cast_target );
p()->procs.demonfire_infusion_inc->occur();
// FnB excludes the primary and Havoc targets, so both lists together contain every unique impact.
fnb_action->set_target( cast_target );
real_total_target_count += as<unsigned>( fnb_action->target_list().size() );
fnb_action->real_total_target_count = real_total_target_count;
}
assert( real_total_target_count > 0 );

warlock_spell_t::execute();

if ( p()->talents.fire_and_brimstone.ok() )
fnb_action->execute();

// Incinerate energize spell triggers procs
p()->trigger_aura_applied_callbacks( p()->proc_data_entries.incinerate_energize, p() );
Expand All @@ -4014,13 +4089,22 @@ using namespace helpers;
{
warlock_spell_t::impact( s );

// TOCHECK: 2025-08-27 Incinerate Havoc crit impacts don't give extra shards (bug?), and only 1 extra shard with Diabolic Embers
if ( s->result == RESULT_CRIT )
if ( result_is_hit( s->result ) )
{
if ( !p()->bugs || s->chain_target == 0 )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1 * energize_mult, p()->gains.incinerate_crits );
else if ( p()->talents.diabolic_embers.ok() )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.incinerate_crits );
// TOCHECK: 2025-08-27 Incinerate Havoc crit impacts don't give extra shards (bug?), and only 1 extra shard with Diabolic Embers
if ( s->result == RESULT_CRIT )
{
if ( !p()->bugs || s->chain_target == 0 )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1 * energize_mult, p()->gains.incinerate_crits );
else if ( p()->talents.diabolic_embers.ok() )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.incinerate_crits );
}

if ( p()->talents.demonfire_infusion.ok() && p()->progress_rng.demonfire_infusion->trigger( s ) )
{
p()->proc_actions.demonfire_infusion->execute_on_target( s->target );
p()->procs.demonfire_infusion_inc->occur();
}
}
}
};
Expand All @@ -4041,22 +4125,25 @@ using namespace helpers;
{
warlock_spell_t::tick( d );

if ( d->state->result == RESULT_CRIT && p()->flat_rng.immolate_crit_energize->trigger() )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.immolate_crits );
if ( result_is_hit( d->state->result ) )
{
if ( d->state->result == RESULT_CRIT && p()->flat_rng.immolate_crit_energize->trigger() )
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.immolate_crits );

p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.immolate );
p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.immolate );

if ( p()->talents.flashpoint.ok() && d->target->health_percentage() >= p()->talents.flashpoint->effectN( 2 ).base_value() )
p()->buffs.flashpoint->trigger();
if ( p()->talents.flashpoint.ok() && d->target->health_percentage() >= p()->talents.flashpoint->effectN( 2 ).base_value() )
p()->buffs.flashpoint->trigger();

if ( p()->talents.demonfire_infusion.ok() && p()->flat_rng.demonfire_infusion_dot->trigger() )
{
p()->proc_actions.demonfire_infusion->execute_on_target( d->target );
p()->procs.demonfire_infusion_dot->occur();
}
if ( p()->talents.demonfire_infusion.ok() && p()->progress_rng.demonfire_infusion->trigger( d->state ) )
{
p()->proc_actions.demonfire_infusion->execute_on_target( d->target );
p()->procs.demonfire_infusion_dot->occur();
}

// Immolate DoT ticks trigger procs through some hidden trigger
p()->trigger_aura_applied_callbacks( proc_data, p() );
// Immolate DoT ticks trigger procs through some hidden trigger
p()->trigger_aura_applied_callbacks( proc_data, p() );
}
}
};

Expand Down Expand Up @@ -5262,17 +5349,8 @@ using namespace helpers;

void execute() override
{
player_t* ib_target = target;

warlock_spell_t::execute();

// 2026-02-18 Infernal Bolt can proc Demonfire Infusion
if ( p()->talents.demonfire_infusion.ok() && p()->flat_rng.demonfire_infusion_inc->trigger() )
{
p()->proc_actions.demonfire_infusion->execute_on_target( ib_target );
p()->procs.demonfire_infusion_inc->occur();
}

// Infernal Bolt energize spell effect triggers procs
p()->trigger_aura_applied_callbacks( proc_data, p() );

Expand All @@ -5284,6 +5362,18 @@ using namespace helpers;
if ( time_to_execute == 0_ms )
p()->buffs.chaotic_inferno->decrement();
}

void impact( action_state_t* s ) override
{
warlock_spell_t::impact( s );

// 2026-07-29 Infernal Bolt hits can proc Demonfire Infusion
if ( result_is_hit( s->result ) && p()->talents.demonfire_infusion.ok() && p()->progress_rng.demonfire_infusion->trigger( s ) )
{
p()->proc_actions.demonfire_infusion->execute_on_target( s->target );
p()->procs.demonfire_infusion_inc->occur();
}
}
};

struct ruination_t : public warlock_spell_t
Expand Down Expand Up @@ -5532,6 +5622,12 @@ using namespace helpers;
// Diabolist Actions End
// Helper Functions Begin

unsigned helpers::incinerate_state_target_count( const action_state_t* state )
{
assert( state );
return debug_cast<const incinerate_t::incinerate_state_t*>( state )->real_total_target_count;
}

void helpers::consume_succulent_soul( warlock_t* p, player_t* target )
{
if ( !p->buffs.succulent_soul->check() )
Expand Down
Loading
Loading