Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

NPC AI/ fleeing adjustments 2: Come back, little NPC #67870

Merged
merged 14 commits into from
Aug 26, 2023
35 changes: 35 additions & 0 deletions data/core/game_balance.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,41 @@
"stype": "float",
"value": 0.0001
},
{
"type": "EXTERNAL_OPTION",
"name": "NPC_DANGER_VERY_LOW",
"info": "This is the minimum amount of danger an NPC will assess a threat at, so even enemy squirrels and zombies register here. The higher this is, the jumpier NPCs will become at small dangers.",
"stype": "float",
"value": 5.0
},
{
"type": "EXTERNAL_OPTION",
"name": "NPC_MONSTER_DANGER_MAX",
"info": "This is the max amount of danger an NPC will assess a monster threat at. It is a bit lower than character danger max, because in general monsters attack in swarms. This is also used as the 'default' max danger rating for things where the NPC is quickly assessing an area and not discriminating between characters and monsters.",
"stype": "float",
"value": 150.0
},
{
"type": "EXTERNAL_OPTION",
"name": "NPC_CHARACTER_DANGER_MAX",
"info": "This is the max amount of danger an NPC will assess a character (player or NPC) threat at. By default it is higher than monsters, because a player charging at you with a machine gun on full auto should probably register as more deadly than a skeletal juggernaut, and because this generally comes up during careful assessment of a specific character. Try increasing this if your heavily geared-up NPC is still running from everything.",
"stype": "float",
"value": 250.0
},
{
"type": "EXTERNAL_OPTION",
"name": "NPC_CROWD_BRAVADO",
"info": "This determines how much the NPC hates being outnumbered by enemies. They will increase their perceived number of allies by this value, so the higher it is, the less they care about a dozen zombies coming their way. Don't set this to a negative number unless you like dividing by zero.",
"stype": "int",
"value": 0
},
{
"type": "EXTERNAL_OPTION",
"name": "NPC_COWARDICE_MODIFIER",
"info": "This is a multiplier applied to an NPC's assessment of overall threat. The lower it is, the less likely they are to run away from their problems. It has been tested between 0.1 and 0.5 pretty effectively.",
"stype": "float",
"value": 0.25
},
{
"type": "EXTERNAL_OPTION",
"name": "NO_FAULTS",
Expand Down
72 changes: 54 additions & 18 deletions src/npcmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,12 @@ static const trait_id trait_RETURN_TO_START_POS( "RETURN_TO_START_POS" );
static const zone_type_id zone_type_NO_NPC_PICKUP( "NO_NPC_PICKUP" );
static const zone_type_id zone_type_NPC_RETREAT( "NPC_RETREAT" );

static constexpr float NPC_DANGER_VERY_LOW = 5.0f;
static constexpr float NPC_DANGER_MAX = 150.0f;

static const std::string NPC_DANGER_VERY_LOW_OPT( "NPC_DANGER_VERY_LOW" );
static const std::string NPC_CROWD_BRAVADO_OPT( "NPC_CROWD_BRAVADO" );
static const std::string NPC_COWARDICE_MODIFIER_OPT( "NPC_COWARDICE_MODIFIER" );
static const std::string NPC_MONSTER_DANGER_MAX_OPT( "NPC_MONSTER_DANGER_MAX" );
static const std::string NPC_CHARACTER_DANGER_MAX_OPT( "NPC_CHARACTER_DANGER_MAX" );
static constexpr float MAX_FLOAT = 5000000000.0f;

// TODO: These would be much better using common code or constants from character.cpp,
Expand Down Expand Up @@ -275,11 +279,22 @@ tripoint npc::good_escape_direction( bool include_pos )
const zone_manager &mgr = zone_manager::get_manager();
std::optional<tripoint_abs_ms> retreat_target = mgr.get_nearest( retreat_zone, abs_pos, 60,
fac_id );
// if there is a retreat zone in range, go there
if( !retreat_target ) {
//if not, consider regrouping on the player if they're getting far away.
Character &player_character = get_player_character();
int dist = rl_dist( pos(), player_character.pos() );
int def_radius = rules.has_flag( ally_rule::follow_close ) ? follow_distance() : 6;
if( dist > def_radius ) {
tripoint_bub_ms player_pos = get_player_character().pos_bub();
retreat_target = here.getglobal( player_pos );
}
}
if( retreat_target && *retreat_target != abs_pos ) {
update_path( here.getlocal( *retreat_target ) );
if( !path.empty() ) {
return path[0];
}
}
if( !path.empty() ) {
return path[0];
}
}

Expand Down Expand Up @@ -387,10 +402,12 @@ float npc::evaluate_enemy( const Creature &target ) const
if( target.is_monster() ) {
const monster &mon = dynamic_cast<const monster &>( target );
float diff = static_cast<float>( mon.type->difficulty );
return std::min( diff, NPC_DANGER_MAX );
float NPC_MONSTER_DANGER_MAX = get_option<float>( NPC_MONSTER_DANGER_MAX_OPT );
return std::min( diff, NPC_MONSTER_DANGER_MAX );
} else if( target.is_npc() || target.is_avatar() ) {
float NPC_CHARACTER_DANGER_MAX = get_option<float>( NPC_CHARACTER_DANGER_MAX_OPT );
return std::min( character_danger( dynamic_cast<const Character &>( target ) ),
NPC_DANGER_MAX );
NPC_CHARACTER_DANGER_MAX );
} else {
return 0.0f;
}
Expand Down Expand Up @@ -428,6 +445,10 @@ void npc::assess_danger()
int hostile_count = 0;
int friendly_count = 1; // count yourself as a friendly
int def_radius = rules.has_flag( ally_rule::follow_close ) ? follow_distance() : 6;
float NPC_COWARDICE_MODIFIER = get_option<float>( NPC_COWARDICE_MODIFIER_OPT );
float NPC_MONSTER_DANGER_MAX = get_option<float>( NPC_MONSTER_DANGER_MAX_OPT );
float NPC_CROWD_BRAVADO = get_option<int>( NPC_CROWD_BRAVADO_OPT );
float NPC_DANGER_VERY_LOW = get_option<float>( NPC_DANGER_VERY_LOW_OPT );
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved

if( !confident_range_cache ) {
invalidate_range_cache();
Expand Down Expand Up @@ -494,7 +515,7 @@ void npc::assess_danger()
continue;
}
int dist = rl_dist( pos(), pt );
cur_threat_map[direction_from( pos(), pt )] += 2.0f * ( NPC_DANGER_MAX - dist );
cur_threat_map[direction_from( pos(), pt )] += 2.0f * ( NPC_MONSTER_DANGER_MAX - dist );
if( dist < 3 && !has_effect( effect_npc_fire_bad ) ) {
warn_about( "fire_bad", 1_minutes );
add_effect( effect_npc_fire_bad, 5_turns );
Expand Down Expand Up @@ -604,11 +625,8 @@ void npc::assess_danger()
ai_cache.danger_assessment = assessment;
return;
}
// being outnumbered is serious. Scale up your assessment if you're outnumbered.
if( hostile_count > friendly_count ) {
assessment *= ( hostile_count / friendly_count );
}

// Warn about sufficiently risky nearby hostiles
const auto handle_hostile = [&]( const Character & foe, float foe_threat,
const std::string & bogey, const std::string & warning ) {
int dist = rl_dist( pos(), foe.pos() );
Expand Down Expand Up @@ -637,6 +655,7 @@ void npc::assess_danger()
}



if( !is_player_ally() || is_too_close || ok_by_rules( foe, dist, scaled_distance ) ) {
float priority = std::max( foe_threat - 2.0f * ( scaled_distance - 1 ),
is_too_close ? std::max( foe_threat, NPC_DANGER_VERY_LOW ) :
Expand All @@ -652,6 +671,7 @@ void npc::assess_danger()
return foe_threat;
};


for( const weak_ptr_fast<Creature> &guy : ai_cache.hostile_guys ) {
Character *foe = dynamic_cast<Character *>( guy.lock().get() );
if( foe && foe->is_npc() ) {
Expand All @@ -669,24 +689,37 @@ void npc::assess_danger()
assessment = std::max( min_danger, assessment - guy_threat * 0.5f );
}

// being outnumbered is serious. Do a flat scale up your assessment if you're outnumbered.
// This is a coarse tool that might be better handled
if( hostile_count > friendly_count + NPC_CROWD_BRAVADO ) {
assessment *= std::min( ( hostile_count / static_cast<float>( friendly_count + NPC_CROWD_BRAVADO ) ), 1.0f );
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
}

if( sees( player_character.pos() ) ) {
// Mod for the player
// cap player difficulty at 150
// Mod for the player's danger level, weight it higher if player is very close
// When the player is almost adjacent, it can exceed max danger ratings, so the
// NPC will try hard not to break and run while in formation.
float player_diff = evaluate_enemy( player_character );
int dist = rl_dist( pos(), player_character.pos() );
if( is_enemy() ) {
assessment += handle_hostile( player_character, player_diff, translate_marker( "maniac" ),
"kill_player" );
} else if( is_friendly( player_character ) ) {
float min_danger = assessment >= NPC_DANGER_VERY_LOW ? NPC_DANGER_VERY_LOW : -10.0f;
assessment = std::max( min_danger, assessment - player_diff * 0.5f );
if( dist <= 3 ) {
assessment = std::max( min_danger, assessment - player_diff * ( 4 - dist ) / 2 );
} else {
assessment = std::max( min_danger, assessment - player_diff * 0.5f );
}
ai_cache.friends.emplace_back( g->shared_from( player_character ) );
}
}

assessment *= 0.5f;
assessment *= NPC_COWARDICE_MODIFIER;
if( !has_effect( effect_npc_run_away ) && !has_effect( effect_npc_fire_bad ) ) {
float my_diff = evaluate_enemy( *this ) * 0.5f + rng( 0, personality.bravery * 2 );
add_msg_debug( debugmode::DF_NPC, "assessment: %1f, diff: %2f.", assessment, my_diff );
float my_diff = evaluate_enemy( *this ) * 0.5f + rng( 0,
( personality.bravery - get_pain() / 10 ) * 2 ) ;
add_msg_debug( debugmode::DF_NPC, "Enemy Danger: %1f, Ally Strength: %2f.", assessment, my_diff );
if( my_diff < assessment ) {
time_duration run_away_for = 10_turns + 1_turns * rng( 0, 10 ) - 1_turns * personality.bravery;
warn_about( "run_away", run_away_for );
Expand Down Expand Up @@ -742,6 +775,7 @@ void npc::regen_ai_cache()
map &here = get_map();
auto i = std::begin( ai_cache.sound_alerts );
creature_tracker &creatures = get_creature_tracker();
float NPC_DANGER_VERY_LOW = get_option<float>( NPC_DANGER_VERY_LOW_OPT );
if( has_trait( trait_RETURN_TO_START_POS ) ) {
if( !ai_cache.guard_pos ) {
ai_cache.guard_pos = get_location();
Expand Down Expand Up @@ -1858,6 +1892,7 @@ healing_options npc::patient_assessment( const Character &c )
npc_action npc::address_needs( float danger )
{
Character &player_character = get_player_character();
float NPC_DANGER_VERY_LOW = get_option<float>( NPC_DANGER_VERY_LOW_OPT );
// rng because NPCs are not meant to be hypervigilant hawks that notice everything
// and swing into action with alarming alacrity.
// no sometimes they are just looking the other way, sometimes they hestitate.
Expand Down Expand Up @@ -2578,6 +2613,7 @@ void npc::avoid_friendly_fire()
const tripoint tar = current_target()->pos();
// Calculate center of weight of friends and move away from that
tripoint center;
float NPC_DANGER_VERY_LOW = get_option<float>( NPC_DANGER_VERY_LOW_OPT );
for( const auto &fr : ai_cache.friends ) {
if( shared_ptr_fast<Creature> fr_p = fr.lock() ) {
center += fr_p->pos();
Expand Down