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