diff --git a/src/character.cpp b/src/character.cpp index 512b1e725d872..8dc70a24a1f1c 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10992,10 +10992,11 @@ bool Character::use_charges_if_avail( const itype_id &it, int quantity ) return false; } -std::list Character::use_charges( const itype_id &what, int qty, +std::list Character::use_charges( const itype_id &what, int qty, const int radius, const std::function &filter ) { std::list res; + inventory inv = crafting_inventory( pos(), radius, true ); if( qty <= 0 ) { return res; @@ -11023,16 +11024,16 @@ std::list Character::use_charges( const itype_id &what, int qty, qty -= std::min( qty, bio ); } - int adv = charges_of( itype_adv_UPS_off, static_cast( std::ceil( qty * 0.6 ) ) ); + int adv = inv.charges_of( itype_adv_UPS_off, static_cast( std::ceil( qty * 0.6 ) ) ); if( adv > 0 ) { - std::list found = use_charges( itype_adv_UPS_off, adv ); + std::list found = use_charges( itype_adv_UPS_off, adv, radius ); res.splice( res.end(), found ); qty -= std::min( qty, static_cast( adv / 0.6 ) ); } - int ups = charges_of( itype_UPS_off, qty ); + int ups = inv.charges_of( itype_UPS_off, qty ); if( ups > 0 ) { - std::list found = use_charges( itype_UPS_off, ups ); + std::list found = use_charges( itype_UPS_off, ups, radius ); res.splice( res.end(), found ); qty -= std::min( qty, ups ); } @@ -11042,27 +11043,44 @@ std::list Character::use_charges( const itype_id &what, int qty, std::vector del; bool has_tool_with_UPS = false; - visit_items( [this, &what, &qty, &res, &del, &has_tool_with_UPS, &filter]( item * e ) { - if( e->use_charges( what, qty, res, pos(), filter ) ) { - del.push_back( e ); - } + // Detection of UPS tool + inv.visit_items( [ &what, &qty, &has_tool_with_UPS, &filter]( item * e ) { if( filter( *e ) && e->typeId() == what && e->has_flag( flag_USE_UPS ) ) { has_tool_with_UPS = true; + return VisitResponse::ABORT; } return qty > 0 ? VisitResponse::NEXT : VisitResponse::ABORT; } ); + if( radius >= 0 ) { + get_map().use_charges( pos(), radius, what, qty, return_true ); + } + if( qty > 0 ) { + visit_items( [this, &what, &qty, &res, &del, &filter]( item * e ) { + if( e->use_charges( what, qty, res, pos(), filter ) ) { + del.push_back( e ); + } + return qty > 0 ? VisitResponse::NEXT : VisitResponse::ABORT; + } ); + } + for( item *e : del ) { - remove_item( *e ); + inv.remove_item( e ); } if( has_tool_with_UPS ) { - use_charges( itype_UPS, qty ); + use_charges( itype_UPS, qty, radius ); } return res; } +std::list Character::use_charges( const itype_id &what, int qty, + const std::function &filter ) +{ + return use_charges( what, qty, -1, filter ); +} + bool Character::has_fire( const int quantity ) const { // TODO: Replace this with a "tool produces fire" flag. diff --git a/src/character.h b/src/character.h index 12c5313497681..c027e5b7ac5f8 100644 --- a/src/character.h +++ b/src/character.h @@ -1983,9 +1983,25 @@ class Character : public Creature, public visitable // Uses up charges bool use_charges_if_avail( const itype_id &it, int quantity ); - // Uses up charges + /** + * Use charges in character inventory. + * @param what itype_id of item using charges + * @param qty Number of charges + * @param filter Filter + * @return List of items used + */ std::list use_charges( const itype_id &what, int qty, const std::function &filter = return_true ); + /** + * Use charges within a radius. Includes character inventory. + * @param what itype_id of item using charges + * @param qty Number of charges + * @param radius Radius from the character. Use -1 to use from character inventory only. + * @param filter Filter + * @return List of items used + */ + std::list use_charges( const itype_id &what, int qty, int radius, + const std::function &filter = return_true ); bool has_fire( int quantity ) const; void use_fire( int quantity ); @@ -2378,6 +2394,13 @@ class Character : public Creature, public visitable bool has_morale_to_read() const; bool has_morale_to_craft() const; const inventory &crafting_inventory( bool clear_path ); + /** + * Returns items that can be used to craft with. Always includes character inventory. + * @param src_pos Character position. + * @param radius Radius from src_pos. -1 to return items in character inventory only. + * @param clear_path True to select only items within view. False to select all within the radius. + * @returns Craftable inventory items found. + * */ const inventory &crafting_inventory( const tripoint &src_pos = tripoint_zero, int radius = PICKUP_RANGE, bool clear_path = true ); void invalidate_crafting_inventory(); diff --git a/src/crafting.cpp b/src/crafting.cpp index 7050d20601029..6a1331365180c 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -542,13 +542,18 @@ const inventory &Character::crafting_inventory( const tripoint &src_pos, int rad if( src_pos == tripoint_zero ) { inv_pos = pos(); } + static int radius_mem = radius; if( cached_moves == moves + && radius_mem == radius && cached_time == calendar::turn && cached_position == inv_pos ) { return *cached_crafting_inventory; } + radius_mem = radius; cached_crafting_inventory->clear(); - cached_crafting_inventory->form_from_map( inv_pos, radius, this, false, clear_path ); + if( radius >= 0 ) { + cached_crafting_inventory->form_from_map( inv_pos, radius, this, false, clear_path ); + } for( const item_location &it : all_items_loc() ) { // can't craft with containers that have items in them @@ -1749,19 +1754,24 @@ Character::select_tool_component( const std::vector &tools, int batch bool found_nocharge = false; std::vector player_has; std::vector map_has; + std::vector both_has; // Use charges of any tools that require charges used for( auto it = tools.begin(); it != tools.end() && !found_nocharge; ++it ) { itype_id type = it->type; if( it->count > 0 ) { const int count = calc_charges( *it ); - if( player_inv ) { - if( has_charges( type, count ) ) { - player_has.push_back( *it ); - } + if( player_inv && crafting_inventory( pos(), -1 ).has_charges( type, count ) ) { + player_has.push_back( *it ); } if( map_inv.has_charges( type, count ) ) { map_has.push_back( *it ); } + // Needed for tools that can have power in a different location, such as a UPS. + // Will only populate if no other options were found. + if( player_inv && crafting_inventory().has_charges( type, count ) + && player_has.size() + map_has.size() == 0 ) { + both_has.push_back( *it ); + } } else if( ( player_inv && has_amount( type, 1 ) ) || map_inv.has_tools( type, 1 ) ) { selected.comp = *it; found_nocharge = true; @@ -1772,21 +1782,16 @@ Character::select_tool_component( const std::vector &tools, int batch return selected; // Default to using a tool that doesn't require charges } - if( player_has.size() + map_has.size() == 1 ) { - if( map_has.empty() ) { - selected.use_from = usage_from::player; - selected.comp = player_has[0]; - } else { + if( ( both_has.size() + player_has.size() + map_has.size() == 1 ) || is_npc() ) { + if( !both_has.empty() ) { + selected.use_from = usage_from::both; + selected.comp = both_has[0]; + } else if( !map_has.empty() ) { selected.use_from = usage_from::map; selected.comp = map_has[0]; - } - } else if( is_npc() ) { - if( !player_has.empty() ) { + } else if( !player_has.empty() ) { selected.use_from = usage_from::player; selected.comp = player_has[0]; - } else if( !map_has.empty() ) { - selected.use_from = usage_from::map; - selected.comp = map_has[0]; } else { selected.use_from = usage_from::none; return selected; @@ -1817,6 +1822,18 @@ Character::select_tool_component( const std::vector &tools, int batch tmenu.addentry( item::nname( player_ha.type ) ); } } + for( auto &both_ha : both_has ) { + if( item::find_type( both_ha.type )->maximum_charges() > 1 ) { + const int charge_count = calc_charges( both_ha ); + std::string tmpStr = string_format( _( "%s (%d/%d charges nearby or on person)" ), + item::nname( both_ha.type ), charge_count, + charges_of( both_ha.type ) ); + tmenu.addentry( tmpStr ); + } else { + std::string tmpStr = item::nname( both_ha.type ) + _( " (at hand)" ); + tmenu.addentry( tmpStr ); + } + } if( tmenu.entries.empty() ) { // This SHOULD only happen if cooking with a fire, selected.use_from = usage_from::none; @@ -1829,7 +1846,8 @@ Character::select_tool_component( const std::vector &tools, int batch tmenu.title = _( "Use which tool?" ); tmenu.query(); - if( tmenu.ret < 0 || static_cast( tmenu.ret ) >= map_has.size() + player_has.size() ) { + if( tmenu.ret < 0 || static_cast( tmenu.ret ) >= map_has.size() + + player_has.size() + both_has.size() ) { selected.use_from = usage_from::cancel; return selected; } @@ -1838,10 +1856,14 @@ Character::select_tool_component( const std::vector &tools, int batch if( uselection < map_has.size() ) { selected.use_from = usage_from::map; selected.comp = map_has[uselection]; - } else { + } else if( uselection < map_has.size() + player_has.size() ) { uselection -= map_has.size(); selected.use_from = usage_from::player; selected.comp = player_has[uselection]; + } else { + uselection -= map_has.size() + player_has.size(); + selected.use_from = usage_from::both; + selected.comp = both_has[uselection]; } } @@ -1914,6 +1936,14 @@ bool Character::craft_consume_tools( item &craft, int mulitplier, bool start_cra } break; case usage_from::both: + if( !( crafting_inventory() ).has_charges( type, count ) ) { + add_msg_player_or_npc( + _( "You have insufficient %s charges and can't continue crafting" ), + _( " has insufficient %s charges and can't continue crafting" ), + item::nname( type ) ); + craft.set_tools_to_continue( false ); + return false; + } case usage_from::none: case usage_from::cancel: case usage_from::num_usages_from: @@ -1953,11 +1983,16 @@ void Character::consume_tools( map &m, const comp_selection &tool, in const itype *tmp = item::find_type( tool.comp.type ); int quantity = tool.comp.count * batch * tmp->charge_factor(); - if( tool.use_from & usage_from::player ) { + if( tool.use_from == usage_from::both ) { + use_charges( tool.comp.type, quantity, radius ); + } else if( tool.use_from == usage_from::player ) { use_charges( tool.comp.type, quantity ); - } - if( tool.use_from & usage_from::map ) { + } else if( tool.use_from == usage_from::map ) { m.use_charges( origin, radius, tool.comp.type, quantity, return_true, bcp ); + // Map::use_charges() does not handle UPS charges. + if( quantity > 0 ) { + use_charges( tool.comp.type, quantity, radius ); + } } // else, usage_from::none (or usage_from::cancel), so we don't use up any tools;