diff --git a/engine/class_modules/sc_rogue.cpp b/engine/class_modules/sc_rogue.cpp
index 81a2c4893c7..449d143f9a6 100644
--- a/engine/class_modules/sc_rogue.cpp
+++ b/engine/class_modules/sc_rogue.cpp
@@ -2224,6 +2224,8 @@ class rogue_action_t : public Base
void trigger_doomblade( const action_state_t* );
void trigger_echoing_reprimand( const action_state_t* state );
void trigger_fatal_flourish( const action_state_t* );
+ void resolve_fatebound_coinflip( player_t* coin_target, fatebound_t::coinflip_e result );
+ void trigger_fatebound_coinflip( player_t* coin_target, fatebound_t::coinflip_e result, timespan_t delay = timespan_t::zero() );
void trigger_fatebound_coinflip( const action_state_t* state, fatebound_t::coinflip_e result, timespan_t delay = timespan_t::zero() );
void trigger_fatebound_edge_case( const action_state_t* state );
void trigger_fazed( const action_state_t* state );
@@ -8029,88 +8031,87 @@ void actions::rogue_action_t::trigger_hand_of_fate( const action_state_t*
if ( cast_state( state )->get_combo_points() < p()->talent.fatebound.hand_of_fate->effectN( 1 ).base_value() )
return;
- fatebound_t::coinflip_e result;
+ auto coin_target = state->target->is_enemy() ? state->target : p()->target;
+ make_event( *p()->sim, current_delay, [ this, coin_target, biased ] {
+ fatebound_t::coinflip_e result;
- // No stacks of either buff or equal stacks of both buffs (thanks to only using edge case)
- // Nothing to bias, just flip the coin fairly
- if ( p()->buffs.fatebound_coin_tails->total_stack() == p()->buffs.fatebound_coin_heads->total_stack() )
- {
- result = p()->rng().roll( 0.5 ) ? fatebound_t::coinflip_e::HEADS : fatebound_t::coinflip_e::TAILS;
- }
- // Flip the coin, potentially with a bias toward matching the last face flipped
- else
- {
- double matching_odds = 0.5;
- if ( biased )
+ // No stacks of either buff or equal stacks of both buffs (thanks to only using edge case)
+ // Nothing to bias, just flip the coin fairly
+ if ( p()->buffs.fatebound_coin_tails->total_stack() == p()->buffs.fatebound_coin_heads->total_stack() )
{
- // TODO: Validate how these stack with the presumed base 50/50 chance and one another
- if ( p()->talent.fatebound.mean_streak->ok() )
- {
- matching_odds += matching_odds * p()->talent.fatebound.mean_streak->effectN( 1 ).percent();
- }
- if ( p()->talent.fatebound.destiny_defined->ok() )
+ result = p()->rng().roll( 0.5 ) ? fatebound_t::coinflip_e::HEADS : fatebound_t::coinflip_e::TAILS;
+ }
+ // Flip the coin, potentially with a bias toward matching the last face flipped
+ else
+ {
+ double matching_odds = 0.5;
+ if ( biased )
{
- matching_odds += p()->talent.fatebound.destiny_defined->effectN( 3 ).percent();
+ // TODO: Validate how these stack with the presumed base 50/50 chance and one another
+ if ( p()->talent.fatebound.mean_streak->ok() )
+ {
+ matching_odds += matching_odds * p()->talent.fatebound.mean_streak->effectN( 1 ).percent();
+ }
+ if ( p()->talent.fatebound.destiny_defined->ok() )
+ {
+ matching_odds += p()->talent.fatebound.destiny_defined->effectN( 3 ).percent();
+ }
+ if ( p()->buffs.fatebound_lucky_coin->check() )
+ {
+ // MIDNIGHT TOCHECK -- Is this additive?
+ matching_odds += p()->spell.fatebound_lucky_coin_buff->effectN( 5 ).percent();
+ }
}
- if ( p()->buffs.fatebound_lucky_coin->check() )
+
+ // TODO: it's an assumption that if you have both buffs (thanks, edge case) the bias prefers the one with more stacks
+ // (since the last one you hit before the edge case has to be the one with more stacks)
+ const bool is_match = p()->rng().roll( matching_odds );
+ const bool current_is_heads = p()->buffs.fatebound_coin_heads->check() > p()->buffs.fatebound_coin_tails->check();
+ result = is_match ?
+ current_is_heads ? fatebound_t::coinflip_e::HEADS : fatebound_t::coinflip_e::TAILS :
+ current_is_heads ? fatebound_t::coinflip_e::TAILS : fatebound_t::coinflip_e::HEADS;
+ }
+
+ if ( p()->talent.fatebound.controlled_chaos->ok() )
+ {
+ int streak = as( p()->talent.fatebound.controlled_chaos->effectN( 1 ).base_value() );
+ if ( ( p()->buffs.fatebound_coin_tails->total_stack() >= streak && result == fatebound_t::coinflip_e::HEADS ) ||
+ ( p()->buffs.fatebound_coin_heads->total_stack() >= streak && result == fatebound_t::coinflip_e::TAILS ) )
{
- // MIDNIGHT TOCHECK -- Is this additive?
- matching_odds += p()->spell.fatebound_lucky_coin_buff->effectN( 5 ).percent();
+ // 250ms so it does not coincide with an Overflowing Purse roll at the same time for now
+ trigger_fatebound_coinflip( coin_target, result, 250_ms );
+ p()->procs.controlled_chaos->occur();
}
}
- // TODO: it's an assumption that if you have both buffs (thanks, edge case) the bias prefers the one with more stacks
- // (since the last one you hit before the edge case has to be the one with more stacks)
- const bool is_match = p()->rng().roll( matching_odds );
- const bool current_is_heads = p()->buffs.fatebound_coin_heads->check() > p()->buffs.fatebound_coin_tails->check();
- result = is_match ?
- current_is_heads ? fatebound_t::coinflip_e::HEADS : fatebound_t::coinflip_e::TAILS :
- current_is_heads ? fatebound_t::coinflip_e::TAILS : fatebound_t::coinflip_e::HEADS;
- }
-
- trigger_fatebound_coinflip( state, result, current_delay );
+ resolve_fatebound_coinflip( coin_target, result );
+ } );
+}
- if ( p()->talent.fatebound.controlled_chaos->ok() )
+template
+void actions::rogue_action_t::resolve_fatebound_coinflip( player_t* coin_target, fatebound_t::coinflip_e result )
+{
+ if ( result == fatebound_t::coinflip_e::HEADS || result == fatebound_t::coinflip_e::EDGE )
{
- int streak = as( p()->talent.fatebound.controlled_chaos->effectN( 1 ).base_value() );
- if ( ( p()->buffs.fatebound_coin_tails->total_stack() >= streak && result == fatebound_t::coinflip_e::HEADS ) ||
- ( p()->buffs.fatebound_coin_heads->total_stack() >= streak && result == fatebound_t::coinflip_e::TAILS ) )
+ p()->buffs.fatebound_coin_heads->increment();
+ if ( result != fatebound_t::coinflip_e::EDGE )
{
- // 250ms so it does not coincide with an Overflowing Purse roll at the same time for now
- trigger_fatebound_coinflip( state, result, 250_ms + current_delay );
- p()->procs.controlled_chaos->occur();
+ p()->buffs.fatebound_coin_tails->expire();
}
}
-}
-
-template
-void actions::rogue_action_t::trigger_fatebound_coinflip( const action_state_t* state, fatebound_t::coinflip_e result, timespan_t delay )
-{
- auto coin_target = state->target->is_enemy() ? state->target : p()->target;
- make_event( *p()->sim, delay, [ this, coin_target, result, delay ] {
- p()->sim->print_log( "{} triggered fatebound coinflip with result '{}' and {} delay", *p(), fatebound_t::coinflip_string( result ), delay );
- if ( result == fatebound_t::coinflip_e::HEADS || result == fatebound_t::coinflip_e::EDGE )
+ if ( result == fatebound_t::coinflip_e::TAILS || result == fatebound_t::coinflip_e::EDGE )
+ {
+ // Don't fling tails coins at enemies precombat, since that'll start combat (assume the player knows not to have an enemy targeted)
+ if ( !ab::is_precombat )
{
- p()->buffs.fatebound_coin_heads->increment();
- if ( result != fatebound_t::coinflip_e::EDGE )
- {
- p()->buffs.fatebound_coin_tails->expire();
- }
+ p()->active.fatebound.fatebound_coin_tails->trigger_secondary_action( coin_target );
}
- if ( result == fatebound_t::coinflip_e::TAILS || result == fatebound_t::coinflip_e::EDGE )
+ if ( result != fatebound_t::coinflip_e::EDGE )
{
- // Don't fling tails coins at enemies precombat, since that'll start combat (assume the player knows not to have an enemy targeted)
- if ( !ab::is_precombat )
- {
- p()->active.fatebound.fatebound_coin_tails->trigger_secondary_action( coin_target );
- }
- if ( result != fatebound_t::coinflip_e::EDGE )
- {
- // 2026-04-26 -- Logs suggest that the first tails attack is calculated before this fades
- p()->buffs.fatebound_coin_heads->expire( !ab::is_precombat ? 1_ms : 0_ms );
- }
+ // 2026-04-26 -- Logs suggest that the first tails attack is calculated before this fades
+ p()->buffs.fatebound_coin_heads->expire( !ab::is_precombat ? 1_ms : 0_ms );
}
- } );
+ }
if ( p()->talent.fatebound.rush_to_the_inevitable->ok() )
{
@@ -8132,6 +8133,22 @@ void actions::rogue_action_t::trigger_fatebound_coinflip( const action_sta
}
}
+template
+void actions::rogue_action_t::trigger_fatebound_coinflip( player_t* coin_target, fatebound_t::coinflip_e result, timespan_t delay )
+{
+ make_event( *p()->sim, delay, [ this, coin_target, result, delay ] {
+ p()->sim->print_log( "{} triggered fatebound coinflip with result '{}' and {} delay", *p(), fatebound_t::coinflip_string( result ), delay );
+ resolve_fatebound_coinflip( coin_target, result );
+ } );
+}
+
+template
+void actions::rogue_action_t::trigger_fatebound_coinflip( const action_state_t* state, fatebound_t::coinflip_e result, timespan_t delay )
+{
+ auto coin_target = state->target->is_enemy() ? state->target : p()->target;
+ trigger_fatebound_coinflip( coin_target, result, delay );
+}
+
template
void actions::rogue_action_t::trigger_fatebound_edge_case( const action_state_t* state )
{