Skip to content

Commit

Permalink
Rework bionic weapon/gun framework
Browse files Browse the repository at this point in the history
Complete first implementation
  • Loading branch information
KheirFerrum committed Mar 21, 2023
1 parent 201367c commit 5e2711e
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 105 deletions.
8 changes: 4 additions & 4 deletions data/json/bionics.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@
"occupied_bodyparts": [ [ "torso", 16 ], [ "arm_l", 4 ], [ "arm_r", 4 ] ],
"act_cost": "100 kJ",
"fake_item": "bio_lightning",
"flags": [ "BIONIC_GUN" ]
"flags": [ "BIONIC_GUN", "BIONIC_NPC_USABLE" ]
},
{
"id": "bio_claws",
Expand Down Expand Up @@ -375,7 +375,7 @@
"occupied_bodyparts": [ [ "arm_r", 10 ], [ "hand_r", 5 ] ],
"act_cost": "50 kJ",
"fake_item": "bio_emp_gun",
"flags": [ "BIONIC_GUN" ]
"flags": [ "BIONIC_GUN", "BIONIC_NPC_USABLE" ]
},
{
"id": "bio_ethanol",
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 @@ -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
182 changes: 132 additions & 50 deletions src/bionics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,76 +451,155 @@ static void force_comedown( effect &eff )

void npc::discharge_cbm_weapon()
{
if( cbm_weapon_index < 0 ) {
if( cbm_active_index < 0 ) {
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( -( *my_bionics )[ cbm_active_index ].info().power_activate );
cbm_fake_active = null_item_reference();
cbm_active_index = -1;
}

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

void npc::check_or_use_weapon_cbm()
{
// if both toggle and active bionics have been chosen, keep using them.
if( cbm_weapon_index >= 0 && cbm_active_index >= 0 ) {
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_weapon_index < 0 && bio.info().has_flag( flag_BIONIC_WEAPON ) ) {
avail_toggle_cbms.push_back( cbm_index );
}
index += 1;
}
if( !found ) {
return;
if( cbm_active_index < 0 && 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_weapon_index < 0 ) {
if( !avail_toggle_cbms.empty() ) {
int melee_index = -1;
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 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 );
}
const int cbm_ammo = free_power / bio.info().power_activate;
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;
}

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;
}
} 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( get_player_character().sees( pos() ) ) {
add_msg( m_info, _( "%s activates their %s." ), disp_name(), bio.info().name );
if( melee_index >= 0 ) {
// Previous iteration chose a CBM. Compare previous and current.
const item cbm_best_fake = item( ( *my_bionics )[ melee_index ].info().fake_item );

if( npc_ai::weapon_value( *this, cbm_best_fake, cbm_best_fake.shots_remaining( free_power ) )
< npc_ai::weapon_value( *this, cbm_fake, cbm_fake.shots_remaining( free_power ) ) ) {
// Current is better, update index.
melee_index = i;
}
} else {
const units::energy ups_charges = units::from_kilojoule( charges_of( itype_UPS ) );

if( npc_ai::weapon_value( *this, weapon, weapon.shots_remaining( ups_charges ) ) <
npc_ai::weapon_value( *this, cbm_fake, cbm_fake.shots_remaining() ) ) {
melee_index = i;
}
}
}
}

if( melee_index > 0 ) {
// Decided on a bionic, swap to it.
if( is_armed() ) {
stow_item( weapon );
}
activate_bionic_by_id( ( *my_bionics )[ melee_index ].id );
if( get_player_character().sees( pos() ) ) {
add_msg( m_info, _( "%s activates their %s." ), disp_name(),
( *my_bionics )[ melee_index ].info().name );
}
cbm_weapon_index = melee_index;
}
}
}

weapon = item( bio.info().fake_item );
mod_power_level( -bio.info().power_activate );
bio.powered = true;
cbm_weapon_index = index;
if( cbm_active_index < 0 ) {
if( !avail_active_cbms.empty() ) {
int ranged_index = -1;
bool wield_gun = weapon.is_gun();
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( ranged_index > 0 ) {
// Previous iteration chose a CBM, compare them.
const item b_cbm_weapon = item( ( *my_bionics )[ ranged_index ].info().fake_item );

int b_cbm_ammo = free_power / ( *my_bionics )[ranged_index].info().power_activate;

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

} 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 units::energy ups_charges = units::from_kilojoule( charges_of( itype_UPS ) );

if( npc_ai::weapon_value( *this, weapon, weapon.shots_remaining( ups_charges ) ) <
npc_ai::weapon_value( *this, cbm_weapon, cbm_ammo ) ) {
ranged_index = i;
}

} else {
// If it's not a gun, then we only need to compare the CBMs as
// You can fire a activated CBM without the need to equip/unequip anything.
ranged_index = i;
}
}
cbm_active_index = ranged_index;
if( cbm_active_index >= 0 ) {
cbm_fake_active = item( ( *my_bionics )[cbm_active_index].info().fake_item );
}
}
}
}

Expand Down Expand Up @@ -574,6 +653,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( g->u.sees( pos() ) ) {
add_msg( m_info, _( "%s activates their %s." ), disp_name(),
bio.info().name );
}
};
auto refund_power = [&]() {
Expand Down
16 changes: 16 additions & 0 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7477,6 +7477,22 @@ int item::ammo_required() const
return 0;
}

int item::shots_remaining( units::energy power ) const
{
int shots = 0;
units::energy energy_drain = units::from_kilojoule( get_gun_ups_drain() );
if( ammo_required() > 0 && get_gun_ups_drain() > 0 ) {
shots = std::min( ammo_remaining(), power / energy_drain );
} else if( get_gun_ups_drain() > 0 ) {
shots = power / energy_drain;
} else if( ammo_required() > 0 ) {
shots = ammo_remaining();
} else {
shots = 10;
}
return shots;
}

bool item::ammo_sufficient( int qty ) const
{
return ammo_remaining() >= ammo_required() * qty;
Expand Down
2 changes: 2 additions & 0 deletions src/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -1723,6 +1723,8 @@ class item : public visitable<item>
int ammo_capacity( bool potential_capacity ) const;
/** Quantity of ammunition consumed per usage of tool or with each shot of gun */
int ammo_required() const;
/** Number of shots left, considers if a gun uses ups, ammo, or both */
int shots_remaining( units::energy power = 0_J ) const;

/**
* Check if sufficient ammo is loaded for given number of uses.
Expand Down
12 changes: 8 additions & 4 deletions src/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,9 @@ class npc : public player
// can't use bionics::activate because it calls plfire directly
void discharge_cbm_weapon();
// check if an NPC has a bionic weapon and activate it if possible
void check_or_use_weapon_cbm( const bionic_id &cbm_id );
void check_or_use_weapon_cbm();
// disable toggled weapon cbms
void deactivate_weapon_cbm();

// complain about a specific issue if enough time has passed
// @param issue string identifier of the issue
Expand Down Expand Up @@ -1370,9 +1372,11 @@ class npc : public player
void load( const JsonObject &data );

private:
// the weapon we're actually holding when using bionic fake guns
item real_weapon;
// the index of the bionics for the fake gun;
// index for chosen activated cbm weapon;
int cbm_active_index = -1;
// REALLY fake item temporarily created to prevent segfaults;
item cbm_fake_active = null_item_reference();
// index for chosen toggled cbm weapon;
int cbm_weapon_index = -1;

bool dead = false; // If true, we need to be cleaned up
Expand Down
Loading

0 comments on commit 5e2711e

Please sign in to comment.