1 #include "vehicle.h" // IWYU pragma: associated
2 #include "vpart_position.h" // IWYU pragma: associated
3 #include "vpart_range.h" // IWYU pragma: associated
4 
5 #include <algorithm>
6 #include <array>
7 #include <cmath>
8 #include <complex>
9 #include <cstdint>
10 #include <cstdlib>
11 #include <list>
12 #include <memory>
13 #include <numeric>
14 #include <queue>
15 #include <set>
16 #include <sstream>
17 #include <tuple>
18 #include <unordered_map>
19 #include <unordered_set>
20 
21 #include "activity_type.h"
22 #include "avatar.h"
23 #include "bionics.h"
24 #include "cata_assert.h"
25 #include "cata_utility.h"
26 #include "character.h"
27 #include "clzones.h"
28 #include "colony.h"
29 #include "coordinate_conversions.h"
30 #include "creature.h"
31 #include "cuboid_rectangle.h"
32 #include "debug.h"
33 #include "enum_traits.h"
34 #include "enums.h"
35 #include "event.h"
36 #include "event_bus.h"
37 #include "explosion.h"
38 #include "faction.h"
39 #include "field_type.h"
40 #include "flag.h"
41 #include "game.h"
42 #include "item.h"
43 #include "item_contents.h"
44 #include "item_group.h"
45 #include "item_pocket.h"
46 #include "itype.h"
47 #include "json.h"
48 #include "make_static.h"
49 #include "map.h"
50 #include "map_iterator.h"
51 #include "mapbuffer.h"
52 #include "mapdata.h"
53 #include "material.h"
54 #include "math_defines.h"
55 #include "messages.h"
56 #include "monster.h"
57 #include "move_mode.h"
58 #include "npc.h"
59 #include "options.h"
60 #include "output.h"
61 #include "overmapbuffer.h"
62 #include "pimpl.h"
63 #include "player.h"
64 #include "player_activity.h"
65 #include "ret_val.h"
66 #include "rng.h"
67 #include "sounds.h"
68 #include "string_formatter.h"
69 #include "submap.h"
70 #include "translations.h"
71 #include "units_utility.h"
72 #include "value_ptr.h"
73 #include "veh_type.h"
74 #include "vehicle_selector.h"
75 #include "weather.h"
76 #include "weather_gen.h"
77 #include "weather_type.h"
78 
79 /*
80  * Speed up all those if ( blarg == "structure" ) statements that are used everywhere;
81  *   assemble "structure" once here instead of repeatedly later.
82  */
83 static const std::string part_location_structure( "structure" );
84 static const std::string part_location_center( "center" );
85 static const std::string part_location_onroof( "on_roof" );
86 
87 static const itype_id fuel_type_animal( "animal" );
88 static const itype_id fuel_type_battery( "battery" );
89 static const itype_id fuel_type_muscle( "muscle" );
90 static const itype_id fuel_type_plutonium_cell( "plut_cell" );
91 static const itype_id fuel_type_wind( "wind" );
92 static const itype_id fuel_type_mana( "mana" );
93 
94 static const fault_id fault_engine_immobiliser( "fault_engine_immobiliser" );
95 
96 static const activity_id ACT_VEHICLE( "ACT_VEHICLE" );
97 
98 static const bionic_id bio_jointservo( "bio_jointservo" );
99 
100 static const proficiency_id proficiency_prof_aircraft_mechanic( "prof_aircraft_mechanic" );
101 
102 static const efftype_id effect_harnessed( "harnessed" );
103 static const efftype_id effect_winded( "winded" );
104 
105 static const itype_id itype_battery( "battery" );
106 static const itype_id itype_plut_cell( "plut_cell" );
107 static const itype_id itype_water( "water" );
108 static const itype_id itype_water_clean( "water_clean" );
109 
110 static const itype_id itype_water_purifier( "water_purifier" );
111 
112 static const std::string flag_E_COMBUSTION( "E_COMBUSTION" );
113 
114 static bool is_sm_tile_outside( const tripoint &real_global_pos );
115 static bool is_sm_tile_over_water( const tripoint &real_global_pos );
116 
117 // 1 kJ per battery charge
118 static const int bat_energy_j = 1000;
119 
120 // For reference what each function is supposed to do, see their implementation in
121 // @ref DefaultRemovePartHandler. Add compatible code for it into @ref MapgenRemovePartHandler,
122 // if needed.
123 class RemovePartHandler
124 {
125     public:
126         virtual ~RemovePartHandler() = default;
127 
128         virtual void unboard( const tripoint &loc ) = 0;
129         virtual void add_item_or_charges( const tripoint &loc, item it, bool permit_oob ) = 0;
130         virtual void set_transparency_cache_dirty( int z ) = 0;
131         virtual void set_floor_cache_dirty( int z ) = 0;
132         virtual void removed( vehicle &veh, int part ) = 0;
133         virtual void spawn_animal_from_part( item &base, const tripoint &loc ) = 0;
134 };
135 
136 class DefaultRemovePartHandler : public RemovePartHandler
137 {
138     public:
139         ~DefaultRemovePartHandler() override = default;
140 
unboard(const tripoint & loc)141         void unboard( const tripoint &loc ) override {
142             get_map().unboard_vehicle( loc );
143         }
add_item_or_charges(const tripoint & loc,item it,bool)144         void add_item_or_charges( const tripoint &loc, item it, bool /*permit_oob*/ ) override {
145             get_map().add_item_or_charges( loc, std::move( it ) );
146         }
set_transparency_cache_dirty(const int z)147         void set_transparency_cache_dirty( const int z ) override {
148             map &here = get_map();
149             here.set_transparency_cache_dirty( z );
150             here.set_seen_cache_dirty( tripoint_zero );
151         }
set_floor_cache_dirty(const int z)152         void set_floor_cache_dirty( const int z ) override {
153             get_map().set_floor_cache_dirty( z );
154         }
removed(vehicle & veh,const int part)155         void removed( vehicle &veh, const int part ) override {
156             avatar &player_character = get_avatar();
157             // If the player is currently working on the removed part, stop them as it's futile now.
158             const player_activity &act = player_character.activity;
159             map &here = get_map();
160             if( act.id() == ACT_VEHICLE && act.moves_left > 0 && act.values.size() > 6 ) {
161                 if( veh_pointer_or_null( here.veh_at( tripoint( act.values[0], act.values[1],
162                                                       player_character.posz() ) ) ) == &veh ) {
163                     if( act.values[6] >= part ) {
164                         player_character.cancel_activity();
165                         add_msg( m_info, _( "The vehicle part you were working on has gone!" ) );
166                     }
167                 }
168             }
169             // TODO: maybe do this for all the nearby NPCs as well?
170 
171             if( player_character.get_grab_type() == object_type::VEHICLE &&
172                 player_character.pos() + player_character.grab_point == veh.global_part_pos3( part ) ) {
173                 if( veh.parts_at_relative( veh.part( part ).mount, false ).empty() ) {
174                     add_msg( m_info, _( "The vehicle part you were holding has been destroyed!" ) );
175                     player_character.grab( object_type::NONE );
176                 }
177             }
178 
179             here.dirty_vehicle_list.insert( &veh );
180         }
spawn_animal_from_part(item & base,const tripoint & loc)181         void spawn_animal_from_part( item &base, const tripoint &loc ) override {
182             base.release_monster( loc, 1 );
183         }
184 };
185 
186 class MapgenRemovePartHandler : public RemovePartHandler
187 {
188     private:
189         map &m;
190 
191     public:
MapgenRemovePartHandler(map & m)192         explicit MapgenRemovePartHandler( map &m ) : m( m ) { }
193 
194         ~MapgenRemovePartHandler() override = default;
195 
unboard(const tripoint &)196         void unboard( const tripoint &/*loc*/ ) override {
197             debugmsg( "Tried to unboard during mapgen!" );
198             // Ignored. Will almost certainly not be called anyway, because
199             // there are no creatures that could have been mounted during mapgen.
200         }
add_item_or_charges(const tripoint & loc,item it,bool permit_oob)201         void add_item_or_charges( const tripoint &loc, item it, bool permit_oob ) override {
202             if( !m.inbounds( loc ) ) {
203                 if( !permit_oob ) {
204                     debugmsg( "Tried to put item %s on invalid tile %s during mapgen!",
205                               it.tname(), loc.to_string() );
206                 }
207                 tripoint copy = loc;
208                 m.clip_to_bounds( copy );
209                 cata_assert( m.inbounds( copy ) ); // prevent infinite recursion
210                 add_item_or_charges( copy, std::move( it ), false );
211                 return;
212             }
213             m.add_item_or_charges( loc, std::move( it ) );
214         }
set_transparency_cache_dirty(const int)215         void set_transparency_cache_dirty( const int /*z*/ ) override {
216             // Ignored for now. We don't initialize the transparency cache in mapgen anyway.
217         }
set_floor_cache_dirty(const int)218         void set_floor_cache_dirty( const int /*z*/ ) override {
219             // Ignored for now. We don't initialize the floor cache in mapgen anyway.
220         }
removed(vehicle & veh,const int)221         void removed( vehicle &veh, const int /*part*/ ) override {
222             // TODO: check if this is necessary, it probably isn't during mapgen
223             m.dirty_vehicle_list.insert( &veh );
224         }
spawn_animal_from_part(item &,const tripoint &)225         void spawn_animal_from_part( item &/*base*/, const tripoint &/*loc*/ ) override {
226             debugmsg( "Tried to spawn animal from vehicle part during mapgen!" );
227             // Ignored. The base item will not be changed and will spawn as is:
228             // still containing the animal.
229             // This should not happend during mapgen anyway.
230             // TODO: *if* this actually happens: create a spawn point for the animal instead.
231         }
232 };
233 
234 // Vehicle stack methods.
erase(vehicle_stack::const_iterator it)235 vehicle_stack::iterator vehicle_stack::erase( vehicle_stack::const_iterator it )
236 {
237     return myorigin->remove_item( part_num, it );
238 }
239 
insert(const item & newitem)240 void vehicle_stack::insert( const item &newitem )
241 {
242     myorigin->add_item( part_num, newitem );
243 }
244 
max_volume() const245 units::volume vehicle_stack::max_volume() const
246 {
247     if( myorigin->part_flag( part_num, "CARGO" ) && !myorigin->part( part_num ).is_broken() ) {
248         // Set max volume for vehicle cargo to prevent integer overflow
249         return std::min( myorigin->part( part_num ).info().size, 10000_liter );
250     }
251     return 0_ml;
252 }
253 
254 // Vehicle class methods.
255 
vehicle(const vproto_id & type_id,int init_veh_fuel,int init_veh_status)256 vehicle::vehicle( const vproto_id &type_id, int init_veh_fuel,
257                   int init_veh_status ): type( type_id )
258 {
259     turn_dir = 0_degrees;
260     face.init( 0_degrees );
261     move.init( 0_degrees );
262     of_turn_carry = 0;
263 
264     if( !type.str().empty() && type.is_valid() ) {
265         const vehicle_prototype &proto = type.obj();
266         // Copy the already made vehicle. The blueprint is created when the json data is loaded
267         // and is guaranteed to be valid (has valid parts etc.).
268         *this = *proto.blueprint;
269         // The game language may have changed after the blueprint was created,
270         // so translated the prototype name again.
271         name = proto.name.translated();
272         init_state( init_veh_fuel, init_veh_status );
273     }
274     precalc_mounts( 0, pivot_rotation[0], pivot_anchor[0] );
275     refresh();
276 }
277 
vehicle()278 vehicle::vehicle() : vehicle( vproto_id() )
279 {
280     sm_pos = tripoint_zero;
281 }
282 
283 vehicle::~vehicle() = default;
284 
player_in_control(const Character & p) const285 bool vehicle::player_in_control( const Character &p ) const
286 {
287     // Debug switch to prevent vehicles from skidding
288     // without having to place the player in them.
289     if( tags.count( "IN_CONTROL_OVERRIDE" ) ) {
290         return true;
291     }
292 
293     const optional_vpart_position vp = get_map().veh_at( p.pos() );
294     if( vp && &vp->vehicle() == this &&
295         p.controlling_vehicle &&
296         ( ( part_with_feature( vp->part_index(), "CONTROL_ANIMAL", true ) >= 0 &&
297             has_engine_type( fuel_type_animal, false ) && has_harnessed_animal() ) ||
298           ( part_with_feature( vp->part_index(), VPFLAG_CONTROLS, false ) >= 0 ) )
299       ) {
300         return true;
301     }
302 
303     return remote_controlled( p );
304 }
305 
remote_controlled(const Character & p) const306 bool vehicle::remote_controlled( const Character &p ) const
307 {
308     vehicle *veh = g->remoteveh();
309     if( veh != this ) {
310         return false;
311     }
312 
313     for( const vpart_reference &vp : get_avail_parts( "REMOTE_CONTROLS" ) ) {
314         if( rl_dist( p.pos(), vp.pos() ) <= 40 ) {
315             return true;
316         }
317     }
318 
319     add_msg( m_bad, _( "Lost connection with the vehicle due to distance!" ) );
320     g->setremoteveh( nullptr );
321     return false;
322 }
323 
init_state(int init_veh_fuel,int init_veh_status)324 void vehicle::init_state( int init_veh_fuel, int init_veh_status )
325 {
326     // vehicle parts excluding engines are by default turned off
327     for( vehicle_part &pt : parts ) {
328         pt.enabled = pt.is_engine();
329     }
330 
331     bool destroySeats = false;
332     bool destroyControls = false;
333     bool destroyTank = false;
334     bool destroyEngine = false;
335     bool destroyTires = false;
336     bool blood_covered = false;
337     bool blood_inside = false;
338     bool has_no_key = false;
339     bool destroyAlarm = false;
340 
341     // More realistically it should be -5 days old
342     last_update = calendar::turn_zero;
343 
344     // veh_fuel_multiplier is percentage of fuel
345     // 0 is empty, 100 is full tank, -1 is random 7% to 35%
346     int veh_fuel_mult = init_veh_fuel;
347     if( init_veh_fuel == - 1 ) {
348         veh_fuel_mult = rng( 1, 7 );
349     }
350     if( init_veh_fuel > 100 ) {
351         veh_fuel_mult = 100;
352     }
353 
354     // veh_status is initial vehicle damage
355     // -1 = light damage (DEFAULT)
356     //  0 = undamaged
357     //  1 = disabled, destroyed tires OR engine
358     int veh_status = -1;
359     if( init_veh_status == 0 ) {
360         veh_status = 0;
361     }
362     if( init_veh_status == 1 ) {
363         int rand = rng( 1, 100 );
364         veh_status = 1;
365 
366         if( rand <= 5 ) {          //  seats are destroyed 5%
367             destroySeats = true;
368         } else if( rand <= 15 ) {  // controls are destroyed 10%
369             destroyControls = true;
370             veh_fuel_mult += rng( 0, 7 );   // add 0-7% more fuel if controls are destroyed
371         } else if( rand <= 23 ) {  // battery, minireactor or gasoline tank are destroyed 8%
372             destroyTank = true;
373         } else if( rand <= 29 ) {  // engine are destroyed 6%
374             destroyEngine = true;
375             veh_fuel_mult += rng( 3, 12 );  // add 3-12% more fuel if engine is destroyed
376         } else if( rand <= 66 ) {  // tires are destroyed 37%
377             destroyTires = true;
378             veh_fuel_mult += rng( 0, 18 );  // add 0-18% more fuel if tires are destroyed
379         } else {                   // vehicle locked 34%
380             has_no_key = true;
381         }
382     }
383     // if locked, 16% chance something damaged
384     if( one_in( 6 ) && has_no_key ) {
385         if( one_in( 3 ) ) {
386             destroyTank = true;
387         } else if( one_in( 2 ) ) {
388             destroyEngine = true;
389         } else {
390             destroyTires = true;
391         }
392     } else if( !one_in( 3 ) ) {
393         //most cars should have a destroyed alarm
394         destroyAlarm = true;
395     }
396 
397     //Provide some variety to non-mint vehicles
398     if( veh_status != 0 ) {
399         //Leave engine running in some vehicles, if the engine has not been destroyed
400         //chance decays from 1 in 4 vehicles on day 0 to 1 in (day + 4) in the future.
401         int current_day = std::max( to_days<int>( calendar::turn - calendar::turn_zero ), 0 );
402         if( veh_fuel_mult > 0 && !empty( get_avail_parts( "ENGINE" ) ) &&
403             one_in( current_day + 4 ) && !destroyEngine && !has_no_key &&
404             has_engine_type_not( fuel_type_muscle, true ) ) {
405             engine_on = true;
406         }
407 
408         bool light_head  = one_in( 20 );
409         bool light_whead  = one_in( 20 ); // wide-angle headlight
410         bool light_dome  = one_in( 16 );
411         bool light_aisle = one_in( 8 );
412         bool light_hoverh = one_in( 4 ); // half circle overhead light
413         bool light_overh = one_in( 4 );
414         bool light_atom  = one_in( 2 );
415         for( vehicle_part &pt : parts ) {
416             if( pt.has_flag( VPFLAG_CONE_LIGHT ) ) {
417                 pt.enabled = light_head;
418             } else if( pt.has_flag( VPFLAG_WIDE_CONE_LIGHT ) ) {
419                 pt.enabled = light_whead;
420             } else if( pt.has_flag( VPFLAG_DOME_LIGHT ) ) {
421                 pt.enabled = light_dome;
422             } else if( pt.has_flag( VPFLAG_AISLE_LIGHT ) ) {
423                 pt.enabled = light_aisle;
424             } else if( pt.has_flag( VPFLAG_HALF_CIRCLE_LIGHT ) ) {
425                 pt.enabled = light_hoverh;
426             } else if( pt.has_flag( VPFLAG_CIRCLE_LIGHT ) ) {
427                 pt.enabled = light_overh;
428             } else if( pt.has_flag( VPFLAG_ATOMIC_LIGHT ) ) {
429                 pt.enabled = light_atom;
430             }
431         }
432 
433         if( one_in( 10 ) ) {
434             blood_covered = true;
435         }
436 
437         if( one_in( 8 ) ) {
438             blood_inside = true;
439         }
440 
441         for( const vpart_reference &vp : get_parts_including_carried( "FRIDGE" ) ) {
442             vp.part().enabled = true;
443         }
444 
445         for( const vpart_reference &vp : get_parts_including_carried( "FREEZER" ) ) {
446             vp.part().enabled = true;
447         }
448 
449         for( const vpart_reference &vp : get_parts_including_carried( "WATER_PURIFIER" ) ) {
450             vp.part().enabled = true;
451         }
452     }
453 
454     cata::optional<point> blood_inside_pos;
455     for( const vpart_reference &vp : get_all_parts() ) {
456         const size_t p = vp.part_index();
457         vehicle_part &pt = vp.part();
458 
459         if( vp.has_feature( VPFLAG_REACTOR ) ) {
460             // De-hardcoded reactors. Should always start active
461             pt.enabled = true;
462         }
463 
464         if( pt.is_reactor() ) {
465             const ammotype plut( "plutonium" );
466             if( veh_fuel_mult == 100 ) { // Mint condition vehicle
467                 pt.ammo_set( itype_plut_cell );
468             } else if( one_in( 2 ) && veh_fuel_mult > 0 ) { // Randomize charge a bit
469                 pt.ammo_set( itype_plut_cell, pt.ammo_capacity( plut ) * ( veh_fuel_mult + rng( 0, 10 ) ) / 100 );
470             } else if( one_in( 2 ) && veh_fuel_mult > 0 ) {
471                 pt.ammo_set( itype_plut_cell, pt.ammo_capacity( plut ) * ( veh_fuel_mult - rng( 0, 10 ) ) / 100 );
472             } else {
473                 pt.ammo_set( itype_plut_cell, pt.ammo_capacity( plut ) * veh_fuel_mult / 100 );
474             }
475         }
476 
477         if( pt.is_battery() ) {
478             const ammotype battery( "battery" );
479             if( veh_fuel_mult == 100 ) { // Mint condition vehicle
480                 pt.ammo_set( itype_battery );
481             } else if( one_in( 2 ) && veh_fuel_mult > 0 ) { // Randomize battery ammo a bit
482                 pt.ammo_set( itype_battery, pt.ammo_capacity( battery ) * ( veh_fuel_mult + rng( 0, 10 ) ) / 100 );
483             } else if( one_in( 2 ) && veh_fuel_mult > 0 ) {
484                 pt.ammo_set( itype_battery, pt.ammo_capacity( battery ) * ( veh_fuel_mult - rng( 0, 10 ) ) / 100 );
485             } else {
486                 pt.ammo_set( itype_battery, pt.ammo_capacity( battery ) * veh_fuel_mult / 100 );
487             }
488         }
489 
490         if( !type->parts[p].fuel.is_null() ) {
491             const itype *loaded = item::find_type( type->parts[p].fuel );
492             const ammotype loaded_ammotype = loaded->ammo->type;
493             if( pt.is_tank() ) {
494                 int qty = pt.ammo_capacity( loaded_ammotype ) * veh_fuel_mult / 100;
495                 qty *= std::max( loaded->stack_size, 1 );
496                 qty /= to_milliliter( units::legacy_volume_factor );
497                 pt.ammo_set( type->parts[p].fuel, qty );
498             } else if( pt.is_fuel_store() ) {
499                 int qty = pt.ammo_capacity( loaded_ammotype ) * veh_fuel_mult / 100;
500                 pt.ammo_set( type->parts[p].fuel, qty );
501             }
502         }
503 
504         if( vp.has_feature( "OPENABLE" ) ) { // doors are closed
505             if( !pt.open && one_in( 4 ) ) {
506                 open( p );
507             }
508         }
509         if( vp.has_feature( "BOARDABLE" ) ) {   // no passengers
510             pt.remove_flag( vehicle_part::passenger_flag );
511         }
512 
513         // initial vehicle damage
514         if( veh_status == 0 ) {
515             // Completely mint condition vehicle
516             set_hp( pt, vp.info().durability );
517         } else {
518             //a bit of initial damage :)
519             //clamp 4d8 to the range of [8,20]. 8=broken, 20=undamaged.
520             int broken = 8;
521             int unhurt = 20;
522             int roll = dice( 4, 8 );
523             if( roll < unhurt ) {
524                 if( roll <= broken ) {
525                     set_hp( pt, 0 );
526                     pt.ammo_unset(); //empty broken batteries and fuel tanks
527                 } else {
528                     set_hp( pt, ( roll - broken ) / static_cast<double>( unhurt - broken ) * vp.info().durability );
529                 }
530             } else {
531                 set_hp( pt, vp.info().durability );
532             }
533 
534             if( vp.has_feature( VPFLAG_ENGINE ) ) {
535                 // If possible set an engine fault rather than destroying the engine outright
536                 if( destroyEngine && pt.faults_potential().empty() ) {
537                     set_hp( pt, 0 );
538                 } else if( destroyEngine || one_in( 3 ) ) {
539                     do {
540                         pt.fault_set( random_entry( pt.faults_potential() ) );
541                     } while( one_in( 3 ) );
542                 }
543 
544             } else if( ( destroySeats && ( vp.has_feature( "SEAT" ) || vp.has_feature( "SEATBELT" ) ) ) ||
545                        ( destroyControls && ( vp.has_feature( "CONTROLS" ) || vp.has_feature( "SECURITY" ) ) ) ||
546                        ( destroyAlarm && vp.has_feature( "SECURITY" ) ) ) {
547                 set_hp( pt, 0 );
548             }
549 
550             // Fuel tanks should be emptied as well
551             if( destroyTank && pt.is_fuel_store() ) {
552                 set_hp( pt, 0 );
553                 pt.ammo_unset();
554             }
555 
556             //Solar panels have 25% of being destroyed
557             if( vp.has_feature( "SOLAR_PANEL" ) && one_in( 4 ) ) {
558                 set_hp( pt, 0 );
559             }
560 
561             /* Bloodsplatter the front-end parts. Assume anything with x > 0 is
562             * the "front" of the vehicle (since the driver's seat is at (0, 0).
563             * We'll be generous with the blood, since some may disappear before
564             * the player gets a chance to see the vehicle. */
565             if( blood_covered && vp.mount().x > 0 ) {
566                 if( one_in( 3 ) ) {
567                     //Loads of blood. (200 = completely red vehicle part)
568                     pt.blood = rng( 200, 600 );
569                 } else {
570                     //Some blood
571                     pt.blood = rng( 50, 200 );
572                 }
573             }
574 
575             if( blood_inside ) {
576                 // blood is splattered around (blood_inside_pos),
577                 // coordinates relative to mount point; the center is always a seat
578                 if( blood_inside_pos ) {
579                     const int distSq = std::pow( blood_inside_pos->x - vp.mount().x, 2 ) +
580                                        std::pow( blood_inside_pos->y - vp.mount().y, 2 );
581                     if( distSq <= 1 ) {
582                         pt.blood = rng( 200, 400 ) - distSq * 100;
583                     }
584                 } else if( vp.has_feature( "SEAT" ) ) {
585                     // Set the center of the bloody mess inside
586                     blood_inside_pos.emplace( vp.mount() );
587                 }
588             }
589         }
590         //sets the vehicle to locked, if there is no key and an alarm part exists
591         if( vp.has_feature( "SECURITY" ) && has_no_key && pt.is_available() ) {
592             is_locked = true;
593 
594             if( one_in( 2 ) ) {
595                 // if vehicle has immobilizer 50% chance to add additional fault
596                 pt.fault_set( fault_engine_immobiliser );
597             }
598         }
599     }
600     // destroy tires until the vehicle is not drivable
601     if( destroyTires && !wheelcache.empty() ) {
602         int tries = 0;
603         while( valid_wheel_config() && tries < 100 ) {
604             // wheel config is still valid, destroy the tire.
605             set_hp( parts[random_entry( wheelcache )], 0 );
606             tries++;
607         }
608     }
609 
610     for( size_t i = 0; i < engines.size(); i++ ) {
611         auto_select_fuel( i );
612     }
613 
614     invalidate_mass();
615 }
616 
activate_magical_follow()617 void vehicle::activate_magical_follow()
618 {
619     for( vehicle_part &vp : parts ) {
620         if( vp.info().fuel_type == fuel_type_mana ) {
621             vp.enabled = true;
622             is_following = true;
623             engine_on = true;
624         } else {
625             vp.enabled = true;
626         }
627     }
628     refresh();
629 }
630 
activate_animal_follow()631 void vehicle::activate_animal_follow()
632 {
633     for( size_t e = 0; e < parts.size(); e++ ) {
634         vehicle_part &vp = parts[ e ];
635         if( vp.info().fuel_type == fuel_type_animal ) {
636             monster *mon = get_monster( e );
637             if( mon && mon->has_effect( effect_harnessed ) ) {
638                 vp.enabled = true;
639                 is_following = true;
640                 engine_on = true;
641             }
642         } else {
643             vp.enabled = true;
644         }
645     }
646     refresh();
647 }
648 
autopilot_patrol()649 void vehicle::autopilot_patrol()
650 {
651     /** choose one single zone ( multiple zones too complex for now )
652      * choose a point at the far edge of the zone
653      * the edge chosen is the edge that is smaller, therefore the longer side
654      * of the rectangle is the one the vehicle drives mostly parallel too.
655      * if its  perfect square then choose a point that is on any edge that the
656      * vehicle is not currently at
657      * drive to that point.
658      * then once arrived, choose a random opposite point of the zone.
659      * this should ( in a simple fashion ) cause a patrolling behavior
660      * in a criss-cross fashion.
661      * in an auto-tractor, this would eventually cover the entire rectangle.
662      */
663     map &here = get_map();
664     // if we are close to a waypoint, then return to come back to this function next turn.
665     if( autodrive_local_target != tripoint_zero ) {
666         if( rl_dist( here.getabs( global_pos3() ), autodrive_local_target ) <= 3 ) {
667             autodrive_local_target = tripoint_zero;
668             return;
669         }
670         if( !here.inbounds( here.getlocal( autodrive_local_target ) ) ) {
671             autodrive_local_target = tripoint_zero;
672             is_patrolling = false;
673             return;
674         }
675         drive_to_local_target( autodrive_local_target, false );
676         return;
677     }
678     zone_manager &mgr = zone_manager::get_manager();
679     const auto &zone_src_set = mgr.get_near( zone_type_id( "VEHICLE_PATROL" ),
680                                here.getabs( global_pos3() ), 60 );
681     if( zone_src_set.empty() ) {
682         is_patrolling = false;
683         return;
684     }
685     // get corners.
686     tripoint min;
687     tripoint max;
688     for( const tripoint &box : zone_src_set ) {
689         if( min == tripoint_zero ) {
690             min = box;
691             max = box;
692             continue;
693         }
694         min.x = std::min( box.x, min.x );
695         min.y = std::min( box.y, min.y );
696         min.z = std::min( box.z, min.z );
697         max.x = std::max( box.x, max.x );
698         max.y = std::max( box.y, max.y );
699         max.z = std::max( box.z, max.z );
700     }
701     const bool x_side = ( max.x - min.x ) < ( max.y - min.y );
702     const int point_along = x_side ? rng( min.x, max.x ) : rng( min.y, max.y );
703     const tripoint max_tri = x_side ? tripoint( point_along, max.y, min.z ) : tripoint( max.x,
704                              point_along,
705                              min.z );
706     const tripoint min_tri = x_side ? tripoint( point_along, min.y, min.z ) : tripoint( min.x,
707                              point_along,
708                              min.z );
709     tripoint chosen_tri = min_tri;
710     if( rl_dist( max_tri, here.getabs( global_pos3() ) ) >= rl_dist( min_tri,
711             here.getabs( global_pos3() ) ) ) {
712         chosen_tri = max_tri;
713     }
714     autodrive_local_target = chosen_tri;
715     drive_to_local_target( autodrive_local_target, false );
716 }
717 
immediate_path(const units::angle & rotate)718 std::set<point> vehicle::immediate_path( const units::angle &rotate )
719 {
720     std::set<point> points_to_check;
721     const int distance_to_check = 10 + ( velocity / 800 );
722     units::angle adjusted_angle = normalize( face.dir() + rotate );
723     // clamp to multiples of 15.
724     adjusted_angle = round_to_multiple_of( adjusted_angle, 15_degrees );
725     tileray collision_vector;
726     collision_vector.init( adjusted_angle );
727     map &here = get_map();
728     point top_left_actual = global_pos3().xy() + coord_translate( front_left );
729     point top_right_actual = global_pos3().xy() + coord_translate( front_right );
730     std::vector<point> front_row = line_to( here.getabs( top_left_actual ),
731                                             here.getabs( top_right_actual ) );
732     for( const point &elem : front_row ) {
733         for( int i = 0; i < distance_to_check; ++i ) {
734             collision_vector.advance( i );
735             point point_to_add = elem + point( collision_vector.dx(), collision_vector.dy() );
736             points_to_check.emplace( point_to_add );
737         }
738     }
739     collision_check_points = points_to_check;
740     return points_to_check;
741 }
742 
get_turn_from_angle(const units::angle & angle,const tripoint & vehpos,const tripoint & target,bool reverse=false)743 static int get_turn_from_angle( const units::angle &angle, const tripoint &vehpos,
744                                 const tripoint &target, bool reverse = false )
745 {
746     if( angle > 10.0_degrees && angle <= 45.0_degrees ) {
747         return reverse ? 4 : 1;
748     } else if( angle > 45.0_degrees && angle <= 90.0_degrees ) {
749         return 3;
750     } else if( angle > 90.0_degrees && angle < 180.0_degrees ) {
751         return reverse ? 1 : 4;
752     } else if( angle < -10.0_degrees && angle >= -45.0_degrees ) {
753         return reverse ? -4 : -1;
754     } else if( angle < -45.0_degrees && angle >= -90.0_degrees ) {
755         return -3;
756     } else if( angle < -90.0_degrees && angle > -180.0_degrees ) {
757         return reverse ? -1 : -4;
758         // edge case of being exactly on the button for the target.
759         // just keep driving, the next path point will be picked up.
760     } else if( ( angle == 180_degrees || angle == -180_degrees ) && vehpos == target ) {
761         return 0;
762     }
763     return 0;
764 }
765 
stop_autodriving()766 void vehicle::stop_autodriving()
767 {
768     if( !is_autodriving && !is_patrolling && !is_following ) {
769         return;
770     }
771     if( velocity > 0 ) {
772         if( is_patrolling || is_following ) {
773             autodrive( point( 0, 10 ) );
774         } else {
775             pldrive( point( 0, 10 ) );
776         }
777     }
778     is_autodriving = false;
779     is_patrolling = false;
780     is_following = false;
781     autopilot_on = false;
782     autodrive_local_target = tripoint_zero;
783     collision_check_points.clear();
784 }
785 
drive_to_local_target(const tripoint & target,bool follow_protocol)786 void vehicle::drive_to_local_target( const tripoint &target, bool follow_protocol )
787 {
788     Character &player_character = get_player_character();
789     if( follow_protocol && player_character.in_vehicle ) {
790         stop_autodriving();
791         return;
792     }
793     refresh();
794     map &here = get_map();
795     tripoint vehpos = here.getabs( global_pos3() );
796     units::angle angle = get_angle_from_targ( target );
797     // now we got the angle to the target, we can work out when we are heading towards disaster.
798     // Check the tileray in the direction we need to head towards.
799     std::set<point> points_to_check = immediate_path( angle );
800     bool stop = false;
801     for( const point &pt_elem : points_to_check ) {
802         point elem = here.getlocal( pt_elem );
803         if( stop ) {
804             break;
805         }
806         const optional_vpart_position ovp = here.veh_at( tripoint( elem, sm_pos.z ) );
807         if( here.impassable_ter_furn( tripoint( elem, sm_pos.z ) ) || ( ovp &&
808                 &ovp->vehicle() != this ) ) {
809             stop = true;
810             break;
811         }
812         if( elem == player_character.pos().xy() ) {
813             if( follow_protocol || player_character.in_vehicle ) {
814                 continue;
815             } else {
816                 stop = true;
817                 break;
818             }
819         }
820         bool its_a_pet = false;
821         if( g->critter_at( tripoint( elem, sm_pos.z ) ) ) {
822             npc *guy = g->critter_at<npc>( tripoint( elem, sm_pos.z ) );
823             if( guy && !guy->in_vehicle ) {
824                 stop = true;
825                 break;
826             }
827             for( const vehicle_part &p : parts ) {
828                 monster *mon = get_monster( index_of_part( &p ) );
829                 if( mon && mon->pos().xy() == elem ) {
830                     its_a_pet = true;
831                     break;
832                 }
833             }
834             if( !its_a_pet ) {
835                 stop = true;
836                 break;
837             }
838         }
839     }
840     if( stop ) {
841         if( autopilot_on ) {
842             sounds::sound( global_pos3(), 30, sounds::sound_t::alert,
843                            string_format( _( "the %s emitting a beep and saying \"Obstacle detected!\"" ),
844                                           name ) );
845         }
846         if( velocity > 0 ) {
847             follow_protocol ||
848             is_patrolling ? autodrive( point( 0, 10 ) ) : pldrive( point( 0, 10 ) );
849         }
850         stop_autodriving();
851         return;
852     }
853     int turn_x = get_turn_from_angle( angle, vehpos, target );
854     int accel_y = 0;
855     // best to cruise around at a safe velocity or 40mph, whichever is lowest
856     // accelerate when it doesn't need to turn.
857     // when following player, take distance to player into account.
858     // we really want to avoid running the player over.
859     // If its a helicopter, we dont need to worry about airborne obstacles so much
860     // And fuel efficiency is terrible at low speeds.
861     const int safe_player_follow_speed = 400 *
862                                          player_character.current_movement_mode()->move_speed_mult();
863     if( follow_protocol ) {
864         if( ( ( turn_x > 0 || turn_x < 0 ) && velocity > safe_player_follow_speed ) ||
865             rl_dist( vehpos, here.getabs( player_character.pos() ) ) < 7 + ( ( mount_max.y * 3 ) + 4 ) ) {
866             accel_y = 1;
867         }
868         if( ( velocity < std::min( safe_velocity(), safe_player_follow_speed ) && turn_x == 0 &&
869               rl_dist( vehpos, here.getabs( player_character.pos() ) ) > 8 + ( ( mount_max.y * 3 ) + 4 ) ) ||
870             velocity < 100 ) {
871             accel_y = -1;
872         }
873     } else {
874         if( ( turn_x > 0 || turn_x < 0 ) && velocity > 1000 ) {
875             accel_y = 1;
876         }
877         if( ( velocity < std::min( safe_velocity(), is_rotorcraft() &&
878                                    is_flying_in_air() ? 12000 : 32 * 100 ) && turn_x == 0 ) || velocity < 500 ) {
879             accel_y = -1;
880         }
881         if( is_patrolling && velocity > 400 ) {
882             accel_y = 1;
883         }
884     }
885     follow_protocol ||
886     is_patrolling ? autodrive( point( turn_x, accel_y ) ) : pldrive( point( turn_x, accel_y ) );
887 }
888 
get_angle_from_targ(const tripoint & targ)889 units::angle vehicle::get_angle_from_targ( const tripoint &targ )
890 {
891     tripoint vehpos = get_map().getabs( global_pos3() );
892     rl_vec2d facevec = face_vec();
893     point rel_pos_target = targ.xy() - vehpos.xy();
894     rl_vec2d targetvec = rl_vec2d( rel_pos_target.x, rel_pos_target.y );
895     // cross product
896     double crossy = ( facevec.x * targetvec.y ) - ( targetvec.x * facevec.y );
897     // dot product.
898     double dotx = ( facevec.x * targetvec.x ) + ( facevec.y * targetvec.y );
899 
900     return units::atan2( crossy, dotx );
901 }
902 
do_autodrive()903 void vehicle::do_autodrive()
904 {
905     if( omt_path.empty() ) {
906         stop_autodriving();
907     }
908     Character &player_character = get_player_character();
909     map &here = get_map();
910     tripoint vehpos = global_pos3();
911     // TODO: fix point types
912     tripoint_abs_omt veh_omt_pos( ms_to_omt_copy( here.getabs( vehpos ) ) );
913     // we're at or close to the waypoint, pop it out and look for the next one.
914     if( ( is_autodriving && !player_character.omt_path.empty() && !omt_path.empty() ) &&
915         veh_omt_pos == omt_path.back() ) {
916         player_character.omt_path.pop_back();
917         omt_path.pop_back();
918     }
919     if( omt_path.empty() ) {
920         stop_autodriving();
921         return;
922     }
923 
924     point_rel_omt omt_diff = omt_path.back().xy() - veh_omt_pos.xy();
925     if( omt_diff.x() > 3 || omt_diff.x() < -3 || omt_diff.y() > 3 || omt_diff.y() < -3 ) {
926         // we've gone walkabout somehow, call off the whole thing
927         stop_autodriving();
928         return;
929     }
930     point side;
931     if( omt_diff.x() > 0 ) {
932         side.x = 2 * SEEX - 1;
933     } else if( omt_diff.x() < 0 ) {
934         side.x = 0;
935     } else {
936         side.x = SEEX;
937     }
938     if( omt_diff.y() > 0 ) {
939         side.y = 2 * SEEY - 1;
940     } else if( omt_diff.y() < 0 ) {
941         side.y = 0;
942     } else {
943         side.y = SEEY;
944     }
945     // get the shared border mid-point of the next path omt
946     tripoint_abs_ms global_a = project_to<coords::ms>( veh_omt_pos );
947     // TODO: fix point types
948     tripoint autodrive_temp_target = ( global_a.raw() + tripoint( side,
949                                        sm_pos.z ) - here.getabs( vehpos ) ) + vehpos;
950     autodrive_local_target = here.getabs( autodrive_temp_target );
951     drive_to_local_target( autodrive_local_target, false );
952 }
953 
954 /**
955  * Smashes up a vehicle that has already been placed; used for generating
956  * very damaged vehicles. Additionally, any spot where two vehicles overlapped
957  * (i.e., any spot with multiple frames) will be completely destroyed, as that
958  * was the collision point.
959  */
smash(map & m,float hp_percent_loss_min,float hp_percent_loss_max,float percent_of_parts_to_affect,point damage_origin,float damage_size)960 void vehicle::smash( map &m, float hp_percent_loss_min, float hp_percent_loss_max,
961                      float percent_of_parts_to_affect, point damage_origin, float damage_size )
962 {
963     for( vehicle_part &part : parts ) {
964         //Skip any parts already mashed up or removed.
965         if( part.is_broken() || part.removed ) {
966             continue;
967         }
968 
969         std::vector<int> parts_in_square = parts_at_relative( part.mount, true );
970         int structures_found = 0;
971         for( int &square_part_index : parts_in_square ) {
972             if( part_info( square_part_index ).location == part_location_structure ) {
973                 structures_found++;
974             }
975         }
976 
977         if( structures_found > 1 ) {
978             //Destroy everything in the square
979             for( int idx : parts_in_square ) {
980                 mod_hp( parts[ idx ], 0 - parts[ idx ].hp(), damage_type::BASH );
981                 parts[ idx ].ammo_unset();
982             }
983             continue;
984         }
985 
986         int roll = dice( 1, 1000 );
987         int pct_af = ( percent_of_parts_to_affect * 1000.0f );
988         if( roll < pct_af ) {
989             double dist =  damage_size == 0.0f ? 1.0f :
990                            clamp( 1.0f - trig_dist( damage_origin, part.precalc[0].xy() ) /
991                                   damage_size, 0.0f, 1.0f );
992             //Everywhere else, drop by 10-120% of max HP (anything over 100 = broken)
993             if( mod_hp( part, 0 - ( rng_float( hp_percent_loss_min * dist,
994                                                hp_percent_loss_max * dist ) *
995                                     part.info().durability ), damage_type::BASH ) ) {
996                 part.ammo_unset();
997             }
998         }
999     }
1000 
1001     std::unique_ptr<RemovePartHandler> handler_ptr;
1002     // clear out any duplicated locations
1003     for( int p = static_cast<int>( parts.size() ) - 1; p >= 0; p-- ) {
1004         vehicle_part &part = parts[ p ];
1005         if( part.removed ) {
1006             continue;
1007         }
1008         std::vector<int> parts_here = parts_at_relative( part.mount, true );
1009         for( int other_i = static_cast<int>( parts_here.size() ) - 1; other_i >= 0; other_i -- ) {
1010             int other_p = parts_here[ other_i ];
1011             if( p == other_p ) {
1012                 continue;
1013             }
1014             const vpart_info &p_info = part_info( p );
1015             const vpart_info &other_p_info = part_info( other_p );
1016 
1017             if( p_info.get_id() == other_p_info.get_id() ||
1018                 ( !p_info.location.empty() && p_info.location == other_p_info.location ) ) {
1019                 // Deferred creation of the handler to here so it is only created when actually needed.
1020                 if( !handler_ptr ) {
1021                     // This is a heuristic: we just assume the default handler is good enough when called
1022                     // on the main game map. And assume that we run from some mapgen code if called on
1023                     // another instance.
1024                     if( g && &get_map() == &m ) {
1025                         handler_ptr = std::make_unique<DefaultRemovePartHandler>();
1026                     } else {
1027                         handler_ptr = std::make_unique<MapgenRemovePartHandler>( m );
1028                     }
1029                 }
1030                 remove_part( other_p, *handler_ptr );
1031             }
1032         }
1033     }
1034 }
1035 
lift_strength() const1036 int vehicle::lift_strength() const
1037 {
1038     units::mass mass = total_mass();
1039     return std::max<std::int64_t>( mass / 10000_gram, 1 );
1040 }
1041 
toggle_specific_engine(int e,bool on)1042 void vehicle::toggle_specific_engine( int e, bool on )
1043 {
1044     toggle_specific_part( engines[e], on );
1045 }
toggle_specific_part(int p,bool on)1046 void vehicle::toggle_specific_part( int p, bool on )
1047 {
1048     parts[p].enabled = on;
1049 }
is_engine_type_on(int e,const itype_id & ft) const1050 bool vehicle::is_engine_type_on( int e, const itype_id &ft ) const
1051 {
1052     return is_engine_on( e ) && is_engine_type( e, ft );
1053 }
1054 
has_engine_type(const itype_id & ft,const bool enabled) const1055 bool vehicle::has_engine_type( const itype_id &ft, const bool enabled ) const
1056 {
1057     for( size_t e = 0; e < engines.size(); ++e ) {
1058         if( is_engine_type( e, ft ) && ( !enabled || is_engine_on( e ) ) ) {
1059             return true;
1060         }
1061     }
1062     return false;
1063 }
has_engine_type_not(const itype_id & ft,const bool enabled) const1064 bool vehicle::has_engine_type_not( const itype_id &ft, const bool enabled ) const
1065 {
1066     for( size_t e = 0; e < engines.size(); ++e ) {
1067         if( !is_engine_type( e, ft ) && ( !enabled || is_engine_on( e ) ) ) {
1068             return true;
1069         }
1070     }
1071     return false;
1072 }
1073 
has_engine_conflict(const vpart_info * possible_conflict,std::string & conflict_type) const1074 bool vehicle::has_engine_conflict( const vpart_info *possible_conflict,
1075                                    std::string &conflict_type ) const
1076 {
1077     std::vector<std::string> new_excludes = possible_conflict->engine_excludes();
1078     // skip expensive string comparisons if there are no exclusions
1079     if( new_excludes.empty() ) {
1080         return false;
1081     }
1082 
1083     bool has_conflict = false;
1084 
1085     for( int engine : engines ) {
1086         std::vector<std::string> install_excludes = part_info( engine ).engine_excludes();
1087         std::vector<std::string> conflicts;
1088         std::set_intersection( new_excludes.begin(), new_excludes.end(), install_excludes.begin(),
1089                                install_excludes.end(), back_inserter( conflicts ) );
1090         if( !conflicts.empty() ) {
1091             has_conflict = true;
1092             conflict_type = conflicts.front();
1093             break;
1094         }
1095     }
1096     return has_conflict;
1097 }
1098 
is_engine_type(const int e,const itype_id & ft) const1099 bool vehicle::is_engine_type( const int e, const itype_id  &ft ) const
1100 {
1101     return parts[engines[e]].ammo_current().is_null() ? parts[engines[e]].fuel_current() == ft :
1102            parts[engines[e]].ammo_current() == ft;
1103 }
1104 
is_combustion_engine_type(const int e) const1105 bool vehicle::is_combustion_engine_type( const int e ) const
1106 {
1107     return parts[engines[e]].info().has_flag( flag_E_COMBUSTION );
1108 }
1109 
is_perpetual_type(const int e) const1110 bool vehicle::is_perpetual_type( const int e ) const
1111 {
1112     const itype_id  &ft = part_info( engines[e] ).fuel_type;
1113     return item( ft ).has_flag( flag_PERPETUAL );
1114 }
1115 
is_engine_on(const int e) const1116 bool vehicle::is_engine_on( const int e ) const
1117 {
1118     return parts[ engines[ e ] ].is_available() && is_part_on( engines[ e ] );
1119 }
1120 
is_part_on(const int p) const1121 bool vehicle::is_part_on( const int p ) const
1122 {
1123     return parts[p].enabled;
1124 }
1125 
is_alternator_on(const int a) const1126 bool vehicle::is_alternator_on( const int a ) const
1127 {
1128     vehicle_part alt = parts[ alternators [ a ] ];
1129     if( alt.is_unavailable() ) {
1130         return false;
1131     }
1132 
1133     return std::any_of( engines.begin(), engines.end(), [this, &alt]( int idx ) {
1134         const vehicle_part &eng = parts [ idx ];
1135         //fuel_left checks that the engine can produce power to be absorbed
1136         return eng.mount == alt.mount && eng.is_available() && eng.enabled &&
1137                fuel_left( eng.fuel_current() ) &&
1138                !eng.has_fault_flag( "NO_ALTERNATOR_CHARGE" );
1139     } );
1140 }
1141 
has_security_working() const1142 bool vehicle::has_security_working() const
1143 {
1144     bool found_security = false;
1145     if( fuel_left( fuel_type_battery ) > 0 ) {
1146         for( int s : speciality ) {
1147             if( part_flag( s, "SECURITY" ) && parts[ s ].is_available() ) {
1148                 found_security = true;
1149                 break;
1150             }
1151         }
1152     }
1153     return found_security;
1154 }
1155 
backfire(const int e) const1156 void vehicle::backfire( const int e ) const
1157 {
1158     const int power = part_vpower_w( engines[e], true );
1159     const tripoint pos = global_part_pos3( engines[e] );
1160     sounds::sound( pos, 40 + power / 10000, sounds::sound_t::movement,
1161                    // single space after the exclamation mark because it does not end the sentence
1162                    //~ backfire sound
1163                    string_format( _( "a loud BANG! from the %s" ), // NOLINT(cata-text-style)
1164                                   parts[ engines[ e ] ].name() ), true, "vehicle", "engine_backfire" );
1165 }
1166 
part_info(int index,bool include_removed) const1167 const vpart_info &vehicle::part_info( int index, bool include_removed ) const
1168 {
1169     if( index < static_cast<int>( parts.size() ) ) {
1170         if( !parts[index].removed || include_removed ) {
1171             return parts[index].info();
1172         }
1173     }
1174     return vpart_id::NULL_ID().obj();
1175 }
1176 
1177 // engines & alternators all have power.
1178 // engines provide, whilst alternators consume.
part_vpower_w(const int index,const bool at_full_hp) const1179 int vehicle::part_vpower_w( const int index, const bool at_full_hp ) const
1180 {
1181     const vehicle_part &vp = parts[ index ];
1182     int pwr = vp.info().power;
1183     if( part_flag( index, VPFLAG_ENGINE ) ) {
1184         if( pwr == 0 ) {
1185             pwr = vhp_to_watts( vp.base.engine_displacement() );
1186         }
1187         if( vp.info().fuel_type == fuel_type_animal ) {
1188             monster *mon = get_monster( index );
1189             if( mon != nullptr && mon->has_effect( effect_harnessed ) ) {
1190                 // An animal that can carry twice as much weight, can pull a cart twice as hard.
1191                 pwr = mon->get_speed() * ( mon->get_size() - 1 ) * 3
1192                       * ( mon->get_mountable_weight_ratio() * 5 );
1193             } else {
1194                 pwr = 0;
1195             }
1196         }
1197         // Weary multiplier
1198         const float weary_mult = get_player_character().exertion_adjusted_move_multiplier();
1199         ///\EFFECT_STR increases power produced for MUSCLE_* vehicles
1200         pwr += ( get_player_character().str_cur - 8 ) * part_info( index ).engine_muscle_power_factor() *
1201                weary_mult;
1202         /// wind-powered vehicles have differing power depending on wind direction
1203         if( vp.info().fuel_type == fuel_type_wind ) {
1204             weather_manager &weather = get_weather();
1205             int windpower = weather.windspeed;
1206             rl_vec2d windvec;
1207             double raddir = ( ( weather.winddirection + 180 ) % 360 ) * ( M_PI / 180 );
1208             windvec = windvec.normalized();
1209             windvec.y = -std::cos( raddir );
1210             windvec.x = std::sin( raddir );
1211             rl_vec2d fv = face_vec();
1212             double dot = windvec.dot_product( fv );
1213             if( dot <= 0 ) {
1214                 dot = std::min( -0.1, dot );
1215             } else {
1216                 dot = std::max( 0.1, dot );
1217             }
1218             int windeffectint = static_cast<int>( ( windpower * dot ) * 200 );
1219             pwr = std::max( 1, pwr + windeffectint );
1220         }
1221     }
1222 
1223     if( pwr < 0 ) {
1224         return pwr; // Consumers always draw full power, even if broken
1225     }
1226     if( at_full_hp ) {
1227         return pwr; // Assume full hp
1228     }
1229     // Damaged engines give less power, but some engines handle it better
1230     double health = parts[index].health_percent();
1231     // dpf is 0 for engines that scale power linearly with damage and
1232     // provides a floor otherwise
1233     float dpf = part_info( index ).engine_damaged_power_factor();
1234     double effective_percent = dpf + ( ( 1 - dpf ) * health );
1235     return static_cast<int>( pwr * effective_percent );
1236 }
1237 
1238 // alternators, solar panels, reactors, and accessories all have epower.
1239 // alternators, solar panels, and reactors provide, whilst accessories consume.
1240 // for motor consumption see @ref vpart_info::energy_consumption instead
part_epower_w(const int index) const1241 int vehicle::part_epower_w( const int index ) const
1242 {
1243     int e = part_info( index ).epower;
1244     if( e < 0 ) {
1245         return e; // Consumers always draw full power, even if broken
1246     }
1247     return e * parts[ index ].health_percent();
1248 }
1249 
power_to_energy_bat(const int power_w,const time_duration & d) const1250 int vehicle::power_to_energy_bat( const int power_w, const time_duration &d ) const
1251 {
1252     // Integrate constant epower (watts) over time to get units of battery energy
1253     // Thousands of watts over millions of seconds can happen, so 32-bit int
1254     // insufficient.
1255     int64_t energy_j = power_w * to_seconds<int64_t>( d );
1256     int energy_bat = energy_j / bat_energy_j;
1257     int sign = power_w >= 0 ? 1 : -1;
1258     // energy_bat remainder results in chance at additional charge/discharge
1259     energy_bat += x_in_y( std::abs( energy_j % bat_energy_j ), bat_energy_j ) ? sign : 0;
1260     return energy_bat;
1261 }
1262 
vhp_to_watts(const int power_vhp)1263 int vehicle::vhp_to_watts( const int power_vhp )
1264 {
1265     // Convert vhp units (0.5 HP ) to watts
1266     // Used primarily for calculating battery charge/discharge
1267     // TODO: convert batteries to use energy units based on watts (watt-ticks?)
1268     constexpr int conversion_factor = 373; // 373 watts == 1 power_vhp == 0.5 HP
1269     return power_vhp * conversion_factor;
1270 }
1271 
has_structural_part(const point & dp) const1272 bool vehicle::has_structural_part( const point &dp ) const
1273 {
1274     for( const int elem : parts_at_relative( dp, false ) ) {
1275         if( part_info( elem ).location == part_location_structure &&
1276             !part_info( elem ).has_flag( "PROTRUSION" ) ) {
1277             return true;
1278         }
1279     }
1280     return false;
1281 }
1282 
1283 /**
1284  * Returns whether or not the vehicle has a structural part queued for removal,
1285  * @return true if a structural is queue for removal, false if not.
1286  * */
is_structural_part_removed() const1287 bool vehicle::is_structural_part_removed() const
1288 {
1289     for( const vpart_reference &vp : get_all_parts() ) {
1290         if( vp.part().removed && vp.info().location == part_location_structure ) {
1291             return true;
1292         }
1293     }
1294     return false;
1295 }
1296 
1297 /**
1298  * Returns whether or not the vehicle part with the given id can be mounted in
1299  * the specified square.
1300  * @param dp The local coordinate to mount in.
1301  * @param id The id of the part to install.
1302  * @return true if the part can be mounted, false if not.
1303  */
can_mount(const point & dp,const vpart_id & id) const1304 bool vehicle::can_mount( const point &dp, const vpart_id &id ) const
1305 {
1306     //The part has to actually exist.
1307     if( !id.is_valid() ) {
1308         return false;
1309     }
1310 
1311     //It also has to be a real part, not the null part
1312     const vpart_info &part = id.obj();
1313     if( part.has_flag( "NOINSTALL" ) ) {
1314         return false;
1315     }
1316 
1317     const std::vector<int> parts_in_square = parts_at_relative( dp, false );
1318 
1319     //First part in an empty square MUST be a structural part
1320     if( parts_in_square.empty() && part.location != part_location_structure ) {
1321         return false;
1322     }
1323     // If its a part that harnesses animals that don't allow placing on it.
1324     if( !parts_in_square.empty() && part_info( parts_in_square[0] ).has_flag( "ANIMAL_CTRL" ) ) {
1325         return false;
1326     }
1327     //No other part can be placed on a protrusion
1328     if( !parts_in_square.empty() && part_info( parts_in_square[0] ).has_flag( "PROTRUSION" ) ) {
1329         return false;
1330     }
1331 
1332     //No part type can stack with itself, or any other part in the same slot
1333     for( const int &elem : parts_in_square ) {
1334         const vpart_info &other_part = parts[elem].info();
1335 
1336         //Parts with no location can stack with each other (but not themselves)
1337         if( part.get_id() == other_part.get_id() ||
1338             ( !part.location.empty() && part.location == other_part.location ) ) {
1339             return false;
1340         }
1341         // Until we have an interface for handling multiple components with CARGO space,
1342         // exclude them from being mounted in the same tile.
1343         if( part.has_flag( "CARGO" ) && other_part.has_flag( "CARGO" ) ) {
1344             return false;
1345         }
1346 
1347     }
1348 
1349     // All parts after the first must be installed on or next to an existing part
1350     // the exception is when a single tile only structural object is being repaired
1351     if( !parts.empty() ) {
1352         if( !is_structural_part_removed() &&
1353             !has_structural_part( dp ) &&
1354             !has_structural_part( dp + point_east ) &&
1355             !has_structural_part( dp + point_south ) &&
1356             !has_structural_part( dp + point_west ) &&
1357             !has_structural_part( dp + point_north ) ) {
1358             return false;
1359         }
1360     }
1361 
1362     // only one exclusive engine allowed
1363     std::string empty;
1364     if( has_engine_conflict( &part, empty ) ) {
1365         return false;
1366     }
1367 
1368     // Check all the flags of the part to see if they require other flags
1369     // If other flags are required check if those flags are present
1370     for( const std::string &flag : part.get_flags() ) {
1371         if( !json_flag::get( flag ).requires_flag().empty() ) {
1372             bool anchor_found = false;
1373             for( const int &elem : parts_in_square ) {
1374                 if( part_info( elem ).has_flag( json_flag::get( flag ).requires_flag() ) ) {
1375                     anchor_found = true;
1376                 }
1377             }
1378             if( !anchor_found ) {
1379                 return false;
1380             }
1381         }
1382     }
1383 
1384     //Mirrors cannot be mounted on OPAQUE parts
1385     if( part.has_flag( "VISION" ) && !part.has_flag( "CAMERA" ) ) {
1386         for( const int &elem : parts_in_square ) {
1387             if( part_info( elem ).has_flag( "OPAQUE" ) ) {
1388                 return false;
1389             }
1390         }
1391     }
1392     //Opaque parts cannot be mounted on mirrors parts
1393     if( part.has_flag( "OPAQUE" ) ) {
1394         for( const int &elem : parts_in_square ) {
1395             if( part_info( elem ).has_flag( "VISION" ) &&
1396                 !part_info( elem ).has_flag( "CAMERA" ) ) {
1397                 return false;
1398             }
1399         }
1400     }
1401 
1402     //Turret mounts must NOT be installed on other (modded) turret mounts
1403     if( part.has_flag( "TURRET_MOUNT" ) ) {
1404         for( const int &elem : parts_in_square ) {
1405             if( part_info( elem ).has_flag( "TURRET_MOUNT" ) ) {
1406                 return false;
1407             }
1408         }
1409     }
1410 
1411     //Anything not explicitly denied is permitted
1412     return true;
1413 }
1414 
can_unmount(const int p) const1415 bool vehicle::can_unmount( const int p ) const
1416 {
1417     std::string no_reason;
1418     return can_unmount( p, no_reason );
1419 }
1420 
can_unmount(const int p,std::string & reason) const1421 bool vehicle::can_unmount( const int p, std::string &reason ) const
1422 {
1423     if( p < 0 || p > static_cast<int>( parts.size() ) ) {
1424         return false;
1425     }
1426 
1427     // Find all the flags on parts in this tile that require other flags
1428     const point pt = parts[p].mount;
1429     std::vector<int> parts_here = parts_at_relative( pt, false );
1430 
1431     for( const int &elem : parts_here ) {
1432         for( const std::string &flag : part_info( elem ).get_flags() ) {
1433             if( part_info( p ).has_flag( json_flag::get( flag ).requires_flag() ) ) {
1434                 reason = string_format( _( "Remove the attached %s first." ), part_info( elem ).name() );
1435                 return false;
1436             }
1437         }
1438     }
1439 
1440     //Can't remove an animal part if the animal is still contained
1441     if( parts[p].has_flag( vehicle_part::animal_flag ) ) {
1442         reason = _( "Remove carried animal first." );
1443         return false;
1444     }
1445 
1446     //Structural parts have extra requirements
1447     if( part_info( p ).location == part_location_structure ) {
1448 
1449         std::vector<int> parts_in_square = parts_at_relative( parts[p].mount, false );
1450         /* To remove a structural part, there can be only structural parts left
1451          * in that square (might be more than one in the case of wreckage) */
1452         for( const int &elem : parts_in_square ) {
1453             if( part_info( elem ).location != part_location_structure ) {
1454                 reason = _( "Remove all other attached parts first." );
1455                 return false;
1456             }
1457         }
1458 
1459         //If it's the last part in the square...
1460         if( parts_in_square.size() == 1 ) {
1461 
1462             /* This is the tricky part: We can't remove a part that would cause
1463              * the vehicle to 'break into two' (like removing the middle section
1464              * of a quad bike, for instance). This basically requires doing some
1465              * breadth-first searches to ensure previously connected parts are
1466              * still connected. */
1467 
1468             //First, find all the squares connected to the one we're removing
1469             std::vector<vehicle_part> connected_parts;
1470 
1471             for( int i = 0; i < 4; i++ ) {
1472                 const point next = parts[p].mount + point( i < 2 ? ( i == 0 ? -1 : 1 ) : 0,
1473                                    i < 2 ? 0 : ( i == 2 ? -1 : 1 ) );
1474                 std::vector<int> parts_over_there = parts_at_relative( next, false );
1475                 //Ignore empty squares
1476                 if( !parts_over_there.empty() ) {
1477                     //Just need one part from the square to track the x/y
1478                     connected_parts.push_back( parts[parts_over_there[0]] );
1479                 }
1480             }
1481 
1482             /* If size = 0, it's the last part of the whole vehicle, so we're OK
1483              * If size = 1, it's one protruding part (i.e., bicycle wheel), so OK
1484              * Otherwise, it gets complicated... */
1485             if( connected_parts.size() > 1 ) {
1486 
1487                 /* We'll take connected_parts[0] to be the target part.
1488                  * Every other part must have some path (that doesn't involve
1489                  * the part about to be removed) to the target part, in order
1490                  * for the part to be legally removable. */
1491                 for( const vehicle_part &next_part : connected_parts ) {
1492                     if( !is_connected( connected_parts[0], next_part, parts[p] ) ) {
1493                         //Removing that part would break the vehicle in two
1494                         reason = _( "Removing this part would split the vehicle." );
1495                         return false;
1496                     }
1497                 }
1498 
1499             }
1500 
1501         }
1502     }
1503     //Anything not explicitly denied is permitted
1504     return true;
1505 }
1506 
1507 /**
1508  * Performs a breadth-first search from one part to another, to see if a path
1509  * exists between the two without going through the excluded part. Used to see
1510  * if a part can be legally removed.
1511  * @param to The part to reach.
1512  * @param from The part to start the search from.
1513  * @param excluded_part The part that is being removed and, therefore, should not
1514  *        be included in the path.
1515  * @return true if a path exists without the excluded part, false otherwise.
1516  */
is_connected(const vehicle_part & to,const vehicle_part & from,const vehicle_part & excluded_part) const1517 bool vehicle::is_connected( const vehicle_part &to, const vehicle_part &from,
1518                             const vehicle_part &excluded_part ) const
1519 {
1520     const point target = to.mount;
1521     const point excluded = excluded_part.mount;
1522 
1523     //Breadth-first-search components
1524     std::list<vehicle_part> discovered;
1525     std::list<vehicle_part> searched;
1526 
1527     //We begin with just the start point
1528     discovered.push_back( from );
1529 
1530     while( !discovered.empty() ) {
1531         vehicle_part current_part = discovered.front();
1532         discovered.pop_front();
1533         point current = current_part.mount;
1534 
1535         for( const point &offset : four_adjacent_offsets ) {
1536             point next = current + offset;
1537 
1538             if( next == target ) {
1539                 //Success!
1540                 return true;
1541             } else if( next == excluded ) {
1542                 //There might be a path, but we're not allowed to go that way
1543                 continue;
1544             }
1545 
1546             std::vector<int> parts_there = parts_at_relative( next, true );
1547 
1548             if( !parts_there.empty() && !parts[ parts_there[ 0 ] ].removed &&
1549                 part_info( parts_there[ 0 ] ).location == "structure" &&
1550                 !part_info( parts_there[ 0 ] ).has_flag( "PROTRUSION" ) ) {
1551                 //Only add the part if we haven't been here before
1552                 bool found = false;
1553                 for( const vehicle_part &elem : discovered ) {
1554                     if( elem.mount == next ) {
1555                         found = true;
1556                         break;
1557                     }
1558                 }
1559                 if( !found ) {
1560                     for( const vehicle_part &elem : searched ) {
1561                         if( elem.mount == next ) {
1562                             found = true;
1563                             break;
1564                         }
1565                     }
1566                 }
1567                 if( !found ) {
1568                     vehicle_part next_part = parts[parts_there[0]];
1569                     discovered.push_back( next_part );
1570                 }
1571             }
1572         }
1573         //Now that that's done, we've finished exploring here
1574         searched.push_back( current_part );
1575     }
1576     //If we completely exhaust the discovered list, there's no path
1577     return false;
1578 }
1579 
1580 /**
1581  * Installs a part into this vehicle.
1582  * @param dp The coordinate of where to install the part.
1583  * @param id The string ID of the part to install. (see vehicle_parts.json)
1584  * @param force Skip check of whether we can mount the part here.
1585  * @return false if the part could not be installed, true otherwise.
1586  */
install_part(const point & dp,const vpart_id & id,const std::string & variant_id,bool force)1587 int vehicle::install_part( const point &dp, const vpart_id &id, const std::string &variant_id,
1588                            bool force )
1589 {
1590     if( !( force || can_mount( dp, id ) ) ) {
1591         return -1;
1592     }
1593     return install_part( dp, vehicle_part( id, variant_id, dp, item( id.obj().base_item ) ) );
1594 }
1595 
install_part(const point & dp,const vpart_id & id,item && obj,const std::string & variant_id,bool force)1596 int vehicle::install_part( const point &dp, const vpart_id &id, item &&obj,
1597                            const std::string &variant_id, bool force )
1598 {
1599     if( !( force || can_mount( dp, id ) ) ) {
1600         return -1;
1601     }
1602     return install_part( dp, vehicle_part( id, variant_id, dp, std::move( obj ) ) );
1603 }
1604 
install_part(const point & dp,const vehicle_part & new_part)1605 int vehicle::install_part( const point &dp, const vehicle_part &new_part )
1606 {
1607     // Should be checked before installing the part
1608     bool enable = false;
1609     if( new_part.is_engine() ) {
1610         enable = true;
1611     } else {
1612         // TODO: read toggle groups from JSON
1613         static const std::vector<std::string> enable_like = {{
1614                 "CONE_LIGHT",
1615                 "CIRCLE_LIGHT",
1616                 "AISLE_LIGHT",
1617                 "AUTOPILOT",
1618                 "DOME_LIGHT",
1619                 "ATOMIC_LIGHT",
1620                 "STEREO",
1621                 "CHIMES",
1622                 "FRIDGE",
1623                 "FREEZER",
1624                 "RECHARGE",
1625                 "PLOW",
1626                 "REAPER",
1627                 "PLANTER",
1628                 "SCOOP",
1629                 "SPACE_HEATER",
1630                 "COOLER",
1631                 "WATER_PURIFIER",
1632                 "ROCKWHEEL",
1633                 "ROADHEAD"
1634             }
1635         };
1636 
1637         for( const std::string &flag : enable_like ) {
1638             if( new_part.info().has_flag( flag ) ) {
1639                 enable = has_part( flag, true );
1640                 break;
1641             }
1642         }
1643     }
1644 
1645     parts.push_back( new_part );
1646     vehicle_part &pt = parts.back();
1647 
1648     pt.enabled = enable;
1649 
1650     pt.mount = dp;
1651 
1652     refresh();
1653     coeff_air_changed = true;
1654     return parts.size() - 1;
1655 }
1656 
try_to_rack_nearby_vehicle(std::vector<std::vector<int>> & list_of_racks)1657 bool vehicle::try_to_rack_nearby_vehicle( std::vector<std::vector<int>> &list_of_racks )
1658 {
1659     map &here = get_map();
1660     for( std::vector<int> &this_bike_rack : list_of_racks ) {
1661         std::vector<vehicle *> carry_vehs;
1662         carry_vehs.assign( 4, nullptr );
1663         vehicle *test_veh = nullptr;
1664         std::set<tripoint> veh_partial_match;
1665         std::vector<std::set<tripoint>> partial_matches;
1666         partial_matches.assign( 4, veh_partial_match );
1667 
1668         //do not attempt to rack onto already filled bike racks
1669         this_bike_rack.erase( std::remove_if( this_bike_rack.begin(),
1670         this_bike_rack.end(), [this]( const int &rack ) {
1671             return parts[rack].has_flag( vehicle_part::carrying_flag );
1672         } ), this_bike_rack.end() );
1673 
1674         for( int &rack_part : this_bike_rack ) {
1675             tripoint rack_pos = global_part_pos3( rack_part );
1676             int i = 0;
1677             for( const point &offset : four_cardinal_directions ) {
1678                 tripoint search_pos( rack_pos + offset );
1679                 test_veh = veh_pointer_or_null( here.veh_at( search_pos ) );
1680                 if( test_veh == nullptr || test_veh == this ) {
1681                     continue;
1682                 } else if( test_veh != carry_vehs[ i ] ) {
1683                     carry_vehs[ i ] = test_veh;
1684                     partial_matches[ i ].clear();
1685                 }
1686                 partial_matches[ i ].insert( search_pos );
1687                 if( partial_matches[ i ] == test_veh->get_points() ) {
1688                     return merge_rackable_vehicle( test_veh, this_bike_rack );
1689                 }
1690                 ++i;
1691             }
1692         }
1693     }
1694     return false;
1695 }
1696 
merge_rackable_vehicle(vehicle * carry_veh,const std::vector<int> & rack_parts)1697 bool vehicle::merge_rackable_vehicle( vehicle *carry_veh, const std::vector<int> &rack_parts )
1698 {
1699     // Mapping between the old vehicle and new vehicle mounting points
1700     struct mapping {
1701         // All the parts attached to this mounting point
1702         std::vector<int> carry_parts_here;
1703 
1704         // the index where the racking part is on the vehicle with the rack
1705         int rack_part = 0;
1706 
1707         // the mount point we are going to add to the vehicle with the rack
1708         point carry_mount;
1709 
1710         // the mount point on the old vehicle (carry_veh) that will be destroyed
1711         point old_mount;
1712     };
1713     invalidate_towing( true );
1714     // By structs, we mean all the parts of the carry vehicle that are at the structure location
1715     // of the vehicle (i.e. frames)
1716     std::vector<int> carry_veh_structs = carry_veh->all_parts_at_location( part_location_structure );
1717     std::vector<mapping> carry_data;
1718     carry_data.reserve( carry_veh_structs.size() );
1719 
1720     //X is forward/backward, Y is left/right
1721     std::string axis;
1722     if( carry_veh_structs.size() == 1 ) {
1723         axis = "X";
1724     } else {
1725         for( const int &carry_part : carry_veh_structs ) {
1726             if( carry_veh->parts[ carry_part ].mount.x || carry_veh->parts[ carry_part ].mount.y ) {
1727                 axis = carry_veh->parts[ carry_part ].mount.x ? "X" : "Y";
1728             }
1729         }
1730     }
1731 
1732     units::angle relative_dir = normalize( carry_veh->face.dir() - face.dir() );
1733     units::angle relative_180 = units::fmod( relative_dir, 180_degrees );
1734     units::angle face_dir_180 = normalize( face.dir(), 180_degrees );
1735 
1736     // if the carrier is skewed N/S and the carried vehicle isn't aligned with
1737     // the carrier, force the carried vehicle to be at a right angle
1738     if( face_dir_180 >= 45_degrees && face_dir_180 <= 135_degrees ) {
1739         if( relative_180 >= 45_degrees && relative_180 <= 135_degrees ) {
1740             if( relative_dir < 180_degrees ) {
1741                 relative_dir = 90_degrees;
1742             } else {
1743                 relative_dir = 270_degrees;
1744             }
1745         }
1746     }
1747 
1748     // We look at each of the structure parts (mount points, i.e. frames) for the
1749     // carry vehicle and then find a rack part adjacent to it. If we don't find a rack part,
1750     // then we can't merge.
1751     bool found_all_parts = true;
1752     for( const int &carry_part : carry_veh_structs ) {
1753 
1754         // The current position on the original vehicle for this part
1755         tripoint carry_pos = carry_veh->global_part_pos3( carry_part );
1756 
1757         bool merged_part = false;
1758         for( int rack_part : rack_parts ) {
1759             size_t j = 0;
1760             // There's no mathematical transform from global pos3 to vehicle mount, so search for the
1761             // carry part in global pos3 after translating
1762             point carry_mount;
1763             for( const point &offset : four_cardinal_directions ) {
1764                 carry_mount = parts[ rack_part ].mount + offset;
1765                 tripoint possible_pos = mount_to_tripoint( carry_mount );
1766                 if( possible_pos == carry_pos ) {
1767                     break;
1768                 }
1769                 ++j;
1770             }
1771 
1772             // We checked the adjacent points from the mounting rack and managed
1773             // to find the current structure part were looking for nearby. If the part was not
1774             // near this particular rack, we would look at each in the list of rack_parts
1775             const bool carry_part_next_to_this_rack = j < four_adjacent_offsets.size();
1776             if( carry_part_next_to_this_rack ) {
1777                 mapping carry_map;
1778                 point old_mount = carry_veh->parts[ carry_part ].mount;
1779                 carry_map.carry_parts_here = carry_veh->parts_at_relative( old_mount, true );
1780                 carry_map.rack_part = rack_part;
1781                 carry_map.carry_mount = carry_mount;
1782                 carry_map.old_mount = old_mount;
1783                 carry_data.push_back( carry_map );
1784                 merged_part = true;
1785                 break;
1786             }
1787         }
1788 
1789         // We checked all the racks and could not find a place for this structure part.
1790         if( !merged_part ) {
1791             found_all_parts = false;
1792             break;
1793         }
1794     }
1795 
1796     // Now that we have mapped all the parts of the carry vehicle to the vehicle with the rack
1797     // we can go ahead and merge
1798     const point mount_zero{};
1799     if( found_all_parts ) {
1800         decltype( loot_zones ) new_zones;
1801         for( const mapping &carry_map : carry_data ) {
1802             std::string offset = string_format( "%s%3d", carry_map.old_mount == mount_zero ? axis : " ",
1803                                                 axis == "X" ? carry_map.old_mount.x : carry_map.old_mount.y );
1804             std::string unique_id = string_format( "%s%3d%s", offset,
1805                                                    static_cast<int>( to_degrees( relative_dir ) ),
1806                                                    carry_veh->name );
1807             for( const int &carry_part : carry_map.carry_parts_here ) {
1808                 parts.push_back( carry_veh->parts[ carry_part ] );
1809                 vehicle_part &carried_part = parts.back();
1810                 carried_part.mount = carry_map.carry_mount;
1811                 carried_part.carry_names.push( unique_id );
1812                 carried_part.enabled = false;
1813                 carried_part.set_flag( vehicle_part::carried_flag );
1814                 //give each carried part a tracked_flag so that we can re-enable overmap tracking on unloading if necessary
1815                 if( carry_veh->tracking_on ) {
1816                     carried_part.set_flag( vehicle_part::tracked_flag );
1817                 }
1818 
1819                 if( carried_part.has_flag( vehicle_part::passenger_flag ) ) {
1820                     carried_part.remove_flag( vehicle_part::passenger_flag );
1821                     carried_part.passenger_id = character_id();
1822                 }
1823 
1824                 parts[ carry_map.rack_part ].set_flag( vehicle_part::carrying_flag );
1825             }
1826 
1827             const std::pair<std::unordered_multimap<point, zone_data>::iterator, std::unordered_multimap<point, zone_data>::iterator>
1828             zones_on_point = carry_veh->loot_zones.equal_range( carry_map.old_mount );
1829             for( std::unordered_multimap<point, zone_data>::const_iterator it = zones_on_point.first;
1830                  it != zones_on_point.second; ++it ) {
1831                 new_zones.emplace( carry_map.carry_mount, it->second );
1832             }
1833         }
1834 
1835         for( auto &new_zone : new_zones ) {
1836             zone_manager::get_manager().create_vehicle_loot_zone(
1837                 *this, new_zone.first, new_zone.second );
1838         }
1839 
1840         // Now that we've added zones to this vehicle, we need to make sure their positions
1841         // update when we next interact with them
1842         zones_dirty = true;
1843 
1844         map &here = get_map();
1845         //~ %1$s is the vehicle being loaded onto the bicycle rack
1846         add_msg( _( "You load the %1$s on the rack." ), carry_veh->name );
1847         here.destroy_vehicle( carry_veh );
1848         here.dirty_vehicle_list.insert( this );
1849         here.set_transparency_cache_dirty( sm_pos.z );
1850         here.set_seen_cache_dirty( tripoint_zero );
1851         refresh();
1852     } else {
1853         //~ %1$s is the vehicle being loaded onto the bicycle rack
1854         add_msg( m_bad, _( "You can't get the %1$s on the rack." ), carry_veh->name );
1855     }
1856     return found_all_parts;
1857 }
1858 
1859 /**
1860  * Mark a part as removed from the vehicle.
1861  * @return bool true if the vehicle's 0,0 point shifted.
1862  */
remove_part(const int p)1863 bool vehicle::remove_part( const int p )
1864 {
1865     DefaultRemovePartHandler handler;
1866     return remove_part( p, handler );
1867 }
1868 
remove_part(const int p,RemovePartHandler & handler)1869 bool vehicle::remove_part( const int p, RemovePartHandler &handler )
1870 {
1871     // NOTE: Don't access g or g->m or anything from it directly here.
1872     // Forward all access to the handler.
1873     // There are currently two implementations of it:
1874     // - one for normal game play (vehicle is on the main map g->m),
1875     // - one for mapgen (vehicle is on a temporary map used only during mapgen).
1876     if( p >= static_cast<int>( parts.size() ) ) {
1877         debugmsg( "Tried to remove part %d but only %d parts!", p, parts.size() );
1878         return false;
1879     }
1880     if( parts[p].removed ) {
1881         /* This happens only when we had to remove part, because it was depending on
1882          * other part (using recursive remove_part() call) - currently curtain
1883          * depending on presence of window and seatbelt depending on presence of seat.
1884          */
1885         return false;
1886     }
1887 
1888     const tripoint part_loc = global_part_pos3( p );
1889 
1890     // Unboard any entities standing on removed boardable parts
1891     if( part_flag( p, "BOARDABLE" ) && parts[p].has_flag( vehicle_part::passenger_flag ) ) {
1892         handler.unboard( part_loc );
1893     }
1894 
1895     // If `p` has flag `parent_flag`, remove child with flag `child_flag`
1896     // Returns true if removal occurs
1897     const auto remove_dependent_part = [&]( const std::string & parent_flag,
1898     const std::string & child_flag ) {
1899         if( part_flag( p, parent_flag ) ) {
1900             int dep = part_with_feature( p, child_flag, false );
1901             if( dep >= 0 && !magic ) {
1902                 handler.add_item_or_charges( part_loc, parts[dep].properties_to_item(), false );
1903                 remove_part( dep, handler );
1904                 return true;
1905             }
1906         }
1907         return false;
1908     };
1909 
1910     // if a windshield is removed (usually destroyed) also remove curtains
1911     // attached to it.
1912     if( remove_dependent_part( "WINDOW", "CURTAIN" ) || part_flag( p, VPFLAG_OPAQUE ) ) {
1913         handler.set_transparency_cache_dirty( sm_pos.z );
1914     }
1915 
1916     if( part_flag( p, VPFLAG_ROOF ) || part_flag( p, VPFLAG_OPAQUE ) ) {
1917         handler.set_floor_cache_dirty( sm_pos.z + 1 );
1918     }
1919 
1920     remove_dependent_part( "SEAT", "SEATBELT" );
1921     remove_dependent_part( "BATTERY_MOUNT", "NEEDS_BATTERY_MOUNT" );
1922 
1923     // Release any animal held by the part
1924     if( parts[p].has_flag( vehicle_part::animal_flag ) ) {
1925         item base = parts[p].get_base();
1926         handler.spawn_animal_from_part( base, part_loc );
1927         parts[p].set_base( base );
1928         parts[p].remove_flag( vehicle_part::animal_flag );
1929     }
1930 
1931     // Update current engine configuration if needed
1932     if( part_flag( p, "ENGINE" ) && engines.size() > 1 ) {
1933         bool any_engine_on = false;
1934 
1935         for( const int &e : engines ) {
1936             if( e != p && is_part_on( e ) ) {
1937                 any_engine_on = true;
1938                 break;
1939             }
1940         }
1941 
1942         if( !any_engine_on ) {
1943             engine_on = false;
1944             for( const int &e : engines ) {
1945                 toggle_specific_part( e, true );
1946             }
1947         }
1948     }
1949 
1950     //Remove loot zone if Cargo was removed.
1951     const auto lz_iter = loot_zones.find( parts[p].mount );
1952     const bool no_zone = lz_iter != loot_zones.end();
1953 
1954     if( no_zone && part_flag( p, "CARGO" ) ) {
1955         // Using the key here (instead of the iterator) will remove all zones on
1956         // this mount points regardless of how many there are
1957         loot_zones.erase( parts[p].mount );
1958         zones_dirty = true;
1959     }
1960     parts[p].removed = true;
1961     removed_part_count++;
1962 
1963     handler.removed( *this, p );
1964 
1965     const point &vp_mount = parts[p].mount;
1966     const auto iter = labels.find( label( vp_mount ) );
1967     if( iter != labels.end() && parts_at_relative( vp_mount, false ).empty() ) {
1968         labels.erase( iter );
1969     }
1970 
1971     for( item &i : get_items( p ) ) {
1972         // Note: this can spawn items on the other side of the wall!
1973         // TODO: fix this ^^
1974         if( !magic ) {
1975             tripoint dest( part_loc + point( rng( -3, 3 ), rng( -3, 3 ) ) );
1976             // This new point might be out of the map bounds.  It's not
1977             // reasonable to try to spawn it outside the currently valid map,
1978             // so we pass true here to cause such points to be clamped to the
1979             // valid bounds without printing an error (as would normally
1980             // occur).
1981             handler.add_item_or_charges( dest, i, true );
1982         }
1983     }
1984     refresh();
1985     coeff_air_changed = true;
1986     return shift_if_needed();
1987 }
1988 
part_removal_cleanup()1989 void vehicle::part_removal_cleanup()
1990 {
1991     bool changed = false;
1992     map &here = get_map();
1993     for( std::vector<vehicle_part>::iterator it = parts.begin(); it != parts.end(); /* noop */ ) {
1994         if( it->removed ) {
1995             vehicle_stack items = get_items( std::distance( parts.begin(), it ) );
1996             while( !items.empty() ) {
1997                 items.erase( items.begin() );
1998             }
1999             const tripoint &pt = global_part_pos3( *it );
2000             here.clear_vehicle_point_from_cache( this, pt );
2001             it = parts.erase( it );
2002             changed = true;
2003         } else {
2004             ++it;
2005         }
2006     }
2007     removed_part_count = 0;
2008     if( changed || parts.empty() ) {
2009         refresh();
2010         if( parts.empty() ) {
2011             here.destroy_vehicle( this );
2012             return;
2013         } else {
2014             here.add_vehicle_to_cache( this );
2015         }
2016     }
2017     shift_if_needed();
2018     refresh(); // Rebuild cached indices
2019     coeff_air_dirty = coeff_air_changed;
2020     coeff_air_changed = false;
2021 }
2022 
remove_carried_flag()2023 void vehicle::remove_carried_flag()
2024 {
2025     for( vehicle_part &part : parts ) {
2026         if( part.carry_names.empty() ) {
2027             // note: we get here if the part was added while the vehicle was carried / mounted. This is not expected.
2028             // still try to remove the carried flag, if any.
2029             part.remove_flag( vehicle_part::carried_flag );
2030         } else {
2031             part.carry_names.pop();
2032             if( part.carry_names.empty() ) {
2033                 part.remove_flag( vehicle_part::carried_flag );
2034             }
2035         }
2036     }
2037 }
2038 
remove_tracked_flag()2039 void vehicle::remove_tracked_flag()
2040 {
2041     for( vehicle_part &part : parts ) {
2042         if( part.carry_names.empty() ) {
2043             part.remove_flag( vehicle_part::tracked_flag );
2044         } else {
2045             part.carry_names.pop();
2046             if( part.carry_names.empty() ) {
2047                 part.remove_flag( vehicle_part::tracked_flag );
2048             }
2049         }
2050     }
2051 }
2052 
remove_carried_vehicle(const std::vector<int> & carried_parts)2053 bool vehicle::remove_carried_vehicle( const std::vector<int> &carried_parts )
2054 {
2055     if( carried_parts.empty() ) {
2056         return false;
2057     }
2058     std::string veh_record;
2059     tripoint new_pos3;
2060     bool x_aligned = false;
2061     bool tracked_parts =
2062         false; // we will set this to true if any of the vehicle parts carry a tracked_flag
2063     for( int carried_part : carried_parts ) {
2064         //check if selected part carries tracking flag
2065         if( parts[carried_part].has_flag(
2066                 vehicle_part::tracked_flag ) ) { //this should only need to run once
2067             tracked_parts = true;
2068         }
2069         const auto &carry_names = parts[carried_part].carry_names;
2070         if( !carry_names.empty() ) {
2071             std::string id_string = carry_names.top().substr( 0, 1 );
2072             if( id_string == "X" || id_string == "Y" ) {
2073                 veh_record = carry_names.top();
2074                 new_pos3 = global_part_pos3( carried_part );
2075                 x_aligned = id_string == "X";
2076                 break;
2077             }
2078         }
2079     }
2080     if( veh_record.empty() ) {
2081         return false;
2082     }
2083     units::angle new_dir =
2084         normalize( units::from_degrees( std::stoi( veh_record.substr( 4, 3 ) ) ) + face.dir() );
2085     units::angle host_dir = normalize( face.dir(), 180_degrees );
2086     // if the host is skewed N/S, and the carried vehicle is going to come at an angle,
2087     // force it to east/west instead
2088     if( host_dir >= 45_degrees && host_dir <= 135_degrees ) {
2089         if( new_dir <= 45_degrees || new_dir >= 315_degrees ) {
2090             new_dir = 0_degrees;
2091         } else if( new_dir >= 135_degrees && new_dir <= 225_degrees ) {
2092             new_dir = 180_degrees;
2093         }
2094     }
2095     map &here = get_map();
2096     vehicle *new_vehicle = here.add_vehicle( vproto_id( "none" ), new_pos3, new_dir );
2097     if( new_vehicle == nullptr ) {
2098         add_msg_debug( "Unable to unload bike rack, host face %d, new_dir %d!",
2099                        to_degrees( face.dir() ), to_degrees( new_dir ) );
2100         return false;
2101     }
2102 
2103     std::vector<point> new_mounts;
2104     new_vehicle->name = veh_record.substr( vehicle_part::name_offset );
2105     for( int carried_part : carried_parts ) {
2106         point new_mount;
2107         std::string mount_str;
2108         if( !parts[carried_part].carry_names.empty() ) {
2109             // the mount string should be something like "X 0 0" for ex. We get the first number out of it.
2110             mount_str = parts[carried_part].carry_names.top().substr( 1, 3 );
2111         } else {
2112             // FIX #28712; if we get here it means that a part was added to the bike while the latter was a carried vehicle.
2113             // This part didn't get a carry_names because those are assigned when the carried vehicle is loaded.
2114             // We can't be sure to which vehicle it really belongs to, so it will be detached from the vehicle.
2115             // We can at least inform the player that there's something wrong.
2116             add_msg( m_warning,
2117                      _( "A part of the vehicle ('%s') has no containing vehicle's name.  It will be detached from the %s vehicle." ),
2118                      parts[carried_part].name(),  new_vehicle->name );
2119 
2120             // check if any other parts at the same location have a valid carry name so we can still have a valid mount location.
2121             for( const int &local_part : parts_at_relative( parts[carried_part].mount, true ) ) {
2122                 if( !parts[local_part].carry_names.empty() ) {
2123                     mount_str = parts[local_part].carry_names.top().substr( 1, 3 );
2124                     break;
2125                 }
2126             }
2127         }
2128         if( mount_str.empty() ) {
2129             add_msg( m_bad,
2130                      _( "There's not viable mount location on this vehicle: %s. It can't be unloaded from the rack." ),
2131                      new_vehicle->name );
2132             return false;
2133         }
2134         if( x_aligned ) {
2135             new_mount.x = std::stoi( mount_str );
2136         } else {
2137             new_mount.y = std::stoi( mount_str );
2138         }
2139         new_mounts.push_back( new_mount );
2140     }
2141 
2142     std::vector<vehicle *> new_vehicles;
2143     new_vehicles.push_back( new_vehicle );
2144     std::vector<std::vector<int>> carried_vehicles;
2145     carried_vehicles.push_back( carried_parts );
2146     std::vector<std::vector<point>> carried_mounts;
2147     carried_mounts.push_back( new_mounts );
2148     const bool success = split_vehicles( carried_vehicles, new_vehicles, carried_mounts );
2149     if( success ) {
2150         //~ %s is the vehicle being loaded onto the bicycle rack
2151         add_msg( _( "You unload the %s from the bike rack." ), new_vehicle->name );
2152         new_vehicle->remove_carried_flag();
2153         if( tracked_parts ) {
2154             new_vehicle->toggle_tracking(); //turn on tracking for our newly created vehicle
2155             new_vehicle->remove_tracked_flag(); //remove our tracking flags now that the vehicle isn't carried
2156         }
2157         here.dirty_vehicle_list.insert( this );
2158         part_removal_cleanup();
2159         new_vehicle->enable_refresh();
2160         for( int idx : new_vehicle->engines ) {
2161             if( !new_vehicle->parts[idx].is_broken() ) {
2162                 new_vehicle->parts[idx].enabled = true;
2163             }
2164         }
2165     } else {
2166         //~ %s is the vehicle being loaded onto the bicycle rack
2167         add_msg( m_bad, _( "You can't unload the %s from the bike rack." ), new_vehicle->name );
2168     }
2169     return success;
2170 }
2171 
2172 // split the current vehicle into up to 3 new vehicles that do not connect to each other
find_and_split_vehicles(int exclude)2173 bool vehicle::find_and_split_vehicles( int exclude )
2174 {
2175     std::vector<int> valid_parts = all_parts_at_location( part_location_structure );
2176     std::set<int> checked_parts;
2177     checked_parts.insert( exclude );
2178 
2179     std::vector<std::vector <int>> all_vehicles;
2180 
2181     for( size_t cnt = 0 ; cnt < 4 ; cnt++ ) {
2182         int test_part = -1;
2183         for( const int &p : valid_parts ) {
2184             if( parts[ p ].removed ) {
2185                 continue;
2186             }
2187             if( checked_parts.find( p ) == checked_parts.end() ) {
2188                 test_part = p;
2189                 break;
2190             }
2191         }
2192         if( test_part == -1 || static_cast<size_t>( test_part ) > parts.size() ) {
2193             break;
2194         }
2195 
2196         std::queue<std::pair<int, std::vector<int>>> search_queue;
2197 
2198         const auto push_neighbor = [&]( int p, const std::vector<int> &with_p ) {
2199             std::pair<int, std::vector<int>> data( p, with_p );
2200             search_queue.push( data );
2201         };
2202         auto pop_neighbor = [&]() {
2203             std::pair<int, std::vector<int>> result = search_queue.front();
2204             search_queue.pop();
2205             return result;
2206         };
2207 
2208         std::vector<int> veh_parts;
2209         push_neighbor( test_part, parts_at_relative( parts[ test_part ].mount, true ) );
2210         while( !search_queue.empty() ) {
2211             std::pair<int, std::vector<int>> test_set = pop_neighbor();
2212             test_part = test_set.first;
2213             if( checked_parts.find( test_part ) != checked_parts.end() ) {
2214                 continue;
2215             }
2216             for( const int &p : test_set.second ) {
2217                 veh_parts.push_back( p );
2218             }
2219             checked_parts.insert( test_part );
2220             for( const point &offset : four_adjacent_offsets ) {
2221                 const point dp = parts[test_part].mount + offset;
2222                 std::vector<int> all_neighbor_parts = parts_at_relative( dp, true );
2223                 int neighbor_struct_part = -1;
2224                 for( int p : all_neighbor_parts ) {
2225                     if( parts[ p ].removed ) {
2226                         continue;
2227                     }
2228                     if( part_info( p ).location == part_location_structure ) {
2229                         neighbor_struct_part = p;
2230                         break;
2231                     }
2232                 }
2233                 if( neighbor_struct_part != -1 ) {
2234                     push_neighbor( neighbor_struct_part, all_neighbor_parts );
2235                 }
2236             }
2237         }
2238         // don't include the first vehicle's worth of parts
2239         if( cnt > 0 ) {
2240             all_vehicles.push_back( veh_parts );
2241         }
2242     }
2243 
2244     if( !all_vehicles.empty() ) {
2245         bool success = split_vehicles( all_vehicles );
2246         if( success ) {
2247             // update the active cache
2248             shift_parts( point_zero );
2249             return true;
2250         }
2251     }
2252     return false;
2253 }
2254 
relocate_passengers(const std::vector<player * > & passengers)2255 void vehicle::relocate_passengers( const std::vector<player *> &passengers )
2256 {
2257     const auto boardables = get_avail_parts( "BOARDABLE" );
2258     for( player *passenger : passengers ) {
2259         for( const vpart_reference &vp : boardables ) {
2260             if( vp.part().passenger_id == passenger->getID() ) {
2261                 passenger->setpos( vp.pos() );
2262             }
2263         }
2264     }
2265 }
2266 
2267 // Split a vehicle into an old vehicle and one or more new vehicles by moving vehicle_parts
2268 // from one the old vehicle to the new vehicles.
2269 // some of the logic borrowed from remove_part
2270 // skipped the grab, curtain, player activity, and engine checks because they deal
2271 // with pos, not a vehicle pointer
2272 // @param new_vehs vector of vectors of part indexes to move to new vehicles
2273 // @param new_vehicles vector of vehicle pointers containing the new vehicles; if empty, new
2274 // vehicles will be created
2275 // @param new_mounts vector of vector of mount points. must have one vector for every vehicle*
2276 // in new_vehicles, and forces the part indices in new_vehs to be mounted on the new vehicle
2277 // at those mount points
split_vehicles(const std::vector<std::vector<int>> & new_vehs,const std::vector<vehicle * > & new_vehicles,const std::vector<std::vector<point>> & new_mounts)2278 bool vehicle::split_vehicles( const std::vector<std::vector <int>> &new_vehs,
2279                               const std::vector<vehicle *> &new_vehicles,
2280                               const std::vector<std::vector <point>> &new_mounts )
2281 {
2282     bool did_split = false;
2283     size_t i = 0;
2284     map &here = get_map();
2285     for( i = 0; i < new_vehs.size(); i ++ ) {
2286         std::vector<int> split_parts = new_vehs[ i ];
2287         if( split_parts.empty() ) {
2288             continue;
2289         }
2290         std::vector<point> split_mounts = new_mounts[ i ];
2291         did_split = true;
2292 
2293         vehicle *new_vehicle = nullptr;
2294         if( i < new_vehicles.size() ) {
2295             new_vehicle = new_vehicles[ i ];
2296         }
2297         int split_part0 = split_parts.front();
2298         tripoint new_v_pos3;
2299         point mnt_offset;
2300 
2301         decltype( labels ) new_labels;
2302         decltype( loot_zones ) new_zones;
2303         if( new_vehicle == nullptr ) {
2304             // make sure the split_part0 is a legal 0,0 part
2305             if( split_parts.size() > 1 ) {
2306                 for( size_t sp = 0; sp < split_parts.size(); sp++ ) {
2307                     int p = split_parts[ sp ];
2308                     if( part_info( p ).location == part_location_structure &&
2309                         !part_info( p ).has_flag( "PROTRUSION" ) ) {
2310                         split_part0 = sp;
2311                         break;
2312                     }
2313                 }
2314             }
2315             new_v_pos3 = global_part_pos3( parts[ split_part0 ] );
2316             mnt_offset = parts[ split_part0 ].mount;
2317             new_vehicle = here.add_vehicle( vproto_id( "none" ), new_v_pos3, face.dir() );
2318             if( new_vehicle == nullptr ) {
2319                 // the split part was out of the map bounds.
2320                 continue;
2321             }
2322             new_vehicle->name = name;
2323             new_vehicle->move = move;
2324             new_vehicle->turn_dir = turn_dir;
2325             new_vehicle->velocity = velocity;
2326             new_vehicle->vertical_velocity = vertical_velocity;
2327             new_vehicle->cruise_velocity = cruise_velocity;
2328             new_vehicle->cruise_on = cruise_on;
2329             new_vehicle->engine_on = engine_on;
2330             new_vehicle->tracking_on = tracking_on;
2331             new_vehicle->camera_on = camera_on;
2332         }
2333         new_vehicle->last_fluid_check = last_fluid_check;
2334 
2335         std::vector<player *> passengers;
2336         for( size_t new_part = 0; new_part < split_parts.size(); new_part++ ) {
2337             int mov_part = split_parts[ new_part ];
2338             point cur_mount = parts[ mov_part ].mount;
2339             point new_mount = cur_mount;
2340             if( !split_mounts.empty() ) {
2341                 new_mount = split_mounts[ new_part ];
2342             } else {
2343                 new_mount -= mnt_offset;
2344             }
2345 
2346             player *passenger = nullptr;
2347             // Unboard any entities standing on any transferred part
2348             if( part_flag( mov_part, "BOARDABLE" ) ) {
2349                 passenger = get_passenger( mov_part );
2350                 if( passenger ) {
2351                     passengers.push_back( passenger );
2352                 }
2353             }
2354             // if this part is a towing part, transfer the tow_data to the new vehicle.
2355             if( part_flag( mov_part, "TOW_CABLE" ) ) {
2356                 if( is_towed() ) {
2357                     tow_data.get_towed_by()->tow_data.set_towing( tow_data.get_towed_by(), new_vehicle );
2358                     tow_data.clear_towing();
2359                 } else if( is_towing() ) {
2360                     tow_data.get_towed()->tow_data.set_towing( new_vehicle, tow_data.get_towed() );
2361                     tow_data.clear_towing();
2362                 }
2363             }
2364             // transfer the vehicle_part to the new vehicle
2365             new_vehicle->parts.emplace_back( parts[ mov_part ] );
2366             new_vehicle->parts.back().mount = new_mount;
2367 
2368             // remove labels associated with the mov_part
2369             const auto iter = labels.find( label( cur_mount ) );
2370             if( iter != labels.end() ) {
2371                 std::string label_str = iter->text;
2372                 labels.erase( iter );
2373                 new_labels.insert( label( new_mount, label_str ) );
2374             }
2375             // Prepare the zones to be moved to the new vehicle
2376             const std::pair<std::unordered_multimap<point, zone_data>::iterator, std::unordered_multimap<point, zone_data>::iterator>
2377             zones_on_point = loot_zones.equal_range( cur_mount );
2378             for( std::unordered_multimap<point, zone_data>::const_iterator lz_iter = zones_on_point.first;
2379                  lz_iter != zones_on_point.second; ++lz_iter ) {
2380                 new_zones.emplace( new_mount, lz_iter->second );
2381             }
2382 
2383             // Erasing on the key removes all the zones from the point at once
2384             loot_zones.erase( cur_mount );
2385 
2386             // The zone manager will be updated when we next interact with it through get_vehicle_zones
2387             zones_dirty = true;
2388 
2389             // remove the passenger from the old vehicle
2390             if( passenger ) {
2391                 parts[ mov_part ].remove_flag( vehicle_part::passenger_flag );
2392                 parts[ mov_part ].passenger_id = character_id();
2393             }
2394             // indicate the part needs to be removed from the old vehicle
2395             parts[ mov_part].removed = true;
2396             removed_part_count++;
2397         }
2398 
2399         // We want to create the vehicle zones after we've setup the parts
2400         // because we need only to move the zone once per mount, not per part. If we move per
2401         // part, we will end up with duplicates of the zone per part on the same mount
2402         for( std::pair<point, zone_data> zone : new_zones ) {
2403             zone_manager::get_manager().create_vehicle_loot_zone( *new_vehicle, zone.first, zone.second );
2404         }
2405 
2406         // create_vehicle_loot_zone marks the vehicle as not dirty but since we got these zones
2407         // in an unknown state from the previous vehicle, we need to let the cache rebuild next
2408         // time we interact with them
2409         new_vehicle->zones_dirty = true;
2410 
2411         here.dirty_vehicle_list.insert( new_vehicle );
2412         here.set_transparency_cache_dirty( sm_pos.z );
2413         here.set_seen_cache_dirty( tripoint_zero );
2414         if( !new_labels.empty() ) {
2415             new_vehicle->labels = new_labels;
2416         }
2417 
2418         if( split_mounts.empty() ) {
2419             new_vehicle->refresh();
2420         } else {
2421             // include refresh
2422             new_vehicle->shift_parts( point_zero - mnt_offset );
2423         }
2424 
2425         // update the precalc points
2426         new_vehicle->precalc_mounts( 1, new_vehicle->skidding ?
2427                                      new_vehicle->turn_dir : new_vehicle->face.dir(),
2428                                      new_vehicle->pivot_point() );
2429         if( !passengers.empty() ) {
2430             new_vehicle->relocate_passengers( passengers );
2431         }
2432     }
2433     return did_split;
2434 }
2435 
split_vehicles(const std::vector<std::vector<int>> & new_vehs)2436 bool vehicle::split_vehicles( const std::vector<std::vector <int>> &new_vehs )
2437 {
2438     std::vector<vehicle *> null_vehicles;
2439     std::vector<std::vector <point>> null_mounts;
2440     std::vector<point> nothing;
2441     null_vehicles.assign( new_vehs.size(), nullptr );
2442     null_mounts.assign( new_vehs.size(), nothing );
2443     return split_vehicles( new_vehs, null_vehicles, null_mounts );
2444 }
2445 
part_base(int p)2446 item_location vehicle::part_base( int p )
2447 {
2448     return item_location( vehicle_cursor( *this, p ), &parts[ p ].base );
2449 }
2450 
find_part(const item & it) const2451 int vehicle::find_part( const item &it ) const
2452 {
2453     auto idx = std::find_if( parts.begin(), parts.end(), [&it]( const vehicle_part & e ) {
2454         return &e.base == &it;
2455     } );
2456     return idx != parts.end() ? std::distance( parts.begin(), idx ) : INT_MIN;
2457 }
2458 
pieces_for_broken_part() const2459 item_group::ItemList vehicle_part::pieces_for_broken_part() const
2460 {
2461     const item_group_id &group = info().breaks_into_group;
2462     // TODO: make it optional? Or use id of empty item group?
2463     if( group.is_empty() ) {
2464         return {};
2465     }
2466 
2467     return item_group::items_from( group, calendar::turn );
2468 }
2469 
parts_at_relative(const point & dp,const bool use_cache) const2470 std::vector<int> vehicle::parts_at_relative( const point &dp,
2471         const bool use_cache ) const
2472 {
2473     if( !use_cache ) {
2474         std::vector<int> res;
2475         for( const vpart_reference &vp : get_all_parts() ) {
2476             if( vp.mount() == dp && !vp.part().removed ) {
2477                 res.push_back( static_cast<int>( vp.part_index() ) );
2478             }
2479         }
2480         return res;
2481     } else {
2482         const auto &iter = relative_parts.find( dp );
2483         if( iter != relative_parts.end() ) {
2484             return iter->second;
2485         } else {
2486             std::vector<int> res;
2487             return res;
2488         }
2489     }
2490 }
2491 
obstacle_at_part() const2492 cata::optional<vpart_reference> vpart_position::obstacle_at_part() const
2493 {
2494     const cata::optional<vpart_reference> part = part_with_feature( VPFLAG_OBSTACLE, true );
2495     if( !part ) {
2496         return cata::nullopt; // No obstacle here
2497     }
2498 
2499     if( part->has_feature( VPFLAG_OPENABLE ) && part->part().open ) {
2500         return cata::nullopt; // Open door here
2501     }
2502 
2503     return part;
2504 }
2505 
part_displayed() const2506 cata::optional<vpart_reference> vpart_position::part_displayed() const
2507 {
2508     int part_id = vehicle().part_displayed_at( mount() );
2509     if( part_id == -1 ) {
2510         return cata::nullopt;
2511     }
2512     return vpart_reference( vehicle(), part_id );
2513 }
2514 
part_with_tool(const itype_id & tool_type) const2515 cata::optional<vpart_reference> vpart_position::part_with_tool( const itype_id &tool_type ) const
2516 {
2517     for( const int idx : vehicle().parts_at_relative( mount(), false ) ) {
2518         const vpart_reference vp( vehicle(), idx );
2519         if( vp.part().is_available() && vp.info().has_tool( tool_type ) ) {
2520             return vp;
2521         }
2522     }
2523     return cata::optional<vpart_reference>();
2524 }
2525 
get_tools() const2526 std::vector<std::pair<itype_id, int>> vpart_position::get_tools() const
2527 {
2528     std::set<std::pair<itype_id, int>> tools;
2529     for( const int part_idx : this->vehicle().parts_at_relative( this->mount(), false ) ) {
2530         const vpart_reference vp( this->vehicle(), part_idx );
2531         if( vp.part().is_broken() ) {
2532             continue;
2533         }
2534         std::set<std::pair<itype_id, int>> items = vp.part().info().get_pseudo_tools();
2535         std::copy( items.cbegin(), items.cend(), std::inserter( tools, tools.end() ) );
2536     }
2537 
2538     return std::vector<std::pair<itype_id, int>>( tools.cbegin(), tools.cend() );
2539 }
2540 
part_with_feature(const std::string & f,const bool unbroken) const2541 cata::optional<vpart_reference> vpart_position::part_with_feature( const std::string &f,
2542         const bool unbroken ) const
2543 {
2544     const int i = vehicle().part_with_feature( part_index(), f, unbroken );
2545     if( i < 0 ) {
2546         return cata::nullopt;
2547     }
2548     return vpart_reference( vehicle(), i );
2549 }
2550 
part_with_feature(const vpart_bitflags f,const bool unbroken) const2551 cata::optional<vpart_reference> vpart_position::part_with_feature( const vpart_bitflags f,
2552         const bool unbroken ) const
2553 {
2554     const int i = vehicle().part_with_feature( part_index(), f, unbroken );
2555     if( i < 0 ) {
2556         return cata::nullopt;
2557     }
2558     return vpart_reference( vehicle(), i );
2559 }
2560 
avail_part_with_feature(const std::string & f) const2561 cata::optional<vpart_reference> vpart_position::avail_part_with_feature(
2562     const std::string &f ) const
2563 {
2564     const int i = vehicle().avail_part_with_feature( part_index(), f );
2565     return i >= 0 ? vpart_reference( vehicle(), i ) : cata::optional<vpart_reference>();
2566 }
2567 
avail_part_with_feature(vpart_bitflags f) const2568 cata::optional<vpart_reference> vpart_position::avail_part_with_feature( vpart_bitflags f ) const
2569 {
2570     const int i = vehicle().avail_part_with_feature( part_index(), f );
2571     return i >= 0 ? vpart_reference( vehicle(), i ) : cata::optional<vpart_reference>();
2572 }
2573 
part_with_feature(const std::string & f,const bool unbroken) const2574 cata::optional<vpart_reference> optional_vpart_position::part_with_feature( const std::string &f,
2575         const bool unbroken ) const
2576 {
2577     return has_value() ? value().part_with_feature( f, unbroken ) : cata::nullopt;
2578 }
2579 
part_with_feature(const vpart_bitflags f,const bool unbroken) const2580 cata::optional<vpart_reference> optional_vpart_position::part_with_feature( const vpart_bitflags f,
2581         const bool unbroken ) const
2582 {
2583     return has_value() ? value().part_with_feature( f, unbroken ) : cata::nullopt;
2584 }
2585 
avail_part_with_feature(const std::string & f) const2586 cata::optional<vpart_reference> optional_vpart_position::avail_part_with_feature(
2587     const std::string &f ) const
2588 {
2589     return has_value() ? value().avail_part_with_feature( f ) : cata::nullopt;
2590 }
2591 
avail_part_with_feature(vpart_bitflags f) const2592 cata::optional<vpart_reference> optional_vpart_position::avail_part_with_feature(
2593     vpart_bitflags f ) const
2594 {
2595     return has_value() ? value().avail_part_with_feature( f ) : cata::nullopt;
2596 }
2597 
obstacle_at_part() const2598 cata::optional<vpart_reference> optional_vpart_position::obstacle_at_part() const
2599 {
2600     return has_value() ? value().obstacle_at_part() : cata::nullopt;
2601 }
2602 
part_displayed() const2603 cata::optional<vpart_reference> optional_vpart_position::part_displayed() const
2604 {
2605     return has_value() ? value().part_displayed() : cata::nullopt;
2606 }
2607 
part_with_tool(const itype_id & tool_type) const2608 cata::optional<vpart_reference> optional_vpart_position::part_with_tool(
2609     const itype_id &tool_type ) const
2610 {
2611     return has_value() ? value().part_with_tool( tool_type ) : cata::nullopt;
2612 }
2613 
part_with_feature(int part,vpart_bitflags const flag,bool unbroken) const2614 int vehicle::part_with_feature( int part, vpart_bitflags const flag, bool unbroken ) const
2615 {
2616     if( part_flag( part, flag ) && ( !unbroken || !parts[part].is_broken() ) ) {
2617         return part;
2618     }
2619     const auto it = relative_parts.find( parts[part].mount );
2620     if( it != relative_parts.end() ) {
2621         const std::vector<int> &parts_here = it->second;
2622         for( const int &i : parts_here ) {
2623             if( part_flag( i, flag ) && ( !unbroken || !parts[i].is_broken() ) ) {
2624                 return i;
2625             }
2626         }
2627     }
2628     return -1;
2629 }
2630 
part_with_feature(int part,const std::string & flag,bool unbroken) const2631 int vehicle::part_with_feature( int part, const std::string &flag, bool unbroken ) const
2632 {
2633     return part_with_feature( parts[part].mount, flag, unbroken );
2634 }
2635 
get_tools() const2636 std::vector<std::pair<itype_id, int>> optional_vpart_position::get_tools() const
2637 {
2638     return has_value() ? value().get_tools() : std::vector<std::pair<itype_id, int>>();
2639 }
2640 
part_with_feature(const point & pt,const std::string & flag,bool unbroken) const2641 int vehicle::part_with_feature( const point &pt, const std::string &flag, bool unbroken ) const
2642 {
2643     std::vector<int> parts_here = parts_at_relative( pt, false );
2644     for( const int &elem : parts_here ) {
2645         if( part_flag( elem, flag ) && ( !unbroken || !parts[ elem ].is_broken() ) ) {
2646             return elem;
2647         }
2648     }
2649     return -1;
2650 }
2651 
avail_part_with_feature(int part,vpart_bitflags const flag) const2652 int vehicle::avail_part_with_feature( int part, vpart_bitflags const flag ) const
2653 {
2654     int part_a = part_with_feature( part, flag, true );
2655     if( ( part_a >= 0 ) && parts[ part_a ].is_available() ) {
2656         return part_a;
2657     }
2658     return -1;
2659 }
2660 
avail_part_with_feature(int part,const std::string & flag) const2661 int vehicle::avail_part_with_feature( int part, const std::string &flag ) const
2662 {
2663     return avail_part_with_feature( parts[ part ].mount, flag );
2664 }
2665 
avail_part_with_feature(const point & pt,const std::string & flag) const2666 int vehicle::avail_part_with_feature( const point &pt, const std::string &flag ) const
2667 {
2668     int part_a = part_with_feature( pt, flag, true );
2669     if( ( part_a >= 0 ) && parts[ part_a ].is_available() ) {
2670         return part_a;
2671     }
2672     return -1;
2673 }
2674 
has_part(const std::string & flag,bool enabled) const2675 bool vehicle::has_part( const std::string &flag, bool enabled ) const
2676 {
2677     return std::any_of( parts.begin(), parts.end(), [&flag, &enabled]( const vehicle_part & e ) {
2678         return !e.removed && ( !enabled || e.enabled ) && !e.is_broken() && e.info().has_flag( flag );
2679     } );
2680 }
2681 
has_part(const tripoint & pos,const std::string & flag,bool enabled) const2682 bool vehicle::has_part( const tripoint &pos, const std::string &flag, bool enabled ) const
2683 {
2684     const tripoint relative_pos = pos - global_pos3();
2685 
2686     for( const vehicle_part &e : parts ) {
2687         if( e.precalc[0] != relative_pos ) {
2688             continue;
2689         }
2690         if( !e.removed && ( !enabled || e.enabled ) && !e.is_broken() && e.info().has_flag( flag ) ) {
2691             return true;
2692         }
2693     }
2694     return false;
2695 }
2696 
get_parts_at(const tripoint & pos,const std::string & flag,const part_status_flag condition)2697 std::vector<vehicle_part *> vehicle::get_parts_at( const tripoint &pos, const std::string &flag,
2698         const part_status_flag condition )
2699 {
2700     const tripoint relative_pos = pos - global_pos3();
2701     std::vector<vehicle_part *> res;
2702     for( vehicle_part &e : parts ) {
2703         if( e.precalc[ 0 ] != relative_pos ) {
2704             continue;
2705         }
2706         if( !e.removed &&
2707             ( flag.empty() || e.info().has_flag( flag ) ) &&
2708             ( !( condition & part_status_flag::enabled ) || e.enabled ) &&
2709             ( !( condition & part_status_flag::working ) || !e.is_broken() ) ) {
2710             res.push_back( &e );
2711         }
2712     }
2713     return res;
2714 }
2715 
get_parts_at(const tripoint & pos,const std::string & flag,const part_status_flag condition) const2716 std::vector<const vehicle_part *> vehicle::get_parts_at( const tripoint &pos,
2717         const std::string &flag,
2718         const part_status_flag condition ) const
2719 {
2720     const tripoint relative_pos = pos - global_pos3();
2721     std::vector<const vehicle_part *> res;
2722     for( const vehicle_part &e : parts ) {
2723         if( e.precalc[ 0 ] != relative_pos ) {
2724             continue;
2725         }
2726         if( !e.removed &&
2727             ( flag.empty() || e.info().has_flag( flag ) ) &&
2728             ( !( condition & part_status_flag::enabled ) || e.enabled ) &&
2729             ( !( condition & part_status_flag::working ) || !e.is_broken() ) ) {
2730             res.push_back( &e );
2731         }
2732     }
2733     return res;
2734 }
2735 
get_label() const2736 cata::optional<std::string> vpart_position::get_label() const
2737 {
2738     const auto it = vehicle().labels.find( label( mount() ) );
2739     if( it == vehicle().labels.end() ) {
2740         return cata::nullopt;
2741     }
2742     if( it->text.empty() ) {
2743         // legacy support TODO: change labels into a map and keep track of deleted labels
2744         return cata::nullopt;
2745     }
2746     return it->text;
2747 }
2748 
set_label(const std::string & text) const2749 void vpart_position::set_label( const std::string &text ) const
2750 {
2751     std::set<label> &labels = vehicle().labels;
2752     const auto it = labels.find( label( mount() ) );
2753     // TODO: empty text should remove the label instead of just storing an empty string, see get_label
2754     if( it == labels.end() ) {
2755         labels.insert( label( mount(), text ) );
2756     } else {
2757         // labels should really be a map
2758         labels.insert( labels.erase( it ), label( mount(), text ) );
2759     }
2760 }
2761 
next_part_to_close(int p,bool outside) const2762 int vehicle::next_part_to_close( int p, bool outside ) const
2763 {
2764     std::vector<int> parts_here = parts_at_relative( parts[p].mount, true );
2765 
2766     // We want reverse, since we close the outermost thing first (curtains), and then the innermost thing (door)
2767     for( std::vector<int>::reverse_iterator part_it = parts_here.rbegin();
2768          part_it != parts_here.rend();
2769          ++part_it ) {
2770 
2771         if( part_flag( *part_it, VPFLAG_OPENABLE )
2772             && parts[ *part_it ].is_available()
2773             && parts[*part_it].open == 1
2774             && ( !outside || !part_flag( *part_it, "OPENCLOSE_INSIDE" ) ) ) {
2775             return *part_it;
2776         }
2777     }
2778     return -1;
2779 }
2780 
next_part_to_open(int p,bool outside) const2781 int vehicle::next_part_to_open( int p, bool outside ) const
2782 {
2783     std::vector<int> parts_here = parts_at_relative( parts[p].mount, true );
2784 
2785     // We want forwards, since we open the innermost thing first (curtains), and then the innermost thing (door)
2786     for( const int &elem : parts_here ) {
2787         if( part_flag( elem, VPFLAG_OPENABLE ) && parts[ elem ].is_available() && parts[elem].open == 0 &&
2788             ( !outside || !part_flag( elem, "OPENCLOSE_INSIDE" ) ) ) {
2789             return elem;
2790         }
2791     }
2792     return -1;
2793 }
2794 
get_avail_parts(std::string feature) const2795 vehicle_part_with_feature_range<std::string> vehicle::get_avail_parts( std::string feature ) const
2796 {
2797     return vehicle_part_with_feature_range<std::string>( const_cast<vehicle &>( *this ),
2798             std::move( feature ),
2799             static_cast<part_status_flag>( part_status_flag::working |
2800                                            part_status_flag::available ) );
2801 }
2802 
get_avail_parts(const vpart_bitflags feature) const2803 vehicle_part_with_feature_range<vpart_bitflags> vehicle::get_avail_parts(
2804     const vpart_bitflags feature ) const
2805 {
2806     return vehicle_part_with_feature_range<vpart_bitflags>( const_cast<vehicle &>( *this ), feature,
2807             static_cast<part_status_flag>( part_status_flag::working |
2808                                            part_status_flag::available ) );
2809 }
2810 
get_parts_including_carried(std::string feature) const2811 vehicle_part_with_feature_range<std::string> vehicle::get_parts_including_carried(
2812     std::string feature ) const
2813 {
2814     return vehicle_part_with_feature_range<std::string>( const_cast<vehicle &>( *this ),
2815             std::move( feature ), part_status_flag::working );
2816 }
2817 
get_parts_including_carried(const vpart_bitflags feature) const2818 vehicle_part_with_feature_range<vpart_bitflags> vehicle::get_parts_including_carried(
2819     const vpart_bitflags feature ) const
2820 {
2821     return vehicle_part_with_feature_range<vpart_bitflags>( const_cast<vehicle &>( *this ), feature,
2822             part_status_flag::working );
2823 }
2824 
get_any_parts(std::string feature) const2825 vehicle_part_with_feature_range<std::string> vehicle::get_any_parts( std::string feature ) const
2826 {
2827     return vehicle_part_with_feature_range<std::string>( const_cast<vehicle &>( *this ),
2828             std::move( feature ), part_status_flag::any );
2829 }
2830 
get_any_parts(const vpart_bitflags feature) const2831 vehicle_part_with_feature_range<vpart_bitflags> vehicle::get_any_parts(
2832     const vpart_bitflags feature ) const
2833 {
2834     return vehicle_part_with_feature_range<vpart_bitflags>( const_cast<vehicle &>( *this ), feature,
2835             part_status_flag::any );
2836 }
2837 
get_enabled_parts(std::string feature) const2838 vehicle_part_with_feature_range<std::string> vehicle::get_enabled_parts( std::string feature ) const
2839 {
2840     return vehicle_part_with_feature_range<std::string>( const_cast<vehicle &>( *this ),
2841             std::move( feature ),
2842             static_cast<part_status_flag>( part_status_flag::enabled |
2843                                            part_status_flag::working |
2844                                            part_status_flag::available ) );
2845 }
2846 
get_enabled_parts(const vpart_bitflags feature) const2847 vehicle_part_with_feature_range<vpart_bitflags> vehicle::get_enabled_parts(
2848     const vpart_bitflags feature ) const
2849 {
2850     return vehicle_part_with_feature_range<vpart_bitflags>( const_cast<vehicle &>( *this ), feature,
2851             static_cast<part_status_flag>( part_status_flag::enabled |
2852                                            part_status_flag::working |
2853                                            part_status_flag::available ) );
2854 }
2855 
2856 /**
2857  * Returns all parts in the vehicle that exist in the given location slot. If
2858  * the empty string is passed in, returns all parts with no slot.
2859  * @param location The location slot to get parts for.
2860  * @return A list of indices to all parts with the specified location.
2861  */
all_parts_at_location(const std::string & location) const2862 std::vector<int> vehicle::all_parts_at_location( const std::string &location ) const
2863 {
2864     std::vector<int> parts_found;
2865     for( size_t part_index = 0; part_index < parts.size(); ++part_index ) {
2866         if( part_info( part_index ).location == location && !parts[part_index].removed ) {
2867             parts_found.push_back( part_index );
2868         }
2869     }
2870     return parts_found;
2871 }
2872 
2873 // another NPC probably removed a part in the time it took to walk here and start the activity.
2874 // as the part index was first "chosen" before the NPC started traveling here.
2875 // therefore the part index is now invalid shifted by one or two ( depending on how many other NPCs working on this vehicle )
2876 // so loop over the part indexes in reverse order to get the next one down that matches the part type we wanted to remove
get_next_shifted_index(int original_index,player & p)2877 int vehicle::get_next_shifted_index( int original_index, player &p )
2878 {
2879     int ret_index = original_index;
2880     bool found_shifted_index = false;
2881     for( std::vector<vehicle_part>::reverse_iterator it = parts.rbegin(); it != parts.rend(); ++it ) {
2882         if( p.get_value( "veh_index_type" ) == it->info().name() ) {
2883             ret_index = index_of_part( &*it );
2884             found_shifted_index = true;
2885             break;
2886         }
2887     }
2888     if( !found_shifted_index ) {
2889         // we are probably down to a few parts left, and things get messy here, so an alternative index maybe can't be found
2890         // if loads of npcs are all removing parts at the same time.
2891         // if that's the case, just bail out and give up, somebody else is probably doing the job right now anyway.
2892         return -1;
2893     } else {
2894         return ret_index;
2895     }
2896 }
2897 
2898 /**
2899  * Returns all parts in the vehicle that have the specified flag in their vpinfo and
2900  * are on the same X-axis or Y-axis as the input part and are contiguous with each other.
2901  * @param part The part to find adjacent parts to
2902  * @param flag The flag to match
2903  * @return A list of lists of indices of all parts sharing the flag and contiguous with the part
2904  * on the X or Y axis. Returns 0, 1, or 2 lists of indices.
2905  */
find_lines_of_parts(int part,const std::string & flag)2906 std::vector<std::vector<int>> vehicle::find_lines_of_parts( int part, const std::string &flag )
2907 {
2908     const auto possible_parts = get_avail_parts( flag );
2909     std::vector<std::vector<int>> ret_parts;
2910     if( empty( possible_parts ) ) {
2911         return ret_parts;
2912     }
2913 
2914     std::vector<int> x_parts;
2915     std::vector<int> y_parts;
2916     vpart_id part_id = part_info( part ).get_id();
2917     // create vectors of parts on the same X or Y axis
2918     point target = parts[ part ].mount;
2919     for( const vpart_reference &vp : possible_parts ) {
2920         if( vp.part().is_unavailable() ||
2921             !vp.has_feature( "MULTISQUARE" ) ||
2922             vp.info().get_id() != part_id )  {
2923             continue;
2924         }
2925         if( vp.mount().x == target.x ) {
2926             x_parts.push_back( vp.part_index() );
2927         }
2928         if( vp.mount().y == target.y ) {
2929             y_parts.push_back( vp.part_index() );
2930         }
2931     }
2932 
2933     if( x_parts.size() > 1 ) {
2934         std::vector<int> x_ret;
2935         // sort by Y-axis, since they're all on the same X-axis
2936         const auto x_sorter = [&]( const int lhs, const int rhs ) {
2937             return( parts[lhs].mount.y > parts[rhs].mount.y );
2938         };
2939         std::sort( x_parts.begin(), x_parts.end(), x_sorter );
2940         int first_part = 0;
2941         int prev_y = parts[ x_parts[ 0 ] ].mount.y;
2942         int i;
2943         bool found_part = x_parts[ 0 ] == part;
2944         for( i = 1; static_cast<size_t>( i ) < x_parts.size(); i++ ) {
2945             // if the Y difference is > 1, there's a break in the run
2946             if( std::abs( parts[ x_parts[ i ] ].mount.y - prev_y )  > 1 ) {
2947                 // if we found the part, this is the run we wanted
2948                 if( found_part ) {
2949                     break;
2950                 }
2951                 first_part = i;
2952             }
2953             found_part |= x_parts[ i ] == part;
2954             prev_y = parts[ x_parts[ i ] ].mount.y;
2955         }
2956         for( size_t j = first_part; j < static_cast<size_t>( i ); j++ ) {
2957             x_ret.push_back( x_parts[ j ] );
2958         }
2959         ret_parts.push_back( x_ret );
2960     }
2961     if( y_parts.size() > 1 ) {
2962         std::vector<int> y_ret;
2963         const auto y_sorter = [&]( const int lhs, const int rhs ) {
2964             return( parts[lhs].mount.x > parts[rhs].mount.x );
2965         };
2966         std::sort( y_parts.begin(), y_parts.end(), y_sorter );
2967         int first_part = 0;
2968         int prev_x = parts[ y_parts[ 0 ] ].mount.x;
2969         int i;
2970         bool found_part = y_parts[ 0 ] == part;
2971         for( i = 1; static_cast<size_t>( i ) < y_parts.size(); i++ ) {
2972             if( std::abs( parts[ y_parts[ i ] ].mount.x - prev_x )  > 1 ) {
2973                 if( found_part ) {
2974                     break;
2975                 }
2976                 first_part = i;
2977             }
2978             found_part |= y_parts[ i ] == part;
2979             prev_x = parts[ y_parts[ i ] ].mount.x;
2980         }
2981         for( size_t j = first_part; j < static_cast<size_t>( i ); j++ ) {
2982             y_ret.push_back( y_parts[ j ] );
2983         }
2984         ret_parts.push_back( y_ret );
2985     }
2986     if( y_parts.size() == 1 && x_parts.size() == 1 ) {
2987         ret_parts.push_back( x_parts );
2988     }
2989     return ret_parts;
2990 }
2991 
part_flag(int part,const std::string & flag) const2992 bool vehicle::part_flag( int part, const std::string &flag ) const
2993 {
2994     if( part < 0 || part >= static_cast<int>( parts.size() ) || parts[part].removed ) {
2995         return false;
2996     } else {
2997         return part_info( part ).has_flag( flag );
2998     }
2999 }
3000 
part_flag(int part,const vpart_bitflags flag) const3001 bool vehicle::part_flag( int part, const vpart_bitflags flag ) const
3002 {
3003     if( part < 0 || part >= static_cast<int>( parts.size() ) || parts[part].removed ) {
3004         return false;
3005     } else {
3006         return part_info( part ).has_flag( flag );
3007     }
3008 }
3009 
part_at(const point & dp) const3010 int vehicle::part_at( const point &dp ) const
3011 {
3012     for( const vpart_reference &vp : get_all_parts() ) {
3013         if( vp.part().precalc[0].xy() == dp && !vp.part().removed ) {
3014             return static_cast<int>( vp.part_index() );
3015         }
3016     }
3017     return -1;
3018 }
3019 
3020 /**
3021  * Given a vehicle part which is inside of this vehicle, returns the index of
3022  * that part. This exists solely because activities relating to vehicle editing
3023  * require the index of the vehicle part to be passed around.
3024  * @param part The part to find.
3025  * @param check_removed Check whether this part can be removed
3026  * @return The part index, -1 if it is not part of this vehicle.
3027  */
index_of_part(const vehicle_part * const part,const bool check_removed) const3028 int vehicle::index_of_part( const vehicle_part *const part, const bool check_removed ) const
3029 {
3030     if( part != nullptr ) {
3031         for( const vpart_reference &vp : get_all_parts() ) {
3032             const vehicle_part &next_part = vp.part();
3033             if( !check_removed && next_part.removed ) {
3034                 continue;
3035             }
3036             if( part->id == next_part.id && part->mount == vp.mount() ) {
3037                 return vp.part_index();
3038             }
3039         }
3040     }
3041     return -1;
3042 }
3043 
3044 /**
3045  * Returns which part (as an index into the parts list) is the one that will be
3046  * displayed for the given square. Returns -1 if there are no parts in that
3047  * square.
3048  * @param dp The local coordinate.
3049  * @return The index of the part that will be displayed.
3050  */
part_displayed_at(const point & dp) const3051 int vehicle::part_displayed_at( const point &dp ) const
3052 {
3053     // Z-order is implicitly defined in game::load_vehiclepart, but as
3054     // numbers directly set on parts rather than constants that can be
3055     // used elsewhere. A future refactor might be nice but this way
3056     // it's clear where the magic number comes from.
3057     const int ON_ROOF_Z = 9;
3058 
3059     std::vector<int> parts_in_square = parts_at_relative( dp, true );
3060 
3061     if( parts_in_square.empty() ) {
3062         return -1;
3063     }
3064 
3065     Character &player_character = get_player_character();
3066     bool in_vehicle = player_character.in_vehicle;
3067     if( in_vehicle ) {
3068         // They're in a vehicle, but are they in /this/ vehicle?
3069         std::vector<int> psg_parts = boarded_parts();
3070         in_vehicle = false;
3071         for( const int &psg_part : psg_parts ) {
3072             if( get_passenger( psg_part ) == &player_character ) {
3073                 in_vehicle = true;
3074                 break;
3075             }
3076         }
3077     }
3078 
3079     int hide_z_at_or_above = ( in_vehicle ) ? ( ON_ROOF_Z ) : INT_MAX;
3080 
3081     int top_part = 0;
3082     for( size_t index = 1; index < parts_in_square.size(); index++ ) {
3083         if( ( part_info( parts_in_square[top_part] ).z_order <
3084               part_info( parts_in_square[index] ).z_order ) &&
3085             ( part_info( parts_in_square[index] ).z_order <
3086               hide_z_at_or_above ) ) {
3087             top_part = index;
3088         }
3089     }
3090 
3091     return parts_in_square[top_part];
3092 }
3093 
roof_at_part(const int part) const3094 int vehicle::roof_at_part( const int part ) const
3095 {
3096     std::vector<int> parts_in_square = parts_at_relative( parts[part].mount, true );
3097     for( const int p : parts_in_square ) {
3098         if( part_info( p ).location == "on_roof" || part_flag( p, "ROOF" ) ) {
3099             return p;
3100         }
3101     }
3102 
3103     return -1;
3104 }
3105 
coord_translate(const point & p) const3106 point vehicle::coord_translate( const point &p ) const
3107 {
3108     tripoint q;
3109     coord_translate( pivot_rotation[0], pivot_anchor[0], p, q );
3110     return q.xy();
3111 }
3112 
coord_translate(const units::angle & dir,const point & pivot,const point & p,tripoint & q) const3113 void vehicle::coord_translate( const units::angle &dir, const point &pivot, const point &p,
3114                                tripoint &q ) const
3115 {
3116     tileray tdir( dir );
3117     tdir.advance( p.x - pivot.x );
3118     q.x = tdir.dx() + tdir.ortho_dx( p.y - pivot.y );
3119     q.y = tdir.dy() + tdir.ortho_dy( p.y - pivot.y );
3120 }
3121 
coord_translate(tileray tdir,const point & pivot,const point & p,tripoint & q) const3122 void vehicle::coord_translate( tileray tdir, const point &pivot, const point &p, tripoint &q ) const
3123 {
3124     tdir.clear_advance();
3125     tdir.advance( p.x - pivot.x );
3126     q.x = tdir.dx() + tdir.ortho_dx( p.y - pivot.y );
3127     q.y = tdir.dy() + tdir.ortho_dy( p.y - pivot.y );
3128 }
3129 
rotate_mount(units::angle old_dir,units::angle new_dir,const point & pivot,const point & p) const3130 point vehicle::rotate_mount( units::angle old_dir, units::angle new_dir, const point &pivot,
3131                              const point &p ) const
3132 {
3133     tripoint q;
3134     coord_translate( new_dir - old_dir, pivot, p, q );
3135     return q.xy();
3136 }
3137 
mount_to_tripoint(const point & mount) const3138 tripoint vehicle::mount_to_tripoint( const point &mount ) const
3139 {
3140     return mount_to_tripoint( mount, point_zero );
3141 }
3142 
mount_to_tripoint(const point & mount,const point & offset) const3143 tripoint vehicle::mount_to_tripoint( const point &mount, const point &offset ) const
3144 {
3145     tripoint mnt_translated;
3146     coord_translate( pivot_rotation[0], pivot_anchor[ 0 ], mount + offset, mnt_translated );
3147     return global_pos3() + mnt_translated;
3148 }
3149 
precalc_mounts(int idir,const units::angle & dir,const point & pivot)3150 void vehicle::precalc_mounts( int idir, const units::angle &dir, const point &pivot )
3151 {
3152     if( idir < 0 || idir > 1 ) {
3153         idir = 0;
3154     }
3155     tileray tdir( dir );
3156     std::unordered_map<point, tripoint> mount_to_precalc;
3157     for( vehicle_part &p : parts ) {
3158         if( p.removed ) {
3159             continue;
3160         }
3161         auto q = mount_to_precalc.find( p.mount );
3162         if( q == mount_to_precalc.end() ) {
3163             coord_translate( tdir, pivot, p.mount, p.precalc[idir] );
3164             mount_to_precalc.insert( { p.mount, p.precalc[idir] } );
3165         } else {
3166             p.precalc[idir] = q->second;
3167         }
3168     }
3169     pivot_anchor[idir] = pivot;
3170     pivot_rotation[idir] = dir;
3171 }
3172 
boarded_parts() const3173 std::vector<int> vehicle::boarded_parts() const
3174 {
3175     std::vector<int> res;
3176     for( const vpart_reference &vp : get_avail_parts( VPFLAG_BOARDABLE ) ) {
3177         if( vp.part().has_flag( vehicle_part::passenger_flag ) ) {
3178             res.push_back( static_cast<int>( vp.part_index() ) );
3179         }
3180     }
3181     return res;
3182 }
3183 
get_riders() const3184 std::vector<rider_data> vehicle::get_riders() const
3185 {
3186     std::vector<rider_data> res;
3187     for( const vpart_reference &vp : get_avail_parts( VPFLAG_BOARDABLE ) ) {
3188         Creature *rider = g->critter_at( vp.pos() );
3189         if( rider ) {
3190             rider_data r;
3191             r.prt = vp.part_index();
3192             r.psg = rider;
3193             res.emplace_back( r );
3194         }
3195     }
3196     return res;
3197 }
3198 
get_passenger(int p) const3199 player *vehicle::get_passenger( int p ) const
3200 {
3201     p = part_with_feature( p, VPFLAG_BOARDABLE, false );
3202     if( p >= 0 && parts[p].has_flag( vehicle_part::passenger_flag ) ) {
3203         return g->critter_by_id<player>( parts[p].passenger_id );
3204     }
3205     return nullptr;
3206 }
3207 
get_monster(int p) const3208 monster *vehicle::get_monster( int p ) const
3209 {
3210     p = part_with_feature( p, VPFLAG_BOARDABLE, false );
3211     if( p >= 0 ) {
3212         return g->critter_at<monster>( global_part_pos3( p ), true );
3213     }
3214     return nullptr;
3215 }
3216 
global_pos3() const3217 tripoint vehicle::global_pos3() const
3218 {
3219     return sm_to_ms_copy( sm_pos ) + pos;
3220 }
3221 
global_part_pos3(const int & index) const3222 tripoint vehicle::global_part_pos3( const int &index ) const
3223 {
3224     return global_part_pos3( parts[ index ] );
3225 }
3226 
global_part_pos3(const vehicle_part & pt) const3227 tripoint vehicle::global_part_pos3( const vehicle_part &pt ) const
3228 {
3229     return global_pos3() + pt.precalc[ 0 ];
3230 }
3231 
set_submap_moved(const tripoint & p)3232 void vehicle::set_submap_moved( const tripoint &p )
3233 {
3234     const point old_msp = get_map().getabs( global_pos3().xy() );
3235     sm_pos = p;
3236     if( !tracking_on ) {
3237         return;
3238     }
3239     // TODO: fix point types
3240     overmap_buffer.move_vehicle( this, point_abs_ms( old_msp ) );
3241 }
3242 
total_mass() const3243 units::mass vehicle::total_mass() const
3244 {
3245     if( mass_dirty ) {
3246         refresh_mass();
3247     }
3248 
3249     return mass_cache;
3250 }
3251 
total_folded_volume() const3252 units::volume vehicle::total_folded_volume() const
3253 {
3254     units::volume m = 0_ml;
3255     for( const vpart_reference &vp : get_all_parts() ) {
3256         if( vp.part().removed ) {
3257             continue;
3258         }
3259         m += vp.info().folded_volume;
3260     }
3261     return m;
3262 }
3263 
rotated_center_of_mass() const3264 const point &vehicle::rotated_center_of_mass() const
3265 {
3266     // TODO: Bring back caching of this point
3267     calc_mass_center( true );
3268 
3269     return mass_center_precalc;
3270 }
3271 
local_center_of_mass() const3272 const point &vehicle::local_center_of_mass() const
3273 {
3274     if( mass_center_no_precalc_dirty ) {
3275         calc_mass_center( false );
3276     }
3277 
3278     return mass_center_no_precalc;
3279 }
3280 
pivot_displacement() const3281 point vehicle::pivot_displacement() const
3282 {
3283     // precalc_mounts always produces a result that puts the pivot point at (0,0).
3284     // If the pivot point changes, this artificially moves the vehicle, as the position
3285     // of the old pivot point will appear to move from (posx+0, posy+0) to some other point
3286     // (posx+dx,posy+dy) even if there is no change in vehicle position or rotation.
3287     // This method finds that movement so it can be canceled out when actually moving
3288     // the vehicle.
3289 
3290     // rotate the old pivot point around the new pivot point with the old rotation angle
3291     tripoint dp;
3292     coord_translate( pivot_rotation[0], pivot_anchor[1], pivot_anchor[0], dp );
3293     return dp.xy();
3294 }
3295 
fuel_left(const itype_id & ftype,bool recurse) const3296 int vehicle::fuel_left( const itype_id &ftype, bool recurse ) const
3297 {
3298     int fl = 0;
3299 
3300     for( const int i : fuel_containers ) {
3301         const vehicle_part &part = parts[i];
3302         if( part.ammo_current() != ftype ||
3303             // don't count frozen liquid
3304             ( !part.base.contents.empty() && part.is_tank() &&
3305               part.base.contents.legacy_front().made_of( phase_id::SOLID ) ) ) {
3306             continue;
3307         }
3308         fl += part.ammo_remaining();
3309     }
3310 
3311     if( recurse && ftype == fuel_type_battery ) {
3312         auto fuel_counting_visitor = [&]( vehicle const * veh, int amount, int ) {
3313             return amount + veh->fuel_left( ftype, false );
3314         };
3315 
3316         // HAX: add 1 to the initial amount so traversal doesn't immediately stop just
3317         // 'cause we have 0 fuel left in the current vehicle. Subtract the 1 immediately
3318         // after traversal.
3319         fl = traverse_vehicle_graph( this, fl + 1, fuel_counting_visitor ) - 1;
3320     }
3321 
3322     //muscle engines have infinite fuel
3323     if( ftype == fuel_type_muscle ) {
3324         Character &player_character = get_player_character();
3325         // TODO: Allow NPCs to power those
3326         const optional_vpart_position vp = get_map().veh_at( player_character.pos() );
3327         bool player_controlling = player_in_control( player_character );
3328 
3329         //if the engine in the player tile is a muscle engine, and player is controlling vehicle
3330         if( vp && &vp->vehicle() == this && player_controlling ) {
3331             const int p = avail_part_with_feature( vp->part_index(), VPFLAG_ENGINE );
3332             if( p >= 0 && is_part_on( p ) && part_info( p ).fuel_type == fuel_type_muscle ) {
3333                 //Broken limbs prevent muscle engines from working
3334                 if( ( part_info( p ).has_flag( "MUSCLE_LEGS" ) &&
3335                       ( player_character.get_working_leg_count() >= 2 ) ) ||
3336                     ( part_info( p ).has_flag( "MUSCLE_ARMS" ) &&
3337                       ( player_character.get_working_arm_count() >= 2 ) ) ) {
3338                     fl += 10;
3339                 }
3340             }
3341         }
3342         // As do any other engine flagged as perpetual
3343     } else if( item( ftype ).has_flag( flag_PERPETUAL ) ) {
3344         fl += 10;
3345     }
3346 
3347     return fl;
3348 }
fuel_left(const int p,bool recurse) const3349 int vehicle::fuel_left( const int p, bool recurse ) const
3350 {
3351     return fuel_left( parts[ p ].fuel_current(), recurse );
3352 }
3353 
engine_fuel_left(const int e,bool recurse) const3354 int vehicle::engine_fuel_left( const int e, bool recurse ) const
3355 {
3356     if( static_cast<size_t>( e ) < engines.size() ) {
3357         return fuel_left( parts[ engines[ e ] ].fuel_current(), recurse );
3358     }
3359     return 0;
3360 }
3361 
fuel_capacity(const itype_id & ftype) const3362 int vehicle::fuel_capacity( const itype_id &ftype ) const
3363 {
3364     return std::accumulate( parts.begin(), parts.end(), 0, [&ftype]( const int &lhs,
3365     const vehicle_part & rhs ) {
3366         return lhs + ( ( rhs.is_available() &&
3367                          rhs.ammo_current() == ftype ) ? rhs.ammo_capacity( item::find_type(
3368                                      ftype )->ammo->type ) : 0 );
3369     } );
3370 }
3371 
fuel_specific_energy(const itype_id & ftype) const3372 float vehicle::fuel_specific_energy( const itype_id &ftype ) const
3373 {
3374     float total_energy = 0.0f;
3375     float total_mass = 0.0f;
3376     for( const vehicle_part &vp : parts ) {
3377         if( vp.is_tank() && vp.ammo_current() == ftype &&
3378             vp.base.contents.legacy_front().made_of( phase_id::LIQUID ) ) {
3379             float energy = vp.base.contents.legacy_front().specific_energy;
3380             float mass = to_gram( vp.base.contents.legacy_front().weight() );
3381             total_energy += energy * mass;
3382             total_mass += mass;
3383         }
3384     }
3385     return total_energy / total_mass;
3386 }
3387 
drain(const itype_id & ftype,int amount)3388 int vehicle::drain( const itype_id &ftype, int amount )
3389 {
3390     if( ftype == fuel_type_battery ) {
3391         // Batteries get special handling to take advantage of jumper
3392         // cables -- discharge_battery knows how to recurse properly
3393         // (including taking cable power loss into account).
3394         int remnant = discharge_battery( amount, true );
3395 
3396         // discharge_battery returns amount of charges that were not
3397         // found anywhere in the power network, whereas this function
3398         // returns amount of charges consumed; simple subtraction.
3399         return amount - remnant;
3400     }
3401 
3402     int drained = 0;
3403     for( vehicle_part &p : parts ) {
3404         if( amount <= 0 ) {
3405             break;
3406         }
3407         if( p.ammo_current() == ftype ) {
3408             int qty = p.ammo_consume( amount, global_part_pos3( p ) );
3409             drained += qty;
3410             amount -= qty;
3411         }
3412     }
3413 
3414     invalidate_mass();
3415     return drained;
3416 }
3417 
drain(const int index,int amount)3418 int vehicle::drain( const int index, int amount )
3419 {
3420     if( index < 0 || index >= static_cast<int>( parts.size() ) ) {
3421         debugmsg( "Tried to drain an invalid part index: %d", index );
3422         return 0;
3423     }
3424     vehicle_part &pt = parts[index];
3425     if( pt.ammo_current() == fuel_type_battery ) {
3426         return drain( fuel_type_battery, amount );
3427     }
3428     if( !pt.is_tank() || !pt.ammo_remaining() ) {
3429         debugmsg( "Tried to drain something without any liquid: %s amount: %d ammo: %d",
3430                   pt.name(), amount, pt.ammo_remaining() );
3431         return 0;
3432     }
3433 
3434     const int drained = pt.ammo_consume( amount, global_part_pos3( pt ) );
3435     invalidate_mass();
3436     return drained;
3437 }
3438 
basic_consumption(const itype_id & ftype) const3439 int vehicle::basic_consumption( const itype_id &ftype ) const
3440 {
3441     int fcon = 0;
3442     for( size_t e = 0; e < engines.size(); ++e ) {
3443         if( is_engine_type_on( e, ftype ) ) {
3444             if( parts[ engines[e] ].ammo_current() == fuel_type_battery &&
3445                 part_epower_w( engines[e] ) >= 0 ) {
3446                 // Electric engine - use epower instead
3447                 fcon -= part_epower_w( engines[e] );
3448 
3449             } else if( !is_perpetual_type( e ) ) {
3450                 fcon += part_vpower_w( engines[e] );
3451                 if( parts[ e ].has_fault_flag( "DOUBLE_FUEL_CONSUMPTION" ) ) {
3452                     fcon *= 2;
3453                 }
3454             }
3455         }
3456     }
3457     return fcon;
3458 }
3459 
consumption_per_hour(const itype_id & ftype,int fuel_rate_w) const3460 int vehicle::consumption_per_hour( const itype_id &ftype, int fuel_rate_w ) const
3461 {
3462     item fuel = item( ftype );
3463     if( fuel_rate_w == 0 || fuel.has_flag( flag_PERPETUAL ) || !engine_on ) {
3464         return 0;
3465     }
3466     // consume this fuel type's share of alternator load for 3600 seconds
3467     int amount_pct = 3600 * alternator_load / 1000;
3468 
3469     // calculate fuel consumption for the lower of safe speed or 70 mph
3470     // or 0 if the vehicle is idling
3471     if( is_moving() ) {
3472         int target_v = std::min( safe_velocity(), 70 * 100 );
3473         int vslowdown = slowdown( target_v );
3474         // add 3600 seconds worth of fuel consumption for the engine
3475         // HACK: engines consume 1 second worth of fuel per turn, even though a turn is 6 seconds
3476         if( vslowdown > 0 ) {
3477             int accel = acceleration( true, target_v );
3478             if( accel == 0 ) {
3479                 // FIXME: Long-term plan is to change the fuel consumption
3480                 // computation entirely; for now just warn if this would
3481                 // otherwise have been division-by-zero
3482                 debugmsg( "Vehicle unexpectedly has zero acceleration" );
3483             } else {
3484                 amount_pct += 600 * vslowdown / accel;
3485             }
3486         }
3487     }
3488     int energy_j_per_mL = fuel.fuel_energy() * 1000;
3489     return -amount_pct * fuel_rate_w / energy_j_per_mL;
3490 }
3491 
total_power_w(const bool fueled,const bool safe) const3492 int vehicle::total_power_w( const bool fueled, const bool safe ) const
3493 {
3494     int pwr = 0;
3495     int cnt = 0;
3496 
3497     for( size_t e = 0; e < engines.size(); e++ ) {
3498         int p = engines[e];
3499         if( is_engine_on( e ) && ( !fueled || engine_fuel_left( e ) ) ) {
3500             int m2c = safe ? part_info( engines[e] ).engine_m2c() : 100;
3501             if( parts[ engines[e] ].has_fault_flag( "REDUCE_ENG_POWER" ) ) {
3502                 m2c *= 0.6;
3503             }
3504             pwr += part_vpower_w( p ) * m2c / 100;
3505             cnt++;
3506         }
3507     }
3508 
3509     for( size_t a = 0; a < alternators.size(); a++ ) {
3510         int p = alternators[a];
3511         if( is_alternator_on( a ) ) {
3512             pwr += part_vpower_w( p ); // alternators have negative power
3513         }
3514     }
3515     pwr = std::max( 0, pwr );
3516 
3517     if( cnt > 1 ) {
3518         pwr = pwr * 4 / ( 4 + cnt - 1 );
3519     }
3520     return pwr;
3521 }
3522 
is_moving() const3523 bool vehicle::is_moving() const
3524 {
3525     return velocity != 0;
3526 }
3527 
can_use_rails() const3528 bool vehicle::can_use_rails() const
3529 {
3530     // do not allow vehicles without rail wheels or with mixed wheels
3531     bool can_use = !rail_wheelcache.empty() && wheelcache.size() == rail_wheelcache.size();
3532     if( !can_use ) {
3533         return false;
3534     }
3535     map &here = get_map();
3536     bool is_wheel_on_rail = false;
3537     for( int part_index : rail_wheelcache ) {
3538         // at least one wheel should be on track
3539         if( here.has_flag_ter_or_furn( TFLAG_RAIL, global_part_pos3( part_index ) ) ) {
3540             is_wheel_on_rail = true;
3541             break;
3542         }
3543     }
3544     return is_wheel_on_rail;
3545 }
3546 
ground_acceleration(const bool fueled,int at_vel_in_vmi) const3547 int vehicle::ground_acceleration( const bool fueled, int at_vel_in_vmi ) const
3548 {
3549     if( !( engine_on || skidding ) ) {
3550         return 0;
3551     }
3552     int target_vmiph = std::max( at_vel_in_vmi, std::max( 1000, max_velocity( fueled ) / 4 ) );
3553     int cmps = vmiph_to_cmps( target_vmiph );
3554     double weight = to_kilogram( total_mass() );
3555     if( is_towing() ) {
3556         vehicle *other_veh = tow_data.get_towed();
3557         if( other_veh ) {
3558             weight = weight + to_kilogram( other_veh->total_mass() );
3559         }
3560     }
3561     int engine_power_ratio = total_power_w( fueled ) / weight;
3562     int accel_at_vel = 100 * 100 * engine_power_ratio / cmps;
3563     add_msg_debug( "%s: accel at %d vimph is %d", name, target_vmiph,
3564                    cmps_to_vmiph( accel_at_vel ) );
3565     return cmps_to_vmiph( accel_at_vel );
3566 }
3567 
rotor_acceleration(const bool fueled,int at_vel_in_vmi) const3568 int vehicle::rotor_acceleration( const bool fueled, int at_vel_in_vmi ) const
3569 {
3570     ( void )at_vel_in_vmi;
3571     if( !( engine_on || is_flying ) ) {
3572         return 0;
3573     }
3574     const int accel_at_vel = 100 * lift_thrust_of_rotorcraft( fueled ) / to_kilogram( total_mass() );
3575     return cmps_to_vmiph( accel_at_vel );
3576 }
3577 
water_acceleration(const bool fueled,int at_vel_in_vmi) const3578 int vehicle::water_acceleration( const bool fueled, int at_vel_in_vmi ) const
3579 {
3580     if( !( engine_on || skidding ) ) {
3581         return 0;
3582     }
3583     int target_vmiph = std::max( at_vel_in_vmi, std::max( 1000,
3584                                  max_water_velocity( fueled ) / 4 ) );
3585     int cmps = vmiph_to_cmps( target_vmiph );
3586     double weight = to_kilogram( total_mass() );
3587     if( is_towing() ) {
3588         vehicle *other_veh = tow_data.get_towed();
3589         if( other_veh ) {
3590             weight = weight + to_kilogram( other_veh->total_mass() );
3591         }
3592     }
3593     int engine_power_ratio = total_power_w( fueled ) / weight;
3594     int accel_at_vel = 100 * 100 * engine_power_ratio / cmps;
3595     add_msg_debug( "%s: water accel at %d vimph is %d", name, target_vmiph,
3596                    cmps_to_vmiph( accel_at_vel ) );
3597     return cmps_to_vmiph( accel_at_vel );
3598 }
3599 
3600 // cubic equation solution
3601 // don't use complex numbers unless necessary and it's usually not
3602 // see https://math.vanderbilt.edu/schectex/courses/cubic/ for the gory details
simple_cubic_solution(double a,double b,double c,double d)3603 static double simple_cubic_solution( double a, double b, double c, double d )
3604 {
3605     double p = -b / ( 3 * a );
3606     double q = p * p * p + ( b * c - 3 * a * d ) / ( 6 * a * a );
3607     double r = c / ( 3 * a );
3608     double t = r - p * p;
3609     double tricky_bit = q * q + t * t * t;
3610     if( tricky_bit < 0 ) {
3611         double cr = 1.0 / 3.0; // approximate the cube root of a complex number
3612         std::complex<double> q_complex( q );
3613         std::complex<double> tricky_complex( std::sqrt( std::complex<double>( tricky_bit ) ) );
3614         std::complex<double> term1( std::pow( q_complex + tricky_complex, cr ) );
3615         std::complex<double> term2( std::pow( q_complex - tricky_complex, cr ) );
3616         std::complex<double> term_sum( term1 + term2 );
3617 
3618         if( imag( term_sum ) < 2 ) {
3619             return p + real( term_sum );
3620         } else {
3621             debugmsg( "cubic solution returned imaginary values" );
3622             return 0;
3623         }
3624     } else {
3625         double tricky_final = std::sqrt( tricky_bit );
3626         double term1_part = q + tricky_final;
3627         double term2_part = q - tricky_final;
3628         double term1 = std::cbrt( term1_part );
3629         double term2 = std::cbrt( term2_part );
3630         return p + term1 + term2;
3631     }
3632 }
3633 
acceleration(const bool fueled,int at_vel_in_vmi) const3634 int vehicle::acceleration( const bool fueled, int at_vel_in_vmi ) const
3635 {
3636     if( is_watercraft() ) {
3637         return water_acceleration( fueled, at_vel_in_vmi );
3638     } else if( is_rotorcraft() && is_flying ) {
3639         return rotor_acceleration( fueled, at_vel_in_vmi );
3640     }
3641     return ground_acceleration( fueled, at_vel_in_vmi );
3642 }
3643 
current_acceleration(const bool fueled) const3644 int vehicle::current_acceleration( const bool fueled ) const
3645 {
3646     return acceleration( fueled, std::abs( velocity ) );
3647 }
3648 
3649 // Ugly physics below:
3650 // maximum speed occurs when all available thrust is used to overcome air/rolling resistance
3651 // sigma F = 0 as we were taught in Engineering Mechanics 301
3652 // engine power is torque * rotation rate (in rads for simplicity)
3653 // torque / wheel radius = drive force at where the wheel meets the road
3654 // velocity is wheel radius * rotation rate (in rads for simplicity)
3655 // air resistance is -1/2 * air density * drag coeff * cross area * v^2
3656 //        and c_air_drag is -1/2 * air density * drag coeff * cross area
3657 // rolling resistance is mass * accel_g * rolling coeff * 0.000225 * ( 33.3 + v )
3658 //        and c_rolling_drag is mass * accel_g * rolling coeff * 0.000225
3659 //        and rolling_constant_to_variable is 33.3
3660 // or by formula:
3661 // max velocity occurs when F_drag = F_wheel
3662 // F_wheel = engine_power / rotation_rate / wheel_radius
3663 // velocity = rotation_rate * wheel_radius
3664 // F_wheel * velocity = engine_power * rotation_rate * wheel_radius / rotation_rate / wheel_radius
3665 // F_wheel * velocity = engine_power
3666 // F_wheel = engine_power / velocity
3667 // F_drag = F_air_drag + F_rolling_drag
3668 // F_air_drag = c_air_drag * velocity^2
3669 // F_rolling_drag = c_rolling_drag * velocity + rolling_constant_to_variable * c_rolling_drag
3670 // engine_power / v = c_air_drag * v^2 + c_rolling_drag * v + 33 * c_rolling_drag
3671 // c_air_drag * v^3 + c_rolling_drag * v^2 + c_rolling_drag * 33.3 * v - engine power = 0
3672 // solve for v with the simplified cubic equation solver
3673 // got it? quiz on Wednesday.
max_ground_velocity(const bool fueled) const3674 int vehicle::max_ground_velocity( const bool fueled ) const
3675 {
3676     int total_engine_w = total_power_w( fueled );
3677     double c_rolling_drag = coeff_rolling_drag();
3678     double max_in_mps = simple_cubic_solution( coeff_air_drag(), c_rolling_drag,
3679                         c_rolling_drag * vehicles::rolling_constant_to_variable,
3680                         -total_engine_w );
3681     add_msg_debug( "%s: power %d, c_air %3.2f, c_rolling %3.2f, max_in_mps %3.2f",
3682                    name, total_engine_w, coeff_air_drag(), c_rolling_drag, max_in_mps );
3683     return mps_to_vmiph( max_in_mps );
3684 }
3685 
3686 // the same physics as ground velocity, but there's no rolling resistance so the math is easy
3687 // F_drag = F_water_drag + F_air_drag
3688 // F_drag = c_water_drag * velocity^2 + c_air_drag * velocity^2
3689 // F_drag = ( c_water_drag + c_air_drag ) * velocity^2
3690 // F_prop = engine_power / velocity
3691 // F_prop = F_drag
3692 // engine_power / velocity = ( c_water_drag + c_air_drag ) * velocity^2
3693 // engine_power = ( c_water_drag + c_air_drag ) * velocity^3
3694 // velocity^3 = engine_power / ( c_water_drag + c_air_drag )
3695 // velocity = cube root( engine_power / ( c_water_drag + c_air_drag ) )
max_water_velocity(const bool fueled) const3696 int vehicle::max_water_velocity( const bool fueled ) const
3697 {
3698     int total_engine_w = total_power_w( fueled );
3699     double total_drag = coeff_water_drag() + coeff_air_drag();
3700     double max_in_mps = std::cbrt( total_engine_w / total_drag );
3701     add_msg_debug( "%s: power %d, c_air %3.2f, c_water %3.2f, water max_in_mps %3.2f",
3702                    name, total_engine_w, coeff_air_drag(), coeff_water_drag(), max_in_mps );
3703     return mps_to_vmiph( max_in_mps );
3704 }
3705 
max_rotor_velocity(const bool fueled) const3706 int vehicle::max_rotor_velocity( const bool fueled ) const
3707 {
3708     const double max_air_mps = std::sqrt( lift_thrust_of_rotorcraft( fueled ) / coeff_air_drag() );
3709     // helicopters just cannot go over 250mph at very maximum
3710     // weird things start happening to their rotors if they do.
3711     // due to the rotor tips going supersonic.
3712     return std::min( 25501, mps_to_vmiph( max_air_mps ) );
3713 }
3714 
max_velocity(const bool fueled) const3715 int vehicle::max_velocity( const bool fueled ) const
3716 {
3717     if( is_flying && is_rotorcraft() ) {
3718         return max_rotor_velocity( fueled );
3719     } else if( is_watercraft() ) {
3720         return max_water_velocity( fueled );
3721     } else {
3722         return max_ground_velocity( fueled );
3723     }
3724 }
3725 
max_reverse_velocity(const bool fueled) const3726 int vehicle::max_reverse_velocity( const bool fueled ) const
3727 {
3728     int max_vel = max_velocity( fueled );
3729     if( has_engine_type( fuel_type_battery, true ) ) {
3730         // Electric motors can go in reverse as well as forward
3731         return -max_vel;
3732     } else {
3733         // All other motive powers do poorly in reverse
3734         return -max_vel / 4;
3735     }
3736 }
3737 
3738 // the same physics as max_ground_velocity, but with a smaller engine power
safe_ground_velocity(const bool fueled) const3739 int vehicle::safe_ground_velocity( const bool fueled ) const
3740 {
3741     int effective_engine_w = total_power_w( fueled, true );
3742     double c_rolling_drag = coeff_rolling_drag();
3743     double safe_in_mps = simple_cubic_solution( coeff_air_drag(), c_rolling_drag,
3744                          c_rolling_drag * vehicles::rolling_constant_to_variable,
3745                          -effective_engine_w );
3746     return mps_to_vmiph( safe_in_mps );
3747 }
3748 
safe_rotor_velocity(const bool fueled) const3749 int vehicle::safe_rotor_velocity( const bool fueled ) const
3750 {
3751     const double max_air_mps = std::sqrt( lift_thrust_of_rotorcraft( fueled,
3752                                           true ) / coeff_air_drag() );
3753     return std::min( 22501, mps_to_vmiph( max_air_mps ) );
3754 }
3755 
3756 // the same physics as max_water_velocity, but with a smaller engine power
safe_water_velocity(const bool fueled) const3757 int vehicle::safe_water_velocity( const bool fueled ) const
3758 {
3759     int total_engine_w = total_power_w( fueled, true );
3760     double total_drag = coeff_water_drag() + coeff_air_drag();
3761     double safe_in_mps = std::cbrt( total_engine_w / total_drag );
3762     return mps_to_vmiph( safe_in_mps );
3763 }
3764 
safe_velocity(const bool fueled) const3765 int vehicle::safe_velocity( const bool fueled ) const
3766 {
3767     if( is_flying && is_rotorcraft() ) {
3768         return safe_rotor_velocity( fueled );
3769     } else if( is_watercraft() ) {
3770         return safe_water_velocity( fueled );
3771     } else {
3772         return safe_ground_velocity( fueled );
3773     }
3774 }
3775 
do_environmental_effects()3776 bool vehicle::do_environmental_effects()
3777 {
3778     bool needed = false;
3779     map &here = get_map();
3780     // check for smoking parts
3781     for( const vpart_reference &vp : get_all_parts() ) {
3782         /* Only lower blood level if:
3783          * - The part is outside.
3784          * - The weather is any effect that would cause the player to be wet. */
3785         if( vp.part().blood > 0 && here.is_outside( vp.pos() ) ) {
3786             needed = true;
3787             if( get_weather().weather_id->rains &&
3788                 get_weather().weather_id->precip != precip_class::very_light ) {
3789                 vp.part().blood--;
3790             }
3791         }
3792     }
3793     return needed;
3794 }
3795 
spew_field(double joules,int part,field_type_id type,int intensity)3796 void vehicle::spew_field( double joules, int part, field_type_id type, int intensity )
3797 {
3798     if( rng( 1, 10000 ) > joules ) {
3799         return;
3800     }
3801     intensity = std::max( joules / 10000, static_cast<double>( intensity ) );
3802     const tripoint dest = exhaust_dest( part );
3803     get_map().mod_field_intensity( dest, type, intensity );
3804 }
3805 
3806 /**
3807  * Generate noise or smoke from a vehicle with engines turned on
3808  * load = how hard the engines are working, from 0.0 until 1.0
3809  * time = how many seconds to generated smoke for
3810  */
noise_and_smoke(int load,time_duration time)3811 void vehicle::noise_and_smoke( int load, time_duration time )
3812 {
3813     static const std::array<std::pair<std::string, int>, 8> sounds = { {
3814             { translate_marker( "hmm" ), 0 }, { translate_marker( "hummm!" ), 15 },
3815             { translate_marker( "whirrr!" ), 30 }, { translate_marker( "vroom!" ), 60 },
3816             { translate_marker( "roarrr!" ), 100 }, { translate_marker( "ROARRR!" ), 140 },
3817             { translate_marker( "BRRROARRR!" ), 180 }, { translate_marker( "BRUMBRUMBRUMBRUM!" ), INT_MAX }
3818         }
3819     };
3820     const std::string heli_noise = translate_marker( "WUMPWUMPWUMP!" );
3821     double noise = 0.0;
3822     double mufflesmoke = 0.0;
3823     double muffle;
3824     int exhaust_part;
3825     std::tie( exhaust_part, muffle ) = get_exhaust_part();
3826 
3827     bool bad_filter = false;
3828     bool combustion = false;
3829 
3830     for( size_t e = 0; e < engines.size(); e++ ) {
3831         int p = engines[e];
3832         if( is_engine_on( e ) &&  engine_fuel_left( e ) ) {
3833             // convert current engine load to units of watts/40K
3834             // then spew more smoke and make more noise as the engine load increases
3835             int part_watts = part_vpower_w( p, true );
3836             double max_stress = static_cast<double>( part_watts / 40000.0 );
3837             double cur_stress = load / 1000.0 * max_stress;
3838             // idle stress = 1.0 resulting in nominal working engine noise = engine_noise_factor()
3839             // and preventing noise = 0
3840             cur_stress = std::max( cur_stress, 1.0 );
3841             double part_noise = cur_stress * part_info( p ).engine_noise_factor();
3842 
3843             if( part_info( p ).has_flag( "E_COMBUSTION" ) ) {
3844                 combustion = true;
3845                 double health = parts[p].health_percent();
3846                 if( parts[ p ].has_fault_flag( "ENG_BACKFIRE" ) ) {
3847                     health = 0.0;
3848                 }
3849                 if( health < part_info( p ).engine_backfire_threshold() && one_in( 50 + 150 * health ) ) {
3850                     backfire( e );
3851                 }
3852                 double j = cur_stress * to_turns<int>( time ) * muffle * 1000;
3853 
3854                 if( parts[ p ].has_fault_flag( "EXTRA_EXHAUST" ) ) {
3855                     bad_filter = true;
3856                     j *= j;
3857                 }
3858 
3859                 if( ( exhaust_part == -1 ) && engine_on ) {
3860                     spew_field( j, p, fd_smoke, bad_filter ? fd_smoke->get_max_intensity() : 1 );
3861                 } else {
3862                     mufflesmoke += j;
3863                 }
3864                 part_noise = ( part_noise + max_stress * 3 + 5 ) * muffle;
3865             }
3866             noise = std::max( noise, part_noise ); // Only the loudest engine counts.
3867         }
3868     }
3869     if( !combustion ) {
3870         return;
3871     }
3872     /// TODO: handle other engine types: muscle / animal / wind / coal / ...
3873 
3874     if( exhaust_part != -1 && engine_on ) {
3875         spew_field( mufflesmoke, exhaust_part, fd_smoke,
3876                     bad_filter ? fd_smoke->get_max_intensity() : 1 );
3877     }
3878     if( is_rotorcraft() ) {
3879         noise *= 2;
3880     }
3881     // Cap engine noise to avoid deafening.
3882     noise = std::min( noise, 100.0 );
3883     // Even a vehicle with engines off will make noise traveling at high speeds
3884     noise = std::max( noise, static_cast<double>( std::fabs( velocity / 500.0 ) ) );
3885     int lvl = 0;
3886     if( one_in( 4 ) && rng( 0, 30 ) < noise ) {
3887         while( noise > sounds[lvl].second ) {
3888             lvl++;
3889         }
3890     }
3891     add_msg_debug( "VEH NOISE final: %d", static_cast<int>( noise ) );
3892     vehicle_noise = static_cast<unsigned char>( noise );
3893     sounds::sound( global_pos3(), noise, sounds::sound_t::movement,
3894                    _( is_rotorcraft() ? heli_noise : sounds[lvl].first ), true );
3895 }
3896 
wheel_area() const3897 int vehicle::wheel_area() const
3898 {
3899     int total_area = 0;
3900     for( const int &wheel_index : wheelcache ) {
3901         total_area += parts[ wheel_index ].wheel_area();
3902     }
3903 
3904     return total_area;
3905 }
3906 
average_or_rating() const3907 float vehicle::average_or_rating() const
3908 {
3909     if( wheelcache.empty() ) {
3910         return 0.0f;
3911     }
3912     float total_rating = 0.0f;
3913     for( const int &wheel_index : wheelcache ) {
3914         total_rating += part_info( wheel_index ).wheel_or_rating();
3915     }
3916     return total_rating / wheelcache.size();
3917 }
3918 
tile_to_width(int tiles)3919 static double tile_to_width( int tiles )
3920 {
3921     if( tiles < 1 ) {
3922         return 0.1;
3923     } else if( tiles < 6 ) {
3924         return 0.5 + 0.4 * tiles;
3925     } else {
3926         return 2.5 + 0.15 * ( tiles - 5 );
3927     }
3928 }
3929 
3930 static constexpr int minrow = -122;
3931 static constexpr int maxrow = 122;
3932 struct drag_column {
3933     int pro = minrow;
3934     int hboard = minrow;
3935     int fboard = minrow;
3936     int aisle = minrow;
3937     int seat = minrow;
3938     int exposed = minrow;
3939     int roof = minrow;
3940     int shield = minrow;
3941     int turret = minrow;
3942     int panel = minrow;
3943     int windmill = minrow;
3944     int sail = minrow;
3945     int rotor = minrow;
3946     int last = maxrow;
3947 };
3948 
coeff_air_drag() const3949 double vehicle::coeff_air_drag() const
3950 {
3951     if( !coeff_air_dirty ) {
3952         return coefficient_air_resistance;
3953     }
3954     constexpr double c_air_base = 0.25;
3955     constexpr double c_air_mod = 0.1;
3956     constexpr double base_height = 1.4;
3957     constexpr double aisle_height = 0.6;
3958     constexpr double fullboard_height = 0.5;
3959     constexpr double roof_height = 0.1;
3960     constexpr double windmill_height = 0.7;
3961     constexpr double sail_height = 0.8;
3962     constexpr double rotor_height = 0.6;
3963 
3964     std::vector<int> structure_indices = all_parts_at_location( part_location_structure );
3965     int width = mount_max.y - mount_min.y + 1;
3966 
3967     // a mess of lambdas to make the next bit slightly easier to read
3968     const auto d_exposed = [&]( const vehicle_part & p ) {
3969         // if it's not inside, it's a center location, and it doesn't need a roof, it's exposed
3970         if( p.info().location != part_location_center ) {
3971             return false;
3972         }
3973         return !( p.inside || p.info().has_flag( "NO_ROOF_NEEDED" ) ||
3974                   p.info().has_flag( "WINDSHIELD" ) ||
3975                   p.info().has_flag( "OPENABLE" ) );
3976     };
3977 
3978     const auto d_protrusion = [&]( std::vector<int> parts_at ) {
3979         if( parts_at.size() > 1 ) {
3980             return false;
3981         } else {
3982             return parts[ parts_at.front() ].info().has_flag( "PROTRUSION" );
3983         }
3984     };
3985     const auto d_check_min = [&]( int &value, const vehicle_part & p, bool test ) {
3986         value = std::min( value, test ? p.mount.x - mount_min.x : maxrow );
3987     };
3988     const auto d_check_max = [&]( int &value, const vehicle_part & p, bool test ) {
3989         value = std::max( value, test ? p.mount.x - mount_min.x : minrow );
3990     };
3991 
3992     // raycast down each column. the least drag vehicle has halfboard, windshield, seat with roof,
3993     // windshield, halfboard and is twice as long as it is wide.
3994     // find the first instance of each item and compare against the ideal configuration.
3995     std::vector<drag_column> drag( width );
3996     for( int p : structure_indices ) {
3997         if( parts[ p ].removed ) {
3998             continue;
3999         }
4000         int col = parts[ p ].mount.y - mount_min.y;
4001         std::vector<int> parts_at = parts_at_relative( parts[ p ].mount, true );
4002         d_check_min( drag[ col ].pro, parts[ p ], d_protrusion( parts_at ) );
4003         for( int pa_index : parts_at ) {
4004             const vehicle_part &pa = parts[ pa_index ];
4005             d_check_max( drag[ col ].hboard, pa, pa.info().has_flag( "HALF_BOARD" ) );
4006             d_check_max( drag[ col ].fboard, pa, pa.info().has_flag( "FULL_BOARD" ) );
4007             d_check_max( drag[ col ].aisle, pa, pa.info().has_flag( "AISLE" ) );
4008             d_check_max( drag[ col ].shield, pa, pa.info().has_flag( "WINDSHIELD" ) &&
4009                          pa.is_available() );
4010             d_check_max( drag[ col ].seat, pa, pa.info().has_flag( "SEAT" ) ||
4011                          pa.info().has_flag( "BED" ) );
4012             d_check_max( drag[ col ].turret, pa, pa.info().location == part_location_onroof &&
4013                          !pa.info().has_flag( "SOLAR_PANEL" ) );
4014             d_check_max( drag[ col ].roof, pa, pa.info().has_flag( "ROOF" ) );
4015             d_check_max( drag[ col ].panel, pa, pa.info().has_flag( "SOLAR_PANEL" ) );
4016             d_check_max( drag[ col ].windmill, pa, pa.info().has_flag( "WIND_TURBINE" ) );
4017             d_check_max( drag[ col ].rotor, pa, pa.info().has_flag( "ROTOR" ) );
4018             d_check_max( drag[ col ].rotor, pa, pa.info().has_flag( "ROTOR_SIMPLE" ) );
4019             d_check_max( drag[ col ].sail, pa, pa.info().has_flag( "WIND_POWERED" ) );
4020             d_check_max( drag[ col ].exposed, pa, d_exposed( pa ) );
4021             d_check_min( drag[ col ].last, pa, pa.info().has_flag( "LOW_FINAL_AIR_DRAG" ) ||
4022                          pa.info().has_flag( "HALF_BOARD" ) );
4023         }
4024     }
4025     double height = 0;
4026     double c_air_drag = 0;
4027     // tally the results of each row and prorate them relative to vehicle width
4028     for( drag_column &dc : drag ) {
4029         // even as m_debug you rarely want to see this
4030         // add_msg_debug( "veh %: pro %d, hboard %d, fboard %d, shield %d, seat %d, roof %d, aisle %d, turret %d, panel %d, exposed %d, last %d\n", name, dc.pro, dc.hboard, dc.fboard, dc.shield, dc.seat, dc.roof, dc.aisle, dc.turret, dc.panel, dc.exposed, dc.last );
4031 
4032         double c_air_drag_c = c_air_base;
4033         // rams in front of the vehicle mildly worsens air drag
4034         c_air_drag_c += ( dc.pro > dc.hboard ) ? c_air_mod : 0;
4035         // not having halfboards in front of any windshields or fullboards moderately worsens
4036         // air drag
4037         c_air_drag_c += ( std::max( std::max( dc.hboard, dc.fboard ),
4038                                     dc.shield ) != dc.hboard ) ? 2 * c_air_mod : 0;
4039         // not having windshields in front of seats severely worsens air drag
4040         c_air_drag_c += ( dc.shield < dc.seat ) ? 3 * c_air_mod : 0;
4041         // missing roofs and open doors severely worsen air drag
4042         c_air_drag_c += ( dc.exposed > minrow ) ? 3 * c_air_mod : 0;
4043         // being twice as long as wide mildly reduces air drag
4044         c_air_drag_c -= ( 2 * ( mount_max.x - mount_min.x ) > width ) ? c_air_mod : 0;
4045         // trunk doors and halfboards at the tail mildly reduce air drag
4046         c_air_drag_c -= ( dc.last == mount_min.x ) ? c_air_mod : 0;
4047         // turrets severely worsen air drag
4048         c_air_drag_c += ( dc.turret > minrow ) ? 3 * c_air_mod : 0;
4049         // having a windmill is terrible for your drag
4050         c_air_drag_c += ( dc.windmill > minrow ) ? 5 * c_air_mod : 0;
4051         // rotors are not great for drag!
4052         c_air_drag_c += ( dc.rotor > minrow ) ? 6 * c_air_mod : 0;
4053         // having a sail is terrible for your drag
4054         c_air_drag_c += ( dc.sail > minrow ) ? 7 * c_air_mod : 0;
4055         c_air_drag += c_air_drag_c;
4056         // vehicles are 1.4m tall
4057         double c_height = base_height;
4058         // plus a bit for a roof
4059         c_height += ( dc.roof > minrow ) ? roof_height : 0;
4060         // plus a lot for an aisle
4061         c_height += ( dc.aisle > minrow ) ?  aisle_height : 0;
4062         // or fullboards
4063         c_height += ( dc.fboard > minrow ) ? fullboard_height : 0;
4064         // and a little for anything on the roof
4065         c_height += ( dc.turret > minrow ) ? 2 * roof_height : 0;
4066         // solar panels are better than turrets or floodlights, though
4067         c_height += ( dc.panel > minrow ) ? roof_height : 0;
4068         // windmills are tall, too
4069         c_height += ( dc.windmill > minrow ) ? windmill_height : 0;
4070         c_height += ( dc.rotor > minrow ) ? rotor_height : 0;
4071         // sails are tall, too
4072         c_height += ( dc.sail > minrow ) ? sail_height : 0;
4073         height += c_height;
4074     }
4075     constexpr double air_density = 1.29; // kg/m^3
4076     // prorate per row height and c_air_drag
4077     height /= width;
4078     c_air_drag /= width;
4079     double cross_area = height * tile_to_width( width );
4080     add_msg_debug( "%s: height %3.2fm, width %3.2fm (%d tiles), c_air %3.2f\n", name, height,
4081                    tile_to_width( width ), width, c_air_drag );
4082     // F_air_drag = c_air_drag * cross_area * 1/2 * air_density * v^2
4083     // coeff_air_resistance = c_air_drag * cross_area * 1/2 * air_density
4084     coefficient_air_resistance = std::max( 0.1, c_air_drag * cross_area * 0.5 * air_density );
4085     coeff_air_dirty = false;
4086     return coefficient_air_resistance;
4087 }
4088 
coeff_rolling_drag() const4089 double vehicle::coeff_rolling_drag() const
4090 {
4091     if( !coeff_rolling_dirty ) {
4092         return coefficient_rolling_resistance;
4093     }
4094     constexpr double wheel_ratio = 1.25;
4095     constexpr double base_wheels = 4.0;
4096     // SAE J2452 measurements are in F_rr = N * C_rr * 0.000225 * ( v + 33.33 )
4097     // Don't ask me why, but it's the numbers we have. We want N * C_rr * 0.000225 here,
4098     // and N is mass * accel from gravity (aka weight)
4099     constexpr double sae_ratio = 0.000225;
4100     constexpr double newton_ratio = accel_g * sae_ratio;
4101     double wheel_factor = 0;
4102     if( wheelcache.empty() ) {
4103         wheel_factor = 50;
4104     } else {
4105         // should really sum the each wheel's c_rolling_resistance * it's share of vehicle mass
4106         for( int wheel : wheelcache ) {
4107             wheel_factor += parts[ wheel ].info().wheel_rolling_resistance();
4108         }
4109         // mildly increasing rolling resistance for vehicles with more than 4 wheels and mildly
4110         // decrease it for vehicles with less
4111         wheel_factor *= wheel_ratio /
4112                         ( base_wheels * wheel_ratio - base_wheels + wheelcache.size() );
4113     }
4114     coefficient_rolling_resistance = newton_ratio * wheel_factor * to_kilogram( total_mass() );
4115     coeff_rolling_dirty = false;
4116     return coefficient_rolling_resistance;
4117 }
4118 
water_hull_height() const4119 double vehicle::water_hull_height() const
4120 {
4121     if( coeff_water_dirty ) {
4122         coeff_water_drag();
4123     }
4124     return hull_height;
4125 }
4126 
water_draft() const4127 double vehicle::water_draft() const
4128 {
4129     if( coeff_water_dirty ) {
4130         coeff_water_drag();
4131     }
4132     return draft_m;
4133 }
4134 
can_float() const4135 bool vehicle::can_float() const
4136 {
4137     if( coeff_water_dirty ) {
4138         coeff_water_drag();
4139     }
4140     // Someday I'll deal with submarines, but now, you can only float if you have freeboard
4141     return draft_m < hull_height;
4142 }
4143 
4144 // apologies for the imperial measurements, they'll get converted before used finally in the vehicle speed at the end of the function.
4145 // sources for the equations to calculate rotor lift thrust were only available in imperial, and the constants used are designed for that.
4146 // r= radius or d = diameter of rotor blades.
4147 // area A [ft^2] = Pi * r^2 -or- A [ft^2] = (Pi/4) * D^2
4148 // Power loading [hp/ft^2] = power( in hp ) / A
4149 // thrust loading [lb/hp]= 8.6859 * Power loading^(-0.3107)
4150 // Lift = Thrust loading * power >>>[lb] = [lb/hp] * [hp]
4151 
lift_thrust_of_rotorcraft(const bool fuelled,const bool safe) const4152 double vehicle::lift_thrust_of_rotorcraft( const bool fuelled, const bool safe ) const
4153 {
4154     int total_diameter = 0;
4155     for( const int rotor : rotors ) {
4156         total_diameter += parts[ rotor ].info().rotor_diameter();
4157     }
4158     int total_engine_w = total_power_w( fuelled, safe );
4159     // take off 15 % due to the imaginary tail rotor power.
4160     double engine_power_in_hp = total_engine_w * 0.00134102;
4161     int rotor_area_in_feet = ( M_PI / 4 ) * std::pow( total_diameter * 3.28084, 2 );
4162     // lift_thrust in lbthrust
4163     double lift_thrust = ( 8.8658 * std::pow( engine_power_in_hp / rotor_area_in_feet,
4164                            -0.3107 ) ) * engine_power_in_hp;
4165     add_msg_debug(
4166         "lift thrust in lbs of %s = %f, rotor area in feet : %d, engine power in hp %f, thrust in newtons : %f",
4167         name, lift_thrust, rotor_area_in_feet, engine_power_in_hp, engine_power_in_hp * 4.45 );
4168     // convert to newtons.
4169     return lift_thrust * 4.45;
4170 }
4171 
has_sufficient_rotorlift() const4172 bool vehicle::has_sufficient_rotorlift() const
4173 {
4174     // comparison of newton to newton - convert kg to newton.
4175     return lift_thrust_of_rotorcraft( true ) > to_kilogram( total_mass() ) * 9.8;
4176 }
4177 
is_rotorcraft() const4178 bool vehicle::is_rotorcraft() const
4179 {
4180     return !rotors.empty() && player_in_control( get_player_character() ) &&
4181            has_sufficient_rotorlift();
4182 }
4183 
is_flyable() const4184 bool vehicle::is_flyable() const
4185 {
4186     return flyable;
4187 }
4188 
set_flyable(bool val)4189 void vehicle::set_flyable( bool val )
4190 {
4191     flyable = val;
4192 }
4193 
get_z_change() const4194 int vehicle::get_z_change() const
4195 {
4196     return requested_z_change;
4197 }
4198 
would_install_prevent_flyable(const vpart_info & vpinfo,Character & pc) const4199 bool vehicle::would_install_prevent_flyable( const vpart_info &vpinfo, Character &pc ) const
4200 {
4201     if( flyable && !rotors.empty() && !( vpinfo.has_flag( "SIMPLE_PART" ) ||
4202                                          vpinfo.has_flag( "AIRCRAFT_REPAIRABLE_NOPROF" ) ) ) {
4203         return !pc.has_proficiency( proficiency_prof_aircraft_mechanic );
4204     } else {
4205         return false;
4206     }
4207 }
4208 
would_repair_prevent_flyable(vehicle_part & vp,Character & pc) const4209 bool vehicle::would_repair_prevent_flyable( vehicle_part &vp, Character &pc ) const
4210 {
4211     if( flyable && !rotors.empty() ) {
4212         if( vp.info().has_flag( "SIMPLE_PART" ) ||
4213             vp.info().has_flag( "AIRCRAFT_REPAIRABLE_NOPROF" ) ) {
4214             vpart_position vppos = vpart_position( const_cast<vehicle &>( *this ),
4215                                                    index_of_part( const_cast<vehicle_part *>( &vp ) ) );
4216             return !vppos.is_inside();
4217         } else {
4218             return !pc.has_proficiency( proficiency_prof_aircraft_mechanic );
4219         }
4220     } else {
4221         return false;
4222     }
4223 }
4224 
would_removal_prevent_flyable(vehicle_part & vp,Character & pc) const4225 bool vehicle::would_removal_prevent_flyable( vehicle_part &vp, Character &pc ) const
4226 {
4227     if( flyable && !rotors.empty() && !vp.info().has_flag( "SIMPLE_PART" ) ) {
4228         return !pc.has_proficiency( proficiency_prof_aircraft_mechanic );
4229     } else {
4230         return false;
4231     }
4232 }
4233 
is_flying_in_air() const4234 bool vehicle::is_flying_in_air() const
4235 {
4236     return is_flying;
4237 }
4238 
set_flying(bool new_flying_value)4239 void vehicle::set_flying( bool new_flying_value )
4240 {
4241     is_flying = new_flying_value;
4242 }
4243 
is_watercraft() const4244 bool vehicle::is_watercraft() const
4245 {
4246     return is_floating || ( in_water && wheelcache.empty() );
4247 }
4248 
is_in_water(bool deep_water) const4249 bool vehicle::is_in_water( bool deep_water ) const
4250 {
4251     return deep_water ? is_floating : in_water;
4252 }
4253 
coeff_water_drag() const4254 double vehicle::coeff_water_drag() const
4255 {
4256     if( !coeff_water_dirty ) {
4257         return coefficient_water_resistance;
4258     }
4259     std::vector<int> structure_indices = all_parts_at_location( part_location_structure );
4260     if( structure_indices.empty() ) {
4261         // huh?
4262         coeff_water_dirty = false;
4263         hull_height = 0.3;
4264         draft_m = 1.0;
4265         return 1250.0;
4266     }
4267     double hull_coverage = static_cast<double>( floating.size() ) / structure_indices.size();
4268 
4269     int tile_width = mount_max.y - mount_min.y + 1;
4270     double width_m = tile_to_width( tile_width );
4271 
4272     // actual area of the hull in m^2 (handles non-rectangular shapes)
4273     // footprint area in tiles = tile width * tile length
4274     // effective footprint percent = # of structure tiles / footprint area in tiles
4275     // actual hull area in m^2 = footprint percent * length in meters * width in meters
4276     // length in meters = length in tiles
4277     // actual area in m = # of structure tiles * length in tiles * width in meters /
4278     //                    ( length in tiles * width in tiles )
4279     // actual area in m = # of structure tiles * width in meters / width in tiles
4280     double actual_area_m = width_m * structure_indices.size() / tile_width;
4281 
4282     // effective hull area is actual hull area * hull coverage
4283     double hull_area_m   = actual_area_m * std::max( 0.1, hull_coverage );
4284     // Treat the hullform as a simple cuboid to calculate displaced depth of
4285     // water.
4286     // Apply Archimedes' principle (mass of water displaced is mass of vehicle).
4287     // area * depth = hull_volume = water_mass / water density
4288     // water_mass = vehicle_mass
4289     // area * depth = vehicle_mass / water_density
4290     // depth = vehicle_mass / water_density / area
4291     constexpr double water_density = 1000.0; // kg/m^3
4292     draft_m = to_kilogram( total_mass() ) / water_density / hull_area_m;
4293     // increase the streamlining as more of the boat is covered in boat boards
4294     double c_water_drag = 1.25 - hull_coverage;
4295     // hull height starts at 0.3m and goes up as you add more boat boards
4296     hull_height = 0.3 + 0.5 * hull_coverage;
4297     // F_water_drag = c_water_drag * cross_area * 1/2 * water_density * v^2
4298     // coeff_water_resistance = c_water_drag * cross_area * 1/2 * water_density
4299     coefficient_water_resistance = c_water_drag * width_m * draft_m * 0.5 * water_density;
4300     coeff_water_dirty = false;
4301     return coefficient_water_resistance;
4302 }
4303 
k_traction(float wheel_traction_area) const4304 float vehicle::k_traction( float wheel_traction_area ) const
4305 {
4306     if( is_floating ) {
4307         return can_float() ? 1.0f : -1.0f;
4308     }
4309     if( is_flying ) {
4310         return is_rotorcraft() ? 1.0f : -1.0f;
4311     }
4312     if( is_watercraft() && can_float() ) {
4313         return 1.0f;
4314     }
4315 
4316     const float fraction_without_traction = 1.0f - wheel_traction_area / wheel_area();
4317     if( fraction_without_traction == 0 ) {
4318         return 1.0f;
4319     }
4320     const float mass_penalty = fraction_without_traction * to_kilogram( total_mass() );
4321     float traction = std::min( 1.0f, wheel_traction_area / mass_penalty );
4322     add_msg_debug( "%s has traction %.2f", name, traction );
4323 
4324     // For now make it easy until it gets properly balanced: add a low cap of 0.1
4325     return std::max( 0.1f, traction );
4326 }
4327 
static_drag(bool actual) const4328 int vehicle::static_drag( bool actual ) const
4329 {
4330     return extra_drag + ( actual && !engine_on && !is_towed() ? -1500 : 0 );
4331 }
4332 
strain() const4333 float vehicle::strain() const
4334 {
4335     if( velocity == 0.0 ) {
4336         return 0.0f;
4337     }
4338     int mv = max_velocity();
4339     int sv = safe_velocity();
4340     if( mv <= sv ) {
4341         mv = sv + 1;
4342     }
4343     if( velocity < sv && velocity > -sv ) {
4344         return 0;
4345     } else {
4346         return static_cast<float>( std::abs( velocity ) - sv ) / static_cast<float>( mv - sv );
4347     }
4348 }
4349 
sufficient_wheel_config() const4350 bool vehicle::sufficient_wheel_config() const
4351 {
4352     if( wheelcache.empty() ) {
4353         // No wheels!
4354         return false;
4355     } else if( wheelcache.size() == 1 ) {
4356         //Has to be a stable wheel, and one wheel can only support a 1-3 tile vehicle
4357         if( !part_info( wheelcache.front() ).has_flag( "STABLE" ) ||
4358             all_parts_at_location( part_location_structure ).size() > 3 ) {
4359             return false;
4360         }
4361     }
4362     return true;
4363 }
4364 
is_owned_by(const Character & c,bool available_to_take) const4365 bool vehicle::is_owned_by( const Character &c, bool available_to_take ) const
4366 {
4367     if( owner.is_null() ) {
4368         return available_to_take;
4369     }
4370     if( !c.get_faction() ) {
4371         debugmsg( "vehicle::is_owned_by() player %s has no faction", c.disp_name() );
4372         return false;
4373     }
4374     return c.get_faction()->id == get_owner();
4375 }
4376 
is_old_owner(const Character & c,bool available_to_take) const4377 bool vehicle::is_old_owner( const Character &c, bool available_to_take ) const
4378 {
4379     if( old_owner.is_null() ) {
4380         return available_to_take;
4381     }
4382     if( !c.get_faction() ) {
4383         debugmsg( "vehicle::is_old_owner() player %s has no faction", c.disp_name() );
4384         return false;
4385     }
4386     return c.get_faction()->id == get_old_owner();
4387 }
4388 
get_owner_name() const4389 std::string vehicle::get_owner_name() const
4390 {
4391     if( !g->faction_manager_ptr->get( owner ) ) {
4392         debugmsg( "vehicle::get_owner_name() vehicle %s has no valid nor null faction id ", disp_name() );
4393         return _( "no owner" );
4394     }
4395     return _( g->faction_manager_ptr->get( owner )->name );
4396 }
4397 
set_owner(const Character & c)4398 void vehicle::set_owner( const Character &c )
4399 {
4400     if( !c.get_faction() ) {
4401         debugmsg( "vehicle::set_owner() player %s has no valid faction", c.disp_name() );
4402         return;
4403     }
4404     owner = c.get_faction()->id;
4405 }
4406 
handle_potential_theft(player & p,bool check_only,bool prompt)4407 bool vehicle::handle_potential_theft( player &p, bool check_only, bool prompt )
4408 {
4409     const bool is_owned_by_player = is_owned_by( p );
4410     std::vector<npc *> witnesses;
4411     for( npc &elem : g->all_npcs() ) {
4412         if( rl_dist( elem.pos(), p.pos() ) < MAX_VIEW_DISTANCE && has_owner() &&
4413             !is_owned_by_player && elem.sees( p.pos() ) ) {
4414             witnesses.push_back( &elem );
4415         }
4416     }
4417     // the vehicle is yours, that's fine.
4418     if( is_owned_by_player ) {
4419         return true;
4420         // if There is no owner
4421         // handle transfer of ownership
4422     } else if( !has_owner() ) {
4423         set_owner( p.get_faction()->id );
4424         remove_old_owner();
4425         return true;
4426         // if there is a marker for having been stolen, but 15 minutes have passed, then officially transfer ownership
4427     } else if( witnesses.empty() && has_old_owner() && !is_old_owner( p ) && theft_time &&
4428                calendar::turn - *theft_time > 15_minutes ) {
4429         set_owner( p.get_faction()->id );
4430         remove_old_owner();
4431         return true;
4432         // No witnesses? then don't need to prompt, we assume the player is in process of stealing it.
4433         // Ownership transfer checking is handled above, and warnings handled below.
4434         // This is just to perform interaction with the vehicle without a prompt.
4435         // It will prompt first-time, even with no witnesses, to inform player it is owned by someone else
4436         // subsequently, no further prompts, the player should know by then.
4437     } else if( witnesses.empty() && old_owner ) {
4438         return true;
4439     }
4440     // if we are just checking if we could continue without problems, then the rest is assumed false
4441     if( check_only ) {
4442         return false;
4443     }
4444     // if we got here, there's some theft occurring
4445     if( prompt ) {
4446         if( !query_yn(
4447                 _( "This vehicle belongs to: %s, there may be consequences if you are observed interacting with it, continue?" ),
4448                 _( get_owner_name() ) ) ) {
4449             return false;
4450         }
4451     }
4452     // set old owner so that we can restore ownership if there are witnesses.
4453     set_old_owner( get_owner() );
4454     for( npc *elem : witnesses ) {
4455         elem->say( "<witnessed_thievery>", 7 );
4456     }
4457     if( !witnesses.empty() ) {
4458         if( p.add_faction_warning( get_owner() ) ) {
4459             for( npc *elem : witnesses ) {
4460                 elem->make_angry();
4461             }
4462         }
4463         // remove the temporary marker for a successful theft, as it was witnessed.
4464         remove_old_owner();
4465     }
4466     // if we got here, then the action will proceed after the previous warning
4467     return true;
4468 }
4469 
balanced_wheel_config() const4470 bool vehicle::balanced_wheel_config() const
4471 {
4472     point min = point_max;
4473     point max = point_min;
4474     // find the bounding box of the wheels
4475     for( const int &w : wheelcache ) {
4476         const point &pt = parts[ w ].mount;
4477         min.x = std::min( min.x, pt.x );
4478         min.y = std::min( min.y, pt.y );
4479         max.x = std::max( max.x, pt.x );
4480         max.y = std::max( max.y, pt.y );
4481     }
4482 
4483     // Check center of mass inside support of wheels (roughly)
4484     const point &com = local_center_of_mass();
4485     const inclusive_rectangle<point> support( min, max );
4486     return support.contains( com );
4487 }
4488 
valid_wheel_config() const4489 bool vehicle::valid_wheel_config() const
4490 {
4491     return sufficient_wheel_config() && balanced_wheel_config();
4492 }
4493 
steering_effectiveness() const4494 float vehicle::steering_effectiveness() const
4495 {
4496     if( is_floating ) {
4497         // I'M ON A BOAT
4498         return can_float() ? 1.0f : 0.0f;
4499     }
4500     if( is_flying ) {
4501         // I'M IN THE AIR
4502         return is_rotorcraft() ? 1.0f : 0.0f;
4503     }
4504     // irksome special case for boats in shallow water
4505     if( is_watercraft() && can_float() ) {
4506         return 1.0f;
4507     }
4508 
4509     if( steering.empty() ) {
4510         return -1.0f; // No steering installed
4511     }
4512     // If the only steering part is an animal harness, with no animal in, it
4513     // is not steerable.
4514     const vehicle_part &vp = parts[ steering[0] ];
4515     if( steering.size() == 1 && vp.info().fuel_type == fuel_type_animal ) {
4516         monster *mon = get_monster( steering[0] );
4517         if( mon == nullptr || !mon->has_effect( effect_harnessed ) ) {
4518             return -2.0f;
4519         }
4520     }
4521     // For now, you just need one wheel working for 100% effective steering.
4522     // TODO: return something less than 1.0 if the steering isn't so good
4523     // (unbalanced, long wheelbase, back-heavy vehicle with front wheel steering,
4524     // etc)
4525     for( int p : steering ) {
4526         if( parts[ p ].is_available() ) {
4527             return 1.0f;
4528         }
4529     }
4530 
4531     // We have steering, but it's all broken.
4532     return 0.0f;
4533 }
4534 
handling_difficulty() const4535 float vehicle::handling_difficulty() const
4536 {
4537     const float steer = std::max( 0.0f, steering_effectiveness() );
4538     const float ktraction = k_traction( get_map().vehicle_wheel_traction( *this ) );
4539     const float aligned = std::max( 0.0f, 1.0f - ( face_vec() - dir_vec() ).magnitude() );
4540 
4541     // TestVehicle: perfect steering, moving on road at 100 mph (25 tiles per turn) = 0.0
4542     // TestVehicle but on grass (0.75 friction) = 2.5
4543     // TestVehicle but with bad steering (0.5 steer) = 5
4544     // TestVehicle but on fungal bed (0.5 friction) and bad steering = 10
4545     // TestVehicle but turned 90 degrees during this turn (0 align) = 10
4546     const float diff_mod = ( ( 1.0f - steer ) + ( 1.0f - ktraction ) + ( 1.0f - aligned ) );
4547     return velocity * diff_mod / vehicles::vmiph_per_tile;
4548 }
4549 
engine_fuel_usage(int e) const4550 int vehicle::engine_fuel_usage( int e ) const
4551 {
4552     if( !is_engine_on( e ) ) {
4553         return 0;
4554     }
4555 
4556     static const itype_id null_fuel_type( "null" );
4557     const itype_id &cur_fuel = parts[engines[e]].fuel_current();
4558     if( cur_fuel  == null_fuel_type ) {
4559         return 0;
4560     }
4561 
4562     if( is_perpetual_type( e ) ) {
4563         return 0;
4564     }
4565     const auto &info = part_info( engines[ e ] );
4566 
4567     int usage = info.energy_consumption;
4568     if( parts[ engines[ e ] ].has_fault_flag( "DOUBLE_FUEL_CONSUMPTION" ) ) {
4569         usage *= 2;
4570     }
4571 
4572     return usage;
4573 }
4574 
fuel_usage() const4575 std::map<itype_id, int> vehicle::fuel_usage() const
4576 {
4577     std::map<itype_id, int> ret;
4578     for( size_t i = 0; i < engines.size(); i++ ) {
4579         // Note: functions with "engine" in name do NOT take part indices
4580         // TODO: Use part indices and not engine vector indices
4581         if( !is_engine_on( i ) ) {
4582             continue;
4583         }
4584 
4585         const size_t e = engines[ i ];
4586         static const itype_id null_fuel_type( "null" );
4587         const itype_id &cur_fuel = parts[ e ].fuel_current();
4588         if( cur_fuel  == null_fuel_type ) {
4589             continue;
4590         }
4591 
4592         if( !is_perpetual_type( i ) ) {
4593             ret[cur_fuel] += engine_fuel_usage( i );
4594         }
4595     }
4596 
4597     return ret;
4598 }
4599 
drain_energy(const itype_id & ftype,double energy_j)4600 double vehicle::drain_energy( const itype_id &ftype, double energy_j )
4601 {
4602     double drained = 0.0f;
4603     for( vehicle_part &p : parts ) {
4604         if( energy_j <= 0.0f ) {
4605             break;
4606         }
4607 
4608         double consumed = p.consume_energy( ftype, energy_j );
4609         drained += consumed;
4610         energy_j -= consumed;
4611     }
4612 
4613     invalidate_mass();
4614     return drained;
4615 }
4616 
consume_fuel(int load,bool idling)4617 void vehicle::consume_fuel( int load, bool idling )
4618 {
4619     double st = strain();
4620     for( const auto &fuel_pr : fuel_usage() ) {
4621         const itype_id &ft = fuel_pr.first;
4622         if( idling && ft == fuel_type_battery ) {
4623             continue;
4624         }
4625 
4626         double amnt_precise_j = static_cast<double>( fuel_pr.second );
4627         amnt_precise_j *= load / 1000.0 * ( 1.0 + st * st * 100.0 );
4628         auto inserted = fuel_used_last_turn.insert( { ft, 0.0f } );
4629         inserted.first->second += amnt_precise_j;
4630         double remainder = fuel_remainder[ ft ];
4631         amnt_precise_j -= remainder;
4632 
4633         if( amnt_precise_j > 0.0f ) {
4634             fuel_remainder[ ft ] = drain_energy( ft, amnt_precise_j ) - amnt_precise_j;
4635         } else {
4636             fuel_remainder[ ft ] = -amnt_precise_j;
4637         }
4638     }
4639     // Only process muscle power things when moving.
4640     if( idling ) {
4641         return;
4642     }
4643     Character &player_character = get_player_character();
4644     if( load > 0 && fuel_left( fuel_type_muscle ) > 0 &&
4645         player_character.has_effect( effect_winded ) ) {
4646         cruise_velocity = 0;
4647         if( velocity == 0 ) {
4648             stop();
4649         }
4650     }
4651     // we want this to update the activity level whenever we're using muscle power to move
4652     if( load > 0 && fuel_left( fuel_type_muscle ) > 0 ) {
4653         player_character.set_activity_level( ACTIVE_EXERCISE );
4654         //do this as a function of current load
4655         // But only if the player is actually there!
4656         int eff_load = load / 10;
4657         int mod = 4 * st; // strain
4658         int base_burn = static_cast<int>( get_option<float>( "PLAYER_BASE_STAMINA_REGEN_RATE" ) ) -
4659                         3;
4660         base_burn = std::max( eff_load / 3, base_burn );
4661         //charge bionics when using muscle engine
4662         const item muscle( "muscle" );
4663         for( const bionic_id &bid : player_character.get_bionic_fueled_with( muscle ) ) {
4664             if( player_character.has_active_bionic( bid ) ) { // active power gen
4665                 // more pedaling = more power
4666                 player_character.mod_power_level( units::from_kilojoule( muscle.fuel_energy() ) *
4667                                                   bid->fuel_efficiency *
4668                                                   ( load / 1000 ) );
4669                 mod += eff_load / 5;
4670             } else { // passive power gen
4671                 player_character.mod_power_level( units::from_kilojoule( muscle.fuel_energy() ) *
4672                                                   bid->passive_fuel_efficiency *
4673                                                   ( load / 1000 ) );
4674                 mod += eff_load / 10;
4675             }
4676         }
4677         // decreased stamina burn scalable with load
4678         if( player_character.has_active_bionic( bio_jointservo ) ) {
4679             player_character.mod_power_level( units::from_kilojoule( -std::max( eff_load / 20, 1 ) ) );
4680             mod -= std::max( eff_load / 5, 5 );
4681         }
4682 
4683         player_character.mod_stamina( -( base_burn + mod ) );
4684         add_msg_debug( "Load: %d", load );
4685         add_msg_debug( "Mod: %d", mod );
4686         add_msg_debug( "Burn: %d", -( base_burn + mod ) );
4687     }
4688 }
4689 
lights(bool active)4690 std::vector<vehicle_part *> vehicle::lights( bool active )
4691 {
4692     std::vector<vehicle_part *> res;
4693     for( vehicle_part &e : parts ) {
4694         if( ( !active || e.enabled ) && e.is_available() && e.is_light() ) {
4695             res.push_back( &e );
4696         }
4697     }
4698     return res;
4699 }
4700 
total_accessory_epower_w() const4701 int vehicle::total_accessory_epower_w() const
4702 {
4703     int epower = 0;
4704     for( int part : accessories ) {
4705         const vehicle_part &vp = parts[part];
4706         if( vp.enabled ) {
4707             epower += vp.info().epower;
4708         }
4709     }
4710     return epower;
4711 }
4712 
battery_power_level() const4713 std::pair<int, int> vehicle::battery_power_level() const
4714 {
4715     int total_epower_capacity = 0;
4716     int remaining_epower = 0;
4717 
4718     for( const int bi : batteries ) {
4719         const vehicle_part &b = parts[bi];
4720         if( b.is_available() ) {
4721             remaining_epower += b.ammo_remaining();
4722             total_epower_capacity += b.ammo_capacity( ammotype( "battery" ) );
4723         }
4724     }
4725 
4726     return std::make_pair( remaining_epower, total_epower_capacity );
4727 }
4728 
start_engine(int e,bool turn_on)4729 bool vehicle::start_engine( int e, bool turn_on )
4730 {
4731     if( parts[engines[e]].enabled == turn_on ) {
4732         return false;
4733     }
4734     bool res = false;
4735     if( turn_on ) {
4736         toggle_specific_engine( e, true );
4737         // prevent starting of the faulty engines
4738         if( ! start_engine( e ) ) {
4739             toggle_specific_engine( e, false );
4740         } else {
4741             res = true;
4742         }
4743     } else {
4744         toggle_specific_engine( e, false );
4745         res = true;
4746     }
4747     return res;
4748 }
4749 
total_alternator_epower_w() const4750 int vehicle::total_alternator_epower_w() const
4751 {
4752     int epower = 0;
4753     if( engine_on ) {
4754         // If the engine is on, the alternators are working.
4755         for( size_t p = 0; p < alternators.size(); ++p ) {
4756             if( is_alternator_on( p ) ) {
4757                 epower += part_epower_w( alternators[p] );
4758             }
4759         }
4760     }
4761     return epower;
4762 }
4763 
total_engine_epower_w() const4764 int vehicle::total_engine_epower_w() const
4765 {
4766     int epower = 0;
4767 
4768     // Engines: can both produce (plasma) or consume (gas, diesel) epower.
4769     // Gas engines require epower to run for ignition system, ECU, etc.
4770     // Electric motor consumption not included, see @ref vpart_info::energy_consumption
4771     if( engine_on ) {
4772         for( size_t e = 0; e < engines.size(); ++e ) {
4773             if( is_engine_on( e ) ) {
4774                 epower += part_epower_w( engines[e] );
4775             }
4776         }
4777     }
4778 
4779     return epower;
4780 }
4781 
total_solar_epower_w() const4782 int vehicle::total_solar_epower_w() const
4783 {
4784     int epower_w = 0;
4785     map &here = get_map();
4786     for( int part : solar_panels ) {
4787         if( parts[ part ].is_unavailable() ) {
4788             continue;
4789         }
4790 
4791         if( !is_sm_tile_outside( here.getabs( global_part_pos3( part ) ) ) ) {
4792             continue;
4793         }
4794 
4795         epower_w += part_epower_w( part );
4796     }
4797     // Weather doesn't change much across the area of the vehicle, so just
4798     // sample it once.
4799     weather_type_id wtype = current_weather( global_pos3() );
4800     const float tick_sunlight = incident_sunlight( wtype, calendar::turn );
4801     double intensity = tick_sunlight / default_daylight_level();
4802     return epower_w * intensity;
4803 }
4804 
total_wind_epower_w() const4805 int vehicle::total_wind_epower_w() const
4806 {
4807     map &here = get_map();
4808     // TODO: fix point types
4809     const oter_id &cur_om_ter =
4810         overmap_buffer.ter( tripoint_abs_omt( ms_to_omt_copy( here.getabs( global_pos3() ) ) ) );
4811     weather_manager &weather = get_weather();
4812     const w_point weatherPoint = *weather.weather_precise;
4813     int epower_w = 0;
4814     for( int part : wind_turbines ) {
4815         if( parts[ part ].is_unavailable() ) {
4816             continue;
4817         }
4818 
4819         if( !is_sm_tile_outside( here.getabs( global_part_pos3( part ) ) ) ) {
4820             continue;
4821         }
4822 
4823         double windpower = get_local_windpower( weather.windspeed, cur_om_ter, global_part_pos3( part ),
4824                                                 weather.winddirection, false );
4825         if( windpower <= ( weather.windspeed / 10.0 ) ) {
4826             continue;
4827         }
4828         epower_w += part_epower_w( part ) * windpower;
4829     }
4830     return epower_w;
4831 }
4832 
total_water_wheel_epower_w() const4833 int vehicle::total_water_wheel_epower_w() const
4834 {
4835     int epower_w = 0;
4836     map &here = get_map();
4837     for( int part : water_wheels ) {
4838         if( parts[ part ].is_unavailable() ) {
4839             continue;
4840         }
4841 
4842         if( !is_sm_tile_over_water( here.getabs( global_part_pos3( part ) ) ) ) {
4843             continue;
4844         }
4845 
4846         epower_w += part_epower_w( part );
4847     }
4848     // TODO: river current intensity changes power - flat for now.
4849     return epower_w;
4850 }
4851 
net_battery_charge_rate_w() const4852 int vehicle::net_battery_charge_rate_w() const
4853 {
4854     return total_engine_epower_w() + total_alternator_epower_w() + total_accessory_epower_w() +
4855            total_solar_epower_w() + total_wind_epower_w() + total_water_wheel_epower_w();
4856 }
4857 
max_reactor_epower_w() const4858 int vehicle::max_reactor_epower_w() const
4859 {
4860     int epower_w = 0;
4861     for( int elem : reactors ) {
4862         epower_w += is_part_on( elem ) ? part_epower_w( elem ) : 0;
4863     }
4864     return epower_w;
4865 }
4866 
update_alternator_load()4867 void vehicle::update_alternator_load()
4868 {
4869     // Update alternator load
4870     if( engine_on ) {
4871         int engine_vpower = 0;
4872         for( size_t e = 0; e < engines.size(); ++e ) {
4873             if( is_engine_on( e ) && parts[engines[e]].info().has_flag( "E_ALTERNATOR" ) ) {
4874                 engine_vpower += part_vpower_w( engines[e] );
4875             }
4876         }
4877         int alternators_power = 0;
4878         for( size_t p = 0; p < alternators.size(); ++p ) {
4879             if( is_alternator_on( p ) ) {
4880                 alternators_power += part_vpower_w( alternators[p] );
4881             }
4882         }
4883         alternator_load =
4884             engine_vpower
4885             ? 1000 * ( std::abs( alternators_power ) + std::abs( extra_drag ) ) / engine_vpower
4886             : 0;
4887     } else {
4888         alternator_load = 0;
4889     }
4890 }
4891 
power_parts()4892 void vehicle::power_parts()
4893 {
4894     update_alternator_load();
4895     // Things that drain energy: engines and accessories.
4896     int engine_epower = total_engine_epower_w();
4897     int epower = engine_epower + total_accessory_epower_w() + total_alternator_epower_w();
4898 
4899     int delta_energy_bat = power_to_energy_bat( epower, 1_turns );
4900     int battery_left, battery_capacity;
4901     std::tie( battery_left, battery_capacity ) = battery_power_level();
4902     int storage_deficit_bat = std::max( 0, battery_capacity - battery_left - delta_energy_bat );
4903     Character &player_character = get_player_character();
4904     // Reactors trigger only on demand. If we'd otherwise run out of power, see
4905     // if we can spin up the reactors.
4906     if( !reactors.empty() && storage_deficit_bat > 0 ) {
4907         // Still not enough surplus epower to fully charge battery
4908         // Produce additional epower from any reactors
4909         bool reactor_working = false;
4910         bool reactor_online = false;
4911         for( int elem : reactors ) {
4912             // Check whether the reactor is on. If not, move on.
4913             if( !is_part_on( elem ) ) {
4914                 continue;
4915             }
4916             // Keep track whether or not the vehicle has any reactors activated
4917             reactor_online = true;
4918             // the amount of energy the reactor generates each turn
4919             const int gen_energy_bat = power_to_energy_bat( part_epower_w( elem ), 1_turns );
4920             if( parts[ elem ].is_unavailable() ) {
4921                 continue;
4922             } else if( parts[ elem ].info().has_flag( STATIC( std::string( "PERPETUAL" ) ) ) ) {
4923                 reactor_working = true;
4924                 delta_energy_bat += std::min( storage_deficit_bat, gen_energy_bat );
4925             } else if( parts[elem].ammo_remaining() > 0 ) {
4926                 // Efficiency: one unit of fuel is this many units of battery
4927                 // Note: One battery is 1 kJ
4928                 const int efficiency = part_info( elem ).power;
4929                 const int avail_fuel = parts[elem].ammo_remaining() * efficiency;
4930                 const int elem_energy_bat = std::min( gen_energy_bat, avail_fuel );
4931                 // Cap output at what we can achieve and utilize
4932                 const int reactors_output_bat = std::min( elem_energy_bat, storage_deficit_bat );
4933                 // Fuel consumed in actual units of the resource
4934                 int fuel_consumed = reactors_output_bat / efficiency;
4935                 // Remainder has a chance of resulting in more fuel consumption
4936                 fuel_consumed += x_in_y( reactors_output_bat % efficiency, efficiency ) ? 1 : 0;
4937                 parts[ elem ].ammo_consume( fuel_consumed, global_part_pos3( elem ) );
4938                 reactor_working = true;
4939                 delta_energy_bat += reactors_output_bat;
4940             }
4941         }
4942 
4943         if( !reactor_working && reactor_online ) {
4944             // All reactors out of fuel or destroyed
4945             for( int elem : reactors ) {
4946                 parts[ elem ].enabled = false;
4947             }
4948             if( player_in_control( player_character ) || player_character.sees( global_pos3() ) ) {
4949                 add_msg( _( "The %s's reactor dies!" ), name );
4950             }
4951         }
4952     }
4953 
4954     int battery_deficit = 0;
4955     if( delta_energy_bat > 0 ) {
4956         // store epower surplus in battery
4957         charge_battery( delta_energy_bat );
4958     } else if( epower < 0 ) {
4959         // draw epower deficit from battery
4960         battery_deficit = discharge_battery( std::abs( delta_energy_bat ) );
4961     }
4962 
4963     if( battery_deficit != 0 ) {
4964         // Scoops need a special case since they consume power during actual use
4965         for( const vpart_reference &vp : get_enabled_parts( "SCOOP" ) ) {
4966             vp.part().enabled = false;
4967         }
4968         // Rechargers need special case since they consume power on demand
4969         for( const vpart_reference &vp : get_enabled_parts( "RECHARGE" ) ) {
4970             vp.part().enabled = false;
4971         }
4972 
4973         for( const vpart_reference &vp : get_enabled_parts( VPFLAG_ENABLED_DRAINS_EPOWER ) ) {
4974             vehicle_part &pt = vp.part();
4975             if( pt.info().epower < 0 ) {
4976                 pt.enabled = false;
4977             }
4978         }
4979 
4980         is_alarm_on = false;
4981         camera_on = false;
4982         if( player_in_control( player_character ) || player_character.sees( global_pos3() ) ) {
4983             add_msg( _( "The %s's battery dies!" ), name );
4984         }
4985         if( engine_epower < 0 ) {
4986             // Not enough epower to run gas engine ignition system
4987             engine_on = false;
4988             if( player_in_control( player_character ) || player_character.sees( global_pos3() ) ) {
4989                 add_msg( _( "The %s's engine dies!" ), name );
4990             }
4991         }
4992     }
4993 }
4994 
find_vehicle(const tripoint & where)4995 vehicle *vehicle::find_vehicle( const tripoint &where )
4996 {
4997     map &here = get_map();
4998     // Is it in the reality bubble?
4999     tripoint veh_local = here.getlocal( where );
5000     if( const optional_vpart_position vp = here.veh_at( veh_local ) ) {
5001         return &vp->vehicle();
5002     }
5003 
5004     // Nope. Load up its submap...
5005     tripoint veh_in_sm = where;
5006     tripoint veh_sm = ms_to_sm_remain( veh_in_sm );
5007 
5008     const submap *sm = MAPBUFFER.lookup_submap( veh_sm );
5009     if( sm == nullptr ) {
5010         return nullptr;
5011     }
5012 
5013     for( const auto &elem : sm->vehicles ) {
5014         vehicle *found_veh = elem.get();
5015         if( veh_in_sm.xy() == found_veh->pos ) {
5016             return found_veh;
5017         }
5018     }
5019 
5020     return nullptr;
5021 }
5022 
enumerate_vehicles(std::map<vehicle *,bool> & connected_vehicles,std::set<vehicle * > & vehicle_list)5023 void vehicle::enumerate_vehicles( std::map<vehicle *, bool> &connected_vehicles,
5024                                   std::set<vehicle *> &vehicle_list )
5025 {
5026     auto enumerate_visitor = [&connected_vehicles]( vehicle * veh, int amount, int ) {
5027         // Only emplaces if element is not present already.
5028         connected_vehicles.emplace( veh, false );
5029         return amount;
5030     };
5031     for( vehicle *veh : vehicle_list ) {
5032         // This autovivifies, and also overwrites the value if already present.
5033         connected_vehicles[veh] = true;
5034         traverse_vehicle_graph( veh, 1, enumerate_visitor );
5035     }
5036 }
5037 
5038 template <typename Func, typename Vehicle>
traverse_vehicle_graph(Vehicle * start_veh,int amount,Func action)5039 int vehicle::traverse_vehicle_graph( Vehicle *start_veh, int amount, Func action )
5040 {
5041     if( start_veh->loose_parts.empty() ) {
5042         return amount;
5043     }
5044     // Breadth-first search! Initialize the queue with a pointer to ourselves and go!
5045     std::queue< std::pair<Vehicle *, int> > connected_vehs;
5046     std::set<Vehicle *> visited_vehs;
5047     connected_vehs.push( std::make_pair( start_veh, 0 ) );
5048 
5049     while( amount > 0 && !connected_vehs.empty() ) {
5050         auto current_node = connected_vehs.front();
5051         Vehicle *current_veh = current_node.first;
5052         int current_loss = current_node.second;
5053 
5054         visited_vehs.insert( current_veh );
5055         connected_vehs.pop();
5056 
5057         add_msg_debug( "Traversing graph with %d power", amount );
5058 
5059         for( int p : current_veh->loose_parts ) {
5060             if( !current_veh->part_info( p ).has_flag( "POWER_TRANSFER" ) ) {
5061                 continue; // ignore loose parts that aren't power transfer cables
5062             }
5063 
5064             vehicle *target_veh = vehicle::find_vehicle( current_veh->parts[p].target.second );
5065             if( target_veh == nullptr || visited_vehs.count( target_veh ) > 0 ) {
5066                 // Either no destination here (that vehicle's rolled away or off-map) or
5067                 // we've already looked at that vehicle.
5068                 continue;
5069             }
5070 
5071             // Add this connected vehicle to the queue of vehicles to search next,
5072             // but only if we haven't seen this one before.
5073             if( visited_vehs.count( target_veh ) < 1 ) {
5074                 int target_loss = current_loss + current_veh->part_info( p ).epower;
5075                 connected_vehs.push( std::make_pair( target_veh, target_loss ) );
5076 
5077                 float loss_amount = ( static_cast<float>( amount ) * static_cast<float>( target_loss ) ) / 100.0f;
5078                 add_msg_debug( "Visiting remote %p with %d power (loss %f, which is %d percent)",
5079                                static_cast<void *>( target_veh ), amount, loss_amount, target_loss );
5080 
5081                 amount = action( target_veh, amount, static_cast<int>( loss_amount ) );
5082                 add_msg_debug( "After remote %p, %d power", static_cast<void *>( target_veh ), amount );
5083 
5084                 if( amount < 1 ) {
5085                     break; // No more charge to donate away.
5086                 }
5087             }
5088         }
5089     }
5090     return amount;
5091 }
5092 
charge_battery(int amount,bool include_other_vehicles)5093 int vehicle::charge_battery( int amount, bool include_other_vehicles )
5094 {
5095     // Key parts by percentage charge level.
5096     std::multimap<int, vehicle_part *> chargeable_parts;
5097     for( vehicle_part &p : parts ) {
5098         if( p.is_available() && p.is_battery() &&
5099             p.ammo_capacity( ammotype( "battery" ) ) > p.ammo_remaining() ) {
5100             chargeable_parts.insert( { ( p.ammo_remaining() * 100 ) / p.ammo_capacity( ammotype( "battery" ) ), &p } );
5101         }
5102     }
5103     while( amount > 0 && !chargeable_parts.empty() ) {
5104         // Grab first part, charge until it reaches the next %, then re-insert with new % key.
5105         auto iter = chargeable_parts.begin();
5106         int charge_level = iter->first;
5107         vehicle_part *p = iter->second;
5108         chargeable_parts.erase( iter );
5109         // Calculate number of charges to reach the next %, but insure it's at least
5110         // one more than current charge.
5111         int next_charge_level = ( ( charge_level + 1 ) * p->ammo_capacity( ammotype( "battery" ) ) ) / 100;
5112         next_charge_level = std::max( next_charge_level, p->ammo_remaining() + 1 );
5113         int qty = std::min( amount, next_charge_level - p->ammo_remaining() );
5114         p->ammo_set( fuel_type_battery, p->ammo_remaining() + qty );
5115         amount -= qty;
5116         if( p->ammo_capacity( ammotype( "battery" ) ) > p->ammo_remaining() ) {
5117             chargeable_parts.insert( { ( p->ammo_remaining() * 100 ) / p->ammo_capacity( ammotype( "battery" ) ), p } );
5118         }
5119     }
5120 
5121     auto charge_visitor = []( vehicle * veh, int amount, int lost ) {
5122         add_msg_debug( "CH: %d", amount - lost );
5123         return veh->charge_battery( amount - lost, false );
5124     };
5125 
5126     if( amount > 0 && include_other_vehicles ) { // still a bit of charge we could send out...
5127         amount = traverse_vehicle_graph( this, amount, charge_visitor );
5128     }
5129 
5130     return amount;
5131 }
5132 
discharge_battery(int amount,bool recurse)5133 int vehicle::discharge_battery( int amount, bool recurse )
5134 {
5135     // Key parts by percentage charge level.
5136     std::multimap<int, vehicle_part *> dischargeable_parts;
5137     for( vehicle_part &p : parts ) {
5138         if( p.is_available() && p.is_battery() && p.ammo_remaining() > 0 ) {
5139             dischargeable_parts.insert( { ( p.ammo_remaining() * 100 ) / p.ammo_capacity( ammotype( "battery" ) ), &p } );
5140         }
5141     }
5142     while( amount > 0 && !dischargeable_parts.empty() ) {
5143         // Grab first part, discharge until it reaches the next %, then re-insert with new % key.
5144         auto iter = std::prev( dischargeable_parts.end() );
5145         int charge_level = iter->first;
5146         vehicle_part *p = iter->second;
5147         dischargeable_parts.erase( iter );
5148         // Calculate number of charges to reach the previous %.
5149         int prev_charge_level = ( ( charge_level - 1 ) * p->ammo_capacity( ammotype( "battery" ) ) ) / 100;
5150         int amount_to_discharge = std::min( p->ammo_remaining() - prev_charge_level, amount );
5151         p->ammo_consume( amount_to_discharge, global_part_pos3( *p ) );
5152         amount -= amount_to_discharge;
5153         if( p->ammo_remaining() > 0 ) {
5154             dischargeable_parts.insert( { ( p->ammo_remaining() * 100 ) / p->ammo_capacity( ammotype( "battery" ) ), p } );
5155         }
5156     }
5157 
5158     auto discharge_visitor = []( vehicle * veh, int amount, int lost ) {
5159         add_msg_debug( "CH: %d", amount + lost );
5160         return veh->discharge_battery( amount + lost, false );
5161     };
5162     if( amount > 0 && recurse ) { // need more power!
5163         amount = traverse_vehicle_graph( this, amount, discharge_visitor );
5164     }
5165 
5166     return amount; // non-zero if we weren't able to fulfill demand.
5167 }
5168 
do_engine_damage(size_t e,int strain)5169 void vehicle::do_engine_damage( size_t e, int strain )
5170 {
5171     strain = std::min( 25, strain );
5172     if( is_engine_on( e ) && !is_perpetual_type( e ) &&
5173         engine_fuel_left( e ) && rng( 1, 100 ) < strain ) {
5174         int dmg = rng( 0, strain * 4 );
5175         damage_direct( engines[e], dmg );
5176         if( one_in( 2 ) ) {
5177             add_msg( _( "Your engine emits a high pitched whine." ) );
5178         } else {
5179             add_msg( _( "Your engine emits a loud grinding sound." ) );
5180         }
5181     }
5182 }
5183 
idle(bool on_map)5184 void vehicle::idle( bool on_map )
5185 {
5186     avg_velocity = ( velocity + avg_velocity ) / 2;
5187 
5188     power_parts();
5189     Character &player_character = get_player_character();
5190     if( engine_on && total_power_w() > 0 ) {
5191         int idle_rate = alternator_load;
5192         if( idle_rate < 10 ) {
5193             idle_rate = 10;    // minimum idle is 1% of full throttle
5194         }
5195         // helicopters use basically nearly all of their power just to hover.
5196         // it becomes more efficient the closer they reach their safe cruise speed.
5197         if( is_rotorcraft() && is_flying_in_air() ) {
5198             idle_rate = 1000;
5199         }
5200         if( has_engine_type_not( fuel_type_muscle, true ) ) {
5201             consume_fuel( idle_rate, true );
5202         }
5203 
5204         if( on_map ) {
5205             noise_and_smoke( idle_rate, 1_turns );
5206         }
5207     } else {
5208         if( engine_on &&
5209             ( has_engine_type_not( fuel_type_muscle, true ) && has_engine_type_not( fuel_type_animal, true ) &&
5210               has_engine_type_not( fuel_type_wind, true ) && has_engine_type_not( fuel_type_mana, true ) ) ) {
5211             add_msg_if_player_sees( global_pos3(), _( "The %s's engine dies!" ), name );
5212         }
5213         engine_on = false;
5214     }
5215 
5216     if( !warm_enough_to_plant( player_character.pos() ) ) {
5217         for( int i : planters ) {
5218             vehicle_part &vp = parts[ i ];
5219             if( vp.enabled ) {
5220                 add_msg_if_player_sees( global_pos3(), _( "The %s's planter turns off due to low temperature." ),
5221                                         name );
5222                 vp.enabled = false;
5223             }
5224         }
5225     }
5226 
5227     smart_controller_handle_turn();
5228 
5229     if( !on_map ) {
5230         return;
5231     } else {
5232         update_time( calendar::turn );
5233     }
5234 
5235     if( has_part( "STEREO", true ) ) {
5236         play_music();
5237     }
5238 
5239     if( has_part( "CHIMES", true ) ) {
5240         play_chimes();
5241     }
5242 
5243     if( has_part( "CRASH_TERRAIN_AROUND", true ) ) {
5244         crash_terrain_around();
5245     }
5246 
5247     if( is_alarm_on ) {
5248         alarm();
5249     }
5250 }
5251 
on_move()5252 void vehicle::on_move()
5253 {
5254     if( has_part( "TRANSFORM_TERRAIN", true ) ) {
5255         transform_terrain();
5256     }
5257     if( has_part( "SCOOP", true ) ) {
5258         operate_scoop();
5259     }
5260     if( has_part( "PLANTER", true ) ) {
5261         operate_planter();
5262     }
5263     if( has_part( "REAPER", true ) ) {
5264         operate_reaper();
5265     }
5266 }
5267 
slow_leak()5268 void vehicle::slow_leak()
5269 {
5270     map &here = get_map();
5271     // for each badly damaged tanks (lower than 50% health), leak a small amount
5272     for( int part : fuel_containers ) {
5273         vehicle_part &p = parts[part];
5274         if( !p.is_leaking() || p.ammo_remaining() <= 0 ) {
5275             continue;
5276         }
5277 
5278         double health = p.health_percent();
5279         itype_id fuel = p.ammo_current();
5280         int qty = std::max( ( 0.5 - health ) * ( 0.5 - health ) * p.ammo_remaining() / 10, 1.0 );
5281         point q = coord_translate( p.mount );
5282         const tripoint dest = global_pos3() + tripoint( q, 0 );
5283 
5284         // damaged batteries self-discharge without leaking, plutonium leaks slurry
5285         if( fuel != fuel_type_battery && fuel != fuel_type_plutonium_cell ) {
5286             item leak( fuel, calendar::turn, qty );
5287             here.add_item_or_charges( dest, leak );
5288             p.ammo_consume( qty, global_part_pos3( p ) );
5289         } else if( fuel == fuel_type_plutonium_cell ) {
5290             if( p.ammo_remaining() >= PLUTONIUM_CHARGES / 10 ) {
5291                 item leak( "plut_slurry_dense", calendar::turn, qty );
5292                 here.add_item_or_charges( dest, leak );
5293                 p.ammo_consume( qty * PLUTONIUM_CHARGES / 10, global_part_pos3( p ) );
5294             } else {
5295                 p.ammo_consume( p.ammo_remaining(), global_part_pos3( p ) );
5296             }
5297         } else {
5298             p.ammo_consume( qty, global_part_pos3( p ) );
5299         }
5300     }
5301 }
5302 
5303 // total volume of all the things
stored_volume(const int part) const5304 units::volume vehicle::stored_volume( const int part ) const
5305 {
5306     return get_items( part ).stored_volume();
5307 }
5308 
max_volume(const int part) const5309 units::volume vehicle::max_volume( const int part ) const
5310 {
5311     return get_items( part ).max_volume();
5312 }
5313 
free_volume(const int part) const5314 units::volume vehicle::free_volume( const int part ) const
5315 {
5316     return get_items( part ).free_volume();
5317 }
5318 
make_active(item_location & loc)5319 void vehicle::make_active( item_location &loc )
5320 {
5321     item &target = *loc;
5322     if( !target.needs_processing() ) {
5323         return;
5324     }
5325     auto cargo_parts = get_parts_at( loc.position(), "CARGO", part_status_flag::any );
5326     if( cargo_parts.empty() ) {
5327         return;
5328     }
5329     // System insures that there is only one part in this vector.
5330     vehicle_part *cargo_part = cargo_parts.front();
5331     active_items.add( target, cargo_part->mount );
5332 }
5333 
add_charges(int part,const item & itm)5334 int vehicle::add_charges( int part, const item &itm )
5335 {
5336     if( !itm.count_by_charges() ) {
5337         debugmsg( "Add charges was called for an item not counted by charges!" );
5338         return 0;
5339     }
5340     const int ret = get_items( part ).amount_can_fit( itm );
5341     if( ret == 0 ) {
5342         return 0;
5343     }
5344 
5345     item itm_copy = itm;
5346     itm_copy.charges = ret;
5347     return add_item( part, itm_copy ) ? ret : 0;
5348 }
5349 
add_item(vehicle_part & pt,const item & obj)5350 cata::optional<vehicle_stack::iterator> vehicle::add_item( vehicle_part &pt, const item &obj )
5351 {
5352     int idx = index_of_part( &pt );
5353     if( idx < 0 ) {
5354         debugmsg( "Tried to add item to invalid part" );
5355         return cata::nullopt;
5356     }
5357     return add_item( idx, obj );
5358 }
5359 
add_item(int part,const item & itm)5360 cata::optional<vehicle_stack::iterator> vehicle::add_item( int part, const item &itm )
5361 {
5362     if( part < 0 || part >= static_cast<int>( parts.size() ) ) {
5363         debugmsg( "int part (%d) is out of range", part );
5364         return cata::nullopt;
5365     }
5366     // const int max_weight = ?! // TODO: weight limit, calculation per vpart & vehicle stats, not a hard user limit.
5367     // add creaking sounds and damage to overloaded vpart, outright break it past a certain point, or when hitting bumps etc
5368     vehicle_part &p = parts[ part ];
5369     if( p.is_broken() ) {
5370         return cata::nullopt;
5371     }
5372 
5373     if( p.base.is_gun() ) {
5374         if( !itm.is_ammo() || !p.base.ammo_types().count( itm.ammo_type() ) ) {
5375             return cata::nullopt;
5376         }
5377     }
5378     bool charge = itm.count_by_charges();
5379     vehicle_stack istack = get_items( part );
5380     const int to_move = istack.amount_can_fit( itm );
5381     if( to_move == 0 || ( charge && to_move < itm.charges ) ) {
5382         return cata::nullopt; // @add_charges should be used in the latter case
5383     }
5384     if( charge ) {
5385         item *here = istack.stacks_with( itm );
5386         if( here ) {
5387             invalidate_mass();
5388             if( !here->merge_charges( itm ) ) {
5389                 return cata::nullopt;
5390             } else {
5391                 return cata::optional<vehicle_stack::iterator>( istack.get_iterator_from_pointer( here ) );
5392             }
5393         }
5394     }
5395 
5396     item itm_copy = itm;
5397 
5398     if( itm_copy.is_bucket_nonempty() ) {
5399         // this is a vehicle, so there is only one pocket.
5400         // so if it will spill, spill all of it
5401         itm_copy.contents.spill_contents( global_part_pos3( part ) );
5402     }
5403 
5404     const vehicle_stack::iterator new_pos = p.items.insert( itm_copy );
5405     if( itm_copy.needs_processing() ) {
5406         active_items.add( *new_pos, p.mount );
5407     }
5408 
5409     invalidate_mass();
5410     return cata::optional<vehicle_stack::iterator>( new_pos );
5411 }
5412 
remove_item(int part,item * it)5413 bool vehicle::remove_item( int part, item *it )
5414 {
5415     const cata::colony<item> &veh_items = parts[part].items;
5416     const cata::colony<item>::const_iterator iter = veh_items.get_iterator_from_pointer( it );
5417     if( iter == veh_items.end() ) {
5418         return false;
5419     }
5420     remove_item( part, iter );
5421     return true;
5422 }
5423 
remove_item(int part,const vehicle_stack::const_iterator & it)5424 vehicle_stack::iterator vehicle::remove_item( int part, const vehicle_stack::const_iterator &it )
5425 {
5426     cata::colony<item> &veh_items = parts[part].items;
5427 
5428     // remove from the active items cache (if it isn't there does nothing)
5429     active_items.remove( &*it );
5430 
5431     invalidate_mass();
5432     return veh_items.erase( it );
5433 }
5434 
get_items(const int part)5435 vehicle_stack vehicle::get_items( const int part )
5436 {
5437     const tripoint pos = global_part_pos3( part );
5438     return vehicle_stack( &parts[part].items, pos.xy(), this, part );
5439 }
5440 
get_items(const int part) const5441 vehicle_stack vehicle::get_items( const int part ) const
5442 {
5443     // HACK: callers could modify items through this
5444     // TODO: a const version of vehicle_stack is needed
5445     return const_cast<vehicle *>( this )->get_items( part );
5446 }
5447 
place_spawn_items()5448 void vehicle::place_spawn_items()
5449 {
5450     if( !type.is_valid() ) {
5451         return;
5452     }
5453 
5454     for( const vehicle_prototype::part_def &pt : type->parts ) {
5455         if( pt.with_ammo ) {
5456             int turret = part_with_feature( pt.pos, "TURRET", true );
5457             if( turret >= 0 && x_in_y( pt.with_ammo, 100 ) ) {
5458                 parts[ turret ].ammo_set( random_entry( pt.ammo_types ), rng( pt.ammo_qty.first,
5459                                           pt.ammo_qty.second ) );
5460             }
5461         }
5462     }
5463 
5464     const float spawn_rate = get_option<float>( "ITEM_SPAWNRATE" );
5465     for( const vehicle_item_spawn &spawn : type.obj().item_spawns ) {
5466         int part = part_with_feature( spawn.pos, "CARGO", false );
5467         if( part < 0 ) {
5468             debugmsg( "No CARGO parts at (%d, %d) of %s!", spawn.pos.x, spawn.pos.y, name );
5469         } else {
5470             bool broken = parts[ part ].is_broken();
5471 
5472             std::vector<item> created;
5473             const int spawn_count = roll_remainder( spawn.chance * std::max( spawn_rate, 1.0f ) / 100.0f );
5474             for( int i = 0; i < spawn_count; ++i ) {
5475                 // if vehicle part is broken only 50% of items spawn and they will be variably damaged
5476                 if( broken && one_in( 2 ) ) {
5477                     continue;
5478                 }
5479 
5480                 for( const itype_id &e : spawn.item_ids ) {
5481                     if( rng_float( 0, 1 ) < spawn_rate ) {
5482                         created.emplace_back( item( e ).in_its_container() );
5483                     }
5484                 }
5485                 for( const item_group_id &e : spawn.item_groups ) {
5486                     item_group::ItemList group_items = item_group::items_from( e, calendar::start_of_cataclysm,
5487                                                        spawn_flags::use_spawn_rate );
5488                     created.insert( created.end(), group_items.begin(), group_items.end() );
5489                 }
5490             }
5491             for( item &e : created ) {
5492                 if( e.is_null() ) {
5493                     continue;
5494                 }
5495                 if( broken && e.mod_damage( rng( 1, e.max_damage() ) ) ) {
5496                     continue; // we destroyed the item
5497                 }
5498                 if( e.is_tool() || e.is_gun() || e.is_magazine() ) {
5499                     bool spawn_ammo = rng( 0, 99 ) < spawn.with_ammo && e.ammo_remaining() == 0;
5500                     bool spawn_mag  = rng( 0, 99 ) < spawn.with_magazine && !e.magazine_integral() &&
5501                                       !e.magazine_current();
5502 
5503                     if( spawn_mag ) {
5504                         item mag( e.magazine_default(), e.birthday() );
5505                         if( spawn_ammo ) {
5506                             mag.ammo_set( mag.ammo_default() );
5507                         }
5508                         e.put_in( mag, item_pocket::pocket_type::MAGAZINE_WELL );
5509                     } else if( spawn_ammo && e.is_magazine() ) {
5510                         e.ammo_set( e.ammo_default() );
5511                     }
5512                 }
5513                 add_item( part, e );
5514             }
5515         }
5516     }
5517 }
5518 
gain_moves()5519 void vehicle::gain_moves()
5520 {
5521     fuel_used_last_turn.clear();
5522     check_falling_or_floating();
5523     const bool pl_control = player_in_control( get_player_character() );
5524     if( is_moving() || is_falling ) {
5525         if( !loose_parts.empty() ) {
5526             shed_loose_parts();
5527         }
5528         of_turn = 1 + of_turn_carry;
5529         const int vslowdown = slowdown( velocity );
5530         if( vslowdown > std::abs( velocity ) ) {
5531             if( cruise_on && cruise_velocity && pl_control ) {
5532                 velocity = velocity > 0 ? 1 : -1;
5533             } else {
5534                 stop();
5535             }
5536         } else if( velocity < 0 ) {
5537             velocity += vslowdown;
5538         } else {
5539             velocity -= vslowdown;
5540         }
5541         is_on_ramp = false;
5542     } else {
5543         of_turn = .001;
5544     }
5545     of_turn_carry = 0;
5546     // cruise control TODO: enable for NPC?
5547     if( ( pl_control || is_following || is_patrolling ) && cruise_on && cruise_velocity != velocity ) {
5548         thrust( ( cruise_velocity ) > velocity ? 1 : -1 );
5549     }
5550 
5551     // Force off-map vehicles to load by visiting them every time we gain moves.
5552     // Shouldn't be too expensive if there aren't fifty trillion vehicles in the graph...
5553     // ...and if there are, it's the player's fault for putting them there.
5554     auto nil_visitor = []( vehicle *, int amount, int ) {
5555         return amount;
5556     };
5557     traverse_vehicle_graph( this, 1, nil_visitor );
5558 
5559     if( check_environmental_effects ) {
5560         check_environmental_effects = do_environmental_effects();
5561     }
5562 
5563     // turrets which are enabled will try to reload and then automatically fire
5564     // Turrets which are disabled but have targets set are a special case
5565     for( vehicle_part *e : turrets() ) {
5566         if( e->enabled || e->target.second != e->target.first ) {
5567             automatic_fire_turret( *e );
5568         }
5569     }
5570 
5571     if( velocity < 0 ) {
5572         beeper_sound();
5573     }
5574 }
5575 
dump_items_from_part(const size_t index)5576 void vehicle::dump_items_from_part( const size_t index )
5577 {
5578     map &here = get_map();
5579     vehicle_part &vp = parts[ index ];
5580     for( item &e : vp.items ) {
5581         here.add_item_or_charges( global_part_pos3( vp ), e );
5582     }
5583     vp.items.clear();
5584 }
5585 
decrement_summon_timer()5586 bool vehicle::decrement_summon_timer()
5587 {
5588     if( !summon_time_limit ) {
5589         return false;
5590     }
5591     if( *summon_time_limit <= 0_turns ) {
5592         for( const vpart_reference &vp : get_all_parts() ) {
5593             const size_t p = vp.part_index();
5594             dump_items_from_part( p );
5595         }
5596         add_msg_if_player_sees( global_pos3(), m_info, _( "Your %s winks out of existence." ), name );
5597         get_map().destroy_vehicle( this );
5598         return true;
5599     } else {
5600         *summon_time_limit -= 1_turns;
5601     }
5602     return false;
5603 }
5604 
suspend_refresh()5605 void vehicle::suspend_refresh()
5606 {
5607     // disable refresh and cache recalculation
5608     no_refresh = true;
5609     mass_dirty = false;
5610     mass_center_precalc_dirty = false;
5611     mass_center_no_precalc_dirty = false;
5612     coeff_rolling_dirty = false;
5613     coeff_air_dirty = false;
5614     coeff_water_dirty = false;
5615     coeff_air_changed = false;
5616 }
5617 
enable_refresh()5618 void vehicle::enable_refresh()
5619 {
5620     // force all caches to recalculate
5621     no_refresh = false;
5622     mass_dirty = true;
5623     mass_center_precalc_dirty = true;
5624     mass_center_no_precalc_dirty = true;
5625     coeff_rolling_dirty = true;
5626     coeff_air_dirty = true;
5627     coeff_water_dirty = true;
5628     coeff_air_changed = true;
5629     refresh();
5630 }
5631 
5632 /**
5633  * Refreshes all caches and refinds all parts. Used after the vehicle has had a part added or removed.
5634  * Makes indices of different part types so they're easy to find. Also calculates power drain.
5635  */
refresh()5636 void vehicle::refresh()
5637 {
5638     if( no_refresh ) {
5639         return;
5640     }
5641 
5642     alternators.clear();
5643     engines.clear();
5644     reactors.clear();
5645     solar_panels.clear();
5646     wind_turbines.clear();
5647     sails.clear();
5648     water_wheels.clear();
5649     funnels.clear();
5650     emitters.clear();
5651     relative_parts.clear();
5652     loose_parts.clear();
5653     wheelcache.clear();
5654     rail_wheelcache.clear();
5655     rotors.clear();
5656     steering.clear();
5657     speciality.clear();
5658     floating.clear();
5659     batteries.clear();
5660     fuel_containers.clear();
5661     turret_locations.clear();
5662     mufflers.clear();
5663     planters.clear();
5664     accessories.clear();
5665 
5666     alternator_load = 0;
5667     extra_drag = 0;
5668     all_wheels_on_one_axis = true;
5669     int first_wheel_y_mount = INT_MAX;
5670 
5671     // Used to sort part list so it displays properly when examining
5672     struct sort_veh_part_vector {
5673         vehicle *veh;
5674         inline bool operator()( const int p1, const int p2 ) {
5675             return veh->part_info( p1 ).list_order < veh->part_info( p2 ).list_order;
5676         }
5677     } svpv = { this };
5678 
5679     mount_min.x = 123;
5680     mount_min.y = 123;
5681     mount_max.x = -123;
5682     mount_max.y = -123;
5683 
5684     int railwheel_xmin = INT_MAX;
5685     int railwheel_ymin = INT_MAX;
5686     int railwheel_xmax = INT_MIN;
5687     int railwheel_ymax = INT_MIN;
5688 
5689     has_enabled_smart_controller = false;
5690     smart_controller_state = cata::nullopt;
5691 
5692     bool refresh_done = false;
5693 
5694     // Main loop over all vehicle parts.
5695     for( const vpart_reference &vp : get_all_parts() ) {
5696         const size_t p = vp.part_index();
5697         const vpart_info &vpi = vp.info();
5698         if( vp.part().removed ) {
5699             continue;
5700         }
5701         refresh_done = true;
5702 
5703         // Build map of point -> all parts in that point
5704         const point pt = vp.mount();
5705         mount_min.x = std::min( mount_min.x, pt.x );
5706         mount_min.y = std::min( mount_min.y, pt.y );
5707         mount_max.x = std::max( mount_max.x, pt.x );
5708         mount_max.y = std::max( mount_max.y, pt.y );
5709 
5710         // This will keep the parts at point pt sorted
5711         std::vector<int>::iterator vii = std::lower_bound( relative_parts[pt].begin(),
5712                                          relative_parts[pt].end(),
5713                                          static_cast<int>( p ), svpv );
5714         relative_parts[pt].insert( vii, p );
5715 
5716         if( vpi.has_flag( VPFLAG_FLOATS ) ) {
5717             floating.push_back( p );
5718         }
5719 
5720         if( vp.part().is_unavailable() ) {
5721             continue;
5722         }
5723         if( vpi.has_flag( VPFLAG_ALTERNATOR ) ) {
5724             alternators.push_back( p );
5725         }
5726         if( vpi.has_flag( VPFLAG_ENGINE ) ) {
5727             engines.push_back( p );
5728         }
5729         if( vpi.has_flag( VPFLAG_REACTOR ) ) {
5730             reactors.push_back( p );
5731         }
5732         if( vpi.has_flag( VPFLAG_SOLAR_PANEL ) ) {
5733             solar_panels.push_back( p );
5734         }
5735         if( vpi.has_flag( VPFLAG_ROTOR ) || vpi.has_flag( VPFLAG_ROTOR_SIMPLE ) ) {
5736             rotors.push_back( p );
5737         }
5738         if( vp.part().is_battery() ) {
5739             batteries.push_back( p );
5740         }
5741         if( vp.part().is_fuel_store( false ) ) {
5742             fuel_containers.push_back( p );
5743         }
5744         if( vp.part().is_turret() ) {
5745             turret_locations.push_back( p );
5746         }
5747         if( vpi.has_flag( "WIND_TURBINE" ) ) {
5748             wind_turbines.push_back( p );
5749         }
5750         if( vpi.has_flag( "WIND_POWERED" ) ) {
5751             sails.push_back( p );
5752         }
5753         if( vpi.has_flag( "WATER_WHEEL" ) ) {
5754             water_wheels.push_back( p );
5755         }
5756         if( vpi.has_flag( "FUNNEL" ) ) {
5757             funnels.push_back( p );
5758         }
5759         if( vpi.has_flag( "UNMOUNT_ON_MOVE" ) ) {
5760             loose_parts.push_back( p );
5761         }
5762         if( !vpi.emissions.empty() || !vpi.exhaust.empty() ) {
5763             emitters.push_back( p );
5764         }
5765         if( vpi.has_flag( VPFLAG_WHEEL ) ) {
5766             wheelcache.push_back( p );
5767         }
5768         if( vpi.has_flag( "SMART_ENGINE_CONTROLLER" ) && vp.part().enabled ) {
5769             has_enabled_smart_controller = true;
5770         }
5771         if( vpi.has_flag( VPFLAG_WHEEL ) && vpi.has_flag( VPFLAG_RAIL ) ) {
5772             rail_wheelcache.push_back( p );
5773             if( first_wheel_y_mount == INT_MAX ) {
5774                 first_wheel_y_mount = vp.part().mount.y;
5775             }
5776             if( first_wheel_y_mount != vp.part().mount.y ) {
5777                 // vehicle have wheels on different axis
5778                 all_wheels_on_one_axis = false;
5779             }
5780 
5781             railwheel_xmin = std::min( railwheel_xmin, pt.x );
5782             railwheel_ymin = std::min( railwheel_ymin, pt.y );
5783             railwheel_xmax = std::max( railwheel_xmax, pt.x );
5784             railwheel_ymax = std::max( railwheel_ymax, pt.y );
5785         }
5786         if( ( vpi.has_flag( "STEERABLE" ) && part_with_feature( pt, "STEERABLE", true ) != -1 ) ||
5787             vpi.has_flag( "TRACKED" ) ) {
5788             // TRACKED contributes to steering effectiveness but
5789             //  (a) doesn't count as a steering axle for install difficulty
5790             //  (b) still contributes to drag for the center of steering calculation
5791             steering.push_back( p );
5792         }
5793         if( vpi.has_flag( "SECURITY" ) ) {
5794             speciality.push_back( p );
5795         }
5796         if( vp.part().enabled && vpi.has_flag( "EXTRA_DRAG" ) ) {
5797             extra_drag += vpi.power;
5798         }
5799         if( vpi.has_flag( "EXTRA_DRAG" ) && ( vpi.has_flag( "WIND_TURBINE" ) ||
5800                                               vpi.has_flag( "WATER_WHEEL" ) ) ) {
5801             extra_drag += vpi.power;
5802         }
5803         if( camera_on && vpi.has_flag( "CAMERA" ) ) {
5804             vp.part().enabled = true;
5805         } else if( !camera_on && vpi.has_flag( "CAMERA" ) ) {
5806             vp.part().enabled = false;
5807         }
5808         if( vpi.has_flag( "TURRET" ) && !has_part( global_part_pos3( vp.part() ), "TURRET_CONTROLS" ) ) {
5809             vp.part().enabled = false;
5810         }
5811         if( vpi.has_flag( "MUFFLER" ) ) {
5812             mufflers.push_back( p );
5813         }
5814         if( vpi.has_flag( "PLANTER" ) ) {
5815             planters.push_back( p );
5816         }
5817         if( vpi.has_flag( VPFLAG_ENABLED_DRAINS_EPOWER ) ) {
5818             accessories.push_back( p );
5819         }
5820     }
5821 
5822     rail_wheel_bounding_box.p1 = point( railwheel_xmin, railwheel_ymin );
5823     rail_wheel_bounding_box.p2 = point( railwheel_xmax, railwheel_ymax );
5824     front_left.x = mount_max.x;
5825     front_left.y = mount_min.y;
5826     front_right = mount_max;
5827 
5828     if( !refresh_done ) {
5829         mount_min = mount_max = point_zero;
5830         rail_wheel_bounding_box.p1 = point_zero;
5831         rail_wheel_bounding_box.p2 = point_zero;
5832     }
5833 
5834     // NB: using the _old_ pivot point, don't recalc here, we only do that when moving!
5835     precalc_mounts( 0, pivot_rotation[0], pivot_anchor[0] );
5836     check_environmental_effects = true;
5837     insides_dirty = true;
5838     zones_dirty = true;
5839     invalidate_mass();
5840     occupied_cache_pos = { -1, -1, -1 };
5841 }
5842 
pivot_point() const5843 const point &vehicle::pivot_point() const
5844 {
5845     if( pivot_dirty ) {
5846         refresh_pivot();
5847     }
5848 
5849     return pivot_cache;
5850 }
5851 
refresh_pivot() const5852 void vehicle::refresh_pivot() const
5853 {
5854     // Const method, but messes with mutable fields
5855     pivot_dirty = false;
5856 
5857     if( wheelcache.empty() || !valid_wheel_config() ) {
5858         // No usable wheels, use CoM (dragging)
5859         pivot_cache = local_center_of_mass();
5860         return;
5861     }
5862 
5863     // The model here is:
5864     //
5865     //  We are trying to rotate around some point (xc,yc)
5866     //  This produces a friction force / moment from each wheel resisting the
5867     //  rotation. We want to find the point that minimizes that resistance.
5868     //
5869     //  For a given wheel w at (xw,yw), find:
5870     //   weight(w): a scaling factor for the friction force based on wheel
5871     //              size, brokenness, steerability/orientation
5872     //   center_dist: the distance from (xw,yw) to (xc,yc)
5873     //   centerline_angle: the angle between the X axis and a line through
5874     //                     (xw,yw) and (xc,yc)
5875     //
5876     //  Decompose the force into two components, assuming that the wheel is
5877     //  aligned along the X axis and we want to apply different weightings to
5878     //  the in-line vs perpendicular parts of the force:
5879     //
5880     //   Resistance force in line with the wheel (X axis)
5881     //    Fi = weightI(w) * center_dist * sin(centerline_angle)
5882     //   Resistance force perpendicular to the wheel (Y axis):
5883     //    Fp = weightP(w) * center_dist * cos(centerline_angle);
5884     //
5885     //  Then find the moment that these two forces would apply around (xc,yc)
5886     //    moment(w) = center_dist * cos(centerline_angle) * Fi +
5887     //                center_dist * sin(centerline_angle) * Fp
5888     //
5889     //  Note that:
5890     //    cos(centerline_angle) = (xw-xc) / center_dist
5891     //    sin(centerline_angle) = (yw-yc) / center_dist
5892     // -> moment(w) = weightP(w)*(xw-xc)^2 + weightI(w)*(yw-yc)^2
5893     //              = weightP(w)*xc^2 - 2*weightP(w)*xc*xw + weightP(w)*xw^2 +
5894     //                weightI(w)*yc^2 - 2*weightI(w)*yc*yw + weightI(w)*yw^2
5895     //
5896     //  which happily means that the X and Y axes can be handled independently.
5897     //  We want to minimize sum(moment(w)) due to wheels w=0,1,..., which
5898     //  occurs when:
5899     //
5900     //    sum( 2*xc*weightP(w) - 2*weightP(w)*xw ) = 0
5901     //     -> xc = (weightP(0)*x0 + weightP(1)*x1 + ...) /
5902     //             (weightP(0) + weightP(1) + ...)
5903     //    sum( 2*yc*weightI(w) - 2*weightI(w)*yw ) = 0
5904     //     -> yc = (weightI(0)*y0 + weightI(1)*y1 + ...) /
5905     //             (weightI(0) + weightI(1) + ...)
5906     //
5907     // so it turns into a fairly simple weighted average of the wheel positions.
5908 
5909     float xc_numerator = 0.0f;
5910     float xc_denominator = 0.0f;
5911     float yc_numerator = 0.0f;
5912     float yc_denominator = 0.0f;
5913 
5914     for( int p : wheelcache ) {
5915         const vehicle_part &wheel = parts[p];
5916 
5917         // TODO: load on tire?
5918         int contact_area = wheel.wheel_area();
5919         float weight_i;  // weighting for the in-line part
5920         float weight_p;  // weighting for the perpendicular part
5921         if( wheel.is_broken() ) {
5922             // broken wheels don't roll on either axis
5923             weight_i = contact_area * 2.0;
5924             weight_p = contact_area * 2.0;
5925         } else if( part_with_feature( wheel.mount, "STEERABLE", true ) != -1 ) {
5926             // Unbroken steerable wheels can handle motion on both axes
5927             // (but roll a little more easily inline)
5928             weight_i = contact_area * 0.1;
5929             weight_p = contact_area * 0.2;
5930         } else {
5931             // Regular wheels resist perpendicular motion
5932             weight_i = contact_area * 0.1;
5933             weight_p = contact_area;
5934         }
5935 
5936         xc_numerator += weight_p * wheel.mount.x;
5937         yc_numerator += weight_i * wheel.mount.y;
5938         xc_denominator += weight_p;
5939         yc_denominator += weight_i;
5940     }
5941 
5942     if( xc_denominator < 0.1 || yc_denominator < 0.1 ) {
5943         debugmsg( "vehicle::refresh_pivot had a bad weight: xc=%.3f/%.3f yc=%.3f/%.3f",
5944                   xc_numerator, xc_denominator, yc_numerator, yc_denominator );
5945         pivot_cache = local_center_of_mass();
5946     } else {
5947         pivot_cache.x = std::round( xc_numerator / xc_denominator );
5948         pivot_cache.y = std::round( yc_numerator / yc_denominator );
5949     }
5950 }
5951 
do_towing_move()5952 void vehicle::do_towing_move()
5953 {
5954     if( !no_towing_slack() || velocity <= 0 ) {
5955         return;
5956     }
5957     bool invalidate = false;
5958     if( !tow_data.get_towed() ) {
5959         debugmsg( "tried to do towing move, but no towed vehicle!" );
5960         invalidate = true;
5961     }
5962     const int tow_index = get_tow_part();
5963     if( tow_index == -1 ) {
5964         debugmsg( "tried to do towing move, but no tow part" );
5965         invalidate = true;
5966     }
5967     vehicle *towed_veh = tow_data.get_towed();
5968     if( !towed_veh ) {
5969         debugmsg( "tried to do towing move, but towed vehicle doesn't exist." );
5970         invalidate_towing();
5971         return;
5972     }
5973     const int other_tow_index = towed_veh->get_tow_part();
5974     if( other_tow_index == -1 ) {
5975         debugmsg( "tried to do towing move but towed vehicle has no towing part" );
5976         invalidate = true;
5977     }
5978     if( towed_veh->global_pos3().z != global_pos3().z ) {
5979         // how the hellicopter did this happen?
5980         // yes, this can happen when towing over a bridge (see #47293)
5981         invalidate = true;
5982         add_msg( m_info, _( "A towing cable snaps off of %s." ), tow_data.get_towed()->disp_name() );
5983     }
5984     if( invalidate ) {
5985         invalidate_towing( true );
5986         return;
5987     }
5988     map &here = get_map();
5989     const tripoint tower_tow_point = here.getabs( global_part_pos3( tow_index ) );
5990     const tripoint towed_tow_point = here.getabs( towed_veh->global_part_pos3( other_tow_index ) );
5991     // same as above, but where the pulling vehicle is pulling from
5992     units::angle towing_veh_angle = towed_veh->get_angle_from_targ( tower_tow_point );
5993     const bool reverse = towed_veh->tow_data.tow_direction == TOW_BACK;
5994     int accel_y = 0;
5995     tripoint vehpos = here.getabs( towed_veh->global_pos3() );
5996     int turn_x = get_turn_from_angle( towing_veh_angle, vehpos, tower_tow_point, reverse );
5997     if( rl_dist( towed_tow_point, tower_tow_point ) < 6 ) {
5998         accel_y = reverse ? -1 : 1;
5999     }
6000     if( towed_veh->velocity <= velocity && rl_dist( towed_tow_point, tower_tow_point ) >= 7 ) {
6001         accel_y = reverse ? 1 : -1;
6002     }
6003     if( rl_dist( towed_tow_point, tower_tow_point ) >= 12 ) {
6004         towed_veh->velocity = velocity * 1.8;
6005         if( reverse ) {
6006             towed_veh->velocity = -towed_veh->velocity;
6007         }
6008     } else {
6009         towed_veh->velocity = reverse ? -velocity : velocity;
6010     }
6011     if( towed_veh->tow_data.tow_direction == TOW_FRONT ) {
6012         towed_veh->autodrive( point( turn_x, accel_y ) );
6013     } else if( towed_veh->tow_data.tow_direction == TOW_BACK ) {
6014         accel_y = 10;
6015         towed_veh->autodrive( point( turn_x, accel_y ) );
6016     } else {
6017         towed_veh->skidding = true;
6018         std::vector<tripoint> lineto = line_to( here.getlocal( towed_tow_point ),
6019                                                 here.getlocal( tower_tow_point ) );
6020         tripoint nearby_destination;
6021         if( lineto.size() >= 2 ) {
6022             nearby_destination = lineto[1];
6023         } else {
6024             nearby_destination = tower_tow_point;
6025         }
6026         const int destination_delta_x = here.getlocal( tower_tow_point ).x - nearby_destination.x;
6027         const int destination_delta_y = here.getlocal( tower_tow_point ).y - nearby_destination.y;
6028         const int destination_delta_z = towed_veh->global_pos3().z;
6029         const tripoint move_destination( clamp( destination_delta_x, -1, 1 ),
6030                                          clamp( destination_delta_y, -1, 1 ),
6031                                          clamp( destination_delta_z, -1, 1 ) );
6032         here.move_vehicle( *towed_veh, move_destination, towed_veh->face );
6033         towed_veh->move = tileray( point( destination_delta_x, destination_delta_y ) );
6034     }
6035 
6036 }
6037 
is_external_part(const tripoint & part_pt) const6038 bool vehicle::is_external_part( const tripoint &part_pt ) const
6039 {
6040     map &here = get_map();
6041     for( const tripoint &elem : here.points_in_radius( part_pt, 1 ) ) {
6042         const optional_vpart_position vp = here.veh_at( elem );
6043         if( !vp ) {
6044             return true;
6045         }
6046         if( &vp->vehicle() != this ) {
6047             return true;
6048         }
6049     }
6050     return false;
6051 }
6052 
is_towing() const6053 bool vehicle::is_towing() const
6054 {
6055     bool ret = false;
6056     if( !tow_data.get_towed() ) {
6057         return ret;
6058     } else {
6059         if( !tow_data.get_towed()->tow_data.get_towed_by() ) {
6060             debugmsg( "vehicle %s is towing, but the towed vehicle has no tower defined", name );
6061             return ret;
6062         }
6063         ret = true;
6064     }
6065     return ret;
6066 }
6067 
is_towed() const6068 bool vehicle::is_towed() const
6069 {
6070     bool ret = false;
6071     if( !tow_data.get_towed_by() ) {
6072         return ret;
6073     } else {
6074         if( !tow_data.get_towed_by()->tow_data.get_towed() ) {
6075             debugmsg( "vehicle %s is marked as towed, but the tower vehicle has no towed defined", name );
6076             return ret;
6077         }
6078         ret = true;
6079     }
6080     return ret;
6081 }
6082 
get_tow_part() const6083 int vehicle::get_tow_part() const
6084 {
6085     for( const vpart_reference &vp : get_all_parts() ) {
6086         const size_t p = vp.part_index();
6087         if( vp.part().removed ) {
6088             continue;
6089         }
6090 
6091         if( part_with_feature( p, "TOW_CABLE", true ) >= 0 && vp.part().is_available() ) {
6092             return p;
6093         }
6094     }
6095     return -1;
6096 }
6097 
has_tow_attached() const6098 bool vehicle::has_tow_attached() const
6099 {
6100     bool ret = false;
6101     for( const vpart_reference &vp : get_all_parts() ) {
6102         const size_t p = vp.part_index();
6103         if( vp.part().removed ) {
6104             continue;
6105         }
6106 
6107         if( part_with_feature( p, "TOW_CABLE", true ) >= 0 && vp.part().is_available() ) {
6108             ret = true;
6109             break;
6110         }
6111     }
6112     return ret;
6113 }
6114 
set_tow_directions()6115 void vehicle::set_tow_directions()
6116 {
6117     const int length = mount_max.x - mount_min.x + 1;
6118     const point mount_of_tow = parts[get_tow_part()].mount;
6119     const point normalized_tow_mount = point( std::abs( mount_of_tow.x - mount_min.x ),
6120                                        std::abs( mount_of_tow.y - mount_min.y ) );
6121     if( length >= 3 ) {
6122         const int trisect = length / 3;
6123         if( normalized_tow_mount.x <= trisect ) {
6124             tow_data.tow_direction = TOW_BACK;
6125         } else if( normalized_tow_mount.x > trisect && normalized_tow_mount.x <= trisect * 2 ) {
6126             tow_data.tow_direction = TOW_SIDE;
6127         } else {
6128             tow_data.tow_direction = TOW_FRONT;
6129         }
6130     } else {
6131         // its a small vehicle, no danger if it flips around.
6132         tow_data.tow_direction = TOW_FRONT;
6133     }
6134 }
6135 
set_towing(vehicle * tower_veh,vehicle * towed_veh)6136 bool towing_data::set_towing( vehicle *tower_veh, vehicle *towed_veh )
6137 {
6138     if( !towed_veh || !tower_veh ) {
6139         return false;
6140     }
6141     towed_veh->tow_data.towed_by = tower_veh;
6142     tower_veh->tow_data.towing = towed_veh;
6143     tower_veh->set_tow_directions();
6144     towed_veh->set_tow_directions();
6145     return true;
6146 }
6147 
invalidate_towing(bool first_vehicle)6148 void vehicle::invalidate_towing( bool first_vehicle )
6149 {
6150     if( !is_towing() && !is_towed() ) {
6151         return;
6152     }
6153     vehicle *other_veh = nullptr;
6154     if( is_towing() ) {
6155         other_veh = tow_data.get_towed();
6156     } else if( is_towed() ) {
6157         other_veh = tow_data.get_towed_by();
6158     }
6159     if( other_veh && first_vehicle ) {
6160         other_veh->invalidate_towing();
6161     }
6162     map &here = get_map();
6163     for( const vpart_reference &vp : get_all_parts() ) {
6164         const size_t p = vp.part_index();
6165         if( vp.part().removed ) {
6166             continue;
6167         }
6168 
6169         if( part_with_feature( p, "TOW_CABLE", true ) >= 0 ) {
6170             if( first_vehicle ) {
6171                 vehicle_part *part = &parts[part_with_feature( p, "TOW_CABLE", true )];
6172                 item drop = part->properties_to_item();
6173                 here.add_item_or_charges( global_part_pos3( *part ), drop );
6174             }
6175             remove_part( part_with_feature( p, "TOW_CABLE", true ) );
6176             break;
6177         }
6178     }
6179     tow_data.clear_towing();
6180 }
6181 
6182 // to be called on the towed vehicle
tow_cable_too_far() const6183 bool vehicle::tow_cable_too_far() const
6184 {
6185     if( !tow_data.get_towed_by() ) {
6186         debugmsg( "checking tow cable length on a vehicle that has no towing vehicle" );
6187         return false;
6188     }
6189     int index = get_tow_part();
6190     if( index == -1 ) {
6191         debugmsg( "towing data exists but no towing part" );
6192         return false;
6193     }
6194     map &here = get_map();
6195     tripoint towing_point = here.getabs( global_part_pos3( index ) );
6196     if( !tow_data.get_towed_by()->tow_data.get_towed() ) {
6197         debugmsg( "vehicle %s has data for a towing vehicle, but that towing vehicle does not have %s listed as towed",
6198                   disp_name(), disp_name() );
6199         return false;
6200     }
6201     int other_index = tow_data.get_towed_by()->get_tow_part();
6202     if( other_index == -1 ) {
6203         debugmsg( "towing data exists but no towing part" );
6204         return false;
6205     }
6206     tripoint towed_point = here.getabs( tow_data.get_towed_by()->global_part_pos3( other_index ) );
6207     if( towing_point == tripoint_zero || towed_point == tripoint_zero ) {
6208         debugmsg( "towing data exists but no towing part" );
6209         return false;
6210     }
6211     return rl_dist( towing_point, towed_point ) >= 25;
6212 }
6213 
6214 // the towing cable only starts pulling at a certain distance between the vehicles
6215 // to be called on the towing vehicle
no_towing_slack() const6216 bool vehicle::no_towing_slack() const
6217 {
6218     if( !tow_data.get_towed() ) {
6219         return false;
6220     }
6221     int index = get_tow_part();
6222     if( index == -1 ) {
6223         debugmsg( "towing data exists but no towing part" );
6224         return false;
6225     }
6226     map &here = get_map();
6227     tripoint towing_point = here.getabs( global_part_pos3( index ) );
6228     if( !tow_data.get_towed()->tow_data.get_towed_by() ) {
6229         debugmsg( "vehicle %s has data for a towed vehicle, but that towed vehicle does not have %s listed as tower",
6230                   disp_name(), disp_name() );
6231         return false;
6232     }
6233     int other_index = tow_data.get_towed()->get_tow_part();
6234     if( other_index == -1 ) {
6235         debugmsg( "towing data exists but no towing part" );
6236         return false;
6237     }
6238     tripoint towed_point = here.getabs( tow_data.get_towed()->global_part_pos3( other_index ) );
6239     if( towing_point == tripoint_zero || towed_point == tripoint_zero ) {
6240         debugmsg( "towing data exists but no towing part" );
6241         return false;
6242     }
6243     return rl_dist( towing_point, towed_point ) >= 8;
6244 
6245 }
6246 
remove_remote_part(int part_num)6247 void vehicle::remove_remote_part( int part_num )
6248 {
6249     vehicle *veh = find_vehicle( parts[part_num].target.second );
6250 
6251     // If the target vehicle is still there, ask it to remove its part
6252     if( veh != nullptr ) {
6253         const tripoint local_abs = get_map().getabs( global_part_pos3( part_num ) );
6254 
6255         for( size_t j = 0; j < veh->loose_parts.size(); j++ ) {
6256             int remote_partnum = veh->loose_parts[j];
6257             const vehicle_part *remote_part = &veh->parts[remote_partnum];
6258 
6259             if( veh->part_flag( remote_partnum, "POWER_TRANSFER" ) && remote_part->target.first == local_abs ) {
6260                 veh->remove_part( remote_partnum );
6261                 return;
6262             }
6263         }
6264     }
6265 }
6266 
shed_loose_parts()6267 void vehicle::shed_loose_parts()
6268 {
6269     map &here = get_map();
6270     // remove_part rebuilds the loose_parts vector, when all of those parts have been removed,
6271     // it will stay empty.
6272     while( !loose_parts.empty() ) {
6273         const int elem = loose_parts.front();
6274         if( part_flag( elem, "POWER_TRANSFER" ) ) {
6275             remove_remote_part( elem );
6276         }
6277         if( is_towing() || is_towed() ) {
6278             vehicle *other_veh = is_towing() ? tow_data.get_towed() : tow_data.get_towed_by();
6279             if( other_veh ) {
6280                 other_veh->remove_part( other_veh->part_with_feature( other_veh->get_tow_part(), "TOW_CABLE",
6281                                         true ) );
6282                 other_veh->tow_data.clear_towing();
6283             }
6284             tow_data.clear_towing();
6285         }
6286         const vehicle_part *part = &parts[elem];
6287         if( !magic ) {
6288             item drop = part->properties_to_item();
6289             here.add_item_or_charges( global_part_pos3( *part ), drop );
6290         }
6291 
6292         remove_part( elem );
6293     }
6294 }
6295 
enclosed_at(const tripoint & pos)6296 bool vehicle::enclosed_at( const tripoint &pos )
6297 {
6298     refresh_insides();
6299     std::vector<vehicle_part *> parts_here = get_parts_at( pos, "BOARDABLE",
6300             part_status_flag::working );
6301     if( !parts_here.empty() ) {
6302         return parts_here.front()->inside;
6303     }
6304     return false;
6305 }
6306 
refresh_insides()6307 void vehicle::refresh_insides()
6308 {
6309     if( !insides_dirty ) {
6310         return;
6311     }
6312     insides_dirty = false;
6313     for( const vpart_reference &vp : get_all_parts() ) {
6314         const size_t p = vp.part_index();
6315         if( vp.part().removed ) {
6316             continue;
6317         }
6318         /* If there's no roof, or there is a roof but it's broken, it's outside.
6319          * (Use short-circuiting && so broken frames don't screw this up) */
6320         if( !( part_with_feature( p, "ROOF", true ) >= 0 && vp.part().is_available() ) ) {
6321             vp.part().inside = false;
6322             continue;
6323         }
6324 
6325         // inside if not otherwise
6326         parts[p].inside = true;
6327         // let's check four neighbor parts
6328         for( const point &offset : four_adjacent_offsets ) {
6329             point near_mount = parts[ p ].mount + offset;
6330             std::vector<int> parts_n3ar = parts_at_relative( near_mount, true );
6331             // if we aren't covered from sides, the roof at p won't save us
6332             bool cover = false;
6333             for( const int &j : parts_n3ar ) {
6334                 // another roof -- cover
6335                 if( part_flag( j, "ROOF" ) && parts[ j ].is_available() ) {
6336                     cover = true;
6337                     break;
6338                 } else if( part_flag( j, "OBSTACLE" ) && parts[ j ].is_available() ) {
6339                     // found an obstacle, like board or windshield or door
6340                     if( parts[j].inside || ( part_flag( j, "OPENABLE" ) && parts[j].open ) ) {
6341                         // door and it's open -- can't cover
6342                         continue;
6343                     }
6344                     cover = true;
6345                     break;
6346                 }
6347                 //Otherwise keep looking, there might be another part in that square
6348             }
6349             if( !cover ) {
6350                 vp.part().inside = false;
6351                 break;
6352             }
6353         }
6354     }
6355 }
6356 
is_inside() const6357 bool vpart_position::is_inside() const
6358 {
6359     // TODO: this is a bit of a hack as refresh_insides has side effects
6360     // this should be called elsewhere and not in a function that intends to just query
6361     // it's also a no-op if the insides are up to date.
6362     vehicle().refresh_insides();
6363     return vehicle().part( part_index() ).inside;
6364 }
6365 
unboard_all()6366 void vehicle::unboard_all()
6367 {
6368     map &here = get_map();
6369     std::vector<int> bp = boarded_parts();
6370     for( const int &i : bp ) {
6371         here.unboard_vehicle( global_part_pos3( i ) );
6372     }
6373 }
6374 
damage(int p,int dmg,damage_type type,bool aimed)6375 int vehicle::damage( int p, int dmg, damage_type type, bool aimed )
6376 {
6377     if( dmg < 1 ) {
6378         return dmg;
6379     }
6380 
6381     std::vector<int> pl = parts_at_relative( parts[p].mount, true );
6382     if( pl.empty() ) {
6383         // We ran out of non removed parts at this location already.
6384         return dmg;
6385     }
6386 
6387     if( !aimed ) {
6388         bool found_obs = false;
6389         for( const int &i : pl ) {
6390             if( part_flag( i, "OBSTACLE" ) &&
6391                 ( !part_flag( i, "OPENABLE" ) || !parts[i].open ) ) {
6392                 found_obs = true;
6393                 break;
6394             }
6395         }
6396 
6397         if( !found_obs ) { // not aimed at this tile and no obstacle here -- fly through
6398             return dmg;
6399         }
6400     }
6401 
6402     int target_part = part_info( p ).rotor_diameter() ? p : random_entry( pl );
6403 
6404     // door motor mechanism is protected by closed doors
6405     if( part_flag( target_part, "DOOR_MOTOR" ) ) {
6406         // find the most strong openable that is not open
6407         int strongest_door_part = -1;
6408         int strongest_door_durability = INT_MIN;
6409         for( int part : pl ) {
6410             if( part_flag( part, "OPENABLE" ) && !parts[part].open ) {
6411                 int door_durability = part_info( part ).durability;
6412                 if( door_durability > strongest_door_durability ) {
6413                     strongest_door_part = part;
6414                     strongest_door_durability = door_durability;
6415                 }
6416             }
6417         }
6418 
6419         // if we found a closed door, target it instead of the door_motor
6420         if( strongest_door_part != -1 ) {
6421             target_part = strongest_door_part;
6422         }
6423     }
6424 
6425     int damage_dealt;
6426 
6427     int armor_part = part_with_feature( p, "ARMOR", true );
6428     if( armor_part < 0 ) {
6429         // Not covered by armor -- damage part
6430         damage_dealt = damage_direct( target_part, dmg, type );
6431     } else {
6432         // Covered by armor -- hit both armor and part, but reduce damage by armor's reduction
6433         int protection = part_info( armor_part ).damage_reduction[ static_cast<int>( type )];
6434         // Parts on roof aren't protected
6435         bool overhead = part_flag( target_part, "ROOF" ) || part_info( target_part ).location == "on_roof";
6436         // Calling damage_direct may remove the damaged part
6437         // completely, therefore the other index (target_part) becomes
6438         // wrong if target_part > armor_part.
6439         // Damaging the part with the higher index first is save,
6440         // as removing a part only changes indices after the
6441         // removed part.
6442         if( armor_part < target_part ) {
6443             damage_direct( target_part, overhead ? dmg : dmg - protection, type );
6444             damage_dealt = damage_direct( armor_part, dmg, type );
6445         } else {
6446             damage_dealt = damage_direct( armor_part, dmg, type );
6447             damage_direct( target_part, overhead ? dmg : dmg - protection, type );
6448         }
6449     }
6450 
6451     return damage_dealt;
6452 }
6453 
damage_all(int dmg1,int dmg2,damage_type type,const point & impact)6454 void vehicle::damage_all( int dmg1, int dmg2, damage_type type, const point &impact )
6455 {
6456     if( dmg2 < dmg1 ) {
6457         std::swap( dmg1, dmg2 );
6458     }
6459 
6460     if( dmg1 < 1 ) {
6461         return;
6462     }
6463 
6464     for( const vpart_reference &vp : get_all_parts() ) {
6465         const size_t p = vp.part_index();
6466         int distance = 1 + square_dist( vp.mount(), impact );
6467         if( distance > 1 ) {
6468             int net_dmg = rng( dmg1, dmg2 ) / ( distance * distance );
6469             if( part_info( p ).location != part_location_structure ||
6470                 !part_info( p ).has_flag( "PROTRUSION" ) ) {
6471                 int shock_absorber = part_with_feature( p, "SHOCK_ABSORBER", true );
6472                 if( shock_absorber >= 0 ) {
6473                     net_dmg = std::max( 0, net_dmg - parts[ shock_absorber ].info().bonus );
6474                 }
6475             }
6476             damage_direct( p, net_dmg, type );
6477         }
6478     }
6479 }
6480 
6481 /**
6482  * Shifts all parts of the vehicle by the given amounts, and then shifts the
6483  * vehicle itself in the opposite direction. The end result is that the vehicle
6484  * appears to have not moved. Useful for re-zeroing a vehicle to ensure that a
6485  * (0, 0) part is always present.
6486  * @param delta How much to shift along each axis
6487  */
shift_parts(const point & delta)6488 void vehicle::shift_parts( const point &delta )
6489 {
6490     // Don't invalidate the active item cache's location!
6491     active_items.subtract_locations( delta );
6492     for( vehicle_part &elem : parts ) {
6493         elem.mount -= delta;
6494     }
6495 
6496     decltype( labels ) new_labels;
6497     for( const label &l : labels ) {
6498         new_labels.insert( label( l - delta, l.text ) );
6499     }
6500     labels = new_labels;
6501 
6502     decltype( loot_zones ) new_zones;
6503     for( auto const &z : loot_zones ) {
6504         new_zones.emplace( z.first - delta, z.second );
6505     }
6506     loot_zones = new_zones;
6507 
6508     pivot_anchor[0] -= delta;
6509     refresh();
6510     //Need to also update the map after this
6511     get_map().rebuild_vehicle_level_caches();
6512 }
6513 
6514 /**
6515  * Detect if the vehicle is currently missing a 0,0 part, and
6516  * adjust if necessary.
6517  * @return bool true if the shift was needed.
6518  */
shift_if_needed()6519 bool vehicle::shift_if_needed()
6520 {
6521     std::vector<int> vehicle_origin = parts_at_relative( point_zero, true );
6522     if( !vehicle_origin.empty() && !parts[ vehicle_origin[ 0 ] ].removed ) {
6523         // Shifting is not needed.
6524         return false;
6525     }
6526     //Find a frame, any frame, to shift to
6527     for( const vpart_reference &vp : get_all_parts() ) {
6528         if( vp.info().location == "structure"
6529             && !vp.has_feature( "PROTRUSION" )
6530             && !vp.part().removed ) {
6531             shift_parts( vp.mount() );
6532             refresh();
6533             return true;
6534         }
6535     }
6536     // There are only parts with PROTRUSION left, choose one of them.
6537     for( const vpart_reference &vp : get_all_parts() ) {
6538         if( !vp.part().removed ) {
6539             shift_parts( vp.mount() );
6540             refresh();
6541             return true;
6542         }
6543     }
6544     return false;
6545 }
6546 
break_off(int p,int dmg)6547 int vehicle::break_off( int p, int dmg )
6548 {
6549     /* Already-destroyed part - chance it could be torn off into pieces.
6550      * Chance increases with damage, and decreases with part max durability
6551      * (so lights, etc are easily removed; frames and plating not so much) */
6552     if( rng( 0, part_info( p ).durability / 10 ) >= dmg ) {
6553         return dmg;
6554     }
6555     map &here = get_map();
6556     const tripoint pos = global_part_pos3( p );
6557     const auto scatter_parts = [&]( const vehicle_part & pt ) {
6558         for( const item &piece : pt.pieces_for_broken_part() ) {
6559             // inside the loop, so each piece goes to a different place
6560             // TODO: this may spawn items behind a wall
6561             const tripoint where = random_entry( here.points_in_radius( pos, SCATTER_DISTANCE ) );
6562             // TODO: balance audit, ensure that less pieces are generated than one would need
6563             // to build the component (smash a vehicle box that took 10 lumps of steel,
6564             // find 12 steel lumps scattered after atom-smashing it with a tree trunk)
6565             if( !magic ) {
6566                 here.add_item_or_charges( where, piece );
6567             }
6568         }
6569     };
6570     if( part_info( p ).location == part_location_structure ) {
6571         // For structural parts, remove other parts first
6572         std::vector<int> parts_in_square = parts_at_relative( parts[p].mount, true );
6573         for( int index = parts_in_square.size() - 1; index >= 0; index-- ) {
6574             // Ignore the frame being destroyed
6575             if( parts_in_square[index] == p ) {
6576                 continue;
6577             }
6578 
6579             if( parts[ parts_in_square[ index ] ].is_broken() ) {
6580                 // Tearing off a broken part - break it up
6581                 add_msg_if_player_sees( pos, m_bad, _( "The %s's %s breaks into pieces!" ), name,
6582                                         parts[ parts_in_square[ index ] ].name() );
6583                 scatter_parts( parts[parts_in_square[index]] );
6584             } else {
6585                 // Intact (but possibly damaged) part - remove it in one piece
6586                 add_msg_if_player_sees( pos, m_bad, _( "The %1$s's %2$s is torn off!" ), name,
6587                                         parts[ parts_in_square[ index ] ].name() );
6588                 if( !magic ) {
6589                     item part_as_item = parts[parts_in_square[index]].properties_to_item();
6590                     here.add_item_or_charges( pos, part_as_item );
6591                 }
6592             }
6593             remove_part( parts_in_square[index] );
6594         }
6595         // After clearing the frame, remove it.
6596         add_msg_if_player_sees( pos, m_bad, _( "The %1$s's %2$s is destroyed!" ), name, parts[ p ].name() );
6597         scatter_parts( parts[p] );
6598         remove_part( p );
6599         find_and_split_vehicles( p );
6600     } else {
6601         //Just break it off
6602         add_msg_if_player_sees( pos, m_bad, _( "The %1$s's %2$s is destroyed!" ), name, parts[ p ].name() );
6603 
6604         scatter_parts( parts[p] );
6605         const point position = parts[p].mount;
6606         remove_part( p );
6607 
6608         // remove parts for which required flags are not present anymore
6609         if( !part_info( p ).get_flags().empty() ) {
6610             const std::vector<int> parts_here = parts_at_relative( position, false );
6611             for( const auto &part : parts_here ) {
6612                 bool remove = false;
6613                 for( const std::string &flag : part_info( part ).get_flags() ) {
6614                     if( !json_flag::get( flag ).requires_flag().empty() ) {
6615                         remove = true;
6616                         for( const auto &elem : parts_here ) {
6617                             if( part_info( elem ).has_flag( json_flag::get( flag ).requires_flag() ) ) {
6618                                 remove = false;
6619                                 continue;
6620                             }
6621                         }
6622                     }
6623                 }
6624                 if( remove ) {
6625                     item part_as_item = parts[part].properties_to_item();
6626                     here.add_item_or_charges( pos, part_as_item );
6627                     remove_part( part );
6628                 }
6629             }
6630         }
6631     }
6632 
6633     return dmg;
6634 }
6635 
explode_fuel(int p,damage_type type)6636 bool vehicle::explode_fuel( int p, damage_type type )
6637 {
6638     const itype_id &ft = part_info( p ).fuel_type;
6639     item fuel = item( ft );
6640     if( !fuel.has_explosion_data() ) {
6641         return false;
6642     }
6643     const fuel_explosion_data &data = fuel.get_explosion_data();
6644 
6645     if( parts[ p ].is_broken() ) {
6646         leak_fuel( parts[ p ] );
6647     }
6648 
6649     int explosion_chance = type == damage_type::HEAT ? data.explosion_chance_hot :
6650                            data.explosion_chance_cold;
6651     if( one_in( explosion_chance ) ) {
6652         get_event_bus().send<event_type::fuel_tank_explodes>( name );
6653         const int pow = 120 * ( 1 - std::exp( data.explosion_factor / -5000 *
6654                                               ( parts[p].ammo_remaining() * data.fuel_size_factor ) ) );
6655         //debugmsg( "damage check dmg=%d pow=%d amount=%d", dmg, pow, parts[p].amount );
6656 
6657         explosion_handler::explosion( global_part_pos3( p ), pow, 0.7, data.fiery_explosion );
6658         mod_hp( parts[p], 0 - parts[ p ].hp(), damage_type::HEAT );
6659         parts[p].ammo_unset();
6660     }
6661 
6662     return true;
6663 }
6664 
damage_direct(int p,int dmg,damage_type type)6665 int vehicle::damage_direct( int p, int dmg, damage_type type )
6666 {
6667     // Make sure p is within range and hasn't been removed already
6668     if( ( static_cast<size_t>( p ) >= parts.size() ) || parts[p].removed ) {
6669         return dmg;
6670     }
6671     // If auto-driving and damage happens, bail out
6672     if( is_autodriving ) {
6673         stop_autodriving();
6674     }
6675     map &here = get_map();
6676     here.set_memory_seen_cache_dirty( global_part_pos3( p ) );
6677     if( parts[p].is_broken() ) {
6678         return break_off( p, dmg );
6679     }
6680 
6681     int tsh = std::min( 20, part_info( p ).durability / 10 );
6682     if( dmg < tsh && type != damage_type::PURE ) {
6683         if( type == damage_type::HEAT && parts[p].is_fuel_store() ) {
6684             explode_fuel( p, type );
6685         }
6686 
6687         return dmg;
6688     }
6689 
6690     dmg -= std::min<int>( dmg, part_info( p ).damage_reduction[ static_cast<int>( type ) ] );
6691     int dres = dmg - parts[p].hp();
6692     if( mod_hp( parts[ p ], 0 - dmg, type ) ) {
6693         if( is_flyable() && !rotors.empty() && !parts[p].has_flag( VPFLAG_SIMPLE_PART ) ) {
6694             // If we break a part, we can no longer fly the vehicle.
6695             set_flyable( false );
6696         }
6697 
6698         insides_dirty = true;
6699         pivot_dirty = true;
6700 
6701         // destroyed parts lose any contained fuels, battery charges or ammo
6702         leak_fuel( parts [ p ] );
6703 
6704         for( const item &e : parts[p].items ) {
6705             here.add_item_or_charges( global_part_pos3( p ), e );
6706         }
6707         parts[p].items.clear();
6708 
6709         invalidate_mass();
6710         coeff_air_changed = true;
6711 
6712         // refresh cache in case the broken part has changed the status
6713         refresh();
6714     }
6715 
6716     if( parts[p].is_fuel_store() ) {
6717         explode_fuel( p, type );
6718     } else if( parts[ p ].is_broken() && part_flag( p, "UNMOUNT_ON_DAMAGE" ) ) {
6719         here.spawn_item( global_part_pos3( p ), part_info( p ).base_item, 1, 0, calendar::turn,
6720                          part_info( p ).base_item.obj().damage_max() - 1 );
6721         monster *mon = get_monster( p );
6722         if( mon != nullptr && mon->has_effect( effect_harnessed ) ) {
6723             mon->remove_effect( effect_harnessed );
6724         }
6725         if( part_flag( p, "TOW_CABLE" ) ) {
6726             invalidate_towing( true );
6727         } else {
6728             remove_part( p );
6729         }
6730     }
6731 
6732     return std::max( dres, 0 );
6733 }
6734 
leak_fuel(vehicle_part & pt)6735 void vehicle::leak_fuel( vehicle_part &pt )
6736 {
6737     // only liquid fuels from non-empty tanks can leak out onto map tiles
6738     if( !pt.is_tank() || pt.ammo_remaining() <= 0 ) {
6739         return;
6740     }
6741 
6742     map &here = get_map();
6743     // leak in random directions but prefer closest tiles and avoid walls or other obstacles
6744     std::vector<tripoint> tiles = closest_points_first( global_part_pos3( pt ), 1 );
6745     tiles.erase( std::remove_if( tiles.begin(), tiles.end(), [&here]( const tripoint & e ) {
6746         return !here.passable( e );
6747     } ), tiles.end() );
6748 
6749     // leak up to 1/3 of remaining fuel per iteration and continue until the part is empty
6750     const itype *fuel = item::find_type( pt.ammo_current() );
6751     while( !tiles.empty() && pt.ammo_remaining() ) {
6752         int qty = pt.ammo_consume( rng( 0, std::max( pt.ammo_remaining() / 3, 1 ) ),
6753                                    global_part_pos3( pt ) );
6754         if( qty > 0 ) {
6755             here.add_item_or_charges( random_entry( tiles ), item( fuel, calendar::turn, qty ) );
6756         }
6757     }
6758 
6759     pt.ammo_unset();
6760 }
6761 
fuels_left() const6762 std::map<itype_id, int> vehicle::fuels_left() const
6763 {
6764     std::map<itype_id, int> result;
6765     for( const vehicle_part &p : parts ) {
6766         if( p.is_fuel_store() && !p.ammo_current().is_null() ) {
6767             result[ p.ammo_current() ] += p.ammo_remaining();
6768         }
6769     }
6770     return result;
6771 }
6772 
is_foldable() const6773 bool vehicle::is_foldable() const
6774 {
6775     for( const vpart_reference &vp : get_all_parts() ) {
6776         if( !vp.has_feature( "FOLDABLE" ) ) {
6777             return false;
6778         }
6779     }
6780     return true;
6781 }
6782 
restore(const std::string & data)6783 bool vehicle::restore( const std::string &data )
6784 {
6785     std::istringstream veh_data( data );
6786     try {
6787         JsonIn json( veh_data );
6788         parts.clear();
6789         json.read( parts );
6790     } catch( const JsonError &e ) {
6791         debugmsg( "Error restoring vehicle: %s", e.c_str() );
6792         return false;
6793     }
6794     refresh();
6795     face.init( 0_degrees );
6796     turn_dir = 0_degrees;
6797     turn( 0_degrees );
6798     precalc_mounts( 0, pivot_rotation[0], pivot_anchor[0] );
6799     precalc_mounts( 1, pivot_rotation[1], pivot_anchor[1] );
6800     last_update = calendar::turn;
6801     return true;
6802 }
6803 
get_points(const bool force_refresh) const6804 const std::set<tripoint> &vehicle::get_points( const bool force_refresh ) const
6805 {
6806     if( force_refresh || occupied_cache_pos != global_pos3() ||
6807         occupied_cache_direction != face.dir() ) {
6808         occupied_cache_pos = global_pos3();
6809         occupied_cache_direction = face.dir();
6810         occupied_points.clear();
6811         for( const std::pair<const point, std::vector<int>> &part_location : relative_parts ) {
6812             occupied_points.insert( global_part_pos3( part_location.second.front() ) );
6813         }
6814     }
6815 
6816     return occupied_points;
6817 }
6818 
use_charges(const vpart_position & vp,const itype_id & type,int & quantity,const std::function<bool (const item &)> & filter)6819 std::list<item> vehicle::use_charges( const vpart_position &vp, const itype_id &type,
6820                                       int &quantity,
6821                                       const std::function<bool( const item & )> &filter )
6822 {
6823     std::list<item> ret;
6824     // HACK: water_faucet pseudo tool gives access to liquids in tanks
6825     const itype_id veh_tool_type = type.obj().phase > phase_id::SOLID
6826                                    ? itype_id( "water_faucet" )
6827                                    : type;
6828     const cata::optional<vpart_reference> tool_vp = vp.part_with_tool( veh_tool_type );
6829     const cata::optional<vpart_reference> cargo_vp = vp.part_with_feature( "CARGO", true );
6830 
6831     const auto tool_wants_battery = []( const itype_id & type ) {
6832         item tool( type, calendar::turn_zero ), mag( tool.magazine_default() );
6833         mag.contents.clear_items();
6834 
6835         return tool.contents.insert_item( mag, item_pocket::pocket_type::MAGAZINE_WELL ).success() &&
6836                tool.ammo_capacity( ammotype( "battery" ) ) > 0;
6837     };
6838 
6839     if( tool_vp ) { // handle vehicle tools
6840         itype_id fuel_type = tool_wants_battery( type ) ? itype_battery : type;
6841         item tmp( type, calendar::turn_zero ); // TODO: add a sane birthday arg
6842         // TODO: Handle water poison when crafting starts respecting it
6843         tmp.charges = tool_vp->vehicle().drain( fuel_type, quantity );
6844         quantity -= tmp.charges;
6845         ret.push_back( tmp );
6846 
6847         if( quantity == 0 ) {
6848             return ret;
6849         }
6850     }
6851 
6852     if( cargo_vp ) {
6853         vehicle_stack veh_stack = get_items( cargo_vp->part_index() );
6854         std::list<item> tmp = veh_stack.use_charges( type, quantity, vp.pos(), filter );
6855         ret.splice( ret.end(), tmp );
6856         if( quantity <= 0 ) {
6857             return ret;
6858         }
6859     }
6860 
6861     return ret;
6862 }
6863 
part() const6864 vehicle_part &vpart_reference::part() const
6865 {
6866     cata_assert( part_index() < static_cast<size_t>( vehicle().part_count() ) );
6867     return vehicle().part( part_index() );
6868 }
6869 
info() const6870 const vpart_info &vpart_reference::info() const
6871 {
6872     return part().info();
6873 }
6874 
get_passenger() const6875 player *vpart_reference::get_passenger() const
6876 {
6877     return vehicle().get_passenger( part_index() );
6878 }
6879 
mount() const6880 point vpart_position::mount() const
6881 {
6882     return vehicle().part( part_index() ).mount;
6883 }
6884 
pos() const6885 tripoint vpart_position::pos() const
6886 {
6887     return vehicle().global_part_pos3( part_index() );
6888 }
6889 
has_feature(const std::string & f) const6890 bool vpart_reference::has_feature( const std::string &f ) const
6891 {
6892     return info().has_flag( f );
6893 }
6894 
has_feature(const vpart_bitflags f) const6895 bool vpart_reference::has_feature( const vpart_bitflags f ) const
6896 {
6897     return info().has_flag( f );
6898 }
6899 
is_sm_tile_over_water(const tripoint & real_global_pos)6900 static bool is_sm_tile_over_water( const tripoint &real_global_pos )
6901 {
6902 
6903     const tripoint smp = ms_to_sm_copy( real_global_pos );
6904     const point p( modulo( real_global_pos.x, SEEX ), modulo( real_global_pos.y, SEEY ) );
6905     const submap *sm = MAPBUFFER.lookup_submap( smp );
6906     if( sm == nullptr ) {
6907         debugmsg( "is_sm_tile_outside(): couldn't find submap %d,%d,%d", smp.x, smp.y, smp.z );
6908         return false;
6909     }
6910 
6911     if( p.x < 0 || p.x >= SEEX || p.y < 0 || p.y >= SEEY ) {
6912         debugmsg( "err %d,%d", p.x, p.y );
6913         return false;
6914     }
6915 
6916     return ( sm->get_ter( p ).obj().has_flag( TFLAG_CURRENT ) ||
6917              sm->get_furn( p ).obj().has_flag( TFLAG_CURRENT ) );
6918 }
6919 
is_sm_tile_outside(const tripoint & real_global_pos)6920 static bool is_sm_tile_outside( const tripoint &real_global_pos )
6921 {
6922 
6923     const tripoint smp = ms_to_sm_copy( real_global_pos );
6924     const point p( modulo( real_global_pos.x, SEEX ), modulo( real_global_pos.y, SEEY ) );
6925     const submap *sm = MAPBUFFER.lookup_submap( smp );
6926     if( sm == nullptr ) {
6927         debugmsg( "is_sm_tile_outside(): couldn't find submap %d,%d,%d", smp.x, smp.y, smp.z );
6928         return false;
6929     }
6930 
6931     if( p.x < 0 || p.x >= SEEX || p.y < 0 || p.y >= SEEY ) {
6932         debugmsg( "err %d,%d", p.x, p.y );
6933         return false;
6934     }
6935 
6936     return !( sm->get_ter( p ).obj().has_flag( TFLAG_INDOORS ) ||
6937               sm->get_furn( p ).obj().has_flag( TFLAG_INDOORS ) );
6938 }
6939 
update_time(const time_point & update_to)6940 void vehicle::update_time( const time_point &update_to )
6941 {
6942     double muffle;
6943     int exhaust_part;
6944     std::tie( exhaust_part, muffle ) = get_exhaust_part();
6945 
6946     map &here = get_map();
6947     // Parts emitting fields
6948     for( int idx : emitters ) {
6949         const vehicle_part &pt = parts[idx];
6950         if( pt.is_unavailable() || !pt.enabled ) {
6951             continue;
6952         }
6953         for( const emit_id &e : pt.info().emissions ) {
6954             here.emit_field( global_part_pos3( pt ), e );
6955         }
6956         for( const emit_id &e : pt.info().exhaust ) {
6957             if( exhaust_part == -1 ) {
6958                 here.emit_field( global_part_pos3( pt ), e );
6959             } else {
6960                 here.emit_field( exhaust_dest( exhaust_part ), e );
6961             }
6962         }
6963         discharge_battery( pt.info().epower );
6964     }
6965 
6966     if( sm_pos.z < 0 ) {
6967         return;
6968     }
6969 
6970     const time_point update_from = last_update;
6971     if( update_to < update_from ) {
6972         // Special case going backwards in time - that happens
6973         last_update = update_to;
6974         return;
6975     }
6976 
6977     if( update_to >= update_from && update_to - update_from < 1_minutes ) {
6978         // We don't need to check every turn
6979         return;
6980     }
6981     time_duration elapsed = update_to - last_update;
6982     last_update = update_to;
6983 
6984     // Weather stuff, only for z-levels >= 0
6985     // TODO: Have it wash cars from blood?
6986     if( funnels.empty() && solar_panels.empty() && wind_turbines.empty() && water_wheels.empty() ) {
6987         return;
6988     }
6989     // Get one weather data set per vehicle, they don't differ much across vehicle area
6990     const weather_sum accum_weather = sum_conditions( update_from, update_to,
6991                                       here.getabs( global_pos3() ) );
6992     // make some reference objects to use to check for reload
6993     const item water( "water" );
6994     const item water_clean( "water_clean" );
6995 
6996     for( int idx : funnels ) {
6997         const vehicle_part &pt = parts[idx];
6998 
6999         // we need an unbroken funnel mounted on the exterior of the vehicle
7000         if( pt.is_unavailable() || !is_sm_tile_outside( here.getabs( global_part_pos3( pt ) ) ) ) {
7001             continue;
7002         }
7003 
7004         // we need an empty tank (or one already containing water) below the funnel
7005         auto tank = std::find_if( parts.begin(), parts.end(), [&]( const vehicle_part & e ) {
7006             return pt.mount == e.mount && e.is_tank() &&
7007                    ( e.can_reload( water ) || e.can_reload( water_clean ) );
7008         } );
7009 
7010         if( tank == parts.end() ) {
7011             continue;
7012         }
7013 
7014         double area = std::pow( pt.info().size / units::legacy_volume_factor, 2 ) * M_PI;
7015         int qty = roll_remainder( funnel_charges_per_turn( area, accum_weather.rain_amount ) );
7016         int c_qty = qty + ( tank->can_reload( water_clean ) ?  tank->ammo_remaining() : 0 );
7017         int cost_to_purify = c_qty * item::find_type( itype_water_purifier )->charges_to_use();
7018 
7019         if( qty > 0 ) {
7020             const cata::optional<vpart_reference> vp_purifier = vpart_position( *this, idx )
7021                     .part_with_tool( itype_water_purifier );
7022 
7023             if( vp_purifier && ( fuel_left( itype_battery, true ) > cost_to_purify ) ) {
7024                 tank->ammo_set( itype_water_clean, c_qty );
7025                 discharge_battery( cost_to_purify );
7026             } else {
7027                 tank->ammo_set( itype_water, tank->ammo_remaining() + qty );
7028             }
7029             invalidate_mass();
7030         }
7031     }
7032 
7033     if( !solar_panels.empty() ) {
7034         int epower_w = 0;
7035         for( int part : solar_panels ) {
7036             if( parts[ part ].is_unavailable() ) {
7037                 continue;
7038             }
7039 
7040             if( !is_sm_tile_outside( here.getabs( global_part_pos3( part ) ) ) ) {
7041                 continue;
7042             }
7043 
7044             epower_w += part_epower_w( part );
7045         }
7046         double intensity = accum_weather.sunlight / default_daylight_level() / to_turns<double>( elapsed );
7047         int energy_bat = power_to_energy_bat( epower_w * intensity, elapsed );
7048         if( energy_bat > 0 ) {
7049             add_msg_debug( "%s got %d kJ energy from solar panels", name, energy_bat );
7050             charge_battery( energy_bat );
7051         }
7052     }
7053     if( !wind_turbines.empty() ) {
7054         // TODO: use accum_weather wind data to backfill wind turbine
7055         // generation capacity.
7056         int epower_w = total_wind_epower_w();
7057         int energy_bat = power_to_energy_bat( epower_w, elapsed );
7058         if( energy_bat > 0 ) {
7059             add_msg_debug( "%s got %d kJ energy from wind turbines", name, energy_bat );
7060             charge_battery( energy_bat );
7061         }
7062     }
7063     if( !water_wheels.empty() ) {
7064         int epower_w = total_water_wheel_epower_w();
7065         int energy_bat = power_to_energy_bat( epower_w, elapsed );
7066         if( energy_bat > 0 ) {
7067             add_msg_debug( "%s got %d kJ energy from water wheels", name, energy_bat );
7068             charge_battery( energy_bat );
7069         }
7070     }
7071 }
7072 
invalidate_mass()7073 void vehicle::invalidate_mass()
7074 {
7075     mass_dirty = true;
7076     mass_center_precalc_dirty = true;
7077     mass_center_no_precalc_dirty = true;
7078     // Anything that affects mass will also affect the pivot
7079     pivot_dirty = true;
7080     coeff_rolling_dirty = true;
7081     coeff_water_dirty = true;
7082 }
7083 
refresh_mass() const7084 void vehicle::refresh_mass() const
7085 {
7086     calc_mass_center( true );
7087 }
7088 
calc_mass_center(bool use_precalc) const7089 void vehicle::calc_mass_center( bool use_precalc ) const
7090 {
7091     units::quantity<float, units::mass::unit_type> xf;
7092     units::quantity<float, units::mass::unit_type> yf;
7093     units::mass m_total = 0_gram;
7094     for( const vpart_reference &vp : get_all_parts() ) {
7095         const size_t i = vp.part_index();
7096         if( vp.part().removed ) {
7097             continue;
7098         }
7099 
7100         units::mass m_part = 0_gram;
7101         units::mass m_part_items = 0_gram;
7102         m_part += vp.part().base.weight();
7103         for( const item &j : get_items( i ) ) {
7104             m_part_items += j.weight();
7105         }
7106         if( vp.part().info().cargo_weight_modifier != 100 ) {
7107             m_part_items *= vp.part().info().cargo_weight_modifier / 100;
7108         }
7109         m_part += m_part_items;
7110 
7111         if( vp.has_feature( VPFLAG_BOARDABLE ) && vp.part().has_flag( vehicle_part::passenger_flag ) ) {
7112             const player *p = get_passenger( i );
7113             // Sometimes flag is wrongly set, don't crash!
7114             m_part += p != nullptr ? p->get_weight() : 0_gram;
7115         }
7116 
7117         if( use_precalc ) {
7118             xf += vp.part().precalc[0].x * m_part;
7119             yf += vp.part().precalc[0].y * m_part;
7120         } else {
7121             xf += vp.mount().x * m_part;
7122             yf += vp.mount().y * m_part;
7123         }
7124 
7125         m_total += m_part;
7126     }
7127 
7128     mass_cache = m_total;
7129     mass_dirty = false;
7130 
7131     const float x = xf / mass_cache;
7132     const float y = yf / mass_cache;
7133     if( use_precalc ) {
7134         mass_center_precalc.x = std::round( x );
7135         mass_center_precalc.y = std::round( y );
7136         mass_center_precalc_dirty = false;
7137     } else {
7138         mass_center_no_precalc.x = std::round( x );
7139         mass_center_no_precalc.y = std::round( y );
7140         mass_center_no_precalc_dirty = false;
7141     }
7142 }
7143 
get_bounding_box()7144 bounding_box vehicle::get_bounding_box()
7145 {
7146     int min_x = INT_MAX;
7147     int max_x = INT_MIN;
7148     int min_y = INT_MAX;
7149     int max_y = INT_MIN;
7150 
7151     face.init( turn_dir );
7152 
7153     precalc_mounts( 0, turn_dir, point() );
7154 
7155     int i_use = 0;
7156     for( const tripoint &p : get_points( true ) ) {
7157         const point pt = parts[part_at( p.xy() )].precalc[i_use].xy();
7158         if( pt.x < min_x ) {
7159             min_x = pt.x;
7160         }
7161         if( pt.x > max_x ) {
7162             max_x = pt.x;
7163         }
7164         if( pt.y < min_y ) {
7165             min_y = pt.y;
7166         }
7167         if( pt.y > max_y ) {
7168             max_y = pt.y;
7169         }
7170     }
7171     bounding_box b;
7172     b.p1 = point( min_x, min_y );
7173     b.p2 = point( max_x, max_y );
7174     return b;
7175 }
7176 
get_all_parts() const7177 vehicle_part_range vehicle::get_all_parts() const
7178 {
7179     return vehicle_part_range( const_cast<vehicle &>( *this ) );
7180 }
7181 
part_count() const7182 int vehicle::part_count() const
7183 {
7184     return static_cast<int>( parts.size() );
7185 }
7186 
part(int part_num)7187 vehicle_part &vehicle::part( int part_num )
7188 {
7189     return parts[part_num];
7190 }
7191 
cpart(int part_num) const7192 const vehicle_part &vehicle::cpart( int part_num ) const
7193 {
7194     return const_cast<vehicle_part &>( parts[part_num] );
7195 }
7196 
valid_part(int part_num) const7197 bool vehicle::valid_part( int part_num ) const
7198 {
7199     return part_num >= 0 && part_num < static_cast<int>( parts.size() );
7200 }
7201 
force_erase_part(int part_num)7202 void vehicle::force_erase_part( int part_num )
7203 {
7204     parts.erase( parts.begin() + part_num );
7205 }
7206 
advance_precalc_mounts(const point & new_pos,const tripoint & src,const tripoint & dp,int ramp_offset,const bool adjust_pos,std::set<int> parts_to_move)7207 std::set<int> vehicle::advance_precalc_mounts( const point &new_pos, const tripoint &src,
7208         const tripoint &dp, int ramp_offset, const bool adjust_pos,
7209         std::set<int> parts_to_move )
7210 {
7211     map &here = get_map();
7212     std::set<int> smzs;
7213     // when a vehicle part enters the low end of a down ramp, or the high end of an up ramp,
7214     // it immediately translates down or up a z-level, respectively, ending up on the low
7215     // end of an up ramp or high end of a down ramp, respectively.  The two ends are set
7216     // past each other, like so:
7217     // (side view)  z+1   Rdh RDl
7218     //              z+0   RUh Rul
7219     // A vehicle moving left to right on z+1 drives down to z+0 by entering the ramp down low end.
7220     // A vehicle moving right to left on z+0 drives up to z+1 by entering the ramp up high end.
7221     // A vehicle moving left to right on z+0 should ideally collide into a wall before entering
7222     //   the ramp up high end, but even if it does, it briefly transitions to z+1 before returning
7223     //   to z0 by entering the ramp down low end.
7224     // A vehicle moving right to left on z+1 drives down to z+0 by entering the ramp down low end,
7225     //   then immediately returns to z+1 by entering the ramp up high end.
7226     // When a vehicle's pivot point transitions a z-level via a ramp, all other pre-calc points
7227     // make the opposite transition, so that points that were above an ascending pivot point are
7228     // now level with it, and parts that were level with an ascending pivot point are now below
7229     // it.
7230     // parts that enter the translation portion of a ramp on the same displacement as the
7231     // pivot point stay at the same relative z to the pivot point, as the ramp_offset adjustments
7232     // cancel out.
7233     // if a vehicle manages move partially up or down a ramp and then veers off course, it
7234     // can get split across the z-levels and continue moving, enough though large parts of the
7235     // vehicle are unsupported.  In that case, move the unsupported parts down until they are
7236     // supported.
7237     int index = -1;
7238     for( vehicle_part &prt : parts ) {
7239         index += 1;
7240         here.clear_vehicle_point_from_cache( this, src + prt.precalc[0] );
7241         // no parts means this is a normal horizontal or vertical move
7242         if( parts_to_move.empty() ) {
7243             prt.precalc[0] = prt.precalc[1];
7244             // partial part movement means we're zero-ing out after missing a ramp
7245         } else if( adjust_pos && parts_to_move.find( index ) == parts_to_move.end() ) {
7246             prt.precalc[0].z -= dp.z;
7247         } else if( !adjust_pos &&  parts_to_move.find( index ) != parts_to_move.end() ) {
7248             prt.precalc[0].z += dp.z;
7249         }
7250         if( here.has_flag( TFLAG_RAMP_UP, src + dp + prt.precalc[0] ) ) {
7251             prt.precalc[0].z += 1;
7252         } else if( here.has_flag( TFLAG_RAMP_DOWN, src + dp + prt.precalc[0] ) ) {
7253             prt.precalc[0].z -= 1;
7254         }
7255         prt.precalc[0].z -= ramp_offset;
7256         prt.precalc[1].z = prt.precalc[0].z;
7257         smzs.insert( prt.precalc[0].z );
7258     }
7259     if( adjust_pos ) {
7260         if( parts_to_move.empty() ) {
7261             pivot_anchor[0] = pivot_anchor[1];
7262             pivot_rotation[0] = pivot_rotation[1];
7263         }
7264         pos = new_pos;
7265     }
7266     occupied_cache_pos = { -1, -1, -1 };
7267     return smzs;
7268 }
7269 
refresh_zones()7270 bool vehicle::refresh_zones()
7271 {
7272     if( zones_dirty ) {
7273         map &here = get_map();
7274         decltype( loot_zones ) new_zones;
7275         for( auto const &z : loot_zones ) {
7276             zone_data zone = z.second;
7277             //Get the global position of the first cargo part at the relative coordinate
7278 
7279             const int part_idx = part_with_feature( z.first, "CARGO", false );
7280             if( part_idx == -1 ) {
7281                 debugmsg( "Could not find cargo part at %d,%d on vehicle %s for loot zone.  Removing loot zone.",
7282                           z.first.x, z.first.y, this->name );
7283 
7284                 // If this loot zone refers to a part that no longer exists at this location, then its unattached somehow.
7285                 // By continuing here and not adding to new_zones, we effectively remove it
7286                 continue;
7287             }
7288             tripoint zone_pos = global_part_pos3( part_idx );
7289             zone_pos = here.getabs( zone_pos );
7290             //Set the position of the zone to that part
7291             zone.set_position( std::pair<tripoint, tripoint>( zone_pos, zone_pos ), false );
7292             new_zones.emplace( z.first, zone );
7293         }
7294         loot_zones = new_zones;
7295         zones_dirty = false;
7296         return true;
7297     }
7298     return false;
7299 }
7300 
get_exhaust_part() const7301 std::pair<int, double> vehicle::get_exhaust_part() const
7302 {
7303     double muffle = 1.0;
7304     double m = 0.0;
7305     int exhaust_part = -1;
7306     for( int part : mufflers ) {
7307         const vehicle_part &vp = parts[ part ];
7308         m = 1.0 - ( 1.0 - vp.info().bonus / 100.0 ) * vp.health_percent();
7309         if( m < muffle ) {
7310             muffle = m;
7311             exhaust_part = part;
7312         }
7313     }
7314     return std::make_pair( exhaust_part, muffle );
7315 }
7316 
exhaust_dest(int part) const7317 tripoint vehicle::exhaust_dest( int part ) const
7318 {
7319     point p = parts[part].mount;
7320     // Move back from engine/muffler until we find an open space
7321     while( relative_parts.find( p ) != relative_parts.end() ) {
7322         p.x += ( velocity < 0 ? 1 : -1 );
7323     }
7324     point q = coord_translate( p );
7325     return global_pos3() + tripoint( q, 0 );
7326 }
7327 
7328 template<>
matches(const size_t part) const7329 bool vehicle_part_with_feature_range<std::string>::matches( const size_t part ) const
7330 {
7331     const vehicle_part &vp = this->vehicle().part( part );
7332     return vp.info().has_flag( feature_ ) &&
7333            !vp.removed &&
7334            ( !( part_status_flag::working & required_ ) || !vp.is_broken() ) &&
7335            ( !( part_status_flag::available & required_ ) || vp.is_available() ) &&
7336            ( !( part_status_flag::enabled & required_ ) || vp.enabled );
7337 }
7338 
7339 template<>
matches(const size_t part) const7340 bool vehicle_part_with_feature_range<vpart_bitflags>::matches( const size_t part ) const
7341 {
7342     const vehicle_part &vp = this->vehicle().part( part );
7343     return vp.info().has_flag( feature_ ) &&
7344            !vp.removed &&
7345            ( !( part_status_flag::working & required_ ) || !vp.is_broken() ) &&
7346            ( !( part_status_flag::available & required_ ) || vp.is_available() ) &&
7347            ( !( part_status_flag::enabled & required_ ) || vp.enabled );
7348 }
7349