Skip to content

[DH] refactor Metamorphosis #9886

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: thewarwithin
Choose a base branch
from
Draft
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
239 changes: 148 additions & 91 deletions engine/class_modules/sc_demon_hunter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ namespace attacks
{
struct auto_attack_damage_t;
}
namespace spells
{
struct metamorphosis_t;
}
} // namespace actions
namespace items
{
Expand Down Expand Up @@ -896,11 +900,12 @@ class demon_hunter_t : public parse_player_effects_t
struct actives_t
{
// General
heal_t* consume_soul_greater = nullptr;
heal_t* consume_soul_lesser = nullptr;
spell_t* immolation_aura = nullptr;
spell_t* immolation_aura_initial = nullptr;
spell_t* collective_anguish = nullptr;
heal_t* consume_soul_greater = nullptr;
heal_t* consume_soul_lesser = nullptr;
spell_t* immolation_aura = nullptr;
spell_t* immolation_aura_initial = nullptr;
spell_t* collective_anguish = nullptr;
actions::spells::metamorphosis_t* metamorphosis_demonic = nullptr;

// Havoc
spell_t* burning_wound = nullptr;
Expand Down Expand Up @@ -3974,6 +3979,20 @@ struct immolation_aura_t : public demon_hunter_spell_t

struct metamorphosis_t : public demon_hunter_spell_t
{
// special action state specifically for vengeance that causes
// all meta actions to proc cast triggers
struct vengeance_state_t : public action_state_t
{
vengeance_state_t( action_t* action, player_t* target ) : action_state_t( action, target )
{
}

proc_types2 cast_proc_type2() const override
{
return PROC2_CAST_GENERIC;
}
};

struct metamorphosis_impact_t : public demon_hunter_spell_t
{
metamorphosis_impact_t( util::string_view name, demon_hunter_t* p )
Expand All @@ -3986,9 +4005,11 @@ struct metamorphosis_t : public demon_hunter_spell_t

double landing_distance;
timespan_t gcd_lag;
bool from_demonic;

// active cast
metamorphosis_t( demon_hunter_t* p, util::string_view options_str )
: demon_hunter_spell_t( "metamorphosis", p, p->spec.metamorphosis ), landing_distance( 0.0 )
: demon_hunter_spell_t( "metamorphosis", p, p->spec.metamorphosis ), landing_distance( 0.0 ), from_demonic( false )
{
add_option( opt_float( "landing_distance", landing_distance, 0.0, 40.0 ) );
parse_options( options_str );
Expand Down Expand Up @@ -4016,19 +4037,42 @@ struct metamorphosis_t : public demon_hunter_spell_t
}
}

// background trigger
metamorphosis_t( util::string_view name, demon_hunter_t* p )
: demon_hunter_spell_t( name, p, p->spec.metamorphosis ), landing_distance( 0.0 ), from_demonic( false )
{
background = true;
cooldown->duration = 0_s;
trigger_gcd = timespan_t::zero();

may_miss = false;
dot_duration = timespan_t::zero();

harmful = false;
}

action_state_t* new_state() override
{
return p()->specialization() == DEMON_HUNTER_VENGEANCE ? new vengeance_state_t( this, target )
: new action_state_t( this, target );
}

// Meta leap travel time and self-pacify is a 1s hidden aura (201453) regardless of distance
// This is affected by aura lag and will slightly delay execution of follow-up attacks
// Not always relevant as GCD can be longer than the 1s + lag ability delay outside of lust
void schedule_execute( action_state_t* s ) override
void schedule_execute( action_state_t* execute_state = nullptr ) override
{
gcd_lag = rng().gauss( sim->gcd_lag );
min_gcd = 1_s + gcd_lag;
demon_hunter_spell_t::schedule_execute( s );
if ( !background )
{
gcd_lag = rng().gauss( sim->gcd_lag );
min_gcd = 1_s + gcd_lag;
}
demon_hunter_spell_t::schedule_execute( execute_state );
}

timespan_t travel_time() const override
{
if ( p()->specialization() == DEMON_HUNTER_HAVOC )
if ( p()->specialization() == DEMON_HUNTER_HAVOC && !background )
return min_gcd;
else // DEMON_HUNTER_VENGEANCE
return timespan_t::zero();
Expand All @@ -4038,76 +4082,96 @@ struct metamorphosis_t : public demon_hunter_spell_t
{
demon_hunter_spell_t::execute();

if ( p()->specialization() == DEMON_HUNTER_HAVOC )
if ( !background ) // hardcast meta
{
// 2023-01-31 -- Metamorphosis's "extension" mechanic technically fades and reapplies the buff
// This means it (probably inadvertently) triggers Restless Hunter
if ( p()->talent.havoc.restless_hunter->ok() && p()->buff.metamorphosis->check() )
{
p()->cooldown.fel_rush->reset( false, 1 );
p()->buff.restless_hunter->trigger();
}
// hardcast meta removes the existing meta buff and then applies a pandemic-ed meta buff
timespan_t duration = p()->buff.metamorphosis->buff_duration();
timespan_t residual = std::min( duration * 0.3, p()->buff.metamorphosis->remains() );
timespan_t new_duration = duration + residual;
p()->buff.metamorphosis->expire();
p()->buff.metamorphosis->trigger( new_duration );

for ( demonsurge_ability ability : demonsurge_havoc_abilities )
if ( p()->specialization() == DEMON_HUNTER_HAVOC )
{
p()->buff.demonsurge_abilities[ ability ]->trigger();
}
p()->buff.demonsurge_demonic->trigger();
p()->buff.demonsurge_hardcast->trigger();
p()->buff.demonsurge->expire();
// 2025-01-31 -- Metamorphosis's "extension" mechanic technically fades and reapplies the buff
// This means it triggers Restless Hunter
if ( p()->talent.havoc.restless_hunter->ok() && p()->buff.metamorphosis->check() )
{
p()->cooldown.fel_rush->reset( false, 1 );
p()->buff.restless_hunter->trigger();
}

// Buff is gained at the start of the leap.
p()->buff.metamorphosis->extend_duration_or_trigger();
p()->buff.inner_demon->trigger();
for ( demonsurge_ability ability : demonsurge_havoc_abilities )
{
p()->buff.demonsurge_abilities[ ability ]->trigger();
}
p()->buff.demonsurge_demonic->trigger();
p()->buff.demonsurge_hardcast->trigger();
p()->buff.demonsurge->expire();

if ( p()->talent.havoc.chaotic_transformation->ok() )
{
p()->cooldown.eye_beam->reset( false );
p()->cooldown.blade_dance->reset( false );
}
if ( p()->talent.havoc.chaotic_transformation->ok() )
{
p()->cooldown.eye_beam->reset( false );
p()->cooldown.blade_dance->reset( false );
}

if ( p()->talent.felscarred.violent_transformation->ok() )
{
p()->cooldown.immolation_aura->reset( false, -1 );
p()->cooldown.sigil_of_flame->reset( false );
}
if ( p()->talent.felscarred.violent_transformation->ok() )
{
p()->cooldown.immolation_aura->reset( false, -1 );
p()->cooldown.sigil_of_flame->reset( false );
}

// Cancel all previous movement events, as Metamorphosis is ground-targeted
// If we are landing outside of point-blank range, trigger the movement buff
p()->set_out_of_range( timespan_t::zero() );
if ( landing_distance > 0.0 )
// Cancel all previous movement events, as Metamorphosis is ground-targeted
// If we are landing outside point-blank range, trigger the movement buff
p()->set_out_of_range( timespan_t::zero() );
if ( landing_distance > 0.0 )
{
p()->buff.metamorphosis_move->distance_moved = landing_distance;
p()->buff.metamorphosis_move->trigger();
}
}
else // DEMON_HUNTER_VENGEANCE
{
p()->buff.metamorphosis_move->distance_moved = landing_distance;
p()->buff.metamorphosis_move->trigger();
for ( demonsurge_ability ability : demonsurge_vengeance_abilities )
{
p()->buff.demonsurge_abilities[ ability ]->trigger();
}
p()->buff.demonsurge_demonic->trigger();
p()->buff.demonsurge_hardcast->trigger();
p()->buff.metamorphosis->trigger();
p()->buff.demonsurge->expire();

if ( p()->talent.felscarred.violent_transformation->ok() )
{
p()->cooldown.fel_devastation->reset( false );
p()->cooldown.sigil_of_flame->reset( false, -1 );
}
}
}
else // DEMON_HUNTER_VENGEANCE
else // triggered meta
{
for ( demonsurge_ability ability : demonsurge_vengeance_abilities )
if ( from_demonic )
{
p()->buff.demonsurge_abilities[ ability ]->trigger();
}
p()->buff.demonsurge_demonic->trigger();
p()->buff.demonsurge_hardcast->trigger();
p()->buff.metamorphosis->trigger();
p()->buff.demonsurge->expire();
if ( !p()->buff.metamorphosis->up() )
{
if ( p()->specialization() == DEMON_HUNTER_HAVOC )
{
p()->buff.demonsurge_abilities[ demonsurge_ability::ANNIHILATION ]->trigger();
p()->buff.demonsurge_abilities[ demonsurge_ability::DEATH_SWEEP ]->trigger();
}
else
{
p()->buff.demonsurge_abilities[ demonsurge_ability::SOUL_SUNDER ]->trigger();
p()->buff.demonsurge_abilities[ demonsurge_ability::SPIRIT_BURST ]->trigger();
}
p()->buff.demonsurge_demonic->trigger();
}

if ( p()->talent.felscarred.violent_transformation->ok() )
{
p()->cooldown.fel_devastation->reset( false );
p()->cooldown.sigil_of_flame->reset( false, -1 );
p()->buff.metamorphosis->extend_duration_or_trigger(
p()->talent.demon_hunter.demonic->effectN( 1 ).time_value() );
}
}
}

bool ready() override
{
// Not usable during the root effect of Stormeater's Boon
if ( p()->buffs.stormeaters_boon && p()->buffs.stormeaters_boon->check() )
return false;

return demon_hunter_spell_t::ready();
}
};

// Pick up Soul Fragment ====================================================
Expand Down Expand Up @@ -7065,31 +7129,9 @@ struct metamorphosis_buff_t : public demon_hunter_buff_t<buff_t>
}
}

void trigger_demonic()
{
if ( !p()->buff.metamorphosis->up() )
{
if ( p()->specialization() == DEMON_HUNTER_HAVOC )
{
p()->buff.demonsurge_abilities[ demonsurge_ability::ANNIHILATION ]->trigger();
p()->buff.demonsurge_abilities[ demonsurge_ability::DEATH_SWEEP ]->trigger();
}
else
{
p()->buff.demonsurge_abilities[ demonsurge_ability::SOUL_SUNDER ]->trigger();
p()->buff.demonsurge_abilities[ demonsurge_ability::SPIRIT_BURST ]->trigger();
}
p()->buff.demonsurge_demonic->trigger();
}

const timespan_t extend_duration = p()->talent.demon_hunter.demonic->effectN( 1 ).time_value();
p()->buff.metamorphosis->extend_duration_or_trigger( extend_duration );
p()->buff.inner_demon->trigger();
}

void start( int stacks, double value, timespan_t duration ) override
{
demon_hunter_buff_t<buff_t>::start( stacks, value, duration );
base_t::start( stacks, value, duration );

if ( p()->specialization() == DEMON_HUNTER_VENGEANCE )
{
Expand All @@ -7113,6 +7155,16 @@ struct metamorphosis_buff_t : public demon_hunter_buff_t<buff_t>
}
}

void bump( int stacks, double value ) override
{
base_t::bump( stacks, value );

if ( p()->talent.havoc.inner_demon->ok() )
{
p()->buff.inner_demon->trigger();
}
}

void expire_override( int expiration_stacks, timespan_t remaining_duration ) override
{
demon_hunter_buff_t<buff_t>::expire_override( expiration_stacks, remaining_duration );
Expand Down Expand Up @@ -7241,7 +7293,7 @@ struct luck_of_the_draw_buff_t : public demon_hunter_buff_t<buff_t>

void bump( int stacks, double value ) override
{
buff_t::bump( stacks, value );
base_t::bump( stacks, value );

if ( p()->talent.demon_hunter.the_hunt->ok() && p()->set_bonuses.tww2_vengeance_4pc->ok() &&
rng().roll( p()->set_bonuses.tww2_vengeance_4pc->effectN( 1 ).percent() ) )
Expand Down Expand Up @@ -8773,6 +8825,11 @@ void demon_hunter_t::init_spells()
{
active.collective_anguish = get_background_action<collective_anguish_t>( "collective_anguish" );
}
if ( talent.demon_hunter.demonic->ok() )
{
active.metamorphosis_demonic = get_background_action<metamorphosis_t>( "metamorphosis_demonic" );
active.metamorphosis_demonic->from_demonic = true;
}

if ( talent.havoc.demon_blades->ok() )
{
Expand Down Expand Up @@ -9761,10 +9818,10 @@ void demon_hunter_t::spawn_soul_fragment( soul_fragment type, unsigned n, player

void demon_hunter_t::trigger_demonic()
{
if ( !talent.demon_hunter.demonic->ok() )
if ( !talent.demon_hunter.demonic->ok() || !active.metamorphosis_demonic )
return;

debug_cast<buffs::metamorphosis_buff_t*>( buff.metamorphosis )->trigger_demonic();
active.metamorphosis_demonic->schedule_execute();
}

// demon_hunter_t::trigger_demonsurge =============================================
Expand Down