diff --git a/engine/class_modules/apl/warlock.cpp b/engine/class_modules/apl/warlock.cpp index b453e2eed0e..3f16d281050 100644 --- a/engine/class_modules/apl/warlock.cpp +++ b/engine/class_modules/apl/warlock.cpp @@ -41,174 +41,91 @@ void affliction( player_t* p ) { action_priority_list_t* default_ = p->get_action_priority_list( "default" ); action_priority_list_t* precombat = p->get_action_priority_list( "precombat" ); - action_priority_list_t* aoe = p->get_action_priority_list( "aoe" ); - action_priority_list_t* cleave = p->get_action_priority_list( "cleave" ); - action_priority_list_t* end_of_fight = p->get_action_priority_list( "end_of_fight" ); - action_priority_list_t* se_maintenance = p->get_action_priority_list( "se_maintenance" ); - action_priority_list_t* opener_cleave_se = p->get_action_priority_list( "opener_cleave_se" ); - action_priority_list_t* cleave_se_maintenance = p->get_action_priority_list( "cleave_se_maintenance" ); + // action_priority_list_t* aoe = p->get_action_priority_list( "aoe" ); + // action_priority_list_t* cleave = p->get_action_priority_list( "cleave" ); + // action_priority_list_t* end_of_fight = p->get_action_priority_list( "end_of_fight" ); action_priority_list_t* items = p->get_action_priority_list( "items" ); action_priority_list_t* ogcd = p->get_action_priority_list( "ogcd" ); - action_priority_list_t* variables = p->get_action_priority_list( "variables" ); + // action_priority_list_t* variables = p->get_action_priority_list( "variables" ); precombat->add_action( "summon_pet" ); - precombat->add_action( "variable,name=cleave_apl,default=0,op=reset" ); - precombat->add_action( "variable,name=trinket_1_buffs,value=trinket.1.has_use_buff|trinket.1.is.funhouse_lens", "Used to set Trinket in slot 1 as Buff Trinkets for the automatic logic" ); - precombat->add_action( "variable,name=trinket_2_buffs,value=trinket.2.has_use_buff|trinket.2.is.funhouse_lens", "Used to set Trinkets in slot 2 as Buff Trinkets for the automatic logic" ); - precombat->add_action( "variable,name=trinket_1_sync,op=setif,value=1,value_else=0.5,condition=variable.trinket_1_buffs&(trinket.1.cooldown.duration%%cooldown.soul_rot.duration=0|cooldown.soul_rot.duration%%trinket.1.cooldown.duration=0)", "Automatic Logic for Buff Trinkets in Trinket Slot 1" ); - precombat->add_action( "variable,name=trinket_2_sync,op=setif,value=1,value_else=0.5,condition=variable.trinket_2_buffs&(trinket.2.cooldown.duration%%cooldown.soul_rot.duration=0|cooldown.soul_rot.duration%%trinket.2.cooldown.duration=0)", "Automatic Logic for Buff Trinkets in Trinket Slot 2" ); - precombat->add_action( "variable,name=trinket_1_manual,value=trinket.1.is.spymasters_web|trinket.1.is.aberrant_spellforge", "Sets a specific Trinkets in Slot 1 to follow an APL line and not the automatic logic" ); - precombat->add_action( "variable,name=trinket_2_manual,value=trinket.2.is.spymasters_web|trinket.2.is.aberrant_spellforge", "Sets a specific Trinkets in Slot 2 to follow an APL line and not the automatic logic" ); - precombat->add_action( "variable,name=trinket_1_exclude,value=trinket.1.is.ruby_whelp_shell", "For On Use Trinkets on slot 1 with on use effects you dont want to use in combat" ); - precombat->add_action( "variable,name=trinket_2_exclude,value=trinket.2.is.ruby_whelp_shell", "For On Use Trinkets on Slot 2 with on use effects you don't want to use in combat" ); - precombat->add_action( "variable,name=trinket_1_buff_duration,value=trinket.1.proc.any_dps.duration+(trinket.1.is.funhouse_lens*15)+(trinket.1.is.signet_of_the_priory*20)", "Sets the duration of Trinket 1 in the automatic logic" ); - precombat->add_action( "variable,name=trinket_2_buff_duration,value=trinket.2.proc.any_dps.duration+(trinket.2.is.funhouse_lens*15)+(trinket.2.is.signet_of_the_priory*20)", "Sets the duration of Trinket 2 in the automatic logic" ); - precombat->add_action( "variable,name=trinket_priority,op=setif,value=2,value_else=1,condition=!variable.trinket_1_buffs&variable.trinket_2_buffs|variable.trinket_2_buffs&((trinket.2.cooldown.duration%variable.trinket_2_buff_duration)*(1+0.5*trinket.2.has_buff.intellect)*(variable.trinket_2_sync))>((trinket.1.cooldown.duration%variable.trinket_1_buff_duration)*(1+0.5*trinket.1.has_buff.intellect)*(variable.trinket_1_sync))", "Automatic Logic in case of 2 On Use Buff Trinkets" ); precombat->add_action( "grimoire_of_sacrifice,if=talent.grimoire_of_sacrifice.enabled" ); precombat->add_action( "snapshot_stats" ); - precombat->add_action( "seed_of_corruption,if=spell_targets.seed_of_corruption_aoe>2|spell_targets.seed_of_corruption_aoe>1&talent.demonic_soul" ); precombat->add_action( "haunt" ); - default_->add_action( "call_action_list,name=variables" ); - default_->add_action( "call_action_list,name=cleave,if=active_enemies!=1&active_enemies<3|variable.cleave_apl" ); - default_->add_action( "call_action_list,name=aoe,if=active_enemies>2" ); - default_->add_action( "call_action_list,name=ogcd" ); - default_->add_action( "call_action_list,name=items" ); - default_->add_action( "call_action_list,name=end_of_fight" ); - default_->add_action( "agony,if=(!talent.vile_taint|cooldown.vile_taint.remains&remainsdot.agony.remains+5" ); - default_->add_action( "haunt,if=talent.demonic_soul&buff.nightfall.react<2-prev_gcd.1.drain_soul&(!talent.vile_taint|cooldown.vile_taint.remains)" ); - default_->add_action( "unstable_affliction,if=(talent.absolute_corruption&remains<3|!talent.absolute_corruption&remains<5|cooldown.soul_rot.remains<5&remains<8)&(!talent.demonic_soul|buff.nightfall.react<2|prev_gcd.1.haunt&buff.nightfall.stack<2)&fight_remains>dot.unstable_affliction.remains+5" ); - default_->add_action( "haunt,if=(talent.absolute_corruption&debuff.haunt.remains<3|!talent.absolute_corruption&debuff.haunt.remains<5|cooldown.soul_rot.remains<5&debuff.haunt.remains<8)&(!talent.vile_taint|cooldown.vile_taint.remains)&fight_remains>debuff.haunt.remains+5" ); - default_->add_action( "wither,if=(talent.wither&!talent.absolute_corruption&remains<5|cooldown.soul_rot.remains<5&remains<8)&fight_remains>dot.wither.remains+5" ); - default_->add_action( "corruption,if=!talent.wither&(!talent.absolute_corruption&remains<5|cooldown.soul_rot.remains<5&remains<8)&fight_remains>dot.corruption.remains+5" ); - default_->add_action( "drain_soul,if=buff.nightfall.react&(buff.nightfall.react=buff.nightfall.max_stack|buff.nightfall.remainsadd_action( "shadow_bolt,if=buff.nightfall.react&(buff.nightfall.react=buff.nightfall.max_stack|buff.nightfall.remains5&(!talent.vile_taint|cooldown.vile_taint.remains)" ); - default_->add_action( "call_action_list,name=se_maintenance" ); - default_->add_action( "vile_taint,if=(!talent.soul_rot|cooldown.soul_rot.remains>20|cooldown.soul_rot.remains<=execute_time+gcd.max|fight_remainsadd_action( "phantom_singularity,if=(!talent.soul_rot|cooldown.soul_rot.remains<=execute_time+gcd.max|fight_remainsadd_action( "soul_rot,if=variable.vt_ps_up" ); - default_->add_action( "summon_darkglare,if=variable.cd_dots_up&(!talent.shadow_embrace|debuff.shadow_embrace.stack=debuff.shadow_embrace.max_stack)" ); - default_->add_action( "malevolence,if=variable.vt_ps_up" ); - default_->add_action( "malefic_rapture,if=(soul_shard>4|buff.tormented_crescendo.react=buff.tormented_crescendo.max_stack)&cooldown.soul_rot.remains>5" ); - default_->add_action( "drain_soul,if=talent.demonic_soul&buff.nightfall.react&buff.tormented_crescendo.reactadd_action( "malefic_rapture,if=talent.demonic_soul&(soul_shard>2|buff.tormented_crescendo.react&cooldown.soul_rot.remains>buff.tormented_crescendo.remains*gcd.max)&(!talent.vile_taint|soul_shard>1&cooldown.vile_taint.remains>10)&(!talent.oblivion|cooldown.oblivion.remains>10|soul_shard>2&cooldown.oblivion.remains<10)" ); - default_->add_action( "oblivion,if=dot.agony.remains&(dot.corruption.remains|dot.wither.remains)&dot.unstable_affliction.remains&debuff.haunt.remains>5" ); - default_->add_action( "malefic_rapture,if=(variable.cd_dots_up|(talent.demonic_soul|talent.phantom_singularity)&variable.vt_ps_up|talent.wither&variable.vt_ps_up&!dot.soul_rot.remains&soul_shard>3)&(!talent.oblivion|cooldown.oblivion.remains>10|soul_shard>2&cooldown.oblivion.remains<10)" ); - default_->add_action( "malefic_rapture,if=talent.demonic_soul&!buff.nightfall.react&(!talent.vile_taint|cooldown.vile_taint.remains>10|soul_shard>1&cooldown.vile_taint.remains<10)" ); - default_->add_action( "malefic_rapture,if=!talent.demonic_soul&buff.tormented_crescendo.react&(buff.tormented_crescendo.remains<=cooldown.soul_rot.remains+10+execute_time)" ); - default_->add_action( "drain_soul,if=buff.nightfall.react" ); - default_->add_action( "shadow_bolt,if=talent.wither&buff.nightfall.react&buff.tormented_crescendo.reactadd_action( "agony,if=refreshable&fight_remains>dot.agony.remains+5" ); - default_->add_action( "unstable_affliction,if=refreshable&fight_remains>dot.unstable_affliction.remains+5" ); - default_->add_action( "drain_soul,chain=1,early_chain_if=buff.nightfall.react,interrupt_if=tick_time>0.5" ); + default_->add_action( "call_action_list,name=ogcd,if=pet.darkglare.active" ); + default_->add_action( "call_action_list,name=items,if=pet.darkglare.active" ); + default_->add_action( "malevolence,if=!dot.haunt.refreshable&!dot.agony.refreshable&!dot.wither.refreshable" ); + default_->add_action( "summon_darkglare,if=dot.agony.ticking&(dot.corruption.ticking|dot.wither.ticking)" ); + default_->add_action( "dark_harvest,if=talent.dark_harvest&!talent.haunt|!dot.haunt.refreshable&!dot.agony.refreshable&((talent.absolute_corruption&(talent.wither&dot.wither.ticking|!talent.wither&dot.corruption.ticking))|(!talent.absolute_corruption&(talent.wither&dot.wither.refreshable|!talent.wither&dot.corruption.refreshable)))" ); + default_->add_action( "seed_of_corruption,if=active_enemies>=2&soul_shard>1" ); + default_->add_action( "unstable_affliction,if=active_enemies<2&soul_shard>1" ); + default_->add_action( "haunt,if=dot.haunt.refreshable" ); + default_->add_action( "agony,if=dot.agony.refreshable" ); + default_->add_action( "wither,if=talent.wither&(!talent.absolute_corruption&dot.wither.refreshable|talent.absolute_corruption&!dot.wither.ticking)" ); + default_->add_action( "corruption,if=!talent.wither&(!talent.absolute_corruption&dot.corruption.refreshable|talent.absolute_corruption&!dot.corruption.ticking)" ); + default_->add_action( "malefic_grasp,if=pet.darkglare.active" ); + default_->add_action( "drain_soul,interrupt=1" ); default_->add_action( "shadow_bolt" ); - aoe->add_action( "call_action_list,name=ogcd" ); - aoe->add_action( "call_action_list,name=items" ); - aoe->add_action( "call_action_list,name=end_of_fight" ); - aoe->add_action( "cycling_variable,name=min_agony,op=min,value=dot.agony.remains+(99*!dot.agony.remains)" ); - aoe->add_action( "cycling_variable,name=min_vt,op=min,default=10,value=dot.vile_taint.remains+(99*!dot.vile_taint.remains)" ); - aoe->add_action( "cycling_variable,name=min_ps,op=min,default=16,value=dot.phantom_singularity.remains+(99*!dot.phantom_singularity.remains)" ); - aoe->add_action( "variable,name=min_ps1,op=set,value=(variable.min_vt*talent.vile_taintadd_action( "haunt,if=debuff.haunt.remains<3" ); - aoe->add_action( "agony,if=refreshable&cooldown.vile_taint.remains>remains-2&active_enemies>10" ); - aoe->add_action( "agony,target_if=(!(debuff.haunt.remains|dot.seed_of_corruption.remains)&refreshable),if=active_enemies>8&active_dot.agony<(active_enemies-8>?(talent.demonic_soul*1+!talent.demonic_soul*5))" ); - aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=5,if=!talent.demonic_soul&talent.vile_taint&active_dot.agony<6&cooldown.vile_taint.remains&remains>0&remains<10&remainsdot.agony.remains+5" ); - aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=5,if=!talent.demonic_soul&talent.phantom_singularity&active_dot.agony<6&(remains<3|cooldown.soul_rot.remains<5&remains<8)&fight_remains>dot.agony.remains+5" ); - aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=3,if=talent.demonic_soul&talent.vile_taint&active_dot.agony<4&cooldown.vile_taint.remains&remains>0&remains<10&remainsdot.agony.remains+5" ); - aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=3,if=talent.demonic_soul&talent.phantom_singularity&active_dot.agony<4&(remains<3|cooldown.soul_rot.remains<5&remains<8)&fight_remains>dot.agony.remains+5" ); - aoe->add_action( "vile_taint,if=(cooldown.soul_rot.remains<=execute_time|cooldown.soul_rot.remains>=25)" ); - aoe->add_action( "unstable_affliction,if=(remains<3|talent.demonic_soul&remainsremains+5" ); - aoe->add_action( "phantom_singularity,if=(cooldown.soul_rot.remains<=execute_time|cooldown.soul_rot.remains>=25)&dot.agony.remains" ); - aoe->add_action( "soul_rot,if=variable.vt_up&(variable.ps_up|variable.vt_up)&dot.agony.remains" ); - aoe->add_action( "seed_of_corruption,if=((dot.corruption.remains0)" ); - aoe->add_action( "summon_darkglare,if=variable.cd_dots_up|cooldown.invoke_power_infusion_0.duration>0&cooldown.invoke_power_infusion_0.up&!talent.soul_rot" ); - aoe->add_action( "malevolence,if=variable.vt_ps_up" ); - aoe->add_action( "malefic_rapture,if=soul_shard>3&cooldown.soul_rot.remains>8|soul_shard>4&cooldown.soul_rot.remains>5" ); - aoe->add_action( "malefic_rapture,if=talent.demonic_soul&(soul_shard>2|buff.tormented_crescendo.react&cooldown.soul_rot.remains>buff.tormented_crescendo.remains*gcd.max)&(!talent.vile_taint|soul_shard>1&cooldown.vile_taint.remains>10)" ); - aoe->add_action( "malefic_rapture,if=(variable.cd_dots_up|(talent.demonic_soul|talent.phantom_singularity)&variable.vt_ps_up|talent.wither&variable.vt_ps_up&!dot.soul_rot.remains&soul_shard>3)" ); - aoe->add_action( "malefic_rapture,if=talent.demonic_soul&(!talent.vile_taint|cooldown.vile_taint.remains>10|soul_shard>1&cooldown.vile_taint.remains<10)" ); - aoe->add_action( "malefic_rapture,if=!talent.demonic_soul&buff.tormented_crescendo.react&(buff.tormented_crescendo.remains<=cooldown.soul_rot.remains+10+execute_time)" ); - aoe->add_action( "malefic_rapture,if=talent.vile_taint&(variable.cd_dots_up|variable.vt_ps_up)" ); - aoe->add_action( "agony,target_if=min:remains,if=remainsadd_action( "wither,target_if=min:(remains*(remains>0)),if=!talent.absolute_corruption&refreshable&!(action.seed_of_corruption.in_flight|dot.seed_of_corruption.remains>0)" ); - aoe->add_action( "corruption,target_if=min:(remains*(remains>0)),if=!talent.absolute_corruption&refreshable&!(action.seed_of_corruption.in_flight|dot.seed_of_corruption.remains>0)" ); - aoe->add_action( "unstable_affliction,if=remainsremains+5" ); - aoe->add_action( "drain_soul,interrupt_if=cooldown.vile_taint.ready,interrupt_global=1,if=talent.drain_soul&(!talent.shadow_embrace|debuff.shadow_embrace.stack<4|debuff.shadow_embrace.remains<3)" ); - aoe->add_action( "shadow_bolt,if=(debuff.shadow_embrace.stack+action.shadow_bolt.in_flight_to_target_count)add_action( "call_action_list,name=ogcd" ); - cleave->add_action( "call_action_list,name=items" ); - cleave->add_action( "call_action_list,name=end_of_fight" ); - cleave->add_action( "agony,target_if=min:remains,if=(!talent.vile_taint|remainsdot.agony.remains+5" ); - cleave->add_action( "wither,target_if=min:remains,if=(talent.wither&!talent.absolute_corruption&remains<5|cooldown.soul_rot.remains<5&remains<8)&fight_remains>dot.wither.remains+5" ); - cleave->add_action( "corruption,target_if=min:remains,if=!talent.wither&(!talent.absolute_corruption&remains<5|cooldown.soul_rot.remains<5&remains<8)&!(action.seed_of_corruption.in_flight|dot.seed_of_corruption.remains>0)&fight_remains>dot.corruption.remains+5" ); - cleave->add_action( "haunt,if=talent.demonic_soul&buff.nightfall.react<2-prev_gcd.1.drain_soul&(!talent.vile_taint|cooldown.vile_taint.remains)|debuff.haunt.remains<3" ); - cleave->add_action( "unstable_affliction,if=(remains<5|talent.demonic_soul&remainsremains+5" ); - cleave->add_action( "call_action_list,name=se_maintenance,if=talent.wither" ); - cleave->add_action( "vile_taint,if=!talent.soul_rot|(variable.min_agony<1.5|cooldown.soul_rot.remains<=execute_time+gcd.max)|cooldown.soul_rot.remains>=20" ); - cleave->add_action( "phantom_singularity,if=(!talent.soul_rot|cooldown.soul_rot.remains<4|fight_remainsadd_action( "soul_rot,if=(variable.vt_ps_up)&active_dot.agony=2" ); - cleave->add_action( "summon_darkglare,if=variable.cd_dots_up&(!talent.shadow_embrace|debuff.shadow_embrace.stack=debuff.shadow_embrace.max_stack)" ); - cleave->add_action( "malevolence,if=variable.vt_ps_up" ); - cleave->add_action( "call_action_list,name=opener_cleave_se,if=talent.demonic_soul" ); - cleave->add_action( "call_action_list,name=cleave_se_maintenance,if=talent.demonic_soul" ); - cleave->add_action( "malefic_rapture,if=(soul_shard>4|buff.tormented_crescendo.react=buff.tormented_crescendo.max_stack)&cooldown.soul_rot.remains>5" ); - cleave->add_action( "drain_soul,if=talent.demonic_soul&buff.nightfall.react&buff.tormented_crescendo.reactadd_action( "malefic_rapture,if=talent.demonic_soul&(soul_shard>2|buff.tormented_crescendo.react&cooldown.soul_rot.remains>buff.tormented_crescendo.remains*gcd.max)&(!talent.vile_taint|soul_shard>1&cooldown.vile_taint.remains>10)" ); - cleave->add_action( "malefic_rapture,if=(variable.cd_dots_up|(talent.demonic_soul|talent.phantom_singularity)&variable.vt_ps_up|talent.wither&variable.vt_ps_up&!dot.soul_rot.remains&soul_shard>3)" ); - cleave->add_action( "malefic_rapture,if=talent.demonic_soul&!buff.nightfall.react&(!talent.vile_taint|cooldown.vile_taint.remains>10|soul_shard>1&cooldown.vile_taint.remains<10)" ); - cleave->add_action( "malefic_rapture,if=!talent.demonic_soul&buff.tormented_crescendo.react&(buff.tormented_crescendo.remains<=cooldown.soul_rot.remains+10+execute_time)" ); - cleave->add_action( "agony,if=refreshable|cooldown.soul_rot.remains<5&remains<8" ); - cleave->add_action( "wither,if=refreshable|cooldown.soul_rot.remains<5&remains<8" ); - cleave->add_action( "unstable_affliction,if=refreshable|cooldown.soul_rot.remains<5&remains<8" ); - cleave->add_action( "drain_soul,if=buff.nightfall.react" ); - cleave->add_action( "shadow_bolt,if=buff.nightfall.react" ); - cleave->add_action( "wither,if=refreshable" ); - cleave->add_action( "corruption,if=refreshable" ); - cleave->add_action( "drain_soul,chain=1,early_chain_if=buff.nightfall.react,interrupt_if=tick_time>0.5" ); - cleave->add_action( "shadow_bolt" ); - - end_of_fight->add_action( "drain_soul,if=talent.demonic_soul&active_enemies<4&(fight_remains<5&buff.nightfall.react|prev_gcd.1.haunt&buff.nightfall.react=2&!buff.tormented_crescendo.react)" ); - end_of_fight->add_action( "oblivion,if=soul_shard>1&fight_remains<(soul_shard+buff.tormented_crescendo.react)*gcd.max+execute_time" ); - end_of_fight->add_action( "malefic_rapture,if=fight_remains<4&(!talent.demonic_soul|talent.demonic_soul&buff.nightfall.react<1)" ); - - se_maintenance->add_action( "drain_soul,interrupt=1,if=talent.shadow_embrace&talent.drain_soul&(debuff.shadow_embrace.stack15,interrupt_if=debuff.shadow_embrace.stack=debuff.shadow_embrace.max_stack" ); - se_maintenance->add_action( "shadow_bolt,if=talent.shadow_embrace&!talent.drain_soul&((debuff.shadow_embrace.stack+action.shadow_bolt.in_flight_to_target_count)15" ); - - opener_cleave_se->add_action( "drain_soul,if=talent.shadow_embrace&talent.drain_soul&buff.nightfall.react&(debuff.shadow_embrace.stack15|time<20),interrupt_if=debuff.shadow_embrace.stack=debuff.shadow_embrace.max_stack" ); - - cleave_se_maintenance->add_action( "drain_soul,target_if=min:debuff.shadow_embrace.remains,if=talent.shadow_embrace&talent.drain_soul&(talent.wither|talent.demonic_soul&buff.nightfall.react)&(debuff.shadow_embrace.stack15,interrupt_if=debuff.shadow_embrace.stack>3" ); - cleave_se_maintenance->add_action( "shadow_bolt,cycle_targets=1,max_cycle_targets=2,if=talent.shadow_embrace&!talent.drain_soul&((debuff.shadow_embrace.stack+action.shadow_bolt.in_flight_to_target_count)15" ); - - items->add_action( "use_item,name=aberrant_spellforge,use_off_gcd=1,if=gcd.remains>gcd.max*0.8" ); - items->add_action( "use_item,name=spymasters_web,if=variable.cd_dots_up&(buff.spymasters_report.stack>=38|fight_remains<=80|talent.drain_soul&target.health.pct<20)|fight_remains<20" ); - items->add_action( "use_item,slot=trinket1,if=(prev_gcd.1.soul_rot)&(variable.trinket_priority=1|variable.trinket_2_exclude|!trinket.2.has_cooldown|(trinket.2.cooldown.remains|variable.trinket_priority=2&cooldown.summon_darkglare.remains>20&!pet.darkglare.active&trinket.2.cooldown.remains=fight_remains)" ); - items->add_action( "use_item,slot=trinket2,if=(prev_gcd.1.soul_rot)&(variable.trinket_priority=2|variable.trinket_1_exclude|!trinket.1.has_cooldown|(trinket.1.cooldown.remains|variable.trinket_priority=1&cooldown.summon_darkglare.remains>20&!pet.darkglare.active&trinket.1.cooldown.remains=fight_remains)" ); - items->add_action( "use_item,name=time_thiefs_gambit,if=variable.cds_active|fight_remains<15|((trinket.1.cooldown.duration1&havoc_active)" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket1,if=!variable.trinket_1_buffs&!variable.trinket_1_manual&(!variable.trinket_1_buffs&(trinket.2.cooldown.remains|!variable.trinket_2_buffs)|talent.summon_darkglare&cooldown.summon_darkglare.remains_expected>20|!talent.summon_darkglare)" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket2,if=!variable.trinket_2_buffs&!variable.trinket_2_manual&(!variable.trinket_2_buffs&(trinket.1.cooldown.remains|!variable.trinket_1_buffs)|talent.summon_darkglare&cooldown.summon_darkglare.remains_expected>20|!talent.summon_darkglare)" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=!neural_synapse_enhancer" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=neural_synapse_enhancer,if=(prev_gcd.1.soul_rot|fight_remains<=15)&!variable.trinket_1_buffs&!variable.trinket_2_buffs" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=neural_synapse_enhancer,if=(prev_gcd.1.soul_rot|fight_remains<=15|cooldown.soul_rot.remains>=45)&trinket.2.cooldown.remains&variable.trinket_2_buffs" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=neural_synapse_enhancer,if=(prev_gcd.1.soul_rot|fight_remains<=15|cooldown.soul_rot.remains>=45)&trinket.1.cooldown.remains&variable.trinket_1_buffs" ); - - ogcd->add_action( "potion,if=variable.cds_active|fight_remains<32|prev_gcd.1.soul_rot" ); - ogcd->add_action( "berserking,if=variable.cds_active|fight_remains<14|prev_gcd.1.soul_rot" ); - ogcd->add_action( "blood_fury,if=variable.cds_active|fight_remains<17|prev_gcd.1.soul_rot" ); - ogcd->add_action( "invoke_external_buff,name=power_infusion,if=variable.sr_up|variable.cds_active" ); - ogcd->add_action( "fireblood,if=variable.cds_active|fight_remains<10|prev_gcd.1.soul_rot" ); - ogcd->add_action( "ancestral_call,if=variable.cds_active|fight_remains<17|prev_gcd.1.soul_rot" ); - - variables->add_action( "variable,name=ps_up,op=set,value=!talent.phantom_singularity|dot.phantom_singularity.remains" ); - variables->add_action( "variable,name=vt_up,op=set,value=!talent.vile_taint|active_dot.vile_taint_dot" ); - variables->add_action( "variable,name=vt_ps_up,op=set,value=(!talent.vile_taint&!talent.phantom_singularity)|active_dot.vile_taint_dot|dot.phantom_singularity.remains" ); - variables->add_action( "variable,name=sr_up,op=set,value=!talent.soul_rot|dot.soul_rot.remains" ); - variables->add_action( "variable,name=cd_dots_up,op=set,value=variable.ps_up&variable.vt_up&variable.sr_up" ); - variables->add_action( "variable,name=has_cds,op=set,value=talent.phantom_singularity|talent.vile_taint|talent.soul_rot|talent.summon_darkglare" ); - variables->add_action( "variable,name=cds_active,op=set,value=variable.cd_dots_up&(!talent.summon_darkglare|pet.darkglare.remains|cooldown.summon_darkglare.remains>20)" ); - variables->add_action( "variable,name=min_vt,op=reset,if=variable.min_vt" ); - variables->add_action( "variable,name=min_ps,op=reset,if=variable.min_ps" ); + // aoe->add_action( "call_action_list,name=ogcd" ); + // aoe->add_action( "call_action_list,name=items" ); + // aoe->add_action( "call_action_list,name=end_of_fight" ); + // aoe->add_action( "cycling_variable,name=min_agony,op=min,value=dot.agony.remains+(99*!dot.agony.remains)" ); + // aoe->add_action( "haunt,if=debuff.haunt.remains<3" ); + // aoe->add_action( "agony,if=refreshable&active_enemies>10" ); + // aoe->add_action( "agony,target_if=(!(debuff.haunt.remains|dot.seed_of_corruption.remains)&refreshable),if=active_enemies>8&active_dot.agony<(active_enemies-8>?(talent.demonic_soul*1+!talent.demonic_soul*5))" ); + // aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=5,if=!talent.demonic_soul&remains>0&remains<10&fight_remains>dot.agony.remains+5" ); + // aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=5,if=!talent.demonic_soul&active_dot.agony<6&(remains<3)&fight_remains>dot.agony.remains+5" ); + // aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=3,if=talent.demonic_soul&remains>0&remains<10&fight_remains>dot.agony.remains+5" ); + // aoe->add_action( "agony,cycle_targets=1,max_cycle_targets=3,if=talent.demonic_soul&active_dot.agony<4&(remains<3)&fight_remains>dot.agony.remains+5" ); + // aoe->add_action( "unstable_affliction,if=(remains<3|talent.demonic_soul)&fight_remains>remains+5" ); + // aoe->add_action( "dark_harvest,if=talent.dark_harvest" ); + // aoe->add_action( "seed_of_corruption,if=((dot.corruption.remains0)" ); + // aoe->add_action( "summon_darkglare,if=cooldown.invoke_power_infusion_0.duration>0&cooldown.invoke_power_infusion_0.up" ); + // aoe->add_action( "malevolence" ); + // aoe->add_action( "agony,target_if=min:remains,if=remainsadd_action( "wither,target_if=min:(remains*(remains>0)),if=!talent.absolute_corruption&refreshable&!(action.seed_of_corruption.in_flight|dot.seed_of_corruption.remains>0)" ); + // aoe->add_action( "corruption,target_if=min:(remains*(remains>0)),if=!talent.absolute_corruption&refreshable&!(action.seed_of_corruption.in_flight|dot.seed_of_corruption.remains>0)" ); + // aoe->add_action( "unstable_affliction,if=remainsremains+5" ); + // aoe->add_action( "malefic_grasp,if=talent.summon_darkglare&talent.malefic_grasp" ); + // aoe->add_action( "drain_soul,if=talent.drain_soul" ); + // aoe->add_action( "shadow_bolt" ); + + // cleave->add_action( "call_action_list,name=ogcd" ); + // cleave->add_action( "call_action_list,name=items" ); + // cleave->add_action( "call_action_list,name=end_of_fight" ); + // cleave->add_action( "agony,target_if=min:remains,if=(talent.absolute_corruption&remains<3|!talent.absolute_corruption&remains<5)&fight_remains>dot.agony.remains+5" ); + // cleave->add_action( "wither,target_if=min:remains,if=(talent.wither&!talent.absolute_corruption&remains<5)&fight_remains>dot.wither.remains+5" ); + // cleave->add_action( "corruption,target_if=min:remains,if=!talent.wither&(!talent.absolute_corruption&remains<5)&!(action.seed_of_corruption.in_flight|dot.seed_of_corruption.remains>0)&fight_remains>dot.corruption.remains+5" ); + // cleave->add_action( "haunt,if=talent.demonic_soul&buff.nightfall.react<2-prev_gcd.1.drain_soul|debuff.haunt.remains<3" ); + // cleave->add_action( "unstable_affliction,if=(remains<5|talent.demonic_soul)&fight_remains>remains+5" ); + // cleave->add_action( "dark_harvest,if=talent.dark_harvest" ); + // cleave->add_action( "summon_darkglare" ); + // cleave->add_action( "malevolence" ); + // cleave->add_action( "drain_soul,if=talent.demonic_soul&buff.nightfall.react&target.health.pct<20" ); + // cleave->add_action( "agony,if=refreshable" ); + // cleave->add_action( "wither,if=refreshable" ); + // cleave->add_action( "unstable_affliction,if=refreshable" ); + // cleave->add_action( "drain_soul,if=buff.nightfall.react" ); + // cleave->add_action( "shadow_bolt,if=buff.nightfall.react" ); + // cleave->add_action( "wither,if=refreshable" ); + // cleave->add_action( "corruption,if=refreshable" ); + // cleave->add_action( "drain_soul,chain=1,early_chain_if=buff.nightfall.react,interrupt_if=tick_time>0.5" ); + // cleave->add_action( "shadow_bolt" ); + + // end_of_fight->add_action( "drain_soul,if=talent.demonic_soul&active_enemies<4&(fight_remains<5&buff.nightfall.react|prev_gcd.1.haunt&buff.nightfall.react=2)" ); + + items->add_action( "use_item,slot=trinket1" ); + items->add_action( "use_item,slot=trinket2" ); + + ogcd->add_action( "potion,if=pet.darkglare.active" ); + ogcd->add_action( "berserking,use_off_gcd=1" ); + ogcd->add_action( "blood_fury" ); + ogcd->add_action( "fireblood" ); + ogcd->add_action( "ancestral_call" ); + + // variables->add_action( "variable,name=cds_active,op=set,value=(!talent.summon_darkglare|pet.darkglare.remains|cooldown.summon_darkglare.remains>20)" ); } //affliction_apl_end @@ -223,65 +140,32 @@ void demonology( player_t* p ) precombat->add_action( "summon_pet" ); precombat->add_action( "snapshot_stats" ); - precombat->add_action( "variable,name=in_opener,op=set,value=1" ); - precombat->add_action( "variable,name=trinket_1_buffs,value=trinket.1.has_use_buff|trinket.1.is.funhouse_lens" ); - precombat->add_action( "variable,name=trinket_2_buffs,value=trinket.2.has_use_buff|trinket.2.is.funhouse_lens" ); - precombat->add_action( "variable,name=trinket_1_exclude,value=trinket.1.is.ruby_whelp_shell" ); - precombat->add_action( "variable,name=trinket_2_exclude,value=trinket.2.is.ruby_whelp_shell" ); - precombat->add_action( "variable,name=trinket_1_manual,value=trinket.1.is.spymasters_web|trinket.1.is.imperfect_ascendancy_serum" ); - precombat->add_action( "variable,name=trinket_2_manual,value=trinket.2.is.spymasters_web|trinket.2.is.imperfect_ascendancy_serum" ); - precombat->add_action( "variable,name=trinket_1_buff_duration,value=trinket.1.proc.any_dps.duration+(trinket.1.is.funhouse_lens*15)+(trinket.1.is.signet_of_the_priory*20)" ); - precombat->add_action( "variable,name=trinket_2_buff_duration,value=trinket.2.proc.any_dps.duration+(trinket.2.is.funhouse_lens*15)+(trinket.2.is.signet_of_the_priory*20)" ); - precombat->add_action( "variable,name=trinket_1_sync,op=setif,value=1,value_else=0.5,condition=variable.trinket_1_buffs&(trinket.1.cooldown.duration%%cooldown.summon_demonic_tyrant.duration=0|cooldown.summon_demonic_tyrant.duration%%trinket.1.cooldown.duration=0)" ); - precombat->add_action( "variable,name=trinket_2_sync,op=setif,value=1,value_else=0.5,condition=variable.trinket_2_buffs&(trinket.2.cooldown.duration%%cooldown.summon_demonic_tyrant.duration=0|cooldown.summon_demonic_tyrant.duration%%trinket.2.cooldown.duration=0)" ); - precombat->add_action( "variable,name=damage_trinket_priority,op=setif,value=2,value_else=1,condition=!variable.trinket_1_buffs&!variable.trinket_2_buffs&trinket.2.ilvl>trinket.1.ilvl" ); - precombat->add_action( "variable,name=trinket_priority,op=setif,value=2,value_else=1,condition=!variable.trinket_1_buffs&variable.trinket_2_buffs|variable.trinket_2_buffs&((trinket.2.cooldown.duration%variable.trinket_2_buff_duration)*(1.5+trinket.2.has_buff.intellect)*(variable.trinket_2_sync))>(((trinket.1.cooldown.duration%variable.trinket_1_buff_duration)*(1.5+trinket.1.has_buff.intellect)*(variable.trinket_1_sync))*(1+((trinket.1.ilvl-trinket.2.ilvl)%100)))" ); precombat->add_action( "power_siphon" ); precombat->add_action( "demonbolt,if=!buff.power_siphon.up" ); precombat->add_action( "shadow_bolt" ); - default_->add_action( "call_action_list,name=variables" ); default_->add_action( "potion,if=pet.demonic_tyrant.active" ); default_->add_action( "call_action_list,name=racials,if=pet.demonic_tyrant.active|fight_remains<22,use_off_gcd=1" ); default_->add_action( "call_action_list,name=items,use_off_gcd=1" ); default_->add_action( "invoke_external_buff,name=power_infusion,if=variable.imp_despawn&variable.imp_despawnadd_action( "hand_of_guldan,if=soul_shard>=3&cooldown.summon_demonic_tyrant.remains_expected<10&pet.dreadstalker.active" ); - default_->add_action( "summon_demonic_tyrant,if=(variable.imp_despawn&pet.vilefiend.active&pet.dreadstalker.active&(variable.imp_despawn=9-2*prev_gcd.1.hand_of_guldan))|(buff.grimoire_felguard.remains>cast_time&buff.grimoire_felguard.remainscast_time&((buff.dreadstalkers.remains10|buff.wild_imps.stack>=9-2*prev_gcd.1.hand_of_guldan))))" ); - default_->add_action( "grimoire_felguard,if=cooldown.summon_demonic_tyrant.remains<=15&cooldown.call_dreadstalkers.remains<10" ); - default_->add_action( "summon_vilefiend,if=cooldown.summon_demonic_tyrant.remains>=25+cast_time&(!pet.vilefiend.active&talent.the_houndmasters_gambit|!talent.the_houndmasters_gambit)|cooldown.summon_demonic_tyrant.remains<=13&cooldown.call_dreadstalkers.remains<10" ); - default_->add_action( "call_dreadstalkers,if=cooldown.summon_demonic_tyrant.remains>=10|cooldown.summon_demonic_tyrant.remains<=10" ); - default_->add_action( "call_dreadstalkers,if=buff.grimoire_felguard.up&buff.grimoire_felguard.remains<12+gcd.max+cast_time" ); - default_->add_action( "call_dreadstalkers,if=buff.vilefiend.up&buff.vilefiend.remains<12+gcd.max+cast_time" ); - default_->add_action( "call_dreadstalkers,if=cooldown.summon_demonic_tyrant.remains>cooldown+gcd.max+action.summon_demonic_tyrant.cast_time" ); - default_->add_action( "call_dreadstalkers,if=(!talent.grimoire_felguard|buff.grimoire_felguard.down&cooldown.grimoire_felguard.remains>cooldown-gcd.max-cast_time-action.summon_demonic_tyrant.cast_time)&(!talent.summon_vilefiend|buff.vilefiend.down>cooldown-gcd.max-cast_time-action.summon_demonic_tyrant.cast_time)" ); - default_->add_action( "demonbolt,target_if=min:debuff.doom.remains,if=buff.demonic_core.stack>=3-(talent.doom&debuff.doom.down)*2&soul_shard<=3&talent.doom" ); - default_->add_action( "demonic_strength,if=pet.demonic_tyrant.active" ); - default_->add_action( "bilescourge_bombers,if=active_enemies>1" ); - default_->add_action( "hand_of_guldan,if=demonic_art&soul_shard>=3" ); - default_->add_action( "implosion,if=(cooldown.summon_demonic_tyrant.remains_expected>10)&(active_enemies>3&set_bonus.tww2_4pc&buff.wild_imps.stack>7&!buff.demonic_core.react&!prev_gcd.1.implosion|!set_bonus.tww2_4pc&active_enemies>2&two_cast_imps>2&!prev_gcd.1.implosion&variable.impl)" ); + default_->add_action( "grimoire_imp_lord" ); + default_->add_action( "grimoire_fel_ravager" ); + default_->add_action( "summon_doomguard" ); + default_->add_action( "call_dreadstalkers" ); + default_->add_action( "summon_demonic_tyrant" ); + default_->add_action( "implosion,if=buff.wild_imps.stack>=6" ); default_->add_action( "ruination" ); default_->add_action( "demonbolt,target_if=(!debuff.doom.up),if=soul_shard<4&buff.demonic_core.stack>=3&talent.doom" ); default_->add_action( "demonbolt,if=soul_shard<4&buff.demonic_core.stack>=3&!talent.doom" ); default_->add_action( "power_siphon,if=!buff.demonic_core.up" ); default_->add_action( "infernal_bolt,if=soul_shard<3" ); - default_->add_action( "hand_of_guldan,if=soul_shard>=3" ); + default_->add_action( "hand_of_guldan" ); default_->add_action( "demonbolt,if=soul_shard<4&buff.demonic_core.react" ); default_->add_action( "shadow_bolt" ); default_->add_action( "infernal_bolt" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket1,if=variable.trinket_1_buffs&!variable.trinket_1_manual&(!pet.demonic_tyrant.active&trinket.1.cast_time>0|!trinket.1.cast_time>0)&(pet.demonic_tyrant.active|!talent.summon_demonic_tyrant|variable.trinket_priority=2&cooldown.summon_demonic_tyrant.remains>20&!pet.demonic_tyrant.active&trinket.2.cooldown.remains=fight_remains" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket2,if=variable.trinket_2_buffs&!variable.trinket_2_manual&(!pet.demonic_tyrant.active&trinket.2.cast_time>0|!trinket.2.cast_time>0)&(pet.demonic_tyrant.active|!talent.summon_demonic_tyrant|variable.trinket_priority=1&cooldown.summon_demonic_tyrant.remains>20&!pet.demonic_tyrant.active&trinket.1.cooldown.remains=fight_remains" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket1,if=!variable.trinket_1_buffs&!variable.trinket_1_manual&((variable.damage_trinket_priority=1|trinket.2.cooldown.remains)&(trinket.1.cast_time>0&!pet.demonic_tyrant.active|!trinket.1.cast_time>0)|(time<20&variable.trinket_2_buffs)|cooldown.summon_demonic_tyrant.remains_expected>20)" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket2,if=!variable.trinket_2_buffs&!variable.trinket_2_manual&((variable.damage_trinket_priority=2|trinket.1.cooldown.remains)&(trinket.2.cast_time>0&!pet.demonic_tyrant.active|!trinket.2.cast_time>0)|(time<20&variable.trinket_1_buffs)|cooldown.summon_demonic_tyrant.remains_expected>20)" ); - items->add_action( "use_item,use_off_gcd=1,name=spymasters_web,if=pet.demonic_tyrant.active&fight_remains<=80&buff.spymasters_report.stack>=30&(!variable.trinket_1_buffs&trinket.2.is.spymasters_web|!variable.trinket_2_buffs&trinket.1.is.spymasters_web)|fight_remains<=20&(trinket.1.cooldown.remains&trinket.2.is.spymasters_web|trinket.2.cooldown.remains&trinket.1.is.spymasters_web|!variable.trinket_1_buffs|!variable.trinket_2_buffs)" ); - items->add_action( "use_item,use_off_gcd=1,name=imperfect_ascendancy_serum,if=pet.demonic_tyrant.active|fight_remains<=30" ); - items->add_action( "use_item,name=mirror_of_fractured_tomorrows,if=trinket.1.is.mirror_of_fractured_tomorrows&variable.trinket_priority=2|trinket.2.is.mirror_of_fractured_tomorrows&variable.trinket_priority=1" ); - items->add_action( "use_item,slot=trinket1,if=!variable.trinket_1_buffs&(variable.damage_trinket_priority=1|trinket.2.cooldown.remains)" ); - items->add_action( "use_item,slot=trinket2,if=!variable.trinket_2_buffs&(variable.damage_trinket_priority=2|trinket.1.cooldown.remains)" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=!neural_synapse_enhancer" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=neural_synapse_enhancer,if=(pet.demonic_tyrant.active|fight_remains<=15)&!variable.trinket_1_buffs&!variable.trinket_2_buffs" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=neural_synapse_enhancer,if=(pet.demonic_tyrant.active|fight_remains<=15|trinket.2.cooldown.remains>cooldown.summon_demonic_tyrant.remains)&variable.trinket_2_buffs" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand,name=neural_synapse_enhancer,if=(pet.demonic_tyrant.active|fight_remains<=15|trinket.1.cooldown.remains>cooldown.summon_demonic_tyrant.remains)&variable.trinket_1_buffs" ); + items->add_action( "use_item,slot=trinket1" ); + items->add_action( "use_item,slot=trinket2" ); racials->add_action( "berserking,use_off_gcd=1" ); racials->add_action( "blood_fury" ); @@ -294,7 +178,6 @@ void demonology( player_t* p ) variables->add_action( "variable,name=imp_despawn,op=set,value=buff.dreadstalkers.remains+time,if=prev_gcd.1.hand_of_guldan&buff.dreadstalkers.up&cooldown.summon_demonic_tyrant.remains<13&variable.imp_despawn=0" ); variables->add_action( "variable,name=imp_despawn,op=set,value=(variable.imp_despawn>?buff.dreadstalkers.remains+time),if=variable.imp_despawn" ); variables->add_action( "variable,name=imp_despawn,op=set,value=variable.imp_despawn>?buff.vilefiend.remains+time,if=variable.imp_despawn&buff.vilefiend.up" ); - variables->add_action( "variable,name=imp_despawn,op=set,value=variable.imp_despawn>?buff.grimoire_felguard.remains+time,if=variable.imp_despawn&buff.grimoire_felguard.up" ); variables->add_action( "variable,name=imp_despawn,op=set,value=0,if=buff.tyrant.up" ); variables->add_action( "variable,name=impl,op=set,value=buff.tyrant.down,if=active_enemies>1+(talent.sacrificed_souls.enabled)" ); variables->add_action( "variable,name=impl,op=set,value=buff.tyrant.remains<6,if=active_enemies>2+(talent.sacrificed_souls.enabled)&active_enemies<5+(talent.sacrificed_souls.enabled)" ); @@ -313,29 +196,14 @@ void destruction( player_t* p ) { action_priority_list_t* default_ = p->get_action_priority_list( "default" ); action_priority_list_t* precombat = p->get_action_priority_list( "precombat" ); - action_priority_list_t* aoe = p->get_action_priority_list( "aoe" ); - action_priority_list_t* cleave = p->get_action_priority_list( "cleave" ); - action_priority_list_t* havoc = p->get_action_priority_list( "havoc" ); + // action_priority_list_t* aoe = p->get_action_priority_list( "aoe" ); + // action_priority_list_t* cleave = p->get_action_priority_list( "cleave" ); + // action_priority_list_t* havoc = p->get_action_priority_list( "havoc" ); action_priority_list_t* items = p->get_action_priority_list( "items" ); action_priority_list_t* ogcd = p->get_action_priority_list( "ogcd" ); action_priority_list_t* variables = p->get_action_priority_list( "variables" ); precombat->add_action( "summon_pet" ); - precombat->add_action( "variable,name=cleave_apl,default=0,op=reset" ); - precombat->add_action( "variable,name=trinket_1_buffs,value=trinket.1.has_use_buff|trinket.1.is.funhouse_lens", "Automatic Logic for Buff Trinkets in Trinket Slot 1" ); - precombat->add_action( "variable,name=trinket_2_buffs,value=trinket.2.has_use_buff|trinket.2.is.funhouse_lens", "Automatic Logic for Buff Trinkets in Trinket Slot 2" ); - precombat->add_action( "variable,name=trinket_1_sync,op=setif,value=1,value_else=0.5,condition=variable.trinket_1_buffs&(trinket.1.cooldown.duration%%cooldown.summon_infernal.duration=0|cooldown.summon_infernal.duration%%trinket.1.cooldown.duration=0)" ); - precombat->add_action( "variable,name=trinket_2_sync,op=setif,value=1,value_else=0.5,condition=variable.trinket_2_buffs&(trinket.2.cooldown.duration%%cooldown.summon_infernal.duration=0|cooldown.summon_infernal.duration%%trinket.2.cooldown.duration=0)" ); - precombat->add_action( "variable,name=trinket_1_manual,value=trinket.1.is.spymasters_web", "Sets a specific Trinkets in Slot 1 to follow an APL line and not the automatic logic" ); - precombat->add_action( "variable,name=trinket_2_manual,value=trinket.2.is.spymasters_web", "Sets a specific Trinkets in Slot 2 to follow an APL line and not the automatic logic" ); - precombat->add_action( "variable,name=trinket_1_exclude,value=trinket.1.is.whispering_incarnate_icon", "For On Use Trinkets on slot 1 with on use effects you dont want to use in combat" ); - precombat->add_action( "variable,name=trinket_2_exclude,value=trinket.2.is.whispering_incarnate_icon", "For On Use Trinkets on slot 2 with on use effects you dont want to use in combat" ); - precombat->add_action( "variable,name=trinket_1_buff_duration,value=trinket.1.proc.any_dps.duration+(trinket.1.is.funhouse_lens*15)+(trinket.1.is.signet_of_the_priory*20)", "Sets the duration of the trinket in the automatic logic" ); - precombat->add_action( "variable,name=trinket_2_buff_duration,value=trinket.2.proc.any_dps.duration+(trinket.2.is.funhouse_lens*15)+(trinket.2.is.signet_of_the_priory*20)", "Sets the duration of the trinket in the automatic logic" ); - precombat->add_action( "variable,name=trinket_priority,op=setif,value=2,value_else=1,condition=!variable.trinket_1_buffs&variable.trinket_2_buffs|variable.trinket_2_buffs&((trinket.2.cooldown.duration%variable.trinket_2_buff_duration)*(1+0.5*trinket.2.has_buff.intellect)*(variable.trinket_2_sync))>((trinket.1.cooldown.duration%variable.trinket_1_buff_duration)*(1+0.5*trinket.1.has_buff.intellect)*(variable.trinket_1_sync))", "Automatic Logic in case both Trinkets are on use buffs" ); - precombat->add_action( "variable,name=allow_rof_2t_spender,default=2,op=reset" ); - precombat->add_action( "variable,name=do_rof_2t,value=variable.allow_rof_2t_spender>1.99&!(talent.cataclysm&talent.improved_chaos_bolt),op=set" ); - precombat->add_action( "variable,name=disable_cb_2t,value=variable.do_rof_2t|variable.allow_rof_2t_spender>0.01&variable.allow_rof_2t_spender<0.99" ); precombat->add_action( "grimoire_of_sacrifice,if=talent.grimoire_of_sacrifice.enabled" ); precombat->add_action( "snapshot_stats" ); precombat->add_action( "cataclysm,if=active_enemies>=2&raid_event.adds.in>15" ); @@ -343,132 +211,119 @@ void destruction( player_t* p ) precombat->add_action( "incinerate" ); default_->add_action( "call_action_list,name=variables" ); - default_->add_action( "call_action_list,name=aoe,if=(active_enemies>=3)&!variable.cleave_apl" ); - default_->add_action( "call_action_list,name=cleave,if=active_enemies!=1|variable.cleave_apl" ); default_->add_action( "call_action_list,name=ogcd" ); default_->add_action( "call_action_list,name=items" ); - default_->add_action( "malevolence,if=cooldown.summon_infernal.remains>=55" ); - default_->add_action( "summon_infernal,if=demonic_art" ); - default_->add_action( "chaos_bolt,if=talent.diabolic_ritual&(demonic_art|((buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)<(action.chaos_bolt.execute_time)))" ); - default_->add_action( "soul_fire,if=buff.decimation.react&(soul_shard<=4|buff.decimation.remains<=gcd.max*2)&debuff.conflagrate.remains>=execute_time" ); - default_->add_action( "wither,if=talent.internal_combustion&(((dot.wither.remains-5*action.chaos_bolt.in_flight)(dot.wither.remains-5))&target.time_to_die>8&!action.soul_fire.in_flight_to_target" ); - default_->add_action( "conflagrate,if=talent.roaring_blaze&debuff.conflagrate.remains<1.5|full_recharge_time<=gcd.max*2|recharge_time<=8&(diabolic_ritual&(buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)=1.5" ); - default_->add_action( "shadowburn,if=talent.wither&((cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight&!talent.diabolic_ritual)&(talent.conflagration_of_chaos|talent.blistering_atrophy)|fight_remains<=8)" ); - default_->add_action( "shadowburn,if=(cooldown.summon_infernal.remains>=90&talent.rain_of_chaos)|buff.malevolence.up" ); - default_->add_action( "chaos_bolt,if=(cooldown.summon_infernal.remains>=90&talent.rain_of_chaos)|buff.malevolence.up" ); - default_->add_action( "ruination" ); - default_->add_action( "cataclysm,if=raid_event.adds.in>15&(talent.wither&dot.wither.refreshable)" ); - default_->add_action( "channel_demonfire,if=talent.raging_demonfire&(dot.immolate.remains+dot.wither.remains-5*(action.chaos_bolt.in_flight&talent.internal_combustion))>cast_time" ); - default_->add_action( "wither,if=!talent.internal_combustion&(((dot.wither.remains-5*(action.chaos_bolt.in_flight))dot.wither.remains)&(!talent.soul_fire|cooldown.soul_fire.remains+action.soul_fire.cast_time>(dot.wither.remains))&target.time_to_die>8&!action.soul_fire.in_flight_to_target" ); - default_->add_action( "immolate,if=(((dot.immolate.remains-5*(action.chaos_bolt.in_flight&talent.internal_combustion))(dot.immolate.remains-5*talent.internal_combustion))&target.time_to_die>8&!action.soul_fire.in_flight_to_target" ); + default_->add_action( "soul_fire,if=(active_enemies=1)" ); + default_->add_action( "cataclysm" ); + default_->add_action( "wither,if=dot.wither.refreshable" ); + default_->add_action( "immolate,if=dot.immolate.refreshable" ); + default_->add_action( "malevolence" ); default_->add_action( "summon_infernal" ); - default_->add_action( "chaos_bolt,if=(variable.pooling_condition_cb&(cooldown.summon_infernal.remains>=gcd.max*3|soul_shard>4))&(talent.wither|((buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)>(action.chaos_bolt.execute_time+2*gcd.max)))" ); - default_->add_action( "channel_demonfire" ); - default_->add_action( "dimensional_rift" ); + default_->add_action( "ruination" ); default_->add_action( "infernal_bolt,if=soul_shard<=3" ); + default_->add_action( "rain_of_fire,if=(active_enemies>=3)" ); + default_->add_action( "shadowburn,if=(active_enemies=1)" ); + default_->add_action( "chaos_bolt,if=(active_enemies=1)" ); + default_->add_action( "conflagrate,if=full_recharge_time<=gcd.max*2|recharge_time<=8" ); + default_->add_action( "channel_demonfire" ); default_->add_action( "conflagrate,if=charges>(max_charges-1)|fight_remainsadd_action( "incinerate" ); - aoe->add_action( "call_action_list,name=ogcd" ); - aoe->add_action( "call_action_list,name=items" ); - aoe->add_action( "malevolence,if=cooldown.summon_infernal.remains>=55&soul_shard<4.7&(active_enemies<=3+active_dot.wither|time>30)" ); - aoe->add_action( "chaos_bolt,if=talent.diabolic_ritual&(((buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)<(action.chaos_bolt.execute_time)&active_enemies<=9)|(demonic_art&active_enemies<=7))" ); - aoe->add_action( "call_action_list,name=havoc,if=havoc_active&havoc_remains>gcd.max&active_enemies<=4&talent.wither" ); - aoe->add_action( "dimensional_rift,if=soul_shard<4.7&(charges>2|fight_remainsadd_action( "incinerate,if=(diabolic_ritual&(buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)<=action.incinerate.cast_time)" ); - aoe->add_action( "rain_of_fire,if=(soul_shard>=(3.5-0.1*(active_dot.immolate+active_dot.wither))|buff.ritual_of_ruin.up)&(talent.wither|(talent.diabolic_ritual&active_enemies>=8))" ); - aoe->add_action( "chaos_bolt,if=soul_shard>=((3.0-0.1*(active_dot.immolate))|buff.ritual_of_ruin.up)&talent.diabolic_ritual&active_enemies<=7" ); - aoe->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains+99*!dot.wither.ticking,if=dot.wither.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.wither.remains)&(!(talent.raging_demonfire&talent.channel_demonfire)|cooldown.channel_demonfire.remains>remains|time<5)&(active_dot.wither<=4|time>15)&target.time_to_die>18" ); - aoe->add_action( "channel_demonfire,if=dot.immolate.remains+dot.wither.remains>cast_time&talent.raging_demonfire" ); - aoe->add_action( "shadowburn,if=talent.wither&(buff.malevolence.up|active_enemies<=6|(fight_remains>60&active_enemies<=14))&((cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight)|fight_remains<=8)" ); - aoe->add_action( "shadowburn,target_if=min:time_to_die,if=talent.wither&(buff.malevolence.up|active_enemies<=6|(fight_remains>60&active_enemies<=14))&((cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight)|fight_remains<=8)" ); - aoe->add_action( "ruination" ); - aoe->add_action( "rain_of_fire,if=pet.infernal.active&talent.rain_of_chaos&(talent.wither|(talent.diabolic_ritual&active_enemies>=8))" ); - aoe->add_action( "chaos_bolt,if=pet.infernal.active&talent.rain_of_chaos&talent.diabolic_ritual&active_enemies<=7" ); - aoe->add_action( "soul_fire,target_if=min:dot.wither.remains+dot.immolate.remains-5*debuff.conflagrate.up+100*debuff.havoc.remains,if=(buff.decimation.up)&!talent.raging_demonfire&havoc_active" ); - aoe->add_action( "soul_fire,target_if=min:(dot.wither.remains+dot.immolate.remains-5*debuff.conflagrate.up+100*debuff.havoc.remains),if=buff.decimation.up&active_dot.immolate<=4" ); - aoe->add_action( "infernal_bolt,if=soul_shard<2.5" ); - aoe->add_action( "chaos_bolt,if=((soul_shard>3.0-(0.1*active_enemies))&!action.rain_of_fire.enabled)" ); - aoe->add_action( "cataclysm,if=raid_event.adds.in>15" ); - aoe->add_action( "havoc,target_if=min:((-target.time_to_die)8&(cooldown.malevolence.remains>15|!talent.malevolence)|time<5" ); - aoe->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains,if=dot.wither.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.wither.remains)&(!(talent.raging_demonfire&talent.channel_demonfire)|cooldown.channel_demonfire.remains>remains|time<5)&active_dot.wither<=active_enemies&target.time_to_die>18" ); - aoe->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=dot.immolate.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.immolate.remains)&(!(talent.raging_demonfire&talent.channel_demonfire)|cooldown.channel_demonfire.remains>remains|time<5)&active_dot.immolate<=6&target.time_to_die>18" ); - aoe->add_action( "call_action_list,name=ogcd" ); - aoe->add_action( "summon_infernal,if=cooldown.invoke_power_infusion_0.up|cooldown.invoke_power_infusion_0.duration=0|fight_remains>=120" ); - aoe->add_action( "rain_of_fire,if=debuff.pyrogenics.down&active_enemies<=4&!talent.diabolic_ritual" ); - aoe->add_action( "channel_demonfire,if=dot.immolate.remains+dot.wither.remains>cast_time" ); - aoe->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=((dot.immolate.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.immolate.remains))|active_enemies>active_dot.immolate)&target.time_to_die>10&!havoc_active" ); - aoe->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=((dot.immolate.refreshable&variable.havoc_immo_time<5.4)|(dot.immolate.remains<2&dot.immolate.remainsdot.immolate.remains)&target.time_to_die>11" ); - aoe->add_action( "dimensional_rift" ); - aoe->add_action( "soul_fire,target_if=min:(dot.wither.remains+dot.immolate.remains-5*debuff.conflagrate.up+100*debuff.havoc.remains),if=buff.decimation.up" ); - aoe->add_action( "incinerate,if=talent.fire_and_brimstone.enabled&buff.backdraft.up" ); - aoe->add_action( "conflagrate,if=buff.backdraft.stack<2|!talent.backdraft" ); - aoe->add_action( "incinerate" ); - - cleave->add_action( "call_action_list,name=items" ); - cleave->add_action( "call_action_list,name=ogcd" ); - cleave->add_action( "call_action_list,name=havoc,if=havoc_active&havoc_remains>gcd.max" ); - cleave->add_action( "variable,name=pool_soul_shards,value=cooldown.havoc.remains<=5|talent.mayhem" ); - cleave->add_action( "malevolence,if=(!cooldown.summon_infernal.up|!talent.summon_infernal)" ); - cleave->add_action( "havoc,target_if=min:((-target.time_to_die)8" ); - cleave->add_action( "chaos_bolt,if=demonic_art" ); - cleave->add_action( "soul_fire,if=buff.decimation.react&(soul_shard<=4|buff.decimation.remains<=gcd.max*2)&debuff.conflagrate.remains>=execute_time&cooldown.havoc.remains" ); - cleave->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains,if=talent.internal_combustion&(((dot.wither.remains-5*action.chaos_bolt.in_flight)(dot.wither.remains-5))&target.time_to_die>8&!action.soul_fire.in_flight_to_target" ); - cleave->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains,if=!talent.internal_combustion&(((dot.wither.remains-5*(action.chaos_bolt.in_flight))(dot.wither.remains))&target.time_to_die>8&!action.soul_fire.in_flight_to_target" ); - cleave->add_action( "conflagrate,if=(talent.roaring_blaze.enabled&full_recharge_time<=gcd.max*2)|recharge_time<=8&(diabolic_ritual&(buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)add_action( "shadowburn,if=(cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight&!talent.diabolic_ritual)&(talent.conflagration_of_chaos|talent.blistering_atrophy)|fight_remains<=8" ); - cleave->add_action( "shadowburn,if=cooldown.summon_infernal.remains>=90&talent.rain_of_chaos&!talent.diabolic_ritual" ); - cleave->add_action( "chaos_bolt,if=cooldown.summon_infernal.remains>=90&talent.rain_of_chaos" ); - cleave->add_action( "ruination,if=(debuff.eradication.remains>=execute_time|!talent.eradication|!talent.shadowburn)" ); - cleave->add_action( "cataclysm,if=raid_event.adds.in>15" ); - cleave->add_action( "channel_demonfire,if=talent.raging_demonfire&(dot.immolate.remains+dot.wither.remains-5*(action.chaos_bolt.in_flight&talent.internal_combustion))>cast_time" ); - cleave->add_action( "soul_fire,if=soul_shard<=3.5&(debuff.conflagrate.remains>cast_time+travel_time|!talent.roaring_blaze&buff.backdraft.up)&!variable.pool_soul_shards" ); - cleave->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=(dot.immolate.refreshable&(dot.immolate.remainsremains)&(!talent.soul_fire|cooldown.soul_fire.remains+(!talent.mayhem*action.soul_fire.cast_time)>dot.immolate.remains)&target.time_to_die>15" ); - cleave->add_action( "summon_infernal" ); - cleave->add_action( "incinerate,if=talent.diabolic_ritual&(diabolic_ritual&(buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains-2-!variable.disable_cb_2t*action.chaos_bolt.cast_time-variable.disable_cb_2t*gcd.max)<=0)" ); - cleave->add_action( "soul_fire,if=soul_shard<=4&talent.mayhem" ); - cleave->add_action( "chaos_bolt,if=(cooldown.summon_infernal.remains>=gcd.max*3|soul_shard>4|!talent.rain_of_chaos)" ); - cleave->add_action( "channel_demonfire" ); - cleave->add_action( "dimensional_rift" ); - cleave->add_action( "infernal_bolt" ); - cleave->add_action( "conflagrate,if=charges>(max_charges-1)|fight_remainsadd_action( "incinerate" ); - - havoc->add_action( "conflagrate,if=talent.backdraft&buff.backdraft.down&soul_shard>=1&soul_shard<=4" ); - havoc->add_action( "soul_fire,if=cast_timeadd_action( "cataclysm,if=raid_event.adds.in>15|(talent.wither&dot.wither.remainsadd_action( "immolate,target_if=min:dot.immolate.remains+100*debuff.havoc.remains,if=(((dot.immolate.refreshable&variable.havoc_immo_time<5.4)&target.time_to_die>5)|((dot.immolate.remains<2&dot.immolate.remains11)&soul_shard<4.5" ); - havoc->add_action( "wither,target_if=min:dot.wither.remains+100*debuff.havoc.remains,if=(((dot.wither.refreshable&variable.havoc_immo_time<5.4)&target.time_to_die>5)|((dot.wither.remains<2&dot.wither.remains11)&soul_shard<4.5" ); - havoc->add_action( "shadowburn,if=(cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight)&(talent.conflagration_of_chaos|talent.blistering_atrophy)" ); - havoc->add_action( "shadowburn,if=havoc_remains<=gcd.max*3" ); - havoc->add_action( "chaos_bolt,if=cast_timeadd_action( "rain_of_fire,if=active_enemies>=3" ); - havoc->add_action( "channel_demonfire,if=soul_shard<4.5" ); - havoc->add_action( "conflagrate,if=!talent.backdraft" ); - havoc->add_action( "dimensional_rift,if=soul_shard<4.7&(charges>2|fight_remainsadd_action( "incinerate,if=cast_timeadd_action( "use_item,name=spymasters_web,if=pet.infernal.remains>=10&pet.infernal.remains<=20&buff.spymasters_report.stack>=38&(fight_remains>240|fight_remains<=140)|fight_remains<=30" ); - items->add_action( "use_item,slot=trinket1,if=(variable.infernal_active|!talent.summon_infernal|variable.trinket_1_will_lose_cast)&(variable.trinket_priority=1|variable.trinket_2_exclude|!trinket.2.has_cooldown|(trinket.2.cooldown.remains|variable.trinket_priority=2&cooldown.summon_infernal.remains>20&!variable.infernal_active&trinket.2.cooldown.remains=fight_remains)" ); - items->add_action( "use_item,slot=trinket2,if=(variable.infernal_active|!talent.summon_infernal|variable.trinket_2_will_lose_cast)&(variable.trinket_priority=2|variable.trinket_1_exclude|!trinket.1.has_cooldown|(trinket.1.cooldown.remains|variable.trinket_priority=1&cooldown.summon_infernal.remains>20&!variable.infernal_active&trinket.1.cooldown.remains=fight_remains)" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket1,if=!variable.trinket_1_buffs&!variable.trinket_1_manual&(!variable.trinket_1_buffs&(trinket.2.cooldown.remains|!variable.trinket_2_buffs)|talent.summon_infernal&cooldown.summon_infernal.remains_expected>20&!prev_gcd.1.summon_infernal|!talent.summon_infernal)" ); - items->add_action( "use_item,use_off_gcd=1,slot=trinket2,if=!variable.trinket_2_buffs&!variable.trinket_2_manual&(!variable.trinket_2_buffs&(trinket.1.cooldown.remains|!variable.trinket_1_buffs)|talent.summon_infernal&cooldown.summon_infernal.remains_expected>20&!prev_gcd.1.summon_infernal|!talent.summon_infernal)" ); - items->add_action( "use_item,use_off_gcd=1,slot=main_hand" ); + // aoe->add_action( "call_action_list,name=ogcd" ); + // aoe->add_action( "call_action_list,name=items" ); + // aoe->add_action( "malevolence,if=cooldown.summon_infernal.remains>=55&soul_shard<4.7&(active_enemies<=3+active_dot.wither|time>30)" ); + // aoe->add_action( "chaos_bolt,if=talent.diabolic_ritual&(((buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)<(action.chaos_bolt.execute_time)&active_enemies<=9)|(demonic_art&active_enemies<=7))" ); + // aoe->add_action( "call_action_list,name=havoc,if=havoc_active&havoc_remains>gcd.max&active_enemies<=4&talent.wither" ); + // aoe->add_action( "dimensional_rift,if=soul_shard<4.7&(charges>2|fight_remainsadd_action( "incinerate,if=(diabolic_ritual&(buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)<=action.incinerate.cast_time)" ); + // aoe->add_action( "rain_of_fire,if=(soul_shard>=(3.5-0.1*(active_dot.immolate+active_dot.wither))|buff.ritual_of_ruin.up)&(talent.wither|(talent.diabolic_ritual&active_enemies>=8))" ); + // aoe->add_action( "chaos_bolt,if=soul_shard>=((3.0-0.1*(active_dot.immolate))|buff.ritual_of_ruin.up)&talent.diabolic_ritual&active_enemies<=7" ); + // aoe->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains+99*!dot.wither.ticking,if=dot.wither.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.wither.remains)&(!(talent.raging_demonfire&talent.channel_demonfire)|cooldown.channel_demonfire.remains>remains|time<5)&(active_dot.wither<=4|time>15)&target.time_to_die>18" ); + // aoe->add_action( "channel_demonfire,if=dot.immolate.remains+dot.wither.remains>cast_time&talent.raging_demonfire" ); + // aoe->add_action( "shadowburn,if=talent.wither&(buff.malevolence.up|active_enemies<=6|(fight_remains>60&active_enemies<=14))&((cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight)|fight_remains<=8)" ); + // aoe->add_action( "shadowburn,target_if=min:time_to_die,if=talent.wither&(buff.malevolence.up|active_enemies<=6|(fight_remains>60&active_enemies<=14))&((cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight)|fight_remains<=8)" ); + // aoe->add_action( "ruination" ); + // aoe->add_action( "rain_of_fire,if=pet.infernal.active&talent.rain_of_chaos&(talent.wither|(talent.diabolic_ritual&active_enemies>=8))" ); + // aoe->add_action( "chaos_bolt,if=pet.infernal.active&talent.rain_of_chaos&talent.diabolic_ritual&active_enemies<=7" ); + // aoe->add_action( "soul_fire,target_if=min:dot.wither.remains+dot.immolate.remains-5*debuff.conflagrate.up+100*debuff.havoc.remains,if=(buff.decimation.up)&!talent.raging_demonfire&havoc_active" ); + // aoe->add_action( "soul_fire,target_if=min:(dot.wither.remains+dot.immolate.remains-5*debuff.conflagrate.up+100*debuff.havoc.remains),if=buff.decimation.up&active_dot.immolate<=4" ); + // aoe->add_action( "infernal_bolt,if=soul_shard<2.5" ); + // aoe->add_action( "chaos_bolt,if=((soul_shard>3.0-(0.1*active_enemies))&!action.rain_of_fire.enabled)" ); + // aoe->add_action( "cataclysm,if=raid_event.adds.in>15" ); + // aoe->add_action( "havoc,target_if=min:((-target.time_to_die)8&(cooldown.malevolence.remains>15|!talent.malevolence)|time<5" ); + // aoe->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains,if=dot.wither.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.wither.remains)&(!(talent.raging_demonfire&talent.channel_demonfire)|cooldown.channel_demonfire.remains>remains|time<5)&active_dot.wither<=active_enemies&target.time_to_die>18" ); + // aoe->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=dot.immolate.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.immolate.remains)&(!(talent.raging_demonfire&talent.channel_demonfire)|cooldown.channel_demonfire.remains>remains|time<5)&active_dot.immolate<=6&target.time_to_die>18" ); + // aoe->add_action( "call_action_list,name=ogcd" ); + // aoe->add_action( "summon_infernal,if=cooldown.invoke_power_infusion_0.up|cooldown.invoke_power_infusion_0.duration=0|fight_remains>=120" ); + // aoe->add_action( "rain_of_fire,if=debuff.pyrogenics.down&active_enemies<=4&!talent.diabolic_ritual" ); + // aoe->add_action( "channel_demonfire,if=dot.immolate.remains+dot.wither.remains>cast_time" ); + // aoe->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=((dot.immolate.refreshable&(!talent.cataclysm.enabled|cooldown.cataclysm.remains>dot.immolate.remains))|active_enemies>active_dot.immolate)&target.time_to_die>10&!havoc_active" ); + // aoe->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=((dot.immolate.refreshable&variable.havoc_immo_time<5.4)|(dot.immolate.remains<2&dot.immolate.remainsdot.immolate.remains)&target.time_to_die>11" ); + // aoe->add_action( "dimensional_rift" ); + // aoe->add_action( "soul_fire,target_if=min:(dot.wither.remains+dot.immolate.remains-5*debuff.conflagrate.up+100*debuff.havoc.remains),if=buff.decimation.up" ); + // aoe->add_action( "incinerate,if=talent.fire_and_brimstone.enabled&buff.backdraft.up" ); + // aoe->add_action( "conflagrate,if=buff.backdraft.stack<2|!talent.backdraft" ); + // aoe->add_action( "incinerate" ); + + // cleave->add_action( "call_action_list,name=items" ); + // cleave->add_action( "call_action_list,name=ogcd" ); + // cleave->add_action( "call_action_list,name=havoc,if=havoc_active&havoc_remains>gcd.max" ); + // cleave->add_action( "variable,name=pool_soul_shards,value=cooldown.havoc.remains<=5|talent.mayhem" ); + // cleave->add_action( "malevolence,if=(!cooldown.summon_infernal.up|!talent.summon_infernal)" ); + // cleave->add_action( "havoc,target_if=min:((-target.time_to_die)8" ); + // cleave->add_action( "chaos_bolt,if=demonic_art" ); + // cleave->add_action( "soul_fire,if=buff.decimation.react&(soul_shard<=4|buff.decimation.remains<=gcd.max*2)&debuff.conflagrate.remains>=execute_time&cooldown.havoc.remains" ); + // cleave->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains,if=talent.internal_combustion&(((dot.wither.remains-5*action.chaos_bolt.in_flight)(dot.wither.remains-5))&target.time_to_die>8&!action.soul_fire.in_flight_to_target" ); + // cleave->add_action( "wither,target_if=min:dot.wither.remains+99*debuff.havoc.remains,if=!talent.internal_combustion&(((dot.wither.remains-5*(action.chaos_bolt.in_flight))(dot.wither.remains))&target.time_to_die>8&!action.soul_fire.in_flight_to_target" ); + // cleave->add_action( "conflagrate,if=(talent.roaring_blaze.enabled&full_recharge_time<=gcd.max*2)|recharge_time<=8&(diabolic_ritual&(buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains)add_action( "shadowburn,if=(cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight&!talent.diabolic_ritual)&(talent.conflagration_of_chaos|talent.blistering_atrophy)|fight_remains<=8" ); + // cleave->add_action( "shadowburn,if=cooldown.summon_infernal.remains>=90&talent.rain_of_chaos&!talent.diabolic_ritual" ); + // cleave->add_action( "chaos_bolt,if=cooldown.summon_infernal.remains>=90&talent.rain_of_chaos" ); + // cleave->add_action( "ruination,if=(debuff.eradication.remains>=execute_time|!talent.eradication|!talent.shadowburn)" ); + // cleave->add_action( "cataclysm,if=raid_event.adds.in>15" ); + // cleave->add_action( "channel_demonfire,if=talent.raging_demonfire&(dot.immolate.remains+dot.wither.remains-5*(action.chaos_bolt.in_flight&talent.internal_combustion))>cast_time" ); + // cleave->add_action( "soul_fire,if=soul_shard<=3.5&(debuff.conflagrate.remains>cast_time+travel_time|!talent.roaring_blaze&buff.backdraft.up)&!variable.pool_soul_shards" ); + // cleave->add_action( "immolate,target_if=min:dot.immolate.remains+99*debuff.havoc.remains,if=(dot.immolate.refreshable&(dot.immolate.remainsremains)&(!talent.soul_fire|cooldown.soul_fire.remains+(!talent.mayhem*action.soul_fire.cast_time)>dot.immolate.remains)&target.time_to_die>15" ); + // cleave->add_action( "summon_infernal" ); + // cleave->add_action( "incinerate,if=talent.diabolic_ritual&(diabolic_ritual&(buff.diabolic_ritual_mother_of_chaos.remains+buff.diabolic_ritual_overlord.remains+buff.diabolic_ritual_pit_lord.remains-2-!variable.disable_cb_2t*action.chaos_bolt.cast_time-variable.disable_cb_2t*gcd.max)<=0)" ); + // cleave->add_action( "soul_fire,if=soul_shard<=4&talent.mayhem" ); + // cleave->add_action( "chaos_bolt,if=(cooldown.summon_infernal.remains>=gcd.max*3|soul_shard>4|!talent.rain_of_chaos)" ); + // cleave->add_action( "channel_demonfire" ); + // cleave->add_action( "dimensional_rift" ); + // cleave->add_action( "infernal_bolt" ); + // cleave->add_action( "conflagrate,if=charges>(max_charges-1)|fight_remainsadd_action( "incinerate" ); + + // havoc->add_action( "conflagrate,if=talent.backdraft&buff.backdraft.down&soul_shard>=1&soul_shard<=4" ); + // havoc->add_action( "soul_fire,if=cast_timeadd_action( "cataclysm,if=raid_event.adds.in>15|(talent.wither&dot.wither.remainsadd_action( "immolate,target_if=min:dot.immolate.remains+100*debuff.havoc.remains,if=(((dot.immolate.refreshable&variable.havoc_immo_time<5.4)&target.time_to_die>5)|((dot.immolate.remains<2&dot.immolate.remains11)&soul_shard<4.5" ); + // havoc->add_action( "wither,target_if=min:dot.wither.remains+100*debuff.havoc.remains,if=(((dot.wither.refreshable&variable.havoc_immo_time<5.4)&target.time_to_die>5)|((dot.wither.remains<2&dot.wither.remains11)&soul_shard<4.5" ); + // havoc->add_action( "shadowburn,if=(cooldown.shadowburn.full_recharge_time<=gcd.max*3|debuff.eradication.remains<=gcd.max&talent.eradication&!action.chaos_bolt.in_flight)&(talent.conflagration_of_chaos|talent.blistering_atrophy)" ); + // havoc->add_action( "shadowburn,if=havoc_remains<=gcd.max*3" ); + // havoc->add_action( "chaos_bolt,if=cast_timeadd_action( "rain_of_fire,if=active_enemies>=3" ); + // havoc->add_action( "channel_demonfire,if=soul_shard<4.5" ); + // havoc->add_action( "conflagrate,if=!talent.backdraft" ); + // havoc->add_action( "dimensional_rift,if=soul_shard<4.7&(charges>2|fight_remainsadd_action( "incinerate,if=cast_timeadd_action( "use_item,slot=trinket1" ); + items->add_action( "use_item,slot=trinket2" ); ogcd->add_action( "potion,if=variable.infernal_active|!talent.summon_infernal" ); - ogcd->add_action( "invoke_external_buff,name=power_infusion,if=variable.infernal_active|!talent.summon_infernal|(fight_remainscooldown.invoke_power_infusion_0.duration)|fight_remainsadd_action( "berserking,if=variable.infernal_active|!talent.summon_infernal|(fight_remains<(cooldown.summon_infernal.remains_expected+cooldown.berserking.duration)&(fight_remains>cooldown.berserking.duration))|fight_remainsadd_action( "blood_fury,if=variable.infernal_active|!talent.summon_infernal|(fight_remainscooldown.blood_fury.duration)|fight_remainsadd_action( "fireblood,if=variable.infernal_active|!talent.summon_infernal|(fight_remainscooldown.fireblood.duration)|fight_remainsadd_action( "ancestral_call,if=variable.infernal_active|!talent.summon_infernal|(fight_remains<(cooldown.summon_infernal.remains_expected+cooldown.berserking.duration)&(fight_remains>cooldown.berserking.duration))|fight_remainsadd_action( "variable,name=havoc_immo_time,op=reset" ); - variables->add_action( "variable,name=pooling_condition,value=(soul_shard>=3|(talent.secrets_of_the_coven&buff.infernal_bolt.up|buff.decimation.up)&soul_shard>=3),default=1,op=set" ); - variables->add_action( "variable,name=pooling_condition_cb,value=variable.pooling_condition|pet.infernal.active&soul_shard>=3,default=1,op=set" ); - variables->add_action( "cycling_variable,name=havoc_immo_time,op=add,value=dot.immolate.remains*debuff.havoc.upadd_action( "berserking,use_off_gcd=1" ); + ogcd->add_action( "blood_fury" ); + ogcd->add_action( "fireblood" ); + ogcd->add_action( "ancestral_call" ); + + // variables->add_action( "variable,name=havoc_immo_time,op=reset" ); + // variables->add_action( "variable,name=pooling_condition,value=(soul_shard>=3|(talent.secrets_of_the_coven&buff.infernal_bolt.up|buff.decimation.up)&soul_shard>=3),default=1,op=set" ); + // variables->add_action( "variable,name=pooling_condition_cb,value=variable.pooling_condition|pet.infernal.active&soul_shard>=3,default=1,op=set" ); + // variables->add_action( "cycling_variable,name=havoc_immo_time,op=add,value=dot.immolate.remains*debuff.havoc.upadd_action( "variable,name=infernal_active,op=set,value=pet.infernal.active|(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains)<20" ); - variables->add_action( "variable,name=trinket_1_will_lose_cast,value=((floor((fight_remains%trinket.1.cooldown.duration)+1)!=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(floor((fight_remains%trinket.1.cooldown.duration)+1))!=(floor(((fight_remains-cooldown.summon_infernal.remains)%trinket.1.cooldown.duration)+1))|((floor((fight_remains%trinket.1.cooldown.duration)+1)=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(((fight_remains-cooldown.summon_infernal.remains%%trinket.1.cooldown.duration)-cooldown.summon_infernal.remains-variable.trinket_1_buff_duration)>0)))&cooldown.summon_infernal.remains>20" ); - variables->add_action( "variable,name=trinket_2_will_lose_cast,value=((floor((fight_remains%trinket.2.cooldown.duration)+1)!=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(floor((fight_remains%trinket.2.cooldown.duration)+1))!=(floor(((fight_remains-cooldown.summon_infernal.remains)%trinket.2.cooldown.duration)+1))|((floor((fight_remains%trinket.2.cooldown.duration)+1)=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(((fight_remains-cooldown.summon_infernal.remains%%trinket.2.cooldown.duration)-cooldown.summon_infernal.remains-variable.trinket_2_buff_duration)>0)))&cooldown.summon_infernal.remains>20" ); + // variables->add_action( "variable,name=trinket_1_will_lose_cast,value=((floor((fight_remains%trinket.1.cooldown.duration)+1)!=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(floor((fight_remains%trinket.1.cooldown.duration)+1))!=(floor(((fight_remains-cooldown.summon_infernal.remains)%trinket.1.cooldown.duration)+1))|((floor((fight_remains%trinket.1.cooldown.duration)+1)=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(((fight_remains-cooldown.summon_infernal.remains%%trinket.1.cooldown.duration)-cooldown.summon_infernal.remains-variable.trinket_1_buff_duration)>0)))&cooldown.summon_infernal.remains>20" ); + // variables->add_action( "variable,name=trinket_2_will_lose_cast,value=((floor((fight_remains%trinket.2.cooldown.duration)+1)!=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(floor((fight_remains%trinket.2.cooldown.duration)+1))!=(floor(((fight_remains-cooldown.summon_infernal.remains)%trinket.2.cooldown.duration)+1))|((floor((fight_remains%trinket.2.cooldown.duration)+1)=floor((fight_remains+(cooldown.summon_infernal.duration-cooldown.summon_infernal.remains))%cooldown.summon_infernal.duration))&(((fight_remains-cooldown.summon_infernal.remains%%trinket.2.cooldown.duration)-cooldown.summon_infernal.remains-variable.trinket_2_buff_duration)>0)))&cooldown.summon_infernal.remains>20" ); } //destruction_apl_end diff --git a/engine/class_modules/warlock/sc_warlock.cpp b/engine/class_modules/warlock/sc_warlock.cpp index 5523d5efcac..6a4a16682ee 100644 --- a/engine/class_modules/warlock/sc_warlock.cpp +++ b/engine/class_modules/warlock/sc_warlock.cpp @@ -21,59 +21,33 @@ warlock_td_t::warlock_td_t( player_t* target, warlock_t& p ) dots.corruption = target->get_dot( "corruption", &p ); dots.agony = target->get_dot( "agony", &p ); dots.drain_soul = target->get_dot( "drain_soul", &p ); - dots.phantom_singularity = target->get_dot( "phantom_singularity", &p ); dots.seed_of_corruption = target->get_dot( "seed_of_corruption", &p ); dots.unstable_affliction = target->get_dot( "unstable_affliction", &p ); - dots.jackpot_ua = target->get_dot( "unstable_affliction_jackpot", &p ); - dots.vile_taint = target->get_dot( "vile_taint_dot", &p ); - dots.soul_rot = target->get_dot( "soul_rot", &p ); + dots.malefic_grasp = target->get_dot( "malefic_grasp", &p ); debuffs.haunt = make_buff( *this, "haunt", p.talents.haunt ) ->set_refresh_behavior( buff_refresh_behavior::PANDEMIC ) ->set_default_value_from_effect( 2 ) ->set_cooldown( 0_ms ); - debuffs.shadow_embrace = make_buff( *this, "shadow_embrace", p.talents.drain_soul.ok() ? p.talents.shadow_embrace_debuff_ds : p.talents.shadow_embrace_debuff_sb ) - ->set_default_value_from_effect( 1 ); - - debuffs.infirmity = make_buff( *this, "infirmity", p.talents.infirmity_debuff ) - ->add_invalidate( CACHE_PLAYER_DAMAGE_MULTIPLIER ); - // Demonology - debuffs.wicked_maw = make_buff( *this, "wicked_maw", p.talents.wicked_maw_debuff ) - ->set_default_value_from_effect( 1 ); - - debuffs.fel_sunder = make_buff( *this, "fel_sunder", p.talents.fel_sunder_debuff ) - ->set_default_value( p.talents.fel_sunder->effectN( 1 ).percent() ); - debuffs.doom = make_buff( *this, "doom", p.talents.doom_debuff ) ->set_stack_change_callback( [ &p ]( buff_t* b, int, int cur ) { if ( cur == 0 ) { p.proc_actions.doom_proc->execute_on_target( b->player ); - - 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(); - } } } ); // Destruction dots.immolate = target->get_dot( "immolate", &p ); - debuffs.eradication = make_buff( *this, "eradication", p.talents.eradication_debuff ); + debuffs.lake_of_fire = make_buff( *this, "lake_of_fire", p.talents.lake_of_fire_debuff ) + ->set_default_value_from_effect( 1 ); debuffs.shadowburn = make_buff( *this, "shadowburn", p.talents.shadowburn ) ->set_default_value( p.talents.shadowburn_2->effectN( 1 ).base_value() / 10 ); - debuffs.pyrogenics = make_buff( *this, "pyrogenics", p.talents.pyrogenics_debuff ) - ->add_invalidate( CACHE_PLAYER_DAMAGE_MULTIPLIER ); - - debuffs.conflagrate = make_buff( *this, "conflagrate", p.talents.conflagrate_debuff ) - ->set_default_value_from_effect( 1 ); - // Use havoc_debuff where we need the data but don't have the active talent debuffs.havoc = make_buff( *this, "havoc", p.talents.havoc_debuff ) ->set_duration( p.talents.mayhem.ok() ? p.talents.mayhem->effectN( 3 ).time_value() : p.talents.havoc->duration() ) @@ -108,24 +82,7 @@ warlock_td_t::warlock_td_t( player_t* target, warlock_t& p ) warlock.proc_actions.blackened_soul->execute_on_target( target ); } ) ->set_tick_behavior( buff_tick_behavior::REFRESH ) - ->set_freeze_stacks( true ) - ->set_tick_time_behavior( buff_tick_time_behavior::CUSTOM ) - ->set_tick_time_callback( [ & ]( const buff_t* b, unsigned int ) { - timespan_t period = b->buff_period; - - if ( p.buffs.maintained_withering->check() ) - { - // TOCHECK: 2025-08-16 Currently Hellcaller TWW3 B4 (Maintained Withering) tier bonus - // of doing Blackened Soul damage faster is bugged for destruction and does not work - if ( !p.bugs || !p.destruction() ) - { - period *= 1.0 + p.buffs.maintained_withering->data() - .effectN( p.affliction() ? 2 : 3 ) - .percent(); - } - } - return period; - } ); + ->set_freeze_stacks( true ); // Soul Harvester dots.soul_anathema = target->get_dot( "soul_anathema", &p ); @@ -147,13 +104,6 @@ void warlock_td_t::target_demise() warlock.resource_gain( RESOURCE_SOUL_SHARD, warlock.talents.unstable_affliction_2->effectN( 1 ).base_value(), warlock.gains.unstable_affliction_refund ); } - if ( dots.jackpot_ua->is_ticking() ) - { - warlock.sim->print_log( "Player {} demised. Warlock {} gains a shard from Unstable Affliction.", target->name(), warlock.name() ); - - warlock.resource_gain( RESOURCE_SOUL_SHARD, warlock.talents.unstable_affliction_2->effectN( 1 ).base_value(), warlock.gains.unstable_affliction_refund ); - } - if ( dots.drain_soul->is_ticking() ) { warlock.sim->print_log( "Player {} demised. Warlock {} gains a shard from Drain Soul.", target->name(), warlock.name() ); @@ -170,10 +120,6 @@ void warlock_td_t::target_demise() if ( debuffs.shadowburn->check() ) { - warlock.sim->print_log( "Player {} demised. Warlock {} refunds one charge of Shadowburn.", target->name(), warlock.name() ); - - warlock.cooldowns.shadowburn->reset( true ); - warlock.sim->print_log( "Player {} demised. Warlock {} gains 1 shard from Shadowburn.", target->name(), warlock.name() ); warlock.resource_gain( RESOURCE_SOUL_SHARD, debuffs.shadowburn->check_value(), warlock.gains.shadowburn_refund ); @@ -217,33 +163,14 @@ int warlock_td_t::count_affliction_dots() const if ( dots.seed_of_corruption->is_ticking() ) count++; + // NOTE: UA counts as 1 dot does not matter how many stacks if ( dots.unstable_affliction->is_ticking() ) count++; - if ( dots.vile_taint->is_ticking() ) - count++; - - if ( dots.phantom_singularity->is_ticking() ) - count++; - - if ( dots.soul_rot->is_ticking() ) - count++; - if ( dots.wither->is_ticking() ) count++; - return count; -} - -int warlock_td_t::count_affliction_dots( bool include_tier_ua ) const -{ - int count = count_affliction_dots(); - - if ( !include_tier_ua ) - return count; - - if ( dots.jackpot_ua->is_ticking() ) - count++; + // TODO: Check if Malefic Grasp and Drain Soul count return count; } @@ -252,7 +179,6 @@ int warlock_td_t::count_affliction_dots( bool include_tier_ua ) const warlock_t::warlock_t( sim_t* sim, util::string_view name, race_e r ) : parse_player_effects_t( sim, WARLOCK, name, r ), havoc_target( nullptr ), - ua_target( nullptr ), havoc_spells(), agony_accumulator( 0.0 ), corruption_accumulator( 0.0 ), @@ -272,12 +198,12 @@ warlock_t::warlock_t( sim_t* sim, util::string_view name, race_e r ) initial_soul_shards(), default_pet(), disable_auto_felstorm( false ), - normalize_destruction_mastery( false ), - demonic_inspiration_double_dip( false ) + normalize_destruction_mastery( false ) { cooldowns.haunt = get_cooldown( "haunt" ); - cooldowns.shadowburn = get_cooldown( "shadowburn" ); + cooldowns.dark_harvest = get_cooldown( "dark_harvest" ); cooldowns.soul_fire = get_cooldown( "soul_fire" ); + cooldowns.summon_doomguard = get_cooldown( "summon_doomguard" ); cooldowns.felstorm_icd = get_cooldown( "felstorm_icd" ); cooldowns.blackened_soul = get_cooldown( "blackened_soul_icd" ); cooldowns.seeds_of_their_demise = get_cooldown( "seeds_of_their_demise_icd" ); @@ -287,7 +213,7 @@ warlock_t::warlock_t( sim_t* sim, util::string_view name, race_e r ) regen_caches[ CACHE_SPELL_HASTE ] = true; sim->register_heartbeat_event_callback( [ this ]( sim_t* ) { - for ( auto& pet : active_pets ) + for ( auto pet : active_pets ) { auto lock_pet = dynamic_cast( pet ); @@ -327,20 +253,6 @@ bool warlock_t::hellcaller() const bool warlock_t::soul_harvester() const { return has_hero_tree( HERO_SOUL_HARVESTER ); } -double warlock_t::composite_player_target_pet_damage_multiplier( player_t* target, bool guardian ) const -{ - double m = parse_player_effects_t::composite_player_target_pet_damage_multiplier( target, guardian ); - - if ( demonology() ) - { - // TOCHECK: Fel Sunder lacks guardian effect, so only player and main pet is benefitting (bug?). Last checked 2025-09-23 - if ( !bugs && guardian && talents.fel_sunder.ok() ) - m *= 1.0 + get_target_data( target )->debuffs.fel_sunder->check_stack_value(); - } - - return m; -} - static void accumulate_seed_of_corruption( warlock_td_t* td, double amount ) { td->soc_threshold -= amount; @@ -448,41 +360,21 @@ int warlock_t::active_demon_count() const { int count = 0; - for ( auto& pet : this->pet_list ) + for ( auto pet : this->pet_list ) { auto lock_pet = dynamic_cast( pet ); - + if ( lock_pet == nullptr ) continue; if ( lock_pet->is_sleeping() ) continue; - + count++; } return count; } -void warlock_t::expendables_trigger_helper( warlock_pet_t* source ) -{ - for ( auto& pet : this->pet_list ) - { - auto lock_pet = dynamic_cast( pet ); - - if ( lock_pet == nullptr ) - continue; - if ( lock_pet->is_sleeping() ) - continue; - - if ( lock_pet == source ) - continue; - - // 2025-03-28: The Expendables talent does not apply to Greater Dreadstalkers (maybe a bug?) - if ( ( lock_pet->pet_type != PET_FELHUNTER || !lock_pet->bugs ) && lock_pet->pet_type != PET_WARLOCK_RANDOM ) - lock_pet->buffs.the_expendables->trigger(); - } -} - std::pair warlock_t::dreadstalkers_delay_duration_adjustment_helper( const player_t& target ) { std::pair ret; @@ -490,7 +382,7 @@ std::pair warlock_t::dreadstalkers_delay_duration_adjust timespan_t& dur_adjust = ret.second; const double dist = get_player_distance( target ); // The summon is considered at melee range if the distance is less than or equal to 5 yards - if (dist > 5.0) + if ( dist > 5.0 ) { // Set a randomized offset on first melee attacks after travel time. Make sure it's the same value for each dog so they're synced delay = rng().range( 0_s, 1_s ); @@ -512,26 +404,6 @@ std::pair warlock_t::dreadstalkers_delay_duration_adjust return ret; } -// Use this as a helper function when two versions are needed simultaneously (ie a PTR cycle) -// It must be adjusted manually over time, and any use of it should be removed once a patch goes live -// Returns TRUE if actor's dbc version >= version specified -// When checking VERSION_PTR, will only return true if PTR dbc is being used, regardless of version number -bool warlock_t::min_version_check( version_check_e version ) const -{ - //If we ever get a full DBC version string checker, replace these returns with that function - switch ( version ) - { - case VERSION_PTR: - return is_ptr(); - case VERSION_11_1_0: - return !( version_11_1_0_data == spell_data_t::not_found() ); - case VERSION_ANY: - return true; - } - - return false; -} - static std::string append_rng_option( warlock_t::rng_settings_t::rng_setting_t setting ) { std::string str = ""; @@ -556,18 +428,14 @@ std::string warlock_t::create_profile( save_e stype ) 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 ( demonic_inspiration_double_dip ) - profile_str += "demonic_inspiration_double_dip=" + util::to_string( demonic_inspiration_double_dip ) + "\n"; profile_str += append_rng_option( rng_settings.cunning_cruelty_sb ); profile_str += append_rng_option( rng_settings.cunning_cruelty_ds ); profile_str += append_rng_option( rng_settings.agony ); profile_str += append_rng_option( rng_settings.nightfall ); profile_str += append_rng_option( rng_settings.pact_of_the_eredruin ); - profile_str += append_rng_option( rng_settings.shadow_invocation ); + profile_str += append_rng_option( rng_settings.avatar_of_destruction_dr ); profile_str += append_rng_option( rng_settings.spiteful_reconstitution ); - profile_str += append_rng_option( rng_settings.decimation ); - profile_str += append_rng_option( rng_settings.dimension_ripper ); profile_str += append_rng_option( rng_settings.blackened_soul ); profile_str += append_rng_option( rng_settings.bleakheart_tactics ); profile_str += append_rng_option( rng_settings.seeds_of_their_demise ); @@ -576,8 +444,7 @@ std::string warlock_t::create_profile( save_e stype ) profile_str += append_rng_option( rng_settings.succulent_soul_demo ); profile_str += append_rng_option( rng_settings.feast_of_souls_aff ); profile_str += append_rng_option( rng_settings.feast_of_souls_demo ); - profile_str += append_rng_option( rng_settings.umbral_lattice ); - profile_str += append_rng_option( rng_settings.empowered_legion_strike ); + profile_str += append_rng_option( rng_settings.manifested_avarice ); } return profile_str; @@ -593,17 +460,14 @@ void warlock_t::copy_from( player_t* source ) default_pet = p->default_pet; disable_auto_felstorm = p->disable_auto_felstorm; normalize_destruction_mastery = p->normalize_destruction_mastery; - demonic_inspiration_double_dip = p->demonic_inspiration_double_dip; 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.avatar_of_destruction_dr = p->rng_settings.avatar_of_destruction_dr; 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; @@ -612,8 +476,7 @@ void warlock_t::copy_from( player_t* source ) rng_settings.succulent_soul_demo = p->rng_settings.succulent_soul_demo; rng_settings.feast_of_souls_aff = p->rng_settings.feast_of_souls_aff; rng_settings.feast_of_souls_demo = p->rng_settings.feast_of_souls_demo; - rng_settings.umbral_lattice = p->rng_settings.umbral_lattice; - rng_settings.empowered_legion_strike = p->rng_settings.empowered_legion_strike; + rng_settings.manifested_avarice = p->rng_settings.manifested_avarice; } stat_e warlock_t::convert_hybrid_stat( stat_e s ) const @@ -637,21 +500,6 @@ stat_e warlock_t::convert_hybrid_stat( stat_e s ) const } } -bool warlock_t::validate_actor() -{ - // TODO: Remove this when Midnight is properly supported - if ( sim->dbc->wowv() < wowv_t( 13, 0, 0 ) ) - { - std::string patch = "Midnight prepatch"; - if ( sim->dbc->wowv() > wowv_t( 12, 0, 0 ) ) - patch = "Midnight"; - throw sc_unsupported_specialization( fmt::format( "Warlock sims are non functional for {}", patch ) ); - return false; - } - - return true; -} - pet_t* warlock_t::create_main_pet( util::string_view pet_name, util::string_view /* pet_type */ ) { pet_t* p = find_pet( pet_name ); @@ -667,7 +515,7 @@ pet_t* warlock_t::create_main_pet( util::string_view pet_name, util::string_view return new pets::base::sayaad_pet_t( this, pet_name ); if ( pet_name == "voidwalker" ) return new pets::base::voidwalker_pet_t( this, pet_name ); - if ( pet_name == "felguard" && demonology() ) + if ( pet_name == "felguard" && demonology() && talents.summon_felguard.ok() ) return new pets::demonology::felguard_pet_t( this, pet_name ); return nullptr; @@ -732,7 +580,7 @@ std::unique_ptr warlock_t::create_expression( util::string_view name_str double active_agonies = get_active_dots( agony ); if ( sim->debug ) sim->out_debug.printf( "active agonies: %f", active_agonies ); - + if ( active_agonies == 0 || !agony->current_action ) { return std::numeric_limits::infinity(); @@ -803,7 +651,7 @@ std::unique_ptr warlock_t::create_expression( util::string_view name_str return make_fn_expr( name_str, [this, soc_list] { std::vector no_dots; - if ( soc_list.empty() ) + if ( soc_list.empty() ) return false; //All the actions should have the same target list, so do this once only @@ -838,7 +686,7 @@ std::unique_ptr warlock_t::create_expression( util::string_view name_str } return false; - }); + } ); } else if ( name_str == "diabolic_ritual" ) { @@ -871,14 +719,14 @@ std::unique_ptr warlock_t::create_expression( util::string_view name_str /* ---------------------------------------------------------- * NOTE NOTE NOTE -* Applies DYNAMIC (Buffs, Debuffs, DoTs, or anything else that could change state during combat) +* Applies DYNAMIC (Buffs, Debuffs, DoTs, or anything else that could change state during combat) * effects that effect the player as a whole, IE: a % Crit Chance buff, all pet/guardian damage modifiers, and the likes * NOTE NOTE NOTE * * This system can also handle passive effects, but increases sim initialization time! -* +* * General Useage is parse_effects( buff, modifying_spell_1, modifying_spell_2, modifying_spell_3 ); -* +* * USEAGE EXAMPLES * * ----------------- * Baseline effect with no affecting talents, or spells @@ -888,7 +736,7 @@ std::unique_ptr warlock_t::create_expression( util::string_view name_str * Buff that is modified by a talent (Buff with a talent that modifies an effect to have a value) * -- * parse_effects( buff.rolling_havoc, talents.rolling_havoc ); -* -- +* -- ****** This system CAN NOT handle buffs that modify other buffs and/or debuffs. ****** * Debuff * -- @@ -898,7 +746,7 @@ std::unique_ptr warlock_t::create_expression( util::string_view name_str * -- * parse_target_effects( d_fn( &warlock_td_t::dots_t::unstable_affliction ), talents.unstable_affliction ); * -- -* More advanced examples can be found in other modules that use this system. +* More advanced examples can be found in other modules that use this system. * A few are sc_druid.cpp, sc_death_knight.cpp, and sc_demon_hunter.cpp ------------------------------------------------------------- */ void warlock_t::parse_player_effects() @@ -914,26 +762,20 @@ void warlock_t::parse_player_effects() { // Affliction Debuffs/DoTs parse_target_effects( d_fn( &warlock_td_t::debuffs_t::haunt ), talents.haunt ); // 48181 - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::shadow_embrace ), talents.drain_soul.ok() ? talents.shadow_embrace_debuff_ds : talents.shadow_embrace_debuff_sb ); // 32390 / 453206 - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::infirmity ), talents.infirmity_debuff ); // 458219 } // Demonology if ( demonology() ) { // Demonology Debuffs/DoTs - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::fel_sunder ), talents.fel_sunder_debuff ); // 387402 } // Destruction if ( destruction() ) { // Destruction Buffs - parse_effects( buffs.rolling_havoc ); // 387570 // Destruction Debuffs/DoTs - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::eradication ), talents.eradication_debuff ); // 196414 - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::pyrogenics ), talents.pyrogenics_debuff ); // 387096 } // Diabolist @@ -952,7 +794,10 @@ void warlock_t::parse_player_effects() } // Soul Harvester - + if ( soul_harvester() ) + { + parse_effects( buffs.manifested_demonic_soul ); // 1269042 // TODO: Need to check if this parse_effects works in doubling the mastery effect when the buff is active? + } } double warlock_t::resource_gain( resource_e resource_type, double amount, gain_t* source, action_t* action ) @@ -996,6 +841,67 @@ void warlock_t::feast_of_souls_gain() procs.feast_of_souls->occur(); } +std::vector warlock_t::get_smart_targets( const std::vector& _tl, propagate_const warlock_td_t::dots_t::* dot, int n_targets, player_t* exclude, double dis, bool really_smart ) +{ + if ( n_targets < 1 || !_tl.size() ) + return {}; + + auto tl = _tl; // make a copy + + if ( exclude ) + { + if ( dis && sim->distance_targeting_enabled ) + { + // remove out of range + range::erase_remove( tl, [ exclude, dis ]( player_t* t ) { + return t == exclude || t->get_player_distance( *exclude ) > dis; + } ); + } + else + { + range::erase_remove( tl, exclude ); + } + } + + if ( tl.size() > 1 ) + { + // randomize remaining targets + rng().shuffle( tl.begin(), tl.end() ); + + if ( really_smart ) + { + // sort by time remaining + range::sort( tl, [ this, &dot ]( player_t* a, player_t* b ) { + return std::invoke( dot, get_target_data( a )->dots )->remains() < + std::invoke( dot, get_target_data( b )->dots )->remains(); + } ); + } + else + { + // prioritize undotted over dotted + std::partition( tl.begin(), tl.end(), [ this, &dot ]( player_t* t ) { + return !std::invoke( dot, get_target_data( t )->dots )->is_ticking(); + } ); + } + } + + // slice to n_targets + if ( as( tl.size() ) > n_targets ) + tl.resize( n_targets ); + + return tl; +} + +player_t* warlock_t::get_smart_target( const std::vector& _tl, propagate_const warlock_td_t::dots_t::* dot, player_t* exclude, double dis, bool really_smart ) +{ + std::vector players = get_smart_targets( _tl, dot, 1, exclude, dis, really_smart ); + + if ( players.size() ) + return players[ 0 ]; + + return nullptr; +} + /* Report Extension Class * Here you can define class specific report extensions/overrides */ @@ -1005,7 +911,7 @@ class warlock_report_t : public player_report_extension_t warlock_report_t( warlock_t& player ) : p( player ) {} - void html_customsection( report::sc_html_stream& os ) override + void html_customsection( report::sc_html_stream& ) override {} private: @@ -1047,10 +953,10 @@ warlock::warlock_t::pets_t::pets_t( warlock_t* w ) dreadstalkers( "dreadstalker", w ), vilefiends( "vilefiend", w ), demonic_tyrants( "demonic_tyrant", w ), - grimoire_felguards( "grimoire_felguard", w ), + grimoire_imp_lords( "demonic_imp_lord", w ), + grimoire_fel_ravagers( "demonic_fel_ravager", w ), wild_imps( "wild_imp", w ), doomguards( "Doomguard", w ), - greater_dreadstalkers( "greater_dreadstalker", w ), shadow_rifts( "shadowy_tear", w ), unstable_rifts( "unstable_tear", w ), chaos_rifts( "chaos_tear", w ), diff --git a/engine/class_modules/warlock/sc_warlock.hpp b/engine/class_modules/warlock/sc_warlock.hpp index da6f5e47a62..7a8ded8f9da 100644 --- a/engine/class_modules/warlock/sc_warlock.hpp +++ b/engine/class_modules/warlock/sc_warlock.hpp @@ -10,14 +10,6 @@ namespace warlock { struct warlock_t; -// Used for version checking in code (e.g. PTR vs Live) -enum version_check_e -{ - VERSION_PTR, - VERSION_11_1_0, - VERSION_ANY -}; - // Finds an action with the given name. If no action exists, a new one will // be created. // @@ -38,19 +30,14 @@ struct warlock_td_t : public actor_target_data_t struct debuffs_t { propagate_const haunt; - propagate_const shadow_embrace; - propagate_const infirmity; // Demo - propagate_const wicked_maw; - propagate_const fel_sunder; // Done in owner target data for easier handling propagate_const doom; + // Destruction + propagate_const lake_of_fire; propagate_const shadowburn; - propagate_const eradication; propagate_const havoc; - propagate_const pyrogenics; - propagate_const conflagrate; // Diabolist propagate_const cloven_soul; @@ -72,11 +59,8 @@ struct warlock_td_t : public actor_target_data_t propagate_const agony; propagate_const seed_of_corruption; propagate_const drain_soul; - propagate_const phantom_singularity; propagate_const unstable_affliction; - propagate_const vile_taint; - propagate_const soul_rot; - propagate_const jackpot_ua; // TWW 11.1 4pc version of Unstable Affliction + propagate_const malefic_grasp; // Hellcaller propagate_const wither; @@ -97,7 +81,6 @@ struct warlock_td_t : public actor_target_data_t void target_demise(); int count_affliction_dots() const; - int count_affliction_dots( bool ) const; }; // utility to create target_effect_t compatible functions from warlock_td_t member references @@ -137,7 +120,6 @@ struct warlock_t : public parse_player_effects_t { public: player_t* havoc_target; - player_t* ua_target; // Used for handling Unstable Affliction target swaps std::vector havoc_spells; // Used for smarter target cache invalidation. double agony_accumulator; double corruption_accumulator; @@ -151,36 +133,29 @@ struct warlock_t : public parse_player_effects_t struct base_t { // Shared + const spell_data_t* nethermancy; // Int bonus for all cloth slots const spell_data_t* drain_life; const spell_data_t* corruption; const spell_data_t* shadow_bolt; - const spell_data_t* nethermancy; // Int bonus for all cloth slots // Affliction - const spell_data_t* agony; - const spell_data_t* agony_2; // Rank 2 still a separate spell (learned automatically). Grants increased max stacks - const spell_data_t* malefic_rapture; // This contains an old sp_coeff value, but it is most likely no longer in use - const spell_data_t* malefic_rapture_dmg; - const spell_data_t* potent_afflictions; // Affliction Mastery - Increased DoT and Malefic Rapture damage const spell_data_t* affliction_warlock; // Spec aura + const spell_data_t* potent_afflictions; // Affliction Mastery - Increased DoT and Malefic Rapture damage // Demonology - const spell_data_t* hand_of_guldan; - const spell_data_t* hog_impact; // Secondary spell responsible for impact damage + const spell_data_t* demonology_warlock; // Spec aura + const spell_data_t* master_demonologist; // Demonology Mastery - Increased demon damage const spell_data_t* wild_imp; // Data for pet summoning const spell_data_t* fel_firebolt_2; // Still a separate spell (learned automatically). Reduces pet's energy cost - const spell_data_t* master_demonologist; // Demonology Mastery - Increased demon damage - const spell_data_t* demonology_warlock; // Spec aura // Destruction + const spell_data_t* destruction_warlock; // Spec aura + const spell_data_t* chaotic_energies; // Destruction Mastery - Increased spell damage with random range const spell_data_t* immolate; // Replaces Corruption const spell_data_t* immolate_old; // For some reason, the spellbook spell is now a new spell, but it points to this old one const spell_data_t* immolate_dot; // Primary spell data only contains information on direct damage const spell_data_t* incinerate; // Replaces Shadow Bolt const spell_data_t* incinerate_energize; // Soul Shard data is in a separate spell - const spell_data_t* chaos_bolt; - const spell_data_t* chaotic_energies; // Destruction Mastery - Increased spell damage with random range - const spell_data_t* destruction_warlock; // Spec aura } warlock_base; // Main pet held in active, guardians should be handled by pet spawners. @@ -195,10 +170,10 @@ struct warlock_t : public parse_player_effects_t spawner::pet_spawner_t dreadstalkers; spawner::pet_spawner_t vilefiends; spawner::pet_spawner_t demonic_tyrants; - spawner::pet_spawner_t grimoire_felguards; + spawner::pet_spawner_t grimoire_imp_lords; + spawner::pet_spawner_t grimoire_fel_ravagers; spawner::pet_spawner_t wild_imps; spawner::pet_spawner_t doomguards; - spawner::pet_spawner_t greater_dreadstalkers; spawner::pet_spawner_t shadow_rifts; spawner::pet_spawner_t unstable_rifts; @@ -226,229 +201,201 @@ struct warlock_t : public parse_player_effects_t { // Class Tree - player_talent_t demonic_inspiration; // Primary pet attack speed increase - const spell_data_t* demonic_inspiration_buff; // This hidden buff is applied to pets/guardians in the first heartbeat update after arise player_talent_t demonic_embrace; player_talent_t demonic_fortitude; - player_talent_t wrathful_minion; // Primary pet damage increase - player_talent_t socrethars_guile; - player_talent_t sargerei_technique; - player_talent_t demonic_tactics; - player_talent_t soul_conduit; + player_talent_t pact_of_the_annihilan; + player_talent_t pact_of_the_satyr; + player_talent_t pact_of_the_eredar; player_talent_t soulburn; const spell_data_t* soulburn_buff; // This buff is applied after using Soulburn and prevents another usage unless cleared // Specializations // Shared + player_talent_t summoners_embrace; player_talent_t grimoire_of_sacrifice; // Aff/Destro only const spell_data_t* grimoire_of_sacrifice_buff; // 1 hour duration, enables proc functionality, canceled if pet summoned const spell_data_t* grimoire_of_sacrifice_proc; // Damage data is here, but RPPM of proc trigger is in buff data - player_talent_t summoners_embrace; // Affliction + const spell_data_t* agony; player_talent_t unstable_affliction; const spell_data_t* unstable_affliction_2; // Soul Shard on demise (learned automatically) - const spell_data_t* unstable_affliction_3; // +5 seconds to duration (learned automatically) - - player_talent_t writhe_in_agony; + // const spell_data_t* unstable_affliction_3; // +5 seconds to duration (learned automatically) player_talent_t seed_of_corruption; const spell_data_t* seed_of_corruption_aoe; // Explosion damage when Seed ticks - - player_talent_t dark_virtuosity; // Note: Spell data contains multiplier on Drain Soul direct damage as well, which doesn't exist - player_talent_t absolute_corruption; - player_talent_t siphon_life; - player_talent_t kindled_malice; + const spell_data_t* seed_of_corruption_is_out_dnt; player_talent_t nightfall; const spell_data_t* nightfall_buff; - player_talent_t volatile_agony; - const spell_data_t* volatile_agony_aoe; + const spell_data_t* nightfall_buff_2; + player_talent_t haunt; + player_talent_t shared_agony; player_talent_t improved_shadow_bolt; player_talent_t drain_soul; // This represents the talent node but not much else const spell_data_t* drain_soul_dot; // Contains all channel data - // Summoner's Embrace (shared with Destruction) - // Grimoire of Sacrifice (shared with Destruction) - player_talent_t phantom_singularity; - const spell_data_t* phantom_singularity_tick; // Actual AoE spell information in here - player_talent_t vile_taint; // Base talent, AoE cast data - const spell_data_t* vile_taint_dot; // DoT data + player_talent_t improved_haunt; + player_talent_t absolute_corruption; + player_talent_t siphon_life; - player_talent_t haunt; - player_talent_t shadow_embrace; - const spell_data_t* shadow_embrace_debuff_ds; // Drain Soul applies a debuff with 4 stacks, and a 2% base value - const spell_data_t* shadow_embrace_debuff_sb; // Shadow Bolt applies a debuff with 2 stacks, and a 4% base value - player_talent_t sacrolashs_dark_strike; // Increased Corruption ticking damage, and ticks extend Curses (not implemented) + player_talent_t cunning_cruelty; // Note: Damage formula in the tooltip indicates this is affected by Imp. Shadow Bolt and Sargerei Technique + const spell_data_t* shadowbolt_volley; // Proc chance is not listed on spell data. Appears to be 50% regardless of talent. Last checked 2024-07-07 + player_talent_t withering_bolt; // Increased damage on Shadow Bolt/Drain Soul based on active DoT count on target + player_talent_t creeping_death; + player_talent_t dark_harvest; // Buffs from hitting targets with Soul Rot + const spell_data_t* dark_harvest_dmg; + + player_talent_t practiced_pestilence; player_talent_t summon_darkglare; const spell_data_t* eye_beam; // Darkglare pet ability - player_talent_t cunning_cruelty; // Note: Damage formula in the tooltip indicates this is affected by Imp. Shadow Bolt and Sargerei Technique - const spell_data_t* shadow_bolt_volley; // Proc chance is not listed on spell data. Appears to be 50% regardless of talent. Last checked 2024-07-07 - player_talent_t infirmity; // TOCHECK: Update to Beta on 2024-07-30 changed this to an AoE application, with some weird results (currently implemented) - const spell_data_t* infirmity_debuff; // Guardian effect is missing from spell data. Last checked 2024-07-07 + // Summoner's Embrace (shared with Destruction) + // Grimoire of Sacrifice (shared with Destruction) + player_talent_t cull_the_weak; - player_talent_t improved_haunt; player_talent_t malediction; - player_talent_t malevolent_visionary; - const spell_data_t* malevolent_visionary_blast; // Damage proc sourced from player when summoning Darkglare + player_talent_t sudden_onset; + player_talent_t eye_contract; + player_talent_t malefic_grasp; + const spell_data_t* malefic_grasp_2; + const spell_data_t* malefic_grasp_3; + player_talent_t nether_plating; + player_talent_t sacrolashs_dark_strike; // Increased Corruption ticking damage, and ticks extend Curses (not implemented) player_talent_t contagion; - player_talent_t cull_the_weak; - player_talent_t creeping_death; - player_talent_t soul_rot; - player_talent_t tormented_crescendo; // Free, instant Malefic Rapture procs from Shadow Bolt/Drain Soul - const spell_data_t* tormented_crescendo_buff; + player_talent_t shard_instability; + const spell_data_t* shard_instability_buff; + player_talent_t niskaran_methods; + player_talent_t potent_soul_shards; + player_talent_t nocturnal_yield; player_talent_t xavius_gambit; // Unstable Affliction Damage Multiplier - player_talent_t focused_malignancy; // Increaed Malefic Rapture damage to target with Unstable Affliction - player_talent_t perpetual_unstability; - const spell_data_t* perpetual_unstability_proc; - player_talent_t malign_omen; - const spell_data_t* malign_omen_buff; - player_talent_t relinquished; - player_talent_t withering_bolt; // Increased damage on Shadow Bolt/Drain Soul based on active DoT count on target - player_talent_t improved_malefic_rapture; + player_talent_t ravenous_afflictions; + player_talent_t seeds_of_destruction; - player_talent_t oblivion; + player_talent_t fatal_echoes; + player_talent_t cascading_calamity; + const spell_data_t* cascading_calamity_buff; player_talent_t deaths_embrace; // Volatile Agony and Perpetual Unstability are unaffected by this - player_talent_t dark_harvest; // Buffs from hitting targets with Soul Rot - const spell_data_t* dark_harvest_buff; - player_talent_t ravenous_afflictions; - player_talent_t malefic_touch; - const spell_data_t* malefic_touch_proc; + player_talent_t patient_zero; + player_talent_t sow_the_seeds; + + player_talent_t shadow_of_nathreza_1; + player_talent_t shadow_of_nathreza_2; + player_talent_t shadow_of_nathreza_3; // Demonology + player_talent_t hand_of_guldan; + const spell_data_t* hand_of_guldan_cast; + const spell_data_t* hog_impact; // Secondary spell responsible for impact damage + player_talent_t demoniac; const spell_data_t* demonbolt_spell; const spell_data_t* demonic_core_spell; const spell_data_t* demonic_core_buff; - - player_talent_t implosion; - const spell_data_t* implosion_aoe; // Note: in combat logs this is attributed to the player, not the imploding pet player_talent_t call_dreadstalkers; const spell_data_t* call_dreadstalkers_2; // Contains duration data - player_talent_t imp_gang_boss; - const spell_data_t* imp_gang_boss_buff; // Buff on Wild Imps - player_talent_t spiteful_reconstitution; // Increased Implosion damage and consuming Demonic Core may spawn a Wild Imp + player_talent_t dominant_hand; + player_talent_t fel_intellect; + player_talent_t practiced_rituals; player_talent_t dreadlash; - player_talent_t carnivorous_stalkers; // Chance for Dreadstalkers to perform additional Dreadbites - - player_talent_t inner_demons; - player_talent_t soul_strike; - const spell_data_t* soul_strike_pet; - const spell_data_t* soul_strike_dmg; - player_talent_t bilescourge_bombers; - const spell_data_t* bilescourge_bombers_aoe; // Ground AoE data - player_talent_t demonic_strength; - player_talent_t sacrificed_souls; - player_talent_t rune_of_shadows; player_talent_t imperator; // Increased critical strike chance for Wild Imps' Fel Firebolt (additive) - player_talent_t fel_invocation; - player_talent_t annihilan_training; // Permanent aura on Felguard that gives 10% damage buff - const spell_data_t* annihilan_training_buff; // Applied to pet, not player - player_talent_t shadow_invocation; // Bilescourge Bomber damage and proc - player_talent_t wicked_maw; - const spell_data_t* wicked_maw_debuff; - + player_talent_t implosion; + const spell_data_t* implosion_aoe; // Note: in combat logs this is attributed to the player, not the imploding pet player_talent_t power_siphon; const spell_data_t* power_siphon_buff; // Semi-hidden aura that controls the bonus Demonbolt damage + player_talent_t summon_felguard; + + player_talent_t infernal_rapidity; + player_talent_t rune_of_shadows; + player_talent_t carnivorous_stalkers; // Chance for Dreadstalkers to perform additional Dreadbites + player_talent_t fel_armaments; + + player_talent_t imp_gang_boss; + const spell_data_t* imp_gang_boss_buff; // Buff on Wild Imps + player_talent_t demonic_brutality; + player_talent_t inner_demons; player_talent_t summon_demonic_tyrant; const spell_data_t* demonic_power_buff; - player_talent_t grimoire_felguard; - const spell_data_t* grimoire_of_service; // Buff on Grimoire: Felguard - - player_talent_t the_expendables; // Per-pet stacking buff to damage when a Wild Imp expires - const spell_data_t* the_expendables_buff; - player_talent_t blood_invocation; - player_talent_t umbral_blaze; - const spell_data_t* umbral_blaze_dot; // Rolling Periodic DoT with DOT_ROLLING default refresh behavior + player_talent_t blighted_maw; + const spell_data_t* blighted_maw_dmg; + player_talent_t improved_demonic_tactics; + player_talent_t empowered_felstorm; + + player_talent_t spiteful_reconstitution; // Increased Implosion damage and consuming Demonic Core may spawn a Wild Imp + player_talent_t tyrants_oblation; + const spell_data_t* tyrants_oblation_buff; + player_talent_t antoran_armaments; // Increased Felguard damage and Soul Strike cleave + player_talent_t flametouched; + const spell_data_t* ferocity_of_fharg_buff; + + player_talent_t demonic_knowledge; + player_talent_t sacrificed_souls; + player_talent_t reign_of_tyranny; + player_talent_t master_summoner; player_talent_t demonic_calling; - const spell_data_t* demonic_calling_buff; - player_talent_t fiendish_oblation; - player_talent_t fel_sunder; // Increase damage taken debuff when hit by main pet Felstorm - const spell_data_t* fel_sunder_debuff; // Fel Sunder lacks guardian effect, so only main pet is benefitting (bug?). Last checked 2024-07-14 player_talent_t doom; const spell_data_t* doom_debuff; const spell_data_t* doom_dmg; - player_talent_t pact_of_the_imp_mother; // Chance for Hand of Gul'dan to proc a second time on execute + player_talent_t hellbent_commander; + const spell_data_t* hellbent_commander_buff; + player_talent_t grimoire_imp_lord; + player_talent_t grimoire_fel_ravager; player_talent_t summon_vilefiend; + const spell_data_t* vilefiend; const spell_data_t* bile_spit; const spell_data_t* headbutt; - player_talent_t dread_calling; // Stacking buff to next Dreadstalkers damage - const spell_data_t* dread_calling_buff; // This buffs stacks on the warlock - const spell_data_t* dread_calling_pet; - player_talent_t antoran_armaments; // Increased Felguard damage and Soul Strike cleave - const spell_data_t* antoran_armaments_buff; - const spell_data_t* soul_cleave; - - player_talent_t doom_eternal; - player_talent_t impending_doom; - player_talent_t foul_mouth; - player_talent_t the_houndmasters_gambit; - const spell_data_t* houndmasters_aura; // Contains actual referenced % increase - player_talent_t improved_demonic_tactics; - player_talent_t demonic_brutality; - player_talent_t pact_of_the_eredruin; - const spell_data_t* doomguard; - const spell_data_t* doom_bolt; - player_talent_t shadowtouched; + player_talent_t summon_doomguard; + const spell_data_t* doom_bolt_volley; + player_talent_t to_hell_and_back; + const spell_data_t* unstable_soul_buff; + player_talent_t stabilized_portals; player_talent_t mark_of_shatug; const spell_data_t* gloom_slash; player_talent_t mark_of_fharg; const spell_data_t* infernal_presence; const spell_data_t* infernal_presence_dmg; - player_talent_t flametouched; - const spell_data_t* ferocity_of_fharg_buff; - player_talent_t immutable_hatred; - const spell_data_t* immutable_hatred_proc; - player_talent_t master_summoner; + player_talent_t dominion_of_argus_1; + player_talent_t dominion_of_argus_2; + player_talent_t dominion_of_argus_3; // Destruction + player_talent_t chaos_bolt; player_talent_t conflagrate; // Base 2 charges const spell_data_t* conflagrate_2; // Energize data - - player_talent_t backdraft; - const spell_data_t* backdraft_buff; player_talent_t rain_of_fire; const spell_data_t* rain_of_fire_tick; - player_talent_t roaring_blaze; - const spell_data_t* conflagrate_debuff; // Debuff associated with Roaring Blaze player_talent_t improved_conflagrate; // +1 charge for Conflagrate - player_talent_t backlash; // Crit chance increase. NOT IMPLEMENTED: Instant Incinerate proc when physically attacked + player_talent_t backdraft; + const spell_data_t* backdraft_buff; + player_talent_t practiced_chaos; + player_talent_t roaring_blaze; + + player_talent_t explosive_potential; // Reduces base Conflagrate cooldown by 2 seconds player_talent_t mayhem; // It appears that the only spells that can proc Mayhem are ones that can be Havoc'd player_talent_t havoc; // Talent data for Havoc is both the debuff and the action const spell_data_t* havoc_debuff; // This is a second copy of the talent data for use in places that are shared by Havoc and Mayhem - player_talent_t pyrogenics; // Enemies affected by Rain of Fire receive debuff for increased Fire damage - const spell_data_t* pyrogenics_debuff; - player_talent_t cataclysm; - - player_talent_t indiscriminate_flames; - player_talent_t rolling_havoc; // Increased damage buff when spells are duplicated by Mayhem/Havoc - const spell_data_t* rolling_havoc_buff; player_talent_t scalding_flames; // Increased Immolate damage and duration player_talent_t shadowburn; const spell_data_t* shadowburn_2; // Contains Soul Shard energize data - player_talent_t explosive_potential; // Reduces base Conflagrate cooldown by 2 seconds - // Summoner's Embrace (shared with Affliction) - // Grimoire of Sacrifice (shared with Affliction) + player_talent_t backlash; // Crit chance increase. NOT IMPLEMENTED: Instant Incinerate proc when physically attacked + player_talent_t improved_havoc; player_talent_t ashen_remains; // Increased Chaos Bolt and Incinerate damage to targets afflicted by Immolate - player_talent_t channel_demonfire; - const spell_data_t* channel_demonfire_tick; - const spell_data_t* channel_demonfire_travel; // Only holds travel speed - player_talent_t demonfire_infusion; + player_talent_t cataclysm; - player_talent_t blistering_atrophy; - player_talent_t conflagration_of_chaos; // Conflagrate/Shadowburn has chance to make next cast of it a guaranteed crit TODO: Review behavior - const spell_data_t* conflagration_of_chaos_cf; // Player buff which affects next Conflagrate - const spell_data_t* conflagration_of_chaos_sb; // Player buff which affects next Shadowburn - player_talent_t emberstorm; + player_talent_t fiendish_cruelty; + const spell_data_t* fiendish_cruelty_buff; + player_talent_t chaotic_inferno; + const spell_data_t* chaotic_inferno_buff; + player_talent_t flashpoint; // Stacking haste buff from Immolate ticks on high-health targets + const spell_data_t* flashpoint_buff; player_talent_t summon_infernal; const spell_data_t* summon_infernal_main; // Data for main infernal summoning const spell_data_t* infernal_awakening; // AoE on impact is attributed to the Warlock @@ -456,39 +403,29 @@ struct warlock_t : public parse_player_effects_t const spell_data_t* immolation_dmg; // Ticking AoE damage from buff const spell_data_t* embers; // Buff which generates Soul Shards const spell_data_t* burning_ember; // Energize data for Soul Shards + player_talent_t emberstorm; player_talent_t fire_and_brimstone; - player_talent_t flashpoint; // Stacking haste buff from Immolate ticks on high-health targets - const spell_data_t* flashpoint_buff; - player_talent_t raging_demonfire; // Additional Demonfire bolts and bolts extend Immolate + player_talent_t lake_of_fire; + const spell_data_t* lake_of_fire_aoe; + const spell_data_t* lake_of_fire_tick; + const spell_data_t* lake_of_fire_debuff; - player_talent_t fiendish_cruelty; - player_talent_t eradication; - const spell_data_t* eradication_debuff; + player_talent_t reverse_entropy; + const spell_data_t* reverse_entropy_buff; + player_talent_t internal_combustion; + const spell_data_t* internal_combustion_dmg; player_talent_t crashing_chaos; // Summon Infernal increases the damage of next 8 Chaos Bolt or Rain of Fire casts const spell_data_t* crashing_chaos_buff; player_talent_t rain_of_chaos; // TOCHECK: Confirm RNG behavior (deck of cards) periodically const spell_data_t* rain_of_chaos_buff; const spell_data_t* summon_infernal_roc; // Contains Rain of Chaos infernal duration - player_talent_t reverse_entropy; - const spell_data_t* reverse_entropy_buff; - player_talent_t internal_combustion; - const spell_data_t* internal_combustion_dmg; - player_talent_t demonfire_mastery; + // Summoner's Embrace (shared with Affliction) + // Grimoire of Sacrifice (shared with Affliction) - player_talent_t devastation; - player_talent_t ritual_of_ruin; - const spell_data_t* impending_ruin_buff; // Stacking buff, triggers Ritual of Ruin buff at max - const spell_data_t* ritual_of_ruin_buff; player_talent_t ruin; // Damage increase to several spells TODO: Review behavior - - player_talent_t soul_fire; - const spell_data_t* soul_fire_2; // Contains Soul Shard energize data player_talent_t improved_chaos_bolt; - player_talent_t burn_to_ashes; // Chaos Bolt and Rain of Fire increase damage of next 2 Incinerates - const spell_data_t* burn_to_ashes_buff; - player_talent_t master_ritualist; // Reduces proc cost of Ritual of Ruin - player_talent_t power_overwhelming; // Stacking mastery buff for spending Soul Shards - const spell_data_t* power_overwhelming_buff; + player_talent_t destructive_rapidity; + player_talent_t devastation; player_talent_t dimensional_rift; const spell_data_t* shadowy_tear_summon; // This only creates the "pet" @@ -499,18 +436,30 @@ struct warlock_t : public parse_player_effects_t const spell_data_t* chaos_barrage_tick; const spell_data_t* chaos_tear_summon; // This only creates the "pet" const spell_data_t* rift_chaos_bolt; // Separate ID from Warlock's Chaos Bolt - player_talent_t dimension_ripper; // TODO: implement the correct proc behavior based on the spell data + player_talent_t soul_fire; + const spell_data_t* soul_fire_2; // Contains Soul Shard energize data + player_talent_t inferno; + player_talent_t conflagration_of_chaos; // Conflagrate/Shadowburn has chance to make next cast of it a guaranteed crit TODO: Review behavior + const spell_data_t* conflagration_of_chaos_cf; // Player buff which affects next Conflagrate + const spell_data_t* conflagration_of_chaos_sb; // Player buff which affects next Shadowburn + player_talent_t diabolic_embers; // Incinerate generates more Soul Shards + player_talent_t demonfire_infusion; + player_talent_t channel_demonfire; + const spell_data_t* channel_demonfire_tick; + const spell_data_t* channel_demonfire_travel; // Only holds travel speed - player_talent_t decimation; // Crits can proc Soul Fire cooldown reset. Proc chance is not in spell data - const spell_data_t* decimation_buff; - player_talent_t chaos_incarnate; // Greater mastery value for some spells player_talent_t avatar_of_destruction; // TOCHECK: Is Overfiend benefitting from owner's Mastery? const spell_data_t* summon_overfiend; const spell_data_t* overfiend_buff; // Buff on Warlock while Overfiend is out, generates Soul Shards const spell_data_t* overfiend_cb; // Chaos Bolt cast by Overfiend - player_talent_t diabolic_embers; // Incinerate generates more Soul Shards - player_talent_t unstable_rifts; - const spell_data_t* dimensional_cinder; + player_talent_t chaos_incarnate; // Greater mastery value for some spells + player_talent_t alythesss_ire; + const spell_data_t* alythesss_ire_buff; + player_talent_t raging_demonfire; + + player_talent_t embers_of_nihilam_1; + player_talent_t embers_of_nihilam_2; + player_talent_t embers_of_nihilam_3; } talents; struct hero_talents_t @@ -536,7 +485,7 @@ struct warlock_t : public parse_player_effects_t player_talent_t cloven_souls; const spell_data_t* cloven_soul_debuff; player_talent_t touch_of_rancora; - player_talent_t secrets_of_the_coven; // TODO: Dimension Ripper? + player_talent_t secrets_of_the_coven; const spell_data_t* infernal_bolt; const spell_data_t* infernal_bolt_buff; @@ -555,11 +504,20 @@ struct warlock_t : public parse_player_effects_t const spell_data_t* ruination_impact; const spell_data_t* diabolic_imp; const spell_data_t* diabolic_bolt; + player_talent_t diabolic_oculi; + const spell_data_t* demonic_oculi_buff; + const spell_data_t* eye_explosion; + player_talent_t looks_that_kill; + const spell_data_t* diabolic_gaze_dmg_1; + const spell_data_t* diabolic_gaze_dmg_2; + const spell_data_t* diabolic_gaze_dmg_3; + player_talent_t minds_eyes; + const spell_data_t* minds_eyes_buff; // Hellcaller player_talent_t wither; const spell_data_t* wither_direct; // TODO: Damage values are picking up some other weird effects similar to Flames of Xoroth. Check damage again after main implementation work is done - const spell_data_t* wither_dot; // TODO: In-game, Affliction is picking up the Socrethar's Guile effect, which is almost certainly a bug + const spell_data_t* wither_dot; player_talent_t xalans_ferocity; player_talent_t blackened_soul; @@ -575,6 +533,10 @@ struct warlock_t : public parse_player_effects_t player_talent_t seeds_of_their_demise; player_talent_t mark_of_perotharn; + player_talent_t through_the_felvine; + player_talent_t devil_fruit; + player_talent_t alzzins_iniquity; + player_talent_t malevolence; const spell_data_t* malevolence_buff; const spell_data_t* malevolence_dmg; @@ -583,7 +545,7 @@ struct warlock_t : public parse_player_effects_t player_talent_t demonic_soul; const spell_data_t* succulent_soul; // Buff triggered by Demonic Soul proc const spell_data_t* demonic_soul_dmg; - + player_talent_t necrolyte_teachings; player_talent_t soul_anathema; const spell_data_t* soul_anathema_dot; @@ -600,79 +562,42 @@ struct warlock_t : public parse_player_effects_t player_talent_t shadow_of_death; const spell_data_t* shadow_of_death_energize; + + player_talent_t manifested_avarice; + const spell_data_t* manifested_avarice_summon; + const spell_data_t* manifested_demonic_soul_buff; + player_talent_t shared_vessel; + player_talent_t eternal_hunger; } hero; struct proc_actions_t { - action_t* bilescourge_bombers_aoe_tick; - action_t* bilescourge_bombers_proc; // From Shadow Invocation talent action_t* doom_proc; action_t* rain_of_fire_tick; + action_t* lake_of_fire_tick; action_t* blackened_soul; action_t* malevolence; action_t* demonic_soul; action_t* shared_fate; action_t* wicked_reaping; action_t* demonfire_infusion; - action_t* jackpot_ua; - action_t* jackpot_cdf; - action_t* eye_blast; // Diabolist 2pc damage proc + action_t* eye_explosion; + action_t* diabolic_gaze_1; + action_t* diabolic_gaze_2; + action_t* diabolic_gaze_3; } proc_actions; struct tier_sets_t { - // Affliction - const spell_data_t* hexflame_aff_2pc; - const spell_data_t* hexflame_aff_4pc; - const spell_data_t* umbral_lattice; - const spell_data_t* spliced_aff_2pc; - const spell_data_t* spliced_aff_4pc; - const spell_data_t* spliced_aff_jackpot; - const spell_data_t* jackpot_ua; - - // Demonology - const spell_data_t* hexflame_demo_2pc; - const spell_data_t* hexflame_demo_4pc; - const spell_data_t* empowered_legion_strike; - const spell_data_t* spliced_demo_2pc; - const spell_data_t* spliced_demo_4pc; - const spell_data_t* greater_dreadstalker; - const spell_data_t* demonic_hunger; // Applied to Dreadstalker when empowered by Jackpot - - // Destruction - const spell_data_t* hexflame_destro_2pc; - const spell_data_t* hexflame_destro_4pc; - const spell_data_t* echo_of_the_azjaqir; - const spell_data_t* spliced_destro_2pc; - const spell_data_t* spliced_destro_4pc; - const spell_data_t* spliced_destro_jackpot; - const spell_data_t* demonfire_flurry; // Procs Demonfire bolts on Jackpot proc - - // Soul Harvester - const spell_data_t* rampaging_demonic_soul; - const spell_data_t* inquisitor_sh_2pc; - const spell_data_t* inquisitor_sh_4pc; - - // Diabolist - const spell_data_t* demonic_oculus; // TWW3 Diabolist 2pc stacking buff - const spell_data_t* eye_blast; // TWW3 Diablist 2pc damage proc - const spell_data_t* demonic_intelligence; // TWW3 Diabolist 4pc stacking buff - const spell_data_t* inquisitor_db_2pc; - const spell_data_t* inquisitor_db_4pc; - - // Hellcaller - const spell_data_t* maintained_withering; // TWW Hellcaller 4pc spell buff - const spell_data_t* inquisitor_hc_2pc; - const spell_data_t* inquisitor_hc_4pc; - } tier; // Cooldowns - Used for accessing cooldowns outside of their respective actions, such as reductions/resets struct cooldowns_t { propagate_const haunt; - propagate_const shadowburn; + propagate_const dark_harvest; propagate_const soul_fire; + propagate_const summon_doomguard; propagate_const felstorm_icd; // Shared between Felstorm, Demonic Strength, and Guillotine TODO: Actually use this! propagate_const blackened_soul; // Internal cooldown on triggering stack increase to Wither propagate_const seeds_of_their_demise; // Estimated internal cooldown, a guess at how Blizzard is minimizing lucky streaks @@ -688,42 +613,36 @@ struct warlock_t : public parse_player_effects_t // Affliction Buffs propagate_const nightfall; - propagate_const tormented_crescendo; - propagate_const malign_omen; - propagate_const dark_harvest; - propagate_const umbral_lattice; // TWW1 4pc - propagate_const jackpot_affliction; + propagate_const shard_instability; + propagate_const cascading_calamity; + propagate_const seed_of_corruption_is_out_dnt; // Demonology Buffs propagate_const demonic_core; propagate_const power_siphon; // Hidden buff from Power Siphon that increases damage of successive Demonbolts - propagate_const demonic_calling; propagate_const inner_demons; + propagate_const tyrants_oblation; + propagate_const hellbent_commander; propagate_const wild_imps; // Buff for tracking how many Wild Imps are currently out (does NOT include imps waiting to be spawned) propagate_const dreadstalkers; // Buff for tracking number of Dreadstalkers currently out propagate_const vilefiend; // Buff for tracking number of Vilefiends currently out + propagate_const grimoire_imp_lord; + propagate_const grimoire_fel_ravager; + propagate_const doomguard; propagate_const tyrant; // Buff for tracking if Demonic Tyrant is currently out - propagate_const grimoire_felguard; // Buff for tracking if GFG pet is currently out - propagate_const dread_calling; // Destruction Buffs propagate_const backdraft; propagate_const reverse_entropy; + propagate_const fiendish_cruelty; + propagate_const chaotic_inferno; propagate_const rain_of_chaos; - propagate_const impending_ruin; - propagate_const ritual_of_ruin; - propagate_const rolling_havoc; propagate_const conflagration_of_chaos_cf; propagate_const conflagration_of_chaos_sb; propagate_const flashpoint; propagate_const crashing_chaos; - propagate_const power_overwhelming; - propagate_const burn_to_ashes; - propagate_const decimation; + propagate_const alythesss_ire; propagate_const summon_overfiend; - propagate_const echo_of_the_azjaqir; - propagate_const demonfire_flurry_trigger; - propagate_const jackpot_destruction; // Diabolist Buffs propagate_const ritual_overlord; @@ -735,22 +654,21 @@ struct warlock_t : public parse_player_effects_t propagate_const infernal_bolt; propagate_const abyssal_dominion; propagate_const ruination; - propagate_const demonic_oculus; // TWW3 Diabolist 2pc buff - propagate_const demonic_intelligence; // TWW3 Diabolist 4pc buff + propagate_const demonic_oculi; + propagate_const minds_eyes; // Hellcaller Buffs propagate_const malevolence; - propagate_const maintained_withering; // TWW3 Hellcaller 4pc buff // Soul Harvester Buffs propagate_const succulent_soul; + propagate_const manifested_demonic_soul; } buffs; // Gains - Many are automatically handled struct gains_t { // Class Talents - gain_t* soul_conduit; // Affliction gain_t* agony; @@ -777,57 +695,41 @@ struct warlock_t : public parse_player_effects_t // Soul Harvester gain_t* feast_of_souls; gain_t* shadow_of_death; - gain_t* rampaging_demonic_soul; // Only with TWW3 4pc } gains; // Procs struct procs_t { // Class Talents - proc_t* soul_conduit; - proc_t* demonic_inspiration; - proc_t* wrathful_minion; // Affliction proc_t* nightfall; - std::array malefic_rapture; // This length should be at least equal to the maximum number of Affliction DoTs that can be active on a target. - proc_t* shadow_bolt_volley; - proc_t* tormented_crescendo; + proc_t* shadowbolt_volley; proc_t* ravenous_afflictions; - proc_t* umbral_lattice; - proc_t* jackpot_affliction; + proc_t* shard_instability; + proc_t* fatal_echoes; // Demonology - proc_t* demonic_calling; - std::array hand_of_guldan_shards; proc_t* demonic_core_dogs; proc_t* demonic_core_imps; proc_t* carnivorous_stalkers; - proc_t* shadow_invocation; // Bilescourge Bomber proc on Shadowbolt, Demonbolt and HoG Impacts - proc_t* imp_gang_boss; + proc_t* infernal_rapidity; proc_t* spiteful_reconstitution; - proc_t* umbral_blaze; - proc_t* pact_of_the_imp_mother; - proc_t* doom_eternal; - proc_t* pact_of_the_eredruin; - proc_t* empowered_legion_strike; // TWW1 4pc buff - proc_t* jackpot_demonology; // TWW2 2pc proc - proc_t* demonic_core_big_dogs; + proc_t* demonic_knowledge; // Destruction proc_t* reverse_entropy; proc_t* rain_of_chaos; - proc_t* ritual_of_ruin; - proc_t* avatar_of_destruction; proc_t* mayhem; + proc_t* fiendish_cruelty; + proc_t* chaotic_inferno; + proc_t* dimensional_rift; + proc_t* avatar_of_destruction; proc_t* conflagration_of_chaos_cf; proc_t* conflagration_of_chaos_sb; proc_t* demonfire_infusion_inc; proc_t* demonfire_infusion_dot; - proc_t* decimation; - proc_t* dimension_ripper; - proc_t* echo_of_the_azjaqir; - proc_t* jackpot_destruction; + proc_t* alythesss_ire; // Diabolist @@ -842,6 +744,7 @@ struct warlock_t : public parse_player_effects_t proc_t* feast_of_souls; } procs; + // TODO: Need to check that these RNG values ​​are still correct in Midnight struct rng_settings_t { struct rng_setting_t @@ -856,17 +759,13 @@ struct warlock_t : public parse_player_effects_t rng_setting_t cunning_cruelty_ds = { 0.25, 0.25, "cunning_cruelty_ds" }; rng_setting_t agony = { 0.368, 0.368, "agony" }; rng_setting_t nightfall = { 0.13, 0.13, "nightfall" }; - rng_setting_t umbral_lattice = { 0.30, 0.30, "umbral_lattice" }; // Demonology rng_setting_t pact_of_the_eredruin = { 0.40, 0.40, "pact_of_the_eredruin" }; - rng_setting_t shadow_invocation = { 0.20, 0.20, "shadow_invocation" }; rng_setting_t spiteful_reconstitution = { 0.30, 0.30, "spiteful_reconstitution" }; - rng_setting_t empowered_legion_strike = { 0.25, 0.25, "empowered_legion_strike" }; // Destruction - rng_setting_t decimation = { 0.10, 0.10, "decimation" }; - rng_setting_t dimension_ripper = { 0.0225, 0.0225, "dimension_ripper" }; + rng_setting_t avatar_of_destruction_dr = { 0.60, 0.60, "avatar_of_destruction_dr" }; // TODO: Need to calculate ingame the type of RNG and the average RNG // Diabolist @@ -881,18 +780,16 @@ struct warlock_t : public parse_player_effects_t rng_setting_t succulent_soul_demo = { 0.15, 0.15, "succulent_soul_demo" }; rng_setting_t feast_of_souls_aff = { 0.15, 0.15, "feast_of_souls" }; rng_setting_t feast_of_souls_demo = { 0.0975, 0.0975, "feast_of_souls" }; + rng_setting_t manifested_avarice = { 0.10, 0.10, "manifested_avarice" }; // TODO: Need to calculate ingame the type of RNG and the average RNG } rng_settings; int initial_soul_shards; std::string default_pet; bool disable_auto_felstorm; // For Demonology main pet bool normalize_destruction_mastery; - bool demonic_inspiration_double_dip; // Enable Demonic Inspiration double dip on main pets shuffled_rng_t* rain_of_chaos_rng; real_ppm_t* ravenous_afflictions_rng; - real_ppm_t* jackpot_demonology_rng; - real_ppm_t* jackpot_destruction_rng; - const spell_data_t* version_11_1_0_data; + real_ppm_t* devil_fruit_rng; warlock_t( sim_t* sim, util::string_view name, race_e r ); @@ -918,9 +815,7 @@ struct warlock_t : public parse_player_effects_t int get_spawning_imp_count(); // TODO: Decide if still needed timespan_t time_to_imps( int count ); // TODO: Decide if still needed int active_demon_count() const; - void expendables_trigger_helper( warlock_pet_t* source ); // TODO: Move to helpers? std::pair dreadstalkers_delay_duration_adjustment_helper( const player_t& target ); // TODO: Move to helpers? or implement in call_dreadstalkers_t? - bool min_version_check( version_check_e version ) const; void create_actions() override; void create_affliction_proc_actions(); void create_demonology_proc_actions(); @@ -936,19 +831,19 @@ struct warlock_t : public parse_player_effects_t resource_e primary_resource() const override { return RESOURCE_MANA; } role_e primary_role() const override { return ROLE_SPELL; } stat_e convert_hybrid_stat( stat_e s ) const override; - double composite_player_target_pet_damage_multiplier( player_t* target, bool guardian ) const override; void init_blizzard_action_list() override; void combat_begin() override; void init_assessors() override; void init_finished() override; void invalidate_cache( cache_e c ) override; - bool validate_actor() override; std::unique_ptr create_expression( util::string_view name_str ) override; std::string default_potion() const override { return warlock_apl::potion( this ); } std::string default_flask() const override { return warlock_apl::flask( this ); } std::string default_food() const override { return warlock_apl::food( this ); } std::string default_rune() const override { return warlock_apl::rune( this ); } std::string default_temporary_enchant() const override { return warlock_apl::temporary_enchant( this ); } + std::vector get_smart_targets( const std::vector& tl, propagate_const warlock_td_t::dots_t::* dot, int n_targets, player_t* exclude = nullptr, double range = 0.0, bool really_smart = false ); + player_t* get_smart_target( const std::vector& tl, propagate_const warlock_td_t::dots_t::* dot, player_t* exclude = nullptr, double range = 0.0, bool really_smart = false ); double resource_gain( resource_e resource_type, double amount, gain_t* source = nullptr, action_t* action = nullptr ) override; void feast_of_souls_gain(); @@ -959,22 +854,16 @@ struct warlock_t : public parse_player_effects_t bool hellcaller() const; bool soul_harvester() const; - template + template bool active_2pc() const { - if constexpr ( Tier == TWW3 ) - return sets->has_set_bonus( Hero, Tier, B2 ); - else - return sets->has_set_bonus( specialization(), Tier, B2 ); + return sets->has_set_bonus( specialization(), Tier, B2 ); } - template + template bool active_4pc() const { - if constexpr ( Tier == TWW3 ) - return sets->has_set_bonus( Hero, Tier, B4 ); - else - return sets->has_set_bonus( specialization(), Tier, B4 ); + return sets->has_set_bonus( specialization(), Tier, B4 ); } target_specific_t target_data; @@ -1044,28 +933,24 @@ namespace helpers { struct imp_delay_event_t : public player_event_t { - imp_delay_event_t( warlock_t*, double, double ); + imp_delay_event_t( warlock_t*, double, double, int ); timespan_t diff; + int index; virtual const char* name() const override; virtual void execute() override; timespan_t expected_time(); }; - struct sc_event_t : public player_event_t + struct ua_stack_event_t : public player_event_t { - sc_event_t( warlock_t*, int ); - gain_t* shard_gain; - warlock_t* pl; - int shards_used; + ua_stack_event_t( warlock_t*, dot_t*, timespan_t ); + dot_t* dot; virtual const char* name() const override; virtual void execute() override; }; - bool crescendo_check( warlock_t* p, player_t* tar ); void nightfall_updater( warlock_t* p, dot_t* d ); void trigger_blackened_soul( warlock_t* p, bool malevolence ); - - void trigger_jackpot_ua( warlock_t* p ); } } // namespace warlock diff --git a/engine/class_modules/warlock/sc_warlock_actions.cpp b/engine/class_modules/warlock/sc_warlock_actions.cpp index 90b06eb6371..ef0bc703db3 100644 --- a/engine/class_modules/warlock/sc_warlock_actions.cpp +++ b/engine/class_modules/warlock/sc_warlock_actions.cpp @@ -19,10 +19,10 @@ using namespace helpers; // Affliction bool deaths_embrace = false; + bool summon_darkglare = false; // Demonology bool sacrificed_souls = false; - bool soul_conduit_base_cost = false; // Destruction bool chaotic_energies = false; @@ -40,16 +40,9 @@ using namespace helpers; // Affliction bool ravenous_afflictions = false; - bool jackpot_affliction = false; - - // Demonology - bool shadow_invocation = false; - bool jackpot_demonology = false; // Destruction - bool decimation = false; - bool dimension_ripper = false; - bool jackpot_destruction = false; + bool fiendish_cruelty = false; // Diabolist bool diabolic_ritual = false; @@ -58,6 +51,43 @@ using namespace helpers; bool rancora_cb_bonus = false; } triggers; + struct warlock_spell_state_t: public action_state_t + { + bool mg_tick; + bool periodic_hit; + + warlock_spell_state_t( action_t* action, player_t* target ) + : action_state_t( action, target ), + mg_tick( false ), + periodic_hit( false ) + { } + + void initialize() override + { + action_state_t::initialize(); + mg_tick = false; + periodic_hit = false; + } + + std::ostringstream& debug_str( std::ostringstream& s ) override + { + action_state_t::debug_str( s ); + s << " mg_tick=" << mg_tick; + s << " periodic_hit=" << periodic_hit; + return s; + } + + void copy_state( const action_state_t* s ) override + { + action_state_t::copy_state( s ); + mg_tick = debug_cast( s )->mg_tick; + periodic_hit = debug_cast( s )->periodic_hit; + } + }; + + action_state_t* new_state() override + { return new warlock_spell_state_t( this, target ); } + warlock_spell_t( util::string_view token, warlock_t* p, const spell_data_t* s = spell_data_t::nil() ) : parse_action_effects_t( token, p, s ), affected_by(), @@ -81,8 +111,6 @@ using namespace helpers; if ( this->data().flags( spell_attribute::SX_ABILITY ) || this->trigger_gcd > 0_ms ) this->not_a_proc = true; } - - triggers.decimation = p->talents.decimation.ok(); } warlock_spell_t( util::string_view token, warlock_t* p, const spell_data_t* s, util::string_view options_str ) @@ -91,7 +119,7 @@ using namespace helpers; warlock_t* p() { return static_cast( player ); } - + const warlock_t* p() const { return static_cast( player ); } @@ -104,10 +132,74 @@ using namespace helpers; void reset() override { action_base_t::reset(); } + // mg_tick is set, and we will record the damage as "direct", even if it is + // from extra ticks + result_amount_type report_amount_type( const action_state_t* s ) const override + { + if ( debug_cast( s )->mg_tick ) + return result_amount_type::DMG_DIRECT; + + //return spell_t::report_amount_type( s ); + return parse_action_effects_t::report_amount_type( s ); + } + + // TODO: I believe this was only necessary in the past because of multistrike, but it's worth checking it anyway + // DS extra ticks can multistrike, so we need to do the stats juggling in + // it's correct place, since multistrikes do not strike immediately, but + // rather on their own event + // void assess_damage( result_amount_type type, action_state_t* s ) override + // { + // warlock_spell_state_t* state = debug_cast( s ); + // stats_t* tmp = nullptr; + // // ds tick->adjust the spell's "stats" object so we collect information + // // to a separate SPELL_ds entry in the report + // if ( state->mg_tick ) + // { + // tmp = stats; + // stats = mg_tick_stats; + // } + // + // spell_t::assess_damage( type, s ); + // + // if ( tmp ) + // { + // stats->add_execute( timespan_t::zero(), s->target ); + // stats = tmp; + // } + // } + + // TODO: Thoroughly check that this is working properly + // TODO: Also check which multipliers actually affect the extra ticks and which ones don't + void trigger_extra_tick( dot_t* dot, double multiplier ) + { + if ( ! dot->is_ticking() ) return; + assert( multiplier != 0.0 ); + + action_state_t* tmp_state = dot->state; + dot->state = dot->current_action->get_state( tmp_state ); + dot->state->ta_multiplier *= multiplier; + + // Carry "mg tickness" in the state, so it works across events etc + auto w_state = debug_cast( dot->state ); + w_state->mg_tick = true; + + dot->current_action->snapshot_internal( dot->state, update_flags | STATE_CRIT, tmp_state->result_type ); + + double tmp_multi = dot->current_action->base_multiplier; + w_state->periodic_hit = true; + dot->current_action->base_multiplier = multiplier; + dot->current_action->tick( dot ); + w_state->periodic_hit = false; + + action_state_t::release( dot->state ); + dot->state = tmp_state; + dot->current_action->base_multiplier = tmp_multi; + } + /* ---------------------------------------------------------- * NOTE NOTE NOTE * Applies DYNAMIC (Buffs, Debuffs, DoTs, or anything else that could change state during combat) - * effects that effect the action. + * effects that effect the action. * NEEDS TO BE A WHITELIST EFFECT! * NOTE NOTE NOTE * @@ -146,10 +238,8 @@ using namespace helpers; if ( affliction() ) { parse_effects( p()->warlock_base.potent_afflictions ); // 77215 - parse_effects( p()->buffs.nightfall, effect_mask_t( true ).disable( 3 ) ); // 264571 // Effect #3 is handled in a custom action_state - parse_effects( p()->buffs.tormented_crescendo ); // 387079 - parse_effects( p()->buffs.umbral_lattice ); // 455679 // TWW1 - parse_effects( p()->buffs.jackpot_affliction ); // 1219034 // TWW2 + parse_effects( p()->buffs.nightfall, effect_mask_t( true ).disable( 3 ) ); // 264571/1260279 // Effect #3 is handled in a custom action_state + parse_effects( p()->buffs.shard_instability ); // 1260269 } // Demonology @@ -157,33 +247,38 @@ using namespace helpers; { parse_effects( p()->buffs.demonic_core ); // 264173 parse_effects( p()->warlock_base.master_demonologist ); // 77219 - // NOTE: Currently 'parse_effects' system does not support some of the Power Siphon dmg amp modifiers spell effects, so we manually override the value + // NOTE: Currently 'parse_effects' system does not support some of the Power Siphon dmg amp modifiers spell effects, so we manually override the value. // TODO: Is this still the case in Midnight? + // TODO: Verify if this Power Siphon effect is working correctly parse_effects( p()->buffs.power_siphon ); // 334581 - parse_effects( p()->buffs.demonic_calling ); // 205146 } // Destruction if ( destruction() ) { parse_effects( p()->buffs.backdraft ); // 117828 - parse_effects( p()->buffs.ritual_of_ruin ); // 387157 + parse_effects( p()->buffs.fiendish_cruelty ); // 1245664 // TODO: check if this buff is applying the power cost reduction properly + parse_effects( p()->buffs.chaotic_inferno ); // 1244860 parse_effects( p()->buffs.conflagration_of_chaos_cf ); // 387109 parse_effects( p()->buffs.conflagration_of_chaos_sb ); // 387110 parse_effects( p()->buffs.crashing_chaos ); // 417282 // RoF is dummy - parse_effects( p()->buffs.burn_to_ashes ); // 387154 - parse_effects( p()->buffs.decimation ); // 457555 - parse_effects( p()->buffs.echo_of_the_azjaqir ); // 455674 // TWW1 + parse_effects( p()->buffs.alythesss_ire ); // 1244947 } // Diabolist if ( diabolist() ) { + // TODO: Check that the damage increase from these buffs (modified by Touch of Rancora) is not being applied twice + // TODO: Actually checking how Touch of Rancora is working ingame for both Destro and Demon and replicating that behavior here parse_effects( p()->buffs.art_overlord ); // 428524 parse_effects( p()->buffs.art_mother ); // 432794 parse_effects( p()->buffs.art_pit_lord ); // 432795 } // Hellcaller + if ( hellcaller() ) + { + parse_effects( p()->buffs.malevolence ); // 442726 // TODO: Check that the effectiveness of Through the Felvine is increased by 100% during Malevolence with this parse_effects + } // Soul Harvester @@ -196,22 +291,19 @@ using namespace helpers; // Affliction if ( affliction() ) { - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::infirmity ), p()->talents.infirmity_debuff ); // 458219 parse_target_effects( d_fn( &warlock_td_t::dots_t::unstable_affliction ), p()->talents.unstable_affliction ); // 316099 - parse_target_effects( d_fn( &warlock_td_t::dots_t::jackpot_ua ), p()->tier.jackpot_ua ); // 1219045 // TWW2 } // Demonology if ( demonology() ) { - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::wicked_maw ), p()->talents.wicked_maw_debuff ); // 270569 } // Destruction if ( destruction() ) { - parse_target_effects( d_fn( &warlock_td_t::debuffs_t::conflagrate ), p()->talents.conflagrate_debuff ); // 265931 parse_target_effects( d_fn( &warlock_td_t::dots_t::immolate ), p()->warlock_base.immolate_dot ); // 157736 + parse_target_effects( d_fn( &warlock_td_t::debuffs_t::lake_of_fire ), p()->talents.lake_of_fire_debuff ); // 157736 } // Diabolist @@ -233,13 +325,7 @@ using namespace helpers; if ( resource_current == RESOURCE_SOUL_SHARD && p()->in_combat ) { int shards_used = as( last_resource_cost ); - int base_shards = as( base_cost() ); // Power Overwhelming is ignoring any cost changes - - if ( p()->talents.soul_conduit.ok() ) - { - // Soul Conduit events are delayed slightly (100 ms) in sims to avoid instantaneous reactions - make_event( *p()->sim, p(), as( affected_by.soul_conduit_base_cost ? base_shards : shards_used ) ); - } + int base_shards = as( base_cost() ); if ( p()->buffs.rain_of_chaos->check() && shards_used > 0 ) { @@ -257,17 +343,6 @@ using namespace helpers; } } - if ( p()->talents.ritual_of_ruin.ok() && shards_used > 0 ) - { - int overflow = p()->buffs.impending_ruin->check() + shards_used - p()->buffs.impending_ruin->max_stack(); - p()->buffs.impending_ruin->trigger( shards_used ); // Stack change callback switches Impending Ruin to Ritual of Ruin if max stacks reached - if ( overflow > 0 ) - make_event( sim, 1_ms, [ this, overflow ] { p()->buffs.impending_ruin->trigger( overflow ); } ); - } - - if ( p()->talents.power_overwhelming.ok() && base_shards > 0 ) - p()->buffs.power_overwhelming->trigger( base_shards ); - if ( diabolist() && triggers.diabolic_ritual ) { timespan_t adjustment = 0_ms; @@ -339,19 +414,18 @@ using namespace helpers; { action_base_t::execute(); - if ( p()->talents.rolling_havoc.ok() && use_havoc() ) - p()->buffs.rolling_havoc->trigger(); - // NOTE: Casted spells do not consume any Demonic Art buff if none were active at the start of the cast if ( diabolist() && triggers.demonic_art && triggers.demonic_art_buff ) { - if ( active_2pc() ) + if ( p()->hero.diabolic_oculi.ok() ) + { make_event( *sim, 0_ms, [ this ] { - if ( p()->buffs.demonic_oculus->check() && + if ( p()->buffs.demonic_oculi->check() && ( p()->buffs.art_overlord->check() || p()->buffs.art_mother->check() || p()->buffs.art_pit_lord->check() ) ) - p()->proc_actions.eye_blast->execute_on_target( this->target ); + p()->proc_actions.eye_explosion->execute_on_target( this->target ); } ); + } // Force event sequencing in a manner that lets Rain of Fire pick up the persistent multiplier for Touch of Rancora make_event( sim, 0_ms, [ this ] { p()->buffs.art_overlord->decrement(); } ); make_event( sim, 0_ms, [ this ] { p()->buffs.art_mother->decrement(); } ); @@ -379,70 +453,21 @@ using namespace helpers; } } - if ( affliction() && active_2pc() && triggers.jackpot_affliction ) - { - bool success = p()->buffs.jackpot_affliction->trigger(); - - if ( success ) - { - p()->procs.jackpot_affliction->occur(); - helpers::trigger_jackpot_ua( p() ); - } - } - - if ( demonology() && active_2pc() && triggers.jackpot_demonology ) - { - if ( p()->jackpot_demonology_rng->trigger() ) - { - const auto delay_dur_adjusts = p()->dreadstalkers_delay_duration_adjustment_helper( *target ); - const timespan_t& delay = delay_dur_adjusts.first; - const timespan_t& dur_adjust = delay_dur_adjusts.second; - - auto dogs = p()->warlock_pet_list.greater_dreadstalkers.spawn( p()->tier.greater_dreadstalker->duration() + dur_adjust, 1u ); - - for ( auto d : dogs ) - { - if ( d->is_active() ) - { - d->server_action_delay = delay; - if ( p()->talents.dread_calling.ok() && !d->buffs.dread_calling->check() ) - d->buffs.dread_calling->trigger( 1, p()->buffs.dread_calling->check_stack_value() ); - } - } - - p()->procs.jackpot_demonology->occur(); - p()->buffs.dread_calling->expire(); - } - } - - 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(); - } - if ( destruction() && p()->talents.reverse_entropy.ok() ) { if ( p()->buffs.reverse_entropy->trigger() ) p()->procs.reverse_entropy->occur(); } - 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() && active_2pc() && triggers.jackpot_destruction ) + if ( destruction() && triggers.fiendish_cruelty ) { - if ( p()->jackpot_destruction_rng->trigger() ) + // TODO: Check if havoc hits and FnB Incinerate hits are affected by fiendish cruelty + if ( s->result == RESULT_CRIT ) { - p()->buffs.demonfire_flurry_trigger->trigger(); - p()->procs.jackpot_destruction->occur(); + bool success = p()->buffs.fiendish_cruelty->trigger(); - if ( active_4pc() ) - p()->buffs.jackpot_destruction->trigger(); + if ( success ) + p()->procs.fiendish_cruelty->occur(); } } } @@ -462,28 +487,6 @@ using namespace helpers; if ( p()->buffs.reverse_entropy->trigger() ) p()->procs.reverse_entropy->occur(); } - - if ( destruction() && triggers.dimension_ripper && rng().roll( p()->rng_settings.dimension_ripper.setting_value ) ) - { - int rift = rng().range( 3 ); - - switch ( rift ) - { - case 0: - p()->warlock_pet_list.shadow_rifts.spawn( p()->talents.shadowy_tear_summon->duration() ); - break; - case 1: - p()->warlock_pet_list.unstable_rifts.spawn( p()->talents.unstable_tear_summon->duration() ); - break; - case 2: - p()->warlock_pet_list.chaos_rifts.spawn( p()->talents.chaos_tear_summon->duration() ); - break; - default: - break; - } - - p()->procs.dimension_ripper->occur(); - } } double action_multiplier() const override @@ -494,9 +497,6 @@ using namespace helpers; { double min_percentage = affected_by.chaos_incarnate ? p()->talents.chaos_incarnate->effectN( 1 ).percent() : 0.5; - if ( p()->buffs.jackpot_destruction->check() ) - min_percentage = 1.0; - double chaotic_energies_rng = rng().range( min_percentage , 1.0 ); if ( p()->normalize_destruction_mastery ) @@ -504,31 +504,37 @@ using namespace helpers; m *= 1.0 + chaotic_energies_rng * p()->cache.mastery_value(); } - - return m; - } - - double composite_persistent_multiplier( const action_state_t* s ) const override - { - double m = action_base_t::composite_persistent_multiplier( s ); - - // Demonology only has Hand of Gul'dan affected by Touch of Rancora, which requires special handling - // Spells affected by touch_of_rancora_casted use a custom action_state_t and require special handling - if ( diabolist() && destruction() && affected_by.touch_of_rancora && !affected_by.touch_of_rancora_casted ) - { - if ( p()->buffs.art_overlord->check() || p()->buffs.art_mother->check() || p()->buffs.art_pit_lord->check() ) - m *= 1.0 + p()->hero.touch_of_rancora->effectN( 1 ).percent(); - } return m; } + // TODO: Touch of Rancora should now affect instant effects (not 'touch_of_rancora_casted') via the Demonic Art buff + // double composite_persistent_multiplier( const action_state_t* s ) const override + // { + // double m = action_base_t::composite_persistent_multiplier( s ); + // + // // Demonology only has Hand of Gul'dan affected by Touch of Rancora, which requires special handling + // // Spells affected by touch_of_rancora_casted use a custom action_state_t and require special handling + // if ( diabolist() && destruction() && affected_by.touch_of_rancora && !affected_by.touch_of_rancora_casted ) + // { + // if ( p()->buffs.art_overlord->check() || p()->buffs.art_mother->check() || p()->buffs.art_pit_lord->check() ) + // m *= 1.0 + p()->hero.touch_of_rancora->effectN( 1 ).percent(); + // } + // + // return m; + // } + double composite_da_multiplier( const action_state_t* s ) const override { double m = action_base_t::composite_da_multiplier( s ); - if ( affliction() && affected_by.deaths_embrace && s->target->health_percentage() < p()->talents.deaths_embrace->effectN( 4 ).base_value() ) - m *= 1.0 + p()->talents.deaths_embrace->effectN( 3 ).percent(); + // TODO: Check that the correct ingame Death's Embrace formula is being applied here + double deaths_embrace_health = p()->talents.deaths_embrace->effectN( 2 ).base_value(); + if ( affliction() && affected_by.deaths_embrace && s->target->health_percentage() < deaths_embrace_health ) + m *= 1.0 + p()->talents.deaths_embrace->effectN( 1 ).percent() * ( 1 - s->target->health_percentage() / deaths_embrace_health ); + + if ( affliction() && affected_by.summon_darkglare && p()->warlock_pet_list.darkglares.n_active_pets() > 0 ) + m *= 1.0 + p()->talents.summon_darkglare->effectN( 4 ).percent(); if ( demonology() && affected_by.sacrificed_souls && p()->talents.sacrificed_souls.ok() ) m *= 1.0 + p()->talents.sacrificed_souls->effectN( 1 ).percent() * p()->active_demon_count(); @@ -540,8 +546,12 @@ using namespace helpers; { double m = action_base_t::composite_ta_multiplier( s ); - if ( affliction() && affected_by.deaths_embrace && s->target->health_percentage() < p()->talents.deaths_embrace->effectN( 4 ).base_value() ) - m *= 1.0 + p()->talents.deaths_embrace->effectN( 3 ).percent(); + double deaths_embrace_health = p()->talents.deaths_embrace->effectN( 2 ).base_value(); + if ( affliction() && affected_by.deaths_embrace && s->target->health_percentage() < deaths_embrace_health ) + m *= 1.0 + p()->talents.deaths_embrace->effectN( 1 ).percent() * ( 1 - s->target->health_percentage() / deaths_embrace_health ); + + if ( affliction() && affected_by.summon_darkglare && p()->warlock_pet_list.darkglares.n_active_pets() > 0 ) + m *= 1.0 + p()->talents.summon_darkglare->effectN( 4 ).percent(); return m; } @@ -561,7 +571,7 @@ using namespace helpers; return affected_by.havoc && p()->havoc_target && p()->havoc_target != target; } - // We need to ensure that the target cache is invalidated, which sometimes does not take + // We need to ensure that the target cache is invalidated, which sometimes does not take // place in select_target() due to other methods we have overridden involving Havoc bool select_target() override { @@ -594,15 +604,15 @@ using namespace helpers; // Check target list size to prevent some silly scenarios where Havoc target // is the only target in the list. - if ( destruction() && tl.size() > 1 && use_havoc()) + if ( destruction() && tl.size() > 1 && use_havoc() ) { // We need to make sure that the Havoc target ends up second in the target list, // so that Havoc spells can pick it up correctly. - auto it = range::find(tl, p()->havoc_target); - if (it != tl.end()) + auto it = range::find( tl, p()->havoc_target ); + if ( it != tl.end() ) { - tl.erase(it); - tl.insert(tl.begin() + 1, p()->havoc_target); + tl.erase( it ); + tl.insert( tl.begin() + 1, p()->havoc_target ); } } @@ -638,13 +648,13 @@ using namespace helpers; bool soul_harvester() const { return p()->soul_harvester(); } - template + template bool active_2pc() const - { return p()->active_2pc(); } + { return p()->active_2pc(); } - template + template bool active_4pc() const - { return p()->active_4pc(); } + { return p()->active_4pc(); } }; // Shared Class Actions Begin @@ -742,9 +752,6 @@ using namespace helpers; p()->warlock_pet_list.active = pet; - if ( is_precombat && pet->affected_by.demonic_inspiration && !pet->buffs.demonic_inspiration->check() ) - pet->buffs.demonic_inspiration->trigger(); - if ( p()->buffs.grimoire_of_sacrifice->check() ) p()->buffs.grimoire_of_sacrifice->expire(); } @@ -786,13 +793,14 @@ using namespace helpers; triggers.ravenous_afflictions = p->talents.ravenous_afflictions.ok(); affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); + affected_by.summon_darkglare = p->talents.summon_darkglare.ok(); } void tick( dot_t* d ) override { warlock_spell_t::tick( d ); - if ( result_is_hit( d->state->result ) && p()->talents.nightfall.ok() ) + if ( result_is_hit( d->state->result ) && p()->talents.nightfall.ok() && !debug_cast( d->state )->periodic_hit ) helpers::nightfall_updater( p(), d ); } }; @@ -806,14 +814,10 @@ using namespace helpers; impact_action = periodic; add_child( periodic ); - triggers.jackpot_affliction = true; - if ( seed_action ) - { spell_power_mod.direct = 0; // Corruption does not deal instant damage when applied from SoC - triggers.jackpot_affliction = false; // Corruption does not trigger jackpot (tww2 tier) when applied from SoC - } + // TODO: Check if this is still true // 2025-08-27: Death's Embrace talent is not applying to the Corruption (direct damage) spell (bug) affected_by.deaths_embrace = p->bugs ? false : p->talents.deaths_embrace.ok(); } @@ -822,26 +826,25 @@ using namespace helpers; { return periodic->get_dot( t ); } }; - struct shadow_bolt_volley_t : public warlock_spell_t + struct shadowbolt_volley_t : public warlock_spell_t { - shadow_bolt_volley_t( warlock_t* p ) - : warlock_spell_t( "Shadow Bolt Volley", p, p->talents.shadow_bolt_volley ) + shadowbolt_volley_t( warlock_t* p ) + : warlock_spell_t( "Shadowbolt Volley", p, p->talents.shadowbolt_volley ) { background = dual = true; + // TODO: Check if Shadowbolt Volley is affected by Death's Embrace } }; struct shadow_bolt_t : public warlock_spell_t { - shadow_bolt_volley_t* volley; + shadowbolt_volley_t* volley; shadow_bolt_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Shadow Bolt", p, p->talents.drain_soul.ok() ? spell_data_t::not_found() : p->warlock_base.shadow_bolt, options_str ) { affected_by.sacrificed_souls = true; - triggers.shadow_invocation = true; - triggers.jackpot_affliction = true; - triggers.jackpot_demonology = true; + affected_by.deaths_embrace = true; if ( demonology() ) { @@ -851,7 +854,7 @@ using namespace helpers; } if ( p->talents.cunning_cruelty.ok() ) - volley = new shadow_bolt_volley_t( p ); + volley = new shadowbolt_volley_t( p ); } bool ready() override @@ -859,6 +862,9 @@ using namespace helpers; if ( diabolist() && p()->executing != this && p()->buffs.infernal_bolt->check() ) return false; + if ( affliction() && p()->talents.malefic_grasp.ok() && p()->warlock_pet_list.darkglares.n_active_pets() > 0 ) + return false; + return warlock_spell_t::ready(); } @@ -880,20 +886,6 @@ using namespace helpers; if ( time_to_execute == 0_ms ) p()->buffs.nightfall->decrement(); - - if ( p()->talents.demonic_calling.ok() ) - p()->buffs.demonic_calling->trigger(); - - if ( demonology() && active_4pc() && rng().roll( p()->rng_settings.empowered_legion_strike.setting_value ) ) - { - auto active_pet = p()->warlock_pet_list.active; - - if ( active_pet && active_pet->pet_type == PET_FELGUARD ) - { - active_pet->buffs.empowered_legion_strike->trigger(); - p()->procs.empowered_legion_strike->occur(); - } - } } void impact( action_state_t* s ) override @@ -902,21 +894,17 @@ using namespace helpers; if ( result_is_hit( s->result ) ) { - if ( p()->talents.shadow_embrace.ok() ) - td( s->target )->debuffs.shadow_embrace->trigger(); - - if ( p()->talents.tormented_crescendo.ok() ) + if ( p()->talents.shard_instability.ok() ) { - if ( crescendo_check( p(), s->target ) && rng().roll( p()->talents.tormented_crescendo->effectN( 1 ).percent() ) ) - { - p()->procs.tormented_crescendo->occur(); - p()->buffs.tormented_crescendo->trigger(); - } + bool success = p()->buffs.shard_instability->trigger(); + + if ( success ) + p()->procs.shard_instability->occur(); } if ( p()->talents.cunning_cruelty.ok() && rng().roll( p()->rng_settings.cunning_cruelty_sb.setting_value ) ) { - p()->procs.shadow_bolt_volley->occur(); + p()->procs.shadowbolt_volley->occur(); volley->execute_on_target( s->target ); } } @@ -937,7 +925,7 @@ using namespace helpers; double m = warlock_spell_t::composite_target_multiplier( t ); if ( p()->talents.withering_bolt.ok() ) - m *= 1.0 + p()->talents.withering_bolt->effectN( 1 ).percent() * std::min( (int)( p()->talents.withering_bolt->effectN( 2 ).base_value() ), p()->get_target_data( t )->count_affliction_dots( !p()->bugs ) ); + m *= 1.0 + p()->talents.withering_bolt->effectN( 1 ).percent() * std::min( ( int )( p()->talents.withering_bolt->effectN( 2 ).base_value() ), p()->get_target_data( t )->count_affliction_dots() ); return m; } @@ -981,8 +969,6 @@ using namespace helpers; { background = true; proc = true; - - triggers.decimation = false; } }; @@ -1090,8 +1076,6 @@ using namespace helpers; affected_by.chaotic_energies = destruction(); - triggers.dimension_ripper = p->talents.dimension_ripper.ok(); - if ( affliction() ) { if ( p->talents.absolute_corruption.ok() ) @@ -1103,7 +1087,8 @@ using namespace helpers; triggers.ravenous_afflictions = p->talents.ravenous_afflictions.ok(); - affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); + affected_by.deaths_embrace = affliction() && p->talents.deaths_embrace.ok(); + affected_by.summon_darkglare = affliction() && p->talents.summon_darkglare.ok(); } } @@ -1154,6 +1139,13 @@ using namespace helpers; d->increment( 1 ); p()->procs.mark_of_perotharn->occur(); } + + if ( p()->hero.devil_fruit.ok() ) + { + // TODO: Is this rppm Devil Fruit working fine? + if ( p()->devil_fruit_rng->trigger() ) + p()->buffs.malevolence->trigger( timespan_t::from_seconds( p()->hero.devil_fruit->effectN( 1 ).base_value() ) ); + } } void trigger_dot( action_state_t* s ) override @@ -1178,25 +1170,16 @@ using namespace helpers; { affected_by.chaotic_energies = destruction(); affected_by.havoc = destruction(); + affected_by.summon_darkglare = affliction() && p->talents.summon_darkglare.ok(); impact_action = new wither_dot_t( p ); add_child( impact_action ); - if ( destruction() ) - { - triggers.decimation = p->talents.decimation.ok() && !dual; - triggers.jackpot_destruction = true; - } - if ( affliction() ) - { affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); - - triggers.jackpot_affliction = true; - } } - wither_t(warlock_t* p, bool havoc, util::string_view options_str ) : wither_t( p, options_str ) + wither_t( warlock_t* p, bool havoc, util::string_view options_str ) : wither_t( p, options_str ) { affected_by.havoc = havoc; } dot_t* get_dot( player_t* t ) override @@ -1223,9 +1206,6 @@ using namespace helpers; background = dual = true; affected_by.chaotic_energies = destruction(); - - triggers.decimation = false; - } double composite_target_multiplier( player_t* target ) const override @@ -1246,7 +1226,7 @@ using namespace helpers; { // Wither stack decrement is done before damage. Relevant for Mark of Xavius talent. // (e.g.) Blackened Soul where 10 to 9 stacks: Mark of Xavius talent bonus damage is calculated on 9 stacks. - if ( td( target )->dots.wither->current_stack() > 1 && !p()->buffs.maintained_withering->check() ) + if ( td( target )->dots.wither->current_stack() > 1 ) td( target )->dots.wither->decrement( 1 ); warlock_spell_t::execute(); @@ -1273,7 +1253,7 @@ using namespace helpers; if ( affliction() && p()->hero.seeds_of_their_demise.ok() && p()->cooldowns.seeds_of_their_demise->up() && rng().roll( p()->rng_settings.seeds_of_their_demise.setting_value ) ) { - p()->buffs.tormented_crescendo->trigger(); + p()->buffs.shard_instability->trigger( -1, buff_t::DEFAULT_VALUE(), 1.0 ); p()->procs.seeds_of_their_demise->occur(); seeds_triggered = true; } @@ -1303,7 +1283,9 @@ using namespace helpers; : warlock_spell_t( "Malevolence", p, p->hero.malevolence, options_str ) { harmful = may_crit = false; + // TODO: Check if these things are still necessary trigger_gcd = p->hero.malevolence_buff->gcd(); + cooldown->duration = p->hero.malevolence_buff->cooldown(); resource_current = RESOURCE_MANA; base_costs[ RESOURCE_MANA ] = p->hero.malevolence_buff->cost( POWER_MANA ); } @@ -1315,19 +1297,6 @@ using namespace helpers; p()->buffs.malevolence->trigger(); helpers::trigger_blackened_soul( p(), true ); - - if ( active_4pc() ) - { - if ( affliction() ) - p()->buffs.tormented_crescendo->trigger( - as( p()->tier.inquisitor_hc_4pc->effectN( 1 ).base_value() ) ); - - if ( destruction() ) - p()->buffs.backdraft->trigger( - as( p()->tier.inquisitor_hc_4pc->effectN( 2 ).base_value() ) ); - - p()->buffs.maintained_withering->trigger(); - } } }; @@ -1345,14 +1314,11 @@ using namespace helpers; struct demonic_soul_t : public warlock_spell_t { - bool demoniacs_fervor; - const double shadowtouched_value = 0.20; - const double shadowtouched_demoniacs_fervor_value = 0.10; - demonic_soul_t( warlock_t* p ) - : warlock_spell_t( "Demonic Soul", p, p->hero.demonic_soul_dmg ), - demoniacs_fervor( false ) + : warlock_spell_t( "Demonic Soul", p, p->hero.demonic_soul_dmg ) { + aoe = -1; + reduced_aoe_targets = 8; background = dual = true; if ( p->hero.soul_anathema.ok() ) @@ -1363,24 +1329,12 @@ using namespace helpers; { double m = warlock_spell_t::composite_da_multiplier( s ); - if ( demoniacs_fervor ) + if ( demonology() && p()->hero.demoniacs_fervor.ok() && s->chain_target == 0 ) m *= 1.0 + p()->hero.demoniacs_fervor->effectN( 1 ).percent(); - return m; - } - - double composite_target_multiplier( player_t* t ) const override - { - double m = warlock_spell_t::composite_target_multiplier( t ); - - // TOCHECK: 2025-07-27 Despite what is listed in spell data, Shadowtouched increases the damage of Demonic Soul (+10% for main target (demoniacs_fervor), +20% for the rest) (bug?) - if ( demonology() && p()->bugs && p()->talents.shadowtouched.ok() && td( t )->debuffs.wicked_maw->check() ) - { - if ( demoniacs_fervor ) - m *= 1.0 + shadowtouched_demoniacs_fervor_value; - else - m *= 1.0 + shadowtouched_value; - } + // TODO: Check ingame if the Demonic Fervor talent works for Affliction, it doesn't seem to be working at the moment (bug?) + if ( affliction() && p()->hero.demoniacs_fervor.ok() && !p()->bugs && td( s->target )->dots.unstable_affliction->is_ticking() ) + m *= 1.0 + p()->hero.demoniacs_fervor->effectN( 1 ).percent(); return m; } @@ -1398,8 +1352,6 @@ using namespace helpers; struct wicked_reaping_t : public warlock_spell_t { - const double shadowtouched_value = 0.50; - wicked_reaping_t( warlock_t* p ) : warlock_spell_t( "Wicked Reaping", p, p->hero.wicked_reaping_dmg ) { @@ -1411,383 +1363,287 @@ using namespace helpers; if ( p->hero.soul_anathema.ok() ) impact_action = new soul_anathema_t( p ); } - - double composite_target_multiplier( player_t* t ) const override - { - double m = warlock_spell_t::composite_target_multiplier( t ); - - // TOCHECK: 2025-07-27 Despite what is listed in spell data, Shadowtouched increases the damage of Wicked Reaping (+50%) (bug?) - if ( demonology() && p()->bugs && p()->talents.shadowtouched.ok() && td( t )->debuffs.wicked_maw->check() ) - m *= 1.0 + shadowtouched_value; - - return m; - } }; // Soul Harvester Actions End // Affliction Actions Begin - struct malefic_rapture_t : public warlock_spell_t + struct agony_t : public warlock_spell_t { - struct malefic_touch_t : public warlock_spell_t - { - malefic_touch_t( warlock_t* p ) - : warlock_spell_t( "Malefic Touch", p, p->talents.malefic_touch_proc ) - { - background = dual = true; - } - - double composite_da_multiplier( const action_state_t* s ) const override - { - double m = warlock_spell_t::composite_da_multiplier( s ); - - // TOCHECK: 2025-08-27 In-game a stack gets consumed before MT hits (bug), doing it like this is easier - if ( soul_harvester() && p()->buffs.succulent_soul->check() > ( p()->bugs ? 1 : 0 ) ) - m *= 1.0 + p()->hero.succulent_soul->effectN( 2 ).percent(); - - return m; - } - }; + agony_t* twin = nullptr; + double twin_range = 0.0; - struct malefic_rapture_damage_t : public warlock_spell_t + agony_t( warlock_t* p, util::string_view options_str, bool is_twin = false ) + : warlock_spell_t( "Agony", p, p->talents.agony, options_str ) { - int target_count; - malefic_touch_t* touch; + may_crit = false; - malefic_rapture_damage_t( warlock_t* p ) - : warlock_spell_t ( "Malefic Rapture (hit)", p, p->warlock_base.malefic_rapture_dmg ), - target_count( 0 ) - { - background = dual = true; - // 2025/08/10 Confirmed Malefic Rapture damage will feed Temporal Wound callback from Evoker. If this does not - // work with specific trinkets it will likely need specific handling, or the simc proc system as a whole needs - // an overriding. - // callbacks = false; + triggers.ravenous_afflictions = p->talents.ravenous_afflictions.ok(); - affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); + affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); + affected_by.summon_darkglare = p->talents.summon_darkglare.ok(); - if ( p->talents.malefic_touch.ok() ) - { - touch = new malefic_touch_t( p ); - add_child( touch ); - } + if ( !is_twin ) + { + if ( p->talents.shared_agony.ok() ) + twin = new agony_t( p, options_str, true ); } - - double composite_da_multiplier( const action_state_t* s ) const override + else { - double m = warlock_spell_t::composite_da_multiplier( s ); - - m *= td( s->target )->count_affliction_dots( true ); + background = dual = proc = true; + base_costs[ RESOURCE_MANA ] = 0; + } - if ( p()->talents.focused_malignancy.ok() && ( td( s->target )->dots.unstable_affliction->is_ticking() || td( s->target )->dots.jackpot_ua->is_ticking() ) ) - m *= 1.0 + p()->talents.focused_malignancy->effectN( 1 ).percent(); + twin_range = p->talents.shared_agony->effectN( 2 ).base_value(); + } - if ( p()->talents.cull_the_weak.ok() ) - m *= 1.0 + ( std::min( target_count, as( p()->talents.cull_the_weak->effectN( 2 ).base_value() ) ) * p()->talents.cull_the_weak->effectN( 1 ).percent() ); + void last_tick( dot_t* d ) override + { + if ( p()->get_active_dots( d ) == 1 ) + p()->agony_accumulator = rng().range( 0.0, 0.99 ); - if ( p()->talents.malign_omen.ok() ) - m *= 1.0 + p()->buffs.malign_omen->check_value(); + warlock_spell_t::last_tick( d ); + } - if ( soul_harvester() && p()->buffs.succulent_soul->check() ) - m *= 1.0 + p()->hero.succulent_soul->effectN( 2 ).percent(); + void execute() override + { + warlock_spell_t::execute(); - return m; + if ( twin != nullptr ) + { + const auto& tl = target_list(); + if ( auto twin_target = p()->get_smart_target( tl, &warlock_td_t::dots_t::agony, target, twin_range ) ) + twin->execute_on_target( twin_target ); } - void execute() override + if ( p()->talents.sudden_onset.ok() ) { - int d = td( target )->count_affliction_dots( true ) - 1; - assert( d < as( p()->procs.malefic_rapture.size() ) && "The procs.malefic_rapture array needs to be expanded." ); + int delta = ( int )( p()->talents.sudden_onset->effectN( 2 ).base_value() ) - td( execute_state->target )->dots.agony->current_stack(); - if ( d >= 0 && d < as( p()->procs.malefic_rapture.size() ) ) - p()->procs.malefic_rapture[ d ]->occur(); - - warlock_spell_t::execute(); + if ( delta > 0 ) + td( execute_state->target )->dots.agony->increment( delta ); } + } - void impact( action_state_t* s ) override + void tick( dot_t* d ) override + { + // TODO: TOCHECK: We assume that Malefic Grasp Agony extra ticks do not contribute to the accumulator nor can they recover shards, but we don't know for sure. It would need to be verified. + if ( !debug_cast( d->state )->periodic_hit ) { - warlock_spell_t::impact( s ); - - if ( p()->buffs.malign_omen->check() ) - { - warlock_td_t* tdata = td( s->target ); - timespan_t extension = timespan_t::from_seconds( p()->talents.malign_omen_buff->effectN( 2 ).base_value() ); - - tdata->dots.agony->adjust_duration( extension ); - tdata->dots.corruption->adjust_duration( extension ); - tdata->dots.phantom_singularity->adjust_duration( extension ); - tdata->dots.vile_taint->adjust_duration( extension ); - tdata->dots.unstable_affliction->adjust_duration( extension ); - tdata->dots.jackpot_ua->adjust_duration( extension ); - tdata->dots.soul_rot->adjust_duration( extension ); - tdata->debuffs.haunt->extend_duration( p(), extension ); - tdata->dots.wither->adjust_duration( extension ); - } + // Blizzard has not publicly released the formula for Agony's chance to generate a Soul Shard. + // This set of code is based on results from 500+ Soul Shard sample sizes, and matches in-game + // 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 = p()->rng_settings.agony.setting_value; - if ( p()->talents.malefic_touch.ok() ) - touch->execute_on_target( s->target ); + double active_agonies = p()->get_active_dots( d ); + increment_max *= std::pow( active_agonies, -2.0 / 3.0 ); - if ( soul_harvester() && p()->buffs.succulent_soul->check() ) - { - bool fervor = td( s->target )->dots.unstable_affliction->is_ticking(); + // 2023-09-01: Recent test noted that Creeping Death is once again renormalizing shard generation to be neutral with/without the talent. + if ( p()->talents.creeping_death.ok() ) + increment_max *= 1.0 + p()->talents.creeping_death->effectN( 1 ).percent(); - if ( !p()->bugs ) - fervor |= td( s->target )->dots.jackpot_ua->is_ticking(); + p()->agony_accumulator += rng().range( 0.0, increment_max ); - debug_cast( p()->proc_actions.demonic_soul )->demoniacs_fervor = fervor; - p()->proc_actions.demonic_soul->execute_on_target( s->target ); + if ( p()->agony_accumulator >= 1 ) + { + p()->resource_gain( RESOURCE_SOUL_SHARD, 1.0, p()->gains.agony ); + p()->agony_accumulator -= 1.0; } } - }; - - malefic_rapture_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Malefic Rapture", p, p->warlock_base.malefic_rapture, options_str ) - { - aoe = -1; - triggers.jackpot_affliction = true; + warlock_spell_t::tick( d ); - impact_action = new malefic_rapture_damage_t( p ); - add_child( impact_action ); + // TOCHECK: Malefic Grasp ticks also appear to be increasing their number of stacks + td( d->state->target )->dots.agony->increment( 1 ); } + }; - bool ready() override + struct unstable_affliction_t : public warlock_spell_t + { + unstable_affliction_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Unstable Affliction", p, p->talents.unstable_affliction, options_str ) { - if ( !warlock_spell_t::ready() ) - return false; + triggers.ravenous_afflictions = p->talents.ravenous_afflictions.ok(); - target_cache.is_valid = false; - return target_list().size() > 0; + affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); + affected_by.summon_darkglare = p->talents.summon_darkglare.ok(); } void execute() override { + // TODO: Check ingame if UA applied by Fatal Echoes procs/consumes any of these effects: Cull the Weak, Shard Instability, and Succulent Soul + // TODO: Currently in simc all these effects are being processed/consumed by UA applied from Fatal Echoes; change if necessary + warlock_spell_t::execute(); - if ( active_4pc() ) + if ( p()->talents.cull_the_weak.ok() ) + p()->cooldowns.dark_harvest->adjust( -p()->talents.cull_the_weak->effectN( 1 ).time_value() ); + + // NOTE: Seems that Shard Instability buff takes effect (and is consumed) even if it is obtained while Unstable Affliction is being cast + p()->buffs.shard_instability->decrement(); + + if ( soul_harvester() && p()->buffs.succulent_soul->check() ) { - bool success = p()->buffs.umbral_lattice->trigger(); + p()->buffs.succulent_soul->decrement(); - if ( success ) - p()->procs.umbral_lattice->occur(); - } + if ( p()->hero.manifested_avarice.ok() && rng().roll( p()->rng_settings.manifested_avarice.setting_value ) ) + { + // TODO: Needed some ingame checks. Is it possible to proc a new Demonic Soul spawn while another Demonic Soul is active? What happens in that case? What happens to the buff and the mastery effect in that case? ​​(Does it stack? Does the duration change?) + p()->warlock_pet_list.demonic_souls.spawn( p()->hero.manifested_avarice_summon->duration() ); + p()->buffs.manifested_demonic_soul->trigger(); + } - p()->buffs.tormented_crescendo->decrement(); - p()->buffs.malign_omen->decrement(); + p()->proc_actions.demonic_soul->execute_on_target( target ); + } } void impact( action_state_t* s ) override { - debug_cast( impact_action )->target_count = as( s->n_targets ); + auto dot = td( s->target )->dots.unstable_affliction; + + if ( p()->talents.cascading_calamity.ok() && dot->is_ticking() ) + p()->buffs.cascading_calamity->trigger(); + + // timespan_t dot_new_last_duration = dot->time_to_next_full_tick() + composite_dot_duration( s ); // TODO: Alternative that takes into account the extra tick on refresh; which is more appropriate? + timespan_t dot_new_last_duration = composite_dot_duration( s ); + // NOTE: If Blizzard change the UA DoT Behavior, this need to be redesigned + assert( dot_behavior == DOT_REFRESH_DURATION && "UA DoT Behavior has changed." ); warlock_spell_t::impact( s ); - if ( soul_harvester() && p()->buffs.succulent_soul->check() ) + // We need to handle the UA stacks/duration manually + if ( result_is_hit( s->result ) ) { - bool primary = ( s->chain_target == 0 ); + // NOTE: The spell data is using DOT_REFRESH_DURATION, which should add the time-until-the-next-full-tick to the total duration + // However, ingame, the duration does not add the last tick and only refresh the dot to the total duration (always 8 seconds) + dot_t* dot = td( s->target )->dots.unstable_affliction; + if ( dot->duration() > dot_new_last_duration ) + dot->adjust_duration(dot_new_last_duration - dot->duration() ); - if ( primary ) - make_event( *sim, 1_ms, [ this ] { p()->buffs.succulent_soul->decrement(); } ); + make_event( *sim, p(), dot, dot_new_last_duration ); } } - size_t available_targets( std::vector& tl ) const override + void last_tick( dot_t* d ) override { - warlock_spell_t::available_targets( tl ); - - range::erase_remove( tl, [ this ]( player_t* t ){ return td( t )->count_affliction_dots( true ) == 0; } ); + int stacks = d->current_stack(); - return tl.size(); - } - }; + warlock_spell_t::last_tick( d ); - struct perpetual_unstability_t : public warlock_spell_t - { - perpetual_unstability_t( warlock_t* p ) - : warlock_spell_t( "Perpetual Unstability", p, p->talents.perpetual_unstability_proc ) - { background = dual = true; } - }; - - struct unstable_affliction_t : public warlock_spell_t - { - perpetual_unstability_t* perpetual_unstability; - - unstable_affliction_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Unstable Affliction", p, p->talents.unstable_affliction, options_str ) - { - triggers.ravenous_afflictions = p->talents.ravenous_afflictions.ok(); - - affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); - - if ( p->talents.perpetual_unstability.ok() ) + if ( p()->talents.fatal_echoes.ok() && !d->state->target->is_sleeping() ) { - perpetual_unstability = new perpetual_unstability_t( p ); - add_child( perpetual_unstability ); + for ( int i = 0; i < stacks; i++ ) + { + if ( rng().roll( p()->talents.fatal_echoes->effectN( 1 ).percent() ) ) + { + p()->procs.fatal_echoes->occur(); + make_event( sim, 1_ms, [ this, t = d->state->target ] { + this->set_target( t ); + auto tmp_cost = this->base_costs[ RESOURCE_SOUL_SHARD ]; + this->base_costs[ RESOURCE_SOUL_SHARD ] = 0; + this->execute(); + this->base_costs[ RESOURCE_SOUL_SHARD ] = tmp_cost; + } ); + } + } } } - - void execute() override - { - if ( p()->ua_target && p()->ua_target != target ) - td( p()->ua_target )->dots.unstable_affliction->cancel(); - - if ( active_4pc() && td( target )->dots.jackpot_ua->is_ticking() ) - td( target )->dots.jackpot_ua->cancel(); - - p()->ua_target = target; - - warlock_spell_t::execute(); - } - - void impact( action_state_t* s ) override - { - bool ticking = td( s->target )->dots.unstable_affliction->is_ticking(); - timespan_t remains = td( s->target )->dots.unstable_affliction->remains(); - - warlock_spell_t::impact( s ); - - if ( p()->talents.perpetual_unstability.ok() && ticking && remains < timespan_t::from_seconds( p()->talents.perpetual_unstability->effectN( 1 ).base_value() ) ) - perpetual_unstability->execute_on_target( s->target ); - } - - void last_tick( dot_t* d ) override - { - warlock_spell_t::last_tick( d ); - - p()->ua_target = nullptr; - } - }; - - struct jackpot_unstable_affliction_t : public warlock_spell_t - { - jackpot_unstable_affliction_t( warlock_t* p ) - : warlock_spell_t( "Unstable Affliction (Jackpot!)", p, p->tier.jackpot_ua ) - { - background = dual = true; - - // TOCHECK: Interaction with Ravenous Afflictions, Death's Embrace - } - }; - - struct volatile_agony_t : public warlock_spell_t - { - volatile_agony_t( warlock_t* p ) - : warlock_spell_t( "Volatile Agony", p, p->talents.volatile_agony_aoe ) - { - background = dual = true; - aoe = -1; - - reduced_aoe_targets = p->talents.volatile_agony->effectN( 2 ).base_value(); - } }; - struct agony_t : public warlock_spell_t + struct seed_of_corruption_t : public warlock_spell_t { - volatile_agony_t* vol_ag; - - agony_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Agony", p, p->warlock_base.agony, options_str ) + struct seed_of_corruption_aoe_t : public warlock_spell_t { - may_crit = false; - - triggers.ravenous_afflictions = p->talents.ravenous_afflictions.ok(); - - affected_by.deaths_embrace = p->talents.deaths_embrace.ok(); + action_t* applied_dot; + double effectiveness; - if ( p->talents.volatile_agony.ok() ) + seed_of_corruption_aoe_t( warlock_t* p ) + : warlock_spell_t( "Seed of Corruption (AoE)", p, p->talents.seed_of_corruption_aoe ), + effectiveness( 1.0 ) { - vol_ag = new volatile_agony_t( p ); - add_child( vol_ag ); - } - } - - void last_tick ( dot_t* d ) override - { - if ( p()->get_active_dots( d ) == 1 ) - p()->agony_accumulator = rng().range( 0.0, 0.99 ); - - warlock_spell_t::last_tick( d ); - } + aoe = -1; + background = dual = true; - void execute() override - { - warlock_spell_t::execute(); + affected_by.deaths_embrace = true; - if ( p()->talents.writhe_in_agony.ok() ) - { - int delta = (int)( p()->talents.writhe_in_agony->effectN( 3 ).base_value() ) - td( execute_state->target )->dots.agony->current_stack(); + if ( p->hero.wither.ok() ) + applied_dot = new wither_t( p, "" ); + else + applied_dot = new corruption_t( p, "", true ); - if ( delta > 0 ) - td( execute_state->target )->dots.agony->increment( delta ); + applied_dot->background = true; + applied_dot->dual = true; + applied_dot->base_costs[ RESOURCE_MANA ] = 0; + applied_dot->base_dd_multiplier = 0.0; } - } - void impact( action_state_t* s ) override - { - if ( p()->talents.volatile_agony.ok() ) + struct seed_of_corruption_aoe_state_t : public warlock_spell_state_t { - if( td(s->target)->dots.agony->is_ticking() && td( s->target )->dots.agony->remains() < timespan_t::from_seconds( p()->talents.volatile_agony->effectN( 1 ).base_value() ) ) - vol_ag->execute_on_target( s->target ); - } + double effectiveness; - warlock_spell_t::impact( s ); - } - - void tick( dot_t* d ) override - { - // Blizzard has not publicly released the formula for Agony's chance to generate a Soul Shard. - // This set of code is based on results from 500+ Soul Shard sample sizes, and matches in-game - // 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 = p()->rng_settings.agony.setting_value; + seed_of_corruption_aoe_state_t( action_t* action, player_t* target ) + : warlock_spell_state_t( action, target ), + effectiveness( 1.0 ) + { } - double active_agonies = p()->get_active_dots( d ); - increment_max *= std::pow( active_agonies, -2.0 / 3.0 ); + void initialize() override + { + warlock_spell_state_t::initialize(); + effectiveness = 1.0; + } - // 2023-09-01: Recent test noted that Creeping Death is once again renormalizing shard generation to be neutral with/without the talent. - if ( p()->talents.creeping_death.ok() ) - increment_max *= 1.0 + p()->talents.creeping_death->effectN( 1 ).percent(); + std::ostringstream& debug_str( std::ostringstream& s ) override + { + warlock_spell_state_t::debug_str( s ); + s << " effectiveness=" << effectiveness; + return s; + } - if ( p()->talents.relinquished.ok() ) - increment_max *= 1.0 + p()->talents.relinquished->effectN( 1 ).percent(); + void copy_state( const action_state_t* s ) override + { + warlock_spell_state_t::copy_state( s ); + effectiveness = debug_cast( s )->effectiveness; + } + }; - p()->agony_accumulator += rng().range( 0.0, increment_max ); + action_state_t* new_state() override + { return new seed_of_corruption_aoe_state_t( this, target ); } - if ( p()->agony_accumulator >= 1 ) + void snapshot_state( action_state_t* s, result_amount_type rt ) override { - p()->resource_gain( RESOURCE_SOUL_SHARD, 1.0, p()->gains.agony ); - p()->agony_accumulator -= 1.0; + debug_cast( s )->effectiveness = effectiveness; + warlock_spell_t::snapshot_state( s, rt ); } - warlock_spell_t::tick( d ); - - td( d->state->target )->dots.agony->increment( 1 ); - } - }; - - struct seed_of_corruption_t : public warlock_spell_t - { - struct seed_of_corruption_aoe_t : public warlock_spell_t - { - action_t* applied_dot; - - seed_of_corruption_aoe_t( warlock_t* p ) - : warlock_spell_t( "Seed of Corruption (AoE)", p, p->talents.seed_of_corruption_aoe ) + double composite_da_multiplier( const action_state_t* s ) const override { - aoe = -1; - background = dual = true; + double m = warlock_spell_t::composite_da_multiplier( s ); - if ( p->hero.wither.ok() ) - applied_dot = new wither_t( p, "" ); - else - applied_dot = new corruption_t( p, "", true ); + // TODO: I'm not sure if this will work ingame in this way along with show the seeds + // TODO: In the ingame tests, it seemed to always apply to the same target in all explosions (the main target of the main seed) + if ( p()->talents.patient_zero.ok() ) + { + if ( s->chain_target == 0 ) + m *= 1.0 + p()->talents.patient_zero->effectN( 1 ).percent(); + } - applied_dot->background = true; - applied_dot->dual = true; - applied_dot->base_costs[ RESOURCE_MANA ] = 0; - applied_dot->base_dd_multiplier = 0.0; + // TODO: Possible bug: the 50% reduced effectiveness of additional seeds seems to only affect the host + if ( p()->talents.sow_the_seeds.ok() ) + { + if ( p()->bugs ) + { + if ( s->chain_target == 0 ) + m *= debug_cast( s )->effectiveness; + } + else + { + m *= debug_cast( s )->effectiveness; + } + } + + return m; } void impact( action_state_t* s ) override @@ -1803,7 +1659,7 @@ using namespace helpers; tdata->soc_threshold = 0; tdata->dots.seed_of_corruption->cancel(); } - + applied_dot->execute_on_target( s->target ); } } @@ -1820,6 +1676,11 @@ using namespace helpers; base_tick_time = dot_duration; hasted_ticks = false; + affected_by.deaths_embrace = true; + + if ( p->talents.sow_the_seeds.ok() ) + aoe = 1 + as( p->talents.sow_the_seeds->effectN( 1 ).base_value() ); + add_child( explosion ); } @@ -1835,7 +1696,8 @@ using namespace helpers; // Targeting behavior appears to be as follows: // 1. If any targets have no current seed (in flight or ticking), they are valid - // 2. If no targets are valid according to the above, all targets are instead valid (will refresh DoT on existing target(s) instead) + // 2. With Sow the Seeds, if at least one target is valid, it will only hit valid targets + // 3. If no targets are valid according to the above, all targets are instead valid (will refresh DoT on existing target(s) instead) bool valid_target = false; for ( auto t : tl ) { @@ -1856,10 +1718,51 @@ using namespace helpers; return tl.size(); } + void execute() override + { + warlock_spell_t::execute(); + + p()->buffs.seed_of_corruption_is_out_dnt->trigger(); + + if ( time_to_execute == 0_ms && soul_harvester() && p()->buffs.nightfall->check() ) + { + if ( p()->hero.wicked_reaping.ok() ) + p()->proc_actions.wicked_reaping->execute_on_target( target ); + + if ( p()->hero.quietus.ok() && p()->hero.shared_fate.ok() ) + p()->proc_actions.shared_fate->execute_on_target( target ); + + if ( p()->hero.quietus.ok() && p()->hero.feast_of_souls.ok() && rng().roll( p()->rng_settings.feast_of_souls_aff.setting_value ) ) + p()->feast_of_souls_gain(); + } + + if ( time_to_execute == 0_ms ) + p()->buffs.nightfall->decrement(); + + if ( p()->talents.cull_the_weak.ok() ) + p()->cooldowns.dark_harvest->adjust( -p()->talents.cull_the_weak->effectN( 1 ).time_value() ); + + if ( soul_harvester() && p()->buffs.succulent_soul->check() ) + { + p()->buffs.succulent_soul->decrement(); + + if ( p()->hero.manifested_avarice.ok() && rng().roll( p()->rng_settings.manifested_avarice.setting_value ) ) + { + // TODO: Needed some ingame checks. Is it possible to proc a new Demonic Soul spawn while another Demonic Soul is active? What happens in that case? What happens to the buff and the mastery effect in that case? ​​(Does it stack? Does the duration change?) + p()->warlock_pet_list.demonic_souls.spawn( p()->hero.manifested_avarice_summon->duration() ); + p()->buffs.manifested_demonic_soul->trigger(); + } + + p()->proc_actions.demonic_soul->execute_on_target( target ); + } + } + void impact( action_state_t* s ) override { if ( result_is_hit( s->result ) ) + { td( s->target )->soc_threshold = s->composite_spell_power() * p()->talents.seed_of_corruption->effectN( 1 ).percent(); + } warlock_spell_t::impact( s ); } @@ -1878,35 +1781,37 @@ using namespace helpers; void last_tick( dot_t* d ) override { explosion->set_target( d->target ); + // TODO: Test this; that the state is passing through correctly + explosion->effectiveness = ( aoe > 1 && d->state->chain_target != 0 ) ? p()->talents.sow_the_seeds->effectN( 2 ).percent() : 1.0; explosion->schedule_execute(); warlock_spell_t::last_tick( d ); } }; - struct drain_soul_t : public warlock_spell_t + struct malefic_grasp_t : public warlock_spell_t { - struct drain_soul_state_t : public action_state_t + struct malefic_grasp_state_t : public warlock_spell_state_t { double tick_time_multiplier; double td_multiplier; - drain_soul_state_t( action_t* action, player_t* target ) - : action_state_t( action, target ), + malefic_grasp_state_t( action_t* action, player_t* target ) + : warlock_spell_state_t( action, target ), tick_time_multiplier( 1.0 ), td_multiplier( 1.0 ) { } void initialize() override { - action_state_t::initialize(); + warlock_spell_state_t::initialize(); tick_time_multiplier = 1.0; td_multiplier = 1.0; } std::ostringstream& debug_str( std::ostringstream& s ) override { - action_state_t::debug_str( s ); + warlock_spell_state_t::debug_str( s ); s << " tick_time_multiplier=" << tick_time_multiplier; s << " td_multiplier=" << td_multiplier; return s; @@ -1914,33 +1819,37 @@ using namespace helpers; void copy_state( const action_state_t* s ) override { - action_state_t::copy_state( s ); - tick_time_multiplier = debug_cast( s )->tick_time_multiplier; - td_multiplier = debug_cast( s )->td_multiplier; + warlock_spell_state_t::copy_state( s ); + tick_time_multiplier = debug_cast( s )->tick_time_multiplier; + td_multiplier = debug_cast( s )->td_multiplier; } }; - shadow_bolt_volley_t* volley; - - drain_soul_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Drain Soul", p, p->talents.drain_soul.ok() ? p->talents.drain_soul_dot : spell_data_t::not_found(), options_str ) + malefic_grasp_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Malefic Grasp", p, p->talents.malefic_grasp.ok() ? p->talents.malefic_grasp_2 : spell_data_t::not_found(), options_str ) { + // TODO: Check ingame which effects that affect Shadow Bolt / Drain Soul also affect Malefic Grasp (such as Cunning Cruelty, Withering Bolt, Shard Instability or Death's Embrace) channeled = true; - - if ( p->talents.cunning_cruelty.ok() ) - volley = new shadow_bolt_volley_t( p ); } action_state_t* new_state() override - { return new drain_soul_state_t( this, target ); } + { return new malefic_grasp_state_t( this, target ); } + + bool ready() override + { + if ( !p()->talents.malefic_grasp.ok() || p()->warlock_pet_list.darkglares.n_active_pets() <= 0 ) + return false; + + return warlock_spell_t::ready(); + } void snapshot_state( action_state_t* s, result_amount_type rt ) override { - // 11.1 onward, nightfall has not buffed hellcaller drain soul dmg (bug) + // TODO: Is this bug still present in Midnight? and it is affecting also to Malefic Grasp? double mul = ( p()->bugs && hellcaller() ) ? 0.0 : ( p()->talents.nightfall_buff->effectN( 2 ).percent() ); - debug_cast( s )->tick_time_multiplier = 1.0 + ( p()->buffs.nightfall->check() ? p()->talents.nightfall_buff->effectN( 3 ).percent() : 0.0 ); - debug_cast( s )->td_multiplier = 1.0 + ( p()->buffs.nightfall->check() ? mul : 0.0 ); + debug_cast( s )->tick_time_multiplier = 1.0 + ( p()->buffs.nightfall->check() ? p()->talents.nightfall_buff->effectN( 3 ).percent() : 0.0 ); + debug_cast( s )->td_multiplier = 1.0 + ( p()->buffs.nightfall->check() ? mul : 0.0 ); warlock_spell_t::snapshot_state( s, rt ); } @@ -1948,7 +1857,7 @@ using namespace helpers; { auto mul = warlock_spell_t::tick_time_pct_multiplier( s ); - mul *= debug_cast( s )->tick_time_multiplier; + mul *= debug_cast( s )->tick_time_multiplier; return mul; } @@ -1977,174 +1886,167 @@ using namespace helpers; if ( result_is_hit( d->state->result ) ) { - if ( p()->talents.shadow_embrace.ok() ) - td( d->target )->debuffs.shadow_embrace->trigger(); - - if ( p()->talents.tormented_crescendo.ok() ) - { - if ( crescendo_check( p(), d->target ) && rng().roll( p()->talents.tormented_crescendo->effectN( 2 ).percent() ) ) - { - p()->procs.tormented_crescendo->occur(); - p()->buffs.tormented_crescendo->trigger(); - } - } + warlock_td_t* tdata = td( d->state->target ); + if ( !tdata ) + return; - 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 ); - } + // TODO: check if this is working properly + double multiplier = p()->talents.malefic_grasp_2->effectN( 2 ).percent(); + trigger_extra_tick( tdata->dots.agony, multiplier ); + trigger_extra_tick( tdata->dots.corruption, multiplier ); + trigger_extra_tick( tdata->dots.unstable_affliction, multiplier ); } } - double composite_target_multiplier( player_t* t ) const override - { - double m = warlock_spell_t::composite_target_multiplier( t ); - - if ( t->health_percentage() < p()->talents.drain_soul_dot->effectN( 3 ).base_value() ) - m *= 1.0 + p()->talents.drain_soul_dot->effectN( 2 ).percent(); - - if ( p()->talents.withering_bolt.ok() ) - m *= 1.0 + p()->talents.withering_bolt->effectN( 1 ).percent() * std::min( (int)( p()->talents.withering_bolt->effectN( 2 ).base_value() ), td( t )->count_affliction_dots( !p()->bugs ) ); - - return m; - } - double composite_ta_multiplier( const action_state_t* s ) const override { double m = warlock_spell_t::composite_ta_multiplier( s ); - m *= debug_cast( s )->td_multiplier; + m *= debug_cast( s )->td_multiplier; return m; } }; - struct vile_taint_t : public warlock_spell_t + struct drain_soul_t : public warlock_spell_t { - struct vile_taint_dot_t : public warlock_spell_t + struct drain_soul_state_t : public warlock_spell_state_t { - vile_taint_dot_t( warlock_t* p ) - : warlock_spell_t( "Vile Taint (DoT)", p, p->talents.vile_taint_dot ) + double tick_time_multiplier; + double td_multiplier; + + drain_soul_state_t( action_t* action, player_t* target ) + : warlock_spell_state_t( action, target ), + tick_time_multiplier( 1.0 ), + td_multiplier( 1.0 ) + { } + + void initialize() override + { + warlock_spell_state_t::initialize(); + tick_time_multiplier = 1.0; + td_multiplier = 1.0; + } + + std::ostringstream& debug_str( std::ostringstream& s ) override { - tick_zero = background = true; - execute_action = new agony_t( p, "" ); - execute_action->background = true; - execute_action->dual = true; - execute_action->base_costs[ RESOURCE_MANA ] = 0.0; + warlock_spell_state_t::debug_str( s ); + s << " tick_time_multiplier=" << tick_time_multiplier; + s << " td_multiplier=" << td_multiplier; + return s; + } + + void copy_state( const action_state_t* s ) override + { + warlock_spell_state_t::copy_state( s ); + tick_time_multiplier = debug_cast( s )->tick_time_multiplier; + td_multiplier = debug_cast( s )->td_multiplier; } }; - - vile_taint_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Vile Taint", p, p->talents.vile_taint, options_str ) + + shadowbolt_volley_t* volley; + + drain_soul_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Drain Soul", p, p->talents.drain_soul.ok() ? p->talents.drain_soul_dot : spell_data_t::not_found(), options_str ) { - impact_action = new vile_taint_dot_t( p ); - add_child( impact_action ); + channeled = true; + + affected_by.deaths_embrace = true; + + if ( p->talents.cunning_cruelty.ok() ) + volley = new shadowbolt_volley_t( p ); } - std::vector& target_list() const override + action_state_t* new_state() override + { return new drain_soul_state_t( this, target ); } + + void snapshot_state( action_state_t* s, result_amount_type rt ) override { - auto& tl = warlock_spell_t::target_list(); - auto original_size = tl.size(); + // 11.1 onward, nightfall has not buffed hellcaller drain soul dmg (bug) // TODO: Is this still true in Midnight? + double mul = ( p()->bugs && hellcaller() ) ? 0.0 : ( p()->talents.nightfall_buff->effectN( 2 ).percent() ); - // if target_list is bigger than dot cap shuffle the list - if ( as( tl.size() ) > aoe ) - { - // randomize targets - rng().shuffle( tl.begin(), tl.end() ); + debug_cast( s )->tick_time_multiplier = 1.0 + ( p()->buffs.nightfall->check() ? p()->talents.nightfall_buff->effectN( 3 ).percent() : 0.0 ); + debug_cast( s )->td_multiplier = 1.0 + ( p()->buffs.nightfall->check() ? mul : 0.0 ); + warlock_spell_t::snapshot_state( s, rt ); + } - // sort targets without Agony to the front - std::sort( tl.begin(), tl.end(), [ this ]( player_t* l, player_t* r ) { - warlock_td_t* tdl = p()->get_target_data( l ); - warlock_td_t* tdr = p()->get_target_data( r ); + double tick_time_pct_multiplier( const action_state_t* s ) const override + { + auto mul = warlock_spell_t::tick_time_pct_multiplier( s ); - return !tdl->dots.agony->is_ticking() && tdr->dots.agony->is_ticking(); - - } ); + mul *= debug_cast( s )->tick_time_multiplier; - // resize to dot target cap - tl.resize( aoe ); - } + return mul; + } - player->sim->print_debug( "{} vile taint dots {} targets of the available {}.", p()->name(), tl.size(), - original_size ); + bool ready() override + { + if ( p()->talents.malefic_grasp.ok() && p()->warlock_pet_list.darkglares.n_active_pets() > 0 ) + return false; - return tl; + return warlock_spell_t::ready(); } + void execute() override { - target_cache.is_valid = false; warlock_spell_t::execute(); - } - void impact( action_state_t* s ) override - { - bool fresh_agony = !td( s->target )->dots.agony->is_ticking(); + if ( soul_harvester() && p()->buffs.nightfall->check() ) + { + if ( p()->hero.wicked_reaping.ok() ) + p()->proc_actions.wicked_reaping->execute_on_target( target ); - warlock_spell_t::impact( s ); + if ( p()->hero.quietus.ok() && p()->hero.shared_fate.ok() ) + p()->proc_actions.shared_fate->execute_on_target( target ); - if ( p()->talents.infirmity.ok() && fresh_agony ) - td( s->target )->dots.agony->increment( as( p()->talents.infirmity->effectN( 1 ).base_value() ) ); + if ( p()->hero.quietus.ok() && p()->hero.feast_of_souls.ok() && rng().roll( p()->rng_settings.feast_of_souls_aff.setting_value ) ) + p()->feast_of_souls_gain(); + } + p()->buffs.nightfall->decrement(); } - }; - struct phantom_singularity_t : public warlock_spell_t - { - struct phantom_singularity_tick_t : public warlock_spell_t + void tick( dot_t* d ) override { - phantom_singularity_tick_t( warlock_t* p ) - : warlock_spell_t( "Phantom Singularity (tick)", p, p->talents.phantom_singularity_tick ) - { - background = dual = true; - may_miss = false; - aoe = -1; - } + warlock_spell_t::tick( d ); - void impact( action_state_t* s ) override + if ( result_is_hit( d->state->result ) ) { - warlock_spell_t::impact( s ); - - if ( p()->talents.infirmity.ok() && !td( s->target )->debuffs.infirmity->check() ) - td( s->target )->debuffs.infirmity->trigger(); - } - }; + if ( p()->talents.shard_instability.ok() ) + { + bool success = p()->buffs.shard_instability->trigger(); - phantom_singularity_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Phantom Singularity", p, p->talents.phantom_singularity, options_str ) - { - callbacks = false; - hasted_ticks = true; - tick_action = new phantom_singularity_tick_t( p ); + if ( success ) + p()->procs.shard_instability->occur(); + } - spell_power_mod.tick = 0; + if ( p()->talents.cunning_cruelty.ok() && rng().roll( p()->rng_settings.cunning_cruelty_ds.setting_value ) ) + { + p()->procs.shadowbolt_volley->occur(); + volley->execute_on_target( d->target ); + } + } } - void init() override + double composite_target_multiplier( player_t* t ) const override { - warlock_spell_t::init(); + double m = warlock_spell_t::composite_target_multiplier( t ); - update_flags &= ~STATE_HASTE; - } + if ( t->health_percentage() < p()->talents.drain_soul_dot->effectN( 3 ).base_value() ) + m *= 1.0 + p()->talents.drain_soul_dot->effectN( 2 ).percent(); - void impact( action_state_t* s ) override - { - warlock_spell_t::impact( s ); + if ( p()->talents.withering_bolt.ok() ) + m *= 1.0 + p()->talents.withering_bolt->effectN( 1 ).percent() * std::min( ( int )( p()->talents.withering_bolt->effectN( 2 ).base_value() ), td( t )->count_affliction_dots() ); - if ( p()->talents.infirmity.ok() ) - td( s->target )->debuffs.infirmity->trigger(); + return m; } - void last_tick( dot_t* d ) override + double composite_ta_multiplier( const action_state_t* s ) const override { - warlock_spell_t::last_tick( d ); + double m = warlock_spell_t::composite_ta_multiplier( s ); - for ( auto t : p()->sim->target_non_sleeping_list ) - { - if ( !td( t ) ) - continue; + m *= debug_cast( s )->td_multiplier; - make_event( *sim, 0_ms, [ this, t ] { td( t )->debuffs.infirmity->expire(); } ); - } + return m; } }; @@ -2152,9 +2054,7 @@ using namespace helpers; { haunt_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Haunt", p, p->talents.haunt, options_str ) - { - triggers.jackpot_affliction = true; - } + { } void execute() override { @@ -2170,151 +2070,104 @@ using namespace helpers; if ( result_is_hit( s->result ) ) { + // TODO: Only one target can have haunt debuff. Is this exclusivity managed here in some way? td( s->target )->debuffs.haunt->trigger(); - - if ( p()->talents.improved_haunt.ok() ) - td( s->target )->debuffs.shadow_embrace->trigger(); } } }; - - struct summon_darkglare_t : public warlock_spell_t { - struct malevolent_visionary_t : public warlock_spell_t - { - malevolent_visionary_t( warlock_t* p ) - : warlock_spell_t( "Malevolent Visionary", p, p->talents.malevolent_visionary_blast ) - { background = dual = true; } - }; - - malevolent_visionary_t* mal_vis; - summon_darkglare_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Summon Darkglare", p, p->talents.summon_darkglare, options_str ) { harmful = callbacks = true; // Set to true because of 10.1 class trinket may_crit = may_miss = false; - - if ( p->talents.malevolent_visionary.ok() ) - { - mal_vis = new malevolent_visionary_t( p ); - add_child( mal_vis ); - } } void execute() override { warlock_spell_t::execute(); - if ( active_2pc() ) - { - p()->buffs.jackpot_affliction->execute(); - p()->buffs.jackpot_affliction->predict(); - p()->procs.jackpot_affliction->occur(); - helpers::trigger_jackpot_ua( p() ); - } - p()->warlock_pet_list.darkglares.spawn( p()->talents.summon_darkglare->duration() ); - - timespan_t darkglare_extension = timespan_t::from_seconds( p()->talents.summon_darkglare->effectN( 2 ).base_value() ); - - darkglare_extension_helper( darkglare_extension ); } + }; - void darkglare_extension_helper( timespan_t darkglare_extension ) + struct dark_harvest_t : public warlock_spell_t + { + struct dark_harvest_dmg_t : public warlock_spell_t { - for ( const auto target : p()->sim->target_non_sleeping_list ) + dark_harvest_dmg_t( warlock_t* p ) + : warlock_spell_t( "Dark Harvest (tick)", p, p->talents.dark_harvest_dmg ) { - warlock_td_t* td = p()->get_target_data( target ); - if ( !td ) - continue; - - td->dots.agony->adjust_duration( darkglare_extension ); - td->dots.corruption->adjust_duration( darkglare_extension ); - td->dots.phantom_singularity->adjust_duration( darkglare_extension ); - td->dots.vile_taint->adjust_duration( darkglare_extension ); - td->dots.unstable_affliction->adjust_duration( darkglare_extension ); - - if ( !p()->bugs ) - td->dots.jackpot_ua->adjust_duration( darkglare_extension ); - - td->dots.soul_rot->adjust_duration( darkglare_extension ); - td->dots.wither->adjust_duration( darkglare_extension ); - - if ( p()->talents.malevolent_visionary.ok() && td->count_affliction_dots( !p()->bugs ) > 0 ) - mal_vis->execute_on_target( target ); + background = dual = true; } - } - }; + }; - struct soul_rot_t : public warlock_spell_t - { - soul_rot_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Soul Rot", p, p->talents.soul_rot, options_str ) - { aoe = 1 + as( p->talents.soul_rot->effectN( 3 ).base_value() ); } + dark_harvest_dmg_t* dark_harvest_dmg; - void execute() override + dark_harvest_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Dark Harvest", p, p->talents.dark_harvest, options_str ), + dark_harvest_dmg( new dark_harvest_dmg_t( p ) ) { - warlock_spell_t::execute(); + aoe = -1; // TODO: Is this needed? + add_child( dark_harvest_dmg ); + } - if ( p()->talents.malign_omen.ok() ) - p()->buffs.malign_omen->trigger( as( p()->talents.malign_omen->effectN( 2 ).base_value() ) ); + std::vector& target_list() const override + { + target_cache.list = warlock_spell_t::target_list(); - if ( soul_harvester() && p()->hero.shadow_of_death.ok() ) + size_t i = target_cache.list.size(); + while ( i > 0 ) { - // TOCHECK: 2025-08-27 The shards gained by Shadow of Death can also proc another Succulent Soul each (bug?) - if ( p()->bugs ) - p()->resource_gain( RESOURCE_SOUL_SHARD, p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0, p()->gains.shadow_of_death ); - else - p()->player_t::resource_gain( RESOURCE_SOUL_SHARD, p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0, p()->gains.shadow_of_death ); - - p()->buffs.succulent_soul->trigger( as( p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0 ) ); - for ( int i = 0; i < as( p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0 ); i++) - p()->procs.succulent_soul->occur(); + i--; - if ( active_2pc() && p()->tier.rampaging_demonic_soul->ok() ) - { - p()->warlock_pet_list.demonic_souls.spawn( p()->tier.rampaging_demonic_soul->duration() ); - } + if ( !td( target_cache.list[ i ] )->dots.corruption->is_ticking() && !td( target_cache.list[ i ] )->dots.wither->is_ticking() + && !td( target_cache.list[ i ] )->dots.agony->is_ticking() && !td( target_cache.list[ i ] )->dots.unstable_affliction->is_ticking() ) + target_cache.list.erase( target_cache.list.begin() + i ); } + + return target_cache.list; } - void impact( action_state_t* s ) override + bool ready() override { - warlock_spell_t::impact( s ); + if ( !warlock_spell_t::ready() ) + return false; - if ( p()->talents.dark_harvest.ok() ) - { - p()->buffs.dark_harvest->trigger(); - } + target_cache.is_valid = false; + return target_list().size() > 0; } - double composite_ta_multiplier( const action_state_t* s ) const override + void tick( dot_t* d ) override { - double m = warlock_spell_t::composite_ta_multiplier( s ); + warlock_spell_t::tick( d ); - if ( s->chain_target == 0 && aoe > 1 ) - m *= 1.0 + p()->talents.soul_rot->effectN( 4 ).base_value() / 10.0; // Primary target takes increased damage + auto t = d->state->target; + if ( td( t )->dots.corruption->is_ticking() || td( t )->dots.wither->is_ticking() || td( t )->dots.agony->is_ticking() || td( t )->dots.unstable_affliction->is_ticking() ) + dark_harvest_dmg->execute_on_target( t ); - return m; - } - }; + // Shadow of Death gain effect is processed only once + if ( soul_harvester() && p()->hero.shadow_of_death.ok() && d->state->chain_target == 0 ) + { + double gain = p()->hero.shadow_of_death->effectN( 2 ).base_value(); + // TODO: Check if the shards gained by Shadow of Death can also proc another Succulent Soul each (bug?) + if ( p()->bugs ) + p()->resource_gain( RESOURCE_SOUL_SHARD, gain, p()->gains.shadow_of_death ); + else + p()->player_t::resource_gain( RESOURCE_SOUL_SHARD, gain, p()->gains.shadow_of_death ); - struct oblivion_t : public warlock_spell_t - { - oblivion_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Oblivion", p, p->talents.oblivion, options_str ) - { channeled = true; } + p()->buffs.succulent_soul->trigger( as( gain ) ); + for ( int i = 0; i < as( gain ); i++ ) + p()->procs.succulent_soul->occur(); + } + } - double composite_ta_multiplier( const action_state_t* s ) const override + void execute() override { - double m = warlock_spell_t::composite_ta_multiplier( s ); - - m *= 1.0 + std::min( td( s->target )->count_affliction_dots( !p()->bugs ), as( p()->talents.oblivion->effectN( 3 ).base_value() ) ) * p()->talents.oblivion->effectN( 2 ).percent(); - - return m; + target_cache.is_valid = false; + warlock_spell_t::execute(); } }; @@ -2323,27 +2176,27 @@ using namespace helpers; struct hand_of_guldan_t : public warlock_spell_t { - struct hand_of_guldan_state_t : public action_state_t + struct hand_of_guldan_state_t : public warlock_spell_state_t { bool demonic_art_buffed; bool rancora_empowered; hand_of_guldan_state_t( action_t* action, player_t* target ) - : action_state_t( action, target ), + : warlock_spell_state_t( action, target ), demonic_art_buffed( false ), rancora_empowered( false ) { } void initialize() override { - action_state_t::initialize(); + warlock_spell_state_t::initialize(); demonic_art_buffed = false; rancora_empowered = false; } std::ostringstream& debug_str( std::ostringstream& s ) override { - action_state_t::debug_str( s ); + warlock_spell_state_t::debug_str( s ); s << " demonic_art_buffed=" << demonic_art_buffed; s << " rancora_empowered=" << rancora_empowered; return s; @@ -2351,98 +2204,74 @@ using namespace helpers; void copy_state( const action_state_t* s ) override { - action_state_t::copy_state( s ); + warlock_spell_state_t::copy_state( s ); demonic_art_buffed = debug_cast( s )->demonic_art_buffed; rancora_empowered = debug_cast( s )->rancora_empowered; } }; - struct umbral_blaze_dot_t : public warlock_spell_t - { - umbral_blaze_dot_t( warlock_t* p ) - : warlock_spell_t( "Umbral Blaze", p, p->talents.umbral_blaze_dot ) - { - background = dual = true; - hasted_ticks = false; - base_td_multiplier = 1.0 + p->talents.umbral_blaze->effectN( 2 ).percent(); - } - }; - struct hog_impact_t : public warlock_spell_t { struct hogi_state_t { int shards_used; bool rancora_empowered; - int first_hit_random_target; - bool allow_umbral_blaze; + int last_hit_random_target; bool allow_succulent_soul; hogi_state_t() : shards_used( 0 ), rancora_empowered( false ), - first_hit_random_target( 0 ), - allow_umbral_blaze( true ), + last_hit_random_target( 0 ), allow_succulent_soul( true ) { } }; - struct hog_impact_state_t : public action_state_t + struct hog_impact_state_t : public warlock_spell_state_t { hogi_state_t state; hog_impact_state_t( action_t* action, player_t* target ) - : action_state_t( action, target ), + : warlock_spell_state_t( action, target ), state() { } void initialize() override { - action_state_t::initialize(); + warlock_spell_state_t::initialize(); state.shards_used = 0; state.rancora_empowered = false; - state.first_hit_random_target = 0; - state.allow_umbral_blaze = true; + state.last_hit_random_target = 0; state.allow_succulent_soul = true; } std::ostringstream& debug_str( std::ostringstream& s ) override { - action_state_t::debug_str( s ); + warlock_spell_state_t::debug_str( s ); s << " shards_used=" << state.shards_used; s << " rancora_empowered=" << state.rancora_empowered; - s << " first_hit_random_target=" << state.first_hit_random_target; - s << " allow_umbral_blaze=" << state.allow_umbral_blaze; + s << " last_hit_random_target=" << state.last_hit_random_target; s << " allow_succulent_soul=" << state.allow_succulent_soul; return s; } void copy_state( const action_state_t* s ) override { - action_state_t::copy_state( s ); + warlock_spell_state_t::copy_state( s ); state = debug_cast( s )->state; } }; timespan_t meteor_time; hogi_state_t state; - umbral_blaze_dot_t* blaze; hog_impact_t( warlock_t* p ) - : warlock_spell_t( "Hand of Gul'dan (Impact)", p, p->warlock_base.hog_impact ), + : warlock_spell_t( "Hand of Gul'dan (Impact)", p, p->talents.hog_impact ), meteor_time( 400_ms ) { aoe = -1; dual = true; affected_by.touch_of_rancora = affected_by.touch_of_rancora_casted = p->hero.touch_of_rancora.ok(); - - triggers.shadow_invocation = true; // all HoG impacts can proc SI, including the ones from Ruination and Pact of the Imp Mother - - if ( p->talents.umbral_blaze.ok() ) - { - blaze = new umbral_blaze_dot_t( p ); - add_child( blaze ); - } } action_state_t* new_state() override @@ -2461,24 +2290,26 @@ using namespace helpers; { double m = warlock_spell_t::composite_da_multiplier( s ); - int first_hit_random_target = debug_cast( s )->state.first_hit_random_target; + int last_hit_random_target = debug_cast( s )->state.last_hit_random_target; - // TOCHECK: 2025-08-16 Due to a bug, Succulent Soul only affects one of the targets hit in AoE - bool allow_succulent_soul = debug_cast( s )->state.allow_succulent_soul; - if ( soul_harvester() && allow_succulent_soul && p()->buffs.succulent_soul->check() && ( !p()->bugs || s->chain_target == first_hit_random_target ) ) - m *= 1.0 + p()->hero.succulent_soul->effectN( 3 ).percent(); + // NOTE: Dominant Hand seems to be applied to a random target instead of the primary one (bug?) + if ( p()->talents.dominant_hand.ok() && ( p()->bugs ? ( s->chain_target == last_hit_random_target ) : ( s->chain_target == 0 ) ) ) + m *= 1.0 + p()->talents.dominant_hand->effectN( 1 ).percent(); - // Touch of Rancora only affects one of HoG's hits in AoE (bug?), randomly selected bool rancora_empowered = debug_cast( s )->state.rancora_empowered; - if ( diabolist() && affected_by.touch_of_rancora && rancora_empowered && ( !p()->bugs || s->chain_target == first_hit_random_target ) ) + // TODO: In TWW Touch of Rancora only affected one of HoG's hits in AoE (bug?), randomly selected. Is this still true in Midnight? Check ingame + //if ( diabolist() && affected_by.touch_of_rancora && rancora_empowered && ( !p()->bugs || s->chain_target == last_hit_random_target ) ) + if ( diabolist() && affected_by.touch_of_rancora && rancora_empowered ) { - // NOTE: Touch of Rancora is a +100% ADDITION to the MULTIPLIER (bug?), we currently believe this must be done at the end of action_multiplier() calculation + // TODO: How does Touch of Rancora interact with HoG in Midnight? Check ingame if ( p()->bugs ) { + // TODO: Currently in Midnight Touch of Rancora seems to not be applying to HoG (at least not through this specialized handling) + // NOTE: Touch of Rancora is a +100% ADDITION to the MULTIPLIER (bug?), we currently believe this must be done at the end of action_multiplier() calculation // At this point, this 'm *= ( X + A ) / A' adjustment is equivalent to ADDITIVELY applying the multiplier 'X' to 'm' just after A // (a.k.a. action_multiplier) but before action_da_multiplier and the subsequent multipliers of warlock_spell_t::composite_da_multiplier - const double mult_am = action_multiplier(); - m *= ( mult_am != 0.0 ) ? ( ( p()->hero.touch_of_rancora->effectN( 1 ).percent() + mult_am ) / mult_am ) : 0.0; + // const double mult_am = action_multiplier(); + // m *= ( mult_am != 0.0 ) ? ( ( p()->hero.touch_of_rancora->effectN( 1 ).percent() + mult_am ) / mult_am ) : 0.0; } else { @@ -2493,7 +2324,7 @@ using namespace helpers; { // NOTE: Some effects only affects one of HoG's hits in AoE (bug?), randomly selected const std::vector& tl = target_list(); - state.first_hit_random_target = rng().range( as( tl.size() ) ); + state.last_hit_random_target = rng().range( as( tl.size() ) ); warlock_spell_t::execute(); } @@ -2520,35 +2351,37 @@ using namespace helpers; // Still keep it in impact instead of execute because of travel delay. if ( result_is_hit( s->result ) && s->target == target ) { + // TODO: It seems this no longer applies; now all three appear simultaneously. Check timings ingame // Wild Imp spawns appear to have been sped up in Shadowlands. Last tested 2021-04-16. // Current behavior: HoG will spawn a meteor on cast finish. Travel time in spell data is 0.7 seconds. // However, damage event occurs before spell effect lands, happening 0.4 seconds after cast. // Imps then spawn roughly every 0.18 seconds seconds after the damage event. for ( int i = 1; i <= debug_cast( s )->state.shards_used; i++ ) { - auto ev = make_event( *sim, p(), rng().gauss( 180.0 * i, 25.0 ), 180.0 * i ); + // auto ev = make_event( *sim, p(), rng().gauss( 180.0 * i, 25.0 ), 180.0 * i ); + auto ev = make_event( *sim, p(), 1.0, 1.0, i-1 ); // TODO: test this p()->wild_imp_spawns.push_back( ev ); } - - if ( p()->talents.umbral_blaze.ok() && debug_cast( s )->state.allow_umbral_blaze && rng().roll( p()->talents.umbral_blaze->effectN( 1 ).percent() ) ) - { - blaze->execute_on_target( s->target ); - p()->procs.umbral_blaze->occur(); - } } - // We need Demonic Soul to proc on every target, but buff is decremented on impact. Fudge this by 1ms to ensure all targets are hit. - // TOCHECK: 2025-08-16 Due to a bug, Succulent Soul only affects one of the targets hit in AoE - // We keep the original buff decrement behavior and handle this bug from 'composite_da_multiplier()' if ( soul_harvester() && debug_cast( s )->state.allow_succulent_soul && p()->buffs.succulent_soul->check() ) { - bool primary = ( s->chain_target == 0 ); + if ( s->chain_target == 0 ) + { + p()->buffs.succulent_soul->decrement(); + // TODO: Check this bug ingame. It seems that if you have more than one Succulent Soul, Hand of Gul'dan will consume two stacks, even though it will only does the effect of one + if ( p()->bugs ) + p()->buffs.succulent_soul->decrement(); - if ( primary ) - make_event( *sim, 1_ms, [ this ] { p()->buffs.succulent_soul->decrement(); } ); + if ( p()->hero.manifested_avarice.ok() && rng().roll( p()->rng_settings.manifested_avarice.setting_value ) ) + { + // TODO: Needed some ingame checks. Is it possible to proc a new Demonic Soul spawn while another Demonic Soul is active? What happens in that case? What happens to the buff and the mastery effect in that case? ​​(Does it stack? Does the duration change?) + p()->warlock_pet_list.demonic_souls.spawn( p()->hero.manifested_avarice_summon->duration() ); + p()->buffs.manifested_demonic_soul->trigger(); + } - debug_cast(p()->proc_actions.demonic_soul)->demoniacs_fervor = primary; - p()->proc_actions.demonic_soul->execute_on_target( s->target ); + p()->proc_actions.demonic_soul->execute_on_target( s->target ); + } } } }; @@ -2556,13 +2389,12 @@ using namespace helpers; hog_impact_t* impact_spell; hand_of_guldan_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Hand of Gul'dan", p, p->warlock_base.hand_of_guldan, options_str ), + : warlock_spell_t( "Hand of Gul'dan", p, p->talents.hand_of_guldan_cast, options_str ), impact_spell( new hog_impact_t( p ) ) { affected_by.touch_of_rancora = affected_by.touch_of_rancora_casted = p->hero.touch_of_rancora.ok(); triggers.diabolic_ritual = triggers.demonic_art = p->hero.diabolic_ritual.ok(); - triggers.jackpot_demonology = true; add_child( impact_spell ); } @@ -2578,7 +2410,7 @@ using namespace helpers; if ( diabolist() && p()->executing != this && p()->buffs.ruination->check() ) return false; - if ( p()->resources.current[ RESOURCE_SOUL_SHARD ] < 1.0 ) + if ( p()->resources.current[ RESOURCE_SOUL_SHARD ] < 3.0 ) // TODO: Is this needed? return false; return warlock_spell_t::ready(); @@ -2619,108 +2451,64 @@ using namespace helpers; impact_spell->state.rancora_empowered = false; triggers.demonic_art_buff = false; } - impact_spell->state.allow_umbral_blaze = true; impact_spell->state.allow_succulent_soul = true; + if ( p()->hero.diabolic_oculi.ok() ) + p()->buffs.demonic_oculi->trigger(); + warlock_spell_t::execute(); - if ( p()->talents.dread_calling.ok() ) - p()->buffs.dread_calling->trigger( shards_used ); if ( p()->talents.doom.ok() ) { for ( const auto t : p()->sim->target_non_sleeping_list ) { if ( td( t )->debuffs.doom->check() ) - td( t )->debuffs.doom->extend_duration( p(), -p()->talents.doom->effectN( 1 ).time_value() * shards_used ); + td( t )->debuffs.doom->extend_duration( p(), -p()->talents.doom->effectN( 1 ).time_value() ); } } - if ( active_4pc() ) + if ( p()->talents.demonic_knowledge.ok() && rng().roll( p()->talents.demonic_knowledge->effectN( 1 ).percent() ) ) { - double mult = p()->tier.spliced_demo_4pc->effectN( 1 ).percent() + p()->tier.spliced_demo_4pc->effectN( 2 ).percent() * shards_used; - - for ( auto dog : p()->warlock_pet_list.dreadstalkers ) - { - if ( !dog->is_sleeping() ) - { - dog->buffs.spliced_4pc->trigger( 1, mult ); - dog->queue_dreadbite(); - } - } - - for ( auto big_dog : p()->warlock_pet_list.greater_dreadstalkers ) - { - if ( !big_dog->is_sleeping() ) - { - big_dog->buffs.spliced_4pc->trigger( 1, mult ); - big_dog->queue_dreadbite(); - } - } + p()->buffs.demonic_core->trigger(); + p()->procs.demonic_knowledge->occur(); } } - void consume_resource() override - { - warlock_spell_t::consume_resource(); - - int lrc = as( last_resource_cost ) - 1; - - assert( lrc < as( p()->procs.hand_of_guldan_shards.size() ) && "The procs.hand_of_guldan_shards array needs to be expanded." ); - - p()->procs.hand_of_guldan_shards[ lrc ]->occur(); - - if ( active_2pc() && lrc == 2 /*Manually hardcoding for now since im not sure how the cost data is read*/) - p()->buffs.demonic_oculus->trigger(); - } - void impact( action_state_t* s ) override { warlock_spell_t::impact( s ); impact_spell->execute_on_target( s->target ); - - if ( p()->talents.pact_of_the_imp_mother.ok() && rng().roll( p()->talents.pact_of_the_imp_mother->effectN( 1 ).percent() ) ) - { - make_event( *sim, rng().gauss( impact_spell->travel_time(), timespan_t::from_seconds( sim->travel_variance ) ), - [ this, t = target, su = impact_spell->state.shards_used ] { - impact_spell->state.shards_used = su; - impact_spell->state.rancora_empowered = false; // Pact of the Imp Mother extra HoG is never rancora empowered - impact_spell->state.allow_umbral_blaze = false; // Pact of the Imp Mother extra HoG can't proc umbral blaze - impact_spell->state.allow_succulent_soul = false; // Pact of the Imp Mother extra HoG can't benefit from succulent soul - impact_spell->execute_on_target( t ); - } ); - p()->procs.pact_of_the_imp_mother->occur(); - } } }; struct demonbolt_t : public warlock_spell_t { - struct demonbolt_state_t : public action_state_t + struct demonbolt_state_t : public warlock_spell_state_t { bool core_spent; demonbolt_state_t( action_t* action, player_t* target ) - : action_state_t( action, target ), + : warlock_spell_state_t( action, target ), core_spent( false ) { } void initialize() override { - action_state_t::initialize(); + warlock_spell_state_t::initialize(); core_spent = false; } std::ostringstream& debug_str( std::ostringstream& s ) override { - action_state_t::debug_str( s ) << " core_spent=" << core_spent; + warlock_spell_state_t::debug_str( s ) << " core_spent=" << core_spent; return s; } void copy_state( const action_state_t* s ) override { - action_state_t::copy_state( s ); + warlock_spell_state_t::copy_state( s ); core_spent = debug_cast( s )->core_spent; } }; @@ -2733,8 +2521,6 @@ using namespace helpers; energize_amount = 2.0; affected_by.sacrificed_souls = true; - triggers.shadow_invocation = true; - triggers.jackpot_demonology = true; } action_state_t* new_state() override @@ -2759,24 +2545,6 @@ using namespace helpers; p()->warlock_pet_list.wild_imps.spawn( 1u ); p()->procs.spiteful_reconstitution->occur(); } - - if ( p()->talents.immutable_hatred.ok() ) - { - auto active_pet = p()->warlock_pet_list.active; - - if ( active_pet->pet_type == PET_FELGUARD ) - debug_cast( active_pet )->hatred_proc->execute_on_target( execute_state->target ); - } - } - else - { - if ( p()->talents.immutable_hatred.ok() && p()->bugs ) - { - auto active_pet = p()->warlock_pet_list.active; - - if ( active_pet->pet_type == PET_FELGUARD ) - debug_cast( active_pet )->hatred_proc->execute_on_target( execute_state->target ); - } } if ( soul_harvester() && p()->buffs.demonic_core->check() ) @@ -2791,12 +2559,12 @@ using namespace helpers; p()->feast_of_souls_gain(); } + if ( p()->talents.summon_doomguard.ok() && p()->buffs.demonic_core->check() ) + p()->cooldowns.summon_doomguard->adjust( -p()->talents.summon_doomguard->effectN( 2 ).time_value() ); + p()->buffs.demonic_core->decrement(); p()->buffs.power_siphon->decrement(); - - if ( p()->talents.demonic_calling.ok() ) - p()->buffs.demonic_calling->trigger(); } void impact( action_state_t* s ) override @@ -2827,8 +2595,9 @@ using namespace helpers; { double m = warlock_spell_t::action_multiplier(); + // TODO: Is this still happening? Do the Imp Gang Boss implosions do 100% more damage? Check ingame if ( debug_cast( next_imp )->buffs.imp_gang_boss->check() ) - m *= 1.0 + p()->talents.imp_gang_boss->effectN( 2 ).percent(); + m *= 1.0 + p()->talents.imp_gang_boss_buff->effectN( 2 ).percent(); return m; } @@ -2865,7 +2634,7 @@ using namespace helpers; // Travel speed is not in spell data, in game test appears to be 65 yds/sec as of 2020-12-04 timespan_t imp_travel_time = calc_imp_travel_time( 65 ); - int launch_counter = 0; + unsigned launch_counter = 0; for ( auto imp : p()->warlock_pet_list.wild_imps ) { if ( !imp->is_sleeping() ) @@ -2893,6 +2662,24 @@ using namespace helpers; } ); launch_counter++; + + // TODO: They are currently being selected from the list in no particular order; check ingame if there is any priority when sacrificing the (max-6) wild imps + if ( launch_counter >= as( data().effectN( 1 ).base_value() ) ) + break; + } + + if ( p()->talents.to_hell_and_back.ok() ) + { + unsigned new_imps = ( launch_counter / as( p()->talents.to_hell_and_back->effectN( 2 ).base_value() ) ) * as( p()->talents.to_hell_and_back->effectN( 1 ).base_value() ); + if ( new_imps > 0 ) + { + auto imps = p()->warlock_pet_list.wild_imps.spawn( new_imps ); + for ( auto imp : imps ) + { + imp->buffs.imp_gang_boss->trigger(); + imp->buffs.unstable_soul->trigger(); + } + } } } @@ -2922,14 +2709,35 @@ using namespace helpers; } }; + struct summon_vilefiend_t : public warlock_spell_t + { + summon_vilefiend_t( warlock_t* p ) + : warlock_spell_t( "Summon Vilefiend", p, p->talents.vilefiend ) + { + background = dual = true; + harmful = may_crit = false; + } + + void execute() override + { + warlock_spell_t::execute(); + + p()->warlock_pet_list.vilefiends.spawn( data().duration() ); + } + }; + struct call_dreadstalkers_t : public warlock_spell_t { + summon_vilefiend_t* summon_vilefiend; + call_dreadstalkers_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Call Dreadstalkers", p, p->talents.call_dreadstalkers, options_str ) { may_crit = false; - affected_by.soul_conduit_base_cost = true; triggers.diabolic_ritual = p->hero.diabolic_ritual.ok(); + + if ( p->talents.summon_vilefiend.ok() ) + summon_vilefiend = new summon_vilefiend_t( p ); } void execute() override @@ -2949,129 +2757,11 @@ using namespace helpers; if ( d->is_active() ) { d->server_action_delay = delay; - if ( p()->talents.dread_calling.ok() && !d->buffs.dread_calling->check() ) - d->buffs.dread_calling->trigger( 1, p()->buffs.dread_calling->check_stack_value() ); } } - if ( p()->buffs.demonic_calling->up() ) - p()->buffs.demonic_calling->decrement(); - - p()->buffs.dread_calling->expire(); - } - }; - - struct bilescourge_bombers_t : public warlock_spell_t - { - struct bilescourge_bombers_tick_t : public warlock_spell_t - { - bilescourge_bombers_tick_t( warlock_t* p ) - : warlock_spell_t( "Bilescourge Bombers (tick)", p, p->talents.bilescourge_bombers_aoe ) - { - aoe = -1; - background = dual = direct_tick = true; - callbacks = false; - radius = p->talents.bilescourge_bombers->effectN( 1 ).radius(); - } - - double composite_target_multiplier( player_t* t ) const override - { - double m = warlock_spell_t::composite_target_multiplier( t ); - - // TOCHECK: 2025-07-27 Despite what is listed in spell data, Bilescourge Bombers benefits from shadowtouched - if ( p()->bugs && td( t )->debuffs.wicked_maw->check() ) - m *= 1.0 + p()->talents.wicked_maw_debuff->effectN( 2 ).percent(); - - return m; - } - }; - - bilescourge_bombers_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Bilescourge Bombers", p, p->talents.bilescourge_bombers, options_str ) - { - dot_duration = 0_ms; - may_miss = may_crit = false; - base_tick_time = 500_ms; - - if ( !p->proc_actions.bilescourge_bombers_aoe_tick ) - { - p->proc_actions.bilescourge_bombers_aoe_tick = new bilescourge_bombers_tick_t( p ); - p->proc_actions.bilescourge_bombers_aoe_tick->stats = stats; - } - } - - void execute() override - { - warlock_spell_t::execute(); - - make_event( *sim, p(), - ground_aoe_params_t() - .target( execute_state->target ) - .x( execute_state->target->x_position ) - .y( execute_state->target->y_position ) - .pulse_time( base_tick_time ) - .duration( p()->talents.bilescourge_bombers->duration() ) - .start_time( sim->current_time() ) - .action( p()->proc_actions.bilescourge_bombers_aoe_tick ) ); - } - }; - - struct bilescourge_bombers_proc_t : public warlock_spell_t - { - bilescourge_bombers_proc_t( warlock_t* p ) - : warlock_spell_t( "Bilescourge Bombers (proc)", p, p->talents.bilescourge_bombers_aoe ) - { - aoe = -1; - background = dual = direct_tick = true; - callbacks = false; - radius = p->find_spell( 267211 )->effectN( 1 ).radius(); // Get radius data even if BB talent not selected - } - - double composite_target_multiplier( player_t* t ) const override - { - double m = warlock_spell_t::composite_target_multiplier( t ); - - // TOCHECK: 2025-07-27 Despite what is listed in spell data, Bilescourge Bombers also benefits from Wicked Maw, but only with Shadowtouched (bug?) - if ( p()->bugs && p()->talents.shadowtouched.ok() ) - m *= 1.0 + td( t )->debuffs.wicked_maw->check_value(); - - return m; - } - }; - - struct demonic_strength_t : public warlock_spell_t - { - demonic_strength_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Demonic Strength", p, p->talents.demonic_strength, options_str ) - { internal_cooldown = p->cooldowns.felstorm_icd; } - - bool ready() override - { - auto active_pet = p()->warlock_pet_list.active; - - if ( !active_pet ) - return false; - - if ( active_pet->pet_type != PET_FELGUARD ) - return false; - - return warlock_spell_t::ready(); - } - - void execute() override - { - auto active_pet = p()->warlock_pet_list.active; - - warlock_spell_t::execute(); - - if ( active_pet->pet_type == PET_FELGUARD ) - { - active_pet->buffs.demonic_strength->trigger(); - - debug_cast( active_pet )->queue_ds_felstorm(); - - internal_cooldown->start( 5_s * active_pet->composite_spell_cast_speed() ); - } + if ( p()->talents.summon_vilefiend.ok() ) + summon_vilefiend->execute_on_target( target ); } }; @@ -3105,7 +2795,7 @@ using namespace helpers; { p()->buffs.power_siphon->trigger( 2, p()->talents.power_siphon_buff->duration() ); p()->buffs.demonic_core->trigger( 2, p()->talents.demonic_core_buff->duration() ); - + return; } @@ -3116,10 +2806,11 @@ using namespace helpers; double lv = imp1->resources.current[ RESOURCE_ENERGY ]; double rv = imp2->resources.current[ RESOURCE_ENERGY ]; + // TODO: Is this still the case in Midnight? // Power Siphon deprioritizes Wild Imps that are Gang Bosses or empowered by Summon Demonic Tyrant // Padding ensures they still sort in order at the back of the list - lv += ( imp1->buffs.imp_gang_boss->check() || imp1->buffs.demonic_power->check() ) ? 200.0 : 0.0; - rv += ( imp2->buffs.imp_gang_boss->check() || imp2->buffs.demonic_power->check() ) ? 200.0 : 0.0; + lv += ( imp1->buffs.imp_gang_boss->check() ) ? 200.0 : 0.0; + rv += ( imp2->buffs.imp_gang_boss->check() ) ? 200.0 : 0.0; if ( lv == rv ) { @@ -3141,6 +2832,7 @@ using namespace helpers; if ( imps.size() > max_imps ) imps.resize( max_imps ); + unsigned sac_counter = 0; while ( !imps.empty() ) { p()->buffs.power_siphon->trigger(); @@ -3149,6 +2841,21 @@ using namespace helpers; imps.erase( imps.begin() ); imp->power_siphon = true; imp->dismiss(); + sac_counter++; + } + + if ( p()->talents.to_hell_and_back.ok() ) + { + unsigned new_imps = ( sac_counter / as( p()->talents.to_hell_and_back->effectN( 2 ).base_value() ) ) * as( p()->talents.to_hell_and_back->effectN( 1 ).base_value() ); + if ( new_imps > 0 ) + { + auto imps = p()->warlock_pet_list.wild_imps.spawn( new_imps ); + for ( auto imp : imps ) + { + imp->buffs.imp_gang_boss->trigger(); + imp->buffs.unstable_soul->trigger(); + } + } } } }; @@ -3169,85 +2876,58 @@ using namespace helpers; { warlock_spell_t::execute(); - if ( active_2pc() ) - { - const auto delay_dur_adjusts = p()->dreadstalkers_delay_duration_adjustment_helper( *target ); - const timespan_t& delay = delay_dur_adjusts.first; - const timespan_t& dur_adjust = delay_dur_adjusts.second; - - auto dogs = p()->warlock_pet_list.greater_dreadstalkers.spawn( p()->tier.greater_dreadstalker->duration() + dur_adjust, 1u ); - - for ( auto d : dogs ) - { - if ( d->is_active() ) - { - d->server_action_delay = delay; - if ( p()->talents.dread_calling.ok() && !d->buffs.dread_calling->check() ) - d->buffs.dread_calling->trigger( 1, p()->buffs.dread_calling->check_stack_value() ); - } - } - - p()->procs.jackpot_demonology->occur(); - p()->buffs.dread_calling->expire(); - } - // Last tested 2021-07-13 // There is a chance for tyrant to get an extra cast off before reaching the required haste breakpoint. // In-game testing found this can be modelled fairly closely using a normal distribution. timespan_t extraTyrantTime = rng().gauss<380,220>(); auto tyrants = p()->warlock_pet_list.demonic_tyrants.spawn( data().duration() + extraTyrantTime ); - timespan_t extension_time = p()->talents.demonic_power_buff->effectN( 3 ).time_value(); + int demon_counter = 0; + const timespan_t extension_time = 15_s; // TODO: Where is this 15_s in the spell data? - int wild_imp_counter = 0; - int imp_cap = as( p()->talents.summon_demonic_tyrant->effectN( 3 ).base_value() ); - - for ( auto& pet : p()->pet_list ) + for ( auto wild_imp : p()->warlock_pet_list.wild_imps ) { - auto lock_pet = dynamic_cast( pet ); - - if ( lock_pet == nullptr ) - continue; + if ( !wild_imp->is_sleeping() ) + demon_counter++; + } - if ( lock_pet->is_sleeping() ) + for ( auto dreadstalker : p()->warlock_pet_list.dreadstalkers ) + { + if ( dreadstalker->is_sleeping() ) continue; - pet_e pet_type = lock_pet->pet_type; + if ( p()->talents.reign_of_tyranny.ok() ) + { + if ( dreadstalker->expiration ) + dreadstalker->expiration->reschedule_time = dreadstalker->expiration->time + extension_time; + } - if ( pet_type == PET_DEMONIC_TYRANT ) - continue; + demon_counter++; + } - if ( pet_type == PET_WILD_IMP && wild_imp_counter < imp_cap ) - { - if ( lock_pet->expiration ) - lock_pet->expiration->reschedule_time = lock_pet->expiration->time + extension_time; + if ( p()->talents.reign_of_tyranny.ok() ) + { + if ( p()->buffs.dreadstalkers->check() ) + p()->buffs.dreadstalkers->extend_duration( p(), extension_time ); + } - lock_pet->buffs.demonic_power->trigger(); - wild_imp_counter++; - } - else if ( pet_type == PET_DREADSTALKER || pet_type == PET_VILEFIEND || pet_type == PET_SERVICE_FELGUARD || pet_type == PET_FELGUARD ) + if ( demon_counter > 0 ) + { + for ( auto t : tyrants ) { - if ( lock_pet->expiration ) - lock_pet->expiration->reschedule_time = lock_pet->expiration->time + extension_time; - - lock_pet->buffs.demonic_power->trigger(); + if ( t->is_active() ) + t->buffs.demonic_power->trigger( demon_counter ); } } p()->buffs.tyrant->trigger(); + if ( p()->talents.tyrants_oblation.ok() ) + p()->buffs.tyrants_oblation->trigger(); + if ( p()->hero.abyssal_dominion.ok() ) p()->buffs.abyssal_dominion->trigger(); - if ( p()->buffs.dreadstalkers->check() ) - p()->buffs.dreadstalkers->extend_duration( p(), extension_time ); - - if ( p()->buffs.grimoire_felguard->check() ) - p()->buffs.grimoire_felguard->extend_duration( p(), extension_time ); - - if ( p()->buffs.vilefiend->check() ) - p()->buffs.vilefiend->extend_duration( p(), extension_time ); - if ( p()->hero.cruelty_of_kerxan.ok() ) { timespan_t reduction = -p()->hero.cruelty_of_kerxan->effectN( 1 ).time_value(); @@ -3260,27 +2940,23 @@ using namespace helpers; if ( soul_harvester() && p()->hero.shadow_of_death.ok() ) { // TOCHECK: 2025-08-27 The shards gained by Shadow of Death can also proc another Succulent Soul each (bug?) + double gain = p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0; if ( p()->bugs ) - p()->resource_gain( RESOURCE_SOUL_SHARD, p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0, p()->gains.shadow_of_death ); + p()->resource_gain( RESOURCE_SOUL_SHARD, gain, p()->gains.shadow_of_death ); else - p()->player_t::resource_gain( RESOURCE_SOUL_SHARD, p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0, p()->gains.shadow_of_death ); + p()->player_t::resource_gain( RESOURCE_SOUL_SHARD, gain, p()->gains.shadow_of_death ); - p()->buffs.succulent_soul->trigger( as( p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0 ) ); - for ( int i = 0; i < as( p()->hero.shadow_of_death_energize->effectN( 1 ).base_value() / 10.0 ); i++) + p()->buffs.succulent_soul->trigger( as( gain ) ); + for ( int i = 0; i < as( gain ); i++ ) p()->procs.succulent_soul->occur(); - - if ( active_2pc() && p()->tier.rampaging_demonic_soul->ok() ) - { - p()->warlock_pet_list.demonic_souls.spawn( p()->tier.rampaging_demonic_soul->duration() ); - } } } }; - struct grimoire_felguard_t : public warlock_spell_t + struct grimoire_imp_lord_t : public warlock_spell_t { - grimoire_felguard_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Grimoire: Felguard", p, p->talents.grimoire_felguard, options_str ) + grimoire_imp_lord_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Grimoire: Imp Lord", p, p->talents.grimoire_imp_lord, options_str ) { harmful = may_crit = false; @@ -3291,15 +2967,14 @@ using namespace helpers; { warlock_spell_t::execute(); - p()->warlock_pet_list.grimoire_felguards.spawn( p()->talents.grimoire_felguard->duration() ); - p()->buffs.grimoire_felguard->trigger(); + p()->warlock_pet_list.grimoire_imp_lords.spawn( p()->talents.grimoire_imp_lord->duration() ); } }; - struct summon_vilefiend_t : public warlock_spell_t + struct grimoire_fel_ravager_t : public warlock_spell_t { - summon_vilefiend_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Summon Vilefiend", p, p->talents.summon_vilefiend, options_str ) + grimoire_fel_ravager_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Grimoire: Fel Ravager", p, p->talents.grimoire_fel_ravager, options_str ) { harmful = may_crit = false; @@ -3309,36 +2984,37 @@ using namespace helpers; void execute() override { warlock_spell_t::execute(); - - p()->buffs.vilefiend->trigger( -1, 1.0 ); // Set value to 1.0 to allow Houndmasters Gambit talent to apply - p()->warlock_pet_list.vilefiends.spawn( p()->talents.summon_vilefiend->duration() ); + + p()->warlock_pet_list.grimoire_fel_ravagers.spawn( p()->talents.grimoire_fel_ravager->duration() ); } }; - struct doom_t : public warlock_spell_t + struct summon_doomguard_t : public warlock_spell_t { - doom_t( warlock_t* p ) - : warlock_spell_t( "Doom", p, p->talents.doom_dmg ) + summon_doomguard_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Summon Doomguard", p, p->talents.summon_doomguard, options_str ) { - background = dual = true; - aoe = -1; - reduced_aoe_targets = p->talents.doom->effectN( 2 ).base_value(); + harmful = may_crit = false; + + triggers.diabolic_ritual = p->hero.diabolic_ritual.ok(); } void execute() override { warlock_spell_t::execute(); - if ( p()->talents.impending_doom.ok() ) - p()->warlock_pet_list.wild_imps.spawn( as( p()->talents.impending_doom->effectN( 2 ).base_value() ) ); - - if ( p()->talents.doom_eternal.ok() ) - { - bool success = p()->buffs.demonic_core->trigger( 1, buff_t::DEFAULT_VALUE(), p()->talents.doom_eternal->effectN( 1 ).percent() ); + p()->warlock_pet_list.doomguards.spawn( p()->talents.summon_doomguard->duration() ); + } + }; - if ( success ) - p()->procs.doom_eternal->occur(); - } + struct doom_t : public warlock_spell_t + { + doom_t( warlock_t* p ) + : warlock_spell_t( "Doom", p, p->talents.doom_dmg ) + { + background = dual = true; + aoe = -1; + reduced_aoe_targets = p->talents.doom->effectN( 2 ).base_value(); } }; @@ -3357,6 +3033,8 @@ using namespace helpers; affected_by.chaotic_energies = true; + triggers.fiendish_cruelty = p->talents.fiendish_cruelty.ok(); // TODO: Check if Fiendish Cruelty actually affects FnB's Incinerate and how it affects it + base_multiplier *= p->talents.fire_and_brimstone->effectN( 1 ).percent(); } @@ -3392,15 +3070,6 @@ using namespace helpers; if ( p()->bugs && p()->talents.diabolic_embers.ok() && s->result == RESULT_CRIT ) p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.incinerate_crits ); } - - double action_multiplier() const override - { - double m = warlock_spell_t::action_multiplier(); - - m *= 1.0 + p()->buffs.burn_to_ashes->check_value(); - - return m; - } }; double energize_mult; @@ -3413,14 +3082,14 @@ using namespace helpers; energize_type = action_energize::PER_HIT; energize_resource = RESOURCE_SOUL_SHARD; energize_amount = ( p->warlock_base.incinerate_energize->effectN( 1 ).base_value() ) / 10.0; - + energize_mult = 1.0 + p->talents.diabolic_embers->effectN( 1 ).percent(); energize_amount *= energize_mult; affected_by.chaotic_energies = true; affected_by.havoc = true; - triggers.jackpot_destruction = true; + triggers.fiendish_cruelty = p->talents.fiendish_cruelty.ok(); add_child( fnb_action ); } @@ -3449,7 +3118,7 @@ using namespace helpers; void execute() override { warlock_spell_t::execute(); - + if ( p()->talents.fire_and_brimstone.ok() ) fnb_action->execute_on_target( target ); @@ -3460,7 +3129,8 @@ using namespace helpers; } p()->buffs.backdraft->decrement(); - p()->buffs.burn_to_ashes->decrement(); // Must do after Fire and Brimstone execute so that child picks up buff + + p()->buffs.chaotic_inferno->decrement(); } void impact( action_state_t* s ) override @@ -3476,15 +3146,6 @@ using namespace helpers; p()->resource_gain( RESOURCE_SOUL_SHARD, 0.1, p()->gains.incinerate_crits ); } } - - double action_multiplier() const override - { - double m = warlock_spell_t::action_multiplier(); - - m *= 1.0 + p()->buffs.burn_to_ashes->check_value(); - - return m; - } }; struct immolate_t : public warlock_spell_t @@ -3497,8 +3158,6 @@ using namespace helpers; background = dual = true; affected_by.chaotic_energies = true; - - triggers.dimension_ripper = p->talents.dimension_ripper.ok(); } void tick( dot_t* d ) override @@ -3527,12 +3186,8 @@ using namespace helpers; affected_by.chaotic_energies = true; affected_by.havoc = true; - triggers.jackpot_destruction = true; - impact_action = new immolate_dot_t( p ); add_child( impact_action ); - - triggers.decimation = p->talents.decimation.ok() && !dual; } immolate_t( warlock_t* p, bool havoc, util::string_view options_str ) : immolate_t( p, options_str ) @@ -3548,8 +3203,6 @@ using namespace helpers; : warlock_spell_t( "Internal Combustion", p, p->talents.internal_combustion_dmg ) { background = dual = true; - - triggers.decimation = false; } void init() override @@ -3569,8 +3222,8 @@ using namespace helpers; double tick_base_damage = state->result_raw; - if ( td( target )->debuffs.conflagrate->up() ) - tick_base_damage /= 1.0 + td( target )->debuffs.conflagrate->check_value(); + // if ( td( target )->debuffs.conflagrate->up() ) + // tick_base_damage /= 1.0 + td( target )->debuffs.conflagrate->check_value(); timespan_t remaining = std::min( dot->remains(), timespan_t::from_seconds( p()->talents.internal_combustion->effectN( 1 ).base_value() ) ); timespan_t dot_tick_time = dot->current_action->tick_time( state ); @@ -3587,29 +3240,67 @@ using namespace helpers; } }; + struct dimensional_rift_t : public warlock_spell_t + { + dimensional_rift_t( warlock_t* p ) + : warlock_spell_t( "Dimensional Rift", p, p->talents.dimensional_rift ) + { + background = dual = proc = true; + } + + void execute() override + { + warlock_spell_t::execute(); + + int rift = rng().range( 3 ); + + switch ( rift ) + { + case 0: + p()->warlock_pet_list.shadow_rifts.spawn( p()->talents.shadowy_tear_summon->duration() ); + break; + case 1: + p()->warlock_pet_list.unstable_rifts.spawn( p()->talents.unstable_tear_summon->duration() ); + break; + case 2: + p()->warlock_pet_list.chaos_rifts.spawn( p()->talents.chaos_tear_summon->duration() ); + break; + default: + break; + } + + if ( p()->talents.avatar_of_destruction.ok() && rng().roll( p()->rng_settings.avatar_of_destruction_dr.setting_value ) ) + { + p()->warlock_pet_list.overfiends.spawn(); + p()->buffs.summon_overfiend->trigger(); + p()->procs.avatar_of_destruction->occur(); + } + } + }; + struct chaos_bolt_t : public warlock_spell_t { - struct chaos_bolt_state_t : public action_state_t + struct chaos_bolt_state_t : public warlock_spell_state_t { bool demonic_art_buffed; bool rancora_empowered; chaos_bolt_state_t( action_t* action, player_t* target ) - : action_state_t( action, target ), + : warlock_spell_state_t( action, target ), demonic_art_buffed( false ), rancora_empowered( false ) { } void initialize() override { - action_state_t::initialize(); + warlock_spell_state_t::initialize(); demonic_art_buffed = false; rancora_empowered = false; } std::ostringstream& debug_str( std::ostringstream& s ) override { - action_state_t::debug_str( s ); + warlock_spell_state_t::debug_str( s ); s << " demonic_art_buffed=" << demonic_art_buffed; s << " rancora_empowered=" << rancora_empowered; return s; @@ -3617,7 +3308,7 @@ using namespace helpers; void copy_state( const action_state_t* s ) override { - action_state_t::copy_state( s ); + warlock_spell_state_t::copy_state( s ); demonic_art_buffed = debug_cast( s )->demonic_art_buffed; rancora_empowered = debug_cast( s )->rancora_empowered; } @@ -3625,9 +3316,10 @@ using namespace helpers; double havoc_rancora_mod_value; internal_combustion_t* internal_combustion; + dimensional_rift_t* dimensional_rift; chaos_bolt_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Chaos Bolt", p, p->warlock_base.chaos_bolt, options_str ), + : warlock_spell_t( "Chaos Bolt", p, p->talents.chaos_bolt, options_str ), havoc_rancora_mod_value( 0.8 ) { affected_by.chaotic_energies = true; @@ -3635,12 +3327,15 @@ using namespace helpers; affected_by.chaos_incarnate = p->talents.chaos_incarnate.ok(); affected_by.touch_of_rancora = affected_by.touch_of_rancora_casted = p->hero.touch_of_rancora.ok(); + triggers.fiendish_cruelty = p->talents.fiendish_cruelty.ok(); triggers.diabolic_ritual = triggers.demonic_art = p->hero.diabolic_ritual.ok(); triggers.rancora_cb_bonus = true; - triggers.jackpot_destruction = true; havoc_rancora_mod_value /= p->talents.havoc_debuff->effectN( 1 ).percent(); + if ( p->talents.dimensional_rift.ok() ) + dimensional_rift = new dimensional_rift_t( p ); + if ( p->talents.internal_combustion.ok() ) { internal_combustion = new internal_combustion_t( p ); @@ -3665,9 +3360,6 @@ using namespace helpers; if ( p()->talents.internal_combustion.ok() && result_is_hit( s->result ) && ( td( s->target )->dots.immolate->is_ticking() || td( s->target )->dots.wither->is_ticking() ) ) internal_combustion->execute_on_target( s->target ); - - if ( p()->talents.eradication.ok() && result_is_hit( s->result ) ) - td( s->target )->debuffs.eradication->trigger(); } void schedule_execute( action_state_t* s ) override @@ -3701,8 +3393,8 @@ using namespace helpers; triggers.demonic_art_buff = false; } - if ( active_2pc() ) - p()->buffs.demonic_oculus->trigger(); + if ( p()->hero.diabolic_oculi.ok() ) + p()->buffs.demonic_oculi->trigger(); // NOTE: 2025-08-27 Rancora Empowered Havoc spells deals 80% of the original damage (bug?) const double prev_base_aoe_multiplier = base_aoe_multiplier; @@ -3714,22 +3406,21 @@ using namespace helpers; base_aoe_multiplier = prev_base_aoe_multiplier; // Restore original previous havoc aoe multiplier - // 2022-10-15: Backdraft is not consumed for Ritual of Ruin empowered casts, but IS hasted by it - if ( !p()->buffs.ritual_of_ruin->check() ) - p()->buffs.backdraft->decrement(); + p()->buffs.crashing_chaos->decrement(); - if ( p()->talents.avatar_of_destruction.ok() && p()->buffs.ritual_of_ruin->check() ) + if ( p()->talents.chaotic_inferno.ok() ) { - p()->warlock_pet_list.overfiends.spawn(); - p()->buffs.summon_overfiend->trigger(); - } + bool success = p()->buffs.chaotic_inferno->trigger(); - p()->buffs.ritual_of_ruin->expire(); - - p()->buffs.crashing_chaos->decrement(); + if ( success ) + p()->procs.chaotic_inferno->occur(); + } - if ( p()->talents.burn_to_ashes.ok() ) - p()->buffs.burn_to_ashes->trigger( as( p()->talents.burn_to_ashes->effectN( 3 ).base_value() ) ); + if ( p()->talents.dimensional_rift.ok() && rng().roll( p()->talents.dimensional_rift->effectN( 1 ).percent() ) ) + { + p()->procs.dimensional_rift->occur(); + dimensional_rift->execute_on_target( target ); + } } double composite_persistent_multiplier( const action_state_t* s ) const override @@ -3737,6 +3428,7 @@ using namespace helpers; double m = warlock_spell_t::composite_persistent_multiplier( s ); // An incoming rancora empowered casted spell will remain empowered even if the Demonic Art buff falls off during cast + // TODO: Check how Touch of Rancora behaves in Midnight and be careful that 'parse_effects' may already be applying the damage buff if ( debug_cast( s )->rancora_empowered ) m *= 1.0 + p()->hero.touch_of_rancora->effectN( 1 ).percent(); @@ -3758,32 +3450,68 @@ using namespace helpers; struct conflagrate_t : public warlock_spell_t { + warlock_spell_t* spread_dot; + double spread_range = 8.0; // TODO: Check if this is indeed the range, and also check where this value would be in the spell data + conflagrate_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Conflagrate", p, p->talents.conflagrate, options_str ) { affected_by.chaotic_energies = true; affected_by.havoc = true; - triggers.jackpot_destruction = true; + triggers.fiendish_cruelty = p->talents.fiendish_cruelty.ok(); energize_type = action_energize::PER_HIT; energize_resource = RESOURCE_SOUL_SHARD; energize_amount = ( p->talents.conflagrate_2->effectN( 1 ).base_value() ) / 10.0; cooldown->hasted = true; + + if ( p->talents.roaring_blaze.ok() ) + { + if ( p->hero.wither.ok() ) + spread_dot = new wither_t( p, false, "" ); + else + spread_dot = new immolate_t( p, false, "" ); + + spread_dot->background = true; + spread_dot->dual = true; + spread_dot->base_costs[ RESOURCE_MANA ] = 0; + spread_dot->base_dd_multiplier = 0.0; + } } void impact( action_state_t* s ) override { warlock_spell_t::impact( s ); + // TODO: It's still needed to make it so that if the hit is from a duplication havoc conflagrate, it doesn't activate roaring blaze, just like happens ingame if ( p()->talents.roaring_blaze.ok() && result_is_hit( s->result ) ) - td( s->target )->debuffs.conflagrate->trigger(); - - if ( active_4pc() && s->result == RESULT_CRIT ) { - p()->buffs.echo_of_the_azjaqir->trigger(); - p()->procs.echo_of_the_azjaqir->occur(); + if ( td( s->target )->dots.immolate->is_ticking() || td( s->target )->dots.wither->is_ticking() ) + { + int n_spread = as( p()->talents.roaring_blaze->effectN( 2 ).base_value() ); + const auto& tl = target_list(); + // TOCHECK: It appears there is a bug ingame where the dot spreads to an additional target, but only if that target already has the dot + if ( p()->bugs ) + { + auto spread_targets = p()->get_smart_targets( tl, &warlock_td_t::dots_t::agony, n_spread + 1, s->target, spread_range ); + int c = 0; + for ( auto t : spread_targets ) + { + if ( c < 3 || ( td( t )->dots.immolate->is_ticking() || td( t )->dots.wither->is_ticking() ) ) + spread_dot->execute_on_target( t ); + + c++; + } + } + else + { + auto spread_targets = p()->get_smart_targets( tl, &warlock_td_t::dots_t::agony, n_spread, s->target, spread_range ); + for ( auto t : spread_targets ) + spread_dot->execute_on_target( t ); + } + } } } @@ -3793,10 +3521,12 @@ using namespace helpers; p()->buffs.conflagration_of_chaos_cf->expire(); - if ( p()->talents.conflagration_of_chaos.ok() && rng().roll( p()->talents.conflagration_of_chaos->effectN( 1 ).percent() ) ) + if ( p()->talents.conflagration_of_chaos.ok() ) { - p()->buffs.conflagration_of_chaos_cf->trigger(); - p()->procs.conflagration_of_chaos_cf->occur(); + bool success = p()->buffs.conflagration_of_chaos_cf->trigger(); + + if ( success ) + p()->procs.conflagration_of_chaos_cf->occur(); } if ( p()->talents.backdraft.ok() ) @@ -3831,16 +3561,6 @@ using namespace helpers; affected_by.chaotic_energies = true; affected_by.chaos_incarnate = p->talents.chaos_incarnate.ok(); affected_by.touch_of_rancora = p->hero.touch_of_rancora.ok(); - - triggers.decimation = false; - } - - void impact( action_state_t* s ) override - { - warlock_spell_t::impact( s ); - - if ( p()->talents.pyrogenics.ok() ) - td( s->target )->debuffs.pyrogenics->trigger(); } double composite_persistent_multiplier( const action_state_t* s ) const override @@ -3860,7 +3580,7 @@ using namespace helpers; may_miss = may_crit = false; base_tick_time = 1_s; dot_duration = 0_s; - aoe = -1; // Needed to apply Pyrogenics + aoe = -1; // NOTE: Needed to apply Pyrogenics // TODO: Pyrogenics not longer exists in Midnight, is this still needed? affected_by.touch_of_rancora = p->hero.touch_of_rancora.ok(); @@ -3875,41 +3595,32 @@ using namespace helpers; void execute() override { - if ( active_2pc() ) - p()->buffs.demonic_oculus->trigger(); + if ( p()->hero.diabolic_oculi.ok() ) + p()->buffs.demonic_oculi->trigger(); warlock_spell_t::execute(); - if ( p()->talents.burn_to_ashes.ok() ) - p()->buffs.burn_to_ashes->trigger( as( p()->talents.burn_to_ashes->effectN( 3 ).base_value() ) ); - - make_event( *sim, p(), + make_event( *sim, p(), ground_aoe_params_t() .target( execute_state->target ) .x( execute_state->target->x_position ) .y( execute_state->target->y_position ) - .pulse_time( base_tick_time * player->cache.spell_haste() ) - .duration( p()->talents.rain_of_fire->duration() * player->cache.spell_haste() ) + .pulse_time( base_tick_time * player->cache.spell_haste() * ( 1.0 + p()->talents.destructive_rapidity->effectN( 1 ).percent() ) ) + .duration( p()->talents.rain_of_fire->duration() * player->cache.spell_haste() * ( 1.0 + p()->talents.destructive_rapidity->effectN( 1 ).percent() ) ) .start_time( sim->current_time() ) .action( p()->proc_actions.rain_of_fire_tick ) ); - if ( p()->talents.avatar_of_destruction.ok() && p()->buffs.ritual_of_ruin->check() ) - { - p()->warlock_pet_list.overfiends.spawn(); - p()->buffs.summon_overfiend->trigger(); - } - - p()->buffs.ritual_of_ruin->expire(); - p()->buffs.crashing_chaos->decrement(); - } - void impact( action_state_t* s ) override - { - warlock_spell_t::impact( s ); + if ( p()->talents.alythesss_ire.ok() ) + { + p()->buffs.alythesss_ire->decrement(); + + bool success = p()->buffs.alythesss_ire->trigger(); - if ( p()->talents.pyrogenics.ok() ) - td( s->target )->debuffs.pyrogenics->trigger(); + if ( success ) + p()->procs.alythesss_ire->occur(); + } } }; @@ -3927,9 +3638,60 @@ using namespace helpers; } }; + // TODO: The Lake of Fire Debuff is not yet implemented (Immolate/Wither dmg increased) + struct lake_of_fire_t : public warlock_spell_t + { + struct lake_of_fire_tick_t : public warlock_spell_t + { + lake_of_fire_tick_t( warlock_t* p ) + : warlock_spell_t( "Lake of Fire (tick)", p, p->talents.lake_of_fire_tick ) + { + background = dual = true; + aoe = -1; + radius = p->talents.cataclysm->effectN( 1 ).radius(); + + affected_by.chaotic_energies = true; + affected_by.chaos_incarnate = p->talents.chaos_incarnate.ok(); + affected_by.touch_of_rancora = p->hero.touch_of_rancora.ok(); + } + }; + + lake_of_fire_t( warlock_t* p, util::string_view options_str ) + : warlock_spell_t( "Lake of Fire", p, p->talents.lake_of_fire_aoe, options_str ) + { + background = dual = true; + may_miss = may_crit = false; + base_tick_time = 1_s; + dot_duration = 0_s; + aoe = -1; // TODO: Needed? + + if ( !p->proc_actions.lake_of_fire_tick ) + { + p->proc_actions.lake_of_fire_tick = new lake_of_fire_tick_t( p ); + p->proc_actions.lake_of_fire_tick->stats = stats; + } + } + + void execute() override + { + warlock_spell_t::execute(); + + make_event( *sim, p(), + ground_aoe_params_t() + .target( execute_state->target ) + .x( execute_state->target->x_position ) + .y( execute_state->target->y_position ) + .pulse_time( base_tick_time * player->cache.spell_haste() ) + .duration( p()->talents.lake_of_fire_aoe->duration() * player->cache.spell_haste() ) + .start_time( sim->current_time() ) + .action( p()->proc_actions.lake_of_fire_tick ) ); + } + }; + struct cataclysm_t : public warlock_spell_t { - action_t* applied_dot; + warlock_spell_t* applied_dot; + // warlock_spell_t* lake_of_fire; cataclysm_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Cataclysm", p, p->talents.cataclysm, options_str ) @@ -3947,6 +3709,12 @@ using namespace helpers; applied_dot->dual = true; applied_dot->base_costs[ RESOURCE_MANA ] = 0; applied_dot->base_dd_multiplier = 0.0; + + if ( p->talents.lake_of_fire.ok() ) + { + impact_action = new lake_of_fire_t( p, "" ); + add_child( impact_action ); + } } void impact( action_state_t* s ) override @@ -3961,22 +3729,33 @@ using namespace helpers; struct shadowburn_t : public warlock_spell_t { double havoc_rancora_mod_value; + dimensional_rift_t* dimensional_rift; shadowburn_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Shadowburn", p, p->talents.shadowburn, options_str ), havoc_rancora_mod_value( 0.8 ) { - cooldown->hasted = true; - + // cooldown->hasted = true; + affected_by.chaotic_energies = true; affected_by.havoc = true; affected_by.chaos_incarnate = p->talents.chaos_incarnate.ok(); affected_by.touch_of_rancora = p->hero.touch_of_rancora.ok(); triggers.diabolic_ritual = triggers.demonic_art = triggers.demonic_art_buff = p->hero.diabolic_ritual.ok(); - triggers.jackpot_destruction = true; havoc_rancora_mod_value /= p->talents.havoc_debuff->effectN( 1 ).percent(); + + if ( p->talents.dimensional_rift.ok() ) + dimensional_rift = new dimensional_rift_t( p ); + } + + bool ready() override + { + if ( ( target->health_percentage() > p()->talents.shadowburn->effectN( 4 ).base_value() ) && !p()->buffs.fiendish_cruelty->check() ) + return false; + + return warlock_spell_t::ready(); } void impact( action_state_t* s ) override @@ -3986,20 +3765,13 @@ using namespace helpers; if ( result_is_hit( s->result ) ) { td( s->target )->debuffs.shadowburn->trigger(); - - if ( p()->talents.eradication.ok() ) - td( s->target )->debuffs.eradication->trigger(); - - // Fiendish Cruelty checks for state after damage is applied - if ( p()->talents.fiendish_cruelty.ok() && s->target->health_percentage() <= p()->talents.fiendish_cruelty->effectN( 2 ).base_value() ) - make_event( *sim, 0_ms, [ this ] { p()->cooldowns.shadowburn->adjust( timespan_t::from_seconds( -p()->talents.fiendish_cruelty->effectN( 1 ).base_value() ) ); } ); } } void execute() override { - if ( active_2pc() ) - p()->buffs.demonic_oculus->trigger(); + if ( p()->hero.diabolic_oculi.ok() ) + p()->buffs.demonic_oculi->trigger(); // NOTE: 2025-08-27 Rancora Empowered Havoc spells deals 80% of the original damage (bug?) const double prev_base_aoe_multiplier = base_aoe_multiplier; @@ -4013,27 +3785,21 @@ using namespace helpers; p()->buffs.conflagration_of_chaos_sb->expire(); - if ( p()->talents.conflagration_of_chaos.ok() && rng().roll( p()->talents.conflagration_of_chaos->effectN( 1 ).percent() ) ) + if ( p()->talents.conflagration_of_chaos.ok() ) { - p()->buffs.conflagration_of_chaos_sb->trigger(); - p()->procs.conflagration_of_chaos_sb->occur(); - } - - if ( p()->talents.burn_to_ashes.ok() ) - p()->buffs.burn_to_ashes->trigger( as( p()->talents.burn_to_ashes->effectN( 4 ).base_value() ) ); - } - - double composite_target_crit_chance( player_t* t ) const override - { - double m = warlock_spell_t::composite_target_crit_chance( t ); + bool success = p()->buffs.conflagration_of_chaos_sb->trigger(); - if ( target->health_percentage() <= p()->talents.shadowburn->effectN( 4 ).base_value() ) - m += p()->talents.shadowburn->effectN( 3 ).percent(); + if ( success ) + p()->procs.conflagration_of_chaos_sb->occur(); + } - if ( target->health_percentage() <= p()->talents.blistering_atrophy->effectN( 4 ).base_value() ) - m += p()->talents.blistering_atrophy->effectN( 3 ).percent(); + if ( p()->talents.dimensional_rift.ok() && rng().roll( p()->talents.dimensional_rift->effectN( 1 ).percent() ) ) + { + p()->procs.dimensional_rift->occur(); + dimensional_rift->execute_on_target( target ); + } - return m; + p()->buffs.fiendish_cruelty->decrement(); } double calculate_direct_amount( action_state_t* state ) const override @@ -4053,7 +3819,6 @@ using namespace helpers; struct channel_demonfire_tick_t : public warlock_spell_t { bool demonfire_infusion; - bool jackpot; channel_demonfire_tick_t( warlock_t* p ) : warlock_spell_t( "Channel Demonfire (tick)", p, p->talents.channel_demonfire_tick ) @@ -4064,12 +3829,9 @@ using namespace helpers; travel_speed = p->talents.channel_demonfire_travel->missile_speed(); demonfire_infusion = false; - jackpot = false; affected_by.chaotic_energies = true; - triggers.decimation = false; - triggers.jackpot_destruction = true; spell_power_mod.direct = p->talents.channel_demonfire_tick->effectN( 1 ).sp_coeff(); } @@ -4078,10 +3840,6 @@ using namespace helpers; : channel_demonfire_tick_t( p ) { demonfire_infusion = dfi; } - channel_demonfire_tick_t( warlock_t* p, bool dfi, bool jp ) - : channel_demonfire_tick_t( p, dfi ) - { jackpot = jp; } - void impact( action_state_t* s ) override { warlock_spell_t::impact( s ); @@ -4103,9 +3861,6 @@ using namespace helpers; if ( ( s->chain_target == 0 || !p()->bugs ) && demonfire_infusion ) m *= 1.0 + p()->talents.demonfire_infusion->effectN( 3 ).percent(); - if ( jackpot ) - m *= p()->tier.spliced_destro_2pc->effectN( 1 ).percent(); - return m; } }; @@ -4127,7 +3882,7 @@ using namespace helpers; if ( p->talents.channel_demonfire.ok() && p->talents.raging_demonfire.ok() ) { - int num_ticks = (int)( dot_duration / base_tick_time ); + int num_ticks = ( int )( dot_duration / base_tick_time ); dot_duration = num_ticks * base_tick_time; } } @@ -4172,40 +3927,6 @@ using namespace helpers; } }; - struct dimensional_rift_t : public warlock_spell_t - { - dimensional_rift_t( warlock_t* p, util::string_view options_str ) - : warlock_spell_t( "Dimensional Rift", p, p->talents.dimensional_rift, options_str ) - { - harmful = true; - - energize_type = action_energize::ON_CAST; - energize_amount = p->talents.dimensional_rift->effectN( 2 ).base_value() / 10.0; - } - - void execute() override - { - warlock_spell_t::execute(); - - int rift = rng().range( 3 ); - - switch ( rift ) - { - case 0: - p()->warlock_pet_list.shadow_rifts.spawn( p()->talents.shadowy_tear_summon->duration() ); - break; - case 1: - p()->warlock_pet_list.unstable_rifts.spawn( p()->talents.unstable_tear_summon->duration() ); - break; - case 2: - p()->warlock_pet_list.chaos_rifts.spawn( p()->talents.chaos_tear_summon->duration() ); - break; - default: - break; - } - } - }; - struct infernal_awakening_t : public warlock_spell_t { infernal_awakening_t( warlock_t* p ) @@ -4213,14 +3934,12 @@ using namespace helpers; { background = dual = true; aoe = -1; - - triggers.decimation = false; } void execute() override { warlock_spell_t::execute(); - + p()->warlock_pet_list.infernals.spawn(); } }; @@ -4257,15 +3976,6 @@ using namespace helpers; p()->buffs.ritual_mother->extend_duration( p(), reduction ); p()->buffs.ritual_pit_lord->extend_duration( p(), reduction ); } - - if ( active_2pc() ) - { - p()->buffs.demonfire_flurry_trigger->trigger(); - p()->procs.jackpot_destruction->occur(); - - if ( active_4pc() ) - p()->buffs.jackpot_destruction->trigger(); - } } }; @@ -4283,8 +3993,6 @@ using namespace helpers; affected_by.chaotic_energies = true; affected_by.havoc = true; - triggers.jackpot_destruction = true; - if ( p->hero.wither.ok() ) applied_dot = new wither_t( p, "" ); else @@ -4304,13 +4012,18 @@ using namespace helpers; p()->buffs.backdraft->decrement(); - p()->buffs.decimation->decrement(); + if ( p()->talents.avatar_of_destruction.ok() ) + { + p()->warlock_pet_list.overfiends.spawn(); + p()->buffs.summon_overfiend->trigger(); + p()->procs.avatar_of_destruction->occur(); + } } }; // Destruction Actions End // Diabolist Actions Begin - + struct infernal_bolt_t : public warlock_spell_t { const double havoc_mod_value = 1.0; @@ -4321,9 +4034,6 @@ using namespace helpers; energize_type = action_energize::ON_CAST; affected_by.havoc = true; - - triggers.jackpot_demonology = true; - triggers.jackpot_destruction = true; } void init() override @@ -4350,27 +4060,14 @@ using namespace helpers; { warlock_spell_t::execute(); - if ( p()->talents.demonic_calling.ok() ) - p()->buffs.demonic_calling->trigger(); - if ( p()->talents.demonfire_infusion.ok() && p()->rng().roll( p()->talents.demonfire_infusion->effectN( 2 ).percent() ) ) { p()->proc_actions.demonfire_infusion->execute_on_target( target ); p()->procs.demonfire_infusion_inc->occur(); } - p()->buffs.burn_to_ashes->decrement(); p()->buffs.infernal_bolt->decrement(); } - - double composite_da_multiplier( const action_state_t* s ) const override - { - double m = warlock_spell_t::composite_da_multiplier( s ); - - m *= 1.0 + p()->buffs.burn_to_ashes->check_value(); - - return m; - } }; struct ruination_t : public warlock_spell_t @@ -4393,7 +4090,6 @@ using namespace helpers; hog_impact_spell = new hand_of_guldan_t::hog_impact_t( p ); hog_impact_spell->state.shards_used = as( p->hero.ruination_buff->effectN( 2 ).base_value() ); hog_impact_spell->state.rancora_empowered = false; // Ruination HoG impact is never rancora empowered - hog_impact_spell->state.allow_umbral_blaze = false; // Ruination HoG impact can't proc umbral blaze hog_impact_spell->state.allow_succulent_soul = false; // Ruination HoG impact can't benefit from succulent soul } } @@ -4422,9 +4118,6 @@ using namespace helpers; ruination_t( warlock_t* p, util::string_view options_str ) : warlock_spell_t( "Ruination", p, p->hero.ruination_cast, options_str ) { - triggers.jackpot_demonology = true; - triggers.jackpot_destruction = true; - impact_action = new ruination_impact_t( p ); add_child( impact_action ); } @@ -4445,130 +4138,88 @@ using namespace helpers; } }; - struct eye_blast_base_t : public warlock_spell_t + struct eye_explosion_t : public warlock_spell_t { - eye_blast_base_t( std::string_view n, warlock_t* p, const spell_data_t* s ) - : warlock_spell_t( n, p, s ) + eye_explosion_t( warlock_t* p ) + : warlock_spell_t( "Eye Explosion", p, p->hero.eye_explosion ) { - affected_by.chaotic_energies = true; + // TODO: Is it affected by destruction mastery? Check + affected_by.chaotic_energies = destruction(); + + background = true; + may_miss = false; // TODO: Is this true? and needed? + aoe = -1; + reduced_aoe_targets = as( p->hero.diabolic_oculi->effectN( 1 ).base_value() ); } double composite_da_multiplier( const action_state_t* s ) const override { double m = warlock_spell_t::composite_da_multiplier( s ); - /* Effect removed, likely a bug - if ( demonology() ) - m *= 1.0 + p()->tier.inquisitor_db_2pc->effectN( 2 ).percent(); */ - - m *= p()->buffs.demonic_oculus->check(); - + m *= p()->buffs.demonic_oculi->check(); return m; } - }; - - struct eye_blast_aoe_t final : public eye_blast_base_t - { - eye_blast_aoe_t( warlock_t* p, std::string_view n ) : eye_blast_base_t( n, p, p->tier.eye_blast ) - { - background = true; - may_miss = false; - spell_power_mod.direct = data().effectN( 2 ).sp_coeff(); - aoe = -1; - target_filter_callback = secondary_targets_only(); - } - }; - - struct eye_blast_t final : public eye_blast_base_t - { - eye_blast_t( warlock_t* p, std::string_view n ) : eye_blast_base_t( n, p, p->tier.eye_blast ) - { - background = true; - may_miss = false; - spell_power_mod.direct = data().effectN( 1 ).sp_coeff(); - aoe = 0; // Single Target version, triggers aoe version on impact - impact_action = new eye_blast_aoe_t( p, "eye_blast_aoe" ); - add_child( impact_action ); - } void execute() override { - // In game happens just before the damage - if ( active_4pc() ) - p()->buffs.demonic_intelligence->trigger( p()->buffs.demonic_oculus->check() ); + // In game happens just before the damage // TOCHECK: Is this still true in Midnight? + if ( p()->hero.minds_eyes.ok() ) + p()->buffs.minds_eyes->trigger( p()->buffs.demonic_oculi->check() ); - eye_blast_base_t::execute(); + warlock_spell_t::execute(); } void impact( action_state_t* s ) override { - eye_blast_base_t::impact( s ); + warlock_spell_t::impact( s ); - p()->buffs.demonic_oculus->expire(); + if ( s->chain_target == 0 ) + p()->buffs.demonic_oculi->expire(); } }; - // Diabolist Actions End - // Helper Functions Begin - - // Event for triggering delayed refunds from Soul Conduit - // Delay prevents instant reaction time issues for rng refunds - sc_event_t::sc_event_t( warlock_t* p, int c ) - : player_event_t( *p, 100_ms ), - shard_gain( p->gains.soul_conduit ), - pl( p ), - shards_used( c ) - { } - - const char* sc_event_t::name() const - { return "soul_conduit_event"; } - - void sc_event_t::execute() + struct diabolic_gaze_1_t : public warlock_spell_t { - double soul_conduit_rng = 0.0; - - if ( pl->affliction() ) - soul_conduit_rng = pl->talents.soul_conduit->effectN( 1 ).percent(); - - if ( pl->demonology() ) - soul_conduit_rng = pl->talents.soul_conduit->effectN( 2 ).percent(); - - if ( pl->destruction() ) - soul_conduit_rng = pl->talents.soul_conduit->effectN( 3 ).percent(); - - for ( int i = 0; i < shards_used; i++ ) + diabolic_gaze_1_t( warlock_t* p ) + : warlock_spell_t( "Diabolic Gaze (1)", p, p->hero.diabolic_gaze_dmg_1 ) { - if ( rng().roll( soul_conduit_rng ) ) - { - pl->sim->print_log( "Soul Conduit proc occurred for Warlock {}, refunding 1.0 soul shards.", pl->name() ); - pl->resource_gain( RESOURCE_SOUL_SHARD, 1.0, shard_gain ); - pl->procs.soul_conduit->occur(); - } + // TODO: Is it affected by destruction mastery? Check + affected_by.chaotic_energies = destruction(); + + background = dual = true; + may_miss = false; // TODO: Is this true? and needed? } - } + }; - // Checks whether Tormented Crescendo conditions are met - bool helpers::crescendo_check( warlock_t* p, player_t* tar ) + struct diabolic_gaze_2_t : public warlock_spell_t { - if ( tar != p->ua_target ) - return false; - - bool valid = p->get_target_data( tar )->dots.unstable_affliction->is_ticking(); - - if ( p->hero.wither.ok() ) + diabolic_gaze_2_t( warlock_t* p ) + : warlock_spell_t( "Diabolic Gaze (2)", p, p->hero.diabolic_gaze_dmg_2 ) { - valid = valid && p->get_target_data( tar )->dots.wither->is_ticking(); + // TODO: Is it affected by destruction mastery? Check + affected_by.chaotic_energies = destruction(); + + background = dual = true; + may_miss = false; // TODO: Is this true? and needed? } - else + }; + + struct diabolic_gaze_3_t : public warlock_spell_t + { + diabolic_gaze_3_t( warlock_t* p ) + : warlock_spell_t( "Diabolic Gaze (3)", p, p->hero.diabolic_gaze_dmg_3 ) { - valid = valid && p->get_target_data( tar )->dots.corruption->is_ticking(); + // TODO: Is it affected by destruction mastery? Check + affected_by.chaotic_energies = destruction(); + + background = dual = true; + may_miss = false; // TODO: Is this true? and needed? } - - valid = valid && p->get_target_data( tar )->dots.agony->is_ticking(); + }; - return valid; - } + // Diabolist Actions End + // Helper Functions Begin void helpers::nightfall_updater( warlock_t* p, dot_t* d ) { @@ -4582,6 +4233,14 @@ using namespace helpers; double active_corruptions = p->get_active_dots( d ); increment_max *= std::pow( active_corruptions, -2.0 / 3.0 ); + // Creeping Death no longer affects the chance of gaining Nightfall + if ( p->talents.creeping_death.ok() ) + increment_max *= 1.0 + p->talents.creeping_death->effectN( 1 ).percent(); + + // Sataiel’s Volition no longer affects the chance of gaining Nightfall + if ( p->hero.sataiels_volition.ok() ) + increment_max *= 1.0 + p->hero.sataiels_volition->effectN( 1 ).percent(); + p->corruption_accumulator += p->rng().range( 0.0, increment_max ); if ( p->corruption_accumulator >= 1 ) @@ -4612,7 +4271,7 @@ using namespace helpers; if( malevolence ) { - stacks = as( p->hero.malevolence->effectN( 1 ).base_value() ); + stacks = as( p->hero.malevolence->effectN( 1 ).base_value() ) + as ( p->hero.alzzins_iniquity->effectN( 2 ).base_value() ) ; } tdata->dots.wither->increment( stacks ); @@ -4655,31 +4314,13 @@ using namespace helpers; p->cooldowns.blackened_soul->start(); } - void helpers::trigger_jackpot_ua( warlock_t* p ) + // Event for spawning Wild Imps for Demonology + imp_delay_event_t::imp_delay_event_t( warlock_t* p, double delay, double exp, int _index ) : player_event_t( *p, timespan_t::from_millis( delay ) ) { - int remaining_triggers = as( p->tier.spliced_aff_4pc->effectN( 2 ).base_value() ); - - for ( const auto target : p->sim->target_non_sleeping_list ) - { - warlock_td_t* tdata = p->get_target_data( target ); - if ( !tdata ) - continue; - - if ( tdata->dots.unstable_affliction->is_ticking() || tdata->dots.jackpot_ua->is_ticking() ) - continue; - - p->proc_actions.jackpot_ua->execute_on_target( target ); - remaining_triggers--; - - if ( remaining_triggers <= 0 ) - return; - } + diff = timespan_t::from_millis( exp - delay ); + index = _index; } - // Event for spawning Wild Imps for Demonology - imp_delay_event_t::imp_delay_event_t( warlock_t* p, double delay, double exp ) : player_event_t( *p, timespan_t::from_millis( delay ) ) - { diff = timespan_t::from_millis( exp - delay ); } - const char* imp_delay_event_t::name() const { return "imp_delay"; } @@ -4687,7 +4328,15 @@ using namespace helpers; { warlock_t* p = static_cast( player() ); - p->warlock_pet_list.wild_imps.spawn(); + auto imps = p->warlock_pet_list.wild_imps.spawn(); + + if ( p->talents.imp_gang_boss.ok() && index == 0 ) + { + for ( auto imp : imps ) + { + imp->buffs.imp_gang_boss->trigger(); + } + } // Remove this event from the vector auto it = std::find( p->wild_imp_spawns.begin(), p->wild_imp_spawns.end(), this ); @@ -4699,8 +4348,43 @@ using namespace helpers; timespan_t imp_delay_event_t::expected_time() { return std::max( 0_ms, this->remains() + diff ); } + // Event ot handle UA stacks decreases + ua_stack_event_t::ua_stack_event_t( warlock_t* p, dot_t* _dot , timespan_t event_time ) + : player_event_t( *p, event_time ), + dot( _dot ) + { } + + const char* ua_stack_event_t::name() const + { return "ua_stack"; } + + void ua_stack_event_t::execute() + { + warlock_t* p = static_cast( player() ); + + // if ( dot->is_ticking() && dot->tick_event && dot->current_action && dot->remains() > 0_ms ) // TODO: Alternative that takes into account the extra tick on refresh; which is more appropriate? + // if ( dot->is_ticking() && dot->tick_event && dot->current_action && dot->remains() > 0_ms && dot->current_stack() > 1 ) + if ( dot->is_ticking() && dot->tick_event && dot->current_action && dot->remains() > 0_ms ) + { + player_t* target = dot->state->target; + + dot->decrement( 1 ); + assert( ( dot->is_ticking() && dot->current_stack() > 0 ) && "UA stack decrement event should not cancel the DoT." ); + + // if ( p->talents.fatal_echoes.ok() && !target->is_sleeping() && dot->is_ticking() && dot->current_stack() > 0 && rng().roll( p->talents.fatal_echoes->effectN( 1 ).percent() ) ) + if ( p->talents.fatal_echoes.ok() && !target->is_sleeping() && rng().roll( p->talents.fatal_echoes->effectN( 1 ).percent() ) ) + { + p->procs.fatal_echoes->occur(); + dot->current_action->set_target( target ); + auto tmp_cost = dot->current_action->base_costs[ RESOURCE_SOUL_SHARD ]; + dot->current_action->base_costs[ RESOURCE_SOUL_SHARD ] = 0; + dot->current_action->execute(); + dot->current_action->base_costs[ RESOURCE_SOUL_SHARD ] = tmp_cost; + } + } + } + // Helper Functions End - + // Action Creation Begin action_t* warlock_t::create_action( util::string_view action_name, util::string_view options_str ) @@ -4749,8 +4433,8 @@ using namespace helpers; // Pets if ( action_name == "summon_felhunter" ) return new summon_main_pet_t( "felhunter", this ); - if ( action_name == "summon_felguard" && demonology() ) - return new summon_main_pet_t( "felguard", this ); + if ( action_name == "summon_felguard" && demonology() && talents.summon_felguard.ok() ) + return new summon_main_pet_t( "felguard", this, 30146 ); if ( action_name == "summon_sayaad" ) return new summon_main_pet_t( "sayaad", this, 366222 ); if ( action_name == "summon_succubus" ) @@ -4772,7 +4456,7 @@ using namespace helpers; // Shared Spells if ( action_name == "drain_life" ) return new drain_life_t( this, options_str ); - if ( action_name == "corruption" && !destruction() ) + if ( action_name == "corruption" && affliction() ) return new corruption_t( this, options_str, false ); if ( action_name == "shadow_bolt" && !destruction() ) return new shadow_bolt_t( this, options_str ); @@ -4796,20 +4480,14 @@ using namespace helpers; return new summon_darkglare_t( this, options_str ); if ( action_name == "drain_soul" ) return new drain_soul_t( this, options_str ); + if ( action_name == "malefic_grasp" ) + return new malefic_grasp_t( this, options_str ); if ( action_name == "haunt" ) return new haunt_t( this, options_str ); - if ( action_name == "phantom_singularity" ) - return new phantom_singularity_t( this, options_str ); - if ( action_name == "vile_taint" ) - return new vile_taint_t( this, options_str ); - if ( action_name == "malefic_rapture" ) - return new malefic_rapture_t( this, options_str ); - if ( action_name == "soul_rot" ) - return new soul_rot_t( this, options_str ); + if ( action_name == "dark_harvest" ) + return new dark_harvest_t( this, options_str ); if ( action_name == "seed_of_corruption" ) return new seed_of_corruption_t( this, options_str ); - if ( action_name == "oblivion" ) - return new oblivion_t( this, options_str ); return nullptr; } @@ -4822,20 +4500,18 @@ using namespace helpers; return new hand_of_guldan_t( this, options_str ); if ( action_name == "implosion" ) return new implosion_t( this, options_str ); - if ( action_name == "demonic_strength" ) - return new demonic_strength_t( this, options_str ); - if ( action_name == "bilescourge_bombers" ) - return new bilescourge_bombers_t( this, options_str ); if ( action_name == "power_siphon" ) return new power_siphon_t( this, options_str ); if ( action_name == "call_dreadstalkers" ) return new call_dreadstalkers_t( this, options_str ); if ( action_name == "summon_demonic_tyrant" ) return new summon_demonic_tyrant_t( this, options_str ); - if ( action_name == "summon_vilefiend" ) - return new summon_vilefiend_t( this, options_str ); - if ( action_name == "grimoire_felguard" ) - return new grimoire_felguard_t( this, options_str ); + if ( action_name == "grimoire_imp_lord" ) + return new grimoire_imp_lord_t( this, options_str ); + if ( action_name == "grimoire_fel_ravager" ) + return new grimoire_fel_ravager_t( this, options_str ); + if ( action_name == "summon_doomguard" ) + return new summon_doomguard_t( this, options_str ); return nullptr; } @@ -4864,8 +4540,6 @@ using namespace helpers; return new cataclysm_t( this, options_str ); if ( action_name == "channel_demonfire" ) return new channel_demonfire_t( this, options_str ); - if ( action_name == "dimensional_rift" ) - return new dimensional_rift_t( this, options_str ); return nullptr; } @@ -4919,24 +4593,24 @@ using namespace helpers; void warlock_t::create_affliction_proc_actions() { - proc_actions.jackpot_ua = new jackpot_unstable_affliction_t( this ); } void warlock_t::create_demonology_proc_actions() { - proc_actions.bilescourge_bombers_proc = new bilescourge_bombers_proc_t( this ); proc_actions.doom_proc = new doom_t( this ); } void warlock_t::create_destruction_proc_actions() { proc_actions.demonfire_infusion = new channel_demonfire_tick_t( this, true ); - proc_actions.jackpot_cdf = new channel_demonfire_tick_t( this, false, true ); } void warlock_t::create_diabolist_proc_actions() { - proc_actions.eye_blast = new eye_blast_t( this, "eye_blast" ); + proc_actions.eye_explosion= new eye_explosion_t( this ); + proc_actions.diabolic_gaze_1 = new diabolic_gaze_1_t( this ); + proc_actions.diabolic_gaze_2 = new diabolic_gaze_2_t( this ); + proc_actions.diabolic_gaze_3 = new diabolic_gaze_3_t( this ); } void warlock_t::create_hellcaller_proc_actions() diff --git a/engine/class_modules/warlock/sc_warlock_init.cpp b/engine/class_modules/warlock/sc_warlock_init.cpp index 28940714bae..5c5fa358eb8 100644 --- a/engine/class_modules/warlock/sc_warlock_init.cpp +++ b/engine/class_modules/warlock/sc_warlock_init.cpp @@ -25,16 +25,10 @@ namespace warlock // Affliction warlock_base.affliction_warlock = find_specialization_spell( "Affliction Warlock", WARLOCK_AFFLICTION ); // Should be ID 137043 warlock_base.potent_afflictions = find_mastery_spell( WARLOCK_AFFLICTION ); // Should be ID 77215 - warlock_base.agony = find_class_spell( "Agony" ); // Should be ID 980 - warlock_base.agony_2 = conditional_spell_lookup( warlock_base.affliction_warlock->ok(), 231792 ); // Rank 2, +4 to max stacks - warlock_base.malefic_rapture = find_specialization_spell( "Malefic Rapture", WARLOCK_AFFLICTION ); // Should be ID 324536 - warlock_base.malefic_rapture_dmg = conditional_spell_lookup( warlock_base.affliction_warlock->ok(), 324540 ); // Demonology warlock_base.demonology_warlock = find_specialization_spell( "Demonology Warlock", WARLOCK_DEMONOLOGY ); // Should be ID 137044 warlock_base.master_demonologist = find_mastery_spell( WARLOCK_DEMONOLOGY ); // Should be ID 77219 - warlock_base.hand_of_guldan = find_class_spell( "Hand of Gul'dan" ); // Should be ID 105174 - warlock_base.hog_impact = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 86040 ); // Contains impact damage data warlock_base.wild_imp = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 104317 ); // Contains pet summoning information warlock_base.fel_firebolt_2 = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 334591 ); // 20% cost reduction for Wild Imps @@ -44,72 +38,55 @@ namespace warlock warlock_base.immolate = find_specialization_spell( "Immolate" ); // Should be ID 193541 warlock_base.immolate_old = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 348 ); // This contains the actual direct damage and cast data, but no longer appears in class_spell list warlock_base.immolate_dot = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 157736 ); // DoT data - warlock_base.incinerate = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 29722 ); // Should be ID 29722 TODO: 2024-07-05 this spell was missing from the non-PTR class spell list. Fix once this comes back + warlock_base.incinerate = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 29722 ); // Should be ID 29722 warlock_base.incinerate_energize = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 244670 ); // Used for resource gain information - warlock_base.chaos_bolt = find_specialization_spell( "Chaos Bolt" ); // Should be ID 116858 warlock_t::init_spells_affliction(); warlock_t::init_spells_demonology(); warlock_t::init_spells_destruction(); // Talents - talents.grimoire_of_sacrifice = find_talent_spell( talent_tree::SPECIALIZATION, "Grimoire of Sacrifice" ); // Aff/Destro only. Should be ID 108503 - talents.grimoire_of_sacrifice_buff = conditional_spell_lookup( talents.grimoire_of_sacrifice->ok(), 196099 ); // Buff data and RPPM - talents.grimoire_of_sacrifice_proc = conditional_spell_lookup( talents.grimoire_of_sacrifice->ok(),196100 ); // Damage data - - talents.summoners_embrace = find_talent_spell( talent_tree::SPECIALIZATION, "Summoner's Embrace" ); // Should be ID 453105 - - talents.demonic_inspiration = find_talent_spell( talent_tree::CLASS, "Demonic Inspiration" ); // Should be ID 386858 - talents.demonic_inspiration_buff = conditional_spell_lookup( talents.demonic_inspiration, 386861 ); - talents.demonic_embrace = find_talent_spell( talent_tree::CLASS, "Demonic Embrace" ); // Should be ID 288843 - talents.wrathful_minion = find_talent_spell( talent_tree::CLASS, "Wrathful Minion" ); // Should be ID 386864 - talents.demonic_fortitude = find_talent_spell( talent_tree::CLASS, "Demonic Fortitude" ); // Should be ID 386617 - talents.socrethars_guile = find_talent_spell( talent_tree::CLASS, "Socrethar's Guile" ); // Should be ID 405936 + talents.pact_of_the_annihilan = find_talent_spell( talent_tree::CLASS, "Pact of the Annihilan" ); // Should be ID 1270693 - talents.sargerei_technique = find_talent_spell( talent_tree::CLASS, "Sargerei Technique" ); // Should be ID 405955 + talents.pact_of_the_satyr = find_talent_spell( talent_tree::CLASS, "Pact of the Satyr" ); // Should be ID 1270691 - talents.demonic_tactics = find_talent_spell( talent_tree::CLASS, "Demonic Tactics" ); // Should be ID 452894 - - talents.soul_conduit = find_talent_spell( talent_tree::CLASS, "Soul Conduit" ); // Should be ID 215941 + talents.pact_of_the_eredar = find_talent_spell( talent_tree::CLASS, "Pact of the Eredar" ); // Should be ID 1270695 talents.soulburn = find_talent_spell( talent_tree::CLASS, "Soulburn" ); // Should be ID 385899 - talents.soulburn_buff = conditional_spell_lookup( talents.soulburn->ok(), 387626 ); + talents.soulburn_buff = conditional_spell_lookup( talents.soulburn.ok(), 387626 ); + + talents.summoners_embrace = find_talent_spell( talent_tree::SPECIALIZATION, "Summoner's Embrace" ); // Should be ID 453105 + + talents.grimoire_of_sacrifice = find_talent_spell( talent_tree::SPECIALIZATION, "Grimoire of Sacrifice" ); // Aff/Destro only. Should be ID 108503 + talents.grimoire_of_sacrifice_buff = conditional_spell_lookup( talents.grimoire_of_sacrifice.ok(), 196099 ); // Buff data and RPPM + talents.grimoire_of_sacrifice_proc = conditional_spell_lookup( talents.grimoire_of_sacrifice.ok(),196100 ); // Damage data warlock_t::init_spells_diabolist(); warlock_t::init_spells_hellcaller(); warlock_t::init_spells_soul_harvester(); - version_11_1_0_data = find_spell( 1214442 ); // For 11.1 version checking, new talent: Demonfire Infusion - // Register passives - // TOCHECK: 2025-08-27 Currently Gloom of Nathreza talent is bugged for Destruction and does not work + // NOTE: 2026-02-13 Currently Gloom of Nathreza talent is bugged for Destruction and does not work if ( destruction() && bugs ) deregister_passive_effect( hero.gloom_of_nathreza->effectN( 2 ) ); - register_passive_effect_mask( talents.socrethars_guile, - affliction() ? effect_mask_t( false ).enable( 1, 4 ) : effect_mask_t( true ).disable( 1, 4 ) ); - register_passive_effect_mask( hero.mark_of_xavius, affliction() ? effect_mask_t( false ).enable( 1 ) : effect_mask_t( true ).disable( 1 ) ); - register_passive_effect_mask( tier.inquisitor_hc_2pc, - affliction() ? effect_mask_t( true ).disable( 2 ) : effect_mask_t( true ).disable( 3 ) ); - - // adjustment to demonfire_flurry is handled manually during buff initialization + // Adjustment to Demonfire Flurry is handled manually during buff initialization register_passive_affect_list( talents.raging_demonfire, affect_list_t( 3 ).remove_spell( 1217731 ) ); - // Shadow Bolt Volley affected but not in the spell data whitelist + // Shadowbolt Volley affected but not in the spell data whitelist register_passive_affect_list( talents.improved_shadow_bolt, affect_list_t( 2 ).add_spell( 453176 ) ); - register_passive_affect_list( talents.sargerei_technique, affect_list_t( 1 ).add_spell( 453176 ) ); parse_all_class_passives(); parse_all_passive_talents(); parse_all_passive_sets(); - + if ( bugs ) // Mark of Perotharn is being applied twice in what appears to be a bug parse_passive_effects( hero.mark_of_perotharn, true ); } @@ -117,325 +94,270 @@ namespace warlock void warlock_t::init_spells_affliction() { // Talents - talents.unstable_affliction = find_talent_spell( talent_tree::SPECIALIZATION, "Unstable Affliction" ); // Should be ID 316099 - talents.unstable_affliction_2 = conditional_spell_lookup( talents.unstable_affliction->ok(), 231791 ); // Soul Shard on demise - talents.unstable_affliction_3 = conditional_spell_lookup( talents.unstable_affliction->ok(), 334315 ); // +5 seconds duration - - talents.writhe_in_agony = find_talent_spell( talent_tree::SPECIALIZATION, "Writhe in Agony" ); // Should be ID 196102 - - talents.seed_of_corruption = find_talent_spell( talent_tree::SPECIALIZATION, "Seed of Corruption" ); // Should be ID 27243 - talents.seed_of_corruption_aoe = conditional_spell_lookup( talents.seed_of_corruption->ok(), 27285 ); // Explosion damage - - talents.dark_virtuosity = find_talent_spell( talent_tree::SPECIALIZATION, "Dark Virtuosity" ); // Should be ID 405327 - - talents.absolute_corruption = find_talent_spell( talent_tree::SPECIALIZATION, "Absolute Corruption" ); // Should be ID 196103 + talents.agony = find_talent_spell( talent_tree::SPECIALIZATION, "Agony" ); // Should be ID 980 - talents.siphon_life = find_talent_spell( talent_tree::SPECIALIZATION, "Siphon Life" ); // Should be ID 452999 + talents.unstable_affliction = find_talent_spell( talent_tree::SPECIALIZATION, "Unstable Affliction" ); // Should be ID 1259790 + talents.unstable_affliction_2 = conditional_spell_lookup( talents.unstable_affliction.ok(), 231791 ); // Soul Shard on demise + // talents.unstable_affliction_3 = conditional_spell_lookup( talents.unstable_affliction.ok(), 334315 ); // +5 seconds duration // TODO: Is this still a thing in Midnight? - talents.kindled_malice = find_talent_spell(talent_tree::SPECIALIZATION, "Kindled Malice"); // Should be ID 405330 + talents.seed_of_corruption = find_talent_spell( talent_tree::SPECIALIZATION, "Seed of Corruption" ); // Should be ID 27243 + talents.seed_of_corruption_aoe = conditional_spell_lookup( talents.seed_of_corruption.ok(), 27285 ); // Explosion damage + talents.seed_of_corruption_is_out_dnt = conditional_spell_lookup( talents.seed_of_corruption.ok(), 1279998 ); talents.nightfall = find_talent_spell( talent_tree::SPECIALIZATION, "Nightfall" ); // Should be ID 108558 talents.nightfall_buff = conditional_spell_lookup( warlock_base.affliction_warlock->ok(), 264571 ); + talents.nightfall_buff_2 = conditional_spell_lookup( warlock_base.affliction_warlock->ok(), 1260279 ); - talents.volatile_agony = find_talent_spell( talent_tree::SPECIALIZATION, "Volatile Agony" ); // Should be ID 453034 - talents.volatile_agony_aoe = conditional_spell_lookup( talents.volatile_agony->ok(), 453035 ); + talents.haunt = find_talent_spell( talent_tree::SPECIALIZATION, "Haunt" ); // Should be ID 48181 + + talents.shared_agony = find_talent_spell( talent_tree::SPECIALIZATION, "Shared Agony" ); // Should be ID 1259825 talents.improved_shadow_bolt = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Shadow Bolt" ); // Should be ID 453080 talents.drain_soul = find_talent_spell( talent_tree::SPECIALIZATION, "Drain Soul" ); // Should be ID 388667 - talents.drain_soul_dot = conditional_spell_lookup( talents.drain_soul->ok(), 198590 ); // This contains all the channel data - - talents.vile_taint = find_talent_spell( talent_tree::SPECIALIZATION, "Vile Taint" ); // Should be ID 278350 - talents.vile_taint_dot = conditional_spell_lookup( talents.vile_taint->ok(), 386931 ); // DoT info here + talents.drain_soul_dot = conditional_spell_lookup( talents.drain_soul.ok(), 198590 ); // This contains all the channel data - talents.phantom_singularity = find_talent_spell( talent_tree::SPECIALIZATION, "Phantom Singularity" ); // Should be ID 205179 - talents.phantom_singularity_tick = conditional_spell_lookup( talents.phantom_singularity->ok(), 205246 ); // AoE damage info + talents.improved_haunt = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Haunt" ); // Should be ID 458034 - talents.haunt = find_talent_spell( talent_tree::SPECIALIZATION, "Haunt" ); // Should be ID 48181 + talents.absolute_corruption = find_talent_spell( talent_tree::SPECIALIZATION, "Absolute Corruption" ); // Should be ID 196103 - talents.shadow_embrace = find_talent_spell( talent_tree::SPECIALIZATION, "Shadow Embrace" ); // Should be ID 32388 - talents.shadow_embrace_debuff_ds = conditional_spell_lookup( warlock_base.affliction_warlock->ok(), 32390 ); - talents.shadow_embrace_debuff_sb = conditional_spell_lookup( warlock_base.affliction_warlock->ok(), 453206 ); + talents.siphon_life = find_talent_spell( talent_tree::SPECIALIZATION, "Siphon Life" ); // Should be ID 452999 - talents.sacrolashs_dark_strike = find_talent_spell( talent_tree::SPECIALIZATION, "Sacrolash's Dark Strike" ); // Should be ID 386986 + talents.cunning_cruelty = find_talent_spell( talent_tree::SPECIALIZATION, "Cunning Cruelty" ); // Should be ID 453172 + talents.shadowbolt_volley = conditional_spell_lookup( talents.cunning_cruelty.ok(), 453176 ); - talents.summon_darkglare = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Darkglare"); // Should be ID 205180 - talents.eye_beam = conditional_spell_lookup( talents.summon_darkglare->ok(), 205231 ); + talents.withering_bolt = find_talent_spell( talent_tree::SPECIALIZATION, "Withering Bolt" ); // Should be ID 386976 - talents.cunning_cruelty = find_talent_spell( talent_tree::SPECIALIZATION, "Cunning Cruelty" ); // Should be ID - talents.shadow_bolt_volley = conditional_spell_lookup( talents.cunning_cruelty->ok(), 453176 ); + talents.creeping_death = find_talent_spell( talent_tree::SPECIALIZATION, "Creeping Death" ); // Should be ID 264000 - talents.infirmity = find_talent_spell( talent_tree::SPECIALIZATION, "Infirmity" ); // Should be ID 458036 - talents.infirmity_debuff = conditional_spell_lookup( talents.infirmity->ok(), 458219 ); + talents.dark_harvest = find_talent_spell( talent_tree::SPECIALIZATION, "Dark Harvest" ); // Should be ID 1257052 + talents.dark_harvest_dmg = conditional_spell_lookup( talents.dark_harvest.ok(), 1257065 ); - talents.improved_haunt = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Haunt" ); // Should be ID 458034 + talents.practiced_pestilence = find_talent_spell( talent_tree::SPECIALIZATION, "Practiced Pestilence" ); // Should be ID 1259811 - talents.malediction = find_talent_spell( talent_tree::SPECIALIZATION, "Malediction" ); // Should be ID 453087 + talents.summon_darkglare = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Darkglare" ); // Should be ID 205180 + talents.eye_beam = conditional_spell_lookup( talents.summon_darkglare.ok(), 205231 ); - talents.malevolent_visionary = find_talent_spell( talent_tree::SPECIALIZATION, "Malevolent Visionary" ); // Should be ID 387273 - talents.malevolent_visionary_blast = conditional_spell_lookup( talents.malevolent_visionary->ok(), 453233 ); + talents.cull_the_weak = find_talent_spell( talent_tree::SPECIALIZATION, "Cull the Weak" ); // Should be ID 1259886 - talents.contagion = find_talent_spell( talent_tree::SPECIALIZATION, "Contagion" ); // Should be ID 453096 + talents.malediction = find_talent_spell( talent_tree::SPECIALIZATION, "Malediction" ); // Should be ID 453087 - talents.cull_the_weak = find_talent_spell( talent_tree::SPECIALIZATION, "Cull the Weak" ); // Should be ID 453056 + talents.sudden_onset = find_talent_spell( talent_tree::SPECIALIZATION, "Sudden Onset" ); // Should be ID 1260209 - talents.creeping_death = find_talent_spell( talent_tree::SPECIALIZATION, "Creeping Death" ); // Should be ID 264000 + talents.eye_contract = find_talent_spell( talent_tree::SPECIALIZATION, "Eye Contract" ); // Should be ID 1279521 - talents.soul_rot = find_talent_spell( talent_tree::SPECIALIZATION, "Soul Rot" ); // Should be ID 386997 + talents.malefic_grasp = find_talent_spell( talent_tree::SPECIALIZATION, "Malefic Grasp" ); // Should be ID 1261149 + talents.malefic_grasp_2 = conditional_spell_lookup( talents.malefic_grasp.ok(), 1261153 ); + talents.malefic_grasp_3 = conditional_spell_lookup( talents.malefic_grasp.ok(), 1279659 ); - talents.tormented_crescendo = find_talent_spell( talent_tree::SPECIALIZATION, "Tormented Crescendo" ); // Should be ID 387075 - talents.tormented_crescendo_buff = conditional_spell_lookup( warlock_base.affliction_warlock->ok(), 387079 ); + talents.nether_plating = find_talent_spell( talent_tree::SPECIALIZATION, "Nether Plating" ); // Should be ID 1280733 - talents.xavius_gambit = find_talent_spell( talent_tree::SPECIALIZATION, "Xavius' Gambit" ); // Should be ID 416615 + talents.sacrolashs_dark_strike = find_talent_spell( talent_tree::SPECIALIZATION, "Sacrolash's Dark Strike" ); // Should be ID 386986 - talents.focused_malignancy = find_talent_spell( talent_tree::SPECIALIZATION, "Focused Malignancy" ); // Should be ID 399668 + talents.contagion = find_talent_spell( talent_tree::SPECIALIZATION, "Contagion" ); // Should be ID 453096 - talents.perpetual_unstability = find_talent_spell( talent_tree::SPECIALIZATION, "Perpetual Unstability" ); // Should be ID 459376 - talents.perpetual_unstability_proc = conditional_spell_lookup( talents.perpetual_unstability->ok(), 459461 ); + talents.shard_instability = find_talent_spell( talent_tree::SPECIALIZATION, "Shard Instability" ); // Should be ID 1260264 + talents.shard_instability_buff = conditional_spell_lookup( talents.shard_instability.ok(), 1260269 ); - talents.malign_omen = find_talent_spell( talent_tree::SPECIALIZATION, "Malign Omen" ); // Should be ID 458041 - talents.malign_omen_buff = conditional_spell_lookup( talents.malign_omen->ok(), 458043 ); + talents.niskaran_methods = find_talent_spell( talent_tree::SPECIALIZATION, "Niskaran Methods" ); // Should be ID 1279510 - talents.relinquished = find_talent_spell( talent_tree::SPECIALIZATION, "Relinquished" ); // Should be ID 453083 + talents.potent_soul_shards = find_talent_spell( talent_tree::SPECIALIZATION, "Potent Soul Shards" ); // Should be ID 1259815 - talents.withering_bolt = find_talent_spell( talent_tree::SPECIALIZATION, "Withering Bolt" ); // Should be ID 386976 + talents.nocturnal_yield = find_talent_spell( talent_tree::SPECIALIZATION, "Nocturnal Yield" ); // Should be ID 1260271 - talents.improved_malefic_rapture = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Malefic Rapture" ); // Should be ID 454378 + talents.xavius_gambit = find_talent_spell( talent_tree::SPECIALIZATION, "Xavius' Gambit" ); // Should be ID 416615 - talents.oblivion = find_talent_spell( talent_tree::SPECIALIZATION, "Oblivion" ); // Should be ID 417537 + talents.ravenous_afflictions = find_talent_spell( talent_tree::SPECIALIZATION, "Ravenous Afflictions" ); // Should be ID 459440 - talents.deaths_embrace = find_talent_spell( talent_tree::SPECIALIZATION, "Death's Embrace" ); // Should be ID 453189 + talents.seeds_of_destruction = find_talent_spell( talent_tree::SPECIALIZATION, "Seeds of Destruction" ); // Should be ID 1259838 - talents.dark_harvest = find_talent_spell( talent_tree::SPECIALIZATION, "Dark Harvest" ); // Should be ID 387016 - talents.dark_harvest_buff = conditional_spell_lookup( talents.dark_harvest->ok(), 387018 ); + talents.fatal_echoes = find_talent_spell( talent_tree::SPECIALIZATION, "Fatal Echoes" ); // Should be ID 1260229 - talents.ravenous_afflictions = find_talent_spell( talent_tree::SPECIALIZATION, "Ravenous Afflictions" ); // Should be ID 459440 + talents.cascading_calamity = find_talent_spell( talent_tree::SPECIALIZATION, "Cascading Calamity" ); // Should be ID 1261124 + talents.cascading_calamity_buff = conditional_spell_lookup( talents.cascading_calamity.ok(), 1261125 ); - talents.malefic_touch = find_talent_spell( talent_tree::SPECIALIZATION, "Malefic Touch" ); // Should be ID 458029 - talents.malefic_touch_proc = conditional_spell_lookup( talents.malefic_touch->ok(), 458131 ); + talents.deaths_embrace = find_talent_spell( talent_tree::SPECIALIZATION, "Death's Embrace" ); // Should be ID 234876 - // Additional Tier Set spell data + talents.patient_zero = find_talent_spell( talent_tree::SPECIALIZATION, "Patient Zero" ); // Should be ID 1260285 - // Nerub-ar Palace - tier.hexflame_aff_2pc = sets->set( WARLOCK_AFFLICTION, TWW1, B2 ); // Should be ID 453643 - tier.hexflame_aff_4pc = sets->set( WARLOCK_AFFLICTION, TWW1, B4 ); // Should be ID 453642 - tier.umbral_lattice = conditional_spell_lookup( tier.hexflame_aff_4pc->ok(), 455679 ); // TWW1_B4 + talents.sow_the_seeds = find_talent_spell( talent_tree::SPECIALIZATION, "Sow the Seeds" ); // Should be ID 196226 - // Liberation of Undermine - tier.spliced_aff_2pc = sets->set( WARLOCK_AFFLICTION, TWW2, B2 ); // Should be ID 1215678 - tier.spliced_aff_4pc = sets->set( WARLOCK_AFFLICTION, TWW2, B4 ); // Should be ID 1215683 - tier.spliced_aff_jackpot = conditional_spell_lookup( tier.spliced_aff_2pc, 1219034 ); // TWW2_B2 - tier.jackpot_ua = conditional_spell_lookup( tier.spliced_aff_2pc, 1219045 ); // TWW2_B2_B4 + // TODO: Not Yet Implemented + talents.shadow_of_nathreza_1 = find_talent_spell( talent_tree::SPECIALIZATION, "Shadow of Nathreza", 1 ); // Should be ID 1261984 (I) + talents.shadow_of_nathreza_2 = find_talent_spell( talent_tree::SPECIALIZATION, "Shadow of Nathreza", 2 ); // Should be ID 1261990 (II) + talents.shadow_of_nathreza_3 = find_talent_spell( talent_tree::SPECIALIZATION, "Shadow of Nathreza", 3 ); // Should be ID 1261992 (III) } void warlock_t::init_spells_demonology() { // Talents + talents.hand_of_guldan = find_talent_spell( talent_tree::SPECIALIZATION, "Hand of Gul'dan" ); // Should be ID 1250273 + talents.hand_of_guldan_cast = conditional_spell_lookup( talents.hand_of_guldan.ok(), 105174 ); + talents.hog_impact = conditional_spell_lookup( talents.hand_of_guldan.ok(), 86040 ); // Contains impact damage data + talents.demoniac = find_talent_spell( talent_tree::SPECIALIZATION, "Demoniac" ); // Should be ID 426115 - talents.demonbolt_spell = conditional_spell_lookup( talents.demoniac->ok(), 264178 ); + talents.demonbolt_spell = conditional_spell_lookup( talents.demoniac.ok(), 264178 ); talents.demonic_core_spell = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 267102 ); talents.demonic_core_buff = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 264173 ); - talents.implosion = find_talent_spell( talent_tree::SPECIALIZATION, "Implosion" ); // Should be ID 196277 - talents.implosion_aoe = conditional_spell_lookup( talents.implosion->ok(), 196278 ); - talents.call_dreadstalkers = find_talent_spell( talent_tree::SPECIALIZATION, "Call Dreadstalkers" ); // Should be ID 104316 talents.call_dreadstalkers_2 = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 193332 ); // Duration data - talents.imp_gang_boss = find_talent_spell( talent_tree::SPECIALIZATION, "Imp Gang Boss" ); // Should be ID 387445 - talents.imp_gang_boss_buff = conditional_spell_lookup( talents.imp_gang_boss->ok(), 387458 ); + talents.dominant_hand = find_talent_spell( talent_tree::SPECIALIZATION, "Dominant Hand" ); // Should be ID 1276433 - talents.spiteful_reconstitution = find_talent_spell( talent_tree::SPECIALIZATION, "Spiteful Reconstitution" ); // Should be ID 428394 + talents.fel_intellect = find_talent_spell( talent_tree::SPECIALIZATION, "Fel Intellect" ); // Should be ID 1250372 - talents.dreadlash = find_talent_spell( talent_tree::SPECIALIZATION, "Dreadlash" ); // Should be ID 264078 + talents.practiced_rituals = find_talent_spell( talent_tree::SPECIALIZATION, "Practiced Rituals" ); // Should be ID 1250375 - talents.carnivorous_stalkers = find_talent_spell( talent_tree::SPECIALIZATION, "Carnivorous Stalkers" ); // Should be ID 386194 - - talents.inner_demons = find_talent_spell( talent_tree::SPECIALIZATION, "Inner Demons" ); // Should be ID 267216 + talents.dreadlash = find_talent_spell( talent_tree::SPECIALIZATION, "Dreadlash" ); // Should be ID 264078 - talents.soul_strike = find_talent_spell( talent_tree::SPECIALIZATION, "Soul Strike" ); // Should be ID 428344 - talents.soul_strike_pet = conditional_spell_lookup( talents.soul_strike->ok(), 264057 ); - talents.soul_strike_dmg = conditional_spell_lookup( talents.soul_strike->ok(), 267964 ); + talents.imperator = find_talent_spell( talent_tree::SPECIALIZATION, "Imp-erator" ); // Should be ID 416230 - talents.bilescourge_bombers = find_talent_spell( talent_tree::SPECIALIZATION, "Bilescourge Bombers" ); // Should be ID 267211 - talents.bilescourge_bombers_aoe = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 267213 ); + talents.implosion = find_talent_spell( talent_tree::SPECIALIZATION, "Implosion" ); // Should be ID 196277 + talents.implosion_aoe = conditional_spell_lookup( talents.implosion.ok(), 196278 ); - talents.demonic_strength = find_talent_spell( talent_tree::SPECIALIZATION, "Demonic Strength" ); // Should be ID 267171 + talents.power_siphon = find_talent_spell( talent_tree::SPECIALIZATION, "Power Siphon" ); // Should be ID 264130 + talents.power_siphon_buff = conditional_spell_lookup( talents.power_siphon.ok(), 334581 ); - talents.sacrificed_souls = find_talent_spell( talent_tree::SPECIALIZATION, "Sacrificed Souls" ); // Should be ID 267214 + talents.summon_felguard = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Felguard" ); // Should be ID 30146 - talents.rune_of_shadows = find_talent_spell( talent_tree::SPECIALIZATION, "Rune of Shadows" ); // Should be ID 453744 + talents.infernal_rapidity = find_talent_spell( talent_tree::SPECIALIZATION, "Infernal Rapidity" ); // Should be ID 1263941; - talents.imperator = find_talent_spell( talent_tree::SPECIALIZATION, "Imp-erator" ); // Should be ID 416230 + talents.rune_of_shadows = find_talent_spell( talent_tree::SPECIALIZATION, "Rune of Shadows" ); // Should be ID 453744; - talents.fel_invocation = find_talent_spell( talent_tree::SPECIALIZATION, "Fel Invocation" ); // Should be ID 428351 + talents.carnivorous_stalkers = find_talent_spell( talent_tree::SPECIALIZATION, "Carnivorous Stalkers" ); // Should be ID 386194; - talents.annihilan_training = find_talent_spell( talent_tree::SPECIALIZATION, "Annihilan Training" ); // Should be ID 386174 - talents.annihilan_training_buff = conditional_spell_lookup( talents.annihilan_training->ok(), 386176 ); + talents.fel_armaments = find_talent_spell( talent_tree::SPECIALIZATION, "Fel Armaments" ); // Should be ID 1263935 - talents.shadow_invocation = find_talent_spell( talent_tree::SPECIALIZATION, "Shadow Invocation" ); // Should be ID 422054 + talents.imp_gang_boss = find_talent_spell( talent_tree::SPECIALIZATION, "Imp Gang Boss" ); // Should be ID 1250768 + talents.imp_gang_boss_buff = conditional_spell_lookup( talents.imp_gang_boss.ok(), 1250772 ); - talents.wicked_maw = find_talent_spell( talent_tree::SPECIALIZATION, "Wicked Maw" ); // Should be ID 267170 - talents.wicked_maw_debuff = conditional_spell_lookup( warlock_base.demonology_warlock->ok(), 270569 ); + talents.demonic_brutality = find_talent_spell( talent_tree::SPECIALIZATION, "Demonic Brutality" ); // Should be ID 453908 - talents.power_siphon = find_talent_spell( talent_tree::SPECIALIZATION, "Power Siphon" ); // Should be ID 264130 - talents.power_siphon_buff = conditional_spell_lookup( talents.power_siphon->ok(), 334581 ); + talents.inner_demons = find_talent_spell( talent_tree::SPECIALIZATION, "Inner Demons" ); // Should be ID 267216 talents.summon_demonic_tyrant = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Demonic Tyrant" ); // Should be ID 265187 - talents.demonic_power_buff = conditional_spell_lookup( talents.summon_demonic_tyrant->ok(), 265273 ); + talents.demonic_power_buff = conditional_spell_lookup( talents.summon_demonic_tyrant.ok(), 1276788 ); - talents.grimoire_felguard = find_talent_spell( talent_tree::SPECIALIZATION, "Grimoire: Felguard" ); // Should be ID 111898 - talents.grimoire_of_service = conditional_spell_lookup( talents.grimoire_felguard->ok(), 216187 ); + talents.blighted_maw = find_talent_spell( talent_tree::SPECIALIZATION, "Blighted Maw" ); // Should be ID 1276956 + talents.blighted_maw_dmg = conditional_spell_lookup( talents.blighted_maw.ok(), 1276960 ); - talents.the_expendables = find_talent_spell( talent_tree::SPECIALIZATION, "The Expendables" ); // Should be ID 387600 - talents.the_expendables_buff = conditional_spell_lookup( talents.the_expendables->ok(), 387601 ); + talents.improved_demonic_tactics = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Demonic Tactics" ); // Should be ID 453800 - talents.blood_invocation = find_talent_spell( talent_tree::SPECIALIZATION, "Blood Invocation" ); // Should be ID 455576 + talents.empowered_felstorm = find_talent_spell( talent_tree::SPECIALIZATION, "Empowered Felstorm" ); // Should be ID 1279575 - talents.umbral_blaze = find_talent_spell( talent_tree::SPECIALIZATION, "Umbral Blaze" ); // Should be ID 405798 - talents.umbral_blaze_dot = conditional_spell_lookup( talents.umbral_blaze->ok(), 405802 ); + talents.spiteful_reconstitution = find_talent_spell( talent_tree::SPECIALIZATION, "Spiteful Reconstitution" ); // Should be ID 428394 - talents.demonic_calling = find_talent_spell( talent_tree::SPECIALIZATION, "Demonic Calling" ); // Should be ID 205145 - talents.demonic_calling_buff = conditional_spell_lookup( talents.demonic_calling->ok(), 205146 ); + talents.tyrants_oblation = find_talent_spell( talent_tree::SPECIALIZATION, "Tyrant's Oblation" ); // Should be ID 1276746 + talents.tyrants_oblation_buff = conditional_spell_lookup( talents.tyrants_oblation.ok(), 1276767 ); - talents.fiendish_oblation = find_talent_spell( talent_tree::SPECIALIZATION, "Fiendish Oblation" ); // Should be ID 455569 + talents.antoran_armaments = find_talent_spell( talent_tree::SPECIALIZATION, "Antoran Armaments" ); // Should be ID 1250921 - talents.fel_sunder = find_talent_spell( talent_tree::SPECIALIZATION, "Fel Sunder" ); // Should be ID 387399 - talents.fel_sunder_debuff = conditional_spell_lookup( talents.fel_sunder->ok(), 387402 ); + talents.flametouched = find_talent_spell( talent_tree::SPECIALIZATION, "Flametouched" ); // Should be ID 453699 + talents.ferocity_of_fharg_buff = conditional_spell_lookup( talents.flametouched.ok(), 453704 ); - talents.doom = find_talent_spell( talent_tree::SPECIALIZATION, "Doom" ); // Should be ID 460551 - talents.doom_debuff = conditional_spell_lookup( talents.doom->ok(), 460553 ); - talents.doom_dmg = conditional_spell_lookup( talents.doom->ok(), 460555 ); + talents.demonic_knowledge = find_talent_spell( talent_tree::SPECIALIZATION, "Demonic Knowledge" ); // Should be ID 386185 - talents.pact_of_the_imp_mother = find_talent_spell( talent_tree::SPECIALIZATION, "Pact of the Imp Mother" ); // Should be ID 387541 + talents.sacrificed_souls = find_talent_spell( talent_tree::SPECIALIZATION, "Sacrificed Souls" ); // Should be ID 267214 - talents.summon_vilefiend = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Vilefiend" ); // Should be ID 264119 - talents.bile_spit = conditional_spell_lookup( talents.summon_vilefiend->ok(), 267997 ); - talents.headbutt = conditional_spell_lookup( talents.summon_vilefiend->ok(), 267999 ); + talents.reign_of_tyranny = find_talent_spell( talent_tree::SPECIALIZATION, "Reign of Tyranny" ); // Should be ID 1276748 - talents.dread_calling = find_talent_spell( talent_tree::SPECIALIZATION, "Dread Calling" ); // Should be ID 387391 - talents.dread_calling_buff = conditional_spell_lookup( talents.dread_calling->ok(), 387393 ); - talents.dread_calling_pet = conditional_spell_lookup( talents.dread_calling->ok(), 387392 ); + talents.master_summoner = find_talent_spell( talent_tree::SPECIALIZATION, "Master Summoner" ); // Should be ID 1240189 - talents.antoran_armaments = find_talent_spell( talent_tree::SPECIALIZATION, "Antoran Armaments" ); // Should be ID 387494 - talents.antoran_armaments_buff = conditional_spell_lookup( talents.antoran_armaments->ok(), 387496 ); - talents.soul_cleave = conditional_spell_lookup( talents.antoran_armaments->ok() && talents.soul_strike->ok(), 387502 ); + talents.demonic_calling = find_talent_spell( talent_tree::SPECIALIZATION, "Demonic Calling" ); // Should be ID 1276947 - talents.doom_eternal = find_talent_spell( talent_tree::SPECIALIZATION, "Doom Eternal" ); // Should be ID 455585 + talents.doom = find_talent_spell( talent_tree::SPECIALIZATION, "Doom" ); // Should be ID 460551 + talents.doom_debuff = conditional_spell_lookup( talents.doom.ok(), 460553 ); + talents.doom_dmg = conditional_spell_lookup( talents.doom.ok(), 460555 ); - talents.impending_doom = find_talent_spell( talent_tree::SPECIALIZATION, "Impending Doom" ); // Should be ID 455587 + talents.hellbent_commander = find_talent_spell( talent_tree::SPECIALIZATION, "Hellbent Commander" ); // Should be ID 460551 + talents.hellbent_commander_buff = conditional_spell_lookup( talents.hellbent_commander.ok(), 1281559 ); - talents.foul_mouth = find_talent_spell( talent_tree::SPECIALIZATION, "Foul Mouth" ); // Should be ID 455502 + talents.grimoire_imp_lord = find_talent_spell( talent_tree::SPECIALIZATION, "Grimoire: Imp Lord" ); // Should be ID 1276452 - talents.the_houndmasters_gambit = find_talent_spell( talent_tree::SPECIALIZATION, "The Houndmaster's Gambit" ); // Should be ID 455572 - talents.houndmasters_aura = conditional_spell_lookup( talents.the_houndmasters_gambit->ok(), 455611 ); + talents.grimoire_fel_ravager = find_talent_spell( talent_tree::SPECIALIZATION, "Grimoire: Fel Ravager" ); // Should be ID 1276467 - talents.improved_demonic_tactics = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Demonic Tactics" ); // Should be ID 453800 + talents.summon_vilefiend = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Vilefiend" ); // Should be ID 1251778 + talents.vilefiend = conditional_spell_lookup( talents.summon_vilefiend.ok(), 1251781 ); + talents.bile_spit = conditional_spell_lookup( talents.summon_vilefiend.ok(), 267997 ); + talents.headbutt = conditional_spell_lookup( talents.summon_vilefiend.ok(), 267999 ); - talents.demonic_brutality = find_talent_spell( talent_tree::SPECIALIZATION, "Demonic Brutality" ); // Should be ID 453908 + talents.summon_doomguard = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Doomguard" ); // Should be ID 1276672 + talents.doom_bolt_volley = conditional_spell_lookup( talents.summon_doomguard.ok(), 1251989 ); - talents.pact_of_the_eredruin = find_talent_spell( talent_tree::SPECIALIZATION, "Pact of the Ered'ruin" ); // Should be ID 453568 - talents.doomguard = conditional_spell_lookup( talents.pact_of_the_eredruin->ok(), 453590 ); - talents.doom_bolt = conditional_spell_lookup( talents.pact_of_the_eredruin->ok(), 453616 ); + talents.to_hell_and_back = find_talent_spell( talent_tree::SPECIALIZATION, "To Hell and Back" ); // Should be ID 1281511 + talents.unstable_soul_buff = conditional_spell_lookup( talents.to_hell_and_back.ok(), 1281512 ); - talents.shadowtouched = find_talent_spell( talent_tree::SPECIALIZATION, "Shadowtouched" ); // Should be ID 453619 + talents.stabilized_portals = find_talent_spell( talent_tree::SPECIALIZATION, "Stabilized Portals" ); // Should be ID 1276661 talents.mark_of_shatug = find_talent_spell( talent_tree::SPECIALIZATION, "Mark of Shatug" ); // Should be ID 455449 - talents.gloom_slash = conditional_spell_lookup( talents.mark_of_shatug->ok(), 455491 ); + talents.gloom_slash = conditional_spell_lookup( talents.mark_of_shatug.ok(), 455491 ); talents.mark_of_fharg = find_talent_spell( talent_tree::SPECIALIZATION, "Mark of F'harg" ); // Should be ID 455450 - talents.infernal_presence = conditional_spell_lookup( talents.mark_of_fharg->ok(), 428453 ); - talents.infernal_presence_dmg = conditional_spell_lookup( talents.mark_of_fharg->ok(), 428455 ); - - talents.flametouched = find_talent_spell( talent_tree::SPECIALIZATION, "Flametouched" ); // Should be ID 453699 - talents.ferocity_of_fharg_buff = conditional_spell_lookup( talents.flametouched->ok(), 453704 ); - - talents.immutable_hatred = find_talent_spell( talent_tree::SPECIALIZATION, "Immutable Hatred" ); // Should be ID 405670 - talents.immutable_hatred_proc = conditional_spell_lookup( talents.immutable_hatred->ok(), 405681 ); + talents.infernal_presence = conditional_spell_lookup( talents.mark_of_fharg.ok(), 428453 ); + talents.infernal_presence_dmg = conditional_spell_lookup( talents.mark_of_fharg.ok(), 428455 ); - talents.master_summoner = find_talent_spell( talent_tree::SPECIALIZATION, "Master Summoner" ); // Should be ID 1240189 - - // Additional Tier Set spell data - - // Nerub-ar Palace - tier.hexflame_demo_2pc = sets->set( WARLOCK_DEMONOLOGY, TWW1, B2 ); // Should be ID 453644 - tier.hexflame_demo_4pc = sets->set( WARLOCK_DEMONOLOGY, TWW1, B4 ); // Should be ID 453645 - tier.empowered_legion_strike = conditional_spell_lookup( tier.hexflame_demo_4pc, 455647 ); // TWW1_B4 - - // Liberation of Undermine - tier.spliced_demo_2pc = sets->set( WARLOCK_DEMONOLOGY, TWW2, B2 ); // Should be ID 1215679 - tier.spliced_demo_4pc = sets->set( WARLOCK_DEMONOLOGY, TWW2, B4 ); // Should be ID 1215682 - tier.greater_dreadstalker = conditional_spell_lookup( tier.spliced_demo_2pc, 1217615 ); // TWW2_B2 - tier.demonic_hunger = conditional_spell_lookup( tier.spliced_demo_2pc, 1217617 ); // TWW2_B2 + // TODO: Not Yet Implemented + talents.dominion_of_argus_1 = find_talent_spell( talent_tree::SPECIALIZATION, "Dominion of Argus", 1 ); // Should be ID 1276163 (I) + talents.dominion_of_argus_2 = find_talent_spell( talent_tree::SPECIALIZATION, "Dominion of Argus", 2 ); // Should be ID 1276190 (II) + talents.dominion_of_argus_3 = find_talent_spell( talent_tree::SPECIALIZATION, "Dominion of Argus", 3 ); // Should be ID 1276222 (III) // Initialize some default values for pet spawners warlock_pet_list.wild_imps.set_default_duration( warlock_base.wild_imp->duration() ); - warlock_pet_list.dreadstalkers.set_default_duration( talents.call_dreadstalkers_2->duration() ); - - warlock_pet_list.doomguards.set_default_duration( talents.doomguard->duration() ); - - warlock_pet_list.greater_dreadstalkers.set_default_duration( tier.greater_dreadstalker->duration() ); + warlock_pet_list.doomguards.set_default_duration( talents.summon_doomguard->duration() ); } void warlock_t::init_spells_destruction() { // Talents - talents.conflagrate = find_talent_spell( talent_tree::SPECIALIZATION, "Conflagrate" ); // Should be ID 17962 - talents.conflagrate_2 = conditional_spell_lookup( talents.conflagrate->ok(), 245330 ); + talents.chaos_bolt = find_talent_spell( talent_tree::SPECIALIZATION, "Chaos Bolt" ); // Should be ID 116858 - talents.backdraft = find_talent_spell( talent_tree::SPECIALIZATION, "Backdraft" ); // Should be ID 196406 - talents.backdraft_buff = conditional_spell_lookup( talents.backdraft->ok(), 117828 ); + talents.conflagrate = find_talent_spell( talent_tree::SPECIALIZATION, "Conflagrate" ); // Should be ID 17962 + talents.conflagrate_2 = conditional_spell_lookup( talents.conflagrate.ok(), 245330 ); talents.rain_of_fire = find_talent_spell( talent_tree::SPECIALIZATION, 5740 ); // Targeting reticle version if ( talents.rain_of_fire == spell_data_t::not_found() ) talents.rain_of_fire = find_talent_spell( talent_tree::SPECIALIZATION, 1214467 ); // If targeting version not found, fall back to checking for on-target version - talents.rain_of_fire_tick = conditional_spell_lookup( talents.rain_of_fire->ok(), 42223 ); - - talents.roaring_blaze = find_talent_spell( talent_tree::SPECIALIZATION, "Roaring Blaze" ); // Should be ID 205184 - talents.conflagrate_debuff = conditional_spell_lookup( talents.roaring_blaze->ok(), 265931 ); + talents.rain_of_fire_tick = conditional_spell_lookup( talents.rain_of_fire.ok(), 42223 ); talents.improved_conflagrate = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Conflagrate" ); // Should be ID 231793 - talents.backlash = find_talent_spell( talent_tree::SPECIALIZATION, "Backlash" ); // Should be ID 387384 - - talents.mayhem = find_talent_spell( talent_tree::SPECIALIZATION, "Mayhem" ); // Should be ID 387506 + talents.backdraft = find_talent_spell( talent_tree::SPECIALIZATION, "Backdraft" ); // Should be ID 196406 + talents.backdraft_buff = conditional_spell_lookup( talents.backdraft.ok(), 117828 ); - talents.havoc = find_talent_spell( talent_tree::SPECIALIZATION, "Havoc" ); // Should be spell 80240 - talents.havoc_debuff = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 80240 ); + talents.practiced_chaos = find_talent_spell( talent_tree::SPECIALIZATION, "Practiced Chaos" ); // Should be ID 1244284 - talents.pyrogenics = find_talent_spell( talent_tree::SPECIALIZATION, "Pyrogenics" ); // Should be ID 387095 - talents.pyrogenics_debuff = conditional_spell_lookup( talents.pyrogenics->ok(), 387096 ); + talents.roaring_blaze = find_talent_spell( talent_tree::SPECIALIZATION, "Roaring Blaze" ); // Should be ID 1244310 - talents.cataclysm = find_talent_spell( talent_tree::SPECIALIZATION, "Cataclysm" ); // Should be ID 152108 + talents.explosive_potential = find_talent_spell( talent_tree::SPECIALIZATION, "Explosive Potential" ); // Should be ID 388827 - talents.indiscriminate_flames = find_talent_spell( talent_tree::SPECIALIZATION, "Indiscriminate Flames" ); // Should be ID 457114 + talents.mayhem = find_talent_spell( talent_tree::SPECIALIZATION, "Mayhem" ); // Should be ID 387506 - talents.rolling_havoc = find_talent_spell( talent_tree::SPECIALIZATION, "Rolling Havoc" ); // Should be ID 387569 - talents.rolling_havoc_buff = conditional_spell_lookup( talents.rolling_havoc->ok(), 387570 ); + talents.havoc = find_talent_spell( talent_tree::SPECIALIZATION, "Havoc" ); // Should be spell 80240 + talents.havoc_debuff = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 80240 ); talents.scalding_flames = find_talent_spell( talent_tree::SPECIALIZATION, "Scalding Flames" ); // Should be ID 388832 talents.shadowburn = find_talent_spell( talent_tree::SPECIALIZATION, "Shadowburn" ); // Should be ID 17877 - talents.shadowburn_2 = conditional_spell_lookup( talents.shadowburn->ok(), 245731 ); + talents.shadowburn_2 = conditional_spell_lookup( talents.shadowburn.ok(), 245731 ); - talents.explosive_potential = find_talent_spell( talent_tree::SPECIALIZATION, "Explosive Potential" ); // Should be ID 388827 + talents.backlash = find_talent_spell( talent_tree::SPECIALIZATION, "Backlash" ); // Should be ID 387384 - talents.ashen_remains = find_talent_spell( talent_tree::SPECIALIZATION, "Ashen Remains" ); // Should be ID 387252 + talents.improved_havoc = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Havoc" ); // Should be ID 1244460 - talents.channel_demonfire = find_talent_spell( talent_tree::SPECIALIZATION, "Channel Demonfire" ); // Should be ID 196447 - talents.channel_demonfire_tick = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 196448 ); // Includes both direct and splash damage values - talents.channel_demonfire_travel = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 196449 ); + talents.ashen_remains = find_talent_spell( talent_tree::SPECIALIZATION, "Ashen Remains" ); // Should be ID 387252 - talents.demonfire_infusion = find_talent_spell( talent_tree::SPECIALIZATION, "Demonfire Infusion" ); // Should be ID 1214442 + talents.cataclysm = find_talent_spell( talent_tree::SPECIALIZATION, "Cataclysm" ); // Should be ID 152108 - talents.blistering_atrophy = find_talent_spell( talent_tree::SPECIALIZATION, "Blistering Atrophy" ); // Should be ID 456939 + talents.fiendish_cruelty = find_talent_spell( talent_tree::SPECIALIZATION, "Fiendish Cruelty" ); // Should be ID 1245633 + talents.fiendish_cruelty_buff = conditional_spell_lookup( talents.fiendish_cruelty.ok(), 1245664 ); - talents.conflagration_of_chaos = find_talent_spell( talent_tree::SPECIALIZATION, "Conflagration of Chaos" ); // Should be ID 387108 - talents.conflagration_of_chaos_cf = conditional_spell_lookup( talents.conflagration_of_chaos->ok(), 387109 ); - talents.conflagration_of_chaos_sb = conditional_spell_lookup( talents.conflagration_of_chaos->ok() && talents.shadowburn->ok(), 387110 ); + talents.chaotic_inferno = find_talent_spell( talent_tree::SPECIALIZATION, "Chaotic Inferno" ); // Should be ID 1244788 + talents.chaotic_inferno_buff = conditional_spell_lookup( talents.chaotic_inferno.ok(), 1244860 ); - talents.emberstorm = find_talent_spell( talent_tree::SPECIALIZATION, "Emberstorm" ); // Should be ID 454744 + talents.flashpoint = find_talent_spell( talent_tree::SPECIALIZATION, "Flashpoint" ); // Should be 387259 + talents.flashpoint_buff = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 387263 ); talents.summon_infernal = find_talent_spell( talent_tree::SPECIALIZATION, "Summon Infernal" ); // Should be ID 1122 talents.summon_infernal_main = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 111685 ); @@ -445,92 +367,80 @@ namespace warlock talents.embers = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 264364 ); talents.burning_ember = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 264365 ); - talents.fire_and_brimstone = find_talent_spell( talent_tree::SPECIALIZATION, "Fire and Brimstone" ); // Should be ID 196408 + talents.emberstorm = find_talent_spell( talent_tree::SPECIALIZATION, "Emberstorm" ); // Should be ID 454744 - talents.flashpoint = find_talent_spell( talent_tree::SPECIALIZATION, "Flashpoint" ); // Should be 387259 - talents.flashpoint_buff = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 387263 ); + talents.fire_and_brimstone = find_talent_spell( talent_tree::SPECIALIZATION, "Fire and Brimstone" ); // Should be ID 196408 - talents.raging_demonfire = find_talent_spell( talent_tree::SPECIALIZATION, "Raging Demonfire" ); // Should be ID 387166 + talents.lake_of_fire = find_talent_spell( talent_tree::SPECIALIZATION, "Lake of Fire" ); // Should be ID 1244877 + talents.lake_of_fire_aoe = conditional_spell_lookup( talents.lake_of_fire.ok(), 1244885 ); + talents.lake_of_fire_tick = conditional_spell_lookup( talents.lake_of_fire.ok(), 1244890 ); + talents.lake_of_fire_debuff = conditional_spell_lookup( talents.lake_of_fire.ok(), 1244918 ); - talents.fiendish_cruelty = find_talent_spell( talent_tree::SPECIALIZATION, "Fiendish Cruelty" ); // Should be ID 456943 + talents.reverse_entropy = find_talent_spell( talent_tree::SPECIALIZATION, "Reverse Entropy" ); // Should be ID 205148 + talents.reverse_entropy_buff = conditional_spell_lookup( talents.reverse_entropy.ok(), 266030 ); - talents.eradication = find_talent_spell( talent_tree::SPECIALIZATION, "Eradication" ); // Should be ID 196412 - talents.eradication_debuff = conditional_spell_lookup( talents.eradication->ok(), 196414 ); + talents.internal_combustion = find_talent_spell( talent_tree::SPECIALIZATION, "Internal Combustion" ); // Should be ID 266134 + talents.internal_combustion_dmg = conditional_spell_lookup( talents.internal_combustion.ok(), 266136 ); talents.crashing_chaos = find_talent_spell( talent_tree::SPECIALIZATION, "Crashing Chaos" ); // Should be ID 417234 - talents.crashing_chaos_buff = conditional_spell_lookup( talents.crashing_chaos->ok(), 417282 ); + talents.crashing_chaos_buff = conditional_spell_lookup( talents.crashing_chaos.ok(), 417282 ); talents.rain_of_chaos = find_talent_spell( talent_tree::SPECIALIZATION, "Rain of Chaos" ); // Should be ID 266086 - talents.rain_of_chaos_buff = conditional_spell_lookup( talents.rain_of_chaos->ok(), 266087 ); - talents.summon_infernal_roc = conditional_spell_lookup( talents.rain_of_chaos->ok(), 335236 ); + talents.rain_of_chaos_buff = conditional_spell_lookup( talents.rain_of_chaos.ok(), 266087 ); + talents.summon_infernal_roc = conditional_spell_lookup( talents.rain_of_chaos.ok(), 335236 ); - talents.reverse_entropy = find_talent_spell( talent_tree::SPECIALIZATION, "Reverse Entropy" ); // Should be ID 205148 - talents.reverse_entropy_buff = conditional_spell_lookup( talents.reverse_entropy->ok(), 266030 ); + talents.ruin = find_talent_spell( talent_tree::SPECIALIZATION, "Ruin" ); // Should be ID 387103 - talents.internal_combustion = find_talent_spell( talent_tree::SPECIALIZATION, "Internal Combustion" ); // Should be ID 266134 - talents.internal_combustion_dmg = conditional_spell_lookup( talents.internal_combustion->ok(), 266136 ); + talents.improved_chaos_bolt = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Chaos Bolt" ); // Should be ID 456951 - talents.demonfire_mastery = find_talent_spell( talent_tree::SPECIALIZATION, "Demonfire Mastery" ); // Should be ID 456946 + // TODO: Does the reduction in time between ticks affect all types of infernals? + talents.destructive_rapidity = find_talent_spell( talent_tree::SPECIALIZATION, "Destructive Rapidity" ); // Should be ID 1244928 talents.devastation = find_talent_spell( talent_tree::SPECIALIZATION, "Devastation" ); // Should be ID 454735 - talents.ritual_of_ruin = find_talent_spell( talent_tree::SPECIALIZATION, "Ritual of Ruin" ); // Should be ID 387156 - talents.impending_ruin_buff = conditional_spell_lookup( talents.ritual_of_ruin->ok(), 387158 ); - talents.ritual_of_ruin_buff = conditional_spell_lookup( talents.ritual_of_ruin->ok(), 387157 ); - - talents.ruin = find_talent_spell( talent_tree::SPECIALIZATION, "Ruin" ); // Should be ID 387103 + talents.dimensional_rift = find_talent_spell( talent_tree::SPECIALIZATION, "Dimensional Rift" ); // Should be ID 1280868 + talents.shadowy_tear_summon = conditional_spell_lookup( talents.dimensional_rift.ok(), 394235 ); + talents.shadow_barrage = conditional_spell_lookup( talents.dimensional_rift.ok(), 394237 ); + talents.rift_shadow_bolt = conditional_spell_lookup( talents.dimensional_rift.ok(), 394238 ); + talents.unstable_tear_summon = conditional_spell_lookup( talents.dimensional_rift.ok(), 387979 ); + talents.chaos_barrage = conditional_spell_lookup( talents.dimensional_rift.ok(), 387984 ); + talents.chaos_barrage_tick = conditional_spell_lookup( talents.dimensional_rift.ok(), 387985 ); + talents.chaos_tear_summon = conditional_spell_lookup( talents.dimensional_rift.ok(), 394243 ); + talents.rift_chaos_bolt = conditional_spell_lookup( talents.dimensional_rift.ok(), 394246 ); talents.soul_fire = find_talent_spell( talent_tree::SPECIALIZATION, "Soul Fire" ); // Should be ID 6353 - talents.soul_fire_2 = conditional_spell_lookup( talents.soul_fire->ok(), 281490 ); + talents.soul_fire_2 = conditional_spell_lookup( talents.soul_fire.ok(), 281490 ); - talents.improved_chaos_bolt = find_talent_spell( talent_tree::SPECIALIZATION, "Improved Chaos Bolt" ); // Should be ID 456951 + talents.inferno = find_talent_spell( talent_tree::SPECIALIZATION, "Inferno" ); // Should be ID 1280483 - talents.burn_to_ashes = find_talent_spell( talent_tree::SPECIALIZATION, "Burn to Ashes" ); // Should be ID 387153 - talents.burn_to_ashes_buff = conditional_spell_lookup( talents.burn_to_ashes->ok(), 387154 ); + talents.conflagration_of_chaos = find_talent_spell( talent_tree::SPECIALIZATION, "Conflagration of Chaos" ); // Should be ID 387108 + talents.conflagration_of_chaos_cf = conditional_spell_lookup( talents.conflagration_of_chaos.ok(), 387109 ); + talents.conflagration_of_chaos_sb = conditional_spell_lookup( talents.conflagration_of_chaos.ok() && talents.shadowburn.ok(), 387110 ); - talents.master_ritualist = find_talent_spell( talent_tree::SPECIALIZATION, "Master Ritualist" ); // Should be ID 387165 + talents.diabolic_embers = find_talent_spell( talent_tree::SPECIALIZATION, "Diabolic Embers" ); // Should be ID 387173 - talents.power_overwhelming = find_talent_spell( talent_tree::SPECIALIZATION, "Power Overwhelming" ); // Should be ID 387279 - talents.power_overwhelming_buff = conditional_spell_lookup( talents.power_overwhelming->ok(), 387283 ); + talents.demonfire_infusion = find_talent_spell( talent_tree::SPECIALIZATION, "Demonfire Infusion" ); // Should be ID 1214442 - talents.dimensional_rift = find_talent_spell( talent_tree::SPECIALIZATION, "Dimensional Rift" ); // Should be ID 387976 - talents.dimension_ripper = find_talent_spell( talent_tree::SPECIALIZATION, "Dimension Ripper" ); // Should be ID 457025 - talents.shadowy_tear_summon = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 394235 ); - talents.shadow_barrage = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 394237 ); - talents.rift_shadow_bolt = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 394238 ); - talents.unstable_tear_summon = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 387979 ); - talents.chaos_barrage = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 387984 ); - talents.chaos_barrage_tick = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 387985 ); - talents.chaos_tear_summon = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 394243 ); - talents.rift_chaos_bolt = conditional_spell_lookup( talents.dimensional_rift->ok() || talents.dimension_ripper->ok(), 394246 ); + talents.channel_demonfire = find_talent_spell( talent_tree::SPECIALIZATION, "Channel Demonfire" ); // Should be ID 196447 + talents.channel_demonfire_tick = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 196448 ); // Includes both direct and splash damage values + talents.channel_demonfire_travel = conditional_spell_lookup( warlock_base.destruction_warlock->ok(), 196449 ); - talents.decimation = find_talent_spell( talent_tree::SPECIALIZATION, "Decimation" ); // Should be ID 456985 - talents.decimation_buff = conditional_spell_lookup( talents.decimation->ok(), 457555 ); + talents.avatar_of_destruction = find_talent_spell( talent_tree::SPECIALIZATION, "Avatar of Destruction" ); // Should be ID 1245089 + talents.summon_overfiend = conditional_spell_lookup( talents.avatar_of_destruction.ok(), 434587 ); + talents.overfiend_buff = conditional_spell_lookup( talents.avatar_of_destruction.ok(), 457578 ); + talents.overfiend_cb = conditional_spell_lookup( talents.avatar_of_destruction.ok(), 434589 ); talents.chaos_incarnate = find_talent_spell( talent_tree::SPECIALIZATION, "Chaos Incarnate" ); // Should be ID 387275 - talents.avatar_of_destruction = find_talent_spell( talent_tree::SPECIALIZATION, "Avatar of Destruction" ); // Should be ID 456975 - talents.summon_overfiend = conditional_spell_lookup( talents.avatar_of_destruction->ok(), 434587 ); - talents.overfiend_buff = conditional_spell_lookup( talents.avatar_of_destruction->ok(), 457578 ); - talents.overfiend_cb = conditional_spell_lookup( talents.avatar_of_destruction->ok(), 434589 ); - - talents.diabolic_embers = find_talent_spell( talent_tree::SPECIALIZATION, "Diabolic Embers" ); // Should be ID 387173 - - talents.unstable_rifts = find_talent_spell( talent_tree::SPECIALIZATION, "Unstable Rifts" ); // Should be ID 457064 - talents.dimensional_cinder = conditional_spell_lookup( talents.unstable_rifts->ok(), 460805 ); - - // Additional Tier Set spell data + talents.alythesss_ire = find_talent_spell( talent_tree::SPECIALIZATION, "Alythess's Ire" ); // Should be ID 1244941 + talents.alythesss_ire_buff = conditional_spell_lookup( talents.alythesss_ire.ok(), 1244947 ); - // Nerub-ar Palace - tier.hexflame_destro_2pc = sets->set( WARLOCK_DESTRUCTION, TWW1, B2 ); // Should be ID 453647 - tier.hexflame_destro_4pc = sets->set( WARLOCK_DESTRUCTION, TWW1, B4 ); // Should be ID 453646 - tier.echo_of_the_azjaqir = conditional_spell_lookup( tier.hexflame_destro_4pc, 455674 ); // TWW1_B4 + talents.raging_demonfire = find_talent_spell( talent_tree::SPECIALIZATION, "Raging Demonfire" ); // Should be ID 387166 - // Liberation of Undermine - tier.spliced_destro_2pc = sets->set( WARLOCK_DESTRUCTION, TWW2, B2 ); // Should be ID 1215680 - tier.spliced_destro_4pc = sets->set( WARLOCK_DESTRUCTION, TWW2, B4 ); // Should be ID 1215681 - tier.spliced_destro_jackpot = conditional_spell_lookup( tier.spliced_destro_2pc, 1217798 ); // TWW2_B2 - tier.demonfire_flurry = conditional_spell_lookup( tier.spliced_destro_2pc, 1217731 ); // TWW2_B2 + // TODO: Not Yet Implemented + talents.embers_of_nihilam_1 = find_talent_spell( talent_tree::SPECIALIZATION, "Embers of Nihilam", 1 ); // Should be ID 1265770 (I) + talents.embers_of_nihilam_2 = find_talent_spell( talent_tree::SPECIALIZATION, "Embers of Nihilam", 2 ); // Should be ID 1265772 (II) + talents.embers_of_nihilam_3 = find_talent_spell( talent_tree::SPECIALIZATION, "Embers of Nihilam", 3 ); // Should be ID 1265774 (III) // Initialize some default values for pet spawners warlock_pet_list.infernals.set_default_duration( talents.summon_infernal_main->duration() ); @@ -540,30 +450,30 @@ namespace warlock void warlock_t::init_spells_diabolist() { hero.diabolic_ritual = find_talent_spell( talent_tree::HERO, "Diabolic Ritual" ); // Should be ID 428514 - hero.ritual_overlord = conditional_spell_lookup( hero.diabolic_ritual->ok(), 431944 ); - hero.ritual_mother = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432815 ); - hero.ritual_pit_lord = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432816 ); - hero.art_overlord = conditional_spell_lookup( hero.diabolic_ritual->ok(), 428524 ); - hero.art_mother = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432794 ); - hero.art_pit_lord = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432795 ); - hero.summon_overlord = conditional_spell_lookup( hero.diabolic_ritual->ok(), 428571 ); - hero.summon_mother = conditional_spell_lookup( hero.diabolic_ritual->ok(), 428565 ); - hero.summon_pit_lord = conditional_spell_lookup( hero.diabolic_ritual->ok(), 434400 ); - hero.wicked_cleave = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432120 ); - hero.chaos_salvo = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432569 ); - hero.chaos_salvo_missile = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432592 ); - hero.chaos_salvo_dmg = conditional_spell_lookup( hero.diabolic_ritual->ok(), 432596 ); - hero.felseeker = conditional_spell_lookup( hero.diabolic_ritual->ok(), 438973 ); - hero.felseeker_dmg = conditional_spell_lookup( hero.diabolic_ritual->ok(), 434404 ); + hero.ritual_overlord = conditional_spell_lookup( hero.diabolic_ritual.ok(), 431944 ); + hero.ritual_mother = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432815 ); + hero.ritual_pit_lord = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432816 ); + hero.art_overlord = conditional_spell_lookup( hero.diabolic_ritual.ok(), 428524 ); + hero.art_mother = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432794 ); + hero.art_pit_lord = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432795 ); + hero.summon_overlord = conditional_spell_lookup( hero.diabolic_ritual.ok(), 428571 ); + hero.summon_mother = conditional_spell_lookup( hero.diabolic_ritual.ok(), 428565 ); + hero.summon_pit_lord = conditional_spell_lookup( hero.diabolic_ritual.ok(), 434400 ); + hero.wicked_cleave = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432120 ); + hero.chaos_salvo = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432569 ); + hero.chaos_salvo_missile = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432592 ); + hero.chaos_salvo_dmg = conditional_spell_lookup( hero.diabolic_ritual.ok(), 432596 ); + hero.felseeker = conditional_spell_lookup( hero.diabolic_ritual.ok(), 438973 ); + hero.felseeker_dmg = conditional_spell_lookup( hero.diabolic_ritual.ok(), 434404 ); hero.cloven_souls = find_talent_spell( talent_tree::HERO, "Cloven Souls" ); // Should be ID 428517 - hero.cloven_soul_debuff = conditional_spell_lookup( hero.cloven_souls->ok(), 434424 ); + hero.cloven_soul_debuff = conditional_spell_lookup( hero.cloven_souls.ok(), 434424 ); hero.touch_of_rancora = find_talent_spell( talent_tree::HERO, "Touch of Rancora" ); // Should be ID 429893 hero.secrets_of_the_coven = find_talent_spell( talent_tree::HERO, "Secrets of the Coven" ); // Should be ID 428518 - hero.infernal_bolt = conditional_spell_lookup( hero.secrets_of_the_coven->ok(), 434506 ); - hero.infernal_bolt_buff = conditional_spell_lookup( hero.secrets_of_the_coven->ok(), 433891 ); + hero.infernal_bolt = conditional_spell_lookup( hero.secrets_of_the_coven.ok(), 434506 ); + hero.infernal_bolt_buff = conditional_spell_lookup( hero.secrets_of_the_coven.ok(), 433891 ); hero.cruelty_of_kerxan = find_talent_spell( talent_tree::HERO, "Cruelty of Kerxan" ); // Should be ID 429902 @@ -572,44 +482,48 @@ namespace warlock hero.flames_of_xoroth = find_talent_spell( talent_tree::HERO, "Flames of Xoroth" ); // Should be ID 429657 hero.abyssal_dominion = find_talent_spell( talent_tree::HERO, "Abyssal Dominion" ); // Should be ID 429581 - hero.abyssal_dominion_buff = conditional_spell_lookup( hero.abyssal_dominion->ok(), 456323 ); - hero.infernal_fragmentation = conditional_spell_lookup( hero.abyssal_dominion->ok() && warlock_base.destruction_warlock, 456310 ); + hero.abyssal_dominion_buff = conditional_spell_lookup( hero.abyssal_dominion.ok(), 456323 ); + hero.infernal_fragmentation = conditional_spell_lookup( hero.abyssal_dominion.ok() && warlock_base.destruction_warlock, 456310 ); hero.gloom_of_nathreza = find_talent_spell( talent_tree::HERO, "Gloom of Nathreza" ); // Should be ID 429899 hero.ruination = find_talent_spell( talent_tree::HERO, "Ruination" ); // Should be ID 428522 - hero.ruination_buff = conditional_spell_lookup( hero.ruination->ok(), 433885 ); - hero.ruination_cast = conditional_spell_lookup( hero.ruination->ok(), 434635 ); - hero.ruination_impact = conditional_spell_lookup( hero.ruination->ok(), 434636 ); - hero.diabolic_imp = conditional_spell_lookup( hero.ruination->ok() && warlock_base.destruction_warlock, 438822 ); - hero.diabolic_bolt = conditional_spell_lookup( hero.ruination->ok() && warlock_base.destruction_warlock, 438823 ); + hero.ruination_buff = conditional_spell_lookup( hero.ruination.ok(), 433885 ); + hero.ruination_cast = conditional_spell_lookup( hero.ruination.ok(), 434635 ); + hero.ruination_impact = conditional_spell_lookup( hero.ruination.ok(), 434636 ); + hero.diabolic_imp = conditional_spell_lookup( hero.ruination.ok() && warlock_base.destruction_warlock, 438822 ); + hero.diabolic_bolt = conditional_spell_lookup( hero.ruination.ok() && warlock_base.destruction_warlock, 438823 ); + + hero.diabolic_oculi = find_talent_spell( talent_tree::HERO, "Diabolic Oculi" ); // Should be ID 1268709 + hero.demonic_oculi_buff = conditional_spell_lookup( hero.diabolic_oculi.ok(), 1269643 ); + hero.eye_explosion = conditional_spell_lookup( hero.diabolic_oculi.ok(), 1269800 ); + + hero.looks_that_kill = find_talent_spell( talent_tree::HERO, "Looks That Kill" ); // Should be ID 1268713 + hero.diabolic_gaze_dmg_1 = conditional_spell_lookup( hero.looks_that_kill.ok(), 1269892 ); + hero.diabolic_gaze_dmg_2 = conditional_spell_lookup( hero.looks_that_kill.ok(), 1269886 ); + hero.diabolic_gaze_dmg_3 = conditional_spell_lookup( hero.looks_that_kill.ok(), 1269885 ); + hero.minds_eyes = find_talent_spell( talent_tree::HERO, "Mind's Eyes" ); // Should be ID 1268716 + hero.minds_eyes_buff = conditional_spell_lookup( hero.minds_eyes.ok(), 1269879 ); + + // Initialize some default values for pet spawners warlock_pet_list.overlords.set_default_duration( hero.summon_overlord->duration() ); warlock_pet_list.mothers.set_default_duration( hero.summon_mother->duration() ); warlock_pet_list.pit_lords.set_default_duration( hero.summon_pit_lord->duration() ); warlock_pet_list.fragments.set_default_duration( hero.infernal_fragmentation->duration() ); - - // Additional Tier Set spell data - - // Manaforge Omega - tier.inquisitor_db_2pc = sets->set( HERO_DIABOLIST, TWW3, B2 ); // Should be ID 1236417 - tier.inquisitor_db_4pc = sets->set( HERO_DIABOLIST, TWW3, B4 ); // Should be ID 1236418 - tier.eye_blast = conditional_spell_lookup( tier.inquisitor_db_2pc, 1239510 ); // TWW3_B2 - tier.demonic_oculus = conditional_spell_lookup( tier.inquisitor_db_2pc, 1238810 ); // TWW3_B2 - tier.demonic_intelligence = conditional_spell_lookup( tier.inquisitor_db_4pc, 1239569 ); // TWW3_B2 } void warlock_t::init_spells_hellcaller() { hero.wither = find_talent_spell( talent_tree::HERO, "Wither" ); // Should be ID 445465 - hero.wither_direct = conditional_spell_lookup( hero.wither->ok(), 445468 ); - hero.wither_dot = conditional_spell_lookup( hero.wither->ok(), 445474 ); + hero.wither_direct = conditional_spell_lookup( hero.wither.ok(), 445468 ); + hero.wither_dot = conditional_spell_lookup( hero.wither.ok(), 445474 ); hero.xalans_ferocity = find_talent_spell( talent_tree::HERO, "Xalan's Ferocity" ); // Should be ID 440044 hero.blackened_soul = find_talent_spell( talent_tree::HERO, "Blackened Soul" ); // Should be ID 440043 - hero.blackened_soul_trigger = conditional_spell_lookup( hero.wither->ok(), 445731 ); - hero.blackened_soul_dmg = conditional_spell_lookup( hero.wither->ok(), 445736 ); + hero.blackened_soul_trigger = conditional_spell_lookup( hero.wither.ok(), 445731 ); + hero.blackened_soul_dmg = conditional_spell_lookup( hero.wither.ok(), 445736 ); hero.xalans_cruelty = find_talent_spell( talent_tree::HERO, "Xalan's Cruelty" ); // Should be ID 440040 @@ -625,55 +539,55 @@ namespace warlock hero.mark_of_perotharn = find_talent_spell( talent_tree::HERO, "Mark of Peroth'arn" ); // Should be ID 440045 + hero.through_the_felvine = find_talent_spell( talent_tree::HERO, "Through the Felvine" ); // Should be ID 1266799 + + hero.devil_fruit = find_talent_spell( talent_tree::HERO, "Devil Fruit" ); // Should be ID 1266805 + + hero.alzzins_iniquity = find_talent_spell( talent_tree::HERO, "Alzzin's Iniquity" ); // Should be ID 1266803 + hero.malevolence = find_talent_spell( talent_tree::HERO, "Malevolence" ); // Should be ID 430014 - hero.malevolence_buff = conditional_spell_lookup( hero.malevolence->ok(), 442726 ); - hero.malevolence_dmg = conditional_spell_lookup( hero.malevolence->ok(), 446285 ); + hero.malevolence_buff = conditional_spell_lookup( hero.malevolence.ok(), 442726 ); + hero.malevolence_dmg = conditional_spell_lookup( hero.malevolence.ok(), 446285 ); cooldowns.blackened_soul->duration = 0_ms; // TODO: Set using data once hotfix is in using hero.blackened_soul->internal_cooldown(); cooldowns.seeds_of_their_demise->duration = 15_s; - - // Additional Tier Set spell data - - // Manaforge Omega - tier.inquisitor_hc_2pc = sets->set( HERO_HELLCALLER, TWW3, B2 ); // Should be ID 1236413 - tier.inquisitor_hc_4pc = sets->set( HERO_HELLCALLER, TWW3, B4 ); // Should be ID 1236414 - tier.maintained_withering = conditional_spell_lookup( tier.inquisitor_hc_4pc, 1239577 ); // TWW3_B4 } void warlock_t::init_spells_soul_harvester() { hero.demonic_soul = find_talent_spell( talent_tree::HERO, "Demonic Soul" ); // Should be ID 449614 - hero.succulent_soul = conditional_spell_lookup( hero.demonic_soul->ok(), 449793 ); - hero.demonic_soul_dmg = conditional_spell_lookup( hero.demonic_soul->ok(), 449801 ); + hero.succulent_soul = conditional_spell_lookup( hero.demonic_soul.ok(), 449793 ); + hero.demonic_soul_dmg = conditional_spell_lookup( hero.demonic_soul.ok(), 449801 ); hero.necrolyte_teachings = find_talent_spell( talent_tree::HERO, "Necrolyte Teachings" ); // Should be ID 449620 hero.soul_anathema = find_talent_spell( talent_tree::HERO, "Soul Anathema" ); // Should be ID 449624 - hero.soul_anathema_dot = conditional_spell_lookup( hero.soul_anathema->ok(), 450538 ); + hero.soul_anathema_dot = conditional_spell_lookup( hero.soul_anathema.ok(), 450538 ); hero.demoniacs_fervor = find_talent_spell( talent_tree::HERO, "Demoniac's Fervor" ); // Should be ID 449629 hero.shared_fate = find_talent_spell( talent_tree::HERO, "Shared Fate" ); // Should be ID 449704 - hero.shared_fate_dot = conditional_spell_lookup( hero.shared_fate->ok(), 450591 ); + hero.shared_fate_dot = conditional_spell_lookup( hero.shared_fate.ok(), 450591 ); hero.feast_of_souls = find_talent_spell( talent_tree::HERO, "Feast of Souls" ); // Should be ID 449706 hero.wicked_reaping = find_talent_spell( talent_tree::HERO, "Wicked Reaping" ); // Should be ID 449631 - hero.wicked_reaping_dmg = conditional_spell_lookup( hero.wicked_reaping->ok(), 449826 ); + hero.wicked_reaping_dmg = conditional_spell_lookup( hero.wicked_reaping.ok(), 449826 ); hero.quietus = find_talent_spell( talent_tree::HERO, "Quietus" ); // Should be ID 449634 hero.sataiels_volition = find_talent_spell( talent_tree::HERO, "Sataiel's Volition" ); // Should be ID 449637 hero.shadow_of_death = find_talent_spell( talent_tree::HERO, "Shadow of Death" ); // Should be ID 449638 - hero.shadow_of_death_energize = conditional_spell_lookup( hero.shadow_of_death->ok(), 449858 ); + hero.shadow_of_death_energize = conditional_spell_lookup( hero.shadow_of_death.ok(), 449858 ); - // Additional Tier Set spell data + hero.manifested_avarice = find_talent_spell( talent_tree::HERO, "Manifested Avarice" ); // Should be ID 1268884 + hero.manifested_avarice_summon = conditional_spell_lookup( hero.manifested_avarice, 1269042 ); + hero.manifested_demonic_soul_buff = conditional_spell_lookup( hero.manifested_avarice.ok(), 1269042 ); - // Manaforge Omega - tier.inquisitor_sh_2pc = sets->set( HERO_SOUL_HARVESTER, TWW3, B2 ); // Should be ID 1236415 - tier.inquisitor_sh_4pc = sets->set( HERO_SOUL_HARVESTER, TWW3, B4 ); // Should be ID 1236416 - tier.rampaging_demonic_soul = conditional_spell_lookup( tier.inquisitor_sh_2pc, 1239689 ); // TWW3_B4 + hero.shared_vessel = find_talent_spell( talent_tree::HERO, "Shared Vessel" ); // Should be ID 1268889 + + hero.eternal_hunger = find_talent_spell( talent_tree::HERO, "Eternal Hunger" ); // Should be ID 1268903 } void warlock_t::init_base_stats() @@ -692,7 +606,10 @@ namespace warlock if ( affliction() ) default_pet = "imp"; else if ( demonology() ) - default_pet = "felguard"; + if ( talents.summon_felguard.ok() ) + default_pet = "felguard"; + else + default_pet = "imp"; else if ( destruction() ) default_pet = "imp"; } @@ -726,25 +643,17 @@ namespace warlock void warlock_t::create_buffs_affliction() { - buffs.nightfall = make_buff( this, "nightfall", talents.nightfall_buff ); - - buffs.tormented_crescendo = make_buff( this, "tormented_crescendo", talents.tormented_crescendo_buff ); - - buffs.malign_omen = make_buff( this, "malign_omen", talents.malign_omen_buff ) - ->set_default_value( talents.malign_omen_buff->effectN( 1 ).percent() ); + buffs.nightfall = make_buff( this, "nightfall", talents.nocturnal_yield.ok() ? talents.nightfall_buff_2 : talents.nightfall_buff ); - buffs.dark_harvest = make_buff( this, "dark_harvest", talents.dark_harvest_buff ) - ->set_pct_buff_type( STAT_PCT_BUFF_HASTE ) - ->set_pct_buff_type( STAT_PCT_BUFF_CRIT ) - ->set_default_value_from_effect( 1 ); // NOTE: 2025-09-24 Effect #1 is for haste and effect #2 is for crit, but the current amount is the same. + buffs.shard_instability = make_buff( this, "shard_instability", talents.shard_instability_buff ) + ->set_chance( talents.drain_soul.ok() ? talents.shard_instability->effectN( 1 ).percent() : talents.shard_instability->effectN( 2 ).percent() ); // TODO: Check RNG type - buffs.umbral_lattice = make_buff( this, "umbral_lattice", tier.umbral_lattice ) - ->set_chance( rng_settings.umbral_lattice.setting_value ); - - buffs.jackpot_affliction = make_buff( this, "jackpot_affliction", tier.spliced_aff_jackpot ) + buffs.cascading_calamity = make_buff( this, "cascading_calamity", talents.cascading_calamity_buff ) ->set_default_value_from_effect( 1 ) - ->set_pct_buff_type( STAT_PCT_BUFF_HASTE ) - ->set_rppm( RPPM_HASTE, tier.spliced_aff_2pc->real_ppm() ); + ->set_pct_buff_type( STAT_PCT_BUFF_HASTE ); + + buffs.seed_of_corruption_is_out_dnt = make_buff( this, "seed_of_corruption_is_out_dnt", talents.seed_of_corruption_is_out_dnt ) + ->set_quiet( true ); } void warlock_t::create_buffs_demonology() @@ -753,9 +662,6 @@ namespace warlock buffs.power_siphon = make_buff( this, "power_siphon", talents.power_siphon_buff ); - buffs.demonic_calling = make_buff( this, "demonic_calling", talents.demonic_calling_buff ) - ->set_chance( talents.demonic_calling->effectN( 3 ).percent() ); - buffs.inner_demons = make_buff( this, "inner_demons", talents.inner_demons ) ->set_period( talents.inner_demons->effectN( 1 ).period() ) ->set_tick_time_behavior( buff_tick_time_behavior::UNHASTED ) @@ -764,59 +670,62 @@ namespace warlock warlock_pet_list.wild_imps.spawn(); } ); - buffs.dread_calling = make_buff( this, "dread_calling", talents.dread_calling_buff ) - ->set_default_value( talents.dread_calling->effectN( 1 ).percent() ); + buffs.tyrants_oblation = make_buff( this, "tyrants_oblation", talents.tyrants_oblation_buff ) + ->set_default_value_from_effect( 1 ) + ->set_pct_buff_type( STAT_PCT_BUFF_HASTE ); + + buffs.hellbent_commander = make_buff( this, "hellbent_commander", talents.hellbent_commander_buff ) + ->set_default_value_from_effect( 1 ); // Pet tracking buffs buffs.wild_imps = make_buff( this, "wild_imps" )->set_max_stack( 40 ); buffs.dreadstalkers = make_buff( this, "dreadstalkers" )->set_max_stack( 8 ) - ->set_duration( talents.call_dreadstalkers_2->duration() ); + ->set_duration( talents.call_dreadstalkers_2->duration() ); buffs.vilefiend = make_buff( this, "vilefiend" )->set_max_stack( 2 ) - ->set_duration( talents.summon_vilefiend->duration() ); + ->set_duration( talents.vilefiend->duration() ); - buffs.tyrant = make_buff( this, "tyrant" )->set_max_stack( 1 ) - ->set_duration( talents.summon_demonic_tyrant->duration() ); + buffs.grimoire_imp_lord = make_buff( this, "grimoire_imp_lord" )->set_max_stack( 1 ) + ->set_duration( talents.grimoire_imp_lord->duration() ); + + buffs.grimoire_fel_ravager = make_buff( this, "grimoire_fel_ravager" )->set_max_stack( 1 ) + ->set_duration( talents.grimoire_fel_ravager->duration() ); - buffs.grimoire_felguard = make_buff( this, "grimoire_felguard" )->set_max_stack( 1 ) - ->set_duration( talents.grimoire_felguard->duration() ); + buffs.doomguard = make_buff( this, "doomguard" )->set_max_stack( 4 ) + ->set_duration( talents.summon_doomguard->duration() ); + + buffs.tyrant = make_buff( this, "tyrant" )->set_max_stack( 1 ) + ->set_duration( talents.summon_demonic_tyrant->duration() ); } void warlock_t::create_buffs_destruction() { buffs.backdraft = make_buff( this, "backdraft", talents.backdraft_buff ); - buffs.rolling_havoc = make_buff( this, "rolling_havoc", talents.rolling_havoc_buff ) - ->add_invalidate( CACHE_PLAYER_DAMAGE_MULTIPLIER ); - buffs.reverse_entropy = make_buff( this, "reverse_entropy", talents.reverse_entropy_buff ) ->set_default_value_from_effect( 1 ) ->set_pct_buff_type( STAT_PCT_BUFF_HASTE ) ->set_trigger_spell( talents.reverse_entropy ) ->set_rppm( RPPM_NONE, talents.reverse_entropy->real_ppm() ); - buffs.rain_of_chaos = make_buff( this, "rain_of_chaos", talents.rain_of_chaos_buff ); + buffs.fiendish_cruelty = make_buff( this, "fiendish_cruelty", talents.fiendish_cruelty_buff ) + ->set_default_value_from_effect( 1 ) + ->set_chance( talents.fiendish_cruelty->effectN( 1 ).percent() ); // TODO: Check RNG type - buffs.impending_ruin = make_buff ( this, "impending_ruin", talents.impending_ruin_buff ) - ->set_stack_change_callback( [ this ]( buff_t* b, int, int cur ) - { - if ( cur == b->max_stack() ) - { - make_event( sim, 0_ms, [ this, b ] { - buffs.ritual_of_ruin->trigger(); - b->expire(); - }); - }; - }); + buffs.chaotic_inferno = make_buff( this, "chaotic_inferno_buff", talents.chaotic_inferno_buff ) + ->set_default_value_from_effect( 1 ) + ->set_chance( talents.chaotic_inferno->effectN( 2 ).percent() ); // TODO: Check RNG type - buffs.ritual_of_ruin = make_buff ( this, "ritual_of_ruin", talents.ritual_of_ruin_buff ); + buffs.rain_of_chaos = make_buff( this, "rain_of_chaos", talents.rain_of_chaos_buff ); buffs.conflagration_of_chaos_cf = make_buff( this, "conflagration_of_chaos_cf", talents.conflagration_of_chaos_cf ) - ->set_default_value_from_effect( 1 ); + ->set_default_value_from_effect( 1 ) + ->set_chance( talents.conflagration_of_chaos->effectN( 1 ).percent() ); buffs.conflagration_of_chaos_sb = make_buff( this, "conflagration_of_chaos_sb", talents.conflagration_of_chaos_sb ) - ->set_default_value_from_effect( 1 ); + ->set_default_value_from_effect( 1 ) + ->set_chance( talents.conflagration_of_chaos->effectN( 1 ).percent() ); buffs.flashpoint = make_buff( this, "flashpoint", talents.flashpoint_buff ) ->set_pct_buff_type( STAT_PCT_BUFF_HASTE ) @@ -826,59 +735,15 @@ namespace warlock ->set_max_stack( std::max( as( talents.crashing_chaos->effectN( 3 ).base_value() ), 1 ) ) ->set_reverse( true ); - // 2024-07-19 - Power Overwhelming appears to grant double the listed value per stack - buffs.power_overwhelming = make_buff( this, "power_overwhelming", talents.power_overwhelming_buff ) - ->set_pct_buff_type( STAT_PCT_BUFF_MASTERY ) - ->set_default_value( talents.power_overwhelming->effectN( 2 ).base_value() / 10.0 ) - ->set_refresh_behavior( buff_refresh_behavior::DISABLED ); - - buffs.burn_to_ashes = make_buff( this, "burn_to_ashes", talents.burn_to_ashes_buff ); - - buffs.decimation = make_buff( this, "decimation", talents.decimation_buff ) - ->set_default_value_from_effect( 1 ); + buffs.alythesss_ire = make_buff( this, "alythesss_ire", talents.alythesss_ire_buff ) + ->set_default_value_from_effect( 1 ) + ->set_chance( talents.alythesss_ire->effectN( 1 ).percent() ); // TODO: Check RNG type buffs.summon_overfiend = make_buff( this, "summon_overfiend", talents.overfiend_buff ) ->set_tick_time_behavior( buff_tick_time_behavior::UNHASTED ) ->set_period( talents.overfiend_buff->effectN( 1 ).period() ) ->set_tick_callback( [ this ]( buff_t*, int, timespan_t ) { resource_gain( RESOURCE_SOUL_SHARD, talents.overfiend_buff->effectN( 1 ).base_value() / 10.0, gains.summon_overfiend ); } ); - - buffs.echo_of_the_azjaqir = make_buff( this, "echo_of_the_azjaqir", tier.echo_of_the_azjaqir ); - - buffs.demonfire_flurry_trigger = make_buff( this, "demonfire_flurry_trigger", tier.demonfire_flurry ) - ->set_refresh_behavior( buff_refresh_behavior::DURATION ) - ->set_tick_callback( [ this ]( buff_t*, int, timespan_t ){ - proc_actions.jackpot_cdf->target_cache.is_valid = false; - const auto& tl = proc_actions.jackpot_cdf->target_list(); - - if ( !tl.empty() ) - { - proc_actions.jackpot_cdf->set_target( tl[ rng().range( size_t(), tl.size() ) ] ); - proc_actions.jackpot_cdf->execute(); - } - } ); - - timespan_t tick_time = tier.demonfire_flurry->effectN( 1 ).period(); - timespan_t duration = tier.demonfire_flurry->duration(); - - int ticks = as( floor( ( duration / tick_time ) ) ); - if ( talents.raging_demonfire.ok() ) - { - ticks += as( talents.raging_demonfire->effectN( 1 ).base_value() ); - tick_time *= 1.0 + talents.raging_demonfire->effectN( 3 ).percent(); - } - duration = ticks * tick_time; - - buffs.demonfire_flurry_trigger->set_period( tick_time ); - buffs.demonfire_flurry_trigger->set_duration( duration ); - - buffs.demonfire_flurry_trigger->set_tick_time_behavior( buff_tick_time_behavior::UNHASTED ); - // TODO: Supposedly this is a hasted effect. Not sure if buff_t has a case for this having periodic ticks *and* hasted duration - //buffs.demonfire_flurry_trigger->set_tick_time_behavior( buff_tick_time_behavior::HASTED ); - - buffs.jackpot_destruction = make_buff( this, "jackpot_destruction", tier.spliced_destro_jackpot ) - ->set_pct_buff_type( STAT_PCT_BUFF_MASTERY ) - ->set_default_value_from_effect( 1 ); } struct diabolic_ritual_buff_t : public buff_t @@ -888,8 +753,8 @@ namespace warlock diabolic_ritual_buff_t( warlock_t* p, util::string_view name, const spell_data_t* spell_data, const int _diabolic_ritual_next_index = 0, buff_t* _art_buff_trigger = nullptr ) : buff_t( p, name, spell_data ), - diabolic_ritual_next_index(_diabolic_ritual_next_index), - art_buff_trigger(_art_buff_trigger) + diabolic_ritual_next_index( _diabolic_ritual_next_index ), + art_buff_trigger( _art_buff_trigger ) { set_can_cancel( false ); set_stack_change_callback( [ this, p ]( buff_t*, int, int cur ) @@ -897,7 +762,7 @@ namespace warlock if ( cur == 0 ) { // The trigger of the Demonic Art buff has a certain delay that can be modeled fairly closely using a normal distribution - const timespan_t buff_delay = timespan_t::from_millis( rng().gauss(200, 15) ); + const timespan_t buff_delay = timespan_t::from_millis( rng().gauss( 200, 15 ) ); make_event( sim, buff_delay, [ this, p ] { if ( p->buffs.art_mother->check() || p->buffs.art_pit_lord->check() || p->buffs.art_overlord->check() ) { @@ -908,7 +773,7 @@ namespace warlock p->buffs.art_overlord->expire(); p->demonic_art_buff_replaced = false; } - if (art_buff_trigger) + if ( art_buff_trigger ) art_buff_trigger->trigger(); } ); p->diabolic_ritual = diabolic_ritual_next_index; @@ -968,31 +833,50 @@ namespace warlock buffs.ruination = make_buff( this, "ruination", hero.ruination_buff ); - buffs.demonic_oculus = make_buff( this, "demonic_oculus", tier.demonic_oculus ); - - buffs.demonic_intelligence = make_buff( this, "demonic_intelligence", tier.demonic_intelligence ) + buffs.demonic_oculi = make_buff( this, "demonic_oculi", hero.demonic_oculi_buff ) + ->set_period( 1_s ) + ->set_tick_callback( [ this ]( buff_t* b, int, timespan_t ) { + if ( hero.looks_that_kill.ok() ) + { + switch ( b->check() ) + { + case 1: + proc_actions.diabolic_gaze_1->execute_on_target( target ); + break; + case 2: + proc_actions.diabolic_gaze_2->execute_on_target( target ); + break; + case 3: + proc_actions.diabolic_gaze_3->execute_on_target( target ); + } + } + } ); + + buffs.minds_eyes = make_buff( this, "minds_eyes", hero.minds_eyes_buff ) ->set_pct_buff_type( STAT_PCT_BUFF_INTELLECT ) - ->set_default_value_from_effect_type( A_MOD_TOTAL_STAT_PERCENTAGE );; + ->set_default_value_from_effect_type( A_MOD_TOTAL_STAT_PERCENTAGE ); } void warlock_t::create_buffs_hellcaller() { buffs.malevolence = make_buff( this, "malevolence", hero.malevolence_buff ) - ->set_cooldown( hero.malevolence_buff->cooldown() - 1_s ) + ->set_cooldown( 0_ms ) + ->set_refresh_behavior( buff_refresh_behavior::EXTEND ) + ->set_max_stack( 1 ) ->set_pct_buff_type( STAT_PCT_BUFF_HASTE ) ->set_default_value_from_effect( 1 ); - - buffs.maintained_withering = make_buff( this, "maintained_withering", tier.maintained_withering ); } void warlock_t::create_buffs_soul_harvester() { buffs.succulent_soul = make_buff( this, "succulent_soul", hero.succulent_soul ); + + buffs.manifested_demonic_soul = make_buff( this, "manifested_demonic_soul", hero.manifested_demonic_soul_buff ); } void warlock_t::create_pets() { - for ( auto& pet : pet_name_list ) + for ( const auto& pet : pet_name_list ) { create_pet( pet ); } @@ -1025,8 +909,6 @@ namespace warlock init_gains_diabolist(); init_gains_hellcaller(); init_gains_soul_harvester(); - - gains.soul_conduit = get_gain( "soul_conduit" ); } void warlock_t::init_gains_affliction() @@ -1065,7 +947,6 @@ namespace warlock { gains.feast_of_souls = get_gain( "feast_of_souls" ); gains.shadow_of_death = get_gain( "shadow_of_death" ); - gains.rampaging_demonic_soul = get_gain( "rampaging_demonic_soul" ); } void warlock_t::init_procs() @@ -1082,31 +963,15 @@ namespace warlock init_procs_diabolist(); init_procs_hellcaller(); init_procs_soul_harvester(); - - procs.demonic_calling = get_proc( "demonic_calling" ); - procs.soul_conduit = get_proc( "soul_conduit" ); - procs.ritual_of_ruin = get_proc( "ritual_of_ruin" ); - procs.avatar_of_destruction = get_proc( "avatar_of_destruction" ); - procs.mayhem = get_proc( "mayhem" ); - procs.conflagration_of_chaos_cf = get_proc( "conflagration_of_chaos_cf" ); - procs.conflagration_of_chaos_sb = get_proc( "conflagration_of_chaos_sb" ); - procs.demonic_inspiration = get_proc( "demonic_inspiration" ); - procs.wrathful_minion = get_proc( "wrathful_minion" ); } void warlock_t::init_procs_affliction() { procs.nightfall = get_proc( "nightfall" ); - procs.shadow_bolt_volley = get_proc( "shadow_bolt_volley" ); - procs.tormented_crescendo = get_proc( "tormented_crescendo" ); + procs.shadowbolt_volley = get_proc( "shadowbolt_volley" ); procs.ravenous_afflictions = get_proc( "ravenous_afflictions" ); - procs.umbral_lattice = get_proc( "umbral_lattice" ); - procs.jackpot_affliction = get_proc( "jackpot_affliction" ); - - for ( size_t i = 0; i < procs.malefic_rapture.size(); i++ ) - { - procs.malefic_rapture[ i ] = get_proc( fmt::format( "Malefic Rapture {}", i + 1 ) ); - } + procs.shard_instability = get_proc( "shard_instability" ); + procs.fatal_echoes = get_proc( "fatal_echoes" ); } void warlock_t::init_procs_demonology() @@ -1114,33 +979,25 @@ namespace warlock procs.demonic_core_dogs = get_proc( "demonic_core_dogs" ); procs.demonic_core_imps = get_proc( "demonic_core_imps" ); procs.carnivorous_stalkers = get_proc( "carnivorous_stalkers" ); - procs.shadow_invocation = get_proc( "shadow_invocation" ); - procs.imp_gang_boss = get_proc( "imp_gang_boss" ); + procs.infernal_rapidity = get_proc( "infernal_rapidity" ); procs.spiteful_reconstitution = get_proc( "spiteful_reconstitution" ); - procs.umbral_blaze = get_proc( "umbral_blaze" ); - procs.pact_of_the_imp_mother = get_proc( "pact_of_the_imp_mother" ); - procs.doom_eternal = get_proc( "doom_eternal" ); - procs.pact_of_the_eredruin = get_proc( "pact_of_the_eredruin" ); - procs.empowered_legion_strike = get_proc( "empowered_legion_strike" ); - procs.jackpot_demonology = get_proc( "jackpot_demonology" ); - procs.demonic_core_big_dogs = get_proc( "demonic_core_greater_dreadstalkers" ); - - for ( size_t i = 0; i < procs.hand_of_guldan_shards.size(); i++ ) - { - procs.hand_of_guldan_shards[ i ] = get_proc( fmt::format( "Hand of Gul'dan {}", i + 1 ) ); - } + procs.demonic_knowledge = get_proc( "demonic_knowledge" ); } void warlock_t::init_procs_destruction() { + procs.mayhem = get_proc( "mayhem" ); + procs.fiendish_cruelty = get_proc( "fiendish_cruelty" ); + procs.chaotic_inferno = get_proc( "chaotic_inferno" ); + procs.dimensional_rift = get_proc( "dimensional_rift" ); + procs.avatar_of_destruction = get_proc( "avatar_of_destruction" ); + procs.conflagration_of_chaos_cf = get_proc( "conflagration_of_chaos_cf" ); + procs.conflagration_of_chaos_sb = get_proc( "conflagration_of_chaos_sb" ); + procs.alythesss_ire = get_proc( "alythesss_ire" ); procs.reverse_entropy = get_proc( "reverse_entropy" ); procs.rain_of_chaos = get_proc( "rain_of_chaos" ); procs.demonfire_infusion_inc = get_proc( "demonfire_infusion_incinerate" ); procs.demonfire_infusion_dot = get_proc( "demonfire_infusion_dot" ); - procs.decimation = get_proc( "decimation" ); - procs.dimension_ripper = get_proc( "dimension_ripper" ); - procs.echo_of_the_azjaqir = get_proc( "echo_of_the_azjaqir" ); - procs.jackpot_destruction = get_proc( "jackpot_destruction" ); } void warlock_t::init_procs_diabolist() @@ -1184,14 +1041,12 @@ namespace warlock void warlock_t::init_rng_demonology() { - jackpot_demonology_rng = get_rppm( "jackpot_demonology", tier.spliced_demo_2pc ); } void warlock_t::init_rng_destruction() { - // TOCHECK: Presumed to use deck of cards at 3 out of 20. Long sample test needed to reconfirm in TWW + // TOCHECK: Presumed to use deck of cards at 3 out of 20. Long sample test needed to reconfirm in TWW/Midnight rain_of_chaos_rng = get_shuffled_rng( "rain_of_chaos", 3, 20 ); - jackpot_destruction_rng = get_rppm( "jackpot_destruction", tier.spliced_destro_2pc ); } void warlock_t::init_rng_diabolist() @@ -1200,6 +1055,7 @@ namespace warlock void warlock_t::init_rng_hellcaller() { + devil_fruit_rng = get_rppm( "devil_fruit", hero.devil_fruit ); } void warlock_t::init_rng_soul_harvester() @@ -1294,7 +1150,6 @@ namespace warlock return player_t::action_names_from_spell_id( spell_id ); } - void warlock_t::init_blizzard_action_list() { [[maybe_unused]] action_priority_list_t* default_ = get_action_priority_list( "default" ); @@ -1347,7 +1202,7 @@ namespace warlock cooldowns->add_action( "summon_infernal" ); break; case WARLOCK_AFFLICTION: - cooldowns->add_action( "summon_darkglare,if=dot.soul_rot.ticking|!talent.soul_rot" ); + cooldowns->add_action( "summon_darkglare" ); break; default: break; @@ -1367,17 +1222,14 @@ namespace warlock 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_bool( "demonic_inspiration_double_dip", demonic_inspiration_double_dip ) ); add_rng_option( rng_settings.cunning_cruelty_sb ); add_rng_option( rng_settings.cunning_cruelty_ds ); add_rng_option( rng_settings.agony ); add_rng_option( rng_settings.nightfall ); add_rng_option( rng_settings.pact_of_the_eredruin ); - add_rng_option( rng_settings.shadow_invocation ); + add_rng_option( rng_settings.avatar_of_destruction_dr ); add_rng_option( rng_settings.spiteful_reconstitution ); - add_rng_option( rng_settings.decimation ); - add_rng_option( rng_settings.dimension_ripper ); add_rng_option( rng_settings.blackened_soul ); add_rng_option( rng_settings.bleakheart_tactics ); add_rng_option( rng_settings.seeds_of_their_demise ); @@ -1386,15 +1238,14 @@ namespace warlock add_rng_option( rng_settings.succulent_soul_demo ); add_rng_option( rng_settings.feast_of_souls_aff ); add_rng_option( rng_settings.feast_of_souls_demo ); - add_rng_option( rng_settings.umbral_lattice ); - add_rng_option( rng_settings.empowered_legion_strike ); + add_rng_option( rng_settings.manifested_avarice ); } void warlock_t::combat_begin() { player_t::combat_begin(); - if ( demonology() && buffs.inner_demons && talents.inner_demons->ok() ) + if ( demonology() && buffs.inner_demons && talents.inner_demons.ok() ) { timespan_t start = timespan_t::from_seconds( rng().range( talents.inner_demons->effectN( 1 ).period().total_seconds() ) ); make_event( sim, start, [ this ] { buffs.inner_demons->trigger(); } ); @@ -1417,7 +1268,6 @@ namespace warlock warlock_pet_list.active = nullptr; havoc_target = nullptr; - ua_target = nullptr; agony_accumulator = rng().range( 0.0, 0.99 ); corruption_accumulator = rng().range( 0.0, 0.99 ); wild_imp_spawns.clear(); diff --git a/engine/class_modules/warlock/sc_warlock_pets.cpp b/engine/class_modules/warlock/sc_warlock_pets.cpp index 546a2f8e170..a294be2b68e 100644 --- a/engine/class_modules/warlock/sc_warlock_pets.cpp +++ b/engine/class_modules/warlock/sc_warlock_pets.cpp @@ -32,41 +32,16 @@ void warlock_pet_t::create_buffs() pet_t::create_buffs(); // Demonology - buffs.demonic_strength = make_buff( this, "demonic_strength", o()->talents.demonic_strength ) - ->set_default_value( o()->talents.demonic_strength->effectN( 2 ).percent() ) - ->set_cooldown( 0_ms ); - - buffs.grimoire_of_service = make_buff( this, "grimoire_of_service", o()->talents.grimoire_of_service ) - ->set_default_value( o()->talents.grimoire_of_service->effectN( 1 ).percent() ); - - buffs.annihilan_training = make_buff( this, "annihilan_training", o()->talents.annihilan_training_buff ) - ->set_default_value( o()->talents.annihilan_training_buff->effectN( 1 ).percent() ); - - buffs.dread_calling = make_buff( this, "dread_calling", o()->talents.dread_calling_pet ); - buffs.imp_gang_boss = make_buff( this, "imp_gang_boss", o()->talents.imp_gang_boss_buff ) ->set_default_value_from_effect( 2 ); - buffs.antoran_armaments = make_buff( this, "antoran_armaments", o()->talents.antoran_armaments_buff ) - ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_DONE ); + buffs.unstable_soul = make_buff( this, "unstable_soul", o()->talents.unstable_soul_buff ) + ->set_default_value_from_effect( 1 ); buffs.ferocity_of_fharg = make_buff( this, "ferocity_of_fharg", o()->talents.ferocity_of_fharg_buff ); - buffs.the_expendables = make_buff( this, "the_expendables", o()->talents.the_expendables_buff ) - ->set_default_value_from_effect( 1 ); - buffs.demonic_power = make_buff( this, "demonic_power", o()->talents.demonic_power_buff ) - ->set_default_value_from_effect( 5 ); - - buffs.empowered_legion_strike = make_buff( this, "empowered_legion_strike", o()->tier.empowered_legion_strike ); - - buffs.demonic_hunger = make_buff( this, "demonic_hunger", o()->tier.demonic_hunger ) - ->set_default_value( o()->tier.demonic_hunger->effectN( 1 ).percent() ) - ->set_rppm( RPPM_DISABLE ) - ->set_chance( 1.0 ); - - buffs.spliced_4pc = make_buff( this, "spliced_fiendtraders_influence_4pc" ) - ->set_chance( 1.0 ); + ->set_default_value_from_effect( 1 ); // Destruction buffs.embers = make_buff( this, "embers", o()->talents.embers ) @@ -75,8 +50,7 @@ void warlock_pet_t::create_buffs() } ); // All Specs - buffs.demonic_inspiration = make_buff( this, "demonic_inspiration", o()->talents.demonic_inspiration_buff ) - ->set_default_value_from_effect( 1 ); + // To avoid clogging the buff reports, we silence the pet movement statistics since Implosion uses them regularly // and there are a LOT of Wild Imps. We can instead lump them into a single tracking buff on the owner. @@ -92,16 +66,10 @@ void warlock_pet_t::create_buffs() // These buffs are needed for operational purposes but serve little to no reporting purpose buffs.imp_gang_boss->quiet = true; - buffs.demonic_strength->quiet = true; - buffs.grimoire_of_service->quiet = true; - buffs.annihilan_training->quiet = true; - buffs.antoran_armaments->quiet = true; + buffs.unstable_soul->quiet = true; buffs.ferocity_of_fharg->quiet = true; buffs.embers->quiet = true; buffs.demonic_power->quiet = true; - buffs.the_expendables->quiet = true; - buffs.demonic_hunger->quiet = true; - buffs.spliced_4pc->quiet = true; } void warlock_pet_t::init_base_stats() @@ -153,8 +121,6 @@ void warlock_pet_t::schedule_ready( timespan_t delta_time, bool waiting ) // Stuff that are updated at the heartbeat update interval void warlock_pet_t::heartbeat_update_event() { - if ( affected_by.demonic_inspiration && !buffs.demonic_inspiration->check() ) - buffs.demonic_inspiration->trigger(); }; /* @@ -167,77 +133,51 @@ double warlock_pet_t::composite_player_multiplier( school_e school ) const { double m = pet_t::composite_player_multiplier( school ); - m *= 1.0 + buffs.grimoire_of_service->check_value(); + m *= 1.0 + o()->buffs.hellbent_commander->check_stack_value(); // TODO: Could 'parse_effects' be used for this? - if ( ( pet_type == PET_DREADSTALKER || pet_type == PET_FELHUNTER ) && o()->talents.dread_calling.ok() ) - m *= 1.0 + buffs.dread_calling->check_value(); + m *= 1.0 + buffs.demonic_power->check_stack_value(); // TODO: Could 'parse_effects' be used for this? - if ( o()->talents.the_expendables.ok() ) - m *= 1.0 + buffs.the_expendables->check_stack_value(); - - m *= 1.0 + buffs.demonic_power->check_value(); + m *= 1.0 + buffs.unstable_soul->check_value(); // TODO: Could 'parse_effects' be used for this? return m; } -double warlock_pet_t::composite_spell_haste() const +double warlock_pet_t::composite_melee_haste() const { - double m = pet_t::composite_spell_haste(); + double m = pet_t::composite_melee_haste(); - if ( buffs.demonic_inspiration->check() ) - { - m /= 1.0 + buffs.demonic_inspiration->check_value(); - if ( is_main_pet && o()->demonic_inspiration_double_dip ) - m /= 1.0 + buffs.demonic_inspiration->check_value(); - } + // TODO: Could 'parse_effects' be used for this? + if ( buffs.ferocity_of_fharg->check() ) + m *= 1.0 + buffs.ferocity_of_fharg->data().effectN( 1 ).percent(); return m; } -double warlock_pet_t::composite_melee_haste() const +double warlock_pet_t::composite_melee_auto_attack_speed() const { - double m = pet_t::composite_melee_haste(); - - if ( buffs.demonic_inspiration->check() ) - { - m /= 1.0 + buffs.demonic_inspiration->check_value(); - if ( is_main_pet && o()->demonic_inspiration_double_dip ) - m /= 1.0 + buffs.demonic_inspiration->check_value(); - } + double m = pet_t::composite_melee_auto_attack_speed(); + // TODO: Could 'parse_effects' be used for this? if ( buffs.ferocity_of_fharg->check() ) - m *= 1.0 + buffs.ferocity_of_fharg->data().effectN( 1 ).percent(); + m /= 1.0 + buffs.ferocity_of_fharg->data().effectN( 1 ).percent(); return m; } -double warlock_pet_t::composite_spell_cast_speed() const +double warlock_pet_t::composite_melee_crit_chance() const { - double m = pet_t::composite_spell_cast_speed(); + double m = pet_t::composite_melee_crit_chance(); - if ( buffs.demonic_inspiration->check() ) - { - m /= 1.0 + buffs.demonic_inspiration->check_value(); - if ( is_main_pet && o()->demonic_inspiration_double_dip ) - m /= 1.0 + buffs.demonic_inspiration->check_value(); - } + m *= 1.0 + o()->talents.improved_demonic_tactics->effectN( 1 ).percent(); return m; } -double warlock_pet_t::composite_melee_auto_attack_speed() const +double warlock_pet_t::composite_spell_crit_chance() const { - double m = pet_t::composite_melee_auto_attack_speed(); + double m = pet_t::composite_spell_crit_chance(); - if ( buffs.demonic_inspiration->check() ) - { - m /= 1.0 + buffs.demonic_inspiration->check_value(); - if ( is_main_pet && o()->demonic_inspiration_double_dip ) - m /= 1.0 + buffs.demonic_inspiration->check_value(); - } - - if ( buffs.ferocity_of_fharg->check() ) - m /= 1.0 + buffs.ferocity_of_fharg->data().effectN( 1 ).percent(); + m *= 1.0 + o()->talents.improved_demonic_tactics->effectN( 1 ).percent(); return m; } @@ -248,14 +188,30 @@ void warlock_pet_t::arise() melee_attack->reset(); pet_t::arise(); + + if ( o()->talents.hellbent_commander.ok() ) + { + o()->buffs.hellbent_commander->trigger(); + assert( o()->buffs.hellbent_commander->check() == o()->active_demon_count() ); + } } void warlock_pet_t::demise() { + if ( !current.sleeping ) + { + if ( o()->talents.hellbent_commander.ok() ) + { + o()->buffs.hellbent_commander->decrement(); + } + } + pet_t::demise(); if ( melee_attack ) melee_attack->reset(); + + assert( !o()->talents.hellbent_commander.ok() || o()->buffs.hellbent_commander->check() == o()->active_demon_count() ); } warlock_pet_td_t::warlock_pet_td_t( player_t* target, warlock_pet_t& p ) : @@ -279,7 +235,7 @@ timespan_t warlock_simple_pet_t::available() const return warlock_pet_t::available(); timespan_t cd_remains = special_ability->cooldown->ready - sim->current_time(); - + if ( cd_remains <= 1_ms ) return warlock_pet_t::available(); @@ -297,7 +253,6 @@ felhunter_pet_t::felhunter_pet_t( warlock_t* owner, util::string_view name ) action_list_str = "shadow_bite"; is_main_pet = true; - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } struct spell_lock_t : public warlock_pet_spell_t @@ -326,7 +281,7 @@ void felhunter_pet_t::init_base_stats() action_t* felhunter_pet_t::create_action( util::string_view name, util::string_view options_str ) { if ( name == "shadow_bite" ) - return new warlock_pet_melee_attack_t( this, "Shadow Bite" ); + return new warlock_pet_melee_attack_t( this, "Shadow Bite" ); // TODO: Is this enough? is this ok? if ( name == "spell_lock" ) return new spell_lock_t( this, options_str ); @@ -347,7 +302,6 @@ imp_pet_t::imp_pet_t( warlock_t* owner, util::string_view name ) owner_coeff.health = 0.45; is_main_pet = true; - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } action_t* imp_pet_t::create_action( util::string_view name, util::string_view options_str ) @@ -385,7 +339,6 @@ sayaad_pet_t::sayaad_pet_t( warlock_t* owner, util::string_view name ) action_list_str = "whiplash/lash_of_pain"; is_main_pet = true; - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } void sayaad_pet_t::init_base_stats() @@ -440,12 +393,11 @@ voidwalker_pet_t::voidwalker_pet_t( warlock_t* owner, util::string_view name ) action_list_str = "consuming_shadows"; is_main_pet = true; - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } struct consuming_shadows_t : public warlock_pet_spell_t { - consuming_shadows_t( warlock_pet_t* p ) + consuming_shadows_t( warlock_pet_t* p ) : warlock_pet_spell_t( p, "Consuming Shadows" ) { aoe = -1; @@ -483,29 +435,20 @@ namespace demonology felguard_pet_t::felguard_pet_t( warlock_t* owner, util::string_view name ) : warlock_pet_t( owner, name, PET_FELGUARD, false ), - soul_strike( nullptr ), - hatred_proc( nullptr ), - demonic_strength_executes( 0 ), min_energy_threshold( find_spell( 89751 )->cost( POWER_ENERGY ) ), max_energy_threshold( 100 ) { action_list_str = "travel"; - action_list_str += "/felstorm_demonic_strength"; if ( !owner->disable_auto_felstorm ) action_list_str += "/felstorm"; action_list_str += "/legion_strike,if=energy>=" + util::to_string( max_energy_threshold ); - if ( owner->talents.soul_strike.ok() ) - action_list_str += "/soul_strike,use_while_casting=1"; - felstorm_cd = get_cooldown( "felstorm" ); - dstr_cd = get_cooldown( "felstorm_demonic_strength" ); owner_coeff.health = 0.75; is_main_pet = true; - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } struct felguard_melee_t : public warlock_pet_melee_t @@ -530,48 +473,12 @@ struct axe_toss_t : public warlock_pet_spell_t struct legion_strike_t : public warlock_pet_melee_attack_t { - bool main_pet; - - legion_strike_t( warlock_pet_t* p, util::string_view options_str ) + legion_strike_t( warlock_pet_t* p, util::string_view options_str ) : warlock_pet_melee_attack_t( p, "Legion Strike" ) { parse_options( options_str ); aoe = -1; weapon = &( p->main_hand_weapon ); - main_pet = true; - } - - legion_strike_t( warlock_pet_t* p, util::string_view options_str, bool is_main_pet ) - : legion_strike_t( p, options_str ) - { main_pet = is_main_pet; } - - void execute() override - { - warlock_pet_melee_attack_t::execute(); - - p()->buffs.empowered_legion_strike->decrement(); - } - - double action_multiplier() const override - { - double m = warlock_pet_melee_attack_t::action_multiplier(); - - if ( p()->buffs.empowered_legion_strike->check() ) - m *= p()->o()->tier.empowered_legion_strike->effectN( 1 ).percent(); - - return m; - } -}; - -struct immutable_hatred_t : public warlock_pet_melee_attack_t -{ - immutable_hatred_t( warlock_pet_t* p ) - : warlock_pet_melee_attack_t( "Immutable Hatred", p, p->o()->talents.immutable_hatred_proc ) - { - background = dual = true; - weapon = &( p->main_hand_weapon ); - may_miss = may_block = may_dodge = may_parry = false; - ignore_false_positive = true; } }; @@ -579,8 +486,6 @@ struct felstorm_t : public warlock_pet_melee_attack_t { struct felstorm_tick_t : public warlock_pet_melee_attack_t { - bool applies_fel_sunder; - felstorm_tick_t( warlock_pet_t* p, const spell_data_t *s ) : warlock_pet_melee_attack_t( "Felstorm (tick)", p, s ) { @@ -588,24 +493,6 @@ struct felstorm_t : public warlock_pet_melee_attack_t reduced_aoe_targets = data().effectN( 3 ).base_value(); background = true; weapon = &( p->main_hand_weapon ); - applies_fel_sunder = false; - } - - double action_multiplier() const override - { - double m = warlock_pet_melee_attack_t::action_multiplier(); - - m *= 1.0 + p()->buffs.demonic_strength->check_value(); - - return m; - } - - void impact( action_state_t* s ) override - { - warlock_pet_melee_attack_t::impact( s ); - - if ( applies_fel_sunder ) - owner_td( s->target )->debuffs.fel_sunder->trigger(); } }; @@ -623,21 +510,7 @@ struct felstorm_t : public warlock_pet_melee_attack_t tick_action = new felstorm_tick_t( p, p->find_spell( 89753 ) ); } - felstorm_t( warlock_pet_t* p, util::string_view options_str, bool main_pet, const std::string n = "Felstorm" ) - : felstorm_t( p, options_str, n ) - { - // 2024-07-14 GFG Felstorm applies Fel Sunder, possibly bug - if ( ( main_pet || p->bugs ) && p->o()->talents.fel_sunder->ok() ) - debug_cast( tick_action )->applies_fel_sunder = true; - - if ( !main_pet ) - cooldown->duration = 45_s; // 2022-11-11: GFG does not appear to cast a second Felstorm even if the cooldown would come up, so we will pad this value to be longer than the possible duration. - - if ( main_pet ) - internal_cooldown = p->o()->get_cooldown( "felstorm_icd" ); - } - - // NOTE: Although Felstorm is a "melee" attack, its duration/ticks depend on the pet's 'spell_cast_speed' + // NOTE: Although Felstorm is a "melee" attack, its duration/ticks depend on the pet's 'spell_cast_speed' double composite_haste() const override { return action_t::composite_haste() * player->cache.spell_cast_speed(); } @@ -648,112 +521,10 @@ struct felstorm_t : public warlock_pet_melee_attack_t { warlock_pet_melee_attack_t::execute(); - // New in 10.0.5 - Hardcoded scripted shared cooldowns while one of Felstorm, Demonic Strength, or Guillotine is active - if ( internal_cooldown ) - internal_cooldown->start( 5_s * p()->composite_spell_cast_speed() ); - p()->melee_attack->cancel(); } }; -struct demonic_strength_t : public felstorm_t -{ - demonic_strength_t( warlock_pet_t* p, util::string_view options_str ) - : felstorm_t( p, options_str, std::string( "Felstorm (Demonic Strength)" ) ) - { - if ( p->o()->talents.fel_sunder.ok() ) - debug_cast( tick_action )->applies_fel_sunder = true; - } - - void execute() override - { - warlock_pet_melee_attack_t::execute(); - debug_cast< felguard_pet_t* >( p() )->demonic_strength_executes--; - p()->melee_attack->cancel(); - } - - void last_tick( dot_t* d ) override - { - warlock_pet_melee_attack_t::last_tick( d ); - - p()->buffs.demonic_strength->expire(); - } - - // 2022-10-03 - Triggering Demonic Strength seems to ignore energy cost for Felstorm - double cost() const override - { return 0.0; } - - bool ready() override - { - if ( debug_cast< felguard_pet_t* >( p() )->demonic_strength_executes <= 0 ) - return false; - - return warlock_pet_melee_attack_t::ready(); - } -}; - -struct soul_strike_t : public warlock_pet_melee_attack_t -{ - struct soul_cleave_t : public warlock_pet_melee_attack_t - { - soul_cleave_t( warlock_pet_t* p ) : warlock_pet_melee_attack_t( "Soul Cleave", p, p->o()->talents.soul_cleave ) - { - background = dual = true; - aoe = -1; - ignores_armor = !p->bugs; // TOCHECK: 2025-04-17 This spell currently does not ignore armor (bug?) - base_dd_min = base_dd_max = 0; - target_filter_callback = secondary_targets_only(); - } - - void init_finished() override - { - warlock_pet_melee_attack_t::init_finished(); - - // TOCHECK: 2025-04-17 Although this spell is not supposed to be affected by modifiers, it is currently affected by them (bug?) - if ( !p()->bugs ) - snapshot_flags &= STATE_NO_MULTIPLIER; - - } - }; - - soul_cleave_t* soul_cleave; - - soul_strike_t( warlock_pet_t* p, util::string_view options_str ) : warlock_pet_melee_attack_t( "Soul Strike", p, p->o()->talents.soul_strike_dmg ) - { - parse_options( options_str ); - - cooldown->duration = p->o()->talents.soul_strike_pet->cooldown(); - trigger_gcd = p->o()->talents.soul_strike_pet->gcd(); - usable_while_casting = true; - - soul_cleave = new soul_cleave_t( p ); - add_child( soul_cleave ); - // TOCHECK: As of 2023-10-16 PTR, Soul Cleave appears to be double-dipping on both Annihilan Training and Antoran Armaments multipliers (implemented) - - base_multiplier *= 1.0 + p->o()->talents.fel_invocation->effectN( 1 ).percent(); - } - - void execute() override - { - warlock_pet_melee_attack_t::execute(); - - if ( p()->o()->talents.fel_invocation.ok() ) - p()->o()->resource_gain( RESOURCE_SOUL_SHARD, 1.0, p()->o()->gains.soul_strike ); - } - - void impact( action_state_t* s ) override - { - auto amount = s->result_raw; - - amount *= p()->o()->talents.antoran_armaments_buff->effectN( 2 ).percent(); - - warlock_pet_melee_attack_t::impact( s ); - - if ( p()->o()->talents.antoran_armaments.ok() ) - soul_cleave->execute_on_target( s->target, amount ); - } -}; - timespan_t felguard_pet_t::available() const { double energy_threshold = max_energy_threshold; @@ -770,24 +541,6 @@ timespan_t felguard_pet_t::available() const if ( deficit < 0 ) time_to_threshold = util::ceil( std::fabs( deficit ) / resource_regen_per_second( RESOURCE_ENERGY ), 3 ); - // Demonic Strength Felstorms do not have an energy requirement, so Felguard must be ready at any time it is up - // If Demonic Strength will be available before regular Felstorm but before energy threshold, we will check at that time - if ( o()->talents.demonic_strength.ok() ) - { - double time_to_dstr = ( dstr_cd->ready - sim->current_time() ).total_seconds(); - if ( time_to_dstr <= 0 ) - { - return warlock_pet_t::available(); - } - else if ( time_to_dstr <= time_to_felstorm && time_to_dstr <= time_to_threshold ) - { - if ( time_to_dstr < 0.001 ) - return warlock_pet_t::available(); - else - return timespan_t::from_seconds( time_to_dstr ); - } - } - // Fuzz regen by making the pet wait a bit extra if it's just below the resource threshold if ( time_to_threshold < 0.001 ) return warlock_pet_t::available(); @@ -827,9 +580,6 @@ void felguard_pet_t::init_base_stats() melee_attack->base_dd_multiplier *= 1.42; special_action = new axe_toss_t( this, "" ); - - if ( o()->talents.immutable_hatred.ok() ) - hatred_proc = new immutable_hatred_t( this ); } action_t* felguard_pet_t::create_action( util::string_view name, util::string_view options_str ) @@ -837,168 +587,15 @@ action_t* felguard_pet_t::create_action( util::string_view name, util::string_vi if ( name == "legion_strike" ) return new legion_strike_t( this, options_str ); if ( name == "felstorm" ) - return new felstorm_t( this, options_str, true ); + return new felstorm_t( this, options_str ); if ( name == "axe_toss" ) return new axe_toss_t( this, options_str ); - if ( name == "felstorm_demonic_strength" ) - return new demonic_strength_t( this, options_str ); - if ( name == "soul_strike" ) - return new soul_strike_t( this, options_str ); return warlock_pet_t::create_action( name, options_str ); } -void felguard_pet_t::queue_ds_felstorm() -{ - demonic_strength_executes++; - - if ( !readying && !channeling && !executing ) - schedule_ready(); -} - -void felguard_pet_t::arise() -{ - warlock_pet_t::arise(); - - if ( o()->talents.annihilan_training.ok() ) - buffs.annihilan_training->trigger(); - - if ( o()->talents.antoran_armaments.ok() ) - buffs.antoran_armaments->trigger(); -} - -double felguard_pet_t::composite_player_multiplier( school_e school ) const -{ - double m = warlock_pet_t::composite_player_multiplier( school ); - - if ( o()->talents.annihilan_training.ok() ) - m *= 1.0 + buffs.annihilan_training->check_value(); - - if ( o()->talents.antoran_armaments.ok() ) - m *= 1.0 + buffs.antoran_armaments->check_value(); - - return m; -} - -double felguard_pet_t::composite_melee_crit_chance() const -{ - double m = warlock_pet_t::composite_melee_crit_chance(); - - m *= 1.0 + o()->talents.improved_demonic_tactics->effectN( 1 ).percent(); - - return m; -} - -double felguard_pet_t::composite_spell_crit_chance() const -{ - double m = warlock_pet_t::composite_spell_crit_chance(); - - m *= 1.0 + o()->talents.improved_demonic_tactics->effectN( 1 ).percent(); - - return m; -} - /// Felguard End -/// Grimoire: Felguard Begin - -grimoire_felguard_pet_t::grimoire_felguard_pet_t( warlock_t* owner ) - : warlock_pet_t( owner, "grimoire_felguard", PET_SERVICE_FELGUARD, true ), - min_energy_threshold( find_spell( 89751 )->cost( POWER_ENERGY ) ), - max_energy_threshold( 100 ) -{ - action_list_str = "travel"; - action_list_str += "/felstorm"; - action_list_str += "/legion_strike,if=energy>=" + util::to_string( max_energy_threshold ); - - felstorm_cd = get_cooldown( "felstorm" ); - - owner_coeff.health = 0.75; - - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); -} - -void grimoire_felguard_pet_t::arise() -{ - warlock_pet_t::arise(); - - buffs.grimoire_of_service->trigger(); -} - -void grimoire_felguard_pet_t::demise() -{ - warlock_pet_t::demise(); - - if ( o()->talents.fiendish_oblation.ok() ) - o()->buffs.demonic_core->trigger(); -} - - // TODO: Grimoire: Felguard only does a single Felstorm at most, rendering some of this unnecessary -timespan_t grimoire_felguard_pet_t::available() const -{ - double energy_threshold = max_energy_threshold; - double time_to_felstorm = ( felstorm_cd->ready - sim->current_time() ).total_seconds(); - - if ( time_to_felstorm <= 0 ) - energy_threshold = min_energy_threshold; - - double deficit = resources.current[ RESOURCE_ENERGY ] - energy_threshold; - double time_to_threshold = 0; - - // Not enough energy, figure out how many milliseconds it'll take to get - if ( deficit < 0 ) - time_to_threshold = util::ceil( std::fabs( deficit ) / resource_regen_per_second( RESOURCE_ENERGY ), 3 ); - - // Fuzz regen by making the pet wait a bit extra if it's just below the resource threshold - if ( time_to_threshold < 0.001 ) - return warlock_pet_t::available(); - - // Next event is either going to be the time to felstorm, or the time to gain enough energy for a - // threshold value - double time_to_next_event = 0; - if ( time_to_felstorm <= 0 ) - time_to_next_event = time_to_threshold; - else - time_to_next_event = std::min( time_to_felstorm, time_to_threshold ); - - if ( sim->debug ) - { - sim->out_debug.print( "{} waiting, deficit={}, threshold={}, t_threshold={}, t_felstorm={} t_wait={}", name(), - deficit, energy_threshold, time_to_threshold, time_to_felstorm, time_to_next_event ); - } - - if ( time_to_next_event < 0.001 ) - return warlock_pet_t::available(); - else - return timespan_t::from_seconds( time_to_next_event ); -} - -void grimoire_felguard_pet_t::init_base_stats() -{ - warlock_pet_t::init_base_stats(); - - // Felguard is the only warlock pet type to use an actual weapon. - main_hand_weapon.type = WEAPON_AXE_2H; - melee_attack = new warlock_pet_melee_t( this ); - - // 2023-09-20: Validated coefficients. - owner_coeff.ap_from_sp = 0.9487; - - melee_attack->base_dd_multiplier *= 1.42; -} - -action_t* grimoire_felguard_pet_t::create_action( util::string_view name, util::string_view options_str ) -{ - if ( name == "legion_strike" ) - return new legion_strike_t( this, options_str, false ); - if ( name == "felstorm" ) - return new felstorm_t( this, options_str, false ); - - return warlock_pet_t::create_action( name, options_str ); -} - -/// Grimoire: Felguard End - /// Wild Imp Begin wild_imp_pet_t::wild_imp_pet_t( warlock_t* owner ) @@ -1006,46 +603,76 @@ wild_imp_pet_t::wild_imp_pet_t( warlock_t* owner ) { resource_regeneration = regen_type::DISABLED; owner_coeff.health = 0.15; - - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } struct fel_firebolt_t : public warlock_pet_spell_t { - fel_firebolt_t( warlock_pet_t* p ) : warlock_pet_spell_t( "fel_firebolt", p, p->find_spell( 104318 ) ) + fel_firebolt_t* twin = nullptr; + const bool is_twin; + + fel_firebolt_t( warlock_pet_t* p, bool _is_twin = false ) + : warlock_pet_spell_t( "fel_firebolt", p, p->find_spell( 104318 ) ), + is_twin( _is_twin ) { - repeating = true; + if ( !is_twin ) + { + repeating = true; + + if ( p->o()->talents.infernal_rapidity.ok() ) + twin = new fel_firebolt_t( p, true ); + } + else + { + background = dual = proc = true; + base_costs[ RESOURCE_ENERGY ] = 0; + } } void schedule_execute( action_state_t* execute_state ) override { // We may not be able to execute anything, so reset executing here before we are going to // schedule anything else. - player->executing = nullptr; + if ( !is_twin ) { + player->executing = nullptr; - if ( player->buffs.movement->check() || player->buffs.stunned->check() ) - return; + if ( player->buffs.movement->check() || player->buffs.stunned->check() ) + return; + } warlock_pet_spell_t::schedule_execute( execute_state ); } - void consume_resource() override + void execute() override { - warlock_pet_spell_t::consume_resource(); + // TODO: Check that this is working properly (AoE and ST) + aoe = 1 + ( p()->buffs.unstable_soul->check() ? as( p()->o()->talents.unstable_soul_buff->effectN( 2 ).base_value() ) : 0 ); - // Imp dies if it cannot cast - if ( player->resources.current[ RESOURCE_ENERGY ] < cost() ) - make_event( sim, 0_ms, [ this ]() { player->cast_pet()->dismiss(); } ); + warlock_pet_spell_t::execute(); + + if ( ( twin != nullptr ) && rng().roll( p()->o()->talents.infernal_rapidity->effectN( 1 ).percent() ) ) + { + p()->o()->procs.infernal_rapidity->occur(); + twin->execute_on_target( target ); + } } - double cost_pct_multiplier() const override + double composite_da_multiplier( const action_state_t* s ) const override { - double c = warlock_pet_spell_t::cost_pct_multiplier(); + double m = warlock_pet_spell_t::composite_da_multiplier( s ); - if ( p()->buffs.demonic_power->check() ) - c *= 1.0 + p()->o()->talents.demonic_power_buff->effectN( 4 ).percent(); + if ( is_twin ) + m *= p()->o()->talents.infernal_rapidity->effectN( 2 ).percent(); - return c; + return m; + } + + void consume_resource() override + { + warlock_pet_spell_t::consume_resource(); + + // Imp dies if it cannot cast + if ( player->resources.current[ RESOURCE_ENERGY ] < cost() ) + make_event( sim, 0_ms, [ this ]() { player->cast_pet()->dismiss(); } ); } }; @@ -1102,10 +729,13 @@ void wild_imp_pet_t::arise() imploded = false; o()->buffs.wild_imps->trigger(); - if ( o()->talents.imp_gang_boss.ok() && rng().roll( o()->talents.imp_gang_boss->effectN( 1 ).percent() ) ) - { - buffs.imp_gang_boss->trigger(); - o()->procs.imp_gang_boss->occur(); + if ( o()->talents.summon_demonic_tyrant.ok() ) + { + for ( auto t : o()->warlock_pet_list.demonic_tyrants ) + { + if ( t->is_active() ) + t->buffs.demonic_power->trigger(); + } } // Start casting fel firebolts @@ -1119,6 +749,15 @@ void wild_imp_pet_t::demise() { o()->buffs.wild_imps->decrement(); + if ( o()->talents.summon_demonic_tyrant.ok() ) + { + for ( auto t : o()->warlock_pet_list.demonic_tyrants ) + { + if ( t->is_active() ) + t->buffs.demonic_power->decrement(); + } + } + if ( !power_siphon ) { double core_chance = o()->talents.demonic_core_spell->effectN( 1 ).percent(); @@ -1137,9 +776,6 @@ void wild_imp_pet_t::demise() if ( expiration ) event_t::cancel( expiration ); - - if ( o()->talents.the_expendables.ok() ) - o()->expendables_trigger_helper( this ); } warlock_pet_t::demise(); @@ -1178,14 +814,34 @@ dreadstalker_t::dreadstalker_t( warlock_t* owner, util::string_view pet_name, pe action_list_str = "leap/travel/dreadbite"; resource_regeneration = regen_type::DISABLED; - // 2024-11-16: Coefficient updated - owner_coeff.ap_from_sp = 0.825; + // 2024-11-16: Coefficient updated + owner_coeff.ap_from_sp = 0.825; + + owner_coeff.health = 0.4; +} + +struct dreadbite_t : public warlock_pet_melee_attack_t +{ + struct blighted_maw_t : public warlock_pet_melee_attack_t // TODO: warlock_pet_melee_attack_t or warlock_pet_spell_t ? + { + blighted_maw_t( warlock_pet_t* p ) : warlock_pet_melee_attack_t( "Blighted Maw", p, p->o()->talents.blighted_maw_dmg ) + { + background = dual = true; + may_crit = false; + base_dd_min = base_dd_max = 0; + } + + void init_finished() override + { + warlock_pet_melee_attack_t::init_finished(); + + // TODO: It remains to be seen exactly which multipliers affect it + snapshot_flags &= STATE_NO_MULTIPLIER; + } + }; - owner_coeff.health = 0.4; -} + blighted_maw_t* blighted_maw; -struct dreadbite_t : public warlock_pet_melee_attack_t -{ dreadbite_t( warlock_pet_t* p ) : warlock_pet_melee_attack_t( "Dreadbite", p, p->find_spell( 205196 ) ) { weapon = &( p->main_hand_weapon ); @@ -1193,11 +849,14 @@ struct dreadbite_t : public warlock_pet_melee_attack_t if ( p->o()->talents.dreadlash.ok() ) { aoe = -1; - reduced_aoe_targets = 5; // TOCHECK regularly: 2025-08-27 This still applies in TWW + reduced_aoe_targets = 5; // TOCHECK regularly: 2025-08-27 This still applies in TWW/Midnight radius = 8.0; base_dd_multiplier *= 1.0 + p->o()->talents.dreadlash->effectN( 1 ).percent(); } + + blighted_maw = new blighted_maw_t( p ); + add_child( blighted_maw ); } bool ready() override @@ -1212,7 +871,6 @@ struct dreadbite_t : public warlock_pet_melee_attack_t { warlock_pet_melee_attack_t::execute(); - p()->buffs.spliced_4pc->expire(); debug_cast< dreadstalker_t* >( p() )->dreadbite_executes--; } @@ -1220,18 +878,14 @@ struct dreadbite_t : public warlock_pet_melee_attack_t { warlock_pet_melee_attack_t::impact( s ); - if ( p()->o()->talents.wicked_maw.ok() ) - owner_td( s->target )->debuffs.wicked_maw->trigger(); - } - - double composite_da_multiplier( const action_state_t* s ) const override - { - double m = warlock_pet_melee_attack_t::composite_da_multiplier( s ); - - if ( p()->buffs.spliced_4pc->check() ) - m *= p()->buffs.spliced_4pc->check_value(); + if ( p()->o()->talents.blighted_maw.ok() && result_is_hit( s->result ) ) + { + // TODO: It remains to be tested how exactly Blighted Maw's damage is calculated ingame + auto amount = s->result_raw; + amount *= p()->o()->talents.blighted_maw->effectN( 1 ).percent(); - return m; + blighted_maw->execute_on_target( s->target, amount ); + } } }; @@ -1338,6 +992,15 @@ void dreadstalker_t::arise() o()->buffs.dreadstalkers->trigger(); + if ( o()->talents.summon_demonic_tyrant.ok() ) + { + for ( auto t : o()->warlock_pet_list.demonic_tyrants ) + { + if ( t->is_active() ) + t->buffs.demonic_power->trigger(); + } + } + if ( o()->talents.flametouched.ok() ) buffs.ferocity_of_fharg->trigger(); @@ -1352,6 +1015,15 @@ void dreadstalker_t::demise() { o()->buffs.dreadstalkers->decrement(); + if ( o()->talents.summon_demonic_tyrant.ok() ) + { + for ( auto t : o()->warlock_pet_list.demonic_tyrants ) + { + if ( t->is_active() ) + t->buffs.demonic_power->decrement(); + } + } + if ( o()->talents.demoniac.ok() ) { bool success = o()->buffs.demonic_core->trigger( 1, buff_t::DEFAULT_VALUE(), o()->talents.demonic_core_spell->effectN( 2 ).percent() ); @@ -1363,21 +1035,11 @@ void dreadstalker_t::demise() warlock_pet_t::demise(); } -double dreadstalker_t::composite_player_multiplier( school_e school ) const -{ - double m = warlock_pet_t::composite_player_multiplier( school ); - - // TOCHECK: 2025-08-27 The Houndmasters Gambit talent cannot be applied by the second Vilefiend (bug?) - if ( o()->talents.the_houndmasters_gambit.ok() && ( bugs ? o()->buffs.vilefiend->check_value() : o()->buffs.vilefiend->check() ) ) - m *= 1.0 + o()->talents.houndmasters_aura->effectN( 1 ).percent(); - - return m; -} - double dreadstalker_t::composite_melee_crit_chance() const { double m = warlock_pet_t::composite_melee_crit_chance(); + // TODO: Could 'parse_effects' be used for this? if ( buffs.ferocity_of_fharg->check() ) m += buffs.ferocity_of_fharg->data().effectN( 2 ).percent(); @@ -1388,6 +1050,7 @@ double dreadstalker_t::composite_spell_crit_chance() const { double m = warlock_pet_t::composite_spell_crit_chance(); + // TODO: Could 'parse_effects' be used for this? if ( buffs.ferocity_of_fharg->check() ) m += buffs.ferocity_of_fharg->data().effectN( 2 ).percent(); @@ -1471,7 +1134,7 @@ struct bile_spit_t : public warlock_pet_spell_t return warlock_pet_spell_t::ready(); } - // NOTE: Bile Spit spell cast time is not affected by any haste effects + // NOTE: Bile Spit spell cast time is not affected by any haste effects // TODO: Is this still true in Midnight? double execute_time_pct_multiplier() const override { return 1.0; } @@ -1481,14 +1144,6 @@ struct bile_spit_t : public warlock_pet_spell_t debug_cast< vilefiend_t* >( p() )->bile_spit_executes--; } - - void impact( action_state_t* s ) override - { - warlock_pet_spell_t::impact( s ); - - if ( p()->o()->talents.foul_mouth.ok() ) - owner_td( s->target )->debuffs.wicked_maw->trigger(); - } }; struct headbutt_t : public warlock_pet_melee_attack_t @@ -1521,15 +1176,6 @@ struct infernal_presence_t : public warlock_pet_spell_t } }; -double vilefiend_t::composite_player_multiplier( school_e school ) const -{ - double m = warlock_simple_pet_t::composite_player_multiplier( school ); - - m *= 1.0 + o()->talents.foul_mouth->effectN( 1 ).percent(); - - return m; -} - void vilefiend_t::init_base_stats() { warlock_simple_pet_t::init_base_stats(); @@ -1564,6 +1210,8 @@ void vilefiend_t::arise() bile_spit_executes = 1; + o()->buffs.vilefiend->trigger(); + if ( o()->talents.mark_of_shatug.ok() ) mark_of_shatug->trigger(); @@ -1577,7 +1225,7 @@ void vilefiend_t::arise() void vilefiend_t::demise() { if ( !current.sleeping ) - o()->buffs.vilefiend->decrement( 1, 0.0 ); // Set value to 0.0 to prevent Houndmasters Gambit talent from being applied by the 2nd Vilefiend + o()->buffs.vilefiend->decrement(); warlock_simple_pet_t::demise(); } @@ -1600,9 +1248,10 @@ demonic_tyrant_t::demonic_tyrant_t( warlock_t* owner, util::string_view name ) : warlock_pet_t( owner, name, PET_DEMONIC_TYRANT, true ) { resource_regeneration = regen_type::DISABLED; - action_list_str += "/demonfire"; - - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); + if ( o()->talents.antoran_armaments.ok() ) + action_list_str = "leap/travel/burning_cleave"; + else + action_list_str = "demonfire"; } struct demonfire_t : public warlock_pet_spell_t @@ -1612,45 +1261,94 @@ struct demonfire_t : public warlock_pet_spell_t { parse_options( options_str ); } }; +struct burning_cleave_t : public warlock_pet_spell_t // TODO: warlock_pet_melee_attack_t or warlock_pet_spell_t ?? +{ + burning_cleave_t( warlock_pet_t* p, util::string_view options_str ) + : warlock_pet_spell_t( "Burning Cleave", p, p->find_spell( 1264093 ) ) + { + parse_options( options_str ); + + trigger_gcd = 1_s; // TODO: Are these values correct? + min_gcd = 0_s; // TODO: Are these values correct? + + weapon = &( p->main_hand_weapon ); + + aoe = -1; + reduced_aoe_targets = as( data().effectN( 2 ).base_value() ); + } + +}; + +struct demonic_tyrant_leap_t : warlock_pet_t::travel_t +{ + demonic_tyrant_leap_t( demonic_tyrant_t* p ) : demonic_tyrant_leap_t::travel_t( p, "leap" ) + { + speed = 32.0; + } + + bool ready() override + { + // Demonic Tyrants will not do a leap if are summoned too close to the target. In addition, the leap can only occur once. + return ( ( !debug_cast( player )->melee_on_summon ) && ( debug_cast( player )->leap_executes > 0 ) && ( warlock_pet_t::travel_t::ready() ) ); + } + + void execute() override + { + warlock_pet_t::travel_t::execute(); + + // TODO: I don't think this is necessary because this pet doesn't have a normal melee attack, but test it + // // There is an observed delay of up to 1 second before a melee attack begins again for pets after a movement action like the leap (possibly server tick?) + // make_event( sim, rng().range( 0_s, 1_s ), [ this ]{ + // debug_cast( player )->melee_attack->reset(); + // debug_cast( player )->melee_attack->schedule_execute(); + // } ); + + debug_cast( player )->leap_executes--; + } +}; + action_t* demonic_tyrant_t::create_action( util::string_view name, util::string_view options_str ) { if ( name == "demonfire" ) return new demonfire_t( this, options_str ); + if ( name == "burning_cleave" ) + return new burning_cleave_t( this, options_str ); + if ( name == "leap" ) + return new demonic_tyrant_leap_t( this ); return warlock_pet_t::create_action( name, options_str ); } +void demonic_tyrant_t::arise() +{ + // TODO: I don't think this is necessary because this pet doesn't have a normal melee attack, but test it + // if ( o()->get_player_distance( *target ) <= 5.0 ) + // { + // melee_on_summon = true; // Within this range, Demonic Tyrant will not does a leap, so it immediately starts using auto attacks + // } + + warlock_pet_t::arise(); + + leap_executes = 1; +} + /// Demonic Tyrant End /// Doomguard Begin -struct doom_bolt_t : public warlock_pet_spell_t +struct doom_bolt_volley_t : public warlock_pet_spell_t { - doom_bolt_t( warlock_pet_t* p ) - : warlock_pet_spell_t( "Doom Bolt", p, p->o()->talents.doom_bolt ) - { } - - double cost_pct_multiplier() const override - { return 0.0; } - - void execute() override + doom_bolt_volley_t( warlock_pet_t* p ) + : warlock_pet_spell_t( "Doom Bolt Volley", p, p->o()->talents.doom_bolt_volley ) { - if ( debug_cast( p() )->doom_bolt_executes <= 0 ) - { - make_event( sim, 0_ms, [ this ]() { player->cast_pet()->dismiss(); } ); - return; - } - - warlock_pet_spell_t::execute(); - - debug_cast( p() )->doom_bolt_executes--; + radius = p->o()->talents.doom_bolt_volley->effectN( 2 ).base_value(); } }; doomguard_t::doomguard_t( warlock_t* owner ) - : warlock_simple_pet_t( owner, "Doomguard", PET_DOOMGUARD ) + : warlock_simple_pet_t( owner, "doomguard", PET_DOOMGUARD ) { - action_list_str = "travel/doom_bolt"; + action_list_str = "doom_bolt_volley"; owner_coeff.ap_from_sp = 1.0; owner_coeff.sp_from_sp = 1.0; @@ -1660,15 +1358,13 @@ void doomguard_t::init_base_stats() { warlock_simple_pet_t::init_base_stats(); - doom_bolt_executes = as( o()->talents.pact_of_the_eredruin->effectN( 1 ).base_value() ); - - special_ability = new doom_bolt_t( this ); + special_ability = new doom_bolt_volley_t( this ); } action_t* doomguard_t::create_action( util::string_view name, util::string_view options_str ) { - if ( name == "doom_bolt" ) - return new doom_bolt_t( this ); + if ( name == "doom_bolt_volley" ) + return new doom_bolt_volley_t( this ); return warlock_simple_pet_t::create_action( name, options_str ); } @@ -1677,73 +1373,122 @@ void doomguard_t::arise() { warlock_simple_pet_t::arise(); - doom_bolt_executes = as( o()->talents.pact_of_the_eredruin->effectN( 1 ).base_value() ); + o()->buffs.doomguard->trigger(); +} + +void doomguard_t::demise() +{ + warlock_simple_pet_t::demise(); + + if ( !current.sleeping ) + o()->buffs.doomguard->decrement(); } /// Doomguard End -/// Greater Dreadstalker Begin +/// Grimoire: Imp Lord Begin -greater_dreadstalker_t::greater_dreadstalker_t( warlock_t* owner ) - : dreadstalker_t( owner, "greater_dreadstalker", PET_FELHUNTER ) +struct greater_felbolt_t : public warlock_pet_spell_t { - action_list_str = "leap/travel/dreadbite"; + greater_felbolt_t( warlock_pet_t* p, util::string_view options_str ) + : warlock_pet_spell_t( "Greater Felbolt", p, p->find_spell( 1277116 ) ) + { parse_options( options_str ); } +}; - owner_coeff.ap_from_sp = 0.825; - owner_coeff.health = 0.4 * o()->tier.demonic_hunger->effectN( 2 ).percent(); +grimoire_imp_lord_t::grimoire_imp_lord_t( warlock_t* owner ) + : warlock_pet_t( owner, "grimoire_imp_lord", PET_DOOMGUARD ) +{ + action_list_str = "greater_felbolt"; +} - melee_on_summon = false; // Greater Dreadstalkers leap from the player location to target, which has a non-negligible travel time - server_action_delay = 0_ms; // Will be set when spawning Greater Dreadstalkers +void grimoire_imp_lord_t::init_base_stats() +{ + warlock_pet_t::init_base_stats(); + + // TODO: Check coeficients + owner_coeff.ap_from_sp = 1.0; + owner_coeff.sp_from_sp = 1.0; } -void greater_dreadstalker_t::arise() +action_t* grimoire_imp_lord_t::create_action( util::string_view name, util::string_view options_str ) { - if ( o()->get_player_distance( *target ) <= 5.0 ) - { - melee_on_summon = true; // Within this range, Greater Dreadstalkers will not do a leap, so they immediately start using auto attacks - } + if ( name == "greater_felbolt" ) + return new greater_felbolt_t( this, options_str ); + return warlock_pet_t::create_action( name, options_str ); +} + +void grimoire_imp_lord_t::arise() +{ warlock_pet_t::arise(); - vilefiend_present_on_summon = bugs ? o()->buffs.vilefiend->check_value() : o()->buffs.vilefiend->check(); + o()->buffs.grimoire_imp_lord->trigger(); +} - dreadbite_executes = 1; - leap_executes = 1; +void grimoire_imp_lord_t::demise() +{ + warlock_pet_t::demise(); - buffs.demonic_hunger->trigger(); + if ( !current.sleeping ) + o()->buffs.grimoire_imp_lord->decrement(); +} - if ( o()->talents.flametouched.ok() ) - buffs.ferocity_of_fharg->trigger(); +/// Grimoire: Imp Lord End + +/// Grimoire: Fel Ravager Begin + +struct abyssal_bite_t : public warlock_pet_spell_t +{ + abyssal_bite_t( warlock_pet_t* p, util::string_view options_str ) // TODO: warlock_pet_melee_attack_t or warlock_pet_spell_t ? + : warlock_pet_spell_t( "Abyssal Bite", p, p->find_spell( 1277117 ) ) + { parse_options( options_str ); } + +}; + +grimoire_fel_ravager_t::grimoire_fel_ravager_t( warlock_t* owner ) + : warlock_pet_t( owner, "grimoire_fel_ravager", PET_DOOMGUARD ) +{ + action_list_str = "travel/abyssal_bite"; } -void greater_dreadstalker_t::demise() +void grimoire_fel_ravager_t::init_base_stats() { - if ( !current.sleeping && o()->talents.demoniac.ok() ) - { - bool success = o()->buffs.demonic_core->trigger( 1, buff_t::DEFAULT_VALUE(), o()->talents.demonic_core_spell->effectN( 2 ).percent() ); - if ( success ) - o()->procs.demonic_core_big_dogs->occur(); - } + warlock_pet_t::init_base_stats(); - warlock_pet_t::demise(); + // TODO: Coefficients need to be calculated; these have simply been copied from Felhunter + owner_coeff.ap_from_sp = 0.575; + owner_coeff.sp_from_sp = 1.15; + + melee_attack = new warlock_pet_melee_t( this ); + //special_action = new base::spell_lock_t( this, "" ); } -double greater_dreadstalker_t::composite_player_multiplier( school_e school ) const +action_t* grimoire_fel_ravager_t::create_action( util::string_view name, util::string_view options_str ) { - double m = warlock_pet_t::composite_player_multiplier( school ); + if ( name == "abyssal_bite" ) + return new abyssal_bite_t( this, options_str ); + if ( name == "spell_lock" ) + return new base::spell_lock_t( this, options_str ); + + return warlock_pet_t::create_action( name, options_str ); +} - // 2025-08-27: Houndmasters Gambit talent is only applied to Greater Dreadstalkers if Vilefiend is present on summon - // Unlike normal Dreadstalkers, summoning Vilefiend when Greater Dreadstalkers are present does not apply the Houndmasters Gambit talent (maybe a bug?) - // TOCHECK: 2025-08-27 The Houndmasters Gambit talent cannot be applied by the second Vilefiend (bug?) - if ( ( vilefiend_present_on_summon || !bugs ) && o()->talents.the_houndmasters_gambit.ok() && ( bugs ? o()->buffs.vilefiend->check_value() : o()->buffs.vilefiend->check() ) ) - m *= 1.0 + o()->talents.houndmasters_aura->effectN( 1 ).percent(); +void grimoire_fel_ravager_t::arise() +{ + warlock_pet_t::arise(); - m *= buffs.demonic_hunger->check_value(); + o()->buffs.grimoire_fel_ravager->trigger(); +} - return m; +void grimoire_fel_ravager_t::demise() +{ + warlock_pet_t::demise(); + + if ( !current.sleeping ) + o()->buffs.grimoire_fel_ravager->decrement(); } -/// Greater Dreadstalker End +/// Grimoire: Fel Ravager End } // namespace demonology @@ -1761,8 +1506,6 @@ infernal_t::infernal_t( warlock_t* owner, util::string_view name ) owner_coeff.ap_from_sp = 2.2275; owner_coeff.sp_from_sp = 2.2275; - - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } struct immolation_tick_t : public warlock_pet_spell_t @@ -1849,51 +1592,18 @@ infernal_roc_t::infernal_roc_t( warlock_t* owner, util::string_view name ) : des type = RAIN; owner_coeff.ap_from_sp = 1.5; owner_coeff.sp_from_sp = 1.5; - - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } /// Infernal Rain of Chaos End -/// +/// /// Dimensional Rifts Begin -struct dimensional_cinder_t : public warlock_pet_spell_t -{ - dimensional_cinder_t( warlock_pet_t* p ) - : warlock_pet_spell_t( "Dimensional Cinder", p, p->o()->talents.dimensional_cinder ) - { - background = dual = true; - may_crit = false; - aoe = -1; - - base_dd_min = base_dd_max = 0; - } - - void init_finished() override - { - warlock_pet_spell_t::init_finished(); - - snapshot_flags &= ~STATE_MUL_PET; - snapshot_flags &= ~STATE_TGT_MUL_PET; - snapshot_flags &= ~STATE_VERSATILITY; - } - - double action_multiplier() const override - { - double m = warlock_pet_spell_t::action_multiplier(); - - m *= p()->o()->talents.unstable_rifts->effectN( 1 ).percent(); - - return m; - } -}; - shadowy_tear_t::shadowy_tear_t( warlock_t* owner, util::string_view name ) : warlock_pet_t( owner, name, PET_WARLOCK_RANDOM, true ) { resource_regeneration = regen_type::DISABLED; - action_list_str = "Shadow Barrage"; + action_list_str = "shadow_barrage"; } struct rift_shadow_bolt_t : public warlock_pet_spell_t @@ -1903,14 +1613,6 @@ struct rift_shadow_bolt_t : public warlock_pet_spell_t { background = dual = true; } - - void impact( action_state_t* s ) override - { - warlock_pet_spell_t::impact( s ); - - if ( p()->o()->talents.unstable_rifts.ok() ) - debug_cast( p() )->cinder->execute_on_target( s->target, s->result_amount ); - } }; struct shadow_barrage_t : public warlock_pet_spell_t @@ -1919,12 +1621,6 @@ struct shadow_barrage_t : public warlock_pet_spell_t : warlock_pet_spell_t( "Shadow Barrage", p, p->o()->talents.shadow_barrage ) { tick_action = new rift_shadow_bolt_t( p ); - - if ( p->o()->talents.unstable_rifts.ok() ) - { - debug_cast( p )->cinder = new dimensional_cinder_t( p ); - add_child( debug_cast( p )->cinder ); - } } bool ready() override @@ -1974,15 +1670,7 @@ struct chaos_barrage_tick_t : public warlock_pet_spell_t chaos_barrage_tick_t( warlock_pet_t* p ) : warlock_pet_spell_t( "Chaos Barrage (tick)", p, p->o()->talents.chaos_barrage_tick ) { - background = dual = true; - } - - void impact( action_state_t* s ) override - { - warlock_pet_spell_t::impact( s ); - - if ( p()->o()->talents.unstable_rifts.ok() ) - debug_cast( p() )->cinder->execute_on_target( s->target, s->result_amount ); + background = dual = true; } }; @@ -1992,12 +1680,6 @@ struct chaos_barrage_t : public warlock_pet_spell_t : warlock_pet_spell_t( "Chaos Barrage", p, p->o()->talents.chaos_barrage ) { tick_action = new chaos_barrage_tick_t( p ); - - if ( p->o()->talents.unstable_rifts.ok() ) - { - debug_cast( p )->cinder = new dimensional_cinder_t( p ); - add_child( debug_cast( p )->cinder ); - } } bool ready() override @@ -2043,13 +1725,7 @@ struct rift_chaos_bolt_t : public warlock_pet_spell_t { rift_chaos_bolt_t( warlock_pet_t* p ) : warlock_pet_spell_t( "Chaos Bolt", p, p->o()->talents.rift_chaos_bolt ) - { - if ( p->o()->talents.unstable_rifts.ok() ) - { - debug_cast( p )->cinder = new dimensional_cinder_t( p ); - add_child( debug_cast( p )->cinder ); - } - } + { } double composite_crit_chance() const override { return 1.0; } @@ -2069,14 +1745,6 @@ struct rift_chaos_bolt_t : public warlock_pet_spell_t debug_cast( p() )->bolts--; } - void impact( action_state_t* s ) override - { - warlock_pet_spell_t::impact( s ); - - if ( p()->o()->talents.unstable_rifts.ok() ) - debug_cast( p() )->cinder->execute_on_target( s->target, s->result_amount ); - } - double calculate_direct_amount( action_state_t* s ) const override { warlock_pet_spell_t::calculate_direct_amount( s ); @@ -2119,7 +1787,7 @@ struct overfiend_chaos_bolt_t : public warlock_pet_spell_t overfiend_chaos_bolt_t( warlock_pet_t* p ) : warlock_pet_spell_t( "Chaos Bolt", p, p->o()->talents.overfiend_cb ) { - spell_power_mod.direct = p->o()->warlock_base.chaos_bolt->effectN( 1 ).sp_coeff(); + spell_power_mod.direct = p->o()->talents.chaos_bolt->effectN( 1 ).sp_coeff(); base_dd_multiplier *= p->o()->talents.avatar_of_destruction->effectN( 1 ).percent(); } @@ -2173,20 +1841,22 @@ darkglare_t::darkglare_t( warlock_t* owner, util::string_view name ) : warlock_pet_t( owner, name, PET_DARKGLARE, true ) { action_list_str += "eye_beam"; - - affected_by.demonic_inspiration = o()->talents.demonic_inspiration.ok(); } struct eye_beam_t : public warlock_pet_spell_t { eye_beam_t( warlock_pet_t* p ) : warlock_pet_spell_t( "Eye Beam", p, p->o()->talents.eye_beam ) - { } + { + // TODO: Test if Nether Plating works properly in AoE + if ( p->o()->talents.nether_plating.ok() ) + aoe = 1 + as( p->o()->talents.nether_plating->effectN( 1 ).base_value() ); + } double composite_target_multiplier( player_t* target ) const override { double m = warlock_pet_spell_t::composite_target_multiplier( target ); - double dots = p()->o()->get_target_data( target )->count_affliction_dots( !p()->o()->bugs ); + double dots = p()->o()->get_target_data( target )->count_affliction_dots(); double dot_multiplier = p()->o()->talents.summon_darkglare->effectN( 3 ).percent(); @@ -2243,6 +1913,7 @@ namespace diabolist debug_cast( p() )->cleaves--; } + // TODO: Is this still true in Midnight? // NOTE: Devastation talent (+5% crit) does affect Wicked Cleave spell from Overlord // NOTE: Overlord Wicked Cleave crits does not benefit from other crit dmg bonus multipliers (bug?) double composite_crit_damage_bonus_multiplier() const override @@ -2274,17 +1945,6 @@ namespace diabolist if ( p()->o()->hero.cloven_souls.ok() ) owner_td( s->target )->debuffs.cloven_soul->trigger(); } - - double composite_target_multiplier( player_t* target ) const override - { - double m = spell_t::composite_target_multiplier( target ); // skip warlock_pet_spell_t::composite_target_multiplier - - // TOCHECK: 2025-07-27 Wicked Cleave spell from Overlord does not benefit from Shadowtouched talent even though its damage school is Shadowflame (bug?) - if ( !p()->bugs && p()->o()->talents.shadowtouched.ok() && dbc::has_common_school( spell_t::get_school(), SCHOOL_SHADOW ) && owner_td( target )->debuffs.wicked_maw->check() ) - m *= 1.0 + p()->o()->talents.wicked_maw_debuff->effectN( 2 ).percent(); - - return m; - } }; void overlord_t::arise() @@ -2393,8 +2053,6 @@ namespace diabolist struct felseeker_tick_t : public warlock_pet_spell_t { - const double shadowtouched_value = 0.25; - felseeker_tick_t( warlock_pet_t* p ) : warlock_pet_spell_t( "Felseeker (tick)", p, p->o()->hero.felseeker_dmg ) { @@ -2404,17 +2062,6 @@ namespace diabolist base_costs[ RESOURCE_ENERGY ] = 0.0; } - double composite_target_multiplier( player_t* target ) const override - { - double m = spell_t::composite_target_multiplier( target ); // skip warlock_pet_spell_t::composite_target_multiplier - - // TOCHECK: 2025-07-27 Despite what is listed in spell data, Shadowtouched increases the damage of Feelseeker spell from Pit Lord by 25% instead of 20% (bug?) - if ( owner_td( target )->debuffs.wicked_maw->check() ) - m *= 1.0 + ( p()->bugs ? shadowtouched_value : p()->o()->talents.wicked_maw_debuff->effectN( 2 ).percent() ); - - return m; - } - double composite_da_multiplier( const action_state_t* s ) const override { double m = warlock_pet_spell_t::composite_da_multiplier( s ); // base value @@ -2494,8 +2141,6 @@ namespace diabolist type = FRAG; owner_coeff.ap_from_sp = 1.5 * owner->hero.abyssal_dominion->effectN( 4 ).percent(); owner_coeff.sp_from_sp = 1.5 * owner->hero.abyssal_dominion->effectN( 4 ).percent(); - - affected_by.demonic_inspiration = false; } /// Infernal Fragment End @@ -2553,31 +2198,8 @@ namespace diabolist namespace soul_harvester { /// Rampaging Demonic Soul Begin -struct rampaging_demonic_soul_shard_event_t : public event_t -{ - rampaging_demonic_soul_shard_event_t( rampaging_demonic_soul_t* pet, timespan_t delay ) - : event_t( *pet->sim, delay ), pet( pet ) - { - } - - void execute() override - { - pet->o()->resource_gain( RESOURCE_SOUL_SHARD, pet->summon_spell->effectN( 2 ).base_value() / 10.0, - pet->o()->gains.rampaging_demonic_soul ); - - if ( !pet->is_sleeping() ) - { - make_event( *pet->sim, pet, pet->summon_spell->effectN( 2 ).period() ); - } - } - - rampaging_demonic_soul_t* pet; -}; - struct soul_swipe_base_t : public warlock_pet_spell_t { - const double shadowtouched_value = 1.30 / 1.10; - soul_swipe_base_t( std::string_view n, warlock_pet_t* p, const spell_data_t* s ) : warlock_pet_spell_t( n, p, s ) { } @@ -2586,27 +2208,11 @@ struct soul_swipe_base_t : public warlock_pet_spell_t { double m = warlock_pet_spell_t::composite_da_multiplier( s ); + // TODO: Is this bug still happening with new Soul Swipe in Midnight? // Not in whitelist but appears to scale, likely a bug. if ( p()->o()->bugs ) m *= 1.0 + p()->o()->hero.wicked_reaping->effectN( 1 ).percent(); - if ( p()->o()->affliction() ) - { - // TOCHECK: 2025-09-23 Oddly a scripted dummy effect, needs to be rechecked regularly to ensure it actually works. - m *= 1.0 + p()->o()->tier.inquisitor_sh_2pc->effectN( 2 ).percent(); - } - - return m; - } - - double composite_target_multiplier( player_t* target ) const override - { - double m = spell_t::composite_target_multiplier( target ); // skip warlock_pet_spell_t::composite_target_multiplier - - // TOCHECK: 2025-09-23 Despite what is listed in spell data, Shadowtouched increases the damage of Soul Swipe spell from Rampaging Demonic Soul by 18.18% (1.30/1.10) instead of 20% (bug?) - if ( owner_td( target )->debuffs.wicked_maw->check() ) - m *= 1.0 + ( p()->bugs ? shadowtouched_value : p()->o()->talents.wicked_maw_debuff->effectN( 2 ).percent() ); - return m; } }; @@ -2614,7 +2220,7 @@ struct soul_swipe_base_t : public warlock_pet_spell_t struct soul_swipe_aoe_t : public soul_swipe_base_t { soul_swipe_aoe_t( warlock_pet_t* p, std::string_view n = "soul_swipe_aoe" ) - : soul_swipe_base_t( n, p, p->find_spell( 1239714 ) ) + : soul_swipe_base_t( n, p, p->find_spell( 1269049 ) ) { spell_power_mod.direct = data().effectN( 2 ).sp_coeff(); aoe = -1; @@ -2625,7 +2231,7 @@ struct soul_swipe_aoe_t : public soul_swipe_base_t struct soul_swipe_t : public soul_swipe_base_t { - soul_swipe_t( warlock_pet_t* p, std::string_view n ) : soul_swipe_base_t( n, p, p->find_spell( 1239714 ) ) + soul_swipe_t( warlock_pet_t* p, std::string_view n ) : soul_swipe_base_t( n, p, p->find_spell( 1269049 ) ) { // Actually just an auto attack with a 1s swing time. Simplifying the code doing it this way. trigger_gcd = 1_s; @@ -2644,14 +2250,12 @@ rampaging_demonic_soul_t::rampaging_demonic_soul_t( warlock_t* owner, std::strin resource_regeneration = regen_type::DISABLED; action_list_str = "soul_swipe"; owner_coeff.sp_from_sp = 1.0; - summon_spell = owner->find_spell( 1239689 ); // Rampaging Demonic Soul + summon_spell = owner->find_spell( 1269042 ); // Rampaging Demonic Soul } void rampaging_demonic_soul_t::arise() { warlock_pet_t::arise(); - if ( o()->active_4pc() ) - make_event( *sim, this, summon_spell->effectN( 2 ).period() ); } action_t* rampaging_demonic_soul_t::create_action( util::string_view name, util::string_view options_str ) diff --git a/engine/class_modules/warlock/sc_warlock_pets.hpp b/engine/class_modules/warlock/sc_warlock_pets.hpp index 82fe45f0228..584bf80cde0 100644 --- a/engine/class_modules/warlock/sc_warlock_pets.hpp +++ b/engine/class_modules/warlock/sc_warlock_pets.hpp @@ -71,27 +71,13 @@ struct warlock_pet_t : public pet_t struct buffs_t { - propagate_const demonic_inspiration; // Hidden buff from talent that gives haste to some demons propagate_const embers; // Infernal Shard Generation - propagate_const demonic_strength; // Talent that buffs Felguard - propagate_const grimoire_of_service; // Buff used by Grimoire: Felguard talent - propagate_const annihilan_training; // Permanent aura when talented, 10% increased damage to all abilities - propagate_const dread_calling; propagate_const imp_gang_boss; // Aura applied to some Wild Imps for increased damage (and size) - propagate_const antoran_armaments; // Permanent aura when talented, 20% increased damage to all abilities plus Soul Strike cleave + propagate_const unstable_soul; propagate_const ferocity_of_fharg; - propagate_const the_expendables; propagate_const demonic_power; - propagate_const empowered_legion_strike; // TWW1 Demonology 4pc buff - propagate_const demonic_hunger; // TWW2 Demonology 2pc buff - propagate_const spliced_4pc; // TWW2 Demonology 4pc dummy buff } buffs; - struct affected_by_t - { - bool demonic_inspiration = false; - } affected_by; - bool is_main_pet = false; bool melee_on_summon = true; // Set this to false for a pet to prevent t=0 melees. You MUST schedule a new auto attack manually elsewhere in the implementation if this is disabled @@ -101,10 +87,10 @@ struct warlock_pet_t : public pet_t void create_buffs() override; void schedule_ready( timespan_t = 0_ms, bool = false ) override; double composite_player_multiplier( school_e ) const override; - double composite_spell_haste() const override; double composite_melee_haste() const override; - double composite_spell_cast_speed() const override; double composite_melee_auto_attack_speed() const override; + double composite_melee_crit_chance() const override; + double composite_spell_crit_chance() const override; void arise() override; void demise() override; @@ -280,16 +266,6 @@ struct warlock_pet_action_t : public parse_action_effects_t const warlock_pet_td_t* pet_td( player_t* t ) const { return p()->get_target_data( t ); } - - double composite_target_multiplier( player_t* target ) const override - { - double m = ab::composite_target_multiplier( target ); - - if ( p()->o()->talents.shadowtouched.ok() && dbc::has_common_school( ab::get_school(), SCHOOL_SHADOW ) && owner_td( target )->debuffs.wicked_maw->check() ) - m *= 1.0 + p()->o()->talents.wicked_maw_debuff->effectN( 2 ).percent(); - - return m; - } }; // TODO: Switch to a general autoattack template if one is added @@ -418,11 +394,7 @@ namespace demonology { struct felguard_pet_t : public warlock_pet_t { - action_t* soul_strike; - action_t* hatred_proc; cooldown_t* felstorm_cd; - cooldown_t* dstr_cd; - int demonic_strength_executes; // Energy thresholds to wake felguard up for something to do, minimum is the felstorm energy cost, // and maximum is a predetermined empirical value from in game @@ -433,30 +405,6 @@ struct felguard_pet_t : public warlock_pet_t void init_base_stats() override; action_t* create_action( util::string_view, util::string_view ) override; timespan_t available() const override; - void arise() override; - double composite_player_multiplier( school_e ) const override; - double composite_melee_crit_chance() const override; - double composite_spell_crit_chance() const override; - - void queue_ds_felstorm(); -}; - -struct grimoire_felguard_pet_t : public warlock_pet_t -{ - const spell_data_t* felstorm_spell; - cooldown_t* felstorm_cd; - - // Energy thresholds to wake felguard up for something to do, minimum is the felstorm energy cost, - // and maximum is a predetermined empirical value from in game - double min_energy_threshold; - double max_energy_threshold; - - grimoire_felguard_pet_t( warlock_t* ); - void init_base_stats() override; - action_t* create_action( util::string_view, util::string_view ) override; - timespan_t available() const override; - void arise() override; - void demise() override; }; struct wild_imp_pet_t : public warlock_pet_t @@ -491,7 +439,6 @@ struct dreadstalker_t : public warlock_pet_t void demise() override; timespan_t available() const override; action_t* create_action( util::string_view, util::string_view ) override; - double composite_player_multiplier( school_e ) const override; double composite_melee_crit_chance() const override; double composite_spell_crit_chance() const override; void queue_dreadbite(); @@ -510,33 +457,42 @@ struct vilefiend_t : public warlock_simple_pet_t void arise() override; void demise() override; action_t* create_action( util::string_view, util::string_view ) override; - double composite_player_multiplier( school_e ) const override; }; struct demonic_tyrant_t : public warlock_pet_t { + int leap_executes; + demonic_tyrant_t( warlock_t*, util::string_view = "demonic_tyrant" ); action_t* create_action( util::string_view, util::string_view ) override; + void arise() override; }; struct doomguard_t : public warlock_simple_pet_t { - int doom_bolt_executes; - doomguard_t( warlock_t* ); void init_base_stats() override; action_t* create_action( util::string_view, util::string_view ) override; void arise() override; + void demise() override; }; -struct greater_dreadstalker_t : public dreadstalker_t +struct grimoire_imp_lord_t : public warlock_pet_t // TODO: warlock_simple_pet_t or warlock_pet_t ? { - bool vilefiend_present_on_summon; + grimoire_imp_lord_t( warlock_t* ); + void init_base_stats() override; + action_t* create_action( util::string_view, util::string_view ) override; + void arise() override; + void demise() override; +}; - greater_dreadstalker_t( warlock_t* ); +struct grimoire_fel_ravager_t : public warlock_pet_t // TODO: warlock_simple_pet_t or warlock_pet_t ? +{ + grimoire_fel_ravager_t( warlock_t* ); + void init_base_stats() override; + action_t* create_action( util::string_view, util::string_view ) override; void arise() override; void demise() override; - double composite_player_multiplier( school_e ) const override; }; } // namespace demonology