1 #include "inventory.h"
2 
3 #include <algorithm>
4 #include <cmath>
5 #include <cstdint>
6 #include <cstdlib>
7 #include <string>
8 #include <type_traits>
9 
10 #include "avatar.h"
11 #include "calendar.h"
12 #include "character.h"
13 #include "colony.h"
14 #include "damage.h"
15 #include "debug.h"
16 #include "enums.h"
17 #include "flag.h"
18 #include "iexamine.h"
19 #include "inventory_ui.h" // auto inventory blocking
20 #include "item_contents.h"
21 #include "item_pocket.h"
22 #include "item_stack.h"
23 #include "map.h"
24 #include "map_iterator.h"
25 #include "mapdata.h"
26 #include "messages.h" //for rust message
27 #include "npc.h"
28 #include "optional.h"
29 #include "options.h"
30 #include "point.h"
31 #include "proficiency.h"
32 #include "ret_val.h"
33 #include "rng.h"
34 #include "translations.h"
35 #include "type_id.h"
36 #include "units.h"
37 #include "vpart_position.h"
38 
39 static const itype_id itype_aspirin( "aspirin" );
40 static const itype_id itype_codeine( "codeine" );
41 static const itype_id itype_heroin( "heroin" );
42 static const itype_id itype_salt_water( "salt_water" );
43 static const itype_id itype_tramadol( "tramadol" );
44 static const itype_id itype_oxycodone( "oxycodone" );
45 static const itype_id itype_water( "water" );
46 
47 struct itype;
48 
49 const invlet_wrapper
50 inv_chars( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#&()+.:;=@[\\]^_{|}" );
51 
valid(const int invlet) const52 bool invlet_wrapper::valid( const int invlet ) const
53 {
54     if( invlet > std::numeric_limits<char>::max() || invlet < std::numeric_limits<char>::min() ) {
55         return false;
56     }
57     return find( static_cast<char>( invlet ) ) != std::string::npos;
58 }
59 
invlet_favorites(const std::unordered_map<itype_id,std::string> & map)60 invlet_favorites::invlet_favorites( const std::unordered_map<itype_id, std::string> &map )
61 {
62     for( const auto &p : map ) {
63         if( p.second.empty() ) {
64             // The map gradually accumulates empty lists; remove those here
65             continue;
66         }
67         invlets_by_id.insert( p );
68         for( char invlet : p.second ) {
69             uint8_t invlet_u = invlet;
70             if( !ids_by_invlet[invlet_u].is_empty() ) {
71                 debugmsg( "Duplicate invlet: %s and %s both mapped to %c",
72                           ids_by_invlet[invlet_u].str(), p.first.str(), invlet );
73             }
74             ids_by_invlet[invlet_u] = p.first;
75         }
76     }
77 }
78 
set(char invlet,const itype_id & id)79 void invlet_favorites::set( char invlet, const itype_id &id )
80 {
81     if( contains( invlet, id ) ) {
82         return;
83     }
84     erase( invlet );
85     uint8_t invlet_u = invlet;
86     ids_by_invlet[invlet_u] = id;
87     invlets_by_id[id].push_back( invlet );
88 }
89 
erase(char invlet)90 void invlet_favorites::erase( char invlet )
91 {
92     uint8_t invlet_u = invlet;
93     const itype_id &id = ids_by_invlet[invlet_u];
94     if( id.is_empty() ) {
95         return;
96     }
97     std::string &invlets = invlets_by_id[id];
98     std::string::iterator it = std::find( invlets.begin(), invlets.end(), invlet );
99     invlets.erase( it );
100     ids_by_invlet[invlet_u] = itype_id();
101 }
102 
contains(char invlet,const itype_id & id) const103 bool invlet_favorites::contains( char invlet, const itype_id &id ) const
104 {
105     uint8_t invlet_u = invlet;
106     return ids_by_invlet[invlet_u] == id;
107 }
108 
invlets_for(const itype_id & id) const109 std::string invlet_favorites::invlets_for( const itype_id &id ) const
110 {
111     auto map_iterator = invlets_by_id.find( id );
112     if( map_iterator == invlets_by_id.end() ) {
113         return {};
114     }
115     return map_iterator->second;
116 }
117 
118 const std::unordered_map<itype_id, std::string> &
get_invlets_by_id() const119 invlet_favorites::get_invlets_by_id() const
120 {
121     return invlets_by_id;
122 }
123 
124 inventory::inventory() = default;
125 
slice()126 invslice inventory::slice()
127 {
128     invslice stacks;
129     for( auto &elem : items ) {
130         stacks.push_back( &elem );
131     }
132     return stacks;
133 }
134 
const_slice() const135 const_invslice inventory::const_slice() const
136 {
137     const_invslice stacks;
138     for( const auto &item : items ) {
139         stacks.push_back( &item );
140     }
141     return stacks;
142 }
143 
const_stack(int i) const144 const std::list<item> &inventory::const_stack( int i ) const
145 {
146     if( i < 0 || i >= static_cast<int>( items.size() ) ) {
147         debugmsg( "Attempted to access stack %d in an inventory (size %d)", i, items.size() );
148         static const std::list<item> nullstack{};
149         return nullstack;
150     }
151 
152     invstack::const_iterator iter = items.begin();
153     for( int j = 0; j < i; ++j ) {
154         ++iter;
155     }
156     return *iter;
157 }
158 
size() const159 size_t inventory::size() const
160 {
161     return items.size();
162 }
163 
operator +=(const inventory & rhs)164 inventory &inventory::operator+= ( const inventory &rhs )
165 {
166     for( size_t i = 0; i < rhs.size(); i++ ) {
167         push_back( rhs.const_stack( i ) );
168     }
169     return *this;
170 }
171 
operator +=(const std::list<item> & rhs)172 inventory &inventory::operator+= ( const std::list<item> &rhs )
173 {
174     for( const auto &rh : rhs ) {
175         add_item( rh, false, false );
176     }
177     return *this;
178 }
179 
operator +=(const std::vector<item> & rhs)180 inventory &inventory::operator+= ( const std::vector<item> &rhs )
181 {
182     for( const auto &rh : rhs ) {
183         add_item( rh, true );
184     }
185     return *this;
186 }
187 
operator +=(const item & rhs)188 inventory &inventory::operator+= ( const item &rhs )
189 {
190     add_item( rhs );
191     return *this;
192 }
193 
operator +=(const item_stack & rhs)194 inventory &inventory::operator+= ( const item_stack &rhs )
195 {
196     for( const auto &p : rhs ) {
197         if( !p.made_of( phase_id::LIQUID ) ) {
198             add_item( p, true );
199         }
200     }
201     return *this;
202 }
203 
operator +(const inventory & rhs)204 inventory inventory::operator+ ( const inventory &rhs )
205 {
206     return inventory( *this ) += rhs;
207 }
208 
operator +(const std::list<item> & rhs)209 inventory inventory::operator+ ( const std::list<item> &rhs )
210 {
211     return inventory( *this ) += rhs;
212 }
213 
operator +(const item & rhs)214 inventory inventory::operator+ ( const item &rhs )
215 {
216     return inventory( *this ) += rhs;
217 }
218 
unsort()219 void inventory::unsort()
220 {
221     binned = false;
222 }
223 
stack_compare(const std::list<item> & lhs,const std::list<item> & rhs)224 static bool stack_compare( const std::list<item> &lhs, const std::list<item> &rhs )
225 {
226     return lhs.front() < rhs.front();
227 }
228 
clear()229 void inventory::clear()
230 {
231     items.clear();
232     binned = false;
233 }
234 
push_back(const std::list<item> & newits)235 void inventory::push_back( const std::list<item> &newits )
236 {
237     for( const auto &newit : newits ) {
238         add_item( newit, true );
239     }
240 }
241 
242 // This function keeps the invlet cache updated when a new item is added.
update_cache_with_item(item & newit)243 void inventory::update_cache_with_item( item &newit )
244 {
245     // This function does two things:
246     // 1. It adds newit's invlet to the list of favorite letters for newit's item type.
247     // 2. It removes newit's invlet from the list of favorite letters for all other item types.
248 
249     // no invlet item, just return.
250     // TODO: Should we instead remember that the invlet was cleared?
251     if( newit.invlet == 0 ) {
252         return;
253     }
254 
255     invlet_cache.set( newit.invlet, newit.typeId() );
256 }
257 
find_usable_cached_invlet(const itype_id & item_type)258 char inventory::find_usable_cached_invlet( const itype_id &item_type )
259 {
260     Character &player_character = get_player_character();
261     // Some of our preferred letters might already be used.
262     for( char invlet : invlet_cache.invlets_for( item_type ) ) {
263         // Don't overwrite user assignments.
264         if( assigned_invlet.count( invlet ) ) {
265             continue;
266         }
267         // Check if anything is using this invlet.
268         if( player_character.invlet_to_item( invlet ) != nullptr ) {
269             continue;
270         }
271         return invlet;
272     }
273 
274     return 0;
275 }
276 
add_item(item newit,bool keep_invlet,bool assign_invlet,bool should_stack)277 item &inventory::add_item( item newit, bool keep_invlet, bool assign_invlet, bool should_stack )
278 {
279     binned = false;
280 
281     Character &player_character = get_player_character();
282     if( should_stack ) {
283         // See if we can't stack this item.
284         for( auto &elem : items ) {
285             std::list<item>::iterator it_ref = elem.begin();
286             if( it_ref->stacks_with( newit ) ) {
287                 if( it_ref->merge_charges( newit ) ) {
288                     return *it_ref;
289                 }
290                 if( it_ref->invlet == '\0' ) {
291                     if( !keep_invlet ) {
292                         update_invlet( newit, assign_invlet );
293                     }
294                     update_cache_with_item( newit );
295                     it_ref->invlet = newit.invlet;
296                 } else {
297                     newit.invlet = it_ref->invlet;
298                 }
299                 elem.push_back( newit );
300                 return elem.back();
301             } else if( keep_invlet && assign_invlet && it_ref->invlet == newit.invlet ) {
302                 // If keep_invlet is true, we'll be forcing other items out of their current invlet.
303                 assign_empty_invlet( *it_ref, player_character );
304             }
305         }
306     }
307 
308     // Couldn't stack the item, proceed.
309     if( !keep_invlet ) {
310         update_invlet( newit, assign_invlet );
311     }
312     update_cache_with_item( newit );
313 
314     std::list<item> newstack;
315     newstack.push_back( newit );
316     items.push_back( newstack );
317     return items.back().back();
318 }
319 
add_item_keep_invlet(const item & newit)320 void inventory::add_item_keep_invlet( const item &newit )
321 {
322     add_item( newit, true );
323 }
324 
push_back(const item & newit)325 void inventory::push_back( const item &newit )
326 {
327     add_item( newit );
328 }
329 
330 #if defined(__ANDROID__)
331 extern void remove_stale_inventory_quick_shortcuts();
332 #endif
333 
provide_pseudo_item(const itype_id & id,int battery)334 item *inventory::provide_pseudo_item( const itype_id &id, int battery )
335 {
336     if( id.is_empty() || !provisioned_pseudo_tools.insert( id ).second ) {
337         return nullptr; // empty itype_id or already provided tool -> bail out
338     }
339 
340     item &it = add_item( item( id, calendar::turn, 0 ) );
341     it.set_flag( flag_PSEUDO );
342 
343     // if tool doesn't need battery bail out early
344     if( battery <= 0 || it.magazine_default().is_null() ) {
345         return &it;
346     }
347     item it_batt( it.magazine_default() );
348     item it_ammo = item( it_batt.ammo_default(), calendar::turn_zero );
349     if( it_ammo.is_null() || it_ammo.typeId() != itype_id( "battery" ) ) {
350         return &it;
351     }
352 
353     it_batt.ammo_set( it_batt.ammo_default(), battery );
354     it.put_in( it_batt, item_pocket::pocket_type::MAGAZINE_WELL );
355 
356     return &it;
357 }
358 
get_book_proficiency_bonuses() const359 book_proficiency_bonuses inventory::get_book_proficiency_bonuses() const
360 {
361     book_proficiency_bonuses ret;
362     for( const std::list<item> &it : this->items ) {
363         ret += it.front().get_book_proficiency_bonuses();
364     }
365     return ret;
366 }
367 
restack(Character & p)368 void inventory::restack( Character &p )
369 {
370     // tasks that the old restack seemed to do:
371     // 1. reassign inventory letters
372     // 2. remove items from non-matching stacks
373     // 3. combine matching stacks
374 
375     binned = false;
376     std::list<item> to_restack;
377     int idx = 0;
378     for( invstack::iterator iter = items.begin(); iter != items.end(); ++iter, ++idx ) {
379         std::list<item> &stack = *iter;
380         item &topmost = stack.front();
381 
382         const item *invlet_item = p.invlet_to_item( topmost.invlet );
383         if( !inv_chars.valid( topmost.invlet ) || ( invlet_item != nullptr &&
384                 position_by_item( invlet_item ) != idx ) ) {
385             assign_empty_invlet( topmost, p );
386             for( auto &stack_iter : stack ) {
387                 stack_iter.invlet = topmost.invlet;
388             }
389         }
390 
391         // remove non-matching items, stripping off end of stack so the first item keeps the invlet.
392         while( stack.size() > 1 && !topmost.stacks_with( stack.back() ) ) {
393             to_restack.splice( to_restack.begin(), *iter, --stack.end() );
394         }
395     }
396 
397     // combine matching stacks
398     // separate loop to ensure that ALL stacks are homogeneous
399     for( invstack::iterator iter = items.begin(); iter != items.end(); ++iter ) {
400         for( invstack::iterator other = iter; other != items.end(); ++other ) {
401             if( iter != other && iter->front().stacks_with( other->front() ) ) {
402                 if( other->front().count_by_charges() ) {
403                     iter->front().charges += other->front().charges;
404                 } else {
405                     iter->splice( iter->begin(), *other );
406                 }
407                 other = items.erase( other );
408                 --other;
409             }
410         }
411     }
412 
413     //re-add non-matching items
414     for( auto &elem : to_restack ) {
415         add_item( elem );
416     }
417 
418     //Ensure that all items in the same stack have the same invlet.
419     for( std::list< item > &outer : items ) {
420         for( item &inner : outer ) {
421             inner.invlet = outer.front().invlet;
422         }
423     }
424     items.sort( stack_compare );
425 
426 #if defined(__ANDROID__)
427     remove_stale_inventory_quick_shortcuts();
428 #endif
429 }
430 
count_charges_in_list(const itype * type,const map_stack & items)431 static int count_charges_in_list( const itype *type, const map_stack &items )
432 {
433     for( const auto &candidate : items ) {
434         if( candidate.type == type ) {
435             return candidate.charges;
436         }
437     }
438     return 0;
439 }
440 
form_from_map(const tripoint & origin,int range,const Character * pl,bool assign_invlet,bool clear_path)441 void inventory::form_from_map( const tripoint &origin, int range, const Character *pl,
442                                bool assign_invlet,
443                                bool clear_path )
444 {
445     form_from_map( get_map(), origin, range, pl, assign_invlet, clear_path );
446 }
447 
form_from_zone(map & m,std::unordered_set<tripoint> & zone_pts,const Character * pl,bool assign_invlet)448 void inventory::form_from_zone( map &m, std::unordered_set<tripoint> &zone_pts, const Character *pl,
449                                 bool assign_invlet )
450 {
451     std::vector<tripoint> pts;
452     for( const tripoint &elem : zone_pts ) {
453         pts.push_back( m.getlocal( elem ) );
454     }
455     form_from_map( m, pts, pl, assign_invlet );
456 }
457 
form_from_map(map & m,const tripoint & origin,int range,const Character * pl,bool assign_invlet,bool clear_path)458 void inventory::form_from_map( map &m, const tripoint &origin, int range, const Character *pl,
459                                bool assign_invlet,
460                                bool clear_path )
461 {
462     // populate a grid of spots that can be reached
463     std::vector<tripoint> reachable_pts = {};
464     // If we need a clear path we care about the reachability of points
465     if( clear_path ) {
466         m.reachable_flood_steps( reachable_pts, origin, range, 1, 100 );
467     } else {
468         // Fill reachable points with points_in_radius
469         tripoint_range<tripoint> in_radius = m.points_in_radius( origin, range );
470         for( const tripoint &p : in_radius ) {
471             reachable_pts.emplace_back( p );
472         }
473     }
474     form_from_map( m, reachable_pts, pl, assign_invlet );
475 }
476 
form_from_map(map & m,std::vector<tripoint> pts,const Character * pl,bool assign_invlet)477 void inventory::form_from_map( map &m, std::vector<tripoint> pts, const Character *pl,
478                                bool assign_invlet )
479 {
480     items.clear();
481     provisioned_pseudo_tools.clear();
482 
483     for( const tripoint &p : pts ) {
484         // a temporary hack while trees are terrain
485         if( m.ter( p )->has_flag( "TREE" ) ) {
486             provide_pseudo_item( itype_id( "butchery_tree_pseudo" ), 0 );
487         }
488         const furn_t &f = m.furn( p ).obj();
489         if( item *furn_item = provide_pseudo_item( f.crafting_pseudo_item, 0 ) ) {
490             const itype *ammo = f.crafting_ammo_item_type();
491             if( furn_item->contents.has_pocket_type( item_pocket::pocket_type::MAGAZINE ) ) {
492                 // NOTE: This only works if the pseudo item has a MAGAZINE pocket, not a MAGAZINE_WELL!
493                 item furn_ammo( ammo, calendar::turn, count_charges_in_list( ammo, m.i_at( p ) ) );
494                 furn_item->put_in( furn_ammo, item_pocket::pocket_type::MAGAZINE );
495             }
496         }
497         if( m.accessible_items( p ) ) {
498             for( item &i : m.i_at( p ) ) {
499                 // if it's *the* player requesting this from from map inventory
500                 // then don't allow items owned by another faction to be factored into recipe components etc.
501                 if( pl && !i.is_owned_by( *pl, true ) ) {
502                     continue;
503                 }
504                 if( !i.made_of( phase_id::LIQUID ) ) {
505                     add_item( i, false, assign_invlet );
506                 }
507             }
508         }
509         // Kludges for now!
510         if( m.has_nearby_fire( p, 0 ) ) {
511             if( item *fire = provide_pseudo_item( itype_id( "fire" ), 0 ) ) {
512                 fire->charges = 1;
513             }
514         }
515         // Handle any water from infinite map sources.
516         item water = m.water_from( p );
517         if( !water.is_null() ) {
518             add_item( water );
519         }
520         // kludge that can probably be done better to check specifically for toilet water to use in
521         // crafting
522         if( m.furn( p )->has_examine( iexamine::toilet ) ) {
523             // get water charges at location
524             map_stack toilet = m.i_at( p );
525             auto water = toilet.end();
526             for( auto candidate = toilet.begin(); candidate != toilet.end(); ++candidate ) {
527                 if( candidate->typeId() == itype_water ) {
528                     water = candidate;
529                     break;
530                 }
531             }
532             if( water != toilet.end() && water->charges > 0 ) {
533                 add_item( *water );
534             }
535         }
536 
537         // keg-kludge
538         if( m.furn( p )->has_examine( iexamine::keg ) ) {
539             map_stack liq_contained = m.i_at( p );
540             for( auto &i : liq_contained ) {
541                 if( i.made_of( phase_id::LIQUID ) ) {
542                     add_item( i );
543                 }
544             }
545         }
546 
547         // form from vehicle
548         if( optional_vpart_position vp = m.veh_at( p ) ) {
549             vp->form_inventory( *this );
550         }
551     }
552     pts.clear();
553 }
554 
reduce_stack(const int position,const int quantity)555 std::list<item> inventory::reduce_stack( const int position, const int quantity )
556 {
557     int pos = 0;
558     std::list<item> ret;
559     for( invstack::iterator iter = items.begin(); iter != items.end(); ++iter ) {
560         if( position == pos ) {
561             binned = false;
562             if( quantity >= static_cast<int>( iter->size() ) || quantity < 0 ) {
563                 ret = *iter;
564                 items.erase( iter );
565             } else {
566                 for( int i = 0 ; i < quantity ; i++ ) {
567                     ret.push_back( remove_item( &iter->front() ) );
568                 }
569             }
570             break;
571         }
572         ++pos;
573     }
574     return ret;
575 }
576 
remove_item(const item * it)577 item inventory::remove_item( const item *it )
578 {
579     auto tmp = remove_items_with( [&it]( const item & i ) {
580         return &i == it;
581     }, 1 );
582     if( !tmp.empty() ) {
583         binned = false;
584         return tmp.front();
585     }
586     debugmsg( "Tried to remove a item not in inventory." );
587     return item();
588 }
589 
remove_item(const int position)590 item inventory::remove_item( const int position )
591 {
592     int pos = 0;
593     for( invstack::iterator iter = items.begin(); iter != items.end(); ++iter ) {
594         if( position == pos ) {
595             binned = false;
596             if( iter->size() > 1 ) {
597                 std::list<item>::iterator stack_member = iter->begin();
598                 char invlet = stack_member->invlet;
599                 ++stack_member;
600                 stack_member->invlet = invlet;
601             }
602             item ret = iter->front();
603             iter->erase( iter->begin() );
604             if( iter->empty() ) {
605                 items.erase( iter );
606             }
607             return ret;
608         }
609         ++pos;
610     }
611 
612     return item();
613 }
614 
remove_randomly_by_volume(const units::volume & volume)615 std::list<item> inventory::remove_randomly_by_volume( const units::volume &volume )
616 {
617     std::list<item> result;
618     units::volume volume_dropped = 0_ml;
619     while( volume_dropped < volume ) {
620         units::volume cumulative_volume = 0_ml;
621         auto chosen_stack = items.begin();
622         auto chosen_item = chosen_stack->begin();
623         for( auto stack = items.begin(); stack != items.end(); ++stack ) {
624             for( auto stack_it = stack->begin(); stack_it != stack->end(); ++stack_it ) {
625                 cumulative_volume += stack_it->volume();
626                 if( x_in_y( stack_it->volume().value(), cumulative_volume.value() ) ) {
627                     chosen_item = stack_it;
628                     chosen_stack = stack;
629                 }
630             }
631         }
632         volume_dropped += chosen_item->volume();
633         result.push_back( std::move( *chosen_item ) );
634         chosen_item = chosen_stack->erase( chosen_item );
635         if( chosen_item == chosen_stack->begin() && !chosen_stack->empty() ) {
636             // preserve the invlet when removing the first item of a stack
637             chosen_item->invlet = result.back().invlet;
638         }
639         if( chosen_stack->empty() ) {
640             binned = false;
641             items.erase( chosen_stack );
642         }
643     }
644     return result;
645 }
646 
dump(std::vector<item * > & dest)647 void inventory::dump( std::vector<item *> &dest )
648 {
649     for( auto &elem : items ) {
650         for( auto &elem_stack_iter : elem ) {
651             dest.push_back( &elem_stack_iter );
652         }
653     }
654 }
655 
find_item(int position) const656 const item &inventory::find_item( int position ) const
657 {
658     if( position < 0 || position >= static_cast<int>( items.size() ) ) {
659         return null_item_reference();
660     }
661     invstack::const_iterator iter = items.begin();
662     for( int j = 0; j < position; ++j ) {
663         ++iter;
664     }
665     return iter->front();
666 }
667 
find_item(int position)668 item &inventory::find_item( int position )
669 {
670     return const_cast<item &>( const_cast<const inventory *>( this )->find_item( position ) );
671 }
672 
invlet_to_position(char invlet) const673 int inventory::invlet_to_position( char invlet ) const
674 {
675     int i = 0;
676     for( const auto &elem : items ) {
677         if( elem.begin()->invlet == invlet ) {
678             return i;
679         }
680         ++i;
681     }
682     return INT_MIN;
683 }
684 
position_by_item(const item * it) const685 int inventory::position_by_item( const item *it ) const
686 {
687     int p = 0;
688     for( const auto &stack : items ) {
689         for( const auto &e : stack ) {
690             if( e.has_item( *it ) ) {
691                 return p;
692             }
693         }
694         p++;
695     }
696     return INT_MIN;
697 }
698 
position_by_type(const itype_id & type) const699 int inventory::position_by_type( const itype_id &type ) const
700 {
701     int i = 0;
702     for( const auto &elem : items ) {
703         if( elem.front().typeId() == type ) {
704             return i;
705         }
706         ++i;
707     }
708     return INT_MIN;
709 }
710 
use_amount(const itype_id & it,int quantity,const std::function<bool (const item &)> & filter)711 std::list<item> inventory::use_amount( const itype_id &it, int quantity,
712                                        const std::function<bool( const item & )> &filter )
713 {
714     items.sort( stack_compare );
715     std::list<item> ret;
716     for( invstack::iterator iter = items.begin(); iter != items.end() && quantity > 0; /* noop */ ) {
717         for( std::list<item>::iterator stack_iter = iter->begin();
718              stack_iter != iter->end() && quantity > 0;
719              /* noop */ ) {
720             if( stack_iter->use_amount( it, quantity, ret, filter ) ) {
721                 stack_iter = iter->erase( stack_iter );
722             } else {
723                 ++stack_iter;
724             }
725         }
726         if( iter->empty() ) {
727             binned = false;
728             iter = items.erase( iter );
729         } else if( iter != items.end() ) {
730             ++iter;
731         }
732     }
733     return ret;
734 }
735 
leak_level(const flag_id & flag) const736 int inventory::leak_level( const flag_id &flag ) const
737 {
738     int ret = 0;
739 
740     for( const auto &elem : items ) {
741         for( const auto &elem_stack_iter : elem ) {
742             if( elem_stack_iter.has_flag( flag ) ) {
743                 if( elem_stack_iter.has_flag( flag_LEAK_ALWAYS ) ) {
744                     ret += elem_stack_iter.volume() / units::legacy_volume_factor;
745                 } else if( elem_stack_iter.has_flag( flag_LEAK_DAM ) && elem_stack_iter.damage() > 0 ) {
746                     ret += elem_stack_iter.damage_level();
747                 }
748             }
749         }
750     }
751     return ret;
752 }
753 
worst_item_value(npc * p) const754 int inventory::worst_item_value( npc *p ) const
755 {
756     int worst = 99999;
757     for( const auto &elem : items ) {
758         const item &it = elem.front();
759         int val = p->value( it );
760         if( val < worst ) {
761             worst = val;
762         }
763     }
764     return worst;
765 }
766 
has_enough_painkiller(int pain) const767 bool inventory::has_enough_painkiller( int pain ) const
768 {
769     for( const auto &elem : items ) {
770         const item &it = elem.front();
771         if( ( pain <= 35 && it.typeId() == itype_aspirin ) ||
772             ( pain >= 50 && it.typeId() == itype_oxycodone ) ||
773             it.typeId() == itype_tramadol || it.typeId() == itype_codeine ) {
774             return true;
775         }
776     }
777     return false;
778 }
779 
most_appropriate_painkiller(int pain)780 item *inventory::most_appropriate_painkiller( int pain )
781 {
782     int difference = 9999;
783     item *ret = &null_item_reference();
784     for( auto &elem : items ) {
785         int diff = 9999;
786         itype_id type = elem.front().typeId();
787         if( type == itype_aspirin ) {
788             diff = std::abs( pain - 15 );
789         } else if( type == itype_codeine ) {
790             diff = std::abs( pain - 30 );
791         } else if( type == itype_oxycodone ) {
792             diff = std::abs( pain - 60 );
793         } else if( type == itype_heroin ) {
794             diff = std::abs( pain - 100 );
795         } else if( type == itype_tramadol ) {
796             diff = std::abs( pain - 40 ) / 2; // Bonus since it's long-acting
797         }
798 
799         if( diff < difference ) {
800             difference = diff;
801             ret = &elem.front();
802         }
803     }
804     return ret;
805 }
806 
rust_iron_items()807 void inventory::rust_iron_items()
808 {
809     Character &player_character = get_player_character();
810     map &here = get_map();
811     for( auto &elem : items ) {
812         for( auto &elem_stack_iter : elem ) {
813             if( elem_stack_iter.made_of( material_id( "iron" ) ) &&
814                 !elem_stack_iter.has_flag( flag_WATERPROOF_GUN ) &&
815                 !elem_stack_iter.has_flag( flag_WATERPROOF ) &&
816                 elem_stack_iter.damage() < elem_stack_iter.max_damage() / 2 &&
817                 //Passivation layer prevents further rusting
818                 one_in( 500 ) &&
819                 //Scale with volume, bigger = slower (see #24204)
820                 one_in(
821                     static_cast<int>(
822                         14 * std::cbrt(
823                             0.5 * std::max(
824                                 0.05, static_cast<double>(
825                                     elem_stack_iter.base_volume().value() ) / 250 ) ) ) ) &&
826                 //                       ^season length   ^14/5*0.75/pi (from volume of sphere)
827                 //Freshwater without oxygen rusts slower than air
828                 here.water_from( player_character.pos() ).typeId() == itype_salt_water ) {
829                 elem_stack_iter.inc_damage( damage_type::ACID ); // rusting never completely destroys an item
830                 add_msg( m_bad, _( "Your %s is damaged by rust." ), elem_stack_iter.tname() );
831             }
832         }
833     }
834 }
835 
weight() const836 units::mass inventory::weight() const
837 {
838     units::mass ret = 0_gram;
839     for( const auto &elem : items ) {
840         for( const auto &elem_stack_iter : elem ) {
841             ret += elem_stack_iter.weight();
842         }
843     }
844     return ret;
845 }
846 
847 // Helper function to iterate over the intersection of the inventory and a list
848 // of items given
849 template<typename F>
for_each_item_in_both(const invstack & items,const std::map<const item *,int> & other,const F & f)850 void for_each_item_in_both(
851     const invstack &items, const std::map<const item *, int> &other, const F &f )
852 {
853     // Shortcut the logic in the common case where other is empty
854     if( other.empty() ) {
855         return;
856     }
857 
858     for( const auto &elem : items ) {
859         const item &representative = elem.front();
860         auto other_it = other.find( &representative );
861         if( other_it == other.end() ) {
862             continue;
863         }
864 
865         int num_to_count = other_it->second;
866         if( representative.count_by_charges() ) {
867             item copy = representative;
868             copy.charges = std::min( copy.charges, num_to_count );
869             f( copy );
870         } else {
871             for( const auto &elem_stack_iter : elem ) {
872                 f( elem_stack_iter );
873                 if( --num_to_count <= 0 ) {
874                     break;
875                 }
876             }
877         }
878     }
879 }
880 
weight_without(const std::map<const item *,int> & without) const881 units::mass inventory::weight_without( const std::map<const item *, int> &without ) const
882 {
883     units::mass ret = weight();
884 
885     for_each_item_in_both( items, without,
886     [&]( const item & i ) {
887         ret -= i.weight();
888     }
889                          );
890 
891     if( ret < 0_gram ) {
892         debugmsg( "Negative mass after removing some of inventory" );
893         ret = {};
894     }
895 
896     return ret;
897 }
898 
volume() const899 units::volume inventory::volume() const
900 {
901     units::volume ret = 0_ml;
902     for( const auto &elem : items ) {
903         for( const auto &elem_stack_iter : elem ) {
904             ret += elem_stack_iter.volume();
905         }
906     }
907     return ret;
908 }
909 
volume_without(const std::map<const item *,int> & without) const910 units::volume inventory::volume_without( const std::map<const item *, int> &without ) const
911 {
912     units::volume ret = volume();
913 
914     for_each_item_in_both( items, without,
915     [&]( const item & i ) {
916         ret -= i.volume();
917     }
918                          );
919 
920     if( ret < 0_ml ) {
921         debugmsg( "Negative volume after removing some of inventory" );
922         ret = 0_ml;
923     }
924 
925     return ret;
926 }
927 
active_items()928 std::vector<item *> inventory::active_items()
929 {
930     std::vector<item *> ret;
931     for( std::list<item> &elem : items ) {
932         for( item &elem_stack_iter : elem ) {
933             if( elem_stack_iter.needs_processing() ) {
934                 ret.push_back( &elem_stack_iter );
935             }
936         }
937     }
938     return ret;
939 }
940 
get_active_enchantment_cache(const Character & owner) const941 enchantment inventory::get_active_enchantment_cache( const Character &owner ) const
942 {
943     enchantment temp_cache;
944     for( const std::list<item> &elem : items ) {
945         for( const item &check_item : elem ) {
946             for( const enchantment &ench : check_item.get_enchantments() ) {
947                 if( ench.is_active( owner, check_item ) ) {
948                     temp_cache.force_add( ench );
949                 }
950             }
951         }
952     }
953     return temp_cache;
954 }
955 
count_item(const itype_id & item_type) const956 int inventory::count_item( const itype_id &item_type ) const
957 {
958     int num = 0;
959     const itype_bin bin = get_binned_items();
960     if( bin.find( item_type ) == bin.end() ) {
961         return num;
962     }
963     const std::list<const item *> items = get_binned_items().find( item_type )->second;
964     for( const item *it : items ) {
965         num += it->count();
966     }
967     return num;
968 }
969 
assign_empty_invlet(item & it,const Character & p,const bool force)970 void inventory::assign_empty_invlet( item &it, const Character &p, const bool force )
971 {
972     const std::string auto_setting = get_option<std::string>( "AUTO_INV_ASSIGN" );
973     if( auto_setting == "disabled" || ( ( auto_setting == "favorites" ) && !it.is_favorite ) ) {
974         return;
975     }
976 
977     invlets_bitset cur_inv = p.allocated_invlets();
978     itype_id target_type = it.typeId();
979     for( const auto &iter : assigned_invlet ) {
980         if( iter.second == target_type && !cur_inv[iter.first] ) {
981             it.invlet = iter.first;
982             return;
983         }
984     }
985     if( cur_inv.count() < inv_chars.size() ) {
986         // XXX YUCK I don't know how else to get the keybindings
987         // FIXME: Find a better way to get bound keys
988         inventory_selector selector( get_avatar() );
989 
990         for( const auto &inv_char : inv_chars ) {
991             if( assigned_invlet.count( inv_char ) ) {
992                 // don't overwrite assigned keys
993                 continue;
994             }
995             if( selector.action_bound_to_key( inv_char ) != "ERROR" ) {
996                 // don't auto-assign bound keys
997                 continue;
998             }
999             if( !cur_inv[inv_char] ) {
1000                 it.invlet = inv_char;
1001                 return;
1002             }
1003         }
1004     }
1005     if( !force ) {
1006         it.invlet = 0;
1007         return;
1008     }
1009     // No free hotkey exist, re-use some of the existing ones
1010     for( auto &elem : items ) {
1011         item &o = elem.front();
1012         if( o.invlet != 0 ) {
1013             it.invlet = o.invlet;
1014             o.invlet = 0;
1015             return;
1016         }
1017     }
1018     debugmsg( "could not find a hotkey for %s", it.tname() );
1019 }
1020 
reassign_item(item & it,char invlet,bool remove_old)1021 void inventory::reassign_item( item &it, char invlet, bool remove_old )
1022 {
1023     if( it.invlet == invlet ) { // no change needed
1024         return;
1025     }
1026     if( remove_old && it.invlet ) {
1027         invlet_cache.erase( it.invlet );
1028     }
1029     it.invlet = invlet;
1030     update_cache_with_item( it );
1031 }
1032 
update_invlet(item & newit,bool assign_invlet)1033 void inventory::update_invlet( item &newit, bool assign_invlet )
1034 {
1035     // Avoid letters that have been manually assigned to other things.
1036     if( newit.invlet && assigned_invlet.find( newit.invlet ) != assigned_invlet.end() &&
1037         assigned_invlet[newit.invlet] != newit.typeId() ) {
1038         newit.invlet = '\0';
1039     }
1040 
1041     // Remove letters that are not in the favorites cache
1042     if( newit.invlet ) {
1043         if( !invlet_cache.contains( newit.invlet, newit.typeId() ) ) {
1044             newit.invlet = '\0';
1045         }
1046     }
1047 
1048     Character &player_character = get_player_character();
1049     // Remove letters that have been assigned to other items in the inventory
1050     if( newit.invlet ) {
1051         char tmp_invlet = newit.invlet;
1052         newit.invlet = '\0';
1053         if( player_character.invlet_to_item( tmp_invlet ) == nullptr ) {
1054             newit.invlet = tmp_invlet;
1055         }
1056     }
1057 
1058     if( assign_invlet ) {
1059         // Assign a cached letter to the item
1060         if( !newit.invlet ) {
1061             newit.invlet = find_usable_cached_invlet( newit.typeId() );
1062         }
1063 
1064         // Give the item an invlet if it has none
1065         if( !newit.invlet ) {
1066             assign_empty_invlet( newit, player_character );
1067         }
1068     }
1069 }
1070 
allocated_invlets() const1071 invlets_bitset inventory::allocated_invlets() const
1072 {
1073     invlets_bitset invlets;
1074 
1075     for( const auto &stack : items ) {
1076         const char invlet = stack.front().invlet;
1077         invlets.set( invlet );
1078     }
1079     invlets[0] = false;
1080     return invlets;
1081 }
1082 
get_binned_items() const1083 const itype_bin &inventory::get_binned_items() const
1084 {
1085     if( binned ) {
1086         return binned_items;
1087     }
1088 
1089     binned_items.clear();
1090 
1091     // HACK: Hack warning
1092     inventory *this_nonconst = const_cast<inventory *>( this );
1093     this_nonconst->visit_items( [ this ]( item * e, item * ) {
1094         binned_items[ e->typeId() ].push_back( e );
1095         for( const item *it : e->softwares() ) {
1096             binned_items[it->typeId()].push_back( it );
1097         }
1098         return VisitResponse::NEXT;
1099     } );
1100 
1101     binned = true;
1102     return binned_items;
1103 }
1104 
copy_invlet_of(const inventory & other)1105 void inventory::copy_invlet_of( const inventory &other )
1106 {
1107     assigned_invlet = other.assigned_invlet;
1108     invlet_cache = other.invlet_cache;
1109 }
1110