From 1dd3ed0272e7861c1b5c82a4542654e8f4f1f32c Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Thu, 29 Aug 2024 05:14:09 -0600 Subject: [PATCH 1/7] [monk] Remove some unused T28 code, begin sketching out SEF rework. --- engine/class_modules/monk/sc_monk.cpp | 3549 ++++++++++---------- engine/class_modules/monk/sc_monk_pets.cpp | 22 - 2 files changed, 1795 insertions(+), 1776 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 6f2108ecfe9..298d43bc6df 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -905,2320 +905,2361 @@ struct storm_earth_and_fire_fixate_t : public monk_spell_t } // namespace pet_summon -namespace attacks +template +struct sef_action_t : base_t { + base_t *sef_fire_action; + base_t *sef_earth_action; -// ========================================================================== -// Windwalking Aura Toggle -// ========================================================================== + // TODO: Target Fire/Earth actions + /* + * If a non-sleeping target exists with MotC debuff <10s, target that. + * If it is already targeted by another elemental, do not retarget. + * If all MotC debuffs >=10s, do not retarget. + * If fixated, do not retarget. + */ + bool is_fixated; + player_t *sef_fire_target; + player_t *sef_earth_target; -struct windwalking_aura_t : public monk_spell_t -{ - windwalking_aura_t( monk_t *player ) : monk_spell_t( player, "windwalking_aura_toggle" ) + template + sef_action_t( monk_t *player, std::string_view name, Args &&...args ) + : base_t( player, name, std::forward( args )... ) { - harmful = false; - background = true; - trigger_gcd = timespan_t::zero(); + if ( !player->talent.windwalker.storm_earth_and_fire->ok() ) + return; + + sef_fire_action = new base_t( player, name, std::forward( args )... ); + sef_earth_action = new base_t( player, name, std::forward( args )... ); } - size_t available_targets( std::vector &tl ) const override + void execute() override { - tl.clear(); - - for ( auto t : sim->player_non_sleeping_list ) - { - tl.push_back( t ); - } + base_t::execute(); - return tl.size(); + sef_fire_action->execute_on_target( base_t::p()->target ); + sef_earth_action->execute_on_target( base_t::p()->target ); } +} - std::vector &check_distance_targeting( std::vector &tl ) const override +namespace attacks +{ + // ========================================================================== + // Windwalking Aura Toggle + // ========================================================================== + + struct windwalking_aura_t : public monk_spell_t { - size_t i = tl.size(); - while ( i > 0 ) + windwalking_aura_t( monk_t *player ) : monk_spell_t( player, "windwalking_aura_toggle" ) { - i--; - player_t *target_to_buff = tl[ i ]; - - if ( p()->get_player_distance( *target_to_buff ) > 10.0 ) - tl.erase( tl.begin() + i ); + harmful = false; + background = true; + trigger_gcd = timespan_t::zero(); } - return tl; - } -}; - -// ========================================================================== -// Flurry Strikes -// ========================================================================== -struct flurry_strikes_t : public monk_melee_attack_t -{ - struct high_impact_t : public monk_spell_t - { - high_impact_t( monk_t *p ) - : monk_spell_t( p, "high_impact", p->talent.shado_pan.high_impact_debuff->effectN( 1 ).trigger() ) // 451039 + size_t available_targets( std::vector &tl ) const override { - aoe = -1; - background = dual = true; + tl.clear(); + + for ( auto t : sim->player_non_sleeping_list ) + { + tl.push_back( t ); + } + + return tl.size(); } - }; - struct flurry_strike_wisdom_t : public monk_spell_t - { - flurry_strike_wisdom_t( monk_t *p ) - : monk_spell_t( p, "flurry_strike_wisdom", p->talent.shado_pan.wisdom_of_the_wall_flurry ) + std::vector &check_distance_targeting( std::vector &tl ) const override { - aoe = -1; - background = dual = true; + size_t i = tl.size(); + while ( i > 0 ) + { + i--; + player_t *target_to_buff = tl[ i ]; + + if ( p()->get_player_distance( *target_to_buff ) > 10.0 ) + tl.erase( tl.begin() + i ); + } - name_str_reporting = "flurry_strike_wisdom_of_the_wall"; + return tl; } }; - struct flurry_strike_t : public monk_melee_attack_t + // ========================================================================== + // Flurry Strikes + // ========================================================================== + struct flurry_strikes_t : public monk_melee_attack_t { - enum wisdom_buff_e + struct high_impact_t : public monk_spell_t { - WISDOM_OF_THE_WALL_CRIT, - WISDOM_OF_THE_WALL_DODGE, - WISDOM_OF_THE_WALL_FLURRY, - WISDOM_OF_THE_WALL_MASTERY + high_impact_t( monk_t *p ) + : monk_spell_t( p, "high_impact", p->talent.shado_pan.high_impact_debuff->effectN( 1 ).trigger() ) // 451039 + { + aoe = -1; + background = dual = true; + } }; - int flurry_strikes_counter; - int flurry_strikes_threshold; - shuffled_rng_t *deck; - flurry_strike_wisdom_t *wisdom_flurry; + struct flurry_strike_wisdom_t : public monk_spell_t + { + flurry_strike_wisdom_t( monk_t *p ) + : monk_spell_t( p, "flurry_strike_wisdom", p->talent.shado_pan.wisdom_of_the_wall_flurry ) + { + aoe = -1; + background = dual = true; + + name_str_reporting = "flurry_strike_wisdom_of_the_wall"; + } + }; - flurry_strike_t( monk_t *p ) - : monk_melee_attack_t( p, "flurry_strike", p->talent.shado_pan.flurry_strikes_hit ), - flurry_strikes_counter( 0 ), - flurry_strikes_threshold( as( p->talent.shado_pan.wisdom_of_the_wall->effectN( 1 ).base_value() ) ), - deck( p->get_shuffled_rng( "wisdom_of_the_wall", { { WISDOM_OF_THE_WALL_CRIT, 1 }, - { WISDOM_OF_THE_WALL_DODGE, 1 }, - { WISDOM_OF_THE_WALL_FLURRY, 1 }, - { WISDOM_OF_THE_WALL_MASTERY, 1 } } ) ) + struct flurry_strike_t : public monk_melee_attack_t { - background = dual = true; + enum wisdom_buff_e + { + WISDOM_OF_THE_WALL_CRIT, + WISDOM_OF_THE_WALL_DODGE, + WISDOM_OF_THE_WALL_FLURRY, + WISDOM_OF_THE_WALL_MASTERY + }; - apply_affecting_aura( p->talent.shado_pan.pride_of_pandaria ); + int flurry_strikes_counter; + int flurry_strikes_threshold; + shuffled_rng_t *deck; + flurry_strike_wisdom_t *wisdom_flurry; + + flurry_strike_t( monk_t *p ) + : monk_melee_attack_t( p, "flurry_strike", p->talent.shado_pan.flurry_strikes_hit ), + flurry_strikes_counter( 0 ), + flurry_strikes_threshold( as( p->talent.shado_pan.wisdom_of_the_wall->effectN( 1 ).base_value() ) ), + deck( p->get_shuffled_rng( "wisdom_of_the_wall", { { WISDOM_OF_THE_WALL_CRIT, 1 }, + { WISDOM_OF_THE_WALL_DODGE, 1 }, + { WISDOM_OF_THE_WALL_FLURRY, 1 }, + { WISDOM_OF_THE_WALL_MASTERY, 1 } } ) ) + { + background = dual = true; - wisdom_flurry = new flurry_strike_wisdom_t( p ); - add_child( wisdom_flurry ); - } + apply_affecting_aura( p->talent.shado_pan.pride_of_pandaria ); - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + wisdom_flurry = new flurry_strike_wisdom_t( p ); + add_child( wisdom_flurry ); + } - if ( p()->talent.shado_pan.wisdom_of_the_wall->ok() ) + void impact( action_state_t *s ) override { - flurry_strikes_counter++; + monk_melee_attack_t::impact( s ); - if ( flurry_strikes_counter >= flurry_strikes_threshold ) + if ( p()->talent.shado_pan.wisdom_of_the_wall->ok() ) { - flurry_strikes_counter -= flurry_strikes_threshold; + flurry_strikes_counter++; - // Draw new card - const auto card = wisdom_buff_e( deck->trigger() ); - switch ( card ) + if ( flurry_strikes_counter >= flurry_strikes_threshold ) { - case WISDOM_OF_THE_WALL_CRIT: - p()->buff.wisdom_of_the_wall_crit->trigger(); - break; - case WISDOM_OF_THE_WALL_DODGE: - p()->buff.wisdom_of_the_wall_dodge->trigger(); - break; - case WISDOM_OF_THE_WALL_FLURRY: - p()->buff.wisdom_of_the_wall_flurry->trigger(); - break; - case WISDOM_OF_THE_WALL_MASTERY: - p()->buff.wisdom_of_the_wall_mastery->trigger(); - break; - default: - break; + flurry_strikes_counter -= flurry_strikes_threshold; + + // Draw new card + const auto card = wisdom_buff_e( deck->trigger() ); + switch ( card ) + { + case WISDOM_OF_THE_WALL_CRIT: + p()->buff.wisdom_of_the_wall_crit->trigger(); + break; + case WISDOM_OF_THE_WALL_DODGE: + p()->buff.wisdom_of_the_wall_dodge->trigger(); + break; + case WISDOM_OF_THE_WALL_FLURRY: + p()->buff.wisdom_of_the_wall_flurry->trigger(); + break; + case WISDOM_OF_THE_WALL_MASTERY: + p()->buff.wisdom_of_the_wall_mastery->trigger(); + break; + default: + break; + } } } - } - p()->buff.against_all_odds->trigger(); + p()->buff.against_all_odds->trigger(); - if ( auto target_data = p()->get_target_data( s->target ); target_data ) - target_data->debuff.high_impact->trigger(); + if ( auto target_data = p()->get_target_data( s->target ); target_data ) + target_data->debuff.high_impact->trigger(); - if ( p()->buff.wisdom_of_the_wall_flurry->up() ) - { - wisdom_flurry->set_target( s->target ); - wisdom_flurry->execute(); + if ( p()->buff.wisdom_of_the_wall_flurry->up() ) + { + wisdom_flurry->set_target( s->target ); + wisdom_flurry->execute(); + } } + }; + + flurry_strike_t *strike; + high_impact_t *high_impact; + + flurry_strikes_t( monk_t *p ) : monk_melee_attack_t( p, "flurry_strikes", p->talent.shado_pan.flurry_strikes ) + { + strike = new flurry_strike_t( p ); + add_child( strike ); + + if ( !p->talent.shado_pan.high_impact->ok() ) + return; + + high_impact = new high_impact_t( p ); + add_child( high_impact ); + p->register_on_kill_callback( [ this, p ]( player_t *target ) { + if ( p->sim->event_mgr.canceled ) + return; + + if ( auto target_data = p->get_target_data( target ); target_data && target_data->debuff.high_impact->up() ) + high_impact->execute_on_target( target ); + } ); + } + + void execute() override + { + // 150ms of delay between executes has been observed, with some small amount of jitter + if ( p()->buff.flurry_charge->up() ) + for ( int charge = 1; charge <= p()->buff.flurry_charge->stack(); charge++ ) + make_event( *sim, p(), strike, p()->target, charge * 150_ms ); + + p()->buff.flurry_charge->expire(); + p()->buff.vigilant_watch->expire(); } }; - flurry_strike_t *strike; - high_impact_t *high_impact; + // ========================================================================== + // Overwhelming Force + // ========================================================================== - flurry_strikes_t( monk_t *p ) : monk_melee_attack_t( p, "flurry_strikes", p->talent.shado_pan.flurry_strikes ) + template + struct overwhelming_force_t : base_action_t { - strike = new flurry_strike_t( p ); - add_child( strike ); + using base_t = overwhelming_force_t; + struct damage_t : monk_spell_t + { + damage_t( monk_t *player, std::string_view name ) + : monk_spell_t( player, fmt::format( "overwhelming_force_{}", name ), + player->talent.master_of_harmony.overwhelming_force_damage ) + { + background = dual = proc = true; + base_multiplier = player->talent.master_of_harmony.overwhelming_force->effectN( 1 ).percent(); + reduced_aoe_targets = player->talent.master_of_harmony.overwhelming_force->effectN( 2 ).base_value(); + } - if ( !p->talent.shado_pan.high_impact->ok() ) - return; + void init() override + { + monk_spell_t::init(); + update_flags = snapshot_flags &= STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; + } + }; + + damage_t *overwhelming_force_damage; - high_impact = new high_impact_t( p ); - add_child( high_impact ); - p->register_on_kill_callback( [ this, p ]( player_t *target ) { - if ( p->sim->event_mgr.canceled ) + template + overwhelming_force_t( monk_t *player, Args &&...args ) + : base_action_t( player, std::forward( args )... ), overwhelming_force_damage( nullptr ) + { + if ( !player->talent.master_of_harmony.overwhelming_force->ok() ) return; - if ( auto target_data = p->get_target_data( target ); target_data && target_data->debuff.high_impact->up() ) - high_impact->execute_on_target( target ); - } ); - } + overwhelming_force_damage = new damage_t( player, base_action_t::name_str ); + base_action_t::add_child( overwhelming_force_damage ); + } - void execute() override - { - // 150ms of delay between executes has been observed, with some small amount of jitter - if ( p()->buff.flurry_charge->up() ) - for ( int charge = 1; charge <= p()->buff.flurry_charge->stack(); charge++ ) - make_event( *sim, p(), strike, p()->target, charge * 150_ms ); + void impact( action_state_t *state ) override + { + base_action_t::impact( state ); - p()->buff.flurry_charge->expire(); - p()->buff.vigilant_watch->expire(); - } -}; + if ( !base_action_t::p()->talent.master_of_harmony.overwhelming_force->ok() || state->chain_target > 0 ) + return; -// ========================================================================== -// Overwhelming Force -// ========================================================================== + /* + * If the triggering hit is a crit, the damage is divided by the crit bonus + * multiplier, and then multiplied by 2.0 (or the context base crit bonus?) + * + * E.g. + * Base Damage (Crit) 64286, Crit Bonus Multiplier 2.02 + * Base Damage (Pre-Crit) 64286 / 2.02 ~ 31825 + * Overwhelming Force Damage 31825 * 0.15 * 2 = ~9547 + */ + double amount = state->result_amount; + if ( state->result == RESULT_CRIT && base_action_t::p()->bugs ) + { + amount /= 1.0 + state->result_crit_bonus; + amount *= 2.0; + } + overwhelming_force_damage->base_dd_min = overwhelming_force_damage->base_dd_max = amount; + overwhelming_force_damage->execute(); + } + }; -template -struct overwhelming_force_t : base_action_t -{ - using base_t = overwhelming_force_t; - struct damage_t : monk_spell_t + // ========================================================================== + // Tiger Palm + // ========================================================================== + + // Tiger's Ferocity ( Windwalker TWW1 4PC ) + struct tigers_ferocity_t : public monk_melee_attack_t { - damage_t( monk_t *player, std::string_view name ) - : monk_spell_t( player, fmt::format( "overwhelming_force_{}", name ), - player->talent.master_of_harmony.overwhelming_force_damage ) + std::vector &t_list; + + tigers_ferocity_t( monk_t *p ) + : monk_melee_attack_t( p, "tigers_ferocity", p->tier.tww1.ww_4pc_dmg ), t_list( target_cache.list ) { - background = dual = proc = true; - base_multiplier = player->talent.master_of_harmony.overwhelming_force->effectN( 1 ).percent(); - reduced_aoe_targets = player->talent.master_of_harmony.overwhelming_force->effectN( 2 ).base_value(); + background = dual = true; + aoe = -1; + reduced_aoe_targets = p->tier.tww1.ww_4pc->effectN( 2 ).base_value(); } - void init() override + std::vector &target_list() const override { - monk_spell_t::init(); - update_flags = snapshot_flags &= STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; + // The player's target is not hit by this ability so we need to modify the target list. + t_list = base_t::target_list(); + t_list.erase( std::remove( t_list.begin(), t_list.end(), player->target ), t_list.end() ); + return t_list; } }; - damage_t *overwhelming_force_damage; - - template - overwhelming_force_t( monk_t *player, Args &&...args ) - : base_action_t( player, std::forward( args )... ), overwhelming_force_damage( nullptr ) + // Tiger Palm base ability =================================================== + struct tiger_palm_t : public overwhelming_force_t { - if ( !player->talent.master_of_harmony.overwhelming_force->ok() ) - return; + bool face_palm; + action_t *tigers_ferocity; - overwhelming_force_damage = new damage_t( player, base_action_t::name_str ); - base_action_t::add_child( overwhelming_force_damage ); - } + tiger_palm_t( monk_t *p, util::string_view options_str ) + : base_t( p, "tiger_palm", p->baseline.monk.tiger_palm ), face_palm( false ) + { + parse_options( options_str ); - void impact( action_state_t *state ) override - { - base_action_t::impact( state ); + // allow Darting Hurricane to reduce GCD all the way down to 500ms + min_gcd = 500_ms; + ww_mastery = true; + may_combo_strike = true; + trigger_chiji = true; + sef_ability = actions::sef_ability_e::SEF_TIGER_PALM; + cast_during_sck = player->specialization() != MONK_WINDWALKER; - if ( !base_action_t::p()->talent.master_of_harmony.overwhelming_force->ok() || state->chain_target > 0 ) - return; + if ( p->specialization() == MONK_WINDWALKER ) + energize_amount = p->baseline.windwalker.aura->effectN( 4 ).base_value(); + else + energize_type = action_energize::NONE; - /* - * If the triggering hit is a crit, the damage is divided by the crit bonus - * multiplier, and then multiplied by 2.0 (or the context base crit bonus?) - * - * E.g. - * Base Damage (Crit) 64286, Crit Bonus Multiplier 2.02 - * Base Damage (Pre-Crit) 64286 / 2.02 ~ 31825 - * Overwhelming Force Damage 31825 * 0.15 * 2 = ~9547 - */ - double amount = state->result_amount; - if ( state->result == RESULT_CRIT && base_action_t::p()->bugs ) - { - amount /= 1.0 + state->result_crit_bonus; - amount *= 2.0; - } - overwhelming_force_damage->base_dd_min = overwhelming_force_damage->base_dd_max = amount; - overwhelming_force_damage->execute(); - } -}; + spell_power_mod.direct = 0.0; -// ========================================================================== -// Tiger Palm -// ========================================================================== + apply_affecting_aura( p->talent.windwalker.touch_of_the_tiger ); + apply_affecting_aura( p->talent.windwalker.inner_peace ); -// Tiger's Ferocity ( Windwalker TWW1 4PC ) -struct tigers_ferocity_t : public monk_melee_attack_t -{ - std::vector &t_list; + if ( const auto &effect = p->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) + add_parse_entry( da_multiplier_effects ) + .set_func( [ & ] { return face_palm; } ) + .set_value( effect.percent() - 1.0 ) + .set_eff( &effect ); + parse_effects( p->buff.combat_wisdom ); + parse_effects( p->buff.martial_mixture ); + parse_effects( p->buff.darting_hurricane ); - tigers_ferocity_t( monk_t *p ) - : monk_melee_attack_t( p, "tigers_ferocity", p->tier.tww1.ww_4pc_dmg ), t_list( target_cache.list ) - { - background = dual = true; - aoe = -1; - reduced_aoe_targets = p->tier.tww1.ww_4pc->effectN( 2 ).base_value(); - } + if ( p->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) + { + tigers_ferocity = new tigers_ferocity_t( p ); + add_child( tigers_ferocity ); + } + } - std::vector &target_list() const override - { - // The player's target is not hit by this ability so we need to modify the target list. - t_list = base_t::target_list(); - t_list.erase( std::remove( t_list.begin(), t_list.end(), player->target ), t_list.end() ); - return t_list; - } -}; + bool ready() override + { + if ( p()->talent.brewmaster.press_the_advantage->ok() ) + return false; + return monk_melee_attack_t::ready(); + } -// Tiger Palm base ability =================================================== -struct tiger_palm_t : public overwhelming_force_t -{ - bool face_palm; - action_t *tigers_ferocity; + void execute() override + { + //============ + // Pre-Execute + //============ - tiger_palm_t( monk_t *p, util::string_view options_str ) - : base_t( p, "tiger_palm", p->baseline.monk.tiger_palm ), face_palm( false ) - { - parse_options( options_str ); + if ( ( face_palm = rng().roll( p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) ) + p()->proc.face_palm->occur(); - // allow Darting Hurricane to reduce GCD all the way down to 500ms - min_gcd = 500_ms; - ww_mastery = true; - may_combo_strike = true; - trigger_chiji = true; - sef_ability = actions::sef_ability_e::SEF_TIGER_PALM; - cast_during_sck = player->specialization() != MONK_WINDWALKER; + if ( p()->buff.blackout_combo->up() ) + p()->proc.blackout_combo_tiger_palm->occur(); - if ( p->specialization() == MONK_WINDWALKER ) - energize_amount = p->baseline.windwalker.aura->effectN( 4 ).base_value(); - else - energize_type = action_energize::NONE; + if ( p()->buff.counterstrike->up() ) + p()->proc.counterstrike_tp->occur(); - spell_power_mod.direct = 0.0; + //------------ - apply_affecting_aura( p->talent.windwalker.touch_of_the_tiger ); - apply_affecting_aura( p->talent.windwalker.inner_peace ); + monk_melee_attack_t::execute(); - if ( const auto &effect = p->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) - add_parse_entry( da_multiplier_effects ) - .set_func( [ & ] { return face_palm; } ) - .set_value( effect.percent() - 1.0 ) - .set_eff( &effect ); - parse_effects( p->buff.combat_wisdom ); - parse_effects( p->buff.martial_mixture ); - parse_effects( p->buff.darting_hurricane ); + p()->buff.blackout_combo->expire(); - if ( p->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) - { - tigers_ferocity = new tigers_ferocity_t( p ); - add_child( tigers_ferocity ); - } - } + if ( result_is_miss( execute_state->result ) ) + return; - bool ready() override - { - if ( p()->talent.brewmaster.press_the_advantage->ok() ) - return false; - return monk_melee_attack_t::ready(); - } + //----------- - void execute() override - { - //============ - // Pre-Execute - //============ + //============ + // Post-hit + //============ - if ( ( face_palm = rng().roll( p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) ) - p()->proc.face_palm->occur(); + p()->buff.teachings_of_the_monastery->trigger(); - if ( p()->buff.blackout_combo->up() ) - p()->proc.blackout_combo_tiger_palm->occur(); + // Combo Breaker calculation + if ( p()->baseline.windwalker.combo_breaker->ok() && p()->buff.bok_proc->trigger() && + p()->buff.storm_earth_and_fire->up() ) + { + p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_FIRE ); + p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_EARTH ); + } - if ( p()->buff.counterstrike->up() ) - p()->proc.counterstrike_tp->occur(); + // Reduces the remaining cooldown on your Brews by 1 sec + p()->baseline.brewmaster.brews.adjust( + timespan_t::from_seconds( p()->baseline.monk.tiger_palm->effectN( 3 ).base_value() ) ); - //------------ + if ( face_palm && !p()->bugs ) + p()->baseline.brewmaster.brews.adjust( p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); - monk_melee_attack_t::execute(); + if ( p()->buff.combat_wisdom->up() ) + { + p()->passive_actions.combat_wisdom_eh->execute(); + p()->buff.combat_wisdom->expire(); + } - p()->buff.blackout_combo->expire(); + p()->buff.darting_hurricane->decrement(); - if ( result_is_miss( execute_state->result ) ) - return; + // T33 Windwalker Set Bonus + p()->buff.tiger_strikes->trigger(); + p()->buff.tigers_ferocity->expire(); - //----------- + p()->buff.martial_mixture->expire(); + } + + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - //============ - // Post-hit - //============ + // Apply Mark of the Crane + p()->trigger_mark_of_the_crane( s ); - p()->buff.teachings_of_the_monastery->trigger(); + if ( p()->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) + { + double damage = s->result_amount; - // Combo Breaker calculation - if ( p()->baseline.windwalker.combo_breaker->ok() && p()->buff.bok_proc->trigger() && - p()->buff.storm_earth_and_fire->up() ) - { - p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_FIRE ); - p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_EARTH ); - } + if ( p()->buff.storm_earth_and_fire->up() ) + { + // Damage during SEF is based on the actor's damage before the SEF modifier. + damage /= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ); - // Reduces the remaining cooldown on your Brews by 1 sec - p()->baseline.brewmaster.brews.adjust( - timespan_t::from_seconds( p()->baseline.monk.tiger_palm->effectN( 3 ).base_value() ) ); + // Tested 09/08/2024. Tiger's Ferocity deals additional damage during SEF. + damage *= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ) * 3; + } - if ( face_palm && !p()->bugs ) - p()->baseline.brewmaster.brews.adjust( p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); + damage *= p()->tier.tww1.ww_4pc->effectN( 1 ).percent(); - if ( p()->buff.combat_wisdom->up() ) - { - p()->passive_actions.combat_wisdom_eh->execute(); - p()->buff.combat_wisdom->expire(); + tigers_ferocity->base_dd_min = tigers_ferocity->base_dd_max = damage; + tigers_ferocity->execute_on_target( s->target ); + } } + }; - p()->buff.darting_hurricane->decrement(); + // ========================================================================== + // Rising Sun Kick + // ========================================================================== - // T33 Windwalker Set Bonus - p()->buff.tiger_strikes->trigger(); - p()->buff.tigers_ferocity->expire(); + // Glory of the Dawn ================================================= + struct glory_of_the_dawn_t : public monk_melee_attack_t + { + glory_of_the_dawn_t( monk_t *p, const std::string &name ) + : monk_melee_attack_t( p, name, p->passives.glory_of_the_dawn_damage ) + { + background = true; + ww_mastery = true; + sef_ability = actions::sef_ability_e::SEF_GLORY_OF_THE_DAWN; - p()->buff.martial_mixture->expire(); - } + apply_affecting_aura( p->talent.windwalker.rising_star ); + } - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); + + if ( p()->talent.windwalker.acclamation.ok() ) + get_td( s->target )->debuff.acclamation->trigger(); + + if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) + { + p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), + true ); + p()->proc.xuens_battlegear_reduction->occur(); + } + } + }; - // Apply Mark of the Crane - p()->trigger_mark_of_the_crane( s ); + // Rising Sun Kick Damage Trigger =========================================== - if ( p()->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) + template + struct press_the_advantage_t : base_action_t + { + using base_t = press_the_advantage_t; + struct damage_t : base_action_t { - double damage = s->result_amount; + const double mod; + bool face_palm; + + damage_t( monk_t *player, std::string_view name ) + : base_action_t( player, {}, name ), + mod( 1.0 - player->talent.brewmaster.press_the_advantage->effectN( 3 ).percent() ), + face_palm( false ) + { + base_action_t::proc = true; + base_action_t::trigger_gcd = 0_s; + base_action_t::background = true; + base_action_t::dual = true; + + base_action_t::parse_effects( player->buff.counterstrike, + affect_list_t( 1 ).add_spell( base_action_t::data().id() ), + player->buff.counterstrike->data().effectN( 1 ).percent() * mod ); + base_action_t::parse_effects( player->buff.blackout_combo, + affect_list_t( 1 ).add_spell( base_action_t::data().id() ), + player->buff.blackout_combo->data().effectN( 1 ).percent() * mod ); + + // effect must still be rolled in execute so it triggers brew cdr + if ( const auto &effect = player->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) + add_parse_entry( base_action_t::da_multiplier_effects ) + .set_func( [ & ] { return face_palm; } ) + .set_value( ( effect.percent() - 1.0 ) * mod ) + .set_eff( &effect ); + } - if ( p()->buff.storm_earth_and_fire->up() ) + void init_finished() override { - // Damage during SEF is based on the actor's damage before the SEF modifier. - damage /= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ); + base_action_t::init_finished(); - // Tested 09/08/2024. Tiger's Ferocity deals additional damage during SEF. - damage *= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ) * 3; + if ( action_t *pta = base_action_t::p()->find_action( "press_the_advantage" ); + pta && base_action_t::p()->talent.brewmaster.press_the_advantage->ok() ) + pta->add_child( this ); } - damage *= p()->tier.tww1.ww_4pc->effectN( 1 ).percent(); + void execute() override + { + base_action_t::p()->buff.press_the_advantage->expire(); - tigers_ferocity->base_dd_min = tigers_ferocity->base_dd_max = damage; - tigers_ferocity->execute_on_target( s->target ); - } - } -}; + if ( ( face_palm = base_action_t::rng().roll( + base_action_t::p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) && + !base_action_t::p()->bugs ) + { + base_action_t::p()->proc.face_palm->occur(); + base_action_t::p()->baseline.brewmaster.brews.adjust( + base_action_t::p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); + } -// ========================================================================== -// Rising Sun Kick -// ========================================================================== + base_action_t::execute(); -// Glory of the Dawn ================================================= -struct glory_of_the_dawn_t : public monk_melee_attack_t -{ - glory_of_the_dawn_t( monk_t *p, const std::string &name ) - : monk_melee_attack_t( p, name, p->passives.glory_of_the_dawn_damage ) - { - background = true; - ww_mastery = true; - sef_ability = actions::sef_ability_e::SEF_GLORY_OF_THE_DAWN; + base_action_t::p()->buff.blackout_combo->expire(); - apply_affecting_aura( p->talent.windwalker.rising_star ); - } + if ( base_action_t::p()->talent.brewmaster.chi_surge->ok() ) + base_action_t::p()->active_actions.chi_surge->execute(); - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + if ( base_action_t::p()->talent.brewmaster.call_to_arms->ok() && base_action_t::rng().roll( 0.3 ) ) + base_action_t::p()->active_actions.niuzao_call_to_arms_summon->execute(); + } + }; - if ( p()->talent.windwalker.acclamation.ok() ) - get_td( s->target )->debuff.acclamation->trigger(); + propagate_const press_the_advantage_action; + propagate_const press_the_advantage_proc; - if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) + template + press_the_advantage_t( monk_t *player, Args &&...args ) + : base_action_t( player, std::forward( args )... ), press_the_advantage_action( nullptr ) { - p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), - true ); - p()->proc.xuens_battlegear_reduction->occur(); + if ( !player->talent.brewmaster.press_the_advantage->ok() ) + return; + + press_the_advantage_action = + new damage_t( player, fmt::format( "{}_press_the_advantage", base_action_t::name_str ) ); + press_the_advantage_proc = player->get_proc( fmt::format( "{} - Press The Advantage", base_action_t::name_str ) ); } - } -}; -// Rising Sun Kick Damage Trigger =========================================== + void impact( action_state_t *state ) override + { + base_action_t::impact( state ); -template -struct press_the_advantage_t : base_action_t -{ - using base_t = press_the_advantage_t; - struct damage_t : base_action_t - { - const double mod; - bool face_palm; + if ( base_action_t::p()->buff.press_the_advantage->stack() != 10 ) + return; - damage_t( monk_t *player, std::string_view name ) - : base_action_t( player, {}, name ), - mod( 1.0 - player->talent.brewmaster.press_the_advantage->effectN( 3 ).percent() ), - face_palm( false ) - { - base_action_t::proc = true; - base_action_t::trigger_gcd = 0_s; - base_action_t::background = true; - base_action_t::dual = true; - - base_action_t::parse_effects( player->buff.counterstrike, - affect_list_t( 1 ).add_spell( base_action_t::data().id() ), - player->buff.counterstrike->data().effectN( 1 ).percent() * mod ); - base_action_t::parse_effects( player->buff.blackout_combo, - affect_list_t( 1 ).add_spell( base_action_t::data().id() ), - player->buff.blackout_combo->data().effectN( 1 ).percent() * mod ); - - // effect must still be rolled in execute so it triggers brew cdr - if ( const auto &effect = player->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) - add_parse_entry( base_action_t::da_multiplier_effects ) - .set_func( [ & ] { return face_palm; } ) - .set_value( ( effect.percent() - 1.0 ) * mod ) - .set_eff( &effect ); + // TODO: Schedule execute with the appropriate delay. + base_action_t::p()->buff.press_the_advantage->expire(); + press_the_advantage_proc->occur(); + press_the_advantage_action->execute(); } + }; - void init_finished() override + struct rising_sun_kick_dmg_t : public overwhelming_force_t + { + rising_sun_kick_dmg_t( monk_t *p, std::string_view /* options_str */, + std::string_view name = "rising_sun_kick_damage" ) + : base_t( p, name, p->talent.monk.rising_sun_kick->effectN( 1 ).trigger() ) { - base_action_t::init_finished(); + ww_mastery = true; - if ( action_t *pta = base_action_t::p()->find_action( "press_the_advantage" ); - pta && base_action_t::p()->talent.brewmaster.press_the_advantage->ok() ) - pta->add_child( this ); + if ( p->specialization() == MONK_WINDWALKER ) + ap_type = attack_power_type::WEAPON_BOTH; + + background = dual = true; + may_crit = true; + trigger_chiji = true; + + apply_affecting_aura( p->talent.windwalker.rising_star ); } void execute() override { - base_action_t::p()->buff.press_the_advantage->expire(); + base_t::execute(); - if ( ( face_palm = base_action_t::rng().roll( - base_action_t::p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) && - !base_action_t::p()->bugs ) + if ( p()->buff.thunder_focus_tea->up() ) { - base_action_t::p()->proc.face_palm->occur(); - base_action_t::p()->baseline.brewmaster.brews.adjust( - base_action_t::p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); - } + p()->cooldown.rising_sun_kick->adjust( p()->talent.mistweaver.thunder_focus_tea->effectN( 1 ).time_value(), + true ); - base_action_t::execute(); + p()->buff.thunder_focus_tea->decrement(); + } - base_action_t::p()->buff.blackout_combo->expire(); + if ( p()->talent.brewmaster.strike_at_dawn->ok() ) + p()->buff.elusive_brawler->trigger(); - if ( base_action_t::p()->talent.brewmaster.chi_surge->ok() ) - base_action_t::p()->active_actions.chi_surge->execute(); + if ( p()->talent.brewmaster.black_ox_adept->ok() ) + p()->buff.ox_stance->trigger(); - if ( base_action_t::p()->talent.brewmaster.call_to_arms->ok() && base_action_t::rng().roll( 0.3 ) ) - base_action_t::p()->active_actions.niuzao_call_to_arms_summon->execute(); + // Brewmaster RSK also applies the WoO debuff. + if ( p()->buff.weapons_of_order->up() ) + for ( const auto &target : target_list() ) + get_td( target )->debuff.weapons_of_order->trigger(); } - }; - propagate_const press_the_advantage_action; - propagate_const press_the_advantage_proc; + void impact( action_state_t *s ) override + { + base_t::impact( s ); - template - press_the_advantage_t( monk_t *player, Args &&...args ) - : base_action_t( player, std::forward( args )... ), press_the_advantage_action( nullptr ) - { - if ( !player->talent.brewmaster.press_the_advantage->ok() ) - return; + p()->buff.transfer_the_power->trigger(); - press_the_advantage_action = - new damage_t( player, fmt::format( "{}_press_the_advantage", base_action_t::name_str ) ); - press_the_advantage_proc = player->get_proc( fmt::format( "{} - Press The Advantage", base_action_t::name_str ) ); - } + if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) + { + p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), + true ); + p()->proc.xuens_battlegear_reduction->occur(); + } - void impact( action_state_t *state ) override - { - base_action_t::impact( state ); + if ( p()->baseline.windwalker.combat_conditioning->ok() ) + s->target->debuffs.mortal_wounds->trigger(); - if ( base_action_t::p()->buff.press_the_advantage->stack() != 10 ) - return; + // Apply Mark of the Crane + p()->trigger_mark_of_the_crane( s ); - // TODO: Schedule execute with the appropriate delay. - base_action_t::p()->buff.press_the_advantage->expire(); - press_the_advantage_proc->occur(); - press_the_advantage_action->execute(); - } -}; + if ( p()->talent.windwalker.acclamation.ok() ) + get_td( s->target )->debuff.acclamation->trigger(); + } + }; -struct rising_sun_kick_dmg_t : public overwhelming_force_t -{ - rising_sun_kick_dmg_t( monk_t *p, std::string_view /* options_str */, - std::string_view name = "rising_sun_kick_damage" ) - : base_t( p, name, p->talent.monk.rising_sun_kick->effectN( 1 ).trigger() ) + struct rising_sun_kick_t : public monk_melee_attack_t { - ww_mastery = true; - - if ( p->specialization() == MONK_WINDWALKER ) - ap_type = attack_power_type::WEAPON_BOTH; + glory_of_the_dawn_t *gotd; - background = dual = true; - may_crit = true; - trigger_chiji = true; + rising_sun_kick_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "rising_sun_kick", p->talent.monk.rising_sun_kick ) + { + parse_options( options_str ); - apply_affecting_aura( p->talent.windwalker.rising_star ); - } + may_combo_strike = true; + sef_ability = actions::sef_ability_e::SEF_RISING_SUN_KICK; + ap_type = attack_power_type::NONE; + cast_during_sck = player->specialization() != MONK_WINDWALKER; - void execute() override - { - base_t::execute(); + attack_power_mod.direct = 0; - if ( p()->buff.thunder_focus_tea->up() ) - { - p()->cooldown.rising_sun_kick->adjust( p()->talent.mistweaver.thunder_focus_tea->effectN( 1 ).time_value(), - true ); + execute_action = new press_the_advantage_t( p, options_str ); + execute_action->stats = stats; - p()->buff.thunder_focus_tea->decrement(); + if ( p->talent.windwalker.glory_of_the_dawn->ok() ) + { + gotd = new glory_of_the_dawn_t( p, "glory_of_the_dawn" ); + add_child( gotd ); + } } - if ( p()->talent.brewmaster.strike_at_dawn->ok() ) - p()->buff.elusive_brawler->trigger(); - - if ( p()->talent.brewmaster.black_ox_adept->ok() ) - p()->buff.ox_stance->trigger(); + void execute() override + { + monk_melee_attack_t::execute(); - // Brewmaster RSK also applies the WoO debuff. - if ( p()->buff.weapons_of_order->up() ) - for ( const auto &target : target_list() ) - get_td( target )->debuff.weapons_of_order->trigger(); - } + // TODO: Is this the correct way to get character sheet haste %? + auto gotd_chance = p()->talent.windwalker.glory_of_the_dawn->effectN( 2 ).percent() * + ( ( 1.0 / p()->composite_spell_haste() ) - 1.0 ); - void impact( action_state_t *s ) override - { - base_t::impact( s ); + if ( rng().roll( gotd_chance ) ) + gotd->execute_on_target( this->target ); - p()->buff.transfer_the_power->trigger(); + p()->buff.whirling_dragon_punch->trigger(); - if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) - { - p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), - true ); - p()->proc.xuens_battlegear_reduction->occur(); - } + p()->active_actions.chi_wave->execute(); - if ( p()->baseline.windwalker.combat_conditioning->ok() ) - s->target->debuffs.mortal_wounds->trigger(); + if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) + p()->buff.ordered_elements->trigger(); - // Apply Mark of the Crane - p()->trigger_mark_of_the_crane( s ); + p()->buff.tigers_ferocity->trigger(); - if ( p()->talent.windwalker.acclamation.ok() ) - get_td( s->target )->debuff.acclamation->trigger(); - } -}; + p()->buff.august_dynasty->expire(); + } + }; -struct rising_sun_kick_t : public monk_melee_attack_t -{ - glory_of_the_dawn_t *gotd; + // ========================================================================== + // Blackout Kick + // ========================================================================== - rising_sun_kick_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "rising_sun_kick", p->talent.monk.rising_sun_kick ) + // Blackout Kick Proc from Teachings of the Monastery ======================= + struct blackout_kick_totm_proc_t : public monk_melee_attack_t { - parse_options( options_str ); - - may_combo_strike = true; - sef_ability = actions::sef_ability_e::SEF_RISING_SUN_KICK; - ap_type = attack_power_type::NONE; - cast_during_sck = player->specialization() != MONK_WINDWALKER; - - attack_power_mod.direct = 0; - - execute_action = new press_the_advantage_t( p, options_str ); - execute_action->stats = stats; + blackout_kick_totm_proc_t( monk_t *p ) + : monk_melee_attack_t( p, "blackout_kick_totm_proc", + p->talent.windwalker.teachings_of_the_monastery_blackout_kick ) + { + sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK_TOTM; + ww_mastery = false; + cooldown->duration = timespan_t::zero(); + background = dual = true; + trigger_chiji = true; + trigger_gcd = timespan_t::zero(); + } - if ( p->talent.windwalker.glory_of_the_dawn->ok() ) + void init_finished() override { - gotd = new glory_of_the_dawn_t( p, "glory_of_the_dawn" ); - add_child( gotd ); + monk_melee_attack_t::init_finished(); + action_t *bok = player->find_action( "blackout_kick" ); + if ( bok ) + { + attack_power_mod = bok->attack_power_mod; + bok->add_child( this ); + } } - } - void execute() override - { - monk_melee_attack_t::execute(); + double composite_target_multiplier( player_t *target ) const override + { + double m = base_t::composite_target_multiplier( target ); - // TODO: Is this the correct way to get character sheet haste %? - auto gotd_chance = p()->talent.windwalker.glory_of_the_dawn->effectN( 2 ).percent() * - ( ( 1.0 / p()->composite_spell_haste() ) - 1.0 ); + if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) + m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); - if ( rng().roll( gotd_chance ) ) - gotd->execute_on_target( this->target ); + return m; + } - p()->buff.whirling_dragon_punch->trigger(); + // Force 100 milliseconds for the animation, but not delay the overall GCD + timespan_t execute_time() const override + { + return timespan_t::from_millis( 100 ); + } - p()->active_actions.chi_wave->execute(); + double cost() const override + { + return 0; + } - if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) - p()->buff.ordered_elements->trigger(); + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - p()->buff.tigers_ferocity->trigger(); + // The initial hit along with each individual TotM hits has a chance to reset the cooldown + auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); - p()->buff.august_dynasty->expire(); - } -}; + if ( p()->specialization() == MONK_MISTWEAVER ) + totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); -// ========================================================================== -// Blackout Kick -// ========================================================================== + if ( rng().roll( totmResetChance ) ) + { + p()->cooldown.rising_sun_kick->reset( true ); + p()->proc.rsk_reset_totm->occur(); + } -// Blackout Kick Proc from Teachings of the Monastery ======================= -struct blackout_kick_totm_proc_t : public monk_melee_attack_t -{ - blackout_kick_totm_proc_t( monk_t *p ) - : monk_melee_attack_t( p, "blackout_kick_totm_proc", p->talent.windwalker.teachings_of_the_monastery_blackout_kick ) - { - sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK_TOTM; - ww_mastery = false; - cooldown->duration = timespan_t::zero(); - background = dual = true; - trigger_chiji = true; - trigger_gcd = timespan_t::zero(); - } + // Mark of the Crane is only triggered on the initial target + if ( s->chain_target == 0 ) + p()->trigger_mark_of_the_crane( s ); - void init_finished() override - { - monk_melee_attack_t::init_finished(); - action_t *bok = player->find_action( "blackout_kick" ); - if ( bok ) - { - attack_power_mod = bok->attack_power_mod; - bok->add_child( this ); + // Martial Mixture triggers from each ToTM impact + p()->buff.martial_mixture->trigger(); } - } + }; - double composite_target_multiplier( player_t *target ) const override + // Charred Passions ============================================================ + template + struct charred_passions_t : base_action_t { - double m = base_t::composite_target_multiplier( target ); + using base_t = charred_passions_t; + struct damage_t : monk_spell_t + { + damage_t( monk_t *player, std::string_view name ) + : monk_spell_t( player, fmt::format( "charred_passions_{}", name ), player->talent.brewmaster.charred_passions ) + { + background = dual = proc = true; + may_crit = false; + base_multiplier = data().effectN( 1 ).percent(); + } - if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) - m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); + void init() override + { + monk_spell_t::init(); + update_flags = snapshot_flags = STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; + } + }; - return m; - } + damage_t *chp_damage; + cooldown_t *chp_cooldown; - // Force 100 milliseconds for the animation, but not delay the overall GCD - timespan_t execute_time() const override - { - return timespan_t::from_millis( 100 ); - } + template + charred_passions_t( monk_t *player, Args &&...args ) : base_action_t( player, std::forward( args )... ) + { + if ( !player->talent.brewmaster.charred_passions->ok() ) + return; - double cost() const override - { - return 0; - } + chp_cooldown = player->get_cooldown( "charred_passions" ); + chp_damage = new damage_t( player, base_action_t::name_str ); + // TODO: Have a more resilient way to re-map stats objects. + // Issue: When SCK tick stats replace the action stats of SCK channel, adding + // a child of SCK tick breaks reporting. + // base_action_t::add_child( damage ); + } - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + void impact( action_state_t *state ) override + { + base_action_t::impact( state ); - // The initial hit along with each individual TotM hits has a chance to reset the cooldown - auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); + if ( !base_action_t::p()->buff.charred_passions->up() ) + return; - if ( p()->specialization() == MONK_MISTWEAVER ) - totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); + base_action_t::p()->proc.charred_passions->occur(); + chp_damage->base_dd_min = chp_damage->base_dd_max = state->result_amount; + chp_damage->execute(); - if ( rng().roll( totmResetChance ) ) - { - p()->cooldown.rising_sun_kick->reset( true ); - p()->proc.rsk_reset_totm->occur(); + if ( monk_td_t *target_data = base_action_t::get_td( state->target ); + target_data && target_data->dot.breath_of_fire->is_ticking() && chp_cooldown->up() ) + { + target_data->dot.breath_of_fire->refresh_duration(); + chp_cooldown->start( chp_damage->data().effectN( 1 ).trigger()->internal_cooldown() ); + } } + }; - // Mark of the Crane is only triggered on the initial target - if ( s->chain_target == 0 ) - p()->trigger_mark_of_the_crane( s ); - - // Martial Mixture triggers from each ToTM impact - p()->buff.martial_mixture->trigger(); - } -}; - -// Charred Passions ============================================================ -template -struct charred_passions_t : base_action_t -{ - using base_t = charred_passions_t; - struct damage_t : monk_spell_t + // Blackout Kick Baseline ability ======================================= + struct blackout_kick_t : overwhelming_force_t> { - damage_t( monk_t *player, std::string_view name ) - : monk_spell_t( player, fmt::format( "charred_passions_{}", name ), player->talent.brewmaster.charred_passions ) - { - background = dual = proc = true; - may_crit = false; - base_multiplier = data().effectN( 1 ).percent(); - } + blackout_kick_totm_proc_t *bok_totm_proc; + cooldown_t *keg_smash_cooldown; - void init() override + blackout_kick_t( monk_t *p, util::string_view options_str ) + : base_t( p, "blackout_kick", + ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.blackout_kick + : p->baseline.monk.blackout_kick ) ), + keg_smash_cooldown( nullptr ) { - monk_spell_t::init(); - update_flags = snapshot_flags = STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; - } - }; + parse_options( options_str ); + if ( p->specialization() == MONK_WINDWALKER ) + ap_type = attack_power_type::WEAPON_BOTH; - damage_t *chp_damage; - cooldown_t *chp_cooldown; + sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK; + ww_mastery = true; + may_combo_strike = true; + trigger_chiji = true; + cast_during_sck = p->specialization() != MONK_WINDWALKER; - template - charred_passions_t( monk_t *player, Args &&...args ) : base_action_t( player, std::forward( args )... ) - { - if ( !player->talent.brewmaster.charred_passions->ok() ) - return; + apply_affecting_aura( p->talent.brewmaster.fluidity_of_motion ); + apply_affecting_aura( p->talent.brewmaster.shadowboxing_treads ); + apply_affecting_aura( p->talent.brewmaster.elusive_footwork ); - chp_cooldown = player->get_cooldown( "charred_passions" ); - chp_damage = new damage_t( player, base_action_t::name_str ); - // TODO: Have a more resilient way to re-map stats objects. - // Issue: When SCK tick stats replace the action stats of SCK channel, adding - // a child of SCK tick breaks reporting. - // base_action_t::add_child( damage ); - } + if ( player->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() ) + keg_smash_cooldown = player->get_cooldown( "keg_smash" ); - void impact( action_state_t *state ) override - { - base_action_t::impact( state ); + if ( p->talent.brewmaster.charred_passions->ok() ) + add_child( base_t::chp_damage ); - if ( !base_action_t::p()->buff.charred_passions->up() ) - return; + if ( p->shared.teachings_of_the_monastery->ok() ) + { + bok_totm_proc = new blackout_kick_totm_proc_t( p ); + add_child( bok_totm_proc ); + } - base_action_t::p()->proc.charred_passions->occur(); - chp_damage->base_dd_min = chp_damage->base_dd_max = state->result_amount; - chp_damage->execute(); + if ( p->baseline.windwalker.blackout_kick_rank_2->ok() ) + base_costs[ RESOURCE_CHI ].base += + p->baseline.windwalker.blackout_kick_rank_2->effectN( 1 ).base_value(); // Reduce base from 3 chi to 1 + } - if ( monk_td_t *target_data = base_action_t::get_td( state->target ); - target_data && target_data->dot.breath_of_fire->is_ticking() && chp_cooldown->up() ) + double composite_target_multiplier( player_t *target ) const override { - target_data->dot.breath_of_fire->refresh_duration(); - chp_cooldown->start( chp_damage->data().effectN( 1 ).trigger()->internal_cooldown() ); + double m = base_t::composite_target_multiplier( target ); + + if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) + m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); + + return m; } - } -}; -// Blackout Kick Baseline ability ======================================= -struct blackout_kick_t : overwhelming_force_t> -{ - blackout_kick_totm_proc_t *bok_totm_proc; - cooldown_t *keg_smash_cooldown; + void consume_resource() override + { + base_t::consume_resource(); - blackout_kick_t( monk_t *p, util::string_view options_str ) - : base_t( p, "blackout_kick", - ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.blackout_kick - : p->baseline.monk.blackout_kick ) ), - keg_smash_cooldown( nullptr ) - { - parse_options( options_str ); - if ( p->specialization() == MONK_WINDWALKER ) - ap_type = attack_power_type::WEAPON_BOTH; + // Register how much chi is saved without actually refunding the chi + if ( p()->buff.bok_proc->up() ) + p()->gain.bok_proc->add( RESOURCE_CHI, base_costs[ RESOURCE_CHI ] ); + } - sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK; - ww_mastery = true; - may_combo_strike = true; - trigger_chiji = true; - cast_during_sck = p->specialization() != MONK_WINDWALKER; + void execute() override + { + base_t::execute(); - apply_affecting_aura( p->talent.brewmaster.fluidity_of_motion ); - apply_affecting_aura( p->talent.brewmaster.shadowboxing_treads ); - apply_affecting_aura( p->talent.brewmaster.elusive_footwork ); + p()->buff.shuffle->trigger( + timespan_t::from_seconds( p()->talent.brewmaster.shuffle->effectN( 1 ).base_value() ) ); - if ( player->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() ) - keg_smash_cooldown = player->get_cooldown( "keg_smash" ); + p()->buff.flow_of_battle_damage->trigger(); + // 08-18-2024: Sampling of a large number of logs strongly suggests a proc rate of 0.33. + // Reproducible via running https://github.com/renanthera/crunch/tree/ec850f8b37b922f177d88b0c1626271a382ce771 + if ( keg_smash_cooldown && p()->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() && p()->rng().roll( 0.33 ) ) + { + keg_smash_cooldown->reset( false ); + p()->buff.flow_of_battle_free_keg_smash->trigger(); + } + + if ( result_is_hit( execute_state->result ) ) + { + if ( p()->buff.bok_proc->up() ) + { + if ( p()->rng().roll( p()->talent.windwalker.energy_burst->effectN( 1 ).percent() ) ) + p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.energy_burst->effectN( 2 ).base_value(), + p()->gain.energy_burst ); - if ( p->talent.brewmaster.charred_passions->ok() ) - add_child( base_t::chp_damage ); + p()->buff.bok_proc->decrement(); + } - if ( p->shared.teachings_of_the_monastery->ok() ) - { - bok_totm_proc = new blackout_kick_totm_proc_t( p ); - add_child( bok_totm_proc ); - } + p()->buff.blackout_combo->trigger(); - if ( p->baseline.windwalker.blackout_kick_rank_2->ok() ) - base_costs[ RESOURCE_CHI ].base += - p->baseline.windwalker.blackout_kick_rank_2->effectN( 1 ).base_value(); // Reduce base from 3 chi to 1 - } + if ( p()->baseline.windwalker.blackout_kick_rank_3->ok() ) + { + // Reduce the cooldown of Rising Sun Kick and Fists of Fury + timespan_t cd_reduction = -1 * p()->baseline.monk.blackout_kick->effectN( 3 ).time_value(); - double composite_target_multiplier( player_t *target ) const override - { - double m = base_t::composite_target_multiplier( target ); + if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) + { + cd_reduction += ( -1 * p()->talent.windwalker.ordered_elements->effectN( 1 ).time_value() ); + p()->proc.blackout_kick_cdr_oe->occur(); + } + else + p()->proc.blackout_kick_cdr->occur(); - if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) - m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); + p()->cooldown.rising_sun_kick->adjust( cd_reduction, true ); + p()->cooldown.fists_of_fury->adjust( cd_reduction, true ); + } - return m; - } + p()->buff.transfer_the_power->trigger(); - void consume_resource() override - { - base_t::consume_resource(); + if ( p()->buff.teachings_of_the_monastery->up() ) + { + p()->buff.teachings_of_the_monastery->expire(); - // Register how much chi is saved without actually refunding the chi - if ( p()->buff.bok_proc->up() ) - p()->gain.bok_proc->add( RESOURCE_CHI, base_costs[ RESOURCE_CHI ] ); - } + if ( p()->rng().roll( p()->talent.conduit_of_the_celestials.xuens_guidance->effectN( 1 ).percent() ) ) + p()->buff.teachings_of_the_monastery->trigger(); + } - void execute() override - { - base_t::execute(); + if ( p()->specialization() == MONK_WINDWALKER ) + { + p()->buff.strength_of_the_black_ox->expire(); + p()->buff.inner_compass_ox_stance->trigger(); + } - p()->buff.shuffle->trigger( timespan_t::from_seconds( p()->talent.brewmaster.shuffle->effectN( 1 ).base_value() ) ); + p()->buff.vigilant_watch->trigger(); - p()->buff.flow_of_battle_damage->trigger(); - // 08-18-2024: Sampling of a large number of logs strongly suggests a proc rate of 0.33. - // Reproducible via running https://github.com/renanthera/crunch/tree/ec850f8b37b922f177d88b0c1626271a382ce771 - if ( keg_smash_cooldown && p()->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() && p()->rng().roll( 0.33 ) ) - { - keg_smash_cooldown->reset( false ); - p()->buff.flow_of_battle_free_keg_smash->trigger(); + p()->buff.tigers_ferocity->trigger(); + } } - if ( result_is_hit( execute_state->result ) ) + void impact( action_state_t *s ) override { - if ( p()->buff.bok_proc->up() ) - { - if ( p()->rng().roll( p()->talent.windwalker.energy_burst->effectN( 1 ).percent() ) ) - p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.energy_burst->effectN( 2 ).base_value(), - p()->gain.energy_burst ); - - p()->buff.bok_proc->decrement(); - } + base_t::impact( s ); - p()->buff.blackout_combo->trigger(); + p()->buff.hit_scheme->trigger(); - if ( p()->baseline.windwalker.blackout_kick_rank_3->ok() ) + // Teachings of the Monastery + // Used by both Windwalker and Mistweaver + if ( p()->buff.teachings_of_the_monastery->up() ) { - // Reduce the cooldown of Rising Sun Kick and Fists of Fury - timespan_t cd_reduction = -1 * p()->baseline.monk.blackout_kick->effectN( 3 ).time_value(); + int stacks = p()->buff.teachings_of_the_monastery->current_stack; - if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) + if ( p()->talent.windwalker.memory_of_the_monastery.enabled() && p()->bugs ) { - cd_reduction += ( -1 * p()->talent.windwalker.ordered_elements->effectN( 1 ).time_value() ); - p()->proc.blackout_kick_cdr_oe->occur(); + // TODO: Confirm proper mechanics for this. Tested 17/06/2024 and behaviour has it expire previous stacks + // before triggering new which feels like a bug. + p()->buff.memory_of_the_monastery->expire(); } - else - p()->proc.blackout_kick_cdr->occur(); - p()->cooldown.rising_sun_kick->adjust( cd_reduction, true ); - p()->cooldown.fists_of_fury->adjust( cd_reduction, true ); - } + for ( int i = 0; i < stacks; i++ ) + { + // Transfer the power and Memory of the Monastery triggers from ToTM hits but only on the primary target + if ( s->chain_target == 0 ) + { + p()->buff.transfer_the_power->trigger(); - p()->buff.transfer_the_power->trigger(); + if ( p()->talent.windwalker.memory_of_the_monastery.enabled() ) + p()->buff.memory_of_the_monastery->trigger(); + } - if ( p()->buff.teachings_of_the_monastery->up() ) - { - p()->buff.teachings_of_the_monastery->expire(); + bok_totm_proc->execute_on_target( s->target ); + } + + // The initial hit along with each individual TotM hits has a chance to reset the cooldown + auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); - if ( p()->rng().roll( p()->talent.conduit_of_the_celestials.xuens_guidance->effectN( 1 ).percent() ) ) - p()->buff.teachings_of_the_monastery->trigger(); + if ( p()->specialization() == MONK_MISTWEAVER ) + totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); + + if ( rng().roll( totmResetChance ) ) + { + p()->cooldown.rising_sun_kick->reset( true ); + p()->proc.rsk_reset_totm->occur(); + } } - if ( p()->specialization() == MONK_WINDWALKER ) + p()->trigger_mark_of_the_crane( s ); + + if ( p()->talent.brewmaster.elusive_footwork->ok() && s->result == RESULT_CRIT ) { - p()->buff.strength_of_the_black_ox->expire(); - p()->buff.inner_compass_ox_stance->trigger(); + p()->buff.elusive_brawler->trigger( + as( p()->talent.brewmaster.elusive_footwork->effectN( 2 ).base_value() ) ); + p()->proc.elusive_footwork_proc->occur(); } - p()->buff.vigilant_watch->trigger(); + if ( p()->talent.brewmaster.staggering_strikes->ok() ) + p()->find_stagger( "Stagger" ) + ->purify_flat( + s->composite_attack_power() * p()->talent.brewmaster.staggering_strikes->effectN( 2 ).percent(), + "staggering_strikes" ); - p()->buff.tigers_ferocity->trigger(); + // Martial Mixture triggers from each BoK impact + p()->buff.martial_mixture->trigger(); } - } - - void impact( action_state_t *s ) override - { - base_t::impact( s ); + }; - p()->buff.hit_scheme->trigger(); + // ========================================================================== + // Rushing Jade Wind + // ========================================================================== - // Teachings of the Monastery - // Used by both Windwalker and Mistweaver - if ( p()->buff.teachings_of_the_monastery->up() ) + struct flight_of_the_red_crane_dmg_t : public monk_spell_t + { + flight_of_the_red_crane_dmg_t( monk_t *p ) + : monk_spell_t( p, "flight_of_the_red_crane_dmg", + p->talent.conduit_of_the_celestials.flight_of_the_red_crane_dmg ) { - int stacks = p()->buff.teachings_of_the_monastery->current_stack; + background = true; + aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); + } - if ( p()->talent.windwalker.memory_of_the_monastery.enabled() && p()->bugs ) - { - // TODO: Confirm proper mechanics for this. Tested 17/06/2024 and behaviour has it expire previous stacks before - // triggering new which feels like a bug. - p()->buff.memory_of_the_monastery->expire(); - } + void execute() override + { + monk_spell_t::execute(); - for ( int i = 0; i < stacks; i++ ) - { - // Transfer the power and Memory of the Monastery triggers from ToTM hits but only on the primary target - if ( s->chain_target == 0 ) - { - p()->buff.transfer_the_power->trigger(); + p()->buff.flight_of_the_red_crane->trigger(); + } + }; - if ( p()->talent.windwalker.memory_of_the_monastery.enabled() ) - p()->buff.memory_of_the_monastery->trigger(); - } + struct flight_of_the_red_crane_heal_t : public monk_heal_t + { + flight_of_the_red_crane_heal_t( monk_t *p ) + : monk_heal_t( p, "flight_of_the_red_crane_heal", + p->talent.conduit_of_the_celestials.flight_of_the_red_crane_heal ) + { + background = true; + aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); + target = p; + } + }; - bok_totm_proc->execute_on_target( s->target ); - } + struct rushing_jade_wind_t : public monk_melee_attack_t + { + buff_t *buff; - // The initial hit along with each individual TotM hits has a chance to reset the cooldown - auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); + rushing_jade_wind_t( monk_t *player, util::string_view options_str ) + : monk_melee_attack_t( player, "rushing_jade_wind", player->shared.rushing_jade_wind ), + buff( player->buff.rushing_jade_wind ) + { + parse_options( options_str ); + may_combo_strike = true; + } - if ( p()->specialization() == MONK_MISTWEAVER ) - totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); + void execute() override + { + monk_melee_attack_t::execute(); - if ( rng().roll( totmResetChance ) ) - { - p()->cooldown.rising_sun_kick->reset( true ); - p()->proc.rsk_reset_totm->occur(); - } + buff->trigger(); } + }; - p()->trigger_mark_of_the_crane( s ); + // ========================================================================== + // Spinning Crane Kick + // ========================================================================== - if ( p()->talent.brewmaster.elusive_footwork->ok() && s->result == RESULT_CRIT ) + // Jade Ignition Legendary + struct chi_explosion_t : public monk_spell_t + { + chi_explosion_t( monk_t *player ) : monk_spell_t( player, "chi_explosion", player->passives.chi_explosion ) { - p()->buff.elusive_brawler->trigger( - as( p()->talent.brewmaster.elusive_footwork->effectN( 2 ).base_value() ) ); - p()->proc.elusive_footwork_proc->occur(); + dual = background = true; + aoe = -1; + school = SCHOOL_NATURE; } - if ( p()->talent.brewmaster.staggering_strikes->ok() ) - p()->find_stagger( "Stagger" ) - ->purify_flat( - s->composite_attack_power() * p()->talent.brewmaster.staggering_strikes->effectN( 2 ).percent(), - "staggering_strikes" ); + double action_multiplier() const override + { + double am = monk_spell_t::action_multiplier(); - // Martial Mixture triggers from each BoK impact - p()->buff.martial_mixture->trigger(); - } -}; + am *= 1 + p()->buff.chi_energy->check_stack_value(); -// ========================================================================== -// Rushing Jade Wind -// ========================================================================== + return am; + } + }; -struct flight_of_the_red_crane_dmg_t : public monk_spell_t -{ - flight_of_the_red_crane_dmg_t( monk_t *p ) - : monk_spell_t( p, "flight_of_the_red_crane_dmg", p->talent.conduit_of_the_celestials.flight_of_the_red_crane_dmg ) + struct sck_tick_action_t : charred_passions_t { - background = true; - aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); - } - - void execute() override - { - monk_spell_t::execute(); - - p()->buff.flight_of_the_red_crane->trigger(); - } -}; - -struct flight_of_the_red_crane_heal_t : public monk_heal_t -{ - flight_of_the_red_crane_heal_t( monk_t *p ) - : monk_heal_t( p, "flight_of_the_red_crane_heal", p->talent.conduit_of_the_celestials.flight_of_the_red_crane_heal ) - { - background = true; - aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); - target = p; - } -}; - -struct rushing_jade_wind_t : public monk_melee_attack_t -{ - buff_t *buff; + sck_tick_action_t( monk_t *p, std::string_view name, const spell_data_t *data ) + : charred_passions_t( p, name, data ) + { + ww_mastery = true; + trigger_chiji = true; - rushing_jade_wind_t( monk_t *player, util::string_view options_str ) - : monk_melee_attack_t( player, "rushing_jade_wind", player->shared.rushing_jade_wind ), - buff( player->buff.rushing_jade_wind ) - { - parse_options( options_str ); - may_combo_strike = true; - } + dual = background = true; + aoe = -1; + reduced_aoe_targets = p->baseline.monk.spinning_crane_kick->effectN( 1 ).base_value(); - void execute() override - { - monk_melee_attack_t::execute(); + ap_type = attack_power_type::WEAPON_BOTH; - buff->trigger(); - } -}; + parse_effects( p->talent.windwalker.crane_vortex ); -// ========================================================================== -// Spinning Crane Kick -// ========================================================================== + // dance of chiji is scripted + if ( const auto &effect = p->talent.windwalker.dance_of_chiji->effectN( 1 ); effect.ok() ) + add_parse_entry( da_multiplier_effects ) + .set_func( [ &b = p->buff.dance_of_chiji_hidden ]() { return b->check(); } ) + .set_value( effect.percent() ) + .set_eff( &effect ); -// Jade Ignition Legendary -struct chi_explosion_t : public monk_spell_t -{ - chi_explosion_t( monk_t *player ) : monk_spell_t( player, "chi_explosion", player->passives.chi_explosion ) - { - dual = background = true; - aoe = -1; - school = SCHOOL_NATURE; - } + parse_effects( p->buff.cyclone_strikes, USE_CURRENT ); + } - double action_multiplier() const override - { - double am = monk_spell_t::action_multiplier(); + result_amount_type report_amount_type( const action_state_t * ) const override + { + return result_amount_type::DMG_DIRECT; + } - am *= 1 + p()->buff.chi_energy->check_stack_value(); + void execute() override + { + monk_melee_attack_t::execute(); - return am; - } -}; + p()->buff.shuffle->trigger( + timespan_t::from_seconds( p()->baseline.brewmaster.spinning_crane_kick_rank_2->effectN( 1 ).base_value() ) ); + } + }; -struct sck_tick_action_t : charred_passions_t -{ - sck_tick_action_t( monk_t *p, std::string_view name, const spell_data_t *data ) - : charred_passions_t( p, name, data ) + struct spinning_crane_kick_t : public monk_melee_attack_t { - ww_mastery = true; - trigger_chiji = true; + struct spinning_crane_kick_state_t : public action_state_t + { + spinning_crane_kick_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) + { + } - dual = background = true; - aoe = -1; - reduced_aoe_targets = p->baseline.monk.spinning_crane_kick->effectN( 1 ).base_value(); + proc_types2 cast_proc_type2() const override + { + // Spinning Crane Kick seems to trigger Bron's Call to Action (and possibly other + // effects that care about casts). + return PROC2_CAST_GENERIC; + } + }; - ap_type = attack_power_type::WEAPON_BOTH; + chi_explosion_t *chi_x; - parse_effects( p->talent.windwalker.crane_vortex ); + spinning_crane_kick_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "spinning_crane_kick", + ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.spinning_crane_kick + : p->baseline.monk.spinning_crane_kick ) ), + chi_x( nullptr ) + { + parse_options( options_str ); - // dance of chiji is scripted - if ( const auto &effect = p->talent.windwalker.dance_of_chiji->effectN( 1 ); effect.ok() ) - add_parse_entry( da_multiplier_effects ) - .set_func( [ &b = p->buff.dance_of_chiji_hidden ]() { return b->check(); } ) - .set_value( effect.percent() ) - .set_eff( &effect ); + sef_ability = actions::sef_ability_e::SEF_SPINNING_CRANE_KICK; + may_combo_strike = true; + tick_zero = true; + tick_action = new sck_tick_action_t( p, "spinning_crane_kick_tick", data().effectN( 1 ).trigger() ); - parse_effects( p->buff.cyclone_strikes, USE_CURRENT ); - } + interrupt_auto_attack = p->specialization() != MONK_WINDWALKER; + if ( p->specialization() == MONK_BREWMASTER ) + { + dot_behavior = DOT_EXTEND; + cast_during_sck = true; - result_amount_type report_amount_type( const action_state_t * ) const override - { - return result_amount_type::DMG_DIRECT; - } + if ( p->talent.brewmaster.charred_passions->ok() ) + add_child( debug_cast( tick_action )->chp_damage ); + } - void execute() override - { - monk_melee_attack_t::execute(); + if ( p->specialization() == MONK_WINDWALKER ) + { + channeled = true; + dot_behavior = DOT_CLIP; + } - p()->buff.shuffle->trigger( - timespan_t::from_seconds( p()->baseline.brewmaster.spinning_crane_kick_rank_2->effectN( 1 ).base_value() ) ); - } -}; + if ( p->talent.windwalker.jade_ignition->ok() ) + { + chi_x = new chi_explosion_t( p ); + add_child( chi_x ); + } -struct spinning_crane_kick_t : public monk_melee_attack_t -{ - struct spinning_crane_kick_state_t : public action_state_t - { - spinning_crane_kick_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) - { - } + if ( p->baseline.windwalker.mark_of_the_crane->ok() && p->user_options.motc_override == 0 ) + { + p->register_on_kill_callback( [ p ]( player_t *target ) { + if ( p->sim->event_mgr.canceled ) + return; - proc_types2 cast_proc_type2() const override - { - // Spinning Crane Kick seems to trigger Bron's Call to Action (and possibly other - // effects that care about casts). - return PROC2_CAST_GENERIC; + if ( auto target_data = p->get_target_data( target ); + target_data && target_data->debuff.mark_of_the_crane->up() ) + { + make_event( p->sim, target_data->debuff.mark_of_the_crane->remains(), [ p, target_data ]() { + p->sim->print_debug( "mark of the crane fell off dead target: {} ", target_data->target->name_str ); + p->buff.cyclone_strikes->decrement(); + } ); + target_data->debuff.mark_of_the_crane->expire(); + } + } ); + } } - }; - - chi_explosion_t *chi_x; - spinning_crane_kick_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "spinning_crane_kick", - ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.spinning_crane_kick - : p->baseline.monk.spinning_crane_kick ) ), - chi_x( nullptr ) - { - parse_options( options_str ); - - sef_ability = actions::sef_ability_e::SEF_SPINNING_CRANE_KICK; - may_combo_strike = true; - tick_zero = true; - tick_action = new sck_tick_action_t( p, "spinning_crane_kick_tick", data().effectN( 1 ).trigger() ); - - interrupt_auto_attack = p->specialization() != MONK_WINDWALKER; - if ( p->specialization() == MONK_BREWMASTER ) + bool ready() override { - dot_behavior = DOT_EXTEND; - cast_during_sck = true; + if ( p()->channeling && p()->channeling->id == id ) + return false; - if ( p->talent.brewmaster.charred_passions->ok() ) - add_child( debug_cast( tick_action )->chp_damage ); + return monk_melee_attack_t::ready(); } - if ( p->specialization() == MONK_WINDWALKER ) + bool usable_moving() const override { - channeled = true; - dot_behavior = DOT_CLIP; + return true; } - if ( p->talent.windwalker.jade_ignition->ok() ) + action_state_t *new_state() override { - chi_x = new chi_explosion_t( p ); - add_child( chi_x ); + return new spinning_crane_kick_state_t( this, p()->target ); } - if ( p->baseline.windwalker.mark_of_the_crane->ok() && p->user_options.motc_override == 0 ) + double cost_flat_modifier() const override { - p->register_on_kill_callback( [ p ]( player_t *target ) { - if ( p->sim->event_mgr.canceled ) - return; - - if ( auto target_data = p->get_target_data( target ); - target_data && target_data->debuff.mark_of_the_crane->up() ) - { - make_event( p->sim, target_data->debuff.mark_of_the_crane->remains(), [ p, target_data ]() { - p->sim->print_debug( "mark of the crane fell off dead target: {} ", target_data->target->name_str ); - p->buff.cyclone_strikes->decrement(); - } ); - target_data->debuff.mark_of_the_crane->expire(); - } - } ); - } - } - - bool ready() override - { - if ( p()->channeling && p()->channeling->id == id ) - return false; - - return monk_melee_attack_t::ready(); - } + double c = monk_melee_attack_t::cost_flat_modifier(); - bool usable_moving() const override - { - return true; - } + c += p()->buff.dance_of_chiji_hidden->check_value(); // saved as -2 - action_state_t *new_state() override - { - return new spinning_crane_kick_state_t( this, p()->target ); - } - - double cost_flat_modifier() const override - { - double c = monk_melee_attack_t::cost_flat_modifier(); - - c += p()->buff.dance_of_chiji_hidden->check_value(); // saved as -2 - - return c; - } + return c; + } - void execute() override - { - if ( p()->specialization() == MONK_WINDWALKER ) + void execute() override { - if ( p()->buff.dance_of_chiji->up() ) + if ( p()->specialization() == MONK_WINDWALKER ) { - p()->buff.dance_of_chiji->decrement(); - p()->buff.dance_of_chiji_hidden->trigger(); + if ( p()->buff.dance_of_chiji->up() ) + { + p()->buff.dance_of_chiji->decrement(); + p()->buff.dance_of_chiji_hidden->trigger(); - if ( p()->rng().roll( p()->talent.windwalker.sequenced_strikes->effectN( 1 ).percent() ) ) - p()->buff.bok_proc->increment(); // increment is used to not incur the rppm cooldown + if ( p()->rng().roll( p()->talent.windwalker.sequenced_strikes->effectN( 1 ).percent() ) ) + p()->buff.bok_proc->increment(); // increment is used to not incur the rppm cooldown + } } - } - monk_melee_attack_t::execute(); + monk_melee_attack_t::execute(); - timespan_t buff_duration = composite_dot_duration( execute_state ); + timespan_t buff_duration = composite_dot_duration( execute_state ); - p()->buff.spinning_crane_kick->trigger( 1, buff_t::DEFAULT_VALUE(), 1.0, buff_duration ); + p()->buff.spinning_crane_kick->trigger( 1, buff_t::DEFAULT_VALUE(), 1.0, buff_duration ); - if ( chi_x && p()->buff.chi_energy->up() ) - chi_x->execute(); + if ( chi_x && p()->buff.chi_energy->up() ) + chi_x->execute(); - if ( p()->buff.celestial_flames->up() ) - p()->active_actions.breath_of_fire->execute_on_target( execute_state->target ); + if ( p()->buff.celestial_flames->up() ) + p()->active_actions.breath_of_fire->execute_on_target( execute_state->target ); - if ( p()->talent.windwalker.transfer_the_power->ok() ) - p()->buff.transfer_the_power->trigger(); + if ( p()->talent.windwalker.transfer_the_power->ok() ) + p()->buff.transfer_the_power->trigger(); - p()->buff.tigers_ferocity->trigger(); - } + p()->buff.tigers_ferocity->trigger(); + } - void last_tick( dot_t *dot ) override - { - monk_melee_attack_t::last_tick( dot ); + void last_tick( dot_t *dot ) override + { + monk_melee_attack_t::last_tick( dot ); - p()->buff.dance_of_chiji_hidden->expire(); + p()->buff.dance_of_chiji_hidden->expire(); - p()->buff.chi_energy->expire(); + p()->buff.chi_energy->expire(); - if ( p()->buff.counterstrike->up() ) - p()->proc.counterstrike_sck->occur(); - } -}; + if ( p()->buff.counterstrike->up() ) + p()->proc.counterstrike_sck->occur(); + } + }; -// ========================================================================== -// Fists of Fury -// ========================================================================== + // ========================================================================== + // Fists of Fury + // ========================================================================== -struct fists_of_fury_tick_t : public monk_melee_attack_t -{ - fists_of_fury_tick_t( monk_t *p, util::string_view name ) - : monk_melee_attack_t( p, name, p->passives.fists_of_fury_tick ) + struct fists_of_fury_tick_t : public monk_melee_attack_t { - background = true; - aoe = -1; - reduced_aoe_targets = p->talent.windwalker.fists_of_fury->effectN( 1 ).base_value(); - full_amount_targets = 1; - ww_mastery = true; + fists_of_fury_tick_t( monk_t *p, util::string_view name ) + : monk_melee_attack_t( p, name, p->passives.fists_of_fury_tick ) + { + background = true; + aoe = -1; + reduced_aoe_targets = p->talent.windwalker.fists_of_fury->effectN( 1 ).base_value(); + full_amount_targets = 1; + ww_mastery = true; - base_costs[ RESOURCE_CHI ] = 0; - dot_duration = timespan_t::zero(); - trigger_gcd = timespan_t::zero(); + base_costs[ RESOURCE_CHI ] = 0; + dot_duration = timespan_t::zero(); + trigger_gcd = timespan_t::zero(); - parse_effects( p->buff.momentum_boost_damage ); - } + parse_effects( p->buff.momentum_boost_damage ); + } - double composite_target_multiplier( player_t *target ) const override - { - double m = monk_melee_attack_t::composite_target_multiplier( target ); + double composite_target_multiplier( player_t *target ) const override + { + double m = monk_melee_attack_t::composite_target_multiplier( target ); - if ( target != p()->target ) - m *= p()->talent.windwalker.fists_of_fury->effectN( 6 ).percent(); + if ( target != p()->target ) + m *= p()->talent.windwalker.fists_of_fury->effectN( 6 ).percent(); - return m; - } + return m; + } - double action_multiplier() const override - { - double am = monk_melee_attack_t::action_multiplier(); + double action_multiplier() const override + { + double am = monk_melee_attack_t::action_multiplier(); - am *= 1 + p()->buff.transfer_the_power->check_stack_value(); + am *= 1 + p()->buff.transfer_the_power->check_stack_value(); - if ( p()->talent.windwalker.momentum_boost.ok() ) - am *= 1 + ( ( ( 1.0 / p()->composite_melee_haste() ) - 1.0 ) * - p()->talent.windwalker.momentum_boost->effectN( 1 ).percent() ); + if ( p()->talent.windwalker.momentum_boost.ok() ) + am *= 1 + ( ( ( 1.0 / p()->composite_melee_haste() ) - 1.0 ) * + p()->talent.windwalker.momentum_boost->effectN( 1 ).percent() ); - return am; - } + return am; + } - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - p()->buff.chi_energy->trigger(); - p()->buff.momentum_boost_damage->trigger(); - } -}; + p()->buff.chi_energy->trigger(); + p()->buff.momentum_boost_damage->trigger(); + } + }; -struct fists_of_fury_t : public monk_melee_attack_t -{ - fists_of_fury_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "fists_of_fury", p->talent.windwalker.fists_of_fury ) + struct fists_of_fury_t : public monk_melee_attack_t { - parse_options( options_str ); - - cooldown = p->cooldown.fists_of_fury; - sef_ability = actions::sef_ability_e::SEF_FISTS_OF_FURY; - may_combo_strike = true; - - channeled = tick_zero = true; - interrupt_auto_attack = true; + fists_of_fury_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "fists_of_fury", p->talent.windwalker.fists_of_fury ) + { + parse_options( options_str ); - attack_power_mod.direct = 0; - weapon_power_mod = 0; + cooldown = p->cooldown.fists_of_fury; + sef_ability = actions::sef_ability_e::SEF_FISTS_OF_FURY; + may_combo_strike = true; - may_crit = may_miss = may_block = may_dodge = may_parry = callbacks = false; + channeled = tick_zero = true; + interrupt_auto_attack = true; - // Effect 1 shows a period of 166 milliseconds which appears to refer to the visual and not the tick period - base_tick_time = dot_duration / 4; + attack_power_mod.direct = 0; + weapon_power_mod = 0; - ability_lag = p->world_lag; + may_crit = may_miss = may_block = may_dodge = may_parry = callbacks = false; - tick_action = new fists_of_fury_tick_t( p, "fists_of_fury_tick" ); - tick_action->stats = stats; - } + // Effect 1 shows a period of 166 milliseconds which appears to refer to the visual and not the tick period + base_tick_time = dot_duration / 4; - bool usable_moving() const override - { - return true; - } + ability_lag = p->world_lag; - void execute() override - { - monk_melee_attack_t::execute(); + tick_action = new fists_of_fury_tick_t( p, "fists_of_fury_tick" ); + tick_action->stats = stats; + } - if ( p()->buff.fury_of_xuen_stacks->up() && rng().roll( p()->buff.fury_of_xuen_stacks->stack_value() ) ) + bool usable_moving() const override { - p()->buff.fury_of_xuen_stacks->expire(); - p()->buff.fury_of_xuen->trigger(); - p()->active_actions.fury_of_xuen_summon->execute(); + return true; } - p()->buff.whirling_dragon_punch->trigger(); - - p()->buff.tigers_ferocity->trigger(); - } + void execute() override + { + monk_melee_attack_t::execute(); - void last_tick( dot_t *dot ) override - { - monk_melee_attack_t::last_tick( dot ); + if ( p()->buff.fury_of_xuen_stacks->up() && rng().roll( p()->buff.fury_of_xuen_stacks->stack_value() ) ) + { + p()->buff.fury_of_xuen_stacks->expire(); + p()->buff.fury_of_xuen->trigger(); + p()->active_actions.fury_of_xuen_summon->execute(); + } - // Delay the expiration of the buffs until after the tick action happens. - // Otherwise things trigger before the tick action happens; which is not intended. - make_event( p()->sim, timespan_t::from_millis( 1 ), [ & ] { - p()->buff.transfer_the_power->expire(); - p()->buff.pressure_point->trigger(); - p()->buff.momentum_boost_damage->expire(); - p()->buff.momentum_boost_speed->trigger(); - } ); - } -}; + p()->buff.whirling_dragon_punch->trigger(); -// ========================================================================== -// Whirling Dragon Punch -// ========================================================================== + p()->buff.tigers_ferocity->trigger(); + } -struct whirling_dragon_punch_aoe_tick_t : public monk_melee_attack_t -{ - timespan_t delay; - whirling_dragon_punch_aoe_tick_t( util::string_view name, monk_t *p, const spell_data_t *s, timespan_t delay ) - : monk_melee_attack_t( p, name, s ), delay( delay ) - { - ww_mastery = true; + void last_tick( dot_t *dot ) override + { + monk_melee_attack_t::last_tick( dot ); - background = true; - aoe = -1; - reduced_aoe_targets = p->talent.windwalker.whirling_dragon_punch->effectN( 1 ).base_value(); + // Delay the expiration of the buffs until after the tick action happens. + // Otherwise things trigger before the tick action happens; which is not intended. + make_event( p()->sim, timespan_t::from_millis( 1 ), [ & ] { + p()->buff.transfer_the_power->expire(); + p()->buff.pressure_point->trigger(); + p()->buff.momentum_boost_damage->expire(); + p()->buff.momentum_boost_speed->trigger(); + } ); + } + }; - name_str_reporting = "wdp_aoe"; - } + // ========================================================================== + // Whirling Dragon Punch + // ========================================================================== - double action_multiplier() const override + struct whirling_dragon_punch_aoe_tick_t : public monk_melee_attack_t { - double am = monk_melee_attack_t::action_multiplier(); + timespan_t delay; + whirling_dragon_punch_aoe_tick_t( util::string_view name, monk_t *p, const spell_data_t *s, timespan_t delay ) + : monk_melee_attack_t( p, name, s ), delay( delay ) + { + ww_mastery = true; - am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); + background = true; + aoe = -1; + reduced_aoe_targets = p->talent.windwalker.whirling_dragon_punch->effectN( 1 ).base_value(); - return am; - } -}; + name_str_reporting = "wdp_aoe"; + } -struct whirling_dragon_punch_st_tick_t : public monk_melee_attack_t -{ - whirling_dragon_punch_st_tick_t( util::string_view name, monk_t *p, const spell_data_t *s ) - : monk_melee_attack_t( p, name, s ) - { - ww_mastery = true; + double action_multiplier() const override + { + double am = monk_melee_attack_t::action_multiplier(); - background = true; + am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); - name_str_reporting = "wdp_st"; - } + return am; + } + }; - double action_multiplier() const override + struct whirling_dragon_punch_st_tick_t : public monk_melee_attack_t { - double am = monk_melee_attack_t::action_multiplier(); - - am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); + whirling_dragon_punch_st_tick_t( util::string_view name, monk_t *p, const spell_data_t *s ) + : monk_melee_attack_t( p, name, s ) + { + ww_mastery = true; - return am; - } -}; + background = true; -struct whirling_dragon_punch_t : public monk_melee_attack_t -{ - struct whirling_dragon_punch_state_t : public action_state_t - { - whirling_dragon_punch_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) - { + name_str_reporting = "wdp_st"; } - proc_types2 cast_proc_type2() const override + double action_multiplier() const override { - // Whirling Dragon Punch seems to trigger Bron's Call to Action (and possibly other - // effects that care about casts). - return PROC2_CAST_GENERIC; - } - }; + double am = monk_melee_attack_t::action_multiplier(); - std::array aoe_ticks; - whirling_dragon_punch_st_tick_t *st_tick; + am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); - struct whirling_dragon_punch_tick_event_t : public event_t - { - whirling_dragon_punch_aoe_tick_t *tick; - - whirling_dragon_punch_tick_event_t( whirling_dragon_punch_aoe_tick_t *tick, timespan_t delay ) - : event_t( *tick->player, delay ), tick( tick ) - { - } - - void execute() override - { - tick->execute(); + return am; } }; - whirling_dragon_punch_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "whirling_dragon_punch", p->talent.windwalker.whirling_dragon_punch ) + struct whirling_dragon_punch_t : public monk_melee_attack_t { - sef_ability = actions::sef_ability_e::SEF_WHIRLING_DRAGON_PUNCH; + struct whirling_dragon_punch_state_t : public action_state_t + { + whirling_dragon_punch_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) + { + } - parse_options( options_str ); - interrupt_auto_attack = false; - channeled = false; - may_combo_strike = true; - cast_during_sck = false; + proc_types2 cast_proc_type2() const override + { + // Whirling Dragon Punch seems to trigger Bron's Call to Action (and possibly other + // effects that care about casts). + return PROC2_CAST_GENERIC; + } + }; - spell_power_mod.direct = 0.0; + std::array aoe_ticks; + whirling_dragon_punch_st_tick_t *st_tick; - // 3 server-side hardcoded ticks - for ( size_t i = 0; i < aoe_ticks.size(); ++i ) + struct whirling_dragon_punch_tick_event_t : public event_t { - auto delay = base_tick_time * i; - aoe_ticks[ i ] = new whirling_dragon_punch_aoe_tick_t( "whirling_dragon_punch_aoe_tick", p, - p->passives.whirling_dragon_punch_aoe_tick, delay ); + whirling_dragon_punch_aoe_tick_t *tick; - add_child( aoe_ticks[ i ] ); - } + whirling_dragon_punch_tick_event_t( whirling_dragon_punch_aoe_tick_t *tick, timespan_t delay ) + : event_t( *tick->player, delay ), tick( tick ) + { + } + + void execute() override + { + tick->execute(); + } + }; - st_tick = new whirling_dragon_punch_st_tick_t( "whirling_dragon_punch_st_tick", p, - p->passives.whirling_dragon_punch_st_tick ); - add_child( st_tick ); + whirling_dragon_punch_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "whirling_dragon_punch", p->talent.windwalker.whirling_dragon_punch ) + { + sef_ability = actions::sef_ability_e::SEF_WHIRLING_DRAGON_PUNCH; - apply_affecting_aura( p->talent.windwalker.revolving_whirl ); - } + parse_options( options_str ); + interrupt_auto_attack = false; + channeled = false; + may_combo_strike = true; + cast_during_sck = false; - action_state_t *new_state() override - { - return new whirling_dragon_punch_state_t( this, p()->target ); - } + spell_power_mod.direct = 0.0; - void execute() override - { - monk_melee_attack_t::execute(); + // 3 server-side hardcoded ticks + for ( size_t i = 0; i < aoe_ticks.size(); ++i ) + { + auto delay = base_tick_time * i; + aoe_ticks[ i ] = new whirling_dragon_punch_aoe_tick_t( "whirling_dragon_punch_aoe_tick", p, + p->passives.whirling_dragon_punch_aoe_tick, delay ); - p()->movement.whirling_dragon_punch->trigger(); + add_child( aoe_ticks[ i ] ); + } - for ( auto &tick : aoe_ticks ) - make_event( *sim, tick, tick->delay ); + st_tick = new whirling_dragon_punch_st_tick_t( "whirling_dragon_punch_st_tick", p, + p->passives.whirling_dragon_punch_st_tick ); + add_child( st_tick ); - st_tick->execute(); + apply_affecting_aura( p->talent.windwalker.revolving_whirl ); + } - if ( p()->talent.windwalker.knowledge_of_the_broken_temple->ok() && - p()->talent.windwalker.teachings_of_the_monastery->ok() ) + action_state_t *new_state() override { - int stacks = as( p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 1 ).base_value() ); - p()->buff.teachings_of_the_monastery->trigger( stacks ); + return new whirling_dragon_punch_state_t( this, p()->target ); } - // TODO: Check if this can proc without being talented into DoCJ - if ( p()->talent.windwalker.dance_of_chiji->ok() && - p()->rng().roll( p()->talent.windwalker.revolving_whirl->effectN( 1 ).percent() ) ) - p()->buff.dance_of_chiji->increment(); // increment is used to not incur the rppm cooldown - - p()->buff.tigers_ferocity->trigger(); - } + void execute() override + { + monk_melee_attack_t::execute(); - bool ready() override - { - // Only usable while Fists of Fury and Rising Sun Kick are on cooldown. - if ( p()->buff.whirling_dragon_punch->up() ) - return monk_melee_attack_t::ready(); + p()->movement.whirling_dragon_punch->trigger(); - return false; - } -}; + for ( auto &tick : aoe_ticks ) + make_event( *sim, tick, tick->delay ); -// ========================================================================== -// Strike of the Windlord -// ========================================================================== -// Off hand hits first followed by main hand -// The ability does NOT require an off-hand weapon to be executed. -// The ability uses the main-hand weapon damage for both attacks + st_tick->execute(); -struct strike_of_the_windlord_main_hand_t : public monk_melee_attack_t -{ - strike_of_the_windlord_main_hand_t( monk_t *p, const char *name, const spell_data_t *s ) - : monk_melee_attack_t( p, name, s ) - { - sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD; + if ( p()->talent.windwalker.knowledge_of_the_broken_temple->ok() && + p()->talent.windwalker.teachings_of_the_monastery->ok() ) + { + int stacks = as( p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 1 ).base_value() ); + p()->buff.teachings_of_the_monastery->trigger( stacks ); + } - ww_mastery = true; - ap_type = attack_power_type::WEAPON_MAINHAND; + // TODO: Check if this can proc without being talented into DoCJ + if ( p()->talent.windwalker.dance_of_chiji->ok() && + p()->rng().roll( p()->talent.windwalker.revolving_whirl->effectN( 1 ).percent() ) ) + p()->buff.dance_of_chiji->increment(); // increment is used to not incur the rppm cooldown - aoe = -1; - may_dodge = may_parry = may_block = may_miss = true; - dual = background = true; - } + p()->buff.tigers_ferocity->trigger(); + } - // Damage must be divided on non-main target by the number of targets - double composite_aoe_multiplier( const action_state_t *state ) const override - { - if ( state->target != target ) + bool ready() override { - return 1.0 / state->n_targets; + // Only usable while Fists of Fury and Rising Sun Kick are on cooldown. + if ( p()->buff.whirling_dragon_punch->up() ) + return monk_melee_attack_t::ready(); + + return false; } + }; - return 1.0; - } + // ========================================================================== + // Strike of the Windlord + // ========================================================================== + // Off hand hits first followed by main hand + // The ability does NOT require an off-hand weapon to be executed. + // The ability uses the main-hand weapon damage for both attacks - double action_multiplier() const override + struct strike_of_the_windlord_main_hand_t : public monk_melee_attack_t { - double am = monk_melee_attack_t::action_multiplier(); - - am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); - - return am; - } -}; + strike_of_the_windlord_main_hand_t( monk_t *p, const char *name, const spell_data_t *s ) + : monk_melee_attack_t( p, name, s ) + { + sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD; -struct strike_of_the_windlord_off_hand_t : public monk_melee_attack_t -{ - strike_of_the_windlord_off_hand_t( monk_t *p, const char *name, const spell_data_t *s ) - : monk_melee_attack_t( p, name, s ) - { - sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD_OH; - ww_mastery = true; - ap_type = attack_power_type::WEAPON_OFFHAND; + ww_mastery = true; + ap_type = attack_power_type::WEAPON_MAINHAND; - aoe = -1; - may_dodge = may_parry = may_block = may_miss = true; - dual = background = true; - } + aoe = -1; + may_dodge = may_parry = may_block = may_miss = true; + dual = background = true; + } - // Damage must be divided on non-main target by the number of targets - double composite_aoe_multiplier( const action_state_t *state ) const override - { - if ( state->target != target ) + // Damage must be divided on non-main target by the number of targets + double composite_aoe_multiplier( const action_state_t *state ) const override { - return 1.0 / state->n_targets; - } + if ( state->target != target ) + { + return 1.0 / state->n_targets; + } - return 1.0; - } + return 1.0; + } - double action_multiplier() const override - { - double am = monk_melee_attack_t::action_multiplier(); + double action_multiplier() const override + { + double am = monk_melee_attack_t::action_multiplier(); - am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); + am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); - return am; - } + return am; + } + }; - void impact( action_state_t *s ) override + struct strike_of_the_windlord_off_hand_t : public monk_melee_attack_t { - monk_melee_attack_t::impact( s ); - - if ( p()->talent.windwalker.thunderfist.ok() ) + strike_of_the_windlord_off_hand_t( monk_t *p, const char *name, const spell_data_t *s ) + : monk_melee_attack_t( p, name, s ) { - int thunderfist_stacks = 1; + sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD_OH; + ww_mastery = true; + ap_type = attack_power_type::WEAPON_OFFHAND; - if ( s->chain_target == 0 ) - thunderfist_stacks += as( p()->talent.windwalker.thunderfist->effectN( 1 ).base_value() ); + aoe = -1; + may_dodge = may_parry = may_block = may_miss = true; + dual = background = true; + } + + // Damage must be divided on non-main target by the number of targets + double composite_aoe_multiplier( const action_state_t *state ) const override + { + if ( state->target != target ) + { + return 1.0 / state->n_targets; + } - p()->buff.thunderfist->trigger( thunderfist_stacks ); + return 1.0; } - if ( p()->talent.windwalker.rushing_jade_wind.ok() ) - p()->trigger_mark_of_the_crane( s ); + double action_multiplier() const override + { + double am = monk_melee_attack_t::action_multiplier(); - if ( p()->talent.windwalker.gale_force.ok() ) - get_td( s->target )->debuff.gale_force->trigger(); - } -}; + am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); -struct strike_of_the_windlord_t : public monk_melee_attack_t -{ - strike_of_the_windlord_main_hand_t *mh_attack; - strike_of_the_windlord_off_hand_t *oh_attack; + return am; + } - strike_of_the_windlord_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "strike_of_the_windlord", p->talent.windwalker.strike_of_the_windlord ), - mh_attack( nullptr ), - oh_attack( nullptr ) - { - apply_affecting_effect( p->talent.windwalker.communion_with_wind->effectN( 1 ) ); + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - may_combo_strike = true; - cast_during_sck = false; - cooldown->hasted = false; - trigger_gcd = data().gcd(); + if ( p()->talent.windwalker.thunderfist.ok() ) + { + int thunderfist_stacks = 1; - parse_options( options_str ); + if ( s->chain_target == 0 ) + thunderfist_stacks += as( p()->talent.windwalker.thunderfist->effectN( 1 ).base_value() ); - oh_attack = - new strike_of_the_windlord_off_hand_t( p, "strike_of_the_windlord_offhand", data().effectN( 4 ).trigger() ); - mh_attack = - new strike_of_the_windlord_main_hand_t( p, "strike_of_the_windlord_mainhand", data().effectN( 3 ).trigger() ); + p()->buff.thunderfist->trigger( thunderfist_stacks ); + } - add_child( oh_attack ); - add_child( mh_attack ); + if ( p()->talent.windwalker.rushing_jade_wind.ok() ) + p()->trigger_mark_of_the_crane( s ); - if ( p->talent.windwalker.thunderfist.ok() ) - add_child( p->passive_actions.thunderfist ); - } + if ( p()->talent.windwalker.gale_force.ok() ) + get_td( s->target )->debuff.gale_force->trigger(); + } + }; - void execute() override + struct strike_of_the_windlord_t : public monk_melee_attack_t { - monk_melee_attack_t::execute(); + strike_of_the_windlord_main_hand_t *mh_attack; + strike_of_the_windlord_off_hand_t *oh_attack; - // Off-hand attack hits first - oh_attack->execute(); + strike_of_the_windlord_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "strike_of_the_windlord", p->talent.windwalker.strike_of_the_windlord ), + mh_attack( nullptr ), + oh_attack( nullptr ) + { + apply_affecting_effect( p->talent.windwalker.communion_with_wind->effectN( 1 ) ); - if ( result_is_hit( oh_attack->execute_state->result ) ) - mh_attack->execute(); + may_combo_strike = true; + cast_during_sck = false; + cooldown->hasted = false; + trigger_gcd = data().gcd(); - if ( p()->talent.windwalker.rushing_jade_wind.ok() ) - { - p()->buff.rushing_jade_wind->trigger(); - if ( p()->bugs ) - combo_strikes_trigger(); - } + parse_options( options_str ); - p()->buff.tigers_ferocity->trigger(); + oh_attack = + new strike_of_the_windlord_off_hand_t( p, "strike_of_the_windlord_offhand", data().effectN( 4 ).trigger() ); + mh_attack = + new strike_of_the_windlord_main_hand_t( p, "strike_of_the_windlord_mainhand", data().effectN( 3 ).trigger() ); - if ( p()->talent.windwalker.darting_hurricane.ok() ) - p()->buff.darting_hurricane->increment( - as( p()->talent.windwalker.darting_hurricane->effectN( 2 ) - .base_value() ) ); // increment is used to not incur the rppm cooldown + add_child( oh_attack ); + add_child( mh_attack ); - if ( p()->buff.heart_of_the_jade_serpent->up() ) - { - p()->buff.heart_of_the_jade_serpent_cdr->trigger(); - p()->buff.inner_compass_serpent_stance->trigger(); - p()->buff.heart_of_the_jade_serpent->decrement(); + if ( p->talent.windwalker.thunderfist.ok() ) + add_child( p->passive_actions.thunderfist ); } - } -}; -// ========================================================================== -// Thunderfist -// ========================================================================== + void execute() override + { + monk_melee_attack_t::execute(); -struct thunderfist_t : public monk_spell_t -{ - thunderfist_t( monk_t *player ) - : monk_spell_t( player, "thunderfist", player->passives.thunderfist->effectN( 1 ).trigger() ) - { - background = true; - may_crit = true; - } + // Off-hand attack hits first + oh_attack->execute(); - virtual void execute() override - { - monk_spell_t::execute(); + if ( result_is_hit( oh_attack->execute_state->result ) ) + mh_attack->execute(); - p()->buff.thunderfist->decrement( 1 ); - } -}; + if ( p()->talent.windwalker.rushing_jade_wind.ok() ) + { + p()->buff.rushing_jade_wind->trigger(); + if ( p()->bugs ) + combo_strikes_trigger(); + } -// ========================================================================== -// Melee -// ========================================================================== + p()->buff.tigers_ferocity->trigger(); -struct press_the_advantage_melee_t : public monk_spell_t -{ - press_the_advantage_melee_t( monk_t *player ) - : monk_spell_t( player, "press_the_advantage", player->find_spell( 418360 ) ) - { - background = true; + if ( p()->talent.windwalker.darting_hurricane.ok() ) + p()->buff.darting_hurricane->increment( + as( p()->talent.windwalker.darting_hurricane->effectN( 2 ) + .base_value() ) ); // increment is used to not incur the rppm cooldown - if ( p()->talent.brewmaster.press_the_advantage->ok() && p()->talent.brewmaster.chi_surge->ok() ) - add_child( p()->active_actions.chi_surge ); - } -}; + if ( p()->buff.heart_of_the_jade_serpent->up() ) + { + p()->buff.heart_of_the_jade_serpent_cdr->trigger(); + p()->buff.inner_compass_serpent_stance->trigger(); + p()->buff.heart_of_the_jade_serpent->decrement(); + } + } + }; -struct melee_t : public monk_melee_attack_t -{ - int sync_weapons; - bool dual_threat_enabled = true; // Dual Threat requires one succesful melee inbetween casts - bool first; - bool oh; + // ========================================================================== + // Thunderfist + // ========================================================================== - melee_t( util::string_view name, monk_t *player, int sw, bool is_oh = false ) - : monk_melee_attack_t( player, name ), sync_weapons( sw ), first( true ), oh( is_oh ) + struct thunderfist_t : public monk_spell_t { - background = repeating = may_glance = true; - may_crit = true; - trigger_gcd = timespan_t::zero(); - special = false; - school = SCHOOL_PHYSICAL; - weapon_multiplier = 1.0; - allow_class_ability_procs = true; - not_a_proc = true; - - monk_melee_attack_t::apply_buff_effects(); - monk_melee_attack_t::apply_debuff_effects(); + thunderfist_t( monk_t *player ) + : monk_spell_t( player, "thunderfist", player->passives.thunderfist->effectN( 1 ).trigger() ) + { + background = true; + may_crit = true; + } - if ( player->main_hand_weapon.group() == WEAPON_1H ) + virtual void execute() override { - if ( player->specialization() != MONK_MISTWEAVER ) - base_hit -= 0.19; + monk_spell_t::execute(); + + p()->buff.thunderfist->decrement( 1 ); } - } + }; - void reset() override - { - monk_melee_attack_t::reset(); - first = true; - } + // ========================================================================== + // Melee + // ========================================================================== - timespan_t execute_time() const override + struct press_the_advantage_melee_t : public monk_spell_t { - timespan_t t = monk_melee_attack_t::execute_time(); + press_the_advantage_melee_t( monk_t *player ) + : monk_spell_t( player, "press_the_advantage", player->find_spell( 418360 ) ) + { + background = true; - if ( first ) - return ( weapon->slot == SLOT_OFF_HAND ) ? ( sync_weapons ? std::min( t / 2, timespan_t::zero() ) : t / 2 ) - : timespan_t::zero(); - else - return t; - } + if ( p()->talent.brewmaster.press_the_advantage->ok() && p()->talent.brewmaster.chi_surge->ok() ) + add_child( p()->active_actions.chi_surge ); + } + }; - void execute() override + struct melee_t : public monk_melee_attack_t { - first = false; - monk_melee_attack_t::execute(); - } + int sync_weapons; + bool dual_threat_enabled = true; // Dual Threat requires one succesful melee inbetween casts + bool first; + bool oh; - void impact( action_state_t *s ) override - { - if ( dual_threat_enabled && p()->rng().roll( p()->talent.windwalker.dual_threat->effectN( 1 ).percent() ) ) + melee_t( util::string_view name, monk_t *player, int sw, bool is_oh = false ) + : monk_melee_attack_t( player, name ), sync_weapons( sw ), first( true ), oh( is_oh ) { - s->result_total = 0; - p()->dual_threat_kick->execute(); - dual_threat_enabled = false; - } - else - { - monk_melee_attack_t::impact( s ); + background = repeating = may_glance = true; + may_crit = true; + trigger_gcd = timespan_t::zero(); + special = false; + school = SCHOOL_PHYSICAL; + weapon_multiplier = 1.0; + allow_class_ability_procs = true; + not_a_proc = true; - if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) - p()->buff.press_the_advantage->trigger(); + monk_melee_attack_t::apply_buff_effects(); + monk_melee_attack_t::apply_debuff_effects(); - if ( result_is_hit( s->result ) ) + if ( player->main_hand_weapon.group() == WEAPON_1H ) { - if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) - { - // Reduce Brew cooldown by 0.5 seconds - p()->baseline.brewmaster.brews.adjust( - p()->talent.brewmaster.press_the_advantage->effectN( 1 ).time_value() ); - - // Trigger the Press the Advantage damage proc - p()->passive_actions.press_the_advantage->target = s->target; - p()->passive_actions.press_the_advantage->schedule_execute(); - } - - if ( p()->buff.thunderfist->up() ) - p()->passive_actions.thunderfist->execute_on_target( s->target ); - - dual_threat_enabled = true; + if ( player->specialization() != MONK_MISTWEAVER ) + base_hit -= 0.19; } } - } -}; - -// ========================================================================== -// Auto Attack -// ========================================================================== -// Dual Threat WW Talent - -struct dual_threat_t : public monk_melee_attack_t -{ - dual_threat_t( monk_t *p ) : monk_melee_attack_t( p, "dual_threat_kick", p->passives.dual_threat_kick ) - { - background = true; - may_glance = true; - may_crit = true; // I assume so? This ability doesn't appear in the combat log yet on alpha + void reset() override + { + monk_melee_attack_t::reset(); + first = true; + } - allow_class_ability_procs = false; // Is not proccing Thunderfist or other class ability procs + timespan_t execute_time() const override + { + timespan_t t = monk_melee_attack_t::execute_time(); - school = SCHOOL_PHYSICAL; - weapon_multiplier = 1.0; - weapon = &( player->main_hand_weapon ); + if ( first ) + return ( weapon->slot == SLOT_OFF_HAND ) ? ( sync_weapons ? std::min( t / 2, timespan_t::zero() ) : t / 2 ) + : timespan_t::zero(); + else + return t; + } - cooldown->duration = base_execute_time = trigger_gcd = timespan_t::zero(); - } + void execute() override + { + first = false; + monk_melee_attack_t::execute(); + } - void execute() override - { - monk_melee_attack_t::execute(); + void impact( action_state_t *s ) override + { + if ( dual_threat_enabled && p()->rng().roll( p()->talent.windwalker.dual_threat->effectN( 1 ).percent() ) ) + { + s->result_total = 0; + p()->dual_threat_kick->execute(); + dual_threat_enabled = false; + } + else + { + monk_melee_attack_t::impact( s ); - p()->buff.dual_threat->trigger(); - } -}; + if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) + p()->buff.press_the_advantage->trigger(); -struct auto_attack_t : public monk_melee_attack_t -{ - int sync_weapons; + if ( result_is_hit( s->result ) ) + { + if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) + { + // Reduce Brew cooldown by 0.5 seconds + p()->baseline.brewmaster.brews.adjust( + p()->talent.brewmaster.press_the_advantage->effectN( 1 ).time_value() ); - dual_threat_t *dual_threat_kick; + // Trigger the Press the Advantage damage proc + p()->passive_actions.press_the_advantage->target = s->target; + p()->passive_actions.press_the_advantage->schedule_execute(); + } - auto_attack_t( monk_t *player, util::string_view options_str ) - : monk_melee_attack_t( player, "auto_attack" ), sync_weapons( 0 ) - { - add_option( opt_bool( "sync_weapons", sync_weapons ) ); - parse_options( options_str ); + if ( p()->buff.thunderfist->up() ) + p()->passive_actions.thunderfist->execute_on_target( s->target ); - ignore_false_positive = true; - trigger_gcd = timespan_t::zero(); - // background = true; + dual_threat_enabled = true; + } + } + } + }; - p()->main_hand_attack = new melee_t( "melee_main_hand", player, sync_weapons ); - p()->main_hand_attack->weapon = &( player->main_hand_weapon ); - p()->main_hand_attack->base_execute_time = player->main_hand_weapon.swing_time; + // ========================================================================== + // Auto Attack + // ========================================================================== - add_child( p()->main_hand_attack ); + // Dual Threat WW Talent - if ( player->off_hand_weapon.type != WEAPON_NONE ) + struct dual_threat_t : public monk_melee_attack_t + { + dual_threat_t( monk_t *p ) : monk_melee_attack_t( p, "dual_threat_kick", p->passives.dual_threat_kick ) { - if ( !player->dual_wield() ) - return; + background = true; + may_glance = true; + may_crit = true; // I assume so? This ability doesn't appear in the combat log yet on alpha + + allow_class_ability_procs = false; // Is not proccing Thunderfist or other class ability procs - p()->off_hand_attack = new melee_t( "melee_off_hand", player, sync_weapons, true ); - p()->off_hand_attack->weapon = &( player->off_hand_weapon ); - p()->off_hand_attack->base_execute_time = player->off_hand_weapon.swing_time; - p()->off_hand_attack->id = 1; + school = SCHOOL_PHYSICAL; + weapon_multiplier = 1.0; + weapon = &( player->main_hand_weapon ); - add_child( p()->off_hand_attack ); + cooldown->duration = base_execute_time = trigger_gcd = timespan_t::zero(); } - if ( p()->talent.windwalker.dual_threat.ok() ) + void execute() override { - p()->dual_threat_kick = new dual_threat_t( player ); + monk_melee_attack_t::execute(); - add_child( p()->dual_threat_kick ); + p()->buff.dual_threat->trigger(); } - } + }; - bool ready() override + struct auto_attack_t : public monk_melee_attack_t { - if ( p()->current.distance_to_move > 5 ) - return false; + int sync_weapons; - return ( p()->main_hand_attack->execute_event == nullptr || - ( p()->off_hand_attack && p()->off_hand_attack->execute_event == nullptr ) ); // not swinging - } + dual_threat_t *dual_threat_kick; - void execute() override - { - if ( player->main_hand_attack ) - p()->main_hand_attack->schedule_execute(); + auto_attack_t( monk_t *player, util::string_view options_str ) + : monk_melee_attack_t( player, "auto_attack" ), sync_weapons( 0 ) + { + add_option( opt_bool( "sync_weapons", sync_weapons ) ); + parse_options( options_str ); - if ( player->off_hand_attack ) - p()->off_hand_attack->schedule_execute(); - } -}; + ignore_false_positive = true; + trigger_gcd = timespan_t::zero(); + // background = true; -// ========================================================================== -// Keg Smash -// ========================================================================== -struct keg_smash_t : monk_melee_attack_t -{ - keg_smash_t( monk_t *player, std::string_view options_str, std::string_view name = "keg_smash" ) - : monk_melee_attack_t( player, name, player->talent.brewmaster.keg_smash ) - { - parse_options( options_str ); - // TODO: can cast_during_sck be automated? - cast_during_sck = true; + p()->main_hand_attack = new melee_t( "melee_main_hand", player, sync_weapons ); + p()->main_hand_attack->weapon = &( player->main_hand_weapon ); + p()->main_hand_attack->base_execute_time = player->main_hand_weapon.swing_time; - // No auto-parsing is presently possible. - reduced_aoe_targets = data().effectN( 7 ).base_value(); - aoe = -1; + add_child( p()->main_hand_attack ); - apply_affecting_aura( player->talent.brewmaster.stormstouts_last_keg ); - parse_effects( player->buff.hit_scheme ); - // we have to set this up by hand, as scalding brew is scripted - if ( const auto &effect = player->talent.brewmaster.scalding_brew->effectN( 1 ); effect.ok() ) - add_parse_entry( target_multiplier_effects ) - .set_func( td_fn( &monk_td_t::dots_t::breath_of_fire ) ) - .set_value( effect.percent() ) - .set_eff( &effect ); - parse_effects( player->buff.flow_of_battle_free_keg_smash ); - } + if ( player->off_hand_weapon.type != WEAPON_NONE ) + { + if ( !player->dual_wield() ) + return; - void execute() override - { - monk_melee_attack_t::execute(); + p()->off_hand_attack = new melee_t( "melee_off_hand", player, sync_weapons, true ); + p()->off_hand_attack->weapon = &( player->off_hand_weapon ); + p()->off_hand_attack->base_execute_time = player->off_hand_weapon.swing_time; + p()->off_hand_attack->id = 1; - p()->buff.hit_scheme->expire(); - p()->buff.flow_of_battle_free_keg_smash->expire(); + add_child( p()->off_hand_attack ); + } - if ( p()->talent.brewmaster.salsalabims_strength->ok() ) - { - p()->cooldown.breath_of_fire->reset( true ); - p()->proc.salsalabims_strength->occur(); - } + if ( p()->talent.windwalker.dual_threat.ok() ) + { + p()->dual_threat_kick = new dual_threat_t( player ); - p()->buff.shuffle->trigger( timespan_t::from_seconds( data().effectN( 6 ).base_value() ) ); + add_child( p()->dual_threat_kick ); + } + } - timespan_t reduction = timespan_t::from_seconds( data().effectN( 4 ).base_value() ); - if ( p()->buff.blackout_combo->up() ) + bool ready() override { - reduction += timespan_t::from_seconds( p()->buff.blackout_combo->data().effectN( 3 ).base_value() ); - p()->proc.blackout_combo_keg_smash->occur(); + if ( p()->current.distance_to_move > 5 ) + return false; + + return ( p()->main_hand_attack->execute_event == nullptr || + ( p()->off_hand_attack && p()->off_hand_attack->execute_event == nullptr ) ); // not swinging } - p()->buff.blackout_combo->expire(); - p()->baseline.brewmaster.brews.adjust( reduction ); - } + void execute() override + { + if ( player->main_hand_attack ) + p()->main_hand_attack->schedule_execute(); - void impact( action_state_t *state ) override - { - monk_melee_attack_t::impact( state ); - get_td( state->target )->debuff.keg_smash->trigger(); - if ( p()->buff.weapons_of_order->up() ) - get_td( state->target )->debuff.weapons_of_order->trigger(); - } -}; + if ( player->off_hand_attack ) + p()->off_hand_attack->schedule_execute(); + } + }; -// ========================================================================== -// Touch of Death -// ========================================================================== -struct touch_of_death_t : public monk_melee_attack_t -{ - touch_of_death_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "touch_of_death", p->baseline.monk.touch_of_death ) + // ========================================================================== + // Keg Smash + // ========================================================================== + struct keg_smash_t : monk_melee_attack_t { - ww_mastery = true; - may_crit = hasted_ticks = false; - may_combo_strike = true; - cast_during_sck = true; - parse_options( options_str ); - - cooldown->duration = data().cooldown(); + keg_smash_t( monk_t *player, std::string_view options_str, std::string_view name = "keg_smash" ) + : monk_melee_attack_t( player, name, player->talent.brewmaster.keg_smash ) + { + parse_options( options_str ); + // TODO: can cast_during_sck be automated? + cast_during_sck = true; - apply_affecting_aura( p->talent.monk.fatal_touch ); - } + // No auto-parsing is presently possible. + reduced_aoe_targets = data().effectN( 7 ).base_value(); + aoe = -1; - void init() override - { - monk_melee_attack_t::init(); + apply_affecting_aura( player->talent.brewmaster.stormstouts_last_keg ); + parse_effects( player->buff.hit_scheme ); + // we have to set this up by hand, as scalding brew is scripted + if ( const auto &effect = player->talent.brewmaster.scalding_brew->effectN( 1 ); effect.ok() ) + add_parse_entry( target_multiplier_effects ) + .set_func( td_fn( &monk_td_t::dots_t::breath_of_fire ) ) + .set_value( effect.percent() ) + .set_eff( &effect ); + parse_effects( player->buff.flow_of_battle_free_keg_smash ); + } - snapshot_flags = update_flags = 0; - } + void execute() override + { + monk_melee_attack_t::execute(); - double composite_target_armor( player_t * ) const override - { - return 0; - } + p()->buff.hit_scheme->expire(); + p()->buff.flow_of_battle_free_keg_smash->expire(); - bool target_ready( player_t *target ) override - { - // Deals damage equal to 35% of your maximum health against players and stronger creatures under 15% health - // 2023-10-19 Tooltip lies and the 15% health works on all non-player targets. - if ( p()->talent.monk.improved_touch_of_death->ok() && - ( target->health_percentage() < p()->talent.monk.improved_touch_of_death->effectN( 1 ).base_value() ) ) - return monk_melee_attack_t::target_ready( target ); + if ( p()->talent.brewmaster.salsalabims_strength->ok() ) + { + p()->cooldown.breath_of_fire->reset( true ); + p()->proc.salsalabims_strength->occur(); + } - // You exploit the enemy target's weakest point, instantly killing creatures if they have less health than you - // Only applicable in health based sims - if ( target->current_health() > 0 && target->current_health() <= p()->max_health() ) - return monk_melee_attack_t::target_ready( target ); + p()->buff.shuffle->trigger( timespan_t::from_seconds( data().effectN( 6 ).base_value() ) ); - return false; - } + timespan_t reduction = timespan_t::from_seconds( data().effectN( 4 ).base_value() ); + if ( p()->buff.blackout_combo->up() ) + { + reduction += timespan_t::from_seconds( p()->buff.blackout_combo->data().effectN( 3 ).base_value() ); + p()->proc.blackout_combo_keg_smash->occur(); + } + p()->buff.blackout_combo->expire(); - void execute() override - { - monk_melee_attack_t::execute(); + p()->baseline.brewmaster.brews.adjust( reduction ); + } - p()->buff.touch_of_death_ww->trigger(); - p()->buff.fatal_touch->trigger(); - } + void impact( action_state_t *state ) override + { + monk_melee_attack_t::impact( state ); + get_td( state->target )->debuff.keg_smash->trigger(); + if ( p()->buff.weapons_of_order->up() ) + get_td( state->target )->debuff.weapons_of_order->trigger(); + } + }; - void impact( action_state_t *s ) override + // ========================================================================== + // Touch of Death + // ========================================================================== + struct touch_of_death_t : public monk_melee_attack_t { - double max_hp, amount; + touch_of_death_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "touch_of_death", p->baseline.monk.touch_of_death ) + { + ww_mastery = true; + may_crit = hasted_ticks = false; + may_combo_strike = true; + cast_during_sck = true; + parse_options( options_str ); + + cooldown->duration = data().cooldown(); - // In execute range ToD deals player's max HP - amount = max_hp = p()->max_health(); + apply_affecting_aura( p->talent.monk.fatal_touch ); + } - // Not in execute range - // or not a health-based fight style - // or a secondary target... these always get hit for the 35% from Improved Touch of Death regardless if you're - // talented into it or not - if ( s->chain_target > 0 || target->current_health() == 0 || target->current_health() > max_hp ) + void init() override { - amount *= p()->passives.improved_touch_of_death->effectN( 2 ).percent(); // 0.35 + monk_melee_attack_t::init(); - amount *= 1 + p()->talent.windwalker.meridian_strikes->effectN( 1 ).percent(); + snapshot_flags = update_flags = 0; + } - // Damage is only affected by Windwalker's Mastery - // Versatility does not affect the damage of Touch of Death. - if ( p()->buff.combo_strikes->up() ) - amount *= 1 + p()->cache.mastery_value(); + double composite_target_armor( player_t * ) const override + { + return 0; } - s->result_total = s->result_raw = amount; + bool target_ready( player_t *target ) override + { + // Deals damage equal to 35% of your maximum health against players and stronger creatures under 15% health + // 2023-10-19 Tooltip lies and the 15% health works on all non-player targets. + if ( p()->talent.monk.improved_touch_of_death->ok() && + ( target->health_percentage() < p()->talent.monk.improved_touch_of_death->effectN( 1 ).base_value() ) ) + return monk_melee_attack_t::target_ready( target ); - monk_melee_attack_t::impact( s ); + // You exploit the enemy target's weakest point, instantly killing creatures if they have less health than you + // Only applicable in health based sims + if ( target->current_health() > 0 && target->current_health() <= p()->max_health() ) + return monk_melee_attack_t::target_ready( target ); - if ( p()->baseline.brewmaster.stagger->ok() ) - { - p()->find_stagger( "Stagger" ) - ->purify_flat( amount * p()->baseline.brewmaster.touch_of_death_rank_3->effectN( 1 ).percent(), - "touch_of_death" ); + return false; } - } -}; -// ========================================================================== -// Touch of Karma -// ========================================================================== -// When Touch of Karma (ToK) is activated, two spells are placed. A buff on the player (id: 125174), and a -// debuff on the target (id: 122470). Whenever the player takes damage, a dot (id: 124280) is placed on -// the target that increases as the player takes damage. Each time the player takes damage, the dot is refreshed -// and recalculates the dot size based on the current dot size. Just to make it easier to code, I'll wait until -// the Touch of Karma buff expires before placing a dot on the target. Net result should be the same. + void execute() override + { + monk_melee_attack_t::execute(); -// 8.1 Good Karma - If the player still has the ToK buff on them, each time the target hits the player, the amount -// absorbed is immediatly healed by the Good Karma spell (id: 285594) + p()->buff.touch_of_death_ww->trigger(); + p()->buff.fatal_touch->trigger(); + } -struct touch_of_karma_dot_t : public residual_action::residual_periodic_action_t -{ - using base_t = residual_action::residual_periodic_action_t; - touch_of_karma_dot_t( monk_t *p ) : base_t( "touch_of_karma", p, p->passives.touch_of_karma_tick ) - { - may_miss = may_crit = false; - dual = true; - proc = true; - ap_type = attack_power_type::NO_WEAPON; - } + void impact( action_state_t *s ) override + { + double max_hp, amount; - // Need to disable multipliers in init() so that it doesn't double-dip on anything - void init() override - { - base_t::init(); - // disable the snapshot_flags for all multipliers - snapshot_flags = update_flags = 0; - snapshot_flags |= STATE_VERSATILITY; - } -}; + // In execute range ToD deals player's max HP + amount = max_hp = p()->max_health(); -struct touch_of_karma_t : public monk_melee_attack_t -{ - double interval; - double interval_stddev; - double interval_stddev_opt; - double pct_health; - touch_of_karma_dot_t *touch_of_karma_dot; - touch_of_karma_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "touch_of_karma", p->baseline.windwalker.touch_of_karma ), - interval( 100 ), - interval_stddev( 0.05 ), - interval_stddev_opt( 0 ), - pct_health( 0.5 ), - touch_of_karma_dot( new touch_of_karma_dot_t( p ) ) - { - add_option( opt_float( "interval", interval ) ); - add_option( opt_float( "interval_stddev", interval_stddev_opt ) ); - add_option( opt_float( "pct_health", pct_health ) ); - parse_options( options_str ); + // Not in execute range + // or not a health-based fight style + // or a secondary target... these always get hit for the 35% from Improved Touch of Death regardless if you're + // talented into it or not + if ( s->chain_target > 0 || target->current_health() == 0 || target->current_health() > max_hp ) + { + amount *= p()->passives.improved_touch_of_death->effectN( 2 ).percent(); // 0.35 + + amount *= 1 + p()->talent.windwalker.meridian_strikes->effectN( 1 ).percent(); - cooldown->duration = data().cooldown(); - base_dd_min = base_dd_max = 0; - ap_type = attack_power_type::NO_WEAPON; - cast_during_sck = true; + // Damage is only affected by Windwalker's Mastery + // Versatility does not affect the damage of Touch of Death. + if ( p()->buff.combo_strikes->up() ) + amount *= 1 + p()->cache.mastery_value(); + } - double max_pct = data().effectN( 3 ).percent(); + s->result_total = s->result_raw = amount; - if ( pct_health > max_pct ) // Does a maximum of 50% of the monk's HP. - pct_health = max_pct; + monk_melee_attack_t::impact( s ); - if ( interval < cooldown->duration.total_seconds() ) - { - sim->error( "{} minimum interval for Touch of Karma is 90 seconds.", *player ); - interval = cooldown->duration.total_seconds(); + if ( p()->baseline.brewmaster.stagger->ok() ) + { + p()->find_stagger( "Stagger" ) + ->purify_flat( amount * p()->baseline.brewmaster.touch_of_death_rank_3->effectN( 1 ).percent(), + "touch_of_death" ); + } } + }; - if ( interval_stddev_opt < 1 ) - interval_stddev = interval * interval_stddev_opt; - // >= 1 seconds is used as a standard deviation normally - else - interval_stddev = interval_stddev_opt; - - trigger_gcd = timespan_t::zero(); - may_crit = may_miss = may_dodge = may_parry = false; - } + // ========================================================================== + // Touch of Karma + // ========================================================================== + // When Touch of Karma (ToK) is activated, two spells are placed. A buff on the player (id: 125174), and a + // debuff on the target (id: 122470). Whenever the player takes damage, a dot (id: 124280) is placed on + // the target that increases as the player takes damage. Each time the player takes damage, the dot is refreshed + // and recalculates the dot size based on the current dot size. Just to make it easier to code, I'll wait until + // the Touch of Karma buff expires before placing a dot on the target. Net result should be the same. - // Need to disable multipliers in init() so that it doesn't double-dip on anything - void init() override - { - monk_melee_attack_t::init(); - // disable the snapshot_flags for all multipliers - snapshot_flags = update_flags = 0; - } + // 8.1 Good Karma - If the player still has the ToK buff on them, each time the target hits the player, the amount + // absorbed is immediatly healed by the Good Karma spell (id: 285594) - bool target_ready( player_t *target ) override + struct touch_of_karma_dot_t : public residual_action::residual_periodic_action_t { - if ( target->name_str == "Target Dummy" ) - return false; + using base_t = residual_action::residual_periodic_action_t; + touch_of_karma_dot_t( monk_t *p ) : base_t( "touch_of_karma", p, p->passives.touch_of_karma_tick ) + { + may_miss = may_crit = false; + dual = true; + proc = true; + ap_type = attack_power_type::NO_WEAPON; + } - return monk_melee_attack_t::target_ready( target ); - } + // Need to disable multipliers in init() so that it doesn't double-dip on anything + void init() override + { + base_t::init(); + // disable the snapshot_flags for all multipliers + snapshot_flags = update_flags = 0; + snapshot_flags |= STATE_VERSATILITY; + } + }; - void execute() override - { - timespan_t new_cd = timespan_t::from_seconds( rng().gauss( interval, interval_stddev ) ); - timespan_t data_cooldown = data().cooldown(); - if ( new_cd < data_cooldown ) - new_cd = data_cooldown; + struct touch_of_karma_t : public monk_melee_attack_t + { + double interval; + double interval_stddev; + double interval_stddev_opt; + double pct_health; + touch_of_karma_dot_t *touch_of_karma_dot; + touch_of_karma_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "touch_of_karma", p->baseline.windwalker.touch_of_karma ), + interval( 100 ), + interval_stddev( 0.05 ), + interval_stddev_opt( 0 ), + pct_health( 0.5 ), + touch_of_karma_dot( new touch_of_karma_dot_t( p ) ) + { + add_option( opt_float( "interval", interval ) ); + add_option( opt_float( "interval_stddev", interval_stddev_opt ) ); + add_option( opt_float( "pct_health", pct_health ) ); + parse_options( options_str ); + + cooldown->duration = data().cooldown(); + base_dd_min = base_dd_max = 0; + ap_type = attack_power_type::NO_WEAPON; + cast_during_sck = true; + + double max_pct = data().effectN( 3 ).percent(); + + if ( pct_health > max_pct ) // Does a maximum of 50% of the monk's HP. + pct_health = max_pct; + + if ( interval < cooldown->duration.total_seconds() ) + { + sim->error( "{} minimum interval for Touch of Karma is 90 seconds.", *player ); + interval = cooldown->duration.total_seconds(); + } - cooldown->duration = new_cd; + if ( interval_stddev_opt < 1 ) + interval_stddev = interval * interval_stddev_opt; + // >= 1 seconds is used as a standard deviation normally + else + interval_stddev = interval_stddev_opt; - monk_melee_attack_t::execute(); + trigger_gcd = timespan_t::zero(); + may_crit = may_miss = may_dodge = may_parry = false; + } - if ( pct_health > 0 ) + // Need to disable multipliers in init() so that it doesn't double-dip on anything + void init() override { - double damage_amount = pct_health * player->max_health(); + monk_melee_attack_t::init(); + // disable the snapshot_flags for all multipliers + snapshot_flags = update_flags = 0; + } - // Redirects 70% of the damage absorbed - damage_amount *= data().effectN( 4 ).percent(); + bool target_ready( player_t *target ) override + { + if ( target->name_str == "Target Dummy" ) + return false; - residual_action::trigger( touch_of_karma_dot, execute_state->target, damage_amount ); + return monk_melee_attack_t::target_ready( target ); } - } -}; -// ========================================================================== -// Provoke -// ========================================================================== + void execute() override + { + timespan_t new_cd = timespan_t::from_seconds( rng().gauss( interval, interval_stddev ) ); + timespan_t data_cooldown = data().cooldown(); + if ( new_cd < data_cooldown ) + new_cd = data_cooldown; -struct provoke_t : public monk_melee_attack_t -{ - provoke_t( monk_t *p, util::string_view options_str ) : monk_melee_attack_t( p, "provoke", p->baseline.monk.provoke ) - { - parse_options( options_str ); - use_off_gcd = true; - ignore_false_positive = true; - } + cooldown->duration = new_cd; - void impact( action_state_t *s ) override - { - if ( s->target->is_enemy() ) - target->taunt( player ); + monk_melee_attack_t::execute(); - monk_melee_attack_t::impact( s ); - } -}; + if ( pct_health > 0 ) + { + double damage_amount = pct_health * player->max_health(); -// ========================================================================== -// Spear Hand Strike -// ========================================================================== + // Redirects 70% of the damage absorbed + damage_amount *= data().effectN( 4 ).percent(); -struct spear_hand_strike_t : public monk_melee_attack_t -{ - spear_hand_strike_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "spear_hand_strike", p->talent.monk.spear_hand_strike ) - { - parse_options( options_str ); - ignore_false_positive = true; - is_interrupt = true; - cast_during_sck = player->specialization() != MONK_WINDWALKER; - may_miss = may_block = may_dodge = may_parry = false; - } -}; + residual_action::trigger( touch_of_karma_dot, execute_state->target, damage_amount ); + } + } + }; -// ========================================================================== -// Leg Sweep -// ========================================================================== + // ========================================================================== + // Provoke + // ========================================================================== -struct leg_sweep_t : public monk_melee_attack_t -{ - leg_sweep_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "leg_sweep", p->baseline.monk.leg_sweep ) + struct provoke_t : public monk_melee_attack_t { - parse_options( options_str ); - ignore_false_positive = true; - may_miss = may_block = may_dodge = may_parry = false; - cast_during_sck = player->specialization() != MONK_WINDWALKER; + provoke_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "provoke", p->baseline.monk.provoke ) + { + parse_options( options_str ); + use_off_gcd = true; + ignore_false_positive = true; + } - radius += p->talent.monk.tiger_tail_sweep->effectN( 1 ).base_value(); - } -}; + void impact( action_state_t *s ) override + { + if ( s->target->is_enemy() ) + target->taunt( player ); -// ========================================================================== -// Paralysis -// ========================================================================== + monk_melee_attack_t::impact( s ); + } + }; -struct paralysis_t : public monk_melee_attack_t -{ - paralysis_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "paralysis", p->talent.monk.paralysis ) + // ========================================================================== + // Spear Hand Strike + // ========================================================================== + + struct spear_hand_strike_t : public monk_melee_attack_t { - parse_options( options_str ); - ignore_false_positive = true; - may_miss = may_block = may_dodge = may_parry = false; - } -}; + spear_hand_strike_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "spear_hand_strike", p->talent.monk.spear_hand_strike ) + { + parse_options( options_str ); + ignore_false_positive = true; + is_interrupt = true; + cast_during_sck = player->specialization() != MONK_WINDWALKER; + may_miss = may_block = may_dodge = may_parry = false; + } + }; -// ========================================================================== -// Flying Serpent Kick -// ========================================================================== + // ========================================================================== + // Leg Sweep + // ========================================================================== -struct flying_serpent_kick_t : public monk_melee_attack_t -{ - bool first_charge; - double movement_speed_increase; - flying_serpent_kick_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "flying_serpent_kick", p->baseline.windwalker.flying_serpent_kick ), - first_charge( true ), - movement_speed_increase( p->baseline.windwalker.flying_serpent_kick->effectN( 1 ).percent() ) + struct leg_sweep_t : public monk_melee_attack_t { - parse_options( options_str ); - may_crit = true; - ww_mastery = true; - may_combo_strike = true; - ignore_false_positive = true; - movement_directionality = movement_direction_type::OMNI; - aoe = -1; - p->cooldown.flying_serpent_kick = cooldown; - } + leg_sweep_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "leg_sweep", p->baseline.monk.leg_sweep ) + { + parse_options( options_str ); + ignore_false_positive = true; + may_miss = may_block = may_dodge = may_parry = false; + cast_during_sck = player->specialization() != MONK_WINDWALKER; - void reset() override - { - monk_melee_attack_t::reset(); - first_charge = true; - } + radius += p->talent.monk.tiger_tail_sweep->effectN( 1 ).base_value(); + } + }; - bool ready() override + // ========================================================================== + // Paralysis + // ========================================================================== + + struct paralysis_t : public monk_melee_attack_t { - if ( first_charge ) // Assumes that we fsk into combat, instead of setting initial distance to 20 yards. - return monk_melee_attack_t::ready(); + paralysis_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "paralysis", p->talent.monk.paralysis ) + { + parse_options( options_str ); + ignore_false_positive = true; + may_miss = may_block = may_dodge = may_parry = false; + } + }; - return monk_melee_attack_t::ready(); - } + // ========================================================================== + // Flying Serpent Kick + // ========================================================================== - void execute() override + struct flying_serpent_kick_t : public monk_melee_attack_t { - if ( p()->current.distance_to_move >= 0 ) + bool first_charge; + double movement_speed_increase; + flying_serpent_kick_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "flying_serpent_kick", p->baseline.windwalker.flying_serpent_kick ), + first_charge( true ), + movement_speed_increase( p->baseline.windwalker.flying_serpent_kick->effectN( 1 ).percent() ) { - p()->buff.flying_serpent_kick_movement->trigger( - 1, movement_speed_increase, 1, - timespan_t::from_seconds( - std::min( 1.5, p()->current.distance_to_move / - ( p()->base_movement_speed * - ( 1 + p()->stacking_movement_modifier() + movement_speed_increase ) ) ) ) ); - p()->current.moving_away = 0; + parse_options( options_str ); + may_crit = true; + ww_mastery = true; + may_combo_strike = true; + ignore_false_positive = true; + movement_directionality = movement_direction_type::OMNI; + aoe = -1; + p->cooldown.flying_serpent_kick = cooldown; } - monk_melee_attack_t::execute(); + void reset() override + { + monk_melee_attack_t::reset(); + first_charge = true; + } - if ( first_charge ) + bool ready() override { - p()->movement.flying_serpent_kick->trigger(); + if ( first_charge ) // Assumes that we fsk into combat, instead of setting initial distance to 20 yards. + return monk_melee_attack_t::ready(); - first_charge = !first_charge; + return monk_melee_attack_t::ready(); } - } -}; + + void execute() override + { + if ( p()->current.distance_to_move >= 0 ) + { + p()->buff.flying_serpent_kick_movement->trigger( + 1, movement_speed_increase, 1, + timespan_t::from_seconds( + std::min( 1.5, p()->current.distance_to_move / + ( p()->base_movement_speed * + ( 1 + p()->stacking_movement_modifier() + movement_speed_increase ) ) ) ) ); + p()->current.moving_away = 0; + } + + monk_melee_attack_t::execute(); + + if ( first_charge ) + { + p()->movement.flying_serpent_kick->trigger(); + + first_charge = !first_charge; + } + } + }; } // namespace attacks namespace spells diff --git a/engine/class_modules/monk/sc_monk_pets.cpp b/engine/class_modules/monk/sc_monk_pets.cpp index 5ab5f7e6f7c..459c7071f18 100644 --- a/engine/class_modules/monk/sc_monk_pets.cpp +++ b/engine/class_modules/monk/sc_monk_pets.cpp @@ -349,20 +349,6 @@ struct monk_pet_buff_t : public buff_t return p().o(); }; }; - -// =============================================================================== -// Tier 28 Primordial Power Buff -// =============================================================================== - -struct primordial_power_buff_t : public monk_pet_buff_t -{ - primordial_power_buff_t( monk_pet_t &p, util::string_view n, const spell_data_t *s ) : monk_pet_buff_t( p, n, s ) - { - add_invalidate( CACHE_PLAYER_DAMAGE_MULTIPLIER ); - set_reverse( true ); - set_reverse_stack_count( s->max_stacks() ); - } -}; } // namespace buffs // ========================================================================== @@ -381,12 +367,6 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t const action_t *source_action; - // Windwalker Tier 28 4-piece info - // Currently Primordial Power is only a visual buff and not tied to any direct damage buff - // the buff is pulled from the player - // Currently the buff appears if SEF is summoned before Primordial Potential becomes Primordial Power - // buff does not appear if SEF is summoned after Primordial Power is active. - sef_action_base_t( util::string_view n, storm_earth_and_fire_pet_t *p, const spell_data_t *data = spell_data_t::nil() ) : super_t( n, p, data ), source_action( nullptr ) @@ -1203,8 +1183,6 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t propagate_const bok_proc_sef = nullptr; propagate_const pressure_point_sef = nullptr; propagate_const rushing_jade_wind_sef = nullptr; - // Tier 28 Buff - propagate_const primordial_power = nullptr; } buff; storm_earth_and_fire_pet_t( util::string_view name, monk_t *owner, bool dual_wield, weapon_e weapon_type ) From 7db1219cda7e438f4d9bbd9652b1d14afb76ec64 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Thu, 29 Aug 2024 06:10:44 -0600 Subject: [PATCH 2/7] [monk] Continue setting up SEF meta action. --- engine/class_modules/monk/sc_monk.cpp | 3609 +++++++++++++------------ 1 file changed, 1845 insertions(+), 1764 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 298d43bc6df..6bbc5ab15e3 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -908,8 +908,92 @@ struct storm_earth_and_fire_fixate_t : public monk_spell_t template struct sef_action_t : base_t { - base_t *sef_fire_action; - base_t *sef_earth_action; + struct child_action_t : base_t + { + template + child_action_t( Args &&...args ) : base_t( std::forward( args )... ) + { + base_t::background = true; + } + + bool from_caster_spells( const spelleffect_data_t *eff ) + { + switch ( eff->subtype() ) + { + case A_MOD_DAMAGE_FROM_CASTER: + case A_MOD_DAMAGE_FROM_CASTER_SPELLS: + case A_MOD_CRIT_CHANCE_FROM_CASTER: + case A_MOD_CRIT_CHANCE_FROM_CASTER_SPELLS: + case A_MOD_AUTO_ATTACK_FROM_CASTER: + case A_MOD_CRIT_DAMAGE_PCT_FROM_CASTER_SPELLS: + case A_MOD_DAMAGE_FROM_CASTER_SPELLS_LABEL: + return true; + } + return false; + } + + // TODO: How do I bypass `parse_effect` overrides? + double composite_player_multiplier( const action_state_t * ) const override + { + // Composite Player Multiplier is strictly school mods, which get skipped. + return 1.0; + } + + double composite_crit_chance() const override + { + auto cc = PLACEHOLDER_T::composite_crit_chance(); + + for ( const auto &i : base_t::crit_chance_effects ) + if ( !from_caster_spells( i.eff ) ) + cc += base_t::get_effect_value( i ); + + return cc; + } + + double composite_crit_damage_bonus_multiplier() const override + { + // Crit Damage Bonus Multipliers double dip. + auto cd = PLACEHOLDER_T::composite_crit_damage_bonus_multiplier() * *2 / 2.0; + + return cd; + } + + double composite_target_crit_damage_bonus_multiplier( player_t *target ) const override + { + auto cd = PLACEHOLDER_T::composite_target_crit_damage_bonus_multiplier( target ); + auto td = base_t::p()->get_target_data( target ); + + for ( const auto &i : base_t::target_crit_bonus_effects ) + if ( !from_caster_spells( i.eff ) ) + cd *= 1.0 + base_t::get_effect_value( i, td ); + + return cd; + } + + double composite_da_multiplier( const action_state_t *state ) const override + { + auto da = PLACEHOLDER_T::composite_da_multiplier( state ); + + for ( const auto &i : base_t::da_multiplier_effects ) + if ( !from_caster_spells( i.eff ) ) + da *= 1.0 + base_t::get_effect_value( i, false ); + + return da; + } + + double composite_ta_multiplier( const action_state_t *state ) const override + { + auto ta = PLACEHOLDER_T::composite_ta_multiplier( state ); + + for ( const auto &i : base_t::ta_multiplier_effects ) + if ( !from_caster_spells( i.eff ) ) + ta *= 1.0 + base_t::get_effect_value( i, false ); + + return ta; + } + }; + action_t *sef_fire_action; + action_t *sef_earth_action; // TODO: Target Fire/Earth actions /* @@ -929,8 +1013,10 @@ struct sef_action_t : base_t if ( !player->talent.windwalker.storm_earth_and_fire->ok() ) return; - sef_fire_action = new base_t( player, name, std::forward( args )... ); - sef_earth_action = new base_t( player, name, std::forward( args )... ); + sef_fire_action = new child_action_t( player, name, std::forward( args )... ); + sef_earth_action = new child_action_t( player, name, std::forward( args )... ); + + // TODO: Set SEF actions as children of the parent SEF action for each elemental. } void execute() override @@ -940,2326 +1026,2321 @@ struct sef_action_t : base_t sef_fire_action->execute_on_target( base_t::p()->target ); sef_earth_action->execute_on_target( base_t::p()->target ); } -} +}; namespace attacks { - // ========================================================================== - // Windwalking Aura Toggle - // ========================================================================== +// ========================================================================== +// Windwalking Aura Toggle +// ========================================================================== + +struct windwalking_aura_t : public monk_spell_t +{ + windwalking_aura_t( monk_t *player ) : monk_spell_t( player, "windwalking_aura_toggle" ) + { + harmful = false; + background = true; + trigger_gcd = timespan_t::zero(); + } - struct windwalking_aura_t : public monk_spell_t + size_t available_targets( std::vector &tl ) const override { - windwalking_aura_t( monk_t *player ) : monk_spell_t( player, "windwalking_aura_toggle" ) + tl.clear(); + + for ( auto t : sim->player_non_sleeping_list ) { - harmful = false; - background = true; - trigger_gcd = timespan_t::zero(); + tl.push_back( t ); } - size_t available_targets( std::vector &tl ) const override - { - tl.clear(); + return tl.size(); + } - for ( auto t : sim->player_non_sleeping_list ) - { - tl.push_back( t ); - } + std::vector &check_distance_targeting( std::vector &tl ) const override + { + size_t i = tl.size(); + while ( i > 0 ) + { + i--; + player_t *target_to_buff = tl[ i ]; - return tl.size(); + if ( p()->get_player_distance( *target_to_buff ) > 10.0 ) + tl.erase( tl.begin() + i ); } - std::vector &check_distance_targeting( std::vector &tl ) const override + return tl; + } +}; + +// ========================================================================== +// Flurry Strikes +// ========================================================================== +struct flurry_strikes_t : public monk_melee_attack_t +{ + struct high_impact_t : public monk_spell_t + { + high_impact_t( monk_t *p ) + : monk_spell_t( p, "high_impact", p->talent.shado_pan.high_impact_debuff->effectN( 1 ).trigger() ) // 451039 { - size_t i = tl.size(); - while ( i > 0 ) - { - i--; - player_t *target_to_buff = tl[ i ]; + aoe = -1; + background = dual = true; + } + }; - if ( p()->get_player_distance( *target_to_buff ) > 10.0 ) - tl.erase( tl.begin() + i ); - } + struct flurry_strike_wisdom_t : public monk_spell_t + { + flurry_strike_wisdom_t( monk_t *p ) + : monk_spell_t( p, "flurry_strike_wisdom", p->talent.shado_pan.wisdom_of_the_wall_flurry ) + { + aoe = -1; + background = dual = true; - return tl; + name_str_reporting = "flurry_strike_wisdom_of_the_wall"; } }; - // ========================================================================== - // Flurry Strikes - // ========================================================================== - struct flurry_strikes_t : public monk_melee_attack_t + struct flurry_strike_t : public monk_melee_attack_t { - struct high_impact_t : public monk_spell_t + enum wisdom_buff_e { - high_impact_t( monk_t *p ) - : monk_spell_t( p, "high_impact", p->talent.shado_pan.high_impact_debuff->effectN( 1 ).trigger() ) // 451039 - { - aoe = -1; - background = dual = true; - } + WISDOM_OF_THE_WALL_CRIT, + WISDOM_OF_THE_WALL_DODGE, + WISDOM_OF_THE_WALL_FLURRY, + WISDOM_OF_THE_WALL_MASTERY }; - struct flurry_strike_wisdom_t : public monk_spell_t - { - flurry_strike_wisdom_t( monk_t *p ) - : monk_spell_t( p, "flurry_strike_wisdom", p->talent.shado_pan.wisdom_of_the_wall_flurry ) - { - aoe = -1; - background = dual = true; - - name_str_reporting = "flurry_strike_wisdom_of_the_wall"; - } - }; + int flurry_strikes_counter; + int flurry_strikes_threshold; + shuffled_rng_t *deck; + flurry_strike_wisdom_t *wisdom_flurry; - struct flurry_strike_t : public monk_melee_attack_t + flurry_strike_t( monk_t *p ) + : monk_melee_attack_t( p, "flurry_strike", p->talent.shado_pan.flurry_strikes_hit ), + flurry_strikes_counter( 0 ), + flurry_strikes_threshold( as( p->talent.shado_pan.wisdom_of_the_wall->effectN( 1 ).base_value() ) ), + deck( p->get_shuffled_rng( "wisdom_of_the_wall", { { WISDOM_OF_THE_WALL_CRIT, 1 }, + { WISDOM_OF_THE_WALL_DODGE, 1 }, + { WISDOM_OF_THE_WALL_FLURRY, 1 }, + { WISDOM_OF_THE_WALL_MASTERY, 1 } } ) ) { - enum wisdom_buff_e - { - WISDOM_OF_THE_WALL_CRIT, - WISDOM_OF_THE_WALL_DODGE, - WISDOM_OF_THE_WALL_FLURRY, - WISDOM_OF_THE_WALL_MASTERY - }; + background = dual = true; - int flurry_strikes_counter; - int flurry_strikes_threshold; - shuffled_rng_t *deck; - flurry_strike_wisdom_t *wisdom_flurry; - - flurry_strike_t( monk_t *p ) - : monk_melee_attack_t( p, "flurry_strike", p->talent.shado_pan.flurry_strikes_hit ), - flurry_strikes_counter( 0 ), - flurry_strikes_threshold( as( p->talent.shado_pan.wisdom_of_the_wall->effectN( 1 ).base_value() ) ), - deck( p->get_shuffled_rng( "wisdom_of_the_wall", { { WISDOM_OF_THE_WALL_CRIT, 1 }, - { WISDOM_OF_THE_WALL_DODGE, 1 }, - { WISDOM_OF_THE_WALL_FLURRY, 1 }, - { WISDOM_OF_THE_WALL_MASTERY, 1 } } ) ) - { - background = dual = true; + apply_affecting_aura( p->talent.shado_pan.pride_of_pandaria ); - apply_affecting_aura( p->talent.shado_pan.pride_of_pandaria ); + wisdom_flurry = new flurry_strike_wisdom_t( p ); + add_child( wisdom_flurry ); + } - wisdom_flurry = new flurry_strike_wisdom_t( p ); - add_child( wisdom_flurry ); - } + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - void impact( action_state_t *s ) override + if ( p()->talent.shado_pan.wisdom_of_the_wall->ok() ) { - monk_melee_attack_t::impact( s ); + flurry_strikes_counter++; - if ( p()->talent.shado_pan.wisdom_of_the_wall->ok() ) + if ( flurry_strikes_counter >= flurry_strikes_threshold ) { - flurry_strikes_counter++; + flurry_strikes_counter -= flurry_strikes_threshold; - if ( flurry_strikes_counter >= flurry_strikes_threshold ) + // Draw new card + const auto card = wisdom_buff_e( deck->trigger() ); + switch ( card ) { - flurry_strikes_counter -= flurry_strikes_threshold; - - // Draw new card - const auto card = wisdom_buff_e( deck->trigger() ); - switch ( card ) - { - case WISDOM_OF_THE_WALL_CRIT: - p()->buff.wisdom_of_the_wall_crit->trigger(); - break; - case WISDOM_OF_THE_WALL_DODGE: - p()->buff.wisdom_of_the_wall_dodge->trigger(); - break; - case WISDOM_OF_THE_WALL_FLURRY: - p()->buff.wisdom_of_the_wall_flurry->trigger(); - break; - case WISDOM_OF_THE_WALL_MASTERY: - p()->buff.wisdom_of_the_wall_mastery->trigger(); - break; - default: - break; - } + case WISDOM_OF_THE_WALL_CRIT: + p()->buff.wisdom_of_the_wall_crit->trigger(); + break; + case WISDOM_OF_THE_WALL_DODGE: + p()->buff.wisdom_of_the_wall_dodge->trigger(); + break; + case WISDOM_OF_THE_WALL_FLURRY: + p()->buff.wisdom_of_the_wall_flurry->trigger(); + break; + case WISDOM_OF_THE_WALL_MASTERY: + p()->buff.wisdom_of_the_wall_mastery->trigger(); + break; + default: + break; } } + } - p()->buff.against_all_odds->trigger(); + p()->buff.against_all_odds->trigger(); - if ( auto target_data = p()->get_target_data( s->target ); target_data ) - target_data->debuff.high_impact->trigger(); + if ( auto target_data = p()->get_target_data( s->target ); target_data ) + target_data->debuff.high_impact->trigger(); - if ( p()->buff.wisdom_of_the_wall_flurry->up() ) - { - wisdom_flurry->set_target( s->target ); - wisdom_flurry->execute(); - } + if ( p()->buff.wisdom_of_the_wall_flurry->up() ) + { + wisdom_flurry->set_target( s->target ); + wisdom_flurry->execute(); } - }; + } + }; - flurry_strike_t *strike; - high_impact_t *high_impact; + flurry_strike_t *strike; + high_impact_t *high_impact; - flurry_strikes_t( monk_t *p ) : monk_melee_attack_t( p, "flurry_strikes", p->talent.shado_pan.flurry_strikes ) - { - strike = new flurry_strike_t( p ); - add_child( strike ); + flurry_strikes_t( monk_t *p ) : monk_melee_attack_t( p, "flurry_strikes", p->talent.shado_pan.flurry_strikes ) + { + strike = new flurry_strike_t( p ); + add_child( strike ); + + if ( !p->talent.shado_pan.high_impact->ok() ) + return; - if ( !p->talent.shado_pan.high_impact->ok() ) + high_impact = new high_impact_t( p ); + add_child( high_impact ); + p->register_on_kill_callback( [ this, p ]( player_t *target ) { + if ( p->sim->event_mgr.canceled ) return; - high_impact = new high_impact_t( p ); - add_child( high_impact ); - p->register_on_kill_callback( [ this, p ]( player_t *target ) { - if ( p->sim->event_mgr.canceled ) - return; + if ( auto target_data = p->get_target_data( target ); target_data && target_data->debuff.high_impact->up() ) + high_impact->execute_on_target( target ); + } ); + } - if ( auto target_data = p->get_target_data( target ); target_data && target_data->debuff.high_impact->up() ) - high_impact->execute_on_target( target ); - } ); - } + void execute() override + { + // 150ms of delay between executes has been observed, with some small amount of jitter + if ( p()->buff.flurry_charge->up() ) + for ( int charge = 1; charge <= p()->buff.flurry_charge->stack(); charge++ ) + make_event( *sim, p(), strike, p()->target, charge * 150_ms ); - void execute() override + p()->buff.flurry_charge->expire(); + p()->buff.vigilant_watch->expire(); + } +}; + +// ========================================================================== +// Overwhelming Force +// ========================================================================== + +template +struct overwhelming_force_t : base_action_t +{ + using base_t = overwhelming_force_t; + struct damage_t : monk_spell_t + { + damage_t( monk_t *player, std::string_view name ) + : monk_spell_t( player, fmt::format( "overwhelming_force_{}", name ), + player->talent.master_of_harmony.overwhelming_force_damage ) { - // 150ms of delay between executes has been observed, with some small amount of jitter - if ( p()->buff.flurry_charge->up() ) - for ( int charge = 1; charge <= p()->buff.flurry_charge->stack(); charge++ ) - make_event( *sim, p(), strike, p()->target, charge * 150_ms ); + background = dual = proc = true; + base_multiplier = player->talent.master_of_harmony.overwhelming_force->effectN( 1 ).percent(); + reduced_aoe_targets = player->talent.master_of_harmony.overwhelming_force->effectN( 2 ).base_value(); + } - p()->buff.flurry_charge->expire(); - p()->buff.vigilant_watch->expire(); + void init() override + { + monk_spell_t::init(); + update_flags = snapshot_flags &= STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; } }; - // ========================================================================== - // Overwhelming Force - // ========================================================================== + damage_t *overwhelming_force_damage; - template - struct overwhelming_force_t : base_action_t + template + overwhelming_force_t( monk_t *player, Args &&...args ) + : base_action_t( player, std::forward( args )... ), overwhelming_force_damage( nullptr ) { - using base_t = overwhelming_force_t; - struct damage_t : monk_spell_t - { - damage_t( monk_t *player, std::string_view name ) - : monk_spell_t( player, fmt::format( "overwhelming_force_{}", name ), - player->talent.master_of_harmony.overwhelming_force_damage ) - { - background = dual = proc = true; - base_multiplier = player->talent.master_of_harmony.overwhelming_force->effectN( 1 ).percent(); - reduced_aoe_targets = player->talent.master_of_harmony.overwhelming_force->effectN( 2 ).base_value(); - } + if ( !player->talent.master_of_harmony.overwhelming_force->ok() ) + return; - void init() override - { - monk_spell_t::init(); - update_flags = snapshot_flags &= STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; - } - }; + overwhelming_force_damage = new damage_t( player, base_action_t::name_str ); + base_action_t::add_child( overwhelming_force_damage ); + } - damage_t *overwhelming_force_damage; + void impact( action_state_t *state ) override + { + base_action_t::impact( state ); - template - overwhelming_force_t( monk_t *player, Args &&...args ) - : base_action_t( player, std::forward( args )... ), overwhelming_force_damage( nullptr ) - { - if ( !player->talent.master_of_harmony.overwhelming_force->ok() ) - return; + if ( !base_action_t::p()->talent.master_of_harmony.overwhelming_force->ok() || state->chain_target > 0 ) + return; - overwhelming_force_damage = new damage_t( player, base_action_t::name_str ); - base_action_t::add_child( overwhelming_force_damage ); - } + /* + * If the triggering hit is a crit, the damage is divided by the crit bonus + * multiplier, and then multiplied by 2.0 (or the context base crit bonus?) + * + * E.g. + * Base Damage (Crit) 64286, Crit Bonus Multiplier 2.02 + * Base Damage (Pre-Crit) 64286 / 2.02 ~ 31825 + * Overwhelming Force Damage 31825 * 0.15 * 2 = ~9547 + */ + double amount = state->result_amount; + if ( state->result == RESULT_CRIT && base_action_t::p()->bugs ) + { + amount /= 1.0 + state->result_crit_bonus; + amount *= 2.0; + } + overwhelming_force_damage->base_dd_min = overwhelming_force_damage->base_dd_max = amount; + overwhelming_force_damage->execute(); + } +}; - void impact( action_state_t *state ) override - { - base_action_t::impact( state ); +// ========================================================================== +// Tiger Palm +// ========================================================================== - if ( !base_action_t::p()->talent.master_of_harmony.overwhelming_force->ok() || state->chain_target > 0 ) - return; +// Tiger's Ferocity ( Windwalker TWW1 4PC ) +struct tigers_ferocity_t : public monk_melee_attack_t +{ + std::vector &t_list; - /* - * If the triggering hit is a crit, the damage is divided by the crit bonus - * multiplier, and then multiplied by 2.0 (or the context base crit bonus?) - * - * E.g. - * Base Damage (Crit) 64286, Crit Bonus Multiplier 2.02 - * Base Damage (Pre-Crit) 64286 / 2.02 ~ 31825 - * Overwhelming Force Damage 31825 * 0.15 * 2 = ~9547 - */ - double amount = state->result_amount; - if ( state->result == RESULT_CRIT && base_action_t::p()->bugs ) - { - amount /= 1.0 + state->result_crit_bonus; - amount *= 2.0; - } - overwhelming_force_damage->base_dd_min = overwhelming_force_damage->base_dd_max = amount; - overwhelming_force_damage->execute(); - } - }; + tigers_ferocity_t( monk_t *p ) + : monk_melee_attack_t( p, "tigers_ferocity", p->tier.tww1.ww_4pc_dmg ), t_list( target_cache.list ) + { + background = dual = true; + aoe = -1; + reduced_aoe_targets = p->tier.tww1.ww_4pc->effectN( 2 ).base_value(); + } + + std::vector &target_list() const override + { + // The player's target is not hit by this ability so we need to modify the target list. + t_list = base_t::target_list(); + t_list.erase( std::remove( t_list.begin(), t_list.end(), player->target ), t_list.end() ); + return t_list; + } +}; - // ========================================================================== - // Tiger Palm - // ========================================================================== +// Tiger Palm base ability =================================================== +struct tiger_palm_t : public overwhelming_force_t +{ + bool face_palm; + action_t *tigers_ferocity; - // Tiger's Ferocity ( Windwalker TWW1 4PC ) - struct tigers_ferocity_t : public monk_melee_attack_t + tiger_palm_t( monk_t *p, util::string_view options_str ) + : base_t( p, "tiger_palm", p->baseline.monk.tiger_palm ), face_palm( false ) { - std::vector &t_list; + parse_options( options_str ); - tigers_ferocity_t( monk_t *p ) - : monk_melee_attack_t( p, "tigers_ferocity", p->tier.tww1.ww_4pc_dmg ), t_list( target_cache.list ) - { - background = dual = true; - aoe = -1; - reduced_aoe_targets = p->tier.tww1.ww_4pc->effectN( 2 ).base_value(); - } + // allow Darting Hurricane to reduce GCD all the way down to 500ms + min_gcd = 500_ms; + ww_mastery = true; + may_combo_strike = true; + trigger_chiji = true; + sef_ability = actions::sef_ability_e::SEF_TIGER_PALM; + cast_during_sck = player->specialization() != MONK_WINDWALKER; + + if ( p->specialization() == MONK_WINDWALKER ) + energize_amount = p->baseline.windwalker.aura->effectN( 4 ).base_value(); + else + energize_type = action_energize::NONE; + + spell_power_mod.direct = 0.0; + + apply_affecting_aura( p->talent.windwalker.touch_of_the_tiger ); + apply_affecting_aura( p->talent.windwalker.inner_peace ); + + if ( const auto &effect = p->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) + add_parse_entry( da_multiplier_effects ) + .set_func( [ & ] { return face_palm; } ) + .set_value( effect.percent() - 1.0 ) + .set_eff( &effect ); + parse_effects( p->buff.combat_wisdom ); + parse_effects( p->buff.martial_mixture ); + parse_effects( p->buff.darting_hurricane ); - std::vector &target_list() const override + if ( p->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) { - // The player's target is not hit by this ability so we need to modify the target list. - t_list = base_t::target_list(); - t_list.erase( std::remove( t_list.begin(), t_list.end(), player->target ), t_list.end() ); - return t_list; + tigers_ferocity = new tigers_ferocity_t( p ); + add_child( tigers_ferocity ); } - }; + } - // Tiger Palm base ability =================================================== - struct tiger_palm_t : public overwhelming_force_t + bool ready() override { - bool face_palm; - action_t *tigers_ferocity; + if ( p()->talent.brewmaster.press_the_advantage->ok() ) + return false; + return monk_melee_attack_t::ready(); + } - tiger_palm_t( monk_t *p, util::string_view options_str ) - : base_t( p, "tiger_palm", p->baseline.monk.tiger_palm ), face_palm( false ) - { - parse_options( options_str ); + void execute() override + { + //============ + // Pre-Execute + //============ - // allow Darting Hurricane to reduce GCD all the way down to 500ms - min_gcd = 500_ms; - ww_mastery = true; - may_combo_strike = true; - trigger_chiji = true; - sef_ability = actions::sef_ability_e::SEF_TIGER_PALM; - cast_during_sck = player->specialization() != MONK_WINDWALKER; + if ( ( face_palm = rng().roll( p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) ) + p()->proc.face_palm->occur(); - if ( p->specialization() == MONK_WINDWALKER ) - energize_amount = p->baseline.windwalker.aura->effectN( 4 ).base_value(); - else - energize_type = action_energize::NONE; + if ( p()->buff.blackout_combo->up() ) + p()->proc.blackout_combo_tiger_palm->occur(); - spell_power_mod.direct = 0.0; + if ( p()->buff.counterstrike->up() ) + p()->proc.counterstrike_tp->occur(); - apply_affecting_aura( p->talent.windwalker.touch_of_the_tiger ); - apply_affecting_aura( p->talent.windwalker.inner_peace ); + //------------ - if ( const auto &effect = p->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) - add_parse_entry( da_multiplier_effects ) - .set_func( [ & ] { return face_palm; } ) - .set_value( effect.percent() - 1.0 ) - .set_eff( &effect ); - parse_effects( p->buff.combat_wisdom ); - parse_effects( p->buff.martial_mixture ); - parse_effects( p->buff.darting_hurricane ); + monk_melee_attack_t::execute(); - if ( p->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) - { - tigers_ferocity = new tigers_ferocity_t( p ); - add_child( tigers_ferocity ); - } - } + p()->buff.blackout_combo->expire(); + + if ( result_is_miss( execute_state->result ) ) + return; + + //----------- + + //============ + // Post-hit + //============ + + p()->buff.teachings_of_the_monastery->trigger(); - bool ready() override + // Combo Breaker calculation + if ( p()->baseline.windwalker.combo_breaker->ok() && p()->buff.bok_proc->trigger() && + p()->buff.storm_earth_and_fire->up() ) { - if ( p()->talent.brewmaster.press_the_advantage->ok() ) - return false; - return monk_melee_attack_t::ready(); + p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_FIRE ); + p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_EARTH ); } - void execute() override + // Reduces the remaining cooldown on your Brews by 1 sec + p()->baseline.brewmaster.brews.adjust( + timespan_t::from_seconds( p()->baseline.monk.tiger_palm->effectN( 3 ).base_value() ) ); + + if ( face_palm && !p()->bugs ) + p()->baseline.brewmaster.brews.adjust( p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); + + if ( p()->buff.combat_wisdom->up() ) { - //============ - // Pre-Execute - //============ + p()->passive_actions.combat_wisdom_eh->execute(); + p()->buff.combat_wisdom->expire(); + } - if ( ( face_palm = rng().roll( p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) ) - p()->proc.face_palm->occur(); + p()->buff.darting_hurricane->decrement(); - if ( p()->buff.blackout_combo->up() ) - p()->proc.blackout_combo_tiger_palm->occur(); + // T33 Windwalker Set Bonus + p()->buff.tiger_strikes->trigger(); + p()->buff.tigers_ferocity->expire(); + + p()->buff.martial_mixture->expire(); + } - if ( p()->buff.counterstrike->up() ) - p()->proc.counterstrike_tp->occur(); + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - //------------ + // Apply Mark of the Crane + p()->trigger_mark_of_the_crane( s ); - monk_melee_attack_t::execute(); + if ( p()->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) + { + double damage = s->result_amount; - p()->buff.blackout_combo->expire(); + if ( p()->buff.storm_earth_and_fire->up() ) + { + // Damage during SEF is based on the actor's damage before the SEF modifier. + damage /= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ); - if ( result_is_miss( execute_state->result ) ) - return; + // Tested 09/08/2024. Tiger's Ferocity deals additional damage during SEF. + damage *= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ) * 3; + } - //----------- + damage *= p()->tier.tww1.ww_4pc->effectN( 1 ).percent(); - //============ - // Post-hit - //============ + tigers_ferocity->base_dd_min = tigers_ferocity->base_dd_max = damage; + tigers_ferocity->execute_on_target( s->target ); + } + } +}; - p()->buff.teachings_of_the_monastery->trigger(); +// ========================================================================== +// Rising Sun Kick +// ========================================================================== - // Combo Breaker calculation - if ( p()->baseline.windwalker.combo_breaker->ok() && p()->buff.bok_proc->trigger() && - p()->buff.storm_earth_and_fire->up() ) - { - p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_FIRE ); - p()->trigger_storm_earth_and_fire_bok_proc( pets::sef_pet_e::SEF_EARTH ); - } +// Glory of the Dawn ================================================= +struct glory_of_the_dawn_t : public monk_melee_attack_t +{ + glory_of_the_dawn_t( monk_t *p, const std::string &name ) + : monk_melee_attack_t( p, name, p->passives.glory_of_the_dawn_damage ) + { + background = true; + ww_mastery = true; + sef_ability = actions::sef_ability_e::SEF_GLORY_OF_THE_DAWN; + + apply_affecting_aura( p->talent.windwalker.rising_star ); + } - // Reduces the remaining cooldown on your Brews by 1 sec - p()->baseline.brewmaster.brews.adjust( - timespan_t::from_seconds( p()->baseline.monk.tiger_palm->effectN( 3 ).base_value() ) ); + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - if ( face_palm && !p()->bugs ) - p()->baseline.brewmaster.brews.adjust( p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); + if ( p()->talent.windwalker.acclamation.ok() ) + get_td( s->target )->debuff.acclamation->trigger(); - if ( p()->buff.combat_wisdom->up() ) - { - p()->passive_actions.combat_wisdom_eh->execute(); - p()->buff.combat_wisdom->expire(); - } + if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) + { + p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), + true ); + p()->proc.xuens_battlegear_reduction->occur(); + } + } +}; - p()->buff.darting_hurricane->decrement(); +// Rising Sun Kick Damage Trigger =========================================== - // T33 Windwalker Set Bonus - p()->buff.tiger_strikes->trigger(); - p()->buff.tigers_ferocity->expire(); +template +struct press_the_advantage_t : base_action_t +{ + using base_t = press_the_advantage_t; + struct damage_t : base_action_t + { + const double mod; + bool face_palm; - p()->buff.martial_mixture->expire(); + damage_t( monk_t *player, std::string_view name ) + : base_action_t( player, {}, name ), + mod( 1.0 - player->talent.brewmaster.press_the_advantage->effectN( 3 ).percent() ), + face_palm( false ) + { + base_action_t::proc = true; + base_action_t::trigger_gcd = 0_s; + base_action_t::background = true; + base_action_t::dual = true; + + base_action_t::parse_effects( player->buff.counterstrike, + affect_list_t( 1 ).add_spell( base_action_t::data().id() ), + player->buff.counterstrike->data().effectN( 1 ).percent() * mod ); + base_action_t::parse_effects( player->buff.blackout_combo, + affect_list_t( 1 ).add_spell( base_action_t::data().id() ), + player->buff.blackout_combo->data().effectN( 1 ).percent() * mod ); + + // effect must still be rolled in execute so it triggers brew cdr + if ( const auto &effect = player->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) + add_parse_entry( base_action_t::da_multiplier_effects ) + .set_func( [ & ] { return face_palm; } ) + .set_value( ( effect.percent() - 1.0 ) * mod ) + .set_eff( &effect ); } - void impact( action_state_t *s ) override + void init_finished() override { - monk_melee_attack_t::impact( s ); + base_action_t::init_finished(); - // Apply Mark of the Crane - p()->trigger_mark_of_the_crane( s ); + if ( action_t *pta = base_action_t::p()->find_action( "press_the_advantage" ); + pta && base_action_t::p()->talent.brewmaster.press_the_advantage->ok() ) + pta->add_child( this ); + } + + void execute() override + { + base_action_t::p()->buff.press_the_advantage->expire(); - if ( p()->sets->has_set_bonus( MONK_WINDWALKER, TWW1, B4 ) ) + if ( ( face_palm = base_action_t::rng().roll( + base_action_t::p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) && + !base_action_t::p()->bugs ) { - double damage = s->result_amount; + base_action_t::p()->proc.face_palm->occur(); + base_action_t::p()->baseline.brewmaster.brews.adjust( + base_action_t::p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); + } - if ( p()->buff.storm_earth_and_fire->up() ) - { - // Damage during SEF is based on the actor's damage before the SEF modifier. - damage /= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ); + base_action_t::execute(); - // Tested 09/08/2024. Tiger's Ferocity deals additional damage during SEF. - damage *= ( 1 + p()->talent.windwalker.storm_earth_and_fire->effectN( 1 ).percent() ) * 3; - } + base_action_t::p()->buff.blackout_combo->expire(); - damage *= p()->tier.tww1.ww_4pc->effectN( 1 ).percent(); + if ( base_action_t::p()->talent.brewmaster.chi_surge->ok() ) + base_action_t::p()->active_actions.chi_surge->execute(); - tigers_ferocity->base_dd_min = tigers_ferocity->base_dd_max = damage; - tigers_ferocity->execute_on_target( s->target ); - } + if ( base_action_t::p()->talent.brewmaster.call_to_arms->ok() && base_action_t::rng().roll( 0.3 ) ) + base_action_t::p()->active_actions.niuzao_call_to_arms_summon->execute(); } }; - // ========================================================================== - // Rising Sun Kick - // ========================================================================== + propagate_const press_the_advantage_action; + propagate_const press_the_advantage_proc; - // Glory of the Dawn ================================================= - struct glory_of_the_dawn_t : public monk_melee_attack_t + template + press_the_advantage_t( monk_t *player, Args &&...args ) + : base_action_t( player, std::forward( args )... ), press_the_advantage_action( nullptr ) { - glory_of_the_dawn_t( monk_t *p, const std::string &name ) - : monk_melee_attack_t( p, name, p->passives.glory_of_the_dawn_damage ) - { - background = true; - ww_mastery = true; - sef_ability = actions::sef_ability_e::SEF_GLORY_OF_THE_DAWN; - - apply_affecting_aura( p->talent.windwalker.rising_star ); - } + if ( !player->talent.brewmaster.press_the_advantage->ok() ) + return; - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + press_the_advantage_action = + new damage_t( player, fmt::format( "{}_press_the_advantage", base_action_t::name_str ) ); + press_the_advantage_proc = player->get_proc( fmt::format( "{} - Press The Advantage", base_action_t::name_str ) ); + } - if ( p()->talent.windwalker.acclamation.ok() ) - get_td( s->target )->debuff.acclamation->trigger(); + void impact( action_state_t *state ) override + { + base_action_t::impact( state ); - if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) - { - p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), - true ); - p()->proc.xuens_battlegear_reduction->occur(); - } - } - }; + if ( base_action_t::p()->buff.press_the_advantage->stack() != 10 ) + return; - // Rising Sun Kick Damage Trigger =========================================== + // TODO: Schedule execute with the appropriate delay. + base_action_t::p()->buff.press_the_advantage->expire(); + press_the_advantage_proc->occur(); + press_the_advantage_action->execute(); + } +}; - template - struct press_the_advantage_t : base_action_t +struct rising_sun_kick_dmg_t : public overwhelming_force_t +{ + rising_sun_kick_dmg_t( monk_t *p, std::string_view /* options_str */, + std::string_view name = "rising_sun_kick_damage" ) + : base_t( p, name, p->talent.monk.rising_sun_kick->effectN( 1 ).trigger() ) { - using base_t = press_the_advantage_t; - struct damage_t : base_action_t - { - const double mod; - bool face_palm; + ww_mastery = true; - damage_t( monk_t *player, std::string_view name ) - : base_action_t( player, {}, name ), - mod( 1.0 - player->talent.brewmaster.press_the_advantage->effectN( 3 ).percent() ), - face_palm( false ) - { - base_action_t::proc = true; - base_action_t::trigger_gcd = 0_s; - base_action_t::background = true; - base_action_t::dual = true; - - base_action_t::parse_effects( player->buff.counterstrike, - affect_list_t( 1 ).add_spell( base_action_t::data().id() ), - player->buff.counterstrike->data().effectN( 1 ).percent() * mod ); - base_action_t::parse_effects( player->buff.blackout_combo, - affect_list_t( 1 ).add_spell( base_action_t::data().id() ), - player->buff.blackout_combo->data().effectN( 1 ).percent() * mod ); - - // effect must still be rolled in execute so it triggers brew cdr - if ( const auto &effect = player->talent.brewmaster.face_palm->effectN( 2 ); effect.ok() ) - add_parse_entry( base_action_t::da_multiplier_effects ) - .set_func( [ & ] { return face_palm; } ) - .set_value( ( effect.percent() - 1.0 ) * mod ) - .set_eff( &effect ); - } + if ( p->specialization() == MONK_WINDWALKER ) + ap_type = attack_power_type::WEAPON_BOTH; - void init_finished() override - { - base_action_t::init_finished(); + background = dual = true; + may_crit = true; + trigger_chiji = true; - if ( action_t *pta = base_action_t::p()->find_action( "press_the_advantage" ); - pta && base_action_t::p()->talent.brewmaster.press_the_advantage->ok() ) - pta->add_child( this ); - } + apply_affecting_aura( p->talent.windwalker.rising_star ); + } - void execute() override - { - base_action_t::p()->buff.press_the_advantage->expire(); + void execute() override + { + base_t::execute(); - if ( ( face_palm = base_action_t::rng().roll( - base_action_t::p()->talent.brewmaster.face_palm->effectN( 1 ).percent() ) ) && - !base_action_t::p()->bugs ) - { - base_action_t::p()->proc.face_palm->occur(); - base_action_t::p()->baseline.brewmaster.brews.adjust( - base_action_t::p()->talent.brewmaster.face_palm->effectN( 3 ).time_value() ); - } + if ( p()->buff.thunder_focus_tea->up() ) + { + p()->cooldown.rising_sun_kick->adjust( p()->talent.mistweaver.thunder_focus_tea->effectN( 1 ).time_value(), + true ); - base_action_t::execute(); + p()->buff.thunder_focus_tea->decrement(); + } - base_action_t::p()->buff.blackout_combo->expire(); + if ( p()->talent.brewmaster.strike_at_dawn->ok() ) + p()->buff.elusive_brawler->trigger(); - if ( base_action_t::p()->talent.brewmaster.chi_surge->ok() ) - base_action_t::p()->active_actions.chi_surge->execute(); + if ( p()->talent.brewmaster.black_ox_adept->ok() ) + p()->buff.ox_stance->trigger(); - if ( base_action_t::p()->talent.brewmaster.call_to_arms->ok() && base_action_t::rng().roll( 0.3 ) ) - base_action_t::p()->active_actions.niuzao_call_to_arms_summon->execute(); - } - }; + // Brewmaster RSK also applies the WoO debuff. + if ( p()->buff.weapons_of_order->up() ) + for ( const auto &target : target_list() ) + get_td( target )->debuff.weapons_of_order->trigger(); + } + + void impact( action_state_t *s ) override + { + base_t::impact( s ); - propagate_const press_the_advantage_action; - propagate_const press_the_advantage_proc; + p()->buff.transfer_the_power->trigger(); - template - press_the_advantage_t( monk_t *player, Args &&...args ) - : base_action_t( player, std::forward( args )... ), press_the_advantage_action( nullptr ) + if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) { - if ( !player->talent.brewmaster.press_the_advantage->ok() ) - return; - - press_the_advantage_action = - new damage_t( player, fmt::format( "{}_press_the_advantage", base_action_t::name_str ) ); - press_the_advantage_proc = player->get_proc( fmt::format( "{} - Press The Advantage", base_action_t::name_str ) ); + p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), + true ); + p()->proc.xuens_battlegear_reduction->occur(); } - void impact( action_state_t *state ) override - { - base_action_t::impact( state ); + if ( p()->baseline.windwalker.combat_conditioning->ok() ) + s->target->debuffs.mortal_wounds->trigger(); - if ( base_action_t::p()->buff.press_the_advantage->stack() != 10 ) - return; + // Apply Mark of the Crane + p()->trigger_mark_of_the_crane( s ); - // TODO: Schedule execute with the appropriate delay. - base_action_t::p()->buff.press_the_advantage->expire(); - press_the_advantage_proc->occur(); - press_the_advantage_action->execute(); - } - }; + if ( p()->talent.windwalker.acclamation.ok() ) + get_td( s->target )->debuff.acclamation->trigger(); + } +}; + +struct rising_sun_kick_t : public monk_melee_attack_t +{ + glory_of_the_dawn_t *gotd; - struct rising_sun_kick_dmg_t : public overwhelming_force_t + rising_sun_kick_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "rising_sun_kick", p->talent.monk.rising_sun_kick ) { - rising_sun_kick_dmg_t( monk_t *p, std::string_view /* options_str */, - std::string_view name = "rising_sun_kick_damage" ) - : base_t( p, name, p->talent.monk.rising_sun_kick->effectN( 1 ).trigger() ) - { - ww_mastery = true; + parse_options( options_str ); - if ( p->specialization() == MONK_WINDWALKER ) - ap_type = attack_power_type::WEAPON_BOTH; + may_combo_strike = true; + sef_ability = actions::sef_ability_e::SEF_RISING_SUN_KICK; + ap_type = attack_power_type::NONE; + cast_during_sck = player->specialization() != MONK_WINDWALKER; - background = dual = true; - may_crit = true; - trigger_chiji = true; + attack_power_mod.direct = 0; - apply_affecting_aura( p->talent.windwalker.rising_star ); - } + execute_action = new press_the_advantage_t( p, options_str ); + execute_action->stats = stats; - void execute() override + if ( p->talent.windwalker.glory_of_the_dawn->ok() ) { - base_t::execute(); + gotd = new glory_of_the_dawn_t( p, "glory_of_the_dawn" ); + add_child( gotd ); + } + } - if ( p()->buff.thunder_focus_tea->up() ) - { - p()->cooldown.rising_sun_kick->adjust( p()->talent.mistweaver.thunder_focus_tea->effectN( 1 ).time_value(), - true ); + void execute() override + { + monk_melee_attack_t::execute(); - p()->buff.thunder_focus_tea->decrement(); - } + // TODO: Is this the correct way to get character sheet haste %? + auto gotd_chance = p()->talent.windwalker.glory_of_the_dawn->effectN( 2 ).percent() * + ( ( 1.0 / p()->composite_spell_haste() ) - 1.0 ); - if ( p()->talent.brewmaster.strike_at_dawn->ok() ) - p()->buff.elusive_brawler->trigger(); + if ( rng().roll( gotd_chance ) ) + gotd->execute_on_target( this->target ); - if ( p()->talent.brewmaster.black_ox_adept->ok() ) - p()->buff.ox_stance->trigger(); + p()->buff.whirling_dragon_punch->trigger(); - // Brewmaster RSK also applies the WoO debuff. - if ( p()->buff.weapons_of_order->up() ) - for ( const auto &target : target_list() ) - get_td( target )->debuff.weapons_of_order->trigger(); - } + p()->active_actions.chi_wave->execute(); - void impact( action_state_t *s ) override - { - base_t::impact( s ); + if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) + p()->buff.ordered_elements->trigger(); - p()->buff.transfer_the_power->trigger(); + p()->buff.tigers_ferocity->trigger(); - if ( p()->talent.windwalker.xuens_battlegear->ok() && ( s->result == RESULT_CRIT ) ) - { - p()->cooldown.fists_of_fury->adjust( -1 * p()->talent.windwalker.xuens_battlegear->effectN( 2 ).time_value(), - true ); - p()->proc.xuens_battlegear_reduction->occur(); - } + p()->buff.august_dynasty->expire(); + } +}; - if ( p()->baseline.windwalker.combat_conditioning->ok() ) - s->target->debuffs.mortal_wounds->trigger(); +// ========================================================================== +// Blackout Kick +// ========================================================================== - // Apply Mark of the Crane - p()->trigger_mark_of_the_crane( s ); +// Blackout Kick Proc from Teachings of the Monastery ======================= +struct blackout_kick_totm_proc_t : public monk_melee_attack_t +{ + blackout_kick_totm_proc_t( monk_t *p ) + : monk_melee_attack_t( p, "blackout_kick_totm_proc", p->talent.windwalker.teachings_of_the_monastery_blackout_kick ) + { + sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK_TOTM; + ww_mastery = false; + cooldown->duration = timespan_t::zero(); + background = dual = true; + trigger_chiji = true; + trigger_gcd = timespan_t::zero(); + } - if ( p()->talent.windwalker.acclamation.ok() ) - get_td( s->target )->debuff.acclamation->trigger(); + void init_finished() override + { + monk_melee_attack_t::init_finished(); + action_t *bok = player->find_action( "blackout_kick" ); + if ( bok ) + { + attack_power_mod = bok->attack_power_mod; + bok->add_child( this ); } - }; + } - struct rising_sun_kick_t : public monk_melee_attack_t + double composite_target_multiplier( player_t *target ) const override { - glory_of_the_dawn_t *gotd; - - rising_sun_kick_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "rising_sun_kick", p->talent.monk.rising_sun_kick ) - { - parse_options( options_str ); + double m = base_t::composite_target_multiplier( target ); - may_combo_strike = true; - sef_ability = actions::sef_ability_e::SEF_RISING_SUN_KICK; - ap_type = attack_power_type::NONE; - cast_during_sck = player->specialization() != MONK_WINDWALKER; + if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) + m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); - attack_power_mod.direct = 0; + return m; + } - execute_action = new press_the_advantage_t( p, options_str ); - execute_action->stats = stats; + // Force 100 milliseconds for the animation, but not delay the overall GCD + timespan_t execute_time() const override + { + return timespan_t::from_millis( 100 ); + } - if ( p->talent.windwalker.glory_of_the_dawn->ok() ) - { - gotd = new glory_of_the_dawn_t( p, "glory_of_the_dawn" ); - add_child( gotd ); - } - } + double cost() const override + { + return 0; + } - void execute() override - { - monk_melee_attack_t::execute(); + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - // TODO: Is this the correct way to get character sheet haste %? - auto gotd_chance = p()->talent.windwalker.glory_of_the_dawn->effectN( 2 ).percent() * - ( ( 1.0 / p()->composite_spell_haste() ) - 1.0 ); + // The initial hit along with each individual TotM hits has a chance to reset the cooldown + auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); - if ( rng().roll( gotd_chance ) ) - gotd->execute_on_target( this->target ); + if ( p()->specialization() == MONK_MISTWEAVER ) + totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); - p()->buff.whirling_dragon_punch->trigger(); + if ( rng().roll( totmResetChance ) ) + { + p()->cooldown.rising_sun_kick->reset( true ); + p()->proc.rsk_reset_totm->occur(); + } - p()->active_actions.chi_wave->execute(); + // Mark of the Crane is only triggered on the initial target + if ( s->chain_target == 0 ) + p()->trigger_mark_of_the_crane( s ); - if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) - p()->buff.ordered_elements->trigger(); + // Martial Mixture triggers from each ToTM impact + p()->buff.martial_mixture->trigger(); + } +}; - p()->buff.tigers_ferocity->trigger(); +// Charred Passions ============================================================ +template +struct charred_passions_t : base_action_t +{ + using base_t = charred_passions_t; + struct damage_t : monk_spell_t + { + damage_t( monk_t *player, std::string_view name ) + : monk_spell_t( player, fmt::format( "charred_passions_{}", name ), player->talent.brewmaster.charred_passions ) + { + background = dual = proc = true; + may_crit = false; + base_multiplier = data().effectN( 1 ).percent(); + } - p()->buff.august_dynasty->expire(); + void init() override + { + monk_spell_t::init(); + update_flags = snapshot_flags = STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; } }; - // ========================================================================== - // Blackout Kick - // ========================================================================== + damage_t *chp_damage; + cooldown_t *chp_cooldown; - // Blackout Kick Proc from Teachings of the Monastery ======================= - struct blackout_kick_totm_proc_t : public monk_melee_attack_t + template + charred_passions_t( monk_t *player, Args &&...args ) : base_action_t( player, std::forward( args )... ) { - blackout_kick_totm_proc_t( monk_t *p ) - : monk_melee_attack_t( p, "blackout_kick_totm_proc", - p->talent.windwalker.teachings_of_the_monastery_blackout_kick ) - { - sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK_TOTM; - ww_mastery = false; - cooldown->duration = timespan_t::zero(); - background = dual = true; - trigger_chiji = true; - trigger_gcd = timespan_t::zero(); - } + if ( !player->talent.brewmaster.charred_passions->ok() ) + return; - void init_finished() override - { - monk_melee_attack_t::init_finished(); - action_t *bok = player->find_action( "blackout_kick" ); - if ( bok ) - { - attack_power_mod = bok->attack_power_mod; - bok->add_child( this ); - } - } + chp_cooldown = player->get_cooldown( "charred_passions" ); + chp_damage = new damage_t( player, base_action_t::name_str ); + // TODO: Have a more resilient way to re-map stats objects. + // Issue: When SCK tick stats replace the action stats of SCK channel, adding + // a child of SCK tick breaks reporting. + // base_action_t::add_child( damage ); + } - double composite_target_multiplier( player_t *target ) const override - { - double m = base_t::composite_target_multiplier( target ); + void impact( action_state_t *state ) override + { + base_action_t::impact( state ); - if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) - m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); + if ( !base_action_t::p()->buff.charred_passions->up() ) + return; - return m; - } + base_action_t::p()->proc.charred_passions->occur(); + chp_damage->base_dd_min = chp_damage->base_dd_max = state->result_amount; + chp_damage->execute(); - // Force 100 milliseconds for the animation, but not delay the overall GCD - timespan_t execute_time() const override + if ( monk_td_t *target_data = base_action_t::get_td( state->target ); + target_data && target_data->dot.breath_of_fire->is_ticking() && chp_cooldown->up() ) { - return timespan_t::from_millis( 100 ); + target_data->dot.breath_of_fire->refresh_duration(); + chp_cooldown->start( chp_damage->data().effectN( 1 ).trigger()->internal_cooldown() ); } + } +}; - double cost() const override - { - return 0; - } +// Blackout Kick Baseline ability ======================================= +struct blackout_kick_t : overwhelming_force_t> +{ + blackout_kick_totm_proc_t *bok_totm_proc; + cooldown_t *keg_smash_cooldown; - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + blackout_kick_t( monk_t *p, util::string_view options_str ) + : base_t( p, "blackout_kick", + ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.blackout_kick + : p->baseline.monk.blackout_kick ) ), + keg_smash_cooldown( nullptr ) + { + parse_options( options_str ); + if ( p->specialization() == MONK_WINDWALKER ) + ap_type = attack_power_type::WEAPON_BOTH; - // The initial hit along with each individual TotM hits has a chance to reset the cooldown - auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); + sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK; + ww_mastery = true; + may_combo_strike = true; + trigger_chiji = true; + cast_during_sck = p->specialization() != MONK_WINDWALKER; - if ( p()->specialization() == MONK_MISTWEAVER ) - totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); + apply_affecting_aura( p->talent.brewmaster.fluidity_of_motion ); + apply_affecting_aura( p->talent.brewmaster.shadowboxing_treads ); + apply_affecting_aura( p->talent.brewmaster.elusive_footwork ); - if ( rng().roll( totmResetChance ) ) - { - p()->cooldown.rising_sun_kick->reset( true ); - p()->proc.rsk_reset_totm->occur(); - } + if ( player->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() ) + keg_smash_cooldown = player->get_cooldown( "keg_smash" ); - // Mark of the Crane is only triggered on the initial target - if ( s->chain_target == 0 ) - p()->trigger_mark_of_the_crane( s ); + if ( p->talent.brewmaster.charred_passions->ok() ) + add_child( base_t::chp_damage ); - // Martial Mixture triggers from each ToTM impact - p()->buff.martial_mixture->trigger(); + if ( p->shared.teachings_of_the_monastery->ok() ) + { + bok_totm_proc = new blackout_kick_totm_proc_t( p ); + add_child( bok_totm_proc ); } - }; - // Charred Passions ============================================================ - template - struct charred_passions_t : base_action_t + if ( p->baseline.windwalker.blackout_kick_rank_2->ok() ) + base_costs[ RESOURCE_CHI ].base += + p->baseline.windwalker.blackout_kick_rank_2->effectN( 1 ).base_value(); // Reduce base from 3 chi to 1 + } + + double composite_target_multiplier( player_t *target ) const override { - using base_t = charred_passions_t; - struct damage_t : monk_spell_t - { - damage_t( monk_t *player, std::string_view name ) - : monk_spell_t( player, fmt::format( "charred_passions_{}", name ), player->talent.brewmaster.charred_passions ) - { - background = dual = proc = true; - may_crit = false; - base_multiplier = data().effectN( 1 ).percent(); - } + double m = base_t::composite_target_multiplier( target ); - void init() override - { - monk_spell_t::init(); - update_flags = snapshot_flags = STATE_NO_MULTIPLIER | STATE_MUL_SPELL_DA; - } - }; + if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) + m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); - damage_t *chp_damage; - cooldown_t *chp_cooldown; + return m; + } - template - charred_passions_t( monk_t *player, Args &&...args ) : base_action_t( player, std::forward( args )... ) - { - if ( !player->talent.brewmaster.charred_passions->ok() ) - return; + void consume_resource() override + { + base_t::consume_resource(); - chp_cooldown = player->get_cooldown( "charred_passions" ); - chp_damage = new damage_t( player, base_action_t::name_str ); - // TODO: Have a more resilient way to re-map stats objects. - // Issue: When SCK tick stats replace the action stats of SCK channel, adding - // a child of SCK tick breaks reporting. - // base_action_t::add_child( damage ); - } + // Register how much chi is saved without actually refunding the chi + if ( p()->buff.bok_proc->up() ) + p()->gain.bok_proc->add( RESOURCE_CHI, base_costs[ RESOURCE_CHI ] ); + } - void impact( action_state_t *state ) override - { - base_action_t::impact( state ); + void execute() override + { + base_t::execute(); - if ( !base_action_t::p()->buff.charred_passions->up() ) - return; + p()->buff.shuffle->trigger( timespan_t::from_seconds( p()->talent.brewmaster.shuffle->effectN( 1 ).base_value() ) ); - base_action_t::p()->proc.charred_passions->occur(); - chp_damage->base_dd_min = chp_damage->base_dd_max = state->result_amount; - chp_damage->execute(); + p()->buff.flow_of_battle_damage->trigger(); + // 08-18-2024: Sampling of a large number of logs strongly suggests a proc rate of 0.33. + // Reproducible via running https://github.com/renanthera/crunch/tree/ec850f8b37b922f177d88b0c1626271a382ce771 + if ( keg_smash_cooldown && p()->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() && p()->rng().roll( 0.33 ) ) + { + keg_smash_cooldown->reset( false ); + p()->buff.flow_of_battle_free_keg_smash->trigger(); + } - if ( monk_td_t *target_data = base_action_t::get_td( state->target ); - target_data && target_data->dot.breath_of_fire->is_ticking() && chp_cooldown->up() ) + if ( result_is_hit( execute_state->result ) ) + { + if ( p()->buff.bok_proc->up() ) { - target_data->dot.breath_of_fire->refresh_duration(); - chp_cooldown->start( chp_damage->data().effectN( 1 ).trigger()->internal_cooldown() ); - } - } - }; + if ( p()->rng().roll( p()->talent.windwalker.energy_burst->effectN( 1 ).percent() ) ) + p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.energy_burst->effectN( 2 ).base_value(), + p()->gain.energy_burst ); - // Blackout Kick Baseline ability ======================================= - struct blackout_kick_t : overwhelming_force_t> - { - blackout_kick_totm_proc_t *bok_totm_proc; - cooldown_t *keg_smash_cooldown; + p()->buff.bok_proc->decrement(); + } - blackout_kick_t( monk_t *p, util::string_view options_str ) - : base_t( p, "blackout_kick", - ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.blackout_kick - : p->baseline.monk.blackout_kick ) ), - keg_smash_cooldown( nullptr ) - { - parse_options( options_str ); - if ( p->specialization() == MONK_WINDWALKER ) - ap_type = attack_power_type::WEAPON_BOTH; + p()->buff.blackout_combo->trigger(); - sef_ability = actions::sef_ability_e::SEF_BLACKOUT_KICK; - ww_mastery = true; - may_combo_strike = true; - trigger_chiji = true; - cast_during_sck = p->specialization() != MONK_WINDWALKER; + if ( p()->baseline.windwalker.blackout_kick_rank_3->ok() ) + { + // Reduce the cooldown of Rising Sun Kick and Fists of Fury + timespan_t cd_reduction = -1 * p()->baseline.monk.blackout_kick->effectN( 3 ).time_value(); - apply_affecting_aura( p->talent.brewmaster.fluidity_of_motion ); - apply_affecting_aura( p->talent.brewmaster.shadowboxing_treads ); - apply_affecting_aura( p->talent.brewmaster.elusive_footwork ); + if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) + { + cd_reduction += ( -1 * p()->talent.windwalker.ordered_elements->effectN( 1 ).time_value() ); + p()->proc.blackout_kick_cdr_oe->occur(); + } + else + p()->proc.blackout_kick_cdr->occur(); - if ( player->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() ) - keg_smash_cooldown = player->get_cooldown( "keg_smash" ); + p()->cooldown.rising_sun_kick->adjust( cd_reduction, true ); + p()->cooldown.fists_of_fury->adjust( cd_reduction, true ); + } - if ( p->talent.brewmaster.charred_passions->ok() ) - add_child( base_t::chp_damage ); + p()->buff.transfer_the_power->trigger(); - if ( p->shared.teachings_of_the_monastery->ok() ) + if ( p()->buff.teachings_of_the_monastery->up() ) { - bok_totm_proc = new blackout_kick_totm_proc_t( p ); - add_child( bok_totm_proc ); - } + p()->buff.teachings_of_the_monastery->expire(); - if ( p->baseline.windwalker.blackout_kick_rank_2->ok() ) - base_costs[ RESOURCE_CHI ].base += - p->baseline.windwalker.blackout_kick_rank_2->effectN( 1 ).base_value(); // Reduce base from 3 chi to 1 - } + if ( p()->rng().roll( p()->talent.conduit_of_the_celestials.xuens_guidance->effectN( 1 ).percent() ) ) + p()->buff.teachings_of_the_monastery->trigger(); + } - double composite_target_multiplier( player_t *target ) const override - { - double m = base_t::composite_target_multiplier( target ); + if ( p()->specialization() == MONK_WINDWALKER ) + { + p()->buff.strength_of_the_black_ox->expire(); + p()->buff.inner_compass_ox_stance->trigger(); + } - if ( target != p()->target && p()->talent.windwalker.shadowboxing_treads->ok() ) - m *= p()->talent.windwalker.shadowboxing_treads->effectN( 3 ).percent(); + p()->buff.vigilant_watch->trigger(); - return m; + p()->buff.tigers_ferocity->trigger(); } + } - void consume_resource() override - { - base_t::consume_resource(); + void impact( action_state_t *s ) override + { + base_t::impact( s ); - // Register how much chi is saved without actually refunding the chi - if ( p()->buff.bok_proc->up() ) - p()->gain.bok_proc->add( RESOURCE_CHI, base_costs[ RESOURCE_CHI ] ); - } + p()->buff.hit_scheme->trigger(); - void execute() override + // Teachings of the Monastery + // Used by both Windwalker and Mistweaver + if ( p()->buff.teachings_of_the_monastery->up() ) { - base_t::execute(); - - p()->buff.shuffle->trigger( - timespan_t::from_seconds( p()->talent.brewmaster.shuffle->effectN( 1 ).base_value() ) ); + int stacks = p()->buff.teachings_of_the_monastery->current_stack; - p()->buff.flow_of_battle_damage->trigger(); - // 08-18-2024: Sampling of a large number of logs strongly suggests a proc rate of 0.33. - // Reproducible via running https://github.com/renanthera/crunch/tree/ec850f8b37b922f177d88b0c1626271a382ce771 - if ( keg_smash_cooldown && p()->sets->set( MONK_BREWMASTER, TWW1, B4 )->ok() && p()->rng().roll( 0.33 ) ) + if ( p()->talent.windwalker.memory_of_the_monastery.enabled() && p()->bugs ) { - keg_smash_cooldown->reset( false ); - p()->buff.flow_of_battle_free_keg_smash->trigger(); + // TODO: Confirm proper mechanics for this. Tested 17/06/2024 and behaviour has it expire previous stacks + // before triggering new which feels like a bug. + p()->buff.memory_of_the_monastery->expire(); } - if ( result_is_hit( execute_state->result ) ) + for ( int i = 0; i < stacks; i++ ) { - if ( p()->buff.bok_proc->up() ) - { - if ( p()->rng().roll( p()->talent.windwalker.energy_burst->effectN( 1 ).percent() ) ) - p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.energy_burst->effectN( 2 ).base_value(), - p()->gain.energy_burst ); - - p()->buff.bok_proc->decrement(); - } - - p()->buff.blackout_combo->trigger(); - - if ( p()->baseline.windwalker.blackout_kick_rank_3->ok() ) + // Transfer the power and Memory of the Monastery triggers from ToTM hits but only on the primary target + if ( s->chain_target == 0 ) { - // Reduce the cooldown of Rising Sun Kick and Fists of Fury - timespan_t cd_reduction = -1 * p()->baseline.monk.blackout_kick->effectN( 3 ).time_value(); - - if ( p()->buff.storm_earth_and_fire->up() && p()->talent.windwalker.ordered_elements->ok() ) - { - cd_reduction += ( -1 * p()->talent.windwalker.ordered_elements->effectN( 1 ).time_value() ); - p()->proc.blackout_kick_cdr_oe->occur(); - } - else - p()->proc.blackout_kick_cdr->occur(); + p()->buff.transfer_the_power->trigger(); - p()->cooldown.rising_sun_kick->adjust( cd_reduction, true ); - p()->cooldown.fists_of_fury->adjust( cd_reduction, true ); + if ( p()->talent.windwalker.memory_of_the_monastery.enabled() ) + p()->buff.memory_of_the_monastery->trigger(); } - p()->buff.transfer_the_power->trigger(); - - if ( p()->buff.teachings_of_the_monastery->up() ) - { - p()->buff.teachings_of_the_monastery->expire(); - - if ( p()->rng().roll( p()->talent.conduit_of_the_celestials.xuens_guidance->effectN( 1 ).percent() ) ) - p()->buff.teachings_of_the_monastery->trigger(); - } + bok_totm_proc->execute_on_target( s->target ); + } - if ( p()->specialization() == MONK_WINDWALKER ) - { - p()->buff.strength_of_the_black_ox->expire(); - p()->buff.inner_compass_ox_stance->trigger(); - } + // The initial hit along with each individual TotM hits has a chance to reset the cooldown + auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); - p()->buff.vigilant_watch->trigger(); + if ( p()->specialization() == MONK_MISTWEAVER ) + totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); - p()->buff.tigers_ferocity->trigger(); + if ( rng().roll( totmResetChance ) ) + { + p()->cooldown.rising_sun_kick->reset( true ); + p()->proc.rsk_reset_totm->occur(); } } - void impact( action_state_t *s ) override + p()->trigger_mark_of_the_crane( s ); + + if ( p()->talent.brewmaster.elusive_footwork->ok() && s->result == RESULT_CRIT ) { - base_t::impact( s ); + p()->buff.elusive_brawler->trigger( + as( p()->talent.brewmaster.elusive_footwork->effectN( 2 ).base_value() ) ); + p()->proc.elusive_footwork_proc->occur(); + } - p()->buff.hit_scheme->trigger(); + if ( p()->talent.brewmaster.staggering_strikes->ok() ) + p()->find_stagger( "Stagger" ) + ->purify_flat( + s->composite_attack_power() * p()->talent.brewmaster.staggering_strikes->effectN( 2 ).percent(), + "staggering_strikes" ); - // Teachings of the Monastery - // Used by both Windwalker and Mistweaver - if ( p()->buff.teachings_of_the_monastery->up() ) - { - int stacks = p()->buff.teachings_of_the_monastery->current_stack; + // Martial Mixture triggers from each BoK impact + p()->buff.martial_mixture->trigger(); + } +}; - if ( p()->talent.windwalker.memory_of_the_monastery.enabled() && p()->bugs ) - { - // TODO: Confirm proper mechanics for this. Tested 17/06/2024 and behaviour has it expire previous stacks - // before triggering new which feels like a bug. - p()->buff.memory_of_the_monastery->expire(); - } +// ========================================================================== +// Rushing Jade Wind +// ========================================================================== - for ( int i = 0; i < stacks; i++ ) - { - // Transfer the power and Memory of the Monastery triggers from ToTM hits but only on the primary target - if ( s->chain_target == 0 ) - { - p()->buff.transfer_the_power->trigger(); - - if ( p()->talent.windwalker.memory_of_the_monastery.enabled() ) - p()->buff.memory_of_the_monastery->trigger(); - } +struct flight_of_the_red_crane_dmg_t : public monk_spell_t +{ + flight_of_the_red_crane_dmg_t( monk_t *p ) + : monk_spell_t( p, "flight_of_the_red_crane_dmg", p->talent.conduit_of_the_celestials.flight_of_the_red_crane_dmg ) + { + background = true; + aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); + } - bok_totm_proc->execute_on_target( s->target ); - } + void execute() override + { + monk_spell_t::execute(); - // The initial hit along with each individual TotM hits has a chance to reset the cooldown - auto totmResetChance = p()->shared.teachings_of_the_monastery->effectN( 1 ).percent(); + p()->buff.flight_of_the_red_crane->trigger(); + } +}; - if ( p()->specialization() == MONK_MISTWEAVER ) - totmResetChance += p()->baseline.mistweaver.aura->effectN( 21 ).percent(); +struct flight_of_the_red_crane_heal_t : public monk_heal_t +{ + flight_of_the_red_crane_heal_t( monk_t *p ) + : monk_heal_t( p, "flight_of_the_red_crane_heal", p->talent.conduit_of_the_celestials.flight_of_the_red_crane_heal ) + { + background = true; + aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); + target = p; + } +}; - if ( rng().roll( totmResetChance ) ) - { - p()->cooldown.rising_sun_kick->reset( true ); - p()->proc.rsk_reset_totm->occur(); - } - } +struct rushing_jade_wind_t : public monk_melee_attack_t +{ + buff_t *buff; - p()->trigger_mark_of_the_crane( s ); + rushing_jade_wind_t( monk_t *player, util::string_view options_str ) + : monk_melee_attack_t( player, "rushing_jade_wind", player->shared.rushing_jade_wind ), + buff( player->buff.rushing_jade_wind ) + { + parse_options( options_str ); + may_combo_strike = true; + } - if ( p()->talent.brewmaster.elusive_footwork->ok() && s->result == RESULT_CRIT ) - { - p()->buff.elusive_brawler->trigger( - as( p()->talent.brewmaster.elusive_footwork->effectN( 2 ).base_value() ) ); - p()->proc.elusive_footwork_proc->occur(); - } + void execute() override + { + monk_melee_attack_t::execute(); - if ( p()->talent.brewmaster.staggering_strikes->ok() ) - p()->find_stagger( "Stagger" ) - ->purify_flat( - s->composite_attack_power() * p()->talent.brewmaster.staggering_strikes->effectN( 2 ).percent(), - "staggering_strikes" ); + buff->trigger(); + } +}; - // Martial Mixture triggers from each BoK impact - p()->buff.martial_mixture->trigger(); - } - }; +// ========================================================================== +// Spinning Crane Kick +// ========================================================================== - // ========================================================================== - // Rushing Jade Wind - // ========================================================================== +// Jade Ignition Legendary +struct chi_explosion_t : public monk_spell_t +{ + chi_explosion_t( monk_t *player ) : monk_spell_t( player, "chi_explosion", player->passives.chi_explosion ) + { + dual = background = true; + aoe = -1; + school = SCHOOL_NATURE; + } - struct flight_of_the_red_crane_dmg_t : public monk_spell_t + double action_multiplier() const override { - flight_of_the_red_crane_dmg_t( monk_t *p ) - : monk_spell_t( p, "flight_of_the_red_crane_dmg", - p->talent.conduit_of_the_celestials.flight_of_the_red_crane_dmg ) - { - background = true; - aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); - } + double am = monk_spell_t::action_multiplier(); - void execute() override - { - monk_spell_t::execute(); + am *= 1 + p()->buff.chi_energy->check_stack_value(); - p()->buff.flight_of_the_red_crane->trigger(); - } - }; + return am; + } +}; - struct flight_of_the_red_crane_heal_t : public monk_heal_t +struct sck_tick_action_t : charred_passions_t +{ + sck_tick_action_t( monk_t *p, std::string_view name, const spell_data_t *data ) + : charred_passions_t( p, name, data ) { - flight_of_the_red_crane_heal_t( monk_t *p ) - : monk_heal_t( p, "flight_of_the_red_crane_heal", - p->talent.conduit_of_the_celestials.flight_of_the_red_crane_heal ) - { - background = true; - aoe = as( p->talent.conduit_of_the_celestials.flight_of_the_red_crane->effectN( 1 ).base_value() ); - target = p; - } - }; + ww_mastery = true; + trigger_chiji = true; - struct rushing_jade_wind_t : public monk_melee_attack_t - { - buff_t *buff; + dual = background = true; + aoe = -1; + reduced_aoe_targets = p->baseline.monk.spinning_crane_kick->effectN( 1 ).base_value(); - rushing_jade_wind_t( monk_t *player, util::string_view options_str ) - : monk_melee_attack_t( player, "rushing_jade_wind", player->shared.rushing_jade_wind ), - buff( player->buff.rushing_jade_wind ) - { - parse_options( options_str ); - may_combo_strike = true; - } + ap_type = attack_power_type::WEAPON_BOTH; - void execute() override - { - monk_melee_attack_t::execute(); + parse_effects( p->talent.windwalker.crane_vortex ); - buff->trigger(); - } - }; + // dance of chiji is scripted + if ( const auto &effect = p->talent.windwalker.dance_of_chiji->effectN( 1 ); effect.ok() ) + add_parse_entry( da_multiplier_effects ) + .set_func( [ &b = p->buff.dance_of_chiji_hidden ]() { return b->check(); } ) + .set_value( effect.percent() ) + .set_eff( &effect ); + + parse_effects( p->buff.cyclone_strikes, USE_CURRENT ); + } - // ========================================================================== - // Spinning Crane Kick - // ========================================================================== + result_amount_type report_amount_type( const action_state_t * ) const override + { + return result_amount_type::DMG_DIRECT; + } + + void execute() override + { + monk_melee_attack_t::execute(); + + p()->buff.shuffle->trigger( + timespan_t::from_seconds( p()->baseline.brewmaster.spinning_crane_kick_rank_2->effectN( 1 ).base_value() ) ); + } +}; - // Jade Ignition Legendary - struct chi_explosion_t : public monk_spell_t +struct spinning_crane_kick_t : public monk_melee_attack_t +{ + struct spinning_crane_kick_state_t : public action_state_t { - chi_explosion_t( monk_t *player ) : monk_spell_t( player, "chi_explosion", player->passives.chi_explosion ) + spinning_crane_kick_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) { - dual = background = true; - aoe = -1; - school = SCHOOL_NATURE; } - double action_multiplier() const override + proc_types2 cast_proc_type2() const override { - double am = monk_spell_t::action_multiplier(); - - am *= 1 + p()->buff.chi_energy->check_stack_value(); - - return am; + // Spinning Crane Kick seems to trigger Bron's Call to Action (and possibly other + // effects that care about casts). + return PROC2_CAST_GENERIC; } }; - struct sck_tick_action_t : charred_passions_t - { - sck_tick_action_t( monk_t *p, std::string_view name, const spell_data_t *data ) - : charred_passions_t( p, name, data ) - { - ww_mastery = true; - trigger_chiji = true; + chi_explosion_t *chi_x; - dual = background = true; - aoe = -1; - reduced_aoe_targets = p->baseline.monk.spinning_crane_kick->effectN( 1 ).base_value(); + spinning_crane_kick_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "spinning_crane_kick", + ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.spinning_crane_kick + : p->baseline.monk.spinning_crane_kick ) ), + chi_x( nullptr ) + { + parse_options( options_str ); - ap_type = attack_power_type::WEAPON_BOTH; + sef_ability = actions::sef_ability_e::SEF_SPINNING_CRANE_KICK; + may_combo_strike = true; + tick_zero = true; + tick_action = new sck_tick_action_t( p, "spinning_crane_kick_tick", data().effectN( 1 ).trigger() ); - parse_effects( p->talent.windwalker.crane_vortex ); + interrupt_auto_attack = p->specialization() != MONK_WINDWALKER; + if ( p->specialization() == MONK_BREWMASTER ) + { + dot_behavior = DOT_EXTEND; + cast_during_sck = true; - // dance of chiji is scripted - if ( const auto &effect = p->talent.windwalker.dance_of_chiji->effectN( 1 ); effect.ok() ) - add_parse_entry( da_multiplier_effects ) - .set_func( [ &b = p->buff.dance_of_chiji_hidden ]() { return b->check(); } ) - .set_value( effect.percent() ) - .set_eff( &effect ); + if ( p->talent.brewmaster.charred_passions->ok() ) + add_child( debug_cast( tick_action )->chp_damage ); + } - parse_effects( p->buff.cyclone_strikes, USE_CURRENT ); + if ( p->specialization() == MONK_WINDWALKER ) + { + channeled = true; + dot_behavior = DOT_CLIP; } - result_amount_type report_amount_type( const action_state_t * ) const override + if ( p->talent.windwalker.jade_ignition->ok() ) { - return result_amount_type::DMG_DIRECT; + chi_x = new chi_explosion_t( p ); + add_child( chi_x ); } - void execute() override + if ( p->baseline.windwalker.mark_of_the_crane->ok() && p->user_options.motc_override == 0 ) { - monk_melee_attack_t::execute(); + p->register_on_kill_callback( [ p ]( player_t *target ) { + if ( p->sim->event_mgr.canceled ) + return; - p()->buff.shuffle->trigger( - timespan_t::from_seconds( p()->baseline.brewmaster.spinning_crane_kick_rank_2->effectN( 1 ).base_value() ) ); + if ( auto target_data = p->get_target_data( target ); + target_data && target_data->debuff.mark_of_the_crane->up() ) + { + make_event( p->sim, target_data->debuff.mark_of_the_crane->remains(), [ p, target_data ]() { + p->sim->print_debug( "mark of the crane fell off dead target: {} ", target_data->target->name_str ); + p->buff.cyclone_strikes->decrement(); + } ); + target_data->debuff.mark_of_the_crane->expire(); + } + } ); } - }; + } - struct spinning_crane_kick_t : public monk_melee_attack_t + bool ready() override { - struct spinning_crane_kick_state_t : public action_state_t - { - spinning_crane_kick_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) - { - } + if ( p()->channeling && p()->channeling->id == id ) + return false; - proc_types2 cast_proc_type2() const override - { - // Spinning Crane Kick seems to trigger Bron's Call to Action (and possibly other - // effects that care about casts). - return PROC2_CAST_GENERIC; - } - }; + return monk_melee_attack_t::ready(); + } - chi_explosion_t *chi_x; + bool usable_moving() const override + { + return true; + } - spinning_crane_kick_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "spinning_crane_kick", - ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.spinning_crane_kick - : p->baseline.monk.spinning_crane_kick ) ), - chi_x( nullptr ) - { - parse_options( options_str ); + action_state_t *new_state() override + { + return new spinning_crane_kick_state_t( this, p()->target ); + } - sef_ability = actions::sef_ability_e::SEF_SPINNING_CRANE_KICK; - may_combo_strike = true; - tick_zero = true; - tick_action = new sck_tick_action_t( p, "spinning_crane_kick_tick", data().effectN( 1 ).trigger() ); + double cost_flat_modifier() const override + { + double c = monk_melee_attack_t::cost_flat_modifier(); - interrupt_auto_attack = p->specialization() != MONK_WINDWALKER; - if ( p->specialization() == MONK_BREWMASTER ) - { - dot_behavior = DOT_EXTEND; - cast_during_sck = true; + c += p()->buff.dance_of_chiji_hidden->check_value(); // saved as -2 - if ( p->talent.brewmaster.charred_passions->ok() ) - add_child( debug_cast( tick_action )->chp_damage ); - } + return c; + } - if ( p->specialization() == MONK_WINDWALKER ) + void execute() override + { + if ( p()->specialization() == MONK_WINDWALKER ) + { + if ( p()->buff.dance_of_chiji->up() ) { - channeled = true; - dot_behavior = DOT_CLIP; - } + p()->buff.dance_of_chiji->decrement(); + p()->buff.dance_of_chiji_hidden->trigger(); - if ( p->talent.windwalker.jade_ignition->ok() ) - { - chi_x = new chi_explosion_t( p ); - add_child( chi_x ); + if ( p()->rng().roll( p()->talent.windwalker.sequenced_strikes->effectN( 1 ).percent() ) ) + p()->buff.bok_proc->increment(); // increment is used to not incur the rppm cooldown } + } - if ( p->baseline.windwalker.mark_of_the_crane->ok() && p->user_options.motc_override == 0 ) - { - p->register_on_kill_callback( [ p ]( player_t *target ) { - if ( p->sim->event_mgr.canceled ) - return; + monk_melee_attack_t::execute(); - if ( auto target_data = p->get_target_data( target ); - target_data && target_data->debuff.mark_of_the_crane->up() ) - { - make_event( p->sim, target_data->debuff.mark_of_the_crane->remains(), [ p, target_data ]() { - p->sim->print_debug( "mark of the crane fell off dead target: {} ", target_data->target->name_str ); - p->buff.cyclone_strikes->decrement(); - } ); - target_data->debuff.mark_of_the_crane->expire(); - } - } ); - } - } + timespan_t buff_duration = composite_dot_duration( execute_state ); - bool ready() override - { - if ( p()->channeling && p()->channeling->id == id ) - return false; + p()->buff.spinning_crane_kick->trigger( 1, buff_t::DEFAULT_VALUE(), 1.0, buff_duration ); - return monk_melee_attack_t::ready(); - } + if ( chi_x && p()->buff.chi_energy->up() ) + chi_x->execute(); - bool usable_moving() const override - { - return true; - } + if ( p()->buff.celestial_flames->up() ) + p()->active_actions.breath_of_fire->execute_on_target( execute_state->target ); - action_state_t *new_state() override - { - return new spinning_crane_kick_state_t( this, p()->target ); - } + if ( p()->talent.windwalker.transfer_the_power->ok() ) + p()->buff.transfer_the_power->trigger(); - double cost_flat_modifier() const override - { - double c = monk_melee_attack_t::cost_flat_modifier(); + p()->buff.tigers_ferocity->trigger(); + } - c += p()->buff.dance_of_chiji_hidden->check_value(); // saved as -2 + void last_tick( dot_t *dot ) override + { + monk_melee_attack_t::last_tick( dot ); - return c; - } + p()->buff.dance_of_chiji_hidden->expire(); - void execute() override - { - if ( p()->specialization() == MONK_WINDWALKER ) - { - if ( p()->buff.dance_of_chiji->up() ) - { - p()->buff.dance_of_chiji->decrement(); - p()->buff.dance_of_chiji_hidden->trigger(); + p()->buff.chi_energy->expire(); - if ( p()->rng().roll( p()->talent.windwalker.sequenced_strikes->effectN( 1 ).percent() ) ) - p()->buff.bok_proc->increment(); // increment is used to not incur the rppm cooldown - } - } + if ( p()->buff.counterstrike->up() ) + p()->proc.counterstrike_sck->occur(); + } +}; - monk_melee_attack_t::execute(); +// ========================================================================== +// Fists of Fury +// ========================================================================== - timespan_t buff_duration = composite_dot_duration( execute_state ); +struct fists_of_fury_tick_t : public monk_melee_attack_t +{ + fists_of_fury_tick_t( monk_t *p, util::string_view name ) + : monk_melee_attack_t( p, name, p->passives.fists_of_fury_tick ) + { + background = true; + aoe = -1; + reduced_aoe_targets = p->talent.windwalker.fists_of_fury->effectN( 1 ).base_value(); + full_amount_targets = 1; + ww_mastery = true; - p()->buff.spinning_crane_kick->trigger( 1, buff_t::DEFAULT_VALUE(), 1.0, buff_duration ); + base_costs[ RESOURCE_CHI ] = 0; + dot_duration = timespan_t::zero(); + trigger_gcd = timespan_t::zero(); - if ( chi_x && p()->buff.chi_energy->up() ) - chi_x->execute(); + parse_effects( p->buff.momentum_boost_damage ); + } - if ( p()->buff.celestial_flames->up() ) - p()->active_actions.breath_of_fire->execute_on_target( execute_state->target ); + double composite_target_multiplier( player_t *target ) const override + { + double m = monk_melee_attack_t::composite_target_multiplier( target ); - if ( p()->talent.windwalker.transfer_the_power->ok() ) - p()->buff.transfer_the_power->trigger(); + if ( target != p()->target ) + m *= p()->talent.windwalker.fists_of_fury->effectN( 6 ).percent(); - p()->buff.tigers_ferocity->trigger(); - } + return m; + } - void last_tick( dot_t *dot ) override - { - monk_melee_attack_t::last_tick( dot ); + double action_multiplier() const override + { + double am = monk_melee_attack_t::action_multiplier(); - p()->buff.dance_of_chiji_hidden->expire(); + am *= 1 + p()->buff.transfer_the_power->check_stack_value(); - p()->buff.chi_energy->expire(); + if ( p()->talent.windwalker.momentum_boost.ok() ) + am *= 1 + ( ( ( 1.0 / p()->composite_melee_haste() ) - 1.0 ) * + p()->talent.windwalker.momentum_boost->effectN( 1 ).percent() ); - if ( p()->buff.counterstrike->up() ) - p()->proc.counterstrike_sck->occur(); - } - }; + return am; + } + + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - // ========================================================================== - // Fists of Fury - // ========================================================================== + p()->buff.chi_energy->trigger(); + p()->buff.momentum_boost_damage->trigger(); + } +}; - struct fists_of_fury_tick_t : public monk_melee_attack_t +struct fists_of_fury_t : public monk_melee_attack_t +{ + fists_of_fury_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "fists_of_fury", p->talent.windwalker.fists_of_fury ) { - fists_of_fury_tick_t( monk_t *p, util::string_view name ) - : monk_melee_attack_t( p, name, p->passives.fists_of_fury_tick ) - { - background = true; - aoe = -1; - reduced_aoe_targets = p->talent.windwalker.fists_of_fury->effectN( 1 ).base_value(); - full_amount_targets = 1; - ww_mastery = true; + parse_options( options_str ); - base_costs[ RESOURCE_CHI ] = 0; - dot_duration = timespan_t::zero(); - trigger_gcd = timespan_t::zero(); + cooldown = p->cooldown.fists_of_fury; + sef_ability = actions::sef_ability_e::SEF_FISTS_OF_FURY; + may_combo_strike = true; - parse_effects( p->buff.momentum_boost_damage ); - } + channeled = tick_zero = true; + interrupt_auto_attack = true; - double composite_target_multiplier( player_t *target ) const override - { - double m = monk_melee_attack_t::composite_target_multiplier( target ); + attack_power_mod.direct = 0; + weapon_power_mod = 0; - if ( target != p()->target ) - m *= p()->talent.windwalker.fists_of_fury->effectN( 6 ).percent(); + may_crit = may_miss = may_block = may_dodge = may_parry = callbacks = false; - return m; - } + // Effect 1 shows a period of 166 milliseconds which appears to refer to the visual and not the tick period + base_tick_time = dot_duration / 4; - double action_multiplier() const override - { - double am = monk_melee_attack_t::action_multiplier(); + ability_lag = p->world_lag; - am *= 1 + p()->buff.transfer_the_power->check_stack_value(); + tick_action = new fists_of_fury_tick_t( p, "fists_of_fury_tick" ); + tick_action->stats = stats; + } - if ( p()->talent.windwalker.momentum_boost.ok() ) - am *= 1 + ( ( ( 1.0 / p()->composite_melee_haste() ) - 1.0 ) * - p()->talent.windwalker.momentum_boost->effectN( 1 ).percent() ); + bool usable_moving() const override + { + return true; + } - return am; - } + void execute() override + { + monk_melee_attack_t::execute(); - void impact( action_state_t *s ) override + if ( p()->buff.fury_of_xuen_stacks->up() && rng().roll( p()->buff.fury_of_xuen_stacks->stack_value() ) ) { - monk_melee_attack_t::impact( s ); - - p()->buff.chi_energy->trigger(); - p()->buff.momentum_boost_damage->trigger(); + p()->buff.fury_of_xuen_stacks->expire(); + p()->buff.fury_of_xuen->trigger(); + p()->active_actions.fury_of_xuen_summon->execute(); } - }; - struct fists_of_fury_t : public monk_melee_attack_t - { - fists_of_fury_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "fists_of_fury", p->talent.windwalker.fists_of_fury ) - { - parse_options( options_str ); + p()->buff.whirling_dragon_punch->trigger(); - cooldown = p->cooldown.fists_of_fury; - sef_ability = actions::sef_ability_e::SEF_FISTS_OF_FURY; - may_combo_strike = true; + p()->buff.tigers_ferocity->trigger(); + } - channeled = tick_zero = true; - interrupt_auto_attack = true; + void last_tick( dot_t *dot ) override + { + monk_melee_attack_t::last_tick( dot ); - attack_power_mod.direct = 0; - weapon_power_mod = 0; + // Delay the expiration of the buffs until after the tick action happens. + // Otherwise things trigger before the tick action happens; which is not intended. + make_event( p()->sim, timespan_t::from_millis( 1 ), [ & ] { + p()->buff.transfer_the_power->expire(); + p()->buff.pressure_point->trigger(); + p()->buff.momentum_boost_damage->expire(); + p()->buff.momentum_boost_speed->trigger(); + } ); + } +}; - may_crit = may_miss = may_block = may_dodge = may_parry = callbacks = false; +// ========================================================================== +// Whirling Dragon Punch +// ========================================================================== - // Effect 1 shows a period of 166 milliseconds which appears to refer to the visual and not the tick period - base_tick_time = dot_duration / 4; +struct whirling_dragon_punch_aoe_tick_t : public monk_melee_attack_t +{ + timespan_t delay; + whirling_dragon_punch_aoe_tick_t( util::string_view name, monk_t *p, const spell_data_t *s, timespan_t delay ) + : monk_melee_attack_t( p, name, s ), delay( delay ) + { + ww_mastery = true; - ability_lag = p->world_lag; + background = true; + aoe = -1; + reduced_aoe_targets = p->talent.windwalker.whirling_dragon_punch->effectN( 1 ).base_value(); - tick_action = new fists_of_fury_tick_t( p, "fists_of_fury_tick" ); - tick_action->stats = stats; - } + name_str_reporting = "wdp_aoe"; + } - bool usable_moving() const override - { - return true; - } + double action_multiplier() const override + { + double am = monk_melee_attack_t::action_multiplier(); - void execute() override - { - monk_melee_attack_t::execute(); + am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); - if ( p()->buff.fury_of_xuen_stacks->up() && rng().roll( p()->buff.fury_of_xuen_stacks->stack_value() ) ) - { - p()->buff.fury_of_xuen_stacks->expire(); - p()->buff.fury_of_xuen->trigger(); - p()->active_actions.fury_of_xuen_summon->execute(); - } + return am; + } +}; - p()->buff.whirling_dragon_punch->trigger(); +struct whirling_dragon_punch_st_tick_t : public monk_melee_attack_t +{ + whirling_dragon_punch_st_tick_t( util::string_view name, monk_t *p, const spell_data_t *s ) + : monk_melee_attack_t( p, name, s ) + { + ww_mastery = true; - p()->buff.tigers_ferocity->trigger(); - } + background = true; - void last_tick( dot_t *dot ) override - { - monk_melee_attack_t::last_tick( dot ); + name_str_reporting = "wdp_st"; + } - // Delay the expiration of the buffs until after the tick action happens. - // Otherwise things trigger before the tick action happens; which is not intended. - make_event( p()->sim, timespan_t::from_millis( 1 ), [ & ] { - p()->buff.transfer_the_power->expire(); - p()->buff.pressure_point->trigger(); - p()->buff.momentum_boost_damage->expire(); - p()->buff.momentum_boost_speed->trigger(); - } ); - } - }; + double action_multiplier() const override + { + double am = monk_melee_attack_t::action_multiplier(); - // ========================================================================== - // Whirling Dragon Punch - // ========================================================================== + am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); + + return am; + } +}; - struct whirling_dragon_punch_aoe_tick_t : public monk_melee_attack_t +struct whirling_dragon_punch_t : public monk_melee_attack_t +{ + struct whirling_dragon_punch_state_t : public action_state_t { - timespan_t delay; - whirling_dragon_punch_aoe_tick_t( util::string_view name, monk_t *p, const spell_data_t *s, timespan_t delay ) - : monk_melee_attack_t( p, name, s ), delay( delay ) + whirling_dragon_punch_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) { - ww_mastery = true; - - background = true; - aoe = -1; - reduced_aoe_targets = p->talent.windwalker.whirling_dragon_punch->effectN( 1 ).base_value(); - - name_str_reporting = "wdp_aoe"; } - double action_multiplier() const override + proc_types2 cast_proc_type2() const override { - double am = monk_melee_attack_t::action_multiplier(); - - am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); - - return am; + // Whirling Dragon Punch seems to trigger Bron's Call to Action (and possibly other + // effects that care about casts). + return PROC2_CAST_GENERIC; } }; - struct whirling_dragon_punch_st_tick_t : public monk_melee_attack_t - { - whirling_dragon_punch_st_tick_t( util::string_view name, monk_t *p, const spell_data_t *s ) - : monk_melee_attack_t( p, name, s ) - { - ww_mastery = true; + std::array aoe_ticks; + whirling_dragon_punch_st_tick_t *st_tick; - background = true; + struct whirling_dragon_punch_tick_event_t : public event_t + { + whirling_dragon_punch_aoe_tick_t *tick; - name_str_reporting = "wdp_st"; + whirling_dragon_punch_tick_event_t( whirling_dragon_punch_aoe_tick_t *tick, timespan_t delay ) + : event_t( *tick->player, delay ), tick( tick ) + { } - double action_multiplier() const override + void execute() override { - double am = monk_melee_attack_t::action_multiplier(); - - am *= 1 + p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 2 ).percent(); - - return am; + tick->execute(); } }; - struct whirling_dragon_punch_t : public monk_melee_attack_t + whirling_dragon_punch_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "whirling_dragon_punch", p->talent.windwalker.whirling_dragon_punch ) { - struct whirling_dragon_punch_state_t : public action_state_t - { - whirling_dragon_punch_state_t( action_t *a, player_t *target ) : action_state_t( a, target ) - { - } + sef_ability = actions::sef_ability_e::SEF_WHIRLING_DRAGON_PUNCH; - proc_types2 cast_proc_type2() const override - { - // Whirling Dragon Punch seems to trigger Bron's Call to Action (and possibly other - // effects that care about casts). - return PROC2_CAST_GENERIC; - } - }; + parse_options( options_str ); + interrupt_auto_attack = false; + channeled = false; + may_combo_strike = true; + cast_during_sck = false; - std::array aoe_ticks; - whirling_dragon_punch_st_tick_t *st_tick; + spell_power_mod.direct = 0.0; - struct whirling_dragon_punch_tick_event_t : public event_t + // 3 server-side hardcoded ticks + for ( size_t i = 0; i < aoe_ticks.size(); ++i ) { - whirling_dragon_punch_aoe_tick_t *tick; - - whirling_dragon_punch_tick_event_t( whirling_dragon_punch_aoe_tick_t *tick, timespan_t delay ) - : event_t( *tick->player, delay ), tick( tick ) - { - } + auto delay = base_tick_time * i; + aoe_ticks[ i ] = new whirling_dragon_punch_aoe_tick_t( "whirling_dragon_punch_aoe_tick", p, + p->passives.whirling_dragon_punch_aoe_tick, delay ); - void execute() override - { - tick->execute(); - } - }; + add_child( aoe_ticks[ i ] ); + } - whirling_dragon_punch_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "whirling_dragon_punch", p->talent.windwalker.whirling_dragon_punch ) - { - sef_ability = actions::sef_ability_e::SEF_WHIRLING_DRAGON_PUNCH; + st_tick = new whirling_dragon_punch_st_tick_t( "whirling_dragon_punch_st_tick", p, + p->passives.whirling_dragon_punch_st_tick ); + add_child( st_tick ); - parse_options( options_str ); - interrupt_auto_attack = false; - channeled = false; - may_combo_strike = true; - cast_during_sck = false; + apply_affecting_aura( p->talent.windwalker.revolving_whirl ); + } - spell_power_mod.direct = 0.0; + action_state_t *new_state() override + { + return new whirling_dragon_punch_state_t( this, p()->target ); + } - // 3 server-side hardcoded ticks - for ( size_t i = 0; i < aoe_ticks.size(); ++i ) - { - auto delay = base_tick_time * i; - aoe_ticks[ i ] = new whirling_dragon_punch_aoe_tick_t( "whirling_dragon_punch_aoe_tick", p, - p->passives.whirling_dragon_punch_aoe_tick, delay ); + void execute() override + { + monk_melee_attack_t::execute(); - add_child( aoe_ticks[ i ] ); - } + p()->movement.whirling_dragon_punch->trigger(); - st_tick = new whirling_dragon_punch_st_tick_t( "whirling_dragon_punch_st_tick", p, - p->passives.whirling_dragon_punch_st_tick ); - add_child( st_tick ); + for ( auto &tick : aoe_ticks ) + make_event( *sim, tick, tick->delay ); - apply_affecting_aura( p->talent.windwalker.revolving_whirl ); - } + st_tick->execute(); - action_state_t *new_state() override + if ( p()->talent.windwalker.knowledge_of_the_broken_temple->ok() && + p()->talent.windwalker.teachings_of_the_monastery->ok() ) { - return new whirling_dragon_punch_state_t( this, p()->target ); + int stacks = as( p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 1 ).base_value() ); + p()->buff.teachings_of_the_monastery->trigger( stacks ); } - void execute() override - { - monk_melee_attack_t::execute(); + // TODO: Check if this can proc without being talented into DoCJ + if ( p()->talent.windwalker.dance_of_chiji->ok() && + p()->rng().roll( p()->talent.windwalker.revolving_whirl->effectN( 1 ).percent() ) ) + p()->buff.dance_of_chiji->increment(); // increment is used to not incur the rppm cooldown - p()->movement.whirling_dragon_punch->trigger(); + p()->buff.tigers_ferocity->trigger(); + } - for ( auto &tick : aoe_ticks ) - make_event( *sim, tick, tick->delay ); + bool ready() override + { + // Only usable while Fists of Fury and Rising Sun Kick are on cooldown. + if ( p()->buff.whirling_dragon_punch->up() ) + return monk_melee_attack_t::ready(); - st_tick->execute(); + return false; + } +}; - if ( p()->talent.windwalker.knowledge_of_the_broken_temple->ok() && - p()->talent.windwalker.teachings_of_the_monastery->ok() ) - { - int stacks = as( p()->talent.windwalker.knowledge_of_the_broken_temple->effectN( 1 ).base_value() ); - p()->buff.teachings_of_the_monastery->trigger( stacks ); - } +// ========================================================================== +// Strike of the Windlord +// ========================================================================== +// Off hand hits first followed by main hand +// The ability does NOT require an off-hand weapon to be executed. +// The ability uses the main-hand weapon damage for both attacks - // TODO: Check if this can proc without being talented into DoCJ - if ( p()->talent.windwalker.dance_of_chiji->ok() && - p()->rng().roll( p()->talent.windwalker.revolving_whirl->effectN( 1 ).percent() ) ) - p()->buff.dance_of_chiji->increment(); // increment is used to not incur the rppm cooldown +struct strike_of_the_windlord_main_hand_t : public monk_melee_attack_t +{ + strike_of_the_windlord_main_hand_t( monk_t *p, const char *name, const spell_data_t *s ) + : monk_melee_attack_t( p, name, s ) + { + sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD; - p()->buff.tigers_ferocity->trigger(); - } + ww_mastery = true; + ap_type = attack_power_type::WEAPON_MAINHAND; - bool ready() override - { - // Only usable while Fists of Fury and Rising Sun Kick are on cooldown. - if ( p()->buff.whirling_dragon_punch->up() ) - return monk_melee_attack_t::ready(); + aoe = -1; + may_dodge = may_parry = may_block = may_miss = true; + dual = background = true; + } - return false; + // Damage must be divided on non-main target by the number of targets + double composite_aoe_multiplier( const action_state_t *state ) const override + { + if ( state->target != target ) + { + return 1.0 / state->n_targets; } - }; - // ========================================================================== - // Strike of the Windlord - // ========================================================================== - // Off hand hits first followed by main hand - // The ability does NOT require an off-hand weapon to be executed. - // The ability uses the main-hand weapon damage for both attacks + return 1.0; + } - struct strike_of_the_windlord_main_hand_t : public monk_melee_attack_t + double action_multiplier() const override { - strike_of_the_windlord_main_hand_t( monk_t *p, const char *name, const spell_data_t *s ) - : monk_melee_attack_t( p, name, s ) - { - sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD; + double am = monk_melee_attack_t::action_multiplier(); - ww_mastery = true; - ap_type = attack_power_type::WEAPON_MAINHAND; + am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); - aoe = -1; - may_dodge = may_parry = may_block = may_miss = true; - dual = background = true; - } + return am; + } +}; - // Damage must be divided on non-main target by the number of targets - double composite_aoe_multiplier( const action_state_t *state ) const override - { - if ( state->target != target ) - { - return 1.0 / state->n_targets; - } +struct strike_of_the_windlord_off_hand_t : public monk_melee_attack_t +{ + strike_of_the_windlord_off_hand_t( monk_t *p, const char *name, const spell_data_t *s ) + : monk_melee_attack_t( p, name, s ) + { + sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD_OH; + ww_mastery = true; + ap_type = attack_power_type::WEAPON_OFFHAND; - return 1.0; - } + aoe = -1; + may_dodge = may_parry = may_block = may_miss = true; + dual = background = true; + } - double action_multiplier() const override + // Damage must be divided on non-main target by the number of targets + double composite_aoe_multiplier( const action_state_t *state ) const override + { + if ( state->target != target ) { - double am = monk_melee_attack_t::action_multiplier(); - - am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); - - return am; + return 1.0 / state->n_targets; } - }; - struct strike_of_the_windlord_off_hand_t : public monk_melee_attack_t + return 1.0; + } + + double action_multiplier() const override { - strike_of_the_windlord_off_hand_t( monk_t *p, const char *name, const spell_data_t *s ) - : monk_melee_attack_t( p, name, s ) - { - sef_ability = actions::sef_ability_e::SEF_STRIKE_OF_THE_WINDLORD_OH; - ww_mastery = true; - ap_type = attack_power_type::WEAPON_OFFHAND; + double am = monk_melee_attack_t::action_multiplier(); - aoe = -1; - may_dodge = may_parry = may_block = may_miss = true; - dual = background = true; - } + am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); - // Damage must be divided on non-main target by the number of targets - double composite_aoe_multiplier( const action_state_t *state ) const override - { - if ( state->target != target ) - { - return 1.0 / state->n_targets; - } + return am; + } - return 1.0; - } + void impact( action_state_t *s ) override + { + monk_melee_attack_t::impact( s ); - double action_multiplier() const override + if ( p()->talent.windwalker.thunderfist.ok() ) { - double am = monk_melee_attack_t::action_multiplier(); + int thunderfist_stacks = 1; - am *= 1 + p()->talent.windwalker.communion_with_wind->effectN( 2 ).percent(); + if ( s->chain_target == 0 ) + thunderfist_stacks += as( p()->talent.windwalker.thunderfist->effectN( 1 ).base_value() ); - return am; + p()->buff.thunderfist->trigger( thunderfist_stacks ); } - void impact( action_state_t *s ) override - { - monk_melee_attack_t::impact( s ); + if ( p()->talent.windwalker.rushing_jade_wind.ok() ) + p()->trigger_mark_of_the_crane( s ); - if ( p()->talent.windwalker.thunderfist.ok() ) - { - int thunderfist_stacks = 1; + if ( p()->talent.windwalker.gale_force.ok() ) + get_td( s->target )->debuff.gale_force->trigger(); + } +}; - if ( s->chain_target == 0 ) - thunderfist_stacks += as( p()->talent.windwalker.thunderfist->effectN( 1 ).base_value() ); +struct strike_of_the_windlord_t : public monk_melee_attack_t +{ + strike_of_the_windlord_main_hand_t *mh_attack; + strike_of_the_windlord_off_hand_t *oh_attack; - p()->buff.thunderfist->trigger( thunderfist_stacks ); - } + strike_of_the_windlord_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "strike_of_the_windlord", p->talent.windwalker.strike_of_the_windlord ), + mh_attack( nullptr ), + oh_attack( nullptr ) + { + apply_affecting_effect( p->talent.windwalker.communion_with_wind->effectN( 1 ) ); - if ( p()->talent.windwalker.rushing_jade_wind.ok() ) - p()->trigger_mark_of_the_crane( s ); + may_combo_strike = true; + cast_during_sck = false; + cooldown->hasted = false; + trigger_gcd = data().gcd(); - if ( p()->talent.windwalker.gale_force.ok() ) - get_td( s->target )->debuff.gale_force->trigger(); - } - }; + parse_options( options_str ); - struct strike_of_the_windlord_t : public monk_melee_attack_t - { - strike_of_the_windlord_main_hand_t *mh_attack; - strike_of_the_windlord_off_hand_t *oh_attack; + oh_attack = + new strike_of_the_windlord_off_hand_t( p, "strike_of_the_windlord_offhand", data().effectN( 4 ).trigger() ); + mh_attack = + new strike_of_the_windlord_main_hand_t( p, "strike_of_the_windlord_mainhand", data().effectN( 3 ).trigger() ); - strike_of_the_windlord_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "strike_of_the_windlord", p->talent.windwalker.strike_of_the_windlord ), - mh_attack( nullptr ), - oh_attack( nullptr ) - { - apply_affecting_effect( p->talent.windwalker.communion_with_wind->effectN( 1 ) ); + add_child( oh_attack ); + add_child( mh_attack ); - may_combo_strike = true; - cast_during_sck = false; - cooldown->hasted = false; - trigger_gcd = data().gcd(); + if ( p->talent.windwalker.thunderfist.ok() ) + add_child( p->passive_actions.thunderfist ); + } - parse_options( options_str ); + void execute() override + { + monk_melee_attack_t::execute(); - oh_attack = - new strike_of_the_windlord_off_hand_t( p, "strike_of_the_windlord_offhand", data().effectN( 4 ).trigger() ); - mh_attack = - new strike_of_the_windlord_main_hand_t( p, "strike_of_the_windlord_mainhand", data().effectN( 3 ).trigger() ); + // Off-hand attack hits first + oh_attack->execute(); - add_child( oh_attack ); - add_child( mh_attack ); + if ( result_is_hit( oh_attack->execute_state->result ) ) + mh_attack->execute(); - if ( p->talent.windwalker.thunderfist.ok() ) - add_child( p->passive_actions.thunderfist ); + if ( p()->talent.windwalker.rushing_jade_wind.ok() ) + { + p()->buff.rushing_jade_wind->trigger(); + if ( p()->bugs ) + combo_strikes_trigger(); } - void execute() override + p()->buff.tigers_ferocity->trigger(); + + if ( p()->talent.windwalker.darting_hurricane.ok() ) + p()->buff.darting_hurricane->increment( + as( p()->talent.windwalker.darting_hurricane->effectN( 2 ) + .base_value() ) ); // increment is used to not incur the rppm cooldown + + if ( p()->buff.heart_of_the_jade_serpent->up() ) { - monk_melee_attack_t::execute(); + p()->buff.heart_of_the_jade_serpent_cdr->trigger(); + p()->buff.inner_compass_serpent_stance->trigger(); + p()->buff.heart_of_the_jade_serpent->decrement(); + } + } +}; - // Off-hand attack hits first - oh_attack->execute(); +// ========================================================================== +// Thunderfist +// ========================================================================== - if ( result_is_hit( oh_attack->execute_state->result ) ) - mh_attack->execute(); +struct thunderfist_t : public monk_spell_t +{ + thunderfist_t( monk_t *player ) + : monk_spell_t( player, "thunderfist", player->passives.thunderfist->effectN( 1 ).trigger() ) + { + background = true; + may_crit = true; + } - if ( p()->talent.windwalker.rushing_jade_wind.ok() ) - { - p()->buff.rushing_jade_wind->trigger(); - if ( p()->bugs ) - combo_strikes_trigger(); - } + virtual void execute() override + { + monk_spell_t::execute(); - p()->buff.tigers_ferocity->trigger(); + p()->buff.thunderfist->decrement( 1 ); + } +}; - if ( p()->talent.windwalker.darting_hurricane.ok() ) - p()->buff.darting_hurricane->increment( - as( p()->talent.windwalker.darting_hurricane->effectN( 2 ) - .base_value() ) ); // increment is used to not incur the rppm cooldown +// ========================================================================== +// Melee +// ========================================================================== - if ( p()->buff.heart_of_the_jade_serpent->up() ) - { - p()->buff.heart_of_the_jade_serpent_cdr->trigger(); - p()->buff.inner_compass_serpent_stance->trigger(); - p()->buff.heart_of_the_jade_serpent->decrement(); - } - } - }; +struct press_the_advantage_melee_t : public monk_spell_t +{ + press_the_advantage_melee_t( monk_t *player ) + : monk_spell_t( player, "press_the_advantage", player->find_spell( 418360 ) ) + { + background = true; + + if ( p()->talent.brewmaster.press_the_advantage->ok() && p()->talent.brewmaster.chi_surge->ok() ) + add_child( p()->active_actions.chi_surge ); + } +}; - // ========================================================================== - // Thunderfist - // ========================================================================== +struct melee_t : public monk_melee_attack_t +{ + int sync_weapons; + bool dual_threat_enabled = true; // Dual Threat requires one succesful melee inbetween casts + bool first; + bool oh; - struct thunderfist_t : public monk_spell_t + melee_t( util::string_view name, monk_t *player, int sw, bool is_oh = false ) + : monk_melee_attack_t( player, name ), sync_weapons( sw ), first( true ), oh( is_oh ) { - thunderfist_t( monk_t *player ) - : monk_spell_t( player, "thunderfist", player->passives.thunderfist->effectN( 1 ).trigger() ) - { - background = true; - may_crit = true; - } + background = repeating = may_glance = true; + may_crit = true; + trigger_gcd = timespan_t::zero(); + special = false; + school = SCHOOL_PHYSICAL; + weapon_multiplier = 1.0; + allow_class_ability_procs = true; + not_a_proc = true; - virtual void execute() override - { - monk_spell_t::execute(); + monk_melee_attack_t::apply_buff_effects(); + monk_melee_attack_t::apply_debuff_effects(); - p()->buff.thunderfist->decrement( 1 ); + if ( player->main_hand_weapon.group() == WEAPON_1H ) + { + if ( player->specialization() != MONK_MISTWEAVER ) + base_hit -= 0.19; } - }; + } - // ========================================================================== - // Melee - // ========================================================================== + void reset() override + { + monk_melee_attack_t::reset(); + first = true; + } - struct press_the_advantage_melee_t : public monk_spell_t + timespan_t execute_time() const override { - press_the_advantage_melee_t( monk_t *player ) - : monk_spell_t( player, "press_the_advantage", player->find_spell( 418360 ) ) - { - background = true; + timespan_t t = monk_melee_attack_t::execute_time(); - if ( p()->talent.brewmaster.press_the_advantage->ok() && p()->talent.brewmaster.chi_surge->ok() ) - add_child( p()->active_actions.chi_surge ); - } - }; + if ( first ) + return ( weapon->slot == SLOT_OFF_HAND ) ? ( sync_weapons ? std::min( t / 2, timespan_t::zero() ) : t / 2 ) + : timespan_t::zero(); + else + return t; + } - struct melee_t : public monk_melee_attack_t + void execute() override { - int sync_weapons; - bool dual_threat_enabled = true; // Dual Threat requires one succesful melee inbetween casts - bool first; - bool oh; + first = false; + monk_melee_attack_t::execute(); + } - melee_t( util::string_view name, monk_t *player, int sw, bool is_oh = false ) - : monk_melee_attack_t( player, name ), sync_weapons( sw ), first( true ), oh( is_oh ) + void impact( action_state_t *s ) override + { + if ( dual_threat_enabled && p()->rng().roll( p()->talent.windwalker.dual_threat->effectN( 1 ).percent() ) ) + { + s->result_total = 0; + p()->dual_threat_kick->execute(); + dual_threat_enabled = false; + } + else { - background = repeating = may_glance = true; - may_crit = true; - trigger_gcd = timespan_t::zero(); - special = false; - school = SCHOOL_PHYSICAL; - weapon_multiplier = 1.0; - allow_class_ability_procs = true; - not_a_proc = true; + monk_melee_attack_t::impact( s ); - monk_melee_attack_t::apply_buff_effects(); - monk_melee_attack_t::apply_debuff_effects(); + if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) + p()->buff.press_the_advantage->trigger(); - if ( player->main_hand_weapon.group() == WEAPON_1H ) + if ( result_is_hit( s->result ) ) { - if ( player->specialization() != MONK_MISTWEAVER ) - base_hit -= 0.19; + if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) + { + // Reduce Brew cooldown by 0.5 seconds + p()->baseline.brewmaster.brews.adjust( + p()->talent.brewmaster.press_the_advantage->effectN( 1 ).time_value() ); + + // Trigger the Press the Advantage damage proc + p()->passive_actions.press_the_advantage->target = s->target; + p()->passive_actions.press_the_advantage->schedule_execute(); + } + + if ( p()->buff.thunderfist->up() ) + p()->passive_actions.thunderfist->execute_on_target( s->target ); + + dual_threat_enabled = true; } } + } +}; - void reset() override - { - monk_melee_attack_t::reset(); - first = true; - } +// ========================================================================== +// Auto Attack +// ========================================================================== - timespan_t execute_time() const override - { - timespan_t t = monk_melee_attack_t::execute_time(); +// Dual Threat WW Talent - if ( first ) - return ( weapon->slot == SLOT_OFF_HAND ) ? ( sync_weapons ? std::min( t / 2, timespan_t::zero() ) : t / 2 ) - : timespan_t::zero(); - else - return t; - } +struct dual_threat_t : public monk_melee_attack_t +{ + dual_threat_t( monk_t *p ) : monk_melee_attack_t( p, "dual_threat_kick", p->passives.dual_threat_kick ) + { + background = true; + may_glance = true; + may_crit = true; // I assume so? This ability doesn't appear in the combat log yet on alpha - void execute() override - { - first = false; - monk_melee_attack_t::execute(); - } + allow_class_ability_procs = false; // Is not proccing Thunderfist or other class ability procs - void impact( action_state_t *s ) override - { - if ( dual_threat_enabled && p()->rng().roll( p()->talent.windwalker.dual_threat->effectN( 1 ).percent() ) ) - { - s->result_total = 0; - p()->dual_threat_kick->execute(); - dual_threat_enabled = false; - } - else - { - monk_melee_attack_t::impact( s ); + school = SCHOOL_PHYSICAL; + weapon_multiplier = 1.0; + weapon = &( player->main_hand_weapon ); + + cooldown->duration = base_execute_time = trigger_gcd = timespan_t::zero(); + } + + void execute() override + { + monk_melee_attack_t::execute(); - if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) - p()->buff.press_the_advantage->trigger(); + p()->buff.dual_threat->trigger(); + } +}; - if ( result_is_hit( s->result ) ) - { - if ( p()->talent.brewmaster.press_the_advantage->ok() && weapon->slot == SLOT_MAIN_HAND ) - { - // Reduce Brew cooldown by 0.5 seconds - p()->baseline.brewmaster.brews.adjust( - p()->talent.brewmaster.press_the_advantage->effectN( 1 ).time_value() ); +struct auto_attack_t : public monk_melee_attack_t +{ + int sync_weapons; - // Trigger the Press the Advantage damage proc - p()->passive_actions.press_the_advantage->target = s->target; - p()->passive_actions.press_the_advantage->schedule_execute(); - } + dual_threat_t *dual_threat_kick; - if ( p()->buff.thunderfist->up() ) - p()->passive_actions.thunderfist->execute_on_target( s->target ); + auto_attack_t( monk_t *player, util::string_view options_str ) + : monk_melee_attack_t( player, "auto_attack" ), sync_weapons( 0 ) + { + add_option( opt_bool( "sync_weapons", sync_weapons ) ); + parse_options( options_str ); - dual_threat_enabled = true; - } - } - } - }; + ignore_false_positive = true; + trigger_gcd = timespan_t::zero(); + // background = true; - // ========================================================================== - // Auto Attack - // ========================================================================== + p()->main_hand_attack = new melee_t( "melee_main_hand", player, sync_weapons ); + p()->main_hand_attack->weapon = &( player->main_hand_weapon ); + p()->main_hand_attack->base_execute_time = player->main_hand_weapon.swing_time; - // Dual Threat WW Talent + add_child( p()->main_hand_attack ); - struct dual_threat_t : public monk_melee_attack_t - { - dual_threat_t( monk_t *p ) : monk_melee_attack_t( p, "dual_threat_kick", p->passives.dual_threat_kick ) + if ( player->off_hand_weapon.type != WEAPON_NONE ) { - background = true; - may_glance = true; - may_crit = true; // I assume so? This ability doesn't appear in the combat log yet on alpha - - allow_class_ability_procs = false; // Is not proccing Thunderfist or other class ability procs + if ( !player->dual_wield() ) + return; - school = SCHOOL_PHYSICAL; - weapon_multiplier = 1.0; - weapon = &( player->main_hand_weapon ); + p()->off_hand_attack = new melee_t( "melee_off_hand", player, sync_weapons, true ); + p()->off_hand_attack->weapon = &( player->off_hand_weapon ); + p()->off_hand_attack->base_execute_time = player->off_hand_weapon.swing_time; + p()->off_hand_attack->id = 1; - cooldown->duration = base_execute_time = trigger_gcd = timespan_t::zero(); + add_child( p()->off_hand_attack ); } - void execute() override + if ( p()->talent.windwalker.dual_threat.ok() ) { - monk_melee_attack_t::execute(); + p()->dual_threat_kick = new dual_threat_t( player ); - p()->buff.dual_threat->trigger(); + add_child( p()->dual_threat_kick ); } - }; + } - struct auto_attack_t : public monk_melee_attack_t + bool ready() override { - int sync_weapons; - - dual_threat_t *dual_threat_kick; - - auto_attack_t( monk_t *player, util::string_view options_str ) - : monk_melee_attack_t( player, "auto_attack" ), sync_weapons( 0 ) - { - add_option( opt_bool( "sync_weapons", sync_weapons ) ); - parse_options( options_str ); + if ( p()->current.distance_to_move > 5 ) + return false; - ignore_false_positive = true; - trigger_gcd = timespan_t::zero(); - // background = true; + return ( p()->main_hand_attack->execute_event == nullptr || + ( p()->off_hand_attack && p()->off_hand_attack->execute_event == nullptr ) ); // not swinging + } - p()->main_hand_attack = new melee_t( "melee_main_hand", player, sync_weapons ); - p()->main_hand_attack->weapon = &( player->main_hand_weapon ); - p()->main_hand_attack->base_execute_time = player->main_hand_weapon.swing_time; + void execute() override + { + if ( player->main_hand_attack ) + p()->main_hand_attack->schedule_execute(); - add_child( p()->main_hand_attack ); + if ( player->off_hand_attack ) + p()->off_hand_attack->schedule_execute(); + } +}; - if ( player->off_hand_weapon.type != WEAPON_NONE ) - { - if ( !player->dual_wield() ) - return; +// ========================================================================== +// Keg Smash +// ========================================================================== +struct keg_smash_t : monk_melee_attack_t +{ + keg_smash_t( monk_t *player, std::string_view options_str, std::string_view name = "keg_smash" ) + : monk_melee_attack_t( player, name, player->talent.brewmaster.keg_smash ) + { + parse_options( options_str ); + // TODO: can cast_during_sck be automated? + cast_during_sck = true; - p()->off_hand_attack = new melee_t( "melee_off_hand", player, sync_weapons, true ); - p()->off_hand_attack->weapon = &( player->off_hand_weapon ); - p()->off_hand_attack->base_execute_time = player->off_hand_weapon.swing_time; - p()->off_hand_attack->id = 1; + // No auto-parsing is presently possible. + reduced_aoe_targets = data().effectN( 7 ).base_value(); + aoe = -1; - add_child( p()->off_hand_attack ); - } + apply_affecting_aura( player->talent.brewmaster.stormstouts_last_keg ); + parse_effects( player->buff.hit_scheme ); + // we have to set this up by hand, as scalding brew is scripted + if ( const auto &effect = player->talent.brewmaster.scalding_brew->effectN( 1 ); effect.ok() ) + add_parse_entry( target_multiplier_effects ) + .set_func( td_fn( &monk_td_t::dots_t::breath_of_fire ) ) + .set_value( effect.percent() ) + .set_eff( &effect ); + parse_effects( player->buff.flow_of_battle_free_keg_smash ); + } - if ( p()->talent.windwalker.dual_threat.ok() ) - { - p()->dual_threat_kick = new dual_threat_t( player ); + void execute() override + { + monk_melee_attack_t::execute(); - add_child( p()->dual_threat_kick ); - } - } + p()->buff.hit_scheme->expire(); + p()->buff.flow_of_battle_free_keg_smash->expire(); - bool ready() override + if ( p()->talent.brewmaster.salsalabims_strength->ok() ) { - if ( p()->current.distance_to_move > 5 ) - return false; - - return ( p()->main_hand_attack->execute_event == nullptr || - ( p()->off_hand_attack && p()->off_hand_attack->execute_event == nullptr ) ); // not swinging + p()->cooldown.breath_of_fire->reset( true ); + p()->proc.salsalabims_strength->occur(); } - void execute() override - { - if ( player->main_hand_attack ) - p()->main_hand_attack->schedule_execute(); + p()->buff.shuffle->trigger( timespan_t::from_seconds( data().effectN( 6 ).base_value() ) ); - if ( player->off_hand_attack ) - p()->off_hand_attack->schedule_execute(); + timespan_t reduction = timespan_t::from_seconds( data().effectN( 4 ).base_value() ); + if ( p()->buff.blackout_combo->up() ) + { + reduction += timespan_t::from_seconds( p()->buff.blackout_combo->data().effectN( 3 ).base_value() ); + p()->proc.blackout_combo_keg_smash->occur(); } - }; + p()->buff.blackout_combo->expire(); - // ========================================================================== - // Keg Smash - // ========================================================================== - struct keg_smash_t : monk_melee_attack_t + p()->baseline.brewmaster.brews.adjust( reduction ); + } + + void impact( action_state_t *state ) override { - keg_smash_t( monk_t *player, std::string_view options_str, std::string_view name = "keg_smash" ) - : monk_melee_attack_t( player, name, player->talent.brewmaster.keg_smash ) - { - parse_options( options_str ); - // TODO: can cast_during_sck be automated? - cast_during_sck = true; + monk_melee_attack_t::impact( state ); + get_td( state->target )->debuff.keg_smash->trigger(); + if ( p()->buff.weapons_of_order->up() ) + get_td( state->target )->debuff.weapons_of_order->trigger(); + } +}; - // No auto-parsing is presently possible. - reduced_aoe_targets = data().effectN( 7 ).base_value(); - aoe = -1; +// ========================================================================== +// Touch of Death +// ========================================================================== +struct touch_of_death_t : public monk_melee_attack_t +{ + touch_of_death_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "touch_of_death", p->baseline.monk.touch_of_death ) + { + ww_mastery = true; + may_crit = hasted_ticks = false; + may_combo_strike = true; + cast_during_sck = true; + parse_options( options_str ); - apply_affecting_aura( player->talent.brewmaster.stormstouts_last_keg ); - parse_effects( player->buff.hit_scheme ); - // we have to set this up by hand, as scalding brew is scripted - if ( const auto &effect = player->talent.brewmaster.scalding_brew->effectN( 1 ); effect.ok() ) - add_parse_entry( target_multiplier_effects ) - .set_func( td_fn( &monk_td_t::dots_t::breath_of_fire ) ) - .set_value( effect.percent() ) - .set_eff( &effect ); - parse_effects( player->buff.flow_of_battle_free_keg_smash ); - } + cooldown->duration = data().cooldown(); - void execute() override - { - monk_melee_attack_t::execute(); + apply_affecting_aura( p->talent.monk.fatal_touch ); + } - p()->buff.hit_scheme->expire(); - p()->buff.flow_of_battle_free_keg_smash->expire(); + void init() override + { + monk_melee_attack_t::init(); - if ( p()->talent.brewmaster.salsalabims_strength->ok() ) - { - p()->cooldown.breath_of_fire->reset( true ); - p()->proc.salsalabims_strength->occur(); - } + snapshot_flags = update_flags = 0; + } - p()->buff.shuffle->trigger( timespan_t::from_seconds( data().effectN( 6 ).base_value() ) ); + double composite_target_armor( player_t * ) const override + { + return 0; + } - timespan_t reduction = timespan_t::from_seconds( data().effectN( 4 ).base_value() ); - if ( p()->buff.blackout_combo->up() ) - { - reduction += timespan_t::from_seconds( p()->buff.blackout_combo->data().effectN( 3 ).base_value() ); - p()->proc.blackout_combo_keg_smash->occur(); - } - p()->buff.blackout_combo->expire(); + bool target_ready( player_t *target ) override + { + // Deals damage equal to 35% of your maximum health against players and stronger creatures under 15% health + // 2023-10-19 Tooltip lies and the 15% health works on all non-player targets. + if ( p()->talent.monk.improved_touch_of_death->ok() && + ( target->health_percentage() < p()->talent.monk.improved_touch_of_death->effectN( 1 ).base_value() ) ) + return monk_melee_attack_t::target_ready( target ); - p()->baseline.brewmaster.brews.adjust( reduction ); - } + // You exploit the enemy target's weakest point, instantly killing creatures if they have less health than you + // Only applicable in health based sims + if ( target->current_health() > 0 && target->current_health() <= p()->max_health() ) + return monk_melee_attack_t::target_ready( target ); - void impact( action_state_t *state ) override - { - monk_melee_attack_t::impact( state ); - get_td( state->target )->debuff.keg_smash->trigger(); - if ( p()->buff.weapons_of_order->up() ) - get_td( state->target )->debuff.weapons_of_order->trigger(); - } - }; + return false; + } - // ========================================================================== - // Touch of Death - // ========================================================================== - struct touch_of_death_t : public monk_melee_attack_t + void execute() override { - touch_of_death_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "touch_of_death", p->baseline.monk.touch_of_death ) - { - ww_mastery = true; - may_crit = hasted_ticks = false; - may_combo_strike = true; - cast_during_sck = true; - parse_options( options_str ); + monk_melee_attack_t::execute(); - cooldown->duration = data().cooldown(); + p()->buff.touch_of_death_ww->trigger(); + p()->buff.fatal_touch->trigger(); + } - apply_affecting_aura( p->talent.monk.fatal_touch ); - } + void impact( action_state_t *s ) override + { + double max_hp, amount; - void init() override + // In execute range ToD deals player's max HP + amount = max_hp = p()->max_health(); + + // Not in execute range + // or not a health-based fight style + // or a secondary target... these always get hit for the 35% from Improved Touch of Death regardless if you're + // talented into it or not + if ( s->chain_target > 0 || target->current_health() == 0 || target->current_health() > max_hp ) { - monk_melee_attack_t::init(); + amount *= p()->passives.improved_touch_of_death->effectN( 2 ).percent(); // 0.35 - snapshot_flags = update_flags = 0; - } + amount *= 1 + p()->talent.windwalker.meridian_strikes->effectN( 1 ).percent(); - double composite_target_armor( player_t * ) const override - { - return 0; + // Damage is only affected by Windwalker's Mastery + // Versatility does not affect the damage of Touch of Death. + if ( p()->buff.combo_strikes->up() ) + amount *= 1 + p()->cache.mastery_value(); } - bool target_ready( player_t *target ) override - { - // Deals damage equal to 35% of your maximum health against players and stronger creatures under 15% health - // 2023-10-19 Tooltip lies and the 15% health works on all non-player targets. - if ( p()->talent.monk.improved_touch_of_death->ok() && - ( target->health_percentage() < p()->talent.monk.improved_touch_of_death->effectN( 1 ).base_value() ) ) - return monk_melee_attack_t::target_ready( target ); - - // You exploit the enemy target's weakest point, instantly killing creatures if they have less health than you - // Only applicable in health based sims - if ( target->current_health() > 0 && target->current_health() <= p()->max_health() ) - return monk_melee_attack_t::target_ready( target ); + s->result_total = s->result_raw = amount; - return false; - } + monk_melee_attack_t::impact( s ); - void execute() override + if ( p()->baseline.brewmaster.stagger->ok() ) { - monk_melee_attack_t::execute(); - - p()->buff.touch_of_death_ww->trigger(); - p()->buff.fatal_touch->trigger(); + p()->find_stagger( "Stagger" ) + ->purify_flat( amount * p()->baseline.brewmaster.touch_of_death_rank_3->effectN( 1 ).percent(), + "touch_of_death" ); } + } +}; - void impact( action_state_t *s ) override - { - double max_hp, amount; +// ========================================================================== +// Touch of Karma +// ========================================================================== +// When Touch of Karma (ToK) is activated, two spells are placed. A buff on the player (id: 125174), and a +// debuff on the target (id: 122470). Whenever the player takes damage, a dot (id: 124280) is placed on +// the target that increases as the player takes damage. Each time the player takes damage, the dot is refreshed +// and recalculates the dot size based on the current dot size. Just to make it easier to code, I'll wait until +// the Touch of Karma buff expires before placing a dot on the target. Net result should be the same. - // In execute range ToD deals player's max HP - amount = max_hp = p()->max_health(); +// 8.1 Good Karma - If the player still has the ToK buff on them, each time the target hits the player, the amount +// absorbed is immediatly healed by the Good Karma spell (id: 285594) - // Not in execute range - // or not a health-based fight style - // or a secondary target... these always get hit for the 35% from Improved Touch of Death regardless if you're - // talented into it or not - if ( s->chain_target > 0 || target->current_health() == 0 || target->current_health() > max_hp ) - { - amount *= p()->passives.improved_touch_of_death->effectN( 2 ).percent(); // 0.35 +struct touch_of_karma_dot_t : public residual_action::residual_periodic_action_t +{ + using base_t = residual_action::residual_periodic_action_t; + touch_of_karma_dot_t( monk_t *p ) : base_t( "touch_of_karma", p, p->passives.touch_of_karma_tick ) + { + may_miss = may_crit = false; + dual = true; + proc = true; + ap_type = attack_power_type::NO_WEAPON; + } - amount *= 1 + p()->talent.windwalker.meridian_strikes->effectN( 1 ).percent(); + // Need to disable multipliers in init() so that it doesn't double-dip on anything + void init() override + { + base_t::init(); + // disable the snapshot_flags for all multipliers + snapshot_flags = update_flags = 0; + snapshot_flags |= STATE_VERSATILITY; + } +}; - // Damage is only affected by Windwalker's Mastery - // Versatility does not affect the damage of Touch of Death. - if ( p()->buff.combo_strikes->up() ) - amount *= 1 + p()->cache.mastery_value(); - } +struct touch_of_karma_t : public monk_melee_attack_t +{ + double interval; + double interval_stddev; + double interval_stddev_opt; + double pct_health; + touch_of_karma_dot_t *touch_of_karma_dot; + touch_of_karma_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "touch_of_karma", p->baseline.windwalker.touch_of_karma ), + interval( 100 ), + interval_stddev( 0.05 ), + interval_stddev_opt( 0 ), + pct_health( 0.5 ), + touch_of_karma_dot( new touch_of_karma_dot_t( p ) ) + { + add_option( opt_float( "interval", interval ) ); + add_option( opt_float( "interval_stddev", interval_stddev_opt ) ); + add_option( opt_float( "pct_health", pct_health ) ); + parse_options( options_str ); - s->result_total = s->result_raw = amount; + cooldown->duration = data().cooldown(); + base_dd_min = base_dd_max = 0; + ap_type = attack_power_type::NO_WEAPON; + cast_during_sck = true; - monk_melee_attack_t::impact( s ); + double max_pct = data().effectN( 3 ).percent(); - if ( p()->baseline.brewmaster.stagger->ok() ) - { - p()->find_stagger( "Stagger" ) - ->purify_flat( amount * p()->baseline.brewmaster.touch_of_death_rank_3->effectN( 1 ).percent(), - "touch_of_death" ); - } + if ( pct_health > max_pct ) // Does a maximum of 50% of the monk's HP. + pct_health = max_pct; + + if ( interval < cooldown->duration.total_seconds() ) + { + sim->error( "{} minimum interval for Touch of Karma is 90 seconds.", *player ); + interval = cooldown->duration.total_seconds(); } - }; - // ========================================================================== - // Touch of Karma - // ========================================================================== - // When Touch of Karma (ToK) is activated, two spells are placed. A buff on the player (id: 125174), and a - // debuff on the target (id: 122470). Whenever the player takes damage, a dot (id: 124280) is placed on - // the target that increases as the player takes damage. Each time the player takes damage, the dot is refreshed - // and recalculates the dot size based on the current dot size. Just to make it easier to code, I'll wait until - // the Touch of Karma buff expires before placing a dot on the target. Net result should be the same. + if ( interval_stddev_opt < 1 ) + interval_stddev = interval * interval_stddev_opt; + // >= 1 seconds is used as a standard deviation normally + else + interval_stddev = interval_stddev_opt; - // 8.1 Good Karma - If the player still has the ToK buff on them, each time the target hits the player, the amount - // absorbed is immediatly healed by the Good Karma spell (id: 285594) + trigger_gcd = timespan_t::zero(); + may_crit = may_miss = may_dodge = may_parry = false; + } - struct touch_of_karma_dot_t : public residual_action::residual_periodic_action_t + // Need to disable multipliers in init() so that it doesn't double-dip on anything + void init() override { - using base_t = residual_action::residual_periodic_action_t; - touch_of_karma_dot_t( monk_t *p ) : base_t( "touch_of_karma", p, p->passives.touch_of_karma_tick ) - { - may_miss = may_crit = false; - dual = true; - proc = true; - ap_type = attack_power_type::NO_WEAPON; - } + monk_melee_attack_t::init(); + // disable the snapshot_flags for all multipliers + snapshot_flags = update_flags = 0; + } - // Need to disable multipliers in init() so that it doesn't double-dip on anything - void init() override - { - base_t::init(); - // disable the snapshot_flags for all multipliers - snapshot_flags = update_flags = 0; - snapshot_flags |= STATE_VERSATILITY; - } - }; + bool target_ready( player_t *target ) override + { + if ( target->name_str == "Target Dummy" ) + return false; - struct touch_of_karma_t : public monk_melee_attack_t - { - double interval; - double interval_stddev; - double interval_stddev_opt; - double pct_health; - touch_of_karma_dot_t *touch_of_karma_dot; - touch_of_karma_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "touch_of_karma", p->baseline.windwalker.touch_of_karma ), - interval( 100 ), - interval_stddev( 0.05 ), - interval_stddev_opt( 0 ), - pct_health( 0.5 ), - touch_of_karma_dot( new touch_of_karma_dot_t( p ) ) - { - add_option( opt_float( "interval", interval ) ); - add_option( opt_float( "interval_stddev", interval_stddev_opt ) ); - add_option( opt_float( "pct_health", pct_health ) ); - parse_options( options_str ); - - cooldown->duration = data().cooldown(); - base_dd_min = base_dd_max = 0; - ap_type = attack_power_type::NO_WEAPON; - cast_during_sck = true; - - double max_pct = data().effectN( 3 ).percent(); - - if ( pct_health > max_pct ) // Does a maximum of 50% of the monk's HP. - pct_health = max_pct; - - if ( interval < cooldown->duration.total_seconds() ) - { - sim->error( "{} minimum interval for Touch of Karma is 90 seconds.", *player ); - interval = cooldown->duration.total_seconds(); - } + return monk_melee_attack_t::target_ready( target ); + } - if ( interval_stddev_opt < 1 ) - interval_stddev = interval * interval_stddev_opt; - // >= 1 seconds is used as a standard deviation normally - else - interval_stddev = interval_stddev_opt; + void execute() override + { + timespan_t new_cd = timespan_t::from_seconds( rng().gauss( interval, interval_stddev ) ); + timespan_t data_cooldown = data().cooldown(); + if ( new_cd < data_cooldown ) + new_cd = data_cooldown; - trigger_gcd = timespan_t::zero(); - may_crit = may_miss = may_dodge = may_parry = false; - } + cooldown->duration = new_cd; - // Need to disable multipliers in init() so that it doesn't double-dip on anything - void init() override - { - monk_melee_attack_t::init(); - // disable the snapshot_flags for all multipliers - snapshot_flags = update_flags = 0; - } + monk_melee_attack_t::execute(); - bool target_ready( player_t *target ) override + if ( pct_health > 0 ) { - if ( target->name_str == "Target Dummy" ) - return false; + double damage_amount = pct_health * player->max_health(); - return monk_melee_attack_t::target_ready( target ); + // Redirects 70% of the damage absorbed + damage_amount *= data().effectN( 4 ).percent(); + + residual_action::trigger( touch_of_karma_dot, execute_state->target, damage_amount ); } + } +}; - void execute() override - { - timespan_t new_cd = timespan_t::from_seconds( rng().gauss( interval, interval_stddev ) ); - timespan_t data_cooldown = data().cooldown(); - if ( new_cd < data_cooldown ) - new_cd = data_cooldown; +// ========================================================================== +// Provoke +// ========================================================================== - cooldown->duration = new_cd; +struct provoke_t : public monk_melee_attack_t +{ + provoke_t( monk_t *p, util::string_view options_str ) : monk_melee_attack_t( p, "provoke", p->baseline.monk.provoke ) + { + parse_options( options_str ); + use_off_gcd = true; + ignore_false_positive = true; + } - monk_melee_attack_t::execute(); + void impact( action_state_t *s ) override + { + if ( s->target->is_enemy() ) + target->taunt( player ); - if ( pct_health > 0 ) - { - double damage_amount = pct_health * player->max_health(); + monk_melee_attack_t::impact( s ); + } +}; - // Redirects 70% of the damage absorbed - damage_amount *= data().effectN( 4 ).percent(); +// ========================================================================== +// Spear Hand Strike +// ========================================================================== - residual_action::trigger( touch_of_karma_dot, execute_state->target, damage_amount ); - } - } - }; +struct spear_hand_strike_t : public monk_melee_attack_t +{ + spear_hand_strike_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "spear_hand_strike", p->talent.monk.spear_hand_strike ) + { + parse_options( options_str ); + ignore_false_positive = true; + is_interrupt = true; + cast_during_sck = player->specialization() != MONK_WINDWALKER; + may_miss = may_block = may_dodge = may_parry = false; + } +}; - // ========================================================================== - // Provoke - // ========================================================================== +// ========================================================================== +// Leg Sweep +// ========================================================================== - struct provoke_t : public monk_melee_attack_t +struct leg_sweep_t : public monk_melee_attack_t +{ + leg_sweep_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "leg_sweep", p->baseline.monk.leg_sweep ) { - provoke_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "provoke", p->baseline.monk.provoke ) - { - parse_options( options_str ); - use_off_gcd = true; - ignore_false_positive = true; - } - - void impact( action_state_t *s ) override - { - if ( s->target->is_enemy() ) - target->taunt( player ); + parse_options( options_str ); + ignore_false_positive = true; + may_miss = may_block = may_dodge = may_parry = false; + cast_during_sck = player->specialization() != MONK_WINDWALKER; - monk_melee_attack_t::impact( s ); - } - }; + radius += p->talent.monk.tiger_tail_sweep->effectN( 1 ).base_value(); + } +}; - // ========================================================================== - // Spear Hand Strike - // ========================================================================== +// ========================================================================== +// Paralysis +// ========================================================================== - struct spear_hand_strike_t : public monk_melee_attack_t +struct paralysis_t : public monk_melee_attack_t +{ + paralysis_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "paralysis", p->talent.monk.paralysis ) { - spear_hand_strike_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "spear_hand_strike", p->talent.monk.spear_hand_strike ) - { - parse_options( options_str ); - ignore_false_positive = true; - is_interrupt = true; - cast_during_sck = player->specialization() != MONK_WINDWALKER; - may_miss = may_block = may_dodge = may_parry = false; - } - }; + parse_options( options_str ); + ignore_false_positive = true; + may_miss = may_block = may_dodge = may_parry = false; + } +}; - // ========================================================================== - // Leg Sweep - // ========================================================================== +// ========================================================================== +// Flying Serpent Kick +// ========================================================================== - struct leg_sweep_t : public monk_melee_attack_t +struct flying_serpent_kick_t : public monk_melee_attack_t +{ + bool first_charge; + double movement_speed_increase; + flying_serpent_kick_t( monk_t *p, util::string_view options_str ) + : monk_melee_attack_t( p, "flying_serpent_kick", p->baseline.windwalker.flying_serpent_kick ), + first_charge( true ), + movement_speed_increase( p->baseline.windwalker.flying_serpent_kick->effectN( 1 ).percent() ) { - leg_sweep_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "leg_sweep", p->baseline.monk.leg_sweep ) - { - parse_options( options_str ); - ignore_false_positive = true; - may_miss = may_block = may_dodge = may_parry = false; - cast_during_sck = player->specialization() != MONK_WINDWALKER; - - radius += p->talent.monk.tiger_tail_sweep->effectN( 1 ).base_value(); - } - }; + parse_options( options_str ); + may_crit = true; + ww_mastery = true; + may_combo_strike = true; + ignore_false_positive = true; + movement_directionality = movement_direction_type::OMNI; + aoe = -1; + p->cooldown.flying_serpent_kick = cooldown; + } - // ========================================================================== - // Paralysis - // ========================================================================== + void reset() override + { + monk_melee_attack_t::reset(); + first_charge = true; + } - struct paralysis_t : public monk_melee_attack_t + bool ready() override { - paralysis_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "paralysis", p->talent.monk.paralysis ) - { - parse_options( options_str ); - ignore_false_positive = true; - may_miss = may_block = may_dodge = may_parry = false; - } - }; + if ( first_charge ) // Assumes that we fsk into combat, instead of setting initial distance to 20 yards. + return monk_melee_attack_t::ready(); - // ========================================================================== - // Flying Serpent Kick - // ========================================================================== + return monk_melee_attack_t::ready(); + } - struct flying_serpent_kick_t : public monk_melee_attack_t + void execute() override { - bool first_charge; - double movement_speed_increase; - flying_serpent_kick_t( monk_t *p, util::string_view options_str ) - : monk_melee_attack_t( p, "flying_serpent_kick", p->baseline.windwalker.flying_serpent_kick ), - first_charge( true ), - movement_speed_increase( p->baseline.windwalker.flying_serpent_kick->effectN( 1 ).percent() ) - { - parse_options( options_str ); - may_crit = true; - ww_mastery = true; - may_combo_strike = true; - ignore_false_positive = true; - movement_directionality = movement_direction_type::OMNI; - aoe = -1; - p->cooldown.flying_serpent_kick = cooldown; - } - - void reset() override + if ( p()->current.distance_to_move >= 0 ) { - monk_melee_attack_t::reset(); - first_charge = true; + p()->buff.flying_serpent_kick_movement->trigger( + 1, movement_speed_increase, 1, + timespan_t::from_seconds( + std::min( 1.5, p()->current.distance_to_move / + ( p()->base_movement_speed * + ( 1 + p()->stacking_movement_modifier() + movement_speed_increase ) ) ) ) ); + p()->current.moving_away = 0; } - bool ready() override - { - if ( first_charge ) // Assumes that we fsk into combat, instead of setting initial distance to 20 yards. - return monk_melee_attack_t::ready(); - - return monk_melee_attack_t::ready(); - } + monk_melee_attack_t::execute(); - void execute() override + if ( first_charge ) { - if ( p()->current.distance_to_move >= 0 ) - { - p()->buff.flying_serpent_kick_movement->trigger( - 1, movement_speed_increase, 1, - timespan_t::from_seconds( - std::min( 1.5, p()->current.distance_to_move / - ( p()->base_movement_speed * - ( 1 + p()->stacking_movement_modifier() + movement_speed_increase ) ) ) ) ); - p()->current.moving_away = 0; - } - - monk_melee_attack_t::execute(); - - if ( first_charge ) - { - p()->movement.flying_serpent_kick->trigger(); + p()->movement.flying_serpent_kick->trigger(); - first_charge = !first_charge; - } + first_charge = !first_charge; } - }; + } +}; } // namespace attacks namespace spells From d63ab07928cbe0674b07c69ec31d82ced7ff2a15 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:30:17 -0600 Subject: [PATCH 3/7] [monk] Bypass `parse_effects` overrides. --- engine/class_modules/monk/sc_monk.cpp | 16 ++++++++++------ engine/class_modules/monk/sc_monk.hpp | 3 ++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 6bbc5ab15e3..ca5c2b0e71e 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -932,7 +932,6 @@ struct sef_action_t : base_t return false; } - // TODO: How do I bypass `parse_effect` overrides? double composite_player_multiplier( const action_state_t * ) const override { // Composite Player Multiplier is strictly school mods, which get skipped. @@ -941,7 +940,7 @@ struct sef_action_t : base_t double composite_crit_chance() const override { - auto cc = PLACEHOLDER_T::composite_crit_chance(); + auto cc = derived_t::composite_crit_chance(); for ( const auto &i : base_t::crit_chance_effects ) if ( !from_caster_spells( i.eff ) ) @@ -953,14 +952,19 @@ struct sef_action_t : base_t double composite_crit_damage_bonus_multiplier() const override { // Crit Damage Bonus Multipliers double dip. - auto cd = PLACEHOLDER_T::composite_crit_damage_bonus_multiplier() * *2 / 2.0; + auto cd = derived_t::composite_crit_damage_bonus_multiplier() * *2 / 2.0; + + for ( const auto &i : crit_bonus_effects ) + { + cd *= 1.0 + get_effect_value( i, false ); + } return cd; } double composite_target_crit_damage_bonus_multiplier( player_t *target ) const override { - auto cd = PLACEHOLDER_T::composite_target_crit_damage_bonus_multiplier( target ); + auto cd = derived_t::composite_target_crit_damage_bonus_multiplier( target ); auto td = base_t::p()->get_target_data( target ); for ( const auto &i : base_t::target_crit_bonus_effects ) @@ -972,7 +976,7 @@ struct sef_action_t : base_t double composite_da_multiplier( const action_state_t *state ) const override { - auto da = PLACEHOLDER_T::composite_da_multiplier( state ); + auto da = derived_t::composite_da_multiplier( state ); for ( const auto &i : base_t::da_multiplier_effects ) if ( !from_caster_spells( i.eff ) ) @@ -983,7 +987,7 @@ struct sef_action_t : base_t double composite_ta_multiplier( const action_state_t *state ) const override { - auto ta = PLACEHOLDER_T::composite_ta_multiplier( state ); + auto ta = derived_t::composite_ta_multiplier( state ); for ( const auto &i : base_t::ta_multiplier_effects ) if ( !from_caster_spells( i.eff ) ) diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 7fdb544461d..b1d9928c12d 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -97,7 +97,8 @@ struct monk_action_t : public parse_action_effects_t std::array _resource_by_stance; public: - using base_t = parse_action_effects_t; + using derived_t = Base; + using base_t = parse_action_effects_t; template monk_action_t( Args &&...args ); From 7dccbf88379eaa6b4cc75bc35caa1a7259c7e237 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Sun, 1 Sep 2024 21:03:23 -0600 Subject: [PATCH 4/7] [monk] Continue work on SEF rework. * Freeze buff expiration while SEF actions are occurring. * Finish crit double dipping. * Correctly call the base action method to bypass parsing. * Add SEF actions as children to the parent action. * Hook up Blackout Kick. * Create SEF BETA monk option to use new SEF. * Remove Chi Burst healing targets monk option. --- engine/class_modules/monk/sc_monk.cpp | 138 ++++++++++++++++++++------ engine/class_modules/monk/sc_monk.hpp | 11 +- 2 files changed, 116 insertions(+), 33 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index ca5c2b0e71e..7be8af5b710 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -761,6 +761,30 @@ monk_buff_t::monk_buff_t( monk_td_t *target_data, std::string_view name, const s { } +void monk_buff_t::expire( timespan_t delay ) +{ + if ( !p().freeze_expiration ) + base_t::expire( delay ); +} + +void monk_buff_t::expire( action_t *action, timespan_t delay ) +{ + if ( !p().freeze_expiration ) + base_t::expire( action, delay ); +} + +void monk_buff_t::expire_override( int expiration_stacks, timespan_t remaining_duration ) +{ + if ( !p().freeze_expiration ) + base_t::expire_override( expiration_stacks, remaining_duration ); +} + +void monk_buff_t::decrement( int stacks, double value ) +{ + if ( !p().freeze_expiration ) + base_t::decrement( stacks, value ); +} + monk_td_t &monk_buff_t::get_td( player_t *t ) { return *( p().get_target_data( t ) ); @@ -839,6 +863,9 @@ namespace pet_summon struct storm_earth_and_fire_t : public monk_spell_t { + action_t *fire_sef; + action_t *earth_sef; + storm_earth_and_fire_t( monk_t *p, util::string_view options_str ) : monk_spell_t( p, "storm_earth_and_fire", p->talent.windwalker.storm_earth_and_fire ) { @@ -848,6 +875,12 @@ struct storm_earth_and_fire_t : public monk_spell_t trigger_gcd = timespan_t::zero(); may_combo_strike = true; callbacks = harmful = may_miss = may_crit = may_dodge = may_parry = may_block = false; + + if ( p->user_options.sef_beta ) + { + fire_sef = new monk_spell_t( p, "fire_elemental", spell_data_t::nil() ); + earth_sef = new monk_spell_t( p, "earth_elemental", spell_data_t::nil() ); + } } bool ready() override @@ -862,13 +895,20 @@ struct storm_earth_and_fire_t : public monk_spell_t { monk_spell_t::execute(); - p()->summon_storm_earth_and_fire( data().duration() ); - - if ( p()->talent.windwalker.ordered_elements.ok() ) + if ( p()->user_options.sef_beta ) { - p()->cooldown.rising_sun_kick->reset( true ); - p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.ordered_elements->effectN( 2 ).base_value(), - p()->gain.ordered_elements ); + p()->buff.storm_earth_and_fire->trigger( 1, buff_t::DEFAULT_VALUE(), 1, data().duration() ); + } + else + { + p()->summon_storm_earth_and_fire( data().duration() ); + + if ( p()->talent.windwalker.ordered_elements.ok() ) + { + p()->cooldown.rising_sun_kick->reset( true ); + p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.ordered_elements->effectN( 2 ).base_value(), + p()->gain.ordered_elements ); + } } } }; @@ -910,13 +950,13 @@ struct sef_action_t : base_t { struct child_action_t : base_t { - template + template child_action_t( Args &&...args ) : base_t( std::forward( args )... ) { base_t::background = true; } - bool from_caster_spells( const spelleffect_data_t *eff ) + bool from_caster_spells( const spelleffect_data_t *eff ) const { switch ( eff->subtype() ) { @@ -928,6 +968,23 @@ struct sef_action_t : base_t case A_MOD_CRIT_DAMAGE_PCT_FROM_CASTER_SPELLS: case A_MOD_DAMAGE_FROM_CASTER_SPELLS_LABEL: return true; + default: + return false; + } + return false; + } + + bool add_mod( const spelleffect_data_t *eff ) const + { + switch ( eff->subtype() ) + { + case A_ADD_FLAT_MODIFIER: + case A_ADD_PCT_MODIFIER: + case A_ADD_PCT_LABEL_MODIFIER: + case A_ADD_FLAT_LABEL_MODIFIER: + return true; + default: + return false; } return false; } @@ -940,7 +997,7 @@ struct sef_action_t : base_t double composite_crit_chance() const override { - auto cc = derived_t::composite_crit_chance(); + auto cc = base_t::derived_t::composite_crit_chance(); for ( const auto &i : base_t::crit_chance_effects ) if ( !from_caster_spells( i.eff ) ) @@ -952,19 +1009,19 @@ struct sef_action_t : base_t double composite_crit_damage_bonus_multiplier() const override { // Crit Damage Bonus Multipliers double dip. - auto cd = derived_t::composite_crit_damage_bonus_multiplier() * *2 / 2.0; + auto cd = std::pow( base_t::derived_t::composite_crit_damage_bonus_multiplier(), 2 ); - for ( const auto &i : crit_bonus_effects ) - { - cd *= 1.0 + get_effect_value( i, false ); - } + // If non-A_ADD, apply again. + for ( const auto &i : base_t::crit_bonus_effects ) + if ( !add_mod( i.eff ) ) + cd *= 1.0 + base_t::get_effect_value( i, false ); return cd; } double composite_target_crit_damage_bonus_multiplier( player_t *target ) const override { - auto cd = derived_t::composite_target_crit_damage_bonus_multiplier( target ); + auto cd = base_t::derived_t::composite_target_crit_damage_bonus_multiplier( target ); auto td = base_t::p()->get_target_data( target ); for ( const auto &i : base_t::target_crit_bonus_effects ) @@ -976,7 +1033,7 @@ struct sef_action_t : base_t double composite_da_multiplier( const action_state_t *state ) const override { - auto da = derived_t::composite_da_multiplier( state ); + auto da = base_t::derived_t::composite_da_multiplier( state ); for ( const auto &i : base_t::da_multiplier_effects ) if ( !from_caster_spells( i.eff ) ) @@ -987,7 +1044,7 @@ struct sef_action_t : base_t double composite_ta_multiplier( const action_state_t *state ) const override { - auto ta = derived_t::composite_ta_multiplier( state ); + auto ta = base_t::derived_t::composite_ta_multiplier( state ); for ( const auto &i : base_t::ta_multiplier_effects ) if ( !from_caster_spells( i.eff ) ) @@ -1010,25 +1067,36 @@ struct sef_action_t : base_t player_t *sef_fire_target; player_t *sef_earth_target; - template + template sef_action_t( monk_t *player, std::string_view name, Args &&...args ) : base_t( player, name, std::forward( args )... ) { if ( !player->talent.windwalker.storm_earth_and_fire->ok() ) return; - sef_fire_action = new child_action_t( player, name, std::forward( args )... ); - sef_earth_action = new child_action_t( player, name, std::forward( args )... ); + sef_fire_action = new child_action_t( player, fmt::format( "{}_{}", name, "fire" ), std::forward( args )... ); + sef_earth_action = + new child_action_t( player, fmt::format( "{}_{}", name, "earth" ), std::forward( args )... ); // TODO: Set SEF actions as children of the parent SEF action for each elemental. } - void execute() override + void init_finished() override { - base_t::execute(); + base_t::init_finished(); + base_t::p()->find_action( "fire_elemental" )->add_child( sef_fire_action ); + base_t::p()->find_action( "earth_elemental" )->add_child( sef_earth_action ); + } + + void execute() override + { + base_t::p()->freeze_expiration = true; sef_fire_action->execute_on_target( base_t::p()->target ); sef_earth_action->execute_on_target( base_t::p()->target ); + + base_t::p()->freeze_expiration = false; + base_t::execute(); } }; @@ -1834,11 +1902,11 @@ struct charred_passions_t : base_action_t // Blackout Kick Baseline ability ======================================= struct blackout_kick_t : overwhelming_force_t> { - blackout_kick_totm_proc_t *bok_totm_proc; + action_t *bok_totm_proc; cooldown_t *keg_smash_cooldown; - blackout_kick_t( monk_t *p, util::string_view options_str ) - : base_t( p, "blackout_kick", + blackout_kick_t( monk_t *p, std::string_view name, util::string_view options_str ) + : base_t( p, name, ( p->specialization() == MONK_BREWMASTER ? p->baseline.brewmaster.blackout_kick : p->baseline.monk.blackout_kick ) ), keg_smash_cooldown( nullptr ) @@ -1865,8 +1933,13 @@ struct blackout_kick_t : overwhelming_force_tshared.teachings_of_the_monastery->ok() ) { - bok_totm_proc = new blackout_kick_totm_proc_t( p ); - add_child( bok_totm_proc ); + if ( action_t *totm = p->find_action( "blackout_kick_totm_proc" ); totm ) + bok_totm_proc = totm; + else + { + bok_totm_proc = new blackout_kick_totm_proc_t( p ); + add_child( bok_totm_proc ); + } } if ( p->baseline.windwalker.blackout_kick_rank_2->ok() ) @@ -6418,6 +6491,7 @@ monk_t::monk_t( sim_t *sim, util::string_view name, race_e r ) efficient_training_energy( 0 ), flurry_strikes_energy( 0 ), flurry_strikes_damage( 0 ), + freeze_expiration( false ), buff(), gain(), proc(), @@ -6465,9 +6539,9 @@ monk_t::monk_t( sim_t *sim, util::string_view name, race_e r ) } user_options.initial_chi = talent.windwalker.combat_wisdom.ok() ? (int)talent.windwalker.combat_wisdom->effectN( 1 ).base_value() : 0; - user_options.chi_burst_healing_targets = 8; - user_options.motc_override = 0; - user_options.squirm_frequency = 15; + user_options.motc_override = 0; + user_options.squirm_frequency = 15; + user_options.sef_beta = false; } void monk_t::parse_player_effects() @@ -6579,7 +6653,7 @@ action_t *monk_t::create_action( util::string_view name, util::string_view optio if ( name == "tiger_palm" ) return new tiger_palm_t( this, options_str ); if ( name == "blackout_kick" ) - return new blackout_kick_t( this, options_str ); + return new sef_action_t( this, "blackout_kick", options_str ); if ( name == "expel_harm" ) return new expel_harm_t( this, options_str ); if ( name == "leg_sweep" ) @@ -8917,9 +8991,9 @@ void monk_t::create_options() base_t::create_options(); add_option( opt_int( "monk.initial_chi", user_options.initial_chi, 0, 6 ) ); - add_option( opt_int( "monk.chi_burst_healing_targets", user_options.chi_burst_healing_targets, 0, 30 ) ); add_option( opt_int( "monk.motc_override", user_options.motc_override, 0, 5 ) ); add_option( opt_float( "monk.squirm_frequency", user_options.squirm_frequency, 0, 30 ) ); + add_option( opt_bool( "monk.sef_beta", user_options.sef_beta ) ); } // monk_t::copy_from ========================================================= diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index b1d9928c12d..0077feba392 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -177,6 +177,8 @@ struct monk_melee_attack_t : public monk_action_t struct monk_buff_t : public buff_t { + using base_t = buff_t; + monk_buff_t( monk_t *player, std::string_view name, const spell_data_t *spell_data = spell_data_t::nil(), const item_t *item = nullptr ); monk_buff_t( monk_td_t *player, std::string_view name, const spell_data_t *spell_data = spell_data_t::nil(), @@ -185,6 +187,11 @@ struct monk_buff_t : public buff_t const monk_td_t *find_td( player_t *target ) const; monk_t &p(); const monk_t &p() const; + + void expire( timespan_t delay = timespan_t::zero() ) override; + void expire( action_t *action, timespan_t delay = timespan_t::zero() ); + void decrement( int stacks = 1, double value = DEFAULT_VALUE() ) override; + void expire_override( int expiration_stacks, timespan_t remaining_duration ) override; }; struct summon_pet_t : public monk_spell_t @@ -490,6 +497,7 @@ struct monk_t : public stagger_t int efficient_training_energy; int flurry_strikes_energy; double flurry_strikes_damage; + bool freeze_expiration; //============================================== // Monk Movement @@ -1303,9 +1311,10 @@ struct monk_t : public stagger_t int initial_chi; double expel_harm_effectiveness; double jadefire_stomp_uptime; - int chi_burst_healing_targets; int motc_override; double squirm_frequency; + + bool sef_beta; } user_options; // exterminate these structs From f4f3428eedce00601dae1add15c8f397be8fc418 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Tue, 3 Sep 2024 23:13:18 -0600 Subject: [PATCH 5/7] [monk] Mucking out SEF pet. --- engine/class_modules/monk/sc_monk.cpp | 15 +++++- engine/class_modules/monk/sc_monk.hpp | 1 + engine/class_modules/monk/sc_monk_pets.cpp | 60 +++++++++------------- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 7be8af5b710..fc1b2ddecf0 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -4450,7 +4450,7 @@ struct celestial_conduit_t : public monk_spell_t struct celestial_conduit_dmg_t : public monk_spell_t { celestial_conduit_dmg_t( monk_t *p ) - : monk_spell_t( p, "celestial_conduit_dmg", p->talent.conduit_of_the_celestials.celestial_conduit_dmg ) + : monk_spell_t( p, "celestial_conduit_damage", p->talent.conduit_of_the_celestials.celestial_conduit_dmg ) { background = true; aoe = -1; @@ -6652,8 +6652,10 @@ action_t *monk_t::create_action( util::string_view name, util::string_view optio return new crackling_jade_lightning_t( this, options_str ); if ( name == "tiger_palm" ) return new tiger_palm_t( this, options_str ); - if ( name == "blackout_kick" ) + if ( name == "blackout_kick" && user_options.sef_beta ) return new sef_action_t( this, "blackout_kick", options_str ); + if ( name == "blackout_kick" ) + return new blackout_kick_t( this, "blackout_kick", options_str ); if ( name == "expel_harm" ) return new expel_harm_t( this, options_str ); if ( name == "leg_sweep" ) @@ -9007,6 +9009,15 @@ void monk_t::copy_from( player_t *source ) user_options = source_p->user_options; } +// monk_t::copy_from ========================================================= +action_t *monk_t::find_action( int id ) +{ + for ( const action_t *action : action_list ) + if ( id == action->id ) + return action; + return nullptr; +} + // monk_t::primary_resource ================================================= resource_e monk_t::primary_resource() const diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 0077feba392..cb714ff00a5 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -1394,6 +1394,7 @@ struct monk_t : public stagger_t void reset() override; void create_options() override; void copy_from( player_t * ) override; + action_t *find_action( int id ); resource_e primary_resource() const override; role_e primary_role() const override; stat_e convert_hybrid_stat( stat_e s ) const override; diff --git a/engine/class_modules/monk/sc_monk_pets.cpp b/engine/class_modules/monk/sc_monk_pets.cpp index 459c7071f18..f609d806bba 100644 --- a/engine/class_modules/monk/sc_monk_pets.cpp +++ b/engine/class_modules/monk/sc_monk_pets.cpp @@ -362,52 +362,42 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t template struct sef_action_base_t : public pet_action_base_t { - using super_t = pet_action_base_t; - using base_t = sef_action_base_t; + using base_t = pet_action_base_t; const action_t *source_action; - sef_action_base_t( util::string_view n, storm_earth_and_fire_pet_t *p, - const spell_data_t *data = spell_data_t::nil() ) - : super_t( n, p, data ), source_action( nullptr ) + sef_action_base_t( std::string_view name, storm_earth_and_fire_pet_t *p, + const spell_data_t *spell_data = spell_data_t::nil() ) + : base_t( name, p, spell_data ), source_action( nullptr ) { - // Make SEF attacks always background, so they do not consume resources - // or do anything associated with "foreground actions". - this->background = this->may_crit = true; - this->callbacks = false; + base_t::background = true; + base_t::cooldown->duration = timespan_t::zero(); - // Cooldowns are handled automatically by the mirror abilities, the SEF specific ones need none. - this->cooldown->duration = timespan_t::zero(); + // TODO: Is this correct? + base_t::callbacks = false; + } - // No costs are needed either - this->base_costs[ RESOURCE_ENERGY ] = 0; - this->base_costs[ RESOURCE_CHI ] = 0; + double base_cost() override + { + return 0.0; } void init() override { - super_t::init(); + base_t::init(); - // Find source_action from the owner by matching the action name and - // spell id with eachother. This basically means that by default, any - // spell-data driven ability with 1:1 mapping of name/spell id will - // always be chosen as the source action. In some cases this needs to be - // overridden (see sef_zen_sphere_t for example). - for ( const action_t *a : this->o()->action_list ) - { - if ( ( this->id > 0 && this->id == a->id ) || util::str_compare_ci( this->name_str, a->name_str ) ) - { - source_action = a; - break; - } - } + // Look up source action based on id, falling back to name if not found. + if ( action_t *action = find_action( base_t::id ) ) + source_action = action; + else + source_action = find_action( base_t::name_str ); - if ( source_action ) - { - this->update_flags = source_action->update_flags; - auto pet_multiplier_snapshot = this->snapshot_flags & STATE_MUL_PET; - this->snapshot_flags = source_action->snapshot_flags | pet_multiplier_snapshot; - } + if ( !source_action ) + return; + + base_t::update_flags = source_action->update_flags; + // isn't base_t::snapshot_flags == source_action->snapshot_flags or 0? + base_t::snapshot_flags = source_action->snapshot_flags | ( base_t::snapshot_flags & STATE_MUL_PET ); } void snapshot_internal( action_state_t *state, unsigned flags, result_amount_type rt ) override @@ -719,7 +709,7 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t sef_glory_of_the_dawn_t *glory_of_the_dawn; sef_rising_sun_kick_dmg_t( storm_earth_and_fire_pet_t *player ) - : sef_melee_attack_t( "rising_sun_kick_dmg", player, + : sef_melee_attack_t( "rising_sun_kick_damage", player, player->o()->talent.monk.rising_sun_kick->effectN( 1 ).trigger() ) { background = true; From ef325273cb37ef3dec3e28f43d6780f85f404614 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:35:17 -0600 Subject: [PATCH 6/7] [monk] SEF Pivot smile --- engine/class_modules/monk/sc_monk.cpp | 73 ++++++++++++++-------- engine/class_modules/monk/sc_monk.hpp | 14 +++-- engine/class_modules/monk/sc_monk_pets.cpp | 8 ++- 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index fc1b2ddecf0..903eaad459b 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -843,16 +843,6 @@ struct monk_snapshot_stats_t : public snapshot_stats_t monk_snapshot_stats_t( monk_t *player, util::string_view options ) : snapshot_stats_t( player, options ) { } - - void execute() override - { - snapshot_stats_t::execute(); - - // auto *monk = debug_cast( player ); - // monk->stagger->sample_data->buffed_base_value = monk->stagger->base_value(); - // monk->stagger->sample_data->buffed_percent_player_level = monk->stagger->percent( monk->level() ); - // monk->stagger->sample_data->buffed_percent_target_level = monk->stagger->percent( target->level() ); - } }; namespace pet_summon @@ -945,13 +935,28 @@ struct storm_earth_and_fire_fixate_t : public monk_spell_t } // namespace pet_summon +enum sef_type_e +{ + SEF_TYPE_FIRE, + SEF_TYPE_EARTH +}; + template struct sef_action_t : base_t { struct child_action_t : base_t { - template - child_action_t( Args &&...args ) : base_t( std::forward( args )... ) + sef_type_e type; + bool is_fixated; + player_t *target; + + child_action_t( sef_type_e type, monk_t *player, std::string_view name, const spell_data_t *spell_data ) + : base_t( player, + fmt::format( "{}_{}", name, + type == SEF_TYPE_FIRE ? "sef_fire" + : type == SEF_TYPE_EARTH ? "sef_earth" + : "NONE" ), + spell_data ) { base_t::background = true; } @@ -989,6 +994,19 @@ struct sef_action_t : base_t return false; } + void init_finished() override + { + base_t::init_finished(); + + switch ( type ) + { + case SEF_TYPE_FIRE: + base_t::p()->find_action( "sef_fire_elemental" )->add_child( this ); + case SEF_TYPE_EARTH: + base_t::p()->find_action( "sef_earth_elemental" )->add_child( this ); + } + } + double composite_player_multiplier( const action_state_t * ) const override { // Composite Player Multiplier is strictly school mods, which get skipped. @@ -1063,20 +1081,17 @@ struct sef_action_t : base_t * If all MotC debuffs >=10s, do not retarget. * If fixated, do not retarget. */ - bool is_fixated; - player_t *sef_fire_target; - player_t *sef_earth_target; template - sef_action_t( monk_t *player, std::string_view name, Args &&...args ) - : base_t( player, name, std::forward( args )... ) + sef_action_t( monk_t *player, Args &&...args ) : base_t( player, std::forward( args )... ) { if ( !player->talent.windwalker.storm_earth_and_fire->ok() ) return; - sef_fire_action = new child_action_t( player, fmt::format( "{}_{}", name, "fire" ), std::forward( args )... ); + sef_fire_action = + new child_action_t( player, fmt::format( "{}_{}", base_t::name_str, "fire" ), std::forward( args )... ); sef_earth_action = - new child_action_t( player, fmt::format( "{}_{}", name, "earth" ), std::forward( args )... ); + new child_action_t( player, fmt::format( "{}_{}", base_t::name_str, "earth" ), std::forward( args )... ); // TODO: Set SEF actions as children of the parent SEF action for each elemental. } @@ -1905,8 +1920,8 @@ struct blackout_kick_t : overwhelming_force_tspecialization() == MONK_BREWMASTER ? p->baseline.brewmaster.blackout_kick : p->baseline.monk.blackout_kick ) ), keg_smash_cooldown( nullptr ) @@ -6652,10 +6667,8 @@ action_t *monk_t::create_action( util::string_view name, util::string_view optio return new crackling_jade_lightning_t( this, options_str ); if ( name == "tiger_palm" ) return new tiger_palm_t( this, options_str ); - if ( name == "blackout_kick" && user_options.sef_beta ) - return new sef_action_t( this, "blackout_kick", options_str ); if ( name == "blackout_kick" ) - return new blackout_kick_t( this, "blackout_kick", options_str ); + return new blackout_kick_t( this, options_str ); if ( name == "expel_harm" ) return new expel_harm_t( this, options_str ); if ( name == "leg_sweep" ) @@ -9009,10 +9022,18 @@ void monk_t::copy_from( player_t *source ) user_options = source_p->user_options; } +template +TAction *monk_t::make_action( Args &&...args ) +{ + if ( talent.windwalker.storm_earth_and_fire->ok() && false ) + return new actions::sef_action_t( this, std::forward( args )... ); + return new TAction( this, std::forward( args )... ); +} + // monk_t::copy_from ========================================================= -action_t *monk_t::find_action( int id ) +action_t *monk_t::find_action( unsigned int id ) const { - for ( const action_t *action : action_list ) + for ( action_t *action : action_list ) if ( id == action->id ) return action; return nullptr; diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index cb714ff00a5..c88a1aa7242 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -83,9 +83,14 @@ enum class sef_ability_e SEF_MAX }; +template +struct sef_action_t; + template struct monk_action_t : public parse_action_effects_t { + using derived_t = Base; + using base_t = parse_action_effects_t; sef_ability_e sef_ability; bool ww_mastery; bool may_combo_strike; @@ -97,9 +102,6 @@ struct monk_action_t : public parse_action_effects_t std::array _resource_by_stance; public: - using derived_t = Base; - using base_t = parse_action_effects_t; - template monk_action_t( Args &&...args ); std::string full_name() const; @@ -498,6 +500,7 @@ struct monk_t : public stagger_t int flurry_strikes_energy; double flurry_strikes_damage; bool freeze_expiration; + bool freeze_buffs; //============================================== // Monk Movement @@ -1394,7 +1397,10 @@ struct monk_t : public stagger_t void reset() override; void create_options() override; void copy_from( player_t * ) override; - action_t *find_action( int id ); + template + TAction *make_action( Args &&...args ); + using player_t::find_action; + action_t *find_action( unsigned int id ) const; resource_e primary_resource() const override; role_e primary_role() const override; stat_e convert_hybrid_stat( stat_e s ) const override; diff --git a/engine/class_modules/monk/sc_monk_pets.cpp b/engine/class_modules/monk/sc_monk_pets.cpp index f609d806bba..4c078ff1183 100644 --- a/engine/class_modules/monk/sc_monk_pets.cpp +++ b/engine/class_modules/monk/sc_monk_pets.cpp @@ -377,7 +377,7 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t base_t::callbacks = false; } - double base_cost() override + double base_cost() const override { return 0.0; } @@ -387,10 +387,10 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t base_t::init(); // Look up source action based on id, falling back to name if not found. - if ( action_t *action = find_action( base_t::id ) ) + if ( action_t *action = base_t::o()->find_action( base_t::id ) ) source_action = action; else - source_action = find_action( base_t::name_str ); + source_action = base_t::o()->find_action( base_t::name_str ); if ( !source_action ) return; @@ -479,6 +479,7 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t struct sef_melee_attack_t : public sef_action_base_t { + using base_t = sef_action_base_t; sef_melee_attack_t( util::string_view n, storm_earth_and_fire_pet_t *p, const spell_data_t *data = spell_data_t::nil() ) : base_t( n, p, data ) @@ -503,6 +504,7 @@ struct storm_earth_and_fire_pet_t : public monk_pet_t struct sef_spell_t : public sef_action_base_t { + using base_t = sef_action_base_t; sef_spell_t( util::string_view n, storm_earth_and_fire_pet_t *p, const spell_data_t *data = spell_data_t::nil() ) : base_t( n, p, data ) { From 0f3abceade211aeb7d9bed9e5e2f3b2f555013b4 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:40:20 -0600 Subject: [PATCH 7/7] [monk] SEF composite actions are set up, but not properly executing for some reason. --- engine/class_modules/monk/sc_monk.cpp | 266 ++++++++++++++------------ engine/class_modules/monk/sc_monk.hpp | 2 +- 2 files changed, 149 insertions(+), 119 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 903eaad459b..72d0ee8ee7d 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -589,7 +589,7 @@ void monk_action_t::execute() base_t::execute(); - trigger_storm_earth_and_fire( this ); + // trigger_storm_earth_and_fire( this ); } template @@ -866,11 +866,8 @@ struct storm_earth_and_fire_t : public monk_spell_t may_combo_strike = true; callbacks = harmful = may_miss = may_crit = may_dodge = may_parry = may_block = false; - if ( p->user_options.sef_beta ) - { - fire_sef = new monk_spell_t( p, "fire_elemental", spell_data_t::nil() ); - earth_sef = new monk_spell_t( p, "earth_elemental", spell_data_t::nil() ); - } + fire_sef = new monk_spell_t( p, "sef_fire_elemental", spell_data_t::nil() ); + earth_sef = new monk_spell_t( p, "sef_earth_elemental", spell_data_t::nil() ); } bool ready() override @@ -885,20 +882,11 @@ struct storm_earth_and_fire_t : public monk_spell_t { monk_spell_t::execute(); - if ( p()->user_options.sef_beta ) - { - p()->buff.storm_earth_and_fire->trigger( 1, buff_t::DEFAULT_VALUE(), 1, data().duration() ); - } - else + if ( p()->talent.windwalker.ordered_elements.ok() ) { - p()->summon_storm_earth_and_fire( data().duration() ); - - if ( p()->talent.windwalker.ordered_elements.ok() ) - { - p()->cooldown.rising_sun_kick->reset( true ); - p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.ordered_elements->effectN( 2 ).base_value(), - p()->gain.ordered_elements ); - } + p()->cooldown.rising_sun_kick->reset( true ); + p()->resource_gain( RESOURCE_CHI, p()->talent.windwalker.ordered_elements->effectN( 2 ).base_value(), + p()->gain.ordered_elements ); } } }; @@ -941,24 +929,37 @@ enum sef_type_e SEF_TYPE_EARTH }; -template -struct sef_action_t : base_t +template +struct sef_action_t : monk_spell_t { - struct child_action_t : base_t + struct child_action_t : TBase { + using base_t = TBase; sef_type_e type; bool is_fixated; player_t *target; - child_action_t( sef_type_e type, monk_t *player, std::string_view name, const spell_data_t *spell_data ) - : base_t( player, - fmt::format( "{}_{}", name, - type == SEF_TYPE_FIRE ? "sef_fire" - : type == SEF_TYPE_EARTH ? "sef_earth" - : "NONE" ), - spell_data ) + template + child_action_t( sef_type_e type, Args &&...args ) : base_t( std::forward( args )... ), type( type ) { base_t::background = true; + + // Action name must be amended after construction, requiring changing out + // several members of `action_t` as well. + switch ( type ) + { + case SEF_TYPE_FIRE: + base_t::name_str += "_sef_fire"; + break; + case SEF_TYPE_EARTH: + base_t::name_str += "_sef_earth"; + break; + } + base_t::internal_id = base_t::p()->get_action_id( base_t::name_str ); + base_t::gain = base_t::p()->get_gain( base_t::name_str ); + base_t::cooldown = base_t::p()->get_cooldown( base_t::name_str, this ); + base_t::internal_cooldown = base_t::p()->get_cooldown( base_t::name_str + "_internal", this ); + base_t::stats = base_t::p()->get_stats( base_t::name_str, this ); } bool from_caster_spells( const spelleffect_data_t *eff ) const @@ -1002,8 +1003,10 @@ struct sef_action_t : base_t { case SEF_TYPE_FIRE: base_t::p()->find_action( "sef_fire_elemental" )->add_child( this ); + break; case SEF_TYPE_EARTH: base_t::p()->find_action( "sef_earth_elemental" )->add_child( this ); + break; } } @@ -1071,50 +1074,72 @@ struct sef_action_t : base_t return ta; } }; + + action_t *parent_action; action_t *sef_fire_action; action_t *sef_earth_action; - // TODO: Target Fire/Earth actions - /* - * If a non-sleeping target exists with MotC debuff <10s, target that. - * If it is already targeted by another elemental, do not retarget. - * If all MotC debuffs >=10s, do not retarget. - * If fixated, do not retarget. - */ - template - sef_action_t( monk_t *player, Args &&...args ) : base_t( player, std::forward( args )... ) + sef_action_t( TBase *parent, Args &&...args ) + : monk_spell_t( parent->p(), parent->name_str + "_sef_composite" ), + parent_action( parent ), + sef_fire_action( new child_action_t( SEF_TYPE_FIRE, parent->p(), std::forward( args )... ) ), + sef_earth_action( new child_action_t( SEF_TYPE_EARTH, parent->p(), std::forward( args )... ) ) { - if ( !player->talent.windwalker.storm_earth_and_fire->ok() ) - return; - - sef_fire_action = - new child_action_t( player, fmt::format( "{}_{}", base_t::name_str, "fire" ), std::forward( args )... ); - sef_earth_action = - new child_action_t( player, fmt::format( "{}_{}", base_t::name_str, "earth" ), std::forward( args )... ); - - // TODO: Set SEF actions as children of the parent SEF action for each elemental. - } - - void init_finished() override - { - base_t::init_finished(); - - base_t::p()->find_action( "fire_elemental" )->add_child( sef_fire_action ); - base_t::p()->find_action( "earth_elemental" )->add_child( sef_earth_action ); } void execute() override { - base_t::p()->freeze_expiration = true; - sef_fire_action->execute_on_target( base_t::p()->target ); - sef_earth_action->execute_on_target( base_t::p()->target ); + p()->freeze_expiration = true; + sef_fire_action->execute(); + sef_earth_action->execute(); + p()->freeze_expiration = false; - base_t::p()->freeze_expiration = false; + parent_action->execute(); base_t::execute(); } }; +// struct sef_action_t : base_t +// { +// action_t *sef_fire_action; +// action_t *sef_earth_action; + +// // TODO: Target Fire/Earth actions +// /* +// * If a non-sleeping target exists with MotC debuff <10s, target that. +// * If it is already targeted by another elemental, do not retarget. +// * If all MotC debuffs >=10s, do not retarget. +// * If fixated, do not retarget. +// */ + +// template +// sef_action_t( monk_t *player, Args &&...args ) : base_t( player, std::forward( args )... ) +// { +// if ( !player->talent.windwalker.storm_earth_and_fire->ok() ) +// return; + +// sef_fire_action = +// new child_action_t( player, fmt::format( "{}_{}", base_t::name_str, "fire" ), std::forward( args )... +// ); +// sef_earth_action = +// new child_action_t( player, fmt::format( "{}_{}", base_t::name_str, "earth" ), std::forward( args )... +// ); + +// // TODO: Set SEF actions as children of the parent SEF action for each elemental. +// } + +// void execute() override +// { +// base_t::p()->freeze_expiration = true; +// sef_fire_action->execute_on_target( base_t::p()->target ); +// sef_earth_action->execute_on_target( base_t::p()->target ); + +// base_t::p()->freeze_expiration = false; +// base_t::execute(); +// } +// }; + namespace attacks { // ========================================================================== @@ -6664,121 +6689,117 @@ action_t *monk_t::create_action( util::string_view name, util::string_view optio if ( name == "auto_attack" ) return new auto_attack_t( this, options_str ); if ( name == "crackling_jade_lightning" ) - return new crackling_jade_lightning_t( this, options_str ); + return make_action( options_str ); if ( name == "tiger_palm" ) - return new tiger_palm_t( this, options_str ); + return make_action( options_str ); if ( name == "blackout_kick" ) - return new blackout_kick_t( this, options_str ); + return make_action( options_str ); if ( name == "expel_harm" ) - return new expel_harm_t( this, options_str ); + return make_action( options_str ); if ( name == "leg_sweep" ) - return new leg_sweep_t( this, options_str ); + return make_action( options_str ); if ( name == "paralysis" ) - return new paralysis_t( this, options_str ); + return make_action( options_str ); if ( name == "rising_sun_kick" ) - return new rising_sun_kick_t( this, options_str ); + return make_action( options_str ); if ( name == "roll" ) - return new roll_t( this, options_str ); + return make_action( options_str ); if ( name == "spear_hand_strike" ) - return new spear_hand_strike_t( this, options_str ); + return make_action( options_str ); if ( name == "spinning_crane_kick" ) - return new spinning_crane_kick_t( this, options_str ); + return make_action( options_str ); if ( name == "vivify" ) - return new vivify_t( this, options_str ); + return make_action( options_str ); // Brewmaster if ( name == "breath_of_fire" ) - return new breath_of_fire_t( this, options_str ); + return make_action( options_str ); if ( name == "celestial_brew" ) - return new celestial_brew_t( this, options_str ); + return make_action( options_str ); if ( name == "exploding_keg" ) - return new exploding_keg_t( this, options_str ); + return make_action( options_str ); if ( name == "fortifying_brew" ) - return new fortifying_brew_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_niuzao" ) - return new niuzao_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_niuzao_the_black_ox" ) - return new niuzao_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "keg_smash" ) - return new press_the_advantage_t( this, options_str ); + return make_action>( options_str ); if ( name == "purifying_brew" ) - return new purifying_brew_t( this, options_str ); + return make_action( options_str ); if ( name == "provoke" ) - return new provoke_t( this, options_str ); + return make_action( options_str ); // Mistweaver if ( name == "enveloping_mist" ) - return new enveloping_mist_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_chiji" ) - return new chiji_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_chiji_the_red_crane" ) - return new chiji_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_yulon" ) - return new yulon_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_yulon_the_jade_serpent" ) - return new yulon_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "life_cocoon" ) - return new life_cocoon_t( this, options_str ); + return make_action( options_str ); if ( name == "mana_tea" ) - return new mana_tea_t( this, options_str ); - // if ( name == "renewing_mist" ) - // return new renewing_mist_t( this, options_str ); + return make_action( options_str ); if ( name == "revival" ) - return new revival_t( this, options_str ); + return make_action( options_str ); if ( name == "thunder_focus_tea" ) - return new thunder_focus_tea_t( this, options_str ); - // if ( name == "zen_pulse" ) - // return new zen_pulse_t( this, options_str ); + return make_action( options_str ); // Windwalker if ( name == "fists_of_fury" ) - return new fists_of_fury_t( this, options_str ); + return make_action( options_str ); if ( name == "flying_serpent_kick" ) - return new flying_serpent_kick_t( this, options_str ); + return make_action( options_str ); if ( name == "touch_of_karma" ) - return new touch_of_karma_t( this, options_str ); + return make_action( options_str ); if ( name == "touch_of_death" ) - return new touch_of_death_t( this, options_str ); + return make_action( options_str ); if ( name == "storm_earth_and_fire" ) - return new storm_earth_and_fire_t( this, options_str ); + return make_action( options_str ); if ( name == "storm_earth_and_fire_fixate" ) - return new storm_earth_and_fire_fixate_t( this, options_str ); + return make_action( options_str ); // Talents if ( name == "chi_burst" ) - return new chi_burst_t( this, options_str ); + return make_action( options_str ); if ( name == "chi_torpedo" ) - return new chi_torpedo_t( this, options_str ); + return make_action( options_str ); if ( name == "black_ox_brew" ) - return new black_ox_brew_t( this, options_str ); + return make_action( options_str ); if ( name == "dampen_harm" ) - return new dampen_harm_t( this, options_str ); + return make_action( options_str ); if ( name == "diffuse_magic" ) - return new diffuse_magic_t( this, options_str ); + return make_action( options_str ); if ( name == "strike_of_the_windlord" ) - return new strike_of_the_windlord_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_xuen" ) - return new xuen_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "invoke_xuen_the_white_tiger" ) - return new xuen_spell_t( this, options_str ); + return make_action( options_str ); if ( name == "refreshing_jade_wind" ) - return new refreshing_jade_wind_t( this, options_str ); + return make_action( options_str ); if ( name == "rushing_jade_wind" ) - return new rushing_jade_wind_t( this, options_str ); + return make_action( options_str ); if ( name == "whirling_dragon_punch" ) - return new whirling_dragon_punch_t( this, options_str ); + return make_action( options_str ); // Covenant Abilities if ( name == "jadefire_stomp" ) - return new jadefire_stomp_t( this, options_str ); + return make_action( options_str ); if ( name == "weapons_of_order" ) - return new weapons_of_order_t( this, options_str ); + return make_action( options_str ); // Hero Talents if ( name == "celestial_conduit" ) - return new celestial_conduit_t( this, options_str ); + return make_action( options_str ); if ( name == "unity_within" ) - return new unity_within_t( this, options_str ); + return make_action( options_str ); return base_t::create_action( name, options_str ); } @@ -9023,11 +9044,20 @@ void monk_t::copy_from( player_t *source ) } template -TAction *monk_t::make_action( Args &&...args ) -{ - if ( talent.windwalker.storm_earth_and_fire->ok() && false ) - return new actions::sef_action_t( this, std::forward( args )... ); - return new TAction( this, std::forward( args )... ); +action_t *monk_t::make_action( Args &&...args ) +{ + // 1. create action A no matter what + // 2. if the action should not be replicated (channeled, repeated action), return action A + // 3. if action A is affected by sef, create sef composite action B using action A + // 4. when executing, use sef composite action B execute as to override necessary behaviour + + TAction *parent = new TAction( this, std::forward( args )... ); + if ( parent->channeled ) + return parent; + if ( talent.windwalker.storm_earth_and_fire->ok() && + parent->data().affected_by( talent.windwalker.storm_earth_and_fire ) ) + return new actions::sef_action_t( parent, std::forward( args )... ); + return parent; } // monk_t::copy_from ========================================================= diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index c88a1aa7242..e5a0d8acdf2 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -1398,7 +1398,7 @@ struct monk_t : public stagger_t void create_options() override; void copy_from( player_t * ) override; template - TAction *make_action( Args &&...args ); + action_t *make_action( Args &&...args ); using player_t::find_action; action_t *find_action( unsigned int id ) const; resource_e primary_resource() const override;