Skip to content

Commit

Permalink
Merge pull request #28245 from kevingranade/reload-all-guns-and-magaz…
Browse files Browse the repository at this point in the history
…ines

Reload all guns and magazines
  • Loading branch information
ZhilkinSerg authored Mar 22, 2019
2 parents ab649c3 + b2ff110 commit 63d1990
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 68 deletions.
25 changes: 25 additions & 0 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,31 @@ std::vector<item_location> Character::find_ammo( const item &obj, bool empty, in
return res;
}

std::vector<item_location> Character::find_reloadables()
{
std::vector<item_location> reloadables;

visit_items( [this, &reloadables]( item * node ) {
if( node->is_holster() ) {
return VisitResponse::NEXT;
}
bool reloadable = false;
if( node->is_gun() && !node->magazine_compatible().empty() ) {
reloadable = node->magazine_current() == nullptr ||
node->ammo_remaining() < node->ammo_capacity();
} else {
reloadable = ( node->is_magazine() || node->is_bandolier() ||
( node->is_gun() && node->magazine_integral() ) ) &&
node->ammo_remaining() < node->ammo_capacity();
}
if( reloadable ) {
reloadables.push_back( item_location( *this, node ) );
}
return VisitResponse::SKIP;
} );
return reloadables;
}

units::mass Character::weight_carried() const
{
return weight_carried_with_tweaks( {} );
Expand Down
4 changes: 4 additions & 0 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@ class Character : public Creature, public visitable<Character>
*/
std::vector<item_location> find_ammo( const item &obj, bool empty = true, int radius = 1 ) const;

/**
* Searches for weapons and magazines that can be reloaded.
*/
std::vector<item_location> find_reloadables();
/**
* Counts ammo and UPS charges (lower of) for a given gun on the character.
*/
Expand Down
97 changes: 67 additions & 30 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9310,7 +9310,7 @@ void game::reload( int pos, bool prompt )
reload( loc, prompt );
}

void game::reload( item_location &loc, bool prompt )
void game::reload( item_location &loc, bool prompt, bool empty )
{
item *it = loc.get_item();
bool use_loc = true;
Expand Down Expand Up @@ -9377,7 +9377,7 @@ void game::reload( item_location &loc, bool prompt )

item::reload_option opt = u.ammo_location && it->can_reload_with( u.ammo_location->typeId() ) ?
item::reload_option( &u, it, it, u.ammo_location.clone() ) :
u.select_ammo( *it, prompt );
u.select_ammo( *it, prompt, empty );

if( opt ) {
u.assign_activity( activity_id( "ACT_RELOAD" ), opt.moves(), opt.qty() );
Expand All @@ -9392,39 +9392,76 @@ void game::reload( item_location &loc, bool prompt )
refresh_all();
}

void game::reload()
{
// general reload item menu will popup if:
// - user is unarmed;
// - weapon wielded can't be reloaded (bows can, they just reload before shooting automatically)
// - weapon wielded reloads before shooting (like bows)
if( !u.is_armed() || !u.can_reload( u.weapon ) || u.weapon.has_flag( "RELOAD_AND_SHOOT" ) ) {
vehicle *veh = veh_pointer_or_null( m.veh_at( u.pos() ) );
turret_data turret;
if( veh && ( turret = veh->turret_query( u.pos() ) ) && turret.can_reload() ) {
item::reload_option opt = g->u.select_ammo( *turret.base(), true );
if( opt ) {
g->u.assign_activity( activity_id( "ACT_RELOAD" ), opt.moves(), opt.qty() );
g->u.activity.targets.emplace_back( turret.base() );
g->u.activity.targets.push_back( std::move( opt.ammo ) );
}
return;
void game::reload( bool try_everything )
{
// As a special streamlined activity, hitting reload repeatedly should:
// Reload wielded gun
// First reload a magazine if necessary.
// Then load said magazine into gun.
// Reload magazines that are compatible with the current gun.
// Reload other guns in inventory.
// Reload misc magazines in inventory.
std::vector<item_location> reloadables = u.find_reloadables();
std::sort( reloadables.begin(), reloadables.end(),
[this]( const item_location & a, const item_location & b ) {
const item *ap = a.get_item();
const item *bp = b.get_item();
// Current wielded weapon comes first.
if( this->u.is_wielding( *ap ) ) {
return true;
}

item_location item_loc = inv_map_splice( [&]( const item & it ) {
return u.rate_action_reload( it ) == HINT_GOOD;
}, _( "Reload item" ), 1, _( "You have nothing to reload." ) );

if( !item_loc ) {
add_msg( _( "Never mind." ) );
if( this->u.is_wielding( *bp ) ) {
return false;
}
// Second sort by afiliation with wielded gun
const std::set<itype_id> compatible_magazines = this->u.weapon.magazine_compatible();
const bool mag_ap = compatible_magazines.count( ap->typeId() ) > 0;
const bool mag_bp = compatible_magazines.count( bp->typeId() ) > 0;
if( mag_ap != mag_bp ) {
return mag_ap;
}
// Third sort by gun vs magazine,
if( ap->is_gun() != bp->is_gun() ) {
return ap->is_gun();
}
// Finally sort by speed to reload.
return ( ap->get_reload_time() * ( ap->ammo_capacity() - ap->ammo_remaining() ) ) <
( bp->get_reload_time() * ( bp->ammo_capacity() - bp->ammo_remaining() ) );
} );
for( item_location &candidate : reloadables ) {
std::vector<item::reload_option> ammo_list;
u.list_ammo( *candidate.get_item(), ammo_list, false );
if( !ammo_list.empty() ) {
reload( candidate, false, false );
return;
}
}
// Just for testing, bail out here to avoid unwanted side effects.
if( !try_everything ) {
return;
}
// If we make it here and haven't found anything to reload, start looking elsewhere.
vehicle *veh = veh_pointer_or_null( m.veh_at( u.pos() ) );
turret_data turret;
if( veh && ( turret = veh->turret_query( u.pos() ) ) && turret.can_reload() ) {
item::reload_option opt = g->u.select_ammo( *turret.base(), true );
if( opt ) {
g->u.assign_activity( activity_id( "ACT_RELOAD" ), opt.moves(), opt.qty() );
g->u.activity.targets.emplace_back( turret.base() );
g->u.activity.targets.push_back( std::move( opt.ammo ) );
}
return;
}
item_location item_loc = inv_map_splice( [&]( const item & it ) {
return u.rate_action_reload( it ) == HINT_GOOD;
}, _( "Reload item" ), 1, _( "You have nothing to reload." ) );

reload( item_loc );

} else {
reload( -1 );
if( !item_loc ) {
add_msg( _( "Never mind." ) );
return;
}

reload( item_loc );
}

// Unload a container, gun, or tool
Expand Down
4 changes: 2 additions & 2 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -965,12 +965,12 @@ class game
void use_item( int pos = INT_MIN ); // Use item; also tries E,R,W 'a'

void change_side( int pos = INT_MIN ); // Change the side on which an item is worn 'c'
void reload(); // Reload a wielded gun/tool 'r'
void reload( int pos, bool prompt = false );
void reload( item_location &loc, bool prompt = false );
void reload( item_location &loc, bool prompt = false, bool empty = true );
void mend( int pos = INT_MIN );
void autoattack();
public:
void reload( bool try_everything = true ); // Reload a wielded gun/tool 'r'
// Places the player at the specified point; hurts feet, lists items etc.
void place_player( const tripoint &dest );
void place_player_overmap( const tripoint &om_dest );
Expand Down
4 changes: 2 additions & 2 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4388,11 +4388,11 @@ bool item::is_firearm() const

int item::get_reload_time() const
{
if( !is_gun() ) {
if( !is_gun() && !is_magazine() ) {
return 0;
}

int reload_time = type->gun->reload_time;
int reload_time = is_gun() ? type->gun->reload_time : type->magazine->reload_time;
for( const auto mod : gunmods() ) {
reload_time = static_cast<int>( reload_time * ( 100 + mod->type->gunmod->reload_modifier ) / 100 );
}
Expand Down
39 changes: 21 additions & 18 deletions src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7372,11 +7372,9 @@ void player::rooted()
item::reload_option player::select_ammo( const item &base,
std::vector<item::reload_option> opts ) const
{
using reload_option = item::reload_option;

if( opts.empty() ) {
add_msg_if_player( m_info, _( "Never mind." ) );
return reload_option();
return item::reload_option();
}

uilist menu;
Expand All @@ -7389,7 +7387,7 @@ item::reload_option player::select_ammo( const item &base,
// Construct item names
std::vector<std::string> names;
std::transform( opts.begin(), opts.end(),
std::back_inserter( names ), [&]( const reload_option & e ) {
std::back_inserter( names ), [&]( const item::reload_option & e ) {
if( e.ammo->is_magazine() && e.ammo->ammo_data() ) {
if( e.ammo->ammo_current() == "battery" ) {
// This battery ammo is not a real object that can be recovered but pseudo-object that represents charge
Expand All @@ -7414,7 +7412,7 @@ item::reload_option player::select_ammo( const item &base,
// Get location descriptions
std::vector<std::string> where;
std::transform( opts.begin(), opts.end(),
std::back_inserter( where ), []( const reload_option & e ) {
std::back_inserter( where ), []( const item::reload_option & e ) {
bool is_ammo_container = e.ammo->is_ammo_container();
if( is_ammo_container || e.ammo->is_container() ) {
if( is_ammo_container && g->u.is_worn( *e.ammo ) ) {
Expand Down Expand Up @@ -7586,7 +7584,7 @@ item::reload_option player::select_ammo( const item &base,
menu.query();
if( menu.ret < 0 || static_cast<size_t>( menu.ret ) >= opts.size() ) {
add_msg_if_player( m_info, _( "Never mind." ) );
return reload_option();
return item::reload_option();
}

const item_location &sel = opts[ menu.ret ].ammo;
Expand All @@ -7595,11 +7593,9 @@ item::reload_option player::select_ammo( const item &base,
return std::move( opts[ menu.ret ] );
}

item::reload_option player::select_ammo( const item &base, bool prompt ) const
bool player::list_ammo( const item &base, std::vector<item::reload_option> &ammo_list,
bool empty ) const
{
using reload_option = item::reload_option;
std::vector<reload_option> ammo_list;

auto opts = base.gunmods();
opts.push_back( &base );

Expand All @@ -7615,7 +7611,7 @@ item::reload_option player::select_ammo( const item &base, bool prompt ) const

bool ammo_match_found = false;
for( const auto e : opts ) {
for( item_location &ammo : find_ammo( *e ) ) {
for( item_location &ammo : find_ammo( *e, empty ) ) {
// don't try to unload frozen liquids
if( ammo->is_watertight_container() && ammo->contents_made_of( SOLID ) ) {
continue;
Expand All @@ -7634,6 +7630,13 @@ item::reload_option player::select_ammo( const item &base, bool prompt ) const
}
}
}
return ammo_match_found;
}

item::reload_option player::select_ammo( const item &base, bool prompt, bool empty ) const
{
std::vector<item::reload_option> ammo_list;
bool ammo_match_found = list_ammo( base, ammo_list, empty );

if( ammo_list.empty() ) {
if( !base.is_magazine() && !base.magazine_integral() && !base.magazine_current() ) {
Expand All @@ -7654,20 +7657,20 @@ item::reload_option player::select_ammo( const item &base, bool prompt ) const
add_msg_if_player( m_info, _( "You don't have any %s to reload your %s!" ),
name.c_str(), base.tname() );
}
return reload_option();
return item::reload_option();
}

// sort in order of move cost (ascending), then remaining ammo (descending) with empty magazines always last
std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const reload_option & lhs,
const reload_option & rhs ) {
std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const item::reload_option & lhs,
const item::reload_option & rhs ) {
return lhs.ammo->ammo_remaining() > rhs.ammo->ammo_remaining();
} );
std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const reload_option & lhs,
const reload_option & rhs ) {
std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const item::reload_option & lhs,
const item::reload_option & rhs ) {
return lhs.moves() < rhs.moves();
} );
std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const reload_option & lhs,
const reload_option & rhs ) {
std::stable_sort( ammo_list.begin(), ammo_list.end(), []( const item::reload_option & lhs,
const item::reload_option & rhs ) {
return ( lhs.ammo->ammo_remaining() != 0 ) > ( rhs.ammo->ammo_remaining() != 0 );
} );

Expand Down
7 changes: 6 additions & 1 deletion src/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -894,12 +894,17 @@ class player : public Character
void rooted_message() const;
void rooted();
int get_lift_assist() const;

bool list_ammo( const item &base, std::vector<item::reload_option> &ammo_list,
bool empty = true ) const;
/**
* Select suitable ammo with which to reload the item
* @param base Item to select ammo for
* @param prompt force display of the menu even if only one choice
* @param empty allow selection of empty magazines
*/
item::reload_option select_ammo( const item &base, bool prompt = false ) const;
item::reload_option select_ammo( const item &base, bool prompt = false,
bool empty = true ) const;

/** Select ammo from the provided options */
item::reload_option select_ammo( const item &base, std::vector<item::reload_option> opts ) const;
Expand Down
10 changes: 10 additions & 0 deletions tests/player_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,13 @@ void clear_player()
const tripoint spot( 60, 60, 0 );
dummy.setpos( spot );
}

void process_activity( player &dummy )
{
do {
dummy.moves += dummy.get_speed();
while( dummy.moves > 0 && dummy.activity ) {
dummy.activity.do_turn( dummy );
}
} while( dummy.activity );
}
3 changes: 3 additions & 0 deletions tests/player_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
#ifndef PLAYER_HELPERS_H
#define PLAYER_HELPERS_H

#include "player.h"

#include <string>

struct itype;

int get_remaining_charges( const std::string &tool_id );
bool player_has_item_of_type( const std::string & );
void clear_player();
void process_activity( player &dummy );

#endif
Loading

0 comments on commit 63d1990

Please sign in to comment.