diff --git a/engine/action/action.cpp b/engine/action/action.cpp index 2e37c66c37b..f9845add958 100644 --- a/engine/action/action.cpp +++ b/engine/action/action.cpp @@ -345,7 +345,6 @@ action_t::action_t( action_e ty, util::string_view token, player_t* p, const spe internal_id( p->get_action_id( name_str ) ), resource_current( RESOURCE_NONE ), aoe(), - secondary_targets_only(), dual(), callbacks( true ), caster_callbacks( true ), @@ -1703,12 +1702,14 @@ int action_t::num_targets() const size_t action_t::available_targets( std::vector& tl ) const { tl.clear(); - if ( !secondary_targets_only && !target->is_sleeping() && target->is_enemy() ) + + const auto& cb = target_filter_callback; + if ( !target->is_sleeping() && target->is_enemy() && ( !cb || cb( this, target ) ) ) tl.push_back( target ); for ( auto* t : sim->target_non_sleeping_list ) { - if ( t->is_enemy() && ( t != target ) ) + if ( t->is_enemy() && ( t != target ) && ( !cb || cb( this, t ) ) ) { tl.push_back( t ); } diff --git a/engine/action/action.hpp b/engine/action/action.hpp index 5aa0edad98e..95a80419626 100644 --- a/engine/action/action.hpp +++ b/engine/action/action.hpp @@ -22,6 +22,7 @@ #include #include +struct action_t; struct action_priority_t; struct action_priority_list_t; struct action_state_t; @@ -51,6 +52,8 @@ namespace report { template struct parsed_value_t; +using target_filter_callback_t = std::function; + // Action =================================================================== struct action_t : private noncopyable @@ -104,8 +107,8 @@ struct action_t : private noncopyable /// The amount of targets that an ability impacts on. -1 will hit all targets. int aoe; - /// Whether the action hits all targets or only the secondary ones. - bool secondary_targets_only; + /// Which targets to include in the list of available targets. + target_filter_callback_t target_filter_callback; /// If set to true, this action will not be counted toward total amount of executes in reporting. Useful for abilities with parent/children attacks. bool dual; @@ -1143,6 +1146,9 @@ struct action_t : private noncopyable static bool has_direct_damage_effect( const spell_data_t& ); static bool has_periodic_damage_effect( const spell_data_t& ); + static target_filter_callback_t secondary_targets_only() + { return [] ( const action_t* a, player_t* t ) { return t != a->target; }; } + friend void sc_format_to( const action_t&, fmt::format_context::iterator ); }; diff --git a/engine/class_modules/paladin/sc_paladin.cpp b/engine/class_modules/paladin/sc_paladin.cpp index 16f36eace20..b314cd2ae04 100644 --- a/engine/class_modules/paladin/sc_paladin.cpp +++ b/engine/class_modules/paladin/sc_paladin.cpp @@ -1829,20 +1829,13 @@ struct hammer_of_light_t : public holy_power_consumer_t affected_by.divine_purpose = false; // We handle this manually base_execute_time = timespan_t::from_millis( 600 ); // Still has a 600ms execute time, for whatever reasons. Not in spell data anymore. dual = true; - } - - size_t available_targets( std::vector& tl ) const override - { - holy_power_consumer_t::available_targets( tl ); + target_filter_callback = secondary_targets_only(); - // Does not hit the main target - auto it = range::find( tl, target ); - if ( it != tl.end() ) - { - tl.erase( it ); - } - - return tl.size(); + if ( p->sets->has_set_bonus( HERO_TEMPLAR, TWW3, B4 ) ) + // Both effect 2 and 4 adjust AoE. This is probably a tuning knob for Blizzard. Also maybe Ret is 2, Prot 4, who knows. + aoe += as( p->sets->set( HERO_TEMPLAR, TWW3, B4 ) + ->effectN( p->specialization() == PALADIN_RETRIBUTION ? 4 : 2 ) + .base_value() ); } action_state_t* new_state() override @@ -2029,9 +2022,10 @@ struct empyrean_hammer_wd_t : public paladin_spell_t empyrean_hammer_wd_t( paladin_t* p ) : paladin_spell_t( "empyrean_hammer_wrathful_descent", p, p->spells.templar.empyrean_hammer_wd ) { - background = true; - may_crit = false; - aoe = -1; + background = true; + may_crit = false; + aoe = -1; + target_filter_callback = secondary_targets_only(); // ToDo (Fluttershy) // This spell currently deals full damage to all targets, even above 20. @@ -2039,20 +2033,6 @@ struct empyrean_hammer_wd_t : public paladin_spell_t reduced_aoe_targets = -1; } - size_t available_targets( std::vector& tl ) const override - { - paladin_spell_t::available_targets( tl ); - - // Does not hit the main target - auto it = range::find( tl, target ); - if ( it != tl.end() ) - { - tl.erase( it ); - } - - return tl.size(); - } - void impact(action_state_t* s) override { paladin_spell_t::impact( s ); diff --git a/engine/class_modules/sc_mage.cpp b/engine/class_modules/sc_mage.cpp index b108b0c7d99..3d54ad574a0 100644 --- a/engine/class_modules/sc_mage.cpp +++ b/engine/class_modules/sc_mage.cpp @@ -366,6 +366,7 @@ struct mage_t final : public player_t bool fof_requires_freezing = true; bool il_requires_freezing = true; bool il_sort_by_freezing = false; + bool randomize_si_target = false; } options; // Pets @@ -1799,6 +1800,19 @@ struct mage_spell_t : public spell_t return c; } + size_t available_targets( std::vector& tl ) const override + { + spell_t::available_targets( tl ); + + if ( tl.size() > 2 && p()->options.randomize_si_target + && data().affected_by( p()->talents.splitting_ice->effectN( 1 ) ) ) + { + std::swap( tl[ 1 ], tl[ rng().range( 1, tl.size() ) ] ); + } + + return tl.size(); + } + void execute() override { spell_t::execute(); @@ -4622,7 +4636,8 @@ struct splintering_ray_t final : public spell_t spell_t( n, p, p->find_spell( 418735 ) ), freezing_source( p->get_proc( "Freezing applied (Splintering Ray)" ) ) { - background = proc = secondary_targets_only = true; + background = proc = true; + target_filter_callback = secondary_targets_only(); base_dd_min = base_dd_max = 1.0; // TODO: Seems to hit 1 fewer target aoe--; @@ -4921,7 +4936,8 @@ struct frostfire_empowerment_t final : public spell_t spell_t( n, p, p->find_spell( 431186 ) ), freezing_source( p->get_proc( "Freezing applied (Frostfire Empowerment)" ) ) { - background = proc = secondary_targets_only = true; + background = proc = true; + target_filter_callback = secondary_targets_only(); aoe = -1; base_dd_min = base_dd_max = 1.0; // TODO: Check how it behaves wrt the excluded main target @@ -4946,7 +4962,8 @@ struct flash_freezeburn_t final : public spell_t spell_t( n, p, p->find_spell( 1278079 ) ), freezing_source( p->get_proc( "Freezing applied (Flash Freezeburn)" ) ) { - background = proc = secondary_targets_only = true; + background = proc = true; + target_filter_callback = secondary_targets_only(); base_dd_min = base_dd_max = 1.0; // TODO: Usually hits one fewer target // It's possible it picks 5 random targets and if one of them happens to be @@ -4968,7 +4985,8 @@ struct controlled_instincts_t final : public spell_t controlled_instincts_t( std::string_view n, mage_t* p ) : spell_t( n, p, p->find_spell( p->specialization() == MAGE_FROST ? 444487 : 444720 ) ) { - background = proc = secondary_targets_only = true; + background = proc = true; + target_filter_callback = secondary_targets_only(); // Only hits 5 targets despite max_targets being 6 aoe -= 1; // TODO: The tooltip still mentions this, but it's untestable at the moment since it can't hit 6 or more targets @@ -5624,6 +5642,7 @@ void mage_t::create_options() add_option( opt_bool( "mage.fof_requires_freezing", options.fof_requires_freezing ) ); add_option( opt_bool( "mage.il_requires_freezing", options.il_requires_freezing ) ); add_option( opt_bool( "mage.il_sort_by_freezing", options.il_sort_by_freezing ) ); + add_option( opt_bool( "mage.randomize_si_target", options.randomize_si_target ) ); player_t::create_options(); }