diff --git a/engine/action/action.cpp b/engine/action/action.cpp index 6ef401545c..14e668af27 100644 --- a/engine/action/action.cpp +++ b/engine/action/action.cpp @@ -1409,6 +1409,9 @@ double action_t::calculate_weapon_damage( double attack_power ) const if ( !weapon || weapon_multiplier <= 0 ) return 0; + // TODO Forever: Do we need special handling for ranged weapons here? + // Currently the weapon roll is multiplied by melee AP so probably need ranged handling + // The weapon damage roll (and its bonus damage) is affected by attak power modifiers just like regular attack power double capm = player -> composite_attack_power_multiplier(); double dmg = capm * ( sim->averaged_range( weapon->min_dmg, weapon->max_dmg ) + weapon->bonus_dmg ); @@ -2798,6 +2801,7 @@ void action_t::init() // this only defines the ap type, the ability may not necessarily use attack power at all, however // that is not possible to know at init time with 100% accuracy. // Only overwrite this if the default attack_power_type::AP_NONE value is still set, since modules may set manually. + // TODO Forever - can a bunch of this go since we're mostly back in classic era weapons? if ( ap_type == attack_power_type::NONE ) { // Weapon multiplier is set. The power calculation for the ability uses base ap only, as the diff --git a/engine/action/attack.cpp b/engine/action/attack.cpp index ca1eabb0f7..c9066ebd34 100644 --- a/engine/action/attack.cpp +++ b/engine/action/attack.cpp @@ -474,8 +474,11 @@ ranged_attack_t::ranged_attack_t( util::string_view token, player_t* p ) ranged_attack_t::ranged_attack_t( util::string_view token, player_t* p, const spell_data_t* s ) : attack_t( token, p, s ) { - // ranged attacks cannot be blocked or parried - may_block = may_parry = false; + // ranged attacks cannot be blocked, dodged, or parried + // TODO Forever: Fairly certain ranged attacks can't be glancing but this needs a bit more verification + may_block = may_parry = may_dodge = may_glance = false; + + ap_type = attack_power_type::RANGED; } // Ranged attacks are identical to melee attacks, but cannot be parried or dodged. diff --git a/engine/action/attack.hpp b/engine/action/attack.hpp index 19758579ef..b7aad592ce 100644 --- a/engine/action/attack.hpp +++ b/engine/action/attack.hpp @@ -98,6 +98,8 @@ struct ranged_attack_t : public attack_t // Ranged Attack Overrides double composite_target_multiplier( player_t* ) const override; void schedule_execute( action_state_t* execute_state = nullptr ) override; + // TODO: Think this will be needed to separate melee/ranged haste + //double execute_time_pct_multiplier() const override; proc_types proc_type() const override; }; @@ -136,7 +138,8 @@ struct auto_attack_t : public action_t parse_options( options_str ); for ( auto& [ slot, weapon ] : p->equipped_weapons ) - create_melee_attack( weapon ); + if ( slot != SLOT_RANGED ) + create_melee_attack( weapon ); } // TODO (FOREVER): handle different weapon types range behaviours diff --git a/engine/buff/buff.cpp b/engine/buff/buff.cpp index ff7558b8b4..c677fbb532 100644 --- a/engine/buff/buff.cpp +++ b/engine/buff/buff.cpp @@ -3335,8 +3335,6 @@ stat_buff_t::stat_buff_t( actor_pair_t q, util::string_view name, const spell_da stat_gain( player->get_gain( fmt::format( "{}_buff", name ) ) ), // append _buff for now to check usage manual_stats_added( false ) { - bool has_ap = false; - for ( size_t i = 1; i <= data().effect_count(); i++ ) { stat_e s = STAT_NONE; @@ -3380,10 +3378,13 @@ stat_buff_t::stat_buff_t( actor_pair_t q, util::string_view name, const spell_da { s = STAT_BONUS_ARMOR; } - else if ( !has_ap && ( effect.subtype() == A_MOD_ATTACK_POWER || effect.subtype() == A_MOD_RANGED_ATTACK_POWER ) ) + else if ( effect.subtype() == A_MOD_ATTACK_POWER ) + { + s = STAT_ATTACK_POWER; + } + else if ( effect.subtype() == A_MOD_RANGED_ATTACK_POWER ) { - s = STAT_ATTACK_POWER; - has_ap = true; + s = STAT_RANGED_ATTACK_POWER; } else if ( effect.subtype() == A_MOD_INCREASE_HEALTH_2 || effect.subtype() == A_MOD_INCREASE_HEALTH ) { diff --git a/engine/class_modules/apl/hunter.cpp b/engine/class_modules/apl/hunter.cpp index 2ae7b78044..f87dd7d6d3 100644 --- a/engine/class_modules/apl/hunter.cpp +++ b/engine/class_modules/apl/hunter.cpp @@ -29,4 +29,16 @@ std::string temporary_enchant( const player_t* ) { return "disabled"; } + +//hunter_apl_start +void hunter( player_t* p ) +{ + action_priority_list_t* precombat = p->get_action_priority_list( "precombat" ); + action_priority_list_t* default_ = p->get_action_priority_list( "default" ); + + //precombat->add_action( "summon_pet" ); + + default_->add_action( "auto_shot" ); +} +//hunter_apl_end } // namespace hunter_apl diff --git a/engine/class_modules/apl/hunter.hpp b/engine/class_modules/apl/hunter.hpp index 51eb11141f..9e1aa4539b 100644 --- a/engine/class_modules/apl/hunter.hpp +++ b/engine/class_modules/apl/hunter.hpp @@ -11,4 +11,5 @@ std::string flask( const player_t* ); std::string food( const player_t* ); std::string rune( const player_t* ); std::string temporary_enchant( const player_t* ); +void hunter( player_t* ); } // namespace hunter_apl diff --git a/engine/class_modules/sc_hunter.cpp b/engine/class_modules/sc_hunter.cpp index 96a5c5631f..1cf66839eb 100644 --- a/engine/class_modules/sc_hunter.cpp +++ b/engine/class_modules/sc_hunter.cpp @@ -4,15 +4,153 @@ // ========================================================================== #include "simulationcraft.hpp" +#include "action/parse_effects.hpp" +#include "class_modules/apl/hunter.hpp" + +// TODO: Default to max distance +// TODO: ItemDamageRanged for ranged weapon damage multipliers +// TODO: Does "plain" +Attack Power contribute to RAP? namespace -{ // UNNAMED NAMESPACE +{ +// ========================================================================= +// Hunter +// ========================================================================= + struct hunter_t final : public player_t { hunter_t( sim_t* sim, util::string_view name, race_e r = RACE_NONE ) : player_t( sim, HUNTER, name, r ) {} + + resource_e primary_resource() const override { return RESOURCE_MANA; } + + void init_base_stats() override; + + action_t* create_action( util::string_view name, util::string_view options_str ) override; + void init_action_list() override; +}; + +template +struct hunter_action_t : public parse_action_effects_t +{ + private: + using ab = parse_action_effects_t; + + public: + using base_t = hunter_action_t; + + template + hunter_action_t( Args&&... args ) : ab( std::forward( args )... ) {} + + hunter_t* p() + { return debug_cast( this->player ); } + + const hunter_t* p() const + { return debug_cast( this->player ); } +}; + +using hunter_melee_t = hunter_action_t; +using hunter_ranged_t = hunter_action_t; +using hunter_spell_t = hunter_action_t; + +// ================================================================================ +// Hunter Ranged +// ================================================================================ + +struct hunter_ranged_attack_t : public hunter_ranged_t +{ + hunter_ranged_attack_t( util::string_view name, player_t* player ) + : hunter_ranged_t( name, player, player->find_spell( 75 ) ) + { + weapon = &player->equipped_weapons[ SLOT_RANGED ]; + weapon_multiplier = 1.0; + base_execute_time = weapon->swing_time; + school = SCHOOL_PHYSICAL; + trigger_gcd = 0_ms; + + background = true; + repeating = true; + may_crit = true; + special = false; + } }; +struct hunter_auto_shot_t : public hunter_ranged_t +{ + hunter_auto_shot_t( hunter_t* p, util::string_view options_str ) + : hunter_ranged_t( "auto_shot", p, p->find_spell( 75 ) ) + { + parse_options( options_str ); + + trigger_gcd = 0_ms; + ignore_false_positive = true; + + assert( p->equipped_weapons[ SLOT_RANGED ].type != WEAPON_NONE ); + + p->ranged_attack = new hunter_ranged_attack_t( "auto_shot", p ); + } + + // TODO: Copy midnight's immediate first shot + + void execute() override + { + player->ranged_attack->schedule_execute(); + } + + bool ready() override + { + // TODO: Copied from auto attacks and not correct for hunter. + // Figure out the auto shot timers around movement. Roughly seemed like you could move until about 0.5s before + // the next shot to avoid interruption + if ( player->is_moving() ) + { + return false; + } + + return player->ranged_attack->execute_event == nullptr; + } +}; + +void hunter_t::init_base_stats() +{ + base.attack_power_per_strength = 1.0; + base.attack_power_per_agility = 1.0; + + base.ranged_attack_power_per_agility = 2.0; + + player_t::init_base_stats(); + + base.attack_crit_chance = 0; + // TODO Forever: Only checked on low level characters + base.spell_crit_chance = 0.036; + + base.stats.attack_power = 2.0 * level() - 20.0; + base.stats.ranged_attack_power = 2.0 * level() - 20.0; + + // TODO Forever: Dodge, parry chance +} + +action_t* hunter_t::create_action( util::string_view name, util::string_view options_str ) +{ + if ( name == "auto_shot" ) return new hunter_auto_shot_t( this, options_str ); + + return player_t::create_action( name, options_str ); +} + +void hunter_t::init_action_list() +{ + if ( !action_list_str.empty() ) + { + player_t::init_action_list(); + return; + } + + clear_action_priority_lists(); + hunter_apl::hunter( this ); + use_default_action_list = true; + player_t::init_action_list(); +} + // HUNTER MODULE INTERFACE ================================================== struct hunter_module_t: public module_t diff --git a/engine/item/item.cpp b/engine/item/item.cpp index 4400be16b5..b948c4b0ef 100644 --- a/engine/item/item.cpp +++ b/engine/item/item.cpp @@ -682,6 +682,7 @@ inventory_type item_t::dbc_inventory_type() const case WEAPON_STAFF: case WEAPON_POLEARM: return INVTYPE_2HWEAPON; + // TODO: Can we remove this branch in Forever since thrown go into the ranged slot? case WEAPON_THROWN: return INVTYPE_THROWN; default: @@ -1441,8 +1442,6 @@ bool item_t::verify_slot() case INVTYPE_2HWEAPON: case INVTYPE_WEAPON: case INVTYPE_WEAPONMAINHAND: - case INVTYPE_RANGED: - case INVTYPE_RANGEDRIGHT: return true; default: return false; @@ -1462,6 +1461,19 @@ bool item_t::verify_slot() return false; } } + case SLOT_RANGED: + { + switch ( it ) + { + case INVTYPE_RANGED: + case INVTYPE_RANGEDRIGHT: + case INVTYPE_THROWN: + case INVTYPE_RELIC: + return true; + default: + return false; + } + } case SLOT_FINGER_1: case SLOT_FINGER_2: return it == INVTYPE_FINGER; diff --git a/engine/item/special_effect.cpp b/engine/item/special_effect.cpp index d4fb99e1e4..344642186a 100644 --- a/engine/item/special_effect.cpp +++ b/engine/item/special_effect.cpp @@ -227,9 +227,11 @@ stat_e special_effect_t::stat_buff_type( const spelleffect_data_t& effect ) cons stat_ = STAT_BONUS_ARMOR; break; case A_MOD_ATTACK_POWER: - case A_MOD_RANGED_ATTACK_POWER: stat_ = STAT_ATTACK_POWER; break; + case A_MOD_RANGED_ATTACK_POWER: + stat_ = STAT_RANGED_ATTACK_POWER; + break; case A_MOD_DAMAGE_DONE: if ( effect.misc_value1() & 0x7E ) stat_ = STAT_SPELL_POWER; diff --git a/engine/player/gear_stats.hpp b/engine/player/gear_stats.hpp index 99a9d69c91..2e3c0e3af4 100644 --- a/engine/player/gear_stats.hpp +++ b/engine/player/gear_stats.hpp @@ -23,6 +23,7 @@ struct gear_stats_t double spell_power; std::array school_spell_power; double attack_power; + double ranged_attack_power; double expertise_rating; double expertise_rating2; double hit_rating; @@ -53,6 +54,7 @@ struct gear_stats_t spell_power( 0.0 ), school_spell_power(), attack_power( 0.0 ), + ranged_attack_power( 0.0 ), expertise_rating( 0.0 ), expertise_rating2( 0.0 ), hit_rating( 0.0 ), @@ -87,6 +89,7 @@ struct gear_stats_t spell_power = initializer; attack_power = initializer; + ranged_attack_power = initializer; expertise_rating = initializer; expertise_rating2 = initializer; hit_rating = initializer; @@ -122,6 +125,7 @@ struct gear_stats_t { spell_power += right.spell_power; attack_power += right.attack_power; + ranged_attack_power += right.ranged_attack_power; expertise_rating += right.expertise_rating; expertise_rating2 += right.expertise_rating2; hit_rating += right.hit_rating; @@ -156,4 +160,4 @@ struct gear_stats_t static double stat_mod( stat_e stat ); friend void sc_format_to( const gear_stats_t&, fmt::format_context::iterator ); -}; \ No newline at end of file +}; diff --git a/engine/player/player.cpp b/engine/player/player.cpp index 3369124d33..4fe24ee2a2 100644 --- a/engine/player/player.cpp +++ b/engine/player/player.cpp @@ -1034,6 +1034,7 @@ player_t::player_t( sim_t* s, player_e t, util::string_view n, race_e r ) // Attacks main_hand_attack( nullptr ), off_hand_attack( nullptr ), + ranged_attack( nullptr ), current_auto_attack_speed( 1.0 ), // Resources resources(), @@ -1249,6 +1250,7 @@ player_t::base_initial_current_t::base_initial_current_t() : attack_power_per_strength( 0 ), attack_power_per_agility( 0 ), attack_power_per_spell_power( 0 ), + ranged_attack_power_per_agility( 0 ), dodge_per_agility( 0 ), parry_per_strength( 0 ), parry_rating_per_crit_rating( 0 ), @@ -1288,6 +1290,7 @@ player_t::base_initial_current_t::base_initial_current_t() : rating_multiplier(), spell_power_multiplier( 1.0 ), attack_power_multiplier( 1.0 ), + ranged_attack_power_multiplier( 1.0 ), base_armor_multiplier( 1.0 ), armor_multiplier( 1.0 ), crit_damage_multiplier(), @@ -1322,6 +1325,7 @@ void sc_format_to( const player_t::base_initial_current_t& s, fmt::format_contex fmt::format_to( out, " attack_power_per_strength={:.6g}", s.attack_power_per_strength ); fmt::format_to( out, " attack_power_per_agility={:.6g}", s.attack_power_per_agility ); fmt::format_to( out, " attack_power_per_spell_power={:.6g}", s.attack_power_per_spell_power ); + fmt::format_to( out, " ranged_attack_power_per_agility={:.6g}", s.ranged_attack_power_per_agility ); fmt::format_to( out, " dodge_per_agility={:.6g}", s.dodge_per_agility ); fmt::format_to( out, " parry_per_strength={:.6g}", s.parry_per_strength ); fmt::format_to( out, " parry_rating_per_crit_rating={:.6g}", s.parry_rating_per_crit_rating ); @@ -1359,6 +1363,7 @@ void sc_format_to( const player_t::base_initial_current_t& s, fmt::format_contex fmt::format_to( out, " spell_power_multiplier={:.6g}", s.spell_power_multiplier ); fmt::format_to( out, " attack_power_multiplier={:.6g}", s.attack_power_multiplier ); + fmt::format_to( out, " ranged_attack_power_multiplier={:.6g}", s.ranged_attack_power_multiplier ); fmt::format_to( out, " base_armor_multiplier={:.6g}", s.base_armor_multiplier ); fmt::format_to( out, " armor_multiplier={:.6g}", s.armor_multiplier ); for ( auto school = SCHOOL_NONE; school < SCHOOL_MAX_PRIMARY; ++school ) @@ -1551,6 +1556,7 @@ void player_t::init_base_stats() base.attack_speed_multiplier /= get_passive_player_value( attack_speed_multiplier, "attack_speed" ); base.attack_speed_multiplier *= get_passive_player_value( attack_speed_multiplier, "attack_speed_modifier" ); base.attack_power_multiplier = get_passive_player_value( base.attack_power_multiplier, "attack_power_multiplier" ); + base.ranged_attack_power_multiplier = get_passive_player_value( base.ranged_attack_power_multiplier, "ranged_attack_power_multiplier" ); base.absorb_multiplier = get_passive_player_value( base.absorb_multiplier, "absorb_multiplier" ); base.healing_multiplier = get_passive_player_value( base.healing_multiplier, "healing_multiplier" ); @@ -3442,6 +3448,8 @@ void player_t::init_scaling() scaling->set( STAT_SPELL_POWER, spell ); scaling->set( STAT_ATTACK_POWER, attack ); + // TODO Forever: Limit this to hunters + scaling->set( STAT_RANGED_ATTACK_POWER, attack ); scaling->enable( STAT_CRIT_RATING ); scaling->enable( STAT_HASTE_RATING ); @@ -3490,6 +3498,10 @@ void player_t::init_scaling() add_stat( initial.stats.attack_power, v, 0 ); break; + case STAT_RANGED_ATTACK_POWER: + add_stat( initial.stats.ranged_attack_power, v, 0 ); + break; + case STAT_CRIT_RATING: add_stat( initial.stats.crit_rating, v, 0 ); break; @@ -4443,6 +4455,15 @@ double player_t::composite_melee_attack_power() const return ap; } +double player_t::composite_ranged_attack_power() const +{ + double ap = current.stats.ranged_attack_power; + + ap += current.ranged_attack_power_per_agility * cache.agility(); + + return ap; +} + double player_t::composite_weapon_attack_power_by_type( attack_power_type ap_type ) const { double wdps = 0; @@ -4518,6 +4539,11 @@ double player_t::composite_total_attack_power_by_type( attack_power_type ap_type return std::round( current.attack_power_per_spell_power * sp ); } + if ( ap_type == attack_power_type::RANGED ) + { + return std::round( cache.ranged_attack_power() * composite_ranged_attack_power_multiplier() ); + } + auto mul = composite_attack_power_multiplier(); // total attack power is rounded to integer @@ -4539,6 +4565,22 @@ double player_t::composite_attack_power_multiplier() const return std::round( m * 1000 ) * 0.001; } +double player_t::composite_ranged_attack_power_multiplier() const +{ + if ( is_pet() || is_enemy() || type == HEALING_ENEMY || current.attack_power_per_spell_power > 0 ) + { + return 1.0; + } + + double m = current.ranged_attack_power_multiplier; + + // TODO Forever: Trueshot aura? + // TODO Forever: Hunter's mark? + + // multiplier is rounded to 3 digits + return std::round( m * 1000 ) * 0.001; +} + double player_t::composite_melee_crit_chance() const { double ac = current.attack_crit_chance; @@ -5238,6 +5280,8 @@ void player_t::invalidate_cache( cache_e c ) case CACHE_AGILITY: if ( current.attack_power_per_agility > 0 ) invalidate_cache( CACHE_ATTACK_POWER ); + if ( current.ranged_attack_power_per_agility > 0 ) + invalidate_cache( CACHE_RANGED_ATTACK_POWER ); if ( current.dodge_per_agility > 0 ) invalidate_cache( CACHE_DODGE ); if ( current.attack_crit_per_agility > 0 ) @@ -5283,6 +5327,8 @@ void player_t::invalidate_cache( cache_e c ) invalidate_cache( CACHE_PARRY ); break; + // TODO Forever: Do we need cases for AP and RAP? + default: break; } @@ -6561,6 +6607,8 @@ void player_t::halt() main_hand_attack->cancel(); if ( off_hand_attack ) off_hand_attack->cancel(); + if ( ranged_attack ) + ranged_attack->cancel(); } void player_t::stun() @@ -6690,6 +6738,8 @@ double player_t::get_stat_value(stat_e stat) return cache.spell_power( SCHOOL_NONE ); case STAT_ATTACK_POWER: return cache.attack_power(); + case STAT_RANGED_ATTACK_POWER: + return cache.ranged_attack_power(); case STAT_CRIT_RATING: return composite_melee_crit_rating(); case STAT_HASTE_RATING: @@ -7047,6 +7097,7 @@ void player_t::stat_gain( stat_e stat, double amount, gain_t* gain, action_t* ac case STAT_NATURE_SPELL_POWER: case STAT_SHADOW_SPELL_POWER: case STAT_ATTACK_POWER: + case STAT_RANGED_ATTACK_POWER: case STAT_EXPERTISE_RATING: case STAT_HIT_RATING: case STAT_CRIT_RATING: @@ -7212,6 +7263,7 @@ void player_t::stat_loss( stat_e stat, double amount, gain_t* gain, action_t* ac case STAT_NATURE_SPELL_POWER: case STAT_SHADOW_SPELL_POWER: case STAT_ATTACK_POWER: + case STAT_RANGED_ATTACK_POWER: case STAT_EXPERTISE_RATING: case STAT_HIT_RATING: case STAT_CRIT_RATING: @@ -11276,6 +11328,13 @@ std::unique_ptr player_t::create_expression( util::string_view expressio } ); } + case STAT_RANGED_ATTACK_POWER: + { + return make_fn_expr( expression_str, [ this ] { + return static_cast( cache.ranged_attack_power() * composite_ranged_attack_power_multiplier() ); + } ); + } + case STAT_EXPERTISE_RATING: return make_mem_fn_expr( expression_str, *this, &player_t::composite_expertise_rating ); case STAT_HIT_RATING: @@ -12167,6 +12226,9 @@ std::string player_t::create_profile( save_e stype ) if ( enchant.attack_power != 0 ) profile_str += "enchant_attack_power=" + util::to_string( enchant.attack_power ) + term; + if ( enchant.ranged_attack_power != 0 ) + profile_str += "enchant_ranged_attack_power=" + util::to_string( enchant.ranged_attack_power ) + term; + if ( enchant.expertise_rating != 0 ) profile_str += "enchant_expertise_rating=" + util::to_string( enchant.expertise_rating ) + term; @@ -12373,6 +12435,7 @@ void player_t::create_options() add_option( opt_string( "back", items[ SLOT_BACK ].options_str ) ); add_option( opt_string( "main_hand", items[ SLOT_MAIN_HAND ].options_str ) ); add_option( opt_string( "off_hand", items[ SLOT_OFF_HAND ].options_str ) ); + add_option( opt_string( "ranged", items[ SLOT_RANGED ].options_str ) ); add_option( opt_string( "tabard", items[ SLOT_TABARD ].options_str ) ); // Set Bonus @@ -12387,6 +12450,7 @@ void player_t::create_options() add_option( opt_float( "gear_spirit", gear.attribute[ ATTR_SPIRIT ] ) ); add_option( opt_float( "gear_spell_power", gear.spell_power ) ); add_option( opt_float( "gear_attack_power", gear.attack_power ) ); + add_option( opt_float( "gear_ranged_attack_power", gear.ranged_attack_power ) ); add_option( opt_float( "gear_expertise_rating", gear.expertise_rating ) ); add_option( opt_float( "gear_haste_rating", gear.haste_rating ) ); add_option( opt_float( "gear_hit_rating", gear.hit_rating ) ); @@ -12414,6 +12478,7 @@ void player_t::create_options() add_option( opt_float( "enchant_spirit", enchant.attribute[ ATTR_SPIRIT ] ) ); add_option( opt_float( "enchant_spell_power", enchant.spell_power ) ); add_option( opt_float( "enchant_attack_power", enchant.attack_power ) ); + add_option( opt_float( "enchant_ranged_attack_power", enchant.ranged_attack_power ) ); add_option( opt_float( "enchant_expertise_rating", enchant.expertise_rating ) ); add_option( opt_float( "enchant_armor", enchant.armor ) ); add_option( opt_float( "enchant_haste_rating", enchant.haste_rating ) ); @@ -13579,10 +13644,10 @@ void player_t::delay_ranged_auto_attacks( timespan_t delay, proc_t* proc ) bool delayed = false; - if ( main_hand_attack && main_hand_attack->execute_event && dynamic_cast(main_hand_attack) ) + if ( ranged_attack && ranged_attack->execute_event && dynamic_cast(ranged_attack) ) { - sim->print_debug( "Delaying ranged auto attack swing timer by {} to {}", delay, main_hand_attack->execute_event->remains() + delay ); - main_hand_attack->execute_event->reschedule( main_hand_attack->execute_event->remains() + delay ); + sim->print_debug( "Delaying ranged auto attack swing timer by {} to {}", delay, ranged_attack->execute_event->remains() + delay ); + ranged_attack->execute_event->reschedule( ranged_attack->execute_event->remains() + delay ); delayed = true; } @@ -14031,6 +14096,7 @@ static constexpr std::pair field_type_map[] = { { A_MOD_BASE_RESISTANCE_PCT, "base_armor_multiplier" }, // 142 { A_MOD_CRIT_DAMAGE_MULTIPLIER, "crit_damage_multiplier" }, // 163 { A_MOD_ATTACK_POWER_PCT, "attack_power_multiplier", }, // 166 + { A_MOD_RANGED_ATTACK_POWER_PCT, "ranged_attack_power_multiplier", }, // 167 { A_MOD_RECHARGE_TIME_PCT_CATEGORY_MASK, "charge_cooldown" }, // 173 { A_MOD_SPELL_DAMAGE_OF_STAT_PERCENT, "spell_power_from_attribute" }, // 174 { A_MOD_MAX_MANA_PCT, "resource_multiplier" }, // 178 @@ -14401,6 +14467,7 @@ bool player_t::register_passive_effect( const spelleffect_data_t& modifying_eff, case A_MOD_HEALING_DONE_PERCENT: // 136 case A_MOD_CRIT_DAMAGE_MULTIPLIER: // 163 case A_MOD_ATTACK_POWER_PCT: // 166 + case A_MOD_RANGED_ATTACK_POWER_PCT: // 167 case A_HASTE_ALL: // 193 case A_MOD_MECHANIC_DAMAGE_DONE_PERCENT: // 276 case A_MOD_MELEE_AUTO_ATTACK_SPEED: // 319 diff --git a/engine/player/player.hpp b/engine/player/player.hpp index 14bceddbb5..5abb562693 100644 --- a/engine/player/player.hpp +++ b/engine/player/player.hpp @@ -203,6 +203,7 @@ struct player_t : public actor_t double spell_power_per_intellect, spell_power_per_spirit, spell_power_per_attack_power; double attack_power_per_strength, attack_power_per_agility, attack_power_per_spell_power; + double ranged_attack_power_per_agility; double dodge_per_agility, parry_per_strength, parry_rating_per_crit_rating; double attack_crit_per_agility, spell_crit_per_intellect; double health_per_stamina; @@ -224,7 +225,7 @@ struct player_t : public actor_t std::array attribute_multiplier; std::array matching_armor_multiplier; std::array rating_multiplier; - double spell_power_multiplier, attack_power_multiplier, base_armor_multiplier, armor_multiplier; + double spell_power_multiplier, attack_power_multiplier, ranged_attack_power_multiplier, base_armor_multiplier, armor_multiplier; std::array crit_damage_multiplier; double crit_healing_multiplier; double attack_speed_multiplier; @@ -268,6 +269,7 @@ struct player_t : public actor_t std::map melee_attacks; attack_t* main_hand_attack; attack_t* off_hand_attack; + attack_t* ranged_attack; // Current attack speed (needed for dynamic attack speed adjustments) double current_auto_attack_speed; @@ -878,8 +880,10 @@ struct player_t : public actor_t virtual double composite_melee_haste() const; virtual double composite_melee_auto_attack_speed() const; virtual double composite_melee_attack_power() const; + virtual double composite_ranged_attack_power() const; virtual double composite_weapon_attack_power_by_type( attack_power_type ) const; virtual double composite_total_attack_power_by_type( attack_power_type ) const; + // TODO Forever: Do I need weapon ranged attack power composites here? virtual double composite_melee_hit() const; virtual double composite_melee_crit_chance() const; virtual double composite_melee_crit_chance_multiplier() const @@ -911,6 +915,7 @@ struct player_t : public actor_t 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_ranged_attack_power_multiplier() const; virtual double composite_spell_power_multiplier() const; virtual double matching_gear_multiplier( attribute_e /* attr */ ) const; /// Player-wide school based multipliers diff --git a/engine/player/player_stat_cache.cpp b/engine/player/player_stat_cache.cpp index 461ecb186e..17aa6edffb 100644 --- a/engine/player/player_stat_cache.cpp +++ b/engine/player/player_stat_cache.cpp @@ -169,6 +169,18 @@ double player_stat_cache_t::attack_power() const return _attack_power; } +double player_stat_cache_t::ranged_attack_power() const +{ + if ( !active || !valid[ CACHE_RANGED_ATTACK_POWER ] ) + { + valid[ CACHE_RANGED_ATTACK_POWER ] = true; + _ranged_attack_power = player->composite_ranged_attack_power(); + } + else + assert( _ranged_attack_power == player->composite_ranged_attack_power() ); + return _ranged_attack_power; +} + double player_stat_cache_t::weapon_attack_power( attack_power_type t ) const { auto type = static_cast( t ); @@ -489,6 +501,7 @@ double player_stat_cache_t::intellect() const { return _player->intellect(); } double player_stat_cache_t::spirit() const { return _player->spirit(); } double player_stat_cache_t::spell_power( school_e s ) const { return _player->composite_spell_power( s ); } double player_stat_cache_t::attack_power() const { return _player->composite_melee_attack_power(); } +double player_stat_cache_t::ranged_attack_power() const { return _player->composite_ranged_attack_power(); } double player_stat_cache_t::weapon_attack_power( attack_power_type t ) { const return _player->composite_weapon_attack_power_by_type( t ); } double player_stat_cache_t::attack_expertise() const { return _player->composite_melee_expertise(); } double player_stat_cache_t::attack_hit() const { return _player->composite_melee_hit(); } diff --git a/engine/player/player_stat_cache.hpp b/engine/player/player_stat_cache.hpp index dbb740da0b..8dae9e7a2a 100644 --- a/engine/player/player_stat_cache.hpp +++ b/engine/player/player_stat_cache.hpp @@ -49,6 +49,7 @@ struct player_stat_cache_t mutable double _strength, _agility, _stamina, _intellect, _spirit; mutable std::array _spell_power; mutable double _attack_power; + mutable double _ranged_attack_power; mutable std::array( attack_power_type::NONE )> _weapon_attack_power; mutable double _attack_expertise; mutable double _attack_hit, _spell_hit; @@ -77,6 +78,7 @@ struct player_stat_cache_t double spirit() const; double spell_power( school_e ) const; double attack_power() const; + double ranged_attack_power() const; double weapon_attack_power( attack_power_type ) const; double attack_expertise() const; double attack_hit() const; @@ -111,6 +113,7 @@ struct player_stat_cache_t double spirit() const { return _player->spirit(); } double spell_power( school_e s ) const { return _player->composite_spell_power( s ); } double attack_power() const { return _player->composite_melee_attack_power(); } + double ranged_attack_power() const { return _player->composite_ranged_attack_power(); } double weapon_attack_power( attack_power_type t ) const { return _player->composite_weapon_attack_power_by_type( t ); } double attack_expertise() const { return _player->composite_melee_expertise(); } double attack_hit() const { return _player->composite_melee_hit(); } diff --git a/engine/sc_enums.hpp b/engine/sc_enums.hpp index c0bdaef9b1..1f5f620eb8 100644 --- a/engine/sc_enums.hpp +++ b/engine/sc_enums.hpp @@ -44,6 +44,9 @@ enum class attack_power_type : unsigned // Attack power is purely based on player power (main stat) NO_WEAPON, + // Attack power is a composite of RAP and ranged weapon DPS + RANGED, + // Let SimC figure out BfA attack power mode based on information assigned to the action object. NONE, @@ -847,6 +850,7 @@ enum stat_e STAT_NATURE_SPELL_POWER, STAT_SHADOW_SPELL_POWER, STAT_ATTACK_POWER, + STAT_RANGED_ATTACK_POWER, STAT_EXPERTISE_RATING, STAT_EXPERTISE_RATING2, STAT_HIT_RATING, @@ -951,6 +955,7 @@ enum cache_e CACHE_STR_AGI_INT, CACHE_SPELL_POWER, CACHE_ATTACK_POWER, + CACHE_RANGED_ATTACK_POWER, CACHE_WEAPON_DPS, CACHE_EXP, CACHE_ATTACK_EXP, @@ -1035,6 +1040,8 @@ inline cache_e cache_from_stat( stat_e st ) return CACHE_SPELL_POWER; case STAT_ATTACK_POWER: return CACHE_ATTACK_POWER; + case STAT_RANGED_ATTACK_POWER: + return CACHE_RANGED_ATTACK_POWER; case STAT_WEAPON_DPS: case STAT_WEAPON_OFFHAND_DPS: return CACHE_WEAPON_DPS; diff --git a/engine/sim/gear_stats.cpp b/engine/sim/gear_stats.cpp index 881280b6c5..9eea7b83fd 100644 --- a/engine/sim/gear_stats.cpp +++ b/engine/sim/gear_stats.cpp @@ -55,6 +55,7 @@ void gear_stats_t::add_stat( stat_e stat, case STAT_SHADOW_SPELL_POWER: school_spell_power[ SCHOOL_SHADOW ] += value; break; case STAT_ATTACK_POWER: attack_power += value; break; + case STAT_RANGED_ATTACK_POWER: ranged_attack_power += value; break; case STAT_EXPERTISE_RATING: expertise_rating += value; break; case STAT_EXPERTISE_RATING2: expertise_rating2 += value; break; @@ -136,6 +137,7 @@ void gear_stats_t::set_stat( stat_e stat, case STAT_SHADOW_SPELL_POWER: school_spell_power[ SCHOOL_SHADOW ] = value; break; case STAT_ATTACK_POWER: attack_power = value; break; + case STAT_RANGED_ATTACK_POWER: ranged_attack_power = value; break; case STAT_EXPERTISE_RATING: expertise_rating = value; break; case STAT_EXPERTISE_RATING2: expertise_rating2 = value; break; @@ -216,6 +218,7 @@ double gear_stats_t::get_stat( stat_e stat ) const case STAT_SHADOW_SPELL_POWER: return school_spell_power[ SCHOOL_SHADOW ]; case STAT_ATTACK_POWER: return attack_power; + case STAT_RANGED_ATTACK_POWER: return ranged_attack_power; case STAT_EXPERTISE_RATING: return expertise_rating; case STAT_EXPERTISE_RATING2: return expertise_rating2; @@ -257,9 +260,10 @@ double gear_stats_t::stat_mod( stat_e stat ) { switch ( stat ) { - case STAT_ATTACK_POWER: return 1.0; - case STAT_SPELL_POWER: return 1.0; - default: return 1.0; + case STAT_ATTACK_POWER: return 1.0; + case STAT_RANGED_ATTACK_POWER: return 1.0; + case STAT_SPELL_POWER: return 1.0; + default: return 1.0; } } diff --git a/engine/util/util.cpp b/engine/util/util.cpp index fce9e439c1..8ba00c4cb4 100644 --- a/engine/util/util.cpp +++ b/engine/util/util.cpp @@ -260,6 +260,10 @@ double util::stat_value( const player_t* p, stat_e stat ) { v -= p->sim->scaling->scale_value * p->composite_attack_power_multiplier(); } + else if ( stat == STAT_RANGED_ATTACK_POWER ) + { + v -= p->sim->scaling->scale_value * p->composite_ranged_attack_power_multiplier(); + } } return v; @@ -1119,6 +1123,7 @@ const char* util::slot_type_string( slot_e slot ) case SLOT_BACK: return "back"; case SLOT_MAIN_HAND: return "main_hand"; case SLOT_OFF_HAND: return "off_hand"; + case SLOT_RANGED: return "ranged"; case SLOT_TABARD: return "tabard"; default: return "unknown"; } @@ -1253,6 +1258,7 @@ const char* util::cache_type_string( cache_e c ) case CACHE_SPIRIT: return "spirit"; case CACHE_SPELL_POWER: return "spell_power"; case CACHE_ATTACK_POWER: return "attack_power"; + case CACHE_RANGED_ATTACK_POWER: return "ranged_attack_power"; case CACHE_WEAPON_DPS: return "weapon_dps"; case CACHE_EXP: return "expertise"; case CACHE_ATTACK_EXP: return "attack_expertise"; @@ -1426,6 +1432,7 @@ const char* util::stat_type_string( stat_e stat ) case STAT_SHADOW_SPELL_POWER: return "shadow_spell_power"; case STAT_ATTACK_POWER: return "attack_power"; + case STAT_RANGED_ATTACK_POWER: return "ranged_attack_power"; case STAT_EXPERTISE_RATING: return "expertise_rating"; case STAT_EXPERTISE_RATING2: return "inverse_expertise_rating"; @@ -1518,6 +1525,7 @@ const char* util::stat_type_abbrev( stat_e stat ) case STAT_SHADOW_SPELL_POWER: return "ShadowSP"; case STAT_ATTACK_POWER: return "AP"; + case STAT_RANGED_ATTACK_POWER: return "RAP"; case STAT_EXPERTISE_RATING: return "Exp"; case STAT_EXPERTISE_RATING2: return "InvExp"; @@ -1580,6 +1588,8 @@ const char* util::stat_type_wowhead( stat_e stat ) case STAT_SPELL_POWER: return "spellPower"; case STAT_ATTACK_POWER: return "attackPower"; + // TODO Forever: Not sure how to verify this + case STAT_RANGED_ATTACK_POWER: return "rangedAttackPower"; case STAT_EXPERTISE_RATING: return "expertiseRating"; case STAT_HIT_RATING: return "hitRating"; @@ -2063,41 +2073,41 @@ stat_e util::translate_item_mod( int item_mod ) { switch ( item_mod ) { - case ITEM_MOD_AGILITY: return STAT_AGILITY; - case ITEM_MOD_STRENGTH: return STAT_STRENGTH; - case ITEM_MOD_INTELLECT: return STAT_INTELLECT; - case ITEM_MOD_SPIRIT: return STAT_SPIRIT; - case ITEM_MOD_STAMINA: return STAT_STAMINA; - case ITEM_MOD_DODGE_RATING: return STAT_DODGE_RATING; - case ITEM_MOD_PARRY_RATING: return STAT_PARRY_RATING; - case ITEM_MOD_BLOCK_RATING: return STAT_BLOCK_RATING; - case ITEM_MOD_HIT_RATING: return STAT_HIT_RATING; - case ITEM_MOD_CRIT_RATING: return STAT_CRIT_RATING; - case ITEM_MOD_HASTE_RATING: return STAT_HASTE_RATING; - case ITEM_MOD_EXPERTISE_RATING: return STAT_EXPERTISE_RATING; - case ITEM_MOD_ATTACK_POWER: return STAT_ATTACK_POWER; - case ITEM_MOD_RANGED_ATTACK_POWER: return STAT_ATTACK_POWER; - case ITEM_MOD_SPELL_POWER: return STAT_SPELL_POWER; - case ITEM_MOD_SPELL_DAMAGE_DONE: return STAT_SPELL_POWER; - case ITEM_MOD_ARCANE_SPELL_POWER: return STAT_ARCANE_SPELL_POWER; - case ITEM_MOD_FIRE_SPELL_POWER: return STAT_FIRE_SPELL_POWER; - case ITEM_MOD_FROST_SPELL_POWER: return STAT_FROST_SPELL_POWER; - case ITEM_MOD_HOLY_SPELL_POWER: return STAT_HOLY_SPELL_POWER; - case ITEM_MOD_NATURE_SPELL_POWER: return STAT_NATURE_SPELL_POWER; - case ITEM_MOD_SHADOW_SPELL_POWER: return STAT_SHADOW_SPELL_POWER; - case ITEM_MOD_EXTRA_ARMOR: return STAT_BONUS_ARMOR; - case ITEM_MOD_RESILIENCE_RATING: return STAT_RESILIENCE_RATING; - case ITEM_MOD_PVP_POWER: return STAT_PVP_POWER; + case ITEM_MOD_AGILITY: return STAT_AGILITY; + case ITEM_MOD_STRENGTH: return STAT_STRENGTH; + case ITEM_MOD_INTELLECT: return STAT_INTELLECT; + case ITEM_MOD_SPIRIT: return STAT_SPIRIT; + case ITEM_MOD_STAMINA: return STAT_STAMINA; + case ITEM_MOD_DODGE_RATING: return STAT_DODGE_RATING; + case ITEM_MOD_PARRY_RATING: return STAT_PARRY_RATING; + case ITEM_MOD_BLOCK_RATING: return STAT_BLOCK_RATING; + case ITEM_MOD_HIT_RATING: return STAT_HIT_RATING; + case ITEM_MOD_CRIT_RATING: return STAT_CRIT_RATING; + case ITEM_MOD_HASTE_RATING: return STAT_HASTE_RATING; + case ITEM_MOD_EXPERTISE_RATING: return STAT_EXPERTISE_RATING; + case ITEM_MOD_ATTACK_POWER: return STAT_ATTACK_POWER; + case ITEM_MOD_RANGED_ATTACK_POWER: return STAT_RANGED_ATTACK_POWER; + case ITEM_MOD_SPELL_POWER: return STAT_SPELL_POWER; + case ITEM_MOD_SPELL_DAMAGE_DONE: return STAT_SPELL_POWER; + case ITEM_MOD_ARCANE_SPELL_POWER: return STAT_ARCANE_SPELL_POWER; + case ITEM_MOD_FIRE_SPELL_POWER: return STAT_FIRE_SPELL_POWER; + case ITEM_MOD_FROST_SPELL_POWER: return STAT_FROST_SPELL_POWER; + case ITEM_MOD_HOLY_SPELL_POWER: return STAT_HOLY_SPELL_POWER; + case ITEM_MOD_NATURE_SPELL_POWER: return STAT_NATURE_SPELL_POWER; + case ITEM_MOD_SHADOW_SPELL_POWER: return STAT_SHADOW_SPELL_POWER; + case ITEM_MOD_EXTRA_ARMOR: return STAT_BONUS_ARMOR; + case ITEM_MOD_RESILIENCE_RATING: return STAT_RESILIENCE_RATING; + case ITEM_MOD_PVP_POWER: return STAT_PVP_POWER; case ITEM_MOD_STRENGTH_AGILITY_INTELLECT: return STAT_STR_AGI_INT; - case ITEM_MOD_AGILITY_INTELLECT: return STAT_AGI_INT; - case ITEM_MOD_STRENGTH_AGILITY: return STAT_STR_AGI; - case ITEM_MOD_STRENGTH_INTELLECT: return STAT_STR_INT; - case ITEM_MOD_LEECH_RATING: return STAT_LEECH_RATING; - case ITEM_MOD_SPEED_RATING: return STAT_SPEED_RATING; - case ITEM_MOD_AVOIDANCE_RATING: return STAT_AVOIDANCE_RATING; - case ITEM_MOD_CORRUPTION: return STAT_CORRUPTION; - case ITEM_MOD_CORRUPTION_RESISTANCE: return STAT_CORRUPTION_RESISTANCE; - default: return STAT_NONE; + case ITEM_MOD_AGILITY_INTELLECT: return STAT_AGI_INT; + case ITEM_MOD_STRENGTH_AGILITY: return STAT_STR_AGI; + case ITEM_MOD_STRENGTH_INTELLECT: return STAT_STR_INT; + case ITEM_MOD_LEECH_RATING: return STAT_LEECH_RATING; + case ITEM_MOD_SPEED_RATING: return STAT_SPEED_RATING; + case ITEM_MOD_AVOIDANCE_RATING: return STAT_AVOIDANCE_RATING; + case ITEM_MOD_CORRUPTION: return STAT_CORRUPTION; + case ITEM_MOD_CORRUPTION_RESISTANCE: return STAT_CORRUPTION_RESISTANCE; + default: return STAT_NONE; } } @@ -2105,38 +2115,39 @@ int util::translate_stat( stat_e stat ) { switch ( stat ) { - case STAT_AGILITY: return ITEM_MOD_AGILITY; - case STAT_STRENGTH: return ITEM_MOD_STRENGTH; - case STAT_INTELLECT: return ITEM_MOD_INTELLECT; - case STAT_SPIRIT: return ITEM_MOD_SPIRIT; - case STAT_STAMINA: return ITEM_MOD_STAMINA; - case STAT_DODGE_RATING: return ITEM_MOD_DODGE_RATING; - case STAT_PARRY_RATING: return ITEM_MOD_PARRY_RATING; - case STAT_HIT_RATING: return ITEM_MOD_HIT_RATING; - case STAT_CRIT_RATING: return ITEM_MOD_CRIT_RATING; - case STAT_HASTE_RATING: return ITEM_MOD_HASTE_RATING; - case STAT_EXPERTISE_RATING: return ITEM_MOD_EXPERTISE_RATING; - case STAT_ATTACK_POWER: return ITEM_MOD_ATTACK_POWER; - case STAT_SPELL_POWER: return ITEM_MOD_SPELL_POWER; - case STAT_ARCANE_SPELL_POWER: return ITEM_MOD_ARCANE_SPELL_POWER; - case STAT_FIRE_SPELL_POWER: return ITEM_MOD_FIRE_SPELL_POWER; - case STAT_FROST_SPELL_POWER: return ITEM_MOD_FROST_SPELL_POWER; - case STAT_HOLY_SPELL_POWER: return ITEM_MOD_HOLY_SPELL_POWER; - case STAT_NATURE_SPELL_POWER: return ITEM_MOD_NATURE_SPELL_POWER; - case STAT_SHADOW_SPELL_POWER: return ITEM_MOD_SHADOW_SPELL_POWER; - case STAT_BONUS_ARMOR: return ITEM_MOD_EXTRA_ARMOR; - case STAT_RESILIENCE_RATING: return ITEM_MOD_RESILIENCE_RATING; - case STAT_PVP_POWER: return ITEM_MOD_PVP_POWER; - case STAT_STR_AGI_INT: return ITEM_MOD_STRENGTH_AGILITY_INTELLECT; - case STAT_AGI_INT: return ITEM_MOD_AGILITY_INTELLECT; - case STAT_STR_AGI: return ITEM_MOD_STRENGTH_AGILITY; - case STAT_STR_INT: return ITEM_MOD_STRENGTH_INTELLECT; - case STAT_LEECH_RATING: return ITEM_MOD_LEECH_RATING; - case STAT_SPEED_RATING: return ITEM_MOD_SPEED_RATING; - case STAT_AVOIDANCE_RATING: return ITEM_MOD_AVOIDANCE_RATING; - case STAT_CORRUPTION: return ITEM_MOD_CORRUPTION; + case STAT_AGILITY: return ITEM_MOD_AGILITY; + case STAT_STRENGTH: return ITEM_MOD_STRENGTH; + case STAT_INTELLECT: return ITEM_MOD_INTELLECT; + case STAT_SPIRIT: return ITEM_MOD_SPIRIT; + case STAT_STAMINA: return ITEM_MOD_STAMINA; + case STAT_DODGE_RATING: return ITEM_MOD_DODGE_RATING; + case STAT_PARRY_RATING: return ITEM_MOD_PARRY_RATING; + case STAT_HIT_RATING: return ITEM_MOD_HIT_RATING; + case STAT_CRIT_RATING: return ITEM_MOD_CRIT_RATING; + case STAT_HASTE_RATING: return ITEM_MOD_HASTE_RATING; + case STAT_EXPERTISE_RATING: return ITEM_MOD_EXPERTISE_RATING; + case STAT_ATTACK_POWER: return ITEM_MOD_ATTACK_POWER; + case STAT_RANGED_ATTACK_POWER: return ITEM_MOD_RANGED_ATTACK_POWER; + case STAT_SPELL_POWER: return ITEM_MOD_SPELL_POWER; + case STAT_ARCANE_SPELL_POWER: return ITEM_MOD_ARCANE_SPELL_POWER; + case STAT_FIRE_SPELL_POWER: return ITEM_MOD_FIRE_SPELL_POWER; + case STAT_FROST_SPELL_POWER: return ITEM_MOD_FROST_SPELL_POWER; + case STAT_HOLY_SPELL_POWER: return ITEM_MOD_HOLY_SPELL_POWER; + case STAT_NATURE_SPELL_POWER: return ITEM_MOD_NATURE_SPELL_POWER; + case STAT_SHADOW_SPELL_POWER: return ITEM_MOD_SHADOW_SPELL_POWER; + case STAT_BONUS_ARMOR: return ITEM_MOD_EXTRA_ARMOR; + case STAT_RESILIENCE_RATING: return ITEM_MOD_RESILIENCE_RATING; + case STAT_PVP_POWER: return ITEM_MOD_PVP_POWER; + case STAT_STR_AGI_INT: return ITEM_MOD_STRENGTH_AGILITY_INTELLECT; + case STAT_AGI_INT: return ITEM_MOD_AGILITY_INTELLECT; + case STAT_STR_AGI: return ITEM_MOD_STRENGTH_AGILITY; + case STAT_STR_INT: return ITEM_MOD_STRENGTH_INTELLECT; + case STAT_LEECH_RATING: return ITEM_MOD_LEECH_RATING; + case STAT_SPEED_RATING: return ITEM_MOD_SPEED_RATING; + case STAT_AVOIDANCE_RATING: return ITEM_MOD_AVOIDANCE_RATING; + case STAT_CORRUPTION: return ITEM_MOD_CORRUPTION; case STAT_CORRUPTION_RESISTANCE: return ITEM_MOD_CORRUPTION_RESISTANCE; - default: return ITEM_MOD_NONE; + default: return ITEM_MOD_NONE; } } @@ -2310,15 +2321,16 @@ slot_e util::translate_invtype( inventory_type inv_type ) case INVTYPE_2HWEAPON: case INVTYPE_WEAPON: case INVTYPE_WEAPONMAINHAND: - case INVTYPE_RANGED: - case INVTYPE_RANGEDRIGHT: return SLOT_MAIN_HAND; case INVTYPE_WEAPONOFFHAND: case INVTYPE_SHIELD: case INVTYPE_HOLDABLE: return SLOT_OFF_HAND; case INVTYPE_THROWN: - return SLOT_MAIN_HAND; + case INVTYPE_RANGED: + case INVTYPE_RANGEDRIGHT: + case INVTYPE_RELIC: + return SLOT_RANGED; case INVTYPE_CHEST: case INVTYPE_ROBE: return SLOT_CHEST; @@ -3225,8 +3237,11 @@ void util::fuzzy_stats( std::string& encoding, stat_search( encoding, splits, STAT_SPELL_POWER, "spell power" ); - stat_search( encoding, splits, STAT_ATTACK_POWER, "attack power" ); - stat_search( encoding, splits, STAT_EXPERTISE_RATING, "expertise rating" ); + stat_search( encoding, splits, STAT_ATTACK_POWER, "attack power" ); + // TODO Forever: I think this is very old code that doesn't atually get used but need to be careful + // since "attack power" would string match "ranged attack power" + stat_search( encoding, splits, STAT_RANGED_ATTACK_POWER, "ranged attack power" ); + stat_search( encoding, splits, STAT_EXPERTISE_RATING, "expertise rating" ); stat_search( encoding, splits, STAT_HASTE_RATING, "haste" ); stat_search( encoding, splits, STAT_HASTE_RATING, "haste rating" ); diff --git a/profiles/CI.simc b/profiles/CI.simc index 53d4363d07..299d2a42fc 100644 --- a/profiles/CI.simc +++ b/profiles/CI.simc @@ -5,5 +5,6 @@ single_actor_batch=1 profiles/LowLevel/shaman-20.simc profiles/LowLevel/warrior-11.simc profiles/LowLevel/warlock-10.simc +profiles/LowLevel/hunter-11.simc iterations=100 diff --git a/profiles/LowLevel/hunter-11.simc b/profiles/LowLevel/hunter-11.simc new file mode 100644 index 0000000000..6fe355e65e --- /dev/null +++ b/profiles/LowLevel/hunter-11.simc @@ -0,0 +1,109 @@ +# Fallochli - 2026-10-19 16:10 - /Classic Beta PvE 2 +# SimC Addon 12.1.5-02 +# WoW 1.60.1.70450, TOC 16001 +# Requires SimulationCraft 1000-01 or newer + +hunter="Fallochli" +level=11 +race=dwarf +region= +server=classic_beta_pve_2 +role= +professions=skinning=56 +spec=unknown + +talents=C0cBZcHs6hiHBl7Csp3hvR+3RCAwDMAAAAAA + +# Primary Talents +# talents=C0cBZcHs6hiHBl7Csp3hvR+3RCAwDMAAAAAA + +legacy_talents=136864:5/137371:5/136855:5/136866:1 + +# Goat Fur Cloak (10) +back=,id=2905 +# Ragged Leather Vest (5) +chest=,id=1364 +# Rugged Trapper's Shirt (1) +shirt=,id=148 +# Frayed Bracers (5) +wrist=,id=3365 +# Driving Gloves (9) +hands=,id=3152 +# Worn Leather Belt (7) +waist=,id=1418 +# Dwarven Leather Pants (5) +legs=,id=61 +# Worn Leather Boots (8) +feet=,id=1419 +# Avala's Binding (8) +finger1=,id=286737 +# Sharp Axe (8) +main_hand=,id=1011 +# Light Shot +ammo=,id=2516 +# ammo_count=956 +# Old Blunderbuss (2) +ranged=,id=2508 + +bag1=,id=2102 +bag2=,id=277977 + +### Gear from Bags +# +# Patchwork Cloak (6) +# back=,id=1429 +# +# Patchwork Cloak (6) +# back=,id=1429 +# +# Bear Shawl (5) +# back=,id=6185 +# +# Patchwork Armor (7) +# chest=,id=1433 +# +# Patchwork Gloves (7) +# hands=,id=1430 +# +# Worn Leather Pants (7) +# legs=,id=1423 +# +# Priest's Mace of Stamina (12) +# main_hand=,id=2075,bonus_id=12731 +# +# Skinning Knife (4) +# main_hand=,id=7005 + +### Additional Character Info +# +# character_stats=health:246/power_type:0/mana:315/rage:0/focus:0/energy:0/strength:26/agility:33/stamina:31/intellect:24/spirit:30 +# character_stats_base=strength:26/agility:33/stamina:31/intellect:24/spirit:27 +# character_stats_bonus=strength:0/agility:0/stamina:0/intellect:0/spirit:3 +# character_stats_gear=damage_per_second:4.79/frost_resistance:1/armor:154/resistance4:1 +# attack_power=melee:61:0:0/ranged:68:20:0 +# spell_power=holy:0/fire:0/nature:0/frost:0/shadow:0/arcane:0 +# spell_stats=healing:0/penetration:0 +# regen=health:3.91:0/health_from_spirit:5.21:0/mana:7.50:0.00/mana_from_spirit:7.50:0/mana_power:7.50:0.00/rage_power:-12.50:0.00/focus_power:10.00:10.00/energy_power:10.00:10.00 +# crit=melee:4.10/ranged:4.42/spell:4.94 +# hit=melee_rating_bonus:0/melee_modifier:0/ranged_rating_bonus:0/ranged_modifier:0/spell_rating_bonus:0/spell_modifier:0 +# haste=melee:0/ranged:0/ranged_ammo:10.00/spell:0 +# combat_modifiers=expertise:0/expertise_offhand:0/expertise_ranged:0/armor_penetration:0/mastery:0 +# combat_ratings= +# defense=armor_base:60/armor_effective:280/armor:280/armor_bonus:220/defense_skill:48/defense_modifier:0/dodge:8.64/parry:4.72/block:0/block_value:1 +# resistances=holy:0/fire:0/nature:0/frost:1/shadow:0/arcane:0 +# weapon_damage=mainhand:17.50:22.55:2.60/pos_buff:0/neg_buff:0/percent:1.01/ranged:17.62:21.65:2.09/ranged_pos_buff:0/ranged_neg_buff:0/ranged_percent:1.01 +# pet_info=npc_id:165189/level:9/name:bear/family:boar/type:beast/talent_tree:default/loyalty:submissive/happiness:3/damage_percent:125/loyalty_rate:20/training_points:18:5 +# pet_stats=health:289/power_type:2/mana:0/rage:0/focus:100/energy:0/strength:0/agility:0/stamina:0/intellect:0/spirit:0 +# pet_stats_base=strength:0/agility:0/stamina:0/intellect:0/spirit:0 +# pet_stats_bonus=strength:0/agility:0/stamina:0/intellect:0/spirit:0 +# pet_attack_power=melee:29:8:0/ranged:0:0:0 +# pet_modifiers=spell_power:0/haste_melee:0/hit_melee_modifier:0/hit_spell_modifier:0 +# pet_defense=armor_base:533/armor_effective:600/armor:600/armor_bonus:67/defense_skill:45/defense_modifier:0 +# pet_resistances=holy:0/fire:0/nature:0/frost:0/shadow:0/arcane:0 +# pet_weapon_damage=mainhand:14.92:19.32:2/pos_buff:0/neg_buff:0/percent:1 +# +# skills=44:46:0:55/46:54:0:55/50:1:0:1/51:1:0:1/95:48:0:55/98:300:0:300/111:300:0:300/162:1:0:55/163:1:0:1/173:1:0:55/185:2:0:75/393:56:0:75/414:1:0:1/415:1:0:1/2939:2:0:75/2947:56:0:75 +# +# buffs=8091:0/8112:0/13165:0 + +# Checksum: d5c9b9ef