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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions engine/action/action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2130,7 +2130,16 @@ void action_t::assess_damage( result_amount_type rt, action_state_t* state )
{
if ( sim->fight_style == FIGHT_STYLE_DUNGEON_SLICE || sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE )
{
if ( state->target->is_boss() )
if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE && sim->dungeon_route_dynamic_targeting )
{
// Dynamic targeting: priority damage is damage into the mob currently gating the end of
// the pull (the highest-hp one), regardless of BOSS_ prefixes.
if ( state->target == sim->dungeon_route_priority_target )
{
player->priority_iteration_dmg += state->result_amount;
}
}
else if ( state->target->is_boss() )
{
player->priority_iteration_dmg += state->result_amount;
}
Expand Down Expand Up @@ -5120,8 +5129,10 @@ void action_t::acquire_target( retarget_source event, player_t* /* context */, p
}

// Don't swap targets if the action's current target is still alive, except in cases
// where the actor has risen (been summoned, start of iteration etc).
// where the actor has risen (been summoned, start of iteration etc), or when dungeon route
// dynamic targeting moves everyone onto the currently-highest-hp mob.
if ( event != retarget_source::SELF_ARISE &&
!( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE && sim->dungeon_route_dynamic_targeting ) &&
target && !target->is_sleeping() && !target->debuffs.invulnerable->check() )
{
return;
Expand Down
38 changes: 36 additions & 2 deletions engine/player/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14657,6 +14657,9 @@ void player_t::acquire_target( retarget_source event, player_t* context )
player_t* candidate_target = nullptr;
player_t* first_invuln_target = nullptr;

bool dynamic_targeting = sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE && sim->dungeon_route_dynamic_targeting;
double candidate_hp = -1.0;

// TODO: Fancier system
for ( auto enemy : sim->target_non_sleeping_list )
{
Expand All @@ -14669,8 +14672,39 @@ void player_t::acquire_target( retarget_source event, player_t* context )
continue;
}

candidate_target = enemy;
break;
if ( dynamic_targeting )
{
// Dynamic targeting: always attack the mob that currently has the most hp, since it gates the
// end of the pull. Re-evaluated on arise/demise and on the periodic PRIORITY_CHANGE event.
double enemy_hp = enemy->resources.current[ RESOURCE_HEALTH ];
if ( enemy_hp > candidate_hp )
{
candidate_hp = enemy_hp;
candidate_target = enemy;
}
}
else
{
candidate_target = enemy;
break;
}
}

if ( dynamic_targeting )
{
// Keep the current target unless the candidate leads it by more than 5%: no two mobs are ever
// exactly tied mid-combat, and per-tick lead flips between mobs dying together would cause
// pointless target flickering. A player swaps on a visible hp lead, not on every frame.
if ( target && target != candidate_target && !target->is_sleeping() && target->is_enemy() &&
!( target->debuffs.invulnerable && target->debuffs.invulnerable->check() ) &&
candidate_hp <= target->resources.current[ RESOURCE_HEALTH ] * 1.05 )
{
candidate_target = target;
}

// Track the current priority mob for the Priority DPS metric. Follows the hysteresis-kept
// choice so the metric always agrees with the target the player is actually prioritizing.
sim->dungeon_route_priority_target = candidate_target;
}

// Invulnerable targets are currently not in the target_non_sleeping_list, so fall back to
Expand Down
3 changes: 2 additions & 1 deletion engine/sc_enums.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ enum class retarget_source
ACTOR_DEMISE, // Any actor demises
ACTOR_INVULNERABLE, // Actor becomes invulnerable
ACTOR_VULNERABLE, // Actor becomes vulnerable (after becoming invulnerable)
SELF_ARISE // Actor has arisen (no context provided)
SELF_ARISE, // Actor has arisen (no context provided)
PRIORITY_CHANGE // DungeonRoute dynamic targeting: periodic re-check of the highest-hp mob (no context)
};

// Misc Constants
Expand Down
33 changes: 33 additions & 0 deletions engine/sim/raid_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,32 @@ struct pull_event_t final : raid_event_t
}
};

// Periodically re-checks which mob is currently the highest-hp one and retargets the players
// onto it (dungeon_route_dynamic_targeting). 1 second approximates a player's tab-retarget
// reaction time.
struct dynamic_retarget_event_t : public event_t
{
pull_event_t* pull;

dynamic_retarget_event_t( pull_event_t* pull_ ) : event_t( *pull_->sim, 1.0_s ), pull( pull_ )
{ }

const char* name() const override
{
return "dynamic_retarget_event_t";
}

void execute() override
{
pull->retarget_event = nullptr;

range::for_each( sim().player_non_sleeping_list,
[]( player_t* p ) { p->acquire_target( retarget_source::PRIORITY_CHANGE ); } );

pull->retarget_event = make_event<dynamic_retarget_event_t>( sim(), pull );
}
};

player_t* master;
std::string enemies_str;
timespan_t delay;
Expand All @@ -489,6 +515,7 @@ struct pull_event_t final : raid_event_t
bool has_boss;
event_t* spawn_event;
event_t* redistribute_event;
event_t* retarget_event;
extended_sample_data_t real_duration;
std::vector<std::unique_ptr<raid_event_t>> child_events;

Expand All @@ -513,6 +540,7 @@ struct pull_event_t final : raid_event_t
has_boss( false ),
spawn_event( nullptr ),
redistribute_event( nullptr ),
retarget_event( nullptr ),
real_duration( "Pull Length", false )
{
add_option( opt_string( "enemies", enemies_str ) );
Expand Down Expand Up @@ -738,6 +766,9 @@ struct pull_event_t final : raid_event_t
if ( shared_health )
redistribute_event = make_event<redistribute_event_t>( *sim, this );

if ( sim->fight_style == FIGHT_STYLE_DUNGEON_ROUTE && sim->dungeon_route_dynamic_targeting )
retarget_event = make_event<dynamic_retarget_event_t>( *sim, this );

sim->print_log( "Spawned Pull {}: {} mobs with {} total health, {:.1f}s delay from previous",
pull, adds.size(), total_health, delay.total_seconds() );

Expand Down Expand Up @@ -768,6 +799,7 @@ struct pull_event_t final : raid_event_t
saved_duration = timespan_t::from_seconds( length );

event_t::cancel( redistribute_event );
event_t::cancel( retarget_event );

if ( has_boss )
{
Expand Down Expand Up @@ -824,6 +856,7 @@ struct pull_event_t final : raid_event_t
raid_event->reset();

redistribute_event = nullptr;
retarget_event = nullptr;
}

double parse_health( util::string_view str )
Expand Down
5 changes: 5 additions & 0 deletions engine/sim/sim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1467,6 +1467,8 @@ sim_t::sim_t()
dungeon_route_simple_dps_members( 0 ),
dungeon_route_pct_hp( 0 ),
dungeon_route_key_level( 0 ),
dungeon_route_dynamic_targeting( false ),
dungeon_route_priority_target( nullptr ),
challenge_mode( false ),
scale_itemlevel_down_only( false ),
disable_set_bonuses( false ),
Expand Down Expand Up @@ -1806,6 +1808,8 @@ void sim_t::reset()
{
print_debug( "Resetting Simulator" );

dungeon_route_priority_target = nullptr;

if ( deterministic )
seed = rng().reseed();

Expand Down Expand Up @@ -3910,6 +3914,7 @@ void sim_t::create_options()
add_option( opt_int( "keystone_level", dungeon_route_key_level ) );
add_option( opt_int( "keystone_pct_hp", dungeon_route_pct_hp, 0, 100 ) );
add_option( opt_bool( "dungeon_route_smart_targeting", dungeon_route_smart_targeting ) );
add_option( opt_bool( "dungeon_route_dynamic_targeting", dungeon_route_dynamic_targeting ) );
add_option( opt_int( "dungeon_route_simple_dps_members", dungeon_route_simple_dps_members, 0, 3 ) );

// Character Creation
Expand Down
2 changes: 2 additions & 0 deletions engine/sim/sim.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ struct sim_t : private sc_thread_t
int dungeon_route_simple_dps_members; // fills the sim with up to 3 simplified dps players, just like your lfg weeklies :^)
int dungeon_route_pct_hp; // the portion of full mob hp being used to sim an incomplete party
int dungeon_route_key_level; // keystone difficulty level
bool dungeon_route_dynamic_targeting; // opt-in: retarget to the currently-highest-hp mob and credit priority damage to it
player_t* dungeon_route_priority_target; // runtime state: the current highest-hp mob when dynamic targeting is active
bool challenge_mode; // if active, players will get scaled down to 620 and set bonuses are deactivated
bool scale_itemlevel_down_only; // Items below the value of scale_to_itemlevel will not be scaled up.
bool disable_set_bonuses; // Disables all set bonuses.
Expand Down
1 change: 1 addition & 0 deletions engine/util/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2604,6 +2604,7 @@ const char* util::retarget_event_string( retarget_source event )
case retarget_source::ACTOR_INVULNERABLE: return "actor_invulnerable";
case retarget_source::ACTOR_VULNERABLE: return "actor_vulnnerable";
case retarget_source::SELF_ARISE: return "self_arise";
case retarget_source::PRIORITY_CHANGE: return "priority_change";
default: return "unknown";
}
}
Expand Down
Loading