diff --git a/src/game.cpp b/src/game.cpp index 2ba4ebb564305..511f2ea8f8d00 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -8569,6 +8569,11 @@ void game::wield( item_location &loc ) loc.remove_item(); if( !u.wield( to_wield ) ) { switch( location_type ) { + case item_location::type::container: + // this will not cause things to spill, as it is inside another item + loc = loc.obtain( g->u ); + wield( loc ); + break; case item_location::type::character: if( worn_index != INT_MIN ) { auto it = u.worn.begin(); diff --git a/src/handle_liquid.cpp b/src/handle_liquid.cpp index b038c0b3939cf..7aaae6912d8c6 100644 --- a/src/handle_liquid.cpp +++ b/src/handle_liquid.cpp @@ -349,6 +349,7 @@ static bool perform_liquid_transfer( item &liquid, const tripoint *const source_ case item_location::type::vehicle: g->m.veh_at( target.item_loc.position() )->vehicle().make_active( target.item_loc ); break; + case item_location::type::container: case item_location::type::character: case item_location::type::invalid: break; diff --git a/src/item_contents.cpp b/src/item_contents.cpp index 82f7884cd34ff..985d51678c670 100644 --- a/src/item_contents.cpp +++ b/src/item_contents.cpp @@ -138,6 +138,28 @@ std::list item_contents::all_items_top() const return ret; } +std::list item_contents::all_items_ptr() +{ + std::list ret; + for( item &it : items ) { + ret.push_back( &it ); + std::list inside = it.contents.all_items_ptr(); + ret.insert( ret.end(), inside.begin(), inside.end() ); + } + return ret; +} + +std::list item_contents::all_items_ptr() const +{ + std::list ret; + for( const item &it : items ) { + ret.push_back( &it ); + std::list inside = it.contents.all_items_ptr(); + ret.insert( ret.end(), inside.begin(), inside.end() ); + } + return ret; +} + std::vector item_contents::gunmods() { std::vector res; diff --git a/src/item_contents.h b/src/item_contents.h index 781008462a14f..3b83b001a2004 100644 --- a/src/item_contents.h +++ b/src/item_contents.h @@ -29,6 +29,11 @@ class item_contents /** returns a list of pointers to all top-level items */ std::list all_items_top() const; + // returns a list of pointers to all items inside recursively + std::list all_items_ptr(); + // returns a list of pointers to all items inside recursively + std::list all_items_ptr() const; + /** gets all gunmods in the item */ std::vector gunmods(); /** gets all gunmods in the item */ diff --git a/src/item_location.cpp b/src/item_location.cpp index e8d1c4f7e8a35..e8028f775aa2f 100644 --- a/src/item_location.cpp +++ b/src/item_location.cpp @@ -64,6 +64,7 @@ class item_location::impl class item_on_map; class item_on_person; class item_on_vehicle; + class item_in_container; impl() = default; impl( item *i ) : what( i->get_safe_reference() ), needs_unpacking( false ) {} @@ -72,6 +73,9 @@ class item_location::impl virtual ~impl() = default; virtual type where() const = 0; + virtual item_location parent_item() const { + return item_location(); + } virtual tripoint position() const = 0; virtual std::string describe( const Character * ) const = 0; virtual item_location obtain( Character &, int ) = 0; @@ -478,6 +482,97 @@ class item_location::impl::item_on_vehicle : public item_location::impl } }; +class item_location::impl::item_in_container : public item_location::impl +{ + private: + item_location container; + + // figures out the index for the item, which is where it is in the total list of contents + // note: could be a better way of handling this? + int calc_index() const { + int idx = 0; + for( const item *it : container->contents.all_items_top() ) { + if( target() == it ) { + return idx; + } + idx++; + } + if( container->contents.empty() ) { + return -1; + } + return idx; + } + public: + item_location parent_item() const override { + return container; + } + + item_in_container( const item_location &container, item *which ) : + impl( which ), container( container ) {} + + void serialize( JsonOut &js ) const override { + js.start_object(); + js.member( "idx", calc_index() ); + js.member( "type", "in_container" ); + js.member( "parent", container ); + js.end_object(); + } + + item *unpack( int idx ) const override { + if( idx < 0 || static_cast( idx ) >= target()->contents.num_item_stacks() ) { + return nullptr; + } + std::list all_items = container->contents.all_items_ptr(); + auto iter = all_items.begin(); + std::advance( iter, idx ); + if( iter != all_items.end() ) { + return const_cast( *iter ); + } else { + return nullptr; + } + } + + std::string describe( const Character * ) const override { + if( !target() ) { + return std::string(); + } + return string_format( _( "inside %s" ), container->tname() ); + } + + type where() const override { + return type::container; + } + + tripoint position() const override { + return container.position(); + } + + void remove_item() override { + container->remove_item( *target() ); + } + + item_location obtain( Character &ch, int qty ) override { + ch.mod_moves( -obtain_cost( ch, qty ) ); + + item obj = target()->split( qty ); + if( !obj.is_null() ) { + return item_location( ch, &ch.i_add( obj, should_stack ) ); + } else { + item *inv = &ch.i_add( *target(), should_stack ); + remove_item(); + return item_location( ch, inv ); + } + } + + int obtain_cost( const Character &ch, int qty ) const override { + if( !target() ) { + return 0; + } + // a temporary measure before pockets + return INVENTORY_HANDLING_PENALTY + container.obtain_cost( ch, qty ); + } +}; + const item_location item_location::nowhere; item_location::item_location() @@ -492,6 +587,9 @@ item_location::item_location( Character &ch, item *which ) item_location::item_location( const vehicle_cursor &vc, item *which ) : ptr( new impl::item_on_vehicle( vc, which ) ) {} +item_location::item_location( const item_location &container, item *which ) + : ptr( new impl::item_in_container( container, which ) ) {} + bool item_location::operator==( const item_location &rhs ) const { return ptr->target() == rhs.ptr->target(); @@ -563,7 +661,23 @@ void item_location::deserialize( JsonIn &js ) if( veh && part >= 0 && part < static_cast( veh->parts.size() ) ) { ptr.reset( new impl::item_on_vehicle( vehicle_cursor( *veh, part ), idx ) ); } + } else if( type == "in_container" ) { + item_location parent; + obj.read( "parent", parent ); + const std::list parent_contents = parent->contents.all_items_top(); + auto iter = parent_contents.begin(); + std::advance( iter, idx ); + ptr.reset( new impl::item_in_container( parent, *iter ) ); + } +} + +item_location item_location::parent_item() const +{ + if( where() == type::container ) { + return ptr->parent_item(); } + debugmsg( "this item location type has no parent" ); + return item_location::nowhere; } item_location::type item_location::where() const diff --git a/src/item_location.h b/src/item_location.h index 3dee2a76aee53..92ded34492bfb 100644 --- a/src/item_location.h +++ b/src/item_location.h @@ -27,7 +27,8 @@ class item_location invalid = 0, character = 1, map = 2, - vehicle = 3 + vehicle = 3, + container = 4 }; item_location(); @@ -37,6 +38,7 @@ class item_location item_location( Character &ch, item *which ); item_location( const map_cursor &mc, item *which ); item_location( const vehicle_cursor &vc, item *which ); + item_location( const item_location &container, item *which ); void serialize( JsonOut &js ) const; void deserialize( JsonIn &js ); @@ -85,6 +87,9 @@ class item_location void set_should_stack( bool should_stack ) const; + /** returns the parent item, or an invalid location if it has no parent */ + item_location parent_item() const; + private: class impl; diff --git a/tests/item_location_test.cpp b/tests/item_location_test.cpp index d302f3492c1a3..8ea770a3c9074 100644 --- a/tests/item_location_test.cpp +++ b/tests/item_location_test.cpp @@ -2,6 +2,7 @@ #include #include +#include "avatar.h" #include "catch/catch.hpp" #include "game.h" #include "item.h" @@ -64,3 +65,28 @@ TEST_CASE( "item_location_doesnt_return_stale_map_item", "[item][item_location]" m.add_item( pos, item( "jeans" ) ); CHECK( !item_loc ); } + +TEST_CASE( "item_in_container", "[item][item_location]" ) +{ + avatar &dummy = g->u; + item &backpack = dummy.i_add( item( "backpack" ) ); + item jeans( "jeans" ); + + REQUIRE( dummy.has_item( backpack ) ); + + backpack.put_in( jeans ); + + item_location backpack_loc( dummy, & **dummy.wear( backpack ) ); + + REQUIRE( dummy.has_item( *backpack_loc ) ); + + item_location jeans_loc( backpack_loc, &jeans ); + + REQUIRE( backpack_loc.where() == item_location::type::character ); + REQUIRE( jeans_loc.where() == item_location::type::container ); + + CHECK( backpack_loc.obtain_cost( dummy ) + INVENTORY_HANDLING_PENALTY == jeans_loc.obtain_cost( + dummy ) ); + + CHECK( jeans_loc.parent_item() == backpack_loc ); +}