diff --git a/src/inventory_ui.cpp b/src/inventory_ui.cpp index b9ec030a1daea..70834925ca366 100644 --- a/src/inventory_ui.cpp +++ b/src/inventory_ui.cpp @@ -144,6 +144,27 @@ struct inventory_input { inventory_entry *entry; }; +struct container_data { + units::volume actual_capacity; + units::volume total_capacity; + units::mass actual_capacity_weight; + units::mass total_capacity_weight; + units::length max_containable_length; + + std::string to_formatted_string( const bool compact = true ) { + std::string string_to_format; + if( compact ) { + string_to_format = "%s/%s : %s/%s : max %s"; + } else { + string_to_format = "(remains %s, %s) max length %s"; + } + return string_format( string_to_format, + unit_to_string( total_capacity - actual_capacity, true, true ), + unit_to_string( total_capacity_weight - actual_capacity_weight, true, true ), + unit_to_string( max_containable_length, true ) ); + } +}; + static int contained_offset( const item_location &loc ) { if( loc.where() != item_location::type::container ) { @@ -388,7 +409,31 @@ std::string inventory_selector_preset::get_cell_text( const inventory_entry &ent if( !entry ) { return std::string(); } else if( entry.is_item() ) { - return cells[cell_index].get_text( entry ); + std::string text = cells[cell_index].get_text( entry ); + const item &actual_item = *entry.locations.front(); + if( cell_index == 0 && !text.empty() && + entry.get_category_ptr()->get_id() == item_category_ITEMS_WORN && + actual_item.is_worn_by_player() && + actual_item.is_container() && actual_item.has_unrestricted_pockets() ) { + const units::volume total_capacity = actual_item.get_total_capacity( true ); + const units::mass total_capacity_weight = actual_item.get_total_weight_capacity( true ); + const units::length max_containable_length = actual_item.max_containable_length( true ); + + const units::volume actual_capacity = actual_item.get_total_contained_volume( true ); + const units::mass actual_capacity_weight = actual_item.get_total_contained_weight( true ); + + container_data container_data = { + actual_capacity, + total_capacity, + actual_capacity_weight, + total_capacity_weight, + max_containable_length + }; + std::string formatted_string = container_data.to_formatted_string( false ); + + text = text + string_format( " %s", formatted_string ); + } + return text; } else if( cell_index != 0 ) { return replace_colors( cells[cell_index].title ); } else { diff --git a/src/inventory_ui.h b/src/inventory_ui.h index 2de9eb2eb6642..452f6d94f46e4 100644 --- a/src/inventory_ui.h +++ b/src/inventory_ui.h @@ -26,6 +26,7 @@ #include "optional.h" #include "pimpl.h" #include "translations.h" +#include "units.h" #include "units_fwd.h" class Character; @@ -51,6 +52,7 @@ enum class toggle_mode : int { }; struct inventory_input; +struct container_data; struct navigation_mode_data; using drop_location = std::pair; diff --git a/src/item.cpp b/src/item.cpp index 94cb44920a103..2877b5fb68f65 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -1028,6 +1028,10 @@ bool item::is_worn_only_with( const item &it ) const { return is_power_armor() && it.is_power_armor() && it.covers( bodypart_id( "torso" ) ); } +bool item::is_worn_by_player() const +{ + return get_player_character().is_worn( *this ); +} item item::in_its_container( int qty ) const { @@ -8104,6 +8108,18 @@ bool item::is_container() const return contents.has_pocket_type( item_pocket::pocket_type::CONTAINER ); } +bool item::is_container_with_restriction() const +{ + if( !is_container() ) { + return false; + } + return contents.is_restricted_container(); +} + +bool item::is_single_container_with_restriction() const +{ + return contents.is_single_restricted_container(); +} bool item::has_pocket_type( item_pocket::pocket_type pk_type ) const { @@ -8417,9 +8433,9 @@ double item::calculate_by_enchantment_wield( double modify, enchant_vals::mod va return modify; } -units::length item::max_containable_length() const +units::length item::max_containable_length( const bool unrestricted_pockets_only ) const { - return contents.max_containable_length(); + return contents.max_containable_length( unrestricted_pockets_only ); } units::length item::min_containable_length() const @@ -9922,14 +9938,33 @@ int item::getlight_emit() const return lumint; } -units::volume item::get_total_capacity() const +units::volume item::get_total_capacity( const bool unrestricted_pockets_only ) const { - return contents.total_container_capacity(); + return contents.total_container_capacity( unrestricted_pockets_only ); } -units::mass item::get_total_weight_capacity() const +units::mass item::get_total_weight_capacity( const bool unrestricted_pockets_only ) const { - return contents.total_container_weight_capacity(); + return contents.total_container_weight_capacity( unrestricted_pockets_only ); +} + +units::volume item::get_remaining_capacity( const bool unrestricted_pockets_only ) const +{ + return contents.remaining_container_capacity( unrestricted_pockets_only ); +} +units::mass item::get_remaining_weight_capacity( const bool unrestricted_pockets_only ) const +{ + return contents.remaining_container_capacity_weight( unrestricted_pockets_only ); +} + + +units::volume item::get_total_contained_volume( const bool unrestricted_pockets_only ) const +{ + return contents.total_contained_volume( unrestricted_pockets_only ); +} +units::mass item::get_total_contained_weight( const bool unrestricted_pockets_only ) const +{ + return contents.total_contained_weight( unrestricted_pockets_only ); } int item::get_remaining_capacity_for_liquid( const item &liquid, bool allow_bucket, @@ -12040,6 +12075,11 @@ units::volume item::get_selected_stack_volume( const std::map return 0_ml; } +bool item::has_unrestricted_pockets() const +{ + return contents.has_unrestricted_pockets(); +} + units::volume item::get_contents_volume_with_tweaks( const std::map &without ) const { diff --git a/src/item.h b/src/item.h index cd5efaa7272cb..086b1c209da62 100644 --- a/src/item.h +++ b/src/item.h @@ -761,6 +761,10 @@ class item : public visitable /** Whether this is container. Note that container does not necessarily means it's * suitable for liquids. */ bool is_container() const; + /** Whether it is a container, and if it is has some restrictions */ + bool is_container_with_restriction() const; + /** Whether it is a container with only one pocket, and if it is has some restrictions */ + bool is_single_container_with_restriction() const; // whether the contents has a pocket with the associated type bool has_pocket_type( item_pocket::pocket_type pk_type ) const; bool has_any_with( const std::function &filter, @@ -829,12 +833,24 @@ class item : public visitable * It returns the maximum volume of any contents, including liquids, * ammo, magazines, weapons, etc. */ - units::volume get_total_capacity() const; - units::mass get_total_weight_capacity() const; + units::volume get_total_capacity( bool unrestricted_pockets_only = false ) const; + units::mass get_total_weight_capacity( bool unrestricted_pockets_only = false ) const; + + units::volume get_remaining_capacity( bool unrestricted_pockets_only = false ) const; + units::mass get_remaining_weight_capacity( bool unrestricted_pockets_only = false ) const; + + units::volume get_total_contained_volume( bool unrestricted_pockets_only = false ) const; + units::mass get_total_contained_weight( bool unrestricted_pockets_only = false ) const; // recursive function that checks pockets for remaining free space units::volume check_for_free_space() const; units::volume get_selected_stack_volume( const std::map &without ) const; + // checks if the item can have things placed in it + bool has_pockets() const { + // what has it gots in them, precious + return contents.has_pocket_type( item_pocket::pocket_type::CONTAINER ); + } + bool has_unrestricted_pockets() const; units::volume get_contents_volume_with_tweaks( const std::map &without ) const; units::volume get_nested_content_volume_recursive( const std::map &without ) const; @@ -1380,7 +1396,7 @@ class item : public visitable std::pair best_pocket( const item &it, item_location &parent, const item *avoid = nullptr, bool allow_sealed = false, bool ignore_settings = false ); - units::length max_containable_length() const; + units::length max_containable_length( bool unrestricted_pockets_only = false ) const; units::length min_containable_length() const; units::volume max_containable_volume() const; @@ -1877,6 +1893,10 @@ class item : public visitable * Returns true whether this item can be worn only when @param it is worn. */ bool is_worn_only_with( const item &it ) const; + /** + * Returns true wether this item is worn or not + */ + bool is_worn_by_player() const; /** * @name Pet armor related functions. diff --git a/src/item_contents.cpp b/src/item_contents.cpp index c1b0247caa942..d12a438da7212 100644 --- a/src/item_contents.cpp +++ b/src/item_contents.cpp @@ -595,12 +595,16 @@ item_pocket *item_contents::contained_where( const item &contained ) return nullptr; } -units::length item_contents::max_containable_length() const +units::length item_contents::max_containable_length( const bool unrestricted_pockets_only ) const { units::length ret = 0_mm; for( const item_pocket &pocket : contents ) { - if( !pocket.is_type( item_pocket::pocket_type::CONTAINER ) || pocket.is_ablative() || - pocket.holster_full() ) { + bool restriction_condition = !pocket.is_type( item_pocket::pocket_type::CONTAINER ) || + pocket.is_ablative() || pocket.holster_full(); + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && pocket.is_restricted(); + } + if( restriction_condition ) { continue; } units::length candidate = pocket.max_containable_length(); @@ -638,14 +642,17 @@ std::set item_contents::magazine_flag_restrictions() const return ret; } -units::volume item_contents::max_containable_volume() const +units::volume item_contents::max_containable_volume( const bool unrestricted_pockets_only ) const { units::volume ret = 0_ml; for( const item_pocket &pocket : contents ) { - // pockets that aren't traditional containers or don't have a default value shouldn't be counted for this - if( !pocket.is_type( item_pocket::pocket_type::CONTAINER ) || pocket.is_ablative() || - pocket.holster_full() || - pocket.volume_capacity() >= pocket_data::max_volume_for_container ) { + bool restriction_condition = !pocket.is_type( item_pocket::pocket_type::CONTAINER ) || + pocket.is_ablative() || pocket.holster_full() || + pocket.volume_capacity() >= pocket_data::max_volume_for_container; + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && pocket.is_restricted(); + } + if( restriction_condition ) { continue; } units::volume candidate = pocket.remaining_volume(); @@ -1037,6 +1044,17 @@ bool item_contents::has_pocket_type( const item_pocket::pocket_type pk_type ) co return false; } +bool item_contents::has_unrestricted_pockets() const +{ + int restricted_pockets_qty = 0; + for( const item_pocket &pocket : contents ) { + if( pocket.is_restricted() ) { + restricted_pockets_qty++; + } + } + return restricted_pockets_qty < static_cast ( get_all_contained_pockets().value().size() ); +} + bool item_contents::has_any_with( const std::function &filter, item_pocket::pocket_type pk_type ) const { @@ -1088,6 +1106,22 @@ bool item_contents::is_funnel_container( units::volume &bigger_than ) const return false; } +bool item_contents::is_restricted_container() const +{ + for( const item_pocket &pocket : contents ) { + if( pocket.is_restricted() ) { + return true; + } + } + return false; +} + +bool item_contents::is_single_restricted_container() const +{ + std::vector contained_pockets = get_all_contained_pockets().value(); + return contained_pockets.size() == 1 && contained_pockets[0]->is_restricted(); +} + item &item_contents::only_item() { if( num_item_stacks() != 1 ) { @@ -1386,12 +1420,18 @@ itype_id item_contents::magazine_default() const return itype_id::NULL_ID(); } -units::mass item_contents::total_container_weight_capacity() const +units::mass item_contents::total_container_weight_capacity( const bool unrestricted_pockets_only ) +const { units::mass total_weight = 0_gram; + for( const item_pocket &pocket : contents ) { - if( pocket.is_type( item_pocket::pocket_type::CONTAINER ) && !pocket.is_ablative() && - pocket.weight_capacity() < pocket_data::max_weight_for_container ) { + bool restriction_condition = pocket.is_type( item_pocket::pocket_type::CONTAINER ) && + !pocket.is_ablative() && pocket.weight_capacity() < pocket_data::max_weight_for_container; + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && !pocket.is_restricted(); + } + if( restriction_condition ) { total_weight += pocket.weight_capacity(); } } @@ -1540,11 +1580,15 @@ std::vector< const item_pocket *> item_contents::get_all_reloadable_pockets() co return pockets; } -units::volume item_contents::total_container_capacity() const +units::volume item_contents::total_container_capacity( const bool unrestricted_pockets_only ) const { units::volume total_vol = 0_ml; for( const item_pocket &pocket : contents ) { - if( pocket.is_type( item_pocket::pocket_type::CONTAINER ) ) { + bool restriction_condition = pocket.is_type( item_pocket::pocket_type::CONTAINER ); + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && !pocket.is_restricted(); + } + if( restriction_condition ) { const pocket_data *p_data = pocket.get_pocket_data(); // if the pocket has default volume or is a holster that has an // item in it instead of returning the volume return the volume of things contained @@ -1559,39 +1603,83 @@ units::volume item_contents::total_container_capacity() const return total_vol; } -units::volume item_contents::total_standard_capacity() const +units::volume item_contents::total_standard_capacity( const bool unrestricted_pockets_only ) const { units::volume total_vol = 0_ml; for( const item_pocket &pocket : contents ) { - if( pocket.is_standard_type() ) { + bool restriction_condition = pocket.is_standard_type(); + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && !pocket.is_restricted(); + } + if( restriction_condition ) { total_vol += pocket.volume_capacity(); } } return total_vol; } -units::volume item_contents::remaining_container_capacity() const +units::volume item_contents::remaining_container_capacity( const bool unrestricted_pockets_only ) +const { units::volume total_vol = 0_ml; for( const item_pocket &pocket : contents ) { - if( pocket.is_type( item_pocket::pocket_type::CONTAINER ) ) { + bool restriction_condition = pocket.is_type( item_pocket::pocket_type::CONTAINER ); + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && !pocket.is_restricted(); + } + if( restriction_condition ) { total_vol += pocket.remaining_volume(); } } return total_vol; } -units::volume item_contents::total_contained_volume() const +units::volume item_contents::total_contained_volume( const bool unrestricted_pockets_only ) const { units::volume total_vol = 0_ml; for( const item_pocket &pocket : contents ) { - if( pocket.is_type( item_pocket::pocket_type::CONTAINER ) ) { + bool restriction_condition = pocket.is_type( item_pocket::pocket_type::CONTAINER ); + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && !pocket.is_restricted(); + } + if( restriction_condition ) { total_vol += pocket.contains_volume(); } } return total_vol; } +units::mass item_contents::remaining_container_capacity_weight( const bool + unrestricted_pockets_only ) const +{ + units::mass total_weight = 0_gram; + for( const item_pocket &pocket : contents ) { + bool restriction_condition = pocket.is_type( item_pocket::pocket_type::CONTAINER ); + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && !pocket.is_restricted(); + } + if( restriction_condition ) { + total_weight += pocket.remaining_weight(); + } + } + return total_weight; +} + +units::mass item_contents::total_contained_weight( const bool unrestricted_pockets_only ) const +{ + units::mass total_weight = 0_gram; + for( const item_pocket &pocket : contents ) { + bool restriction_condition = pocket.is_type( item_pocket::pocket_type::CONTAINER ); + if( unrestricted_pockets_only ) { + restriction_condition = restriction_condition && !pocket.is_restricted(); + } + if( restriction_condition ) { + total_weight += pocket.contains_weight(); + } + } + return total_weight; +} + units::volume item_contents::get_contents_volume_with_tweaks( const std::map &without ) const { diff --git a/src/item_contents.h b/src/item_contents.h index fd7785c1daa37..358a1a95e5d8b 100644 --- a/src/item_contents.h +++ b/src/item_contents.h @@ -41,12 +41,11 @@ class item_contents std::pair best_pocket( const item &it, item_location &parent, const item *avoid = nullptr, bool allow_sealed = false, bool ignore_settings = false ); - units::length max_containable_length() const; + units::length max_containable_length( bool unrestricted_pockets_only = false ) const; units::length min_containable_length() const; - units::volume max_containable_volume() const; + units::volume max_containable_volume( bool unrestricted_pockets_only = false ) const; std::set magazine_flag_restrictions() const; - /** * returns whether any of the pockets contained is compatible with the specified item. * Does not check if the item actually fits volume/weight wise @@ -138,19 +137,21 @@ class item_contents units::length item_length_modifier() const; // gets the total weight capacity of all pockets - units::mass total_container_weight_capacity() const; + units::mass total_container_weight_capacity( bool unrestricted_pockets_only = false ) const; /** * gets the total volume available to be used. * does not guarantee that an item of that size can be inserted. */ - units::volume total_container_capacity() const; + units::volume total_container_capacity( bool unrestricted_pockets_only = false ) const; // Gets the total volume of every is_standard_type container - units::volume total_standard_capacity() const; + units::volume total_standard_capacity( bool unrestricted_pockets_only = false ) const; - units::volume remaining_container_capacity() const; - units::volume total_contained_volume() const; + units::volume remaining_container_capacity( bool unrestricted_pockets_only = false ) const; + units::volume total_contained_volume( bool unrestricted_pockets_only = false ) const; + units::mass remaining_container_capacity_weight( bool unrestricted_pockets_only = false ) const; + units::mass total_contained_weight( bool unrestricted_pockets_only = false ) const; units::volume get_contents_volume_with_tweaks( const std::map &without ) const; units::volume get_nested_content_volume_recursive( const std::map &without ) const; @@ -279,6 +280,7 @@ class item_contents // whether the contents has a pocket with the associated type bool has_pocket_type( item_pocket::pocket_type pk_type ) const; + bool has_unrestricted_pockets() const; bool has_any_with( const std::function &filter, item_pocket::pocket_type pk_type ) const; @@ -294,6 +296,9 @@ class item_contents bool same_contents( const item_contents &rhs ) const; // can this item be used as a funnel? bool is_funnel_container( units::volume &bigger_than ) const; + // the container has restrictions + bool is_restricted_container() const; + bool is_single_restricted_container() const; /** * @relates visitable * NOTE: upon expansion, this may need to be filtered by type enum depending on accessibility diff --git a/src/item_pocket.cpp b/src/item_pocket.cpp index d50230eaf9527..0ccd7aca758d8 100644 --- a/src/item_pocket.cpp +++ b/src/item_pocket.cpp @@ -392,6 +392,11 @@ bool item_pocket::is_funnel_container( units::volume &bigger_than ) const return false; } +bool item_pocket::is_restricted() const +{ + return !data->get_flag_restrictions().empty(); +} + std::list item_pocket::all_items_top() { std::list items; diff --git a/src/item_pocket.h b/src/item_pocket.h index 65fe0c2575bbe..0692df7599604 100644 --- a/src/item_pocket.h +++ b/src/item_pocket.h @@ -122,6 +122,7 @@ class item_pocket bool stacks_with( const item_pocket &rhs ) const; bool is_funnel_container( units::volume &bigger_than ) const; + bool is_restricted() const; bool has_any_with( const std::function &filter ) const; bool is_valid() const; diff --git a/src/units_utility.cpp b/src/units_utility.cpp index 9b6206fdfec5f..6861f958abd92 100644 --- a/src/units_utility.cpp +++ b/src/units_utility.cpp @@ -166,10 +166,23 @@ std::string length_units( const units::length &length ) } } -std::string weight_to_string( const units::mass &weight ) +std::string length_to_string( const units::length &length, const bool compact ) { - const double converted_weight = convert_weight( weight ); - return string_format( "%.2f %s", converted_weight, weight_units() ); + const int converted_length = convert_length( length ); + std::string string_to_format = "%u%s%s"; + return string_format( string_to_format, converted_length, compact ? "" : " ", + length_units( length ) ); +} + +std::string weight_to_string( const units::mass &weight, const bool compact, + const bool remove_trailing_zeroes ) +{ + const int default_decimal_places = 2; + const double converted_weight = round_with_places( convert_weight( weight ), + default_decimal_places ); + std::string string_to_format = remove_trailing_zeroes ? "%g%s%s" : "%." + + std::to_string( default_decimal_places ) + "f%s%s"; + return string_format( string_to_format, converted_weight, compact ? "" : " ", weight_units() ); } double convert_volume( int volume ) @@ -198,12 +211,39 @@ double convert_volume( int volume, int *out_scale ) return ret; } -std::string vol_to_string( const units::volume &vol ) +std::string vol_to_string( const units::volume &vol, const bool compact, + const bool remove_trailing_zeroes ) { int converted_volume_scale = 0; + const int default_decimal_places = 3; const double converted_volume = - convert_volume( vol.value(), - &converted_volume_scale ); + round_with_places( convert_volume( vol.value(), + &converted_volume_scale ), default_decimal_places ); + std::string string_to_format = remove_trailing_zeroes ? "%g%s%s" : "%." + + std::to_string( default_decimal_places ) + "f%s%s"; + return string_format( string_to_format, converted_volume, compact ? "" : " ", volume_units_abbr() ); +} + +std::string unit_to_string( const units::volume &unit, const bool compact, + const bool remove_trailing_zeroes ) +{ + return vol_to_string( unit, compact, remove_trailing_zeroes ); +} +std::string unit_to_string( const units::mass &unit, const bool compact, + const bool remove_trailing_zeroes ) +{ + return weight_to_string( unit, compact, remove_trailing_zeroes ); +} +std::string unit_to_string( const units::length &unit, const bool compact ) +{ + return length_to_string( unit, compact ); +} - return string_format( "%.3f %s", converted_volume, volume_units_abbr() ); +/** + * round a float @value, with int @decimal_places limitation +*/ +double round_with_places( double value, int decimal_places ) +{ + const double multiplier = std::pow( 10.0, decimal_places ); + return std::round( value * multiplier ) / multiplier; } diff --git a/src/units_utility.h b/src/units_utility.h index 50b5f277935f3..b5353d6996477 100644 --- a/src/units_utility.h +++ b/src/units_utility.h @@ -85,12 +85,14 @@ double convert_weight( const units::mass &weight ); */ int convert_length( const units::length &length ); std::string length_units( const units::length &length ); +std::string length_to_string( const units::length &length, bool compact = false ); /** Convert length to inches or cm. Used in pickup UI */ double convert_length_cm_in( const units::length &length ); /** convert a mass unit to a string readable by a human */ -std::string weight_to_string( const units::mass &weight ); +std::string weight_to_string( const units::mass &weight, bool compact = false, + bool remove_trailing_zeroes = false ); /** * Convert volume from ml to units defined by user. @@ -104,6 +106,16 @@ double convert_volume( int volume ); double convert_volume( int volume, int *out_scale ); /** convert a volume unit to a string readable by a human */ -std::string vol_to_string( const units::volume &vol ); - +std::string vol_to_string( const units::volume &vol, bool compact = false, + bool remove_trailing_zeroes = false ); + +/** convert any type of unit to a string readable by a human */ +std::string unit_to_string( const units::volume &unit, bool compact = false, + bool remove_trailing_zeroes = false ); +std::string unit_to_string( const units::mass &unit, bool compact = false, + bool remove_trailing_zeroes = false ); +std::string unit_to_string( const units::length &unit, bool compact = false ); + +/** utility function to round with specified decimal places */ +double round_with_places( double value, int decimal_places ); #endif // CATA_SRC_UNITS_UTILITY_H