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

Rework Bionic weapon selection and make BIONIC_GUNS work for NPCs. Fix NPC Aiming. #1705

Merged
merged 38 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
276849a
Rework bionic weapon/gun framework
KheirFerrum Mar 20, 2023
670762d
Fixes guns in general
KheirFerrum Mar 22, 2023
a89129b
Update npcmove.cpp
KheirFerrum Mar 22, 2023
d80815d
Reload toggled CBMs out of combat
KheirFerrum Mar 22, 2023
f407de6
Don't need to nest it so deep.
KheirFerrum Mar 30, 2023
b788347
Reduce item generation for comparison.
KheirFerrum Mar 30, 2023
3a75c7f
Refactor
KheirFerrum Mar 30, 2023
8ac26cd
Merge branch 'upload' into Fix-bionic-guns
KheirFerrum Mar 30, 2023
bff27ef
Merge branch 'upload' into Fix-bionic-guns
KheirFerrum Apr 9, 2023
acfce67
Merge branch 'upload' into Fix-bionic-guns
KheirFerrum Apr 11, 2023
c5e9530
Rework
KheirFerrum Apr 11, 2023
3f4b1ab
shots_remaining moved
KheirFerrum Apr 11, 2023
28dd9ce
g->u to get_player_character()
KheirFerrum Apr 11, 2023
7c8adc8
Modify shots_remaining.
KheirFerrum Apr 11, 2023
7c304d2
Remove unused variables
KheirFerrum Apr 11, 2023
ec3086d
Change cache to use enums
KheirFerrum Apr 11, 2023
3c07faa
Convert to using bionic_id
KheirFerrum Apr 11, 2023
77862cd
Update src/character.h
KheirFerrum Apr 14, 2023
298388d
Use key directly
KheirFerrum Apr 14, 2023
188d379
Shots_remaining update and comments
KheirFerrum Apr 14, 2023
86c21c1
Update bionics.cpp
KheirFerrum Apr 14, 2023
3b600c5
Update src/npcmove.cpp
KheirFerrum Apr 14, 2023
2e82c53
Streamline functions
KheirFerrum Apr 14, 2023
efe8bae
Merge branch 'upload' into Fix-bionic-guns
KheirFerrum Apr 19, 2023
72d2060
modes.clear()
KheirFerrum Apr 19, 2023
fe59696
Rename enums
KheirFerrum Apr 20, 2023
30e30f3
Fake shots
KheirFerrum Apr 20, 2023
1b38ab8
No need to find, just return the value if any
KheirFerrum Apr 20, 2023
289683b
flag_NO_UNWIELD
KheirFerrum Apr 20, 2023
a9c2272
Swap to mutable array
KheirFerrum Apr 20, 2023
d573e68
shots_remaining reorganize
KheirFerrum Apr 25, 2023
62cfbc9
npc_ai_info_cache defaults to -1
KheirFerrum Apr 25, 2023
488eb9d
Reorganize
KheirFerrum Apr 25, 2023
ee7988b
Update melee.cpp
KheirFerrum Apr 25, 2023
4b4f825
Fix things
KheirFerrum Apr 25, 2023
851a5f1
Clean up
KheirFerrum Apr 26, 2023
1bdc7ae
Update deserialization
KheirFerrum Apr 26, 2023
382ca65
Remove unused static ids
KheirFerrum Apr 27, 2023
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
8 changes: 4 additions & 4 deletions data/json/bionics.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
"encumbrance": [ [ "arm_l", 5 ] ],
"act_cost": "50 J",
"fake_item": "bio_shotgun_gun",
"flags": [ "BIONIC_TOGGLED", "BIONIC_WEAPON", "NO_UNWIELD" ]
"flags": [ "BIONIC_TOGGLED", "BIONIC_WEAPON", "BIONIC_NPC_USABLE" ]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like having it require a flag. Are not all bionic weapon/gun items going to be usable?

Copy link
Collaborator Author

@KheirFerrum KheirFerrum Apr 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My biggest concern is that this might cause NPCs to pick an item that they have no business using like rockets and similarly destructive weaponry. I could remove this though and define it using the fake_item's firing modes.

The other issue is that disallowing this from that end leads to the illusion that those items are NPC usable when NPCs actually will not use them.

},
{
"id": "bio_blindfold",
Expand Down Expand Up @@ -607,7 +607,7 @@
"occupied_bodyparts": [ [ "hand_r", 1 ] ],
"act_cost": "30 kJ",
"fake_item": "bio_laser_gun",
"flags": [ "BIONIC_GUN" ]
"flags": [ "BIONIC_GUN", "BIONIC_NPC_USABLE" ]
},
{
"id": "bio_leaky",
Expand Down Expand Up @@ -1234,7 +1234,7 @@
"description": "Your right hand can fold inward to reveal a mechanism capable of igniting and firing 40mm grenades across medium distances with great accuracy due built in ballistic calculator.",
"occupied_bodyparts": [ [ "hand_r", 3 ] ],
"fake_item": "afs_bionic_rocket",
"flags": [ "BIONIC_TOGGLED", "BIONIC_WEAPON", "NO_UNWIELD" ],
"flags": [ "BIONIC_TOGGLED", "BIONIC_WEAPON" ],
"act_cost": 5
},
{
Expand Down Expand Up @@ -1263,7 +1263,7 @@
"occupied_bodyparts": [ [ "arm_l", 20 ], [ "hand_l", 5 ] ],
"act_cost": 50,
"fake_item": "bio_blaster_gun",
"flags": [ "BIONIC_GUN" ]
"flags": [ "BIONIC_GUN", "BIONIC_NPC_USABLE" ]
},
{
"id": "bio_perpetual_test",
Expand Down
2 changes: 1 addition & 1 deletion data/json/items/gun/bio.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"type": "GUN",
"name": { "str": "laser finger" },
"description": "this a pseudo item",
"volume": "3 L",
"volume": "1 L",
"price": 0,
"material": [ "steel", "plastic" ],
"symbol": "(",
Expand Down
8 changes: 8 additions & 0 deletions data/json/monsters/misc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
"regeneration_modifiers": [ { "effect": "onfire", "base_mod": -0.3, "scaling_mod": -0.15 }, { "effect": "corroding", "base_mod": -0.8 } ],
"flags": [ "IMMOBILE", "NOT_HALLUCINATION", "FILTHY" ]
},
{
"id": "debug_mon_threat",
"type": "MONSTER",
"copy-from": "debug_mon",
"name": { "str": "menacing debug monster" },
"description": "This monster exists only for testing purposes. It's threatening enough to warrant bullets from NPCs",
"diff": 20
},
{
"id": "mon_dragon_dummy",
"type": "MONSTER",
Expand Down
2 changes: 1 addition & 1 deletion src/avatar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@ bool avatar::wield( item &target )
if( !unwield() ) {
return false;
}
clear_npc_ai_info_cache( "weapon_value" );
clear_npc_ai_info_cache( npc_ai_info::weapon_value );
if( target.is_null() ) {
return true;
}
Expand Down
194 changes: 146 additions & 48 deletions src/bionics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "input.h"
#include "int_id.h"
#include "item.h"
#include "item_functions.h"
#include "item_location.h"
#include "itype.h"
#include "json.h"
Expand Down Expand Up @@ -454,76 +455,170 @@ static void force_comedown( effect &eff )

void npc::discharge_cbm_weapon()
{
if( cbm_weapon_index < 0 ) {
if( cbm_active.is_null() ) {
return;
}
const bionic &bio = ( *my_bionics )[cbm_weapon_index];
mod_power_level( -bio.info().power_activate );
weapon = real_weapon;
cbm_weapon_index = -1;
mod_power_level( -cbm_active->power_activate );
cbm_fake_active = null_item_reference();
cbm_active = bionic_id::NULL_ID();
}

void npc::check_or_use_weapon_cbm( const bionic_id &cbm_id )
void deactivate_weapon_cbm( npc &who )
{
// if we're already using a bio_weapon, keep using it
if( cbm_weapon_index >= 0 ) {
for( bionic &i : *who.my_bionics ) {
if( i.powered && i.info().has_flag( flag_BIONIC_WEAPON ) ) {
who.deactivate_bionic( i );
}
}
who.clear_npc_ai_info_cache( npc_ai_info::weapon_value );
}

std::vector<std::pair<bionic_id, item>> find_reloadable_cbms( npc &who )
{
std::vector<std::pair<bionic_id, item>> cbm_list;
// Runs down full list of CBMs that qualify as weapons.
// Need a way to make this less costly.
for( bionic bio : *who.my_bionics ) {
if( !bio.info().has_flag( flag_BIONIC_WEAPON ) ) {
continue;
}
item cbm_fake = item( bio.info().fake_item );
// I'd hope it's not possible to be greater than but...
if( static_cast<int>( bio.ammo_count ) >= cbm_fake.ammo_capacity() ) {
continue;
}
if( bio.ammo_count > 0 ) {
cbm_fake.ammo_set( bio.ammo_loaded, bio.ammo_count );
}
cbm_list.emplace_back( std::make_pair( bio.id, cbm_fake ) );
}
return cbm_list;
}

void npc::check_or_use_weapon_cbm()
{
// if both toggle and active bionics have been chosen, keep using them.
if( !cbm_toggled.is_null() && !cbm_active.is_null() ) {
return;
}

std::vector<int> avail_toggle_cbms;
std::vector<int> avail_active_cbms;

const float allowed_ratio = static_cast<int>( rules.cbm_reserve ) / 100.0f;
const units::energy free_power = get_power_level() - get_max_power_level() * allowed_ratio;
if( free_power <= 0_J ) {
return;
}

int index = 0;
bool found = false;
for( bionic &i : *my_bionics ) {
if( i.id == cbm_id && !i.powered ) {
found = true;
break;
int cbm_index = 0;
for( bionic &bio : *my_bionics ) {
// I'm not checking if NPC_USABLE because if it isn't it shouldn't be in them.
if( cbm_toggled.is_null() && bio.info().has_flag( flag_BIONIC_WEAPON ) ) {
avail_toggle_cbms.push_back( cbm_index );
}
index += 1;
}
if( !found ) {
return;
if( cbm_active.is_null() && bio.info().has_flag( flag_BIONIC_GUN ) ) {
avail_active_cbms.push_back( cbm_index );
}
cbm_index++;
}
bionic &bio = ( *my_bionics )[index];

if( bio.info().has_flag( flag_BIONIC_GUN ) ) {
const item cbm_weapon = item( bio.info().fake_item );
bool not_allowed = !rules.has_flag( ally_rule::use_guns ) ||
( rules.has_flag( ally_rule::use_silent ) && !cbm_weapon.is_silent() );
if( is_player_ally() && not_allowed ) {
return;
}
// There's no point in checking the bionics if they can't unwield what they have.
if( !weapon.has_flag( flag_NO_UNWIELD ) && cbm_toggled.is_null() && !avail_toggle_cbms.empty() ) {
int toggle_index = -1;
item best_cbm_weap = null_item_reference();
for( int i : avail_toggle_cbms ) {
bionic &bio = ( *my_bionics )[ i ];
if( free_power > bio.info().power_activate ) {
item cbm_fake = item( bio.info().fake_item );
if( bio.ammo_count > 0 ) {
cbm_fake.ammo_set( bio.ammo_loaded, bio.ammo_count );
}
const int fake_shots = item_funcs::shots_remaining( *this, cbm_fake );

const int ups_charges = charges_of( itype_UPS );
int ammo_count = weapon.ammo_remaining();
const int ups_drain = weapon.get_gun_ups_drain();
if( ups_drain > 0 ) {
ammo_count = std::min( ammo_count, ups_charges / ups_drain );
bool not_allowed = ( !rules.has_flag( ally_rule::use_guns ) && cbm_fake.is_gun() ) ||
( rules.has_flag( ally_rule::use_silent ) && !cbm_fake.is_silent() );
if( not_allowed ) {
continue;
}

const item to_compare = toggle_index >= 0 ? best_cbm_weap : weapon;
const int to_compare_shots = item_funcs::shots_remaining( *this, to_compare );

if( npc_ai::weapon_value( *this, to_compare, to_compare_shots ) <
npc_ai::weapon_value( *this, cbm_fake, fake_shots ) ) {
toggle_index = i;
best_cbm_weap = cbm_fake;
}
}
}
const int cbm_ammo = free_power / bio.info().power_activate;

if( npc_ai::weapon_value( *this, weapon, ammo_count ) <
npc_ai::weapon_value( *this, cbm_weapon, cbm_ammo ) ) {
real_weapon = weapon;
weapon = cbm_weapon;
cbm_weapon_index = index;
if( toggle_index >= 0 ) {
// Decided on a bionic, swap to it.
if( is_armed() ) {
stow_item( weapon );
}
activate_bionic_by_id( ( *my_bionics )[ toggle_index ].id );
if( get_player_character().sees( pos() ) ) {
add_msg( m_info, _( "%s activates their %s." ), disp_name(),
( *my_bionics )[ toggle_index ].info().name );
}
clear_npc_ai_info_cache( npc_ai_info::weapon_value );
cbm_toggled = ( *my_bionics )[ toggle_index ].id;
}
} else if( bio.info().has_flag( flag_BIONIC_WEAPON ) && !weapon.has_flag( flag_NO_UNWIELD ) &&
free_power > bio.info().power_activate ) {
if( is_armed() ) {
stow_item( weapon );
}

if( cbm_active.is_null() && !avail_active_cbms.empty() ) {
int active_index = -1;
bool wield_gun = weapon.is_gun();
item best_cbm_active = null_item_reference();
for( int i : avail_active_cbms ) {
bionic &bio = ( *my_bionics )[ i ];
const item cbm_weapon = item( bio.info().fake_item );

bool not_allowed = !rules.has_flag( ally_rule::use_guns ) ||
( rules.has_flag( ally_rule::use_silent ) && !cbm_weapon.is_silent() );
if( is_player_ally() && not_allowed ) {
continue;
}

// Simpler than weapons because they're not real items and cannot be reloaded.
// Have fun changing this in the future.
int cbm_ammo = free_power / bio.info().power_activate;

if( active_index >= 0 ) {
// Previous iteration chose a CBM, compare them.
int b_cbm_ammo = free_power / ( *my_bionics )[active_index].info().power_activate;

if( npc_ai::weapon_value( *this, best_cbm_active, b_cbm_ammo ) < npc_ai::weapon_value( *this,
cbm_weapon, cbm_ammo ) ) {
// New one is better, update.
active_index = i;
best_cbm_active = cbm_weapon;
}

} else if( wield_gun ) {
// For melee we keep it in reserve anyway, but ranged we're using either it or this one.
// Right now no BIONIC_GUNS are melee weapons, unlike BIONIC_WEAPONS and the bionic shotgun.
const int weapon_shots = item_funcs::shots_remaining( *this, weapon );

if( npc_ai::weapon_value( *this, weapon, weapon_shots ) <
npc_ai::weapon_value( *this, cbm_weapon, cbm_ammo ) ) {
active_index = i;
best_cbm_active = cbm_weapon;
}

} else {
// If it's not a gun, then we only need to compare the CBMs as
// You can fire an activated CBM without the need to equip/unequip anything.
active_index = i;
best_cbm_active = cbm_weapon;
}
}
if( get_player_character().sees( pos() ) ) {
add_msg( m_info, _( "%s activates their %s." ), disp_name(), bio.info().name );
cbm_active = ( *my_bionics )[ active_index ].id;
if( !cbm_active.is_null() ) {
cbm_fake_active = best_cbm_active;
}

weapon = item( bio.info().fake_item );
mod_power_level( -bio.info().power_activate );
bio.powered = true;
cbm_weapon_index = index;
}
}

Expand Down Expand Up @@ -577,6 +672,9 @@ bool Character::activate_bionic( bionic &bio, bool eff_only )
auto add_msg_activate = [&]() {
if( !eff_only && !bio.is_auto_start_keep_full() ) {
add_msg_if_player( m_info, _( "You activate your %s." ), bio.info().name );
} else if( get_player_character().sees( pos() ) ) {
add_msg( m_info, _( "%s activates their %s." ), disp_name(),
bio.info().name );
}
};
auto refund_power = [&]() {
Expand Down
21 changes: 9 additions & 12 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ Character::Character() :
drench_capacity[bp_hand_l] = 3;
drench_capacity[bp_hand_r] = 3;
drench_capacity[bp_torso] = 40;
npc_ai_info_cache.fill(-1.0);
}
// *INDENT-ON*

Expand Down Expand Up @@ -2261,7 +2262,8 @@ item &Character::i_add( item it, bool should_stack )
}
auto &item_in_inv = inv.add_item( it, keep_invlet, true, should_stack );
item_in_inv.on_pickup( *this );
clear_npc_ai_info_cache( "reloadables" );
clear_npc_ai_info_cache( npc_ai_info::reloadables );
clear_npc_ai_info_cache( npc_ai_info::reloadable_cbms );
return item_in_inv;
}

Expand Down Expand Up @@ -2490,7 +2492,7 @@ item Character::remove_weapon()
{
item tmp = weapon;
weapon = item();
clear_npc_ai_info_cache( "weapon_value" );
clear_npc_ai_info_cache( npc_ai_info::weapon_value );
return tmp;
}

Expand Down Expand Up @@ -10585,24 +10587,19 @@ void Character::set_underwater( bool x )
}
}

void Character::clear_npc_ai_info_cache( const std::string &key ) const
void Character::clear_npc_ai_info_cache( npc_ai_info key ) const
{
npc_ai_info_cache.erase( key );
npc_ai_info_cache[key] = -1.0;
}

void Character::set_npc_ai_info_cache( const std::string &key, double val ) const
void Character::set_npc_ai_info_cache( npc_ai_info key, double val ) const
{
npc_ai_info_cache[key] = val;
}

std::optional<double> Character::get_npc_ai_info_cache( const std::string &key ) const
std::optional<double> Character::get_npc_ai_info_cache( npc_ai_info key ) const
{
auto it = npc_ai_info_cache.find( key );
if( it == npc_ai_info_cache.end() ) {
return std::nullopt;
} else {
return it->second;
}
return npc_ai_info_cache[key];
}

float Character::stability_roll() const
Expand Down
15 changes: 11 additions & 4 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ enum vision_modes {
NUM_VISION_MODES
};

enum npc_ai_info : size_t {
weapon_value = 0,
reloadables,
reloadable_cbms,
num_npc_ai_info,
};
KheirFerrum marked this conversation as resolved.
Show resolved Hide resolved

enum character_movemode : int {
CMM_WALK = 0,
CMM_RUN,
Expand Down Expand Up @@ -2241,7 +2248,7 @@ class Character : public Creature, public visitable<Character>
tripoint cached_position;
inventory cached_crafting_inventory;

mutable std::map<std::string, double> npc_ai_info_cache;
mutable std::array<double, npc_ai_info::num_npc_ai_info> npc_ai_info_cache;

safe_reference_anchor anchor;

Expand All @@ -2264,9 +2271,9 @@ class Character : public Creature, public visitable<Character>

void set_underwater( bool x ) override;

void clear_npc_ai_info_cache( const std::string &key ) const;
void set_npc_ai_info_cache( const std::string &key, double val ) const;
std::optional<double> get_npc_ai_info_cache( const std::string &key ) const;
void clear_npc_ai_info_cache( npc_ai_info key ) const;
void set_npc_ai_info_cache( npc_ai_info key, double val ) const;
std::optional<double> get_npc_ai_info_cache( npc_ai_info key ) const;

safe_reference<Character> get_safe_reference();
};
Expand Down
Loading