1 #include "LoadSaveVehicleType.h"
2 #include "SaveLoadGame.h"
3 #include "Soldier_Find.h"
4 #include "Vehicles.h"
5 #include "Strategic_Pathing.h"
6 #include "Assignments.h"
7 #include "Strategic_Movement.h"
8 #include "Squads.h"
9 #include "Map_Screen_Helicopter.h"
10 #include "Game_Clock.h"
11 #include "Overhead.h"
12 #include "Soldier_Profile.h"
13 #include "Sound_Control.h"
14 #include "SoundMan.h"
15 #include "Soldier_Add.h"
16 #include "Strategic.h"
17 #include "WorldDef.h"
18 #include "Tile_Animation.h"
19 #include "Isometric_Utils.h"
20 #include "Interface.h"
21 #include "Random.h"
22 #include "Text.h"
23 #include "Explosion_Control.h"
24 #include "Soldier_Create.h"
25 #include "StrategicMap.h"
26 #include "Campaign_Types.h"
27 #include "Sys_Globals.h"
28 #include "Map_Screen_Interface.h"
29 #include "JAScreens.h"
30 #include "Quests.h"
31 #include "Tactical_Save.h"
32 #include "Soldier_Macros.h"
33 #include "OppList.h"
34 #include "Soldier_Ani.h"
35 #include "MemMan.h"
36 #include "Debug.h"
37 #include "ScreenIDs.h"
38 #include "FileMan.h"
39
40 #include "ContentManager.h"
41 #include "GameInstance.h"
42 #include "ShippingDestinationModel.h"
43
44 #include <stdexcept>
45 #include <vector>
46
47
48 INT8 gubVehicleMovementGroups[ MAX_VEHICLES ];
49
50 // the list of vehicle slots
51 std::vector<VEHICLETYPE> pVehicleList;
52
53
54 //ATE: These arrays below should all be in a large LUT which contains
55 // static info for each vehicle....
56
57
58 struct VehicleTypeInfo
59 {
60 SoundID enter_sound;
61 SoundID move_sound;
62 ProfileID profile;
63 UINT8 movement_type;
64 UINT16 armour_type;
65 UINT8 seats;
66 };
67
68 static const VehicleTypeInfo g_vehicle_type_info[] =
69 {
70 { S_VECH1_INTO, S_VECH1_MOVE, PROF_ELDERODO, CAR, KEVLAR_VEST, 6 }, // Eldorado
71 { S_VECH1_INTO, S_VECH1_MOVE, PROF_HUMMER, CAR, SPECTRA_VEST, 6 }, // Hummer
72 { S_VECH1_INTO, S_VECH1_MOVE, PROF_ICECREAM, CAR, KEVLAR_VEST, 6 }, // Ice cream truck
73 { S_VECH1_INTO, S_VECH1_MOVE, NPC164, CAR, KEVLAR_VEST, 6 }, // Jeep
74 { S_VECH1_INTO, S_VECH1_MOVE, NPC164, CAR, SPECTRA_VEST, 6 }, // Tank
75 { S_VECH1_INTO, S_VECH1_MOVE, PROF_HELICOPTER, AIR, KEVLAR_VEST, 6 } // Helicopter
76 };
77
78
79 // Loop through and create a few soldier squad ID's for vehicles ( max # 3 )
InitVehicles(void)80 void InitVehicles(void)
81 {
82 INT32 cnt;
83 for( cnt = 0; cnt < MAX_VEHICLES; cnt++ )
84 {
85 // create mvt groups
86 GROUP* const g = CreateNewVehicleGroupDepartingFromSector(1, 1);
87 g->fPersistant = TRUE;
88 gubVehicleMovementGroups[cnt] = g->ubGroupID;
89 }
90 }
91
92
SetVehicleValuesIntoSoldierType(SOLDIERTYPE * const vs)93 void SetVehicleValuesIntoSoldierType(SOLDIERTYPE* const vs)
94 {
95 const VEHICLETYPE* const v = &pVehicleList[vs->bVehicleID];
96 vs->name = zVehicleName[v->ubVehicleType];
97 vs->ubProfile = g_vehicle_type_info[v->ubVehicleType].profile;
98 vs->sBreathRed = 10000; // Init fuel
99 vs->bBreath = 100;
100 vs->ubWhatKindOfMercAmI = MERC_TYPE__VEHICLE;
101 }
102
103
AddVehicleToList(const INT16 sMapX,const INT16 sMapY,const INT16 sGridNo,const UINT8 ubType)104 INT32 AddVehicleToList(const INT16 sMapX, const INT16 sMapY, const INT16 sGridNo, const UINT8 ubType)
105 {
106 INT32 vid;
107 for (vid = 0;; ++vid)
108 {
109 Assert(pVehicleList.size() <= INT32_MAX);
110 if (vid == static_cast<INT32>(pVehicleList.size()))
111 {
112 pVehicleList.push_back(VEHICLETYPE{});
113 break;
114 }
115 if (!pVehicleList[vid].fValid) break;
116 }
117 VEHICLETYPE* const v = &pVehicleList[vid];
118
119 // found a slot
120 *v = VEHICLETYPE{};
121 v->ubMovementGroup = 0;
122 v->sSectorX = sMapX;
123 v->sSectorY = sMapY;
124 v->sSectorZ = 0;
125 v->sGridNo = sGridNo;
126 v->fValid = TRUE;
127 v->ubVehicleType = ubType;
128 v->pMercPath = NULL;
129 v->fDestroyed = FALSE;
130 v->ubMovementGroup = gubVehicleMovementGroups[vid];
131
132 // ATE: Add movement mask to group...
133 GROUP* const g = GetGroup(v->ubMovementGroup);
134 // This is okay, no groups exist, so simply return.
135 if (!g && gfEditMode) return vid;
136 Assert(g);
137
138 // ARM: setup group movement defaults
139 g->ubTransportationMask = g_vehicle_type_info[ubType].movement_type;
140 g->ubSectorX = sMapX;
141 g->ubNextX = sMapX;
142 g->ubSectorY = sMapY;
143 g->ubNextY = sMapY;
144 g->uiTraverseTime = 0;
145 g->uiArrivalTime = 0;
146
147 return vid;
148 }
149
150
RemoveVehicleFromList(VEHICLETYPE & v)151 void RemoveVehicleFromList(VEHICLETYPE& v)
152 {
153 v.pMercPath = ClearStrategicPathList(v.pMercPath, 0);
154 v = VEHICLETYPE{};
155 }
156
157
ClearOutVehicleList(void)158 void ClearOutVehicleList(void)
159 {
160 if (pVehicleList.size() == 0) return;
161
162 FOR_EACH_VEHICLE(v)
163 {
164 v.pMercPath = ClearStrategicPathList(v.pMercPath, 0);
165 }
166
167 pVehicleList.clear();
168 }
169
170
IsThisVehicleAccessibleToSoldier(SOLDIERTYPE const & s,VEHICLETYPE const & v)171 bool IsThisVehicleAccessibleToSoldier(SOLDIERTYPE const& s, VEHICLETYPE const& v)
172 {
173 return !s.fBetweenSectors &&
174 !v.fBetweenSectors &&
175 s.sSectorX == v.sSectorX &&
176 s.sSectorY == v.sSectorY &&
177 s.bSectorZ == v.sSectorZ &&
178 OKUseVehicle(g_vehicle_type_info[v.ubVehicleType].profile);
179 }
180
181
AddSoldierToVehicle(SOLDIERTYPE & s,VEHICLETYPE & v)182 static bool AddSoldierToVehicle(SOLDIERTYPE& s, VEHICLETYPE& v)
183 {
184 // ok now check if any free slots in the vehicle
185
186 SOLDIERTYPE* vs = 0;
187 if (!IsHelicopter(v))
188 {
189 vs = &GetSoldierStructureForVehicle(v);
190 if (vs->bTeam != OUR_TEAM)
191 {
192 // Change sides
193 vs = ChangeSoldierTeam(vs, OUR_TEAM);
194 // add it to mapscreen list
195 fReBuildCharacterList = TRUE;
196 }
197
198 // If vehicle is empty, add to unique squad now that it has somebody in it!
199 if (GetNumberInVehicle(v) == 0)
200 {
201 // 2 ) Add to unique squad...
202 AddCharacterToUniqueSquad(vs);
203
204 // ATE: OK funcky stuff here!
205 // We have now a guy on a squad group, remove him!
206 RemovePlayerFromGroup(*vs);
207
208 // I really have vehicles.
209 // ONLY add to vehicle group once!
210 GROUP& g = *GetGroup(v.ubMovementGroup);
211 if (!DoesPlayerExistInPGroup(g, *vs))
212 {
213 //NOW.. add guy to vehicle group....
214 AddPlayerToGroup(g, *vs);
215 }
216 else
217 {
218 vs->ubGroupID = v.ubMovementGroup;
219 }
220 }
221 }
222
223 // check if the grunt is already here
224 CFOR_EACH_PASSENGER(v, i)
225 {
226 if (*i == &s) return true; // guy found, no need to add
227 }
228
229 if (vs)
230 {
231 // can't call SelectSoldier in mapscreen, that will initialize interface panels!!!
232 if (guiCurrentScreen == GAME_SCREEN)
233 {
234 SelectSoldier(vs, SELSOLDIER_FORCE_RESELECT);
235 }
236
237 PlayLocationJA2Sample(vs->sGridNo, g_vehicle_type_info[v.ubVehicleType].enter_sound, HIGHVOLUME, 1);
238 }
239
240 INT32 const seats = GetVehicleSeats(v);
241 for (INT32 i = 0; i < seats; ++i)
242 {
243 if (v.pPassengers[i]) continue;
244 v.pPassengers[i] = &s;
245
246 if (s.bAssignment == VEHICLE)
247 {
248 TakeSoldierOutOfVehicle(&s);
249 // NOTE: This will leave the soldier on a squad. Must be done PRIOR TO
250 // and in AS WELL AS the call to RemoveCharacterFromSquads() that's coming
251 // up, to permit direct vehicle->vehicle reassignment!
252 }
253
254 // if in a squad, remove from squad, if not, then check if in mvt group, if
255 // so, move and destroy group
256 if (s.bAssignment < ON_DUTY)
257 {
258 RemoveCharacterFromSquads(&s);
259 }
260 else if (s.ubGroupID != 0)
261 {
262 // destroy group and set to zero
263 RemoveGroup(*GetGroup(s.ubGroupID));
264 s.ubGroupID = 0;
265 }
266
267 if (s.bAssignment != VEHICLE || s.iVehicleId != VEHICLE2ID(v))
268 {
269 SetTimeOfAssignmentChangeForMerc(&s);
270 }
271
272 ChangeSoldiersAssignment(&s, VEHICLE);
273
274 s.iVehicleId = VEHICLE2ID(v);
275
276 // if vehicle is part of mvt group, then add character to mvt group
277 if (v.ubMovementGroup != 0)
278 {
279 AddPlayerToGroup(*GetGroup(v.ubMovementGroup), s);
280 }
281
282 // Are we the first?
283 s.uiStatusFlags |= GetNumberInVehicle(v) == 1 ?
284 SOLDIER_DRIVER : SOLDIER_PASSENGER;
285
286 RemoveSoldierFromGridNo(s);
287
288 if (vs)
289 {
290 // Set gridno for vehicle.....
291 EVENT_SetSoldierPositionXY(&s, vs->dXPos, vs->dYPos, SSP_NONE);
292
293 // Stop from any movement.....
294 EVENT_StopMerc(&s);
295
296 // can't call SetCurrentSquad OR SelectSoldier in mapscreen,
297 // that will initialize interface panels!!!
298 if (guiCurrentScreen == GAME_SCREEN)
299 {
300 SetCurrentSquad(vs->bAssignment, TRUE);
301 }
302 }
303
304 return true;
305 }
306
307 // no slots, leave
308 return false;
309 }
310
311
SetSoldierExitHelicopterInsertionData(SOLDIERTYPE * const s)312 void SetSoldierExitHelicopterInsertionData(SOLDIERTYPE* const s)
313 {
314 if (s->bInSector) return;
315
316 auto shippingDest = GCM->getPrimaryShippingDestination();
317 if (s->sSectorX == shippingDest->deliverySectorX &&
318 s->sSectorY == shippingDest->deliverySectorY &&
319 s->bSectorZ == shippingDest->deliverySectorZ)
320 {
321 // This is Drassen, make insertion gridno specific
322 s->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
323 s->usStrategicInsertionData = 10125;
324 }
325 else
326 {
327 // Not anything different here - just use center gridno
328 s->ubStrategicInsertionCode = INSERTION_CODE_CENTER;
329 }
330 }
331
332
333 static void TeleportVehicleToItsClosestSector(UINT8 ubGroupID);
334
335
336 // remove soldier from vehicle
RemoveSoldierFromVehicle(SOLDIERTYPE & s)337 static bool RemoveSoldierFromVehicle(SOLDIERTYPE& s)
338 {
339 VEHICLETYPE& v = GetVehicle(s.iVehicleId);
340
341 // now look for the grunt
342 INT32 const seats = GetVehicleSeats(v);
343 for (INT32 i = 0;; ++i)
344 {
345 if (i == seats) return false;
346 if (v.pPassengers[i] != &s) continue;
347 v.pPassengers[i] = 0;
348 break;
349 }
350
351 RemovePlayerFromGroup(s);
352
353 s.ubGroupID = 0;
354 s.sSectorY = v.sSectorY;
355 s.sSectorX = v.sSectorX;
356 s.bSectorZ = v.sSectorZ;
357 s.uiStatusFlags &= ~(SOLDIER_DRIVER | SOLDIER_PASSENGER);
358
359 if (IsHelicopter(v))
360 {
361 // The vehicle the helicopter? It can continue moving when no soldiers
362 // aboard (Skyrider remains)
363 if (s.bLife >= OKLIFE)
364 {
365 // Mark the sector as visited (flying around in the chopper doesn't, so
366 // this does it as soon as we get off it)
367 SetSectorFlag(s.sSectorX, s.sSectorY, s.bSectorZ, SF_ALREADY_VISITED);
368 }
369
370 SetSoldierExitHelicopterInsertionData(&s);
371
372 // Update in sector if this is the current sector
373 if (s.sSectorX == gWorldSectorX &&
374 s.sSectorY == gWorldSectorY &&
375 s.bSectorZ == gbWorldSectorZ)
376 {
377 UpdateMercInSector(s, gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
378 }
379 }
380 else
381 {
382 // check if anyone left in vehicle
383 CFOR_EACH_PASSENGER(v, i) return true;
384
385 SOLDIERTYPE& vs = GetSoldierStructureForVehicle(v);
386
387 // and he has a route set
388 if (GetLengthOfMercPath(&vs) > 0)
389 {
390 // cancel the entire path (also handles reversing directions)
391 CancelPathForVehicle(v, FALSE);
392 }
393
394 if (v.fBetweenSectors)
395 {
396 // The vehicle was abandoned between sectors. Teleport it to the closer of
397 // its current and next sectors (it beats having it arrive empty later)
398 TeleportVehicleToItsClosestSector(vs.ubGroupID);
399 }
400
401 // Remove vehicle from squad
402 RemoveCharacterFromSquads(&vs);
403 // ATE: Add him back to vehicle group!
404 GROUP& g = *GetGroup(v.ubMovementGroup);
405 if (!DoesPlayerExistInPGroup(g, vs)) AddPlayerToGroup(g, vs);
406 ChangeSoldiersAssignment(&vs, ASSIGNMENT_EMPTY);
407 }
408 return true;
409 }
410
411
MoveCharactersPathToVehicle(SOLDIERTYPE * const s)412 BOOLEAN MoveCharactersPathToVehicle(SOLDIERTYPE* const s)
413 {
414 if (!s) return FALSE;
415
416 // check if character is in fact in a vehicle
417 INT32 vid;
418 if (s->uiStatusFlags & SOLDIER_VEHICLE)
419 {
420 vid = s->bVehicleID;
421 }
422 else if (s->bAssignment == VEHICLE)
423 {
424 vid = s->iVehicleId;
425 }
426 else
427 {
428 s->pMercPath = ClearStrategicPathList(s->pMercPath, 0);
429 return FALSE;
430 }
431
432 VEHICLETYPE& v = GetVehicle(vid);
433
434 ClearStrategicPathList(v.pMercPath, v.ubMovementGroup);
435 v.pMercPath = CopyPaths(s->pMercPath);
436
437 s->pMercPath = ClearStrategicPathList(s->pMercPath, 0);
438 return TRUE;
439 }
440
441
SetUpMvtGroupForVehicle(SOLDIERTYPE * const s)442 void SetUpMvtGroupForVehicle(SOLDIERTYPE* const s)
443 {
444 INT32 vid;
445 // Check if character is in fact in a vehicle
446 if (s->uiStatusFlags & SOLDIER_VEHICLE) vid = s->bVehicleID;
447 else if (s->bAssignment == VEHICLE) vid = s->iVehicleId;
448 else return;
449 VEHICLETYPE& v = GetVehicle(vid);
450 ClearStrategicPathList(s->pMercPath, s->ubGroupID);
451 s->pMercPath = CopyPaths(v.pMercPath);
452 s->ubGroupID = v.ubMovementGroup;
453 }
454
455
GetVehicle(INT32 const vehicle_id)456 VEHICLETYPE& GetVehicle(INT32 const vehicle_id)
457 {
458 Assert(pVehicleList.size() <= INT32_MAX);
459 if (0 <= vehicle_id && vehicle_id < static_cast<INT32>(pVehicleList.size()))
460 {
461 VEHICLETYPE& v = pVehicleList[vehicle_id];
462 if (v.fValid) return v;
463 }
464 throw std::logic_error("Invalid vehicle ID");
465 }
466
467
GetVehicleFromMvtGroup(GROUP const & g)468 VEHICLETYPE& GetVehicleFromMvtGroup(GROUP const& g)
469 {
470 // given the id of a mvt group, find a vehicle in this group
471 FOR_EACH_VEHICLE(v)
472 {
473 if (v.ubMovementGroup == g.ubGroupID) return v;
474 }
475 throw std::logic_error("Group does not contain a vehicle");
476 }
477
478
479 // Kill this person in the vehicle
KillPersonInVehicle(SOLDIERTYPE & s)480 static bool KillPersonInVehicle(SOLDIERTYPE& s)
481 {
482 if (s.bLife == 0) return false; // Guy is dead, leave
483 // Otherwise hurt him
484 SoldierTakeDamage(&s, 100, 100, TAKE_DAMAGE_BLOODLOSS, 0);
485 return true;
486 }
487
488
KillAllInVehicle(VEHICLETYPE const & v)489 BOOLEAN KillAllInVehicle(VEHICLETYPE const& v)
490 {
491 // go through list of occupants and kill them
492 CFOR_EACH_PASSENGER(v, i)
493 {
494 if (!KillPersonInVehicle(**i)) return FALSE;
495 }
496 return TRUE;
497 }
498
499
GetNumberInVehicle(VEHICLETYPE const & v)500 INT32 GetNumberInVehicle(VEHICLETYPE const& v)
501 {
502 // go through list of occupants in vehicles and count them
503 INT32 count = 0;
504 CFOR_EACH_PASSENGER(v, i) ++count;
505 return count;
506 }
507
508
GetNumberOfNonEPCsInVehicle(INT32 iId)509 INT32 GetNumberOfNonEPCsInVehicle( INT32 iId )
510 {
511 // go through list of occupants in vehicles and count them
512 VEHICLETYPE const& v = GetVehicle(iId);
513
514 INT32 count = 0;
515 CFOR_EACH_PASSENGER(v, i)
516 {
517 const SOLDIERTYPE* const s = *i;
518 if (!AM_AN_EPC(s)) ++count;
519 }
520 return count;
521 }
522
523
IsRobotControllerInVehicle(INT32 iId)524 BOOLEAN IsRobotControllerInVehicle( INT32 iId )
525 {
526 VEHICLETYPE const& v = GetVehicle(iId);
527 CFOR_EACH_PASSENGER(v, i)
528 {
529 if (ControllingRobot(*i)) return TRUE;
530 }
531 return FALSE;
532 }
533
534
AnyAccessibleVehiclesInSoldiersSector(SOLDIERTYPE const & s)535 bool AnyAccessibleVehiclesInSoldiersSector(SOLDIERTYPE const& s)
536 {
537 CFOR_EACH_VEHICLE(v)
538 {
539 if (IsThisVehicleAccessibleToSoldier(s, v)) return true;
540 }
541 return false;
542 }
543
544
IsEnoughSpaceInVehicle(VEHICLETYPE const & v)545 bool IsEnoughSpaceInVehicle(VEHICLETYPE const& v)
546 {
547 return GetNumberInVehicle(v) != GetVehicleSeats(v);
548 }
549
550
TakeSoldierOutOfVehicle(SOLDIERTYPE * const s)551 BOOLEAN TakeSoldierOutOfVehicle(SOLDIERTYPE* const s)
552 {
553 // if not in vehicle, don't take out, not much point, now is there?
554 if (s->bAssignment != VEHICLE) return FALSE;
555
556 if (s->sSectorX == gWorldSectorX &&
557 s->sSectorY == gWorldSectorY &&
558 s->bSectorZ == 0 &&
559 s->bInSector &&
560 !InHelicopter(*s)) // helicopter isn't a soldiertype instance
561 {
562 return ExitVehicle(s);
563 }
564 else
565 {
566 return RemoveSoldierFromVehicle(*s);
567 }
568 }
569
570
PutSoldierInVehicle(SOLDIERTYPE & s,VEHICLETYPE & v)571 bool PutSoldierInVehicle(SOLDIERTYPE& s, VEHICLETYPE& v)
572 {
573 if (!AddSoldierToVehicle(s, v)) return false;
574
575 if (s.sSectorX == gWorldSectorX &&
576 s.sSectorY == gWorldSectorY &&
577 s.bSectorZ == 0 &&
578 !IsHelicopter(v) &&
579 guiCurrentScreen == GAME_SCREEN)
580 {
581 SetCurrentInterfacePanel(TEAM_PANEL);
582 }
583
584 return true;
585 }
586
587
ExitVehicle(SOLDIERTYPE * const s)588 BOOLEAN ExitVehicle(SOLDIERTYPE* const s)
589 {
590 SOLDIERTYPE& vs = GetSoldierStructureForVehicle(GetVehicle(s->iVehicleId));
591
592 INT16 sGridNo = FindGridNoFromSweetSpotWithStructDataFromSoldier(s, s->usUIMovementMode, 5, 3, &vs);
593 if (sGridNo == NOWHERE)
594 {
595 // ATE: BUT we need a place, widen the search
596 sGridNo = FindGridNoFromSweetSpotWithStructDataFromSoldier(s, s->usUIMovementMode, 20, 3, &vs);
597 }
598
599 RemoveSoldierFromVehicle(*s);
600
601 s->sInsertionGridNo = sGridNo;
602 s->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
603 s->usStrategicInsertionData = s->sInsertionGridNo;
604 s->iVehicleId = -1;
605
606 //AllTeamsLookForAll( FALSE );
607 s->bOppList[vs.ubID] = 1;
608
609 // Add to sector....
610 EVENT_SetSoldierPosition(s, sGridNo, SSP_NONE);
611
612 // Update visiblity.....
613 HandleSight(*s, SIGHT_LOOK | SIGHT_RADIO);
614
615 AddCharacterToUniqueSquad(s);
616
617 // can't call SetCurrentSquad OR SelectSoldier in mapscreen, that will initialize interface panels!!!
618 if (guiCurrentScreen == GAME_SCREEN)
619 {
620 SetCurrentSquad(s->bAssignment, TRUE);
621 SelectSoldier(s, SELSOLDIER_FORCE_RESELECT);
622 }
623
624 PlayLocationJA2Sample(vs.sGridNo, g_vehicle_type_info[pVehicleList[vs.bVehicleID].ubVehicleType].enter_sound, HIGHVOLUME, 1);
625 return TRUE;
626 }
627
628
629 static void HandleCriticalHitForVehicleInLocation(UINT8 ubID, INT16 sDmg, INT16 sGridNo, SOLDIERTYPE* att);
630
631
VehicleTakeDamage(const UINT8 ubID,const UINT8 ubReason,const INT16 sDamage,const INT16 sGridNo,SOLDIERTYPE * const att)632 void VehicleTakeDamage(const UINT8 ubID, const UINT8 ubReason, const INT16 sDamage, const INT16 sGridNo, SOLDIERTYPE* const att)
633 {
634 if ( ubReason != TAKE_DAMAGE_GAS )
635 {
636 PlayLocationJA2Sample(sGridNo, S_METAL_IMPACT3, MIDVOLUME, 1);
637 }
638
639 // check if there was in fact damage done to the vehicle
640 if( ( ubReason == TAKE_DAMAGE_HANDTOHAND ) || ( ubReason == TAKE_DAMAGE_GAS ) )
641 {
642 // nope
643 return;
644 }
645
646 if (!pVehicleList[ubID].fDestroyed)
647 {
648 switch( ubReason )
649 {
650 case( TAKE_DAMAGE_GUNFIRE ):
651 case( TAKE_DAMAGE_EXPLOSION):
652 case( TAKE_DAMAGE_STRUCTURE_EXPLOSION):
653
654 HandleCriticalHitForVehicleInLocation(ubID, sDamage, sGridNo, att);
655 break;
656 }
657 }
658 }
659
660
661 // handle crit hit to vehicle in this location
HandleCriticalHitForVehicleInLocation(const UINT8 ubID,const INT16 sDmg,const INT16 sGridNo,SOLDIERTYPE * const att)662 static void HandleCriticalHitForVehicleInLocation(const UINT8 ubID, const INT16 sDmg, const INT16 sGridNo, SOLDIERTYPE* const att)
663 {
664 // check state the armor was s'posed to be in vs. the current state..the difference / orig state is % chance
665 // that a critical hit will occur
666 BOOLEAN fMadeCorpse = FALSE;
667
668 VEHICLETYPE& v = pVehicleList[ubID];
669 SOLDIERTYPE& vs = GetSoldierStructureForVehicle(v);
670
671 if (sDmg > vs.bLife)
672 {
673 vs.bLife = 0;
674 }
675 else
676 {
677 // Decrease Health
678 vs.bLife -= sDmg;
679 }
680
681 if (vs.bLife < OKLIFE) vs.bLife = 0;
682
683 //Show damage
684 vs.sDamage += sDmg;
685
686 if (vs.bInSector && vs.bVisible != -1)
687 {
688 // If we are already dead, don't show damage!
689 if ( sDmg != 0 )
690 {
691 // Display damage
692
693 // Set Damage display counter
694 vs.fDisplayDamage = TRUE;
695 vs.bDisplayDamageCount = 0;
696
697 vs.sDamageX = vs.sBoundingBoxOffsetX;
698 vs.sDamageY = vs.sBoundingBoxOffsetY;
699 }
700 }
701
702 if (vs.bLife == 0 && !v.fDestroyed)
703 {
704 v.fDestroyed = TRUE;
705
706 // Explode vehicle...
707 IgniteExplosion(att, 0, sGridNo, GREAT_BIG_EXPLOSION, 0);
708
709 CheckForAndHandleSoldierDeath(&vs, &fMadeCorpse);
710
711 KillAllInVehicle(v);
712 }
713 }
714
715
DoesVehicleNeedAnyRepairs(VEHICLETYPE const & v)716 bool DoesVehicleNeedAnyRepairs(VEHICLETYPE const& v)
717 {
718 // Skyrider isn't damagable/repairable
719 if (IsHelicopter(v)) return false;
720
721 // get the vehicle soldiertype
722 SOLDIERTYPE const& vs = GetSoldierStructureForVehicle(v);
723 return vs.bLife != vs.bLifeMax;
724 }
725
726
RepairVehicle(VEHICLETYPE const & v,INT8 const bRepairPtsLeft,BOOLEAN * const pfNothingToRepair)727 INT8 RepairVehicle(VEHICLETYPE const& v, INT8 const bRepairPtsLeft, BOOLEAN* const pfNothingToRepair)
728 {
729 INT8 bRepairPtsUsed = 0;
730 INT8 bOldLife;
731
732 if (!DoesVehicleNeedAnyRepairs(v)) return bRepairPtsUsed;
733
734 // get the vehicle soldiertype
735 SOLDIERTYPE& vs = GetSoldierStructureForVehicle(v);
736
737 bOldLife = vs.bLife;
738
739 // Repair
740 vs.bLife += bRepairPtsLeft / VEHICLE_REPAIR_POINTS_DIVISOR;
741
742 // Check
743 if (vs.bLife > vs.bLifeMax) vs.bLife = vs.bLifeMax;
744
745 // Calculate pts used;
746 bRepairPtsUsed = (vs.bLife - bOldLife) * VEHICLE_REPAIR_POINTS_DIVISOR;
747
748 // ARM: personally, I'd love to know where in Arulco the mechanic gets the PARTS to do this stuff, but hey, it's a game!
749 *pfNothingToRepair = !DoesVehicleNeedAnyRepairs(v);
750
751 return( bRepairPtsUsed );
752 }
753
754
GetSoldierStructureForVehicle(VEHICLETYPE const & v)755 SOLDIERTYPE& GetSoldierStructureForVehicle(VEHICLETYPE const& v)
756 {
757 FOR_EACH_SOLDIER(s)
758 {
759 if (!(s->uiStatusFlags & SOLDIER_VEHICLE)) continue;
760 if (s->bVehicleID != VEHICLE2ID(v)) continue;
761 return *s;
762 }
763 throw std::logic_error("Vehicle has no corresponding soldier");
764 }
765
766
SaveVehicleInformationToSaveGameFile(HWFILE const f)767 void SaveVehicleInformationToSaveGameFile(HWFILE const f)
768 {
769 //Save the number of elements
770 Assert(pVehicleList.size() <= UINT8_MAX);
771 UINT8 numVehicles = static_cast<UINT8>(pVehicleList.size());
772 FileWrite(f, &numVehicles, sizeof(UINT8));
773
774 //loop through all the vehicles and save each one
775 for (const VEHICLETYPE& v : pVehicleList)
776 {
777 //save if the vehicle spot is valid
778 FileWrite(f, &v.fValid, sizeof(BOOLEAN));
779 if (!v.fValid) continue;
780
781 InjectVehicleTypeIntoFile(f, &v);
782 SaveMercPath(f, v.pMercPath);
783 }
784 }
785
786
LoadVehicleInformationFromSavedGameFile(HWFILE const hFile,UINT32 const uiSavedGameVersion)787 void LoadVehicleInformationFromSavedGameFile(HWFILE const hFile, UINT32 const uiSavedGameVersion)
788 {
789 ClearOutVehicleList();
790
791 //Load the number of elements
792 UINT8 numVehicles = 0;
793 FileRead(hFile, &numVehicles, sizeof(UINT8));
794 if (numVehicles == 0) return;
795
796 //allocate memory to hold the vehicle list
797 pVehicleList.assign(numVehicles, VEHICLETYPE{});
798
799 //loop through all the vehicles and load each one
800 for (VEHICLETYPE& v : pVehicleList)
801 {
802 //Load if the vehicle spot is valid
803 FileRead(hFile, &v.fValid, sizeof(BOOLEAN));
804 if (!v.fValid) continue;
805
806 ExtractVehicleTypeFromFile(hFile, &v, uiSavedGameVersion);
807 LoadMercPath(hFile, &v.pMercPath);
808 }
809 }
810
811
SetVehicleSectorValues(VEHICLETYPE & v,UINT8 const x,UINT8 const y)812 void SetVehicleSectorValues(VEHICLETYPE& v, UINT8 const x, UINT8 const y)
813 {
814 v.sSectorX = x;
815 v.sSectorY = y;
816
817 MERCPROFILESTRUCT& p = GetProfile(g_vehicle_type_info[v.ubVehicleType].profile);
818 p.sSectorX = x;
819 p.sSectorY = y;
820
821 // Go through list of mercs in vehicle and set all their states as arrived
822 CFOR_EACH_PASSENGER(v, i)
823 {
824 SOLDIERTYPE& s = **i;
825 s.sSectorX = x;
826 s.sSectorY = y;
827 s.fBetweenSectors = FALSE;
828 }
829 }
830
831
UpdateAllVehiclePassengersGridNo(SOLDIERTYPE * const vs)832 void UpdateAllVehiclePassengersGridNo(SOLDIERTYPE* const vs)
833 {
834 // If not a vehicle, ignore!
835 if (!(vs->uiStatusFlags & SOLDIER_VEHICLE)) return;
836 VEHICLETYPE const& v = pVehicleList[vs->bVehicleID];
837
838 // Loop through passengers and update each guy's position
839 CFOR_EACH_PASSENGER(v, i)
840 {
841 EVENT_SetSoldierPositionXY(*i, vs->dXPos, vs->dYPos, SSP_NONE);
842 }
843 }
844
845
LoadVehicleMovementInfoFromSavedGameFile(HWFILE const hFile)846 void LoadVehicleMovementInfoFromSavedGameFile(HWFILE const hFile)
847 {
848 INT32 cnt;
849
850 //Load in the Squad movement id's
851 FileRead(hFile, gubVehicleMovementGroups, sizeof(INT8) * 5);
852
853 for( cnt = 5; cnt < MAX_VEHICLES; cnt++ )
854 {
855 // create mvt groups
856 GROUP* const g = CreateNewVehicleGroupDepartingFromSector(1, 1);
857 g->fPersistant = TRUE;
858 gubVehicleMovementGroups[cnt] = g->ubGroupID;
859 }
860 }
861
862
NewSaveVehicleMovementInfoToSavedGameFile(HWFILE const hFile)863 void NewSaveVehicleMovementInfoToSavedGameFile(HWFILE const hFile)
864 {
865 //Save all the vehicle movement id's
866 FileWrite(hFile, gubVehicleMovementGroups, sizeof(INT8) * MAX_VEHICLES);
867 }
868
869
NewLoadVehicleMovementInfoFromSavedGameFile(HWFILE const hFile)870 void NewLoadVehicleMovementInfoFromSavedGameFile(HWFILE const hFile)
871 {
872 //Load in the Squad movement id's
873 FileRead(hFile, gubVehicleMovementGroups, sizeof(INT8) * MAX_VEHICLES);
874 }
875
876
OKUseVehicle(UINT8 ubProfile)877 BOOLEAN OKUseVehicle( UINT8 ubProfile )
878 {
879 if ( ubProfile == PROF_HUMMER )
880 {
881 return( CheckFact( FACT_OK_USE_HUMMER, NO_PROFILE ) );
882 }
883 else if ( ubProfile == PROF_ICECREAM )
884 {
885 return( CheckFact( FACT_OK_USE_ICECREAM, NO_PROFILE ) );
886 }
887 else if ( ubProfile == PROF_HELICOPTER )
888 {
889 // don't allow mercs to get inside vehicle if it's grounded (enemy controlled, Skyrider owed money, etc.)
890 return( CanHelicopterFly() );
891 }
892 else
893 {
894 return( TRUE );
895 }
896 }
897
898
TeleportVehicleToItsClosestSector(const UINT8 ubGroupID)899 static void TeleportVehicleToItsClosestSector(const UINT8 ubGroupID)
900 {
901 GROUP *pGroup = NULL;
902 UINT32 uiTimeToNextSector;
903 UINT32 uiTimeToLastSector;
904 INT16 sPrevX, sPrevY, sNextX, sNextY;
905
906
907 pGroup = GetGroup( ubGroupID );
908 Assert( pGroup );
909
910 Assert(pGroup->uiTraverseTime > 0 && pGroup->uiTraverseTime != TRAVERSE_TIME_IMPOSSIBLE);
911
912 Assert( pGroup->uiArrivalTime >= GetWorldTotalMin() );
913 uiTimeToNextSector = pGroup->uiArrivalTime - GetWorldTotalMin();
914
915 Assert( pGroup->uiTraverseTime >= uiTimeToNextSector );
916 uiTimeToLastSector = pGroup->uiTraverseTime - uiTimeToNextSector;
917
918 if ( uiTimeToNextSector >= uiTimeToLastSector )
919 {
920 // go to the last sector
921 sPrevX = pGroup->ubNextX;
922 sPrevY = pGroup->ubNextY;
923
924 sNextX = pGroup->ubSectorX;
925 sNextY = pGroup->ubSectorY;
926 }
927 else
928 {
929 // go to the next sector
930 sPrevX = pGroup->ubSectorX;
931 sPrevY = pGroup->ubSectorY;
932
933 sNextX = pGroup->ubNextX;
934 sNextY = pGroup->ubNextY;
935 }
936
937 // make it arrive immediately, not eventually (it's driverless)
938 pGroup->setArrivalTime(GetWorldTotalMin());
939
940 // change where it is and where it's going, then make it arrive there. Don't check for battle
941 PlaceGroupInSector(*pGroup, sPrevX, sPrevY, sNextX, sNextY, 0, false);
942 }
943
944
AddVehicleFuelToSave()945 void AddVehicleFuelToSave( )
946 {
947 CFOR_EACH_VEHICLE(v)
948 {
949 if (IsHelicopter(v)) continue;
950 SOLDIERTYPE& vs = GetSoldierStructureForVehicle(v);
951 // Init fuel!
952 vs.sBreathRed = 10000;
953 vs.bBreath = 100;
954 }
955 }
956
957
CanSoldierDriveVehicle(SOLDIERTYPE const & s,INT32 const vehicle_id,bool const ignore_asleep)958 static bool CanSoldierDriveVehicle(SOLDIERTYPE const& s, INT32 const vehicle_id, bool const ignore_asleep)
959 {
960 return s.bAssignment == VEHICLE && // In a vehicle?
961 vehicle_id == s.iVehicleId && // In this vehicle?
962 vehicle_id != iHelicopterVehicleId && // Only Skyrider can pilot the helicopter
963 (ignore_asleep || !s.fMercAsleep) &&
964 !IsMechanical(s) && // Vehicles, robot, and EPCs can't drive
965 !AM_AN_EPC(&s) &&
966 s.bLife >= OKLIFE && // Too wounded to drive?
967 s.bBreathMax > BREATHMAX_ABSOLUTE_MINIMUM; // Too tired to drive?
968 }
969
970
OnlyThisSoldierCanDriveVehicle(SOLDIERTYPE const & s,INT32 const vehicle_id)971 static bool OnlyThisSoldierCanDriveVehicle(SOLDIERTYPE const& s, INT32 const vehicle_id)
972 {
973 CFOR_EACH_IN_TEAM(i, OUR_TEAM)
974 {
975 SOLDIERTYPE const& other = *i;
976 // Skip checking this soldier, we want to know about everyone else
977 if (&other == &s) continue;
978 // Don't count mercs who are asleep here
979 if (!CanSoldierDriveVehicle(other, vehicle_id, false)) continue;
980 // This guy can drive it, too
981 return false;
982 }
983 return true;
984 }
985
986
SoldierMustDriveVehicle(SOLDIERTYPE const & s,bool const trying_to_travel)987 bool SoldierMustDriveVehicle(SOLDIERTYPE const& s, bool const trying_to_travel)
988 {
989 INT32 const vehicle_id = s.iVehicleId;
990 VEHICLETYPE const& v = GetVehicle(vehicle_id);
991
992 // If vehicle is not going anywhere, then nobody has to be driving it. Need
993 // the path length check in case we're doing a test while actually in a sector
994 // even though we're moving.
995 if (!trying_to_travel && !v.fBetweenSectors && GetLengthOfPath(v.pMercPath) == 0) return false;
996
997 // Can he drive it (don't care if he is currently asleep) and is he the only
998 // one aboard who can do so? If there are multiple possible drivers, than the
999 // assumption is that this guy isn't driving, so he can sleep.
1000 if (!CanSoldierDriveVehicle(s, vehicle_id, true)) return false;
1001 if (!OnlyThisSoldierCanDriveVehicle(s, vehicle_id)) return false;
1002 return true;
1003 }
1004
1005
IsSoldierInThisVehicleSquad(const SOLDIERTYPE * const pSoldier,const INT8 bSquadNumber)1006 BOOLEAN IsSoldierInThisVehicleSquad(const SOLDIERTYPE* const pSoldier, const INT8 bSquadNumber)
1007 {
1008 Assert( pSoldier );
1009 Assert( ( bSquadNumber >= 0 ) && ( bSquadNumber < NUMBER_OF_SQUADS ) );
1010
1011 // not in a vehicle?
1012 if( pSoldier->bAssignment != VEHICLE )
1013 {
1014 return( FALSE );
1015 }
1016
1017 if (InHelicopter(*pSoldier)) return FALSE; // they don't get a squad #
1018
1019 SOLDIERTYPE const& vs = GetSoldierStructureForVehicle(GetVehicle(pSoldier->iVehicleId));
1020
1021 // check squad vehicle is on
1022 if (vs.bAssignment != bSquadNumber)
1023 {
1024 return( FALSE );
1025 }
1026
1027
1028 // yes, he's in a vehicle assigned to this squad
1029 return( TRUE );
1030 }
1031
1032
PickRandomPassengerFromVehicle(SOLDIERTYPE * const pSoldier)1033 SOLDIERTYPE* PickRandomPassengerFromVehicle(SOLDIERTYPE* const pSoldier)
1034 {
1035 // If not a vehicle, ignore!
1036 if (!(pSoldier->uiStatusFlags & SOLDIER_VEHICLE)) return NULL;
1037
1038 VEHICLETYPE const& v = pVehicleList[pSoldier->bVehicleID];
1039
1040 INT32 n_mercs = 0;
1041 SOLDIERTYPE* mercs_in_vehicle[20];
1042 CFOR_EACH_PASSENGER(v, i) mercs_in_vehicle[n_mercs++] = *i;
1043
1044 return n_mercs == 0 ? NULL : mercs_in_vehicle[Random(n_mercs)];
1045 }
1046
1047
DoesVehicleGroupHaveAnyPassengers(GROUP const & g)1048 bool DoesVehicleGroupHaveAnyPassengers(GROUP const& g)
1049 {
1050 return GetNumberInVehicle(GetVehicleFromMvtGroup(g)) != 0;
1051 }
1052
1053
HandleVehicleMovementSound(const SOLDIERTYPE * const s,const BOOLEAN fOn)1054 void HandleVehicleMovementSound(const SOLDIERTYPE* const s, const BOOLEAN fOn)
1055 {
1056 VEHICLETYPE* const v = &pVehicleList[s->bVehicleID];
1057 if (fOn)
1058 {
1059 if (v->uiMovementSoundID == NO_SAMPLE)
1060 {
1061 v->uiMovementSoundID = PlayLocationJA2Sample(s->sGridNo, g_vehicle_type_info[v->ubVehicleType].move_sound, HIGHVOLUME, 1);
1062 }
1063 }
1064 else
1065 {
1066 if (v->uiMovementSoundID != NO_SAMPLE)
1067 {
1068 SoundStop(v->uiMovementSoundID);
1069 v->uiMovementSoundID = NO_SAMPLE;
1070 }
1071 }
1072 }
1073
1074
GetVehicleArmourType(const UINT8 vehicle_id)1075 UINT8 GetVehicleArmourType(const UINT8 vehicle_id)
1076 {
1077 return GCM->getItem(g_vehicle_type_info[pVehicleList[vehicle_id].ubVehicleType].armour_type)->getClassIndex();
1078 }
1079
1080
GetVehicleSeats(VEHICLETYPE const & v)1081 UINT8 GetVehicleSeats(VEHICLETYPE const& v)
1082 {
1083 return g_vehicle_type_info[v.ubVehicleType].seats;
1084 }
1085