From 0e89a2656fb876c701e3ce265228aa18bfb4cdf4 Mon Sep 17 00:00:00 2001 From: mqrause Date: Fri, 22 Dec 2023 18:47:50 +0100 Subject: [PATCH 1/6] allow forcing inventory selector to be a single column --- src/inventory_ui.cpp | 4 ++-- src/inventory_ui.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/inventory_ui.cpp b/src/inventory_ui.cpp index 1c443618fa851..b01d6200d85b0 100644 --- a/src/inventory_ui.cpp +++ b/src/inventory_ui.cpp @@ -2282,14 +2282,14 @@ void inventory_selector::rearrange_columns( size_t client_width ) const item_location prev_selection = prev_entry.is_item() ? prev_entry.any_item() : item_location::nowhere; bool const front_only = prev_entry.is_collation_entry(); - if( is_overflown( client_width ) && !own_gear_column.empty() ) { + if( ( is_overflown( client_width ) || force_single_column ) && !own_gear_column.empty() ) { if( own_inv_column.empty() ) { own_inv_column.set_indent_entries_override( own_gear_column.indent_entries() ); } own_gear_column.move_entries_to( own_inv_column ); own_inv_column.reset_width( {} ); } - if( is_overflown( client_width ) && !map_column.empty() ) { + if( ( is_overflown( client_width ) || force_single_column ) && !map_column.empty() ) { if( own_inv_column.empty() ) { own_inv_column.set_indent_entries_override( map_column.indent_entries() ); } diff --git a/src/inventory_ui.h b/src/inventory_ui.h index 53e88c976239e..e129477e228b7 100644 --- a/src/inventory_ui.h +++ b/src/inventory_ui.h @@ -783,6 +783,7 @@ class inventory_selector // NOLINTNEXTLINE(cata-use-named-point-constants) point _fixed_origin{ -1, -1 }, _fixed_size{ -1, -1 }; bool _categorize_map_items = false; + bool force_single_column = false; private: // These functions are called from resizing/redraw callbacks of ui_adaptor From c3bd7eb7d8a5f80ccad613c18f66b6eefc663ff5 Mon Sep 17 00:00:00 2001 From: mqrause Date: Fri, 22 Dec 2023 18:50:41 +0100 Subject: [PATCH 2/6] add inventory_selector method to get the amount of entries and return the only choice if there is only one --- src/inventory_ui.cpp | 27 +++++++++++++++++++++++++++ src/inventory_ui.h | 2 ++ 2 files changed, 29 insertions(+) diff --git a/src/inventory_ui.cpp b/src/inventory_ui.cpp index b01d6200d85b0..6a4383f88575a 100644 --- a/src/inventory_ui.cpp +++ b/src/inventory_ui.cpp @@ -2873,6 +2873,33 @@ bool inventory_selector::has_available_choices() const } ); } +uint64_t inventory_selector::item_entry_count() const +{ + uint64_t count = 0; + for( const inventory_column *col : columns ) { + count += col->get_entries( return_item, true ).size(); + } + + return count; +} + +drop_location inventory_selector::get_only_choice() const +{ + if( item_entry_count() != 1 ) { + debugmsg( "inventory_selector::get_only_choice called with more that one choice" ); + return drop_location(); + } + + for( const inventory_column *col : columns ) { + const std::vector ent = col->get_entries( return_item, true ); + if( !ent.empty() ) { + return { ent.front()->any_item(), ent.front()->get_available_count() }; + } + } + + return drop_location(); +} + inventory_input inventory_selector::get_input() { std::string const &action = ctxt.handle_input(); diff --git a/src/inventory_ui.h b/src/inventory_ui.h index e129477e228b7..2bc09172b0c67 100644 --- a/src/inventory_ui.h +++ b/src/inventory_ui.h @@ -642,6 +642,8 @@ class inventory_selector bool empty() const; /** @return true when there are enabled entries to select. */ bool has_available_choices() const; + uint64_t item_entry_count() const; + drop_location get_only_choice() const; /** Apply filter string to all columns */ void set_filter( const std::string &str ); From ae0c892894d2ef36a9d984ddaf412dfdb8128e48 Mon Sep 17 00:00:00 2001 From: mqrause Date: Fri, 22 Dec 2023 18:51:44 +0100 Subject: [PATCH 3/6] migrate reload ammo selection to an inventory selector menu --- src/avatar.cpp | 10 ++ src/avatar.h | 3 + src/character.h | 8 +- src/character_ammo.cpp | 312 ----------------------------------------- src/game_inventory.cpp | 164 ++++++++++++++++++++++ src/game_inventory.h | 2 + src/iexamine.cpp | 20 +-- src/inventory_ui.cpp | 72 ++++++++++ src/inventory_ui.h | 13 ++ src/npc.h | 2 + src/npcmove.cpp | 35 +++++ 11 files changed, 306 insertions(+), 335 deletions(-) diff --git a/src/avatar.cpp b/src/avatar.cpp index 8f23fbd6a04ba..83ae79c35cdf0 100644 --- a/src/avatar.cpp +++ b/src/avatar.cpp @@ -1452,6 +1452,16 @@ bool avatar::wield( item &target, const int obtain_cost ) return true; } +item::reload_option avatar::select_ammo( const item_location &base, bool prompt, + bool empty ) +{ + if( !base ) { + return item::reload_option(); + } + + return game_menus::inv::select_ammo( *this, base, prompt, empty ); +} + bool avatar::invoke_item( item *used, const tripoint &pt, int pre_obtain_moves ) { const std::map &use_methods = used->type->use_methods; diff --git a/src/avatar.h b/src/avatar.h index 7ba2d5a388075..e110a6de2ed15 100644 --- a/src/avatar.h +++ b/src/avatar.h @@ -307,6 +307,9 @@ class avatar : public Character bool wield( item &target ) override; bool wield( item &target, int obtain_cost ); + item::reload_option select_ammo( const item_location &base, bool prompt = false, + bool empty = true ) override; + /** gets the inventory from the avatar that is interactible via advanced inventory management */ std::vector get_AIM_inventory( const advanced_inventory_pane &pane, advanced_inv_area &square ); diff --git a/src/character.h b/src/character.h index 46f4a8df651ad..546a512100bb5 100644 --- a/src/character.h +++ b/src/character.h @@ -1819,12 +1819,8 @@ class Character : public Creature, public visitable * @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_location &base, bool prompt = false, - bool empty = true ) const; - - /** Select ammo from the provided options */ - item::reload_option select_ammo( const item_location &base, std::vector opts, - const std::string &name_override = std::string() ) const; + virtual item::reload_option select_ammo( const item_location &base, bool prompt = false, + bool empty = true ) = 0; void process_items(); void leak_items(); diff --git a/src/character_ammo.cpp b/src/character_ammo.cpp index 295505c3e4e48..79001def8a8f7 100644 --- a/src/character_ammo.cpp +++ b/src/character_ammo.cpp @@ -10,7 +10,6 @@ #include "output.h" static const character_modifier_id character_modifier_reloading_move_mod( "reloading_move_mod" ); -static const itype_id itype_battery( "battery" ); static const skill_id skill_gun( "gun" ); int Character::ammo_count_for( const item_location &gun ) const @@ -111,317 +110,6 @@ bool Character::list_ammo( const item_location &base, std::vector opts, const std::string &name_override ) const -{ - if( opts.empty() ) { - add_msg_if_player( m_info, _( "Never mind." ) ); - return item::reload_option(); - } - - std::string name = name_override.empty() ? base->tname() : name_override; - uilist menu; - menu.text = string_format( base->is_watertight_container() ? _( "Refill %s" ) : - base->has_flag( flag_RELOAD_AND_SHOOT ) ? _( "Select ammo for %s" ) : _( "Reload %s" ), - name ); - - // Construct item names - std::vector names; - std::transform( opts.begin(), opts.end(), - std::back_inserter( names ), [&]( const item::reload_option & e ) { - if( e.ammo->is_magazine() && e.ammo->ammo_data() ) { - if( e.ammo->ammo_current() == itype_battery ) { - // This battery ammo is not a real object that can be recovered but pseudo-object that represents charge - //~ battery storage (charges) - return string_format( pgettext( "magazine", "%1$s (%2$d)" ), e.ammo->type_name(), - e.ammo->ammo_remaining() ); - } else { - //~ magazine with ammo (count) - return string_format( pgettext( "magazine", "%1$s with %2$s (%3$d)" ), e.ammo->type_name(), - e.ammo->ammo_data()->nname( e.ammo->ammo_remaining() ), e.ammo->ammo_remaining() ); - } - } else if( e.ammo->is_watertight_container() || - ( e.ammo->is_ammo_container() && is_worn( *e.ammo ) ) ) { - // worn ammo containers should be named by their ammo contents with their location also updated below - return e.ammo->first_ammo().display_name(); - - } else { - return ( ammo_location && ammo_location == e.ammo ? "* " : "" ) + e.ammo->display_name(); - } - } ); - - // Get location descriptions - std::vector where; - std::transform( opts.begin(), opts.end(), - std::back_inserter( where ), [this]( const item::reload_option & e ) { - bool is_ammo_container = e.ammo->is_ammo_container(); - Character &player_character = get_player_character(); - if( is_ammo_container || e.ammo->is_container() ) { - if( is_ammo_container && is_worn( *e.ammo ) ) { - return e.ammo->type_name(); - } - return string_format( _( "%s, %s" ), e.ammo->type_name(), e.ammo.describe( &player_character ) ); - } - return e.ammo.describe( &player_character ); - } ); - // Get destination names - std::vector destination; - std::transform( opts.begin(), opts.end(), - std::back_inserter( destination ), [&]( const item::reload_option & e ) { - name = name_override.empty() ? e.target->tname( 1, false, 0, false ) : - name_override; - if( ( e.target->is_gunmod() || e.target->is_magazine() ) && e.target.has_parent() ) { - return string_format( _( "%s in %s" ), name, e.target.parent_item()->tname( 1, false, 0, false ) ); - } else { - return name; - } - } ); - // Pads elements to match longest member and return length - auto pad = []( std::vector &vec, int n, int t ) -> int { - for( const auto &e : vec ) - { - n = std::max( n, utf8_width( e, true ) + t ); - } - for( auto &e : vec ) - { - e += std::string( n - utf8_width( e, true ), ' ' ); - } - return n; - }; - - // Pad the first column including 4 trailing spaces - int w = pad( names, utf8_width( menu.text, true ), 6 ); - menu.text.insert( 0, 2, ' ' ); // add space for UI hotkeys - menu.text += std::string( w + 2 - utf8_width( menu.text, true ), ' ' ); - - // Pad the location similarly (excludes leading "| " and trailing " ") - w = pad( where, utf8_width( _( "| Location " ) ) - 3, 6 ); - menu.text += _( "| Location " ); - menu.text += std::string( w + 3 - utf8_width( _( "| Location " ) ), ' ' ); - - // Pad the names of target - w = pad( destination, utf8_width( _( "| Destination " ) ) - 3, 6 ); - menu.text += _( "| Destination " ); - menu.text += std::string( w + 3 - utf8_width( _( "| Destination " ) ), ' ' ); - - menu.text += _( "| Amount " ); - menu.text += _( "| Moves " ); - - // We only show ammo statistics for guns and magazines - if( ( base->is_gun() || base->is_magazine() ) && !base->is_tool() ) { - menu.text += _( "| Damage | Pierce " ); - } - - auto draw_row = [&]( int idx ) { - const item::reload_option &sel = opts[ idx ]; - std::string row = string_format( "%s| %s | %s |", names[ idx ], where[ idx ], destination[ idx ] ); - row += string_format( ( sel.ammo->is_ammo() || - sel.ammo->is_ammo_container() ) ? " %-7d |" : " |", sel.qty() ); - row += string_format( " %-7d ", sel.moves() ); - - if( ( base->is_gun() || base->is_magazine() ) && !base->is_tool() ) { - const itype *ammo = sel.ammo->is_ammo_container() ? sel.ammo->first_ammo().ammo_data() : - sel.ammo->ammo_data(); - if( ammo ) { - const damage_instance &dam = ammo->ammo->damage; - row += string_format( "| %-7d | %-7d", static_cast( dam.total_damage() ), - static_cast( dam.empty() ? 0.0f : ( *dam.begin() ).res_pen ) ); - } else { - row += "| | "; - } - } - return row; - }; - - const ammotype base_ammotype( base->ammo_default().str() ); - itype_id last = uistate.lastreload[ base_ammotype ]; - // We keep the last key so that pressing the key twice (for example, r-r for reload) - // will always pick the first option on the list. - int last_key = inp_mngr.get_previously_pressed_key(); - bool last_key_bound = false; - // This is the entry that has out default - int default_to = 0; - - // If last_key is RETURN, don't use that to override hotkey - if( last_key == '\n' ) { - last_key_bound = true; - default_to = -1; - } - - for( int i = 0; i < static_cast( opts.size() ); ++i ) { - const item &ammo = opts[ i ].ammo->is_ammo_container() ? opts[ i ].ammo->first_ammo() : - *opts[ i ].ammo; - - char hotkey = -1; - if( has_item( ammo ) ) { - // if ammo in player possession and either it or any container has a valid invlet use this - if( ammo.invlet ) { - hotkey = ammo.invlet; - } else { - for( const item *obj : parents( ammo ) ) { - if( obj->invlet ) { - hotkey = obj->invlet; - break; - } - } - } - } - if( last == ammo.typeId() ) { - if( !last_key_bound && hotkey == -1 ) { - // If this is the first occurrence of the most recently used type of ammo and the hotkey - // was not already set above then set it to the keypress that opened this prompt - hotkey = last_key; - last_key_bound = true; - } - if( !last_key_bound ) { - // Pressing the last key defaults to the first entry of compatible type - default_to = i; - last_key_bound = true; - } - } - if( hotkey == last_key ) { - last_key_bound = true; - // Prevent the default from being used: key is bound to something already - default_to = -1; - } - - menu.addentry( i, true, hotkey, draw_row( i ) ); - } - - struct reload_callback : public uilist_callback { - public: - std::vector &opts; - const std::function draw_row; - int last_key; - const int default_to; - const bool can_partial_reload; - - reload_callback( std::vector &_opts, - std::function _draw_row, - int _last_key, int _default_to, bool _can_partial_reload ) : - opts( _opts ), draw_row( std::move( _draw_row ) ), - last_key( _last_key ), default_to( _default_to ), - can_partial_reload( _can_partial_reload ) - {} - - bool key( const input_context &, const input_event &event, int idx, uilist *menu ) override { - int cur_key = event.get_first_input(); - if( default_to != -1 && cur_key == last_key ) { - // Select the first entry on the list - menu->ret = default_to; - return true; - } - if( idx < 0 || idx >= static_cast( opts.size() ) ) { - return false; - } - auto &sel = opts[ idx ]; - switch( cur_key ) { - case KEY_LEFT: - if( can_partial_reload ) { - sel.qty( sel.qty() - 1 ); - menu->entries[ idx ].txt = draw_row( idx ); - } - return true; - - case KEY_RIGHT: - if( can_partial_reload ) { - sel.qty( sel.qty() + 1 ); - menu->entries[ idx ].txt = draw_row( idx ); - } - return true; - } - return false; - } - } cb( opts, draw_row, last_key, default_to, !base->has_flag( flag_RELOAD_ONE ) ); - menu.callback = &cb; - - menu.query(); - if( menu.ret < 0 || static_cast( menu.ret ) >= opts.size() ) { - add_msg_if_player( m_info, _( "Never mind." ) ); - return item::reload_option(); - } - - const item_location &sel = opts[ menu.ret ].ammo; - uistate.lastreload[ base_ammotype ] = sel->is_ammo_container() ? - // get first item in all magazine pockets - sel->first_ammo().typeId() : - sel->typeId(); - return opts[ menu.ret ]; -} - -item::reload_option Character::select_ammo( const item_location &base, bool prompt, - bool empty ) const -{ - if( !base ) { - return item::reload_option(); - } - - std::vector ammo_list; - bool ammo_match_found = list_ammo( base, ammo_list, empty ); - - if( ammo_list.empty() ) { - if( !is_npc() ) { - if( !base->magazine_integral() && !base->magazine_current() ) { - add_msg_if_player( m_info, _( "You need a compatible magazine to reload the %s!" ), - base->tname() ); - - } else if( ammo_match_found ) { - add_msg_if_player( m_info, _( "You can't reload anything with the ammo you have on hand." ) ); - } else { - std::string name; - if( base->ammo_data() ) { - name = base->ammo_data()->nname( 1 ); - } else if( base->is_watertight_container() ) { - name = base->is_container_empty() ? "liquid" : base->legacy_front().tname(); - } else { - const std::set types_of_ammo = base->ammo_types(); - name = enumerate_as_string( types_of_ammo.begin(), - types_of_ammo.end(), []( const ammotype & at ) { - return at->name(); - }, enumeration_conjunction::none ); - } - if( base->is_magazine_full() ) { - add_msg_if_player( m_info, _( "The %s is already full!" ), - base->tname() ); - } else { - add_msg_if_player( m_info, _( "You don't have any %s to reload your %s!" ), - name, base->tname() ); - } - } - } - 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 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 item::reload_option & lhs, - const item::reload_option & rhs ) { - return lhs.moves() < rhs.moves(); - } ); - 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 ); - } ); - - if( is_npc() ) { - if( ammo_list[0].ammo.get_item()->ammo_remaining() > 0 ) { - return ammo_list[0]; - } else { - return item::reload_option(); - } - } - - if( !prompt && ammo_list.size() == 1 ) { - // unconditionally suppress the prompt if there's only one option - return ammo_list[ 0 ]; - } - - return select_ammo( base, std::move( ammo_list ) ); -} - int Character::item_reload_cost( const item &it, const item &ammo, int qty ) const { if( ammo.is_ammo() || ammo.is_frozen_liquid() || ammo.made_of_from_type( phase_id::LIQUID ) ) { diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index ecc851bea297f..fe2d4e7d0cb01 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -2769,3 +2769,167 @@ std::pair game_menus::inv::unload( Character &you ) return inv_s.execute(); } + +class select_ammo_inventory_preset : public inventory_selector_preset +{ + public: + select_ammo_inventory_preset( Character &you, const item_location &target, + bool empty ) : you( you ), + target( target ), empty( empty ) { + _indent_entries = false; + + append_cell( [&you]( const item_location & loc ) { + bool is_ammo_container = loc->is_ammo_container(); + Character &player_character = get_player_character(); + if( is_ammo_container || loc->is_container() ) { + if( is_ammo_container && you.is_worn( *loc ) ) { + return loc->type_name(); + } + return string_format( _( "%s, %s" ), loc->type_name(), loc.describe( &player_character ) ); + } + return loc.describe( &player_character ); + }, _( "LOCATION" ) ); + + append_cell( []( const inventory_entry & entry ) { + if( entry.any_item()->is_ammo() ) { + return std::to_string( entry.chosen_count ); + } + return std::string(); + }, _( "AMOUNT" ) ); + + append_cell( [&you, &target]( const item_location & loc ) { + item::reload_option opt( &you, target, loc ); + return std::to_string( opt.moves() ); + }, _( "MOVES" ) ); + + append_cell( []( const item_location & loc ) { + const itype *ammo = loc->is_ammo_container() ? loc->first_ammo().ammo_data() : + loc->ammo_data(); + if( ammo ) { + const damage_instance &dam = ammo->ammo->damage; + return std::to_string( static_cast( dam.total_damage() ) ); + } + return std::string(); + }, _( "DAMAGE" ) ); + + append_cell( []( const item_location & loc ) { + const itype *ammo = loc->is_ammo_container() ? loc->first_ammo().ammo_data() : + loc->ammo_data(); + if( ammo ) { + const damage_instance &dam = ammo->ammo->damage; + return std::to_string( static_cast( dam.empty() ? 0.0f : ( *dam.begin() ).res_pen ) ); + } + return std::string(); + }, _( "PIERCE" ) ); + } + + bool is_shown( const item_location &loc ) const override { + // todo: allow to reload a magazine/magazine well from a container pocket on the same item + if( loc.parent_item() == target ) { + return false; + } + + if( loc->made_of( phase_id::LIQUID ) && loc.where() == item_location::type::map ) { + map &here = get_map(); + if( !here.has_flag_ter_or_furn( ter_furn_flag::TFLAG_LIQUIDCONT, loc.pos_bub() ) ) { + return false; + } + } + + if( loc->is_frozen_liquid() ) { + return false; + } + + if( !empty && loc->is_magazine() && !loc->ammo_remaining() ) { + return false; + } + + std::vector opts; + opts.emplace_back( target ); + + if( target->magazine_current() ) { + opts.emplace_back( target, const_cast( target->magazine_current() ) ); + } + + for( const item *mod : target->gunmods() ) { + item_location mod_loc( target, const_cast( mod ) ); + opts.emplace_back( mod_loc ); + if( mod->magazine_current() ) { + opts.emplace_back( mod_loc, const_cast( mod->magazine_current() ) ); + } + } + + for( item_location &p : opts ) { + if( ( loc->has_flag( flag_SPEEDLOADER ) && p->allows_speedloader( loc->typeId() ) && + loc->ammo_remaining() > 1 && p->ammo_remaining() < 1 ) && p->can_reload_with( *loc, true ) ) { + return true; + } + + if( p->can_reload_with( *loc, true ) ) { + return true; + } + } + + return false; + } + + // sort in order of move cost (ascending), then remaining ammo (descending) with empty magazines always last + bool sort_compare( const inventory_entry &lhs, const inventory_entry &rhs ) const override { + item_location left = lhs.any_item(); + item_location right = rhs.any_item(); + + if( left->ammo_remaining() == 0 || right->ammo_remaining() == 0 ) { + return ( left->ammo_remaining() != 0 ) > ( right->ammo_remaining() != 0 ); + } + + if( left.obtain_cost( you ) != right.obtain_cost( you ) ) { + return left.obtain_cost( you ) < right.obtain_cost( you ); + } + + return left->ammo_remaining() > right->ammo_remaining(); + } + + private: + Character &you; + const item_location target; + bool empty; +}; + +item::reload_option game_menus::inv::select_ammo( Character &you, const item_location &loc, + bool prompt, bool empty ) +{ + const select_ammo_inventory_preset preset( you, loc, empty ); + ammo_inventory_selector inv_s( you, loc, preset ); + + inv_s.set_title( string_format( loc->is_watertight_container() ? _( "Refill %s" ) : + loc->has_flag( flag_RELOAD_AND_SHOOT ) ? _( "Select ammo for %s" ) : _( "Reload %s" ), + loc->display_name() ) ); + inv_s.set_hint( _( "Choose ammo to reload" ) ); + inv_s.set_display_stats( false ); + + inv_s.clear_items(); + inv_s.add_character_items( you ); + inv_s.add_nearby_items( 1 ); + inv_s.set_all_entries_chosen_count(); + + if( inv_s.empty() ) { + popup( _( "You have nothing to reload." ), PF_GET_KEY ); + return item::reload_option(); + } + + drop_location selected; + if( !prompt && inv_s.item_entry_count() == 1 ) { + selected = inv_s.get_only_choice(); + } else { + selected = inv_s.execute(); + } + + if( !selected.first ) { + return item::reload_option(); + } + + item::reload_option opt( &you, loc, selected.first ); + opt.qty( selected.second ); + + return opt; +} diff --git a/src/game_inventory.h b/src/game_inventory.h index 99fd91a408162..310a4f70c511c 100644 --- a/src/game_inventory.h +++ b/src/game_inventory.h @@ -157,6 +157,8 @@ item_location sterilize_cbm( Character &you ); item_location change_sprite( Character &you ); /** Unload item menu **/ std::pair unload( Character &you ); +item::reload_option select_ammo( Character &you, const item_location &loc, bool prompt = false, + bool empty = true ); /*@}*/ } // namespace inv diff --git a/src/iexamine.cpp b/src/iexamine.cpp index eb9b6bd38fc24..7a98bb3b00c71 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -4428,25 +4428,11 @@ static void reload_furniture( Character &you, const tripoint &examp, bool allow_ // maybe at some point we need a pseudo item_location or something // but for now this should at least work as intended item_location pseudo_loc( map_cursor( examp ), &pseudo ); - std::vector ammo_list; - for( item_location &ammo : you.find_ammo( pseudo, false, PICKUP_RANGE ) ) { - // Only allow the same type to reload if partially loaded. - if( ( amount_in_furn > 0 || !use_ammotype ) && ammo_itypeID != ammo.get_item()->typeId() ) { - continue; - } - if( pseudo.can_reload_with( *ammo, true ) ) { - ammo_list.emplace_back( &you, pseudo_loc, std::move( ammo ) ); - } - } - if( ammo_list.empty() ) { - //~ Reloading or restocking a piece of furniture, for example a forge. - add_msg( m_info, _( "You need some %1$s to reload this %2$s." ), ammo->nname( 2 ), - f.name() ); - return; - } + // used to only allow one type of ammo, changed with move to inventory_selector + // todo: use furniture name instead of pseudo item name + item::reload_option opt = game_menus::inv::select_ammo( you, pseudo_loc ); - item::reload_option opt = you.select_ammo( pseudo_loc, std::move( ammo_list ), f.name() ); if( !opt ) { return; } diff --git a/src/inventory_ui.cpp b/src/inventory_ui.cpp index 6a4383f88575a..5301f76594bf6 100644 --- a/src/inventory_ui.cpp +++ b/src/inventory_ui.cpp @@ -3322,6 +3322,78 @@ inventory_selector::stats container_inventory_selector::get_raw_stats() const loc->get_used_holsters(), loc->get_total_holsters() ); } +ammo_inventory_selector::ammo_inventory_selector( Character &you, + const item_location &reload_loc, const inventory_selector_preset &preset ) : + inventory_selector( you, preset ), reload_loc( reload_loc ) +{ + ctxt.register_action( "INCREASE_COUNT" ); + ctxt.register_action( "DECREASE_COUNT" ); + + force_single_column = true; +} + +// todo: this should happen when the entries are created, but that's a different refactoring +void ammo_inventory_selector::set_all_entries_chosen_count() +{ + for( inventory_column *col : columns ) { + for( inventory_entry *entry : col->get_entries( return_item, true ) ) { + item::reload_option tmp_opt( &u, reload_loc, entry->any_item() ); + tmp_opt.qty( entry->get_available_count() ); + entry->chosen_count = tmp_opt.qty(); + } + } +} + +void ammo_inventory_selector::mod_chosen_count( inventory_entry &entry, int value ) +{ + item::reload_option tmp_opt( &u, reload_loc, entry.any_item() ); + tmp_opt.qty( entry.chosen_count + value ); + entry.chosen_count = tmp_opt.qty(); + + entry.make_entry_cell_cache( preset ); + on_change( entry ); +} + +drop_location ammo_inventory_selector::execute() +{ + shared_ptr_fast ui = create_or_get_ui_adaptor(); + debug_print_timer( tp_start ); + while( true ) { + ui_manager::redraw(); + const inventory_input input = get_input(); + + if( input.entry != nullptr ) { + if( input.action == "MOUSE_MOVE" ) { + if( highlight( input.entry->any_item() ) ) { + ui_manager::redraw(); + } + } else if( input.action == "ANY_INPUT" || input.action == "SELECT" ) { + return { input.entry->any_item(), static_cast( input.entry->chosen_count ) }; + } else { + if( highlight( input.entry->any_item() ) ) { + ui_manager::redraw(); + } + on_input( input ); + } + } else if( input.action == "QUIT" ) { + return drop_location(); + } else if( input.action == "CONFIRM" ) { + const inventory_entry &highlighted = get_active_column().get_highlighted(); + if( highlighted && highlighted.is_selectable() ) { + return { highlighted.any_item(), static_cast( highlighted.chosen_count ) }; + } + } else if( input.action == "INCREASE_COUNT" ) { + inventory_entry &highlighted = get_active_column().get_highlighted(); + mod_chosen_count( highlighted, 1 ); + } else if( input.action == "DECREASE_COUNT" ) { + inventory_entry &highlighted = get_active_column().get_highlighted(); + mod_chosen_count( highlighted, -1 ); + } else { + on_input( input ); + } + } +} + void inventory_selector::action_examine( const item_location &sitem ) { // Code below pulled from the action_examine function in advanced_inv.cpp diff --git a/src/inventory_ui.h b/src/inventory_ui.h index 2bc09172b0c67..ae3e0ba422ab6 100644 --- a/src/inventory_ui.h +++ b/src/inventory_ui.h @@ -948,6 +948,19 @@ class container_inventory_selector : public inventory_pick_selector item_location loc; }; +class ammo_inventory_selector : public inventory_selector +{ + public: + explicit ammo_inventory_selector( Character &you, const item_location &reload_loc, + const inventory_selector_preset &preset = default_preset ); + + drop_location execute(); + void set_all_entries_chosen_count(); + private: + void mod_chosen_count( inventory_entry &entry, int val ); + const item_location reload_loc; +}; + class inventory_multiselector : public inventory_selector { public: diff --git a/src/npc.h b/src/npc.h index 1e562ddeda778..53ff356aa5a82 100644 --- a/src/npc.h +++ b/src/npc.h @@ -1158,6 +1158,8 @@ class npc : public Character /** Finds ammo the NPC could use to reload a given object */ item_location find_usable_ammo( const item_location &weap ); item_location find_usable_ammo( const item_location &weap ) const; + item::reload_option select_ammo( const item_location &base, bool prompt = false, + bool empty = true ) override; bool dispose_item( item_location &&obj, const std::string &prompt = std::string() ) override; diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 47235c41edb5f..560d3da585dce 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -13,6 +13,7 @@ #include "active_item_cache.h" #include "activity_handlers.h" +#include "ammo.h" #include "avatar.h" #include "basecamp.h" #include "bionics.h" @@ -2158,6 +2159,40 @@ item_location npc::find_usable_ammo( const item_location &weap ) const return const_cast( this )->find_usable_ammo( weap ); } +item::reload_option npc::select_ammo( const item_location &base, bool, bool empty ) +{ + if( !base ) { + return item::reload_option(); + } + + std::vector ammo_list; + list_ammo( base, ammo_list, empty ); + + if( ammo_list.empty() ) { + 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 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 item::reload_option & lhs, + const item::reload_option & rhs ) { + return lhs.moves() < rhs.moves(); + } ); + 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 ); + } ); + + if( ammo_list[0].ammo.get_item()->ammo_remaining() > 0 ) { + return ammo_list[0]; + } else { + return item::reload_option(); + } +} + void npc::activate_combat_cbms() { for( const bionic_id &cbm_id : defense_cbms ) { From 7db9f3582aa7369f27ec69cb46e290d16461882c Mon Sep 17 00:00:00 2001 From: mqrause <38702195+mqrause@users.noreply.github.com> Date: Sat, 23 Dec 2023 05:34:56 +0100 Subject: [PATCH 4/6] collation and stable sorting Co-authored-by: andrei <68240139+andrei8l@users.noreply.github.com> --- src/game_inventory.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index fe2d4e7d0cb01..2116d0c47ddcb 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -2777,6 +2777,7 @@ class select_ammo_inventory_preset : public inventory_selector_preset bool empty ) : you( you ), target( target ), empty( empty ) { _indent_entries = false; + _collate_entries = true; append_cell( [&you]( const item_location & loc ) { bool is_ammo_container = loc->is_ammo_container(); @@ -2886,7 +2887,11 @@ class select_ammo_inventory_preset : public inventory_selector_preset return left.obtain_cost( you ) < right.obtain_cost( you ); } - return left->ammo_remaining() > right->ammo_remaining(); + if( left->ammo_remaining() != right->ammo_remaining() ) { + return left->ammo_remaining() > right->ammo_remaining(); + } + + return inventory_selector_preset::sort_compare( lhs, rhs ); } private: From b91c55da5f14cdf759af853f252613d24f4358b0 Mon Sep 17 00:00:00 2001 From: mqrause Date: Sun, 24 Dec 2023 04:55:41 +0100 Subject: [PATCH 5/6] merge ammo sorting in npc::select_ammo --- src/npcmove.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/npcmove.cpp b/src/npcmove.cpp index 560d3da585dce..09635de41946e 100644 --- a/src/npcmove.cpp +++ b/src/npcmove.cpp @@ -2175,16 +2175,16 @@ item::reload_option npc::select_ammo( const item_location &base, bool, bool empt // 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 item::reload_option & lhs, const item::reload_option & rhs ) { + if( lhs.ammo->ammo_remaining() == 0 || rhs.ammo->ammo_remaining() == 0 ) { + return ( lhs.ammo->ammo_remaining() != 0 ) > ( rhs.ammo->ammo_remaining() != 0 ); + } + + if( lhs.moves() != rhs.moves() ) { + return lhs.moves() < rhs.moves(); + } + return lhs.ammo->ammo_remaining() > rhs.ammo->ammo_remaining(); } ); - 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 item::reload_option & lhs, - const item::reload_option & rhs ) { - return ( lhs.ammo->ammo_remaining() != 0 ) > ( rhs.ammo->ammo_remaining() != 0 ); - } ); if( ammo_list[0].ammo.get_item()->ammo_remaining() > 0 ) { return ammo_list[0]; From e441c353204e19f24095ae8f03d57071b8849ca6 Mon Sep 17 00:00:00 2001 From: mqrause Date: Tue, 9 Jan 2024 06:21:19 +0100 Subject: [PATCH 6/6] enable reloading attached gunmods by reloading the gun --- src/game_inventory.cpp | 37 ++++++++++++++++++++++--------------- src/inventory_ui.cpp | 42 ++++++++++++++++++++++++++++++++++++------ src/inventory_ui.h | 1 + 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index 2116d0c47ddcb..3dc2903a8e281 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -2791,6 +2791,18 @@ class select_ammo_inventory_preset : public inventory_selector_preset return loc.describe( &player_character ); }, _( "LOCATION" ) ); + append_cell( [&you, target]( const item_location & loc ) { + for( const item_location &opt : get_possible_reload_targets( target ) ) { + if( opt->can_reload_with( *loc, true ) ) { + if( opt == target ) { + return std::string(); + } + return string_format( _( "%s, %s" ), opt->type_name(), opt.describe( &you ) ); + } + } + return std::string(); + }, _( "DESTINATION" ) ); + append_cell( []( const inventory_entry & entry ) { if( entry.any_item()->is_ammo() ) { return std::to_string( entry.chosen_count ); @@ -2845,20 +2857,7 @@ class select_ammo_inventory_preset : public inventory_selector_preset return false; } - std::vector opts; - opts.emplace_back( target ); - - if( target->magazine_current() ) { - opts.emplace_back( target, const_cast( target->magazine_current() ) ); - } - - for( const item *mod : target->gunmods() ) { - item_location mod_loc( target, const_cast( mod ) ); - opts.emplace_back( mod_loc ); - if( mod->magazine_current() ) { - opts.emplace_back( mod_loc, const_cast( mod->magazine_current() ) ); - } - } + std::vector opts = get_possible_reload_targets( target ); for( item_location &p : opts ) { if( ( loc->has_flag( flag_SPEEDLOADER ) && p->allows_speedloader( loc->typeId() ) && @@ -2933,7 +2932,15 @@ item::reload_option game_menus::inv::select_ammo( Character &you, const item_loc return item::reload_option(); } - item::reload_option opt( &you, loc, selected.first ); + item_location target_loc; + for( const item_location &opt : get_possible_reload_targets( loc ) ) { + if( opt->can_reload_with( *selected.first, true ) ) { + target_loc = opt; + break; + } + } + + item::reload_option opt( &you, target_loc, selected.first ); opt.qty( selected.second ); return opt; diff --git a/src/inventory_ui.cpp b/src/inventory_ui.cpp index 5301f76594bf6..75585ebbe1723 100644 --- a/src/inventory_ui.cpp +++ b/src/inventory_ui.cpp @@ -3332,23 +3332,53 @@ ammo_inventory_selector::ammo_inventory_selector( Character &you, force_single_column = true; } +std::vector get_possible_reload_targets( const item_location &target ) +{ + std::vector opts; + opts.emplace_back( target ); + + if( target->magazine_current() ) { + opts.emplace_back( target, const_cast( target->magazine_current() ) ); + } + + for( const item *mod : target->gunmods() ) { + item_location mod_loc( target, const_cast( mod ) ); + opts.emplace_back( mod_loc ); + if( mod->magazine_current() ) { + opts.emplace_back( mod_loc, const_cast( mod->magazine_current() ) ); + } + } + + return opts; +} + // todo: this should happen when the entries are created, but that's a different refactoring void ammo_inventory_selector::set_all_entries_chosen_count() { for( inventory_column *col : columns ) { for( inventory_entry *entry : col->get_entries( return_item, true ) ) { - item::reload_option tmp_opt( &u, reload_loc, entry->any_item() ); - tmp_opt.qty( entry->get_available_count() ); - entry->chosen_count = tmp_opt.qty(); + for( const item_location &loc : get_possible_reload_targets( reload_loc ) ) { + if( loc->can_reload_with( *entry->any_item(), true ) ) { + item::reload_option tmp_opt( &u, loc, entry->any_item() ); + tmp_opt.qty( entry->get_available_count() ); + entry->chosen_count = tmp_opt.qty(); + break; + } + } } } } void ammo_inventory_selector::mod_chosen_count( inventory_entry &entry, int value ) { - item::reload_option tmp_opt( &u, reload_loc, entry.any_item() ); - tmp_opt.qty( entry.chosen_count + value ); - entry.chosen_count = tmp_opt.qty(); + for( const item_location &loc : get_possible_reload_targets( reload_loc ) ) { + if( loc->can_reload_with( *entry.any_item(), true ) ) { + item::reload_option tmp_opt( &u, loc, entry.any_item() ); + tmp_opt.qty( entry.chosen_count + value ); + entry.chosen_count = tmp_opt.qty(); + break; + } + } entry.make_entry_cell_cache( preset ); on_change( entry ); diff --git a/src/inventory_ui.h b/src/inventory_ui.h index ae3e0ba422ab6..bbe8ca99bcce4 100644 --- a/src/inventory_ui.h +++ b/src/inventory_ui.h @@ -948,6 +948,7 @@ class container_inventory_selector : public inventory_pick_selector item_location loc; }; +std::vector get_possible_reload_targets( const item_location &target ); class ammo_inventory_selector : public inventory_selector { public: