diff --git a/engine/action/action.cpp b/engine/action/action.cpp index db614fc398a..5007ded46eb 100644 --- a/engine/action/action.cpp +++ b/engine/action/action.cpp @@ -353,6 +353,7 @@ action_t::action_t( action_e ty, util::string_view token, player_t* p, const spe target_callbacks( true ), suppress_caster_procs(), suppress_target_procs(), + suppress_callback_from_energize(), enable_proc_from_suppressed(), allow_class_ability_procs(), not_a_proc(), @@ -497,6 +498,7 @@ action_t::action_t( action_e ty, util::string_view token, player_t* p, const spe pre_execute_state(), snapshot_flags(), update_flags( STATE_TGT_MUL_DA | STATE_TGT_MUL_TA | STATE_TGT_CRIT ), + energize_state( std::make_unique( this, p ) ), target_cache(), options(), state_cache(), @@ -5143,7 +5145,16 @@ player_t* action_t::get_expression_target() double action_t::gain_energize_resource( resource_e resource_type, double amount, gain_t* g ) { - return player->resource_gain( resource_type, amount, g, this ); + auto ret = player->resource_gain( resource_type, amount, g, this ); + + // energize_power can trigger generic helpful proc effects + if ( callbacks && caster_callbacks && ( !suppress_caster_procs || enable_proc_from_suppressed ) && + !suppress_callback_from_energize ) + { + player->trigger_callbacks( PROC1_NONE_HELPFUL, PROC2_HIT, this, energize_state.get() ); + } + + return ret; } bool action_t::usable_during_current_cast() const diff --git a/engine/action/action.hpp b/engine/action/action.hpp index f6b5e57ffe7..7e1abc27509 100644 --- a/engine/action/action.hpp +++ b/engine/action/action.hpp @@ -121,6 +121,9 @@ struct action_t : private noncopyable /// if true, does not trigger callbacks on caster/target. bool suppress_caster_procs, suppress_target_procs; + /// engerize_power effects can trigger generic helpful procs. if true, disable this behavior for the action. + bool suppress_callback_from_energize; + /// can trigger callbacks on caster even if suppress_caster_proc is true, as long as the callback has can_proc_from_suppressed = true. /// TODO: determine if equivalent for suppressed target procs is needed. bool enable_proc_from_suppressed; @@ -594,7 +597,6 @@ struct action_t : private noncopyable std::unique_ptr line_cooldown; const action_priority_t* signature; - /// State of the last execute() action_state_t* execute_state; @@ -605,6 +607,9 @@ struct action_t : private noncopyable unsigned update_flags; + /// placeholder dummy to use for energize effects that trigger generic helpful procs + std::unique_ptr energize_state; + /** * Target Cache System * - list: contains the cached target pointers diff --git a/engine/action/action_state.cpp b/engine/action/action_state.cpp index 3b3c7297f0e..e807e85c32e 100644 --- a/engine/action/action_state.cpp +++ b/engine/action/action_state.cpp @@ -272,7 +272,7 @@ void travel_event_t::execute() void action_state_t::release( action_state_t*& s ) { - assert( s ); + assert( s && !s->is_energize() ); s->action->release_state( s ); s = nullptr; } @@ -404,3 +404,8 @@ proc_types2 action_state_t::interrupt_proc_type2() const else return PROC2_INVALID; } + +bool action_state_t::is_energize() const +{ + return this == action->energize_state.get(); +} diff --git a/engine/action/action_state.hpp b/engine/action/action_state.hpp index 276a1cbe066..e0db2569ffb 100644 --- a/engine/action/action_state.hpp +++ b/engine/action/action_state.hpp @@ -140,6 +140,9 @@ struct action_state_t : private noncopyable virtual proc_types2 cast_proc_type2() const; virtual proc_types2 interrupt_proc_type2() const; + + // checks for special states + bool is_energize() const; }; struct travel_event_t : public event_t diff --git a/engine/action/dbc_proc_callback.cpp b/engine/action/dbc_proc_callback.cpp index 7bc985e721d..6b92c2bf4e6 100644 --- a/engine/action/dbc_proc_callback.cpp +++ b/engine/action/dbc_proc_callback.cpp @@ -32,10 +32,22 @@ struct proc_event_t : public event_t proc_event_t( dbc_proc_callback_t* c, action_t* a, action_state_t* s ) : event_t( *c->listener->sim ), cb( c ), - source_action( a ), - // Note, state has to be cloned as it's about to get recycled back into the action state cache - source_state( s ? s->action->get_state( s ) : nullptr ) + source_action( a ) { + // Unless the state is an energize state, it has to be cloned as it's about to get recycled back into the action + // state cache + if ( s ) + { + if ( s->is_energize() ) + source_state = s; + else + source_state = s->action->get_state( s ); + } + else + { + source_state = nullptr; + } + schedule( timespan_t::zero() ); #ifndef NDEBUG if ( !cb ) @@ -57,7 +69,8 @@ struct proc_event_t : public event_t ~proc_event_t() override { - if ( source_state ) + // DON'T RELEASE ENERGIZE STATES + if ( source_state && !source_state->is_energize() ) action_state_t::release( source_state ); } @@ -255,7 +268,7 @@ void dbc_proc_callback_t::trigger( action_t* a, action_state_t* state ) if ( triggered ) { - assert( state && state -> action); + assert( state && state->action ); // Detach proc execution from proc triggering make_event( *listener->sim, this, a, state ); @@ -435,9 +448,6 @@ player_t* dbc_proc_callback_t::target( const action_state_t* state, action_t* pr return state->target; } - // TODO: Verify this behaviour with damage to friendly Allies. - bool self_hit = state->action->player == listener; - // Incoming callbacks target either the callback actor, or the source of the incoming state. // Which is selected depends on the type of the callback proc action. // @@ -446,13 +456,15 @@ player_t* dbc_proc_callback_t::target( const action_state_t* state, action_t* pr assert( _action && "Cannot determine target of incoming callback, there is no proc_action" ); switch ( _action->type ) { - // Heals are always targeted to the callback actor on incoming events - case ACTION_ABSORB: - case ACTION_HEAL: - return listener; - // Self Damage targets are redirected to the players main target. Else they target the player. + case ACTION_ATTACK: + case ACTION_SPELL: + // Self Damage and energize targets are redirected to the players main target. Else they target the player. + // TODO: Verify this behaviour with damage to friendly Allies. + if ( state->action->player == listener ) + return listener->target; + SC_FALLTHROUGH; default: - return self_hit ? state->action->player->target : state->action->player; + return listener; } } diff --git a/engine/player/unique_gear_dragonflight.cpp b/engine/player/unique_gear_dragonflight.cpp index db3e18a222a..dbf4a7c4f1e 100644 --- a/engine/player/unique_gear_dragonflight.cpp +++ b/engine/player/unique_gear_dragonflight.cpp @@ -1501,10 +1501,6 @@ void emerald_coachs_whistle( special_effect_t& effect ) effect.custom_buff = buff; - // self driver procs off druid hostile abilities as well as shadow hostile abilities - if ( effect.player->type == player_e::DRUID || effect.player->specialization() == PRIEST_SHADOW || effect.player->type == EVOKER ) - effect.proc_flags_ |= PF_MAGIC_SPELL | PF_MELEE_ABILITY; - new dbc_proc_callback_t( effect.player, effect ); // Pretend we are our bonded partner for the sake of procs from partner diff --git a/engine/player/unique_gear_midnight.cpp b/engine/player/unique_gear_midnight.cpp index 9ab3a807357..bdda460bab5 100644 --- a/engine/player/unique_gear_midnight.cpp +++ b/engine/player/unique_gear_midnight.cpp @@ -702,8 +702,8 @@ void blessed_pango_charm( special_effect_t& effect ) // 1259130 heal void primal_spore_binding( special_effect_t& effect ) { - effect.player->sim->error( UNVERIFIED_VALUE, - "Primal Spore Binding: Damage has only been verified to the tooltip and not to actual in-game damage." ); + effect.player->sim->error( UNVERIFIED_IMPLEMENTATION, + "Primal Spore Binding: What determines if you get a damage proc vs healing proc is unknown." ); auto damage_amount = effect.driver()->effectN( 1 ).average( effect ); auto heal_amount = effect.driver()->effectN( 2 ).average( effect );