1 #include "vehicle.h" // IWYU pragma: associated
2 
3 #include <algorithm>
4 #include <array>
5 #include <cmath>
6 #include <cstdlib>
7 #include <list>
8 #include <memory>
9 #include <sstream>
10 #include <string>
11 #include <tuple>
12 
13 #include "action.h"
14 #include "activity_actor_definitions.h"
15 #include "activity_handlers.h"
16 #include "activity_type.h"
17 #include "avatar.h"
18 #include "character.h"
19 #include "clzones.h"
20 #include "color.h"
21 #include "creature.h"
22 #include "debug.h"
23 #include "enums.h"
24 #include "game.h"
25 #include "iexamine.h"
26 #include "input.h"
27 #include "inventory.h"
28 #include "item.h"
29 #include "item_contents.h"
30 #include "item_pocket.h"
31 #include "itype.h"
32 #include "iuse.h"
33 #include "json.h"
34 #include "make_static.h"
35 #include "map.h"
36 #include "map_iterator.h"
37 #include "mapdata.h"
38 #include "messages.h"
39 #include "monster.h"
40 #include "mtype.h"
41 #include "output.h"
42 #include "overmapbuffer.h"
43 #include "pickup.h"
44 #include "player_activity.h"
45 #include "requirements.h"
46 #include "ret_val.h"
47 #include "rng.h"
48 #include "smart_controller_ui.h"
49 #include "sounds.h"
50 #include "string_formatter.h"
51 #include "string_input_popup.h"
52 #include "translations.h"
53 #include "ui.h"
54 #include "units.h"
55 #include "value_ptr.h"
56 #include "veh_interact.h"
57 #include "veh_type.h"
58 #include "vpart_position.h"
59 #include "vpart_range.h"
60 #include "weather.h"
61 
62 static const activity_id ACT_REPAIR_ITEM( "ACT_REPAIR_ITEM" );
63 static const activity_id ACT_START_ENGINES( "ACT_START_ENGINES" );
64 
65 static const itype_id fuel_type_battery( "battery" );
66 static const itype_id fuel_type_muscle( "muscle" );
67 static const itype_id fuel_type_none( "null" );
68 static const itype_id fuel_type_wind( "wind" );
69 
70 static const itype_id itype_battery( "battery" );
71 static const itype_id itype_detergent( "detergent" );
72 static const itype_id itype_fungal_seeds( "fungal_seeds" );
73 static const itype_id itype_marloss_seed( "marloss_seed" );
74 static const itype_id itype_soldering_iron( "soldering_iron" );
75 static const itype_id itype_water( "water" );
76 static const itype_id itype_water_clean( "water_clean" );
77 static const itype_id itype_water_faucet( "water_faucet" );
78 static const itype_id itype_water_purifier( "water_purifier" );
79 static const itype_id itype_welder( "welder" );
80 
81 static const efftype_id effect_harnessed( "harnessed" );
82 static const efftype_id effect_tied( "tied" );
83 
84 static const fault_id fault_engine_starter( "fault_engine_starter" );
85 
86 static const skill_id skill_mechanics( "mechanics" );
87 
88 static const flag_id json_flag_FILTHY( "FILTHY" );
89 
90 enum change_types : int {
91     OPENCURTAINS = 0,
92     OPENBOTH,
93     CLOSEDOORS,
94     CLOSEBOTH,
95     CANCEL
96 };
97 
keybind(const std::string & opt,const std::string & context="VEHICLE")98 static input_event keybind( const std::string &opt,
99                             const std::string &context = "VEHICLE" )
100 {
101     const std::vector<input_event> keys = input_context( context, keyboard_mode::keycode )
102                                           .keys_bound_to( opt, /*maximum_modifier_count=*/1 );
103     return keys.empty() ? input_event() : keys.front();
104 }
105 
add_toggle_to_opts(std::vector<uilist_entry> & options,std::vector<std::function<void ()>> & actions,const std::string & name,const input_event & key,const std::string & flag)106 void vehicle::add_toggle_to_opts( std::vector<uilist_entry> &options,
107                                   std::vector<std::function<void()>> &actions,
108                                   const std::string &name,
109                                   const input_event &key,
110                                   const std::string &flag )
111 {
112     // fetch matching parts and abort early if none found
113     const auto found = get_avail_parts( flag );
114     if( empty( found ) ) {
115         return;
116     }
117 
118     // can this menu option be selected by the user?
119     bool allow = true;
120 
121     // determine target state - currently parts of similar type are all switched concurrently
122     bool state = std::none_of( found.begin(), found.end(), []( const vpart_reference & vp ) {
123         return vp.part().enabled;
124     } );
125 
126     // if toggled part potentially usable check if could be enabled now (sufficient fuel etc.)
127     if( state ) {
128         allow = std::any_of( found.begin(), found.end(), []( const vpart_reference & vp ) {
129             return vp.vehicle().can_enable( vp.part() );
130         } );
131     }
132 
133     auto msg = string_format( state ?
134                               _( "Turn on %s" ) :
135                               colorize( _( "Turn off %s" ), c_pink ),
136                               name );
137     options.emplace_back( -1, allow, key, msg );
138 
139     actions.push_back( [ = ] {
140         for( const vpart_reference &vp : found )
141         {
142             vehicle_part &e = vp.part();
143             if( e.enabled != state ) {
144                 add_msg( state ? _( "Turned on %s." ) : _( "Turned off %s." ), e.name() );
145                 e.enabled = state;
146             }
147         }
148         refresh();
149     } );
150 }
151 
handbrake()152 void handbrake()
153 {
154     Character &player_character = get_player_character();
155     const optional_vpart_position vp = get_map().veh_at( player_character.pos() );
156     if( !vp ) {
157         return;
158     }
159     vehicle *const veh = &vp->vehicle();
160     add_msg( _( "You pull a handbrake." ) );
161     veh->cruise_velocity = 0;
162     if( veh->last_turn != 0_degrees && rng( 15, 60 ) * 100 < std::abs( veh->velocity ) ) {
163         veh->skidding = true;
164         add_msg( m_warning, _( "You lose control of %s." ), veh->name );
165         veh->turn( veh->last_turn > 0_degrees ? 60_degrees : -60_degrees );
166     } else {
167         int braking_power = std::abs( veh->velocity ) / 2 + 10 * 100;
168         if( std::abs( veh->velocity ) < braking_power ) {
169             veh->stop();
170         } else {
171             int sgn = veh->velocity > 0 ? 1 : -1;
172             veh->velocity = sgn * ( std::abs( veh->velocity ) - braking_power );
173         }
174     }
175     player_character.moves = 0;
176 }
177 
control_doors()178 void vehicle::control_doors()
179 {
180     const auto door_motors = get_avail_parts( "DOOR_MOTOR" );
181     // Indices of doors
182     std::vector< int > doors_with_motors;
183     // Locations used to display the doors
184     std::vector< tripoint > locations;
185     // it is possible to have one door to open and one to close for single motor
186     if( empty( door_motors ) ) {
187         debugmsg( "vehicle::control_doors called but no door motors found" );
188         return;
189     }
190 
191     uilist pmenu;
192     pmenu.title = _( "Select door to toggle" );
193     for( const vpart_reference &vp : door_motors ) {
194         const size_t p = vp.part_index();
195         if( vp.part().is_unavailable() ) {
196             continue;
197         }
198         const std::array<int, 2> doors = { { next_part_to_open( p ), next_part_to_close( p ) } };
199         for( int door : doors ) {
200             if( door == -1 ) {
201                 continue;
202             }
203 
204             int val = doors_with_motors.size();
205             doors_with_motors.push_back( door );
206             locations.push_back( global_part_pos3( p ) );
207             const char *actname = parts[door].open ? _( "Close" ) : _( "Open" );
208             pmenu.addentry( val, true, MENU_AUTOASSIGN, "%s %s", actname, parts[ door ].name() );
209         }
210     }
211 
212     pmenu.addentry( doors_with_motors.size() + OPENCURTAINS, true, MENU_AUTOASSIGN,
213                     _( "Open all curtains" ) );
214     pmenu.addentry( doors_with_motors.size() + OPENBOTH, true, MENU_AUTOASSIGN,
215                     _( "Open all curtains and doors" ) );
216     pmenu.addentry( doors_with_motors.size() + CLOSEDOORS, true, MENU_AUTOASSIGN,
217                     _( "Close all doors" ) );
218     pmenu.addentry( doors_with_motors.size() + CLOSEBOTH, true, MENU_AUTOASSIGN,
219                     _( "Close all curtains and doors" ) );
220 
221     pointmenu_cb callback( locations );
222     pmenu.callback = &callback;
223     // Move the menu so that we can see our vehicle
224     pmenu.w_y_setup = 0;
225     pmenu.query();
226 
227     if( pmenu.ret >= 0 ) {
228         if( pmenu.ret < static_cast<int>( doors_with_motors.size() ) ) {
229             int part = doors_with_motors[pmenu.ret];
230             open_or_close( part, !( parts[part].open ) );
231         } else if( pmenu.ret < ( static_cast<int>( doors_with_motors.size() ) + CANCEL ) ) {
232             int option = pmenu.ret - static_cast<int>( doors_with_motors.size() );
233             bool open = option == OPENBOTH || option == OPENCURTAINS;
234             for( const vpart_reference &vp : door_motors ) {
235                 const size_t motor = vp.part_index();
236                 int next_part = -1;
237                 if( open ) {
238                     int part = next_part_to_open( motor );
239                     if( part != -1 ) {
240                         if( !part_flag( part, "CURTAIN" ) &&  option == OPENCURTAINS ) {
241                             continue;
242                         }
243                         open_or_close( part, open );
244                         if( option == OPENBOTH ) {
245                             next_part = next_part_to_open( motor );
246                         }
247                         if( next_part != -1 ) {
248                             open_or_close( next_part, open );
249                         }
250                     }
251                 } else {
252                     int part = next_part_to_close( motor );
253                     if( part != -1 ) {
254                         if( part_flag( part, "CURTAIN" ) &&  option == CLOSEDOORS ) {
255                             continue;
256                         }
257                         open_or_close( part, open );
258                         if( option == CLOSEBOTH ) {
259                             next_part = next_part_to_close( motor );
260                         }
261                         if( next_part != -1 ) {
262                             open_or_close( next_part, open );
263                         }
264                     }
265                 }
266             }
267         }
268     }
269 }
270 
set_electronics_menu_options(std::vector<uilist_entry> & options,std::vector<std::function<void ()>> & actions)271 void vehicle::set_electronics_menu_options( std::vector<uilist_entry> &options,
272         std::vector<std::function<void()>> &actions )
273 {
274     auto add_toggle = [&]( const std::string & name, const input_event & key,
275     const std::string & flag ) {
276         add_toggle_to_opts( options, actions, name, key, flag );
277     };
278     add_toggle( pgettext( "electronics menu option", "reactor" ),
279                 keybind( "TOGGLE_REACTOR" ), "REACTOR" );
280     add_toggle( pgettext( "electronics menu option", "headlights" ),
281                 keybind( "TOGGLE_HEADLIGHT" ), "CONE_LIGHT" );
282     add_toggle( pgettext( "electronics menu option", "wide angle headlights" ),
283                 keybind( "TOGGLE_WIDE_HEADLIGHT" ), "WIDE_CONE_LIGHT" );
284     add_toggle( pgettext( "electronics menu option", "directed overhead lights" ),
285                 keybind( "TOGGLE_HALF_OVERHEAD_LIGHT" ), "HALF_CIRCLE_LIGHT" );
286     add_toggle( pgettext( "electronics menu option", "overhead lights" ),
287                 keybind( "TOGGLE_OVERHEAD_LIGHT" ), "CIRCLE_LIGHT" );
288     add_toggle( pgettext( "electronics menu option", "aisle lights" ),
289                 keybind( "TOGGLE_AISLE_LIGHT" ), "AISLE_LIGHT" );
290     add_toggle( pgettext( "electronics menu option", "dome lights" ),
291                 keybind( "TOGGLE_DOME_LIGHT" ), "DOME_LIGHT" );
292     add_toggle( pgettext( "electronics menu option", "atomic lights" ),
293                 keybind( "TOGGLE_ATOMIC_LIGHT" ), "ATOMIC_LIGHT" );
294     add_toggle( pgettext( "electronics menu option", "stereo" ),
295                 keybind( "TOGGLE_STEREO" ), "STEREO" );
296     add_toggle( pgettext( "electronics menu option", "chimes" ),
297                 keybind( "TOGGLE_CHIMES" ), "CHIMES" );
298     add_toggle( pgettext( "electronics menu option", "fridge" ),
299                 keybind( "TOGGLE_FRIDGE" ), "FRIDGE" );
300     add_toggle( pgettext( "electronics menu option", "freezer" ),
301                 keybind( "TOGGLE_FREEZER" ), "FREEZER" );
302     add_toggle( pgettext( "electronics menu option", "space heater" ),
303                 keybind( "TOGGLE_SPACE_HEATER" ), "SPACE_HEATER" );
304     add_toggle( pgettext( "electronics menu option", "cooler" ),
305                 keybind( "TOGGLE_COOLER" ), "COOLER" );
306     add_toggle( pgettext( "electronics menu option", "recharger" ),
307                 keybind( "TOGGLE_RECHARGER" ), "RECHARGE" );
308     add_toggle( pgettext( "electronics menu option", "plow" ),
309                 keybind( "TOGGLE_PLOW" ), "PLOW" );
310     add_toggle( pgettext( "electronics menu option", "reaper" ),
311                 keybind( "TOGGLE_REAPER" ), "REAPER" );
312     add_toggle( pgettext( "electronics menu option", "planter" ),
313                 keybind( "TOGGLE_PLANTER" ), "PLANTER" );
314     add_toggle( pgettext( "electronics menu option", "rockwheel" ),
315                 keybind( "TOGGLE_PLOW" ), "ROCKWHEEL" );
316     add_toggle( pgettext( "electronics menu option", "roadheader" ),
317                 keybind( "TOGGLE_PLOW" ), "ROADHEAD" );
318     add_toggle( pgettext( "electronics menu option", "scoop" ),
319                 keybind( "TOGGLE_SCOOP" ), "SCOOP" );
320     add_toggle( pgettext( "electronics menu option", "water purifier" ),
321                 keybind( "TOGGLE_WATER_PURIFIER" ), "WATER_PURIFIER" );
322     add_toggle( pgettext( "electronics menu option", "smart controller" ),
323                 keybind( "TOGGLE_SMART_ENGINE_CONTROLLER" ), "SMART_ENGINE_CONTROLLER" );
324 
325     if( has_part( "DOOR_MOTOR" ) ) {
326         options.emplace_back( _( "Toggle doors" ), keybind( "TOGGLE_DOORS" ) );
327         actions.push_back( [&] { control_doors(); refresh(); } );
328     }
329     if( camera_on || ( has_part( "CAMERA" ) && has_part( "CAMERA_CONTROL" ) ) ) {
330         options.emplace_back( camera_on ?
331                               colorize( _( "Turn off camera system" ), c_pink ) :
332                               _( "Turn on camera system" ),
333                               keybind( "TOGGLE_CAMERA" ) );
334         actions.push_back( [&] {
335             if( camera_on )
336             {
337                 camera_on = false;
338                 add_msg( _( "Camera system disabled" ) );
339             } else if( fuel_left( fuel_type_battery, true ) )
340             {
341                 camera_on = true;
342                 add_msg( _( "Camera system enabled" ) );
343             } else
344             {
345                 add_msg( _( "Camera system won't turn on" ) );
346             }
347             map &m = get_map();
348             m.invalidate_map_cache( m.get_abs_sub().z );
349             refresh();
350         } );
351     }
352 }
353 
control_electronics()354 void vehicle::control_electronics()
355 {
356     // exit early if you can't control the vehicle
357     if( !interact_vehicle_locked() ) {
358         return;
359     }
360 
361     bool valid_option = false;
362     do {
363         std::vector<uilist_entry> options;
364         std::vector<std::function<void()>> actions;
365 
366         set_electronics_menu_options( options, actions );
367 
368         uilist menu;
369         menu.text = _( "Electronics controls" );
370         menu.entries = options;
371         menu.query();
372         valid_option = menu.ret >= 0 && static_cast<size_t>( menu.ret ) < actions.size();
373         if( valid_option ) {
374             actions[menu.ret]();
375         }
376     } while( valid_option );
377 }
378 
control_engines()379 void vehicle::control_engines()
380 {
381     int e_toggle = 0;
382     bool dirty = false;
383     //count active engines
384     int active_mask = 0;
385     int fuel_count = 0;
386     int i = 0;
387     for( int e : engines ) {
388         if( is_part_on( e ) ) {
389             active_mask |= 1 << i++;
390         }
391         fuel_count += part_info( e ).engine_fuel_opts().size();
392     }
393 
394     const auto adjust_engine = [this]( int e_toggle ) {
395         int i = 0;
396         for( int e : engines ) {
397             for( const itype_id &fuel : part_info( e ).engine_fuel_opts() ) {
398                 if( i == e_toggle ) {
399                     if( parts[ e ].fuel_current() == fuel ) {
400                         toggle_specific_part( e, !is_part_on( e ) );
401                     } else {
402                         parts[ e ].fuel_set( fuel );
403                     }
404                     return;
405                 }
406                 i += 1;
407             }
408         }
409     };
410 
411     //show menu until user finishes
412     do {
413         e_toggle = select_engine();
414         if( e_toggle < 0 || e_toggle >= fuel_count ) {
415             break;
416         }
417         dirty = true;
418         adjust_engine( e_toggle );
419     } while( e_toggle < fuel_count );
420 
421     if( !dirty ) {
422         return;
423     }
424 
425     bool engines_were_on = engine_on;
426     int new_active_mask = 0;
427     i = 0;
428     for( int e : engines ) {
429         engine_on |= is_part_on( e );
430         new_active_mask |= 1 << i++;
431     }
432 
433     // if current velocity greater than new configuration safe speed
434     // drop down cruise velocity.
435     int safe_vel = safe_velocity();
436     if( velocity > safe_vel ) {
437         cruise_velocity = safe_vel;
438     }
439 
440     if( engines_were_on && !engine_on ) {
441         add_msg( _( "You turn off the %s's engines to change their configurations." ), name );
442     } else if( !get_player_character().controlling_vehicle ) {
443         add_msg( _( "You change the %s's engine configuration." ), name );
444     }
445 
446     if( engine_on ) {
447         start_engines();
448     }
449 }
450 
select_engine()451 int vehicle::select_engine()
452 {
453     uilist tmenu;
454     tmenu.text = _( "Toggle which?" );
455     int i = 0;
456     for( size_t x = 0; x < engines.size(); x++ ) {
457         int e = engines[ x ];
458         for( const itype_id &fuel_id : part_info( e ).engine_fuel_opts() ) {
459             bool is_active = parts[ e ].enabled && parts[ e ].fuel_current() == fuel_id;
460             bool is_available = parts[ e ].is_available() &&
461                                 ( is_perpetual_type( x ) || fuel_id == fuel_type_muscle ||
462                                   fuel_left( fuel_id ) );
463             tmenu.addentry( i++, is_available, -1, "[%s] %s %s",
464                             is_active ? "x" : " ", parts[ e ].name(),
465                             item::nname( fuel_id ) );
466         }
467     }
468     tmenu.query();
469     return tmenu.ret;
470 }
471 
interact_vehicle_locked()472 bool vehicle::interact_vehicle_locked()
473 {
474     if( !is_locked ) {
475         return true;
476     }
477 
478     Character &player_character = get_player_character();
479     add_msg( _( "You don't find any keys in the %s." ), name );
480     const inventory &inv = player_character.crafting_inventory();
481     if( inv.has_quality( quality_id( "SCREW" ) ) ) {
482         if( query_yn( _( "You don't find any keys in the %s. Attempt to hotwire vehicle?" ), name ) ) {
483             ///\EFFECT_MECHANICS speeds up vehicle hotwiring
484             int skill = player_character.get_skill_level( skill_mechanics );
485             const int moves = to_moves<int>( 6000_seconds / ( ( skill > 0 ) ? skill : 1 ) );
486             tripoint target = get_map().getabs( global_pos3() ) + coord_translate( parts[0].mount );
487             player_character.assign_activity(
488                 player_activity( hotwire_car_activity_actor( moves, target ) ) );
489         } else if( has_security_working() && query_yn( _( "Trigger the %s's Alarm?" ), name ) ) {
490             is_alarm_on = true;
491         } else {
492             add_msg( _( "You leave the controls alone." ) );
493         }
494     } else {
495         add_msg( _( "You could use a screwdriver to hotwire it." ) );
496     }
497 
498     return false;
499 }
500 
smash_security_system()501 void vehicle::smash_security_system()
502 {
503 
504     //get security and controls location
505     int s = -1;
506     int c = -1;
507     for( int p : speciality ) {
508         if( part_flag( p, "SECURITY" ) && !parts[ p ].is_broken() ) {
509             s = p;
510             c = part_with_feature( s, "CONTROLS", true );
511             break;
512         }
513     }
514     Character &player_character = get_player_character();
515     //controls and security must both be valid
516     if( c >= 0 && s >= 0 ) {
517         ///\EFFECT_MECHANICS reduces chance of damaging controls when smashing security system
518         int skill = player_character.get_skill_level( skill_mechanics );
519         int percent_controls = 70 / ( 1 + skill );
520         int percent_alarm = ( skill + 3 ) * 10;
521         int rand = rng( 1, 100 );
522 
523         if( percent_controls > rand ) {
524             damage_direct( c, part_info( c ).durability / 4 );
525 
526             if( parts[ c ].removed || parts[ c ].is_broken() ) {
527                 player_character.controlling_vehicle = false;
528                 is_alarm_on = false;
529                 add_msg( _( "You destroy the controls…" ) );
530             } else {
531                 add_msg( _( "You damage the controls." ) );
532             }
533         }
534         if( percent_alarm > rand ) {
535             damage_direct( s, part_info( s ).durability / 5 );
536             // chance to disable alarm immediately, or disable on destruction
537             if( percent_alarm / 4 > rand || parts[ s ].is_broken() ) {
538                 is_alarm_on = false;
539             }
540         }
541         add_msg( ( is_alarm_on ) ? _( "The alarm keeps going." ) : _( "The alarm stops." ) );
542     } else {
543         debugmsg( "No security system found on vehicle." );
544     }
545 }
546 
tracking_toggle_string()547 std::string vehicle::tracking_toggle_string()
548 {
549     return tracking_on ? _( "Forget vehicle position" ) : _( "Remember vehicle position" );
550 }
551 
autopilot_patrol_check()552 void vehicle::autopilot_patrol_check()
553 {
554     zone_manager &mgr = zone_manager::get_manager();
555     if( mgr.has_near( zone_type_id( "VEHICLE_PATROL" ), get_map().getabs( global_pos3() ), 60 ) ) {
556         enable_patrol();
557     } else {
558         g->zones_manager();
559     }
560 }
561 
toggle_autopilot()562 void vehicle::toggle_autopilot()
563 {
564     uilist smenu;
565     enum autopilot_option : int {
566         PATROL,
567         FOLLOW,
568         STOP
569     };
570     smenu.desc_enabled = true;
571     smenu.text = _( "Choose action for the autopilot" );
572     smenu.addentry_col( PATROL, true, 'P', _( "Patrol…" ),
573                         "", string_format( _( "Program the autopilot to patrol a nearby vehicle patrol zone.  "
574                                            "If no zones are nearby, you will be prompted to create one." ) ) );
575     smenu.addentry_col( FOLLOW, true, 'F', _( "Follow…" ),
576                         "", string_format(
577                             _( "Program the autopilot to follow you.  It might be a good idea to have a remote control available to tell it to stop, too." ) ) );
578     smenu.addentry_col( STOP, true, 'S', _( "Stop…" ),
579                         "", string_format( _( "Stop all autopilot related activities." ) ) );
580     smenu.query();
581     switch( smenu.ret ) {
582         case PATROL:
583             autopilot_patrol_check();
584             break;
585         case STOP:
586             autopilot_on = false;
587             is_patrolling = false;
588             is_following = false;
589             is_autodriving = false;
590             autodrive_local_target = tripoint_zero;
591             stop_engines();
592             break;
593         case FOLLOW:
594             autopilot_on = true;
595             is_following = true;
596             is_patrolling = false;
597             is_autodriving = true;
598             start_engines();
599             refresh();
600         default:
601             return;
602     }
603 }
604 
toggle_tracking()605 void vehicle::toggle_tracking()
606 {
607     if( tracking_on ) {
608         overmap_buffer.remove_vehicle( this );
609         tracking_on = false;
610         add_msg( _( "You stop keeping track of the vehicle position." ) );
611     } else {
612         overmap_buffer.add_vehicle( this );
613         tracking_on = true;
614         add_msg( _( "You start keeping track of this vehicle's position." ) );
615     }
616 }
617 
use_controls(const tripoint & pos)618 void vehicle::use_controls( const tripoint &pos )
619 {
620     std::vector<uilist_entry> options;
621     std::vector<std::function<void()>> actions;
622 
623     bool remote = g->remoteveh() == this;
624     bool has_electronic_controls = false;
625     avatar &player_character = get_avatar();
626 
627     if( remote ) {
628         options.emplace_back( _( "Stop controlling" ), keybind( "RELEASE_CONTROLS" ) );
629         actions.push_back( [&] {
630             player_character.controlling_vehicle = false;
631             g->setremoteveh( nullptr );
632             add_msg( _( "You stop controlling the vehicle." ) );
633             refresh();
634         } );
635 
636         has_electronic_controls = has_part( "CTRL_ELECTRONIC" ) || has_part( "REMOTE_CONTROLS" );
637 
638     } else if( veh_pointer_or_null( get_map().veh_at( pos ) ) == this ) {
639         if( player_character.controlling_vehicle ) {
640             options.emplace_back( _( "Let go of controls" ), keybind( "RELEASE_CONTROLS" ) );
641             actions.push_back( [&] {
642                 player_character.controlling_vehicle = false;
643                 add_msg( _( "You let go of the controls." ) );
644                 refresh();
645             } );
646         }
647         has_electronic_controls = !get_parts_at( pos, "CTRL_ELECTRONIC",
648                                   part_status_flag::any ).empty();
649     }
650 
651     if( get_parts_at( pos, "CONTROLS", part_status_flag::any ).empty() && !has_electronic_controls ) {
652         add_msg( m_info, _( "No controls there." ) );
653         return;
654     }
655 
656     // exit early if you can't control the vehicle
657     if( !interact_vehicle_locked() ) {
658         return;
659     }
660 
661     if( has_part( "ENGINE" ) ) {
662         if( player_character.controlling_vehicle || ( remote && engine_on ) ) {
663             options.emplace_back( _( "Stop driving" ), keybind( "TOGGLE_ENGINE" ) );
664             actions.push_back( [&] {
665                 if( engine_on && has_engine_type_not( fuel_type_muscle, true ) )
666                 {
667                     add_msg( _( "You turn the engine off and let go of the controls." ) );
668                     sounds::sound( pos, 2, sounds::sound_t::movement,
669                                    _( "the engine go silent" ) );
670                 } else
671                 {
672                     add_msg( _( "You let go of the controls." ) );
673                 }
674 
675                 for( size_t e = 0; e < engines.size(); ++e )
676                 {
677                     if( is_engine_on( e ) ) {
678                         if( sfx::has_variant_sound( "engine_stop", parts[ engines[ e ] ].info().get_id().str() ) ) {
679                             sfx::play_variant_sound( "engine_stop", parts[ engines[ e ] ].info().get_id().str(),
680                                                      parts[ engines[ e ] ].info().engine_noise_factor() );
681                         } else if( is_engine_type( e, fuel_type_muscle ) ) {
682                             sfx::play_variant_sound( "engine_stop", "muscle",
683                                                      parts[ engines[ e ] ].info().engine_noise_factor() );
684                         } else if( is_engine_type( e, fuel_type_wind ) ) {
685                             sfx::play_variant_sound( "engine_stop", "wind",
686                                                      parts[ engines[ e ] ].info().engine_noise_factor() );
687                         } else if( is_engine_type( e, fuel_type_battery ) ) {
688                             sfx::play_variant_sound( "engine_stop", "electric",
689                                                      parts[ engines[ e ] ].info().engine_noise_factor() );
690                         } else {
691                             sfx::play_variant_sound( "engine_stop", "combustion",
692                                                      parts[ engines[ e ] ].info().engine_noise_factor() );
693                         }
694                     }
695                 }
696                 vehicle_noise = 0;
697                 engine_on = false;
698                 player_character.controlling_vehicle = false;
699                 g->setremoteveh( nullptr );
700                 sfx::do_vehicle_engine_sfx();
701                 refresh();
702             } );
703 
704         } else if( has_engine_type_not( fuel_type_muscle, true ) ) {
705             options.emplace_back( engine_on ? _( "Turn off the engine" ) : _( "Turn on the engine" ),
706                                   keybind( "TOGGLE_ENGINE" ) );
707             actions.push_back( [&] {
708                 if( engine_on )
709                 {
710                     engine_on = false;
711                     sounds::sound( pos, 2, sounds::sound_t::movement,
712                                    _( "the engine go silent" ) );
713                     stop_engines();
714                 } else
715                 {
716                     start_engines();
717                 }
718                 refresh();
719             } );
720         }
721     }
722 
723     if( has_part( "HORN" ) ) {
724         options.emplace_back( _( "Honk horn" ), keybind( "SOUND_HORN" ) );
725         actions.push_back( [&] { honk_horn(); refresh(); } );
726     }
727     if( has_part( "AUTOPILOT" ) && ( has_part( "CTRL_ELECTRONIC" ) ||
728                                      has_part( "REMOTE_CONTROLS" ) ) ) {
729         options.emplace_back( _( "Control autopilot" ),
730                               keybind( "CONTROL_AUTOPILOT" ) );
731         actions.push_back( [&] { toggle_autopilot(); refresh(); } );
732     }
733 
734     options.emplace_back( cruise_on ? _( "Disable cruise control" ) : _( "Enable cruise control" ),
735                           keybind( "TOGGLE_CRUISE_CONTROL" ) );
736     actions.emplace_back( [&] {
737         cruise_on = !cruise_on;
738         add_msg( cruise_on ? _( "Cruise control turned on" ) : _( "Cruise control turned off" ) );
739         refresh();
740     } );
741 
742     if( has_electronic_controls ) {
743         set_electronics_menu_options( options, actions );
744         options.emplace_back( _( "Control multiple electronics" ), keybind( "CONTROL_MANY_ELECTRONICS" ) );
745         actions.push_back( [&] { control_electronics(); refresh(); } );
746     }
747 
748     options.emplace_back( tracking_on ? _( "Forget vehicle position" ) :
749                           _( "Remember vehicle position" ),
750                           keybind( "TOGGLE_TRACKING" ) );
751     actions.push_back( [&] { toggle_tracking(); } );
752 
753     if( ( is_foldable() || tags.count( "convertible" ) ) && !remote ) {
754         options.emplace_back( string_format( _( "Fold %s" ), name ), keybind( "FOLD_VEHICLE" ) );
755         actions.push_back( [&] { fold_up(); } );
756     }
757 
758     if( has_part( "ENGINE" ) ) {
759         options.emplace_back( _( "Control individual engines" ), keybind( "CONTROL_ENGINES" ) );
760         actions.push_back( [&] { control_engines(); refresh(); } );
761     }
762 
763     if( has_part( "SMART_ENGINE_CONTROLLER" ) ) {
764         options.emplace_back( _( "Smart controller settings" ),
765                               keybind( "TOGGLE_SMART_ENGINE_CONTROLLER" ) );
766         actions.push_back( [&] {
767             if( !smart_controller_cfg )
768             {
769                 smart_controller_cfg = smart_controller_config();
770             }
771 
772             smart_controller_settings cfg_view = smart_controller_settings( has_enabled_smart_controller,
773                     smart_controller_cfg -> battery_lo, smart_controller_cfg -> battery_hi );
774             smart_controller_ui( cfg_view ).control();
775             for( const vpart_reference &vp : get_avail_parts( "SMART_ENGINE_CONTROLLER" ) )
776             {
777                 vp.part().enabled = cfg_view.enabled;
778             }
779             refresh();
780         } );
781     }
782 
783     if( is_alarm_on ) {
784         if( velocity == 0 && !remote ) {
785             options.emplace_back( _( "Try to disarm alarm" ), keybind( "TOGGLE_ALARM" ) );
786             actions.push_back( [&] { smash_security_system(); refresh(); } );
787 
788         } else if( has_electronic_controls && has_part( "SECURITY" ) ) {
789             options.emplace_back( _( "Trigger alarm" ), keybind( "TOGGLE_ALARM" ) );
790             actions.push_back( [&] {
791                 is_alarm_on = true;
792                 add_msg( _( "You trigger the alarm" ) );
793                 refresh();
794             } );
795         }
796     }
797 
798     if( has_part( "TURRET" ) ) {
799         options.emplace_back( _( "Set turret targeting modes" ), keybind( "TURRET_TARGET_MODE" ) );
800         actions.push_back( [&] { turrets_set_targeting(); refresh(); } );
801 
802         options.emplace_back( _( "Set turret firing modes" ), keybind( "TURRET_FIRE_MODE" ) );
803         actions.push_back( [&] { turrets_set_mode(); refresh(); } );
804 
805         // We can also fire manual turrets with ACTION_FIRE while standing at the controls.
806         options.emplace_back( _( "Aim turrets manually" ), keybind( "TURRET_MANUAL_AIM" ) );
807         actions.push_back( [&] { turrets_aim_and_fire_all_manual( true ); refresh(); } );
808 
809         // This lets us manually override and set the target for the automatic turrets instead.
810         options.emplace_back( _( "Aim automatic turrets" ), keybind( "TURRET_MANUAL_OVERRIDE" ) );
811         actions.push_back( [&] { turrets_override_automatic_aim(); refresh(); } );
812 
813         options.emplace_back( _( "Aim individual turret" ), keybind( "TURRET_SINGLE_FIRE" ) );
814         actions.push_back( [&] { turrets_aim_and_fire_single(); refresh(); } );
815     }
816 
817     uilist menu;
818     menu.text = _( "Vehicle controls" );
819     menu.entries = options;
820     menu.query();
821     if( menu.ret >= 0 ) {
822         // allow player to turn off engine without triggering another warning
823         if( menu.ret != 0 && menu.ret != 1 && menu.ret != 2 && menu.ret != 3 ) {
824             if( !handle_potential_theft( player_character ) ) {
825                 return;
826             }
827         }
828         actions[menu.ret]();
829         // Don't access `this` from here on, one of the actions above is to call
830         // fold_up(), which may have deleted `this` object.
831     }
832 }
833 
fold_up()834 bool vehicle::fold_up()
835 {
836     const bool can_be_folded = is_foldable();
837     const bool is_convertible = ( tags.count( "convertible" ) > 0 );
838     if( !( can_be_folded || is_convertible ) ) {
839         debugmsg( _( "Tried to fold non-folding vehicle %s" ), name );
840         return false;
841     }
842 
843     avatar &player_character = get_avatar();
844     if( player_character.controlling_vehicle ) {
845         add_msg( m_warning,
846                  _( "As the pitiless metal bars close on your nether regions, you reconsider trying to fold the %s while riding it." ),
847                  name );
848         return false;
849     }
850 
851     if( velocity > 0 ) {
852         add_msg( m_warning, _( "You can't fold the %s while it's in motion." ), name );
853         return false;
854     }
855 
856     add_msg( _( "You painstakingly pack the %s into a portable configuration." ), name );
857 
858     if( player_character.get_grab_type() != object_type::NONE ) {
859         player_character.grab( object_type::NONE );
860         add_msg( _( "You let go of %s as you fold it." ), name );
861     }
862 
863     std::string itype_id = "folding_bicycle";
864     for( const auto &elem : tags ) {
865         if( elem.compare( 0, 12, "convertible:" ) == 0 ) {
866             itype_id = elem.substr( 12 );
867             break;
868         }
869     }
870 
871     // create a folding [non]bicycle item
872     item bicycle( can_be_folded ? "generic_folded_vehicle" : "folding_bicycle", calendar::turn );
873 
874     map &here = get_map();
875     // Drop stuff in containers on ground
876     for( const vpart_reference &vp : get_any_parts( "CARGO" ) ) {
877         const size_t p = vp.part_index();
878         for( auto &elem : get_items( p ) ) {
879             here.add_item_or_charges( player_character.pos(), elem );
880         }
881         while( !get_items( p ).empty() ) {
882             get_items( p ).erase( get_items( p ).begin() );
883         }
884     }
885 
886     unboard_all();
887 
888     // Store data of all parts, iuse::unfold_bicyle only loads
889     // some of them, some are expect to be
890     // vehicle specific and therefore constant (like id, mount).
891     // Writing everything here is easier to manage, as only
892     // iuse::unfold_bicyle has to adopt to changes.
893     try {
894         std::ostringstream veh_data;
895         JsonOut json( veh_data );
896         json.write( parts );
897         bicycle.set_var( "folding_bicycle_parts", veh_data.str() );
898     } catch( const JsonError &e ) {
899         debugmsg( "Error storing vehicle: %s", e.c_str() );
900     }
901 
902     if( can_be_folded ) {
903         bicycle.set_var( "weight", to_milligram( total_mass() ) );
904         bicycle.set_var( "volume", total_folded_volume() / units::legacy_volume_factor );
905         bicycle.set_var( "name", string_format( _( "folded %s" ), name ) );
906         bicycle.set_var( "vehicle_name", name );
907         // TODO: a better description?
908         bicycle.set_var( "description", string_format( _( "A folded %s." ), name ) );
909     }
910 
911     here.add_item_or_charges( global_part_pos3( 0 ), bicycle );
912     here.destroy_vehicle( this );
913 
914     // TODO: take longer to fold bigger vehicles
915     // TODO: make this interruptible
916     player_character.moves -= 500;
917     return true;
918 }
919 
engine_cold_factor(const int e) const920 double vehicle::engine_cold_factor( const int e ) const
921 {
922     if( !part_info( engines[e] ).has_flag( "E_COLD_START" ) ) {
923         return 0.0;
924     }
925 
926     int eff_temp = get_weather().get_temperature( get_player_character().pos() );
927     if( !parts[ engines[ e ] ].has_fault_flag( "BAD_COLD_START" ) ) {
928         eff_temp = std::min( eff_temp, 20 );
929     }
930 
931     return 1.0 - ( std::max( 0, std::min( 30, eff_temp ) ) / 30.0 );
932 }
933 
engine_start_time(const int e) const934 int vehicle::engine_start_time( const int e ) const
935 {
936     if( !is_engine_on( e ) || part_info( engines[e] ).has_flag( "E_STARTS_INSTANTLY" ) ||
937         !engine_fuel_left( e ) ) {
938         return 0;
939     }
940 
941     const double dmg = parts[engines[e]].damage_percent();
942 
943     // non-linear range [100-1000]; f(0.0) = 100, f(0.6) = 250, f(0.8) = 500, f(0.9) = 1000
944     // diesel engines with working glow plugs always start with f = 0.6 (or better)
945     const double cold = 100 / tanh( 1 - std::min( engine_cold_factor( e ), 0.9 ) );
946 
947     // watts to old vhp = watts / 373
948     // divided by magic 16 = watts / 6000
949     const double watts_per_time = 6000;
950     return part_vpower_w( engines[ e ], true ) / watts_per_time + 100 * dmg + cold;
951 }
952 
auto_select_fuel(int e)953 bool vehicle::auto_select_fuel( int e )
954 {
955     vehicle_part &vp_engine = parts[ engines[ e ] ];
956     const vpart_info &vp_engine_info = part_info( engines[e] );
957     if( !vp_engine.is_available() ) {
958         return false;
959     }
960     if( vp_engine_info.fuel_type == fuel_type_none ||
961         vp_engine_info.has_flag( "PERPETUAL" ) ||
962         engine_fuel_left( e ) > 0 ) {
963         return true;
964     }
965     for( const itype_id &fuel_id : vp_engine_info.engine_fuel_opts() ) {
966         if( fuel_left( fuel_id ) > 0 ) {
967             vp_engine.fuel_set( fuel_id );
968             return true;
969         }
970     }
971     return false; // not a single fuel type left for this engine
972 }
973 
start_engine(const int e)974 bool vehicle::start_engine( const int e )
975 {
976     if( !is_engine_on( e ) ) {
977         return false;
978     }
979 
980     const vpart_info &einfo = part_info( engines[e] );
981     vehicle_part &eng = parts[ engines[ e ] ];
982 
983     bool out_of_fuel = !auto_select_fuel( e );
984 
985     Character &player_character = get_player_character();
986     if( out_of_fuel ) {
987         if( einfo.fuel_type == fuel_type_muscle ) {
988             // Muscle engines cannot start with broken limbs
989             if( einfo.has_flag( "MUSCLE_ARMS" ) && ( player_character.get_working_arm_count() < 2 ) ) {
990                 add_msg( _( "You cannot use %s with a broken arm." ), eng.name() );
991                 return false;
992             } else if( einfo.has_flag( "MUSCLE_LEGS" ) && ( player_character.get_working_leg_count() < 2 ) ) {
993                 add_msg( _( "You cannot use %s with a broken leg." ), eng.name() );
994                 return false;
995             }
996         } else {
997             add_msg( _( "Looks like the %1$s is out of %2$s." ), eng.name(),
998                      item::nname( einfo.fuel_type ) );
999             return false;
1000         }
1001     }
1002 
1003     const double dmg = parts[engines[e]].damage_percent();
1004     const int engine_power = std::abs( part_epower_w( engines[e] ) );
1005     const double cold_factor = engine_cold_factor( e );
1006     const int start_moves = engine_start_time( e );
1007 
1008     const tripoint pos = global_part_pos3( engines[e] );
1009     if( einfo.engine_backfire_threshold() ) {
1010         if( ( 1 - dmg ) < einfo.engine_backfire_threshold() &&
1011             one_in( einfo.engine_backfire_freq() ) ) {
1012             backfire( e );
1013         } else {
1014             sounds::sound( pos, start_moves / 10, sounds::sound_t::movement,
1015                            string_format( _( "the %s bang as it starts!" ), eng.name() ), true, "vehicle",
1016                            "engine_bangs_start" );
1017         }
1018     }
1019 
1020     // Immobilizers need removing before the vehicle can be started
1021     if( eng.has_fault_flag( "IMMOBILIZER" ) ) {
1022         sounds::sound( pos, 5, sounds::sound_t::alarm,
1023                        string_format( _( "the %s making a long beep." ), eng.name() ), true, "vehicle",
1024                        "fault_immobiliser_beep" );
1025         return false;
1026     }
1027 
1028     // Engine with starter motors can fail on both battery and starter motor
1029     if( eng.faults_potential().count( fault_engine_starter ) ) {
1030         if( eng.has_fault_flag( "BAD_STARTER" ) ) {
1031             sounds::sound( pos, eng.info().engine_noise_factor(), sounds::sound_t::alarm,
1032                            string_format( _( "the %s clicking once." ), eng.name() ), true, "vehicle",
1033                            "engine_single_click_fail" );
1034             return false;
1035         }
1036 
1037         const int start_draw_bat = power_to_energy_bat( engine_power *
1038                                    ( 1.0 + dmg / 2 + cold_factor / 5 ) * 10, time_duration::from_moves( start_moves ) );
1039         if( discharge_battery( start_draw_bat, true ) != 0 ) {
1040             sounds::sound( pos, eng.info().engine_noise_factor(), sounds::sound_t::alarm,
1041                            string_format( _( "the %s rapidly clicking." ), eng.name() ), true, "vehicle",
1042                            "engine_multi_click_fail" );
1043             return false;
1044         }
1045     }
1046 
1047     // Engines always fail to start with faulty fuel pumps
1048     if( eng.has_fault_flag( "BAD_FUEL_PUMP" ) ) {
1049         sounds::sound( pos, eng.info().engine_noise_factor(), sounds::sound_t::movement,
1050                        string_format( _( "the %s quickly stuttering out." ), eng.name() ), true, "vehicle",
1051                        "engine_stutter_fail" );
1052         return false;
1053     }
1054 
1055     // Damaged non-electric engines have a chance of failing to start
1056     if( !is_engine_type( e, fuel_type_battery ) && einfo.fuel_type != fuel_type_muscle &&
1057         x_in_y( dmg * 100, 120 ) ) {
1058         sounds::sound( pos, eng.info().engine_noise_factor(), sounds::sound_t::movement,
1059                        string_format( _( "the %s clanking and grinding." ), eng.name() ), true, "vehicle",
1060                        "engine_clanking_fail" );
1061         return false;
1062     }
1063     sounds::sound( pos, eng.info().engine_noise_factor(), sounds::sound_t::movement,
1064                    string_format( _( "the %s starting." ), eng.name() ) );
1065 
1066     if( sfx::has_variant_sound( "engine_start", eng.info().get_id().str() ) ) {
1067         sfx::play_variant_sound( "engine_start", eng.info().get_id().str(),
1068                                  eng.info().engine_noise_factor() );
1069     } else if( einfo.fuel_type == fuel_type_muscle ) {
1070         sfx::play_variant_sound( "engine_start", "muscle", eng.info().engine_noise_factor() );
1071     } else if( is_engine_type( e, fuel_type_wind ) ) {
1072         sfx::play_variant_sound( "engine_start", "wind", eng.info().engine_noise_factor() );
1073     } else if( is_engine_type( e, fuel_type_battery ) ) {
1074         sfx::play_variant_sound( "engine_start", "electric", eng.info().engine_noise_factor() );
1075     } else {
1076         sfx::play_variant_sound( "engine_start", "combustion", eng.info().engine_noise_factor() );
1077     }
1078     return true;
1079 }
1080 
stop_engines()1081 void vehicle::stop_engines()
1082 {
1083     vehicle_noise = 0;
1084     engine_on = false;
1085     add_msg( _( "You turn the engine off." ) );
1086     for( size_t e = 0; e < engines.size(); ++e ) {
1087         if( is_engine_on( e ) ) {
1088             if( sfx::has_variant_sound( "engine_stop", parts[ engines[ e ] ].info().get_id().str() ) ) {
1089                 sfx::play_variant_sound( "engine_stop", parts[ engines[ e ] ].info().get_id().str(),
1090                                          parts[ engines[ e ] ].info().engine_noise_factor() );
1091             } else if( is_engine_type( e, fuel_type_battery ) ) {
1092                 sfx::play_variant_sound( "engine_stop", "electric",
1093                                          parts[ engines[ e ] ].info().engine_noise_factor() );
1094             } else {
1095                 sfx::play_variant_sound( "engine_stop", "combustion",
1096                                          parts[ engines[ e ] ].info().engine_noise_factor() );
1097             }
1098         }
1099     }
1100     sfx::do_vehicle_engine_sfx();
1101 }
1102 
start_engines(const bool take_control,const bool autodrive)1103 void vehicle::start_engines( const bool take_control, const bool autodrive )
1104 {
1105     bool has_engine = std::any_of( engines.begin(), engines.end(), [&]( int idx ) {
1106         return parts[ idx ].enabled && !parts[ idx ].is_broken();
1107     } );
1108 
1109     // if no engines enabled then enable all before trying to start the vehicle
1110     if( !has_engine ) {
1111         for( int idx : engines ) {
1112             if( !parts[ idx ].is_broken() ) {
1113                 parts[ idx ].enabled = true;
1114             }
1115         }
1116     }
1117 
1118     int start_time = 0;
1119     // record the first usable engine as the referenced position checked at the end of the engine starting activity
1120     bool has_starting_engine_position = false;
1121     tripoint starting_engine_position;
1122     for( size_t e = 0; e < engines.size(); ++e ) {
1123         if( !has_starting_engine_position && !parts[ engines[ e ] ].is_broken() &&
1124             parts[ engines[ e ] ].enabled ) {
1125             starting_engine_position = global_part_pos3( engines[ e ] );
1126             has_starting_engine_position = true;
1127         }
1128         has_engine = has_engine || is_engine_on( e );
1129         start_time = std::max( start_time, engine_start_time( e ) );
1130     }
1131 
1132     if( !has_starting_engine_position ) {
1133         starting_engine_position = global_pos3();
1134     }
1135 
1136     if( !has_engine ) {
1137         add_msg( m_info, _( "The %s doesn't have an engine!" ), name );
1138         return;
1139     }
1140 
1141     Character &player_character = get_player_character();
1142     if( take_control && !player_character.controlling_vehicle ) {
1143         player_character.controlling_vehicle = true;
1144         add_msg( _( "You take control of the %s." ), name );
1145     }
1146     if( !autodrive ) {
1147         player_character.assign_activity( ACT_START_ENGINES, start_time );
1148         player_character.activity.placement = starting_engine_position - player_character.pos();
1149         player_character.activity.values.push_back( take_control );
1150     }
1151 }
1152 
enable_patrol()1153 void vehicle::enable_patrol()
1154 {
1155     is_patrolling = true;
1156     autopilot_on = true;
1157     autodrive_local_target = tripoint_zero;
1158     start_engines();
1159     refresh();
1160 }
1161 
honk_horn()1162 void vehicle::honk_horn()
1163 {
1164     const bool no_power = !fuel_left( fuel_type_battery, true );
1165     bool honked = false;
1166 
1167     for( const vpart_reference &vp : get_avail_parts( "HORN" ) ) {
1168         //Only bicycle horn doesn't need electricity to work
1169         const vpart_info &horn_type = vp.info();
1170         if( ( horn_type.get_id() != vpart_id( "horn_bicycle" ) ) && no_power ) {
1171             continue;
1172         }
1173         if( !honked ) {
1174             add_msg( _( "You honk the horn!" ) );
1175             honked = true;
1176         }
1177         //Get global position of horn
1178         const tripoint horn_pos = vp.pos();
1179         //Determine sound
1180         if( horn_type.bonus >= 110 ) {
1181             //~ Loud horn sound
1182             sounds::sound( horn_pos, horn_type.bonus, sounds::sound_t::alarm, _( "HOOOOORNK!" ), false,
1183                            "vehicle", "horn_loud" );
1184         } else if( horn_type.bonus >= 80 ) {
1185             //~ Moderate horn sound
1186             sounds::sound( horn_pos, horn_type.bonus, sounds::sound_t::alarm, _( "BEEEP!" ), false, "vehicle",
1187                            "horn_medium" );
1188         } else {
1189             //~ Weak horn sound
1190             sounds::sound( horn_pos, horn_type.bonus, sounds::sound_t::alarm, _( "honk." ), false, "vehicle",
1191                            "horn_low" );
1192         }
1193     }
1194 
1195     if( !honked ) {
1196         add_msg( _( "You honk the horn, but nothing happens." ) );
1197     }
1198 }
1199 
reload_seeds(const tripoint & pos)1200 void vehicle::reload_seeds( const tripoint &pos )
1201 {
1202     Character &player_character = get_player_character();
1203     std::vector<item *> seed_inv = player_character.items_with( []( const item & itm ) {
1204         return itm.is_seed();
1205     } );
1206 
1207     auto seed_entries = iexamine::get_seed_entries( seed_inv );
1208     seed_entries.emplace( seed_entries.begin(), seed_tuple( itype_id( "null" ), _( "No seed" ), 0 ) );
1209 
1210     int seed_index = iexamine::query_seed( seed_entries );
1211 
1212     if( seed_index > 0 && seed_index < static_cast<int>( seed_entries.size() ) ) {
1213         const int count = std::get<2>( seed_entries[seed_index] );
1214         int amount = 0;
1215         const std::string popupmsg = string_format( _( "Move how many?  [Have %d] (0 to cancel)" ), count );
1216 
1217         amount = string_input_popup()
1218                  .title( popupmsg )
1219                  .width( 5 )
1220                  .only_digits( true )
1221                  .query_int();
1222 
1223         if( amount > 0 ) {
1224             int actual_amount = std::min( amount, count );
1225             itype_id seed_id = std::get<0>( seed_entries[seed_index] );
1226             std::list<item> used_seed;
1227             if( item::count_by_charges( seed_id ) ) {
1228                 used_seed = player_character.use_charges( seed_id, actual_amount );
1229             } else {
1230                 used_seed = player_character.use_amount( seed_id, actual_amount );
1231             }
1232             used_seed.front().set_age( 0_turns );
1233             //place seeds into the planter
1234             put_into_vehicle_or_drop( player_character, item_drop_reason::deliberate, used_seed, pos );
1235         }
1236     }
1237 }
1238 
beeper_sound()1239 void vehicle::beeper_sound()
1240 {
1241     // No power = no sound
1242     if( fuel_left( fuel_type_battery, true ) == 0 ) {
1243         return;
1244     }
1245 
1246     const bool odd_turn = calendar::once_every( 2_turns );
1247     for( const vpart_reference &vp : get_avail_parts( "BEEPER" ) ) {
1248         if( ( odd_turn && vp.has_feature( VPFLAG_EVENTURN ) ) ||
1249             ( !odd_turn && vp.has_feature( VPFLAG_ODDTURN ) ) ) {
1250             continue;
1251         }
1252 
1253         //~ Beeper sound
1254         sounds::sound( vp.pos(), vp.info().bonus, sounds::sound_t::alarm, _( "beep!" ), false, "vehicle",
1255                        "rear_beeper" );
1256     }
1257 }
1258 
play_music()1259 void vehicle::play_music()
1260 {
1261     Character &player_character = get_player_character();
1262     for( const vpart_reference &vp : get_enabled_parts( "STEREO" ) ) {
1263         iuse::play_music( player_character, vp.pos(), 15, 30 );
1264     }
1265 }
1266 
play_chimes()1267 void vehicle::play_chimes()
1268 {
1269     if( !one_in( 3 ) ) {
1270         return;
1271     }
1272 
1273     for( const vpart_reference &vp : get_enabled_parts( "CHIMES" ) ) {
1274         sounds::sound( vp.pos(), 40, sounds::sound_t::music,
1275                        _( "a simple melody blaring from the loudspeakers." ), false, "vehicle", "chimes" );
1276     }
1277 }
1278 
crash_terrain_around()1279 void vehicle::crash_terrain_around()
1280 {
1281     if( total_power_w() <= 0 ) {
1282         return;
1283     }
1284     map &here = get_map();
1285     for( const vpart_reference &vp : get_enabled_parts( "CRASH_TERRAIN_AROUND" ) ) {
1286         tripoint crush_target( 0, 0, -OVERMAP_LAYERS );
1287         const tripoint start_pos = vp.pos();
1288         const transform_terrain_data &ttd = vp.info().transform_terrain;
1289         for( size_t i = 0; i < eight_horizontal_neighbors.size() &&
1290              !here.inbounds_z( crush_target.z ); i++ ) {
1291             tripoint cur_pos = start_pos + eight_horizontal_neighbors.at( i );
1292             bool busy_pos = false;
1293             for( const vpart_reference &vp_tmp : get_all_parts() ) {
1294                 busy_pos |= vp_tmp.pos() == cur_pos;
1295             }
1296             for( const std::string &flag : ttd.pre_flags ) {
1297                 if( here.has_flag( flag, cur_pos ) && !busy_pos ) {
1298                     crush_target = cur_pos;
1299                     break;
1300                 }
1301             }
1302         }
1303         //target chosen
1304         if( here.inbounds_z( crush_target.z ) ) {
1305             velocity = 0;
1306             cruise_velocity = 0;
1307             here.destroy( crush_target );
1308             sounds::sound( crush_target, 500, sounds::sound_t::combat, _( "Clanggggg!" ), false,
1309                            "smash_success", "hit_vehicle" );
1310         }
1311     }
1312 }
1313 
transform_terrain()1314 void vehicle::transform_terrain()
1315 {
1316     map &here = get_map();
1317     for( const vpart_reference &vp : get_enabled_parts( "TRANSFORM_TERRAIN" ) ) {
1318         const tripoint start_pos = vp.pos();
1319         const transform_terrain_data &ttd = vp.info().transform_terrain;
1320         bool prereq_fulfilled = false;
1321         for( const std::string &flag : ttd.pre_flags ) {
1322             if( here.has_flag( flag, start_pos ) ) {
1323                 prereq_fulfilled = true;
1324                 break;
1325             }
1326         }
1327         if( prereq_fulfilled ) {
1328             const ter_id new_ter = ter_id( ttd.post_terrain );
1329             if( new_ter != t_null ) {
1330                 here.ter_set( start_pos, new_ter );
1331             }
1332             const furn_id new_furn = furn_id( ttd.post_furniture );
1333             if( new_furn != f_null ) {
1334                 here.furn_set( start_pos, new_furn );
1335             }
1336             const field_type_id new_field = field_type_id( ttd.post_field );
1337             if( new_field.id() ) {
1338                 here.add_field( start_pos, new_field, ttd.post_field_intensity, ttd.post_field_age );
1339             }
1340         } else {
1341             const int speed = std::abs( velocity );
1342             int v_damage = rng( 3, speed );
1343             damage( vp.part_index(), v_damage, damage_type::BASH, false );
1344             sounds::sound( start_pos, v_damage, sounds::sound_t::combat, _( "Clanggggg!" ), false,
1345                            "smash_success", "hit_vehicle" );
1346         }
1347     }
1348 }
1349 
operate_reaper()1350 void vehicle::operate_reaper()
1351 {
1352     map &here = get_map();
1353     for( const vpart_reference &vp : get_enabled_parts( "REAPER" ) ) {
1354         const size_t reaper_id = vp.part_index();
1355         const tripoint reaper_pos = vp.pos();
1356         const int plant_produced = rng( 1, vp.info().bonus );
1357         const int seed_produced = rng( 1, 3 );
1358         const units::volume max_pickup_volume = vp.info().size / 20;
1359         if( here.furn( reaper_pos ) != f_plant_harvest ) {
1360             continue;
1361         }
1362         // Can't use item_stack::only_item() since there might be fertilizer
1363         map_stack items = here.i_at( reaper_pos );
1364         map_stack::iterator seed = std::find_if( items.begin(), items.end(), []( const item & it ) {
1365             return it.is_seed();
1366         } );
1367         if( seed == items.end() || seed->typeId() == itype_fungal_seeds ||
1368             seed->typeId() == itype_marloss_seed ) {
1369             // Otherworldly plants, the earth-made reaper can not handle those.
1370             continue;
1371         }
1372         here.furn_set( reaper_pos, f_null );
1373         // Secure the seed type before i_clear destroys the item.
1374         const itype &seed_type = *seed->type;
1375         here.i_clear( reaper_pos );
1376         for( auto &i : iexamine::get_harvest_items(
1377                  seed_type, plant_produced, seed_produced, false ) ) {
1378             here.add_item_or_charges( reaper_pos, i );
1379         }
1380         sounds::sound( reaper_pos, rng( 10, 25 ), sounds::sound_t::combat, _( "Swish" ), false, "vehicle",
1381                        "reaper" );
1382         if( vp.has_feature( "CARGO" ) ) {
1383             for( map_stack::iterator iter = items.begin(); iter != items.end(); ) {
1384                 if( ( iter->volume() <= max_pickup_volume ) &&
1385                     add_item( reaper_id, *iter ) ) {
1386                     iter = items.erase( iter );
1387                 } else {
1388                     ++iter;
1389                 }
1390             }
1391         }
1392     }
1393 }
1394 
operate_planter()1395 void vehicle::operate_planter()
1396 {
1397     map &here = get_map();
1398     for( const vpart_reference &vp : get_enabled_parts( "PLANTER" ) ) {
1399         const size_t planter_id = vp.part_index();
1400         const tripoint loc = vp.pos();
1401         vehicle_stack v = get_items( planter_id );
1402         for( auto i = v.begin(); i != v.end(); i++ ) {
1403             if( i->is_seed() ) {
1404                 // If it is an "advanced model" then it will avoid damaging itself or becoming damaged. It's a real feature.
1405                 if( here.ter( loc ) != t_dirtmound && vp.has_feature( "ADVANCED_PLANTER" ) ) {
1406                     //then don't put the item there.
1407                     break;
1408                 } else if( here.ter( loc ) == t_dirtmound ) {
1409                     here.set( loc, t_dirt, f_plant_seed );
1410                 } else if( !here.has_flag( "PLOWABLE", loc ) ) {
1411                     //If it isn't plowable terrain, then it will most likely be damaged.
1412                     damage( planter_id, rng( 1, 10 ), damage_type::BASH, false );
1413                     sounds::sound( loc, rng( 10, 20 ), sounds::sound_t::combat, _( "Clink" ), false, "smash_success",
1414                                    "hit_vehicle" );
1415                 }
1416                 if( !i->count_by_charges() || i->charges == 1 ) {
1417                     i->set_age( 0_turns );
1418                     here.add_item( loc, *i );
1419                     v.erase( i );
1420                 } else {
1421                     item tmp = *i;
1422                     tmp.charges = 1;
1423                     tmp.set_age( 0_turns );
1424                     here.add_item( loc, tmp );
1425                     i->charges--;
1426                 }
1427                 break;
1428             }
1429         }
1430     }
1431 }
1432 
operate_scoop()1433 void vehicle::operate_scoop()
1434 {
1435     map &here = get_map();
1436     for( const vpart_reference &vp : get_enabled_parts( "SCOOP" ) ) {
1437         const size_t scoop = vp.part_index();
1438         const int chance_to_damage_item = 9;
1439         const units::volume max_pickup_volume = vp.info().size / 10;
1440         const std::array<std::string, 4> sound_msgs = {{
1441                 _( "Whirrrr" ), _( "Ker-chunk" ), _( "Swish" ), _( "Cugugugugug" )
1442             }
1443         };
1444         sounds::sound( global_part_pos3( scoop ), rng( 20, 35 ), sounds::sound_t::combat,
1445                        random_entry_ref( sound_msgs ), false, "vehicle", "scoop" );
1446         std::vector<tripoint> parts_points;
1447         for( const tripoint &current :
1448              here.points_in_radius( global_part_pos3( scoop ), 1 ) ) {
1449             parts_points.push_back( current );
1450         }
1451         for( const tripoint &position : parts_points ) {
1452             here.mop_spills( position );
1453             if( !here.has_items( position ) ) {
1454                 continue;
1455             }
1456             item *that_item_there = nullptr;
1457             map_stack items = here.i_at( position );
1458             if( here.has_flag( "SEALED", position ) ) {
1459                 // Ignore it. Street sweepers are not known for their ability to harvest crops.
1460                 continue;
1461             }
1462             for( item &it : items ) {
1463                 if( it.volume() < max_pickup_volume ) {
1464                     that_item_there = &it;
1465                     break;
1466                 }
1467             }
1468             if( !that_item_there ) {
1469                 continue;
1470             }
1471             if( one_in( chance_to_damage_item ) && that_item_there->damage() < that_item_there->max_damage() ) {
1472                 //The scoop will not destroy the item, but it may damage it a bit.
1473                 that_item_there->inc_damage( damage_type::BASH );
1474                 //The scoop gets a lot louder when breaking an item.
1475                 sounds::sound( position, rng( 10,
1476                                               that_item_there->volume() / units::legacy_volume_factor * 2 + 10 ),
1477                                sounds::sound_t::combat, _( "BEEEThump" ), false, "vehicle", "scoop_thump" );
1478             }
1479             //This attempts to add the item to the scoop inventory and if successful, removes it from the map.
1480             if( add_item( scoop, *that_item_there ) ) {
1481                 here.i_rem( position, that_item_there );
1482             } else {
1483                 break;
1484             }
1485         }
1486     }
1487 }
1488 
alarm()1489 void vehicle::alarm()
1490 {
1491     if( one_in( 4 ) ) {
1492         //first check if the alarm is still installed
1493         bool found_alarm = has_security_working();
1494 
1495         //if alarm found, make noise, else set alarm disabled
1496         if( found_alarm ) {
1497             const std::array<std::string, 4> sound_msgs = {{
1498                     _( "WHOOP WHOOP" ), _( "NEEeu NEEeu NEEeu" ), _( "BLEEEEEEP" ), _( "WREEP" )
1499                 }
1500             };
1501             sounds::sound( global_pos3(), static_cast<int>( rng( 45, 80 ) ),
1502                            sounds::sound_t::alarm,  random_entry_ref( sound_msgs ), false, "vehicle", "car_alarm" );
1503             if( one_in( 1000 ) ) {
1504                 is_alarm_on = false;
1505             }
1506         } else {
1507             is_alarm_on = false;
1508         }
1509     }
1510 }
1511 
1512 /**
1513  * Opens an openable part at the specified index. If it's a multipart, opens
1514  * all attached parts as well.
1515  * @param part_index The index in the parts list of the part to open.
1516  */
open(int part_index)1517 void vehicle::open( int part_index )
1518 {
1519     if( !part_info( part_index ).has_flag( "OPENABLE" ) ) {
1520         debugmsg( "Attempted to open non-openable part %d (%s) on a %s!", part_index,
1521                   parts[ part_index ].name(), name );
1522     } else {
1523         open_or_close( part_index, true );
1524     }
1525 }
1526 
1527 /**
1528  * Closes an openable part at the specified index. If it's a multipart, closes
1529  * all attached parts as well.
1530  * @param part_index The index in the parts list of the part to close.
1531  */
close(int part_index)1532 void vehicle::close( int part_index )
1533 {
1534     if( !part_info( part_index ).has_flag( "OPENABLE" ) ) {
1535         debugmsg( "Attempted to close non-closeable part %d (%s) on a %s!", part_index,
1536                   parts[ part_index ].name(), name );
1537     } else {
1538         open_or_close( part_index, false );
1539     }
1540 }
1541 
is_open(int part_index) const1542 bool vehicle::is_open( int part_index ) const
1543 {
1544     return parts[part_index].open;
1545 }
1546 
can_close(int part_index,Character & who)1547 bool vehicle::can_close( int part_index, Character &who )
1548 {
1549     for( auto const &vec : find_lines_of_parts( part_index, "OPENABLE" ) ) {
1550         for( auto const &partID : vec ) {
1551             const Creature *const mon = g->critter_at( global_part_pos3( parts[partID] ) );
1552             if( mon ) {
1553                 if( mon->is_player() ) {
1554                     who.add_msg_if_player( m_info, _( "There's some buffoon in the way!" ) );
1555                 } else if( mon->is_monster() ) {
1556                     // TODO: Houseflies, mosquitoes, etc shouldn't count
1557                     who.add_msg_if_player( m_info, _( "The %s is in the way!" ), mon->get_name() );
1558                 } else {
1559                     who.add_msg_if_player( m_info, _( "%s is in the way!" ), mon->disp_name() );
1560                 }
1561                 return false;
1562             }
1563         }
1564     }
1565     return true;
1566 }
1567 
open_all_at(int p)1568 void vehicle::open_all_at( int p )
1569 {
1570     std::vector<int> parts_here = parts_at_relative( parts[p].mount, true );
1571     for( auto &elem : parts_here ) {
1572         if( part_flag( elem, VPFLAG_OPENABLE ) ) {
1573             // Note that this will open multi-square and non-multipart parts in the tile. This
1574             // means that adjacent open multi-square openables can still have closed stuff
1575             // on same tile after this function returns
1576             open( elem );
1577         }
1578     }
1579 }
1580 
1581 /**
1582  * Opens or closes an openable part at the specified index based on the @opening value passed.
1583  * If it's a multipart, opens or closes all attached parts as well.
1584  * @param part_index The index in the parts list of the part to open or close.
1585  */
open_or_close(const int part_index,const bool opening)1586 void vehicle::open_or_close( const int part_index, const bool opening )
1587 {
1588     //find_lines_of_parts() doesn't return the part_index we passed, so we set it on it's own
1589     parts[part_index].open = opening;
1590     insides_dirty = true;
1591     map &here = get_map();
1592     here.set_transparency_cache_dirty( sm_pos.z );
1593     const tripoint part_location = mount_to_tripoint( parts[part_index].mount );
1594     here.set_seen_cache_dirty( part_location );
1595     const int dist = rl_dist( get_player_character().pos(), part_location );
1596     if( dist < 20 ) {
1597         sfx::play_variant_sound( opening ? "vehicle_open" : "vehicle_close",
1598                                  parts[ part_index ].info().get_id().str(), 100 - dist * 3 );
1599     }
1600     for( auto const &vec : find_lines_of_parts( part_index, "OPENABLE" ) ) {
1601         for( auto const &partID : vec ) {
1602             parts[partID].open = opening;
1603         }
1604     }
1605 
1606     coeff_air_changed = true;
1607     coeff_air_dirty = true;
1608 }
1609 
use_autoclave(int p)1610 void vehicle::use_autoclave( int p )
1611 {
1612     vehicle_stack items = get_items( p );
1613     bool filthy_items = std::any_of( items.begin(), items.end(), []( const item & i ) {
1614         return i.has_flag( json_flag_FILTHY );
1615     } );
1616 
1617     bool unpacked_items = std::any_of( items.begin(), items.end(), []( const item & i ) {
1618         return i.has_flag( STATIC( flag_id( "NO_PACKED" ) ) );
1619     } );
1620 
1621     bool cbms = std::all_of( items.begin(), items.end(), []( const item & i ) {
1622         return i.is_bionic();
1623     } );
1624 
1625     if( parts[p].enabled ) {
1626         parts[p].enabled = false;
1627         add_msg( m_bad,
1628                  _( "You turn the autoclave off before it's finished the program, and open its door." ) );
1629     } else if( items.empty() ) {
1630         add_msg( m_bad, _( "The autoclave is empty; there's no point in starting it." ) );
1631     } else if( fuel_left( itype_water ) < 8 && fuel_left( itype_water_clean ) < 8 ) {
1632         add_msg( m_bad, _( "You need 8 charges of water in the tanks of the %s for the autoclave to run." ),
1633                  name );
1634     } else if( filthy_items ) {
1635         add_msg( m_bad,
1636                  _( "You need to remove all filthy items from the autoclave to start the sterilizing cycle." ) );
1637     } else if( !cbms ) {
1638         add_msg( m_bad, _( "Only CBMs can be sterilized in an autoclave." ) );
1639     } else if( unpacked_items ) {
1640         add_msg( m_bad, _( "You should put your CBMs in autoclave pouches to keep them sterile." ) );
1641     } else {
1642         parts[p].enabled = true;
1643         for( auto &n : items ) {
1644             n.set_age( 0_turns );
1645         }
1646 
1647         if( fuel_left( itype_water ) >= 8 ) {
1648             drain( itype_water, 8 );
1649         } else {
1650             drain( itype_water_clean, 8 );
1651         }
1652 
1653         add_msg( m_good,
1654                  _( "You turn the autoclave on and it starts its cycle." ) );
1655     }
1656 }
1657 
use_washing_machine(int p)1658 void vehicle::use_washing_machine( int p )
1659 {
1660     avatar &player_character = get_avatar();
1661     // Get all the items that can be used as detergent
1662     const inventory &inv = player_character.crafting_inventory();
1663     std::vector<const item *> detergents = inv.items_with( [inv]( const item & it ) {
1664         return it.has_flag( STATIC( flag_id( "DETERGENT" ) ) ) && inv.has_charges( it.typeId(), 5 );
1665     } );
1666 
1667     vehicle_stack items = get_items( p );
1668     bool filthy_items = std::all_of( items.begin(), items.end(), []( const item & i ) {
1669         return i.has_flag( json_flag_FILTHY );
1670     } );
1671 
1672     bool cbms = std::any_of( items.begin(), items.end(), []( const item & i ) {
1673         return i.is_bionic();
1674     } );
1675 
1676     if( parts[p].enabled ) {
1677         parts[p].enabled = false;
1678         add_msg( m_bad,
1679                  _( "You turn the washing machine off before it's finished its cycle, and open its lid." ) );
1680     } else if( items.empty() ) {
1681         add_msg( m_bad,
1682                  _( "The washing machine is empty; there's no point in starting it." ) );
1683     } else if( fuel_left( itype_water ) < 24 && fuel_left( itype_water_clean ) < 24 ) {
1684         add_msg( m_bad,
1685                  _( "You need 24 charges of water in the tanks of the %s to fill the washing machine." ),
1686                  name );
1687     } else if( detergents.empty() ) {
1688         add_msg( m_bad, _( "You need 5 charges of a detergent for the washing machine." ) );
1689     } else if( !filthy_items ) {
1690         add_msg( m_bad,
1691                  _( "You need to remove all non-filthy items from the washing machine to start the washing program." ) );
1692     } else if( cbms ) {
1693         add_msg( m_bad,
1694                  _( "CBMs can't be cleaned in a washing machine.  You need to remove them." ) );
1695     } else {
1696         uilist detergent_selector;
1697         detergent_selector.text = _( "Use what detergent?" );
1698 
1699         std::vector<itype_id> det_types;
1700         for( const item *it : detergents ) {
1701             itype_id det_type = it->typeId();
1702             // If the vector does not contain the detergent type, add it
1703             if( std::find( det_types.begin(), det_types.end(), det_type ) == det_types.end() ) {
1704                 det_types.emplace_back( det_type );
1705             }
1706 
1707         }
1708         int chosen_detergent = 0;
1709         // If there's a choice to be made on what detergent to use, ask the player
1710         if( det_types.size() > 1 ) {
1711             for( size_t i = 0; i < det_types.size(); ++i ) {
1712                 detergent_selector.addentry( i, true, 0, item::nname( det_types[i] ) );
1713             }
1714             detergent_selector.addentry( UILIST_CANCEL, true, 0, _( "Cancel" ) );
1715             detergent_selector.query();
1716             chosen_detergent = detergent_selector.ret;
1717         }
1718 
1719         // If the player exits the menu, don't do anything else
1720         if( chosen_detergent == UILIST_CANCEL ) {
1721             return;
1722         }
1723 
1724         parts[p].enabled = true;
1725         for( auto &n : items ) {
1726             n.set_age( 0_turns );
1727         }
1728 
1729         if( fuel_left( itype_water ) >= 24 ) {
1730             drain( itype_water, 24 );
1731         } else {
1732             drain( itype_water_clean, 24 );
1733         }
1734 
1735         std::vector<item_comp> detergent;
1736         detergent.push_back( item_comp( det_types[chosen_detergent], 5 ) );
1737         player_character.consume_items( detergent, 1, is_crafting_component );
1738 
1739         add_msg( m_good,
1740                  _( "You pour some detergent into the washing machine, close its lid, and turn it on.  The washing machine is being filled with water from your vehicle's tanks." ) );
1741     }
1742 }
1743 
use_dishwasher(int p)1744 void vehicle::use_dishwasher( int p )
1745 {
1746     avatar &player_character = get_avatar();
1747     bool detergent_is_enough = player_character.crafting_inventory().has_charges( itype_detergent, 5 );
1748     vehicle_stack items = get_items( p );
1749     bool filthy_items = std::all_of( items.begin(), items.end(), []( const item & i ) {
1750         return i.has_flag( json_flag_FILTHY );
1751     } );
1752 
1753     std::string buffer;
1754     buffer += _( "Soft items can't be cleaned in a dishwasher; you should use a washing machine for that.  You need to remove them:" );
1755     bool soft_items = false;
1756     for( const item &it : items ) {
1757         if( it.is_soft() ) {
1758             soft_items = true;
1759             buffer += " " + it.tname();
1760         }
1761     }
1762 
1763     if( parts[p].enabled ) {
1764         parts[p].enabled = false;
1765         add_msg( m_bad,
1766                  _( "You turn the dishwasher off before it's finished its cycle, and open its lid." ) );
1767     } else if( items.empty() ) {
1768         add_msg( m_bad,
1769                  _( "The dishwasher is empty, there's no point in starting it." ) );
1770     } else if( fuel_left( itype_water ) < 24 && fuel_left( itype_water_clean ) < 24 ) {
1771         add_msg( m_bad, _( "You need 24 charges of water in the tanks of the %s to fill the dishwasher." ),
1772                  name );
1773     } else if( !detergent_is_enough ) {
1774         add_msg( m_bad, _( "You need 5 charges of a detergent for the dishwasher." ) );
1775     } else if( !filthy_items ) {
1776         add_msg( m_bad,
1777                  _( "You need to remove all non-filthy items from the dishwasher to start the washing program." ) );
1778     } else if( soft_items ) {
1779         add_msg( m_bad, buffer );
1780     } else {
1781         parts[p].enabled = true;
1782         for( auto &n : items ) {
1783             n.set_age( 0_turns );
1784         }
1785 
1786         if( fuel_left( itype_water ) >= 24 ) {
1787             drain( itype_water, 24 );
1788         } else {
1789             drain( itype_water_clean, 24 );
1790         }
1791 
1792         std::vector<item_comp> detergent;
1793         detergent.push_back( item_comp( itype_detergent, 5 ) );
1794         player_character.consume_items( detergent, 1, is_crafting_component );
1795 
1796         add_msg( m_good,
1797                  _( "You pour some detergent into the dishwasher, close its lid, and turn it on.  The dishwasher is being filled with water from your vehicle's tanks." ) );
1798     }
1799 }
1800 
use_monster_capture(int part,const tripoint & pos)1801 void vehicle::use_monster_capture( int part, const tripoint &pos )
1802 {
1803     if( parts[part].is_broken() || parts[part].removed ) {
1804         return;
1805     }
1806     item base = item( parts[part].get_base() );
1807     base.type->invoke( get_avatar(), base, pos );
1808     parts[part].set_base( base );
1809     if( base.has_var( "contained_name" ) ) {
1810         parts[part].set_flag( vehicle_part::animal_flag );
1811     } else {
1812         parts[part].remove_flag( vehicle_part::animal_flag );
1813     }
1814     invalidate_mass();
1815 }
1816 
use_harness(int part,const tripoint & pos)1817 void vehicle::use_harness( int part, const tripoint &pos )
1818 {
1819     if( parts[part].is_unavailable() || parts[part].removed ) {
1820         return;
1821     }
1822     if( !g->is_empty( pos ) ) {
1823         add_msg( m_info, _( "The harness is blocked." ) );
1824         return;
1825     }
1826     const std::function<bool( const tripoint & )> f = []( const tripoint & pnt ) {
1827         monster *mon_ptr = g->critter_at<monster>( pnt );
1828         if( mon_ptr == nullptr ) {
1829             return false;
1830         }
1831         monster &f = *mon_ptr;
1832         return ( f.friendly != 0 && ( f.has_flag( MF_PET_MOUNTABLE ) ||
1833                                       f.has_flag( MF_PET_HARNESSABLE ) ) );
1834     };
1835 
1836     const cata::optional<tripoint> pnt_ = choose_adjacent_highlight(
1837             _( "Where is the creature to harness?" ), _( "There is no creature to harness nearby." ), f,
1838             false );
1839     if( !pnt_ ) {
1840         add_msg( m_info, _( "Never mind." ) );
1841         return;
1842     }
1843     const tripoint &target = *pnt_;
1844     monster *mon_ptr = g->critter_at<monster>( target );
1845     if( mon_ptr == nullptr ) {
1846         add_msg( m_info, _( "No creature there." ) );
1847         return;
1848     }
1849     monster &m = *mon_ptr;
1850     std::string Harness_Bodytype = "HARNESS_" + m.type->bodytype;
1851     if( m.friendly == 0 ) {
1852         add_msg( m_info, _( "This creature is not friendly!" ) );
1853         return;
1854     } else if( !m.has_flag( MF_PET_MOUNTABLE ) && !m.has_flag( MF_PET_HARNESSABLE ) ) {
1855         add_msg( m_info, _( "This creature cannot be harnessed." ) );
1856         return;
1857     } else if( !part_flag( part, Harness_Bodytype ) && !part_flag( part, "HARNESS_any" ) ) {
1858         add_msg( m_info, _( "The harness is not adapted for this creature morphology." ) );
1859         return;
1860     }
1861 
1862     m.add_effect( effect_harnessed, 1_turns, true );
1863     m.setpos( pos );
1864     //~ %1$s: monster name, %2$s: vehicle name
1865     add_msg( m_info, _( "You harness your %1$s to %2$s." ), m.get_name(), disp_name() );
1866     if( m.has_effect( effect_tied ) ) {
1867         add_msg( m_info, _( "You untie your %s." ), m.get_name() );
1868         m.remove_effect( effect_tied );
1869         if( m.tied_item ) {
1870             get_player_character().i_add( *m.tied_item );
1871             m.tied_item.reset();
1872         }
1873     }
1874 }
1875 
use_bike_rack(int part)1876 void vehicle::use_bike_rack( int part )
1877 {
1878     if( parts[part].is_unavailable() || parts[part].removed ) {
1879         return;
1880     }
1881     std::vector<std::vector <int>> racks_parts = find_lines_of_parts( part, "BIKE_RACK_VEH" );
1882     if( racks_parts.empty() ) {
1883         return;
1884     }
1885 
1886     // check if we're storing a vehicle on this rack
1887     std::vector<std::vector<int>> carried_vehicles;
1888     std::vector<std::vector<int>> carrying_racks;
1889     bool found_vehicle = false;
1890     bool full_rack = true;
1891     for( const std::vector<int> &rack_parts : racks_parts ) {
1892         std::vector<int> carried_parts;
1893         std::vector<int> carry_rack;
1894         size_t carry_size = 0;
1895         std::string cur_vehicle;
1896 
1897         const auto add_vehicle = []( std::vector<int> &carried_parts,
1898                                      std::vector<std::vector<int>> &carried_vehicles,
1899                                      std::vector<int> &carry_rack,
1900         std::vector<std::vector<int>> &carrying_racks ) {
1901             if( !carry_rack.empty() ) {
1902                 carrying_racks.emplace_back( carry_rack );
1903                 carried_vehicles.emplace_back( carried_parts );
1904                 carry_rack.clear();
1905                 carried_parts.clear();
1906             }
1907         };
1908 
1909         for( const int &rack_part : rack_parts ) {
1910             // skip parts that aren't carrying anything
1911             if( !parts[ rack_part ].has_flag( vehicle_part::carrying_flag ) ) {
1912                 add_vehicle( carried_parts, carried_vehicles, carry_rack, carrying_racks );
1913                 cur_vehicle.clear();
1914                 continue;
1915             }
1916             for( const point &mount_dir : five_cardinal_directions ) {
1917                 point near_loc = parts[ rack_part ].mount + mount_dir;
1918                 std::vector<int> near_parts = parts_at_relative( near_loc, true );
1919                 if( near_parts.empty() ) {
1920                     continue;
1921                 }
1922                 if( parts[ near_parts[ 0 ] ].has_flag( vehicle_part::carried_flag ) ) {
1923                     carry_size += 1;
1924                     found_vehicle = true;
1925                     // found a carried vehicle part
1926                     if( parts[ near_parts[ 0 ] ].carried_name() != cur_vehicle ) {
1927                         add_vehicle( carried_parts, carried_vehicles, carry_rack, carrying_racks );
1928                         cur_vehicle = parts[ near_parts[ 0 ] ].carried_name();
1929                     }
1930                     for( const int &carried_part : near_parts ) {
1931                         carried_parts.push_back( carried_part );
1932                     }
1933                     carry_rack.push_back( rack_part );
1934                     // we're not adjacent to another carried vehicle on this rack
1935                     break;
1936                 }
1937             }
1938         }
1939 
1940         add_vehicle( carried_parts, carried_vehicles, carry_rack, carrying_racks );
1941         full_rack &= carry_size == rack_parts.size();
1942     }
1943     int unload_carried = full_rack ? 0 : -1;
1944     bool success = false;
1945 
1946     validate_carried_vehicles( carried_vehicles );
1947     validate_carried_vehicles( carrying_racks );
1948 
1949     if( found_vehicle && !full_rack ) {
1950         uilist rack_menu;
1951         rack_menu.addentry( 0, true, '0', _( "Load a vehicle on the rack" ) );
1952         for( size_t i = 0; i < carried_vehicles.size(); i++ ) {
1953             rack_menu.addentry( i + 1, true, '1' + i,
1954                                 string_format( _( "Remove the %s from the rack" ),
1955                                                parts[ carried_vehicles[i].front() ].carried_name() ) );
1956         }
1957         rack_menu.query();
1958         unload_carried = rack_menu.ret - 1;
1959     }
1960     if( unload_carried > -1 ) {
1961         success = remove_carried_vehicle( carried_vehicles[unload_carried] );
1962         if( success ) {
1963             for( const int &rack_part : carrying_racks[unload_carried] ) {
1964                 parts[ rack_part ].remove_flag( vehicle_part::carrying_flag );
1965                 parts[rack_part].remove_flag( vehicle_part::tracked_flag );
1966             }
1967         }
1968     } else {
1969         success = try_to_rack_nearby_vehicle( racks_parts );
1970     }
1971     if( success ) {
1972         map &here = get_map();
1973         here.invalidate_map_cache( here.get_abs_sub().z );
1974         here.rebuild_vehicle_level_caches();
1975     }
1976 }
1977 
1978 /*
1979 * Todo: find a way to split and rewrite use_bikerack so that this check is no longer necessary
1980 */
validate_carried_vehicles(std::vector<std::vector<int>> & carried_vehicles)1981 void vehicle::validate_carried_vehicles( std::vector<std::vector<int>>
1982         &carried_vehicles )
1983 {
1984     std::sort( carried_vehicles.begin(), carried_vehicles.end(), []( const std::vector<int> &a,
1985     const std::vector<int> &b ) {
1986         return a.size() < b.size();
1987     } );
1988 
1989     std::vector<std::vector<int>>::iterator it = carried_vehicles.begin();
1990     while( it != carried_vehicles.end() ) {
1991         for( std::vector<std::vector<int>>::iterator it2 = it + 1; it2 < carried_vehicles.end(); it2++ ) {
1992             if( std::search( ( *it2 ).begin(), ( *it2 ).end(), ( *it ).begin(),
1993                              ( *it ).end() ) != ( *it2 ).end() ) {
1994                 it = carried_vehicles.erase( it-- );
1995             }
1996         }
1997         it++;
1998     }
1999 }
2000 
2001 
form_inventory(inventory & inv)2002 void vpart_position::form_inventory( inventory &inv )
2003 {
2004     const int veh_battery = vehicle().fuel_left( itype_id( "battery" ), true );
2005     const cata::optional<vpart_reference> vp_faucet = part_with_tool( itype_water_faucet );
2006     const cata::optional<vpart_reference> vp_cargo = part_with_feature( "CARGO", true );
2007 
2008     if( vp_cargo ) {
2009         const vehicle_stack items = vehicle().get_items( vp_cargo->part_index() );
2010         inv += std::list<item>( items.begin(), items.end() );
2011     }
2012 
2013     // HACK: water_faucet pseudo tool gives access to liquids in tanks
2014     if( vp_faucet && inv.provide_pseudo_item( itype_water_faucet, 0 ) ) {
2015         for( const std::pair<const itype_id, int> &it : vehicle().fuels_left() ) {
2016             item fuel( it.first, calendar::turn_zero );
2017             if( fuel.made_of( phase_id::LIQUID ) ) {
2018                 fuel.charges = it.second;
2019                 inv.add_item( fuel );
2020             }
2021         }
2022     }
2023 
2024     for( const std::pair<itype_id, int> &tool : get_tools() ) {
2025         inv.provide_pseudo_item( tool.first, veh_battery );
2026     }
2027 }
2028 
2029 // Handles interactions with a vehicle in the examine menu.
interact_with(const vpart_position & vp)2030 void vehicle::interact_with( const vpart_position &vp )
2031 {
2032     map &here = get_map();
2033     avatar &player_character = get_avatar();
2034     const bool has_items_on_ground = here.sees_some_items( vp.pos(), player_character );
2035     const bool items_are_sealed = here.has_flag( "SEALED", vp.pos() );
2036     const turret_data turret = turret_query( vp.pos() );
2037     const cata::optional<vpart_reference> vp_curtain = vp.avail_part_with_feature( "CURTAIN" );
2038     const cata::optional<vpart_reference> vp_faucet = vp.part_with_tool( itype_water_faucet );
2039     const cata::optional<vpart_reference> vp_purify = vp.part_with_tool( itype_water_purifier );
2040     const cata::optional<vpart_reference> vp_controls = vp.avail_part_with_feature( "CONTROLS" );
2041     const cata::optional<vpart_reference> vp_electronics =
2042         vp.avail_part_with_feature( "CTRL_ELECTRONIC" );
2043     const cata::optional<vpart_reference> vp_autoclave = vp.avail_part_with_feature( "AUTOCLAVE" );
2044     const cata::optional<vpart_reference> vp_washing_machine =
2045         vp.avail_part_with_feature( "WASHING_MACHINE" );
2046     const cata::optional<vpart_reference> vp_dishwasher = vp.avail_part_with_feature( "DISHWASHER" );
2047     const cata::optional<vpart_reference> vp_monster_capture =
2048         vp.avail_part_with_feature( "CAPTURE_MONSTER_VEH" );
2049     const cata::optional<vpart_reference> vp_bike_rack = vp.avail_part_with_feature( "BIKE_RACK_VEH" );
2050     const cata::optional<vpart_reference> vp_harness = vp.avail_part_with_feature( "ANIMAL_CTRL" );
2051     const cata::optional<vpart_reference> vp_workbench = vp.avail_part_with_feature( "WORKBENCH" );
2052     const cata::optional<vpart_reference> vp_cargo = vp.part_with_feature( "CARGO", false );
2053     const bool has_cargo = vp_cargo && !get_items( vp_cargo->part_index() ).empty();
2054     const bool has_planter = vp.avail_part_with_feature( "PLANTER" ) ||
2055                              vp.avail_part_with_feature( "ADVANCED_PLANTER" );
2056 
2057     enum {
2058         EXAMINE, TRACK, HANDBRAKE, CONTROL, CONTROL_ELECTRONICS, GET_ITEMS, GET_ITEMS_ON_GROUND, FOLD_VEHICLE, UNLOAD_TURRET,
2059         RELOAD_TURRET, FILL_CONTAINER, DRINK, PURIFY_TANK, USE_AUTOCLAVE, USE_WASHMACHINE,
2060         USE_DISHWASHER, USE_MONSTER_CAPTURE, USE_BIKE_RACK, USE_HARNESS, RELOAD_PLANTER, WORKBENCH, PEEK_CURTAIN, TOOLS_OFFSET
2061     };
2062     uilist selectmenu;
2063 
2064     selectmenu.addentry( EXAMINE, true, 'e', _( "Examine vehicle" ) );
2065     selectmenu.addentry( TRACK, true, keybind( "TOGGLE_TRACKING" ), tracking_toggle_string() );
2066     if( vp_controls ) {
2067         selectmenu.addentry( HANDBRAKE, true, 'h', _( "Pull handbrake" ) );
2068         selectmenu.addentry( CONTROL, true, 'v', _( "Control vehicle" ) );
2069     }
2070     if( vp_electronics ) {
2071         selectmenu.addentry( CONTROL_ELECTRONICS, true, keybind( "CONTROL_MANY_ELECTRONICS" ),
2072                              _( "Control multiple electronics" ) );
2073     }
2074 
2075     // retrieves a list of tools at that vehicle part
2076     // first is tool itype_id, second is the hotkey
2077     const std::vector<std::pair<itype_id, int>> veh_tools = vp.get_tools();
2078 
2079     for( size_t i = 0; i < veh_tools.size(); i++ ) {
2080         const std::pair<itype_id, int> pair = veh_tools[i];
2081         const itype &tool = pair.first.obj();
2082         if( pair.second == -1 || !tool.has_use() ) {
2083             continue;
2084         }
2085 
2086         const bool enabled = fuel_left( itype_battery, true ) >= tool.charges_to_use();
2087         selectmenu.addentry( TOOLS_OFFSET + i, enabled, pair.second, _( "Use " ) + tool.nname( 1 ) );
2088     }
2089 
2090     if( vp_autoclave ) {
2091         selectmenu.addentry( USE_AUTOCLAVE, true, 'a', vp_autoclave->part().enabled
2092                              ? _( "Deactivate the autoclave" )
2093                              : _( "Activate the autoclave (1.5 hours)" ) );
2094     }
2095     if( vp_washing_machine ) {
2096         selectmenu.addentry( USE_WASHMACHINE, true, 'W', vp_washing_machine->part().enabled
2097                              ? _( "Deactivate the washing machine" )
2098                              : _( "Activate the washing machine (1.5 hours)" ) );
2099     }
2100     if( vp_dishwasher ) {
2101         selectmenu.addentry( USE_DISHWASHER, true, 'D', vp_dishwasher->part().enabled
2102                              ? _( "Deactivate the dishwasher" )
2103                              : _( "Activate the dishwasher (1.5 hours)" ) );
2104     }
2105     if( has_cargo &&
2106         ( !vp_autoclave || !vp_autoclave->part().enabled ) &&
2107         ( !vp_washing_machine || !vp_washing_machine->part().enabled ) &&
2108         ( !vp_dishwasher || !vp_dishwasher->part().enabled ) ) {
2109         selectmenu.addentry( GET_ITEMS, true, 'g', _( "Get items" ) );
2110     }
2111     if( has_items_on_ground && !items_are_sealed ) {
2112         selectmenu.addentry( GET_ITEMS_ON_GROUND, true, 'i', _( "Get items on the ground" ) );
2113     }
2114     if( ( is_foldable() || tags.count( "convertible" ) > 0 ) && g->remoteveh() != this ) {
2115         selectmenu.addentry( FOLD_VEHICLE, true, 'f', _( "Fold vehicle" ) );
2116     }
2117     if( turret.can_unload() ) {
2118         selectmenu.addentry( UNLOAD_TURRET, true, 'u', _( "Unload %s" ), turret.name() );
2119     }
2120     if( turret.can_reload() ) {
2121         selectmenu.addentry( RELOAD_TURRET, true, 'r', _( "Reload %s" ), turret.name() );
2122     }
2123     if( vp_curtain && !vp_curtain->part().open ) {
2124         selectmenu.addentry( PEEK_CURTAIN, true, 'p', _( "Peek through the closed curtains" ) );
2125     }
2126     if( vp_faucet && fuel_left( itype_water_clean ) > 0 ) {
2127         selectmenu.addentry( FILL_CONTAINER, true, 'c', _( "Fill a container with water" ) );
2128         selectmenu.addentry( DRINK, true, 'd', _( "Have a drink" ) );
2129     }
2130     if( vp_purify ) {
2131         bool can_purify = fuel_left( itype_water ) &&
2132                           fuel_left( itype_battery, true ) >= itype_water_purifier.obj().charges_to_use();
2133         selectmenu.addentry( PURIFY_TANK, can_purify, 'P', _( "Purify water in vehicle tank" ) );
2134     }
2135     if( vp_monster_capture ) {
2136         selectmenu.addentry( USE_MONSTER_CAPTURE, true, 'G', _( "Capture or release a creature" ) );
2137     }
2138     if( vp_bike_rack ) {
2139         selectmenu.addentry( USE_BIKE_RACK, true, 'R', _( "Load or unload a vehicle" ) );
2140     }
2141     if( vp_harness ) {
2142         selectmenu.addentry( USE_HARNESS, true, 'H', _( "Harness an animal" ) );
2143     }
2144     if( has_planter ) {
2145         selectmenu.addentry( RELOAD_PLANTER, true, 's', _( "Reload seed drill with seeds" ) );
2146     }
2147     if( vp_workbench ) {
2148         selectmenu.addentry( WORKBENCH, true,
2149                              hotkey_for_action( ACTION_CRAFT, /*maximum_modifier_count=*/1 ),
2150                              string_format( _( "Craft at the %s" ), vp_workbench->part().name() ) );
2151     }
2152 
2153     int choice;
2154     if( selectmenu.entries.size() == 1 ) {
2155         choice = selectmenu.entries.front().retval;
2156     } else {
2157         selectmenu.text = _( "Select an action" );
2158         selectmenu.query();
2159         choice = selectmenu.ret;
2160     }
2161     if( choice != EXAMINE && choice != TRACK && choice != GET_ITEMS_ON_GROUND ) {
2162         if( !handle_potential_theft( dynamic_cast<player &>( player_character ) ) ) {
2163             return;
2164         }
2165     }
2166 
2167     auto tool_wants_battery = []( const itype_id & type ) {
2168         item tool( type, calendar::turn_zero );
2169         item mag( tool.magazine_default() );
2170         mag.contents.clear_items();
2171 
2172         return tool.contents.insert_item( mag, item_pocket::pocket_type::MAGAZINE_WELL ).success() &&
2173                tool.ammo_capacity( ammotype( "battery" ) ) > 0;
2174     };
2175 
2176     auto use_vehicle_tool = [&]( const itype_id & tool_type ) {
2177         item pseudo( tool_type, calendar::turn_zero );
2178         pseudo.set_flag( STATIC( flag_id( "PSEUDO" ) ) );
2179         if( !tool_wants_battery( tool_type ) ) {
2180             player_character.invoke_item( &pseudo );
2181             return true;
2182         }
2183         if( fuel_left( itype_battery, true ) < pseudo.ammo_required() ) {
2184             return false;
2185         }
2186         // TODO: Figure out this comment: Pseudo items don't have a magazine in it, and they don't need it anymore.
2187         item pseudo_magazine( pseudo.magazine_default() );
2188         pseudo_magazine.contents.clear_items(); // no initial ammo
2189         pseudo.put_in( pseudo_magazine, item_pocket::pocket_type::MAGAZINE_WELL );
2190         const int capacity = pseudo.ammo_capacity( ammotype( "battery" ) );
2191         const int qty = capacity - discharge_battery( capacity );
2192         pseudo.ammo_set( itype_battery, qty );
2193         player_character.invoke_item( &pseudo );
2194         player_activity &act = player_character.activity;
2195 
2196         // HACK: Evil hack incoming
2197         if( act.id() == ACT_REPAIR_ITEM &&
2198             ( tool_type == itype_welder || tool_type == itype_soldering_iron ) ) {
2199             act.index = INT_MIN; // tell activity the item doesn't really exist
2200             act.coords.push_back( vp.pos() ); // tell it to search for the tool on `pos`
2201             act.str_values.push_back( tool_type.str() ); // specific tool on the rig
2202         }
2203 
2204         charge_battery( pseudo.ammo_remaining() );
2205         return true;
2206     };
2207 
2208     switch( choice ) {
2209         case USE_BIKE_RACK: {
2210             use_bike_rack( vp_bike_rack->part_index() );
2211             return;
2212         }
2213         case USE_HARNESS: {
2214             use_harness( vp_harness->part_index(), vp.pos() );
2215             return;
2216         }
2217         case USE_MONSTER_CAPTURE: {
2218             use_monster_capture( vp_monster_capture->part_index(), vp.pos() );
2219             return;
2220         }
2221         case PEEK_CURTAIN: {
2222             add_msg( _( "You carefully peek through the curtains." ) );
2223             g->peek( vp.pos() );
2224             return;
2225         }
2226         case USE_AUTOCLAVE: {
2227             use_autoclave( vp_autoclave->part_index() );
2228             return;
2229         }
2230         case USE_WASHMACHINE: {
2231             use_washing_machine( vp_washing_machine->part_index() );
2232             return;
2233         }
2234         case USE_DISHWASHER: {
2235             use_dishwasher( vp_dishwasher->part_index() );
2236             return;
2237         }
2238         case FILL_CONTAINER: {
2239             player_character.siphon( *this, itype_water_clean );
2240             return;
2241         }
2242         case DRINK: {
2243             item water( itype_water_clean, calendar::turn_zero );
2244             if( player_character.can_consume( water ) ) {
2245                 player_character.assign_activity( player_activity( consume_activity_actor( water ) ) );
2246                 drain( itype_water_clean, 1 );
2247             }
2248             return;
2249         }
2250         case PURIFY_TANK: {
2251             auto sel = []( const vehicle_part & pt ) {
2252                 return pt.is_tank() && pt.ammo_current() == itype_water;
2253             };
2254             std::string title = string_format( _( "Purify <color_%s>water</color> in tank" ),
2255                                                get_all_colors().get_name( item::find_type( itype_water )->color ) );
2256             vehicle_part &tank = veh_interact::select_part( *this, sel, title );
2257             if( tank ) {
2258                 int cost = item::find_type( itype_water_purifier )->charges_to_use();
2259                 if( fuel_left( itype_battery, true ) < tank.ammo_remaining() * cost ) {
2260                     //~ $1 - vehicle name, $2 - part name
2261                     add_msg( m_bad, _( "Insufficient power to purify the contents of the %1$s's %2$s" ),
2262                              name, tank.name() );
2263                 } else {
2264                     //~ $1 - vehicle name, $2 - part name
2265                     add_msg( m_good, _( "You purify the contents of the %1$s's %2$s" ), name, tank.name() );
2266                     discharge_battery( tank.ammo_remaining() * cost );
2267                     tank.ammo_set( itype_water_clean, tank.ammo_remaining() );
2268                 }
2269             }
2270             return;
2271         }
2272         case UNLOAD_TURRET: {
2273             item_location loc = turret.base();
2274             player_character.unload( loc );
2275             return;
2276         }
2277         case RELOAD_TURRET: {
2278             item::reload_option opt = player_character.select_ammo( *turret.base(), true );
2279             std::vector<item_location> targets;
2280             if( opt ) {
2281                 const int moves = opt.moves();
2282                 targets.emplace_back( turret.base() );
2283                 targets.push_back( std::move( opt.ammo ) );
2284                 player_character.assign_activity( player_activity( reload_activity_actor( moves, opt.qty(),
2285                                                   targets ) ) );
2286             }
2287             return;
2288         }
2289         case FOLD_VEHICLE: {
2290             fold_up();
2291             return;
2292         }
2293         case HANDBRAKE: {
2294             handbrake();
2295             return;
2296         }
2297         case CONTROL: {
2298             use_controls( vp.pos() );
2299             return;
2300         }
2301         case CONTROL_ELECTRONICS: {
2302             control_electronics();
2303             return;
2304         }
2305         case EXAMINE: {
2306             g->exam_vehicle( *this );
2307             return;
2308         }
2309         case TRACK: {
2310             toggle_tracking( );
2311             return;
2312         }
2313         case GET_ITEMS_ON_GROUND: {
2314             Pickup::pick_up( vp.pos(), 0, Pickup::from_ground );
2315             return;
2316         }
2317         case GET_ITEMS: {
2318             if( has_cargo ) {
2319                 Pickup::pick_up( vp.pos(), 0, Pickup::from_cargo );
2320             } else {
2321                 Pickup::pick_up( vp.pos(), 0, Pickup::from_ground );
2322             }
2323             return;
2324         }
2325         case RELOAD_PLANTER: {
2326             reload_seeds( vp.pos() );
2327             return;
2328         }
2329         case WORKBENCH: {
2330             iexamine::workbench_internal( player_character, vp.pos(), vp_workbench );
2331             return;
2332         }
2333         default: {
2334             if( choice >= TOOLS_OFFSET ) {
2335                 use_vehicle_tool( veh_tools[choice - TOOLS_OFFSET].first );
2336             }
2337             return;
2338         }
2339     }
2340 }
2341