Skip to content

Commit 5d04f68

Browse files
committed
Add raid_events=shield + helpers for enemy-side absorbs
A new `shield` raid event puts an absorb buff on a target enemy (target=, amount=, school= options plus standard timing controls). The raid event creates a real absorb_buff_t so the shield correctly absorbs damage and influences fight termination — the encounter- modeling use case, analogous to raid_event=vulnerable. When a player damages an enemy with an absorb, the attacker's stats include the absorbed portion as damage dealt (action.cpp), so DPS attribution reflects the rolled damage rather than only the post- absorb HP impact. Adds has_absorb() and current_absorb_amount() (virtual on player_t, defined for enemy_t) plus a target.has_absorb expression for APL queries.
1 parent c7607fd commit 5d04f68

5 files changed

Lines changed: 129 additions & 3 deletions

File tree

engine/action/action.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,20 +2133,30 @@ void action_t::assess_damage( result_amount_type rt, action_state_t* state )
21332133
// Execute outbound damage assessor pipeline on the state object
21342134
player->assessor_out_damage.execute( rt, state );
21352135

2136+
// Credit absorbed-on-enemy damage to attacker stats. (mitigated - absorbed)
2137+
// is per-event; self_absorb_amount would accumulate across DoT ticks.
2138+
double credited_amount = state->result_amount;
2139+
if ( state->target && state->target->is_enemy() )
2140+
{
2141+
double absorbed = state->result_mitigated - state->result_absorbed;
2142+
if ( absorbed > 0 )
2143+
credited_amount += absorbed;
2144+
}
2145+
21362146
// TODO: Should part of this move to assessing, priority_iteration_damage for example?
21372147
if ( state->result_raw > 0 || result_is_miss( state->result ) )
21382148
{
21392149
if ( sim->fight_style == FIGHT_STYLE_DUNGEON_SLICE || sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE )
21402150
{
21412151
if ( state->target->is_boss() )
21422152
{
2143-
player->priority_iteration_dmg += state->result_amount;
2153+
player->priority_iteration_dmg += credited_amount;
21442154
}
21452155
}
21462156
else if ( state->target == sim->target ||
21472157
( sim->merge_enemy_priority_dmg && state->target->is_boss() ) )
21482158
{
2149-
player->priority_iteration_dmg += state->result_amount;
2159+
player->priority_iteration_dmg += credited_amount;
21502160
}
21512161

21522162
record_data( state );
@@ -2158,7 +2168,15 @@ void action_t::record_data( action_state_t* data )
21582168
if ( !stats )
21592169
return;
21602170

2161-
stats->add_result( data->result_amount, data->result_total, report_amount_type( data ), data->result,
2171+
double credited_amount = data->result_amount;
2172+
if ( data->target && data->target->is_enemy() )
2173+
{
2174+
double absorbed = data->result_mitigated - data->result_absorbed;
2175+
if ( absorbed > 0 )
2176+
credited_amount += absorbed;
2177+
}
2178+
2179+
stats->add_result( credited_amount, data->result_total, report_amount_type( data ), data->result,
21622180
( may_block || player->position() != POSITION_BACK ) ? data->block_result : BLOCK_RESULT_UNKNOWN,
21632181
data->target );
21642182
}

engine/class_modules/sc_enemy.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ struct enemy_t : public player_t
9292
void demise() override;
9393
double armor_coefficient( int level, tank_dummy_e diff );
9494
std::unique_ptr<expr_t> create_expression( util::string_view expression_str ) override;
95+
96+
bool has_absorb() const override
97+
{
98+
return range::any_of( absorb_buff_list,
99+
[]( const absorb_buff_t* ab ) { return ab->check_value() > 0; } );
100+
}
101+
102+
double current_absorb_amount() const override
103+
{
104+
return range::accumulate( absorb_buff_list, 0.0,
105+
[]( const absorb_buff_t* ab ) { return ab->check_value(); } );
106+
}
107+
95108
timespan_t available() const override
96109
{
97110
return waiting_time;

engine/player/player.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12090,6 +12090,9 @@ std::unique_ptr<expr_t> player_t::create_expression( util::string_view expressio
1209012090
if ( expression_str == "is_enemy" )
1209112091
return expr_t::create_constant( "is_enemy", is_enemy() );
1209212092

12093+
if ( expression_str == "has_absorb" )
12094+
return make_fn_expr( expression_str, [ this ] { return has_absorb() ? 1.0 : 0.0; } );
12095+
1209312096
if ( expression_str == "attack_haste" )
1209412097
return make_fn_expr( expression_str, [this] { return cache.attack_haste(); } );
1209512098

engine/player/player.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,10 @@ struct player_t : public actor_t
14131413
virtual void assess_damage_imminent_pre_absorb( school_e, result_amount_type, action_state_t* );
14141414
virtual void assess_damage_imminent( school_e, result_amount_type, action_state_t* );
14151415
virtual void do_damage( action_state_t* );
1416+
1417+
virtual bool has_absorb() const { return false; }
1418+
virtual double current_absorb_amount() const { return 0.0; }
1419+
14161420
virtual void assess_heal( school_e, result_amount_type, action_state_t* );
14171421
virtual void trigger_callbacks( proc_types, proc_types2, action_t* action, action_state_t* state,
14181422
proc_trigger_type_e pt_type = TRIGGER_ACTION );

engine/sim/raid_event.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,92 @@ struct vulnerable_event_t final : public raid_event_t
16671667
}
16681668
};
16691669

1670+
// Shield ===================================================================
1671+
1672+
struct shield_event_t final : public raid_event_t
1673+
{
1674+
double amount;
1675+
std::string school_str;
1676+
player_t* target = nullptr;
1677+
std::string target_str;
1678+
school_e school;
1679+
absorb_buff_t* buff = nullptr;
1680+
int instance_id;
1681+
1682+
shield_event_t( sim_t* s, std::string_view options_str )
1683+
: raid_event_t( s, "shield" ), amount( 0.0 ), school( SCHOOL_NONE )
1684+
{
1685+
// Position in sim->raid_events; consistent across sim_t clones.
1686+
instance_id = static_cast<int>( s->raid_events.size() );
1687+
1688+
add_option( opt_float( "amount", amount ) );
1689+
add_option( opt_string( "school", school_str ) );
1690+
1691+
if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE )
1692+
add_option( opt_string( "target", target_str ) );
1693+
else
1694+
add_option( opt_func( "target", [ this ]( auto, auto, std::string_view v ) { return parse_target( v ); } ) );
1695+
1696+
parse_options( options_str );
1697+
1698+
if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE )
1699+
target_str = "Pull_" + util::to_string( pull ) + "_" + target_str;
1700+
1701+
if ( !school_str.empty() )
1702+
{
1703+
school = util::parse_school_type( school_str );
1704+
if ( school == SCHOOL_NONE )
1705+
throw std::invalid_argument( fmt::format( "Unknown shield raid event school '{}'", school_str ) );
1706+
}
1707+
}
1708+
1709+
bool parse_target( std::string_view value )
1710+
{
1711+
auto it = range::find_if( sim->target_list, [ &value ]( const player_t* t ) {
1712+
return util::str_compare_ci( value, t->name() );
1713+
} );
1714+
1715+
if ( it != sim->target_list.end() )
1716+
{
1717+
target = *it;
1718+
return true;
1719+
}
1720+
sim->error( "Unknown shield raid event target '{}'", value );
1721+
return true;
1722+
}
1723+
1724+
void _start() override
1725+
{
1726+
if ( !buff )
1727+
{
1728+
if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE )
1729+
{
1730+
target = sim->find_player( target_str );
1731+
if ( !target )
1732+
throw std::invalid_argument( fmt::format( "Unknown shield raid event target '{}'", target_str ) );
1733+
}
1734+
else if ( !target )
1735+
{
1736+
target = sim->target;
1737+
}
1738+
1739+
buff = make_buff<absorb_buff_t>( target, fmt::format( "raid_event_shield_{}", instance_id ) );
1740+
if ( school != SCHOOL_NONE )
1741+
buff->set_absorb_school( school );
1742+
}
1743+
1744+
// No amount => unbreakable; infinity makes consume() never reduce to 0.
1745+
double value = amount > 0 ? amount : std::numeric_limits<double>::infinity();
1746+
buff->trigger( 1, value, -1.0, timespan_t::max() );
1747+
}
1748+
1749+
void _finish() override
1750+
{
1751+
if ( buff )
1752+
buff->expire();
1753+
}
1754+
};
1755+
16701756
// Position Switch ==========================================================
16711757

16721758
struct position_event_t : public raid_event_t
@@ -2305,6 +2391,8 @@ std::unique_ptr<raid_event_t> raid_event_t::create( sim_t* sim, util::string_vie
23052391
return std::unique_ptr<raid_event_t>( new stun_event_t( sim, options_str ) );
23062392
if ( name == "vulnerable" )
23072393
return std::unique_ptr<raid_event_t>( new vulnerable_event_t( sim, options_str ) );
2394+
if ( name == "shield" )
2395+
return std::unique_ptr<raid_event_t>( new shield_event_t( sim, options_str ) );
23082396
if ( name == "position_switch" )
23092397
return std::unique_ptr<raid_event_t>( new position_event_t( sim, options_str ) );
23102398
if ( name == "flying" )

0 commit comments

Comments
 (0)