From 501975e8de9a74f11952d4206d4df1df6564b618 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Fri, 26 Dec 2025 05:51:21 -0700 Subject: [PATCH 1/7] [monk] Initial updates for Invoke Xuen, Flurry of Xuen, Empowered Tiger Lightning. --- engine/class_modules/monk/sc_monk.cpp | 293 ++++++++------------- engine/class_modules/monk/sc_monk.hpp | 9 +- engine/class_modules/monk/sc_monk_pets.cpp | 20 +- 3 files changed, 120 insertions(+), 202 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 4868de83329..dbd458575fb 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -447,14 +447,15 @@ void monk_action_t::execute() } template -void monk_action_t::impact( action_state_t *s ) +void monk_action_t::impact( action_state_t *state ) { - trigger_mystic_touch( s ); + trigger_mystic_touch( state ); - base_t::impact( s ); + base_t::impact( state ); - if ( s->result_type == result_amount_type::DMG_DIRECT || s->result_type == result_amount_type::DMG_OVER_TIME ) - p()->trigger_empowered_tiger_lightning( s ); + if ( monk_td_t *target_data = p()->get_target_data( state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) + debug_cast( debuff.get_ref() )->trigger( state ); } template @@ -462,8 +463,9 @@ void monk_action_t::tick( dot_t *dot ) { base_t::tick( dot ); - if ( !base_t::result_is_miss( dot->state->result ) && dot->state->result_type == result_amount_type::DMG_OVER_TIME ) - p()->trigger_empowered_tiger_lightning( dot->state ); + if ( monk_td_t *target_data = p()->get_target_data( dot->state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) + debug_cast( debuff.get_ref() )->trigger( dot->state ); } template @@ -3389,16 +3391,15 @@ struct courage_of_the_white_tiger_t : conduit_of_the_celestials_container_t } }; -struct xuen_spell_t : public monk_spell_t +struct xuen_summon_t : public monk_spell_t { - xuen_spell_t( monk_t *p, std::string_view options_str ) - : monk_spell_t( p, "invoke_xuen_the_white_tiger", p->talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger ) + xuen_summon_t( monk_t *player, std::string_view options_str ) + : monk_spell_t( player, "invoke_xuen_the_white_tiger", + player->talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger ) { parse_options( options_str ); cast_during_sck = true; - // Specifically set for 10.1 class trinket - harmful = true; } void execute() override @@ -3406,54 +3407,34 @@ struct xuen_spell_t : public monk_spell_t monk_spell_t::execute(); if ( p()->bugs ) - { - // BUG: Invoke Xuen and Fury of Xuen reset both damage cache to 0 when either spawn for ( auto target : p()->sim->target_non_sleeping_list ) - { - auto td = p()->get_target_data( target ); - if ( td ) - { + if ( auto *td = p()->get_target_data( target ); td ) td->debuff.empowered_tiger_lightning->current_value = 0; - } - } - } p()->pets.xuen.spawn( p()->talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger->duration(), 1 ); - p()->buff.invoke_xuen->trigger(); - - if ( p()->talent.windwalker.flurry_of_xuen->ok() ) - p()->buff.flurry_of_xuen->trigger(); - + p()->buff.flurry_of_xuen->trigger(); p()->buff.courage_of_the_white_tiger->trigger(); } }; struct empowered_tiger_lightning_t : public monk_spell_t { - empowered_tiger_lightning_t( monk_t *p ) - : monk_spell_t( p, "empowered_tiger_lightning", p->baseline.windwalker.empowered_tiger_lightning_damage ) + empowered_tiger_lightning_t( monk_t *player ) + : monk_spell_t( player, "empowered_tiger_lightning", player->baseline.windwalker.empowered_tiger_lightning_damage ) { background = true; - may_crit = false; - } - - bool ready() override - { - return p()->baseline.windwalker.empowered_tiger_lightning->ok(); } }; struct flurry_of_xuen_t : public monk_spell_t { - flurry_of_xuen_t( monk_t *p ) - : monk_spell_t( p, "flurry_of_xuen", p->talent.windwalker.flurry_of_xuen_driver->effectN( 1 ).trigger() ) + flurry_of_xuen_t( monk_t *player ) + : monk_spell_t( player, "flurry_of_xuen", player->talent.windwalker.flurry_of_xuen_driver->effectN( 1 ).trigger() ) { - background = true; - may_crit = true; - + background = true; aoe = -1; - reduced_aoe_targets = p->talent.windwalker.flurry_of_xuen->effectN( 2 ).base_value(); + reduced_aoe_targets = player->talent.windwalker.flurry_of_xuen->effectN( 2 ).base_value(); } }; @@ -4254,63 +4235,83 @@ struct rushing_jade_wind_buff_t : public monk_buff_t<> struct invoke_xuen_the_white_tiger_buff_t : public monk_buff_t<> { - static void invoke_xuen_callback( buff_t *b, int, timespan_t ) + double multiplier; + action_t *etl; + + invoke_xuen_the_white_tiger_buff_t( monk_t *player, std::string_view name, const spell_data_t *spell_data ) + : monk_buff_t( player, name, spell_data ), multiplier( 0.0 ), etl( nullptr ) { - auto *p = debug_cast( b->player ); - if ( p->baseline.windwalker.empowered_tiger_lightning->ok() ) - { - double empowered_tiger_lightning_multiplier = - p->baseline.windwalker.empowered_tiger_lightning->effectN( 2 ).percent(); + set_cooldown( timespan_t::zero() ); + set_period( spell_data->effectN( 2 ).period() ); - for ( auto target : p->sim->target_non_sleeping_list ) - { - if ( p->find_target_data( target ) ) - { - auto td = p->get_target_data( target ); - if ( td->debuff.empowered_tiger_lightning->up() ) - { - double value = td->debuff.empowered_tiger_lightning->check_value(); - td->debuff.empowered_tiger_lightning->current_value = 0; - if ( value > 0 ) + if ( !player->baseline.windwalker.empowered_tiger_lightning->ok() ) + return; + + multiplier = player->baseline.windwalker.empowered_tiger_lightning->effectN( 2 ).percent(); + // defer etl action lookup until callback invocation, as (background) actions + // are not yet constructed (see: sim_t::init_actor()) + + set_tick_callback( [ & ]( buff_t *, int, timespan_t ) { + if ( !etl ) + etl = p().find_action( "empowered_tiger_lightning" ); + + for ( player_t *target : p().sim->target_non_sleeping_list ) + if ( monk_td_t *target_data = p().get_target_data( target ); target_data ) + if ( buff_t *debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) + if ( double value = debuff->check_value(); value ) { - p->action.empowered_tiger_lightning->base_dd_min = value * empowered_tiger_lightning_multiplier; - p->action.empowered_tiger_lightning->base_dd_max = value * empowered_tiger_lightning_multiplier; - p->action.empowered_tiger_lightning->execute_on_target( target ); + etl->base_dd_min = etl->base_dd_max = value * multiplier; + etl->execute_on_target( target ); + debuff->current_value = 0; } - } - } - } - } + } ); } +}; - invoke_xuen_the_white_tiger_buff_t( monk_t *p, std::string_view n, const spell_data_t *s ) : monk_buff_t( p, n, s ) - { - set_cooldown( timespan_t::zero() ); - set_duration( s->duration() ); +empowered_tiger_lightning_t::empowered_tiger_lightning_t( monk_td_t &target_data ) + : monk_buff_t( &target_data, "empowered_tiger_lightning", spell_data_t::nil() ) +{ + set_quiet( true ); + set_trigger_spell( target_data.monk.baseline.windwalker.empowered_tiger_lightning ); + set_duration( 5_s ); + set_default_value( 0.0 ); +} - set_period( s->effectN( 2 ).period() ); +bool empowered_tiger_lightning_t::trigger( const action_state_t *state ) +{ + if ( !action_t::result_is_miss( state->result ) ) + return false; - set_tick_callback( invoke_xuen_callback ); - } + const std::array blacklist = { + p().baseline.monk.touch_of_death->id(), + p().baseline.windwalker.empowered_tiger_lightning_damage->id(), + p().baseline.windwalker.touch_of_karma_tick->id(), + p().talent.windwalker.skyfire_heel_damage->id(), + }; - bool trigger( int stacks, double value, double chance, timespan_t duration ) override + switch ( state->result_type ) { - if ( buff_t::trigger( stacks, value, chance, duration ) ) - { - if ( p().talent.conduit_of_the_celestials.restore_balance->ok() ) - p().buff.rushing_jade_wind->trigger( remains() ); + case result_amount_type::DMG_DIRECT: + case result_amount_type::DMG_OVER_TIME: + if ( !state->result_amount ) + return false; - return true; - } + if ( !p().buff.invoke_xuen->check() ) + return false; - return false; - } + if ( range::contains( blacklist, state->action->id ) ) + return false; - void expire_override( int expiration_stacks, timespan_t remaining_duration ) override - { - monk_buff_t::expire_override( expiration_stacks, remaining_duration ); + if ( check() ) + current_value += state->result_amount; + else + trigger( 1, state->result_amount ); + + return true; + default: + return false; } -}; +} struct touch_of_death_ww_buff_t : public monk_buff_t<> { @@ -4625,64 +4626,38 @@ absorb_buff_t *fractional_absorb_t::set_absorb_fraction( double fraction ) return this; } } // namespace buffs - -namespace items -{ -void do_trinket_init( monk_t *player, specialization_e spec, const special_effect_t *&ptr, - const special_effect_t &effect ) -{ - // Ensure we have the spell data. This will prevent the trinket effect from working on live - // Simulationcraft. Also ensure correct specialization. - if ( !player->find_spell( effect.spell_id )->ok() || player->specialization() != spec ) - { - return; - } - - // Set pointer, module considers non-null pointer to mean the effect is "enabled" - ptr = &( effect ); -} - -void init() -{ -} -} // namespace items } // end namespace monk namespace monk { -monk_td_t::monk_td_t( player_t *target, monk_t *p ) : actor_target_data_t( target, p ), dot(), debuff(), monk( *p ) +monk_td_t::monk_td_t( player_t *target, monk_t *player ) + : actor_target_data_t( target, player ), dot(), debuff(), monk( *player ) { // Windwalker - debuff.empowered_tiger_lightning = make_buff_fallback( p->specialization() == MONK_WINDWALKER, *this, - "empowered_tiger_lightning", spell_data_t::nil() ) - ->set_trigger_spell( p->baseline.windwalker.empowered_tiger_lightning ) - ->set_quiet( true ) - ->set_cooldown( timespan_t::zero() ) - ->set_refresh_behavior( buff_refresh_behavior::NONE ) - ->set_max_stack( 1 ) - ->set_default_value( 0 ); + debuff.empowered_tiger_lightning = make_buff_fallback( + player->baseline.windwalker.empowered_tiger_lightning->ok(), *this, "empowered_tiger_lightning" ); // Brewmaster - debuff.keg_smash = - make_buff_fallback( p->talent.brewmaster.keg_smash->ok(), *this, "keg_smash", p->talent.brewmaster.keg_smash ) - ->set_cooldown( timespan_t::zero() ) - ->set_default_value_from_effect( 3 ); - - debuff.exploding_keg = make_buff_fallback( p->talent.brewmaster.exploding_keg->ok(), *this, "exploding_keg_debuff", - p->talent.brewmaster.exploding_keg ) - ->set_trigger_spell( p->talent.brewmaster.exploding_keg ) + debuff.keg_smash = make_buff_fallback( player->talent.brewmaster.keg_smash->ok(), *this, "keg_smash", + player->talent.brewmaster.keg_smash ) + ->set_cooldown( timespan_t::zero() ) + ->set_default_value_from_effect( 3 ); + + debuff.exploding_keg = make_buff_fallback( player->talent.brewmaster.exploding_keg->ok(), *this, + "exploding_keg_debuff", player->talent.brewmaster.exploding_keg ) + ->set_trigger_spell( player->talent.brewmaster.exploding_keg ) ->set_cooldown( timespan_t::zero() ); // Shado-Pan - debuff.high_impact = make_buff_fallback( p->talent.shado_pan.high_impact->ok(), *this, "high_impact", - p->talent.shado_pan.high_impact_debuff ) - ->set_trigger_spell( p->talent.shado_pan.high_impact ) + debuff.high_impact = make_buff_fallback( player->talent.shado_pan.high_impact->ok(), *this, "high_impact", + player->talent.shado_pan.high_impact_debuff ) + ->set_trigger_spell( player->talent.shado_pan.high_impact ) ->set_quiet( true ); - dot.breath_of_fire = target->get_dot( "breath_of_fire_dot", p ); - dot.crackling_jade_lightning_aoe = target->get_dot( "crackling_jade_lightning_aoe", p ); - dot.aspect_of_harmony = target->get_dot( "aspect_of_harmony_damage", p ); + dot.breath_of_fire = target->get_dot( "breath_of_fire_dot", player ); + dot.crackling_jade_lightning_aoe = target->get_dot( "crackling_jade_lightning_aoe", player ); + dot.aspect_of_harmony = target->get_dot( "aspect_of_harmony_damage", player ); } monk_t::monk_t( sim_t *sim, std::string_view name, race_e r ) @@ -4850,9 +4825,9 @@ action_t *monk_t::create_action( std::string_view name, std::string_view options if ( name == "strike_of_the_windlord" ) return new strike_of_the_windlord_t( this, options_str ); if ( name == "invoke_xuen" ) - return new xuen_spell_t( this, options_str ); + return new xuen_summon_t( this, options_str ); if ( name == "invoke_xuen_the_white_tiger" ) - return new xuen_spell_t( this, options_str ); + return new xuen_summon_t( this, options_str ); if ( name == "rushing_jade_wind" ) return new rushing_jade_wind_t( this, options_str ); if ( name == "whirling_dragon_punch" ) @@ -6680,65 +6655,6 @@ void monk_t::create_actions() buff.aspect_of_harmony.construct_actions( this ); } -void monk_t::trigger_empowered_tiger_lightning( action_state_t *s ) -{ - /* - * From discovery by the Peak of Serenity Discord server, ETL has two remaining bugs - * 1.) If both tigers are up the damage cache is a shared pool for both tigers and resets to 0 when either spawn - */ - - if ( specialization() != MONK_WINDWALKER || !baseline.windwalker.empowered_tiger_lightning->ok() ) - return; - - if ( s->result_amount <= 0 ) - return; - - // Proc cannot proc from itself - if ( s->action->id == baseline.windwalker.empowered_tiger_lightning->id() ) - return; - - auto td = get_target_data( s->target ); - - if ( !td ) - return; - - // These abilities are always blacklisted by both tigers - auto etl_blacklist = { - 122470, // Touch of Karma - 451585, // Gale Force - 450615, // Flurry Strikes - 115129, // Expel Harm - 389541, // White Tiger State - }; - - for ( unsigned int id : etl_blacklist ) - if ( s->action->id == id ) - return; - - // 1 = Xuen, 2 = FoX, 3 = Both - auto mode = buff.invoke_xuen->check(); - - if ( mode == 0 ) - return; - - double xuen_contribution = mode != 2 ? s->result_amount : 0; - double fox_contribution = mode > 1 ? s->result_amount : 0; - - // Return value - double amount = xuen_contribution + fox_contribution; - - if ( amount > 0 ) - { - auto cache = td->debuff.empowered_tiger_lightning; - auto duration = buff.invoke_xuen->remains(); - - if ( cache->check() ) - cache->current_value += amount; - else - cache->trigger( -1, amount, -1, duration ); - } -} - std::unique_ptr monk_t::create_expression( std::string_view name_str ) { auto splits = util::string_split( name_str, "." ); @@ -6852,7 +6768,6 @@ struct monk_module_t : public module_t void static_init() const override { - items::init(); } void register_hotfixes() const override diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 3457763d7b7..dd5b030ac48 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -264,6 +264,14 @@ struct gift_of_the_ox_t : monk_buff_t<> void reset(); }; +struct empowered_tiger_lightning_t : monk_buff_t<> +{ + empowered_tiger_lightning_t( monk_td_t & ); + + using monk_buff_t<>::trigger; + bool trigger( const action_state_t * ); +}; + struct aspect_of_harmony_t { private: @@ -1184,7 +1192,6 @@ struct monk_t : public stagger_t // Actions void trigger_celestial_fortune( action_state_t * ); - void trigger_empowered_tiger_lightning( action_state_t * ); }; namespace events diff --git a/engine/class_modules/monk/sc_monk_pets.cpp b/engine/class_modules/monk/sc_monk_pets.cpp index aeb9c89bc24..4f2609a8554 100644 --- a/engine/class_modules/monk/sc_monk_pets.cpp +++ b/engine/class_modules/monk/sc_monk_pets.cpp @@ -86,26 +86,22 @@ struct pet_action_base_t : public BASE return debug_cast( this->player ); } - void impact( action_state_t *s ) override + void impact( action_state_t *state ) override { - super_t::impact( s ); - - if ( s->result_type != result_amount_type::DMG_DIRECT && s->result_type != result_amount_type::DMG_OVER_TIME ) - return; - - o()->trigger_empowered_tiger_lightning( s ); + super_t::impact( state ); - if ( super_t::result_is_miss( s->result ) || s->result_amount <= 0.0 ) - return; + if ( monk_td_t *target_data = o()->get_target_data( state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) + debug_cast( debuff.get_ref() )->trigger( state ); } void tick( dot_t *dot ) override { super_t::tick( dot ); - if ( !super_t::result_is_miss( dot->state->result ) && - dot->state->result_type == result_amount_type::DMG_OVER_TIME ) - o()->trigger_empowered_tiger_lightning( dot->state ); + if ( monk_td_t *target_data = o()->get_target_data( dot->state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) + debug_cast( debuff.get_ref() )->trigger( dot->state ); } }; From 2753eb652cd7901bcfae12328ba9b40a078f943d Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Sun, 28 Dec 2025 13:31:11 -0700 Subject: [PATCH 2/7] [monk] Fix ETL trigger behaviour. --- engine/class_modules/monk/sc_monk.cpp | 39 +++++++++++----------- engine/class_modules/monk/sc_monk_pets.cpp | 14 ++++---- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index dbd458575fb..9621eec7a94 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -453,9 +453,10 @@ void monk_action_t::impact( action_state_t *state ) base_t::impact( state ); - if ( monk_td_t *target_data = p()->get_target_data( state->target ); target_data ) - if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) - debug_cast( debuff.get_ref() )->trigger( state ); + if ( p()->baseline.windwalker.empowered_tiger_lightning->ok() ) + if ( monk_td_t *target_data = p()->get_target_data( state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff ) + debug_cast( debuff.get() )->trigger( state ); } template @@ -463,9 +464,10 @@ void monk_action_t::tick( dot_t *dot ) { base_t::tick( dot ); - if ( monk_td_t *target_data = p()->get_target_data( dot->state->target ); target_data ) - if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) - debug_cast( debuff.get_ref() )->trigger( dot->state ); + if ( p()->baseline.windwalker.empowered_tiger_lightning->ok() ) + if ( monk_td_t *target_data = p()->get_target_data( dot->state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff ) + debug_cast( debuff.get() )->trigger( dot->state ); } template @@ -4279,9 +4281,6 @@ empowered_tiger_lightning_t::empowered_tiger_lightning_t( monk_td_t &target_data bool empowered_tiger_lightning_t::trigger( const action_state_t *state ) { - if ( !action_t::result_is_miss( state->result ) ) - return false; - const std::array blacklist = { p().baseline.monk.touch_of_death->id(), p().baseline.windwalker.empowered_tiger_lightning_damage->id(), @@ -4289,24 +4288,26 @@ bool empowered_tiger_lightning_t::trigger( const action_state_t *state ) p().talent.windwalker.skyfire_heel_damage->id(), }; + if ( action_t::result_is_miss( state->result ) ) + return false; + + if ( !state->result_amount ) + return false; + + if ( !p().buff.invoke_xuen->check() ) + return false; + + if ( range::contains( blacklist, state->action->id ) ) + return false; + switch ( state->result_type ) { case result_amount_type::DMG_DIRECT: case result_amount_type::DMG_OVER_TIME: - if ( !state->result_amount ) - return false; - - if ( !p().buff.invoke_xuen->check() ) - return false; - - if ( range::contains( blacklist, state->action->id ) ) - return false; - if ( check() ) current_value += state->result_amount; else trigger( 1, state->result_amount ); - return true; default: return false; diff --git a/engine/class_modules/monk/sc_monk_pets.cpp b/engine/class_modules/monk/sc_monk_pets.cpp index 4f2609a8554..ab6ad2a5935 100644 --- a/engine/class_modules/monk/sc_monk_pets.cpp +++ b/engine/class_modules/monk/sc_monk_pets.cpp @@ -90,18 +90,20 @@ struct pet_action_base_t : public BASE { super_t::impact( state ); - if ( monk_td_t *target_data = o()->get_target_data( state->target ); target_data ) - if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) - debug_cast( debuff.get_ref() )->trigger( state ); + if ( o()->baseline.windwalker.empowered_tiger_lightning->ok() ) + if ( monk_td_t *target_data = o()->get_target_data( state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff ) + debug_cast( debuff.get() )->trigger( state ); } void tick( dot_t *dot ) override { super_t::tick( dot ); - if ( monk_td_t *target_data = o()->get_target_data( dot->state->target ); target_data ) - if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff && debuff->up() ) - debug_cast( debuff.get_ref() )->trigger( dot->state ); + if ( o()->baseline.windwalker.empowered_tiger_lightning->ok() ) + if ( monk_td_t *target_data = o()->get_target_data( dot->state->target ); target_data ) + if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff ) + debug_cast( debuff.get() )->trigger( dot->state ); } }; From 49c84c9b59c70bbb65d5419f07b9db1e8f097b89 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:32:10 -0700 Subject: [PATCH 3/7] [monk] Clean up Ferociousness. Implement Restore Balance. --- engine/class_modules/monk/sc_monk.cpp | 37 ++++++++++----------------- engine/class_modules/monk/sc_monk.hpp | 1 - 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 9621eec7a94..3d319189367 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -140,6 +140,7 @@ void monk_action_t::apply_buff_effects() parse_effects( p()->buff.combo_breaker, affect_list_t( 1, 2, 3 ).remove_spell( p()->talent.windwalker.teachings_of_the_monastery_blackout_kick->id() ) ); parse_effects( p()->buff.zenith ); + parse_effects( p()->buff.invoke_xuen, effect_mask_t( false ).enable( 3 ), "Ferociousness" ); // Conduit of the Celestials parse_effects( p()->buff.heart_of_the_jade_serpent_cdr, @@ -148,6 +149,8 @@ void monk_action_t::apply_buff_effects() parse_effects( p()->tier.tww3.coc_2pc_heart_of_the_jade_serpent ); parse_effects( p()->buff.jade_sanctuary ); parse_effects( p()->buff.strength_of_the_black_ox ); + if ( p()->talent.conduit_of_the_celestials.restore_balance->ok() ) + parse_effects( p()->buff.invoke_xuen, effect_mask_t( false ).enable( 4, 5 ), "Restore Balance" ); // Master of Harmony // TODO: parse_effects implementation for A_MOD_HEALING_RECEIVED_FROM_SPELL (283) @@ -4722,7 +4725,12 @@ void monk_t::parse_player_effects() // windwalker talent auras parse_effects( buff.memory_of_the_monastery ); parse_effects( buff.momentum_boost_speed ); - parse_effects( buff.ferociousness, USE_CURRENT ); + parse_effects( talent.windwalker.ferociousness, [ & ]( double value ) { + if ( buff.invoke_xuen->check() ) + value *= 1.0 + talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger->effectN( 3 ).percent(); + + return value; + } ); // Shadopan @@ -5470,6 +5478,8 @@ void monk_t::init_spells() ? effect_mask_t( true ).disable( 5 ) : effect_mask_t( true ) ); + deregister_passive_spell( talent.windwalker.ferociousness ); + parse_all_class_passives(); parse_all_passive_talents(); parse_all_passive_sets(); @@ -5791,22 +5801,6 @@ void monk_t::create_buffs() ->set_duration( timespan_t::from_seconds( 1.5 ) ) ->set_quiet( true ); - buff.ferociousness = make_buff_fallback( talent.windwalker.ferociousness->ok(), this, "ferociousness", - talent.windwalker.ferociousness ) - ->set_quiet( true ) - ->set_default_value_from_effect( 1 ) - ->set_tick_callback( [ this ]( buff_t *self, int, timespan_t ) { - double previous = self->current_value; - self->current_value = self->default_value; - if ( pets.xuen.n_active_pets() ) - self->current_value *= 1 + self->data().effectN( 2 ).percent(); - if ( previous != self->current_value ) - invalidate_cache( CACHE_CRIT_CHANCE ); - } ) - ->set_period( 1_s ) - ->set_freeze_stacks( true ) - ->set_tick_behavior( buff_tick_behavior::CLIP ); - buff.hit_combo = make_buff_fallback( talent.windwalker.hit_combo->ok(), this, "hit_combo", talent.windwalker.hit_combo_buff ) ->set_default_value_from_effect( 1 ); @@ -5820,8 +5814,9 @@ void monk_t::create_buffs() ->set_freeze_stacks( true ); buff.invoke_xuen = make_buff_fallback( - talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger->ok(), this, "invoke_xuen_the_white_tiger", - talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger ); + talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger->ok(), this, + "invoke_xuen_the_white_tiger", talent.conduit_of_the_celestials.invoke_xuen_the_white_tiger ) + ->add_invalidate( CACHE_CRIT_CHANCE ); buff.memory_of_the_monastery = make_buff_fallback( talent.windwalker.memory_of_the_monastery->ok(), this, "memory_of_the_monastery", @@ -6522,10 +6517,6 @@ stat_e monk_t::convert_hybrid_stat( stat_e s ) const void monk_t::combat_begin() { - // Trigger Ferociousness precombat - if ( talent.windwalker.ferociousness->ok() ) - buff.ferociousness->trigger(); - base_t::combat_begin(); if ( talent.monk.chi_wave->ok() ) diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index dd5b030ac48..122b09e0dd5 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -507,7 +507,6 @@ struct monk_t : public stagger_t propagate_const combo_strikes; propagate_const dance_of_chiji; propagate_const dance_of_chiji_hidden; // Used for trigger DoCJ ticks - propagate_const ferociousness; propagate_const hit_combo; propagate_const flurry_of_xuen; propagate_const invoke_xuen; From ff74ef35401f9f475570fd20c412d3cc42042611 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Wed, 31 Dec 2025 19:36:57 -0700 Subject: [PATCH 4/7] [monk] Convert ETL proc for player into proc callback. --- engine/class_modules/monk/sc_monk.cpp | 35 +++++++++++++++------------ engine/class_modules/monk/sc_monk.hpp | 1 - 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 3d319189367..76503007878 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -455,22 +455,6 @@ void monk_action_t::impact( action_state_t *state ) trigger_mystic_touch( state ); base_t::impact( state ); - - if ( p()->baseline.windwalker.empowered_tiger_lightning->ok() ) - if ( monk_td_t *target_data = p()->get_target_data( state->target ); target_data ) - if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff ) - debug_cast( debuff.get() )->trigger( state ); -} - -template -void monk_action_t::tick( dot_t *dot ) -{ - base_t::tick( dot ); - - if ( p()->baseline.windwalker.empowered_tiger_lightning->ok() ) - if ( monk_td_t *target_data = p()->get_target_data( dot->state->target ); target_data ) - if ( auto debuff = target_data->debuff.empowered_tiger_lightning; debuff ) - debug_cast( debuff.get() )->trigger( dot->state ); } template @@ -6341,6 +6325,25 @@ void monk_t::init_special_effects() action.flurry_strikes->execute( actions::attacks::flurry_strikes_t::STAND_READY ); } ); + if ( baseline.windwalker.empowered_tiger_lightning->ok() ) + { + auto cb_t = create_proc_callback( { baseline.windwalker.empowered_tiger_lightning, PF_ALL_DAMAGE, + static_cast( PF2_ALL_HIT | PF2_PERIODIC_DAMAGE ) } ) + ->register_callback_execute_function( + [ & ]( const dbc_proc_callback_t *, action_t *, action_state_t *state ) { + monk_td_t *target_data = get_target_data( state->target ); + if ( !target_data ) + return; + + propagate_const debuff = target_data->debuff.empowered_tiger_lightning; + if ( !debuff ) + return; + + debug_cast( debuff.get() )->trigger( state ); + } ); + cb_t->proc_chance = 1.0; + } + base_t::init_special_effects(); } diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 122b09e0dd5..ad08c377396 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -106,7 +106,6 @@ struct monk_action_t : public parse_action_effects_t void consume_resource() override; void execute() override; void impact( action_state_t *state ) override; - void tick( dot_t *dot ) override; void trigger_mystic_touch( action_state_t *state ); }; From 1c55748adb1f0d515cd19cd0a569744b7b73614b Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Wed, 31 Dec 2025 19:47:56 -0700 Subject: [PATCH 5/7] [monk] Update Celestial Conduit availability. --- engine/class_modules/monk/sc_monk.cpp | 10 ++++------ engine/class_modules/monk/sc_monk.hpp | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 76503007878..76f9ed9f92f 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -3404,6 +3404,7 @@ struct xuen_summon_t : public monk_spell_t p()->buff.invoke_xuen->trigger(); p()->buff.flurry_of_xuen->trigger(); p()->buff.courage_of_the_white_tiger->trigger(); + p()->buff.celestial_conduit->trigger(); } }; @@ -3603,10 +3604,8 @@ struct celestial_conduit_t : public monk_spell_t bool ready() override { - if ( p()->talent.conduit_of_the_celestials.celestial_conduit->ok() ) - return monk_spell_t::ready(); - - return false; + return p()->talent.conduit_of_the_celestials.celestial_conduit->ok() && p()->buff.celestial_conduit->check() && + monk_spell_t::ready(); } bool usable_moving() const override @@ -5308,7 +5307,6 @@ void monk_t::init_spells() talent.conduit_of_the_celestials.jade_sanctuary_buff = find_spell( 448508 ); talent.conduit_of_the_celestials.celestial_conduit = _HT( "Celestial Conduit" ); talent.conduit_of_the_celestials.celestial_conduit_action = find_spell( 443028 ); - talent.conduit_of_the_celestials.celestial_conduit_buff = find_spell( 443028 ); talent.conduit_of_the_celestials.celestial_conduit_damage = find_spell( 443038 ); talent.conduit_of_the_celestials.celestial_conduit_heal = find_spell( 443039 ); talent.conduit_of_the_celestials.inner_compass = _HT( "Inner Compass" ); @@ -5835,7 +5833,7 @@ void monk_t::create_buffs() // Conduit of the Celestials buff.celestial_conduit = make_buff_fallback( talent.conduit_of_the_celestials.celestial_conduit->ok(), this, "celestial_conduit", - talent.conduit_of_the_celestials.celestial_conduit_buff ); + talent.conduit_of_the_celestials.celestial_conduit->effectN( 1 ).trigger() ); buff.chijis_swiftness = make_buff_fallback( talent.conduit_of_the_celestials.chijis_swiftness->ok(), this, "chijis_swiftness", diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index ad08c377396..66083b2268d 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -963,7 +963,6 @@ struct monk_t : public stagger_t const spell_data_t *jade_sanctuary_buff; player_talent_t celestial_conduit; const spell_data_t *celestial_conduit_action; - const spell_data_t *celestial_conduit_buff; const spell_data_t *celestial_conduit_damage; const spell_data_t *celestial_conduit_heal; player_talent_t inner_compass; From 26f877d972f8660cb2040fe3a45f7a61ef7e3e29 Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Wed, 31 Dec 2025 20:13:42 -0700 Subject: [PATCH 6/7] [monk] Fix debuff expiry to avoid dropped damage. --- engine/class_modules/monk/sc_monk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index 76f9ed9f92f..a827d5a8fb7 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -4250,7 +4250,7 @@ struct invoke_xuen_the_white_tiger_buff_t : public monk_buff_t<> { etl->base_dd_min = etl->base_dd_max = value * multiplier; etl->execute_on_target( target ); - debuff->current_value = 0; + debuff->expire(); } } ); } From a2291350c81a8ae603c8a6f71e0bd8d612ca95ad Mon Sep 17 00:00:00 2001 From: Kate Martin <51387586+renanthera@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:52:06 -0700 Subject: [PATCH 7/7] [monk] Fix up ETL proc behaviour. --- engine/class_modules/monk/sc_monk.cpp | 50 ++++++++++++++++++--------- engine/class_modules/monk/sc_monk.hpp | 8 +++++ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/engine/class_modules/monk/sc_monk.cpp b/engine/class_modules/monk/sc_monk.cpp index a827d5a8fb7..0b04173a097 100644 --- a/engine/class_modules/monk/sc_monk.cpp +++ b/engine/class_modules/monk/sc_monk.cpp @@ -6064,6 +6064,21 @@ void monk_effect_callback_t::execute( action_t *action, action_state_t *state ) dbc_proc_callback_t::execute( action, state ); } +void monk_effect_callback_t::initialize() +{ + dbc_proc_callback_t::initialize(); + + for ( const monk_effect_callback_t::post_init_callback_fn_t &fn : post_init_callbacks ) + fn( this ); +} + +monk_effect_callback_t *monk_effect_callback_t::register_post_init_callback( + const monk_effect_callback_t::post_init_callback_fn_t &fn ) +{ + post_init_callbacks.emplace_back( std::move( fn ) ); + return this; +} + monk_effect_callback_t *monk_effect_callback_t::register_callback_trigger_function( dbc_proc_callback_t::trigger_fn_type t, const dbc_proc_callback_t::trigger_fn_t &fn ) { @@ -6324,23 +6339,24 @@ void monk_t::init_special_effects() } ); if ( baseline.windwalker.empowered_tiger_lightning->ok() ) - { - auto cb_t = create_proc_callback( { baseline.windwalker.empowered_tiger_lightning, PF_ALL_DAMAGE, - static_cast( PF2_ALL_HIT | PF2_PERIODIC_DAMAGE ) } ) - ->register_callback_execute_function( - [ & ]( const dbc_proc_callback_t *, action_t *, action_state_t *state ) { - monk_td_t *target_data = get_target_data( state->target ); - if ( !target_data ) - return; - - propagate_const debuff = target_data->debuff.empowered_tiger_lightning; - if ( !debuff ) - return; - - debug_cast( debuff.get() )->trigger( state ); - } ); - cb_t->proc_chance = 1.0; - } + create_proc_callback( { baseline.windwalker.empowered_tiger_lightning, PF_ALL_DAMAGE, + static_cast( PF2_ALL_HIT | PF2_PERIODIC_DAMAGE ) } ) + ->register_callback_execute_function( [ & ]( const dbc_proc_callback_t *, action_t *, action_state_t *state ) { + monk_td_t *target_data = get_target_data( state->target ); + if ( !target_data ) + return; + + propagate_const debuff = target_data->debuff.empowered_tiger_lightning; + if ( !debuff ) + return; + + debug_cast( debuff.get() )->trigger( state ); + } ) + ->register_post_init_callback( []( monk_effect_callback_t *cb ) { + cb->proc_chance = 1.0; + cb->can_proc_from_procs = true; + cb->can_only_proc_from_class_abilites = true; + } ); base_t::init_special_effects(); } diff --git a/engine/class_modules/monk/sc_monk.hpp b/engine/class_modules/monk/sc_monk.hpp index 66083b2268d..40ba540958d 100644 --- a/engine/class_modules/monk/sc_monk.hpp +++ b/engine/class_modules/monk/sc_monk.hpp @@ -379,10 +379,18 @@ struct monk_effect_callback_t : dbc_proc_callback_t { monk_t *player; + using post_init_callback_fn_t = std::function; + +private: + std::vector post_init_callbacks; + +public: monk_effect_callback_t( const special_effect_t &effect, monk_t *player ); void trigger( action_t *action, action_state_t *state ) override; void execute( action_t *action, action_state_t *state ) override; + void initialize() override; + monk_effect_callback_t *register_post_init_callback( const post_init_callback_fn_t &fn ); monk_effect_callback_t *register_callback_trigger_function( dbc_proc_callback_t::trigger_fn_type t, const dbc_proc_callback_t::trigger_fn_t &fn ); monk_effect_callback_t *register_callback_execute_function( const dbc_proc_callback_t::execute_fn_t &fn );