1 #include <stdexcept>
2 
3 #include "Directories.h"
4 #include "LoadSaveBasicSoldierCreateStruct.h"
5 #include "LoadSaveSoldierCreate.h"
6 #include "Types.h"
7 #include "StrategicMap.h"
8 #include "Overhead.h"
9 #include "Soldier_Add.h"
10 #include "Soldier_Create.h"
11 #include "Soldier_Init_List.h"
12 #include "Debug.h"
13 #include "Random.h"
14 #include "Items.h"
15 #include "Map_Information.h"
16 #include "Soldier_Profile.h"
17 #include "Sys_Globals.h"
18 #include "EditorMercs.h"
19 #include "Animation_Data.h"
20 #include "Message.h"
21 #include "Font_Control.h"
22 #include "Sound_Control.h"
23 #include "Quests.h"
24 #include "Render_Fun.h"
25 #include "Meanwhile.h"
26 #include "Strategic_AI.h"
27 #include "Map_Screen_Interface_Map.h"
28 #include "Inventory_Choosing.h"
29 #include "Campaign_Types.h"
30 #include "AI.h"
31 #include "NPC.h"
32 #include "Scheduling.h"
33 #include "MemMan.h"
34 #include "FileMan.h"
35 #include "Logger.h"
36 #include "MercProfile.h"
37 
38 #include "ContentManager.h"
39 #include "GameInstance.h"
40 #include "externalized/strategic/BloodCatSpawnsModel.h"
41 
42 BOOLEAN gfOriginalList = TRUE;
43 
44 SOLDIERINITNODE *gSoldierInitHead = NULL;
45 SOLDIERINITNODE *gSoldierInitTail = NULL;
46 
47 SOLDIERINITNODE *gOriginalSoldierInitListHead = NULL;
48 SOLDIERINITNODE *gAlternateSoldierInitListHead = NULL;
49 
InitSoldierInitList()50 void InitSoldierInitList()
51 {
52 	if( gSoldierInitHead )
53 		KillSoldierInitList();
54 	gSoldierInitHead = NULL;
55 	gSoldierInitTail = NULL;
56 }
57 
KillSoldierInitList()58 void KillSoldierInitList()
59 {
60 	while( gSoldierInitHead )
61 		RemoveSoldierNodeFromInitList( gSoldierInitTail );
62 	if( gfOriginalList )
63 		gOriginalSoldierInitListHead = NULL;
64 	else
65 		gAlternateSoldierInitListHead = NULL;
66 }
67 
68 
AddBasicPlacementToSoldierInitList(BASIC_SOLDIERCREATE_STRUCT const & bp)69 SOLDIERINITNODE* AddBasicPlacementToSoldierInitList(BASIC_SOLDIERCREATE_STRUCT const& bp)
70 {
71 	SOLDIERINITNODE* const si = new SOLDIERINITNODE{};
72 
73 	si->pBasicPlacement  = new BASIC_SOLDIERCREATE_STRUCT{};
74 	*si->pBasicPlacement = bp;
75 
76 	// It is impossible to set up detailed placement stuff now. If there is any
77 	// detailed placement information during map load, it will be added
78 	// immediately after this function call.
79 	si->pDetailedPlacement = 0;
80 	si->pSoldier           = 0;
81 	si->next               = 0;
82 	si->prev               = gSoldierInitTail;
83 
84 	// Insert the new node in the list in its proper place
85 	if (!gSoldierInitTail)
86 	{
87 		gSoldierInitHead = si;
88 		if (gfOriginalList)
89 			gOriginalSoldierInitListHead = si;
90 		else
91 			gAlternateSoldierInitListHead = si;
92 	}
93 	else
94 	{
95 		// TEMP: no sorting, just enemies
96 		gSoldierInitTail->next = si;
97 	}
98 	gSoldierInitTail = si;
99 
100 	if (gfOriginalList) ++gMapInformation.ubNumIndividuals;
101 	return si;
102 }
103 
104 
RemoveSoldierNodeFromInitList(SOLDIERINITNODE * pNode)105 void RemoveSoldierNodeFromInitList( SOLDIERINITNODE *pNode )
106 {
107 	if( !pNode )
108 		return;
109 	if( gfOriginalList )
110 		gMapInformation.ubNumIndividuals--;
111 	if( pNode->pBasicPlacement )
112 	{
113 		delete pNode->pBasicPlacement;
114 		pNode->pBasicPlacement = NULL;
115 	}
116 	if( pNode->pDetailedPlacement )
117 	{
118 		delete pNode->pDetailedPlacement;
119 		pNode->pDetailedPlacement = NULL;
120 	}
121 	if( pNode->pSoldier )
122 	{
123 		if( pNode->pSoldier->ubID >= 20 )
124 		{
125 			TacticalRemoveSoldier(*pNode->pSoldier);
126 		}
127 	}
128 	if( pNode == gSoldierInitHead )
129 	{
130 		gSoldierInitHead = gSoldierInitHead->next;
131 		if( gSoldierInitHead )
132 			gSoldierInitHead->prev = NULL;
133 		if( gfOriginalList )
134 			gOriginalSoldierInitListHead = gSoldierInitHead;
135 		else
136 			gAlternateSoldierInitListHead = gSoldierInitHead;
137 	}
138 	else if( pNode == gSoldierInitTail )
139 	{
140 		gSoldierInitTail = gSoldierInitTail->prev;
141 		gSoldierInitTail->next = NULL;
142 	}
143 	else
144 	{
145 		pNode->prev->next = pNode->next;
146 		pNode->next->prev = pNode->prev;
147 	}
148 	delete pNode;
149 }
150 
151 
152 //These serialization functions are assuming the passing of a valid file
153 //pointer to the beginning of the save/load area, which is not necessarily at
154 //the beginning of the file.  This is just a part of the whole map serialization.
SaveSoldiersToMap(HWFILE fp)155 BOOLEAN SaveSoldiersToMap( HWFILE fp )
156 {
157 	UINT32 i;
158 	SOLDIERINITNODE *curr;
159 
160 	if( !fp )
161 		return FALSE;
162 
163 	if (gMapInformation.ubNumIndividuals > MAX_NUM_SOLDIERS)
164 		return FALSE;
165 
166 	//If we are perhaps in the alternate version of the editor, we don't want bad things to
167 	//happen.  This is probably the only place I know where the user gets punished now.  If the
168 	//person was in the alternate editor mode, then decided to save the game, the current mercs may
169 	//not be there.  This would be bad.  What we do is override any merc editing done while in this
170 	//mode, and kill them all, while replacing them with the proper ones.  Not only that, the alternate
171 	//editing mode is turned off, and if intentions are to play the game, the user will be facing many
172 	//enemies!
173 	if (!gfOriginalList) ResetAllMercPositions();
174 
175 	curr = gSoldierInitHead;
176 	for( i=0; i < gMapInformation.ubNumIndividuals; i++ )
177 	{
178 		if( !curr )
179 			return FALSE;
180 		curr->ubNodeID = (UINT8)i;
181 		InjectBasicSoldierCreateStructIntoFile(fp, *curr->pBasicPlacement);
182 
183 		if( curr->pBasicPlacement->fDetailedPlacement )
184 		{
185 			if( !curr->pDetailedPlacement )
186 				return FALSE;
187 			InjectSoldierCreateIntoFile(fp, curr->pDetailedPlacement);
188 		}
189 		curr = curr->next;
190 	}
191 	return TRUE;
192 }
193 
194 
LoadSoldiersFromMap(HWFILE const f,bool stracLinuxFormat)195 void LoadSoldiersFromMap(HWFILE const f, bool stracLinuxFormat)
196 {
197 	UINT8 const n_individuals = gMapInformation.ubNumIndividuals;
198 
199 	UseEditorAlternateList();
200 	KillSoldierInitList();
201 	UseEditorOriginalList();
202 	KillSoldierInitList();
203 	InitSoldierInitList();
204 
205 	if (n_individuals > MAX_NUM_SOLDIERS)
206 	{
207 		throw std::runtime_error("Corrupt map check failed. ubNumIndividuals is greater than MAX_NUM_SOLDIERS.");
208 	}
209 
210 	// Because we are loading the map, we needed to know how many guys are being
211 	// loaded, but when we add them to the list here, it automatically increments
212 	// that number, effectively doubling it, which would be a problem. Now that we
213 	// know the number, we clear it here, so it gets built again.
214 	gMapInformation.ubNumIndividuals = 0; // Must be cleared here
215 
216 	bool cow_in_sector = false;
217 	for (UINT32 i = 0; i != n_individuals; ++i)
218 	{
219 		BASIC_SOLDIERCREATE_STRUCT bp;
220 		ExtractBasicSoldierCreateStructFromFile(f, bp);
221 		SOLDIERINITNODE* const n = AddBasicPlacementToSoldierInitList(bp);
222 		n->ubNodeID = i;
223 
224 		if (bp.fDetailedPlacement)
225 		{
226 			// Add the static detailed placement information in the same newly created
227 			// node as the basic placement.
228 			SOLDIERCREATE_STRUCT* const sc = new SOLDIERCREATE_STRUCT{};
229 			ExtractSoldierCreateFromFile(f, sc, stracLinuxFormat);
230 
231 			if (sc->ubProfile != NO_PROFILE)
232 			{
233 				UINT8 const civ_group = GetProfile(sc->ubProfile).ubCivilianGroup;
234 				sc->ubCivilianGroup                 = civ_group;
235 				n->pBasicPlacement->ubCivilianGroup = civ_group;
236 			}
237 
238 			n->pDetailedPlacement = sc;
239 		}
240 
241 		if (bp.bBodyType == COW) cow_in_sector = true;
242 	}
243 
244 	if (cow_in_sector)
245 	{
246 		char str[40];
247 		sprintf(str, SOUNDSDIR "/cowmoo%d.wav", Random(3) + 1);
248 		PlayJA2SampleFromFile(str, MIDVOLUME, 1, MIDDLEPAN);
249 	}
250 }
251 
252 
253 //Because soldiers, creatures, etc., maybe added to the game at anytime theoretically, the
254 //list will need to be sorted to reflect this.  It is quite likely that this won't be needed,
255 //but the flexibility is there just incase.  Now the list is sorted in the following manner:
256 //-1st priority:  Any nodes containing valid pointers to soldiers are moved to the end of the list.
257 //                We don't ever want to use two identical placements.
258 //-2nd priority:  Any nodes that have priority existance and detailed placement information are
259 //                put first in the list.
260 //-3rd priority:  Any nodes that have priority existance and no detailed placement information are used next.
261 //-4th priority:  Any nodes that have detailed placement and no priority existance information are used next.
262 //-5th priority:  The rest of the nodes are basic placements and are placed in the center of the list.  Of
263 //                these, they are randomly filled based on the number needed.
264 //NOTE:  This function is called by AddSoldierInitListTeamToWorld().  There is no other place it needs to
265 //       be called.
SortSoldierInitList(void)266 static void SortSoldierInitList(void)
267 {
268 	SOLDIERINITNODE *temp, *curr;
269 
270 	if( !gSoldierInitHead )
271 		return;
272 
273 	//1st priority sort
274 	curr = gSoldierInitTail;
275 	while( curr )
276 	{
277 		if( curr->pSoldier && curr != gSoldierInitTail )
278 		{
279 			//This node has an existing soldier, so move to end of list.
280 			//copy node
281 			temp = curr;
282 			if( temp == gSoldierInitHead )
283 			{
284 				//If we dealing with the head, we need to move it now.
285 				gSoldierInitHead = gSoldierInitHead->next;
286 				if( gfOriginalList )
287 					gOriginalSoldierInitListHead = gSoldierInitHead;
288 				else
289 					gAlternateSoldierInitListHead = gSoldierInitHead;
290 				gSoldierInitHead->prev = NULL;
291 				temp->next = NULL;
292 			}
293 			curr = curr->prev;
294 			//detach node from list
295 			if( temp->prev )
296 				temp->prev->next = temp->next;
297 			if( temp->next )
298 				temp->next->prev = temp->prev;
299 			//add node to end of list
300 			temp->prev = gSoldierInitTail;
301 			temp->next = NULL;
302 			gSoldierInitTail->next = temp;
303 			gSoldierInitTail = temp;
304 		}
305 		else
306 		{
307 			curr = curr->prev;
308 		}
309 	}
310 	//4th -- put to start
311 	curr = gSoldierInitHead;
312 	while( curr )
313 	{
314 		if( !curr->pSoldier && !curr->pBasicPlacement->fPriorityExistance && curr->pDetailedPlacement && curr != gSoldierInitHead )
315 		{
316 			//Priority existance nodes without detailed placement info are moved to beginning of list
317 			//copy node
318 			temp = curr;
319 			if( temp == gSoldierInitTail )
320 			{
321 				//If we dealing with the tail, we need to move it now.
322 				gSoldierInitTail = gSoldierInitTail->prev;
323 				gSoldierInitTail->next = NULL;
324 				temp->prev = NULL;
325 			}
326 			curr = curr->next;
327 			//detach node from list
328 			if( temp->prev )
329 				temp->prev->next = temp->next;
330 			if( temp->next )
331 				temp->next->prev = temp->prev;
332 			//add node to beginning of list
333 			temp->prev = NULL;
334 			temp->next = gSoldierInitHead;
335 			gSoldierInitHead->prev = temp;
336 			gSoldierInitHead = temp;
337 			if( gfOriginalList )
338 				gOriginalSoldierInitListHead = gSoldierInitHead;
339 			else
340 				gAlternateSoldierInitListHead = gSoldierInitHead;
341 		}
342 		else
343 		{
344 			curr = curr->next;
345 		}
346 	}
347 	//3rd priority sort (see below for reason why we do 2nd after 3rd)
348 	curr = gSoldierInitHead;
349 	while( curr )
350 	{
351 		if( !curr->pSoldier && curr->pBasicPlacement->fPriorityExistance && !curr->pDetailedPlacement && curr != gSoldierInitHead )
352 		{
353 			//Priority existance nodes without detailed placement info are moved
354 			//to beginning of list copy node
355 			temp = curr;
356 			if( temp == gSoldierInitTail )
357 			{
358 				//If we dealing with the tail, we need to move it now.
359 				gSoldierInitTail = gSoldierInitTail->prev;
360 				gSoldierInitTail->next = NULL;
361 				temp->prev = NULL;
362 			}
363 			curr = curr->next;
364 			//detach node from list
365 			if( temp->prev )
366 				temp->prev->next = temp->next;
367 			if( temp->next )
368 				temp->next->prev = temp->prev;
369 			//add node to beginning of list
370 			temp->prev = NULL;
371 			temp->next = gSoldierInitHead;
372 			gSoldierInitHead->prev = temp;
373 			gSoldierInitHead = temp;
374 			if( gfOriginalList )
375 				gOriginalSoldierInitListHead = gSoldierInitHead;
376 			else
377 				gAlternateSoldierInitListHead = gSoldierInitHead;
378 		}
379 		else
380 		{
381 			curr = curr->next;
382 		}
383 	}
384 	//2nd priority sort (by adding these to the front, it'll be before the
385 	//3rd priority sort.  This is why we do it after.
386 	curr = gSoldierInitHead;
387 	while( curr )
388 	{
389 		if( !curr->pSoldier && curr->pBasicPlacement->fPriorityExistance && curr->pDetailedPlacement && curr != gSoldierInitHead )
390 		{	//Priority existance nodes are moved to beginning of list
391 			//copy node
392 			temp = curr;
393 			if( temp == gSoldierInitTail )
394 			{
395 				//If we dealing with the tail, we need to move it now.
396 				gSoldierInitTail = gSoldierInitTail->prev;
397 				gSoldierInitTail->next = NULL;
398 				temp->prev = NULL;
399 			}
400 			curr = curr->next;
401 			//detach node from list
402 			if( temp->prev )
403 				temp->prev->next = temp->next;
404 			if( temp->next )
405 				temp->next->prev = temp->prev;
406 			//add node to beginning of list
407 			temp->prev = NULL;
408 			temp->next = gSoldierInitHead;
409 			gSoldierInitHead->prev = temp;
410 			gSoldierInitHead = temp;
411 			if( gfOriginalList )
412 				gOriginalSoldierInitListHead = gSoldierInitHead;
413 			else
414 				gAlternateSoldierInitListHead = gSoldierInitHead;
415 		}
416 		else
417 		{
418 			curr = curr->next;
419 		}
420 	}
421 	//4th priority sort
422 	//Done!  If the soldier existing slots are at the end of the list and the
423 	//       priority placements are at the beginning of the list, then the
424 	//       basic placements are in the middle.
425 }
426 
427 
AddPlacementToWorld(SOLDIERINITNODE * const init)428 bool AddPlacementToWorld(SOLDIERINITNODE* const init)
429 {
430 	SOLDIERCREATE_STRUCT dp;
431 	dp = SOLDIERCREATE_STRUCT{};
432 
433 	// First check if this guy has a profile and if so check his location such that it matches
434 	if (SOLDIERCREATE_STRUCT* const init_dp = init->pDetailedPlacement)
435 	{
436 		if (!gfEditMode)
437 		{
438 			ProfileID const pid = init_dp->ubProfile;
439 			if (pid != NO_PROFILE)
440 			{
441 				MERCPROFILESTRUCT& p = GetProfile(pid);
442 				if (p.sSectorX != gWorldSectorX)  return false;
443 				if (p.sSectorY != gWorldSectorY)  return false;
444 				if (p.bSectorZ != gbWorldSectorZ) return false;
445 				if (p.ubMiscFlags & (PROFILE_MISC_FLAG_RECRUITED | PROFILE_MISC_FLAG_EPCACTIVE)) return false;
446 				if (p.bLife == 0)                 return false;
447 				if (p.fUseProfileInsertionInfo)   return false;
448 			}
449 
450 			// Special case code when adding icecream truck.
451 			// CJC, August 18, 1999: don't do this code unless the ice cream truck is on our team
452 			if (init_dp->bBodyType == ICECREAMTRUCK &&
453 				FindSoldierByProfileIDOnPlayerTeam(ICECREAMTRUCK))
454 			{
455 				// Check to see if Hamous is here and not recruited. If so, add truck
456 				MERCPROFILESTRUCT& hamous = GetProfile(HAMOUS);
457 				// If not here, do not add
458 				if (hamous.sSectorX != gWorldSectorX) return true;
459 				if (hamous.sSectorY != gWorldSectorY) return true;
460 				if (hamous.bSectorZ != 0)             return true;
461 				// Check to make sure he isn't recruited.
462 				if (hamous.ubMiscFlags & PROFILE_MISC_FLAG_RECRUITED) return true;
463 			}
464 		}
465 		CreateDetailedPlacementGivenStaticDetailedPlacementAndBasicPlacementInfo(&dp, init_dp, init->pBasicPlacement);
466 	}
467 	else
468 	{
469 		CreateDetailedPlacementGivenBasicPlacementInfo(&dp, init->pBasicPlacement);
470 	}
471 
472 	if (!gfEditMode)
473 	{
474 		if (dp.bTeam == CIV_TEAM)
475 		{
476 			// Quest-related overrides
477 			INT16 const x = gWorldSectorX;
478 			INT16 const y = gWorldSectorY;
479 			INT8  const z = gbWorldSectorZ;
480 			if (x == 5 && y == MAP_ROW_C)
481 			{
482 				// Kinpin guys might be guarding Tony
483 				if (dp.ubCivilianGroup == KINGPIN_CIV_GROUP && (
484 					gTacticalStatus.fCivGroupHostile[KINGPIN_CIV_GROUP] == CIV_GROUP_WILL_BECOME_HOSTILE || (
485 					gubQuest[QUEST_KINGPIN_MONEY] == QUESTINPROGRESS &&
486 					CheckFact(FACT_KINGPIN_CAN_SEND_ASSASSINS, KINGPIN))))
487 				{
488 					if (dp.ubProfile == NO_PROFILE)
489 					{
490 						// These guys should be guarding Tony
491 						dp.sInsertionGridNo = 13531 +
492 							PreRandom(8) * (PreRandom(1) ? -1 : 1) +
493 							PreRandom(8) * (PreRandom(1) ? -1 : 1) * WORLD_ROWS;
494 
495 						switch (PreRandom(3))
496 						{
497 							case 0: dp.bOrders = ONGUARD;     break;
498 							case 1: dp.bOrders = CLOSEPATROL; break;
499 							case 2: dp.bOrders = ONCALL;      break;
500 						}
501 					}
502 					else if (dp.ubProfile == BILLY)
503 					{
504 						// Billy should now be able to roam around
505 						dp.sInsertionGridNo = 13531 +
506 							PreRandom(30) * (PreRandom(1) ? -1 : 1) +
507 							PreRandom(30) * (PreRandom(1) ? -1 : 1) * WORLD_ROWS;
508 						dp.bOrders = SEEKENEMY;
509 					}
510 					else if (dp.ubProfile == MADAME)
511 					{
512 						// She shouldn't be here
513 						return true;
514 					}
515 					else if (dp.ubProfile == NO_PROFILE)
516 					{
517 						// XXX unreachable due to same condition above
518 						UINT8 const room = GetRoom(dp.sInsertionGridNo);
519 						if (IN_BROTHEL(room))
520 						{
521 							// Must be a hooker, shouldn't be here
522 							return true;
523 						}
524 					}
525 				}
526 			}
527 			else if (x == 3 && y == MAP_ROW_P && z == 0 && !gfInMeanwhile)
528 			{
529 				// Special civilian setup for queen's palace
530 				if (gubFact[FACT_QUEEN_DEAD])
531 				{
532 					// The queen's civs aren't added if queen is dead
533 					if (dp.ubCivilianGroup == QUEENS_CIV_GROUP) return true;
534 				}
535 				else
536 				{
537 					if (gfUseAlternateQueenPosition && dp.ubProfile == QUEEN)
538 					{
539 						dp.sInsertionGridNo = 11448;
540 					}
541 					if (dp.ubCivilianGroup != QUEENS_CIV_GROUP)
542 					{
543 						// The free civilians aren't added if queen is alive
544 						return true;
545 					}
546 				}
547 			}
548 			else if (x == TIXA_SECTOR_X && y == TIXA_SECTOR_Y && z == 0)
549 			{
550 				// Tixa prison, once liberated, should not have any civs without
551 				// profiles unless they are kids
552 				if (!StrategicMap[TIXA_SECTOR_X + TIXA_SECTOR_Y * MAP_WORLD_X].fEnemyControlled &&
553 					dp.ubProfile == NO_PROFILE &&
554 					dp.bBodyType != HATKIDCIV &&
555 					dp.bBodyType != KIDCIV)
556 				{
557 					// not there
558 					return true;
559 				}
560 			}
561 			else if (x == 13 && y == MAP_ROW_C && z == 0)
562 			{
563 				if (CheckFact(FACT_KIDS_ARE_FREE, 0) &&
564 					(dp.bBodyType == HATKIDCIV || dp.bBodyType == KIDCIV))
565 				{
566 					// Not there any more, kids have been freed.
567 					return true;
568 				}
569 			}
570 		}
571 		else if (dp.bTeam == ENEMY_TEAM && dp.ubSoldierClass == SOLDIER_CLASS_ELITE)
572 		{
573 			// Special! Certain events in the game can cause profiled NPCs to become
574 			// enemies. The two cases are adding Mike and Iggy. We will only add one
575 			// NPC in any given combat and the conditions for setting the associated
576 			// facts are done elsewhere. There is also another place where NPCs can
577 			// get added, which is in TacticalCreateElite() used for inserting
578 			// offensive enemies.
579 			OkayToUpgradeEliteToSpecialProfiledEnemy(&dp);
580 		}
581 	}
582 
583 	if (SOLDIERTYPE* const s = TacticalCreateSoldier(dp))
584 	{
585 		init->pSoldier    = s;
586 		init->ubSoldierID = s->ubID;
587 		AddSoldierToSectorNoCalculateDirection(s);
588 		return true;
589 	}
590 	else
591 	{
592 		SLOGD(
593 			"Failed to create soldier using TacticalCreateSoldier within AddPlacementToWorld");
594 		return false;
595 	}
596 }
597 
598 
AddSoldierInitListTeamToWorld(INT8 const team)599 void AddSoldierInitListTeamToWorld(INT8 const team)
600 {
601 	// Sort the list in the following manner:
602 	// - Priority placements first
603 	// - Basic placements next
604 	// - Any placements with existing soldiers last (overrides others)
605 	SortSoldierInitList();
606 
607 	if (giCurrentTilesetID == CAVES_1) // Cave/mine tileset only
608 	{
609 		// Convert all civilians to miners which use uniforms and more masculine
610 		// body types.
611 		CFOR_EACH_SOLDIERINITNODE(i)
612 		{
613 			BASIC_SOLDIERCREATE_STRUCT& bp = *i->pBasicPlacement;
614 			if (bp.bTeam != CIV_TEAM || i->pDetailedPlacement) continue;
615 			bp.ubSoldierClass = SOLDIER_CLASS_MINER;
616 			bp.bBodyType      = BODY_RANDOM;
617 		}
618 	}
619 
620 	// While we have a list, with no active soldiers, process the list to add new
621 	// soldiers.
622 	for (SOLDIERINITNODE* i = gSoldierInitHead; i && !i->pSoldier; i = i->next)
623 	{
624 		if (i->pBasicPlacement->bTeam != team) continue;
625 
626 		// mgl: Missing civilians Fix
627 		// AddPlacementToWorld() returns false for people (civilians) who have a profile
628 		// but are not currently in the sector of the loaded map. It doesn't mean that
629 		// there isn't any remaining slot for them in this case.
630 		if (!AddPlacementToWorld(i) && i->pBasicPlacement->bTeam != CIV_TEAM)
631 		{
632 			// If it fails to create the soldier, it is likely that it is because the
633 			// slots in the tactical engine are already full. Besides, the strategic
634 			// AI shouldn't be trying to fill a map with more than the maximum
635 			// allowable soldiers of team. All teams can have a max of 32 individuals,
636 			// except for the player which is 20. Players aren't processed in this
637 			// list anyway.
638 			break;
639 		}
640 	}
641 }
642 
643 
AddSoldierInitListEnemyDefenceSoldiers(UINT8 ubTotalAdmin,UINT8 ubTotalTroops,UINT8 ubTotalElite)644 void AddSoldierInitListEnemyDefenceSoldiers( UINT8 ubTotalAdmin, UINT8 ubTotalTroops, UINT8 ubTotalElite )
645 {
646 	SOLDIERINITNODE *mark;
647 	INT32 iRandom;
648 	UINT8 ubMaxNum;
649 	UINT8 ubElitePDSlots = 0, ubEliteDSlots = 0, ubElitePSlots = 0, ubEliteBSlots = 0;
650 	UINT8 ubTroopPDSlots = 0, ubTroopDSlots = 0, ubTroopPSlots = 0, ubTroopBSlots = 0;
651 	UINT8 ubAdminPDSlots = 0, ubAdminDSlots = 0, ubAdminPSlots = 0, ubAdminBSlots = 0;
652 	UINT8 ubFreeSlots;
653 	UINT8 *pCurrSlots=NULL;
654 	UINT8 *pCurrTotal=NULL;
655 	UINT8 ubCurrClass;
656 
657 	ResetMortarsOnTeamCount();
658 
659 	//Specs call for only one profiled enemy can be in a sector at a time due to flavor reasons.
660 	gfProfiledEnemyAdded = FALSE;
661 
662 	//Because the enemy defence forces work differently than the regular map placements, the numbers
663 	//of each type of enemy may not be the same.  Elites will choose the best placements, then army, then
664 	//administrators.
665 
666 	ubMaxNum = ubTotalAdmin + ubTotalTroops + ubTotalElite;
667 
668 	//Sort the list in the following manner:
669 	//-Priority placements first
670 	//-Basic placements next
671 	//-Any placements with existing soldiers last (overrides others)
672 	SortSoldierInitList();
673 
674 	//Now count the number of nodes that are basic placements of desired team AND CLASS
675 	//This information will be used to randomly determine which of these placements
676 	//will be added based on the number of slots we can still add.
677 	CFOR_EACH_SOLDIERINITNODE(curr)
678 	{
679 		if (curr->pSoldier) break;
680 		if( curr->pBasicPlacement->bTeam == ENEMY_TEAM )
681 		{
682 			switch( curr->pBasicPlacement->ubSoldierClass )
683 			{
684 				case SOLDIER_CLASS_ELITE:
685 					if( curr->pBasicPlacement->fPriorityExistance && curr->pDetailedPlacement )
686 						ubElitePDSlots++;
687 					else if( curr->pBasicPlacement->fPriorityExistance )
688 						ubElitePSlots++;
689 					else if( curr->pDetailedPlacement )
690 						ubEliteDSlots++;
691 					else
692 						ubEliteBSlots++;
693 					break;
694 				case SOLDIER_CLASS_ADMINISTRATOR:
695 					if( curr->pBasicPlacement->fPriorityExistance && curr->pDetailedPlacement )
696 						ubAdminPDSlots++;
697 					else if( curr->pBasicPlacement->fPriorityExistance )
698 						ubAdminPSlots++;
699 					else if( curr->pDetailedPlacement )
700 						ubAdminDSlots++;
701 					else
702 						ubAdminBSlots++;
703 					break;
704 				case SOLDIER_CLASS_ARMY:
705 					if( curr->pBasicPlacement->fPriorityExistance && curr->pDetailedPlacement )
706 						ubTroopPDSlots++;
707 					else if( curr->pBasicPlacement->fPriorityExistance )
708 						ubTroopPSlots++;
709 					else if( curr->pDetailedPlacement )
710 						ubTroopDSlots++;
711 					else
712 						ubTroopBSlots++;
713 					break;
714 			}
715 		}
716 	}
717 
718 	//ADD PLACEMENTS WITH PRIORITY EXISTANCE WITH DETAILED PLACEMENT INFORMATION FIRST
719 	//we now have the numbers of available slots for each soldier class, so loop through three times
720 	//and randomly choose some (or all) of the matching slots to fill.  This is done randomly.
721 	for( ubCurrClass = SOLDIER_CLASS_ADMINISTRATOR; ubCurrClass <= SOLDIER_CLASS_ARMY; ubCurrClass++ )
722 	{
723 		//First, prepare the counters.
724 		switch( ubCurrClass )
725 		{
726 			case SOLDIER_CLASS_ADMINISTRATOR:
727 				pCurrSlots = &ubAdminPDSlots;
728 				pCurrTotal = &ubTotalAdmin;
729 				break;
730 			case SOLDIER_CLASS_ELITE:
731 				pCurrSlots = &ubElitePDSlots;
732 				pCurrTotal = &ubTotalElite;
733 				break;
734 			case SOLDIER_CLASS_ARMY:
735 				pCurrSlots = &ubTroopPDSlots;
736 				pCurrTotal = &ubTotalTroops;
737 				break;
738 		}
739 		//Now, loop through the priority existance and detailed placement section of the list.
740 		FOR_EACH_SOLDIERINITNODE(curr)
741 		{
742 			if (ubMaxNum == 0 ||
743 				*pCurrTotal == 0 ||
744 				*pCurrSlots == 0 ||
745 				curr->pDetailedPlacement == NULL ||
746 				!curr->pBasicPlacement->fPriorityExistance)
747 			{
748 				break;
749 			}
750 			if( !curr->pSoldier && curr->pBasicPlacement->bTeam == ENEMY_TEAM )
751 			{
752 				if( curr->pBasicPlacement->ubSoldierClass == ubCurrClass )
753 				{
754 					if( *pCurrSlots <= *pCurrTotal || Random( *pCurrSlots ) < *pCurrTotal )
755 					{
756 						//found matching team, so add this soldier to the game.
757 						if( AddPlacementToWorld( curr ) )
758 						{
759 							(*pCurrTotal)--;
760 							ubMaxNum--;
761 						}
762 						else
763 							return;
764 					}
765 					(*pCurrSlots)--;
766 					//With the decrementing of the slot vars in this manner, the chances increase so that all slots
767 					//will be full by the time the end of the list comes up.
768 				}
769 			}
770 			curr = curr->next;
771 		}
772 	}
773 	if( !ubMaxNum )
774 		return;
775 	SOLDIERINITNODE* curr = gSoldierInitHead;
776 	while( curr && curr->pDetailedPlacement && curr->pBasicPlacement->fPriorityExistance )
777 		curr = curr->next;
778 	mark = curr;
779 
780 	//ADD PLACEMENTS WITH PRIORITY EXISTANCE AND NO DETAILED PLACEMENT INFORMATION SECOND
781 	//we now have the numbers of available slots for each soldier class, so loop through three times
782 	//and randomly choose some (or all) of the matching slots to fill.  This is done randomly.
783 	for( ubCurrClass = SOLDIER_CLASS_ADMINISTRATOR; ubCurrClass <= SOLDIER_CLASS_ARMY; ubCurrClass++ )
784 	{
785 		//First, prepare the counters.
786 		switch( ubCurrClass )
787 		{
788 			case SOLDIER_CLASS_ADMINISTRATOR:
789 				pCurrSlots = &ubAdminPSlots;
790 				pCurrTotal = &ubTotalAdmin;
791 				break;
792 			case SOLDIER_CLASS_ELITE:
793 				pCurrSlots = &ubElitePSlots;
794 				pCurrTotal = &ubTotalElite;
795 				break;
796 			case SOLDIER_CLASS_ARMY:
797 				pCurrSlots = &ubTroopPSlots;
798 				pCurrTotal = &ubTotalTroops;
799 				break;
800 		}
801 		//Now, loop through the priority existance and non detailed placement section of the list.
802 		curr = mark;
803 		while( curr && ubMaxNum && *pCurrTotal && *pCurrSlots &&
804 			!curr->pDetailedPlacement && curr->pBasicPlacement->fPriorityExistance )
805 		{
806 			if( !curr->pSoldier && curr->pBasicPlacement->bTeam == ENEMY_TEAM )
807 			{
808 				if( curr->pBasicPlacement->ubSoldierClass == ubCurrClass )
809 				{
810 					if( *pCurrSlots <= *pCurrTotal || Random( *pCurrSlots ) < *pCurrTotal )
811 					{
812 						//found matching team, so add this soldier to the game.
813 						if( AddPlacementToWorld( curr ) )
814 						{
815 							(*pCurrTotal)--;
816 							ubMaxNum--;
817 						}
818 						else
819 							return;
820 					}
821 					(*pCurrSlots)--;
822 					//With the decrementing of the slot vars in this manner, the chances increase so that all slots
823 					//will be full by the time the end of the list comes up.
824 				}
825 			}
826 			curr = curr->next;
827 		}
828 	}
829 	if( !ubMaxNum )
830 		return;
831 	curr = mark;
832 	while( curr && !curr->pDetailedPlacement && curr->pBasicPlacement->fPriorityExistance )
833 		curr = curr->next;
834 	mark = curr;
835 
836 	//ADD PLACEMENTS WITH NO DETAILED PLACEMENT AND PRIORITY EXISTANCE INFORMATION SECOND
837 	//we now have the numbers of available slots for each soldier class, so loop through three times
838 	//and randomly choose some (or all) of the matching slots to fill.  This is done randomly.
839 	for( ubCurrClass = SOLDIER_CLASS_ADMINISTRATOR; ubCurrClass <= SOLDIER_CLASS_ARMY; ubCurrClass++ )
840 	{
841 		//First, prepare the counters.
842 		switch( ubCurrClass )
843 		{
844 			case SOLDIER_CLASS_ADMINISTRATOR:
845 				pCurrSlots = &ubAdminDSlots;
846 				pCurrTotal = &ubTotalAdmin;
847 				break;
848 			case SOLDIER_CLASS_ELITE:
849 				pCurrSlots = &ubEliteDSlots;
850 				pCurrTotal = &ubTotalElite;
851 				break;
852 			case SOLDIER_CLASS_ARMY:
853 				pCurrSlots = &ubTroopDSlots;
854 				pCurrTotal = &ubTotalTroops;
855 				break;
856 		}
857 		//Now, loop through the priority existance and detailed placement section of the list.
858 		curr = mark;
859 		while( curr && ubMaxNum && *pCurrTotal && *pCurrSlots &&
860 			curr->pDetailedPlacement && !curr->pBasicPlacement->fPriorityExistance )
861 		{
862 			if( !curr->pSoldier && curr->pBasicPlacement->bTeam == ENEMY_TEAM )
863 			{
864 				if( curr->pBasicPlacement->ubSoldierClass == ubCurrClass )
865 				{
866 					if( *pCurrSlots <= *pCurrTotal || Random( *pCurrSlots ) < *pCurrTotal )
867 					{
868 						//found matching team, so add this soldier to the game.
869 						if( AddPlacementToWorld( curr ) )
870 						{
871 							(*pCurrTotal)--;
872 							ubMaxNum--;
873 						}
874 						else
875 							return;
876 					}
877 					(*pCurrSlots)--;
878 					//With the decrementing of the slot vars in this manner, the chances increase so that all slots
879 					//will be full by the time the end of the list comes up.
880 				}
881 			}
882 			curr = curr->next;
883 		}
884 	}
885 	if( !ubMaxNum )
886 		return;
887 	curr = mark;
888 	while( curr && curr->pDetailedPlacement && !curr->pBasicPlacement->fPriorityExistance )
889 		curr = curr->next;
890 	mark = curr;
891 
892 	//Kris: January 11, 2000 -- NEW!!!
893 	//PRIORITY EXISTANT SLOTS MUST BE FILLED
894 	//This must be done to ensure all priority existant slots are filled before ANY other slots are filled,
895 	//even if that means changing the class of the slot.  Also, assume that there are no matching fits left
896 	//for priority existance slots.  All of the matches have been already assigned in the above passes.
897 	//We'll have to convert the soldier type of the slot to match.
898 	curr = gSoldierInitHead;
899 	while( curr && ubMaxNum && curr->pBasicPlacement->fPriorityExistance )
900 	{
901 		if( !curr->pSoldier && curr->pBasicPlacement->bTeam == ENEMY_TEAM )
902 		{
903 			//Choose which team to use.
904 			iRandom = Random( ubMaxNum );
905 			if( iRandom < ubTotalElite )
906 			{
907 				curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ELITE;
908 				ubTotalElite--;
909 			}
910 			else if( iRandom < ubTotalElite + ubTotalTroops )
911 			{
912 				curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ARMY;
913 				ubTotalTroops--;
914 			}
915 			else if( iRandom < ubTotalElite + ubTotalTroops + ubTotalAdmin )
916 			{
917 				curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ADMINISTRATOR;
918 				ubTotalAdmin--;
919 			}
920 			else
921 				SLOGA("AddSoldierInitListEnemyDefenceSoldiers: something wrong with random");
922 			if( AddPlacementToWorld( curr ) )
923 			{
924 				ubMaxNum--;
925 			}
926 			else
927 				return;
928 		}
929 		curr = curr->next;
930 	}
931 	if( !ubMaxNum )
932 		return;
933 
934 	//ADD REMAINING PLACEMENTS WITH BASIC PLACEMENT INFORMATION
935 	//we now have the numbers of available slots for each soldier class, so loop through three times
936 	//and randomly choose some (or all) of the matching slots to fill.  This is done randomly.
937 	for( ubCurrClass = SOLDIER_CLASS_ADMINISTRATOR; ubCurrClass <= SOLDIER_CLASS_ARMY; ubCurrClass++ )
938 	{
939 		//First, prepare the counters.
940 		switch( ubCurrClass )
941 		{
942 			case SOLDIER_CLASS_ADMINISTRATOR:
943 				pCurrSlots = &ubAdminBSlots;
944 				pCurrTotal = &ubTotalAdmin;
945 				break;
946 			case SOLDIER_CLASS_ELITE:
947 				pCurrSlots = &ubEliteBSlots;
948 				pCurrTotal = &ubTotalElite;
949 				break;
950 			case SOLDIER_CLASS_ARMY:
951 				pCurrSlots = &ubTroopBSlots;
952 				pCurrTotal = &ubTotalTroops;
953 				break;
954 		}
955 		//Now, loop through the regular basic placements section of the list.
956 		curr = mark;
957 		while( curr && ubMaxNum && *pCurrTotal && *pCurrSlots )
958 		{
959 			if( !curr->pSoldier && curr->pBasicPlacement->bTeam == ENEMY_TEAM )
960 			{
961 				if( curr->pBasicPlacement->ubSoldierClass == ubCurrClass )
962 				{
963 					if( *pCurrSlots <= *pCurrTotal || Random( *pCurrSlots ) < *pCurrTotal )
964 					{
965 						//found matching team, so add this soldier to the game.
966 						if( AddPlacementToWorld( curr ) )
967 						{
968 							(*pCurrTotal)--;
969 							ubMaxNum--;
970 						}
971 						else
972 							return;
973 					}
974 					(*pCurrSlots)--;
975 					//With the decrementing of the slot vars in this manner, the chances increase so that all slots
976 					//will be full by the time the end of the list comes up.
977 				}
978 			}
979 			curr = curr->next;
980 		}
981 	}
982 	if( !ubMaxNum )
983 		return;
984 
985 	//If we are at this point, that means that there are some compatibility issues.  This is fine.  An example
986 	//would be a map containing 1 elite placement, and 31 troop placements.  If we had 3 elites move into this
987 	//sector, we would not have placements for two of them.  What we have to do is override the class information
988 	//contained in the list by choosing unused placements, and assign them to the elites.  This time, we will
989 	//use all free slots including priority placement slots (ignoring the priority placement information).
990 
991 	//First, count up the total number of free slots.
992 	ubFreeSlots = 0;
993 	CFOR_EACH_SOLDIERINITNODE(curr)
994 	{
995 		if( !curr->pSoldier && curr->pBasicPlacement->bTeam == ENEMY_TEAM )
996 			ubFreeSlots++;
997 	}
998 
999 	//Now, loop through the entire list again, but for the last time.  All enemies will be inserted now ignoring
1000 	//detailed placements and classes.
1001 	FOR_EACH_SOLDIERINITNODE(curr)
1002 	{
1003 		if (ubFreeSlots == 0 || ubMaxNum == 0) break;
1004 		if( !curr->pSoldier && curr->pBasicPlacement->bTeam == ENEMY_TEAM )
1005 		{
1006 			//Randomly determine if we will use this slot; the more available slots in proportion to
1007 			//the number of enemies, the lower the chance of accepting the slot.
1008 			if( ubFreeSlots <= ubMaxNum || Random( ubFreeSlots ) < ubMaxNum )
1009 			{
1010 				//Choose which team to use.
1011 				iRandom = Random( ubMaxNum );
1012 				if( iRandom < ubTotalElite )
1013 				{
1014 					curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ELITE;
1015 					ubTotalElite--;
1016 				}
1017 				else if( iRandom < ubTotalElite + ubTotalTroops )
1018 				{
1019 					curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ARMY;
1020 					ubTotalTroops--;
1021 				}
1022 				else if( iRandom < ubTotalElite + ubTotalTroops + ubTotalAdmin )
1023 				{
1024 					curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ADMINISTRATOR;
1025 					ubTotalAdmin--;
1026 				}
1027 				else
1028 					SLOGA("AddSoldierInitListEnemyDefenceSoldiers: something wrong with random");
1029 				/* DISABLE THE OVERRIDE FOR NOW...
1030 				if( curr->pDetailedPlacement )
1031 				{ //delete the detailed placement information.
1032 					delete curr->pDetailedPlacement;
1033 					curr->pDetailedPlacement = NULL;
1034 					curr->pBasicPlacement->fDetailedPlacement = FALSE;
1035 				}
1036 				*/
1037 				if( AddPlacementToWorld( curr ) )
1038 				{
1039 					ubMaxNum--;
1040 				}
1041 				else
1042 					return;
1043 			}
1044 			ubFreeSlots--;
1045 			//With the decrementing of the slot vars in this manner, the chances increase so that all slots
1046 			//will be full by the time the end of the list comes up.
1047 		}
1048 	}
1049 }
1050 
1051 //If we are adding militia to our map, then we do a few things differently.
1052 //First of all, they exist exclusively to the enemy troops, so if the militia exists in the
1053 //sector, then they get to use the enemy placements.  However, we remove any orders from
1054 //placements containing RNDPTPATROL or POINTPATROL orders, as well as remove any detailed
1055 //placement information.
AddSoldierInitListMilitia(UINT8 ubNumGreen,UINT8 ubNumRegs,UINT8 ubNumElites)1056 void AddSoldierInitListMilitia( UINT8 ubNumGreen, UINT8 ubNumRegs, UINT8 ubNumElites )
1057 {
1058 	SOLDIERINITNODE *mark;
1059 	SOLDIERINITNODE *curr;
1060 	INT32 iRandom;
1061 	UINT8 ubMaxNum;
1062 	BOOLEAN fDoPlacement;
1063 	UINT8 ubEliteSlots = 0;
1064 	UINT8 ubRegSlots = 0;
1065 	UINT8 ubGreenSlots = 0;
1066 	UINT8 ubFreeSlots;
1067 	UINT8 *pCurrSlots=NULL;
1068 	UINT8 *pCurrTotal=NULL;
1069 	UINT8 ubCurrClass;
1070 
1071 	ubMaxNum = ubNumGreen + ubNumRegs + ubNumElites;
1072 
1073 	//Sort the list in the following manner:
1074 	//-Priority placements first
1075 	//-Basic placements next
1076 	//-Any placements with existing soldiers last (overrides others)
1077 	SortSoldierInitList();
1078 
1079 	curr = gSoldierInitHead;
1080 
1081 	//First fill up only the priority existance slots (as long as the availability and class are okay)
1082 	while( curr && curr->pBasicPlacement->fPriorityExistance && ubMaxNum )
1083 	{
1084 		fDoPlacement = TRUE;
1085 
1086 		if( curr->pBasicPlacement->bTeam == ENEMY_TEAM || curr->pBasicPlacement->bTeam == MILITIA_TEAM )
1087 		{
1088 			//Matching team (kindof), now check the soldier class...
1089 			if( ubNumElites && curr->pBasicPlacement->ubSoldierClass == SOLDIER_CLASS_ELITE )
1090 			{
1091 				curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ELITE_MILITIA;
1092 				ubNumElites--;
1093 			}
1094 			else if( ubNumRegs && curr->pBasicPlacement->ubSoldierClass == SOLDIER_CLASS_ARMY )
1095 			{
1096 				curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_REG_MILITIA;
1097 				ubNumRegs--;
1098 			}
1099 			else if( ubNumGreen && curr->pBasicPlacement->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR )
1100 			{
1101 				curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_GREEN_MILITIA;
1102 				ubNumGreen--;
1103 			}
1104 			else
1105 				fDoPlacement = FALSE;
1106 
1107 			if ( fDoPlacement )
1108 			{
1109 				curr->pBasicPlacement->bTeam = MILITIA_TEAM;
1110 				curr->pBasicPlacement->bOrders = STATIONARY;
1111 				curr->pBasicPlacement->bAttitude = (INT8) Random( MAXATTITUDES );
1112 				if( curr->pDetailedPlacement )
1113 				{
1114 					//delete the detailed placement information.
1115 					delete curr->pDetailedPlacement;
1116 					curr->pDetailedPlacement = NULL;
1117 					curr->pBasicPlacement->fDetailedPlacement = FALSE;
1118 					RandomizeRelativeLevel( &( curr->pBasicPlacement->bRelativeAttributeLevel ), curr->pBasicPlacement->ubSoldierClass );
1119 					RandomizeRelativeLevel( &( curr->pBasicPlacement->bRelativeEquipmentLevel ), curr->pBasicPlacement->ubSoldierClass );
1120 				}
1121 				if( AddPlacementToWorld( curr ) )
1122 				{
1123 					ubMaxNum--;
1124 				}
1125 				else
1126 					return;
1127 			}
1128 		}
1129 		curr = curr->next;
1130 	}
1131 	if( !ubMaxNum )
1132 		return;
1133 	//Now count the number of nodes that are basic placements of desired team AND CLASS
1134 	//This information will be used to randomly determine which of these placements
1135 	//will be added based on the number of slots we can still add.
1136 	mark = curr;
1137 	while( curr && !curr->pSoldier )
1138 	{
1139 		if( curr->pBasicPlacement->bTeam == ENEMY_TEAM || curr->pBasicPlacement->bTeam == MILITIA_TEAM )
1140 		{
1141 			switch( curr->pBasicPlacement->ubSoldierClass )
1142 			{
1143 				case SOLDIER_CLASS_ELITE:         ubEliteSlots++; break;
1144 				case SOLDIER_CLASS_ADMINISTRATOR: ubGreenSlots++; break;
1145 				case SOLDIER_CLASS_ARMY:          ubRegSlots++;   break;
1146 			}
1147 		}
1148 		curr = curr->next;
1149 	}
1150 
1151 	//we now have the numbers of available slots for each soldier class, so loop through three times
1152 	//and randomly choose some (or all) of the matching slots to fill.  This is done randomly.
1153 	for( ubCurrClass = SOLDIER_CLASS_ADMINISTRATOR; ubCurrClass <= SOLDIER_CLASS_ARMY; ubCurrClass++ )
1154 	{
1155 		//First, prepare the counters.
1156 		switch( ubCurrClass )
1157 		{
1158 			case SOLDIER_CLASS_ADMINISTRATOR:
1159 				pCurrSlots = &ubGreenSlots;
1160 				pCurrTotal = &ubNumGreen;
1161 				break;
1162 			case SOLDIER_CLASS_ELITE:
1163 				pCurrSlots = &ubEliteSlots;
1164 				pCurrTotal = &ubNumElites;
1165 				break;
1166 			case SOLDIER_CLASS_ARMY:
1167 				pCurrSlots = &ubRegSlots;
1168 				pCurrTotal = &ubNumRegs;
1169 				break;
1170 		}
1171 		//Now, loop through the basic placement of the list.
1172 		curr = mark; //mark is the marker where the basic placements start.
1173 		while( curr && !curr->pSoldier && ubMaxNum && *pCurrTotal && *pCurrSlots )
1174 		{
1175 			if( curr->pBasicPlacement->bTeam == ENEMY_TEAM || curr->pBasicPlacement->bTeam == MILITIA_TEAM )
1176 			{
1177 				if( curr->pBasicPlacement->ubSoldierClass == ubCurrClass )
1178 				{
1179 					if( *pCurrSlots <= *pCurrTotal || Random( *pCurrSlots ) < *pCurrTotal )
1180 					{
1181 						curr->pBasicPlacement->bTeam = MILITIA_TEAM;
1182 						curr->pBasicPlacement->bOrders = STATIONARY;
1183 						switch( ubCurrClass )
1184 						{
1185 							case SOLDIER_CLASS_ADMINISTRATOR:
1186 								curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_GREEN_MILITIA;
1187 								break;
1188 							case SOLDIER_CLASS_ARMY:
1189 								curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_REG_MILITIA;
1190 								break;
1191 							case SOLDIER_CLASS_ELITE:
1192 								curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ELITE_MILITIA;
1193 								break;
1194 						}
1195 						//found matching team, so add this soldier to the game.
1196 						if( AddPlacementToWorld( curr ) )
1197 						{
1198 							(*pCurrTotal)--;
1199 							ubMaxNum--;
1200 						}
1201 						else
1202 							return;
1203 					}
1204 					(*pCurrSlots)--;
1205 					//With the decrementing of the slot vars in this manner, the chances increase so that all slots
1206 					//will be full by the time the end of the list comes up.
1207 				}
1208 			}
1209 			curr = curr->next;
1210 		}
1211 	}
1212 	if( !ubMaxNum )
1213 		return;
1214 	//If we are at this point, that means that there are some compatibility issues.  This is fine.  An example
1215 	//would be a map containing 1 elite placement, and 31 troop placements.  If we had 3 elites move into this
1216 	//sector, we would not have placements for two of them.  What we have to do is override the class information
1217 	//contained in the list by choosing unused placements, and assign them to the elites.  This time, we will
1218 	//use all free slots including priority placement slots (ignoring the priority placement information).
1219 
1220 	//First, count up the total number of free slots.
1221 	ubFreeSlots = 0;
1222 	CFOR_EACH_SOLDIERINITNODE(curr)
1223 	{
1224 		if(!curr->pSoldier &&
1225 			(curr->pBasicPlacement->bTeam == ENEMY_TEAM ||
1226 			curr->pBasicPlacement->bTeam == MILITIA_TEAM))
1227 		{
1228 			ubFreeSlots++;
1229 		}
1230 	}
1231 
1232 	//Now, loop through the entire list again, but for the last time.  All enemies will be inserted now ignoring
1233 	//detailed placements and classes.
1234 	FOR_EACH_SOLDIERINITNODE(curr)
1235 	{
1236 		if (ubFreeSlots == 0 || ubMaxNum == 0) break;
1237 		if(!curr->pSoldier &&
1238 			(curr->pBasicPlacement->bTeam == ENEMY_TEAM ||
1239 			curr->pBasicPlacement->bTeam == MILITIA_TEAM))
1240 		{
1241 			//Randomly determine if we will use this slot; the more available slots in proportion to
1242 			//the number of enemies, the lower the chance of accepting the slot.
1243 			if( ubFreeSlots <= ubMaxNum || Random( ubFreeSlots ) < ubMaxNum )
1244 			{
1245 				//Choose which team to use.
1246 				iRandom = Random( ubMaxNum );
1247 				if( iRandom < ubNumElites )
1248 				{
1249 					curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_ELITE_MILITIA;
1250 					ubNumElites--;
1251 				}
1252 				else if( iRandom < ubNumElites + ubNumRegs )
1253 				{
1254 					curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_REG_MILITIA;
1255 					ubNumRegs--;
1256 				}
1257 				else if( iRandom < ubNumElites + ubNumRegs + ubNumGreen )
1258 				{
1259 					curr->pBasicPlacement->ubSoldierClass = SOLDIER_CLASS_GREEN_MILITIA;
1260 					ubNumGreen--;
1261 				}
1262 				else
1263 					SLOGE(
1264 						"AddSoldierInitListMilitia: something wrong with random");
1265 				curr->pBasicPlacement->bTeam = MILITIA_TEAM;
1266 				curr->pBasicPlacement->bOrders = STATIONARY;
1267 				curr->pBasicPlacement->bAttitude = (INT8) Random( MAXATTITUDES );
1268 				if( curr->pDetailedPlacement )
1269 				{
1270 					//delete the detailed placement information.
1271 					delete curr->pDetailedPlacement;
1272 					curr->pDetailedPlacement = NULL;
1273 					curr->pBasicPlacement->fDetailedPlacement = FALSE;
1274 					RandomizeRelativeLevel( &( curr->pBasicPlacement->bRelativeAttributeLevel), curr->pBasicPlacement->ubSoldierClass );
1275 					RandomizeRelativeLevel( &( curr->pBasicPlacement->bRelativeEquipmentLevel), curr->pBasicPlacement->ubSoldierClass );
1276 				}
1277 				if( AddPlacementToWorld( curr ) )
1278 				{
1279 					ubMaxNum--;
1280 				}
1281 				else
1282 					return;
1283 			}
1284 			ubFreeSlots--;
1285 			//With the decrementing of the slot vars in this manner, the chances increase so that all slots
1286 			//will be full by the time the end of the list comes up.
1287 		}
1288 	}
1289 }
1290 
AddSoldierInitListCreatures(BOOLEAN fQueen,UINT8 ubNumLarvae,UINT8 ubNumInfants,UINT8 ubNumYoungMales,UINT8 ubNumYoungFemales,UINT8 ubNumAdultMales,UINT8 ubNumAdultFemales)1291 void AddSoldierInitListCreatures(BOOLEAN fQueen, UINT8 ubNumLarvae, UINT8 ubNumInfants,
1292 					UINT8 ubNumYoungMales, UINT8 ubNumYoungFemales,
1293 					UINT8 ubNumAdultMales, UINT8 ubNumAdultFemales )
1294 {
1295 	INT32 iRandom;
1296 	UINT8 ubFreeSlots;
1297 	BOOLEAN fDoPlacement;
1298 	UINT8 ubNumCreatures;
1299 
1300 	SortSoldierInitList();
1301 
1302 	//Okay, if we have a queen, place her first.  She MUST have a special placement, else
1303 	//we can't use anything.
1304 	ubNumCreatures = (UINT8)(ubNumLarvae + ubNumInfants + ubNumYoungMales + ubNumYoungFemales + ubNumAdultMales + ubNumAdultFemales);
1305 	if( fQueen )
1306 	{
1307 		FOR_EACH_SOLDIERINITNODE(curr)
1308 		{
1309 			if( !curr->pSoldier && curr->pBasicPlacement->bTeam == CREATURE_TEAM && curr->pBasicPlacement->bBodyType == QUEENMONSTER )
1310 			{
1311 				if( !AddPlacementToWorld( curr ) )
1312 				{
1313 					fQueen = FALSE;
1314 					break;
1315 				}
1316 			}
1317 		}
1318 		if( !fQueen )
1319 		{
1320 			SLOGE("Couldn't place the queen.");
1321 		}
1322 	}
1323 
1324 	//First fill up only the priority existance slots (as long as the availability and bodytypes match)
1325 	FOR_EACH_SOLDIERINITNODE(curr)
1326 	{
1327 		if (!curr->pBasicPlacement->fPriorityExistance || ubNumCreatures == 0) break;
1328 		fDoPlacement = TRUE;
1329 
1330 		if( curr->pBasicPlacement->bTeam == CREATURE_TEAM )
1331 		{
1332 			//Matching team, now check the soldier class...
1333 			if( ubNumLarvae && curr->pBasicPlacement->bBodyType == LARVAE_MONSTER )
1334 				ubNumLarvae--;
1335 			else if( ubNumInfants && curr->pBasicPlacement->bBodyType == INFANT_MONSTER )
1336 				ubNumInfants--;
1337 			else if( ubNumYoungMales && curr->pBasicPlacement->bBodyType == YAM_MONSTER )
1338 				ubNumYoungMales--;
1339 			else if( ubNumYoungFemales && curr->pBasicPlacement->bBodyType == YAF_MONSTER )
1340 				ubNumYoungFemales--;
1341 			else if( ubNumAdultMales && curr->pBasicPlacement->bBodyType == AM_MONSTER )
1342 				ubNumAdultMales--;
1343 			else if( ubNumAdultFemales && curr->pBasicPlacement->bBodyType == ADULTFEMALEMONSTER )
1344 				ubNumAdultFemales--;
1345 			else
1346 				fDoPlacement = FALSE;
1347 			if ( fDoPlacement )
1348 			{
1349 				if( AddPlacementToWorld( curr ) )
1350 				{
1351 					ubNumCreatures--;
1352 				}
1353 				else
1354 					return;
1355 			}
1356 		}
1357 	}
1358 	if( !ubNumCreatures )
1359 		return;
1360 
1361 	//Count how many free creature slots are left.
1362 	ubFreeSlots = 0;
1363 	CFOR_EACH_SOLDIERINITNODE(curr)
1364 	{
1365 		if( !curr->pSoldier && curr->pBasicPlacement->bTeam == CREATURE_TEAM )
1366 			ubFreeSlots++;
1367 	}
1368 	//Now, if we still have creatures to place, do so completely randomly, overriding priority
1369 	//placements, etc.
1370 	FOR_EACH_SOLDIERINITNODE(curr)
1371 	{
1372 		if (ubFreeSlots == 0 || ubNumCreatures == 0) break;
1373 		if( !curr->pSoldier && curr->pBasicPlacement->bTeam == CREATURE_TEAM )
1374 		{
1375 			//Randomly determine if we will use this slot; the more available slots in proportion to
1376 			//the number of enemies, the lower the chance of accepting the slot.
1377 			if( ubFreeSlots <= ubNumCreatures || Random( ubFreeSlots ) < ubNumCreatures )
1378 			{
1379 				//Choose which team to use.
1380 				iRandom = Random( ubNumCreatures );
1381 
1382 				if( ubNumLarvae && iRandom < ubNumLarvae )
1383 				{
1384 					ubNumLarvae--;
1385 					curr->pBasicPlacement->bBodyType = LARVAE_MONSTER;
1386 				}
1387 				else if( ubNumInfants && iRandom < ubNumLarvae + ubNumInfants )
1388 				{
1389 					ubNumInfants--;
1390 					curr->pBasicPlacement->bBodyType = INFANT_MONSTER;
1391 				}
1392 				else if( ubNumYoungMales && iRandom < ubNumLarvae + ubNumInfants + ubNumYoungMales )
1393 				{
1394 					ubNumYoungMales--;
1395 					curr->pBasicPlacement->bBodyType = YAM_MONSTER;
1396 				}
1397 				else if( ubNumYoungFemales && iRandom < ubNumLarvae + ubNumInfants + ubNumYoungMales + ubNumYoungFemales )
1398 				{
1399 					ubNumYoungFemales--;
1400 					curr->pBasicPlacement->bBodyType = YAF_MONSTER;
1401 				}
1402 				else if( ubNumAdultMales && iRandom < ubNumLarvae + ubNumInfants + ubNumYoungMales + ubNumYoungFemales + ubNumAdultMales )
1403 				{
1404 					ubNumAdultMales--;
1405 					curr->pBasicPlacement->bBodyType = AM_MONSTER;
1406 				}
1407 				else if( ubNumAdultFemales && iRandom < ubNumLarvae + ubNumInfants + ubNumYoungMales + ubNumYoungFemales + ubNumAdultMales + ubNumAdultFemales )
1408 				{
1409 					ubNumAdultFemales--;
1410 					curr->pBasicPlacement->bBodyType = ADULTFEMALEMONSTER;
1411 				}
1412 				else
1413 					SLOGA("AddSoldierInitListCreatures: something wrong with random");
1414 				if( curr->pDetailedPlacement )
1415 				{ //delete the detailed placement information.
1416 					delete curr->pDetailedPlacement;
1417 					curr->pDetailedPlacement = NULL;
1418 					curr->pBasicPlacement->fDetailedPlacement = FALSE;
1419 				}
1420 				if( AddPlacementToWorld( curr ) )
1421 				{
1422 					ubNumCreatures--;
1423 				}
1424 				else
1425 				{
1426 					return;
1427 				}
1428 			}
1429 			ubFreeSlots--;
1430 			//With the decrementing of the slot vars in this manner, the chances increase so that all slots
1431 			//will be full by the time the end of the list comes up.
1432 		}
1433 	}
1434 }
1435 
1436 
FindSoldierInitNodeWithID(UINT16 usID)1437 SOLDIERINITNODE* FindSoldierInitNodeWithID( UINT16 usID )
1438 {
1439 	FOR_EACH_SOLDIERINITNODE(curr)
1440 	{
1441 		if( curr->pSoldier->ubID == usID )
1442 			return curr;
1443 	}
1444 	return NULL;
1445 }
1446 
1447 
FindSoldierInitNodeBySoldier(SOLDIERTYPE const & s)1448 SOLDIERINITNODE* FindSoldierInitNodeBySoldier(SOLDIERTYPE const& s)
1449 {
1450 	FOR_EACH_SOLDIERINITNODE(i)
1451 	{
1452 		if (i->pSoldier == &s) return i;
1453 	}
1454 	return 0;
1455 }
1456 
1457 
UseEditorOriginalList()1458 void UseEditorOriginalList()
1459 {
1460 	SOLDIERINITNODE *curr;
1461 	gfOriginalList = TRUE;
1462 	gSoldierInitHead = gOriginalSoldierInitListHead;
1463 	curr = gSoldierInitHead;
1464 	if( curr )
1465 	{
1466 		while( curr->next )
1467 			curr = curr->next;
1468 	}
1469 	if( curr )
1470 		gSoldierInitTail = curr;
1471 }
1472 
UseEditorAlternateList()1473 void UseEditorAlternateList()
1474 {
1475 	SOLDIERINITNODE *curr;
1476 	gfOriginalList = FALSE;
1477 	gSoldierInitHead = gAlternateSoldierInitListHead;
1478 	curr = gSoldierInitHead;
1479 	if( curr )
1480 	{
1481 		while( curr->next )
1482 			curr = curr->next;
1483 	}
1484 	if( curr )
1485 		gSoldierInitTail = curr;
1486 }
1487 
1488 
EvaluateDeathEffectsToSoldierInitList(SOLDIERTYPE const & s)1489 void EvaluateDeathEffectsToSoldierInitList(SOLDIERTYPE const& s)
1490 {
1491 	if (s.bTeam == MILITIA_TEAM) return;
1492 	SOLDIERINITNODE* const curr = FindSoldierInitNodeBySoldier(s);
1493 	if (!curr || !curr->pDetailedPlacement) return;
1494 	delete curr->pDetailedPlacement;
1495 	curr->pDetailedPlacement = 0;
1496 	curr->pSoldier           = 0;
1497 }
1498 
1499 
1500 //For the purpose of keeping track of which soldier belongs to which placement within the game,
1501 //the only way we can do this properly is to save the soldier ID from the list and reconnect the
1502 //soldier pointer whenever we load the game.
SaveSoldierInitListLinks(HWFILE const hfile)1503 void SaveSoldierInitListLinks(HWFILE const hfile)
1504 {
1505 	UINT8 ubSlots = 0;
1506 
1507 	//count the number of soldier init nodes...
1508 	CFOR_EACH_SOLDIERINITNODE(curr) ++ubSlots;
1509 	//...and save it.
1510 	FileWrite(hfile, &ubSlots, 1);
1511 	//Now, go through each node, and save just the ubSoldierID, if that soldier is alive.
1512 	FOR_EACH_SOLDIERINITNODE(curr)
1513 	{
1514 		if( curr->pSoldier && !curr->pSoldier->bActive )
1515 		{
1516 			curr->ubSoldierID = 0;
1517 		}
1518 		FileWrite(hfile, &curr->ubNodeID,    1);
1519 		FileWrite(hfile, &curr->ubSoldierID, 1);
1520 	}
1521 }
1522 
1523 
LoadSoldierInitListLinks(HWFILE const f)1524 void LoadSoldierInitListLinks(HWFILE const f)
1525 {
1526 	UINT8 slots;
1527 	FileRead(f, &slots, 1);
1528 	for (UINT8 n = slots; n != 0; --n)
1529 	{
1530 		UINT8 node_id;
1531 		UINT8 soldier_id;
1532 		FileRead(f, &node_id,    1);
1533 		FileRead(f, &soldier_id, 1);
1534 
1535 		if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME)) continue;
1536 
1537 		FOR_EACH_SOLDIERINITNODE(curr)
1538 		{
1539 			if (curr->ubNodeID != node_id) continue;
1540 
1541 			curr->ubSoldierID = soldier_id;
1542 			TacticalTeamType const* const team = gTacticalStatus.Team;
1543 			if ((team[ENEMY_TEAM].bFirstID <= soldier_id &&
1544 				soldier_id <= team[CREATURE_TEAM].bLastID) ||
1545 				(team[CIV_TEAM].bFirstID <= soldier_id &&
1546 				soldier_id <= team[CIV_TEAM].bLastID))
1547 			{
1548 				// only enemies, creatures and civilians
1549 				curr->pSoldier = &GetMan(soldier_id);
1550 			}
1551 		}
1552 	}
1553 }
1554 
1555 
AddSoldierInitListBloodcats()1556 void AddSoldierInitListBloodcats()
1557 {
1558 	SECTORINFO *pSector;
1559 	UINT8 ubSectorID;
1560 
1561 	if( gbWorldSectorZ )
1562 	{
1563 		return; //no bloodcats underground.
1564 	}
1565 
1566 	ubSectorID = (UINT8)SECTOR( gWorldSectorX, gWorldSectorY );
1567 	pSector = &SectorInfo[ ubSectorID ];
1568 
1569 	if( !pSector->bBloodCatPlacements )
1570 	{ //This map has no bloodcat placements, so don't waste CPU time.
1571 		return;
1572 	}
1573 
1574 	if( pSector->bBloodCatPlacements )
1575 	{ //We don't yet know the number of bloodcat placements in this sector so
1576 		//count them now, and permanently record it.
1577 		INT8 bBloodCatPlacements = 0;
1578 		CFOR_EACH_SOLDIERINITNODE(curr)
1579 		{
1580 			if( curr->pBasicPlacement->bBodyType == BLOODCAT )
1581 			{
1582 				bBloodCatPlacements++;
1583 			}
1584 		}
1585 
1586 		auto spawns = GCM->getBloodCatSpawnsOfSector( ubSectorID );
1587 		if( bBloodCatPlacements != pSector->bBloodCatPlacements && spawns == NULL )
1588 		{
1589 			pSector->bBloodCatPlacements = bBloodCatPlacements;
1590 			pSector->bBloodCats = -1;
1591 			if( !bBloodCatPlacements )
1592 			{
1593 				return;
1594 			}
1595 		}
1596 	}
1597 	if( pSector->bBloodCats > 0 )
1598 	{
1599 		//Add them to the world now...
1600 		UINT8 ubNumAdded = 0;
1601 		UINT8 ubMaxNum = (UINT8)pSector->bBloodCats;
1602 		SOLDIERINITNODE *mark;
1603 		UINT8 ubSlotsToFill;
1604 		UINT8 ubSlotsAvailable;
1605 		SOLDIERINITNODE *curr;
1606 
1607 		//Sort the list in the following manner:
1608 		//-Priority placements first
1609 		//-Basic placements next
1610 		//-Any placements with existing soldiers last (overrides others)
1611 		SortSoldierInitList();
1612 
1613 		//Count the current number of soldiers of the specified team
1614 		CFOR_EACH_SOLDIERINITNODE(curr)
1615 		{
1616 			if( curr->pBasicPlacement->bBodyType == BLOODCAT && curr->pSoldier )
1617 				ubNumAdded++;  //already one here!
1618 		}
1619 
1620 		curr = gSoldierInitHead;
1621 
1622 		//First fill up all of the priority existance slots...
1623 		while( curr && curr->pBasicPlacement->fPriorityExistance && ubNumAdded < ubMaxNum )
1624 		{
1625 			if( curr->pBasicPlacement->bBodyType == BLOODCAT )
1626 			{
1627 				//Matching team, so add this placement.
1628 				if( AddPlacementToWorld( curr ) )
1629 				{
1630 					ubNumAdded++;
1631 				}
1632 			}
1633 			curr = curr->next;
1634 		}
1635 		if( ubNumAdded == ubMaxNum )
1636 			return;
1637 
1638 		//Now count the number of nodes that are basic placements of desired team
1639 		//This information will be used to randomly determine which of these placements
1640 		//will be added based on the number of slots we can still add.
1641 		mark = curr;
1642 		ubSlotsAvailable = 0;
1643 		ubSlotsToFill = ubMaxNum - ubNumAdded;
1644 		while( curr && !curr->pSoldier && ubNumAdded < ubMaxNum )
1645 		{
1646 			if( curr->pBasicPlacement->bBodyType == BLOODCAT )
1647 				ubSlotsAvailable++;
1648 			curr = curr->next;
1649 		}
1650 
1651 		//we now have the number, so compared it to the num we can add, and determine how we will
1652 		//randomly determine which nodes to add.
1653 		if( !ubSlotsAvailable )
1654 		{	//There aren't any basic placements of desired team, so exit.
1655 			return;
1656 		}
1657 		curr = mark;
1658 		//while we have a list, with no active soldiers, the num added is less than the max num requested, and
1659 		//we have slots available, process the list to add new soldiers.
1660 		while( curr && !curr->pSoldier && ubNumAdded < ubMaxNum && ubSlotsAvailable )
1661 		{
1662 			if( curr->pBasicPlacement->bBodyType == BLOODCAT )
1663 			{
1664 				if( ubSlotsAvailable <= ubSlotsToFill || Random( ubSlotsAvailable ) < ubSlotsToFill )
1665 				{
1666 					//found matching team, so add this soldier to the game.
1667 					if( AddPlacementToWorld( curr ) )
1668 					{
1669 						ubNumAdded++;
1670 					}
1671 					else
1672 					{
1673 						//if it fails to create the soldier, it is likely that it is because the slots in the tactical
1674 						//engine are already full.  Besides, the strategic AI shouldn't be trying to fill a map with
1675 						//more than the maximum allowable soldiers of team.  All teams can have a max of 32 individuals,
1676 						//except for the player which is 20.  Players aren't processed in this list anyway.
1677 						return;
1678 					}
1679 					ubSlotsToFill--;
1680 				}
1681 				ubSlotsAvailable--;
1682 				//With the decrementing of the slot vars in this manner, the chances increase so that all slots
1683 				//will be full by the time the end of the list comes up.
1684 			}
1685 			curr = curr->next;
1686 		}
1687 		return;
1688 	}
1689 }
1690 
1691 
FindSoldierInitListNodeByProfile(UINT8 ubProfile)1692 static SOLDIERINITNODE* FindSoldierInitListNodeByProfile(UINT8 ubProfile)
1693 {
1694 	FOR_EACH_SOLDIERINITNODE(curr)
1695 	{
1696 		if ( curr->pDetailedPlacement && curr->pDetailedPlacement->ubProfile == ubProfile )
1697 		{
1698 			return( curr );
1699 		}
1700 	}
1701 	return( NULL );
1702 }
1703 
1704 
1705 // Loop through the profiles starting at the RPCs, add them using strategic
1706 // insertion information and not editor placements. The key flag involved for
1707 // doing it this way is the GetProfile(i).fUseProfileInsertionInfo.
AddProfilesUsingProfileInsertionData()1708 void AddProfilesUsingProfileInsertionData()
1709 {
1710 	for (const MercProfile* prof : GCM->listMercProfiles())
1711 	{
1712 		if (!prof->isNPCorRPC() && !prof->isVehicle())   continue;
1713 
1714 		// Perform various checks to make sure the soldier is actually in the same
1715 		// sector, alive and so on. More importantly, the flag to use profile
1716 		// insertion data must be set.
1717 		ProfileID                i = prof->getID();
1718 		MERCPROFILESTRUCT const& p = prof->getStruct();
1719 		if (p.sSectorX != gWorldSectorX)                 continue;
1720 		if (p.sSectorY != gWorldSectorY)                 continue;
1721 		if (p.bSectorZ != gbWorldSectorZ)                continue;
1722 		if (p.ubMiscFlags & PROFILE_MISC_FLAG_RECRUITED) continue;
1723 		if (p.ubMiscFlags & PROFILE_MISC_FLAG_EPCACTIVE) continue;
1724 		if (p.bLife == 0)                                continue;
1725 		if (!p.fUseProfileInsertionInfo)                 continue;
1726 
1727 		SOLDIERTYPE* ps = FindSoldierByProfileID(i);
1728 		if (!ps)
1729 		{
1730 			// Create a new soldier, as this one doesn't exist
1731 			SOLDIERCREATE_STRUCT c;
1732 			c = SOLDIERCREATE_STRUCT{};
1733 			c.bTeam     = CIV_TEAM;
1734 			c.ubProfile = i;
1735 			c.sSectorX  = gWorldSectorX;
1736 			c.sSectorY  = gWorldSectorY;
1737 			c.bSectorZ  = gbWorldSectorZ;
1738 			ps = TacticalCreateSoldier(c);
1739 			if (!ps) continue; // XXX exception?
1740 		}
1741 		SOLDIERTYPE& s = *ps;
1742 
1743 		// Insert the soldier
1744 		s.ubStrategicInsertionCode = p.ubStrategicInsertionCode;
1745 		s.usStrategicInsertionData = p.usStrategicInsertionData;
1746 		UpdateMercInSector(s, gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
1747 
1748 		// check action ID values
1749 		if (p.ubQuoteRecord != 0)
1750 		{
1751 			s.ubQuoteRecord   = p.ubQuoteRecord;
1752 			s.ubQuoteActionID = p.ubQuoteActionID;
1753 			if (s.ubQuoteActionID == QUOTE_ACTION_ID_CHECKFORDEST)
1754 			{
1755 				// Gridno will have been changed to destination, so we're there
1756 				NPCReachedDestination(&s, FALSE);
1757 			}
1758 		}
1759 
1760 		// Make sure this person's pointer is set properly in the init list
1761 		if (SOLDIERINITNODE* const init = FindSoldierInitListNodeByProfile(s.ubProfile))
1762 		{
1763 			init->pSoldier    = &s;
1764 			init->ubSoldierID = s.ubID;
1765 			// Also connect schedules here
1766 			SOLDIERCREATE_STRUCT& dp = *init->pDetailedPlacement;
1767 			if (dp.ubScheduleID != 0)
1768 			{
1769 				if (SCHEDULENODE* const sched = GetSchedule(dp.ubScheduleID))
1770 				{
1771 					sched->soldier = &s;
1772 					s.ubScheduleID = dp.ubScheduleID;
1773 				}
1774 			}
1775 		}
1776 	}
1777 }
1778 
1779 
AddProfilesNotUsingProfileInsertionData()1780 void AddProfilesNotUsingProfileInsertionData()
1781 {
1782 	FOR_EACH_SOLDIERINITNODE(i)
1783 	{
1784 		SOLDIERINITNODE& si = *i;
1785 		if (si.pSoldier)                           continue;
1786 		if (si.pBasicPlacement->bTeam != CIV_TEAM) continue;
1787 		if (!si.pDetailedPlacement)                continue;
1788 		ProfileID const pid = si.pDetailedPlacement->ubProfile;
1789 		if (pid == NO_PROFILE)                     continue;
1790 		MERCPROFILESTRUCT const& p = GetProfile(pid);
1791 		if (p.fUseProfileInsertionInfo)            continue;
1792 		if (p.bLife == 0)                          continue;
1793 		AddPlacementToWorld(&si);
1794 	}
1795 }
1796 
1797 
NewWayOfLoadingEnemySoldierInitListLinks(HWFILE const f)1798 void NewWayOfLoadingEnemySoldierInitListLinks(HWFILE const f)
1799 {
1800 	UINT8 slots;
1801 	FileRead(f, &slots, 1);
1802 	for (UINT8 n = slots; n != 0; --n)
1803 	{
1804 		UINT8 node_id;
1805 		UINT8 soldier_id;
1806 		FileRead(f, &node_id,    1);
1807 		FileRead(f, &soldier_id, 1);
1808 
1809 		if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME)) continue;
1810 
1811 		FOR_EACH_SOLDIERINITNODE(curr)
1812 		{
1813 			if (curr->ubNodeID != node_id) continue;
1814 
1815 			curr->ubSoldierID = soldier_id;
1816 			TacticalTeamType const* const team = gTacticalStatus.Team;
1817 			if (soldier_id < team[ENEMY_TEAM].bFirstID || team[CREATURE_TEAM].bLastID < soldier_id) continue;
1818 			// only enemies and creatures
1819 			curr->pSoldier = &GetMan(soldier_id);
1820 		}
1821 	}
1822 }
1823 
1824 
NewWayOfLoadingCivilianInitListLinks(HWFILE const f)1825 void NewWayOfLoadingCivilianInitListLinks(HWFILE const f)
1826 {
1827 	UINT8 slots;
1828 	FileRead(f, &slots, 1);
1829 	for (UINT8 n = slots; n != 0; --n)
1830 	{
1831 		UINT8 node_id;
1832 		UINT8 soldier_id;
1833 		FileRead(f, &node_id,    1);
1834 		FileRead(f, &soldier_id, 1);
1835 
1836 		if (!(gTacticalStatus.uiFlags & LOADING_SAVED_GAME)) continue;
1837 
1838 		FOR_EACH_SOLDIERINITNODE(curr)
1839 		{
1840 			if (curr->ubNodeID != node_id) continue;
1841 
1842 			curr->ubSoldierID = soldier_id;
1843 			TacticalTeamType const* const team = gTacticalStatus.Team;
1844 			if (soldier_id < team[CIV_TEAM].bFirstID || team[CIV_TEAM].bLastID < soldier_id) continue;
1845 			// only civilians
1846 			curr->pSoldier = &GetMan(soldier_id);
1847 		}
1848 	}
1849 }
1850 
1851 
StripEnemyDetailedPlacementsIfSectorWasPlayerLiberated()1852 void StripEnemyDetailedPlacementsIfSectorWasPlayerLiberated()
1853 {
1854 	if (!gfWorldLoaded || gbWorldSectorZ != 0)
1855 	{
1856 		// No world loaded or underground.  Underground sectors don't matter seeing
1857 		// enemies (not creatures) never rejuvenate underground.
1858 		return;
1859 	}
1860 
1861 	SECTORINFO const& sector = SectorInfo[SECTOR(gWorldSectorX, gWorldSectorY)];
1862 	if (sector.uiTimeLastPlayerLiberated == 0)
1863 	{
1864 		// The player has never owned the sector.
1865 		return;
1866 	}
1867 
1868 	// The player has owned the sector at one point.  By stripping all of the
1869 	// detailed placements, only basic placements will remain.  This prevents
1870 	// tanks and "specially detailed" enemies from coming back.
1871 	FOR_EACH_SOLDIERINITNODE(i)
1872 	{
1873 		SOLDIERINITNODE&            si = *i;
1874 		BASIC_SOLDIERCREATE_STRUCT& bp = *si.pBasicPlacement;
1875 		if (bp.bTeam != ENEMY_TEAM) continue;
1876 		if (!si.pDetailedPlacement) continue;
1877 
1878 		delete si.pDetailedPlacement;
1879 		si.pDetailedPlacement = 0;
1880 		bp.fDetailedPlacement = FALSE;
1881 		bp.fPriorityExistance = FALSE;
1882 		bp.bBodyType          = BODY_RANDOM;
1883 		RandomizeRelativeLevel(&bp.bRelativeAttributeLevel, bp.ubSoldierClass);
1884 		RandomizeRelativeLevel(&bp.bRelativeEquipmentLevel, bp.ubSoldierClass);
1885 	}
1886 }
1887