1 #include <cstddef>
2 #include <iterator>
3 #include <list>
4 #include <map>
5 #include <memory>
6 #include <new>
7 #include <set>
8 #include <unordered_map>
9 #include <utility>
10 
11 #include "advanced_inv_area.h"
12 #include "advanced_inv_listitem.h"
13 #include "avatar.h"
14 #include "cata_assert.h"
15 #include "character.h"
16 #include "enums.h"
17 #include "field.h"
18 #include "field_type.h"
19 #include "game_constants.h"
20 #include "inventory.h"
21 #include "item.h"
22 #include "item_contents.h"
23 #include "map.h"
24 #include "map_selector.h"
25 #include "mapdata.h"
26 #include "optional.h"
27 #include "pimpl.h"
28 #include "translations.h"
29 #include "trap.h"
30 #include "type_id.h"
31 #include "uistate.h"
32 #include "units.h"
33 #include "veh_type.h"
34 #include "vehicle.h"
35 #include "vehicle_selector.h"
36 #include "vpart_position.h"
37 
get_item_count() const38 int advanced_inv_area::get_item_count() const
39 {
40     Character &player_character = get_player_character();
41     if( id == AIM_INVENTORY ) {
42         return player_character.inv->size();
43     } else if( id == AIM_WORN ) {
44         return player_character.worn.size();
45     } else if( id == AIM_ALL ) {
46         return 0;
47     } else if( id == AIM_DRAGGED ) {
48         return can_store_in_vehicle() ? veh->get_items( vstor ).size() : 0;
49     } else {
50         return get_map().i_at( pos ).size();
51     }
52 }
53 
advanced_inv_area(aim_location id,const point & h,tripoint off,const std::string & name,const std::string & shortname,std::string minimapname,std::string actionname,aim_location relative_location)54 advanced_inv_area::advanced_inv_area( aim_location id, const point &h, tripoint off,
55                                       const std::string &name, const std::string &shortname,
56                                       std::string minimapname, std::string actionname,
57                                       aim_location relative_location ) :
58     id( id ), hscreen( h ),
59     off( off ), name( name ), shortname( shortname ),
60     vstor( -1 ), volume( 0_ml ),
61     weight( 0_gram ), minimapname( minimapname ), actionname( actionname ),
62     relative_location( relative_location )
63 {
64 }
65 
init()66 void advanced_inv_area::init()
67 {
68     avatar &player_character = get_avatar();
69     pos = player_character.pos() + off;
70     veh = nullptr;
71     vstor = -1;
72     // must update in main function
73     volume = 0_ml;
74     volume_veh = 0_ml;
75     // must update in main function
76     weight = 0_gram;
77     weight_veh = 0_gram;
78     map &here = get_map();
79     switch( id ) {
80         case AIM_INVENTORY:
81         case AIM_WORN:
82             canputitemsloc = true;
83             break;
84         case AIM_DRAGGED:
85             if( player_character.get_grab_type() != object_type::VEHICLE ) {
86                 canputitemsloc = false;
87                 desc[0] = _( "Not dragging any vehicle!" );
88                 break;
89             }
90             // offset for dragged vehicles is not statically initialized, so get it
91             off = player_character.grab_point;
92             // Reset position because offset changed
93             pos = player_character.pos() + off;
94             if( const cata::optional<vpart_reference> vp = here.veh_at( pos ).part_with_feature( "CARGO",
95                     false ) ) {
96                 veh = &vp->vehicle();
97                 vstor = vp->part_index();
98             } else {
99                 veh = nullptr;
100                 vstor = -1;
101             }
102             if( vstor >= 0 ) {
103                 desc[0] = veh->name;
104                 canputitemsloc = true;
105                 max_size = MAX_ITEM_IN_VEHICLE_STORAGE;
106             } else {
107                 veh = nullptr;
108                 canputitemsloc = false;
109                 desc[0] = _( "No dragged vehicle!" );
110             }
111             break;
112         case AIM_CONTAINER:
113             // set container position based on location
114             set_container_position();
115             // location always valid, actual check is done in canputitems()
116             // and depends on selected item in pane (if it is valid container)
117             canputitemsloc = true;
118             if( !get_container() ) {
119                 desc[0] = _( "Invalid container!" );
120             }
121             break;
122         case AIM_ALL:
123             desc[0] = _( "All 9 squares" );
124             canputitemsloc = true;
125             break;
126         case AIM_SOUTHWEST:
127         case AIM_SOUTH:
128         case AIM_SOUTHEAST:
129         case AIM_WEST:
130         case AIM_CENTER:
131         case AIM_EAST:
132         case AIM_NORTHWEST:
133         case AIM_NORTH:
134         case AIM_NORTHEAST: {
135             const cata::optional<vpart_reference> vp =
136                 here.veh_at( pos ).part_with_feature( "CARGO", false );
137             if( vp ) {
138                 veh = &vp->vehicle();
139                 vstor = vp->part_index();
140             } else {
141                 veh = nullptr;
142                 vstor = -1;
143             }
144             canputitemsloc = can_store_in_vehicle() || here.can_put_items_ter_furn( pos );
145             max_size = MAX_ITEM_IN_SQUARE;
146             if( can_store_in_vehicle() ) {
147                 std::string part_name = vp->info().name();
148                 desc[1] = vp->get_label().value_or( part_name );
149             }
150             // get graffiti or terrain name
151             desc[0] = here.has_graffiti_at( pos ) ?
152                       here.graffiti_at( pos ) : here.name( pos );
153         }
154         default:
155             break;
156     }
157 
158     /* assemble a list of interesting traits of the target square */
159     // fields? with a special case for fire
160     bool danger_field = false;
161     const field &tmpfld = here.field_at( pos );
162     for( const auto &fld : tmpfld ) {
163         const field_entry &cur = fld.second;
164         if( fld.first.obj().has_fire ) {
165             flags.append( _( " <color_white_red>FIRE</color>" ) );
166         } else {
167             if( cur.is_dangerous() ) {
168                 danger_field = true;
169             }
170         }
171     }
172     if( danger_field ) {
173         flags.append( _( " DANGER" ) );
174     }
175 
176     // trap?
177     const trap &tr = here.tr_at( pos );
178     if( tr.can_see( pos, player_character ) && !tr.is_benign() ) {
179         flags.append( _( " TRAP" ) );
180     }
181 
182     // water?
183     if( here.has_flag_ter( TFLAG_SHALLOW_WATER, pos ) || here.has_flag_ter( TFLAG_DEEP_WATER, pos ) ) {
184         flags.append( _( " WATER" ) );
185     }
186 
187     // remove leading space
188     if( !flags.empty() && flags[0] == ' ' ) {
189         flags.erase( 0, 1 );
190     }
191 }
192 
free_volume(bool in_vehicle) const193 units::volume advanced_inv_area::free_volume( bool in_vehicle ) const
194 {
195     // should be a specific location instead
196     cata_assert( id != AIM_ALL );
197     if( id == AIM_INVENTORY || id == AIM_WORN ) {
198         return get_player_character().free_space();
199     }
200     return in_vehicle ? veh->free_volume( vstor ) : get_map().free_volume( pos );
201 }
202 
is_same(const advanced_inv_area & other) const203 bool advanced_inv_area::is_same( const advanced_inv_area &other ) const
204 {
205     // All locations (sans the below) are compared by the coordinates,
206     // e.g. dragged vehicle (to the south) and AIM_SOUTH are the same.
207     if( id != AIM_INVENTORY && other.id != AIM_INVENTORY &&
208         id != AIM_WORN && other.id != AIM_WORN &&
209         id != AIM_CONTAINER && other.id != AIM_CONTAINER ) {
210         //     have a vehicle?...     ...do the cargo index and pos match?...    ...at least pos?
211         return veh == other.veh ? pos == other.pos && vstor == other.vstor : pos == other.pos;
212     }
213     //      ...is the id?
214     return id == other.id;
215 }
216 
canputitems(const advanced_inv_listitem * advitem)217 bool advanced_inv_area::canputitems( const advanced_inv_listitem *advitem )
218 {
219     bool canputitems = false;
220     bool from_vehicle = false;
221     switch( id ) {
222         case AIM_CONTAINER: {
223             item_location it;
224             if( advitem != nullptr ) {
225                 it = advitem->items.front();
226                 from_vehicle = advitem->from_vehicle;
227             }
228             if( get_container( from_vehicle ) ) {
229                 it = get_container( from_vehicle );
230             }
231             if( it ) {
232                 canputitems = it->is_watertight_container();
233             }
234             break;
235         }
236         default:
237             canputitems = canputitemsloc;
238             break;
239     }
240     return canputitems;
241 }
242 
get_container(bool in_vehicle)243 item_location advanced_inv_area::get_container( bool in_vehicle )
244 {
245     item_location container;
246 
247     Character &player_character = get_player_character();
248     if( uistate.adv_inv_container_location != -1 ) {
249         // try to find valid container in the area
250         if( uistate.adv_inv_container_location == AIM_INVENTORY ) {
251             const invslice &stacks = player_character.inv->slice();
252 
253             // check index first
254             if( stacks.size() > static_cast<size_t>( uistate.adv_inv_container_index ) ) {
255                 auto &it = stacks[uistate.adv_inv_container_index]->front();
256                 if( is_container_valid( &it ) ) {
257                     container = item_location( player_character, &it );
258                 }
259             }
260 
261             // try entire area
262             if( !container ) {
263                 for( size_t x = 0; x < stacks.size(); ++x ) {
264                     item &it = stacks[x]->front();
265                     if( is_container_valid( &it ) ) {
266                         container = item_location( player_character, &it );
267                         uistate.adv_inv_container_index = x;
268                         break;
269                     }
270                 }
271             }
272         } else if( uistate.adv_inv_container_location == AIM_WORN ) {
273             std::list<item> &worn = player_character.worn;
274             size_t idx = static_cast<size_t>( uistate.adv_inv_container_index );
275             if( worn.size() > idx ) {
276                 auto iter = worn.begin();
277                 std::advance( iter, idx );
278                 if( is_container_valid( &*iter ) ) {
279                     container = item_location( player_character, &*iter );
280                 }
281             }
282 
283             // no need to reinvent the wheel
284             if( !container ) {
285                 auto iter = worn.begin();
286                 for( size_t i = 0; i < worn.size(); ++i, ++iter ) {
287                     if( is_container_valid( &*iter ) ) {
288                         container = item_location( player_character, &*iter );
289                         uistate.adv_inv_container_index = i;
290                         break;
291                     }
292                 }
293             }
294         } else {
295             map &here = get_map();
296             bool is_in_vehicle = veh &&
297                                  ( uistate.adv_inv_container_in_vehicle || ( can_store_in_vehicle() && in_vehicle ) );
298 
299             const itemstack &stacks = is_in_vehicle ?
300                                       i_stacked( veh->get_items( vstor ) ) :
301                                       i_stacked( here.i_at( pos ) );
302 
303             // check index first
304             if( stacks.size() > static_cast<size_t>( uistate.adv_inv_container_index ) ) {
305                 item *it = stacks[uistate.adv_inv_container_index].front();
306                 if( is_container_valid( it ) ) {
307                     if( is_in_vehicle ) {
308                         container = item_location( vehicle_cursor( *veh, vstor ), it );
309                     } else {
310                         container = item_location( map_cursor( pos ), it );
311                     }
312                 }
313             }
314 
315             // try entire area
316             if( !container ) {
317                 for( size_t x = 0; x < stacks.size(); ++x ) {
318                     item *it = stacks[x].front();
319                     if( is_container_valid( it ) ) {
320                         if( is_in_vehicle ) {
321                             container = item_location( vehicle_cursor( *veh, vstor ), it );
322                         } else {
323                             container = item_location( map_cursor( pos ), it );
324                         }
325                         uistate.adv_inv_container_index = x;
326                         break;
327                     }
328                 }
329             }
330         }
331 
332         // no valid container in the area, resetting container
333         if( !container ) {
334             set_container( nullptr );
335             desc[0] = _( "Invalid container" );
336         }
337     }
338 
339     return container;
340 }
341 
set_container(const advanced_inv_listitem * advitem)342 void advanced_inv_area::set_container( const advanced_inv_listitem *advitem )
343 {
344     if( advitem != nullptr ) {
345         const item_location &it = advitem->items.front();
346         uistate.adv_inv_container_location = advitem->area;
347         uistate.adv_inv_container_in_vehicle = advitem->from_vehicle;
348         uistate.adv_inv_container_index = advitem->idx;
349         uistate.adv_inv_container_type = it->typeId();
350         uistate.adv_inv_container_content_type = !it->is_container_empty() ?
351                 it->contents.legacy_front().typeId() : itype_id::NULL_ID();
352         set_container_position();
353     } else {
354         uistate.adv_inv_container_location = -1;
355         uistate.adv_inv_container_index = 0;
356         uistate.adv_inv_container_in_vehicle = false;
357         uistate.adv_inv_container_type = itype_id::NULL_ID();
358         uistate.adv_inv_container_content_type = itype_id::NULL_ID();
359     }
360 }
361 
is_container_valid(const item * it) const362 bool advanced_inv_area::is_container_valid( const item *it ) const
363 {
364     if( it != nullptr ) {
365         if( it->typeId() == uistate.adv_inv_container_type ) {
366             if( it->is_container_empty() ) {
367                 if( uistate.adv_inv_container_content_type.is_null() ) {
368                     return true;
369                 }
370             } else {
371                 if( it->contents.legacy_front().typeId() == uistate.adv_inv_container_content_type ) {
372                     return true;
373                 }
374             }
375         }
376     }
377 
378     return false;
379 }
380 
aim_vector(aim_location id)381 static tripoint aim_vector( aim_location id )
382 {
383     switch( id ) {
384         case AIM_SOUTHWEST:
385             return tripoint_south_west;
386         case AIM_SOUTH:
387             return tripoint_south;
388         case AIM_SOUTHEAST:
389             return tripoint_south_east;
390         case AIM_WEST:
391             return tripoint_west;
392         case AIM_EAST:
393             return tripoint_east;
394         case AIM_NORTHWEST:
395             return tripoint_north_west;
396         case AIM_NORTH:
397             return tripoint_north;
398         case AIM_NORTHEAST:
399             return tripoint_north_east;
400         default:
401             return tripoint_zero;
402     }
403 }
set_container_position()404 void advanced_inv_area::set_container_position()
405 {
406     avatar &player_character = get_avatar();
407     // update the offset of the container based on location
408     if( uistate.adv_inv_container_location == AIM_DRAGGED ) {
409         off = player_character.grab_point;
410     } else {
411         off = aim_vector( static_cast<aim_location>( uistate.adv_inv_container_location ) );
412     }
413     // update the absolute position
414     pos = player_character.pos() + off;
415     // update vehicle information
416     if( const cata::optional<vpart_reference> vp = get_map().veh_at( pos ).part_with_feature( "CARGO",
417             false ) ) {
418         veh = &vp->vehicle();
419         vstor = vp->part_index();
420     } else {
421         veh = nullptr;
422         vstor = -1;
423     }
424 }
425 
offset_to_location() const426 aim_location advanced_inv_area::offset_to_location() const
427 {
428     static aim_location loc_array[3][3] = {
429         {AIM_NORTHWEST,     AIM_NORTH,      AIM_NORTHEAST},
430         {AIM_WEST,          AIM_CENTER,     AIM_EAST},
431         {AIM_SOUTHWEST,     AIM_SOUTH,      AIM_SOUTHEAST}
432     };
433     return loc_array[off.y + 1][off.x + 1];
434 }
435 
can_store_in_vehicle() const436 bool advanced_inv_area::can_store_in_vehicle() const
437 {
438     // disallow for non-valid vehicle locations
439     if( id > AIM_DRAGGED || id < AIM_SOUTHWEST ) {
440         return false;
441     }
442     // Prevent AIM access to activated dishwasher, washing machine, or autoclave.
443     if( veh != nullptr && vstor >= 0 ) {
444         return !veh->part( vstor ).is_cleaner_on();
445     } else {
446         return false;
447     }
448 }
449 
450 template <typename T>
i_stacked(T items)451 advanced_inv_area::itemstack advanced_inv_area::i_stacked( T items )
452 {
453     //create a new container for our stacked items
454     advanced_inv_area::itemstack stacks;
455     // used to recall indices we stored `itype_id' item at in itemstack
456     std::unordered_map<itype_id, std::set<int>> cache;
457     // iterate through and create stacks
458     for( auto &elem : items ) {
459         const auto id = elem.typeId();
460         auto iter = cache.find( id );
461         bool got_stacked = false;
462         // cache entry exists
463         if( iter != cache.end() ) {
464             // check to see if it stacks with each item in a stack, not just front()
465             for( auto &idx : iter->second ) {
466                 for( auto &it : stacks[idx] ) {
467                     if( ( got_stacked = it->display_stacked_with( elem ) ) ) {
468                         stacks[idx].push_back( &elem );
469                         break;
470                     }
471                 }
472                 if( got_stacked ) {
473                     break;
474                 }
475             }
476         }
477         if( !got_stacked ) {
478             cache[id].insert( stacks.size() );
479             stacks.push_back( { &elem } );
480         }
481     }
482     return stacks;
483 }
484 
485 // instantiate the template
486 template
487 advanced_inv_area::itemstack advanced_inv_area::i_stacked<vehicle_stack>( vehicle_stack items );
488 
489 template
490 advanced_inv_area::itemstack advanced_inv_area::i_stacked<map_stack>( map_stack items );
491