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