From e47a2a1b53eefe567e37d76855491654194a015e Mon Sep 17 00:00:00 2001 From: KorGgenT Date: Sun, 29 Dec 2019 11:35:26 -0500 Subject: [PATCH] class definitions for item_pocket and item_contents --- src/crafting.cpp | 4 +- src/crafting.h | 3 + src/item.cpp | 5 + src/item.h | 2 + src/item_contents.cpp | 409 +++++++++++++++++++++++++++++ src/item_contents.h | 107 ++++++++ src/item_pocket.cpp | 582 ++++++++++++++++++++++++++++++++++++++++++ src/item_pocket.h | 186 ++++++++++++++ src/map.h | 16 +- 9 files changed, 1303 insertions(+), 11 deletions(-) create mode 100644 src/item_contents.cpp create mode 100644 src/item_contents.h create mode 100644 src/item_pocket.cpp create mode 100644 src/item_pocket.h diff --git a/src/crafting.cpp b/src/crafting.cpp index 32debcf157369..fd36eb91d36e4 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -71,8 +71,6 @@ class basecamp; static const efftype_id effect_contacts( "contacts" ); -void drop_or_handle( const item &newit, player &p ); - static const trait_id trait_DEBUG_HS( "DEBUG_HS" ); static const trait_id trait_BURROW( "BURROW" ); @@ -2247,7 +2245,7 @@ void remove_ammo( std::list &dis_items, player &p ) } } -void drop_or_handle( const item &newit, player &p ) +void drop_or_handle( const item &newit, Character &p ) { if( newit.made_of( LIQUID ) && p.is_player() ) { // TODO: what about NPCs? liquid_handler::handle_all_liquid( newit, PICKUP_RANGE ); diff --git a/src/crafting.h b/src/crafting.h index 4649ce9cd643e..1ca1e0aeb8bf4 100644 --- a/src/crafting.h +++ b/src/crafting.h @@ -4,6 +4,7 @@ #include +class Character; class item; class player; class recipe; @@ -16,4 +17,6 @@ void remove_ammo( std::list &dis_items, player &p ); const recipe *select_crafting_recipe( int &batch_size ); +void drop_or_handle( const item &newit, Character &p ); + #endif diff --git a/src/item.cpp b/src/item.cpp index b83c0e9a511e6..5dac3fa10eac6 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -8984,6 +8984,11 @@ bool item::process_blackpowder_fouling( player *carrier ) return false; } +void item::set_last_rot_check( const time_point &pt ) +{ + last_rot_check = pt; +} + bool item::process( player *carrier, const tripoint &pos, bool activate, float insulation, const temperature_flag flag ) { diff --git a/src/item.h b/src/item.h index d54d521738b31..6eba609023b1c 100644 --- a/src/item.h +++ b/src/item.h @@ -1140,6 +1140,8 @@ class item : public visitable item *get_food(); const item *get_food() const; + void set_last_rot_check( const time_point &pt ); + /** What faults can potentially occur with this item? */ std::set faults_potential() const; diff --git a/src/item_contents.cpp b/src/item_contents.cpp new file mode 100644 index 0000000000000..a7f57756e80dc --- /dev/null +++ b/src/item_contents.cpp @@ -0,0 +1,409 @@ +#include "item_contents.h" + +#include "generic_factory.h" +#include "item.h" +#include "item_pocket.h" +#include "optional.h" +#include "point.h" +#include "units.h" + +namespace fake_item +{ +static item_pocket none_pocket( nullptr ); +static pocket_data legacy_pocket_data; +static item_pocket legacy_pocket( &legacy_pocket_data ); +} // namespace fake_item + +void item_contents::serialize( JsonOut &json ) const +{ + json.start_object(); + + json.member( "contents", contents ); + + json.end_object(); +} + +void item_contents::deserialize( JsonIn &jsin ) +{ + JsonObject data = jsin.get_object(); + optional( data, was_loaded, "contents", contents ); +} + +void item_contents::add_legacy_pocket() +{ + for( const item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + // an item should not have more than one legacy pocket + return; + } + } + contents.emplace_front( fake_item::legacy_pocket ); +} + +bool item_contents::stacks_with( const item_contents &rhs ) const +{ + if( contents.size() != rhs.contents.size() ) { + return false; + } + return std::equal( contents.begin(), contents.end(), + rhs.contents.begin(), + []( const item_pocket & a, const item_pocket & b ) { + return a.stacks_with( b ); + } ); +} + +ret_val item_contents::can_contain( const item &it ) const +{ + ret_val ret = ret_val::make_failure( _( "is not a container" ) ); + for( const item_pocket &pocket : contents ) { + const ret_val pocket_contain_code = pocket.can_contain( it ); + if( pocket_contain_code.success() ) { + return ret_val::make_success(); + } + if( pocket_contain_code.value() != item_pocket::contain_code::ERR_LEGACY_CONTAINER ) { + ret = ret_val::make_failure( pocket_contain_code.str() ); + } + } + return ret; +} + +item *item_contents::magazine_current() +{ + for( item_pocket &pocket : contents ) { + item *mag = pocket.magazine_current(); + if( mag != nullptr ) { + return mag; + } + } + return nullptr; +} + +void item_contents::casings_handle( const std::function &func ) +{ + for( item_pocket &pocket : contents ) { + pocket.casings_handle( func ); + } +} + +bool item_contents::use_amount( const itype_id &it, int &quantity, std::list &used ) +{ + bool used_item = false; + for( item_pocket &pocket : contents ) { + used_item = pocket.use_amount( it, quantity, used ) || used_item; + } + return used_item; +} + +bool item_contents::will_explode_in_a_fire() const +{ + for( const item_pocket &pocket : contents ) { + if( pocket.will_explode_in_a_fire() ) { + return true; + } + } + return false; +} + +bool item_contents::detonate( const tripoint &p, std::vector &drops ) +{ + bool detonated = false; + for( item_pocket &pocket : contents ) { + detonated = pocket.detonate( p, drops ) || detonated; + } + return detonated; +} + +bool item_contents::process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, const temperature_flag flag ) +{ + for( item_pocket &pocket : contents ) { + pocket.process( type, carrier, pos, activate, insulation, flag ); + } + return true; +} + +bool item_contents::legacy_unload( player *guy, bool &changed ) +{ + for( item_pocket &pocket : contents ) { + pocket.legacy_unload( guy, changed ); + } + return true; +} + +void item_contents::remove_all_ammo( Character &guy ) +{ + for( item_pocket &pocket : contents ) { + pocket.remove_all_ammo( guy ); + } +} + +void item_contents::remove_all_mods( Character &guy ) +{ + for( item_pocket &pocket : contents ) { + pocket.remove_all_mods( guy ); + } +} + +bool item_contents::empty() const +{ + if( contents.empty() ) { + return true; + } + for( const item_pocket &pocket : contents ) { + if( pocket.empty() ) { + return true; + } + } + return false; +} + +item &item_contents::legacy_back() +{ + return legacy_pocket().back(); +} + +const item &item_contents::legacy_back() const +{ + return legacy_pocket().back(); +} + +item &item_contents::legacy_front() +{ + return legacy_pocket().front(); +} + +const item &item_contents::legacy_front() const +{ + return legacy_pocket().front(); +} + +size_t item_contents::legacy_size() const +{ + if( contents.empty() ) { + return 0; + } + return legacy_pocket().size(); +} + +void item_contents::legacy_pop_back() +{ + legacy_pocket().pop_back(); +} + +size_t item_contents::num_item_stacks() const +{ + size_t num = 0; + for( const item_pocket &pocket : contents ) { + num += pocket.size(); + } + return num; +} + +size_t item_contents::size() const +{ + // always has a legacy pocket due to contstructor + // we want to ignore this + return contents.size() - 1; +} + +item_pocket &item_contents::legacy_pocket() +{ + for( item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + return pocket; + } + } + debugmsg( "Tried to access non-existing legacy pocket" ); + return fake_item::none_pocket; +} + +const item_pocket &item_contents::legacy_pocket() const +{ + for( const item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + return pocket; + } + } + debugmsg( "Tried to access non-existing legacy pocket" ); + return fake_item::none_pocket; +} + +units::volume item_contents::item_size_modifier() const +{ + units::volume total_vol = 0_ml; + for( const item_pocket &pocket : contents ) { + if( !pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + total_vol += pocket.item_size_modifier(); + } + } + return total_vol; +} + +units::volume item_contents::total_container_capacity() const +{ + units::volume total_vol = 0_ml; + for( const item_pocket &pocket : contents ) { + if( pocket.is_type( item_pocket::pocket_type::CONTAINER ) ) { + total_vol += pocket.volume_capacity(); + } + } + return total_vol; +} + +units::mass item_contents::item_weight_modifier() const +{ + units::mass total_mass = 0_gram; + for( const item_pocket &pocket : contents ) { + total_mass += pocket.item_weight_modifier(); + } + return total_mass; +} + +bool item_contents::spill_contents( const tripoint &pos ) +{ + for( item_pocket &pocket : contents ) { + pocket.spill_contents( pos ); + } + return true; +} + +cata::optional item_contents::remove_item( const item &it ) +{ + for( item_pocket &pocket : contents ) { + cata::optional ret = pocket.remove_item( it ); + if( ret ) { + return ret; + } + } + return cata::nullopt; +} + +cata::optional item_contents::remove_item( const item_location &it ) +{ + if( !it ) { + return cata::nullopt; + } + return remove_item( *it ); +} + +void item_contents::clear_items() +{ + for( item_pocket &pocket : contents ) { + pocket.clear_items(); + } +} + +bool item_contents::has_item( const item &it ) const +{ + for( const item_pocket &pocket : contents ) { + if( pocket.has_item( it ) ) { + return true; + } + } + return false; +} + +item *item_contents::get_item_with( const std::function &filter ) +{ + for( item_pocket &pocket : contents ) { + item *it = pocket.get_item_with( filter ); + if( it != nullptr ) { + return it; + } + } + return nullptr; +} + +void item_contents::remove_items_if( const std::function &filter ) +{ + for( item_pocket &pocket : contents ) { + pocket.remove_items_if( filter ); + } +} + +void item_contents::has_rotten_away( const tripoint &pnt ) +{ + for( item_pocket &pocket : contents ) { + pocket.has_rotten_away( pnt ); + } +} + +ret_val item_contents::insert_item( const item &it ) +{ + ret_val ret = ret_val::make_failure( _( "is not a container" ) ); + for( item_pocket &pocket : contents ) { + const ret_val pocket_contain_code = pocket.insert_item( it ); + if( pocket_contain_code.success() ) { + return ret_val::make_success(); + } + if( pocket_contain_code.value() != item_pocket::contain_code::ERR_LEGACY_CONTAINER ) { + ret = ret_val::make_failure( pocket_contain_code.str() ); + } + } + return ret; +} + +int item_contents::obtain_cost( const item &it ) const +{ + for( const item_pocket &pocket : contents ) { + const int mv = pocket.obtain_cost( it ); + if( mv != 0 ) { + return mv; + } + } + return 0; +} + +static void insert_separation_line( std::vector &info ) +{ + if( info.empty() || info.back().sName != "--" ) { + info.push_back( iteminfo( "DESCRIPTION", "--" ) ); + } +} + +void item_contents::info( std::vector &info ) const +{ + int pocket_number = 1; + std::vector contents_info; + std::vector found_pockets; + std::map pocket_num; // index, amount + for( const item_pocket &pocket : contents ) { + if( !pocket.is_type( item_pocket::pocket_type::LEGACY_CONTAINER ) ) { + bool found = false; + int idx = 0; + for( const item_pocket &found_pocket : found_pockets ) { + if( found_pocket == pocket ) { + found = true; + pocket_num[idx]++; + } + idx++; + } + if( !found ) { + found_pockets.push_back( pocket ); + pocket_num[idx]++; + } + pocket.contents_info( contents_info, pocket_number++, size() != 1 ); + } + } + int idx = 0; + for( const item_pocket &pocket : found_pockets ) { + insert_separation_line( info ); + if( pocket_num[idx] > 1 ) { + info.emplace_back( "DESCRIPTION", _( string_format( "Pockets (%d)", + pocket_num[idx] ) ) ); + } + idx++; + pocket.general_info( info, 0, false ); + } + info.insert( info.end(), contents_info.begin(), contents_info.end() ); +} + +void item_contents::insert_legacy( const item &it ) +{ + legacy_pocket().add( it ); +} + +std::list &item_contents::legacy_items() +{ + return legacy_pocket().edit_contents(); +} diff --git a/src/item_contents.h b/src/item_contents.h new file mode 100644 index 0000000000000..e7ac9f7ffe57a --- /dev/null +++ b/src/item_contents.h @@ -0,0 +1,107 @@ +#pragma once +#ifndef ITEM_CONTENTS_H +#define ITEM_CONTENTS_H + +#include "enums.h" +#include "item_pocket.h" +#include "optional.h" +#include "ret_val.h" +#include "units.h" +#include "visitable.h" + +class item; +class item_location; +class player; + +struct iteminfo; +struct tripoint; + +class item_contents +{ + public: + item_contents() { + // items should have a legacy pocket until everything is migrated + add_legacy_pocket(); + } + + // used for loading itype + item_contents( const std::vector &pockets ) { + for( const pocket_data &data : pockets ) { + contents.push_back( item_pocket( &data ) ); + } + add_legacy_pocket(); + } + + // for usage with loading to aid migration + void add_legacy_pocket(); + + bool stacks_with( const item_contents &rhs ) const; + + ret_val can_contain( const item &it ) const; + bool empty() const; + + // total size the parent item needs to be modified based on rigidity of pockets + units::volume item_size_modifier() const; + units::volume total_container_capacity() const; + // total weight the parent item needs to be modified based on weight modifiers of pockets + units::mass item_weight_modifier() const; + + item *magazine_current(); + void casings_handle( const std::function &func ); + bool use_amount( const itype_id &it, int &quantity, std::list &used ); + bool will_explode_in_a_fire() const; + bool detonate( const tripoint &p, std::vector &drops ); + bool process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, temperature_flag flag ); + bool legacy_unload( player *guy, bool &changed ); + void remove_all_ammo( Character &guy ); + void remove_all_mods( Character &guy ); + + // removes and returns the item from the pocket. + cata::optional remove_item( const item &it ); + cata::optional remove_item( const item_location &it ); + + // tries to put an item in a pocket. returns false on failure + // has similar code to can_contain in order to avoid running it twice + ret_val insert_item( const item &it ); + // finds or makes a fake pocket and puts this item into it + void insert_legacy( const item &it ); + // equivalent to contents.back() when item::contents was a std::list + std::list &legacy_items(); + item &legacy_back(); + const item &legacy_back() const; + item &legacy_front(); + const item &legacy_front() const; + size_t legacy_size() const; + // ignores legacy_pocket, so -1 + size_t size() const; + void legacy_pop_back(); + size_t num_item_stacks() const; + bool spill_contents( const tripoint &pos ); + void clear_items(); + bool has_item( const item &it ) const; + item *get_item_with( const std::function &filter ); + void remove_items_if( const std::function &filter ); + void has_rotten_away( const tripoint &pnt ); + + int obtain_cost( const item &it ) const; + // @relates visitable + // NOTE: upon expansion, this may need to be filtered by type enum depending on accessibility + VisitResponse visit_contents( const std::function &func, + item *parent = nullptr ); + + void info( std::vector &info ) const; + + void serialize( JsonOut &json ) const; + void deserialize( JsonIn &jsin ); + + bool was_loaded; + private: + // gets the pocket described as legacy, or creates one + item_pocket &legacy_pocket(); + const item_pocket &legacy_pocket() const; + + std::list contents; +}; + +#endif diff --git a/src/item_pocket.cpp b/src/item_pocket.cpp new file mode 100644 index 0000000000000..d456ddd0f7371 --- /dev/null +++ b/src/item_pocket.cpp @@ -0,0 +1,582 @@ +#include "item_pocket.h" + +#include "assign.h" +#include "crafting.h" +#include "enums.h" +#include "game.h" +#include "generic_factory.h" +#include "item.h" +#include "itype.h" +#include "json.h" +#include "map.h" +#include "player.h" +#include "point.h" +#include "units.h" + +namespace io +{ +// *INDENT-OFF* +template<> +std::string enum_to_string( item_pocket::pocket_type data ) +{ + switch ( data ) { + case item_pocket::pocket_type::CONTAINER: return "CONTAINER"; + case item_pocket::pocket_type::MAGAZINE: return "MAGAZINE"; + case item_pocket::pocket_type::LEGACY_CONTAINER: return "LEGACY_CONTAINER"; + case item_pocket::pocket_type::LAST: break; + } + debugmsg( "Invalid valid_target" ); + abort(); +} +// *INDENT-ON* +} // namespace io + +void pocket_data::load( const JsonObject &jo ) +{ + optional( jo, was_loaded, "pocket_type", type, item_pocket::pocket_type::CONTAINER ); + optional( jo, was_loaded, "min_item_volume", min_item_volume, volume_reader(), 0_ml ); + mandatory( jo, was_loaded, "max_contains_volume", max_contains_volume, volume_reader() ); + mandatory( jo, was_loaded, "max_contains_weight", max_contains_weight, mass_reader() ); + optional( jo, was_loaded, "spoil_multiplier", spoil_multiplier, 1.0f ); + optional( jo, was_loaded, "weight_multiplier", weight_multiplier, 1.0f ); + optional( jo, was_loaded, "moves", moves, 100 ); + optional( jo, was_loaded, "fire_protection", fire_protection, false ); + optional( jo, was_loaded, "watertight", watertight, false ); + optional( jo, was_loaded, "gastight", gastight, false ); + optional( jo, was_loaded, "open_container", open_container, false ); + optional( jo, was_loaded, "flag_restriction", flag_restriction ); + optional( jo, was_loaded, "rigid", rigid, false ); +} + +void item_pocket::serialize( JsonOut &json ) const +{ + json.start_object(); + + json.member( "contents", contents ); + + json.end_object(); +} + +bool item_pocket::operator==( const item_pocket &rhs ) const +{ + return *data == *rhs.data; +} + +bool pocket_data::operator==( const pocket_data &rhs ) const +{ + return rigid == rhs.rigid && + watertight == rhs.watertight && + gastight == rhs.gastight && + fire_protection == rhs.fire_protection && + flag_restriction == rhs.flag_restriction && + type == rhs.type && + max_contains_volume == rhs.max_contains_volume && + min_item_volume == rhs.min_item_volume && + max_contains_weight == rhs.max_contains_weight && + spoil_multiplier == rhs.spoil_multiplier && + weight_multiplier == rhs.weight_multiplier && + moves == rhs.moves; +} + +bool item_pocket::stacks_with( const item_pocket &rhs ) const +{ + if( contents.size() != rhs.contents.size() ) { + return false; + } + return std::equal( contents.begin(), contents.end(), rhs.contents.begin(), + []( const item & a, const item & b ) { + return a.charges == b.charges && a.stacks_with( b ); + } ); +} + +void item_pocket::deserialize( JsonIn &jsin ) +{ + JsonObject data = jsin.get_object(); + optional( data, was_loaded, "contents", contents ); +} + +item &item_pocket::back() +{ + return contents.back(); +} + +const item &item_pocket::back() const +{ + return contents.back(); +} + +item &item_pocket::front() +{ + return contents.front(); +} + +const item &item_pocket::front() const +{ + return contents.front(); +} + +void item_pocket::pop_back() +{ + contents.pop_back(); +} + +size_t item_pocket::size() const +{ + return contents.size(); +} + +units::volume item_pocket::volume_capacity() const +{ + return data->max_contains_volume; +} + +units::volume item_pocket::remaining_volume() const +{ + return data->max_contains_volume - contains_volume(); +} + +units::volume item_pocket::item_size_modifier() const +{ + if( data->rigid ) { + return 0_ml; + } + units::volume total_vol = 0_ml; + for( const item &it : contents ) { + total_vol += it.volume(); + } + return total_vol; +} + +units::mass item_pocket::item_weight_modifier() const +{ + units::mass total_mass = 0_gram; + for( const item &it : contents ) { + total_mass += it.weight() * data->weight_multiplier; + } + return total_mass; +} + +item *item_pocket::magazine_current() +{ + auto iter = std::find_if( contents.begin(), contents.end(), []( const item & it ) { + return it.is_magazine(); + } ); + return iter != contents.end() ? &*iter : nullptr; +} + +void item_pocket::casings_handle( const std::function &func ) +{ + for( auto it = contents.begin(); it != contents.end(); ) { + if( it->has_flag( "CASING" ) ) { + it->unset_flag( "CASING" ); + if( func( *it ) ) { + it = contents.erase( it ); + continue; + } + // didn't handle the casing so reset the flag ready for next call + it->set_flag( "CASING" ); + } + ++it; + } +} + +bool item_pocket::use_amount( const itype_id &it, int &quantity, std::list &used ) +{ + bool used_item = false; + for( auto a = contents.begin(); a != contents.end() && quantity > 0; ) { + if( a->use_amount( it, quantity, used ) ) { + used_item = true; + a = contents.erase( a ); + } else { + ++a; + } + } + return used_item; +} + +bool item_pocket::will_explode_in_a_fire() const +{ + if( data->fire_protection ) { + return false; + } + return std::any_of( contents.begin(), contents.end(), []( const item & it ) { + return it.will_explode_in_fire(); + } ); +} + +bool item_pocket::detonate( const tripoint &pos, std::vector &drops ) +{ + const auto new_end = std::remove_if( contents.begin(), contents.end(), [&pos, &drops]( item & it ) { + return it.detonate( pos, drops ); + } ); + if( new_end != contents.end() ) { + contents.erase( new_end, contents.end() ); + // If any of the contents explodes, so does the container + return true; + } + return false; +} + +bool item_pocket::process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, const temperature_flag flag ) +{ + const bool preserves = type.container && type.container->preserves; + bool processed = false; + for( auto it = contents.begin(); it != contents.end(); ) { + if( preserves ) { + // Simulate that the item has already "rotten" up to last_rot_check, but as item::rot + // is not changed, the item is still fresh. + it->set_last_rot_check( calendar::turn ); + } + if( it->process( carrier, pos, activate, type.insulation_factor * insulation, flag ) ) { + it = contents.erase( it ); + processed = true; + } else { + ++it; + } + } + return processed; +} + +bool item_pocket::legacy_unload( player *guy, bool &changed ) +{ + contents.erase( std::remove_if( contents.begin(), contents.end(), + [guy, &changed]( item & e ) { + int old_charges = e.charges; + const bool consumed = guy->add_or_drop_with_msg( e, true ); + changed = changed || consumed || e.charges != old_charges; + if( consumed ) { + guy->mod_moves( -guy->item_handling_cost( e ) ); + } + return consumed; + } ), contents.end() ); + return changed; +} + +void item_pocket::remove_all_ammo( Character &guy ) +{ + for( auto iter = contents.begin(); iter != contents.end(); ) { + if( iter->is_irremovable() ) { + iter++; + continue; + } + drop_or_handle( *iter, guy ); + iter = contents.erase( iter ); + } +} + +void item_pocket::remove_all_mods( Character &guy ) +{ + auto mod = std::find_if( contents.begin(), contents.end(), []( const item & e ) { + return e.is_toolmod(); + } ); + guy.i_add_or_drop( *mod ); + contents.erase( mod ); +} + +static void insert_separation_line( std::vector &info ) +{ + if( info.empty() || info.back().sName != "--" ) { + info.push_back( iteminfo( "DESCRIPTION", "--" ) ); + } +} + +static std::string vol_to_string( const units::volume &vol ) +{ + int converted_volume_scale = 0; + const double converted_volume = + convert_volume( vol.value(), + &converted_volume_scale ); + + return string_format( "%.3f %s", converted_volume, volume_units_abbr() ); +} + +static std::string weight_to_string( const units::mass &weight ) +{ + const double converted_weight = convert_weight( weight ); + return string_format( "%.2f %s", converted_weight, weight_units() ); +} + +void item_pocket::general_info( std::vector &info, int pocket_number, + bool disp_pocket_number ) const +{ + const std::string space = " "; + + if( type != LEGACY_CONTAINER ) { + if( disp_pocket_number ) { + info.emplace_back( "DESCRIPTION", _( string_format( "Pocket %d:", pocket_number ) ) ); + } + if( data->rigid ) { + info.emplace_back( "DESCRIPTION", _( "This pocket is rigid." ) ); + } + if( data->min_item_volume > 0_ml ) { + info.emplace_back( "DESCRIPTION", + _( string_format( "Minimum volume of item allowed: %s", + vol_to_string( data->min_item_volume ) ) ) ); + } + info.emplace_back( "DESCRIPTION", + _( string_format( "Volume Capacity: %s", + vol_to_string( data->max_contains_volume ) ) ) ); + info.emplace_back( "DESCRIPTION", + _( string_format( "Weight Capacity: %s", + weight_to_string( data->max_contains_weight ) ) ) ); + + info.emplace_back( "DESCRIPTION", + _( string_format( "This pocket takes %d base moves to take an item out.", + data->moves ) ) ); + + if( data->watertight ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket can contain a liquid." ) ); + } + if( data->gastight ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket can contain a gas." ) ); + } + if( data->open_container ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket will spill if placed into another item or worn." ) ); + } + if( data->fire_protection ) { + info.emplace_back( "DESCRIPTION", + _( "This pocket protects its contents from fire." ) ); + } + if( data->spoil_multiplier != 1.0f ) { + info.emplace_back( "DESCRIPTION", + string_format( + _( "This pocket makes contained items spoil at %.0f%% their original rate." ), + data->spoil_multiplier * 100 ) ); + } + if( data->weight_multiplier != 1.0f ) { + info.emplace_back( "DESCRIPTION", + string_format( _( "Items in this pocket weigh %.0f%% their original weight." ), + data->weight_multiplier * 100 ) ); + } + } +} + +void item_pocket::contents_info( std::vector &info, int pocket_number, + bool disp_pocket_number ) const +{ + const std::string space = " "; + + insert_separation_line( info ); + if( disp_pocket_number ) { + info.emplace_back( "DESCRIPTION", _( string_format( "Pocket %d", pocket_number ) ) ); + } + if( contents.empty() ) { + info.emplace_back( "DESCRIPTION", _( "This pocket is empty." ) ); + return; + } + info.emplace_back( "DESCRIPTION", + _( string_format( "Volume: %s / %s", + vol_to_string( contains_volume() ), vol_to_string( data->max_contains_volume ) ) ) ); + info.emplace_back( "DESCRIPTION", + _( string_format( "Weight: %s / %s", + weight_to_string( contains_weight() ), weight_to_string( data->max_contains_weight ) ) ) ); + + bool contents_header = false; + for( const item &contents_item : contents ) { + if( !contents_item.type->mod ) { + if( !contents_header ) { + info.emplace_back( "DESCRIPTION", _( "Contents of this pocket:" ) ); + contents_header = true; + } else { + // Separate items with a blank line + info.emplace_back( "DESCRIPTION", space ); + } + + const translation &description = contents_item.type->description; + + if( contents_item.made_of_from_type( LIQUID ) ) { + units::volume contents_volume = contents_item.volume(); + int converted_volume_scale = 0; + const double converted_volume = + round_up( convert_volume( contents_volume.value(), + &converted_volume_scale ), 2 ); + info.emplace_back( "DESCRIPTION", contents_item.display_name() ); + iteminfo::flags f = iteminfo::no_newline; + if( converted_volume_scale != 0 ) { + f |= iteminfo::is_decimal; + } + info.emplace_back( "CONTAINER", description + space, + string_format( " %s", volume_units_abbr() ), f, + converted_volume ); + } else { + info.emplace_back( "DESCRIPTION", contents_item.display_name() ); + } + } + } +} + +ret_val item_pocket::can_contain( const item &it ) const +{ + // legacy container must be added to explicitly + if( type == pocket_type::LEGACY_CONTAINER ) { + return ret_val::make_failure( contain_code::ERR_LEGACY_CONTAINER ); + } + if( it.made_of( phase_id::LIQUID ) && !data->watertight ) { + return ret_val::make_failure( + contain_code::ERR_LIQUID, _( "can't contain liquid" ) ); + } + if( it.made_of( phase_id::GAS ) && !data->gastight ) { + return ret_val::make_failure( + contain_code::ERR_GAS, _( "can't contain gas" ) ); + } + if( it.volume() < data->min_item_volume ) { + return ret_val::make_failure( + contain_code::ERR_TOO_SMALL, _( "item is too small" ) ); + } + if( it.weight() > data->max_contains_weight ) { + return ret_val::make_failure( + contain_code::ERR_TOO_HEAVY, _( "item is too heavy" ) ); + } + if( it.weight() > remaining_weight() ) { + return ret_val::make_failure( + contain_code::ERR_CANNOT_SUPPORT, _( "pocket is holding too much weight" ) ); + } + if( !data->flag_restriction.empty() && !it.has_any_flag( data->flag_restriction ) ) { + return ret_val::make_failure( + contain_code::ERR_FLAG, _( "item does not have correct flag" ) ); + } + if( it.volume() > data->max_contains_volume ) { + return ret_val::make_failure( + contain_code::ERR_TOO_BIG, _( "item too big" ) ); + } + if( it.volume() > remaining_volume() ) { + return ret_val::make_failure( + contain_code::ERR_NO_SPACE, _( "not enough space" ) ); + } + return ret_val::make_success(); +} + +cata::optional item_pocket::remove_item( const item &it ) +{ + item ret( it ); + const size_t sz = contents.size(); + contents.remove_if( [&it]( const item & rhs ) { + return &rhs == ⁢ + } ); + if( sz == contents.size() ) { + return cata::nullopt; + } else { + return ret; + } +} + +cata::optional item_pocket::remove_item( const item_location &it ) +{ + if( !it ) { + return cata::nullopt; + } + return remove_item( *it ); +} + +bool item_pocket::spill_contents( const tripoint &pos ) +{ + for( item &it : contents ) { + g->m.add_item_or_charges( pos, it ); + } + + contents.clear(); + return true; +} + +void item_pocket::clear_items() +{ + contents.clear(); +} + +bool item_pocket::has_item( const item &it ) const +{ + return contents.end() != + std::find_if( contents.begin(), contents.end(), [&it]( const item & e ) { + return &it == &e; + } ); +} + +item *item_pocket::get_item_with( const std::function &filter ) +{ + for( item &it : contents ) { + if( filter( it ) ) { + return ⁢ + } + } + return nullptr; +} + +void item_pocket::remove_items_if( const std::function &filter ) +{ + contents.remove_if( filter ); +} + +void item_pocket::has_rotten_away( const tripoint &pnt ) +{ + for( auto it = contents.begin(); it != contents.end(); ) { + if( g->m.has_rotten_away( *it, pnt ) ) { + it = contents.erase( it ); + } else { + ++it; + } + } +} + +bool item_pocket::empty() const +{ + return contents.empty(); +} + +void item_pocket::add( const item &it ) +{ + contents.push_back( it ); +} + +std::list &item_pocket::edit_contents() +{ + return contents; +} + +ret_val item_pocket::insert_item( const item &it ) +{ + const ret_val ret = can_contain( it ); + if( ret.success() ) { + contents.push_back( it ); + } + return ret; +} + +int item_pocket::obtain_cost( const item &it ) const +{ + if( has_item( it ) ) { + return data->moves; + } + return 0; +} + +bool item_pocket::is_type( pocket_type ptype ) const +{ + return ptype == type; +} + +units::volume item_pocket::contains_volume() const +{ + units::volume vol = 0_ml; + for( const item &it : contents ) { + vol += it.volume(); + } + return vol; +} + +units::mass item_pocket::contains_weight() const +{ + units::mass weight = 0_gram; + for( const item &it : contents ) { + weight += it.weight(); + } + return weight; +} + +units::mass item_pocket::remaining_weight() const +{ + return data->max_contains_weight - contains_weight(); +} diff --git a/src/item_pocket.h b/src/item_pocket.h new file mode 100644 index 0000000000000..dd558bd20952b --- /dev/null +++ b/src/item_pocket.h @@ -0,0 +1,186 @@ +#pragma once +#ifndef ITEM_POCKET_H +#define ITEM_POCKET_H + +#include + +#include "enums.h" +#include "enum_traits.h" +#include "optional.h" +#include "type_id.h" +#include "ret_val.h" +#include "translations.h" +#include "units.h" +#include "visitable.h" + +class Character; +class item; +class item_location; +class player; +class pocket_data; + +struct iteminfo; +struct itype; +struct tripoint; + +using itype_id = std::string; + +class item_pocket +{ + public: + enum pocket_type { + // this is to aid the transition from the previous way item contents were handled. + // this will have the rules that previous contents would have + LEGACY_CONTAINER, + CONTAINER, + MAGAZINE, + LAST + }; + enum contain_code { + SUCCESS, + // legacy containers can't technically contain anything + ERR_LEGACY_CONTAINER, + // trying to put a liquid into a non-watertight container + ERR_LIQUID, + // trying to put a gas in a non-gastight container + ERR_GAS, + // trying to put an item that wouldn't fit if the container were empty + ERR_TOO_BIG, + // trying to put an item that wouldn't fit if the container were empty + ERR_TOO_HEAVY, + // trying to put an item that wouldn't fit if the container were empty + ERR_TOO_SMALL, + // pocket doesn't have sufficient space left + ERR_NO_SPACE, + // pocket doesn't have sufficient weight left + ERR_CANNOT_SUPPORT, + // requires a flag + ERR_FLAG + }; + + item_pocket() = default; + item_pocket( const pocket_data *data ) : data( data ) {} + + bool stacks_with( const item_pocket &rhs ) const; + + bool is_type( pocket_type ptype ) const; + bool empty() const; + + item &back(); + const item &back() const; + item &front(); + const item &front() const; + size_t size() const; + void pop_back(); + + ret_val can_contain( const item &it ) const; + + // combined volume of contained items + units::volume contains_volume() const; + units::volume remaining_volume() const; + units::volume volume_capacity() const; + // combined weight of contained items + units::mass contains_weight() const; + units::mass remaining_weight() const; + + units::volume item_size_modifier() const; + units::mass item_weight_modifier() const; + + item *magazine_current(); + void casings_handle( const std::function &func ); + bool use_amount( const itype_id &it, int &quantity, std::list &used ); + bool will_explode_in_a_fire() const; + bool detonate( const tripoint &p, std::vector &drops ); + bool process( const itype &type, player *carrier, const tripoint &pos, bool activate, + float insulation, temperature_flag flag ); + bool legacy_unload( player *guy, bool &changed ); + void remove_all_ammo( Character &guy ); + void remove_all_mods( Character &guy ); + + // removes and returns the item from the pocket. + cata::optional remove_item( const item &it ); + cata::optional remove_item( const item_location &it ); + bool spill_contents( const tripoint &pos ); + void clear_items(); + bool has_item( const item &it ) const; + item *get_item_with( const std::function &filter ); + void remove_items_if( const std::function &filter ); + void has_rotten_away( const tripoint &pnt ); + + // tries to put an item in the pocket. returns false if failure + ret_val insert_item( const item &it ); + void add( const item &it ); + + // only available to help with migration from previous usage of std::list + std::list &edit_contents(); + + // cost of getting an item from this pocket + // @TODO: make move cost vary based on other contained items + int obtain_cost( const item &it ) const; + // @relates visitable + VisitResponse visit_contents( const std::function &func, + item *parent = nullptr ); + + void general_info( std::vector &info, int pocket_number, bool disp_pocket_number ) const; + void contents_info( std::vector &info, int pocket_number, bool disp_pocket_number ) const; + + void serialize( JsonOut &json ) const; + void deserialize( JsonIn &jsin ); + + bool operator==( const item_pocket &rhs ) const; + + bool was_loaded; + private: + const pocket_data *data = nullptr; + // the items inside the pocket + std::list contents; +}; + +class pocket_data +{ + public: + bool was_loaded; + + item_pocket::pocket_type type = item_pocket::pocket_type::LEGACY_CONTAINER; + // max volume of stuff the pocket can hold + units::volume max_contains_volume = 0_ml; + // min volume of item that can be contained, otherwise it spills + units::volume min_item_volume = 0_ml; + // max weight of stuff the pocket can hold + units::mass max_contains_weight = 0_gram; + // multiplier for spoilage rate of contained items + float spoil_multiplier = 1.0f; + // items' weight in this pocket are modified by this number + float weight_multiplier = 1.0f; + // base time it takes to pull an item out of the pocket + int moves = 100; + // protects contents from exploding in a fire + bool fire_protection = false; + // can hold liquids + bool watertight = false; + // can hold gas + bool gastight = false; + // the pocket will spill its contents if placed in another container + bool open_container = false; + // allows only items with the appropriate flags to be stored inside + // empty means no restriction + std::vector flag_restriction; + // container's size and encumbrance does not change based on contents. + bool rigid = false; + + bool operator==( const pocket_data &rhs ) const; + + void load( const JsonObject &jo ); +}; + +template<> +struct enum_traits { + static constexpr auto last = item_pocket::pocket_type::LAST; +}; + +template<> +struct ret_val::default_success + : public std::integral_constant {}; + +#endif diff --git a/src/map.h b/src/map.h index e295c04501ccd..67f23bd01a85c 100644 --- a/src/map.h +++ b/src/map.h @@ -1438,6 +1438,14 @@ class map * If false, monsters are not spawned in view of player character. */ void spawn_monsters( bool ignore_sight ); + /** + * Whether the item has to be removed as it has rotten away completely. + * @param itm Item to check for rotting + * @param pnt The *absolute* position of the item in the world (not just on this map!), + * used for rot calculation. + * @return true if the item has rotten away and should be removed, false otherwise. + */ + bool has_rotten_away( item &itm, const tripoint &pnt ) const; private: // Helper #1 - spawns monsters on one submap void spawn_monsters_submap( const tripoint &gp, bool ignore_sight ); @@ -1471,14 +1479,6 @@ class map * Hacks in missing roofs. Should be removed when 3D mapgen is done. */ void add_roofs( const tripoint &grid ); - /** - * Whether the item has to be removed as it has rotten away completely. - * @param itm Item to check for rotting - * @param pnt The *absolute* position of the item in the world (not just on this map!), - * used for rot calculation. - * @return true if the item has rotten away and should be removed, false otherwise. - */ - bool has_rotten_away( item &itm, const tripoint &pnt ) const; /** * Go through the list of items, update their rotten status and remove items * that have rotten away completely.