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