1 #include <algorithm>
2 #include <functional>
3 #include <list>
4 #include <memory>
5 #include <new>
6 #include <vector>
7
8 #include "calendar.h"
9 #include "cata_utility.h"
10 #include "catch/catch.hpp"
11 #include "character.h"
12 #include "inventory.h"
13 #include "item.h"
14 #include "item_contents.h"
15 #include "itype.h"
16 #include "map.h"
17 #include "map_selector.h"
18 #include "optional.h"
19 #include "pimpl.h"
20 #include "point.h"
21 #include "rng.h"
22 #include "type_id.h"
23 #include "units.h"
24 #include "vehicle.h"
25 #include "vehicle_selector.h"
26 #include "visitable.h"
27 #include "vpart_position.h"
28
29 template <typename T>
count_items(const T & src,const itype_id & id)30 static int count_items( const T &src, const itype_id &id )
31 {
32 int n = 0;
33 src.visit_items( [&n, &id]( const item * e, item * ) {
34 n += ( e->typeId() == id );
35 return VisitResponse::NEXT;
36 } );
37 return n;
38 }
39
40 TEST_CASE( "visitable_remove", "[visitable]" )
41 {
42 const itype_id liquid_id( "water" );
43 const itype_id container_id( "bottle_plastic" );
44 const itype_id worn_id( "flask_hip" );
45 const int count = 5;
46
47 REQUIRE( item( container_id ).is_container() );
48 REQUIRE( item( worn_id ).is_container() );
49
50 Character &p = get_player_character();
51 p.worn.clear();
52 p.worn.push_back( item( "backpack" ) );
53 p.inv->clear();
54 p.remove_weapon();
55 p.wear_item( item( "backpack" ) ); // so we don't drop anything
56 map &here = get_map();
57
58 // check if all tiles within radius are loaded within current submap and passable
__anon0c438c330202( const tripoint & pos, const int radius ) 59 const auto suitable = [&here]( const tripoint & pos, const int radius ) {
60 std::vector<tripoint> tiles = closest_points_first( pos, radius );
61 return std::all_of( tiles.begin(), tiles.end(), [&here]( const tripoint & e ) {
62 if( !here.inbounds( e ) ) {
63 return false;
64 }
65 if( const optional_vpart_position vp = here.veh_at( e ) ) {
66 here.destroy_vehicle( &vp->vehicle() );
67 }
68 here.i_clear( e );
69 return here.passable( e );
70 } );
71 };
72
73 // Move to ground level to avoid weirdnesses around being underground.
74 p.setz( 0 );
75 // move player randomly until we find a suitable position
76 while( !suitable( p.pos(), 1 ) ) {
77 CHECK( !p.in_vehicle );
78 p.setpos( random_entry( closest_points_first( p.pos(), 1 ) ) );
79 }
80
81 item temp_liquid( liquid_id );
82 item obj = temp_liquid.in_container( temp_liquid.type->default_container.value_or( "null" ) );
83 REQUIRE( obj.contents.num_item_stacks() == 1 );
__anon0c438c330402( const item & it ) 84 const auto has_liquid_filter = [&liquid_id]( const item & it ) {
85 return it.typeId() == liquid_id;
86 };
87 REQUIRE( obj.has_item_with( has_liquid_filter ) );
88
89 GIVEN( "A player with several bottles of water" ) {
90 for( int i = 0; i != count; ++i ) {
91 p.i_add( obj );
92 }
93 REQUIRE( count_items( p, container_id ) == count );
94 REQUIRE( count_items( p, liquid_id ) == count );
95
96 WHEN( "all the bottles are removed" ) {
__anon0c438c330502( const item & e ) 97 std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
98 return e.typeId() == container_id;
99 } );
100
101 THEN( "no bottles remain in the players possession" ) {
102 REQUIRE( count_items( p, container_id ) == 0 );
103 }
104 THEN( "no water remain in the players possession" ) {
105 REQUIRE( count_items( p, liquid_id ) == 0 );
106 }
107 THEN( "the correct number of items were removed" ) {
108 REQUIRE( del.size() == count );
109
110 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c330602( const item & e ) 111 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
112 return e.typeId() == container_id;
113 } ) );
114 }
115 AND_THEN( "the removed items all contain water" ) {
__anon0c438c330702( const item & e ) 116 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
117 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
118 } ) );
119 }
120 }
121 }
122
123 WHEN( "one of the bottles is removed" ) {
__anon0c438c330802( const item & e ) 124 std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
125 return e.typeId() == container_id;
126 }, 1 );
127
128 THEN( "there is one less bottle in the players possession" ) {
129 REQUIRE( count_items( p, container_id ) == count - 1 );
130 }
131 THEN( "there is one less water in the players possession" ) {
132 REQUIRE( count_items( p, liquid_id ) == count - 1 );
133 }
134 THEN( "the correct number of items were removed" ) {
135 REQUIRE( del.size() == 1 );
136
137 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c330902( const item & e ) 138 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
139 return e.typeId() == container_id;
140 } ) );
141 }
142 AND_THEN( "the removed items all contained water" ) {
__anon0c438c330a02( const item & e ) 143 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
144 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
145 } ) );
146 }
147 }
148 }
149
150 WHEN( "one of the bottles is wielded" ) {
151 p.wield( p.worn.front().contents.legacy_front() );
152 REQUIRE( p.weapon.typeId() == container_id );
153 REQUIRE( count_items( p, container_id ) == count );
154 REQUIRE( count_items( p, liquid_id ) == count );
155
156 AND_WHEN( "all the bottles are removed" ) {
__anon0c438c330b02( const item & e ) 157 std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
158 return e.typeId() == container_id;
159 } );
160
161 THEN( "no bottles remain in the players possession" ) {
162 REQUIRE( count_items( p, container_id ) == 0 );
163 }
164 THEN( "no water remain in the players possession" ) {
165 REQUIRE( count_items( p, liquid_id ) == 0 );
166 }
167 THEN( "there is no currently wielded item" ) {
168 REQUIRE( p.weapon.is_null() );
169 }
170 THEN( "the correct number of items were removed" ) {
171 REQUIRE( del.size() == count );
172
173 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c330c02( const item & e ) 174 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
175 return e.typeId() == container_id;
176 } ) );
177 }
178 AND_THEN( "the removed items all contain water" ) {
__anon0c438c330d02( const item & e ) 179 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
180 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
181 } ) );
182 }
183 }
184 }
185
186 AND_WHEN( "all but one of the bottles is removed" ) {
__anon0c438c330e02( const item & e ) 187 std::list<item> del = p.remove_items_with( [&container_id]( const item & e ) {
188 return e.typeId() == container_id;
189 }, count - 1 );
190
191 THEN( "there is only one bottle remaining in the players possession" ) {
192 REQUIRE( count_items( p, container_id ) == 1 );
193 AND_THEN( "the remaining bottle is currently wielded" ) {
194 REQUIRE( p.weapon.typeId() == container_id );
195
196 AND_THEN( "the remaining water is contained by the currently wielded bottle" ) {
197 REQUIRE( p.weapon.contents.num_item_stacks() == 1 );
198 REQUIRE( p.weapon.has_item_with( has_liquid_filter ) );
199 }
200 }
201 }
202 THEN( "there is only one water remaining in the players possession" ) {
203 REQUIRE( count_items( p, liquid_id ) == 1 );
204 }
205
206 THEN( "the correct number of items were removed" ) {
207 REQUIRE( del.size() == count - 1 );
208
209 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c330f02( const item & e ) 210 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
211 return e.typeId() == container_id;
212 } ) );
213 }
214 AND_THEN( "the removed items all contained water" ) {
__anon0c438c331002( const item & e ) 215 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
216 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
217 } ) );
218 }
219 }
220 }
221 }
222
223 WHEN( "a hip flask containing water is wielded" ) {
224 item obj( worn_id );
225 item liquid( liquid_id, calendar::turn );
226 liquid.charges -= obj.fill_with( liquid, liquid.charges );
227 p.wield( obj );
228
229 REQUIRE( count_items( p, container_id ) == count );
230 REQUIRE( count_items( p, liquid_id ) == count + 1 );
231
232 AND_WHEN( "all but one of the water is removed" ) {
__anon0c438c331102( const item & e ) 233 std::list<item> del = p.remove_items_with( [&liquid_id]( const item & e ) {
234 return e.typeId() == liquid_id;
235 }, count );
236
237 THEN( "all of the bottles remain in the players possession" ) {
238 REQUIRE( count_items( p, container_id ) == 5 );
239 AND_THEN( "all of the bottles are now empty" ) {
__anon0c438c331202( const item * e, item * ) 240 REQUIRE( p.visit_items( [&container_id]( const item * e, item * ) {
241 return ( e->typeId() != container_id || e->contents.empty() ) ?
242 VisitResponse::NEXT : VisitResponse::ABORT;
243 } ) != VisitResponse::ABORT );
244 }
245 }
246 THEN( "the hip flask remains in the players possession" ) {
__anon0c438c331302( const item & e ) 247 auto found = p.items_with( [&worn_id]( const item & e ) {
248 return e.typeId() == worn_id;
249 } );
250 REQUIRE( found.size() == 1 );
251 AND_THEN( "the hip flask is still worn" ) {
252 REQUIRE( p.is_wielding( *found[0] ) );
253
254 AND_THEN( "the hip flask contains water" ) {
255 REQUIRE( found[0]->contents.num_item_stacks() == 1 );
256 REQUIRE( found[0]->has_item_with( has_liquid_filter ) );
257 }
258 }
259 }
260 THEN( "there is only one water remaining in the players possession" ) {
261 REQUIRE( count_items( p, liquid_id ) == 1 );
262 }
263 THEN( "the correct number of items were removed" ) {
264 REQUIRE( del.size() == count );
265
266 AND_THEN( "the removed items were all water" ) {
__anon0c438c331402( const item & e ) 267 CHECK( std::all_of( del.begin(), del.end(), [&liquid_id]( const item & e ) {
268 return e.typeId() == liquid_id;
269 } ) );
270 }
271 }
272
273 AND_WHEN( "the final water is removed" ) {
__anon0c438c331502( const item & e ) 274 std::list<item> del = p.remove_items_with( [&liquid_id]( const item & e ) {
275 return e.typeId() == liquid_id;
276 }, 1 );
277
278 THEN( "no water remain in the players possession" ) {
279 REQUIRE( count_items( p, liquid_id ) == 0 );
280 }
281
282 THEN( "the hip flask remains in the players possession" ) {
__anon0c438c331602( const item & e ) 283 auto found = p.items_with( [&worn_id]( const item & e ) {
284 return e.typeId() == worn_id;
285 } );
286 REQUIRE( found.size() == 1 );
287 AND_THEN( "the hip flask is worn" ) {
288 REQUIRE( p.is_wielding( *found[0] ) );
289
290 AND_THEN( "the hip flask is empty" ) {
291 REQUIRE( found[0]->contents.empty() );
292 }
293 }
294 }
295 }
296 }
297 }
298 }
299
300 GIVEN( "A player surrounded by several bottles of water" ) {
301 std::vector<tripoint> tiles = closest_points_first( p.pos(), 1 );
302 tiles.erase( tiles.begin() ); // player tile
303
304 int our = 0; // bottles placed on player tile
305 int adj = 0; // bottles placed on adjacent tiles
306
307 for( int i = 0; i != count; ++i ) {
308 if( i == 0 || tiles.empty() ) {
309 // always place at least one bottle on player tile
310 our++;
311 here.add_item( p.pos(), obj );
312 } else {
313 // randomly place bottles on adjacent tiles
314 adj++;
315 here.add_item( random_entry( tiles ), obj );
316 }
317 }
318 REQUIRE( our + adj == count );
319
320 map_selector sel( p.pos(), 1 );
321 map_cursor cur( p.pos() );
322
323 REQUIRE( count_items( sel, container_id ) == count );
324 REQUIRE( count_items( sel, liquid_id ) == count );
325
326 REQUIRE( count_items( cur, container_id ) == our );
327 REQUIRE( count_items( cur, liquid_id ) == our );
328
329 WHEN( "all the bottles are removed" ) {
__anon0c438c331702( const item & e ) 330 std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
331 return e.typeId() == container_id;
332 } );
333
334 THEN( "no bottles remain on the map" ) {
335 REQUIRE( count_items( sel, container_id ) == 0 );
336 }
337 THEN( "no water remains on the map" ) {
338 REQUIRE( count_items( sel, liquid_id ) == 0 );
339 }
340 THEN( "the correct number of items were removed" ) {
341 REQUIRE( del.size() == count );
342
343 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c331802( const item & e ) 344 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
345 return e.typeId() == container_id;
346 } ) );
347 }
348 AND_THEN( "the removed items all contain water" ) {
__anon0c438c331902( const item & e ) 349 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
350 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
351 } ) );
352 }
353 }
354 }
355
356 WHEN( "one of the bottles is removed" ) {
__anon0c438c331a02( const item & e ) 357 std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
358 return e.typeId() == container_id;
359 }, 1 );
360
361 THEN( "there is one less bottle on the map" ) {
362 REQUIRE( count_items( sel, container_id ) == count - 1 );
363 }
364 THEN( "there is one less water on the map" ) {
365 REQUIRE( count_items( sel, liquid_id ) == count - 1 );
366 }
367 THEN( "the correct number of items were removed" ) {
368 REQUIRE( del.size() == 1 );
369
370 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c331b02( const item & e ) 371 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
372 return e.typeId() == container_id;
373 } ) );
374 }
375 AND_THEN( "the removed items all contained water" ) {
__anon0c438c331c02( const item & e ) 376 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
377 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
378 } ) );
379 }
380 }
381 }
382
383 WHEN( "all of the bottles on the player tile are removed" ) {
__anon0c438c331d02( const item & e ) 384 std::list<item> del = cur.remove_items_with( [&container_id]( const item & e ) {
385 return e.typeId() == container_id;
386 }, our );
387
388 THEN( "no bottles remain on the player tile" ) {
389 REQUIRE( count_items( cur, container_id ) == 0 );
390 }
391 THEN( "no water remains on the player tile" ) {
392 REQUIRE( count_items( cur, liquid_id ) == 0 );
393 }
394 THEN( "the correct amount of bottles remains on the map" ) {
395 REQUIRE( count_items( sel, container_id ) == count - our );
396 }
397 THEN( "there correct amount of water remains on the map" ) {
398 REQUIRE( count_items( sel, liquid_id ) == count - our );
399 }
400 THEN( "the correct number of items were removed" ) {
401 REQUIRE( static_cast<int>( del.size() ) == our );
402
403 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c331e02( const item & e ) 404 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
405 return e.typeId() == container_id;
406 } ) );
407 }
408 AND_THEN( "the removed items all contained water" ) {
__anon0c438c331f02( const item & e ) 409 CHECK( std::all_of( del.begin(), del.end(), [has_liquid_filter]( const item & e ) {
410 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
411 } ) );
412 }
413 }
414 }
415 }
416
417 GIVEN( "An adjacent vehicle contains several bottles of water" ) {
418 std::vector<tripoint> tiles = closest_points_first( p.pos(), 1 );
419 tiles.erase( tiles.begin() ); // player tile
420 tripoint veh = random_entry( tiles );
421 REQUIRE( here.add_vehicle( vproto_id( "shopping_cart" ), veh, 0_degrees, 0, 0 ) );
422
__anon0c438c332002( const tripoint & e ) 423 REQUIRE( std::count_if( tiles.begin(), tiles.end(), [&here]( const tripoint & e ) {
424 return static_cast<bool>( here.veh_at( e ) );
425 } ) == 1 );
426
427 const cata::optional<vpart_reference> vp = here.veh_at( veh ).part_with_feature( "CARGO", true );
428 REQUIRE( vp );
429 vehicle *const v = &vp->vehicle();
430 const int part = vp->part_index();
431 REQUIRE( part >= 0 );
432 // Empty the vehicle of any cargo.
433 v->get_items( part ).clear();
434 for( int i = 0; i != count; ++i ) {
435 v->add_item( part, obj );
436 }
437
438 vehicle_selector sel( p.pos(), 1 );
439
440 REQUIRE( count_items( sel, container_id ) == count );
441 REQUIRE( count_items( sel, liquid_id ) == count );
442
443 WHEN( "all the bottles are removed" ) {
__anon0c438c332102( const item & e ) 444 std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
445 return e.typeId() == container_id;
446 } );
447
448 THEN( "no bottles remain within the vehicle" ) {
449 REQUIRE( count_items( sel, container_id ) == 0 );
450 }
451 THEN( "no water remains within the vehicle" ) {
452 REQUIRE( count_items( sel, liquid_id ) == 0 );
453 }
454 THEN( "the correct number of items were removed" ) {
455 REQUIRE( del.size() == count );
456
457 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c332202( const item & e ) 458 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
459 return e.typeId() == container_id;
460 } ) );
461 }
462 AND_THEN( "the removed items all contain water" ) {
__anon0c438c332302( const item & e ) 463 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
464 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
465 } ) );
466 }
467 }
468 }
469
470 WHEN( "one of the bottles is removed" ) {
__anon0c438c332402( const item & e ) 471 std::list<item> del = sel.remove_items_with( [&container_id]( const item & e ) {
472 return e.typeId() == container_id;
473 }, 1 );
474
475 THEN( "there is one less bottle within the vehicle" ) {
476 REQUIRE( count_items( sel, container_id ) == count - 1 );
477 }
478 THEN( "there is one less water within the vehicle" ) {
479 REQUIRE( count_items( sel, liquid_id ) == count - 1 );
480 }
481 THEN( "the correct number of items were removed" ) {
482 REQUIRE( del.size() == 1 );
483
484 AND_THEN( "the removed items were all bottles" ) {
__anon0c438c332502( const item & e ) 485 CHECK( std::all_of( del.begin(), del.end(), [&container_id]( const item & e ) {
486 return e.typeId() == container_id;
487 } ) );
488 }
489 AND_THEN( "the removed items all contained water" ) {
__anon0c438c332602( const item & e ) 490 CHECK( std::all_of( del.begin(), del.end(), [&has_liquid_filter]( const item & e ) {
491 return e.contents.num_item_stacks() == 1 && e.has_item_with( has_liquid_filter );
492 } ) );
493 }
494 }
495 }
496 }
497 }
498
499 TEST_CASE( "inventory_remove_invalidates_binning_cache", "[visitable][inventory]" )
500 {
501 inventory inv;
502 std::list<item> items = { item( "bone" ) };
503 inv += items;
504 CHECK( inv.charges_of( itype_id( "bone" ) ) == 1 );
505 inv.remove_items_with( return_true<item> );
506 CHECK( inv.size() == 0 );
507 // The following used to be a heap use-after-free due to a caching bug.
508 // Now should be safe.
509 CHECK( inv.charges_of( itype_id( "bone" ) ) == 0 );
510 }
511