Skip to content

Commit 405dbe4

Browse files
authored
Unify NPC weapon evaluation (#82151)
* Simplify calls to evaluate_weapon() by giving it a helper * Move all npc<-->npc evaluation of weapons to evaluate_weapon() * ...and then move evaluate_weapon() to Character * Remove the last vestiges of weapon_value() outside of char creation * Move the existing cache * Replace character creation's offense summary with evaluate_weapon() values * Various clang-demanded fixes
2 parents c4b4cf0 + 878c120 commit 405dbe4

File tree

9 files changed

+99
-86
lines changed

9 files changed

+99
-86
lines changed

src/avatar_action.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ static float rate_critter( const Creature &c )
694694
if( np != nullptr ) {
695695
item_location wielded = np->get_wielded_item();
696696
if( wielded ) {
697-
return np->weapon_value( *wielded );
697+
return np->evaluate_weapon( *wielded );
698698
} else {
699699
return np->unarmed_value();
700700
}

src/bionics.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -683,9 +683,7 @@ void npc::check_or_use_weapon_cbm( const bionic_id &cbm_id )
683683
return;
684684
}
685685

686-
const int cbm_ammo = free_power / cbm_weapon.get_gun_energy_drain();
687-
688-
if( weapon_value( weap, ammo_count ) < weapon_value( cbm_weapon, cbm_ammo ) ) {
686+
if( evaluate_weapon( weap ) < evaluate_weapon( cbm_weapon ) ) {
689687
if( real_weapon.is_null() ) {
690688
// Prevent replacing real weapon when migrating saves
691689
real_weapon = weap;
@@ -705,7 +703,7 @@ void npc::check_or_use_weapon_cbm( const bionic_id &cbm_id )
705703

706704
const item cbm_weapon = bio.get_weapon();
707705

708-
if( weapon_value( weap, ammo_count ) < weapon_value( cbm_weapon, 0 ) ) {
706+
if( evaluate_weapon( weap ) < evaluate_weapon( cbm_weapon ) ) {
709707
if( is_armed() ) {
710708
stow_item( *weapon );
711709
}

src/character.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ class Character : public Creature, public visitable
11971197
bool can_attack_high() const override;
11981198

11991199
/** NPC-related item rating functions */
1200-
double weapon_value( const item &weap, int ammo = 10 ) const; // Evaluates item as a weapon
1200+
double evaluate_weapon( const item &maybe_weapon, bool pretend_have_ammo = false ) const;
12011201
double gun_value( const item &weap, int ammo = 10 ) const; // Evaluates item as a gun
12021202
double melee_value( const item &weap ) const; // As above, but only as melee
12031203
double unarmed_value() const; // Evaluate yourself!
@@ -1220,6 +1220,8 @@ class Character : public Creature, public visitable
12201220
bool is_dead_state() const override;
12211221

12221222
private:
1223+
double evaluate_weapon_internal( const item &maybe_weapon, bool can_use_gun,
1224+
bool use_silent, int pretend_ammo = 0 ) const;
12231225
mutable std::optional<bool> cached_dead_state;
12241226
public:
12251227
void set_part_hp_cur( const bodypart_id &id, int set ) override;

src/melee.cpp

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2726,37 +2726,72 @@ int Character::attack_speed( const item &weap ) const
27262726
return std::round( move_cost );
27272727
}
27282728

2729-
double Character::weapon_value( const item &weap, int ammo ) const
2729+
double Character::evaluate_weapon( const item &maybe_weapon, const bool pretend_have_ammo ) const
27302730
{
2731-
if( is_wielding( weap ) || ( !get_wielded_item() && weap.is_null() ) ) {
2731+
bool can_use_gun = true;
2732+
bool use_silent = false;
2733+
const npc *me_as_npc = dynamic_cast<const npc *>( this );
2734+
if( me_as_npc ) {
2735+
if( me_as_npc->is_player_ally() && !me_as_npc->rules.has_flag( ally_rule::use_guns ) ) {
2736+
can_use_gun = false;
2737+
}
2738+
if( me_as_npc->is_player_ally() && me_as_npc->rules.has_flag( ally_rule::use_silent ) ) {
2739+
use_silent = true;
2740+
}
2741+
}
2742+
// ABSOLUTELY disgusting fake gun assembly for character creation
2743+
int pretend_ammo = 0;
2744+
if( pretend_have_ammo && maybe_weapon.is_gun() ) {
2745+
itype_id ammo_id = itype_id::NULL_ID();
2746+
if( maybe_weapon.ammo_default().is_null() ) {
2747+
ammo_id = item( maybe_weapon.magazine_default() ).ammo_default();
2748+
} else {
2749+
ammo_id = maybe_weapon.ammo_default();
2750+
}
2751+
const ammotype &type_of_ammo = item::find_type( ammo_id )->ammo->type;
2752+
if( maybe_weapon.magazine_integral() ) {
2753+
pretend_ammo = maybe_weapon.ammo_capacity( type_of_ammo );
2754+
} else {
2755+
pretend_ammo = item( maybe_weapon.magazine_default() ).ammo_capacity( type_of_ammo );
2756+
}
2757+
}
2758+
return evaluate_weapon_internal( maybe_weapon, can_use_gun, use_silent, pretend_ammo );
2759+
}
2760+
2761+
double Character::evaluate_weapon_internal( const item &maybe_weapon, bool can_use_gun,
2762+
bool use_silent, const int pretend_ammo ) const
2763+
{
2764+
if( is_wielding( maybe_weapon ) || ( !get_wielded_item() && maybe_weapon.is_null() ) ) {
27322765
auto cached_value = cached_info.find( "weapon_value" );
27332766
if( cached_value != cached_info.end() ) {
27342767
return cached_value->second;
27352768
}
27362769
}
2737-
double val_gun = gun_value( weap, ammo );
2738-
val_gun = val_gun /
2739-
5.0f; // This is an emergency patch to get melee and ranged in approximate parity, if you're looking at it in 2025 or later and it's still here... I'm sorry. Kill it with fire. Tear it all down, and rebuild a glorious castle from the ashes.
2770+
// Needed because evaluation includes electricity via linked cables.
2771+
const map &here = get_map();
2772+
2773+
bool allowed = can_use_gun && maybe_weapon.is_gun() && ( !use_silent || maybe_weapon.is_silent() );
2774+
// According to unmodified evaluation score, NPCs almost always prioritize wielding guns if they have one.
2775+
// This is relatively reasonable, as players can issue commands to NPCs when we do not want them to use ranged weapons.
2776+
// Conversely, we cannot directly issue commands when we want NPCs to prioritize ranged weapons.
2777+
// Note that the scoring method here is different from the 'weapon_value' used elsewhere.
2778+
double val_gun = allowed ? gun_value( maybe_weapon,
2779+
std::max( maybe_weapon.shots_remaining( here, this ), pretend_ammo )
2780+
) : 0;
27402781
add_msg_debug( debugmode::DF_NPC_ITEMAI,
2741-
"<color_magenta>weapon_value</color>%s %s valued at <color_light_cyan>%1.2f as a ranged weapon</color>.",
2742-
disp_name( true ), weap.type->get_id().str(), val_gun );
2743-
double val_melee = melee_value( weap );
2744-
val_melee *=
2745-
val_melee; // Same emergency patch. Same purple prose descriptors, you already saw them above.
2782+
"%s %s valued at <color_light_cyan>%1.2f as a ranged weapon to wield</color>.",
2783+
disp_name( true ), maybe_weapon.type->get_id().str(), val_gun );
2784+
double val_melee = melee_value( maybe_weapon );
27462785
add_msg_debug( debugmode::DF_NPC_ITEMAI,
2747-
"%s %s valued at <color_light_cyan>%1.2f as a melee weapon</color>.", disp_name( true ),
2748-
weap.type->get_id().str(), val_melee );
2749-
const double more = std::max( val_gun, val_melee );
2750-
const double less = std::min( val_gun, val_melee );
2751-
2752-
// A small bonus for guns you can also use to hit stuff with (bayonets etc.)
2753-
const double my_val = more + ( less / 2.0 );
2754-
add_msg_debug( debugmode::DF_MELEE, "%s (%ld ammo) sum value: %.1f", weap.type->get_id().str(),
2755-
ammo, my_val );
2756-
if( is_wielding( weap ) || ( !get_wielded_item() && weap.is_null() ) ) {
2757-
cached_info.emplace( "weapon_value", my_val );
2758-
}
2759-
return my_val;
2786+
"%s %s valued at <color_light_cyan>%1.2f as a melee weapon to wield</color>.", disp_name( true ),
2787+
maybe_weapon.type->get_id().str(), val_melee );
2788+
double val = std::max( val_gun, val_melee );
2789+
2790+
2791+
if( is_wielding( maybe_weapon ) || ( !get_wielded_item() && maybe_weapon.is_null() ) ) {
2792+
cached_info.emplace( "weapon_value", val );
2793+
}
2794+
return val;
27602795
}
27612796

27622797
double Character::melee_value( const item &weap ) const

src/npc.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,7 +1622,7 @@ npc_opinion npc::get_opinion_values( const Character &you ) const
16221622
} else {
16231623
npc_values.fear += 6;
16241624
}
1625-
} else if( you.weapon_value( *weapon ) > 20 ) {
1625+
} else if( you.evaluate_weapon( *weapon ) > 20 ) {
16261626
npc_values.fear += 2;
16271627
}
16281628

@@ -1852,7 +1852,7 @@ void npc::decide_needs()
18521852
}
18531853

18541854
const item &weap = weapon ? *weapon : null_item_reference();
1855-
needrank[need_weapon] = weapon_value( weap );
1855+
needrank[need_weapon] = evaluate_weapon( weap );
18561856
needrank[need_food] = 15 - get_hunger();
18571857
needrank[need_drink] = 15 - get_thirst();
18581858
cache_visit_items_with( "is_food", &item::is_food, [&]( const item & it ) {
@@ -2231,8 +2231,8 @@ double npc::value( const item &it, double market_price ) const
22312231
float ret = 1;
22322232
if( it.is_maybe_melee_weapon() || it.is_gun() ) {
22332233
// todo: remove when weapon_value takes an item_location
2234-
double wield_val = weapon ? weapon_value( *weapon ) : weapon_value( null_item_reference() );
2235-
double weapon_val = weapon_value( it ) - wield_val;
2234+
double wield_val = weapon ? evaluate_weapon( *weapon ) : evaluate_weapon( null_item_reference() );
2235+
double weapon_val = evaluate_weapon( it ) - wield_val;
22362236

22372237
if( weapon_val > 0 ) {
22382238
ret += weapon_val * 0.0002;

src/npc.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,8 +1131,6 @@ class npc : public Character
11311131
int evaluate_sleep_spot( tripoint_bub_ms p );
11321132
// Returns true if did something and we should end turn
11331133
bool scan_new_items();
1134-
// Returns score for how well this weapon might kill things
1135-
double evaluate_weapon( item &maybe_weapon, bool can_use_gun, bool use_silent ) const;
11361134
// Returns best weapon. Can return null (fists)
11371135
item *evaluate_best_weapon() const;
11381136
// Returns true if did wield it

src/npcmove.cpp

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ float npc::evaluate_character( const Character &candidate, bool my_gun, bool ene
512512
bool candidate_gun = candidate.get_wielded_item() && candidate.get_wielded_item()->is_gun();
513513
const item &candidate_weap = candidate.get_wielded_item() ? *candidate.get_wielded_item() :
514514
null_item_reference();
515-
double candidate_weap_val = candidate.weapon_value( candidate_weap );
515+
double candidate_weap_val = candidate.evaluate_weapon( candidate_weap );
516516
float candidate_health = candidate.hp_percentage() / 100.0f;
517517
float armour = estimate_armour( candidate );
518518
float speed = std::max( 0.25f, candidate.get_speed() / 100.0f );
@@ -1317,7 +1317,7 @@ void npc::regen_ai_cache()
13171317
ai_cache.danger = 0.0f;
13181318
ai_cache.total_danger = 0.0f;
13191319
item &weapon = get_wielded_item() ? *get_wielded_item() : null_item_reference();
1320-
ai_cache.my_weapon_value = weapon_value( weapon );
1320+
ai_cache.my_weapon_value = evaluate_weapon( weapon );
13211321
ai_cache.dangerous_explosives = find_dangerous_explosives();
13221322
mem_combat.formation_distance = -1;
13231323

@@ -3897,7 +3897,7 @@ bool npc::wants_take_that( const item &it )
38973897

38983898
item &weap = get_wielded_item() ? *get_wielded_item() : null_item_reference();
38993899
if( ( ( !whitelisting && value( it ) > min_value ) || item_whitelisted( it ) ) ||
3900-
weapon_value( it ) > weapon_value( weap ) ) {
3900+
evaluate_weapon( it ) > evaluate_weapon( weap ) ) {
39013901
good = true;
39023902
}
39033903

@@ -4133,62 +4133,36 @@ bool npc::do_player_activity()
41334133
return moves != old_moves;
41344134
}
41354135

4136-
double npc::evaluate_weapon( item &maybe_weapon, bool can_use_gun, bool use_silent ) const
4137-
{
4138-
// Needed because evaluation includes electricity via linked cables.
4139-
const map &here = get_map();
4140-
4141-
bool allowed = can_use_gun && maybe_weapon.is_gun() && ( !use_silent || maybe_weapon.is_silent() );
4142-
// According to unmodified evaluation score, NPCs almost always prioritize wielding guns if they have one.
4143-
// This is relatively reasonable, as players can issue commands to NPCs when we do not want them to use ranged weapons.
4144-
// Conversely, we cannot directly issue commands when we want NPCs to prioritize ranged weapons.
4145-
// Note that the scoring method here is different from the 'weapon_value' used elsewhere.
4146-
double val_gun = allowed ? gun_value( maybe_weapon, maybe_weapon.shots_remaining( here,
4147-
this ) ) : 0;
4148-
add_msg_debug( debugmode::DF_NPC_ITEMAI,
4149-
"%s %s valued at <color_light_cyan>%1.2f as a ranged weapon to wield</color>.",
4150-
disp_name( true ), maybe_weapon.type->get_id().str(), val_gun );
4151-
double val_melee = melee_value( maybe_weapon );
4152-
add_msg_debug( debugmode::DF_NPC_ITEMAI,
4153-
"%s %s valued at <color_light_cyan>%1.2f as a melee weapon to wield</color>.", disp_name( true ),
4154-
maybe_weapon.type->get_id().str(), val_melee );
4155-
double val = std::max( val_gun, val_melee );
4156-
return val;
4157-
}
4158-
41594136
item *npc::evaluate_best_weapon() const
41604137
{
4161-
bool can_use_gun = !is_player_ally() || rules.has_flag( ally_rule::use_guns );
4162-
bool use_silent = is_player_ally() && rules.has_flag( ally_rule::use_silent );
4163-
41644138
item_location weapon = get_wielded_item();
41654139
item &weap = weapon ? *weapon : null_item_reference();
41664140

41674141
// Check if there's something better to wield
41684142
item *best = &weap;
4169-
double best_value = evaluate_weapon( weap, can_use_gun, use_silent );
4143+
double best_value = evaluate_weapon( weap );
41704144

41714145
// To prevent changing to barely better stuff
41724146
best_value *= std::max<float>( 1.0f, ai_cache.danger_assessment / 10.0f );
41734147

41744148
// Fists aren't checked below
4175-
double fist_value = evaluate_weapon( null_item_reference(), can_use_gun, use_silent );
4149+
double fist_value = evaluate_weapon( null_item_reference() );
41764150

41774151
if( fist_value > best_value ) {
41784152
best = &null_item_reference();
41794153
best_value = fist_value;
41804154
}
41814155

41824156
//Now check through the NPC's inventory for melee weapons, guns, or holstered items
4183-
visit_items( [this, can_use_gun, use_silent, &weap, &best_value, &best]( item * node, item * ) {
4157+
visit_items( [this, &weap, &best_value, &best]( item * node, item * ) {
41844158
if( can_wield( *node ).success() ) {
41854159
double weapon_value = 0.0;
41864160
bool using_same_type_bionic_weapon = is_using_bionic_weapon()
41874161
&& node != &weap
41884162
&& node->type->get_id() == weap.type->get_id();
41894163

41904164
if( node->is_melee() || node->is_gun() ) {
4191-
weapon_value = evaluate_weapon( *node, can_use_gun, use_silent );
4165+
weapon_value = evaluate_weapon( *node );
41924166
if( weapon_value > best_value && !using_same_type_bionic_weapon ) {
41934167
best = const_cast<item *>( node );
41944168
best_value = weapon_value;
@@ -4207,10 +4181,6 @@ item *npc::evaluate_best_weapon() const
42074181

42084182
bool npc::wield_better_weapon()
42094183
{
4210-
// These are also assigned here so npc::evaluate_best_weapon() can be called by itself
4211-
bool can_use_gun = !is_player_ally() || rules.has_flag( ally_rule::use_guns );
4212-
bool use_silent = is_player_ally() && rules.has_flag( ally_rule::use_silent );
4213-
42144184
item_location weapon = get_wielded_item();
42154185
item &weap = weapon ? *weapon : null_item_reference();
42164186
item *best = &weap;
@@ -4220,12 +4190,12 @@ bool npc::wield_better_weapon()
42204190
if( best == better_weapon ) {
42214191
add_msg_debug( debugmode::DF_NPC, "Wielded %s is best at %.1f, not switching",
42224192
best->type->get_id().str(),
4223-
evaluate_weapon( *better_weapon, can_use_gun, use_silent ) );
4193+
evaluate_weapon( *better_weapon ) );
42244194
return false;
42254195
}
42264196

42274197
add_msg_debug( debugmode::DF_NPC, "Wielding %s at value %.1f", better_weapon->type->get_id().str(),
4228-
evaluate_weapon( *better_weapon, can_use_gun, use_silent ) );
4198+
evaluate_weapon( *better_weapon ) );
42294199

42304200
// Always returns true, but future proof
42314201
bool wield_success = wield( *better_weapon );

src/player_difficulty.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ static const damage_type_id damage_bash( "bash" );
3030
static const itype_id itype_bat( "bat" );
3131
static const itype_id itype_knife_combat( "knife_combat" );
3232
static const itype_id itype_machete( "machete" );
33+
static const itype_id itype_mossberg_500( "mossberg_500" );
34+
static const itype_id itype_ruger_1022( "ruger_1022" );
35+
static const itype_id itype_swbodyguard( "swbodyguard" );
3336

3437
static const profession_id profession_unemployed( "unemployed" );
3538

@@ -205,14 +208,23 @@ double player_difficulty::calc_dps_value( const Character &u )
205208
item early_cutting = item( itype_machete );
206209
item early_bashing = item( itype_bat );
207210

208-
double baseline = std::max( u.weapon_value( early_piercing ),
209-
u.weapon_value( early_cutting ) );
210-
baseline = std::max( baseline, u.weapon_value( early_bashing ) );
211+
// and some firearms, based on availability.
212+
item most_common_pistol = item( itype_swbodyguard );
213+
item most_common_rifle = item( itype_ruger_1022 );
214+
item most_common_shotgun = item( itype_mossberg_500 );
215+
216+
std::vector<item> weapons_to_test = {early_piercing, early_cutting, early_bashing, most_common_pistol, most_common_rifle, most_common_shotgun};
217+
double baseline = 0.0;
218+
219+
for( item &weapon : weapons_to_test ) {
220+
const double new_weapon_value = u.evaluate_weapon( weapon, true );
221+
baseline = std::max( baseline, new_weapon_value );
222+
}
211223

212224
// check any other items the character has on them
213225
if( u.prof ) {
214226
for( const item &i : u.prof->items( true, std::vector<trait_id>() ) ) {
215-
baseline = std::max( baseline, u.weapon_value( i ) );
227+
baseline = std::max( baseline, u.evaluate_weapon( i, true ) );
216228
}
217229
}
218230

src/talker_npc.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -433,15 +433,13 @@ std::string talker_npc::give_item_to( const bool to_use )
433433
bool taken = false;
434434
std::string reason = me_npc->chat_snippets().snip_give_nope.translated();
435435
const item_location weapon = me_npc->get_wielded_item();
436-
int our_ammo = me_npc->ammo_count_for( weapon );
437-
int new_ammo = me_npc->ammo_count_for( loc );
438-
const double new_weapon_value = me_npc->weapon_value( given, new_ammo );
436+
const double new_weapon_value = me_npc->evaluate_weapon( given );
439437
const item &weap = weapon ? *weapon : null_item_reference();
440-
const double cur_weapon_value = me_npc->weapon_value( weap, our_ammo );
441-
add_msg_debug( debugmode::DF_TALKER, "NPC evaluates own %s (%d ammo): %0.1f",
442-
weap.typeId().str(), our_ammo, cur_weapon_value );
443-
add_msg_debug( debugmode::DF_TALKER, "NPC evaluates your %s (%d ammo): %0.1f",
444-
given.typeId().str(), new_ammo, new_weapon_value );
438+
const double cur_weapon_value = me_npc->evaluate_weapon( weap );
439+
add_msg_debug( debugmode::DF_TALKER, "NPC evaluates own %s: %0.1f",
440+
weap.typeId().str(), cur_weapon_value );
441+
add_msg_debug( debugmode::DF_TALKER, "NPC evaluates your %s: %0.1f",
442+
given.typeId().str(), new_weapon_value );
445443
if( to_use ) {
446444
// Eating first, to avoid evaluating bread as a weapon
447445
const consumption_result consume_res = try_consume( *me_npc, given, reason );

0 commit comments

Comments
 (0)