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 ¤t :
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 = ⁢
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