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 ⁢
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 ⁢
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 ⁢
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