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