Skip to content

Commit

Permalink
Many small logic adjustments and bugfixes for NPC targeting / threat …
Browse files Browse the repository at this point in the history
…assessment (CleverRaven#69840)

* Cleanup and small logic adjustments

rename memory function to match other caches
fix a small oversight with melee engagement
Adjust swarming range to use weapon ranges instead of arbitrary numbers

* missed a word

* astyle

* monster difficulty assessment adjustments

added armour calculations
added some special attack calculations

* fix? npc yo-yo behaviour when repositioning

* minor fix

* allow NPCs to pursue hit-and-run enemies

* astyle

* Change several NPC default settings

* don't pulp if far from player

* assstyle

* fix bugs, balance changes. The usual.

* pulping corpses seeks within engagement distance

* revert monster diff changes, for now

We can return here later but I want this PR done.

* update test

* fix two clang errors, one test left

* Update npc_talk_test.cpp

set tests to use a flag that's less likely to change. It is unlikely we'll set "forbid engagement" to default to on.

* Update npc_talk_test.cpp

* Update npc_talk_test.cpp

try disabling this line of the test because I have a headache

* Update npc_talk_test.cpp

* Update TALK_TEST.json

* Update data/json/npcs/TALK_TEST.json

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Apply suggestions from code review

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
I-am-Erk and github-actions[bot] authored Dec 2, 2023
1 parent 68aba67 commit 924079e
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 68 deletions.
2 changes: 2 additions & 0 deletions data/json/damage_types.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"magic_color": "light_red",
"name": "pierce",
"skill": "stabbing",
"mon_difficulty": true,
"//2": "derived from cut only for monster defs",
"derived_from": [ "cut", 0.8 ],
"immune_flags": { "character": [ "STAB_IMMUNE" ] }
Expand All @@ -79,6 +80,7 @@
"type": "damage_type",
"physical": true,
"magic_color": "light_red",
"mon_difficulty": true,
"name": "ballistic",
"material_required": true,
"immune_flags": { "character": [ "BULLET_IMMUNE" ] }
Expand Down
6 changes: 5 additions & 1 deletion data/json/npcs/TALK_TEST.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@
"topic": "TALK_DONE",
"condition": { "npc_aim_rule": "AIM_SPRAY" }
},
{ "text": "This is a npc rule test response.", "topic": "TALK_DONE", "condition": { "npc_rule": "use_silent" } }
{
"text": "This is a npc rule test response.",
"topic": "TALK_DONE",
"condition": { "npc_rule": "avoid_doors" }
}
]
},
{
Expand Down
19 changes: 12 additions & 7 deletions src/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ static const mfaction_str_id monfaction_bee( "bee" );
static const mfaction_str_id monfaction_human( "human" );
static const mfaction_str_id monfaction_player( "player" );

static const mon_flag_str_id mon_flag_HIT_AND_RUN( "HIT_AND_RUN" );
static const mon_flag_str_id mon_flag_RIDEABLE_MECH( "RIDEABLE_MECH" );

static const overmap_location_str_id overmap_location_source_of_ammo( "source_of_ammo" );
Expand Down Expand Up @@ -2629,7 +2630,11 @@ Creature::Attitude npc::attitude_to( const Creature &other ) const
case MATT_FPASSIVE:
case MATT_IGNORE:
case MATT_FLEE:
return Attitude::NEUTRAL;
if( m.has_flag( mon_flag_HIT_AND_RUN ) ) {
return Attitude::HOSTILE;
} else {
return Attitude::NEUTRAL;
}
case MATT_FRIEND:
return Attitude::FRIENDLY;
case MATT_ATTACK:
Expand Down Expand Up @@ -3781,22 +3786,22 @@ npc_follower_rules::npc_follower_rules()
override_enable = ally_rule::DEFAULT;

set_flag( ally_rule::use_guns );
set_flag( ally_rule::use_grenades );
clear_flag( ally_rule::use_silent );
clear_flag( ally_rule::use_grenades );
set_flag( ally_rule::use_silent );
set_flag( ally_rule::avoid_friendly_fire );

clear_flag( ally_rule::allow_pick_up );
clear_flag( ally_rule::allow_bash );
clear_flag( ally_rule::allow_sleep );
set_flag( ally_rule::allow_complain );
set_flag( ally_rule::allow_pulp );
clear_flag( ally_rule::close_doors );
clear_flag( ally_rule::follow_close );
set_flag( ally_rule::close_doors );
set_flag( ally_rule::follow_close );
clear_flag( ally_rule::avoid_doors );
clear_flag( ally_rule::hold_the_line );
clear_flag( ally_rule::ignore_noise );
set_flag( ally_rule::ignore_noise );
clear_flag( ally_rule::forbid_engage );
set_flag( ally_rule::follow_distance_2 );
clear_flag( ally_rule::follow_distance_2 );
}

bool npc_follower_rules::has_flag( ally_rule test, bool check_override ) const
Expand Down
36 changes: 20 additions & 16 deletions src/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,21 +256,7 @@ struct npc_opinion {
void deserialize( const JsonObject &data );
};

// npc_combat_memory should store short-term trackers that don't really need to be saved if
// the player exits the game. Minor logic behaviour changes might occur, but nothing serious.
struct npc_combat_memory {
float assess_ally = 0.0f;
float assess_enemy = 0.0f;
int panic = 0;
int swarm_count = 0; //so you can tell if you're getting away over multiple turns
int failing_to_reposition = 0; // Inc. when tries to flee/move and doesn't change assess
int reposition_countdown = 0; // set when repos fails so that we don't keep trying.
int assessment_before_repos = 0; // assessment of enemy threat level at the start of repos
float my_health = 1.0f; // saved when we evaluate_self. Health 1.0 means 100% unhurt.
bool repositioning = false; // is NPC running away or just moving around / kiting.
int formation_distance = -1; // dist to nearest ally with a gun, or to player
int engagement_distance = 6; // applies to melee NPCs in formation with ranged ones or the player.
};


enum class combat_engagement : int {
NONE = 0,
Expand Down Expand Up @@ -587,6 +573,7 @@ struct npc_short_term_cache {
npc_attack_rating current_attack_evaluation;
std::shared_ptr<npc_attack> current_attack;


// Use weak_ptr to avoid circular references between Creatures
// attitude of creatures the npc can see
std::vector<weak_ptr_fast<Creature>> hostile_guys;
Expand All @@ -602,6 +589,22 @@ struct npc_short_term_cache {
std::optional<int> closest_enemy_to_friendly_distance() const;
};

// npc_combat_memory should store short-term trackers that don't really need to be saved if
// the player exits the game. Minor logic behaviour changes might occur, but nothing serious.
struct npc_combat_memory_cache {
float assess_ally = 0.0f;
float assess_enemy = 0.0f;
int panic = 0;
int swarm_count = 0; //so you can tell if you're getting away over multiple turns
int failing_to_reposition = 0; // Inc. when tries to flee/move and doesn't change assess
int reposition_countdown = 0; // set when repos fails so that we don't keep trying.
int assessment_before_repos = 0; // assessment of enemy threat level at the start of repos
float my_health = 1.0f; // saved when we evaluate_self. Health 1.0 means 100% unhurt.
bool repositioning = false; // is NPC running away or just moving around / kiting.
int formation_distance = -1; // dist to nearest ally with a gun, or to player
int engagement_distance = 6; // applies to melee NPCs in formation with ranged ones or the player.
};

struct npc_need_goal_cache {
tripoint_abs_omt goal;
tripoint_abs_omt omt_loc;
Expand Down Expand Up @@ -1374,7 +1377,8 @@ class npc : public Character
npc_mission previous_mission = NPC_MISSION_NULL;
npc_personality personality;
npc_opinion op_of_u;
npc_combat_memory mem_combat;
npc_combat_memory_cache mem_combat;

dialogue_chatbin chatbin;
int patience = 0; // Used when we expect the player to leave the area
npc_follower_rules rules;
Expand Down
9 changes: 7 additions & 2 deletions src/npc_attack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,18 @@ void npc_attack_melee::use( npc &source, const tripoint &location ) const
if( clear_path && source.mem_combat.formation_distance == -1 ) {
source.move_to_next();
add_msg_debug( debugmode::DF_NPC_MOVEAI,
"<color_light_gray>%s has no nearby ranged allies. Going for the attack.</color>", source.name );
"<color_light_gray>%s has no nearby ranged allies. Going for attack.</color>", source.name );
} else if( clear_path && source.mem_combat.formation_distance > target_distance ) {
source.move_to_next();
add_msg_debug( debugmode::DF_NPC_MOVEAI,
"<color_light_gray>%s is at least %i away from ranged allies, enemy within %i. Going for attack.</color>",
source.name, source.mem_combat.formation_distance, target_distance );

} else if( clear_path &&
source.mem_combat.formation_distance > source.closest_enemy_to_friendly_distance() ) {
source.move_to_next();
//add_msg_debug( debugmode::DF_NPC_MOVEAI,
// "<color_light_gray>%s is at least %i away from allies, enemy within %i of ally. Going for attack.</color>",
// source.name, source.mem_combat.formation_distance, source.closest_enemy_to_friendly_distance() );
} else {
add_msg_debug( debugmode::DF_NPC_MOVEAI,
"<color_light_gray>%s can't path to melee target, or is staying close to ranged allies.</color>",
Expand Down
86 changes: 50 additions & 36 deletions src/npcmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -476,23 +476,16 @@ std::vector<sphere> npc::find_dangerous_explosives() const
float npc::evaluate_monster( const monster &target, int dist ) const
{
float speed = target.speed_rating();
float scaled_distance = std::max( 1.0f, dist * dist / ( speed + 10.0f ) );
float scaled_distance = std::max( 1.0f, dist * dist / ( speed * 250.0f ) );
float hp_percent = static_cast<float>( target.get_hp() ) / target.get_hp_max();
float diff = std::min( static_cast<float>( target.type->difficulty ), NPC_DANGER_VERY_LOW );
float diff = std::max( static_cast<float>( target.type->difficulty ), NPC_DANGER_VERY_LOW );
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"<color_yellow>evaluate_monster </color><color_light_gray>%s thinks %s threat level is %1.2f before considering situation.</color>",
name,
target.type->nname(), diff );
"<color_yellow>evaluate_monster </color><color_dark_gray>%s thinks %s threat level is <color_light_gray>%1.2f</color><color_dark_gray> before considering situation. Speed rating: %1.2f; dist: %i; scaled_distance: %1.0f; HP: %1.0f%%</color>",
name, target.type->nname(), diff, speed, dist, scaled_distance, hp_percent * 100 );
// Note that the danger can pass below "very low" if the monster is weak and far away.
diff *= ( hp_percent * 0.5f + 0.5f ) / scaled_distance;
/*add_msg_debug( debugmode::DF_NPC_COMBATAI,
"<color_light_gray>%s distance from %s: %i. Speed rating: %1.2f. Scaled distance: %1.2f.</color>",
name, target.type->nname(), dist, speed, scaled_distance );
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"<color_light_gray>%s sees %s hp percent remaining is %1.0f%%.</color>",
name, target.type->nname(), hp_percent * 100 );*/
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"%s puts final %s threat level at %1.2f<color_light_gray> after counting speed, distance, hp</color>",
"<color_light_gray>%s puts final %s threat level at </color>%1.2f<color_light_gray> after counting speed, distance, hp</color>",
name, target.type->nname(), diff );
return std::min( diff, NPC_MONSTER_DANGER_MAX );
}
Expand Down Expand Up @@ -679,10 +672,11 @@ float npc::estimate_armour( const Character &candidate ) const
"<color_light_gray>%s: %s armour value for %s rated as %i.</color>", name,
candidate.disp_name( true ), body_part_name( part_id ), armour_step );
if( part_id == bodypart_id( "head" ) || part_id == bodypart_id( "torso" ) ) {
armour_step *= 3;
number_of_parts += 2;
armour_step *= 4;
number_of_parts += 3;
}
armour += static_cast<float>( armour_step );
// obtain an average value of the 4 armour types we checked.
armour += static_cast<float>( armour_step ) / 4.0f;
}
armour /= number_of_parts;

Expand All @@ -694,7 +688,6 @@ float npc::estimate_armour( const Character &candidate ) const
return armour;
}


static bool too_close( const tripoint &critter_pos, const tripoint &ally_pos, const int def_radius )
{
return rl_dist( critter_pos, ally_pos ) <= def_radius;
Expand Down Expand Up @@ -726,14 +719,20 @@ void npc::assess_danger()
int hostile_count = 0; // for tallying nearby threatening enemies
int friendly_count = 1; // count yourself as a friendly
int def_radius = rules.has_flag( ally_rule::follow_close ) ? follow_distance() : 6;
float bravery_vs_pain = static_cast<float>( personality.bravery ) - get_pain() / 10.0f;
bool npc_ranged = get_wielded_item() && get_wielded_item()->is_gun();

if( !confident_range_cache ) {
invalidate_range_cache();
}
// Radius we can attack without moving
int max_range = *confident_range_cache;
// Radius in which enemy threats are multiplied to avoid surrounding
int preferred_medium_range = std::max( max_range, 8 );
preferred_medium_range = std::min( preferred_medium_range, 15 );
// Radius in which enemy threats are hugely multiplied to encourage repositioning
int preferred_close_range = std::max( max_range, 1 );
preferred_close_range = std::min( preferred_close_range, preferred_medium_range / 2 );

Character &player_character = get_player_character();
bool sees_player = sees( player_character.pos() );
const bool self_defense_only = rules.engagement == combat_engagement::NO_MOVE ||
Expand Down Expand Up @@ -854,14 +853,14 @@ void npc::assess_danger()
if( !clear_shot_reach( pos(), critter.pos(), false ) ) {
if( is_enemy() || !critter.friendly ) {
// still warn about enemies behind impassable glass walls, but not as often.
add_msg_debug( debugmode::DF_NPC,
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"%s ignored %s because there's an obstacle in between. Might warn about it.",
name, critter.type->nname() );
if( critter_threat > 2 * ( 8.0f + personality.bravery + rng( 0, 5 ) ) ) {
warn_about( "monster", 10_minutes, critter.type->nname(), dist, critter.pos() );
}
} else {
add_msg_debug( debugmode::DF_NPC,
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"%s ignored %s because there's an obstacle in between, and it's not worth warning about.",
name, critter.type->nname() );
}
Expand All @@ -873,26 +872,24 @@ void npc::assess_danger()
if( critter_threat > ( 8.0f + personality.bravery + rng( 0, 5 ) ) ) {
warn_about( "monster", 10_minutes, critter.type->nname(), dist, critter.pos() );
}
if( dist < 8 && critter_threat > bravery_vs_pain ) {
if( dist < preferred_medium_range ) {
hostile_count += 1;
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"<color_light_gray>%s added %s to nearby hostile count. Total: %i</color>", name,
critter.type->nname(), hostile_count );
}
if( dist < 4 && npc_ranged ) {
if( dist <= preferred_close_range ) {
mem_combat.swarm_count += 1;
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"<color_light_gray>%s added %s to swarming enemies count. Total: %i</color>",
name,
critter.type->nname(), mem_combat.swarm_count );
"<color_light_gray>%s added %s to swarm count. Total: %i</color>",
name, critter.type->nname(), mem_combat.swarm_count );
}
}
if( must_retreat || no_fighting ) {
continue;
}


add_msg_debug( debugmode::DF_NPC_COMBATAI,
add_msg_debug( debugmode::DF_NPC,
"%s assessed threat of critter %s as %1.2f.",
name, critter.type->nname(), critter_threat );
ai_cache.total_danger += critter_threat;
Expand Down Expand Up @@ -1040,7 +1037,7 @@ void npc::assess_danger()
name, player_diff );
if( dist <= 3 ) {
player_diff = player_diff * ( 4 - dist ) / 2;
mem_combat.swarm_count = 0;
mem_combat.swarm_count /= ( 4 - dist );
mem_combat.assess_ally += player_diff;
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"<color_green>Player is %i tiles from %s.</color><color_light_gray> Adding </color><color_light_green>%1.2f to ally strength</color><color_light_gray> and bolstering morale.</color>",
Expand Down Expand Up @@ -1153,7 +1150,6 @@ void npc::act_on_danger_assessment()
} else {
add_msg_debug( debugmode::DF_NPC_COMBATAI, "%s still wants to reposition, but they just tried.",
name );
mem_combat.reposition_countdown --;
}
mem_combat.panic *= ( mem_combat.assess_enemy / ( mem_combat.assess_ally + 0.5f ) );
mem_combat.panic += std::min(
Expand All @@ -1176,10 +1172,10 @@ void npc::act_on_danger_assessment()
}
}
}
} else if( failed_reposition || ( npc_ranged &&
mem_combat.assess_ally < mem_combat.assess_enemy * mem_combat.swarm_count ) ) {
} else if( failed_reposition ||
( mem_combat.assess_ally < mem_combat.assess_enemy * mem_combat.swarm_count ) ) {
add_msg_debug( debugmode::DF_NPC_COMBATAI,
"<color_light_gray>Due to ranged weapon, %s considers </color>repositioning<color_light_gray> from swarming enemies.</color>",
"<color_light_gray>%s considers </color>repositioning<color_light_gray> from swarming enemies.</color>",
name );
if( failed_reposition ) {
add_msg_debug( debugmode::DF_NPC_COMBATAI, "%s failed repositioning, trying again." );
Expand Down Expand Up @@ -1269,6 +1265,19 @@ void npc::regen_ai_cache()
mem_combat.assess_enemy = 0.0f;
mem_combat.assess_ally = 0.0f;
mem_combat.swarm_count = 0;
if( mem_combat.reposition_countdown > 0 ) {
mem_combat.reposition_countdown --;
}

if( mem_combat.repositioning && !has_effect( effect_npc_run_away ) &&
!has_effect( effect_npc_fire_bad ) ) {
// if NPC no longer has the run away effect and isn't fleeing in panic,
// they can stop moving away.
mem_combat.repositioning = false;
mem_combat.reposition_countdown = 1;
path.clear();
}

assess_danger();
if( old_assessment > NPC_DANGER_VERY_LOW && ai_cache.danger_assessment <= 0 ) {
warn_about( "relax", 30_minutes );
Expand Down Expand Up @@ -3708,10 +3717,15 @@ std::list<item> npc::pick_up_item_vehicle( vehicle &veh, int part_index )
bool npc::find_corpse_to_pulp()
{
Character &player_character = get_player_character();
if( ( is_player_ally() && ( !rules.has_flag( ally_rule::allow_pulp ) ||
player_character.in_vehicle ) ) ||
is_hallucination() ) {
return false;
if( is_player_ally() ) {
if( !rules.has_flag( ally_rule::allow_pulp ) ||
player_character.in_vehicle || is_hallucination() ) {
return false;
}
if( rl_dist( pos(), player_character.pos() ) >= mem_combat.engagement_distance ) {
// don't start to pulp corpses if you're already far from the player.
return false;
}
}

map &here = get_map();
Expand Down Expand Up @@ -3751,7 +3765,7 @@ bool npc::find_corpse_to_pulp()
return nullptr;
};

const int range = 6;
const int range = mem_combat.engagement_distance;

const item *corpse = nullptr;
if( pulp_location && square_dist( get_location(), *pulp_location ) <= range ) {
Expand Down
Loading

0 comments on commit 924079e

Please sign in to comment.