517 lines
22 KiB
C++
517 lines
22 KiB
C++
#include <algorithm>
|
|
#include <functional>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <vector>
|
|
|
|
#include "calendar.h"
|
|
#include "cata_utility.h"
|
|
#include "cata_catch.h"
|
|
#include "character.h"
|
|
#include "inventory.h"
|
|
#include "item.h"
|
|
#include "itype.h"
|
|
#include "map.h"
|
|
#include "map_selector.h"
|
|
#include "optional.h"
|
|
#include "pimpl.h"
|
|
#include "player_helpers.h"
|
|
#include "point.h"
|
|
#include "rng.h"
|
|
#include "type_id.h"
|
|
#include "units.h"
|
|
#include "vehicle.h"
|
|
#include "vehicle_selector.h"
|
|
#include "visitable.h"
|
|
#include "vpart_position.h"
|
|
|
|
static const itype_id itype_bone( "bone" );
|
|
static const itype_id itype_bottle_plastic( "bottle_plastic" );
|
|
static const itype_id itype_flask_hip( "flask_hip" );
|
|
static const itype_id itype_water( "water" );
|
|
|
|
static const vproto_id vehicle_prototype_shopping_cart( "shopping_cart" );
|
|
|
|
template <typename T>
|
|
static int count_items( const T &src, const itype_id &id )
|
|
{
|
|
int n = 0;
|
|
src.visit_items( [&n, &id]( const item * e, item * ) {
|
|
n += ( e->typeId() == id );
|
|
return VisitResponse::NEXT;
|
|
} );
|
|
return n;
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-function-size)
|
|
TEST_CASE( "visitable_remove", "[visitable]" )
|
|
{
|
|
const itype_id liquid_id = itype_water;
|
|
const itype_id container_id = itype_bottle_plastic;
|
|
const itype_id worn_id = itype_flask_hip;
|
|
const int count = 5;
|
|
|
|
REQUIRE( item( container_id ).is_container() );
|
|
REQUIRE( item( worn_id ).is_container() );
|
|
|
|
clear_avatar();
|
|
Character &p = get_player_character();
|
|
p.worn.wear_item( p, item( "backpack" ), false, false );
|
|
p.wear_item( item( "backpack" ) ); // so we don't drop anything
|
|
map &here = get_map();
|
|
|
|
// check if all tiles within radius are loaded within current submap and passable
|
|
const auto suitable = [&here]( const tripoint & pos, const int radius ) {
|
|
std::vector<tripoint> tiles = closest_points_first( pos, radius );
|
|
return std::all_of( tiles.begin(), tiles.end(), [&here]( const tripoint & e ) {
|
|
if( !here.inbounds( e ) ) {
|
|
return false;
|
|
}
|
|
if( const optional_vpart_position vp = here.veh_at( e ) ) {
|
|
here.destroy_vehicle( &vp->vehicle() );
|
|
}
|
|
here.i_clear( e );
|
|
return here.passable( e );
|
|
} );
|
|
};
|
|
|
|
// move player randomly until we find a suitable position
|
|
constexpr int num_trials = 100;
|
|
for( int i = 0; i < num_trials && !suitable( p.pos(), 1 ); ++i ) {
|
|
CHECK( !p.in_vehicle );
|
|
p.setpos( random_entry( closest_points_first( p.pos(), 1 ) ) );
|
|
}
|
|
REQUIRE( suitable( p.pos(), 1 ) );
|
|
|
|
item temp_liquid( liquid_id );
|
|
item obj = temp_liquid.in_container( temp_liquid.type->default_container.value_or( "null" ) );
|
|
REQUIRE( obj.num_item_stacks() == 1 );
|
|
const auto has_liquid_filter = [&liquid_id]( const item & it ) {
|
|
return it.typeId() == liquid_id;
|
|
};
|
|
REQUIRE( obj.has_item_with( has_liquid_filter ) );
|
|
|
|
GIVEN( "A player with several bottles of water" ) {
|
|
for( int i = 0; i != count; ++i ) {
|
|
p.i_add( obj );
|
|
}
|
|
REQUIRE( count_items( p, container_id ) == count );
|
|
REQUIRE( count_items( p, liquid_id ) == count );
|
|
|
|
WHEN( "all the bottles are removed" ) {
|
|
std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} );
|
|
|
|
THEN( "no bottles remain in the players possession" ) {
|
|
REQUIRE( count_items( p, container_id ) == 0 );
|
|
}
|
|
THEN( "no water remain in the players possession" ) {
|
|
REQUIRE( count_items( p, liquid_id ) == 0 );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == count );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contain water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
WHEN( "one of the bottles is removed" ) {
|
|
std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
}, 1 );
|
|
|
|
THEN( "there is one less bottle in the players possession" ) {
|
|
REQUIRE( count_items( p, container_id ) == count - 1 );
|
|
}
|
|
THEN( "there is one less water in the players possession" ) {
|
|
REQUIRE( count_items( p, liquid_id ) == count - 1 );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == 1 );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contained water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
WHEN( "one of the bottles is wielded" ) {
|
|
p.wield( p.worn.front().legacy_front() );
|
|
REQUIRE( p.get_wielded_item()->typeId() == container_id );
|
|
REQUIRE( count_items( p, container_id ) == count );
|
|
REQUIRE( count_items( p, liquid_id ) == count );
|
|
|
|
AND_WHEN( "all the bottles are removed" ) {
|
|
std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} );
|
|
|
|
THEN( "no bottles remain in the players possession" ) {
|
|
REQUIRE( count_items( p, container_id ) == 0 );
|
|
}
|
|
THEN( "no water remain in the players possession" ) {
|
|
REQUIRE( count_items( p, liquid_id ) == 0 );
|
|
}
|
|
THEN( "there is no currently wielded item" ) {
|
|
REQUIRE( !p.get_wielded_item() );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == count );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contain water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
AND_WHEN( "all but one of the bottles is removed" ) {
|
|
std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
}, count - 1 );
|
|
|
|
THEN( "there is only one bottle remaining in the players possession" ) {
|
|
REQUIRE( count_items( p, container_id ) == 1 );
|
|
AND_THEN( "the remaining bottle is currently wielded" ) {
|
|
REQUIRE( p.get_wielded_item()->typeId() == container_id );
|
|
|
|
AND_THEN( "the remaining water is contained by the currently wielded bottle" ) {
|
|
REQUIRE( p.get_wielded_item()->num_item_stacks() == 1 );
|
|
REQUIRE( p.get_wielded_item()->has_item_with( has_liquid_filter ) );
|
|
}
|
|
}
|
|
}
|
|
THEN( "there is only one water remaining in the players possession" ) {
|
|
REQUIRE( count_items( p, liquid_id ) == 1 );
|
|
}
|
|
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == count - 1 );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contained water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WHEN( "a hip flask containing water is wielded" ) {
|
|
item obj( worn_id );
|
|
item liquid( liquid_id, calendar::turn );
|
|
liquid.charges -= obj.fill_with( liquid, liquid.charges );
|
|
p.wield( obj );
|
|
|
|
REQUIRE( count_items( p, container_id ) == count );
|
|
REQUIRE( count_items( p, liquid_id ) == count + 1 );
|
|
|
|
AND_WHEN( "all but one of the water is removed" ) {
|
|
std::list<item> del = p.remove_items_with( [&liquid_id]( const item & e ) {
|
|
return e.typeId() == liquid_id;
|
|
}, count );
|
|
|
|
THEN( "all of the bottles remain in the players possession" ) {
|
|
REQUIRE( count_items( p, container_id ) == 5 );
|
|
AND_THEN( "all of the bottles are now empty" ) {
|
|
REQUIRE( p.visit_items( [&container_id]( const item * e, item * ) {
|
|
return ( e->typeId() != container_id || e->empty() ) ?
|
|
VisitResponse::NEXT : VisitResponse::ABORT;
|
|
} ) != VisitResponse::ABORT );
|
|
}
|
|
}
|
|
THEN( "the hip flask remains in the players possession" ) {
|
|
auto found = p.items_with( [&worn_id]( const item & e ) {
|
|
return e.typeId() == worn_id;
|
|
} );
|
|
REQUIRE( found.size() == 1 );
|
|
AND_THEN( "the hip flask is still worn" ) {
|
|
REQUIRE( p.is_wielding( *found[0] ) );
|
|
|
|
AND_THEN( "the hip flask contains water" ) {
|
|
REQUIRE( found[0]->num_item_stacks() == 1 );
|
|
REQUIRE( found[0]->has_item_with( has_liquid_filter ) );
|
|
}
|
|
}
|
|
}
|
|
THEN( "there is only one water remaining in the players possession" ) {
|
|
REQUIRE( count_items( p, liquid_id ) == 1 );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == count );
|
|
|
|
AND_THEN( "the removed items were all water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) {
|
|
return e.typeId() == liquid_id;
|
|
} ) );
|
|
}
|
|
}
|
|
|
|
AND_WHEN( "the final water is removed" ) {
|
|
std::list<item> del = p.remove_items_with( [&liquid_id]( const item & e ) {
|
|
return e.typeId() == liquid_id;
|
|
}, 1 );
|
|
|
|
THEN( "no water remain in the players possession" ) {
|
|
REQUIRE( count_items( p, liquid_id ) == 0 );
|
|
}
|
|
|
|
THEN( "the hip flask remains in the players possession" ) {
|
|
auto found = p.items_with( [&worn_id]( const item & e ) {
|
|
return e.typeId() == worn_id;
|
|
} );
|
|
REQUIRE( found.size() == 1 );
|
|
AND_THEN( "the hip flask is worn" ) {
|
|
REQUIRE( p.is_wielding( *found[0] ) );
|
|
|
|
AND_THEN( "the hip flask is empty" ) {
|
|
REQUIRE( found[0]->empty() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GIVEN( "A player surrounded by several bottles of water" ) {
|
|
std::vector<tripoint> tiles = closest_points_first( p.pos(), 1 );
|
|
tiles.erase( tiles.begin() ); // player tile
|
|
|
|
int our = 0; // bottles placed on player tile
|
|
int adj = 0; // bottles placed on adjacent tiles
|
|
|
|
for( int i = 0; i != count; ++i ) {
|
|
if( i == 0 || tiles.empty() ) {
|
|
// always place at least one bottle on player tile
|
|
our++;
|
|
here.add_item( p.pos(), obj );
|
|
} else {
|
|
// randomly place bottles on adjacent tiles
|
|
adj++;
|
|
here.add_item( random_entry( tiles ), obj );
|
|
}
|
|
}
|
|
REQUIRE( our + adj == count );
|
|
|
|
map_selector sel( p.pos(), 1 );
|
|
map_cursor cur( p.pos() );
|
|
|
|
REQUIRE( count_items( sel, container_id ) == count );
|
|
REQUIRE( count_items( sel, liquid_id ) == count );
|
|
|
|
REQUIRE( count_items( cur, container_id ) == our );
|
|
REQUIRE( count_items( cur, liquid_id ) == our );
|
|
|
|
WHEN( "all the bottles are removed" ) {
|
|
std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} );
|
|
|
|
THEN( "no bottles remain on the map" ) {
|
|
REQUIRE( count_items( sel, container_id ) == 0 );
|
|
}
|
|
THEN( "no water remains on the map" ) {
|
|
REQUIRE( count_items( sel, liquid_id ) == 0 );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == count );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contain water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
WHEN( "one of the bottles is removed" ) {
|
|
std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
}, 1 );
|
|
|
|
THEN( "there is one less bottle on the map" ) {
|
|
REQUIRE( count_items( sel, container_id ) == count - 1 );
|
|
}
|
|
THEN( "there is one less water on the map" ) {
|
|
REQUIRE( count_items( sel, liquid_id ) == count - 1 );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == 1 );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contained water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
WHEN( "all of the bottles on the player tile are removed" ) {
|
|
std::list<item> del = cur.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
}, our );
|
|
|
|
THEN( "no bottles remain on the player tile" ) {
|
|
REQUIRE( count_items( cur, container_id ) == 0 );
|
|
}
|
|
THEN( "no water remains on the player tile" ) {
|
|
REQUIRE( count_items( cur, liquid_id ) == 0 );
|
|
}
|
|
THEN( "the correct amount of bottles remains on the map" ) {
|
|
REQUIRE( count_items( sel, container_id ) == count - our );
|
|
}
|
|
THEN( "there correct amount of water remains on the map" ) {
|
|
REQUIRE( count_items( sel, liquid_id ) == count - our );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( static_cast<int>( del.size() ) == our );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contained water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
GIVEN( "An adjacent vehicle contains several bottles of water" ) {
|
|
std::vector<tripoint> tiles = closest_points_first( p.pos(), 1 );
|
|
tiles.erase( tiles.begin() ); // player tile
|
|
tripoint veh = random_entry( tiles );
|
|
REQUIRE( here.add_vehicle( vehicle_prototype_shopping_cart, veh, 0_degrees, 0, 0 ) );
|
|
|
|
REQUIRE( std::count_if( tiles.begin(), tiles.end(), [&here]( const tripoint & e ) {
|
|
return static_cast<bool>( here.veh_at( e ) );
|
|
} ) == 1 );
|
|
|
|
const cata::optional<vpart_reference> vp = here.veh_at( veh ).part_with_feature( "CARGO", true );
|
|
REQUIRE( vp );
|
|
vehicle *const v = &vp->vehicle();
|
|
const int part = vp->part_index();
|
|
REQUIRE( part >= 0 );
|
|
// Empty the vehicle of any cargo.
|
|
v->get_items( part ).clear();
|
|
for( int i = 0; i != count; ++i ) {
|
|
v->add_item( part, obj );
|
|
}
|
|
|
|
vehicle_selector sel( p.pos(), 1 );
|
|
|
|
REQUIRE( count_items( sel, container_id ) == count );
|
|
REQUIRE( count_items( sel, liquid_id ) == count );
|
|
|
|
WHEN( "all the bottles are removed" ) {
|
|
std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} );
|
|
|
|
THEN( "no bottles remain within the vehicle" ) {
|
|
REQUIRE( count_items( sel, container_id ) == 0 );
|
|
}
|
|
THEN( "no water remains within the vehicle" ) {
|
|
REQUIRE( count_items( sel, liquid_id ) == 0 );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == count );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contain water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
WHEN( "one of the bottles is removed" ) {
|
|
std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
}, 1 );
|
|
|
|
THEN( "there is one less bottle within the vehicle" ) {
|
|
REQUIRE( count_items( sel, container_id ) == count - 1 );
|
|
}
|
|
THEN( "there is one less water within the vehicle" ) {
|
|
REQUIRE( count_items( sel, liquid_id ) == count - 1 );
|
|
}
|
|
THEN( "the correct number of items were removed" ) {
|
|
REQUIRE( del.size() == 1 );
|
|
|
|
AND_THEN( "the removed items were all bottles" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
|
|
return e.typeId() == container_id;
|
|
} ) );
|
|
}
|
|
AND_THEN( "the removed items all contained water" ) {
|
|
CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
|
|
return e.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE( "inventory_remove_invalidates_binning_cache", "[visitable][inventory]" )
|
|
{
|
|
inventory inv;
|
|
std::list<item> items = { item( "bone" ) };
|
|
inv += items;
|
|
CHECK( inv.charges_of( itype_bone ) == 1 );
|
|
inv.remove_items_with( return_true<item> );
|
|
CHECK( inv.size() == 0 );
|
|
// The following used to be a heap use-after-free due to a caching bug.
|
|
// Now should be safe.
|
|
CHECK( inv.charges_of( itype_bone ) == 0 );
|
|
}
|