diff --git a/engine/action/sc_action.cpp b/engine/action/sc_action.cpp index 8555416353a..1adcb29454d 100644 --- a/engine/action/sc_action.cpp +++ b/engine/action/sc_action.cpp @@ -267,20 +267,6 @@ struct action_execute_event_t : public player_event_t } }; -struct aoe_target_list_callback_t -{ - action_t* action; - - aoe_target_list_callback_t( action_t* a ) : - action( a ) {} - - void operator()(player_t*) - { - // Invalidate target cache - action -> target_cache.is_valid = false; - } -}; - struct power_entry_without_aura { bool operator()( const spellpower_data_t* p ) @@ -437,6 +423,7 @@ action_t::action_t( action_e ty, dynamic_tick_action( true), // WoD updates everything on tick by default. If you need snapshotted values for a periodic effect, use persistent multipliers. interrupt_immediate_occurred(), hit_any_target(), + dynamic_aoe( false ), dot_behavior( DOT_REFRESH ), ability_lag(), ability_lag_stddev(), @@ -2170,8 +2157,6 @@ void action_t::init() sim -> out_debug.printf( "%s - radius %.1f - range - %.1f", name(), radius, range ); #endif - init_target_cache(); - consume_per_tick_ = range::find_if( base_costs_per_tick, []( const double& d ) { return d != 0; } ) != base_costs_per_tick.end(); // Setup default target in init @@ -2248,11 +2233,6 @@ bool action_t::init_finished() return ret; } -void action_t::init_target_cache() -{ - sim -> target_non_sleeping_list.register_callback( aoe_target_list_callback_t( this ) ); -} - // action_t::reset ========================================================== void action_t::reset() @@ -3661,3 +3641,16 @@ void action_t::acquire_target( retarget_event_e /* event */, target_cache.is_valid = false; } } + +void action_t::activate() +{ + // On AOE actions, enable target cache regeneration when state of the enemy targets change. Also + // explicitly enable the target cache invalidator if the ability is flagged as being dynamic aoe + // (changes the number of targets hit dynamically during combat) + if ( n_targets() != 0 || dynamic_aoe ) + { + sim -> target_non_sleeping_list.register_callback( [ this ]( player_t* ) { + target_cache.is_valid = false; + } ); + } +} diff --git a/engine/action/sc_spell.cpp b/engine/action/sc_spell.cpp index fec3a3bc9a8..3796a8e6b81 100644 --- a/engine/action/sc_spell.cpp +++ b/engine/action/sc_spell.cpp @@ -5,22 +5,6 @@ #include "simulationcraft.hpp" -namespace { - -struct aoe_player_list_callback_t -{ - action_t* action; - aoe_player_list_callback_t( action_t* a ) : action( a ) {} - - void operator()(player_t*) - { - // Invalidate target cache - action -> target_cache.is_valid = false; - } -}; - -} // unnamed namespace - // ========================================================================== // Spell Base // ========================================================================== @@ -242,10 +226,14 @@ heal_t::heal_t( const std::string& token, } } -void heal_t::init_target_cache() +void heal_t::activate() { - if ( aoe ) - sim -> player_non_sleeping_list.register_callback( aoe_player_list_callback_t( this ) ); + if ( n_targets() != 0 || dynamic_aoe ) + { + sim -> player_non_sleeping_list.register_callback( [ this ]( player_t* ) { + target_cache.is_valid = false; + } ); + } } // heal_t::parse_effect_data ================================================ @@ -634,10 +622,14 @@ absorb_t::absorb_t( const std::string& token, stats -> type = STATS_ABSORB; } -void absorb_t::init_target_cache() +void absorb_t::activate() { - if ( aoe ) - sim -> player_non_sleeping_list.register_callback( aoe_player_list_callback_t( this ) ); + if ( n_targets() != 0 || dynamic_aoe ) + { + sim -> player_non_sleeping_list.register_callback( [ this ]( player_t* ) { + target_cache.is_valid = false; + } ); + } } // absorb_t::execute ======================================================== diff --git a/engine/class_modules/sc_death_knight.cpp b/engine/class_modules/sc_death_knight.cpp index 3781ac68b4f..7bd2c9a9692 100644 --- a/engine/class_modules/sc_death_knight.cpp +++ b/engine/class_modules/sc_death_knight.cpp @@ -5347,6 +5347,8 @@ struct scourge_strike_base_t : public death_knight_melee_attack_t // // TODO: Changed in 7.1.5, new probability distribution unknown/untested instructors_chance = { { .3, .4, .2, .1 } }; + + dynamic_aoe = true; // Dynamically becomes AOE if you stand in Defile/DnD } int n_targets() const override diff --git a/engine/class_modules/sc_demon_hunter.cpp b/engine/class_modules/sc_demon_hunter.cpp index 08ed262cd65..1a2b1580a0b 100644 --- a/engine/class_modules/sc_demon_hunter.cpp +++ b/engine/class_modules/sc_demon_hunter.cpp @@ -551,6 +551,7 @@ class demon_hunter_t : public player_t void invalidate_cache( cache_e ) override; resource_e primary_resource() const override; role_e primary_role() const override; + void activate() override; // custom demon_hunter_t init functions private: @@ -5576,14 +5577,21 @@ bool demon_hunter_t::create_actions() annihilation_dmg = new damage_calc_helper_t( annihilation_attacks ); } + return ca; +} + +// demon_hunter_t::activate ================================================= + +void demon_hunter_t::activate() +{ + player_t::activate(); + if ( blade_dance_dmg || death_sweep_dmg || chaos_strike_dmg || annihilation_dmg ) { sim -> target_non_sleeping_list.register_callback( damage_calc_invalidate_callback_t( this ) ); } - - return ca; } // demon_hunter_t::create_buffs ============================================= diff --git a/engine/class_modules/sc_druid.cpp b/engine/class_modules/sc_druid.cpp index d19eebe42cf..d98dc7fb0a1 100644 --- a/engine/class_modules/sc_druid.cpp +++ b/engine/class_modules/sc_druid.cpp @@ -739,10 +739,10 @@ struct druid_t : public player_t starshards( 0.0 ), predator_rppm_rate( 0.0 ), initial_astral_power( 0 ), - t20_2pc(false), - t20_4pc(false), initial_moon_stage( NEW_MOON ), ahhhhh_the_great_outdoors( true ), + t20_2pc(false), + t20_4pc(false), active( active_actions_t() ), pet_fey_moonwing(), force_of_nature(), @@ -3308,6 +3308,17 @@ struct maim_t : public cat_attack_t weapon_multiplier = data().effectN( 3 ).pp_combo_points() / 100.0; } + void init() override + { + cat_attack_t::init(); + + // Fiery Red Maimers legendary allows maim to dynamically change the number of targets. Note, + // needs to be done in action_t::init override, since special effects (including the fallback + // buff) are initialized after create_actions() (which is the method that calls maim_t + // constructor if the user has a Maim action in the APL). + dynamic_aoe = p() -> buff.fiery_red_maimers -> default_chance != 0; + } + int n_targets() const override { int n = cat_attack_t::n_targets(); @@ -3900,6 +3911,8 @@ struct mangle_t : public bear_attack_t } base_multiplier *= 1.0 + player -> artifact.vicious_bites.percent(); + + dynamic_aoe = true; // Incarnation increases number of targets dynamically } double composite_energize_amount( const action_state_t* s ) const override @@ -4669,6 +4682,8 @@ struct wild_growth_t : public druid_heal_t druid_heal_t( "wild_growth", p, p -> find_class_spell( "Wild Growth" ), options_str ) { ignore_false_positive = true; + + dynamic_aoe = true; // Incarnation increases number of targets dynamically } int n_targets() const override diff --git a/engine/class_modules/sc_mage.cpp b/engine/class_modules/sc_mage.cpp index 4665202feb8..2cef4c1779b 100755 --- a/engine/class_modules/sc_mage.cpp +++ b/engine/class_modules/sc_mage.cpp @@ -630,6 +630,7 @@ struct mage_t : public player_t virtual void arise() override; virtual action_t* select_action( const action_priority_list_t& ) override; virtual void copy_from( player_t* ) override; + void activate() override; target_specific_t target_data; @@ -8337,39 +8338,46 @@ void mage_t::init_benefits() } } -// mage_t::init_stats ========================================================= +// mage_t::activate =========================================================== -void mage_t::init_stats() +void mage_t::activate() { - player_t::init_stats(); - - // Cache Icy Veins haste multiplier for performance reasons - double haste = buffs.icy_veins -> data().effectN( 1 ).percent(); - iv_haste = 1.0 / ( 1.0 + haste ); + player_t::activate(); // Register target reset callback here (anywhere later on than in // constructor) so older GCCs are happy // Forcibly reset mage's current target, if it dies. - sim->target_non_sleeping_list.register_callback( [this]( player_t* ) { + sim -> target_non_sleeping_list.register_callback( [ this ]( player_t* ) { // If the mage's current target is still alive, bail out early. if ( range::find( sim->target_non_sleeping_list, current_target ) != - sim->target_non_sleeping_list.end() ) + sim -> target_non_sleeping_list.end() ) { return; } - if ( sim->debug ) + if ( sim -> debug ) { sim->out_debug.printf( "%s current target %s died. Resetting target to %s.", name(), - current_target->name(), target->name() ); + current_target -> name(), target -> name() ); } current_target = target; } ); } +// mage_t::init_stats ========================================================= + +void mage_t::init_stats() +{ + player_t::init_stats(); + + // Cache Icy Veins haste multiplier for performance reasons + double haste = buffs.icy_veins -> data().effectN( 1 ).percent(); + iv_haste = 1.0 / ( 1.0 + haste ); +} + // mage_t::init_assessors ===================================================== diff --git a/engine/class_modules/sc_monk.cpp b/engine/class_modules/sc_monk.cpp index de4a1a9dbea..6ee5b093bc4 100644 --- a/engine/class_modules/sc_monk.cpp +++ b/engine/class_modules/sc_monk.cpp @@ -794,6 +794,7 @@ struct monk_t: public player_t virtual void assess_heal( school_e, dmg_e, action_state_t* s) override; virtual void invalidate_cache( cache_e ) override; virtual void init_action_list() override; + void activate() override; virtual bool has_t18_class_trinket() const override; virtual expr_t* create_expression( action_t* a, const std::string& name_str ) override; virtual monk_td_t* get_target_data( player_t* target ) const override @@ -7710,8 +7711,6 @@ void monk_t::create_pets() if ( specialization() == MONK_WINDWALKER && find_action( "storm_earth_and_fire" ) ) { - sim -> target_non_sleeping_list.register_callback( actions::sef_despawn_cb_t( this ) ); - pet.sef[ SEF_FIRE ] = new pets::storm_earth_and_fire_pet_t( "fire_spirit", sim, this, true ); // The player BECOMES the Storm Spirit // SEF EARTH was changed from 2-handed user to dual welding in Legion @@ -7719,6 +7718,18 @@ void monk_t::create_pets() } } +// monk_t::activate ========================================================= + +void monk_t::activate() +{ + player_t::activate(); + + if ( specialization() == MONK_WINDWALKER && find_action( "storm_earth_and_fire" ) ) + { + sim -> target_non_sleeping_list.register_callback( actions::sef_despawn_cb_t( this ) ); + } +} + // monk_t::init_spells ====================================================== void monk_t::init_spells() diff --git a/engine/class_modules/sc_paladin.cpp b/engine/class_modules/sc_paladin.cpp index 89a60d118a9..61905d31465 100644 --- a/engine/class_modules/sc_paladin.cpp +++ b/engine/class_modules/sc_paladin.cpp @@ -3102,9 +3102,11 @@ struct zeal_t : public holy_power_generator_t // TODO: figure out wtf happened to this spell data hasted_cd = hasted_gcd = true; + + dynamic_aoe = true; // Zeal buff changes the number of targets dynamically } - virtual int n_targets() const override + int n_targets() const override { if ( p() -> buffs.zeal -> stack() ) return 1 + p() -> buffs.zeal -> stack(); diff --git a/engine/class_modules/sc_warlock.cpp b/engine/class_modules/sc_warlock.cpp index f13d739f365..3693bba8995 100644 --- a/engine/class_modules/sc_warlock.cpp +++ b/engine/class_modules/sc_warlock.cpp @@ -2465,6 +2465,12 @@ struct warlock_spell_t: public spell_t } } + // Havoc makes single-target spells potentially AOE + if ( aoe == 0 && p() -> specialization() == WARLOCK_DESTRUCTION ) + { + dynamic_aoe = true; + } + return spell_t::init_finished(); } diff --git a/engine/class_modules/sc_warrior.cpp b/engine/class_modules/sc_warrior.cpp index 2d1f94f73b6..93301e01941 100644 --- a/engine/class_modules/sc_warrior.cpp +++ b/engine/class_modules/sc_warrior.cpp @@ -536,7 +536,7 @@ struct warrior_t: public player_t void init_action_list() override; action_t* create_action( const std::string& name, const std::string& options ) override; - bool create_actions() override; + void activate() override; resource_e primary_resource() const override { return RESOURCE_RAGE; } role_e primary_role() const override; stat_e convert_hybrid_stat( stat_e s ) const override; @@ -1659,6 +1659,8 @@ struct bloodthirst_t: public warrior_attack_t } base_aoe_multiplier = p -> spec.whirlwind -> effectN( 1 ).trigger() -> effectN( 3 ).percent(); oathblood_chance = p -> artifact.oathblood.percent(); + + dynamic_aoe = true; // Meat Cleaver buff makes Bloodthirst AOE } double action_multiplier() const override @@ -1676,7 +1678,7 @@ struct bloodthirst_t: public warrior_attack_t { return aoe_targets + 1; } - return 1; + return warrior_attack_t::n_targets(); } double composite_target_crit_chance( player_t* target ) const override @@ -3122,6 +3124,8 @@ struct rampage_attack_t: public warrior_attack_t base_aoe_multiplier = p -> spec.whirlwind -> effectN( 1 ).trigger() -> effectN( 3 ).percent(); if ( p -> spec.rampage -> effectN( 3 ).trigger() == rampage ) first_attack = true; + + dynamic_aoe = true; // Meat Cleaver buff makes Rampage AOE } void execute() override @@ -3146,7 +3150,7 @@ struct rampage_attack_t: public warrior_attack_t { return aoe_targets + 1; } - return 1; + return warrior_attack_t::n_targets(); } void odyns_champion( timespan_t ) override { // Only procs odyns champion once from the spell being initially cast. @@ -5960,15 +5964,14 @@ struct into_the_fray_callback_t // warrior_t::create_actions ================================================ -bool warrior_t::create_actions() +void warrior_t::activate() { - bool ca = player_t::create_actions(); + player_t::activate(); if ( talents.into_the_fray -> ok() ) { sim -> target_non_sleeping_list.register_callback( into_the_fray_callback_t( this ) ); } - return ca; } // warrior_t::reset ========================================================= diff --git a/engine/player/sc_player.cpp b/engine/player/sc_player.cpp index 926d8ba9824..851e1cada14 100644 --- a/engine/player/sc_player.cpp +++ b/engine/player/sc_player.cpp @@ -11783,3 +11783,12 @@ void expansion::legion::initialize_concordance( player_t& player ) player.special_effects.push_back( effect ); } } + +void player_t::activate() +{ + // Activate all actions of the actor + range::for_each( action_list, []( action_t* a ) { a -> activate(); } ); + + // .. and activate all actor pets + range::for_each( pet_list, []( player_t* p ) { p -> activate(); } ); +} diff --git a/engine/sim/sc_sim.cpp b/engine/sim/sc_sim.cpp index 4b37d37adec..d9f9dacedf7 100644 --- a/engine/sim/sc_sim.cpp +++ b/engine/sim/sc_sim.cpp @@ -2514,6 +2514,8 @@ bool sim_t::iterate() sim_phase_str = "Generating " + player_no_pet_list[ current_index ] -> name_str; } + activate_actors(); + bool more_work = true; do { @@ -2549,6 +2551,7 @@ bool sim_t::iterate() current_iteration = -1; range::for_each( target_list, []( player_t* t ) { t -> actor_changed(); } ); + activate_actors(); } } } @@ -3627,3 +3630,29 @@ void sim_t::disable_debug_seed() static_cast(out_std.get_stream()) -> close(); } } + +// Activates the relevant actors in the simulator just before simulating, based on the relevant +// simulation mode (single vs multi actor). +void sim_t::activate_actors() +{ + // Clear out all callbacks + target_list.reset_callbacks(); + target_non_sleeping_list.reset_callbacks(); + player_list.reset_callbacks(); + player_no_pet_list.reset_callbacks(); + player_non_sleeping_list.reset_callbacks(); + healing_no_pet_list.reset_callbacks(); + healing_pet_list.reset_callbacks(); + + // Normal sim mode activates all actors .. and this method is only called once at the beginning of + // the simulation run. + if ( ! single_actor_batch ) + { + range::for_each( player_list, []( player_t* p ) { p -> activate(); } ); + } + // Single-actor batch mode activates the current active actor + else + { + player_no_pet_list[ current_index ] -> activate(); + } +} diff --git a/engine/simulationcraft.hpp b/engine/simulationcraft.hpp index 5d1395a59cd..88fea97cdac 100644 --- a/engine/simulationcraft.hpp +++ b/engine/simulationcraft.hpp @@ -1375,6 +1375,9 @@ struct vector_with_callback bool empty() const { return _data.empty(); } + void reset_callbacks() + { _callbacks.clear(); } + private: void erase_unordered( typename std::vector::iterator it ) { @@ -1866,6 +1869,9 @@ struct sim_t : private sc_thread_t void combat_end(); void add_chart_data( const highchart::chart_t& chart ); + // Activates the necessary actor/actors before iteration begins. + void activate_actors(); + timespan_t current_time() const { return event_mgr.current_time; } static double distribution_mean_error( const sim_t& s, const extended_sample_data_t& sd ) @@ -4093,6 +4099,14 @@ struct player_t : public actor_t // Single actor batch mode calls this every time the active (player) actor changes for all targets virtual void actor_changed() { } + // A method to "activate" an actor in the simulator. Single actor batch mode activates one primary + // actor at a time, while normal simulation mode activates all actors at the beginning of the sim. + // + // NOTE: Currently, the only thing that occurs during activation of an actor is the registering of + // various state change callbacks (via the action_t::activate method) to the global actor lists. + // Actor pets are also activated by default. + virtual void activate(); + virtual int level() const; virtual double energy_regen_per_second() const; @@ -5245,6 +5259,9 @@ struct action_t : private noncopyable bool hit_any_target; + /// The ability may dynamically change the number of targets (e.g., due to a buff) + bool dynamic_aoe; + /** * @brief Behavior of dot. * @@ -5910,14 +5927,16 @@ struct action_t : private noncopyable virtual bool init_finished(); - virtual void init_target_cache(); - virtual void reset(); virtual void cancel(); virtual void interrupt_action(); + // Perform activation duties for the action. This is used to "enable" the action when the actor + // becomes active. + virtual void activate(); + virtual expr_t* create_expression(const std::string& name); virtual action_state_t* new_state(); @@ -6189,7 +6208,7 @@ struct heal_t : public spell_base_t virtual dmg_e amount_type( const action_state_t* /* state */, bool /* periodic */ = false ) const override; virtual dmg_e report_amount_type( const action_state_t* /* state */ ) const override; virtual size_t available_targets( std::vector< player_t* >& ) const override; - virtual void init_target_cache() override; + void activate() override; virtual double calculate_direct_amount( action_state_t* state ) const override; virtual double calculate_tick_amount( action_state_t* state, double dmg_multiplier ) const override; virtual void execute() override; @@ -6266,7 +6285,7 @@ struct absorb_t : public spell_base_t virtual dmg_e amount_type( const action_state_t* /* state */, bool /* periodic */ = false ) const override { return ABSORB; } virtual void impact( action_state_t* ) override; - virtual void init_target_cache() override; + virtual void activate() override; virtual size_t available_targets( std::vector< player_t* >& ) const override; virtual int num_targets() const override;