diff --git a/engine/class_modules/sc_enemy.cpp b/engine/class_modules/sc_enemy.cpp index eb153f6414c..b2e73995190 100644 --- a/engine/class_modules/sc_enemy.cpp +++ b/engine/class_modules/sc_enemy.cpp @@ -92,6 +92,19 @@ struct enemy_t : public player_t void demise() override; double armor_coefficient( int level, tank_dummy_e diff ); std::unique_ptr create_expression( util::string_view expression_str ) override; + + bool has_absorb() const override + { + return range::any_of( absorb_buff_list, + []( const absorb_buff_t* ab ) { return ab->check_value() > 0; } ); + } + + double current_absorb_amount() const override + { + return range::accumulate( absorb_buff_list, 0.0, + []( const absorb_buff_t* ab ) { return ab->check_value(); } ); + } + timespan_t available() const override { return waiting_time; diff --git a/engine/player/player.cpp b/engine/player/player.cpp index bc201317549..ac5d56b280d 100644 --- a/engine/player/player.cpp +++ b/engine/player/player.cpp @@ -12090,6 +12090,9 @@ std::unique_ptr player_t::create_expression( util::string_view expressio if ( expression_str == "is_enemy" ) return expr_t::create_constant( "is_enemy", is_enemy() ); + if ( expression_str == "has_absorb" ) + return make_fn_expr( expression_str, [ this ] { return has_absorb() ? 1.0 : 0.0; } ); + if ( expression_str == "attack_haste" ) return make_fn_expr( expression_str, [this] { return cache.attack_haste(); } ); diff --git a/engine/player/player.hpp b/engine/player/player.hpp index 3cfaa0ffdad..3578ff232b4 100644 --- a/engine/player/player.hpp +++ b/engine/player/player.hpp @@ -1413,6 +1413,10 @@ struct player_t : public actor_t virtual void assess_damage_imminent_pre_absorb( school_e, result_amount_type, action_state_t* ); virtual void assess_damage_imminent( school_e, result_amount_type, action_state_t* ); virtual void do_damage( action_state_t* ); + + virtual bool has_absorb() const { return false; } + virtual double current_absorb_amount() const { return 0.0; } + virtual void assess_heal( school_e, result_amount_type, action_state_t* ); virtual void trigger_callbacks( proc_types, proc_types2, action_t* action, action_state_t* state, proc_trigger_type_e pt_type = TRIGGER_ACTION ); diff --git a/engine/sim/raid_event.cpp b/engine/sim/raid_event.cpp index 7f785fac583..51bfabe7eac 100644 --- a/engine/sim/raid_event.cpp +++ b/engine/sim/raid_event.cpp @@ -5,6 +5,7 @@ #include "action/heal.hpp" #include "action/action.hpp" +#include "action/action_state.hpp" #include "action/spell.hpp" #include "buff/buff.hpp" #include "dbc/dbc.hpp" @@ -1667,6 +1668,88 @@ struct vulnerable_event_t final : public raid_event_t } }; +// Absorb =================================================================== + +struct absorb_event_t final : public raid_event_t +{ + double amount; + std::string school_str; + player_t* target = nullptr; + std::string target_str; + school_e school; + absorb_buff_t* buff = nullptr; + + absorb_event_t( sim_t* s, std::string_view options_str ) + : raid_event_t( s, "absorb" ), amount( 0.0 ), school( SCHOOL_NONE ) + { + add_option( opt_float( "amount", amount ) ); + add_option( opt_string( "school", school_str ) ); + + if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE ) + add_option( opt_string( "target", target_str ) ); + else + add_option( opt_func( "target", [ this ]( auto, auto, std::string_view v ) { return parse_target( v ); } ) ); + + parse_options( options_str ); + + if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE ) + target_str = "Pull_" + util::to_string( pull ) + "_" + target_str; + + if ( !school_str.empty() ) + { + school = util::parse_school_type( school_str ); + if ( school == SCHOOL_NONE ) + throw std::invalid_argument( fmt::format( "Unknown absorb raid event school '{}'", school_str ) ); + } + } + + bool parse_target( std::string_view value ) + { + auto it = range::find_if( sim->target_list, [ &value ]( const player_t* t ) { + return util::str_compare_ci( value, t->name() ); + } ); + + if ( it != sim->target_list.end() ) + { + target = *it; + return true; + } + sim->error( "Unknown absorb raid event target '{}'", value ); + return true; + } + + void _start() override + { + if ( !buff ) + { + if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE ) + { + target = sim->find_player( target_str ); + if ( !target ) + throw std::invalid_argument( fmt::format( "Unknown absorb raid event target '{}'", target_str ) ); + } + else if ( !target ) + { + target = sim->target; + } + + buff = make_buff( target, fmt::format( "raid_event_absorb_{}", internal_id ) ); + if ( school != SCHOOL_NONE ) + buff->set_absorb_school( school ); + } + + // No amount => unbreakable; infinity makes consume() never reduce to 0. + double value = amount > 0 ? amount : std::numeric_limits::infinity(); + buff->trigger( 1, value, -1.0, timespan_t::max() ); + } + + void _finish() override + { + if ( buff ) + buff->expire(); + } +}; + // Position Switch ========================================================== struct position_event_t : public raid_event_t @@ -2311,6 +2394,8 @@ std::unique_ptr raid_event_t::create( sim_t* sim, util::string_vie return std::unique_ptr( new stun_event_t( sim, options_str ) ); if ( name == "vulnerable" ) return std::unique_ptr( new vulnerable_event_t( sim, options_str ) ); + if ( name == "absorb" ) + return std::unique_ptr( new absorb_event_t( sim, options_str ) ); if ( name == "position_switch" ) return std::unique_ptr( new position_event_t( sim, options_str ) ); if ( name == "flying" ) @@ -2414,6 +2499,25 @@ void raid_event_t::init( sim_t* sim ) } throw sc_initialization_error( "DungeonRoute fight style requires at least one pull event with pull=1." ); } + + bool has_absorb_event = range::any_of( sim->raid_events, + []( const auto& e ) { return e->type == "absorb"; } ); + if ( has_absorb_event ) + { + for ( player_t* p : sim->player_no_pet_list ) + { + p->assessor_out_damage.add( assessor::TARGET_DAMAGE + 1, + []( result_amount_type, action_state_t* s ) { + if ( s->target && s->target->is_enemy() ) + { + double absorbed = s->result_mitigated - s->result_absorbed; + if ( absorbed > 0 ) + s->result_amount += absorbed; + } + return assessor::CONTINUE; + } ); + } + } } void raid_event_t::reset( sim_t* sim )