diff --git a/engine/action/action.cpp b/engine/action/action.cpp index 66f5c561951..f8de5b980f9 100644 --- a/engine/action/action.cpp +++ b/engine/action/action.cpp @@ -393,7 +393,7 @@ action_t::action_t( action_e ty, util::string_view token, player_t* p, const spe may_dodge(), may_parry(), may_glance(), - may_block(), + may_block( true ), may_crit(), tick_may_crit(), tick_zero(), @@ -676,6 +676,8 @@ void action_t::parse_spell_data( const spell_data_t& spell_data ) treat_as_periodic = spell_data.flags( spell_attribute::SX_TREAT_AS_PERIODIC ); ignores_armor = spell_data.flags( spell_attribute::SX_TREAT_AS_PERIODIC ); // TODO: better way to parse this? may_miss = !spell_data.flags( spell_attribute::SX_ALWAYS_HIT ); + may_block = !spell_data.flags( spell_attribute::SX_NO_BLOCK ) && + !spell_data.flags( spell_attribute::SX_NO_D_P_B ); not_a_proc = spell_data.flags( spell_attribute::SX_NOT_A_PROC ); if ( spell_data.flags( spell_attribute::SX_REFRESH_EXTENDS_DURATION ) ) @@ -1671,9 +1673,12 @@ double action_t::composite_total_spell_power() const return spell_power; } -double action_t::composite_target_armor( player_t* t ) const +double action_t::composite_target_armor( const action_state_t* s ) const { - return player->composite_player_target_armor( t ); + if ( get_school() == SCHOOL_PHYSICAL ) + return player->composite_player_target_armor( s->target ); + else + return 0.0; } double action_t::composite_target_crit_chance( player_t* t ) const @@ -1782,46 +1787,23 @@ player_t* action_t::find_target_by_number( int number ) const } // action_t::calculate_block_result ========================================= -// moved here now that we found out that spells can be blocked (Holy Shield) -// block_chance() and crit_block_chance() govern whether any given attack can -// be blocked or not (zero return if not) block_result_e action_t::calculate_block_result( action_state_t* s ) const { - block_result_e block_result = BLOCK_RESULT_UNBLOCKED; - - // 2019-06-02: Looking at logs from Uldir, Battle of Dazar'alor and Crucible of Storms, - // It appears that non players can't block attacks or abilities anymore - // Non-player Parry and Miss seem unchanged - if ( s -> target -> is_enemy() ) + // target_block_value is only set by enemy actions that damage players + if ( may_block && s->target_block_value && result_is_hit( s->result ) && player->position() == POSITION_FRONT && + s->result != RESULT_NONE ) { - return BLOCK_RESULT_UNBLOCKED; - } - - // Blocks also get a their own roll, and glances/crits can be blocked. - if ( result_is_hit( s->result ) && may_block && ( player->position() == POSITION_FRONT ) && - !( s->result == RESULT_NONE ) ) - { - double block_total = block_chance( s ); + // Blocks also get a their own roll, and glances/crits can be blocked. + auto block_result = s->target->target_block_resolution( s ); - if ( block_total > 0 ) - { - double crit_block = crit_block_chance( s ); + if ( sim->debug ) + sim->print_debug( "{} block result for {} is {}", *player, *this, block_result ); - // Roll once for block, then again for crit block if the block succeeds - if ( rng().roll( block_total ) ) - { - if ( rng().roll( crit_block ) ) - block_result = BLOCK_RESULT_CRIT_BLOCKED; - else - block_result = BLOCK_RESULT_BLOCKED; - } - } + return block_result; } - sim->print_debug("{} result for {} is {}", *player, *this, block_result ); - - return block_result; + return BLOCK_RESULT_UNBLOCKED; } // action_t::execute ======================================================== @@ -2739,12 +2721,16 @@ void action_t::init() if ( does_periodic_damage() ) { - snapshot_flags |= STATE_MUL_TA | STATE_TGT_MUL_TA | STATE_MUL_PERSISTENT | STATE_VERSATILITY; + snapshot_flags |= STATE_MUL_TA | STATE_TGT_MUL_TA | STATE_TGT_MITG_TA | STATE_MUL_PERSISTENT | STATE_VERSATILITY; } if ( does_direct_damage() ) { - snapshot_flags |= STATE_MUL_DA | STATE_TGT_MUL_DA | STATE_MUL_PERSISTENT | STATE_VERSATILITY; + snapshot_flags |= STATE_MUL_DA | STATE_TGT_MUL_DA | STATE_TGT_MITG_DA | STATE_MUL_PERSISTENT | STATE_VERSATILITY; + + // Because schools can change during runtime, armor is flagged and not snapshot if determined to be non-physical + if ( !ignores_armor ) + snapshot_flags |= STATE_TGT_ARMOR; } if ( player->is_pet() && ( snapshot_flags & ( STATE_MUL_DA | STATE_MUL_TA | STATE_TGT_MUL_DA | STATE_TGT_MUL_TA | @@ -2753,9 +2739,6 @@ void action_t::init() snapshot_flags |= STATE_MUL_PET | STATE_TGT_MUL_PET; } - if ( school == SCHOOL_PHYSICAL ) - snapshot_flags |= STATE_TGT_ARMOR; - if ( data().flags( spell_attribute::SX_DISABLE_PLAYER_MULT ) || data().flags( spell_attribute::SX_DISABLE_PLAYER_HEALING_MULT ) ) { @@ -4368,13 +4351,13 @@ void action_t::snapshot_internal( action_state_t* state, unsigned flags, result_ state->target_crit_chance = composite_target_crit_chance( state->target ) * composite_crit_chance_multiplier(); if ( flags & STATE_TGT_MITG_DA ) - state->target_mitigation_da_multiplier = composite_target_mitigation( state->target, get_school() ); + state->target_mitigation_da_multiplier = composite_target_mitigation( state, true ); if ( flags & STATE_TGT_MITG_TA ) - state->target_mitigation_ta_multiplier = composite_target_mitigation( state->target, get_school() ); + state->target_mitigation_ta_multiplier = composite_target_mitigation( state, false ); if ( flags & STATE_TGT_ARMOR ) - state->target_armor = composite_target_armor( state->target ); + state->target_armor = composite_target_armor( state ); } // action_t::composite_dot_duration ========================================= @@ -5012,14 +4995,15 @@ double action_t::composite_rolling_ta_multiplier( const action_state_t* s ) cons /// Persistent modifiers that are snapshot at the start of the spell cast -double action_t::composite_persistent_multiplier(const action_state_t*) const +double action_t::composite_persistent_multiplier( const action_state_t* ) const { - return player->composite_persistent_multiplier(get_school()); + return player->composite_persistent_multiplier( get_school() ); } -double action_t::composite_target_mitigation(player_t* t, school_e s) const +double action_t::composite_target_mitigation( const action_state_t* s, bool direct ) const { - return t->composite_mitigation_multiplier(s); + return s->target->composite_mitigation_multiplier( s, get_school(), direct ) * + s->target->composite_mitigation_from_player_multiplier( s->action->player, s, get_school(), direct ); } double action_t::composite_player_critical_multiplier( const action_state_t* s ) const diff --git a/engine/action/action.hpp b/engine/action/action.hpp index f2bfdced51c..dbbe7c6ae88 100644 --- a/engine/action/action.hpp +++ b/engine/action/action.hpp @@ -817,12 +817,6 @@ struct action_t : private noncopyable virtual double glance_chance( int /* delta_level */ ) const { return 0; } - virtual double block_chance( action_state_t* /* state */ ) const - { return 0; } - - virtual double crit_block_chance( action_state_t* /* state */ ) const - { return 0; } - virtual double total_crit_bonus( const action_state_t* /* state */ ) const; // Check if we want to move this into the stateless system. virtual int num_targets() const; @@ -918,7 +912,7 @@ struct action_t : private noncopyable virtual double composite_total_spell_power() const; - virtual double composite_target_armor( player_t* ) const; + virtual double composite_target_armor( const action_state_t* ) const; virtual double composite_target_crit_chance( player_t* ) const; @@ -976,9 +970,9 @@ struct action_t : private noncopyable virtual double composite_aoe_multiplier( const action_state_t* ) const { return 1.0; } - virtual double composite_target_mitigation( player_t* t, school_e s ) const; + virtual double composite_target_mitigation( const action_state_t*, bool direct ) const; - virtual double composite_player_critical_multiplier( const action_state_t* s ) const; + virtual double composite_player_critical_multiplier( const action_state_t* ) const; /// Action proc type, needed for dynamic aoe stuff and such. virtual proc_types proc_type() const diff --git a/engine/action/action_state.cpp b/engine/action/action_state.cpp index 3b3c7297f0e..9d96f2814ee 100644 --- a/engine/action/action_state.cpp +++ b/engine/action/action_state.cpp @@ -55,8 +55,7 @@ void action_state_t::initialize() result = RESULT_NONE; result_type = result_amount_type::NONE; block_result = BLOCK_RESULT_UNBLOCKED; - result_raw = result_total = result_mitigated = result_absorbed = - result_amount = blocked_amount = self_absorb_amount = 0; + result_raw = result_total = result_mitigated = result_absorbed = result_amount = self_absorb_amount = 0; } /* void action_state_t::copy_state( const action_state_t* o ) @@ -91,7 +90,6 @@ void action_state_t::copy_state( const action_state_t* o ) result_absorbed = o->result_absorbed; result_crit_bonus = o->result_crit_bonus; result_amount = o->result_amount; - blocked_amount = o->blocked_amount; self_absorb_amount = o->self_absorb_amount; haste = o->haste; crit_chance = o->crit_chance; @@ -133,7 +131,6 @@ action_state_t::action_state_t( action_t* a, player_t* t ) result_absorbed( 0 ), result_crit_bonus( 0 ), result_amount( 0 ), - blocked_amount( 0 ), self_absorb_amount( 0 ), haste( 1.0 ), crit_chance( 0 ), @@ -152,7 +149,8 @@ action_state_t::action_state_t( action_t* a, player_t* t ) target_pet_multiplier( 1.0 ), target_mitigation_da_multiplier( 1.0 ), target_mitigation_ta_multiplier( 1.0 ), - target_armor( 0 ) + target_armor( 0 ), + target_block_value( 0 ) { assert( target ); } @@ -210,7 +208,6 @@ std::ostringstream& action_state_t::debug_str( std::ostringstream& s ) s << " absorbed_amount=" << result_absorbed; s << " crit_bonus=" << result_crit_bonus; s << " actual_amount=" << result_amount; - s << " only_blocked_damage=" << blocked_amount; s << " self_absorbed_damage=" << self_absorb_amount; s << " ap=" << attack_power; s << " sp=" << spell_power; @@ -234,7 +231,14 @@ std::ostringstream& action_state_t::debug_str( std::ostringstream& s ) s << " tgt_mitg_da_mul=" << target_mitigation_da_multiplier; s << " tgt_mitg_ta_mul=" << target_mitigation_ta_multiplier; - s << " target_armor=" << target_armor; + if ( target_armor ) + { + s << " target_armor=" << target_armor; + } + if ( target_block_value ) + { + s << " target_block_value=" << target_block_value; + } s.precision( ss ); diff --git a/engine/action/action_state.hpp b/engine/action/action_state.hpp index 276a1cbe066..0f5d773f7ce 100644 --- a/engine/action/action_state.hpp +++ b/engine/action/action_state.hpp @@ -34,11 +34,10 @@ struct action_state_t : private noncopyable block_result_e block_result; double result_raw; // Base result value, without crit/glance etc. double result_total; // Total unmitigated result, including crit bonus, glance penalty, etc. - double result_mitigated; // Result after mitigation / resist. *NOTENOTENOTE* Only filled after action_t::impact() call + double result_mitigated; // Result after mitigation / resist. double result_absorbed; // Result after absorption. *NOTENOTENOTE* Only filled after action_t::impact() call double result_crit_bonus; // Crit bonus multiplier used in the final calculation double result_amount; // Final (actual) result - double blocked_amount; // The amount of damage reduced via block or critical block double self_absorb_amount; // The amount of damage reduced via personal absorbs such as shield_barrier. // Snapshotted stats during execution double haste; @@ -61,6 +60,7 @@ struct action_state_t : private noncopyable double target_mitigation_da_multiplier; double target_mitigation_ta_multiplier; double target_armor; + double target_block_value; // Only players can block, so this is only set in enemy_action_t::snapshot_internal() static void release( action_state_t*& s ); static std::string flags_to_str( unsigned flags ); diff --git a/engine/action/attack.cpp b/engine/action/attack.cpp index 06e25e9f784..05052a98f10 100644 --- a/engine/action/attack.cpp +++ b/engine/action/attack.cpp @@ -34,7 +34,6 @@ attack_t::attack_t( util::string_view n, player_t* p, const spell_data_t* s ) { may_dodge = !data().flags( spell_attribute::SX_NO_DODGE ); may_parry = !data().flags( spell_attribute::SX_NO_PARRY ); - may_block = !data().flags( spell_attribute::SX_NO_BLOCK ); } } @@ -123,34 +122,6 @@ double attack_t::dodge_chance( double expertise, player_t* t ) const return dodge; } -double attack_t::block_chance( action_state_t* s ) const -{ - if ( s->target->is_enemy() && sim->auto_attacks_always_land && !special ) - { - return 0.0; - } - - // cache.block() contains the target's block chance (3.0 base for bosses, more for shield tanks) - double block = s->target->cache.block(); - - // add or subtract 1.5% per level difference -- Level difference does not seem to matter anymore. - // block += ( s->target->level() - player->level() ) * 0.015; - - return block; -} - -double attack_t::crit_block_chance( action_state_t* s ) const -{ - // This function is probably unnecessary, as we could just query - // cache.crit_block() directly. - // I'm leaving it for consistency with *_chance() and in case future changes - // modify crit block mechanics - - // Crit Block does not suffer from level-based suppression, return cached - // value directly - return s->target->cache.crit_block(); -} - double attack_t::bonus_da( const action_state_t* s ) const { double da = action_t::bonus_da( s ); @@ -343,6 +314,9 @@ result_e attack_t::calculate_result( action_state_t* s ) const result = RESULT_CRIT; } + if ( sim->debug ) + sim->print_debug( "{} result for attack {} is {}.", *player, *this, result ); + return result; } diff --git a/engine/action/attack.hpp b/engine/action/attack.hpp index 8d1d7ecd664..01d3285ec36 100644 --- a/engine/action/attack.hpp +++ b/engine/action/attack.hpp @@ -29,8 +29,6 @@ struct attack_t : public action_t double miss_chance( double hit, player_t* t ) const override; double dodge_chance( double /* expertise */, player_t* t ) const override; - double block_chance( action_state_t* s ) const override; - double crit_block_chance( action_state_t* s ) const override; double bonus_da( const action_state_t* ) const override; double action_multiplier() const override; diff --git a/engine/action/heal.cpp b/engine/action/heal.cpp index 2fccc7f8b1d..d0b96d9fb28 100644 --- a/engine/action/heal.cpp +++ b/engine/action/heal.cpp @@ -77,6 +77,9 @@ void heal_t::init() { base_t::init(); + snapshot_flags &= ~( STATE_TGT_MITG_DA | STATE_TGT_MITG_TA ); + update_flags &= ~( STATE_TGT_MITG_DA | STATE_TGT_MITG_TA ); + record_healing = player->record_healing(); } @@ -96,6 +99,11 @@ double heal_t::composite_ta_multiplier( const action_state_t* s ) const return m; } +double heal_t::composite_target_multiplier( player_t* t ) const +{ + return t->composite_player_healing_received_multiplier(); +} + double heal_t::composite_player_critical_multiplier( const action_state_t* ) const { return player->composite_player_critical_healing_multiplier(); diff --git a/engine/action/heal.hpp b/engine/action/heal.hpp index 59852b1f21a..0c35cdb6f55 100644 --- a/engine/action/heal.hpp +++ b/engine/action/heal.hpp @@ -48,6 +48,7 @@ struct heal_t : public spell_base_t int num_targets() const override; double composite_da_multiplier( const action_state_t* s ) const override; double composite_ta_multiplier( const action_state_t* s ) const override; + double composite_target_multiplier( player_t* ) const override; double composite_player_critical_multiplier( const action_state_t* /* s */ ) const override; double composite_versatility( const action_state_t* state ) const override; double total_crit_bonus( const action_state_t* ) const override; diff --git a/engine/action/parse_effects.cpp b/engine/action/parse_effects.cpp index 6e9e3a59986..9a294ea2c23 100644 --- a/engine/action/parse_effects.cpp +++ b/engine/action/parse_effects.cpp @@ -70,7 +70,7 @@ std::string ratings_invalidate( const player_effect_t& data ) std::string school( uint32_t opt ) { - return opt == 0x7f ? "All" : util::school_type_string( dbc::get_school_type( opt ) ); + return opt == 0x7f ? "all" : util::school_type_string( dbc::get_school_type( opt ) ); } std::string pet_type( uint32_t opt ) @@ -664,7 +664,7 @@ bool parse_effects_t::parse_effect( pack_t& pack, size_t i, bool force ) return false; } - if ( pack.data.value != 0.0 ) + if ( pack.data.type & VALUE_OVERRIDE ) { val = pack.data.value; val_mul = 1.0; @@ -689,7 +689,7 @@ bool parse_effects_t::parse_effect( pack_t& pack, size_t i, bool force ) if constexpr ( is_detected_v ) { - if ( !val && tmp.type == USE_DATA ) + if ( !val && !( tmp.type & ( USE_CURRENT | USE_DEFAULT | ALLOW_ZERO | VALUE_FUNCTION ) ) ) return false; } else @@ -750,7 +750,7 @@ bool parse_effects_t::parse_effect( pack_t& pack, size_t i, bool force ) tmp.simple = false; } - if ( tmp.simple && !tmp.buff ) + if ( tmp.simple && !tmp.buff && !( tmp.type & PARSE_PASSIVE ) ) { throw_passive_error( pack.spell ); return false; @@ -1132,16 +1132,28 @@ double parse_player_effects_t::composite_player_target_pet_damage_multiplier( pl return tm; } -void parse_player_effects_t::target_mitigation( school_e school, result_amount_type, action_state_t* state ) +double parse_player_effects_t::composite_mitigation_multiplier( const action_state_t* s, school_e school, bool direct ) const { - for ( const auto& i : damage_taken_multiplier_effects ) + auto m = player_t::composite_mitigation_multiplier( s, school, direct ); + + for ( const auto& i : mitigation_multiplier_effects ) if ( i.opt_enum & dbc::get_school_mask( school ) ) - state->result_amount *= 1.0 + get_effect_value( i ); + m *= 1.0 + get_effect_value( i ); + + return m; +} + +double parse_player_effects_t::composite_mitigation_from_player_multiplier( player_t* source, const action_state_t* s, + school_e school, bool direct ) const +{ + auto m = player_t::composite_mitigation_from_player_multiplier( source, s, school, direct ); + auto td = get_target_data( source ); - auto td = get_target_data( state->target ); - for ( const auto& i : target_damage_done_multiplier_effects ) + for ( const auto& i : mitigation_from_target_multiplier_effects ) if ( i.opt_enum & dbc::get_school_mask( school ) ) - state->result_amount *= 1.0 + get_effect_value( i, td ); + m *= 1.0 + get_effect_value( i, td ); + + return m; } void parse_player_effects_t::invalidate_cache( cache_e c ) @@ -1322,7 +1334,7 @@ std::vector* parse_player_effects_t::get_effect_vector( const s tmp.opt_enum = eff.misc_value1(); str = opt_strings::school( tmp.opt_enum ); str += " damage taken"; - return &damage_taken_multiplier_effects; + return &mitigation_multiplier_effects; case A_MOD_ABSORB_DONE_PERCENT: str = "absorb multiplier"; @@ -1379,15 +1391,15 @@ void parse_player_effects_t::debug_message( const player_effect_t& data, std::st void parse_player_effects_t::throw_passive_error( const spell_data_t* s ) { if ( s->flags( SX_PASSIVE ) ) - sim->error( TRIVIAL, - "Parse Effects: Spell `{}` ignored. Passive effects are applied automatically. Please remove this from " - "parse_effects().", - s->name_cstr() ); + { + sim->error( + "Parse Effects: {} is a passive spell and applied automatically. Please remove this from parse_effects().", *s ); + } else - sim->error( TRIVIAL, - "Parse Effects: Spell `{}` was ignored due to being detected as a passive effect. If this is " - "incorrect, please report it.", - s->name_cstr() ); + { + sim->error( + "Parse Effects: {} was determined to be a passive spell and ignored. Please report if incorrect.", *s ); + } } bool parse_player_effects_t::is_valid_target_aura( const spelleffect_data_t& eff ) const @@ -1408,23 +1420,23 @@ std::vector* parse_player_effects_t::get_effect_vector( const s { case A_MOD_DAMAGE_FROM_CASTER: tmp.opt_enum = eff.misc_value1(); - str = opt_strings::school( tmp.opt_enum ); + str = fmt::format( "{} damage taken from {}", opt_strings::school( tmp.opt_enum ), *_player ); return &target_multiplier_effects; case A_MOD_DAMAGE_FROM_CASTER_PET: tmp.opt_enum = 0; - str = "pet"; + str = fmt::format( "pet damage taken from {}", *_player ); return &target_pet_multiplier_effects; case A_MOD_DAMAGE_FROM_CASTER_GUARDIAN: tmp.opt_enum = 1; - str = "guardian"; + str = fmt::format( "guardian damage taken from {}", *_player ); return &target_pet_multiplier_effects; case A_MOD_DAMAGE_TO_CASTER: tmp.opt_enum = eff.misc_value1(); - str = opt_strings::school( tmp.opt_enum ); - return &target_damage_done_multiplier_effects; + str = fmt::format( "{} damage done to {}", opt_strings::school( tmp.opt_enum ), *_player ); + return &mitigation_from_target_multiplier_effects; default: return nullptr; @@ -1436,7 +1448,7 @@ std::vector* parse_player_effects_t::get_effect_vector( const s void parse_player_effects_t::debug_message( const target_effect_t&, std::string_view type_str, std::string_view val_str, const spelleffect_data_t& eff ) { - sim->print_debug( "target-effects: Target {} damage taken modified by {} from {}", type_str, val_str, eff ); + sim->print_debug( "target-effects: Target {} modified by {} from {}", type_str, val_str, eff ); } void parse_player_effects_t::print_custom_parsed_effects( report::sc_html_stream& os ) const @@ -1468,8 +1480,9 @@ void parse_player_effects_t::print_custom_parsed_effects( report::sc_html_stream print_parsed_type( os, crit_chance_effects, "Crit Chance" ); print_parsed_type( os, crit_bonus_effects, "Crit Damage Bonus" ); print_parsed_type( os, dodge_effects, "Dodge" ); - print_parsed_type( os, damage_taken_multiplier_effects, "Damage Taken Multiplier", &opt_strings::school ); - print_parsed_type( os, target_damage_done_multiplier_effects, "Target Damage Done Multiplier", &opt_strings::school ); + print_parsed_type( os, mitigation_multiplier_effects, "Damage Taken Multiplier", &opt_strings::school ); + print_parsed_type( os, mitigation_from_target_multiplier_effects, "Damage Taken From Target Multiplier", + &opt_strings::school ); print_parsed_type( os, expertise_effects, "Expertise" ); print_parsed_type( os, haste_effects, "All Haste" ); print_parsed_type( os, melee_haste_effects, "Melee Haste" ); @@ -1516,8 +1529,8 @@ size_t parse_player_effects_t::total_effects_count() const mastery_effects.size() + parry_rating_from_crit_effects.size() + dodge_effects.size() + - damage_taken_multiplier_effects.size() + - target_damage_done_multiplier_effects.size() + + mitigation_multiplier_effects.size() + + mitigation_from_target_multiplier_effects.size() + absorb_multiplier_effects.size() + healing_received_effects.size() + absorb_received_mult_effects.size() + @@ -1761,16 +1774,15 @@ void parse_action_base_t::debug_message( const player_effect_t& data, std::strin void parse_action_base_t::throw_passive_error( const spell_data_t* s ) { if ( s->flags( SX_PASSIVE ) ) + { _action->sim->error( - TRIVIAL, - "Parse Effects: Spell `{}` ignored. Passive effects are applied automatically. Please remove this from " - "parse_effects().", - s->name_cstr() ); + "Parse Effects: {} is a passive spell and applied automatically. Please remove this from parse_effects().", *s ); + } else - _action->sim->error( TRIVIAL, - "Parse Effects: Spell `{}` was ignored due to being detected as a passive effect. If this is " - "incorrect, please report it.", - s->name_cstr() ); + { + _action->sim->error( + "Parse Effects: {} was determined to be a passive spell and ignored. Please report if incorrect.", *s ); + } } bool parse_action_base_t::is_valid_target_aura( const spelleffect_data_t& eff ) const diff --git a/engine/action/parse_effects.hpp b/engine/action/parse_effects.hpp index fdbd6ef4373..fbc6acc4d6a 100644 --- a/engine/action/parse_effects.hpp +++ b/engine/action/parse_effects.hpp @@ -41,11 +41,12 @@ enum parse_flag_e : uint16_t CONSUME_BUFF = 0x0010, ROUND_VALUE = 0x0020, // uses std::round (round to nearest integer, round half away from zero) IGNORE_WHITELIST = 0x0040, + PARSE_PASSIVE = 0x0080, // force parsing of passive effects // internal flags that should not be used in parse_effects() - VALUE_OVERRIDE = 0x0100, - AFFECTED_OVERRIDE = 0x0200, - MANUAL_ENTRY = 0x0400, - VALUE_FUNCTION = 0x0800 + VALUE_OVERRIDE = 0x1000, + AFFECTED_OVERRIDE = 0x2000, + MANUAL_ENTRY = 0x4000, + VALUE_FUNCTION = 0x8000 }; enum parse_callback_e @@ -346,7 +347,7 @@ struct parse_base_t return; } - if ( mod == ROUND_VALUE ) + if ( mod == ROUND_VALUE || mod == PARSE_PASSIVE ) { pack.data.type |= mod; return; @@ -715,8 +716,8 @@ struct parse_player_effects_t : public player_t, public parse_effects_t std::vector mastery_effects; std::vector parry_rating_from_crit_effects; std::vector dodge_effects; - std::vector damage_taken_multiplier_effects; - std::vector target_damage_done_multiplier_effects; + std::vector mitigation_multiplier_effects; + std::vector mitigation_from_target_multiplier_effects; std::vector absorb_multiplier_effects; std::vector absorb_received_mult_effects; std::vector healing_received_effects; @@ -753,13 +754,14 @@ struct parse_player_effects_t : public player_t, public parse_effects_t double composite_mastery() const override; double composite_parry_rating() const override; double composite_dodge() const override; - double composite_player_absorb_multiplier( const action_state_t* s ) const override; + double composite_player_absorb_multiplier( const action_state_t* ) const override; double composite_player_healing_received_multiplier() const override; double composite_player_absorb_received_multiplier() const override; double composite_player_target_multiplier( player_t*, school_e ) const override; double composite_player_target_pet_damage_multiplier( player_t*, bool ) const override; - - void target_mitigation( school_e, result_amount_type, action_state_t* ) override; + double composite_mitigation_multiplier( const action_state_t*, school_e, bool direct ) const override; + double composite_mitigation_from_player_multiplier( player_t*, const action_state_t*, school_e, + bool direct ) const override; void invalidate_cache( cache_e c ) override; diff --git a/engine/action/spell.cpp b/engine/action/spell.cpp index 98fa6e9e114..2b5fd944b7b 100644 --- a/engine/action/spell.cpp +++ b/engine/action/spell.cpp @@ -81,7 +81,8 @@ result_e spell_base_t::calculate_result( action_state_t* s ) const } } - sim->print_debug( "{} result for {} is {}.", *player, *this, result ); + if ( sim->debug ) + sim->print_debug( "{} result for spell {} is {}.", *player, *this, result ); return result; } diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index b83b0f33aa9..e35366c5a7e 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -2594,6 +2594,7 @@ struct touch_of_death_t : public monk_melee_attack_t may_crit = hasted_ticks = false; may_combo_strike = true; cast_during_sck = true; + ignores_armor = true; // instead use the trick to have no multipliers apply? parse_options( options_str ); cooldown->duration = data().cooldown(); @@ -2606,12 +2607,6 @@ struct touch_of_death_t : public monk_melee_attack_t snapshot_flags = update_flags = 0; } - double composite_target_armor( player_t * ) const override - { - // instead use the trick to have no multipliers apply? - return 0; - } - bool target_ready( player_t *target ) override { // Deals damage equal to 35% of your maximum health against players and stronger creatures under 15% health diff --git a/engine/class_modules/paladin/sc_paladin.cpp b/engine/class_modules/paladin/sc_paladin.cpp index bd8325e0cd4..2a6791ff883 100644 --- a/engine/class_modules/paladin/sc_paladin.cpp +++ b/engine/class_modules/paladin/sc_paladin.cpp @@ -4618,6 +4618,90 @@ double paladin_t::composite_block() const return b; } +// paladin_t::composite_mitigation_multiplier ================================= + +double paladin_t::composite_mitigation_multiplier( const action_state_t* s, school_e school, bool direct ) const +{ + double m = player_t::composite_mitigation_multiplier( s, school, direct ); + + // Passive sources + m *= 1.0 + passives.sanctuary->effectN( 1 ).percent(); + m *= 1.0 + passives.aegis_of_light->effectN( 3 ).percent(); + + // Damage Reduction Cooldowns + if ( buffs.sentinel->up() ) + { + m *= 1.0 + buffs.sentinel->get_damage_reduction_mod(); + } + + if ( buffs.guardian_of_ancient_kings->up() ) + { + m *= 1.0 + buffs.guardian_of_ancient_kings->check_value(); + } + + if ( buffs.ardent_defender->up() ) + { + m *= 1.0 + buffs.ardent_defender->check_value(); + } + + if ( buffs.divine_protection->up() ) + { + m *= 1.0 + buffs.divine_protection->check_value(); + } + + if ( talents.blessing_of_dusk->ok() ) + { + // ToDo Fluttershy: Fix or remove + m *= 1.0 - talents.blessing_of_dusk->effectN( 1 ).percent(); + } + + if ( buffs.devotion_aura->up() ) + { + double devoRed = buffs.devotion_aura->value(); + + if ( talents.lightsmith.shared_resolve->ok() ) + { + if ( buffs.lightsmith.sacred_weapon->up() ) + devoRed *= 1 + buffs.lightsmith.sacred_weapon->data().effectN( 1 ).percent(); + + if ( buffs.lightsmith.holy_bulwark->up() ) + devoRed *= 1 + buffs.lightsmith.holy_bulwark->data().effectN( 1 ).percent(); + } + + m *= 1.0 + devoRed; + } + + if ( buffs.shield_of_the_righteous->up() && spells.sotr_buff->effectN( 3 ).has_common_school( school ) ) + { + m *= 1.0 + buffs.shield_of_the_righteous->check_value(); + } + + if ( specialization() == PALADIN_PROTECTION && standing_in_consecration() ) + { + m *= 1.0 + spells.standing_in_consecration_buff->effectN( 3 ).percent(); + } + + return m; +} + +// paladin_t::composite_mitigation_from_player_multiplier ===================== + +double paladin_t::composite_mitigation_from_player_multiplier( player_t* source, const action_state_t* s, + school_e school, bool direct ) const +{ + double m = player_t::composite_mitigation_from_player_multiplier( source, s, school, direct ); + + if ( auto td = find_target_data( source ) ) + { + if ( td->debuff.empyrean_hammer->up() ) + { + m *= 1.0 + td->debuff.empyrean_hammer->data().effectN( 3 ).percent(); + } + } + + return m; +} + // paladin_t::composite_crit_avoidance ======================================== double paladin_t::composite_crit_avoidance() const @@ -4764,48 +4848,6 @@ void paladin_t::assess_damage( school_e school, result_amount_type dtype, action trigger_grand_crusader(); } - // Holy Shield's magic block - // 2022-11-10 Holy Shield can now only block direct magical damage, standing in Consecration can reduce damage over time, but doesn't proc damage - if ( school != SCHOOL_PHYSICAL && s->action->harmful ) - { - // Block code mimics attack_t::block_chance() - // cache.block() contains our block chance - - // ToDo Fluttershy: Check if we can get double block chance from mastery - - double block = cache.block() * 2.0; - // add or subtract 1.5% per level difference - block += ( level() - s->action->player->level() ) * 0.015; - - auto absorbName = s->result_type != result_amount_type::DMG_OVER_TIME ? "Holy Shield" : "Divine Bulwark"; - - if ( block > 0 ) - { - // Roll for "block" - if ( rng().roll( block ) ) - { - // Can't find a block method so lets just copy+paste from sc_player.cpp - double block_value = composite_block_reduction( s ); - double block_amount = - s->result_amount * - clamp( block_value / ( block_value + s->action->player->current.armor_coeff ), 0.0, 0.85 ); - sim->print_debug( "{} {} absorbs {}", name(), absorbName, block_amount ); - - // update the relevant counters - iteration_absorb_taken += block_amount; - s->self_absorb_amount += block_amount; - s->result_amount -= block_amount; - s->result_absorbed = s->result_amount; - } - else - { - sim->print_debug( "{} {} fails to activate", name(), absorbName ); - } - } - - sim->print_debug( "Damage to {} after {} mitigation is {}", name(), absorbName, s->result_amount ); - } - player_t::assess_damage( school, dtype, s ); } diff --git a/engine/class_modules/paladin/sc_paladin.hpp b/engine/class_modules/paladin/sc_paladin.hpp index 6b3b7a06c3a..49ea05d7e82 100644 --- a/engine/class_modules/paladin/sc_paladin.hpp +++ b/engine/class_modules/paladin/sc_paladin.hpp @@ -345,7 +345,6 @@ struct paladin_t : public player_t const spell_data_t* sanctuary; const spell_data_t* aegis_of_light; - const spell_data_t* aegis_of_light_2; const spell_data_t* art_of_war; const spell_data_t* art_of_war_2; @@ -396,6 +395,7 @@ struct paladin_t : public player_t const spell_data_t* sanctify; const spell_data_t* sotr_buff; + const spell_data_t* standing_in_consecration_buff; const spell_data_t* consecrated_blade; const spell_data_t* crusade; @@ -767,68 +767,71 @@ struct paladin_t : public player_t paladin_t( sim_t* sim, util::string_view name, race_e r = RACE_TAUREN ); - virtual void init_assessors() override; - virtual void init_base_stats() override; - virtual void init_gains() override; - virtual void init_procs() override; - virtual void init() override; - virtual void init_scaling() override; - virtual void create_buffs() override; - virtual void init_special_effects() override; - virtual void init_rng() override; - virtual void init_spells() override; - virtual void init_action_list() override; - virtual void init_blizzard_action_list() override; - virtual void init_proc_data_entries(); + void init_assessors() override; + void init_base_stats() override; + void init_gains() override; + void init_procs() override; + void init() override; + void init_scaling() override; + void create_buffs() override; + void init_special_effects() override; + void init_rng() override; + void init_spells() override; + void init_action_list() override; + void init_blizzard_action_list() override; + void init_proc_data_entries(); std::vector action_names_from_spell_id( unsigned int spell_id ) const override; parsed_assisted_combat_rule_t parse_assisted_combat_rule( const assisted_combat_rule_data_t& rule, const assisted_combat_step_data_t& step ) const override; - virtual bool validate_fight_style( fight_style_e style ) const override; - virtual bool validate_actor() override; - virtual void reset() override; - virtual std::unique_ptr create_expression( util::string_view name ) override; + bool validate_fight_style( fight_style_e style ) const override; + bool validate_actor() override; + void reset() override; + std::unique_ptr create_expression( util::string_view name ) override; // player stat functions - virtual double composite_player_multiplier( school_e ) const override; - virtual double composite_attribute_multiplier( attribute_e attr ) const override; - virtual double composite_attack_power_multiplier() const override; - virtual double composite_bonus_armor() const override; - virtual double composite_melee_crit_chance() const override; - virtual double composite_spell_crit_chance() const override; - virtual double composite_damage_versatility() const override; - virtual double composite_heal_versatility() const override; - virtual double composite_mitigation_versatility() const override; - virtual double composite_mastery() const override; - virtual double composite_melee_haste() const override; - virtual double composite_melee_auto_attack_speed() const override; - virtual double composite_spell_haste() const override; - virtual double composite_crit_avoidance() const override; - virtual double composite_parry() const override; - virtual double composite_parry_rating() const override; - virtual double composite_block() const override; - virtual double non_stacking_movement_modifier() const override; - virtual double composite_player_target_multiplier( player_t* target, school_e school ) const override; - virtual double composite_base_armor_multiplier() const override; - - virtual double resource_gain( resource_e resource_type, double amount, gain_t* source = nullptr, + double composite_player_multiplier( school_e ) const override; + double composite_attribute_multiplier( attribute_e attr ) const override; + double composite_attack_power_multiplier() const override; + double composite_bonus_armor() const override; + double composite_melee_crit_chance() const override; + double composite_spell_crit_chance() const override; + double composite_damage_versatility() const override; + double composite_heal_versatility() const override; + double composite_mitigation_versatility() const override; + double composite_mastery() const override; + double composite_melee_haste() const override; + double composite_melee_auto_attack_speed() const override; + double composite_spell_haste() const override; + double composite_crit_avoidance() const override; + double composite_parry() const override; + double composite_parry_rating() const override; + double composite_block() const override; + double composite_mitigation_multiplier( const action_state_t*, school_e, bool direct ) const override; + double composite_mitigation_from_player_multiplier( player_t*, const action_state_t*, school_e, bool direct ) const override; + double non_stacking_movement_modifier() const override; + double composite_player_target_multiplier( player_t* target, school_e school ) const override; + double composite_base_armor_multiplier() const override; + + double resource_gain( resource_e resource_type, double amount, gain_t* source = nullptr, action_t* action = nullptr ) override; - virtual double resource_loss( resource_e resource_type, double amount, gain_t* source = nullptr, + double resource_loss( resource_e resource_type, double amount, gain_t* source = nullptr, action_t* action = nullptr ) override; // combat outcome functions - virtual void assess_damage( school_e, result_amount_type, action_state_t* ) override; - virtual void target_mitigation( school_e, result_amount_type, action_state_t* ) override; - - virtual void invalidate_cache( cache_e ) override; - virtual void create_options() override; - virtual double matching_gear_multiplier( attribute_e attr ) const override; - virtual void create_actions() override; - virtual action_t* create_action( util::string_view name, util::string_view options_str ) override; - virtual resource_e primary_resource() const override; - virtual role_e primary_role() const override; - virtual stat_e convert_hybrid_stat( stat_e s ) const override; - virtual void combat_begin() override; - virtual void copy_from( player_t* ) override; + void assess_damage( school_e, result_amount_type, action_state_t* ) override; + void target_mitigation( school_e, result_amount_type, action_state_t* ) override; + block_result_e target_block_resolution( const action_state_t* ) const override; + + void invalidate_cache( cache_e ) override; + void create_options() override; + double matching_gear_multiplier( attribute_e attr ) const override; + void create_actions() override; + action_t* create_action( util::string_view name, util::string_view options_str ) override; + resource_e primary_resource() const override; + role_e primary_role() const override; + stat_e convert_hybrid_stat( stat_e s ) const override; + void combat_begin() override; + void copy_from( player_t* ) override; void trigger_grand_crusader( grand_crusader_source source = GC_NORMAL ); void trigger_laying_down_arms(); diff --git a/engine/class_modules/paladin/sc_paladin_holy.cpp b/engine/class_modules/paladin/sc_paladin_holy.cpp index a1e73f975e0..688c4965933 100644 --- a/engine/class_modules/paladin/sc_paladin_holy.cpp +++ b/engine/class_modules/paladin/sc_paladin_holy.cpp @@ -554,6 +554,7 @@ action_t* paladin_t::create_action_holy( util::string_view name, util::string_vi void paladin_t::create_buffs_holy() { buffs.divine_protection = make_buff( this, "divine_protection", find_class_spell( "Divine Protection" ) ) + ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ) ->set_cooldown( 0_ms ); // Handled by the action buffs.infusion_of_light = make_buff( this, "infusion_of_light", find_spell( 54149 ) ); buffs.avenging_crusader = diff --git a/engine/class_modules/paladin/sc_paladin_protection.cpp b/engine/class_modules/paladin/sc_paladin_protection.cpp index 915001ffcad..e19141b01ae 100644 --- a/engine/class_modules/paladin/sc_paladin_protection.cpp +++ b/engine/class_modules/paladin/sc_paladin_protection.cpp @@ -715,83 +715,27 @@ void buffs::sentinel_decay_buff_t::expire_override( int expiration_stacks, times // paladin_t::target_mitigation =============================================== -void paladin_t::target_mitigation( school_e school, - result_amount_type dt, - action_state_t* s ) +void paladin_t::target_mitigation( school_e school, result_amount_type dt, action_state_t* s ) { player_t::target_mitigation( school, dt, s ); - // various mitigation effects, Ardent Defender goes last due to absorb/heal mechanics - - // Passive sources (Sanctuary) - s->result_amount *= 1.0 + passives.sanctuary->effectN( 1 ).percent(); - - if ( passives.aegis_of_light_2->ok() ) - s->result_amount *= 1.0 + passives.aegis_of_light_2->effectN( 1 ).percent(); - - if ( sim->debug && s->action && ! s->target->is_enemy() && ! s->target->is_add() ) - sim->print_debug( "Damage to {} after passive mitigation is {}", s->target->name(), s->result_amount ); - - // Damage Reduction Cooldowns - - // Sentinel - if (buffs.sentinel->up()) - { - s->result_amount *= 1.0 + buffs.sentinel->get_damage_reduction_mod(); - } - - // Guardian of Ancient Kings - if ( buffs.guardian_of_ancient_kings->up() ) - { - s->result_amount *= 1.0 + buffs.guardian_of_ancient_kings->data().effectN( 3 ).percent(); - } - - // Divine Protection - if ( buffs.divine_protection->up() ) + // Mastery 'block' of periodic damage (absorbed) + if ( s->block_result == BLOCK_RESULT_BLOCKED && dt == result_amount_type::DMG_OVER_TIME ) { - s->result_amount *= 1.0 + buffs.divine_protection->data().effectN( 1 ).percent(); - } - - if ( buffs.ardent_defender->up() ) - { - double adReduce = buffs.ardent_defender->data().effectN( 1 ).percent(); - s->result_amount *= 1.0 + adReduce; - } + auto block_value = s->target_block_value; + auto block_resist = util::calculate_armor_resist( block_value, s->action->player->current.armor_coeff ); + auto block_amount = s->result_amount * block_resist; - if ( talents.blessing_of_dusk->ok() ) - { - // ToDo Fluttershy: Fix or remove - s->result_amount *= 1.0 - talents.blessing_of_dusk->effectN( 1 ).percent(); - } + // update the relevant counters + iteration_absorb_taken += block_amount; + s->self_absorb_amount += block_amount; + s->result_amount -= block_amount; + s->result_absorbed = s->result_amount; - if ( buffs.devotion_aura->up() ) - { - double devoRed = buffs.devotion_aura->value(); - if ( talents.lightsmith.shared_resolve->ok() && ( buffs.lightsmith.sacred_weapon->up() || buffs.lightsmith.holy_bulwark->up() ) ) + if ( sim->debug ) { - devoRed *= 1 + buffs.lightsmith.holy_bulwark->data().effectN( 1 ).percent(); // Not sure why this is in Holy Bulwark's spell data + sim->print_debug( "{} Divine Bulwark absorbs {} damage from block on DOT {}.", *this, block_amount, *s->action ); } - s->result_amount *= 1.0 + devoRed; - } - - if (buffs.shield_of_the_righteous->up()) - { - s->result_amount *= 1.0 + buffs.shield_of_the_righteous->default_value; - } - - paladin_td_t* td = get_target_data( s->action->player ); - - if (td->debuff.empyrean_hammer->up()) - { - s->result_amount *= 1.0 + td->debuff.empyrean_hammer->data().effectN( 3 ).percent(); - } - - // Divine Bulwark and consecration reduction - if ( standing_in_consecration() && specialization() == PALADIN_PROTECTION ) - { - // ToDo Fluttershy: Get the 0.0 from 188370 effect 3 - double reduction = 0.0 + talents.sanctuary->effectN( 2 ).percent(); - s->result_amount *= 1.0 + reduction; } // Blessed Hammer @@ -825,11 +769,6 @@ void paladin_t::target_mitigation( school_e school, } } - // Other stuff - if ( sim->debug && s->action && ! s->target->is_enemy() && ! s->target->is_add() ) - sim->print_debug( "Damage to {} after mitigation effects is {}", s->target->name(), s->result_amount ); - - // Ardent Defender if ( buffs.ardent_defender->check() ) { @@ -864,6 +803,28 @@ void paladin_t::target_mitigation( school_e school, } } +block_result_e paladin_t::target_block_resolution( const action_state_t* s ) const +{ + double block_chance = 0.0; + + // Normal block for direct physical attacks + if ( s->result_type == result_amount_type::DMG_DIRECT && s->action->get_school() == SCHOOL_PHYSICAL && + s->action->type == ACTION_ATTACK ) + { + block_chance = cache.block(); + } + // Spell block + else if ( s->action->get_school() != SCHOOL_PHYSICAL && s->action->type == ACTION_SPELL ) + { + block_chance = cache.mastery() * mastery.divine_bulwark_2->effectN( 1 ).mastery_value(); + } + + if ( rng().roll( block_chance ) ) + return BLOCK_RESULT_BLOCKED; + else + return BLOCK_RESULT_UNBLOCKED; +} + void paladin_t::trigger_grand_crusader( grand_crusader_source /* source */ ) { // escape if we don't have Grand Crusader @@ -927,10 +888,11 @@ action_t* paladin_t::create_action_protection( util::string_view name, util::str void paladin_t::create_buffs_protection() { - buffs.ardent_defender = - make_buff( this, "ardent_defender", find_spell( 31850 ) ) - ->set_cooldown( 0_ms ); // handled by the ability + buffs.ardent_defender = make_buff( this, "ardent_defender", find_spell( 31850 ) ) + ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ) + ->set_cooldown( 0_ms ); // handled by the ability buffs.guardian_of_ancient_kings = make_buff( this, "guardian_of_ancient_kings", find_spell( 86659 ) ) + ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ) ->set_cooldown( 0_ms ); //HS and BH fake absorbs buffs.divine_bulwark_absorb = make_buff( this, "divine_bulwark", mastery.divine_bulwark ); @@ -1040,6 +1002,7 @@ void paladin_t::init_spells_protection() spec.judgment_4 = find_rank_spell( "Judgment", "Rank 4" ); spells.judgment_debuff = find_spell( 197277 ); + spells.standing_in_consecration_buff = find_spell( 188370 ); } spec.shield_of_the_righteous = find_class_spell( "Shield of the Righteous" ); @@ -1047,9 +1010,7 @@ void paladin_t::init_spells_protection() passives.riposte = find_specialization_spell( "Riposte" ); passives.sanctuary = find_specialization_spell( "Sanctuary" ); - passives.aegis_of_light = find_specialization_spell( "Aegis of Light" ); - passives.aegis_of_light_2 = find_rank_spell( "Aegis of Light", "Rank 2" ); spells.sentinel = find_spell( 389539 ); spells.refining_fire_tick = find_spell( 469882 ); diff --git a/engine/class_modules/priest/sc_priest.cpp b/engine/class_modules/priest/sc_priest.cpp index 0c242e5b168..26330ea84a0 100644 --- a/engine/class_modules/priest/sc_priest.cpp +++ b/engine/class_modules/priest/sc_priest.cpp @@ -2656,6 +2656,22 @@ double priest_t::composite_attribute_multiplier( attribute_e attr ) const return mul; } +double priest_t::composite_mitigation_multiplier( const action_state_t* s, school_e school, bool direct ) const +{ + double m = player_t::composite_mitigation_multiplier( s, school, direct ); + + if ( talents.translucent_image.ok() && buffs.fade->check() ) + m *= 1.0 + buffs.fade->check_value(); + + if ( buffs.protective_light->check() ) + m *= 1.0 + buffs.protective_light->check_value(); + + if ( buffs.dispersion->check() ) + m *= 1.0 + specs.dispersion->effectN( 1 ).percent(); + + return m; +} + void priest_t::pre_analyze_hook() { player_t::pre_analyze_hook(); @@ -3164,7 +3180,7 @@ void priest_t::create_buffs() // Generic buffs buffs.desperate_prayer = make_buff( *this ); buffs.power_word_shield = new buffs::power_word_shield_buff_t( this, this ); - buffs.fade = make_buff( this, "fade", find_class_spell( "Fade" ) )->set_default_value_from_effect( 1 ); + buffs.fade = make_buff( this, "fade", find_class_spell( "Fade" ) )->set_default_value_from_effect( 4 ); buffs.levitate = make_buff( this, "levitate", specs.levitate_buff )->set_duration( timespan_t::zero() ); // Shared talent buffs @@ -3783,26 +3799,6 @@ void priest_t::reset() state = state_t(); } -void priest_t::target_mitigation( school_e school, result_amount_type dt, action_state_t* s ) -{ - base_t::target_mitigation( school, dt, s ); - - if ( buffs.dispersion->check() ) - { - s->result_amount *= 1.0 + buffs.dispersion->data().effectN( 1 ).percent(); - } - - if ( talents.translucent_image.enabled() && buffs.fade->up() ) - { - s->result_amount *= 1.0 + talents.fade->effectN( 4 ).percent(); - } - - if ( talents.protective_light.enabled() && buffs.protective_light->up() ) - { - s->result_amount *= 1.0 + talents.protective_light_buff->effectN( 1 ).percent(); - } -} - void priest_t::create_options() { base_t::create_options(); @@ -4204,10 +4200,12 @@ struct priest_module_t final : public module_t { p->buffs.body_and_soul = make_buff( p, "body_and_soul", p->find_spell( 65081 ) ); p->buffs.angelic_feather = make_buff( p, "angelic_feather", p->find_spell( 121557 ) ); - p->buffs.guardian_spirit = make_buff( p, "guardian_spirit", - p->find_spell( 47788 ) ); // Let the ability handle the CD - p->buffs.pain_suppression = make_buff( p, "pain_suppression", - p->find_spell( 33206 ) ); // Let the ability handle the CD + p->buffs.guardian_spirit = make_buff( p, "guardian_spirit", p->find_spell( 47788 ) ) + ->set_default_value_from_effect_type( A_MOD_HEALING_RECEIVED_PCT ) + ->set_cooldown( 0_ms ); // Let the ability handle the CD + p->buffs.pain_suppression = make_buff( p, "pain_suppression", p->find_spell( 33206 ) ) + ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ) + ->set_cooldown( 0_ms ); // Let the ability handle the CD } void static_init() const override { diff --git a/engine/class_modules/priest/sc_priest.hpp b/engine/class_modules/priest/sc_priest.hpp index 44da30200cb..aad05eb617c 100644 --- a/engine/class_modules/priest/sc_priest.hpp +++ b/engine/class_modules/priest/sc_priest.hpp @@ -941,10 +941,10 @@ struct priest_t final : public player_t double composite_player_target_multiplier( player_t* t, school_e school ) const override; double composite_leech() const override; double composite_attribute_multiplier( attribute_e ) const override; + double composite_mitigation_multiplier( const action_state_t*, school_e, bool direct ) const override; void pre_analyze_hook() override; void analyze( sim_t& sim ) override; double matching_gear_multiplier( attribute_e attr ) const override; - void target_mitigation( school_e, result_amount_type, action_state_t* ) override; void init_action_list() override; void init_blizzard_action_list() override; void parse_assisted_combat_step( const assisted_combat_step_data_t& step, diff --git a/engine/class_modules/sc_death_knight.cpp b/engine/class_modules/sc_death_knight.cpp index 07b8869dd52..72a8460c17b 100644 --- a/engine/class_modules/sc_death_knight.cpp +++ b/engine/class_modules/sc_death_knight.cpp @@ -1631,6 +1631,7 @@ struct death_knight_t : public parse_player_effects_t const spell_data_t* razorice_debuff; const spell_data_t* sanguination_cooldown; const spell_data_t* sanguination_heal; + const spell_data_t* spellwarding_driver; const spell_data_t* spellwarding_absorb; const spell_data_t* stoneskin_gargoyle; const spell_data_t* unending_thirst; @@ -1992,7 +1993,6 @@ struct death_knight_t : public parse_player_effects_t void adjust_dynamic_cooldowns() override; void assess_damage( school_e, result_amount_type, action_state_t* ) override; void assess_damage_imminent( school_e, result_amount_type, action_state_t* ) override; - void target_mitigation( school_e, result_amount_type, action_state_t* ) override; void do_damage( action_state_t* ) override; void create_actions() override; action_t* create_action( std::string_view name, std::string_view options ) override; @@ -14778,6 +14778,7 @@ void death_knight_t::spell_lookups() runeforge_spell.razorice_debuff = conditional_spell_lookup( spec.glacial_advance->ok() || has_runeforge( RUNEFORGE_RAZORICE ), 51714 ); runeforge_spell.sanguination_cooldown = conditional_spell_lookup( has_runeforge( RUNEFORGE_SANGUINATION ), 326809 ); runeforge_spell.sanguination_heal = conditional_spell_lookup( has_runeforge( RUNEFORGE_SANGUINATION ), 326808 ); + runeforge_spell.spellwarding_driver = conditional_spell_lookup( has_runeforge( RUNEFORGE_SPELLWARDING ), 326864 ); runeforge_spell.spellwarding_absorb = conditional_spell_lookup( has_runeforge( RUNEFORGE_SPELLWARDING ), 326867 ); runeforge_spell.stoneskin_gargoyle = conditional_spell_lookup( has_runeforge( RUNEFORGE_STONESKIN_GARGOYLE ), 62157 ); runeforge_spell.unending_thirst = conditional_spell_lookup( has_runeforge( RUNEFORGE_UNENDING_THIRST ), 326984 ); @@ -16224,36 +16225,6 @@ void death_knight_t::do_damage( action_state_t* state ) } } -// death_knight_t::target_mitigation ======================================== - -void death_knight_t::target_mitigation( school_e school, result_amount_type type, action_state_t* state ) -{ - if ( buffs.icebound_fortitude->up() && buffs.icebound_fortitude->data().effectN( 3 ).has_common_school( school ) ) - state->result_amount *= 1.0 + buffs.icebound_fortitude->data().effectN( 3 ).percent(); - - if ( buffs.bloodsoaked_ground->up() && buffs.bloodsoaked_ground->data().effectN( 1 ).has_common_school( school ) ) - state->result_amount *= 1.0 + buffs.bloodsoaked_ground->data().effectN( 1 ).percent(); - - const death_knight_td_t* td = get_target_data( state->action->player ); - if ( td && has_runeforge( RUNEFORGE_APOCALYPSE ) && - runeforge_spell.apocalypse_famine_debuff->effectN( 1 ).has_common_school( school ) ) - state->result_amount *= 1.0 + td->debuff.apocalypse_famine->check_stack_value(); - - if ( has_runeforge( RUNEFORGE_SPELLWARDING ) && - runeforge_spell.spellwarding_absorb->effectN( 2 ).has_common_school( school ) ) - { - double val = 0; - if ( mh_runeforge == RUNEFORGE_SPELLWARDING ) - val += runeforge_spell.spellwarding_absorb->effectN( 2 ).percent(); - if ( oh_runeforge == RUNEFORGE_SPELLWARDING ) - val += runeforge_spell.spellwarding_absorb->effectN( 2 ).percent(); - - state->result_amount *= 1.0 + val; - } - - player_t::target_mitigation( school, type, state ); -} - // death_knight_t::composite_bonus_armor ========================================= double death_knight_t::composite_bonus_armor() const @@ -16618,8 +16589,18 @@ void death_knight_t::parse_player_effects() parse_effects( buffs.icy_talons, talent.icy_talons ); parse_effects( buffs.rune_mastery ); parse_effects( buffs.antimagic_shell ); + parse_effects( buffs.icebound_fortitude ); + + if ( has_runeforge( RUNEFORGE_SPELLWARDING ) ) + { + double val = ( ( mh_runeforge == RUNEFORGE_SPELLWARDING ) + ( oh_runeforge == RUNEFORGE_SPELLWARDING ) ) * + runeforge_spell.spellwarding_driver->effectN( 2 ).percent(); + parse_effects( runeforge_spell.spellwarding_driver, val, PARSE_PASSIVE ); + } + parse_target_effects( d_fn( &death_knight_td_t::debuffs_t::brittle ), spell.brittle_debuff ); parse_target_effects( d_fn( &death_knight_td_t::debuffs_t::apocalypse_war ), runeforge_spell.apocalypse_war_debuff ); + parse_target_effects( d_fn( &death_knight_td_t::debuffs_t::apocalypse_famine ), runeforge_spell.apocalypse_famine_debuff ); switch ( specialization() ) { @@ -16677,6 +16658,7 @@ void death_knight_t::parse_player_effects() pet_spell.trollbanes_chains_of_ice_debuff ); break; case HERO_SANLAYN: + parse_effects( buffs.bloodsoaked_ground ); parse_effects( buffs.essence_of_the_blood_queen, effect_mask_t( true ).disable( 3 ), [ & ]( double v ) { v *= 0.1; // Divides by 10 in spell data if ( buffs.gift_of_the_sanlayn->check() ) diff --git a/engine/class_modules/sc_demon_hunter.cpp b/engine/class_modules/sc_demon_hunter.cpp index 7daa663cc48..12e16bbf45e 100644 --- a/engine/class_modules/sc_demon_hunter.cpp +++ b/engine/class_modules/sc_demon_hunter.cpp @@ -875,8 +875,6 @@ class demon_hunter_t : public parse_player_effects_t const spell_data_t* sigil_of_flame_damage; const spell_data_t* sigil_of_flame_fury; - const spell_data_t* demonic_wards_2; - const spell_data_t* demonic_wards_3; const spell_data_t* fiery_brand_debuff; const spell_data_t* frailty_debuff; const spell_data_t* riposte; @@ -1307,7 +1305,6 @@ class demon_hunter_t : public parse_player_effects_t void merge( player_t& other ) override; void datacollection_begin() override; void datacollection_end() override; - void target_mitigation( school_e, result_amount_type, action_state_t* ) override; void analyze( sim_t& sim ) override; // custom demon_hunter_t functions @@ -10385,8 +10382,6 @@ void demon_hunter_t::init_spells() // Spec-Overriden Passives spec.demonic_wards = find_specialization_spell( "Demonic Wards" ); - spec.demonic_wards_2 = find_rank_spell( "Demonic Wards", "Rank 2" ); - spec.demonic_wards_3 = find_rank_spell( "Demonic Wards", "Rank 3" ); spec.immolation_aura_cdr = find_spell( 320378, DEMON_HUNTER_VENGEANCE ); spec.thick_skin = find_specialization_spell( "Thick Skin" ); @@ -11840,57 +11835,6 @@ void demon_hunter_t::recalculate_resource_max( resource_e r, gain_t* source ) } } -// demon_hunter_t::target_mitigation ======================================== - -void demon_hunter_t::target_mitigation( school_e school, result_amount_type dt, action_state_t* s ) -{ - base_t::target_mitigation( school, dt, s ); - - if ( s->result_amount <= 0 ) - { - return; - } - - if ( dbc::get_school_mask( school ) & SCHOOL_MAGIC_MASK ) - { - s->result_amount *= 1.0 + talent.demon_hunter.illidari_knowledge->effectN( 1 ).percent(); - } - - const demon_hunter_td_t* td = get_target_data( s->action->player ); - switch ( specialization() ) - { - case DEMON_HUNTER_DEVOURER: - s->result_amount *= 1.0 + buff.blur->value(); - - break; - case DEMON_HUNTER_HAVOC: - s->result_amount *= 1.0 + buff.blur->value(); - - if ( dbc::get_school_mask( school ) & SCHOOL_MAGIC_MASK ) - { - s->result_amount *= - 1.0 + spec.demonic_wards->effectN( 1 ).percent() + spec.demonic_wards_2->effectN( 1 ).percent(); - } - - if ( dbc::get_school_mask( school ) & SCHOOL_MASK_PHYSICAL ) - { - s->result_amount *= 1.0 + talent.havoc.demon_hide->effectN( 2 ).percent(); - } - break; - case DEMON_HUNTER_VENGEANCE: - s->result_amount *= 1.0 + spec.demonic_wards->effectN( 1 ).percent() + - spec.demonic_wards_2->effectN( 1 ).percent() + spec.demonic_wards_3->effectN( 1 ).percent(); - - if ( td->debuffs.frailty->check() && talent.vengeance.void_reaver->ok() ) - { - s->result_amount *= 1.0 + spec.frailty_debuff->effectN( 3 ).percent(); - } - break; - default: - break; - } -} - // demon_hunter_t::reset ==================================================== void demon_hunter_t::reset() @@ -12380,6 +12324,9 @@ void demon_hunter_t::trigger_voidsurge( const voidsurge_ability ability, timespa void demon_hunter_t::parse_player_effects() { // Shared + parse_effects( talent.demon_hunter.illidari_knowledge, PARSE_PASSIVE ); + parse_effects( talent.havoc.demon_hide, PARSE_PASSIVE ); + parse_effects( spec.demonic_wards, PARSE_PASSIVE ); // Devourer if ( specialization() == DEMON_HUNTER_DEVOURER ) @@ -12404,6 +12351,12 @@ void demon_hunter_t::parse_player_effects() parse_effects( mastery.fel_blood_rank_2 ); parse_effects( buff.fiery_brand ); parse_effects( buff.painbringer ); + + if ( talent.vengeance.void_reaver.ok() ) + { + parse_target_effects( d_fn( &demon_hunter_td_t::debuffs_t::frailty ), spec.frailty_debuff ); + } + } // Aldrachi Reaver diff --git a/engine/class_modules/sc_druid.cpp b/engine/class_modules/sc_druid.cpp index 58c87995b79..021f62b99f7 100644 --- a/engine/class_modules/sc_druid.cpp +++ b/engine/class_modules/sc_druid.cpp @@ -803,7 +803,6 @@ struct druid_t final : public parse_player_effects_t buff_t* implant; buff_t* killing_strikes; buff_t* killing_strikes_combat; - buff_t* protective_growth; buff_t* ravage_fb; buff_t* ravage_maul; buff_t* root_network; @@ -1359,7 +1358,6 @@ struct druid_t final : public parse_player_effects_t stat_e convert_hybrid_stat( stat_e s ) const override; double resource_regen_per_second( resource_e ) const override; double resource_gain( resource_e, double, gain_t*, action_t* a = nullptr ) override; - void target_mitigation( school_e, result_amount_type, action_state_t* ) override; void assess_damage( school_e, result_amount_type, action_state_t* ) override; void recalculate_resource_max( resource_e, gain_t* source = nullptr ) override; void create_options() override; @@ -2533,9 +2531,9 @@ struct druid_attack_t : public druid_action_t } } - double composite_target_armor( player_t* t ) const override + double composite_target_armor( const action_state_t* s ) const override { - return direct_bleed ? 0.0 : ab::composite_target_armor( t ); + return direct_bleed ? 0.0 : ab::composite_target_armor( s ); } }; @@ -6378,9 +6376,6 @@ struct regrowth_t final : public trigger_thriving_growth_tbuff.forestwalk->trigger( this ); - if ( target == p() ) - p()->buff.protective_growth->trigger(); - p()->buff.blooming_infusion_damage_counter->trigger( this ); if ( p()->buff.blooming_infusion_damage_counter->at_max_stacks() ) { @@ -6393,14 +6388,6 @@ struct regrowth_t final : public trigger_thriving_growth_tcheck() ) boon_of_the_oathsworn_hack->trigger(); } - - void last_tick( dot_t* d ) override - { - base_t::last_tick( d ); - - if ( d->target == p() ) - p()->buff.protective_growth->expire(); - } }; // Rejuvenation ============================================================= @@ -10519,7 +10506,7 @@ void druid_t::init_spells() talent.potent_enchantments = HT( "Potent Enchantments" ); talent.power_of_nature = HT( "Power of Nature" ); // TODO: grove guardian buff NYI talent.power_of_the_dream = HT( "Power of the Dream" ); - talent.protective_growth = HT( "Protective Growth" ); + talent.protective_growth = HT( "Protective Growth" ); // TODO: NYI talent.spirit_of_the_thicket = HT( "Spirit of the Thicket" ); talent.sylvan_beckoning = HT( "Sylvan Beckoning" ); talent.treants_of_the_moon = HT( "Treants of the Moon" ); @@ -10808,7 +10795,6 @@ void druid_t::create_buffs() // Baseline buff.barkskin = make_buff( this, "barkskin", find_class_spell( "Barkskin" ) ) ->set_cooldown( 0_ms ) - ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ) ->set_refresh_behavior( buff_refresh_behavior::DURATION ) ->set_tick_behavior( buff_tick_behavior::NONE ); if ( talent.brambles.ok() ) @@ -10918,8 +10904,7 @@ void druid_t::create_buffs() // The buff ID in-game is same as the talent, 61336, but the buff effect is in the hidden buff 50322 buff.survival_instincts = make_fallback( talent.survival_instincts.ok(), this, "survival_instincts", talent.survival_instincts ) - ->set_cooldown( 0_ms ) - ->set_default_value( find_effect( find_spell( 50322 ), A_MOD_DAMAGE_PERCENT_TAKEN ).percent() ); + ->set_cooldown( 0_ms ); // Balance buffs buff.ascendant_fires = make_fallback( talent.ascendant_eclipses_1.ok(), @@ -11478,10 +11463,6 @@ void druid_t::create_buffs() make_fallback( talent.killing_strikes.ok(), this, "killing_strikes_combat", find_spell( 441827 ) ) ->set_quiet( true ); - buff.protective_growth = - make_fallback( talent.protective_growth.ok(), this, "protective_growth", find_spell( 433749 ) ) - ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ); - buff.ravage_fb = make_fallback( talent.ravage.ok() && specialization() == DRUID_FERAL, this, "ravage", find_spell( 441585 ) ) ->set_trigger_spell( talent.ravage ); @@ -13193,7 +13174,7 @@ void druid_t::invalidate_cache( cache_e c ) // Composite combat stat override functions ================================= -// Armor ==================================================================== +// Defense ================================================================== double druid_t::composite_armor() const { double a = player_t::composite_armor(); @@ -13212,7 +13193,6 @@ double druid_t::composite_armor() const return a; } -// Defense ================================================================== double druid_t::composite_dodge_rating() const { double dr = player_t::composite_dodge_rating(); @@ -13663,66 +13643,6 @@ void druid_t::init_absorb_priority() add_absorb( buff.brambles ); // brambles always goes first } -void druid_t::target_mitigation( school_e school, result_amount_type rt, action_state_t* s ) -{ - s->result_amount *= 1.0 + buff.barkskin->value(); - - s->result_amount *= 1.0 + buff.survival_instincts->value(); - - s->result_amount *= 1.0 + talent.thick_hide->effectN( 1 ).percent(); - - s->result_amount *= 1.0 + buff.protective_growth->check_value(); - - if ( spec.ursine_adept->ok() && form == BEAR_FORM ) - s->result_amount *= 1.0 + spec.ursine_adept->effectN( 2 ).percent(); - - // as this is run-time, we can't use find_effect. TODO: possibly cache these values somewhere - if ( talent.glistening_fur.ok() ) - { - switch ( form ) - { - case BEAR_FORM: - if ( dbc::is_school( school, SCHOOL_ARCANE ) ) - s->result_amount *= 1.0 + buff.bear_form->data().effectN( 14 ).percent(); - else - s->result_amount *= 1.0 + buff.bear_form->data().effectN( 13 ).percent(); - break; - - case MOONKIN_FORM: - if ( dbc::is_school( school, SCHOOL_ARCANE ) ) - s->result_amount *= 1.0 + buff.moonkin_form->data().effectN( 13 ).percent(); - else - s->result_amount *= 1.0 + buff.moonkin_form->data().effectN( 12 ).percent(); - break; - - default: break; - } - } - - if ( talent.empowered_shapeshifting.ok() && form == BEAR_FORM && - spec.bear_form_passive_2->effectN( 3 ).has_common_school( school ) ) - { - s->result_amount *= 1.0 + talent.empowered_shapeshifting->effectN( 4 ).percent(); - } - - if ( s->action->player != this ) - { - if ( auto td = find_target_data( s->action->player ) ) - { - if ( talent.rend_and_tear.ok() ) - s->result_amount *= 1.0 + talent.rend_and_tear->effectN( 3 ).percent() * td->dots.thrash->current_stack(); - - if ( talent.scintillating_moonlight.ok() && td->dots.moonfire->is_ticking() ) - s->result_amount *= 1.0 + talent.scintillating_moonlight->effectN( 1 ).percent(); - - if ( talent.dreadful_wound.ok() && td->dots.dreadful_wound->is_ticking() ) - s->result_amount *= 1.0 + spec.dreadful_wound->effectN( 2 ).percent(); - } - } - - player_t::target_mitigation( school, rt, s ); -} - void druid_t::assess_damage( school_e school, result_amount_type dtype, action_state_t* s ) { if ( dbc::is_school( school, SCHOOL_PHYSICAL ) && dtype == result_amount_type::DMG_DIRECT && s->result == RESULT_HIT ) @@ -14138,6 +14058,8 @@ void druid_t::parse_action_effects( action_t* action ) assert( _a ); // Class + _a->parse_effects( buff.bear_form ); + _a->parse_effects( spec.bear_form_passive_2, [ this ] { return form == BEAR_FORM; } ); _a->parse_effects( buff.cat_form ); _a->parse_effects( spec.cat_form_passive_2, [ this ] { return form == CAT_FORM; } ); _a->parse_effects( buff.moonkin_form ); @@ -14216,9 +14138,6 @@ void druid_t::parse_action_effects( action_t* action ) _a->parse_effects( buff.unseen_predators_craving, talent.unseen_predator_2->effectN( 1 ).percent() ); // Guardian - _a->parse_effects( buff.bear_form ); - _a->parse_effects( spec.bear_form_passive_2, [ this ] { return form == BEAR_FORM; } ); - _a->parse_effects( buff.berserk_bear, effect_mask_t( true ).disable( 6, 7, 8, 9 ) ); _a->parse_effects( buff.incarnation_bear, effect_mask_t( true ).disable( 6, 7, 8, 9, 14, 15, 16, 17 ) ); // additional effects if astral insight is talented @@ -14265,19 +14184,12 @@ void druid_t::parse_action_target_effects( action_t* action ) auto _a = dynamic_cast( action ); assert( _a ); - // Balance + _a->parse_target_effects( d_fn( &druid_td_t::debuffs_t::atmospheric_exposure ), spec.atmospheric_exposure ); + _a->parse_target_effects( d_fn( &druid_td_t::debuffs_t::sabertooth ), spec.sabertooth, + talent.sabertooth->effectN( 2 ).percent() ); _a->parse_target_effects( d_fn( &druid_td_t::debuffs_t::stellar_amplification ), spec.stellar_amplification ); - - // Feral - _a->parse_target_effects( d_fn( &druid_td_t::debuffs_t::sabertooth ), - spec.sabertooth, talent.sabertooth->effectN( 2 ).percent() ); - - // Guardian _a->parse_target_effects( d_fn( &druid_td_t::dots_t::thrash ), spec.thrash_bleed ); - // Hero talent - _a->parse_target_effects( d_fn( &druid_td_t::debuffs_t::atmospheric_exposure ), spec.atmospheric_exposure ); - if ( talent.exacerbating_wounds.ok() ) _a->parse_target_effects( d_fn( &druid_td_t::dots_t::dreadful_wound ), spec.dreadful_wound ); } @@ -14285,14 +14197,25 @@ void druid_t::parse_action_target_effects( action_t* action ) void druid_t::parse_player_effects() { parse_effects( mastery.natures_guardian_AP ); - parse_effects( spec.bear_form_passive, [ this ] { return buff.bear_form->check(); } ); + parse_effects( talent.thick_hide, PARSE_PASSIVE ); - if ( talent.lycaras_inspiration.ok() ) - parse_effects( buff.lycaras_teachings_crit ); + parse_effects( buff.bear_form, effect_mask_t( true ).disable( 13, 14 ) ); + parse_effects( spec.bear_form_passive, [ this ] { return form == BEAR_FORM; } ); + parse_effects( spec.bear_form_passive_2, [ this ] { return form == BEAR_FORM; } ); + parse_effects( buff.moonkin_form, effect_mask_t( true ).disable( 12, 13 ) ); - parse_effects( buff.bear_form ); + if ( talent.glistening_fur.ok() ) + { + parse_effects( buff.bear_form, effect_mask_t( false ).enable( 13, 14 ) ); + parse_effects( buff.moonkin_form, effect_mask_t( false ).enable( 12, 13 ) ); + } + + parse_effects( buff.barkskin ); parse_effects( buff.killing_strikes ); + if ( talent.lycaras_inspiration.ok() ) + parse_effects( buff.lycaras_teachings_crit ); + parse_effects( buff.persistence, effect_mask_t( false ).enable( 1 ), // stamina find_effect( spec.bear_form_passive, A_MOD_TOTAL_STAT_PERCENTAGE ).percent() / buff.persistence->max_stack() ); @@ -14300,6 +14223,7 @@ void druid_t::parse_player_effects() find_effect( buff.bear_form, A_MOD_BASE_RESISTANCE_PCT ).percent() / buff.persistence->max_stack() ); parse_effects( buff.ruthless_aggression ); + parse_effects( buff.survival_instincts ); parse_effects( buff.ursine_vigor, talent.ursine_vigor ); parse_effects( buff.wildshape_mastery, effect_mask_t( false ).enable( 2 ), // base armor find_effect( buff.bear_form, A_MOD_BASE_RESISTANCE_PCT ).percent() * @@ -14310,6 +14234,15 @@ void druid_t::parse_player_effects() parse_effects( buff.wildshape_mastery, effect_mask_t( false ).enable( 4, 5 ) ); // crit avoidance & expertise parse_target_effects( d_fn( &druid_td_t::debuffs_t::bloodseeker_vines ), spec.bloodseeker_vines ); + parse_target_effects( d_fn( &druid_td_t::dots_t::dreadful_wound ), spec.dreadful_wound ); + + if ( talent.scintillating_moonlight.ok() ) + { + parse_target_effects( d_fn( &druid_td_t::dots_t::moonfire ), spec.moonfire_dmg, effect_mask_t( false ).enable( 6 ), + -talent.scintillating_moonlight->effectN( 1 ).percent() ); + } + + parse_target_effects( d_fn( &druid_td_t::dots_t::thrash ), spec.thrash_bleed ); } /* Report Extension Class diff --git a/engine/class_modules/sc_enemy.cpp b/engine/class_modules/sc_enemy.cpp index eb153f6414c..ef6cc3a3816 100644 --- a/engine/class_modules/sc_enemy.cpp +++ b/engine/class_modules/sc_enemy.cpp @@ -259,6 +259,14 @@ struct enemy_action_t : public ACTION_TYPE return tl.size(); } + void snapshot_internal( action_state_t* s, unsigned fl, result_amount_type rt ) override + { + action_type_t::snapshot_internal( s, fl, rt ); + + if ( s->target->is_player() ) + s->target_block_value = s->target->composite_block_value( s ); + } + double calculate_direct_amount( action_state_t* s ) const override { // force boss attack size to vary regardless of whether the sim itself does diff --git a/engine/class_modules/sc_evoker.cpp b/engine/class_modules/sc_evoker.cpp index 45d2abf97de..036f561ee12 100644 --- a/engine/class_modules/sc_evoker.cpp +++ b/engine/class_modules/sc_evoker.cpp @@ -1657,11 +1657,11 @@ struct evoker_t : public player_t double composite_player_target_crit_chance( player_t* target ) const override; double composite_spell_haste() const override; double composite_melee_haste() const override; + double composite_mitigation_multiplier( const action_state_t*, school_e, bool direct ) const override; stat_e convert_hybrid_stat( stat_e ) const override; double non_stacking_movement_modifier() const override; double stacking_movement_modifier() const override; double resource_regen_per_second( resource_e ) const override; - void target_mitigation( school_e, result_amount_type, action_state_t* ) override; void bounce_naszuro( player_t*, timespan_t ); @@ -10075,8 +10075,9 @@ void evoker_t::create_buffs() buff.leaping_flames = MBF( talent.leaping_flames.ok(), this, "leaping_flames", find_spell( 370901 ) ); - buff.obsidian_scales = - MBF( talent.obsidian_scales.ok(), this, "obsidian_scales", talent.obsidian_scales )->set_cooldown( 0_ms ); + buff.obsidian_scales = MBF( talent.obsidian_scales.ok(), this, "obsidian_scales", talent.obsidian_scales ) + ->set_cooldown( 0_ms ) + ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ); buff.scarlet_adaptation = MBF( talent.scarlet_adaptation.ok(), this, "scarlet_adaptation", find_spell( 372470 ) ) ->set_constant_behavior( buff_constant_behavior::NEVER_CONSTANT ); @@ -10751,6 +10752,23 @@ double evoker_t::composite_melee_haste() const return h; } +double evoker_t::composite_mitigation_multiplier( const action_state_t* s, school_e school, bool direct ) const +{ + double m = player_t::composite_mitigation_multiplier( s, school, direct ); + + if ( buff.obsidian_scales->check() ) + m *= 1.0 + buff.obsidian_scales->check_value(); + + if ( talent.inherent_resistance.ok() ) + { + const auto& eff = talent.inherent_resistance->effectN( 1 ); + if ( eff.has_common_school( school ) ) + m *= 1.0 + eff.percent(); + } + + return m; +} + stat_e evoker_t::convert_hybrid_stat( stat_e stat ) const { switch ( stat ) @@ -10977,25 +10995,6 @@ double evoker_t::resource_regen_per_second( resource_e resource ) const return rrps; } -void evoker_t::target_mitigation( school_e school, result_amount_type rt, action_state_t* s ) -{ - if ( buff.obsidian_scales->check() ) - { - const auto& eff = buff.obsidian_scales->data().effectN( 2 ); - if ( eff.has_common_school( school ) ) - s->result_amount *= 1.0 + eff.percent(); - } - - if ( talent.inherent_resistance.ok() ) - { - const auto& eff = talent.inherent_resistance->effectN( 1 ); - if ( eff.has_common_school( school ) ) - s->result_amount *= 1.0 + eff.percent(); - } - - player_t::target_mitigation( school, rt, s ); -} - // Utility functions ======================================================== // Utility function to search spell data for matching effect. diff --git a/engine/class_modules/sc_shaman.cpp b/engine/class_modules/sc_shaman.cpp index a1d187ec1f9..fb40fe6bda1 100644 --- a/engine/class_modules/sc_shaman.cpp +++ b/engine/class_modules/sc_shaman.cpp @@ -4608,11 +4608,8 @@ struct windstrike_attack_t : public stormstrike_attack_t windstrike_attack_t( util::string_view n, shaman_t* player, const spell_data_t* s, weapon_t* w, strike_variant sf = strike_variant::NORMAL ) : stormstrike_attack_t( n, player, s, w, sf ) - { } - - double composite_target_armor( player_t* ) const override { - return 0.0; + ignores_armor = true; } }; @@ -4623,7 +4620,7 @@ struct windlash_t : public shaman_attack_t windlash_t( util::string_view n, const spell_data_t* s, shaman_t* player, weapon_t* w, double stv ) : shaman_attack_t( n, player, s ), swing_timer_variance( stv ) { - background = repeating = may_miss = may_dodge = may_parry = true; + background = repeating = may_miss = may_dodge = may_parry = ignores_armor = true; may_proc_ability_procs = may_glance = special = false; weapon = w; weapon_multiplier = 1.0; @@ -4637,11 +4634,6 @@ struct windlash_t : public shaman_attack_t return PROC1_MELEE; } - double composite_target_armor( player_t* ) const override - { - return 0.0; - } - timespan_t execute_time() const override { timespan_t t = shaman_attack_t::execute_time(); @@ -7552,7 +7544,7 @@ struct earthquake_damage_base_t : public shaman_spell_t shaman_spell_t( name, player, spell ), mote_buffed( false ), parent( p ) { aoe = -1; - ground_aoe = background = true; + ground_aoe = background = ignores_armor = true; } // Snapshot base state from the parent to grab proper persistent multiplier for all damage @@ -7570,9 +7562,6 @@ struct earthquake_damage_base_t : public shaman_spell_t } } - double composite_target_armor( player_t* ) const override - { return 0; } - double composite_target_multiplier( player_t* t ) const override { double m = shaman_spell_t::composite_target_multiplier( t ); diff --git a/engine/class_modules/sc_warrior.cpp b/engine/class_modules/sc_warrior.cpp index 2af8a2fb029..32c5cc20b95 100644 --- a/engine/class_modules/sc_warrior.cpp +++ b/engine/class_modules/sc_warrior.cpp @@ -881,13 +881,13 @@ struct warrior_t : public parse_player_effects_t double composite_armor_multiplier() const override; double composite_bonus_armor() const override; double composite_block() const override; - double composite_block_reduction( action_state_t* s ) const override; + double composite_block_value( const action_state_t* s ) const override; double composite_parry_rating() const override; double composite_parry() const override; double composite_attack_power_multiplier() const override; - double composite_crit_block() const override; double composite_melee_crit_chance() const override; double composite_leech() const override; + block_result_e target_block_resolution( const action_state_t* ) const override; double resource_gain( resource_e, double, gain_t* = nullptr, action_t* = nullptr ) override; void teleport( double yards, timespan_t duration ) override; void trigger_movement( double distance, movement_direction_type direction ) override; @@ -8103,7 +8103,8 @@ void warrior_t::create_buffs() buff.whirlwind = make_buff( this, "whirlwind", spell.whirlwind_buff ); - buff.spell_reflection = make_buff( this, "spell_reflection", talents.warrior.spell_reflection ) + // reflection NYI + buff.spell_reflection = make_buff( this, "spell_reflection", talents.warrior.spell_reflection->effectN( 2 ).trigger() ) -> set_cooldown( 0_ms ); // handled by the ability buff.sweeping_strikes = make_buff( this, "sweeping_strikes", spec.sweeping_strikes) @@ -8910,18 +8911,19 @@ double warrior_t::composite_block() const return b; } -// warrior_t::composite_block_reduction ===================================== +// warrior_t::composite_block_value =========================================== -double warrior_t::composite_block_reduction( action_state_t* s ) const +double warrior_t::composite_block_value( const action_state_t* s ) const { - double br = parse_player_effects_t::composite_block_reduction( s ); + double bv = parse_player_effects_t::composite_block_value( s ); - if ( buff.brace_for_impact -> check() ) + if ( buff.brace_for_impact->check() ) { - br *= 1.0 + buff.brace_for_impact -> check() * talents.protection.brace_for_impact->effectN( 1 ).trigger() -> effectN( 2 ).percent(); + bv *= 1.0 + buff.brace_for_impact->check() * + talents.protection.brace_for_impact->effectN( 1 ).trigger()->effectN( 2 ).percent(); } - return br; + return bv; } // warrior_t::composite_parry_rating() ======================================== @@ -8962,21 +8964,6 @@ double warrior_t::composite_attack_power_multiplier() const return ap; } -// warrior_t::composite_crit_block ===================================== - -double warrior_t::composite_crit_block() const -{ - - double b = parse_player_effects_t::composite_crit_block(); - - if ( mastery.critical_block->ok() ) - { - b += cache.mastery() * mastery.critical_block->effectN( 1 ).mastery_value(); - } - - return b; -} - // warrior_t::composite_melee_crit_chance ========================================= double warrior_t::composite_melee_crit_chance() const @@ -8995,6 +8982,29 @@ double warrior_t::composite_leech() const return m; } +// warrior_t::target_block_resolution ======================================= + +block_result_e warrior_t::target_block_resolution( const action_state_t* s ) const +{ + // Only direct physical attacks may be blocked + if ( s->result_type == result_amount_type::DMG_DIRECT && s->action->get_school() == SCHOOL_PHYSICAL && + s->action->type == ACTION_ATTACK ) + { + auto block_chance = cache.block(); + auto crit_block_chance = cache.mastery() * mastery.critical_block->effectN( 1 ).mastery_value(); + + if ( rng().roll( block_chance ) ) + { + if ( rng().roll( crit_block_chance ) ) + return BLOCK_RESULT_CRIT_BLOCKED; + else + return BLOCK_RESULT_BLOCKED; + } + } + + return BLOCK_RESULT_UNBLOCKED; +} + // warrior_t::resource_gain ================================================= double warrior_t::resource_gain( resource_e r, double a, gain_t* g, action_t* action ) @@ -9061,7 +9071,6 @@ void warrior_t::invalidate_cache( cache_e c ) if ( c == CACHE_MASTERY ) { parse_player_effects_t::invalidate_cache( CACHE_BLOCK ); - parse_player_effects_t::invalidate_cache( CACHE_CRIT_BLOCK ); parse_player_effects_t::invalidate_cache( CACHE_ATTACK_POWER ); } if ( c == CACHE_CRIT_CHANCE ) @@ -9170,38 +9179,20 @@ void warrior_t::target_mitigation( school_e school, result_amount_type dtype, ac { parse_player_effects_t::target_mitigation( school, dtype, s ); - if ( s->result == RESULT_HIT || s->result == RESULT_CRIT || s->result == RESULT_GLANCE ) + if ( s->block_result == BLOCK_RESULT_CRIT_BLOCKED ) { - if ( buff.defensive_stance->up() ) - s->result_amount *= 1.0 + buff.defensive_stance->data().effectN( 1 ).percent(); - - if ( buff.defensive_stance->up() && talents.warrior.defensive_stance->effectN( 3 ).affected_schools() & school ) - s->result_amount *= 1.0 + buff.defensive_stance->data().effectN( 3 ).percent(); - - if ( buff.enrage->up() && talents.fury.warpaint->ok() ) - s->result_amount *= 1.0 + buff.enrage->data().effectN( 3 ).percent(); + double block_value = s->target_block_value; + double block_resist = util::calculate_armor_resist( block_value, s->action->player->current.armor_coeff, 2.0 ); - warrior_td_t* td = get_target_data( s->action->player ); + s->result_amount *= 1.0 - block_resist; - if ( td->debuffs_demoralizing_shout->up() ) - s->result_amount *= 1.0 + td->debuffs_demoralizing_shout->value(); - - if ( td -> debuffs_punish -> up() ) - s -> result_amount *= 1.0 + td -> debuffs_punish -> value(); - - if ( school != SCHOOL_PHYSICAL && buff.spell_reflection->up() ) + if ( sim->debug ) { - s -> result_amount *= 1.0 + buff.spell_reflection->data().effectN( 2 ).percent(); - buff.spell_reflection->expire(); + sim->print_debug( + "{} {} damage to {} reduced by {:.7g}% from crit block (block value={:.7g}, armor coeff={:.7g}).", + *s->action->player, *s->action, *s->target, block_resist * 100, block_value, + s->action->player->current.armor_coeff ); } - // take care of dmg reduction CDs - if ( buff.shield_wall->up() ) - s->result_amount *= 1.0 + buff.shield_wall->value(); - else if ( buff.die_by_the_sword->up() ) - s->result_amount *= 1.0 + buff.die_by_the_sword->default_value; - - if ( specialization() == WARRIOR_PROTECTION ) - s->result_amount *= 1.0 + spec.vanguard -> effectN( 3 ).percent(); } } @@ -9255,11 +9246,15 @@ void warrior_t::parse_player_effects() if ( specialization() != WARRIOR_PROTECTION ) parse_effects( buff.defensive_stance, effect_mask_t( false ).enable( 2 ) ); + parse_effects( buff.spell_reflection ); + parse_target_effects( d_fn( &warrior_td_t::debuffs_honed_reflexes ), talents.warrior.honed_reflexes->effectN( 5 ).trigger() ); if ( specialization() == WARRIOR_ARMS ) { - if( main_hand_weapon.group() == WEAPON_2H ) + parse_effects( buff.die_by_the_sword ); + + if ( main_hand_weapon.group() == WEAPON_2H ) parse_effects( mastery.master_of_arms ); } else if ( specialization() == WARRIOR_FURY ) @@ -9270,6 +9265,7 @@ void warrior_t::parse_player_effects() if ( talents.warrior.stance_mastery->ok() ) parse_effects( buff.berserker_stance, effect_mask_t( false ).enable( 6 ) ); + parse_effects( buff.enrage, effect_mask_t( false ).enable( 3 ) ); if ( talents.fury.frenzied_enrage->ok() ) parse_effects( buff.enrage, effect_mask_t( false ).enable( 1, 2 ) ); if ( talents.fury.powerful_enrage->ok() ) @@ -9281,6 +9277,11 @@ void warrior_t::parse_player_effects() { parse_effects( buff.into_the_fray ); parse_effects( buff.avatar, effect_mask_t( false ).enable( 8 ) ); + parse_effects( buff.shield_wall ); + parse_effects( spec.vanguard, PARSE_PASSIVE ); + + parse_target_effects( d_fn( &warrior_td_t::debuffs_demoralizing_shout ), talents.protection.demoralizing_shout ); + parse_target_effects( d_fn( &warrior_td_t::debuffs_punish ), talents.protection.punish->effectN( 2 ).trigger() ); } // Colossus diff --git a/engine/player/player.cpp b/engine/player/player.cpp index bc201317549..328c239195a 100644 --- a/engine/player/player.cpp +++ b/engine/player/player.cpp @@ -1277,7 +1277,7 @@ player_t::base_initial_current_t::base_initial_current_t() : crit_avoidance( 0 ), spell_crit_chance(), attack_crit_chance(), - block_reduction(), + block_value(), mastery( 0 ), versatility( 0 ), all_crit( 0 ), @@ -1345,7 +1345,7 @@ void sc_format_to( const player_t::base_initial_current_t& s, fmt::format_contex fmt::format_to( out, " block={}", s.block ); fmt::format_to( out, " spell_crit_chance={}", s.spell_crit_chance ); fmt::format_to( out, " attack_crit_chance={}", s.attack_crit_chance ); - fmt::format_to( out, " block_reduction={}", s.block_reduction ); + fmt::format_to( out, " block_value={}", s.block_value ); fmt::format_to( out, " mastery={}", s.mastery ); fmt::format_to( out, " versatility={}", s.versatility ); fmt::format_to( out, " all_haste={}", s.all_haste ); @@ -1673,47 +1673,44 @@ void player_t::init_base_stats() base.dodge = get_passive_player_value( base.dodge, "dodge" ); - // Only Warriors and Paladins (and enemies) can block, defaults to 0 - if ( type == WARRIOR || type == PALADIN || type == ENEMY || type == TANK_DUMMY ) + switch ( type ) { - // Base block chance is 3%, increased in warriors' and paladins' class aura and protection warrior's spec aura - // Further increased by mastery for both Protection specs - base.block = 0.03; - base.block = get_passive_player_value( base.block, "block" ); + // Only Warriors and Paladins can block, defaults to 0 + case PALADIN: + case WARRIOR: + // Base block chance is 3%, increased in warriors' and paladins' class aura and protection warrior's spec aura + // Further increased by mastery for both Protection specs + base.block = 0.03; + base.block = get_passive_player_value( base.block, "block" ); - switch ( type ) - { - case WARRIOR: - case PALADIN: - // Currently block reduction is 2.5x the armor value of the shield - if ( items[ SLOT_OFF_HAND ].dbc_inventory_type() == INVTYPE_SHIELD ) - base.block_reduction = items[ SLOT_OFF_HAND ].stats.armor * 2.5; - else - base.block_reduction = 0; - break; - default: - base.block_reduction = 0.30; - break; - } - } - - base.block_reduction = get_passive_player_value( base.block_reduction, "block_reduction" ); + // Currently block reduction is 2.5x the armor value of the shield + if ( items[ SLOT_OFF_HAND ].dbc_inventory_type() == INVTYPE_SHIELD ) + base.block_value = items[ SLOT_OFF_HAND ].stats.armor * 2.5; + else + base.block_value = 0; - // Only certain classes can parry, and get 3% base parry, default is 0 - // Parry from base strength isn't affected by diminishing returns and is added here - if ( type == WARRIOR || type == PALADIN || type == ROGUE || type == DEATH_KNIGHT || type == MONK || - type == DEMON_HUNTER ) - { - base.parry = 0.03 + ( dbc->race_base( race ).strength + dbc->attribute_base( type, level() ).strength ) * base.parry_per_strength; - base.parry = get_passive_player_value( base.parry, "parry" ); - } - else if ( type == ENEMY || type == TANK_DUMMY ) - { - base.parry = 0.03; + base.block_value = get_passive_player_value( base.block_value, "block_value" ); + SC_FALLTHROUGH; + // Only certain classes can parry, and get 3% base parry, default is 0 + case DEATH_KNIGHT: + // Parry from base strength isn't affected by diminishing returns and is added here + base.parry = ( dbc->race_base( race ).strength + dbc->attribute_base( type, level() ).strength ) * base.parry_per_strength; + SC_FALLTHROUGH; + case DEMON_HUNTER: + case MONK: + case ROGUE: + base.parry += 0.03; + base.parry = get_passive_player_value( base.parry, "parry" ); + base.parry_rating_per_crit_rating = get_passive_player_value( base.parry_rating_per_crit_rating, "parry_from_crit_rating" ); + break; + case ENEMY: + case TANK_DUMMY: + base.parry = 0.03; + break; + default: + break; } - base.parry_rating_per_crit_rating = get_passive_player_value( base.parry_rating_per_crit_rating, "parry_from_crit_rating" ); - // Movement Speed base.movement_speed = 7.0; // yards per second base.stacking_movement_speed_modifier = @@ -4892,25 +4889,27 @@ void player_t::create_buffs() { // Racials buffs.berserking = make_buff_fallback( race == RACE_TROLL, this, "berserking", find_spell( 26297 ) ) - ->add_invalidate( CACHE_HASTE ); + ->add_invalidate( CACHE_HASTE ); - buffs.stoneform = make_buff_fallback( race == RACE_DWARF, this, "stoneform", find_spell( 65116 ) ); + buffs.stoneform = make_buff_fallback( race == RACE_DWARF, this, "stoneform", find_spell( 65116 ) ) + ->set_default_value_from_effect_type( A_MOD_DAMAGE_PERCENT_TAKEN ); - buffs.blood_fury = make_buff_fallback( race == RACE_ORC, this, "blood_fury", find_racial_spell( "Blood Fury" ) ) - ->add_invalidate( CACHE_SPELL_POWER ) - ->add_invalidate( CACHE_ATTACK_POWER ); + buffs.blood_fury = + make_buff_fallback( race == RACE_ORC, this, "blood_fury", find_racial_spell( "Blood Fury" ) ) + ->add_invalidate( CACHE_SPELL_POWER ) + ->add_invalidate( CACHE_ATTACK_POWER ); buffs.shadowmeld = make_buff_fallback( race == RACE_NIGHT_ELF, this, "shadowmeld", find_spell( 58984 ) ) - ->set_cooldown( 0_ms ); + ->set_cooldown( 0_ms ); - buffs.ancestral_call[ 0 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, this, - "rictus_of_the_laughing_skull", find_spell( 274739 ) ); - buffs.ancestral_call[ 1 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, this, - "zeal_of_the_burning_blade", find_spell( 274740 ) ); - buffs.ancestral_call[ 2 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, this, - "ferocity_of_the_frostwolf", find_spell( 274741 ) ); - buffs.ancestral_call[ 3 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, this, - "might_of_the_blackrock", find_spell( 274742 ) ); + buffs.ancestral_call[ 0 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, + this, "rictus_of_the_laughing_skull", find_spell( 274739 ) ); + buffs.ancestral_call[ 1 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, + this, "zeal_of_the_burning_blade", find_spell( 274740 ) ); + buffs.ancestral_call[ 2 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, + this, "ferocity_of_the_frostwolf", find_spell( 274741 ) ); + buffs.ancestral_call[ 3 ] = make_buff_fallback( race == RACE_MAGHAR_ORC, + this, "might_of_the_blackrock", find_spell( 274742 ) ); if ( race == RACE_DARK_IRON_DWARF ) { @@ -5531,16 +5530,9 @@ double player_t::composite_parry() const return total_parry; } -double player_t::composite_block_reduction( action_state_t* ) const +double player_t::composite_block_value( const action_state_t* ) const { - double b = current.block_reduction; - - return b; -} - -double player_t::composite_crit_block() const -{ - return 0; + return current.block_value; } double player_t::composite_crit_avoidance() const @@ -5880,7 +5872,12 @@ double player_t::composite_player_absorb_multiplier( const action_state_t* ) con double player_t::composite_player_healing_received_multiplier() const { - return current.healing_received_multiplier; + auto m = current.healing_received_multiplier; + + if ( buffs.guardian_spirit && buffs.guardian_spirit->up() ) + m *= 1.0 + buffs.guardian_spirit->check_value(); + + return m; } double player_t::composite_player_absorb_received_multiplier() const @@ -6195,9 +6192,44 @@ double player_t::composite_player_target_armor( player_t* t ) const return a; } -double player_t::composite_mitigation_multiplier( school_e /* school */ ) const +double player_t::composite_mitigation_multiplier( const action_state_t* s, school_e school, bool direct ) const { - return 1.0; + double m = 1.0; + + if ( !is_enemy() && type != HEALING_ENEMY ) + { + if ( !is_pet() ) + { + if ( buffs.stoneform && buffs.stoneform->up() && school == SCHOOL_PHYSICAL ) + m *= 1.0 + buffs.stoneform->check_value(); + + if ( buffs.elemental_chaos_earth && buffs.elemental_chaos_earth->up() ) + m *= 1.0 + buffs.elemental_chaos_earth->check_value(); + + if ( buffs.pain_suppression && buffs.pain_suppression->up() ) + m *= 1.0 + buffs.pain_suppression->check_value(); + } + + m *= 1.0 - cache.mitigation_versatility(); + + if ( sim->debug ) + { + sim->print_debug( "{} {} damage to {} reduced by {:.7g}% from versatility.", *s->action->player, *s->action, + *s->target, cache.mitigation_versatility() * 100 ); + } + + if ( s->action->is_aoe() ) + m *= 1.0 - cache.avoidance(); + } + + return m; +} + +double player_t::composite_mitigation_from_player_multiplier( player_t*, const action_state_t*, school_e, bool ) const +{ + double m = 1.0; + + return m; } double player_t::composite_mastery_value() const @@ -8673,7 +8705,10 @@ void player_t::assess_damage( school_e school, result_amount_type rt, action_sta s->result_mitigated = s->result_amount; if ( sim->debug && s->action && !s->target->is_enemy() && !s->target->is_add() ) - sim->out_debug.printf( "Damage to %s after all mitigation is %f", s->target->name(), s->result_amount ); + { + sim->print_debug( "{} {} damage to {} after mitigation is {}.", *s->action->player, *s->action, *this, + s->result_amount ); + } account_blessing_of_sacrifice( *this, s ); @@ -8759,87 +8794,61 @@ void player_t::target_mitigation( school_e school, result_amount_type dmg_type, if ( s->result_amount == 0 ) return; - if ( buffs.pain_suppression && buffs.pain_suppression->up() ) - s->result_amount *= 1.0 + buffs.pain_suppression->data().effectN( 1 ).percent(); - - if ( buffs.stoneform && buffs.stoneform->up() ) - s->result_amount *= 1.0 + buffs.stoneform->data().effectN( 1 ).percent(); - - if ( buffs.elemental_chaos_earth && buffs.elemental_chaos_earth->check() ) - s->result_amount *= 1.0 + buffs.elemental_chaos_earth->check_value(); - - if ( s->action->is_aoe() ) - s->result_amount *= 1.0 - cache.avoidance(); - - // TODO-WOD: Where should this be? Or does it matter? - s->result_amount *= 1.0 - cache.mitigation_versatility(); - if ( debuffs.invulnerable && debuffs.invulnerable->check() ) { s->result_amount = 0; + return; } - if ( school == SCHOOL_PHYSICAL && dmg_type == result_amount_type::DMG_DIRECT ) + if ( dmg_type == result_amount_type::DMG_OVER_TIME ) + { + s->result_amount *= s->target_mitigation_ta_multiplier; + } + else if ( dmg_type == result_amount_type::DMG_DIRECT ) { - if ( s->action && !s->target->is_enemy() && !s->target->is_add() ) - sim->print_debug( "Damage to {} before armor mitigation is {:.6f}", s->target->name(), s->result_amount ); + s->result_amount *= s->target_mitigation_da_multiplier; + + if ( !s->action ) + return; - // Maximum amount of damage reduced by armor - double armor_cap = 0.85; + // Maximum amount of damage reduced by armor/block + static constexpr double armor_cap = 0.85; // Armor - if ( s->action && !s->action->ignores_armor ) + if ( auto armor = s->target_armor ) { - double armor = s -> target_armor; - double resist = armor / ( armor + s -> action -> player -> base.armor_coeff ); - resist = clamp( resist, 0.0, armor_cap ); - s -> result_amount *= 1.0 - resist; - } + double resist = util::calculate_armor_resist( armor, s->action->player->current.armor_coeff ); + s->result_amount *= 1.0 - resist; - if ( s->action && !s->target->is_enemy() && !s->target->is_add() ) - { - if ( s->action->ignores_armor ) - sim->print_debug( "Damage to {} after armor mitigation is {:.6f} (ignores armor)", s->target->name(), s->result_amount ); - else - sim->print_debug( "Damage to {} after armor mitigation is {:.6f} ({:.7g} armor, {:.7g} armor coeff)", - s->target->name(), s->result_amount, s->target_armor, s->action->player->current.armor_coeff ); + if ( sim->debug ) + { + sim->print_debug( "{} {} damage to {} reduced by {:.7g}% from armor (armor={:.7g}, armor coeff={:.7g}).", + *s->action->player, *s->action, *s->target, resist * 100, armor, + s->action->player->current.armor_coeff ); + } } - double pre_block_amount = s->result_amount; - - // In BfA, Block and Crit Block work in the same manner as armor and are affected by the same cap - if ( s ->action && (s -> block_result == BLOCK_RESULT_BLOCKED || s -> block_result == BLOCK_RESULT_CRIT_BLOCKED )) + // Block and Crit Block work in the same manner as armor and are affected by the same cap + if ( s->block_result == BLOCK_RESULT_BLOCKED ) { - double block_reduction = composite_block_reduction( s ); - - double block_resist = block_reduction / ( block_reduction + s -> action -> player -> current.armor_coeff ); - - if ( s -> block_result == BLOCK_RESULT_CRIT_BLOCKED ) - block_resist *= 2.0; + double block_value = s->target_block_value; + double block_resist = util::calculate_armor_resist( block_value, s->action->player->current.armor_coeff ); block_resist = clamp( block_resist, 0.0, armor_cap ); - s -> result_amount *= 1.0 - block_resist; + s->result_amount *= 1.0 - block_resist; - if ( s -> result_amount <= 0 ) - return; + if ( sim->debug ) + { + sim->print_debug( "{} {} damage to {} reduced by {:.7g}% from block (block value={:.7g}, armor coeff={:.7g}).", + *s->action->player, *s->action, *s->target, block_resist * 100, block_value, + s->action->player->current.armor_coeff ); + } } - - s->blocked_amount = pre_block_amount - s->result_amount; - - if ( sim->debug && s->action && !s->target->is_enemy() && !s->target->is_add() && s->blocked_amount > 0.0 ) - sim->print_debug( "Damage to {} after blocking is {:.6f}", s->target->name(), s->result_amount ); } } void player_t::assess_heal( school_e, result_amount_type, action_state_t* s ) { - // Increases to healing taken should modify result_total in order to correctly calculate overhealing - // and other effects based on raw healing. - if ( buffs.guardian_spirit->up() ) - s->result_total *= 1.0 + buffs.guardian_spirit->data().effectN( 1 ).percent(); - - s->result_total *= composite_player_healing_received_multiplier(); - // process heal s->result_amount = resource_gain( RESOURCE_HEALTH, s->result_total, nullptr, s->action ); @@ -15410,7 +15419,7 @@ static constexpr std::pair field_type_map[] = { { A_MOD_RECHARGE_TIME_CATEGORY_MASK, "charge_cooldown" }, // 205 { A_MODIFY_SCHOOL, "school" }, // 220 { A_MOD_EXPERTISE, "expertise" }, // 240 - { A_MOD_BLOCK_PCT, "block_reduction" }, // 272 + { A_MOD_BLOCK_PCT, "block_value" }, // 272 { A_MOD_MECHANIC_DAMAGE_DONE_PERCENT, "mechanic_damage_done" }, // 276 { A_MOD_TARGET_ARMOR_PCT, "armor_penetration" }, // 280 { A_MOD_ALL_CRIT_CHANCE, "all_crit" }, // 290 diff --git a/engine/player/player.hpp b/engine/player/player.hpp index 3cfaa0ffdad..62e0cd877ad 100644 --- a/engine/player/player.hpp +++ b/engine/player/player.hpp @@ -251,7 +251,7 @@ struct player_t : public actor_t std::array resource_reduction; double miss, dodge, parry, block; double hit, expertise, leech, avoidance, crit_avoidance; - double spell_crit_chance, attack_crit_chance, block_reduction; + double spell_crit_chance, attack_crit_chance, block_value; double mastery, versatility, all_crit, all_haste; double melee_haste, spell_haste, ranged_haste; double skill, skill_debuff, distance; @@ -1286,8 +1286,7 @@ struct player_t : public actor_t virtual double composite_dodge() const; virtual double composite_parry() const; virtual double composite_block() const; - virtual double composite_block_reduction( action_state_t* s ) const; - virtual double composite_crit_block() const; + virtual double composite_block_value( const action_state_t* s ) const; virtual double composite_crit_avoidance() const; virtual double composite_attack_power_multiplier() const; virtual double composite_spell_power_multiplier() const; @@ -1309,7 +1308,9 @@ struct player_t : public actor_t virtual double composite_player_target_armor( player_t* ) const; virtual double composite_player_healing_received_multiplier() const; virtual double composite_player_absorb_received_multiplier() const; - virtual double composite_mitigation_multiplier( school_e ) const; + virtual double composite_mitigation_multiplier( const action_state_t*, school_e, bool direct ) const; + virtual double composite_mitigation_from_player_multiplier( player_t*, const action_state_t*, school_e, + bool direct ) const; virtual double non_stacking_movement_modifier() const; virtual double stacking_movement_modifier() const; virtual double composite_movement_speed() const; @@ -1408,6 +1409,8 @@ struct player_t : public actor_t virtual void cost_reduction_loss( school_e school, double amount, action_t* a = nullptr ); virtual void collect_resource_timeline_information(); + virtual block_result_e target_block_resolution( const action_state_t* ) const + { return BLOCK_RESULT_UNBLOCKED; } virtual void assess_damage( school_e, result_amount_type, action_state_t* ); virtual void target_mitigation( school_e, result_amount_type, action_state_t* ); virtual void assess_damage_imminent_pre_absorb( school_e, result_amount_type, action_state_t* ); diff --git a/engine/player/player_stat_cache.cpp b/engine/player/player_stat_cache.cpp index 75495610a6d..54e7427e99b 100644 --- a/engine/player/player_stat_cache.cpp +++ b/engine/player/player_stat_cache.cpp @@ -361,18 +361,6 @@ double player_stat_cache_t::block() const return _block; } -double player_stat_cache_t::crit_block() const -{ - if ( !active || !valid[ CACHE_CRIT_BLOCK ] ) - { - valid[ CACHE_CRIT_BLOCK ] = true; - _crit_block = player->composite_crit_block(); - } - else - assert( _crit_block == player->composite_crit_block() ); - return _crit_block; -} - double player_stat_cache_t::crit_avoidance() const { if ( !active || !valid[ CACHE_CRIT_AVOIDANCE ] ) @@ -609,7 +597,6 @@ double player_stat_cache_t::spell_cast_speed() const { return _player->composite double player_stat_cache_t::dodge() const { return _player->composite_dodge(); } double player_stat_cache_t::parry() const { return _player->composite_parry(); } double player_stat_cache_t::block() const { return _player->composite_block(); } -double player_stat_cache_t::crit_block() const { return _player->composite_crit_block(); } double player_stat_cache_t::crit_avoidance() const { return _player->composite_crit_avoidance(); } double player_stat_cache_t::miss() const { return _player->composite_miss(); } double player_stat_cache_t::armor() const { return _player->composite_armor(); } diff --git a/engine/player/player_stat_cache.hpp b/engine/player/player_stat_cache.hpp index 769915a83af..ac8086525df 100644 --- a/engine/player/player_stat_cache.hpp +++ b/engine/player/player_stat_cache.hpp @@ -55,7 +55,7 @@ struct player_stat_cache_t mutable double _attack_crit_chance, _spell_crit_chance; mutable double _attack_haste, _spell_haste; mutable double _auto_attack_speed, _spell_cast_speed; - mutable double _dodge, _parry, _block, _crit_block, _armor, _bonus_armor; + mutable double _dodge, _parry, _block, _armor, _bonus_armor; mutable double _mastery, _mastery_value, _crit_avoidance, _miss; mutable std::array _player_mult; mutable std::array _player_heal_mult; @@ -92,7 +92,6 @@ struct player_stat_cache_t double dodge() const; double parry() const; double block() const; - double crit_block() const; double crit_avoidance() const; double miss() const; double armor() const; @@ -134,7 +133,6 @@ struct player_stat_cache_t double dodge() const { return _player->composite_dodge(); } double parry() const { return _player->composite_parry(); } double block() const { return _player->composite_block(); } - double crit_block() const { return _player->composite_crit_block(); } double crit_avoidance() const { return _player->composite_crit_avoidance(); } double miss() const { return _player->composite_miss(); } double armor() const { return _player->composite_armor(); } diff --git a/engine/player/unique_gear.cpp b/engine/player/unique_gear.cpp index 28031b48506..799db5674b9 100644 --- a/engine/player/unique_gear.cpp +++ b/engine/player/unique_gear.cpp @@ -392,13 +392,15 @@ void enchants::mark_of_the_shattered_hand( special_effect_t& effect ) bleed_attack_t( player_t* p, const special_effect_t& effect ) : attack_t( effect.name(), p, p -> find_spell( effect.trigger_spell_id ) ) { - hasted_ticks = false; background = true; callbacks = false; special = true; - may_miss = may_block = may_dodge = may_parry = false; may_crit = true; + hasted_ticks = false; + background = true; + callbacks = false; + special = true; + may_miss = may_block = may_dodge = may_parry = false; + may_crit = true; tick_may_crit = false; + ignores_armor = true; } - - double composite_target_armor( player_t* ) const override - { return 0.0; } }; action_t* bleed = effect.player -> find_action( "shattered_bleed" ); @@ -1892,20 +1894,21 @@ struct cleave_t : public T cleave_t( const item_t* item, const std::string& name, school_e s ) : T( name, item -> player ) { - this -> callbacks = false; - this -> may_crit = false; - this -> may_glance = false; - this -> may_miss = true; - this -> special = true; - this -> proc = true; - this -> background = true; - this -> school = s; - this -> aoe = 5; - if ( this -> type == ACTION_ATTACK ) + this->callbacks = false; + this->may_crit = false; + this->may_glance = false; + this->may_miss = true; + this->special = true; + this->proc = true; + this->background = true; + this->school = s; + this->aoe = 5; + if ( this->type == ACTION_ATTACK ) { - this -> may_dodge = true; - this -> may_parry = true; - this -> may_block = true; + this->may_dodge = true; + this->may_parry = true; + this->may_block = true; + this->ignores_armor = true; } } @@ -1930,9 +1933,6 @@ struct cleave_t : public T return tl.size(); } - - double composite_target_armor( player_t* ) const override - { return 0.0; } }; void item::cleave( special_effect_t& effect ) diff --git a/engine/player/unique_gear_legion.cpp b/engine/player/unique_gear_legion.cpp index 090400ed752..b541daf9afc 100644 --- a/engine/player/unique_gear_legion.cpp +++ b/engine/player/unique_gear_legion.cpp @@ -559,10 +559,8 @@ void item::choker_of_barbed_reins( special_effect_t& effect ) choker_of_barbed_reins_t( const special_effect_t& e ): proc_attack_t( "barbed_rebuke", e.player, e.player -> find_spell( 234108 ), e.item ) { - may_block = true; + may_block = ignores_armor = true; } - double composite_target_armor( player_t* ) const override - { return 0.0; } }; effect.execute_action = effect.player -> find_action( "barbed_rebuke" ); @@ -4678,10 +4676,9 @@ struct spontaneous_appendages_t: public proc_spell_t proc_spell_t( "horrific_slam", effect.player, effect.player -> find_spell( effect.trigger() -> effectN( 1 ).trigger() -> id() ), effect.item ) - {} - - double composite_target_armor( player_t* ) const override - { return 0.0; } + { + ignores_armor = true; + } }; void item::spontaneous_appendages( special_effect_t& effect ) @@ -5102,17 +5099,14 @@ struct legion_potion_damage_t : public T legion_potion_damage_t( const special_effect_t& effect, ::util::string_view name_str, const spell_data_t* spell ) : T( name_str, effect.player, spell ) { - this -> background = this -> may_crit = this -> special = true; - this -> callbacks = false; - this -> base_dd_min = spell -> effectN( 1 ).min( this -> player ); - this -> base_dd_max = spell -> effectN( 1 ).max( this -> player ); + this->background = this->may_crit = this->special = this->ignores_armor = true; + this->callbacks = false; + this->base_dd_min = spell->effectN( 1 ).min( this->player ); + this->base_dd_max = spell->effectN( 1 ).max( this->player ); // Currently 0, but future proof if they decide to make it scale .. - this -> attack_power_mod.direct = spell -> effectN( 1 ).ap_coeff(); - this -> spell_power_mod.direct = spell -> effectN( 1 ).sp_coeff(); + this->attack_power_mod.direct = spell->effectN( 1 ).ap_coeff(); + this->spell_power_mod.direct = spell->effectN( 1 ).sp_coeff(); } - - double composite_target_armor( player_t* ) const override - { return 0.0; } }; // Potion of the Old War ==================================================== diff --git a/engine/sc_enums.hpp b/engine/sc_enums.hpp index a9790b6e314..da3bcccd56a 100644 --- a/engine/sc_enums.hpp +++ b/engine/sc_enums.hpp @@ -711,8 +711,6 @@ enum school_e SCHOOL_MAX }; -const school_e SCHOOL_RADIANT = SCHOOL_HOLYFIRE; - enum school_mask_e { SCHOOL_MASK_PHYSICAL = 0x01, @@ -1004,7 +1002,7 @@ enum stat_e STAT_RESILIENCE_RATING, STAT_DODGE_RATING, STAT_PARRY_RATING, - STAT_BLOCK_RATING, // Block CHANCE rating. Block damage reduction is in player_t::composite_block_reduction() + STAT_BLOCK_RATING, // Block CHANCE rating. Block damage reduction is in player_t::composite_block_value() STAT_PVP_POWER, STAT_WEAPON_DPS, STAT_WEAPON_OFFHAND_DPS, @@ -1119,7 +1117,6 @@ enum cache_e CACHE_DODGE, CACHE_PARRY, CACHE_BLOCK, - CACHE_CRIT_BLOCK, CACHE_ARMOR, CACHE_BONUS_ARMOR, CACHE_CRIT_AVOIDANCE, diff --git a/engine/util/util.cpp b/engine/util/util.cpp index 55cdaec29f9..2ebeb6efe24 100644 --- a/engine/util/util.cpp +++ b/engine/util/util.cpp @@ -977,13 +977,13 @@ const char* util::amount_type_string( result_amount_type type ) { switch ( type ) { - case result_amount_type::NONE: return "none"; - case result_amount_type::DMG_DIRECT: return "direct_damage"; - case result_amount_type::DMG_OVER_TIME: return "tick_damage"; - case result_amount_type::HEAL_DIRECT: return "direct_heal"; - case result_amount_type::HEAL_OVER_TIME: return "tick_heal"; - case result_amount_type::ABSORB: return "absorb"; - default: return "unknown"; + case result_amount_type::NONE: return "none"; + case result_amount_type::DMG_DIRECT: return "direct_damage"; + case result_amount_type::DMG_OVER_TIME: return "tick_damage"; + case result_amount_type::HEAL_DIRECT: return "direct_heal"; + case result_amount_type::HEAL_OVER_TIME: return "tick_heal"; + case result_amount_type::ABSORB: return "absorb"; + default: return "unknown"; } } @@ -1457,6 +1457,7 @@ const char* util::cache_type_string( cache_e c ) case CACHE_CORRUPTION: return "corruption"; case CACHE_CORRUPTION_RESISTANCE: return "corruption_resistance"; case CACHE_AVOIDANCE: return "avoidance"; + case CACHE_CRIT_AVOIDANCE: return "crit_avoidance"; default: return "unknown"; } } @@ -3709,4 +3710,14 @@ double approx_sqrt( double arg ) return 0.0; } +double calculate_armor_resist( double armor, double armor_coeff, double multipler ) +{ + static constexpr double armor_cap = 0.85; + + double resist = armor / ( armor + armor_coeff ); + resist *= multipler; + resist = clamp( resist, 0.0, armor_cap ); + + return resist; +} } // namespace util diff --git a/engine/util/util.hpp b/engine/util/util.hpp index b238346eb7b..5eb33d11db0 100644 --- a/engine/util/util.hpp +++ b/engine/util/util.hpp @@ -196,7 +196,6 @@ bool is_combat_rating( item_mod_type t ); bool is_combat_rating( stat_e t ); bool is_primary_stat( stat_e ); int translate_stat( stat_e stat ); -stat_e translate_attribute( attribute_e attribute ); stat_e translate_rating_mod( unsigned ratings ); std::vector translate_all_rating_mod( unsigned ratings ); unsigned rating_to_rating_mod( rating_e ); @@ -207,6 +206,7 @@ profession_e translate_profession_id( int skill_id ); bool socket_gem_match( item_socket_color socket, item_socket_color gem ); double crit_multiplier( meta_gem_e gem ); bool scale_metric_is_raid( scale_metric_e ); +double calculate_armor_resist( double armor, double armor_coeff, double multipler = 1.0 ); template inline std::vector string_split( util::string_view str, util::string_view delim, bool skip_empty_entries = true )