1 #include "Interface_Panels.h"
2 #include "LoadSaveData.h"
3 #include "Types.h"
4 #include "Squads.h"
5 #include "Strategic_Pathing.h"
6 #include "StrategicMap.h"
7 #include "Faces.h"
8 #include "Strategic_Movement.h"
9 #include "Assignments.h"
10 #include "Overhead.h"
11 #include "Interface.h"
12 #include "Vehicles.h"
13 #include "Map_Screen_Helicopter.h"
14 #include "Soldier_Profile.h"
15 #include "Debug.h"
16 #include "JAScreens.h"
17 #include "Soldier_Macros.h"
18 #include "ScreenIDs.h"
19 #include "FileMan.h"
20 
21 #include <algorithm>
22 #include <stdexcept>
23 
24 // squad array
25 SOLDIERTYPE *Squad[ NUMBER_OF_SQUADS ][ NUMBER_OF_SOLDIERS_PER_SQUAD ];
26 
27 // list of dead guys for squads...in id values -> -1 means no one home
28 INT16 sDeadMercs[ NUMBER_OF_SQUADS ][ NUMBER_OF_SOLDIERS_PER_SQUAD ];
29 
30 // the movement group ids
31 INT8 SquadMovementGroups[ NUMBER_OF_SQUADS ];
32 
33 BOOLEAN fExitingVehicleToSquad = FALSE;
34 
35 
36 INT32 iCurrentTacticalSquad = FIRST_SQUAD;
37 
InitSquads(void)38 void InitSquads( void )
39 {
40 	// init the squad lists to NULL ptrs.
41 	INT32 iCounter =0;
42 
43 	// null each list of ptrs.
44 	for( iCounter = 0; iCounter <  NUMBER_OF_SQUADS; iCounter++ )
45 	{
46 		FOR_EACH_SLOT_IN_SQUAD(i, iCounter)
47 		{
48 			*i = 0;
49 		}
50 
51 		// create mvt groups
52 		GROUP* const g = CreateNewPlayerGroupDepartingFromSector(1, 1);
53 		g->fPersistant = TRUE;
54 		SquadMovementGroups[iCounter] = g->ubGroupID;
55 	}
56 
57 	for (int i = 0; i < NUMBER_OF_SQUADS; ++i)
58 	{
59 		std::fill_n(sDeadMercs[i], NUMBER_OF_SOLDIERS_PER_SQUAD, -1);
60 	}
61 }
62 
IsThisSquadFull(INT8 bSquadValue)63 BOOLEAN IsThisSquadFull( INT8 bSquadValue )
64 {
65 	// run through entries in the squad list, make sure there is a free entry
66 	FOR_EACH_SLOT_IN_SQUAD(i, bSquadValue)
67 	{
68 		if (!*i) return FALSE;
69 	}
70 	return TRUE;
71 }
72 
GetFirstEmptySquad(void)73 INT8 GetFirstEmptySquad( void )
74 {
75 	UINT8 ubCounter = 0;
76 
77 	for( ubCounter = 0; ubCounter < NUMBER_OF_SQUADS; ubCounter++ )
78 	{
79 		if (SquadIsEmpty(ubCounter)) return ubCounter;
80 	}
81 
82 	// not found - none are completely empty (shouldn't ever happen!)
83 	throw std::logic_error("Found no empty squad");
84 }
85 
86 
87 static BOOLEAN CopyPathOfSquadToCharacter(SOLDIERTYPE* pCharacter, INT8 bSquadValue);
88 
89 
AddCharacterToSquad(SOLDIERTYPE * const s,INT8 const bSquadValue)90 BOOLEAN AddCharacterToSquad(SOLDIERTYPE* const s, INT8 const bSquadValue)
91 {
92 	// add character to squad...return success or failure
93 	// run through list of people in squad, find first free slo
94 
95 	if (fExitingVehicleToSquad) return FALSE;
96 
97 	// ATE: If any vehicle exists in this squad AND we're not set to
98 	// a driver or or passenger, when return false
99 	if (DoesVehicleExistInSquad(bSquadValue))
100 	{
101 		// We're not allowing anybody to go on a vehicle if they are not passengers!
102 		// NB: We obviously need to make sure that REAL passengers have their
103 		// flags set before adding them to a squad!
104 		if (!(s->uiStatusFlags & (SOLDIER_PASSENGER | SOLDIER_DRIVER | SOLDIER_VEHICLE)))
105 		{
106 			return FALSE;
107 		}
108 	}
109 
110 	// if squad is on the move, can't add someone
111 	if (IsThisSquadOnTheMove(bSquadValue)) return FALSE;
112 
113 	FOR_EACH_SLOT_IN_SQUAD(i, bSquadValue)
114 	{
115 		SOLDIERTYPE const* const t = *i;
116 		// check if on current squad and current slot?
117 		// 'successful of sorts, if there, then he's 'added'
118 		if (t == s) return TRUE;
119 
120 		if (t) continue;
121 		// free slot, add here
122 
123 		// check if squad empty, if not check sector x,y,z are the same as this guys
124 		INT16 sX;
125 		INT16 sY;
126 		INT8  bZ;
127 		if (SectorSquadIsIn(bSquadValue, &sX, &sY, &bZ) &&
128 			(s->sSectorX != sX || s->sSectorY != sY || s->bSectorZ != bZ))
129 		{
130 			return FALSE;
131 		}
132 
133 		RemoveCharacterFromSquads(s);
134 
135 		// copy path of squad to this char
136 		CopyPathOfSquadToCharacter(s, bSquadValue);
137 
138 		// check if old mvt group
139 		if (s->ubGroupID != 0)
140 		{
141 			// in valid group, remove from that group
142 			RemovePlayerFromGroup(*s);
143 
144 			// character not on a reserved group
145 			if (s->bAssignment >= ON_DUTY && s->bAssignment != VEHICLE)
146 			{
147 				// if valid group, delete it
148 				GROUP* const pGroup = GetGroup(s->ubGroupID);
149 				if (pGroup) RemoveGroup(*pGroup);
150 			}
151 		}
152 
153 		GROUP& g = *GetGroup(SquadMovementGroups[bSquadValue]);
154 		if (s->bAssignment != VEHICLE || s->iVehicleId == -1)
155 		{
156 			AddPlayerToGroup(g, *s);
157 			SetGroupSectorValue(s->sSectorX, s->sSectorY, s->bSectorZ, g);
158 		}
159 		else if (InHelicopter(*s))
160 		{
161 			// if creating a new squad from guys exiting the chopper
162 			BOOLEAN const fNewSquad = SquadIsEmpty(bSquadValue);
163 
164 			RemoveSoldierFromHelicopter(s);
165 
166 			AddPlayerToGroup(g, *s);
167 			SetGroupSectorValue(s->sSectorX, s->sSectorY, s->bSectorZ, g);
168 
169 			// if we've just started a new squad
170 			if (fNewSquad)
171 			{
172 				// set mvt group for
173 				GROUP const* const pGroup = GetGroup(GetHelicopter().ubMovementGroup);
174 				Assert(pGroup);
175 				if (pGroup)
176 				{
177 					// set where it is and where it's going, then make it arrive there.  Don't check for battle
178 					PlaceGroupInSector(g, pGroup->ubPrevX, pGroup->ubPrevY, pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ, false); // XXX TODO001D
179 				}
180 			}
181 		}
182 		else
183 		{
184 			fExitingVehicleToSquad = TRUE;
185 			// remove from vehicle
186 			TakeSoldierOutOfVehicle(s);
187 			fExitingVehicleToSquad = FALSE;
188 
189 			AddPlayerToGroup(g, *s);
190 			SetGroupSectorValue(s->sSectorX, s->sSectorY, s->bSectorZ, g);
191 		}
192 
193 		*i = s;
194 
195 		if (s->bAssignment != bSquadValue)
196 		{
197 			// check to see if we should wake them up
198 			if (s->fMercAsleep) SetMercAwake(s, FALSE, FALSE);
199 			SetTimeOfAssignmentChangeForMerc(s);
200 		}
201 
202 		// set squad value
203 		ChangeSoldiersAssignment(s, bSquadValue);
204 
205 		if (SquadIsEmpty(iCurrentTacticalSquad)) SetCurrentSquad(bSquadValue, TRUE);
206 
207 		if (bSquadValue == iCurrentTacticalSquad) CheckForAndAddMercToTeamPanel(s);
208 
209 		if (s == GetSelectedMan()) SetCurrentSquad(bSquadValue, TRUE);
210 
211 		return TRUE;
212 	}
213 
214 	return FALSE;
215 }
216 
217 
218 // find the first slot we can fit the guy in
AddCharacterToAnySquad(SOLDIERTYPE * const pCharacter)219 void AddCharacterToAnySquad(SOLDIERTYPE* const pCharacter)
220 {
221 	// add character to any squad, if character is assigned to a squad, returns TRUE
222 	INT8 bCounter = 0;
223 	INT8 bFirstEmptySquad = -1;
224 
225 
226 	// remove them from current squad
227 	RemoveCharacterFromSquads( pCharacter ); // REDUNDANT AddCharacterToSquad()
228 
229 	// first look for a compatible NON-EMPTY squad (don't start new squad if we don't have to)
230 	for( bCounter = 0; bCounter < NUMBER_OF_SQUADS; bCounter++ )
231 	{
232 		if (!SquadIsEmpty(bCounter))
233 		{
234 			if (AddCharacterToSquad(pCharacter, bCounter))
235 			{
236 				return;
237 			}
238 		}
239 		else
240 		{
241 			if ( bFirstEmptySquad == -1 )
242 			{
243 				bFirstEmptySquad = bCounter;
244 			}
245 		}
246 	}
247 
248 	// no non-empty compatible squads were found
249 
250 	// try the first empty one (and there better be one)
251 	if ( bFirstEmptySquad != -1 )
252 	{
253 		if (AddCharacterToSquad(pCharacter, bFirstEmptySquad))
254 		{
255 			return;
256 		}
257 	}
258 
259 	throw std::logic_error("Failed to add character to any squad");
260 }
261 
262 
263 // find the first slot we can fit the guy in
AddCharacterToUniqueSquad(SOLDIERTYPE * const s)264 INT8 AddCharacterToUniqueSquad(SOLDIERTYPE* const s)
265 {
266 	RemoveCharacterFromSquads(s); // REDUNDANT AddCharacterToSquad()
267 	INT8 const squad = GetFirstEmptySquad();
268 	AddCharacterToSquad(s, squad);
269 	return squad;
270 }
271 
272 
SquadIsEmpty(INT8 bSquadValue)273 BOOLEAN SquadIsEmpty( INT8 bSquadValue )
274 {
275 	// run through this squad's slots and find if they ALL are empty
276 	FOR_EACH_IN_SQUAD(i, bSquadValue)
277 	{
278 		return FALSE;
279 	}
280 	return TRUE;
281 }
282 
283 
284 static BOOLEAN AddDeadCharacterToSquadDeadGuys(SOLDIERTYPE* pSoldier, INT32 iSquadValue);
285 static void RebuildSquad(INT8 bSquadValue);
286 static void UpdateCurrentlySelectedMerc(SOLDIERTYPE* pSoldier, INT8 bSquadValue);
287 
288 
289 // find and remove character from any squad
RemoveCharacterFromSquads(SOLDIERTYPE * const s)290 BOOLEAN RemoveCharacterFromSquads(SOLDIERTYPE* const s)
291 {
292 	for (INT32 squad = 0; squad < NUMBER_OF_SQUADS; ++squad)
293 	{
294 		FOR_EACH_SLOT_IN_SQUAD(i, squad)
295 		{
296 			if (*i != s) continue;
297 			*i = 0;
298 
299 			// Release memory for his personal path, but don't clear his group's
300 			// path/waypoints (pass in groupID -1).  Just because one guy leaves a
301 			// group is no reason to cancel movement for the rest of the group.
302 			s->pMercPath = ClearStrategicPathList(s->pMercPath, -1);
303 
304 			RemovePlayerFromGroup(*s);
305 			s->ubGroupID = 0;
306 
307 			if (s->fBetweenSectors && s->uiStatusFlags & SOLDIER_VEHICLE)
308 			{
309 				GROUP& g = *CreateNewPlayerGroupDepartingFromSector(s->sSectorX, s->sSectorY);
310 				AddPlayerToGroup(g, *s);
311 			}
312 
313 			RebuildSquad(squad);
314 
315 			if (s->bLife == 0)
316 			{
317 				AddDeadCharacterToSquadDeadGuys(s, squad);
318 			}
319 
320 			if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME) && guiCurrentScreen == GAME_SCREEN)
321 			{
322 				UpdateCurrentlySelectedMerc(s, squad);
323 			}
324 
325 			return TRUE;
326 		}
327 	}
328 
329 	// not found
330 	return FALSE;
331 }
332 
333 
NumberOfPeopleInSquad(INT8 bSquadValue)334 INT8 NumberOfPeopleInSquad( INT8 bSquadValue )
335 {
336 	INT8 bSquadCount = 0;
337 
338 	if( bSquadValue == NO_CURRENT_SQUAD )
339 	{
340 		return( 0 );
341 	}
342 
343 	// find number of characters in particular squad.
344 	FOR_EACH_IN_SQUAD(i, bSquadValue)
345 	{
346 		++bSquadCount;
347 	}
348 
349 	// return number found
350 	return( bSquadCount );
351 }
352 
NumberOfNonEPCsInSquad(INT8 bSquadValue)353 INT8 NumberOfNonEPCsInSquad( INT8 bSquadValue )
354 {
355 	INT8 bSquadCount = 0;
356 
357 	if( bSquadValue == NO_CURRENT_SQUAD )
358 	{
359 		return( 0 );
360 	}
361 
362 	// find number of characters in particular squad.
363 	FOR_EACH_IN_SQUAD(i, bSquadValue)
364 	{
365 		// valid slot?
366 		if (!AM_AN_EPC(*i)) ++bSquadCount;
367 	}
368 
369 	// return number found
370 	return( bSquadCount );
371 }
372 
IsRobotControllerInSquad(INT8 bSquadValue)373 BOOLEAN IsRobotControllerInSquad( INT8 bSquadValue )
374 {
375 	if( bSquadValue == NO_CURRENT_SQUAD )
376 	{
377 		return( 0 );
378 	}
379 
380 	// find number of characters in particular squad.
381 	FOR_EACH_IN_SQUAD(i, bSquadValue)
382 	{
383 		// valid slot?
384 		if (ControllingRobot(*i)) return TRUE;
385 	}
386 
387 	// return number found
388 	return( FALSE );
389 }
390 
391 
SectorSquadIsIn(const INT8 bSquadValue,INT16 * const sMapX,INT16 * const sMapY,INT8 * const sMapZ)392 BOOLEAN SectorSquadIsIn(const INT8 bSquadValue, INT16* const sMapX, INT16* const sMapY, INT8* const sMapZ)
393 {
394 	// returns if there is anyone on the squad and what sector ( strategic ) they are in
395 	Assert( bSquadValue < ON_DUTY );
396 
397 	FOR_EACH_IN_SQUAD(i, bSquadValue)
398 	{
399 		SOLDIERTYPE const* const s = *i;
400 		// if valid soldier, get current sector and return
401 		*sMapX = s->sSectorX;
402 		*sMapY = s->sSectorY;
403 		*sMapZ = s->bSectorZ;
404 		return TRUE;
405 	}
406 
407 	// return there is no squad
408 	return ( FALSE );
409 }
410 
411 
CopyPathOfSquadToCharacter(SOLDIERTYPE * pCharacter,INT8 bSquadValue)412 static BOOLEAN CopyPathOfSquadToCharacter(SOLDIERTYPE* pCharacter, INT8 bSquadValue)
413 {
414 	// copy path from squad to character
415 	FOR_EACH_IN_SQUAD(i, bSquadValue)
416 	{
417 		SOLDIERTYPE const* const t = *i;
418 		if (t == pCharacter) continue;
419 		// valid character, copy paths
420 		ClearStrategicPathList(pCharacter->pMercPath, 0);
421 		pCharacter->pMercPath = CopyPaths(t->pMercPath);
422 		// return success
423 		return TRUE;
424 	}
425 
426 	// return failure
427 	return ( FALSE );
428 }
429 
430 
CopyPathOfCharacterToSquad(SOLDIERTYPE * const pCharacter,const INT8 bSquadValue)431 void CopyPathOfCharacterToSquad(SOLDIERTYPE* const pCharacter, const INT8 bSquadValue)
432 {
433 	// copy path of this character to members of squad
434 	FOR_EACH_IN_SQUAD(i, bSquadValue)
435 	{
436 		SOLDIERTYPE* const t = *i;
437 		if (t == pCharacter) continue;
438 		// valid character, copy paths
439 		ClearStrategicPathList(t->pMercPath, -1);
440 		t->pMercPath = CopyPaths(pCharacter->pMercPath);
441 	}
442 }
443 
444 
445 
CurrentSquad(void)446 INT32 CurrentSquad( void )
447 {
448 	// returns which squad is current squad
449 
450 	return( iCurrentTacticalSquad );
451 }
452 
SetCurrentSquad(INT32 iCurrentSquad,BOOLEAN fForce)453 BOOLEAN SetCurrentSquad( INT32 iCurrentSquad, BOOLEAN fForce )
454 {
455 	// set the current tactical squad
456 
457 	// ARM: can't call SetCurrentSquad() in mapscreen, it calls SelectSoldier(), that will initialize interface panels!!!
458 	// ATE: Adjusted conditions a bit ( sometimes were not getting selected )
459 	if ( guiCurrentScreen == LAPTOP_SCREEN || guiCurrentScreen == MAP_SCREEN )
460 	{
461 		return( FALSE );
462 	}
463 
464 	// ATE; Added to allow us to have NO current squad
465 	if ( iCurrentSquad == NO_CURRENT_SQUAD )
466 	{
467 		// set current squad and return success
468 		iCurrentTacticalSquad = iCurrentSquad;
469 
470 		// cleat list
471 		RemoveAllPlayersFromSlot( );
472 
473 		// set all auto faces inactive
474 		SetAllAutoFacesInactive( );
475 
476 		return( FALSE );
477 	}
478 
479 
480 	// check if valid value passed
481 	if( ( iCurrentSquad >= NUMBER_OF_SQUADS )||( iCurrentSquad < 0 ) )
482 	{
483 		// no
484 		return ( FALSE );
485 	}
486 
487 	// check if squad is current
488 	if( iCurrentSquad == iCurrentTacticalSquad && !fForce )
489 	{
490 		return ( TRUE );
491 	}
492 
493 	// set current squad and return success
494 	iCurrentTacticalSquad = iCurrentSquad;
495 
496 	// cleat list
497 	RemoveAllPlayersFromSlot( );
498 
499 	// set all auto faces inactive
500 	SetAllAutoFacesInactive( );
501 
502 	if (iCurrentSquad != NO_CURRENT_SQUAD)
503 	{
504 		FOR_EACH_IN_SQUAD(i, iCurrentSquad)
505 		{
506 			// squad set, now add soldiers in
507 			CheckForAndAddMercToTeamPanel(*i);
508 		}
509 	}
510 
511 	// check if the currently selected guy is on this squad, if not, get the first one on the new squad
512 	const SOLDIERTYPE* const sel = GetSelectedMan();
513 	if (sel == NULL || sel->bAssignment != iCurrentSquad)
514 	{
515 		// ATE: Changed this to FALSE for acknowledgement sounds.. sounds bad if just starting/entering sector..
516 		SelectSoldier(Squad[iCurrentSquad][0], SELSOLDIER_FORCE_RESELECT);
517 	}
518 
519 	return ( TRUE );
520 }
521 
RebuildCurrentSquad(void)522 void RebuildCurrentSquad( void )
523 {
524 	// rebuilds current squad to reset faces in tactical
525 
526 	// check if valid value passed
527 	if( ( iCurrentTacticalSquad >= NUMBER_OF_SQUADS ) || ( iCurrentTacticalSquad < 0 ) )
528 	{
529 		// no
530 		return;
531 	}
532 
533 	// set default squad..just inc ase we no longer have a valid squad
534 	SetDefaultSquadOnSectorEntry( TRUE );
535 
536 	// cleat list
537 	RemoveAllPlayersFromSlot( );
538 
539 	// set all auto faces inactive
540 	SetAllAutoFacesInactive( );
541 
542 	gfPausedTacticalRenderInterfaceFlags = DIRTYLEVEL2;
543 
544 	if( iCurrentTacticalSquad != NO_CURRENT_SQUAD )
545 	{
546 		FOR_EACH_IN_SQUAD(i, iCurrentTacticalSquad)
547 		{
548 			// squad set, now add soldiers in
549 			CheckForAndAddMercToTeamPanel(*i);
550 		}
551 
552 		for (INT32 iCounter = 0; iCounter < NUMBER_OF_SOLDIERS_PER_SQUAD; ++iCounter)
553 		{
554 			const INT16 dead_id = sDeadMercs[iCurrentTacticalSquad][iCounter];
555 			if (dead_id == -1) continue;
556 
557 			SOLDIERTYPE* const dead_soldier = FindSoldierByProfileIDOnPlayerTeam(dead_id);
558 			if (!dead_soldier) continue;
559 
560 			// squad set, now add soldiers in
561 			CheckForAndAddMercToTeamPanel(dead_soldier);
562 		}
563 	}
564 }
565 
566 
ExamineCurrentSquadLights(void)567 void ExamineCurrentSquadLights( void )
568 {
569 	// OK, we should add lights for any guy currently bInSector who is not bad OKLIFE...
570 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
571 	{
572 		if (s->bInSector && s->bLife >= OKLIFE) PositionSoldierLight(s);
573 	}
574 }
575 
576 
IsSquadOnCurrentTacticalMap(INT32 iCurrentSquad)577 BOOLEAN IsSquadOnCurrentTacticalMap( INT32 iCurrentSquad )
578 {
579 	// check to see if this squad is on the current map
580 
581 	// check if valid value passed
582 	if( ( iCurrentSquad >= NUMBER_OF_SQUADS ) || ( iCurrentSquad < 0 ) )
583 	{
584 		// no
585 		return ( FALSE );
586 	}
587 
588 	// go through memebrs of squad...if anyone on this map, return true
589 	FOR_EACH_IN_SQUAD(i, iCurrentSquad)
590 	{
591 		SOLDIERTYPE const* const s = *i;
592 		// ATE; Added more checks here for being in sector ( fBetweenSectors and SectorZ )
593 		if (s->sSectorX == gWorldSectorX  &&
594 				s->sSectorY == gWorldSectorY  &&
595 				s->bSectorZ == gbWorldSectorZ &&
596 				!s->fBetweenSectors)
597 		{
598 			return( TRUE );
599 		}
600 	}
601 
602 	return( FALSE );
603 }
604 
605 
SetDefaultSquadOnSectorEntry(BOOLEAN fForce)606 void SetDefaultSquadOnSectorEntry( BOOLEAN fForce )
607 {
608 	INT32 iCounter = 0;
609 	// check if selected squad is in current sector, if so, do nothing, if not...first first case that they are
610 
611 	if (IsSquadOnCurrentTacticalMap(iCurrentTacticalSquad))
612 	{
613 		// is in sector, leave
614 		return;
615 	}
616 
617 	//otherwise...
618 
619 	// find first squad availiable
620 	for( iCounter = 0; iCounter < NUMBER_OF_SQUADS; iCounter++ )
621 	{
622 		if (IsSquadOnCurrentTacticalMap(iCounter))
623 		{
624 			// squad in sector...set as current
625 			SetCurrentSquad( iCounter, fForce );
626 
627 			return;
628 		}
629 	}
630 
631 	// If here, set to no current squad
632 	SetCurrentSquad( NO_CURRENT_SQUAD, FALSE );
633 }
634 
GetLastSquadActive(void)635 INT32 GetLastSquadActive( void )
636 {
637 	// find id of last squad in the list with active mercs in it
638 	INT32 iCounter = 0;
639 	INT32 iLastSquad = 0;
640 
641 	for( iCounter = 0; iCounter < NUMBER_OF_SQUADS; iCounter++ )
642 	{
643 		FOR_EACH_IN_SQUAD(i, iCounter)
644 		{
645 			iLastSquad = iCounter;
646 			break;
647 		}
648 	}
649 
650 	return ( iLastSquad );
651 }
652 
653 
SaveSquadInfoToSavedGameFile(HWFILE const f)654 void SaveSquadInfoToSavedGameFile(HWFILE const f)
655 {
656 	// Save the squad info to the Saved Game File
657 	BYTE data[NUMBER_OF_SQUADS * NUMBER_OF_SOLDIERS_PER_SQUAD * 12];
658 	DataWriter d{data};
659 	for (INT32 squad = 0; squad < NUMBER_OF_SQUADS; ++squad)
660 	{
661 		FOR_EACH_SLOT_IN_SQUAD(slot, squad)
662 		{
663 			SOLDIERTYPE const* const s = *slot;
664 			INJ_I16(d, s ? s->ubID : -1)
665 			INJ_SKIP(d, 10)
666 		}
667 	}
668 	Assert(d.getConsumed() == lengthof(data));
669 	FileWrite(f, data, sizeof(data));
670 
671 	// Save all the squad movement IDs
672 	FileWrite(f, SquadMovementGroups, sizeof(SquadMovementGroups));
673 }
674 
675 
LoadSquadInfoFromSavedGameFile(HWFILE const f)676 void LoadSquadInfoFromSavedGameFile(HWFILE const f)
677 {
678 	// Load in the squad info
679 	BYTE data[NUMBER_OF_SQUADS * NUMBER_OF_SOLDIERS_PER_SQUAD * 12];
680 	DataReader d{data};
681 	FileRead(f, data, sizeof(data));
682 	for (INT32 squad = 0; squad != NUMBER_OF_SQUADS; ++squad)
683 	{
684 		FOR_EACH_SLOT_IN_SQUAD(slot, squad)
685 		{
686 			INT16 id;
687 			EXTR_I16(d, id)
688 			EXTR_SKIP(d, 10)
689 			*slot = id != -1 ? &GetMan(id) : 0;
690 		}
691 	}
692 	Assert(d.getConsumed() == lengthof(data));
693 
694 	// Load in the Squad movement IDs
695 	FileRead(f, SquadMovementGroups, sizeof(SquadMovementGroups));
696 }
697 
698 
IsThisSquadOnTheMove(INT8 bSquadValue)699 BOOLEAN IsThisSquadOnTheMove( INT8 bSquadValue )
700 {
701 	FOR_EACH_IN_SQUAD(i, bSquadValue)
702 	{
703 		return (*i)->fBetweenSectors;
704 	}
705 	return FALSE;
706 }
707 
708 
709 // rebuild this squad after someone has been removed, to 'squeeze' together any empty spots
RebuildSquad(INT8 const squad_id)710 static void RebuildSquad(INT8 const squad_id)
711 {
712 	SOLDIERTYPE** const squad = Squad[squad_id];
713 	SOLDIERTYPE** const end   = endof(Squad[squad_id]);
714 	SOLDIERTYPE**       dst   = squad;
715 	for (SOLDIERTYPE** i = squad; i != end; ++i)
716 	{
717 		if (!*i) continue;
718 		*dst++ = *i;
719 	}
720 	for (; dst != end; ++dst) *dst = 0;
721 }
722 
723 
724 // update current merc selected in tactical
UpdateCurrentlySelectedMerc(SOLDIERTYPE * pSoldier,INT8 bSquadValue)725 static void UpdateCurrentlySelectedMerc(SOLDIERTYPE* pSoldier, INT8 bSquadValue)
726 {
727 	// if this squad is the current one and and the psoldier is the currently selected soldier, get rid of 'em
728 	if( bSquadValue != iCurrentTacticalSquad )
729 	{
730 		return;
731 	}
732 
733 	// Are we the selected guy?
734 	if (GetSelectedMan() == pSoldier)
735 	{
736 		SOLDIERTYPE* const next = FindNextActiveAndAliveMerc(pSoldier, FALSE, FALSE);
737 		if (next != pSoldier)
738 		{
739 			SelectSoldier(next, SELSOLDIER_NONE);
740 		}
741 		else
742 		{
743 			SetSelectedMan(NULL);
744 
745 			// ATE: Make sure we are in TEAM panel at this point!
746 			SetCurrentInterfacePanel( TEAM_PANEL );
747 		}
748 	}
749 }
750 
751 
IsDeadGuyOnSquad(const ProfileID pid,const INT8 squad)752 static BOOLEAN IsDeadGuyOnSquad(const ProfileID pid, const INT8 squad)
753 {
754 	for (INT32 i = 0; i < NUMBER_OF_SOLDIERS_PER_SQUAD; ++i)
755 	{
756 		if (sDeadMercs[squad][i] == pid) return TRUE;
757 	}
758 	return FALSE;
759 }
760 
761 
762 static BOOLEAN IsDeadGuyOnAnySquad(SOLDIERTYPE* pSoldier);
763 
764 
AddDeadCharacterToSquadDeadGuys(SOLDIERTYPE * pSoldier,INT32 iSquadValue)765 static BOOLEAN AddDeadCharacterToSquadDeadGuys(SOLDIERTYPE* pSoldier, INT32 iSquadValue)
766 {
767 	INT32 iCounter = 0;
768 
769 	// is dead guy in any squad
770 	if (IsDeadGuyOnAnySquad(pSoldier)) return TRUE;
771 
772 	if (IsDeadGuyOnSquad(pSoldier->ubProfile, iSquadValue)) return TRUE;
773 
774 	// now insert the guy
775 	for( iCounter = 0; iCounter < NUMBER_OF_SOLDIERS_PER_SQUAD; iCounter++ )
776 	{
777 		const INT16 dead_id = sDeadMercs[iSquadValue][iCounter];
778 		if (dead_id == -1 || FindSoldierByProfileIDOnPlayerTeam(dead_id) == NULL)
779 		{
780 			sDeadMercs[iSquadValue][iCounter] = pSoldier->ubProfile;
781 		}
782 	}
783 
784 	// no go
785 	return( FALSE );
786 }
787 
788 
IsDeadGuyOnAnySquad(SOLDIERTYPE * pSoldier)789 static BOOLEAN IsDeadGuyOnAnySquad(SOLDIERTYPE* pSoldier)
790 {
791 	// squad?
792 	for (INT32 iCounterA = 0; iCounterA < NUMBER_OF_SQUADS; ++iCounterA)
793 	{
794 		if (IsDeadGuyOnSquad(pSoldier->ubProfile, iCounterA)) return TRUE;
795 	}
796 
797 	return ( FALSE );
798 }
799 
800 
SoldierIsDeadAndWasOnSquad(SOLDIERTYPE * pSoldier,INT8 bSquadValue)801 BOOLEAN SoldierIsDeadAndWasOnSquad( SOLDIERTYPE *pSoldier, INT8 bSquadValue )
802 {
803 	return
804 		bSquadValue != NO_CURRENT_SQUAD &&
805 		IsDeadGuyOnSquad(pSoldier->ubProfile, bSquadValue);
806 }
807 
808 
ResetDeadSquadMemberList(INT32 const iSquadValue)809 void ResetDeadSquadMemberList(INT32 const iSquadValue)
810 {
811 	std::fill_n(sDeadMercs[ iSquadValue ], NUMBER_OF_SOLDIERS_PER_SQUAD, -1);
812 }
813 
814 
815 // this passed  soldier on the current squad int he tactical map
IsMercOnCurrentSquad(const SOLDIERTYPE * pSoldier)816 BOOLEAN IsMercOnCurrentSquad(const SOLDIERTYPE* pSoldier)
817 {
818 	// current squad valid?
819 	if( iCurrentTacticalSquad >= NUMBER_OF_SQUADS )
820 	{
821 		// no
822 		return( FALSE );
823 	}
824 
825 	FOR_EACH_SLOT_IN_SQUAD(i, iCurrentTacticalSquad)
826 	{
827 		if (*i == pSoldier) return TRUE;
828 	}
829 
830 	return( FALSE );
831 }
832 
NumberOfPlayerControllableMercsInSquad(INT8 bSquadValue)833 INT8 NumberOfPlayerControllableMercsInSquad( INT8 bSquadValue )
834 {
835 	INT8 bSquadCount = 0;
836 
837 	if( bSquadValue == NO_CURRENT_SQUAD )
838 	{
839 		return( 0 );
840 	}
841 
842 	// find number of characters in particular squad.
843 	FOR_EACH_IN_SQUAD(i, bSquadValue)
844 	{
845 		SOLDIERTYPE const* const pSoldier = *i;
846 		//Kris:  This breaks the CLIENT of this function, tactical traversal.
847 		//       Do NOT check for EPCS or ROBOT here.
848 		//if ( !AM_AN_EPC( pSoldier ) && !AM_A_ROBOT( pSoldier ) &&
849 		if( !( pSoldier->uiStatusFlags & SOLDIER_VEHICLE ) )
850 		{
851 			bSquadCount++;
852 		}
853 	}
854 
855 	// return number found
856 	return( bSquadCount );
857 }
858 
859 
DoesVehicleExistInSquad(INT8 bSquadValue)860 BOOLEAN DoesVehicleExistInSquad( INT8 bSquadValue )
861 {
862 	if( bSquadValue == NO_CURRENT_SQUAD )
863 	{
864 		return( FALSE );
865 	}
866 
867 	// find number of characters in particular squad.
868 	FOR_EACH_IN_SQUAD(i, bSquadValue)
869 	{
870 		if ((*i)->uiStatusFlags & SOLDIER_VEHICLE) return TRUE;
871 	}
872 
873 	return(FALSE );
874 }
875 
876 
CheckSquadMovementGroups()877 void CheckSquadMovementGroups()
878 {
879 	FOR_EACH(INT8, i, SquadMovementGroups)
880 	{
881 		if (GetGroup(*i)) continue;
882 
883 		// recreate group
884 		GROUP* const g = CreateNewPlayerGroupDepartingFromSector(1, 1);
885 		g->fPersistant = TRUE;
886 		*i = g->ubGroupID;
887 	}
888 }
889