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 == ⁢
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