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
6 changes: 6 additions & 0 deletions src/cached_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ int pixel_minimap_g;
int pixel_minimap_b;
int pixel_minimap_a;

float NPC_DANGER_VERY_LOW;
float NPC_COWARDICE_MODIFIER;
float NPC_MONSTER_DANGER_MAX;
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
float NPC_CHARACTER_DANGER_MAX;
int NPC_CROWD_BRAVADO;

namespace cata::options
{
std::vector<std::string> damage_indicators;
Expand Down
6 changes: 6 additions & 0 deletions src/cached_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ extern int pixel_minimap_g;
extern int pixel_minimap_b;
extern int pixel_minimap_a;

extern float NPC_DANGER_VERY_LOW;
extern float NPC_COWARDICE_MODIFIER;
extern float NPC_MONSTER_DANGER_MAX;
I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
extern float NPC_CHARACTER_DANGER_MAX;
extern int NPC_CROWD_BRAVADO;

namespace cata::options
{
extern std::vector<std::string> damage_indicators;
Expand Down
65 changes: 47 additions & 18 deletions src/npcmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "basecamp.h"
#include "bionics.h"
#include "bodypart.h"
#include "cached_options.h"
#include "cata_algo.h"
#include "character.h"
#include "character_id.h"
Expand Down Expand Up @@ -146,8 +147,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 +280,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 +403,10 @@ 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 );
return std::min( diff, NPC_MONSTER_DANGER_MAX );
} else if( target.is_npc() || target.is_avatar() ) {
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 @@ -494,7 +510,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 +620,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 / static_cast<float>( 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 +650,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 +666,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 +684,38 @@ 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
6 changes: 6 additions & 0 deletions src/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4003,6 +4003,12 @@ void options_manager::update_options_cache()
prevent_occlusion_transp = ::get_option<bool>( "PREVENT_OCCLUSION_TRANSP" );
prevent_occlusion_min_dist = ::get_option<float>( "PREVENT_OCCLUSION_MIN_DIST" );
prevent_occlusion_max_dist = ::get_option<float>( "PREVENT_OCCLUSION_MAX_DIST" );

I-am-Erk marked this conversation as resolved.
Show resolved Hide resolved
NPC_DANGER_VERY_LOW = ::get_option<float>( "NPC_DANGER_VERY_LOW" );
NPC_CROWD_BRAVADO = ::get_option<int>( "NPC_CROWD_BRAVADO" );
NPC_COWARDICE_MODIFIER = ::get_option<float>( "NPC_COWARDICE_MODIFIER" );
NPC_MONSTER_DANGER_MAX = ::get_option<float>( "NPC_MONSTER_DANGER_MAX" );
NPC_CHARACTER_DANGER_MAX = ::get_option<float>( "NPC_CHARACTER_DANGER_MAX" );

// if the tilesets are identical don't duplicate
use_far_tiles = ::get_option<bool>( "USE_DISTANT_TILES" ) ||
Expand Down