diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index 3fe30a3f0294..05de62704e12 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #include "avatar.h" @@ -37,6 +39,7 @@ #include "iuse.h" #include "iuse_actor.h" #include "item_category.h" +#include "material.h" #include "map.h" #include "npc.h" #include "options.h" @@ -1477,8 +1480,55 @@ item *game_menus::inv::salvage( player &p ) class repair_inventory_preset: public inventory_selector_preset { public: - repair_inventory_preset( const repair_item_actor *actor, const item *main_tool ) : - actor( actor ), main_tool( main_tool ) { + repair_inventory_preset( const repair_item_actor *actor, const item *main_tool, + player &character ) : + actor( actor ), main_tool( main_tool ), character( character ) { + append_cell( [ this, actor, &character ]( const item * loc ) { + const auto comp_needed = std::max( 1, + std::ceil( loc->volume() / 250_ml * actor->cost_scaling ) ); + auto valid_entries = std::set {}; + std::ranges::for_each( actor->materials, [ &valid_entries, &loc ]( const auto & mat ) { + if( loc->made_of( mat ) ) { + valid_entries.emplace( mat ); + } + } ); + + const auto &crafting_inv = character.crafting_inventory(); + auto listed_components = std::set {}; + auto material_list = std::vector {}; + std::ranges::for_each( valid_entries, [ this, &listed_components, &material_list, &crafting_inv, + &comp_needed ]( const auto & entry ) { + const auto &component_id = entry.obj().repaired_with(); + if( listed_components.contains( component_id ) ) { + return; + } + listed_components.emplace( component_id ); + const auto num_comp = get_cached_component_count( crafting_inv, component_id ); + if( num_comp > 0 ) { + material_list.emplace_back( colorize( string_format( _( "%s (%d)" ), item::nname( component_id ), + num_comp ), num_comp < comp_needed ? c_red : c_unset ) ); + } + } ); + + auto ret = join( material_list, ", " ); + if( ret.empty() ) { + ret = _( "NONE" ); + } + return ret; + }, _( "MATERIALS AVAILABLE" ) ); + + append_cell( [ this ]( const item * loc ) { + const auto chance = get_cached_repair_chance( *loc ); + return colorize( string_format( "%0.1f%%", 100.0f * chance.first ), + chance.first == 0 ? c_yellow : ( chance.second == 0 ? c_light_green : c_unset ) ); + }, _( "SUCCESS" ) ); + + append_cell( [ this ]( const item * loc ) { + const auto chance = get_cached_repair_chance( *loc ); + return colorize( string_format( "%0.1f%%", 100.0f * chance.second ), + chance.second > chance.first ? c_yellow : ( chance.second == 0 && + chance.first > 0 ? c_light_green : c_unset ) ); + }, _( "DMG" ) ); } bool is_shown( const item *loc ) const override { @@ -1487,17 +1537,60 @@ class repair_inventory_preset: public inventory_selector_preset } private: + auto get_cached_component_count( const inventory &crafting_inv, + const itype_id &component_id ) const -> int { + auto [ iter, inserted ] = component_count_cache.try_emplace( component_id, 0 ); + if( !inserted ) { + return iter->second; + } + + if( item::count_by_charges( component_id ) ) { + if( crafting_inv.has_charges( component_id, 1 ) ) { + iter->second = crafting_inv.charges_of( component_id ); + } + } else if( crafting_inv.has_amount( component_id, 1, false, is_crafting_component ) ) { + iter->second = crafting_inv.amount_of( component_id, false ); + } + return iter->second; + } + + auto get_cached_repair_chance( const item &loc ) const -> std::pair { + auto [ iter, inserted ] = chance_cache.try_emplace( &loc ); + if( inserted ) { + const auto level = character.get_skill_level( actor->used_skill ); + const auto action_type = actor->default_action( loc, level ); + iter->second = actor->repair_chance( character, loc, action_type ); + } + return iter->second; + } + const repair_item_actor *actor; const item *main_tool; + player &character; + mutable std::unordered_map> chance_cache; + mutable std::unordered_map component_count_cache; }; +static auto get_repair_hint( const player &character, const repair_item_actor *actor, + const item *main_tool ) -> std::string +{ + auto hint = std::string{}; + hint.append( string_format( _( "Tool: %s" ), main_tool->display_name() ) ); + hint.append( " | " ); + hint.append( string_format( _( "Skill used: %s (%d)" ), + actor->used_skill.obj().name(), + character.get_skill_level( actor->used_skill ) ) ); + return hint; +} + item *game_menus::inv::repair( player &p, const repair_item_actor *actor, const item *main_tool ) { - return inv_internal( p, repair_inventory_preset( actor, main_tool ), + return inv_internal( p, repair_inventory_preset( actor, main_tool, p ), _( "Repair what?" ), 1, string_format( _( "You have no items that could be repaired with a %s." ), - main_tool->type_name( 1 ) ) ); + main_tool->type_name( 1 ) ), + get_repair_hint( p, actor, main_tool ) ); } item *game_menus::inv::saw_barrel( player &p, item &tool )