Skip to content

Commit

Permalink
[Warlock] Various quality of life improvements (simulationcraft#9131)
Browse files Browse the repository at this point in the history
* Add options for RNG control

* Add QoL expressions for Diabolist

* Add option to normalize Chaotic Energies
  • Loading branch information
Azevara authored Jul 28, 2024
1 parent 579f113 commit 02521c7
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 21 deletions.
65 changes: 62 additions & 3 deletions engine/class_modules/warlock/sc_warlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ warlock_td_t::warlock_td_t( player_t* target, warlock_t& p )
{
p.proc_actions.doom_proc->execute_on_target( b->player );

if ( p.talents.pact_of_the_eredruin.ok() && p.rng().roll( 0.4 ) )
if ( p.talents.pact_of_the_eredruin.ok() && p.rng().roll( p.rng_settings.pact_of_the_eredruin.setting_value ) )
{
p.warlock_pet_list.doomguards.spawn( 1u );
p.procs.pact_of_the_eredruin->occur();
Expand Down Expand Up @@ -194,7 +194,6 @@ warlock_t::warlock_t( sim_t* sim, util::string_view name, race_e r )
havoc_spells(),
agony_accumulator( 0.0 ),
corruption_accumulator( 0.0 ),
shadow_invocation_proc_chance( 0.0 ),
diabolic_ritual( 0 ),
active_pets( 0 ),
warlock_pet_list( this ),
Expand All @@ -206,7 +205,9 @@ warlock_t::warlock_t( sim_t* sim, util::string_view name, race_e r )
buffs(),
gains(),
procs(),
rng_settings(),
initial_soul_shards( 3 ),
normalize_destruction_mastery( false ),
default_pet(),
disable_auto_felstorm( false )
{
Expand Down Expand Up @@ -563,7 +564,36 @@ std::string warlock_t::create_profile( save_e stype )
if ( !default_pet.empty() )
profile_str += "default_pet=" + default_pet + "\n";
if ( disable_auto_felstorm )
profile_str += "disable_felstorm=" + util::to_string( disable_auto_felstorm );
profile_str += "disable_felstorm=" + util::to_string( disable_auto_felstorm ) + "\n";
if ( normalize_destruction_mastery )
profile_str += "normalize_destruction_mastery=" + util::to_string( normalize_destruction_mastery ) + "\n";

if ( rng_settings.cunning_cruelty_sb.setting_value != rng_settings.cunning_cruelty_sb.default_value )
profile_str += "rng_cunning_cruelty_sb=" + util::to_string( rng_settings.cunning_cruelty_sb.setting_value ) + "\n";
if ( rng_settings.cunning_cruelty_ds.setting_value != rng_settings.cunning_cruelty_ds.default_value )
profile_str += "rng_cunning_cruelty_ds=" + util::to_string( rng_settings.cunning_cruelty_ds.setting_value ) + "\n";
if ( rng_settings.agony.setting_value != rng_settings.agony.default_value )
profile_str += "rng_agony=" + util::to_string( rng_settings.agony.setting_value ) + "\n";
if ( rng_settings.nightfall.setting_value != rng_settings.nightfall.default_value )
profile_str += "rng_nightfall=" + util::to_string( rng_settings.nightfall.setting_value ) + "\n";
if ( rng_settings.pact_of_the_eredruin.setting_value != rng_settings.pact_of_the_eredruin.default_value )
profile_str += "rng_pact_of_the_eredruin=" + util::to_string( rng_settings.pact_of_the_eredruin.setting_value ) + "\n";
if ( rng_settings.shadow_invocation.setting_value != rng_settings.shadow_invocation.default_value )
profile_str += "rng_shadow_invocation=" + util::to_string( rng_settings.shadow_invocation.setting_value ) + "\n";
if ( rng_settings.spiteful_reconstitution.setting_value != rng_settings.spiteful_reconstitution.default_value )
profile_str += "rng_spiteful_reconsitution=" + util::to_string( rng_settings.spiteful_reconstitution.setting_value ) + "\n";
if ( rng_settings.decimation.setting_value != rng_settings.decimation.default_value )
profile_str += "rng_decimation=" + util::to_string( rng_settings.decimation.setting_value ) + "\n";
if ( rng_settings.dimension_ripper.setting_value != rng_settings.dimension_ripper.default_value )
profile_str += "rng_dimension_ripper=" + util::to_string( rng_settings.dimension_ripper.setting_value ) + "\n";
if ( rng_settings.blackened_soul.setting_value != rng_settings.blackened_soul.default_value )
profile_str += "rng_blackened_soul=" + util::to_string( rng_settings.blackened_soul.setting_value ) + "\n";
if ( rng_settings.bleakheart_tactics.setting_value != rng_settings.bleakheart_tactics.default_value )
profile_str += "rng_bleakheart_tactics=" + util::to_string( rng_settings.bleakheart_tactics.setting_value ) + "\n";
if ( rng_settings.seeds_of_their_demise.setting_value != rng_settings.seeds_of_their_demise.default_value )
profile_str += "rng_seeds_of_their_demise=" + util::to_string( rng_settings.seeds_of_their_demise.setting_value ) + "\n";
if ( rng_settings.mark_of_perotharn.setting_value != rng_settings.mark_of_perotharn.default_value )
profile_str += "rng_mark_of_perotharn=" + util::to_string( rng_settings.mark_of_perotharn.setting_value) + "\n";
}

return profile_str;
Expand All @@ -578,6 +608,21 @@ void warlock_t::copy_from( player_t* source )
initial_soul_shards = p->initial_soul_shards;
default_pet = p->default_pet;
disable_auto_felstorm = p->disable_auto_felstorm;
normalize_destruction_mastery = p->normalize_destruction_mastery;

rng_settings.cunning_cruelty_sb = p->rng_settings.cunning_cruelty_sb;
rng_settings.cunning_cruelty_ds = p->rng_settings.cunning_cruelty_ds;
rng_settings.agony = p->rng_settings.agony;
rng_settings.nightfall = p->rng_settings.nightfall;
rng_settings.pact_of_the_eredruin = p->rng_settings.pact_of_the_eredruin;
rng_settings.shadow_invocation = p->rng_settings.shadow_invocation;
rng_settings.spiteful_reconstitution = p->rng_settings.spiteful_reconstitution;
rng_settings.decimation = p->rng_settings.decimation;
rng_settings.dimension_ripper = p->rng_settings.dimension_ripper;
rng_settings.blackened_soul = p->rng_settings.blackened_soul;
rng_settings.bleakheart_tactics = p->rng_settings.bleakheart_tactics;
rng_settings.seeds_of_their_demise = p->rng_settings.seeds_of_their_demise;
rng_settings.mark_of_perotharn = p->rng_settings.mark_of_perotharn;
}

stat_e warlock_t::convert_hybrid_stat( stat_e s ) const
Expand Down Expand Up @@ -789,6 +834,20 @@ std::unique_ptr<expr_t> warlock_t::create_expression( util::string_view name_str
return false;
});
}
else if ( name_str == "diabolic_ritual" )
{
return make_fn_expr( name_str, [ this ]()
{
return buffs.ritual_overlord->check() || buffs.ritual_mother->check() || buffs.ritual_pit_lord->check();
} );
}
else if ( name_str == "demonic_art" )
{
return make_fn_expr( name_str, [ this ]()
{
return buffs.art_overlord->check() || buffs.art_mother->check() || buffs.art_pit_lord->check();
} );
}

auto splits = util::string_split<util::string_view>( name_str, "." );

Expand Down
34 changes: 33 additions & 1 deletion engine/class_modules/warlock/sc_warlock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ struct warlock_t : public player_t
std::vector<action_t*> havoc_spells; // Used for smarter target cache invalidation.
double agony_accumulator;
double corruption_accumulator;
double shadow_invocation_proc_chance; // 2023-09-10: Annoyingly, at this time there is no listed proc chance in data for Shadow Invocation
std::vector<event_t*> wild_imp_spawns; // Used for tracking incoming imps from HoG TODO: Is this still needed with faster spawns?
int diabolic_ritual;

Expand Down Expand Up @@ -711,9 +710,42 @@ struct warlock_t : public player_t
proc_t* mark_of_perotharn;
} procs;

struct rng_settings_t
{
struct rng_setting_t
{
double setting_value;
double default_value;
};

// Affliction
rng_setting_t cunning_cruelty_sb = { 0.50, 0.50 };
rng_setting_t cunning_cruelty_ds = { 0.25, 0.25 };
rng_setting_t agony = { 0.368, 0.368 };
rng_setting_t nightfall = { 0.13, 0.13 };

// Demonology
rng_setting_t pact_of_the_eredruin = { 0.40, 0.40 };
rng_setting_t shadow_invocation = { 0.20, 0.20 };
rng_setting_t spiteful_reconstitution = { 0.30, 0.30 };

// Destruction
rng_setting_t decimation = { 0.10, 0.10 };
rng_setting_t dimension_ripper = { 0.05, 0.05 };

// Diabolist

// Hellcaller
rng_setting_t blackened_soul = { 0.10, 0.10 };
rng_setting_t bleakheart_tactics = { 0.15, 0.15 };
rng_setting_t seeds_of_their_demise = { 0.15, 0.15 };
rng_setting_t mark_of_perotharn = { 0.15, 0.15 };
} rng_settings;

int initial_soul_shards;
std::string default_pet;
bool disable_auto_felstorm; // For Demonology main pet
bool normalize_destruction_mastery;
shuffled_rng_t* rain_of_chaos_rng;
real_ppm_t* ravenous_afflictions_rng;

Expand Down
33 changes: 17 additions & 16 deletions engine/class_modules/warlock/sc_warlock_actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ using namespace helpers;
}
}

if ( p()->talents.shadow_invocation.ok() && triggers.shadow_invocation && rng().roll( p()->shadow_invocation_proc_chance ) )
if ( p()->talents.shadow_invocation.ok() && triggers.shadow_invocation && rng().roll( p()->rng_settings.shadow_invocation.setting_value ) )
{
p()->proc_actions.bilescourge_bombers_proc->execute_on_target( s->target );
p()->procs.shadow_invocation->occur();
Expand All @@ -277,14 +277,14 @@ using namespace helpers;
p()->procs.reverse_entropy->occur();
}

if ( destruction() && triggers.decimation && s->result == RESULT_CRIT && rng().roll( 0.10 ) )
if ( destruction() && triggers.decimation && s->result == RESULT_CRIT && rng().roll( p()->rng_settings.decimation.setting_value ) )
{
p()->buffs.decimation->trigger();
p()->cooldowns.soul_fire->reset( true );
p()->procs.decimation->occur();
}

if ( destruction() && triggers.dimension_ripper && rng().roll( 0.05 ) )
if ( destruction() && triggers.dimension_ripper && rng().roll( p()->rng_settings.dimension_ripper.setting_value ) )
{
if ( p()->talents.dimensional_rift.ok() )
{
Expand Down Expand Up @@ -409,6 +409,9 @@ using namespace helpers;
double min_percentage = affected_by.chaos_incarnate ? p()->talents.chaos_incarnate->effectN( 1 ).percent() : 0.5;
double chaotic_energies_rng = rng().range( min_percentage , 1.0 );

if ( p()->normalize_destruction_mastery )
chaotic_energies_rng = ( 1.0 + min_percentage ) / 2.0;

m *= 1.0 + chaotic_energies_rng * p()->cache.mastery_value();
}

Expand Down Expand Up @@ -963,7 +966,7 @@ using namespace helpers;
}
}

if ( p()->talents.cunning_cruelty.ok() && rng().roll( 0.5 ) )
if ( p()->talents.cunning_cruelty.ok() && rng().roll( p()->rng_settings.cunning_cruelty_sb.setting_value ) )
{
p()->procs.shadow_bolt_volley->occur();
volley->execute_on_target( s->target );
Expand Down Expand Up @@ -1198,7 +1201,7 @@ using namespace helpers;
p()->buffs.flashpoint->trigger();
}

if ( d->state->result == RESULT_CRIT && p()->hero.mark_of_perotharn.ok() && rng().roll( 0.15 ) )
if ( d->state->result == RESULT_CRIT && p()->hero.mark_of_perotharn.ok() && rng().roll( p()->rng_settings.mark_of_perotharn.setting_value ) )
{
d->increment( 1 );
p()->procs.mark_of_perotharn->occur();
Expand Down Expand Up @@ -1251,7 +1254,7 @@ using namespace helpers;
{
warlock_spell_t::impact( s );

if ( s->result == RESULT_CRIT && p()->hero.mark_of_perotharn.ok() && rng().roll( 0.15 ) )
if ( s->result == RESULT_CRIT && p()->hero.mark_of_perotharn.ok() && rng().roll( p()->rng_settings.mark_of_perotharn.setting_value ) )
{
td( s->target )->dots_wither->increment( 1 );
p()->procs.mark_of_perotharn->occur();
Expand Down Expand Up @@ -1300,15 +1303,13 @@ using namespace helpers;
if ( td( tar )->dots_wither->current_stack() <= 1 )
make_event( *sim, 0_ms, [ this, tar ] { td( tar )->debuffs_blackened_soul->expire(); } );

double seeds_rng = 0.15;

if ( affliction() && p()->hero.seeds_of_their_demise.ok() && rng().roll( seeds_rng ) )
if ( affliction() && p()->hero.seeds_of_their_demise.ok() && rng().roll( p()->rng_settings.seeds_of_their_demise.setting_value ) )
{
p()->buffs.tormented_crescendo->trigger();
p()->procs.seeds_of_their_demise->occur();
}

if ( destruction() && p()->hero.seeds_of_their_demise.ok() && rng().roll( seeds_rng ) )
if ( destruction() && p()->hero.seeds_of_their_demise.ok() && rng().roll( p()->rng_settings.seeds_of_their_demise.setting_value ) )
{
p()->buffs.flashpoint->trigger( 2 );
p()->procs.seeds_of_their_demise->occur();
Expand Down Expand Up @@ -1652,7 +1653,7 @@ using namespace helpers;
// results to within 0.1% of accuracy in all tests conducted on all targets numbers up to 8.
// Accurate as of 08-24-2018. TOCHECK regularly. If any changes are made to this section of
// code, please also update the Time_to_Shard expression in sc_warlock.cpp.
double increment_max = 0.368;
double increment_max = p()->rng_settings.agony.setting_value;

double active_agonies = p()->get_active_dots( d );
increment_max *= std::pow( active_agonies, -2.0 / 3.0 );
Expand Down Expand Up @@ -1903,7 +1904,7 @@ using namespace helpers;
}
}

if ( p()->talents.cunning_cruelty.ok() && rng().roll( 0.25 ) )
if ( p()->talents.cunning_cruelty.ok() && rng().roll( p()->rng_settings.cunning_cruelty_ds.setting_value ) )
{
p()->procs.shadow_bolt_volley->occur();
volley->execute_on_target( d->target );
Expand Down Expand Up @@ -2373,7 +2374,7 @@ using namespace helpers;

if ( p()->buffs.demonic_core->check() )
{
if ( p()->talents.spiteful_reconstitution.ok() && rng().roll( 0.3 ) )
if ( p()->talents.spiteful_reconstitution.ok() && rng().roll( p()->rng_settings.spiteful_reconstitution.setting_value ) )
{
p()->warlock_pet_list.wild_imps.spawn( 1u );
p()->procs.spiteful_reconstitution->occur();
Expand Down Expand Up @@ -4112,7 +4113,7 @@ using namespace helpers;
// DR formula was the same and then confirming that you can get procs on 1st tick.
// The procs also have a regularity that suggest it does not use a proc chance or rppm.
// Last checked 09-28-2020.
double increment_max = 0.13;
double increment_max = p->rng_settings.nightfall.setting_value;

double active_corruptions = p->get_active_dots( d );
increment_max *= std::pow( active_corruptions, -2.0 / 3.0 );
Expand Down Expand Up @@ -4144,7 +4145,7 @@ using namespace helpers;
tdata->dots_wither->increment( as<int>( p->hero.malevolence->effectN( 2 ).base_value() ) );

// TOCHECK: Chance for this effect is not in spell data!
if ( p->hero.bleakheart_tactics.ok() && p->rng().roll( 0.15 ) )
if ( p->hero.bleakheart_tactics.ok() && p->rng().roll( p->rng_settings.bleakheart_tactics.setting_value ) )
{
tdata->dots_wither->increment( 1 );
p->procs.bleakheart_tactics->occur();
Expand All @@ -4158,7 +4159,7 @@ using namespace helpers;
{
tdata->debuffs_blackened_soul->trigger();
}
else if ( p->rng().roll( 0.1 ) )
else if ( p->rng().roll( p->rng_settings.blackened_soul.setting_value ) )
{
// TOCHECK: Chance for this effect is not in spell data!
tdata->debuffs_blackened_soul->trigger();
Expand Down
16 changes: 15 additions & 1 deletion engine/class_modules/warlock/sc_warlock_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,21 @@ namespace warlock
add_option( opt_int( "soul_shards", initial_soul_shards ) );
add_option( opt_string( "default_pet", default_pet ) );
add_option( opt_bool( "disable_felstorm", disable_auto_felstorm ) );
add_option( opt_bool( "normalize_destruction_mastery", normalize_destruction_mastery ) );

add_option( opt_float( "rng_cunning_cruelty_sb", rng_settings.cunning_cruelty_sb.setting_value ) );
add_option( opt_float( "rng_cunning_cruelty_ds", rng_settings.cunning_cruelty_ds.setting_value ) );
add_option( opt_float( "rng_agony", rng_settings.agony.setting_value ) );
add_option( opt_float( "rng_nightfall", rng_settings.nightfall.setting_value ) );
add_option( opt_float( "rng_pact_of_the_eredruin", rng_settings.pact_of_the_eredruin.setting_value ) );
add_option( opt_float( "rng_shadow_invocation", rng_settings.shadow_invocation.setting_value ) );
add_option( opt_float( "rng_spiteful_reconstitution", rng_settings.spiteful_reconstitution.setting_value ) );
add_option( opt_float( "rng_decimation", rng_settings.decimation.setting_value ) );
add_option( opt_float( "rng_dimension_ripper", rng_settings.dimension_ripper.setting_value ) );
add_option( opt_float( "rng_blackened_soul", rng_settings.blackened_soul.setting_value ) );
add_option( opt_float( "rng_bleakheart_tactics", rng_settings.bleakheart_tactics.setting_value ) );
add_option( opt_float( "rng_seeds_of_their_demise", rng_settings.seeds_of_their_demise.setting_value ) );
add_option( opt_float( "rng_mark_of_perotharn", rng_settings.mark_of_perotharn.setting_value ) );
}

void warlock_t::combat_begin()
Expand Down Expand Up @@ -1056,7 +1071,6 @@ namespace warlock
ua_target = nullptr;
agony_accumulator = rng().range( 0.0, 0.99 );
corruption_accumulator = rng().range( 0.0, 0.99 );
shadow_invocation_proc_chance = 0.2;
wild_imp_spawns.clear();
diabolic_ritual = as<int>( rng().range( 0, 3 ) );
}
Expand Down
3 changes: 3 additions & 0 deletions engine/class_modules/warlock/sc_warlock_pets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2095,6 +2095,9 @@ struct overfiend_chaos_bolt_t : public warlock_pet_spell_t
double min_percentage = p()->o()->talents.chaos_incarnate.ok() ? p()->o()->talents.chaos_incarnate->effectN( 1 ).percent() : 0.5;
double chaotic_energies_rng = rng().range( min_percentage , 1.0 );

if ( p()->o()->normalize_destruction_mastery )
chaotic_energies_rng = ( min_percentage + 1.0 ) / 2.0;

m *= 1.0 + chaotic_energies_rng * p()->o()->cache.mastery_value();

return m;
Expand Down

0 comments on commit 02521c7

Please sign in to comment.