From 5f071c9ef9f9ef76de5d39d44fd7f5e44f7751fd Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Sun, 12 Jan 2025 05:15:51 -0700 Subject: [PATCH] [monk] Fix Power of the Thunder King and Emperor's Last Capacitor (#9848) [monk] - Delay Last Capacitor expiration so it occurs after all final ticks. - Implement generic delay callback event. - Remove PotTK logic from SEF, convert the aoe channel into a sef action. - Merge SEF action container into a single map. - Index map based off of `sef_action_e` instead of horrible int casting. - Remove actor-based action targeting from base monk pet and into sef specifically. - If `source_action->background`, obey current `source_action` target, disregarding sef actor target/fixation. - Use `source_action` `composite_dot_duration` and `tick_time` so CJL has sensible channel times and tick separation, to avoid buffs being removed too early and channels being absolute nonsense. - Override CJL AoE `source_action` lookup behaviour. - Skip automatic `source_action` lookup if already set. - Update SEF canceling behaviour to be consistent with SCK changes. - Simplify codepath in several SEF helper functions. - Set array entries instead of override function. Remove unnecessary `SEF_MAX` entry. --- engine/class_modules/monk/sc_monk.cpp | 28 +-- engine/class_modules/monk/sc_monk.hpp | 40 ++-- engine/class_modules/monk/sc_monk_pets.cpp | 216 +++++++++------------ 3 files changed, 129 insertions(+), 155 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index fdbc04e0f37..311609df314 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -60,7 +60,7 @@ template template monk_action_t::monk_action_t( Args &&...args ) : parse_action_effects_t( std::forward( args )... ), - sef_ability( actions::sef_ability_e::SEF_NONE ), + sef_ability( actions::sef_ability_e::SEF_MIN ), ww_mastery( false ), may_combo_strike( false ), trigger_jadefire_stomp( false ), @@ -3352,6 +3352,7 @@ struct crackling_jade_lightning_t : public monk_spell_t dual = background = true; ww_mastery = true; trigger_jadefire_stomp = true; + sef_ability = actions::sef_ability_e::SEF_CRACKLING_JADE_LIGHTNING_AOE; parse_effects( p->talent.windwalker.power_of_the_thunder_king, effect_mask_t( true ).disable( 1 ) ); parse_effects( p->buff.the_emperors_capacitor ); @@ -3415,18 +3416,21 @@ struct crackling_jade_lightning_t : public monk_spell_t { monk_spell_t::last_tick( dot ); - p()->buff.the_emperors_capacitor->expire(); - if ( p()->talent.windwalker.power_of_the_thunder_king->ok() ) - { - const auto &tl = target_list(); - for ( const auto &t : tl ) - { - get_td( t )->dot.crackling_jade_lightning_aoe->cancel(); - get_td( t )->dot.crackling_jade_lightning_sef->cancel(); - get_td( t )->dot.crackling_jade_lightning_sef_aoe->cancel(); - } - } + // delay expiration so it occurs after final tick of cjl aoe + make_event( *sim, p(), 1_ms, [ & ]() { + p()->buff.the_emperors_capacitor->expire(); + const auto &tl = target_list(); + for ( const auto &t : tl ) + { + get_td( t )->dot.crackling_jade_lightning_aoe->cancel(); + get_td( t )->dot.crackling_jade_lightning_sef->cancel(); + get_td( t )->dot.crackling_jade_lightning_sef_aoe->cancel(); + } + } ); + else + p()->buff.the_emperors_capacitor->expire(); + // Reset swing timer if ( player->main_hand_attack ) { diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 9e3cf9f3c04..1d243baafdd 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -55,8 +55,7 @@ namespace actions { enum class sef_ability_e { - SEF_NONE = -1, - // Attacks begin here + SEF_MIN = -1, SEF_TIGER_PALM, SEF_BLACKOUT_KICK, SEF_BLACKOUT_KICK_TOTM, @@ -69,19 +68,9 @@ enum class sef_ability_e SEF_STRIKE_OF_THE_WINDLORD_OH, SEF_CELESTIAL_CONDUIT, SEF_RJW_TICK, - SEF_ATTACK_MAX, - // Attacks end here - - // Spells begin here SEF_CHI_WAVE, SEF_CRACKLING_JADE_LIGHTNING, - SEF_SPELL_MAX, - // Spells end here - - // Misc - SEF_SPELL_MIN = SEF_CHI_WAVE, - SEF_ATTACK_MIN = SEF_TIGER_PALM, - SEF_MAX + SEF_CRACKLING_JADE_LIGHTNING_AOE }; template @@ -336,11 +325,6 @@ struct aspect_of_harmony_t }; } // namespace buffs -inline int sef_spell_index( int x ) -{ - return x - static_cast( actions::sef_ability_e::SEF_SPELL_MIN ); -} - struct monk_td_t : public actor_target_data_t { public: @@ -1506,6 +1490,26 @@ struct delayed_execute_event_t : event_t action->execute_on_target( target ); } }; + +struct delayed_cb_event_t : event_t +{ + std::function cb; + + delayed_cb_event_t( monk_t *player, timespan_t delay, std::function cb ) + : event_t( *player->sim, delay ), cb( std::move( cb ) ) + { + } + + const char *name() const override + { + return "delayed_cb_event_t"; + } + + void execute() override + { + cb(); + } +}; } // namespace events } // namespace monk diff --git a/engine/class_modules/monk/sc_monk_pets.cpp b/engine/class_modules/monk/sc_monk_pets.cpp index 70820ddc65e..d84c025d0e2 100644 --- a/engine/class_modules/monk/sc_monk_pets.cpp +++ b/engine/class_modules/monk/sc_monk_pets.cpp @@ -53,9 +53,12 @@ struct pet_action_base_t : public BASE : BASE( n, p, data ), merge_report( true ) { // No costs are needed either - this->base_costs[ RESOURCE_ENERGY ] = 0; - this->base_costs[ RESOURCE_CHI ] = 0; - this->base_costs[ RESOURCE_MANA ] = 0; + super_t::base_costs[ RESOURCE_ENERGY ] = 0; + super_t::base_costs[ RESOURCE_CHI ] = 0; + super_t::base_costs[ RESOURCE_MANA ] = 0; + super_t::base_costs_per_tick[ RESOURCE_ENERGY ] = 0; + super_t::base_costs_per_tick[ RESOURCE_CHI ] = 0; + super_t::base_costs_per_tick[ RESOURCE_MANA ] = 0; } void init() override @@ -94,13 +97,6 @@ struct pet_action_base_t : public BASE return debug_cast( this->player ); } - void execute() override - { - this->target = this->player->target; - - super_t::execute(); - } - void impact( action_state_t *s ) override { super_t::impact( s ); @@ -393,14 +389,13 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t // spell-data driven ability with 1:1 mapping of name/spell id will // always be chosen as the source action. In some cases this needs to be // overridden (see sef_zen_sphere_t for example). - for ( const action_t *a : this->o()->action_list ) - { - if ( ( this->id > 0 && this->id == a->id ) || util::str_compare_ci( this->name_str, a->name_str ) ) - { - source_action = a; - break; - } - } + if ( !source_action ) + for ( const action_t *a : this->o()->action_list ) + if ( ( this->id > 0 && this->id == a->id ) || util::str_compare_ci( this->name_str, a->name_str ) ) + { + source_action = a; + break; + } if ( source_action ) { @@ -476,6 +471,18 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t state->target_armor = source_action->composite_target_armor( state->target ); } + // added so cjl channel time is sane, mults are correct and extra ticks don't get added + timespan_t composite_dot_duration( const action_state_t *state ) const override + { + return source_action->composite_dot_duration( state ); + } + + // added so cjl channel time is sane, mults are correct and extra ticks don't get added + timespan_t tick_time( const action_state_t *state ) const override + { + return source_action->tick_time( state ); + } + double total_crit_bonus( const action_state_t *state ) const override { return source_action->total_crit_bonus( state ); @@ -485,6 +492,14 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t { return source_action->travel_time(); } + + void execute() override + { + if ( !source_action->background ) + super_t::target = super_t::player->target; + + super_t::execute(); + } }; struct sef_melee_attack_t : public sef_action_base_t @@ -1020,56 +1035,26 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t struct sef_crackling_jade_lightning_aoe_t : public sef_spell_t { sef_crackling_jade_lightning_aoe_t( storm_earth_and_fire_pet_t *player ) - : sef_spell_t( "crackling_jade_lightning_sef_aoe", player, player->o()->baseline.monk.crackling_jade_lightning ) + : sef_spell_t( "crackling_jade_lightning_aoe", player, player->o()->baseline.monk.crackling_jade_lightning ) { dual = background = true; } - double cost_per_tick( resource_e ) const override + void init() override { - return 0.0; + // has same id as standard cjl, so we need to look up the correct action + // independently of auto action resolution + source_action = o()->find_action( "crackling_jade_lightning_aoe" ); + + sef_spell_t::init(); } }; - sef_crackling_jade_lightning_aoe_t *aoe_dot; - sef_crackling_jade_lightning_t( storm_earth_and_fire_pet_t *player ) - : sef_spell_t( "crackling_jade_lightning_sef", player, player->o()->baseline.monk.crackling_jade_lightning ), - aoe_dot( new sef_crackling_jade_lightning_aoe_t( player ) ) + : sef_spell_t( "crackling_jade_lightning", player, player->o()->baseline.monk.crackling_jade_lightning ) { interrupt_auto_attack = true; channeled = true; - - add_child( aoe_dot ); - } - - double cost_per_tick( resource_e ) const override - { - return 0; - } - - void execute() override - { - sef_spell_t::execute(); - - if ( p()->o()->talent.windwalker.power_of_the_thunder_king->ok() ) - { - const auto &tl = target_list(); - double count = 0; - - for ( auto &t : tl ) - { - // Don't apply AoE version to primary target - if ( t == target ) - continue; - - if ( count < p()->o()->talent.windwalker.power_of_the_thunder_king->effectN( 1 ).base_value() ) - { - aoe_dot->execute_on_target( t ); - count++; - } - } - } } }; @@ -1103,19 +1088,13 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t // Storm, Earth, and Fire abilities end =================================== - std::vector attacks; - std::vector spells; + std::map actions; public: - // SEF applies the Cyclone Strike debuff as well - bool sticky_target; // When enabled, SEF pets will stick to the target they have storm_earth_and_fire_pet_t( util::string_view name, monk_t *owner, bool dual_wield, weapon_e weapon_type ) - : monk_pet_t( owner, name, PET_NONE, true, true ), - attacks( (int)actions::sef_ability_e::SEF_ATTACK_MAX ), - spells( (int)actions::sef_ability_e::SEF_SPELL_MAX - (int)actions::sef_ability_e::SEF_SPELL_MIN ), - sticky_target( false ) + : monk_pet_t( owner, name, PET_NONE, true, true ), actions(), sticky_target( false ) { // Storm, Earth, and Fire pets have to become "Windwalkers", so we can get // around some sanity checks in the action execution code, that prevents @@ -1156,23 +1135,35 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t { monk_pet_t::init_spells(); - attacks.at( (int)actions::sef_ability_e::SEF_TIGER_PALM ) = new sef_tiger_palm_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_BLACKOUT_KICK ) = new sef_blackout_kick_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_BLACKOUT_KICK_TOTM ) = new sef_blackout_kick_totm_proc_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_RISING_SUN_KICK ) = new sef_rising_sun_kick_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_GLORY_OF_THE_DAWN ) = new sef_glory_of_the_dawn_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_FISTS_OF_FURY ) = new sef_fists_of_fury_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_SPINNING_CRANE_KICK ) = new sef_spinning_crane_kick_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_WHIRLING_DRAGON_PUNCH ) = new sef_whirling_dragon_punch_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD ) = new sef_strike_of_the_windlord_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD_OH ) = - new sef_strike_of_the_windlord_oh_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_CELESTIAL_CONDUIT ) = new sef_celestial_conduit_t( this ); - attacks.at( (int)actions::sef_ability_e::SEF_RJW_TICK ) = new sef_rushing_jade_wind_tick_t( this ); - - spells.at( sef_spell_index( (int)actions::sef_ability_e::SEF_CHI_WAVE ) ) = new sef_chi_wave_damage_t( this ); - spells.at( sef_spell_index( (int)actions::sef_ability_e::SEF_CRACKLING_JADE_LIGHTNING ) ) = - new sef_crackling_jade_lightning_t( this ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_TIGER_PALM, new sef_tiger_palm_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_TIGER_PALM, new sef_tiger_palm_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_BLACKOUT_KICK, new sef_blackout_kick_t( this ) ) ); + actions.emplace( + std::make_pair( actions::sef_ability_e::SEF_BLACKOUT_KICK_TOTM, new sef_blackout_kick_totm_proc_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_RISING_SUN_KICK, new sef_rising_sun_kick_t( this ) ) ); + actions.emplace( + std::make_pair( actions::sef_ability_e::SEF_GLORY_OF_THE_DAWN, new sef_glory_of_the_dawn_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_FISTS_OF_FURY, new sef_fists_of_fury_t( this ) ) ); + actions.emplace( + std::make_pair( actions::sef_ability_e::SEF_SPINNING_CRANE_KICK, new sef_spinning_crane_kick_t( this ) ) ); + actions.emplace( + std::make_pair( actions::sef_ability_e::SEF_WHIRLING_DRAGON_PUNCH, new sef_whirling_dragon_punch_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD, + new sef_strike_of_the_windlord_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD_OH, + new sef_strike_of_the_windlord_oh_t( this ) ) ); + actions.emplace( + std::make_pair( actions::sef_ability_e::SEF_CELESTIAL_CONDUIT, new sef_celestial_conduit_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_RJW_TICK, new sef_rushing_jade_wind_tick_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_CHI_WAVE, new sef_chi_wave_damage_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_CRACKLING_JADE_LIGHTNING, + new sef_crackling_jade_lightning_t( this ) ) ); + actions.emplace( std::make_pair( actions::sef_ability_e::SEF_CRACKLING_JADE_LIGHTNING_AOE, + new sef_crackling_jade_lightning_t::sef_crackling_jade_lightning_aoe_t( this ) ) ); + + if ( o()->talent.windwalker.power_of_the_thunder_king->ok() ) + actions[ actions::sef_ability_e::SEF_CRACKLING_JADE_LIGHTNING ]->add_child( + actions[ actions::sef_ability_e::SEF_CRACKLING_JADE_LIGHTNING_AOE ] ); } void init_action_list() override @@ -1208,23 +1199,15 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t void trigger_attack( actions::sef_ability_e ability, const action_t *source_action, bool combo_strike = false ) { - if ( channeling && source_action->background == false ) + if ( channeling && !source_action->background ) channeling->cancel(); - if ( (int)ability >= (int)actions::sef_ability_e::SEF_SPELL_MIN ) - { - auto spell_index = sef_spell_index( (int)ability ); - assert( spells[ spell_index ] ); + action_t *action = actions[ ability ]; - spells[ spell_index ]->source_action = source_action; - spells[ spell_index ]->execute(); - } - else - { - assert( attacks[ (int)ability ] ); - attacks[ (int)ability ]->source_action = source_action; - attacks[ (int)ability ]->execute(); - } + if ( source_action->background ) + action->set_target( source_action->target ); + + action->execute(); if ( combo_strike ) trigger_combo_strikes(); @@ -1684,10 +1667,11 @@ void monk_t::trigger_storm_earth_and_fire( const action_t *a, actions::sef_abili if ( specialization() != MONK_WINDWALKER ) return; - if ( !talent.windwalker.storm_earth_and_fire->ok() ) + // if action has no SEF counterpart... + if ( !( sef_ability > actions::sef_ability_e::SEF_MIN ) ) return; - if ( sef_ability == actions::sef_ability_e::SEF_NONE ) + if ( !talent.windwalker.storm_earth_and_fire->ok() ) return; if ( !buff.storm_earth_and_fire->up() ) @@ -1709,37 +1693,25 @@ void monk_t::trigger_storm_earth_and_fire( const action_t *a, actions::sef_abili void monk_t::storm_earth_and_fire_fixate( player_t *target ) { - sim->print_debug( "{} storm_earth_and_fire sticky target {} to {} (old={})", *this, - *pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ], *target, - *pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->target ); - - pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->target = target; - pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->sticky_target = true; - - sim->print_debug( "{} storm_earth_and_fire sticky target {} to {} (old={})", *this, - *pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ], *target, - *pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ]->target ); + auto fixate = [ & ]( pets::sef_pet_e pet ) { + sim->print_debug( "{} storm_earth_and_fire sticky target {} to {} (old={})", *this, *pets.sef[ (int)pet ], *target, + *pets.sef[ (int)pet ]->target ); + pets.sef[ (int)pet ]->target = target; + pets.sef[ (int)pet ]->sticky_target = true; + }; - pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ]->target = target; - pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ]->sticky_target = true; + fixate( pets::sef_pet_e::SEF_EARTH ); + fixate( pets::sef_pet_e::SEF_FIRE ); } bool monk_t::storm_earth_and_fire_fixate_ready( player_t *target ) { if ( buff.storm_earth_and_fire->check() ) - { if ( pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->sticky_target || pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ]->sticky_target ) - { - if ( pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->target != target ) - return true; - else if ( pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ]->target != target ) + if ( pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->target != target || + pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ]->target != target ) return true; - } - else if ( !pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->sticky_target || - !pets.sef[ (int)pets::sef_pet_e::SEF_FIRE ]->sticky_target ) - return true; - } return false; } @@ -1767,9 +1739,7 @@ void monk_t::summon_storm_earth_and_fire( timespan_t duration ) void monk_t::retarget_storm_earth_and_fire_pets() const { if ( pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ]->sticky_target ) - { return; - } auto targets = create_storm_earth_and_fire_target_list(); retarget_storm_earth_and_fire( pets.sef[ (int)pets::sef_pet_e::SEF_EARTH ], targets ); @@ -1782,9 +1752,7 @@ void sef_despawn_cb_t::operator()( player_t * ) { // No pets up, don't do anything if ( !monk->buff.storm_earth_and_fire->check() ) - { return; - } auto targets = monk->create_storm_earth_and_fire_target_list(); @@ -1811,9 +1779,7 @@ void sef_despawn_cb_t::operator()( player_t * ) player_t *monk_t::storm_earth_and_fire_fixate_target( pets::sef_pet_e sef_pet ) { if ( pets.sef[ (int)sef_pet ]->sticky_target ) - { return pets.sef[ (int)sef_pet ]->target; - } return nullptr; }