Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions engine/action/action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions engine/action/attack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion engine/action/attack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions engine/buff/buff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 )
{
Expand Down
12 changes: 12 additions & 0 deletions engine/class_modules/apl/hunter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions engine/class_modules/apl/hunter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
140 changes: 139 additions & 1 deletion engine/class_modules/sc_hunter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class Base>
struct hunter_action_t : public parse_action_effects_t<Base>
{
private:
using ab = parse_action_effects_t<Base>;

public:
using base_t = hunter_action_t<Base>;

template <typename... Args>
hunter_action_t( Args&&... args ) : ab( std::forward<Args>( args )... ) {}

hunter_t* p()
{ return debug_cast<hunter_t*>( this->player ); }

const hunter_t* p() const
{ return debug_cast<const hunter_t*>( this->player ); }
};

using hunter_melee_t = hunter_action_t<melee_attack_t>;
using hunter_ranged_t = hunter_action_t<ranged_attack_t>;
using hunter_spell_t = hunter_action_t<spell_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
Expand Down
16 changes: 14 additions & 2 deletions engine/item/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion engine/item/special_effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion engine/player/gear_stats.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct gear_stats_t
double spell_power;
std::array<double, SCHOOL_MAX_PRIMARY> school_spell_power;
double attack_power;
double ranged_attack_power;
double expertise_rating;
double expertise_rating2;
double hit_rating;
Expand Down Expand Up @@ -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 ),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
};
};
Loading
Loading