1 #include "Strategic_AI.h"
2 #include "ArmyCompositionModel.h"
3 #include "CacheSectorsModel.h"
4 #include "Campaign.h"
5 #include "Campaign_Init.h"
6 #include "Campaign_Types.h"
7 #include "ContentManager.h"
8 #include "Debug.h"
9 #include "Facts.h"
10 #include "FileMan.h"
11 #include "Game_Clock.h"
12 #include "Game_Event_Hook.h"
13 #include "Game_Init.h"
14 #include "GameInstance.h"
15 #include "GameSettings.h"
16 #include "Interface_Dialogue.h"
17 #include "Logger.h"
18 #include "Map_Information.h"
19 #include "MapScreen.h"
20 #include "Overhead.h"
21 #include "Overhead_Types.h"
22 #include "Player_Command.h"
23 #include "PreBattle_Interface.h"
24 #include "Queen_Command.h"
25 #include "Quests.h"
26 #include "Random.h"
27 #include "SAM_Sites.h"
28 #include "Scheduling.h"
29 #include "SGPFile.h"
30 #include "Soldier_Control.h"
31 #include "Soldier_Profile.h"
32 #include "Soldier_Tile.h"
33 #include "Strategic_Movement.h"
34 #include "Strategic_Movement_Costs.h"
35 #include "Strategic_Pathing.h"
36 #include "StrategicAIPolicy.h"
37 #include "StrategicMap.h"
38 #include "Town_Militia.h"
39 #include <vector>
40 
41 #define SAI_VERSION		29
42 
43 /*
44 STRATEGIC AI -- UNDERLYING PHILOSOPHY
45 The most fundamental part of the strategic AI which takes from reality and gives to gameplay is the manner
46 the queen attempts to take her towns back.  Finances and owning mines are the most important way
47 to win the game.  As the player takes more mines over, the queen will focus more on quality and defense.  In
48 the beginning of the game, she will focus more on offense than mid-game or end-game.
49 
50 REALITY
51 The queen owns the entire country, and the player starts the game with a small lump of cash, enough to hire
52 some mercenaries for about a week.  In that week, the queen may not notice what is going on, and the player
53 would believably take over one of the towns before she could feasibly react.  As soon as her military was
54 aware of the situation, she would likely proceed to send 300-400 troops to annihilate the opposition, and the
55 game would be over relatively quickly.  If the player was a prodigy, and managed to hold the town against such
56 a major assault, he would probably lose in the long run being forced into a defensive position and running out
57 of money quickly while the queen could continue to pump out the troops.  On the other hand, if the player
58 somehow managed to take over most of the mines, he would be able to casually walk over the queen eventually
59 just from the sheer income allowing him to purchase several of the best mercs.  That would have the effect of
60 making the game impossibly difficult in the beginning of the game, and a joke at the end (this is very much
61 like Master Of Orion II on the more difficult settings )
62 
63 GAMEPLAY
64 Because we want the game to be like a normal game and make it fun, we need to make the game easy in the
65 beginning and harder at the end.  In order to accomplish this, I feel that pure income shouldn't be the factor
66 for the queen, because she would likely crucify a would-be leader in his early days.  So, in the beginning of
67 the game, the forces would already be situated with the majority of forces being the administrators in the towns,
68 and army troops and elites in the more important sectors.  Restricting the queen's offensive
69 abilities using a distance penalty would mean that the furthest sectors from the queen's palace would be
70 much easier to defend because she would only be allowed to send x number of troops.  As you get closer to the
71 queen, she would be allowed to send larger forces to attack those towns in question.  Also, to further
72 increase the games difficulty as the campaign progresses in the player's favor, we could also increase the
73 quality of the queen's troops based purely on the peek progress percentage.  This is calculated using a formula
74 that determines how well the player is doing by combining loyalty of towns owned, income generated, etc.  So,
75 in the beginning of the game, the quality is at the worst, but once you capture your first mines/towns, it
76 permanently  increase the queen's quality rating, effectively bumping up the stakes.  By the time you capture
77 four or five mines, the queen is going to focus more (but not completely) on quality defense as she prepares
78 for your final onslaught.  This quality rating will augment the experience level, equipment rating, and/or
79 attribute ratings of the queen's troops.  I would maintain a table of these enhancements based on the current
80 quality rating hooking into the difficulty all along.
81 
82 //EXPLANATION OF THE WEIGHT SYSTEM:
83 The strategic AI has two types of groups:  garrisons and patrol groups.  Each of these groups contain
84 information of it's needs, mainly desired population.  If the current population is greater than the
85 desired population, and the group will get a negative weight assigned to it, which means that it is willing
86 to give up troops to areas that need them more.  On the other hand, if a group has less than the desired population,
87 then the weight will be positive, meaning they are requesting reinforcements.
88 
89 The weight generated will range between -100 and +100.  The calculated weight is modified by the priority
90 of the group.  If the priority of the group is high, they
91 */
92 
93 BOOLEAN gfAutoAIAware = FALSE;
94 
95 //Saved vars
96 BOOLEAN      gfExtraElites = 0;			//Set when queen compositions are augmented with bonus elites.
97 INT32        giForcePercentage = 0;		//Modifies the starting group sizes relative by percentage
98 INT32        giArmyAlertness = 0;		//The chance the group will spot an adjacent player/militia
99 INT32        giArmyAlertnessDecay = 0;		//How much the spotting chance decreases when spot check succeeds
100 UINT8        gubNumAwareBattles = 0;		//When non-zero, this means the queen is very aware and searching for players.  Every time
101 						//there is an enemy initiated battle, this counter decrements until zero.  Until that point,
102 						//all adjacent sector checks automatically succeed.
103 BOOLEAN gfQueenAIAwake = FALSE;			//This flag turns on/off the strategic decisions.  If it's off, no reinforcements
104 						//or assaults will happen.
105 						//@@@Alex, this flag is ONLY set by the first meanwhile scene which calls an action.  If this
106 						//action isn't called, the AI will never turn on.  It is completely dependant on this action.  It can
107 						//be toggled at will in the AIViewer for testing purposes.
108 INT32        giReinforcementPool = 0;		//How many troops the queen has in reserve in noman's land.  These guys are spawned as needed in P3.
109 INT32        giReinforcementPoints = 0;		//the entire army's capacity to provide reinforcements.
110 INT32        giRequestPoints = 0;		//the entire army's need for reinforcements.
111 UINT8        gubSAIVersion = SAI_VERSION;	//Used for adding new features to be saved.
112 UINT8        gubQueenPriorityPhase = 0;		//Defines how far into defence the queen is -- abstractly related to defcon index ranging from 0-10.
113 																			//10 is the most defensive
114 //Used for authorizing the use of the first battle meanwhile scene AFTER the battle is complete.  This is the case used when
115 //the player attacks a town, and is set once militia are sent to investigate.
116 BOOLEAN gfFirstBattleMeanwhileScenePending = FALSE;
117 
118 //After the first battle meanwhile scene is finished, this flag is set, and the queen orders patrol groups to immediately fortify all towns.
119 BOOLEAN gfMassFortificationOrdered = FALSE;
120 
121 UINT8   gubMinEnemyGroupSize     = 0;
122 UINT8   gubHoursGracePeriod      = 0;
123 UINT16  gusPlayerBattleVictories = 0;
124 BOOLEAN gfUseAlternateQueenPosition = FALSE;
125 
126 //padding for generic globals
127 #define SAI_PADDING_BYTES		97
128 INT8		gbPadding[SAI_PADDING_BYTES]; // XXX HACK000B
129 //patrol group info plus padding
130 std::vector<PATROL_GROUP> gPatrolGroup;
131 //army composition info plus padding
132 std::vector<ARMY_COMPOSITION> gArmyComp;
133 //garrison info plus padding
134 std::vector<GARRISON_GROUP> gGarrisonGroup;
135 
136 extern UINT8 gubNumGroupsArrivedSimultaneously;
137 
138 //This refers to the number of force points that are *saved* for the AI to use.  This is basically an array of each
139 //group.  When the queen wants to send forces to attack a town that is defended, the initial number of forces that
140 //she would send would be considered too weak.  So, instead, she will send that force to the sector's adjacent sector,
141 //and stage, while
142 UINT8 *gubGarrisonReinforcementsDenied = NULL;
143 UINT8 *gubPatrolReinforcementsDenied = NULL;
144 
145 //Unsaved vars
146 BOOLEAN gfDisplayStrategicAILogs = FALSE;
147 
148 extern INT16 sWorldSectorLocationOfFirstBattle;
149 
150 //returns the number of reinforcements permitted to be sent.  Will increased if the denied counter is non-zero.
GarrisonReinforcementsRequested(INT32 iGarrisonID,UINT8 * pubExtraReinforcements)151 static INT32 GarrisonReinforcementsRequested(INT32 iGarrisonID, UINT8* pubExtraReinforcements)
152 {
153 	INT32 iReinforcementsRequested;
154 	INT32 iExistingForces;
155 	SECTORINFO *pSector;
156 
157 	pSector = &SectorInfo[ gGarrisonGroup[ iGarrisonID ].ubSectorID ];
158 	iExistingForces = pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites;
159 	iReinforcementsRequested = gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bDesiredPopulation - iExistingForces;
160 
161 	//Record how many of the reinforcements are additionally provided due to being denied in the past.  This will grow
162 	//until it is finally excepted or an absolute max is made.
163 	*pubExtraReinforcements = (UINT8)(gubGarrisonReinforcementsDenied[ iGarrisonID ] / (6 - gGameOptions.ubDifficultyLevel));
164 	//Make sure the number of extra reinforcements don't bump the force size past the max of MAX_STRATEGIC_TEAM_SIZE.
165 	*pubExtraReinforcements = (UINT8)MIN( (INT32)*pubExtraReinforcements, MIN( (INT32)(*pubExtraReinforcements), MAX_STRATEGIC_TEAM_SIZE - iReinforcementsRequested ) );
166 
167 	iReinforcementsRequested = MIN( MAX_STRATEGIC_TEAM_SIZE, iReinforcementsRequested );
168 
169 	return iReinforcementsRequested;
170 }
171 
172 
PatrolReinforcementsRequested(PATROL_GROUP const * const pg)173 static INT32 PatrolReinforcementsRequested(PATROL_GROUP const* const pg)
174 {
175 	GROUP* const g = GetGroup(pg->ubGroupID);
176 	INT32 size = pg->bSize;
177 	if (g) size -= g->ubGroupSize;
178 	return size;
179 }
180 
181 
ReinforcementsAvailable(INT32 iGarrisonID)182 static INT32 ReinforcementsAvailable(INT32 iGarrisonID)
183 {
184 	SECTORINFO *pSector;
185 	INT32 iReinforcementsAvailable;
186 
187 	pSector = &SectorInfo[ gGarrisonGroup[ iGarrisonID ].ubSectorID ];
188 	iReinforcementsAvailable = pSector->ubNumTroops + pSector->ubNumElites + pSector->ubNumAdmins;
189 	iReinforcementsAvailable -= gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bDesiredPopulation;
190 
191 	switch( gGarrisonGroup[ iGarrisonID ].ubComposition )
192 	{
193 		case LEVEL1_DEFENCE:
194 		case LEVEL2_DEFENCE:
195 		case LEVEL3_DEFENCE:
196 		case ALMA_DEFENCE:
197 		case ALMA_MINE:
198 			//Legal spawning locations
199 			break;
200 		default:
201 			//No other sector permitted to send surplus troops
202 			return 0;
203 	}
204 
205 	return iReinforcementsAvailable;
206 }
207 
208 
PlayerForceTooStrong(UINT8 ubSectorID,UINT16 usOffensePoints,UINT16 * pusDefencePoints)209 static BOOLEAN PlayerForceTooStrong(UINT8 ubSectorID, UINT16 usOffensePoints, UINT16* pusDefencePoints)
210 {
211 	SECTORINFO *pSector;
212 	UINT8 ubSectorX, ubSectorY;
213 
214 	ubSectorX = (UINT8)SECTORX( ubSectorID );
215 	ubSectorY = (UINT8)SECTORY( ubSectorID );
216 	pSector = &SectorInfo[ ubSectorID ];
217 
218 	*pusDefencePoints = pSector->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]		* 1 +
219 											pSector->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] * 2 +
220 											pSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]		* 3 +
221 											PlayerMercsInSector( ubSectorX, ubSectorY, 0 )		* 5;
222 	if( *pusDefencePoints > usOffensePoints )
223 	{
224 		return TRUE;
225 	}
226 	return FALSE;
227 }
228 
229 
230 static void SendReinforcementsForGarrison(INT32 iDstGarrisonID, UINT16 usDefencePoints, GROUP** pOptionalGroup);
231 
232 
RequestAttackOnSector(UINT8 ubSectorID,UINT16 usDefencePoints)233 static void RequestAttackOnSector(UINT8 ubSectorID, UINT16 usDefencePoints)
234 {
235 	UINT32 i;
236 	for( i = 0; i < gGarrisonGroup.size(); i++ )
237 	{
238 		if( gGarrisonGroup[ i ].ubSectorID == ubSectorID && !gGarrisonGroup[ i ].ubPendingGroupID )
239 		{
240 			SLOGD("An attack has been requested in sector %c%d.",
241 					SECTORY( ubSectorID ) + 'A' - 1, SECTORX( ubSectorID ) );
242 			SendReinforcementsForGarrison( i, usDefencePoints, NULL );
243 			return;
244 		}
245 	}
246 }
247 
248 
AdjacentSectorIsImportantAndUndefended(UINT8 const sector_id)249 static bool AdjacentSectorIsImportantAndUndefended(UINT8 const sector_id)
250 {
251 	switch (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(sector_id)].bNameId)
252 	{
253 		case OMERTA:
254 		case SAN_MONA:
255 		case ESTONI:
256 			// These towns aren't important.
257 			return false;
258 	}
259 	SECTORINFO const& si = SectorInfo[sector_id];
260 	return
261 		si.ubNumTroops == 0                                 &&
262 		si.ubNumElites == 0                                 &&
263 		si.ubNumAdmins == 0                                 &&
264 		si.ubTraversability[THROUGH_STRATEGIC_MOVE] == TOWN &&
265 		!PlayerSectorDefended(sector_id);
266 }
267 
268 
269 static void ReassignAIGroup(GROUP** pGroup);
270 
271 
ValidateGroup(GROUP * pGroup)272 static void ValidateGroup(GROUP* pGroup)
273 {
274 	if( !pGroup->ubSectorX || !pGroup->ubSectorY || pGroup->ubSectorX > 16 || pGroup->ubSectorY > 16 )
275 	{
276 		if( gTacticalStatus.uiFlags & LOADING_SAVED_GAME )
277 		{
278 			SLOGE( "Internal error (invalid enemy group #%d location at %c%d, destination %c%d).  Please send PRIOR save file and Debug Log.",
279 				pGroup->ubGroupID, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX, pGroup->ubNextY + 'A' - 1, pGroup->ubNextX );
280 			RemoveGroupFromStrategicAILists(*pGroup);
281 			RemoveGroup(*pGroup);
282 			return;
283 		}
284 	}
285 	if( !pGroup->ubNextX || !pGroup->ubNextY )
286 	{
287 		if( !pGroup->fPlayer && pGroup->pEnemyGroup->ubIntention != STAGING
288 			&& pGroup->pEnemyGroup->ubIntention != REINFORCEMENTS )
289 		{
290 			SLOGE( "Internal error (floating group).  Please send PRIOR save file and Debug Log." );
291 			if( gTacticalStatus.uiFlags & LOADING_SAVED_GAME )
292 			{
293 				RemoveGroupFromStrategicAILists(*pGroup);
294 				ReassignAIGroup( &pGroup );
295 				return;
296 			}
297 		}
298 	}
299 	if( pGroup->pEnemyGroup->ubNumAdmins + pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites != pGroup->ubGroupSize ||
300 			pGroup->ubGroupSize > MAX_STRATEGIC_TEAM_SIZE )
301 		{
302 			SLOGE( "Internal error (bad group populations).  Please send PRIOR save file and Debug Log." );
303 		}
304 }
305 
306 
ValidateLargeGroup(GROUP * pGroup)307 static void ValidateLargeGroup(GROUP* pGroup)
308 {
309 		if( pGroup->ubGroupSize > 25 )
310 		{
311 			SLOGW( "warning:  Enemy group containing %d soldiers\n\
312 				(%d admins, %d troops, %d elites) in sector %c%d.  This message is a temporary test message\n\
313 				to evaluate a potential problems with very large enemy groups.",
314 				pGroup->ubGroupSize, pGroup->pEnemyGroup->ubNumAdmins, pGroup->pEnemyGroup->ubNumTroops, pGroup->pEnemyGroup->ubNumElites,
315 				pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX );
316 		}
317 }
318 
319 
InitStrategicAI()320 void InitStrategicAI()
321 {
322 	gfExtraElites                      = FALSE;
323 	gubNumAwareBattles                 = 0;
324 	gfQueenAIAwake                     = FALSE;
325 	giReinforcementPoints              = 0;
326 	giRequestPoints                    = 0;
327 	gubSAIVersion                      = SAI_VERSION;
328 	gubQueenPriorityPhase              = 0;
329 	gfFirstBattleMeanwhileScenePending = FALSE;
330 	gfMassFortificationOrdered         = FALSE;
331 	gusPlayerBattleVictories           = 0;
332 	gfUseAlternateQueenPosition        = FALSE;
333 
334 	// 475 is 7:55am in minutes since midnight, the time the game starts on day 1
335 	UINT32      evaluate_time = 475;
336 	UINT8 const difficulty    = gGameOptions.ubDifficultyLevel;
337 
338 	giReinforcementPool   = saipolicy(queens_pool_of_troops);
339 	giForcePercentage     = saipolicy(initial_garrison_percentages);
340 	giArmyAlertness       = saipolicy(enemy_starting_alert_level);
341 	giArmyAlertnessDecay  = saipolicy(enemy_starting_alert_decay);
342 	gubMinEnemyGroupSize  = saipolicy(min_enemy_group_size);
343 	gubHoursGracePeriod   = saipolicy(grace_period_in_hours);
344 	evaluate_time        += saipolicy(time_evaluate_in_minutes) + Random(saipolicy(time_evaluate_variance));
345 
346 	AddStrategicEvent(EVENT_EVALUATE_QUEEN_SITUATION, evaluate_time, 0);
347 
348 	//Initialize the sectorinfo structure so all sectors don't point to a garrisonID.
349 	FOR_EACH(SECTORINFO, i, SectorInfo)
350 	{
351 		i->ubGarrisonID = NO_GARRISON;
352 	}
353 
354 	/* Copy over the original army composition as it does get modified during the
355 	 * campaign. This bulletproofs starting the game over again. */
356 	gArmyComp = GCM->getArmyCompositions();
357 
358 	// Eliminate more perimeter defenses on the easier levels.
359 	switch (difficulty)
360 	{
361 		case DIF_LEVEL_EASY:
362 			gArmyComp[LEVEL2_DEFENCE].bDesiredPopulation = 0;
363 			gArmyComp[LEVEL2_DEFENCE].bStartPopulation   = 0;
364 			/* FALLTHROUGH */
365 		case DIF_LEVEL_MEDIUM:
366 			gArmyComp[LEVEL3_DEFENCE].bDesiredPopulation = 0;
367 			gArmyComp[LEVEL3_DEFENCE].bStartPopulation   = 0;
368 			break;
369 	}
370 
371 	// Initialize the patrol group definitions
372 	gPatrolGroup = GCM->getPatrolGroups();
373 	gubPatrolReinforcementsDenied = new UINT8[gPatrolGroup.size()]{};
374 
375 	// Initialize the garrison group definitions
376 	auto origGarrisonGroups = GCM->getGarrisonGroups();
377 	auto iGarrisonArraySize = origGarrisonGroups.size();
378 	gGarrisonGroup = origGarrisonGroups;
379 
380 	gubGarrisonReinforcementsDenied = new UINT8[iGarrisonArraySize]{};
381 
382 	// Modify initial force sizes?
383 	INT32 const force_percentage = giForcePercentage;
384 	if (force_percentage != 100)
385 	{ /* The initial force sizes are being modified, so go through each of the
386 		 * army compositions and adjust them accordingly. */
387 		for (size_t i = 0; i != gArmyComp.size(); ++i)
388 		{
389 			ARMY_COMPOSITION& a = gArmyComp[i];
390 			if (i != QUEEN_DEFENCE)
391 			{
392 				a.bDesiredPopulation = MIN(MAX_STRATEGIC_TEAM_SIZE, a.bDesiredPopulation * force_percentage / 100);
393 				if (a.bStartPopulation != MAX_STRATEGIC_TEAM_SIZE)
394 				{ /* If the value is MAX_STRATEGIC_TEAM_SIZE, then that means the
395 					 * particular sector is a spawning location. Don't modify the value if
396 					 * it is MAX_STRATEGIC_TEAM_SIZE. Everything else is game. */
397 					a.bStartPopulation = MIN(MAX_STRATEGIC_TEAM_SIZE, a.bStartPopulation * force_percentage / 100);
398 				}
399 			}
400 			else
401 			{
402 				a.bDesiredPopulation = MIN(32, a.bDesiredPopulation * force_percentage / 100);
403 				a.bStartPopulation   = a.bDesiredPopulation;
404 			}
405 		}
406 		for (size_t i = 0; i != gPatrolGroup.size(); ++i)
407 		{ // Force modified range within 1 - MAX_STRATEGIC_TEAM_SIZE.
408 			INT8& size = gPatrolGroup[i].bSize;
409 			size = MAX(gubMinEnemyGroupSize, MIN(MAX_STRATEGIC_TEAM_SIZE, size * force_percentage / 100));
410 		}
411 	}
412 
413 	/* Initialize the garrisons based on the initial sizes (all variances are plus
414 	 * or minus 1). */
415 	for (UINT32 i = 0; i != iGarrisonArraySize; ++i)
416 	{
417 		GARRISON_GROUP& gg = gGarrisonGroup[i];
418 		SECTORINFO&     si = SectorInfo[gg.ubSectorID];
419 		si.ubGarrisonID = i;
420 		ARMY_COMPOSITION const& ac = gArmyComp[gg.ubComposition];
421 		INT32       start_pop    = ac.bStartPopulation;
422 		INT32 const desired_pop  = ac.bDesiredPopulation;
423 		INT32 const iPriority    = ac.bPriority;
424 		INT32 const elite_chance = ac.bElitePercentage;
425 		INT32 const troop_chance = ac.bTroopPercentage + elite_chance;
426 		INT32 const admin_chance = ac.bAdminPercentage;
427 
428 		switch (gg.ubComposition)
429 		{
430 			case ROADBLOCK:
431 				si.uiFlags |= SF_ENEMY_AMBUSH_LOCATION;
432 				start_pop   = Chance(20) ? ac.bDesiredPopulation : 0;
433 				break;
434 
435 			case SANMONA_SMALL:
436 				start_pop = 0; // Not appropriate until Kingpin is killed.
437 				break;
438 		}
439 
440 		if (start_pop != 0)
441 		{
442 			if (gg.ubSectorID != SEC_P3)
443 			{
444 				// if population is less than maximum
445 				if (start_pop != MAX_STRATEGIC_TEAM_SIZE)
446 				{
447 					// then vary it a bit (+/- 25%)
448 					start_pop = start_pop * (100 + Random(51) - 25) / 100;
449 				}
450 
451 				start_pop = MAX(gubMinEnemyGroupSize, MIN(MAX_STRATEGIC_TEAM_SIZE, start_pop));
452 			}
453 
454 			if (admin_chance != 0)
455 			{
456 				si.ubNumAdmins = admin_chance * start_pop / 100;
457 			}
458 			else for (INT32 cnt = start_pop; cnt != 0; --cnt)
459 			{ // For each soldier randomly determine the type.
460 				INT32 const roll = Random(100);
461 				if (roll < elite_chance)
462 				{
463 					++si.ubNumElites;
464 				}
465 				else if (roll < troop_chance)
466 				{
467 					++si.ubNumTroops;
468 				}
469 			}
470 
471 			switch (gg.ubComposition)
472 			{
473 				case CAMBRIA_DEFENCE:
474 				case CAMBRIA_MINE:
475 				case ALMA_MINE:
476 				case GRUMM_MINE:
477 					// Fill up extra start slots with troops
478 					start_pop      -= si.ubNumAdmins;
479 					si.ubNumTroops  = start_pop;
480 					break;
481 
482 				case DRASSEN_AIRPORT:
483 				case DRASSEN_DEFENCE:
484 				case DRASSEN_MINE:
485 					si.ubNumAdmins = MAX(5, si.ubNumAdmins);
486 					break;
487 
488 				case TIXA_PRISON:
489 					si.ubNumAdmins = MAX(8, si.ubNumAdmins);
490 					break;
491 			}
492 		}
493 
494 		if (admin_chance != 0 && si.ubNumAdmins < gubMinEnemyGroupSize)
495 		{
496 			si.ubNumAdmins = gubMinEnemyGroupSize;
497 		}
498 
499 		/* Calculate weight (range is -20 to +20 before multiplier). The multiplier
500 		 * of 3 brings it to a range of -96 to +96 which is close enough to a
501 		 * plus/minus 100%. The resultant percentage is then converted based on the
502 		 * priority. */
503 		INT32 weight = (desired_pop - start_pop) * 3;
504 		if (weight > 0)
505 		{ // Modify it by its priority.
506 			// Generates a value between 2 and 100
507 			weight = weight * iPriority / 96;
508 			weight = MAX(weight, 2);
509 			giRequestPoints += weight;
510 		}
511 		else if (weight < 0)
512 		{ // Modify it by its reverse priority
513 			// Generates a value between -2 and -100
514 			weight = weight * (100 - iPriority) / 96;
515 			weight = MIN(weight, -2);
516 			giReinforcementPoints -= weight;
517 		}
518 		gg.bWeight = weight;
519 
520 		/* Post an event which allows them to check adjacent sectors periodically.
521 		 * Spread them out so that they process at different times. */
522 		AddPeriodStrategicEventWithOffset(EVENT_CHECK_ENEMY_CONTROLLED_SECTOR, 140 - 20 * difficulty + Random(4), 475 + i, gg.ubSectorID);
523 	}
524 
525 	// Initialize each of the patrol groups
526 	for (size_t i = 0; i != gPatrolGroup.size(); ++i)
527 	{
528 		PATROL_GROUP& pg = gPatrolGroup[i];
529 		UINT8 n_troops = pg.bSize + Random(3) - 1;
530 		n_troops = MAX(gubMinEnemyGroupSize, MIN(MAX_STRATEGIC_TEAM_SIZE, n_troops));
531 		/* Note on adding patrol groups: The patrol group can't actually start on
532 		 * the first waypoint, so we set it to the second way point for
533 		 * initialization, and then add the waypoints from 0 up */
534 		GROUP&      g  = *CreateNewEnemyGroupDepartingFromSector(pg.ubSectorID[1], 0, n_troops, 0);
535 		ENEMYGROUP& eg = *g.pEnemyGroup;
536 
537 		if (i == 3 || i == 4)
538 		{ /* Special case: Two patrol groups are administrator groups -- rest are
539 			 * troops */
540 			eg.ubNumAdmins = eg.ubNumTroops;
541 			eg.ubNumTroops = 0;
542 		}
543 		pg.ubGroupID   = g.ubGroupID;
544 		eg.ubIntention = PATROL;
545 		g.ubMoveType   = ENDTOEND_FORWARDS;
546 		FOR_EACH(UINT8 const, i, pg.ubSectorID)
547 		{
548 			if (*i == 0) break;
549 			AddWaypointIDToPGroup(&g, *i);
550 		}
551 		RandomizePatrolGroupLocation(&g);
552 		ValidateGroup(&g);
553 	}
554 
555 	/* Choose one cache map out of five possible maps. Select the sector randomly,
556 	 * set up the flags to use the alternate map, then place 8-12 regular troops
557 	 * there (no AI though). Changing MAX_STRATEGIC_TEAM_SIZE may require changes
558 	 * to to the defending force here. */
559 	auto model = GCM->getCacheSectors();
560 	INT16 sSectorID = model->pickSector();
561 	if (sSectorID >= 0)
562 	{
563 		SECTORINFO &si = SectorInfo[sSectorID];
564 		si.uiFlags |= SF_USE_ALTERNATE_MAP;
565 		si.ubNumTroops = model->getNumTroops(difficulty);
566 		STLOGD("Weapon cache is at {}", SECTOR_SHORT_STRING(sSectorID));
567 	}
568 }
569 
570 
KillStrategicAI()571 void KillStrategicAI()
572 {
573 	gPatrolGroup.clear();
574 	gGarrisonGroup.clear();
575 
576 	if( gubPatrolReinforcementsDenied )
577 	{
578 		delete[] gubPatrolReinforcementsDenied;
579 		gubPatrolReinforcementsDenied = NULL;
580 	}
581 	if( gubGarrisonReinforcementsDenied )
582 	{
583 		delete[] gubGarrisonReinforcementsDenied;
584 		gubGarrisonReinforcementsDenied = NULL;
585 	}
586 	DeleteAllStrategicEventsOfType( EVENT_EVALUATE_QUEEN_SITUATION );
587 }
588 
OkayForEnemyToMoveThroughSector(UINT8 ubSectorID)589 BOOLEAN OkayForEnemyToMoveThroughSector( UINT8 ubSectorID )
590 {
591 	SECTORINFO *pSector;
592 	pSector = &SectorInfo[ ubSectorID ];
593 	if( pSector->uiTimeLastPlayerLiberated && pSector->uiTimeLastPlayerLiberated + (gubHoursGracePeriod * 3600) > GetWorldTotalSeconds() )
594 	{
595 		return FALSE;
596 	}
597 	return TRUE;
598 }
599 
600 
EnemyPermittedToAttackSector(GROUP ** pGroup,UINT8 ubSectorID)601 static BOOLEAN EnemyPermittedToAttackSector(GROUP** pGroup, UINT8 ubSectorID)
602 {
603 	SECTORINFO *pSector;
604 	BOOLEAN fPermittedToAttack = TRUE;
605 
606 	pSector = &SectorInfo[ ubSectorID ];
607 	fPermittedToAttack = OkayForEnemyToMoveThroughSector( ubSectorID );
608 	if( pGroup && *pGroup && pSector->ubGarrisonID != NO_GARRISON )
609 	{
610 		if( gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID )
611 		{
612 			GROUP *pPendingGroup;
613 			pPendingGroup = GetGroup( gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID );
614 			if( pPendingGroup == *pGroup )
615 			{
616 				if( fPermittedToAttack )
617 				{
618 					if( GroupAtFinalDestination( *pGroup ) )
619 					{ //High priority reinforcements have arrived.  This overrides most other situations.
620 						return TRUE;
621 					}
622 				}
623 				else
624 				{ //Reassign the group
625 					ReassignAIGroup( pGroup );
626 				}
627 			}
628 		}
629 	}
630 	if( !fPermittedToAttack )
631 	{
632 		return FALSE;
633 	}
634 	//If Hill-billies are alive, then enemy won't attack the sector.
635 	switch( ubSectorID )
636 	{
637 		case SEC_F10:
638 			//Hill-billy farm -- not until hill billies are dead.
639 			if (CheckFact(FACT_HILLBILLIES_KILLED, FALSE))
640 				return FALSE;
641 			break;
642 		case SEC_A9:
643 		case SEC_A10:
644 			//Omerta -- not until Day 2 at 7:45AM.
645 			if( GetWorldTotalMin() < 3345 )
646 				return FALSE;
647 			break;
648 		case SEC_B13:
649 		case SEC_C13:
650 		case SEC_D13:
651 			//Drassen -- not until Day 3 at 6:30AM.
652 			if( GetWorldTotalMin() < 4710 )
653 				return FALSE;
654 			break;
655 		case SEC_C5:
656 		case SEC_C6:
657 		case SEC_D5:
658 			//San Mona -- not until Kingpin is dead.
659 			return CheckFact(FACT_KINGPIN_DEAD, 0);
660 
661 		case SEC_G1:
662 			if( PlayerSectorDefended( SEC_G2 ) && (PlayerSectorDefended( SEC_H1 ) || PlayerSectorDefended( SEC_H2 )) )
663 			{
664 				return FALSE;
665 			}
666 			break;
667 		case SEC_H2:
668 			if( PlayerSectorDefended( SEC_H2 ) && (PlayerSectorDefended( SEC_G1 ) || PlayerSectorDefended( SEC_G2 )) )
669 			{
670 				return FALSE;
671 			}
672 			break;
673 	}
674 	return TRUE;
675 }
676 
677 
678 enum SAIMOVECODE
679 {
680 	DIRECT,
681 	EVASIVE,
682 	STAGE
683 };
684 static void MoveSAIGroupToSector(GROUP**, UINT8 sector, SAIMOVECODE, UINT8 intention);
685 
686 
HandlePlayerGroupNoticedByPatrolGroup(const GROUP * const pPlayerGroup,GROUP * pEnemyGroup)687 static BOOLEAN HandlePlayerGroupNoticedByPatrolGroup(const GROUP* const pPlayerGroup, GROUP* pEnemyGroup)
688 {
689 	UINT16 usDefencePoints;
690 	UINT16 usOffensePoints;
691 
692 	UINT8 const ubSectorID = SECTOR(pPlayerGroup->ubSectorX, pPlayerGroup->ubSectorY);
693 	usOffensePoints = pEnemyGroup->pEnemyGroup->ubNumAdmins * 2 +
694 										pEnemyGroup->pEnemyGroup->ubNumTroops * 4 +
695 										pEnemyGroup->pEnemyGroup->ubNumElites * 6;
696 
697 	const UINT8 playerSector = pPlayerGroup->ubNextX
698 			? SECTOR(pPlayerGroup->ubNextX, pPlayerGroup->ubNextY)
699 			: SECTOR(pPlayerGroup->ubSectorX, pPlayerGroup->ubSectorY);
700 
701 	if(PlayerForceTooStrong(ubSectorID, usOffensePoints, &usDefencePoints)
702 	   || SectorInfo[playerSector].ubGarrisonID != NO_GARRISON)
703 	{
704 		RequestAttackOnSector(ubSectorID, usDefencePoints);
705 		return FALSE;
706 	}
707 	//For now, automatically attack.
708 	if( pPlayerGroup->ubNextX )
709 	{
710 		MoveSAIGroupToSector( &pEnemyGroup, playerSector, DIRECT, PURSUIT );
711 
712 		SLOGD("Enemy group at %c%d detected player group at %c%d and is moving to intercept them at %c%d.",
713 					pEnemyGroup->ubSectorY + 'A' - 1, pEnemyGroup->ubSectorX,
714 					pPlayerGroup->ubSectorY + 'A' - 1, pPlayerGroup->ubSectorX,
715 					pPlayerGroup->ubNextY + 'A' - 1, pPlayerGroup->ubNextX );
716 	}
717 	else
718 	{
719 		MoveSAIGroupToSector( &pEnemyGroup, playerSector, DIRECT, PURSUIT );
720 
721 		SLOGD("Enemy group at %c%d detected player group at %c%d and is moving to intercept them at %c%d.",
722 					pEnemyGroup->ubSectorY + 'A' - 1, pEnemyGroup->ubSectorX,
723 					pPlayerGroup->ubSectorY + 'A' - 1, pPlayerGroup->ubSectorX,
724 					pPlayerGroup->ubSectorY + 'A' - 1, pPlayerGroup->ubSectorX );
725 	}
726 	return TRUE;
727 }
728 
729 
730 static void ConvertGroupTroopsToComposition(GROUP* pGroup, INT32 iCompositionID);
731 static void RemoveSoldiersFromGarrisonBasedOnComposition(INT32 iGarrisonID, UINT8 ubSize);
732 
733 
HandlePlayerGroupNoticedByGarrison(const GROUP * const pPlayerGroup,const UINT8 ubSectorID)734 static void HandlePlayerGroupNoticedByGarrison(const GROUP* const pPlayerGroup, const UINT8 ubSectorID)
735 {
736 	SECTORINFO *pSector;
737 	GROUP *pGroup;
738 	INT32 iReinforcementsApproved;
739 	UINT16 usOffensePoints, usDefencePoints;
740 	UINT8 ubEnemies;
741 	pSector = &SectorInfo[ ubSectorID ];
742 	//First check to see if the player is at his final destination.
743 	if( !GroupAtFinalDestination( pPlayerGroup ) )
744 	{
745 		return;
746 	}
747 	usOffensePoints = pSector->ubNumAdmins * 2 +
748 										pSector->ubNumTroops * 4 +
749 										pSector->ubNumElites * 6;
750 	if( PlayerForceTooStrong( ubSectorID, usOffensePoints, &usDefencePoints ) )
751 	{
752 		RequestAttackOnSector( ubSectorID, usDefencePoints );
753 		return;
754 	}
755 
756 	if( pSector->ubGarrisonID != NO_GARRISON )
757 	{
758 		//Decide whether or not they will attack them with some of the troops.
759 		ubEnemies = (UINT8)(pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites);
760 		iReinforcementsApproved = (ubEnemies - gArmyComp[ gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition ].bDesiredPopulation / 2);
761 		if( iReinforcementsApproved*2 > pPlayerGroup->ubGroupSize*3 && iReinforcementsApproved > gubMinEnemyGroupSize )
762 		{ //Then enemy's available outnumber the player by at least 3:2, so attack them.
763 			pGroup = CreateNewEnemyGroupDepartingFromSector( ubSectorID, 0, (UINT8)iReinforcementsApproved, 0 );
764 
765 			ConvertGroupTroopsToComposition( pGroup, gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition );
766 
767 			MoveSAIGroupToSector( &pGroup, (UINT8)SECTOR( pPlayerGroup->ubSectorX, pPlayerGroup->ubSectorY ), DIRECT, REINFORCEMENTS );
768 
769 			RemoveSoldiersFromGarrisonBasedOnComposition( pSector->ubGarrisonID, pGroup->ubGroupSize );
770 
771 			if( pSector->ubNumTroops + pSector->ubNumElites + pSector->ubNumAdmins > MAX_STRATEGIC_TEAM_SIZE )
772 			{
773 				SLOGE("Sector %c%d now has %d enemies (max %d).",
774 						gGarrisonGroup[ pSector->ubGarrisonID ].ubSectorID / 16 + 'A' , gGarrisonGroup[ pSector->ubGarrisonID ].ubSectorID % 16,
775 						pSector->ubNumTroops + pSector->ubNumElites + pSector->ubNumAdmins, MAX_STRATEGIC_TEAM_SIZE );
776 			}
777 
778 			SLOGD("Enemy garrison at %c%d detected stopped player group at %c%d and is sending %d troops to attack.",
779 					gGarrisonGroup[ pSector->ubGarrisonID ].ubSectorID / 16 + 'A' , gGarrisonGroup[ pSector->ubGarrisonID ].ubSectorID % 16,
780 					pPlayerGroup->ubSectorY + 'A' - 1, pPlayerGroup->ubSectorX,
781 					pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumTroops );
782 		}
783 	}
784 }
785 
786 
HandleMilitiaNoticedByPatrolGroup(UINT8 ubSectorID,GROUP * pEnemyGroup)787 static BOOLEAN HandleMilitiaNoticedByPatrolGroup(UINT8 ubSectorID, GROUP* pEnemyGroup)
788 {
789 	//For now, automatically attack.
790 	UINT16 usOffensePoints, usDefencePoints;
791 	UINT8 ubSectorX = (UINT8)(ubSectorID % 16) + 1;
792 	UINT8 ubSectorY = (UINT8)(ubSectorID / 16) + 1;
793 	usOffensePoints = pEnemyGroup->pEnemyGroup->ubNumAdmins * 2 +
794 										pEnemyGroup->pEnemyGroup->ubNumTroops * 4 +
795 										pEnemyGroup->pEnemyGroup->ubNumElites * 6;
796 	if( PlayerForceTooStrong( ubSectorID, usOffensePoints, &usDefencePoints ) )
797 	{
798 		RequestAttackOnSector( ubSectorID, usDefencePoints );
799 		return FALSE;
800 	}
801 
802 	MoveSAIGroupToSector( &pEnemyGroup, (UINT8)SECTOR( ubSectorX, ubSectorY ), DIRECT, REINFORCEMENTS );
803 	SLOGD("Enemy group at %c%d detected militia at %c%d and is moving to attack them.",
804 			pEnemyGroup->ubSectorY + 'A' - 1, pEnemyGroup->ubSectorX,
805 			ubSectorY + 'A' - 1, ubSectorX );
806 	return FALSE;
807 }
808 
809 
AttemptToNoticeEmptySectorSucceeds(void)810 static BOOLEAN AttemptToNoticeEmptySectorSucceeds(void)
811 {
812 	if( gubNumAwareBattles || gfAutoAIAware )
813 	{ //The queen is in high-alert and is searching for players.  All adjacent checks will automatically succeed.
814 		return TRUE;
815 	}
816 	if( DayTime() )
817 	{ //Day time chances are normal
818 		if( Chance( giArmyAlertness ) )
819 		{
820 			giArmyAlertness -= giArmyAlertnessDecay;
821 			//Minimum alertness should always be at least 0.
822 			giArmyAlertness = MAX( 0, giArmyAlertness );
823 			return TRUE;
824 		}
825 		giArmyAlertness++;
826 		return FALSE;
827 	}
828 	//Night time chances are one third of normal.
829 	if( Chance( giArmyAlertness/3 ) )
830 	{
831 		giArmyAlertness -= giArmyAlertnessDecay;
832 		//Minimum alertness should always be at least 0.
833 		giArmyAlertness = MAX( 0, giArmyAlertness );
834 		return TRUE;
835 	}
836 	if( Chance( 33 ) )
837 	{
838 		giArmyAlertness++;
839 	}
840 	return FALSE;
841 }
842 
843 
844 //Calling the function assumes that a player group is found to be adjacent to an enemy group.
845 //This uses the alertness rating to emulate the chance that the group will notice.  If it does
846 //notice, then the alertness drops accordingly to simulate a period of time where the enemy would
847 //not notice as much.  If it fails, the alertness gradually increases until it succeeds.
AttemptToNoticeAdjacentGroupSucceeds(void)848 static BOOLEAN AttemptToNoticeAdjacentGroupSucceeds(void)
849 {
850 	if( gubNumAwareBattles || gfAutoAIAware )
851 	{ //The queen is in high-alert and is searching for players.  All adjacent checks will automatically succeed.
852 		return TRUE;
853 	}
854 	if( DayTime() )
855 	{ //Day time chances are normal
856 		if( Chance( giArmyAlertness ) )
857 		{
858 			giArmyAlertness -= giArmyAlertnessDecay;
859 			//Minimum alertness should always be at least 0.
860 			giArmyAlertness = MAX( 0, giArmyAlertness );
861 			return TRUE;
862 		}
863 		giArmyAlertness++;
864 		return FALSE;
865 	}
866 	//Night time chances are one third of normal.
867 	if( Chance( giArmyAlertness/3 ) )
868 	{
869 		giArmyAlertness -= giArmyAlertnessDecay;
870 		//Minimum alertness should always be at least 0.
871 		giArmyAlertness = MAX( 0, giArmyAlertness );
872 		return TRUE;
873 	}
874 	if( Chance( 33 ) )
875 	{
876 		giArmyAlertness++;
877 	}
878 	return FALSE;
879 }
880 
881 
HandleEmptySectorNoticedByPatrolGroup(GROUP * pGroup,UINT8 ubEmptySectorID)882 static BOOLEAN HandleEmptySectorNoticedByPatrolGroup(GROUP* pGroup, UINT8 ubEmptySectorID)
883 {
884 	UINT8 ubGarrisonID;
885 	UINT8 ubSectorX = (UINT8)(ubEmptySectorID % 16) + 1;
886 	UINT8 ubSectorY = (UINT8)(ubEmptySectorID / 16) + 1;
887 
888 	ubGarrisonID = SectorInfo[ ubEmptySectorID ].ubGarrisonID;
889 	if( ubGarrisonID != NO_GARRISON )
890 	{
891 		if( gGarrisonGroup[ ubGarrisonID ].ubPendingGroupID )
892 		{
893 			return FALSE;
894 		}
895 	}
896 	else
897 	{
898 		return FALSE;
899 	}
900 
901 	//Clear the patrol group's previous orders.
902 	RemoveGroupFromStrategicAILists(*pGroup);
903 
904 	gGarrisonGroup[ ubGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
905 	MoveSAIGroupToSector( &pGroup, (UINT8)SECTOR( ubSectorX, ubSectorY ), DIRECT, REINFORCEMENTS );
906 	SLOGD("Enemy group at %c%d detected undefended sector at %c%d and is moving to retake it.",
907 			pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX,
908 			ubSectorY + 'A' - 1, ubSectorX );
909 	return TRUE;
910 }
911 
912 
HandleEmptySectorNoticedByGarrison(UINT8 ubGarrisonSectorID,UINT8 ubEmptySectorID)913 static void HandleEmptySectorNoticedByGarrison(UINT8 ubGarrisonSectorID, UINT8 ubEmptySectorID)
914 {
915 	SECTORINFO *pSector;
916 	GROUP *pGroup;
917 	UINT8 ubAvailableTroops;
918 	UINT8 ubSrcGarrisonID = 255, ubDstGarrisonID = 255;
919 
920 	//Make sure that the destination sector doesn't already have a pending group.
921 	pSector = &SectorInfo[ ubEmptySectorID ];
922 
923 	ubSrcGarrisonID = SectorInfo[ ubGarrisonSectorID ].ubGarrisonID;
924 	ubDstGarrisonID = SectorInfo[ ubEmptySectorID ].ubGarrisonID;
925 
926 	if( ubSrcGarrisonID == NO_GARRISON || ubDstGarrisonID == NO_GARRISON )
927 	{ //Bad logic
928 		return;
929 	}
930 
931 	if( gGarrisonGroup[ ubDstGarrisonID ].ubPendingGroupID )
932 	{ //A group is already on-route, so don't send anybody from here.
933 		return;
934 	}
935 
936 	//An opportunity has arisen, where the enemy has noticed an important sector that is undefended.
937 	pSector = &SectorInfo[ ubGarrisonSectorID ];
938 	ubAvailableTroops = pSector->ubNumTroops + pSector->ubNumElites + pSector->ubNumAdmins;
939 
940 	if( ubAvailableTroops >= gubMinEnemyGroupSize * 2 )
941 	{ //split group into two groups, and move one of the groups to the next sector.
942 		pGroup = CreateNewEnemyGroupDepartingFromSector( ubGarrisonSectorID, 0, (UINT8)(ubAvailableTroops / 2), 0 );
943 		ConvertGroupTroopsToComposition( pGroup, gGarrisonGroup[ ubDstGarrisonID ].ubComposition );
944 		RemoveSoldiersFromGarrisonBasedOnComposition( ubSrcGarrisonID, pGroup->ubGroupSize );
945 		gGarrisonGroup[ ubDstGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
946 		MoveSAIGroupToSector( &pGroup, ubEmptySectorID, DIRECT, REINFORCEMENTS );
947 	}
948 }
949 
950 
ReinforcementsApproved(INT32 iGarrisonID,UINT16 * pusDefencePoints)951 static BOOLEAN ReinforcementsApproved(INT32 iGarrisonID, UINT16* pusDefencePoints)
952 {
953 	SECTORINFO *pSector;
954 	UINT16 usOffensePoints;
955 	UINT8 ubSectorX, ubSectorY;
956 
957 	pSector = &SectorInfo[ gGarrisonGroup[ iGarrisonID ].ubSectorID ];
958 	ubSectorX = (UINT8)SECTORX( gGarrisonGroup[ iGarrisonID ].ubSectorID );
959 	ubSectorY = (UINT8)SECTORY( gGarrisonGroup[ iGarrisonID ].ubSectorID );
960 
961 	*pusDefencePoints = pSector->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]		* 1 +
962 										pSector->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] * 2 +
963 										pSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]		* 3 +
964 										PlayerMercsInSector( ubSectorX, ubSectorY, 0 )		* 4;
965 	usOffensePoints = gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bAdminPercentage * 2 +
966 										gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bTroopPercentage * 3 +
967 										gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bElitePercentage * 4 +
968 										gubGarrisonReinforcementsDenied[ iGarrisonID ];
969 	usOffensePoints = usOffensePoints * gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bDesiredPopulation / 100;
970 
971 	if( usOffensePoints > *pusDefencePoints )
972 	{
973 		return TRUE;
974 	}
975 	//Before returning false, determine if reinforcements have been denied repeatedly.  If so, then
976 	//we might send an augmented force to take it back.
977 	if( gubGarrisonReinforcementsDenied[ iGarrisonID ] + usOffensePoints > *pusDefencePoints )
978 	{
979 		SLOGD("Sector %c%d will now recieve an %d extra troops due to multiple denials for reinforcements in the past for strong player presence.",
980 				ubSectorY + 'A' - 1, ubSectorX, gubGarrisonReinforcementsDenied[ iGarrisonID ] / 3 );
981 		return TRUE;
982 	}
983 	//Reinforcements will have to wait.  For now, increase the reinforcements denied.  The amount increase is 20 percent
984 	//of the garrison's priority.
985 	gubGarrisonReinforcementsDenied[ iGarrisonID ] += (UINT8)(gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bPriority / 2);
986 
987 	return FALSE;
988 }
989 
990 
991 static void EliminateSurplusTroopsForGarrison(GROUP* pGroup, SECTORINFO* pSector);
992 static void RecalculateGarrisonWeight(INT32 iGarrisonID);
993 static void RecalculatePatrolWeight(PATROL_GROUP&);
994 
995 
996 //if the group has arrived in a sector, and doesn't have any particular orders, then
997 //send him back where they came from.
998 //RETURNS TRUE if the group is deleted or told to move somewhere else.
999 //This is important as the calling function will need
1000 //to abort processing of the group for obvious reasons.
EvaluateGroupSituation(GROUP * pGroup)1001 static BOOLEAN EvaluateGroupSituation(GROUP* pGroup)
1002 {
1003 	SECTORINFO *pSector;
1004 	GROUP *pPatrolGroup;
1005 	UINT32 i;
1006 
1007 	if( !gfQueenAIAwake )
1008 	{
1009 		return FALSE;
1010 	}
1011 	Assert( !pGroup->fPlayer );
1012 	if( pGroup->pEnemyGroup->ubIntention == PURSUIT )
1013 	{ //Lost the player group that he was going to attack.  Return to original position.
1014 		ReassignAIGroup( &pGroup );
1015 		return TRUE;
1016 	}
1017 	else if( pGroup->pEnemyGroup->ubIntention == REINFORCEMENTS )
1018 	{ //The group has arrived at the location where he is supposed to reinforce.
1019 		//Step 1 -- Check for matching garrison location
1020 		for( i = 0; i < gGarrisonGroup.size(); i++ )
1021 		{
1022 			if( gGarrisonGroup[ i ].ubSectorID == SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) &&
1023 					gGarrisonGroup[ i ].ubPendingGroupID == pGroup->ubGroupID )
1024 			{
1025 				pSector = &SectorInfo[ SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) ];
1026 
1027 				if( gGarrisonGroup[ i ].ubSectorID != SEC_P3 )
1028 				{
1029 					EliminateSurplusTroopsForGarrison( pGroup, pSector );
1030 					pSector->ubNumAdmins = (UINT8)(pSector->ubNumAdmins + pGroup->pEnemyGroup->ubNumAdmins);
1031 					pSector->ubNumTroops = (UINT8)(pSector->ubNumTroops + pGroup->pEnemyGroup->ubNumTroops);
1032 					pSector->ubNumElites = (UINT8)(pSector->ubNumElites + pGroup->pEnemyGroup->ubNumElites);
1033 
1034 					SLOGD("%d reinforcements have arrived to garrison sector %c%d",
1035 							pGroup->pEnemyGroup->ubNumAdmins + pGroup->pEnemyGroup->ubNumTroops +
1036 							pGroup->pEnemyGroup->ubNumElites, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX );
1037 					if( IsThisSectorASAMSector( pGroup->ubSectorX, pGroup->ubSectorY, 0 ) )
1038 					{
1039 						StrategicMap[ pGroup->ubSectorX + pGroup->ubSectorY * MAP_WORLD_X ].bSAMCondition = 100;
1040 						UpdateSAMDoneRepair( pGroup->ubSectorX, pGroup->ubSectorY, 0 );
1041 					}
1042 				}
1043 				else
1044 				{ //The group was sent back to the queen's palace (probably because they couldn't be reassigned
1045 					//anywhere else, but it is possible that the queen's sector is requesting the reinforcements.  In
1046 					//any case, if the queen's sector is less than full strength, fill it up first, then
1047 					//simply add the rest to the global pool.
1048 					if( pSector->ubNumElites < MAX_STRATEGIC_TEAM_SIZE )
1049 					{
1050 						if( pSector->ubNumElites + pGroup->ubGroupSize >= MAX_STRATEGIC_TEAM_SIZE )
1051 						{ //Fill up the queen's guards, then apply the rest to the reinforcement pool
1052 							giReinforcementPool += MAX_STRATEGIC_TEAM_SIZE - pSector->ubNumElites;
1053 							pSector->ubNumElites = MAX_STRATEGIC_TEAM_SIZE;
1054 							SLOGD("%d reinforcements have arrived to garrison queen's sector.  The excess troops will be relocated to the reinforcement pool.",
1055 									pGroup->ubGroupSize );
1056 						}
1057 						else
1058 						{ //Add all the troops to the queen's guard.
1059 							pSector->ubNumElites += pGroup->ubGroupSize;
1060 							SLOGD(ST::format("{} reinforcements have arrived to garrison queen's sector ({}{}).",
1061 									pGroup->ubGroupSize, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX));
1062 						}
1063 					}
1064 					else
1065 					{ //Add all the troops to the reinforcement pool as the queen's guard is at full strength.
1066 						giReinforcementPool += pGroup->ubGroupSize;
1067 						SLOGD(ST::format("{} reinforcements have arrived at queen's sector ({}{}) and have been added to the reinforcement pool.",
1068 								pGroup->ubGroupSize, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX));
1069 					}
1070 				}
1071 
1072 				SetThisSectorAsEnemyControlled(pGroup->ubSectorX, pGroup->ubSectorY, 0);
1073 				RemoveGroup(*pGroup);
1074 				RecalculateGarrisonWeight( i );
1075 
1076 				return TRUE;
1077 			}
1078 		}
1079 		//Step 2 -- Check for Patrol groups matching waypoint index.
1080 		for( i = 0; i < gPatrolGroup.size(); i++ )
1081 		{
1082 			if( gPatrolGroup[ i ].ubSectorID[ 1 ] == SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) &&
1083 					gPatrolGroup[ i ].ubPendingGroupID == pGroup->ubGroupID )
1084 			{
1085 				gPatrolGroup[ i ].ubPendingGroupID = 0;
1086 				if( gPatrolGroup[ i ].ubGroupID && gPatrolGroup[ i ].ubGroupID != pGroup->ubGroupID )
1087 				{ //cheat, and warp our reinforcements to them!
1088 					pPatrolGroup = GetGroup( gPatrolGroup[ i ].ubGroupID );
1089 					pPatrolGroup->pEnemyGroup->ubNumTroops += pGroup->pEnemyGroup->ubNumTroops;
1090 					pPatrolGroup->pEnemyGroup->ubNumElites += pGroup->pEnemyGroup->ubNumElites;
1091 					pPatrolGroup->pEnemyGroup->ubNumAdmins += pGroup->pEnemyGroup->ubNumAdmins;
1092 					pPatrolGroup->ubGroupSize += (UINT8)(pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins );
1093 					SLOGD("%d reinforcements have joined patrol group at sector %c%d (new size: %d)",
1094 							pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins,
1095 							pPatrolGroup->ubSectorY + 'A' - 1, pPatrolGroup->ubSectorX, pPatrolGroup->ubGroupSize );
1096 					if( pPatrolGroup->ubGroupSize > MAX_STRATEGIC_TEAM_SIZE )
1097 					{
1098 						UINT8 ubCut;
1099 						SLOGE( "Patrol group #%d in %c%d received too many reinforcements from group #%d that was created in %c%d.  Size truncated from %d to %d.\n\
1100 							Please send Strategic Decisions.txt and PRIOR save.",
1101 							pPatrolGroup->ubGroupID, pPatrolGroup->ubSectorY + 'A' - 1, pPatrolGroup->ubSectorX,
1102 							pGroup->ubGroupID, SECTORY( pGroup->ubCreatedSectorID ) + 'A' - 1, SECTORX( pGroup->ubCreatedSectorID ),
1103 							pPatrolGroup->ubGroupSize, MAX_STRATEGIC_TEAM_SIZE );
1104 						//truncate the group size.
1105 						ubCut = pPatrolGroup->ubGroupSize - MAX_STRATEGIC_TEAM_SIZE;
1106 						while( ubCut-- )
1107 						{
1108 							if( pGroup->pEnemyGroup->ubNumAdmins )
1109 							{
1110 								pGroup->pEnemyGroup->ubNumAdmins--;
1111 								pPatrolGroup->pEnemyGroup->ubNumAdmins--;
1112 							}
1113 							else if( pGroup->pEnemyGroup->ubNumTroops )
1114 							{
1115 								pGroup->pEnemyGroup->ubNumTroops--;
1116 								pPatrolGroup->pEnemyGroup->ubNumTroops--;
1117 							}
1118 							else if( pGroup->pEnemyGroup->ubNumElites )
1119 							{
1120 								pGroup->pEnemyGroup->ubNumElites--;
1121 								pPatrolGroup->pEnemyGroup->ubNumElites--;
1122 							}
1123 						}
1124 						pPatrolGroup->ubGroupSize = MAX_STRATEGIC_TEAM_SIZE;
1125 						Assert( pPatrolGroup->pEnemyGroup->ubNumAdmins +
1126 										pPatrolGroup->pEnemyGroup->ubNumTroops +
1127 										pPatrolGroup->pEnemyGroup->ubNumElites == MAX_STRATEGIC_TEAM_SIZE );
1128 					}
1129 					RemoveGroup(*pGroup);
1130 					RecalculatePatrolWeight(gPatrolGroup[i]);
1131 					ValidateLargeGroup( pPatrolGroup );
1132 				}
1133 				else
1134 				{ //the reinforcements have become the new patrol group (even if same group)
1135 					gPatrolGroup[ i ].ubGroupID = pGroup->ubGroupID;
1136 					pGroup->pEnemyGroup->ubIntention = PATROL;
1137 					pGroup->ubMoveType = ENDTOEND_FORWARDS;
1138 					RemoveGroupWaypoints(*pGroup);
1139 					AddWaypointIDToPGroup( pGroup, gPatrolGroup[ i ].ubSectorID[ 0 ] );
1140 					AddWaypointIDToPGroup( pGroup, gPatrolGroup[ i ].ubSectorID[ 1 ] );
1141 					if( gPatrolGroup[ i ].ubSectorID[ 2 ] )
1142 					{ //Add optional waypoints if included.
1143 						AddWaypointIDToPGroup( pGroup, gPatrolGroup[ i ].ubSectorID[ 2 ] );
1144 						if( gPatrolGroup[ i ].ubSectorID[ 3 ] )
1145 							AddWaypointIDToPGroup( pGroup, gPatrolGroup[ i ].ubSectorID[ 3 ] );
1146 					}
1147 					SLOGD("%d soldiers have arrived to patrol area near sector %c%d",
1148 							pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins,
1149 							pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX );
1150 					RecalculatePatrolWeight(gPatrolGroup[i]);
1151 				}
1152 				return TRUE;
1153 			}
1154 		}
1155 	}
1156 	else
1157 	{	//This is a floating group at his final destination...
1158 		if( pGroup->pEnemyGroup->ubIntention != STAGING && pGroup->pEnemyGroup->ubIntention != REINFORCEMENTS )
1159 		{
1160 			ReassignAIGroup( &pGroup );
1161 			return TRUE;
1162 		}
1163 	}
1164 	return FALSE;
1165 }
1166 
1167 
EnemyNoticesPlayerArrival(GROUP const & pg,UINT8 const x,UINT8 const y)1168 static bool EnemyNoticesPlayerArrival(GROUP const& pg, UINT8 const x, UINT8 const y)
1169 {
1170 	GROUP* const eg = FindEnemyMovementGroupInSector(x, y);
1171 	if (eg && AttemptToNoticeAdjacentGroupSucceeds())
1172 	{
1173 		HandlePlayerGroupNoticedByPatrolGroup(&pg, eg);
1174 		return true;
1175 	}
1176 
1177 	SECTORINFO const& s         = SectorInfo[SECTOR(x, y)];
1178 	UINT8      const  n_enemies = s.ubNumAdmins + s.ubNumTroops + s.ubNumElites;
1179 	if (n_enemies && s.ubGarrisonID != NO_GARRISON && AttemptToNoticeAdjacentGroupSucceeds())
1180 	{
1181 		HandlePlayerGroupNoticedByGarrison(&pg, SECTOR(x, y));
1182 		return true;
1183 	}
1184 
1185 	return false;
1186 }
1187 
1188 
1189 static void SendGroupToPool(GROUP** pGroup);
1190 
1191 
1192 //returns TRUE if the group was deleted.
StrategicAILookForAdjacentGroups(GROUP * pGroup)1193 BOOLEAN StrategicAILookForAdjacentGroups( GROUP *pGroup )
1194 {
1195 	UINT8 ubSectorID;
1196 	if( !gfQueenAIAwake )
1197 	{ //The queen isn't aware the player's presence yet, so she is oblivious to any situations.
1198 
1199 		if( !pGroup->fPlayer )
1200 		{
1201 			//Exception case!
1202 			//In the beginning of the game, a group is sent to A9 after the first battle.  If you leave A9, when they arrive,
1203 			//they will stay there indefinately because the AI isn't awake.  What we do, is if this is a group in A9, then
1204 			//send them home.
1205 			if( GroupAtFinalDestination( pGroup ) )
1206 			{
1207 				//Wake up the queen now, if she hasn't woken up already.
1208 				WakeUpQueen();
1209 				if( (pGroup->ubSectorX == 9 && pGroup->ubSectorY == 1) ||
1210 						(pGroup->ubSectorX == 3 && pGroup->ubSectorY == 16) )
1211 				{
1212 					SendGroupToPool( &pGroup );
1213 					if( !pGroup )
1214 					{ //Group was transferred to the pool
1215 						return TRUE;
1216 					}
1217 				}
1218 			}
1219 		}
1220 
1221 		if( !gfQueenAIAwake )
1222 		{
1223 			return FALSE;
1224 		}
1225 	}
1226 	if( !pGroup->fPlayer )
1227 	{	//The enemy group has arrived at a new sector and now controls it.
1228 		//Look in each of the four directions, and the alertness rating will
1229 		//determine the chance to detect any players that may exist in that sector.
1230 		GROUP* pEnemyGroup = pGroup;
1231 		if( GroupAtFinalDestination( pEnemyGroup ) )
1232 		{
1233 			return EvaluateGroupSituation( pEnemyGroup );
1234 		}
1235 		ubSectorID = (UINT8)SECTOR( pEnemyGroup->ubSectorX, pEnemyGroup->ubSectorY );
1236 		if( pEnemyGroup && pEnemyGroup->ubSectorY > 1 && EnemyPermittedToAttackSector( &pEnemyGroup, (UINT8)(ubSectorID - 16) ) )
1237 		{
1238 			GROUP* const pPlayerGroup = FindPlayerMovementGroupInSector(pEnemyGroup->ubSectorX, pEnemyGroup->ubSectorY - 1);
1239 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1240 			{
1241 				return HandlePlayerGroupNoticedByPatrolGroup( pPlayerGroup, pEnemyGroup );
1242 			}
1243 			else if( CountAllMilitiaInSector( pEnemyGroup->ubSectorX, (UINT8)(pEnemyGroup->ubSectorY-1) ) &&
1244 							AttemptToNoticeAdjacentGroupSucceeds() )
1245 			{
1246 				return HandleMilitiaNoticedByPatrolGroup( (UINT8)SECTOR( pEnemyGroup->ubSectorX, pEnemyGroup->ubSectorY-1 ), pEnemyGroup );
1247 			}
1248 			else if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID-16) ) && AttemptToNoticeEmptySectorSucceeds() )
1249 			{
1250 				return HandleEmptySectorNoticedByPatrolGroup( pEnemyGroup, (UINT8)(ubSectorID-16) );
1251 			}
1252 		}
1253 		if( pEnemyGroup && pEnemyGroup->ubSectorX > 1 && EnemyPermittedToAttackSector( &pEnemyGroup, (UINT8)(ubSectorID - 1) ) )
1254 		{
1255 			GROUP* const pPlayerGroup = FindPlayerMovementGroupInSector(pEnemyGroup->ubSectorX - 1, pEnemyGroup->ubSectorY);
1256 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1257 			{
1258 				return HandlePlayerGroupNoticedByPatrolGroup( pPlayerGroup, pEnemyGroup );
1259 			}
1260 			else if( CountAllMilitiaInSector( (UINT8)(pEnemyGroup->ubSectorX-1), pEnemyGroup->ubSectorY ) &&
1261 							AttemptToNoticeAdjacentGroupSucceeds() )
1262 			{
1263 				return HandleMilitiaNoticedByPatrolGroup( (UINT8)SECTOR( pEnemyGroup->ubSectorX-1, pEnemyGroup->ubSectorY ), pEnemyGroup );
1264 			}
1265 			else if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID-1) ) && AttemptToNoticeEmptySectorSucceeds() )
1266 			{
1267 				return HandleEmptySectorNoticedByPatrolGroup( pEnemyGroup, (UINT8)(ubSectorID-1) );
1268 			}
1269 		}
1270 		if( pEnemyGroup && pEnemyGroup->ubSectorY < 16 && EnemyPermittedToAttackSector( &pEnemyGroup, (UINT8)(ubSectorID + 16) ) )
1271 		{
1272 			GROUP* const pPlayerGroup = FindPlayerMovementGroupInSector(pEnemyGroup->ubSectorX, pEnemyGroup->ubSectorY + 1);
1273 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1274 			{
1275 				return HandlePlayerGroupNoticedByPatrolGroup( pPlayerGroup, pEnemyGroup );
1276 			}
1277 			else if( CountAllMilitiaInSector( pEnemyGroup->ubSectorX, (UINT8)(pEnemyGroup->ubSectorY+1) ) &&
1278 							AttemptToNoticeAdjacentGroupSucceeds() )
1279 			{
1280 				return HandleMilitiaNoticedByPatrolGroup( (UINT8)SECTOR( pEnemyGroup->ubSectorX, pEnemyGroup->ubSectorY+1 ), pEnemyGroup );
1281 			}
1282 			else if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID+16) ) && AttemptToNoticeEmptySectorSucceeds() )
1283 			{
1284 				return HandleEmptySectorNoticedByPatrolGroup( pEnemyGroup, (UINT8)(ubSectorID+16) );
1285 			}
1286 		}
1287 		if( pEnemyGroup && pEnemyGroup->ubSectorX < 16 && EnemyPermittedToAttackSector( &pEnemyGroup, (UINT8)(ubSectorID + 1) ) )
1288 		{
1289 			GROUP* const pPlayerGroup = FindPlayerMovementGroupInSector(pEnemyGroup->ubSectorX + 1, pEnemyGroup->ubSectorY);
1290 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1291 			{
1292 				return HandlePlayerGroupNoticedByPatrolGroup( pPlayerGroup, pEnemyGroup );
1293 			}
1294 			else if( CountAllMilitiaInSector( (UINT8)(pEnemyGroup->ubSectorX+1), pEnemyGroup->ubSectorY ) &&
1295 							AttemptToNoticeAdjacentGroupSucceeds() )
1296 			{
1297 				return HandleMilitiaNoticedByPatrolGroup( (UINT8)SECTOR( pEnemyGroup->ubSectorX+1, pEnemyGroup->ubSectorY ), pEnemyGroup );
1298 			}
1299 			else if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID+1) ) && AttemptToNoticeEmptySectorSucceeds() )
1300 			{
1301 				return HandleEmptySectorNoticedByPatrolGroup( pEnemyGroup, (UINT8)(ubSectorID+1) );
1302 			}
1303 		}
1304 		if( !pEnemyGroup )
1305 		{ //group deleted.
1306 			return TRUE;
1307 		}
1308 	}
1309 	else
1310 	{ /* The player group has arrived at a new sector and now controls it.  Look
1311 		 * in each of the four directions, and the enemy alertness rating will
1312 		 * determine if the enemy notices that the player is here.  Additionally,
1313 		 * there are also stationary enemy groups that may also notice the player's
1314 		 * new presence.
1315 		 * NOTE: Always returns false because it is the player group that we are
1316 		 *       handling.  We don't mess with the player group here! */
1317 		GROUP const& pg = *pGroup;
1318 		if (pg.ubSectorZ != 0) return FALSE;
1319 		UINT8 const x = pg.ubSectorX;
1320 		UINT8 const y = pg.ubSectorY;
1321 		if (!EnemyPermittedToAttackSector(0, SECTOR(x, y))) return FALSE;
1322 		if (y >  1 && EnemyNoticesPlayerArrival(pg, x,     y - 1)) return FALSE;
1323 		if (x < 16 && EnemyNoticesPlayerArrival(pg, x + 1, y))     return FALSE;
1324 		if (y < 16 && EnemyNoticesPlayerArrival(pg, x,     y + 1)) return FALSE;
1325 		if (x >  1 && EnemyNoticesPlayerArrival(pg, x - 1, y))     return FALSE;
1326 	}
1327 	return FALSE;
1328 }
1329 
1330 
1331 //This is called periodically for each enemy occupied sector containing garrisons.
CheckEnemyControlledSector(UINT8 ubSectorID)1332 void CheckEnemyControlledSector( UINT8 ubSectorID )
1333 {
1334 	SECTORINFO *pSector;
1335 	UINT8 ubSectorX, ubSectorY;
1336 	if( !gfQueenAIAwake )
1337 	{
1338 		return;
1339 	}
1340 	//First, determine if the sector is still owned by the enemy.
1341 	pSector = &SectorInfo[ ubSectorID ];
1342 	if( pSector->ubGarrisonID != NO_GARRISON )
1343 	{
1344 		if( gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID )
1345 		{ //Look for a staging group.
1346 			GROUP *pGroup;
1347 			pGroup = GetGroup( gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID );
1348 			if( pGroup )
1349 			{ //We have a staging group
1350 				if( GroupAtFinalDestination( pGroup ) )
1351 				{
1352 					if( pGroup->pEnemyGroup->ubPendingReinforcements )
1353 					{
1354 						if( pGroup->pEnemyGroup->ubPendingReinforcements > 4 )
1355 						{
1356 							UINT8 ubNum = (UINT8)(3 + Random( 3 ));
1357 							pGroup->pEnemyGroup->ubNumTroops += ubNum;
1358 							pGroup->ubGroupSize += ubNum;
1359 							pGroup->pEnemyGroup->ubPendingReinforcements -= ubNum;
1360 							RecalculateGroupWeight(*pGroup);
1361 							ValidateLargeGroup( pGroup );
1362 						}
1363 						else
1364 						{
1365 							pGroup->pEnemyGroup->ubNumTroops += pGroup->pEnemyGroup->ubPendingReinforcements;
1366 							pGroup->ubGroupSize += pGroup->pEnemyGroup->ubPendingReinforcements;
1367 							pGroup->pEnemyGroup->ubPendingReinforcements = 0;
1368 							ValidateLargeGroup( pGroup );
1369 						}
1370 					}
1371 					else if( SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) != gGarrisonGroup[ pSector->ubGarrisonID ].ubSectorID )
1372 					{
1373 						MoveSAIGroupToSector( &pGroup, gGarrisonGroup[ pSector->ubGarrisonID ].ubSectorID, DIRECT, pGroup->pEnemyGroup->ubIntention );
1374 					}
1375 				}
1376 				//else the group is on route to stage hopefully...
1377 			}
1378 		}
1379 	}
1380 	if( pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites )
1381 	{
1382 
1383 		//The sector is still controlled, so look around to see if there are any players nearby.
1384 		ubSectorX = (UINT8)SECTORX( ubSectorID );
1385 		ubSectorY = (UINT8)SECTORY( ubSectorID );
1386 		if( ubSectorY > 1 && EnemyPermittedToAttackSector( NULL, (UINT8)(ubSectorID - 16) ) )
1387 		{
1388 			/*
1389 			pPlayerGroup = FindPlayerMovementGroupInSector(bSectorX, ubSectorY - 1);
1390 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1391 			{
1392 				HandlePlayerGroupNoticedByGarrison( pPlayerGroup, ubSectorID );
1393 				return;
1394 			}
1395 			else
1396 			*/
1397 			if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID-16) ) && AttemptToNoticeEmptySectorSucceeds() )
1398 			{
1399 				HandleEmptySectorNoticedByGarrison( ubSectorID, (UINT8)(ubSectorID-16) );
1400 				return;
1401 			}
1402 		}
1403 		if( ubSectorX < 16 && EnemyPermittedToAttackSector( NULL, (UINT8)(ubSectorID + 1) ) )
1404 		{
1405 			/*
1406 			pPlayerGroup = FindPlayerMovementGroupInSector(ubSectorX + 1, ubSectorY);
1407 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1408 			{
1409 				HandlePlayerGroupNoticedByGarrison( pPlayerGroup, ubSectorID );
1410 				return;
1411 			}
1412 			else
1413 			*/
1414 			if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID+1) ) && AttemptToNoticeEmptySectorSucceeds() )
1415 			{
1416 				HandleEmptySectorNoticedByGarrison( ubSectorID, (UINT8)(ubSectorID+1) );
1417 				return;
1418 			}
1419 		}
1420 		if( ubSectorY < 16 && EnemyPermittedToAttackSector( NULL, (UINT8)(ubSectorID + 16) ) )
1421 		{
1422 			/*
1423 			pPlayerGroup = FindPlayerMovementGroupInSector(ubSectorX, ubSectorY + 1);
1424 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1425 			{
1426 				HandlePlayerGroupNoticedByGarrison( pPlayerGroup, ubSectorID );
1427 				return;
1428 			}
1429 			else
1430 			*/
1431 			if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID+16) ) && AttemptToNoticeEmptySectorSucceeds() )
1432 			{
1433 				HandleEmptySectorNoticedByGarrison( ubSectorID, (UINT8)(ubSectorID+16) );
1434 				return;
1435 			}
1436 		}
1437 		if( ubSectorX > 1 && EnemyPermittedToAttackSector( NULL, (UINT8)(ubSectorID - 1) ) )
1438 		{
1439 			/*
1440 			pPlayerGroup = FindPlayerMovementGroupInSector(ubSectorX - 1, ubSectorY);
1441 			if( pPlayerGroup && AttemptToNoticeAdjacentGroupSucceeds() )
1442 			{
1443 				HandlePlayerGroupNoticedByGarrison( pPlayerGroup, ubSectorID );
1444 				return;
1445 			}
1446 			else
1447 			*/
1448 			if( AdjacentSectorIsImportantAndUndefended( (UINT8)(ubSectorID-1) ) && AttemptToNoticeEmptySectorSucceeds() )
1449 			{
1450 				HandleEmptySectorNoticedByGarrison( ubSectorID, (UINT8)(ubSectorID-1) );
1451 				return;
1452 			}
1453 		}
1454 	}
1455 }
1456 
1457 
RemoveGroupFromStrategicAILists(GROUP const & g)1458 void RemoveGroupFromStrategicAILists(GROUP const& g)
1459 {
1460 	UINT8 const group_id = g.ubGroupID;
1461 	for (size_t i = 0; i < gPatrolGroup.size(); ++i)
1462 	{
1463 		PATROL_GROUP& pg = gPatrolGroup[i];
1464 		if (pg.ubGroupID == group_id)
1465 		{ // Patrol group was destroyed
1466 			pg.ubGroupID = 0;
1467 			RecalculatePatrolWeight(pg);
1468 			return;
1469 		}
1470 		if (pg.ubPendingGroupID == group_id)
1471 		{ // Group never arrived to reinforce
1472 			pg.ubPendingGroupID = 0;
1473 			return;
1474 		}
1475 	}
1476 	for (size_t i = 0; i < gGarrisonGroup.size(); ++i)
1477 	{
1478 		GARRISON_GROUP& gg = gGarrisonGroup[i];
1479 		if (gg.ubPendingGroupID == group_id)
1480 		{ // Group never arrived to reinforce
1481 			gg.ubPendingGroupID = 0;
1482 			return;
1483 		}
1484 	}
1485 }
1486 
1487 
1488 /* Recalculates a group's weight based on any changes.
1489  * @@@Alex, this is possibly missing in some areas. It is hard to ensure it is
1490  * everywhere with all the changes I've made. I'm sure you could probably find
1491  * some missing calls. */
RecalculatePatrolWeight(PATROL_GROUP & p)1492 static void RecalculatePatrolWeight(PATROL_GROUP& p)
1493 {
1494 	// First, remove the previous weight from the applicable field
1495 	INT32 const prev_weight = p.bWeight;
1496 	if (prev_weight > 0) giRequestPoints -= prev_weight;
1497 
1498 	INT32 need_population;
1499 	if (p.ubGroupID != 0)
1500 	{
1501 		need_population = p.bSize - GetGroup(p.ubGroupID)->ubGroupSize;
1502 		if (need_population < 0)
1503 		{
1504 			p.bWeight = 0;
1505 			return;
1506 		}
1507 	}
1508 	else
1509 	{
1510 		need_population = p.bSize;
1511 	}
1512 	INT32 weight = need_population * 3 * p.bPriority / 96;
1513 	weight = MIN(weight, 2);
1514 	p.bWeight        = weight;
1515 	giRequestPoints += weight;
1516 }
1517 
1518 
1519 //Recalculates a group's weight based on any changes.
1520 //@@@Alex, this is possibly missing in some areas.  It is hard to ensure it is
1521 //everywhere with all the changes I've made.  I'm sure you could probably find some missing calls.
RecalculateGarrisonWeight(INT32 iGarrisonID)1522 static void RecalculateGarrisonWeight(INT32 iGarrisonID)
1523 {
1524 	SECTORINFO *pSector;
1525 	INT32 iWeight, iPrevWeight;
1526 	INT32 iDesiredPop, iCurrentPop, iPriority;
1527 
1528 	pSector = &SectorInfo[ gGarrisonGroup[ iGarrisonID ].ubSectorID ];
1529 	iDesiredPop = gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bDesiredPopulation;
1530 	iCurrentPop = pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites;
1531 	iPriority = gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bPriority;
1532 
1533 	//First, remove the previous weight from the applicable field.
1534 	iPrevWeight = gGarrisonGroup[ iGarrisonID ].bWeight;
1535 	if( iPrevWeight > 0 )
1536 		giRequestPoints -= iPrevWeight;
1537 	else if( iPrevWeight < 0 )
1538 		giReinforcementPoints += iPrevWeight;
1539 
1540 	//Calculate weight (range is -20 to +20 before multiplier).
1541 	//The multiplier of 3 brings it to a range of -96 to +96 which is
1542 	//close enough to a plus/minus 100%.  The resultant percentage is then
1543 	//converted based on the priority.
1544 	iWeight = (iDesiredPop - iCurrentPop) * 3;
1545 	if( iWeight > 0 )
1546 	{ //modify it by it's priority.
1547 		//generates a value between 2 and 100
1548 		iWeight = iWeight * iPriority / 96;
1549 		iWeight = MAX( iWeight, 2 );
1550 		giRequestPoints += iWeight;
1551 	}
1552 	else if( iWeight < 0 )
1553 	{ //modify it by it's reverse priority
1554 		//generates a value between -2 and -100
1555 		iWeight = iWeight * (100-iPriority) / 96;
1556 		iWeight = MIN( iWeight, -2 );
1557 		giReinforcementPoints -= (INT8)iWeight;
1558 	}
1559 
1560 	gGarrisonGroup[ iGarrisonID ].bWeight = (INT8)iWeight;
1561 }
1562 
RecalculateSectorWeight(UINT8 ubSectorID)1563 void RecalculateSectorWeight( UINT8 ubSectorID )
1564 {
1565 	for( size_t i = 0; i < gGarrisonGroup.size(); i++ )
1566 	{
1567 		if( gGarrisonGroup[ i ].ubSectorID == ubSectorID )
1568 		{
1569 			RecalculateGarrisonWeight( i );
1570 			return;
1571 		}
1572 	}
1573 }
1574 
1575 
1576 static void TagSAIGroupWithGracePeriod(GROUP const&);
1577 
1578 
RecalculateGroupWeight(GROUP const & g)1579 void RecalculateGroupWeight(GROUP const& g)
1580 {
1581 	for (size_t i = 0; i != gPatrolGroup.size(); ++i)
1582 	{
1583 		PATROL_GROUP& p = gPatrolGroup[i];
1584 		if (p.ubGroupID != g.ubGroupID) continue;
1585 		if (g.ubGroupSize == 0)
1586 		{
1587 			TagSAIGroupWithGracePeriod(g);
1588 			p.ubGroupID = 0;
1589 		}
1590 		RecalculatePatrolWeight(p);
1591 		return;
1592 	}
1593 }
1594 
1595 
1596 static BOOLEAN GarrisonCanProvideMinimumReinforcements(INT32 iGarrisonID);
1597 
1598 
ChooseSuitableGarrisonToProvideReinforcements(INT32 iDstGarrisonID,INT32 iReinforcementsRequested)1599 static INT32 ChooseSuitableGarrisonToProvideReinforcements(INT32 iDstGarrisonID, INT32 iReinforcementsRequested)
1600 {
1601 	UINT32 iSrcGarrisonID, iBestGarrisonID = NO_GARRISON;
1602 	INT32 iReinforcementsAvailable;
1603 	UINT32 i, iRandom, iWeight;
1604 	INT8 bBestWeight;
1605 	UINT8 ubSectorID;
1606 	size_t iGarrisonArraySize = gGarrisonGroup.size();
1607 
1608 	//Check to see if we could send reinforcements from Alma.  Only Drassen/Cambria get preferred
1609 	//service from Alma, due to it's proximity and Alma's purpose as a forward military base.
1610 	ubSectorID = gGarrisonGroup[ iDstGarrisonID ].ubSectorID;
1611 	switch( ubSectorID )
1612 	{
1613 		case SEC_B13: case SEC_C13: case SEC_D13: case SEC_D15:								//Drassen + nearby SAM site
1614 		case SEC_F8:  case SEC_F9:  case SEC_G8:  case SEC_G9:  case SEC_H8:	//Cambria
1615 			//reinforcements will be primarily sent from Alma whenever possible.
1616 
1617 			//find which the first sector that contains Alma soldiers.
1618 			for( i = 0; i < gGarrisonGroup.size(); i++ )
1619 			{
1620 				if( gGarrisonGroup[ i ].ubComposition == ALMA_DEFENCE )
1621 					break;
1622 			}
1623 			iSrcGarrisonID = i;
1624 			//which of these 4 Alma garrisons have the most reinforcements available?  It is
1625 			//possible that none of these garrisons can provide any reinforcements.
1626 			bBestWeight = 0;
1627 			for( i = iSrcGarrisonID; i < iSrcGarrisonID + 4; i++ )
1628 			{
1629 				RecalculateGarrisonWeight( i );
1630 				if( bBestWeight > gGarrisonGroup[ i ].bWeight && GarrisonCanProvideMinimumReinforcements( i ) )
1631 				{
1632 					bBestWeight = gGarrisonGroup[ i ].bWeight;
1633 					iBestGarrisonID = i;
1634 				}
1635 			}
1636 			//If we can provide reinforcements from Alma, then make sure that it can provide at least 67% of
1637 			//the requested reinforcements.
1638 			if( bBestWeight < 0 )
1639 			{
1640 				iReinforcementsAvailable = ReinforcementsAvailable( iBestGarrisonID );
1641 				if( iReinforcementsAvailable * 100 >= iReinforcementsRequested * 67 )
1642 				{ //This is the approved group to provide the reinforcements.
1643 					return iBestGarrisonID;
1644 				}
1645 			}
1646 			break;
1647 	}
1648 
1649 	//The Alma case either wasn't applicable or failed to have the right reinforcements.  Do a general weighted search.
1650 	iRandom = Random( giReinforcementPoints );
1651 	for( iSrcGarrisonID = 0; iSrcGarrisonID < iGarrisonArraySize; iSrcGarrisonID++ )
1652 	{ //go through the garrisons
1653 		RecalculateGarrisonWeight( iSrcGarrisonID );
1654 		iWeight = -gGarrisonGroup[ iSrcGarrisonID ].bWeight;
1655 		if( iWeight > 0 )
1656 		{ //if group is able to provide reinforcements.
1657 			if( iRandom < iWeight && GarrisonCanProvideMinimumReinforcements( iSrcGarrisonID ) )
1658 			{
1659 				iReinforcementsAvailable = ReinforcementsAvailable( iSrcGarrisonID );
1660 				if( iReinforcementsAvailable * 100 >= iReinforcementsRequested * 67 )
1661 				{ //This is the approved group to provide the reinforcements.
1662 					return iSrcGarrisonID;
1663 				}
1664 			}
1665 			iRandom -= iWeight;
1666 		}
1667 	}
1668 
1669 	//So far we have failed on all accounts.  Now, simply process all the garrisons, and return the first garrison that can
1670 	//provide the reinforcements.
1671 	for( iSrcGarrisonID = 0; iSrcGarrisonID < iGarrisonArraySize; iSrcGarrisonID++ )
1672 	{ //go through the garrisons
1673 		RecalculateGarrisonWeight( iSrcGarrisonID );
1674 		iWeight = -gGarrisonGroup[ iSrcGarrisonID ].bWeight;
1675 		if( iWeight > 0 && GarrisonCanProvideMinimumReinforcements( iSrcGarrisonID ) )
1676 		{ //if group is able to provide reinforcements.
1677 			iReinforcementsAvailable = ReinforcementsAvailable( iSrcGarrisonID );
1678 			if( iReinforcementsAvailable * 100 >= iReinforcementsRequested * 67 )
1679 			{ //This is the approved group to provide the reinforcements.
1680 				return iSrcGarrisonID;
1681 			}
1682 		}
1683 	}
1684 
1685 	//Well, if we get this far, the queen must be low on troops.  Send whatever we can.
1686 	iRandom = Random( giReinforcementPoints );
1687 	for( iSrcGarrisonID = 0; iSrcGarrisonID < iGarrisonArraySize; iSrcGarrisonID++ )
1688 	{ //go through the garrisons
1689 		RecalculateGarrisonWeight( iSrcGarrisonID );
1690 		iWeight = -gGarrisonGroup[ iSrcGarrisonID ].bWeight;
1691 		if( iWeight > 0 && GarrisonCanProvideMinimumReinforcements( iSrcGarrisonID ) )
1692 		{ //if group is able to provide reinforcements.
1693 			if( iRandom < iWeight )
1694 			{
1695 				iReinforcementsAvailable = ReinforcementsAvailable( iSrcGarrisonID );
1696 				return iSrcGarrisonID;
1697 			}
1698 			iRandom -= iWeight;
1699 		}
1700 	}
1701 
1702 	//Failed completely.
1703 	return -1;
1704 }
1705 
1706 
SendReinforcementsForGarrison(INT32 iDstGarrisonID,UINT16 usDefencePoints,GROUP ** pOptionalGroup)1707 static void SendReinforcementsForGarrison(INT32 iDstGarrisonID, UINT16 usDefencePoints, GROUP** pOptionalGroup)
1708 {
1709 	INT32 iChance, iRandom, iSrcGarrisonID;
1710 	INT32 iMaxReinforcementsAllowed, iReinforcementsAvailable, iReinforcementsRequested, iReinforcementsApproved;
1711 	GROUP *pGroup;
1712 	UINT8 ubSrcSectorX, ubSrcSectorY, ubDstSectorX, ubDstSectorY;
1713 	UINT8 ubNumExtraReinforcements;
1714 	UINT8 ubGroupSize;
1715 	BOOLEAN fLimitMaxTroopsAllowable = FALSE;
1716 
1717 	//Determine how many units the garrison needs.
1718 	iReinforcementsRequested = GarrisonReinforcementsRequested( iDstGarrisonID, &ubNumExtraReinforcements );
1719 
1720 	//The maximum number of reinforcements can't be offsetted past a certain point based on the
1721 	//priority of the garrison.
1722 	iMaxReinforcementsAllowed = //from 1 to 3 times the desired size of the normal force.
1723 		gArmyComp[ gGarrisonGroup[ iDstGarrisonID ].ubComposition ].bDesiredPopulation +
1724 		gArmyComp[ gGarrisonGroup[ iDstGarrisonID ].ubComposition ].bDesiredPopulation *
1725 		gArmyComp[ gGarrisonGroup[ iDstGarrisonID ].ubComposition ].bPriority / 50;
1726 
1727 	if( iReinforcementsRequested + ubNumExtraReinforcements > iMaxReinforcementsAllowed )
1728 	{ //adjust the extra reinforcements so that it doesn't exceed the maximum allowed.
1729 		fLimitMaxTroopsAllowable = TRUE;
1730 		ubNumExtraReinforcements = (UINT8)(iMaxReinforcementsAllowed - iReinforcementsRequested);
1731 	}
1732 
1733 	iReinforcementsRequested += ubNumExtraReinforcements;
1734 
1735 	if( iReinforcementsRequested <= 0 )
1736 	{
1737 		return;
1738 	}
1739 
1740 	ubDstSectorX = (UINT8)SECTORX( gGarrisonGroup[ iDstGarrisonID ].ubSectorID );
1741 	ubDstSectorY = (UINT8)SECTORY( gGarrisonGroup[ iDstGarrisonID ].ubSectorID );
1742 
1743 	if( pOptionalGroup && *pOptionalGroup )
1744 	{ //This group will provide the reinforcements
1745 		pGroup = *pOptionalGroup;
1746 
1747 		SLOGD("%d troops have been reassigned from %c%d to garrison sector %c%d",
1748 				pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins,
1749 				pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX,
1750 				ubDstSectorY + 'A' - 1, ubDstSectorX );
1751 
1752 		gGarrisonGroup[ iDstGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
1753 		ConvertGroupTroopsToComposition( pGroup, gGarrisonGroup[ iDstGarrisonID ].ubComposition );
1754 		MoveSAIGroupToSector( pOptionalGroup, gGarrisonGroup[ iDstGarrisonID ].ubSectorID, STAGE, REINFORCEMENTS );
1755 		return;
1756 	}
1757 	iRandom = Random( giReinforcementPoints + giReinforcementPool );
1758 	if( iRandom < giReinforcementPool )
1759 	{ //use the pool and send the requested amount from SECTOR P3 (queen's palace)
1760 		QUEEN_POOL:
1761 
1762 		//KM : Sep 9, 1999
1763 		//If the player owns sector P3, any troops that spawned there were causing serious problems, seeing battle checks
1764 		//were not performed!
1765 		if( !StrategicMap[ CALCULATE_STRATEGIC_INDEX( 3, 16 ) ].fEnemyControlled )
1766 		{ //Queen can no longer send reinforcements from the palace if she doesn't control it!
1767 			return;
1768 		}
1769 
1770 
1771 		if( !giReinforcementPool )
1772 		{
1773 			return;
1774 		}
1775 		iReinforcementsApproved = MIN( iReinforcementsRequested, giReinforcementPool );
1776 
1777 		if( iReinforcementsApproved * 3 < usDefencePoints )
1778 		{ //The enemy force that would be sent would likely be decimated by the player forces.
1779 			gubGarrisonReinforcementsDenied[ iDstGarrisonID ] += (UINT8)(gArmyComp[ gGarrisonGroup[ iDstGarrisonID ].ubComposition ].bPriority / 2);
1780 			return;
1781 		}
1782 		else
1783 		{
1784 			//The force is strong enough to be able to take the sector.
1785 			gubGarrisonReinforcementsDenied[ iDstGarrisonID ] = 0;
1786 		}
1787 
1788 		//The chance she will send them is related with the strength difference between the
1789 		//player's force and the queen's.
1790 		if( ubNumExtraReinforcements && fLimitMaxTroopsAllowable && iReinforcementsApproved == iMaxReinforcementsAllowed )
1791 		{
1792 			iChance = (iReinforcementsApproved + ubNumExtraReinforcements) * 100 / usDefencePoints;
1793 			if( !Chance( iChance ) )
1794 			{
1795 				return;
1796 			}
1797 		}
1798 
1799 
1800 		pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_P3, 0, (UINT8)iReinforcementsApproved, 0 );
1801 		ConvertGroupTroopsToComposition( pGroup, gGarrisonGroup[ iDstGarrisonID ].ubComposition );
1802 		pGroup->ubOriginalSector = (UINT8)SECTOR( ubDstSectorX, ubDstSectorY );
1803 		giReinforcementPool -= iReinforcementsApproved;
1804 		pGroup->ubMoveType = ONE_WAY;
1805 		gGarrisonGroup[ iDstGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
1806 
1807 		ubGroupSize = (UINT8)(pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins);
1808 
1809 		if( ubNumExtraReinforcements )
1810 		{
1811 			SLOGD("%d troops have been sent from palace to stage assault near sector %c%d",
1812 					ubGroupSize, ubDstSectorY + 'A' - 1, ubDstSectorX );
1813 			MoveSAIGroupToSector( &pGroup, gGarrisonGroup[ iDstGarrisonID ].ubSectorID, STAGE, STAGING );
1814 		}
1815 		else
1816 		{
1817 			SLOGD("%d troops have been sent from palace to garrison sector %c%d",
1818 					ubGroupSize, ubDstSectorY + 'A' - 1, ubDstSectorX );
1819 			MoveSAIGroupToSector( &pGroup, gGarrisonGroup[ iDstGarrisonID ].ubSectorID, STAGE, REINFORCEMENTS );
1820 		}
1821 		return;
1822 	}
1823 	else
1824 	{
1825 		iSrcGarrisonID = ChooseSuitableGarrisonToProvideReinforcements( iDstGarrisonID, iReinforcementsRequested );
1826 		if( iSrcGarrisonID == -1 )
1827 		{
1828 			goto QUEEN_POOL;
1829 		}
1830 
1831 		ubSrcSectorX = (gGarrisonGroup[ iSrcGarrisonID ].ubSectorID % 16) + 1;
1832 		ubSrcSectorY = (gGarrisonGroup[ iSrcGarrisonID ].ubSectorID / 16) + 1;
1833 		if( ubSrcSectorX != gWorldSectorX || ubSrcSectorY != gWorldSectorY || gbWorldSectorZ > 0 )
1834 		{ //The reinforcements aren't coming from the currently loaded sector!
1835 			iReinforcementsAvailable = ReinforcementsAvailable( iSrcGarrisonID );
1836 			if( iReinforcementsAvailable <= 0)
1837 			{
1838 				SLOGE( "Attempting to send reinforcements from a garrison that doesn't have any! -- KM:0 (with prior saved game and Debug Log)" );
1839 				return;
1840 			}
1841 			//Send the lowest of the two:  number requested or number available
1842 
1843 			iReinforcementsApproved = MIN( iReinforcementsRequested, iReinforcementsAvailable );
1844 			if( iReinforcementsApproved > iMaxReinforcementsAllowed - ubNumExtraReinforcements )
1845 			{ //The force isn't strong enough, but the queen isn't willing to apply extra resources
1846 				iReinforcementsApproved = iMaxReinforcementsAllowed - ubNumExtraReinforcements;
1847 			}
1848 			else if( (iReinforcementsApproved + ubNumExtraReinforcements) * 3 < usDefencePoints )
1849 			{ //The enemy force that would be sent would likely be decimated by the player forces.
1850 				gubGarrisonReinforcementsDenied[ iDstGarrisonID ] += (UINT8)(gArmyComp[ gGarrisonGroup[ iDstGarrisonID ].ubComposition ].bPriority / 2);
1851 				return;
1852 			}
1853 			else
1854 			{
1855 				//The force is strong enough to be able to take the sector.
1856 				gubGarrisonReinforcementsDenied[ iDstGarrisonID ] = 0;
1857 			}
1858 
1859 			//The chance she will send them is related with the strength difference between the
1860 			//player's force and the queen's.
1861 			if( iReinforcementsApproved + ubNumExtraReinforcements == iMaxReinforcementsAllowed && usDefencePoints )
1862 			{
1863 				iChance = (iReinforcementsApproved + ubNumExtraReinforcements) * 100 / usDefencePoints;
1864 				if( !Chance( iChance ) )
1865 				{
1866 					return;
1867 				}
1868 			}
1869 
1870 			pGroup = CreateNewEnemyGroupDepartingFromSector( gGarrisonGroup[ iSrcGarrisonID ].ubSectorID, 0, (UINT8)iReinforcementsApproved, 0 );
1871 			ConvertGroupTroopsToComposition( pGroup, gGarrisonGroup[ iDstGarrisonID ].ubComposition );
1872 			RemoveSoldiersFromGarrisonBasedOnComposition( iSrcGarrisonID, pGroup->ubGroupSize );
1873 			pGroup->ubOriginalSector = (UINT8)SECTOR( ubDstSectorX, ubDstSectorY );
1874 			pGroup->ubMoveType = ONE_WAY;
1875 			gGarrisonGroup[ iDstGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
1876 			ubGroupSize = (UINT8)( pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins );
1877 
1878 			if( ubNumExtraReinforcements )
1879 			{
1880 				pGroup->pEnemyGroup->ubPendingReinforcements = ubNumExtraReinforcements;
1881 				SLOGD("%d troops have been sent from sector %c%d to stage assault near sector %c%d",
1882 						ubGroupSize, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX, ubDstSectorY + 'A' - 1, ubDstSectorX );
1883 
1884 				MoveSAIGroupToSector( &pGroup, gGarrisonGroup[ iDstGarrisonID ].ubSectorID, STAGE, STAGING );
1885 			}
1886 			else
1887 			{
1888 				SLOGD("%d troops have been sent from sector %c%d to garrison sector %c%d",
1889 						ubGroupSize, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX, ubDstSectorY + 'A' - 1, ubDstSectorX );
1890 
1891 				MoveSAIGroupToSector( &pGroup, gGarrisonGroup[ iDstGarrisonID ].ubSectorID, STAGE, REINFORCEMENTS );
1892 			}
1893 			SLOGD("%d troops have been sent from garrison sector %c%d to patrol area near sector %c%d",
1894 					ubGroupSize, ubSrcSectorY + 'A' - 1, ubSrcSectorX, ubDstSectorY + 'A' - 1, ubDstSectorX );
1895 
1896 			return;
1897 		}
1898 	}
1899 }
1900 
1901 
SendReinforcementsForPatrol(INT32 iPatrolID,GROUP ** pOptionalGroup)1902 static void SendReinforcementsForPatrol(INT32 iPatrolID, GROUP** pOptionalGroup)
1903 {
1904 	GROUP *pGroup;
1905 	INT32 iRandom, iWeight;
1906 	UINT32 iSrcGarrisonID;
1907 	INT32 iReinforcementsAvailable, iReinforcementsRequested, iReinforcementsApproved;
1908 	UINT8 ubSrcSectorX, ubSrcSectorY;
1909 
1910 	PATROL_GROUP* const pg = &gPatrolGroup[iPatrolID];
1911 
1912 	//Determine how many units the patrol group needs.
1913 	iReinforcementsRequested = PatrolReinforcementsRequested(pg);
1914 
1915 	if( iReinforcementsRequested <= 0)
1916 		return;
1917 
1918 	UINT8 const ubDstSectorX = (pg->ubSectorID[1] % 16) + 1;
1919 	UINT8 const ubDstSectorY = (pg->ubSectorID[1] / 16) + 1;
1920 
1921 	if( pOptionalGroup && *pOptionalGroup )
1922 	{ //This group will provide the reinforcements
1923 		pGroup = *pOptionalGroup;
1924 		pg->ubPendingGroupID = pGroup->ubGroupID;
1925 
1926 		SLOGD("%d troops have been reassigned from %c%d to reinforce patrol group covering sector %c%d",
1927 				pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins,
1928 				pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX,
1929 				ubDstSectorY + 'A' - 1, ubDstSectorX );
1930 
1931 		MoveSAIGroupToSector(pOptionalGroup, pg->ubSectorID[1], EVASIVE, REINFORCEMENTS);
1932 		return;
1933 	}
1934 	iRandom = Random( giReinforcementPoints + giReinforcementPool );
1935 	if( iRandom < giReinforcementPool )
1936 	{ //use the pool and send the requested amount from SECTOR P3 (queen's palace)
1937 		iReinforcementsApproved = MIN( iReinforcementsRequested, giReinforcementPool );
1938 		if( !iReinforcementsApproved )
1939 		{
1940 			AssertMsg(FALSE, "Trying to create empty reinforcements group!");
1941 			return;
1942 		}
1943 		pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_P3, 0, (UINT8)iReinforcementsApproved, 0 );
1944 		pGroup->ubOriginalSector = (UINT8)SECTOR( ubDstSectorX, ubDstSectorY );
1945 		giReinforcementPool -= iReinforcementsApproved;
1946 		pg->ubPendingGroupID = pGroup->ubGroupID;
1947 
1948 		SLOGD("%d troops have been sent from palace to patrol area near sector %c%d",
1949 				pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins,
1950 				ubDstSectorY + 'A' - 1, ubDstSectorX );
1951 
1952 		MoveSAIGroupToSector(&pGroup, pg->ubSectorID[1], EVASIVE, REINFORCEMENTS);
1953 		return;
1954 	}
1955 	else
1956 	{
1957 		iRandom -= giReinforcementPool;
1958 		for( iSrcGarrisonID = 0; iSrcGarrisonID < gGarrisonGroup.size(); iSrcGarrisonID++ )
1959 		{ //go through the garrisons
1960 			RecalculateGarrisonWeight( iSrcGarrisonID );
1961 			iWeight = -gGarrisonGroup[ iSrcGarrisonID ].bWeight;
1962 			if( iWeight > 0 )
1963 			{ //if group is able to provide reinforcements.
1964 				if( iRandom < iWeight )
1965 				{ //This is the group that gets the reinforcements!
1966 					ubSrcSectorX = (UINT8)SECTORX(gGarrisonGroup[ iSrcGarrisonID ].ubSectorID );
1967 					ubSrcSectorY = (UINT8)SECTORY(gGarrisonGroup[ iSrcGarrisonID ].ubSectorID );
1968 					if( ubSrcSectorX != gWorldSectorX || ubSrcSectorY != gWorldSectorY || gbWorldSectorZ > 0 )
1969 					{ //The reinforcements aren't coming from the currently loaded sector!
1970 						iReinforcementsAvailable = ReinforcementsAvailable( iSrcGarrisonID );
1971 						//Send the lowest of the two:  number requested or number available
1972 						iReinforcementsApproved = MIN( iReinforcementsRequested, iReinforcementsAvailable );
1973 						pGroup = CreateNewEnemyGroupDepartingFromSector( gGarrisonGroup[ iSrcGarrisonID ].ubSectorID, 0, (UINT8)iReinforcementsApproved, 0 );
1974 						pGroup->ubOriginalSector = (UINT8)SECTOR( ubDstSectorX, ubDstSectorY );
1975 						pg->ubPendingGroupID = pGroup->ubGroupID;
1976 
1977 						RemoveSoldiersFromGarrisonBasedOnComposition( iSrcGarrisonID, pGroup->ubGroupSize );
1978 
1979 						SLOGD("%d troops have been sent from garrison sector %c%d to patrol area near sector %c%d",
1980 								pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins,
1981 								ubSrcSectorY + 'A' - 1, ubSrcSectorX,
1982 								ubDstSectorY + 'A' - 1, ubDstSectorX );
1983 
1984 						MoveSAIGroupToSector(&pGroup, pg->ubSectorID[1], EVASIVE, REINFORCEMENTS);
1985 						return;
1986 					}
1987 				}
1988 				iRandom -= iWeight;
1989 			}
1990 		}
1991 	}
1992 }
1993 
1994 
1995 static void EvolveQueenPriorityPhase(BOOLEAN fForceChange);
1996 static BOOLEAN GarrisonRequestingMinimumReinforcements(INT32 iGarrisonID);
1997 static BOOLEAN PatrolRequestingMinimumReinforcements(INT32 iPatrolID);
1998 static void UpgradeAdminsToTroops();
1999 
2000 
2001 //Periodically does a general poll and check on each of the groups and garrisons, determines
2002 //reinforcements, new patrol groups, planned assaults, etc.
EvaluateQueenSituation()2003 void EvaluateQueenSituation()
2004 {
2005 	UINT32 i, iRandom;
2006 	UINT32 iWeight;
2007 	UINT32 uiOffset;
2008 	UINT16 usDefencePoints;
2009 	INT32 iSumOfAllWeights = 0;
2010 
2011 	// figure out how long it shall be before we call this again
2012 
2013 	// The more work to do there is (request points the queen's army is asking for), the more often she will make decisions
2014 	// This can increase the decision intervals by up to 500 extra minutes (> 8 hrs)
2015 	uiOffset = MAX( 100 - giRequestPoints, 0);
2016 	uiOffset = uiOffset + Random( uiOffset * 4 );
2017 	uiOffset += saipolicy(time_evaluate_in_minutes) + Random(saipolicy(time_evaluate_variance));
2018 
2019 	if( !giReinforcementPool )
2020 	{ //Queen has run out of reinforcements.  Simulate recruiting and training new troops
2021 		uiOffset *= 10;
2022 		giReinforcementPool += 30;
2023 		AddStrategicEvent( EVENT_EVALUATE_QUEEN_SITUATION, GetWorldTotalMin() + uiOffset, 0 );
2024 		return;
2025 	}
2026 
2027 	//Re-post the event
2028 	AddStrategicEvent( EVENT_EVALUATE_QUEEN_SITUATION, GetWorldTotalMin() + uiOffset, 0 );
2029 
2030 	// if the queen hasn't been alerted to player's presence yet
2031 	if( !gfQueenAIAwake )
2032 	{ //no decisions can be made yet.
2033 		return;
2034 	}
2035 
2036 	// Adjust queen's disposition based on player's progress
2037 	EvolveQueenPriorityPhase( FALSE );
2038 
2039 	// Gradually promote any remaining admins into troops
2040 	UpgradeAdminsToTroops();
2041 
2042 	if( ( giRequestPoints <= 0 ) || ( ( giReinforcementPoints <= 0 ) && ( giReinforcementPool <= 0 ) ) )
2043 	{ //we either have no reinforcements or request for reinforcements.
2044 		return;
2045 	}
2046 
2047 	//now randomly choose who gets the reinforcements.
2048 	// giRequestPoints is the combined sum of all the individual weights of all garrisons and patrols requesting reinforcements
2049 	iRandom = Random( giRequestPoints );
2050 
2051 	//go through garrisons first
2052 	for( i = 0; i < gGarrisonGroup.size(); i++ )
2053 	{
2054 		RecalculateGarrisonWeight( i );
2055 		iWeight = gGarrisonGroup[ i ].bWeight;
2056 		if( iWeight > 0 )
2057 		{	//if group is requesting reinforcements.
2058 
2059 			iSumOfAllWeights += iWeight;	// debug only!
2060 
2061 			if( iRandom < iWeight && !gGarrisonGroup[ i ].ubPendingGroupID &&
2062 					EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) &&
2063 					GarrisonRequestingMinimumReinforcements( i ) )
2064 			{ //This is the group that gets the reinforcements!
2065 				if( ReinforcementsApproved( i, &usDefencePoints ) )
2066 				{
2067 					SendReinforcementsForGarrison( i, usDefencePoints, NULL );
2068 				}
2069 				else
2070 				{
2071 					SLOGD("Reinforcements were denied to go to %c%d because player forces too strong.",
2072 							SECTORY( gGarrisonGroup[ i ].ubSectorID ) + 'A' - 1, SECTORX( gGarrisonGroup[ i ].ubSectorID ) );
2073 				}
2074 				return;
2075 			}
2076 			iRandom -= iWeight;
2077 		}
2078 	}
2079 
2080 	//go through the patrol groups
2081 	for( i = 0; i < gPatrolGroup.size(); i++ )
2082 	{
2083 		RecalculatePatrolWeight(gPatrolGroup[i]);
2084 		iWeight = gPatrolGroup[ i ].bWeight;
2085 		if( iWeight > 0 )
2086 		{
2087 			iSumOfAllWeights += iWeight;	// debug only!
2088 
2089 			if( iRandom < iWeight && !gPatrolGroup[ i ].ubPendingGroupID && PatrolRequestingMinimumReinforcements( i ) )
2090 			{ //This is the group that gets the reinforcements!
2091 				SendReinforcementsForPatrol( i, NULL );
2092 				return;
2093 			}
2094 			iRandom -= iWeight;
2095 		}
2096 	}
2097 }
2098 
2099 
SaveStrategicAI(HWFILE const hFile)2100 void SaveStrategicAI(HWFILE const hFile)
2101 {
2102 	GARRISON_GROUP gEmptyGarrisonGroup;
2103 	PATROL_GROUP gEmptyPatrolGroup;
2104 	ARMY_COMPOSITION gEmptyArmyComp;
2105 	INT32 iPatrolArraySize = static_cast<INT32>(gPatrolGroup.size());
2106 	INT32 iGarrisonArraySize = static_cast<INT32>(gGarrisonGroup.size());
2107 	size_t i;
2108 
2109 	gEmptyPatrolGroup = PATROL_GROUP{};
2110 	gEmptyArmyComp = ARMY_COMPOSITION{};
2111 	gEmptyGarrisonGroup = GARRISON_GROUP{};
2112 
2113 	FileSeek(hFile, 3, FILE_SEEK_FROM_CURRENT);
2114 	FileWrite(hFile, &gfExtraElites,                      1);
2115 	FileWrite(hFile, &iGarrisonArraySize,                 4);
2116 	FileWrite(hFile, &iPatrolArraySize,                   4);
2117 	FileWrite(hFile, &giReinforcementPool,                4);
2118 	FileWrite(hFile, &giForcePercentage,                  4);
2119 	FileWrite(hFile, &giArmyAlertness,                    4);
2120 	FileWrite(hFile, &giArmyAlertnessDecay,               4);
2121 	FileWrite(hFile, &gfQueenAIAwake,                     1);
2122 	FileWrite(hFile, &giReinforcementPoints,              4);
2123 	FileWrite(hFile, &giRequestPoints,                    4);
2124 	FileWrite(hFile, &gubNumAwareBattles,                 1);
2125 	FileWrite(hFile, &gubSAIVersion,                      1);
2126 	FileWrite(hFile, &gubQueenPriorityPhase,              1);
2127 	FileWrite(hFile, &gfFirstBattleMeanwhileScenePending, 1);
2128 	FileWrite(hFile, &gfMassFortificationOrdered,         1);
2129 	FileWrite(hFile, &gubMinEnemyGroupSize,               1);
2130 	FileWrite(hFile, &gubHoursGracePeriod,                1);
2131 	FileWrite(hFile, &gusPlayerBattleVictories,           2);
2132 	FileWrite(hFile, &gfUseAlternateQueenPosition,        1);
2133 	FileWrite(hFile, gbPadding,           SAI_PADDING_BYTES);
2134 	//Save the army composition (which does get modified)
2135 	FileWrite(hFile, gArmyComp.data(), gArmyComp.size() * sizeof(ARMY_COMPOSITION));
2136 	i = SAVED_ARMY_COMPOSITIONS - gArmyComp.size();
2137 	while( i-- )
2138 	{
2139 		FileWrite(hFile, &gEmptyArmyComp, sizeof(ARMY_COMPOSITION));
2140 	}
2141 	//Save the patrol group definitions
2142 	if (!gPatrolGroup.empty()) FileWrite(hFile, gPatrolGroup.data(), gPatrolGroup.size() * sizeof(PATROL_GROUP));
2143 	i = SAVED_PATROL_GROUPS - gPatrolGroup.size();
2144 	while( i-- )
2145 	{
2146 		FileWrite(hFile, &gEmptyPatrolGroup, sizeof(PATROL_GROUP));
2147 	}
2148 	//Save the garrison information!
2149 	if (!gGarrisonGroup.empty()) FileWrite(hFile, gGarrisonGroup.data(), gGarrisonGroup.size() * sizeof(GARRISON_GROUP));
2150 	i = SAVED_GARRISON_GROUPS - gGarrisonGroup.size();
2151 	while( i-- )
2152 	{
2153 		FileWrite(hFile, &gEmptyGarrisonGroup, sizeof(GARRISON_GROUP));
2154 	}
2155 
2156 	FileWrite(hFile, gubPatrolReinforcementsDenied, gPatrolGroup.size());
2157 
2158 	FileWrite(hFile, gubGarrisonReinforcementsDenied, gGarrisonGroup.size());
2159 }
2160 
2161 
2162 static void ReinitializeUnvisitedGarrisons(void);
2163 
2164 
LoadStrategicAI(HWFILE const hFile)2165 void LoadStrategicAI(HWFILE const hFile)
2166 {
2167 	size_t i;
2168 	UINT8 ubSAIVersion;
2169 	UINT32 iPatrolArraySize, iGarrisonArraySize;
2170 
2171 	FileSeek(hFile, 3, FILE_SEEK_FROM_CURRENT);
2172 	FileRead(hFile, &gfExtraElites,                      1);
2173 	FileRead(hFile, &iGarrisonArraySize,                 4);
2174 	FileRead(hFile, &iPatrolArraySize,                   4);
2175 	FileRead(hFile, &giReinforcementPool,                4);
2176 	FileRead(hFile, &giForcePercentage,                  4);
2177 	FileRead(hFile, &giArmyAlertness,                    4);
2178 	FileRead(hFile, &giArmyAlertnessDecay,               4);
2179 	FileRead(hFile, &gfQueenAIAwake,                     1);
2180 	FileRead(hFile, &giReinforcementPoints,              4);
2181 	FileRead(hFile, &giRequestPoints,                    4);
2182 	FileRead(hFile, &gubNumAwareBattles,                 1);
2183 	FileRead(hFile, &ubSAIVersion,                       1);
2184 	FileRead(hFile, &gubQueenPriorityPhase,              1);
2185 	FileRead(hFile, &gfFirstBattleMeanwhileScenePending, 1);
2186 	FileRead(hFile, &gfMassFortificationOrdered,         1);
2187 	FileRead(hFile, &gubMinEnemyGroupSize,               1);
2188 	FileRead(hFile, &gubHoursGracePeriod,                1);
2189 	FileRead(hFile, &gusPlayerBattleVictories,           2);
2190 	FileRead(hFile, &gfUseAlternateQueenPosition,        1);
2191 	FileRead(hFile, gbPadding,           SAI_PADDING_BYTES);
2192 	//Restore the army composition
2193 	gArmyComp.clear();
2194 	gArmyComp.assign(SAVED_ARMY_COMPOSITIONS, ARMY_COMPOSITION{});
2195 	FileRead(hFile, gArmyComp.data(), SAVED_ARMY_COMPOSITIONS * sizeof(ARMY_COMPOSITION)); // read everything first, will discard what we don't need when we have also the Garrison Groups
2196 
2197 	//Restore the patrol group definitions
2198 	if (iPatrolArraySize != GCM->getPatrolGroups().size())
2199 	{
2200 		STLOGW("Number of Patrol Groups in save ({}) is different from definition ({}). Save might not work properly.", iPatrolArraySize, GCM->getPatrolGroups().size());
2201 	}
2202 	auto buffPG = new PATROL_GROUP[SAVED_PATROL_GROUPS]{};
2203 	FileRead(hFile, buffPG, SAVED_PATROL_GROUPS * sizeof(PATROL_GROUP));
2204 	gPatrolGroup = std::vector<PATROL_GROUP>(buffPG, buffPG + iPatrolArraySize);
2205 	delete[] buffPG;
2206 
2207 	gubSAIVersion = SAI_VERSION;
2208 	//Load the garrison information!
2209 	if (iGarrisonArraySize != GCM->getGarrisonGroups().size())
2210 	{
2211 		STLOGW("Number of Garrison Groups in save ({}) is different from definition ({}). Save might not work properly.", iGarrisonArraySize, GCM->getGarrisonGroups().size());
2212 	}
2213 	auto buffGG = new GARRISON_GROUP[SAVED_GARRISON_GROUPS]{};
2214 	FileRead(hFile, buffGG, SAVED_GARRISON_GROUPS * sizeof(GARRISON_GROUP));
2215 	gGarrisonGroup = std::vector<GARRISON_GROUP>(buffGG, buffGG + iGarrisonArraySize);
2216 	delete[] buffGG;
2217 
2218 	// resize gArmyComp, ensuring all army compositions referenced by Garrison Groups exist
2219 	size_t numArmyCompositions = NUM_ARMY_COMPOSITIONS;
2220 	for (auto gGroup : gGarrisonGroup)
2221 	{
2222 		numArmyCompositions = std::max<size_t>(numArmyCompositions, gGroup.ubComposition + 1);
2223 	}
2224 	gArmyComp.resize(numArmyCompositions);
2225 	if (gArmyComp.size() != GCM->getArmyCompositions().size())
2226 	{
2227 		STLOGW("Number of Army Compositions in save ({}) is different from definition ({}). Save might not work properly.", gArmyComp.size(), GCM->getArmyCompositions().size());
2228 	}
2229 	ArmyCompositionModel::validateLoadedData(gArmyComp);
2230 
2231 	//Load the list of reinforcement patrol points.
2232 	if( gubPatrolReinforcementsDenied )
2233 	{
2234 		delete[] gubPatrolReinforcementsDenied;
2235 		gubPatrolReinforcementsDenied = NULL;
2236 	}
2237 	gubPatrolReinforcementsDenied = new UINT8[iPatrolArraySize]{};
2238 	FileRead(hFile, gubPatrolReinforcementsDenied, iPatrolArraySize);
2239 
2240 	//Load the list of reinforcement garrison points.
2241 	if( gubGarrisonReinforcementsDenied )
2242 	{
2243 		delete[] gubGarrisonReinforcementsDenied;
2244 		gubGarrisonReinforcementsDenied = NULL;
2245 	}
2246 	gubGarrisonReinforcementsDenied = new UINT8[iGarrisonArraySize]{};
2247 	FileRead(hFile, gubGarrisonReinforcementsDenied, iGarrisonArraySize);
2248 
2249 	if( ubSAIVersion < 6 )
2250 	{ //Reinitialize the costs since they have changed.
2251 
2252 		//Recreate the compositions
2253 		gArmyComp = GCM->getArmyCompositions();
2254 		EvolveQueenPriorityPhase( TRUE );
2255 
2256 		//Recreate the patrol desired sizes
2257 		auto origPatrolGroup = GCM->getPatrolGroups();
2258 		for( i = 0; i < gPatrolGroup.size(); i++ )
2259 		{
2260 			gPatrolGroup[ i ].bSize = origPatrolGroup[ i ].bSize;
2261 		}
2262 	}
2263 	if( ubSAIVersion < 7 )
2264 	{
2265 		BuildUndergroundSectorInfoList();
2266 	}
2267 	if( ubSAIVersion < 8 )
2268 	{
2269 		ReinitializeUnvisitedGarrisons();
2270 	}
2271 	if( ubSAIVersion < 10 )
2272 	{
2273 		for( i = 0; i < gPatrolGroup.size(); i++ )
2274 		{
2275 			if( gPatrolGroup[ i ].bSize >= 16 )
2276 			{
2277 				gPatrolGroup[ i ].bSize = 10;
2278 			}
2279 		}
2280 		FOR_EACH_ENEMY_GROUP(pGroup)
2281 		{
2282 			if (pGroup->ubGroupSize >= 16)
2283 			{ //accident in patrol groups being too large
2284 				UINT8	ubGetRidOfXTroops = pGroup->ubGroupSize - 10;
2285 				if( gbWorldSectorZ || pGroup->ubSectorX != gWorldSectorX || pGroup->ubSectorY != gWorldSectorY )
2286 				{ //don't modify groups in the currently loaded sector.
2287 					if( pGroup->pEnemyGroup->ubNumTroops >= ubGetRidOfXTroops )
2288 					{
2289 						pGroup->pEnemyGroup->ubNumTroops -= ubGetRidOfXTroops;
2290 						pGroup->ubGroupSize -= ubGetRidOfXTroops;
2291 					}
2292 				}
2293 			}
2294 		}
2295 	}
2296 	if( ubSAIVersion < 13 )
2297 	{
2298 		for( i = 0; i < 255; i++ )
2299 		{
2300 			SectorInfo[ i ].bBloodCatPlacements = 0;
2301 			SectorInfo[ i ].bBloodCats = -1;
2302 		}
2303 		InitBloodCatSectors();
2304 		//This info is used to clean up the two new codes inserted into the
2305 		//middle of the enumeration for battle codes.
2306 		if( gubEnemyEncounterCode > CREATURE_ATTACK_CODE )
2307 		{
2308 			gubEnemyEncounterCode += 2;
2309 		}
2310 		if( gubExplicitEnemyEncounterCode > CREATURE_ATTACK_CODE )
2311 		{
2312 			gubExplicitEnemyEncounterCode += 2;
2313 		}
2314 
2315 	}
2316 	if( ubSAIVersion < 14 )
2317 	{
2318 		UNDERGROUND_SECTORINFO *pSector;
2319 		pSector = FindUnderGroundSector( 4, 11, 1 );
2320 		if( pSector->ubNumTroops + pSector->ubNumElites > 20 )
2321 		{
2322 			pSector->ubNumTroops -= 2;
2323 		}
2324 		pSector = FindUnderGroundSector( 3, 15, 1 );
2325 		if( pSector->ubNumTroops + pSector->ubNumElites > 20 )
2326 		{
2327 			pSector->ubNumTroops -= 2;
2328 		}
2329 	}
2330 	if( ubSAIVersion < 16 )
2331 	{
2332 		UNDERGROUND_SECTORINFO *pSector;
2333 		pSector = FindUnderGroundSector( 3, 15, 1 );
2334 		if( pSector )
2335 		{
2336 			pSector->ubAdjacentSectors |= SOUTH_ADJACENT_SECTOR;
2337 		}
2338 		pSector = FindUnderGroundSector( 3, 16, 1 );
2339 		if( pSector )
2340 		{
2341 			pSector->ubAdjacentSectors |= NORTH_ADJACENT_SECTOR;
2342 		}
2343 	}
2344 	if( ubSAIVersion < 17 )
2345 	{ //Patch all groups that have this flag set
2346 		gubNumGroupsArrivedSimultaneously = 0;
2347 		{
2348 			FOR_EACH_GROUP(pGroup)
2349 			{
2350 				if( pGroup->uiFlags & GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY )
2351 				{
2352 					pGroup->uiFlags &= ~GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY;
2353 				}
2354 			}
2355 		}
2356 	}
2357 	if( ubSAIVersion < 18 )
2358 	{ //adjust down the number of bloodcats based on difficulty in the two special bloodcat levels
2359 		switch( gGameOptions.ubDifficultyLevel )
2360 		{
2361 			case DIF_LEVEL_EASY: //50%
2362 				SectorInfo[ SEC_I16	].bBloodCatPlacements = 14;
2363 				SectorInfo[ SEC_N5	].bBloodCatPlacements = 13;
2364 				SectorInfo[ SEC_I16	].bBloodCats = 14;
2365 				SectorInfo[ SEC_N5	].bBloodCats = 13;
2366 				break;
2367 			case DIF_LEVEL_MEDIUM: //75%
2368 				SectorInfo[ SEC_I16	].bBloodCatPlacements = 19;
2369 				SectorInfo[ SEC_N5	].bBloodCatPlacements = 18;
2370 				SectorInfo[ SEC_I16	].bBloodCats = 19;
2371 				SectorInfo[ SEC_N5	].bBloodCats = 18;
2372 				break;
2373 			case DIF_LEVEL_HARD: //100%
2374 				SectorInfo[ SEC_I16	].bBloodCatPlacements = 26;
2375 				SectorInfo[ SEC_N5	].bBloodCatPlacements = 25;
2376 				SectorInfo[ SEC_I16	].bBloodCats = 26;
2377 				SectorInfo[ SEC_N5	].bBloodCats = 25;
2378 				break;
2379 		}
2380 	}
2381 	if( ubSAIVersion < 19 )
2382 	{
2383 		//Clear the garrison in C5
2384 		gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_C5 ].ubGarrisonID ].ubComposition ].bPriority = 0;
2385 		gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_C5 ].ubGarrisonID ].ubComposition ].bDesiredPopulation = 0;
2386 	}
2387 	if( ubSAIVersion < 20 )
2388 	{
2389 		gArmyComp[ QUEEN_DEFENCE ].bDesiredPopulation = 32;
2390 		SectorInfo[ SEC_P3 ].ubNumElites = 32;
2391 	}
2392 	if( ubSAIVersion < 21 )
2393 	{
2394 		FOR_EACH_GROUP(pGroup) pGroup->uiFlags = 0;
2395 	}
2396 	if( ubSAIVersion < 22 )
2397 	{ //adjust down the number of bloodcats based on difficulty in the two special bloodcat levels
2398 		switch( gGameOptions.ubDifficultyLevel )
2399 		{
2400 			case DIF_LEVEL_EASY: //50%
2401 				SectorInfo[ SEC_N5	].bBloodCatPlacements = 8;
2402 				SectorInfo[ SEC_N5	].bBloodCats = 10;
2403 				break;
2404 			case DIF_LEVEL_MEDIUM: //75%
2405 				SectorInfo[ SEC_N5	].bBloodCatPlacements = 8;
2406 				SectorInfo[ SEC_N5	].bBloodCats = 10;
2407 				break;
2408 			case DIF_LEVEL_HARD: //100%
2409 				SectorInfo[ SEC_N5	].bBloodCatPlacements = 8;
2410 				SectorInfo[ SEC_N5	].bBloodCats = 10;
2411 				break;
2412 		}
2413 	}
2414 	if( ubSAIVersion < 23 )
2415 	{
2416 		if( gWorldSectorX != 3 || gWorldSectorY != 16 || !gbWorldSectorZ )
2417 		{
2418 			SectorInfo[ SEC_P3 ].ubNumElites = 32;
2419 		}
2420 	}
2421 	if( ubSAIVersion < 24 )
2422 	{
2423 		//If the queen has escaped to the basement, do not use the profile insertion info
2424 		//when we finally go down there, otherwise she will end up in the wrong spot, possibly inside
2425 		//the walls.
2426 		if( !gubFact[ FACT_QUEEN_DEAD ] && gMercProfiles[ QUEEN ].bSectorZ == 1 )
2427 				{
2428 		if( gbWorldSectorZ != 1 || gWorldSectorX != 16 || gWorldSectorY != 3 )
2429 		{ //We aren't in the basement sector
2430 			gMercProfiles[ QUEEN ].fUseProfileInsertionInfo = FALSE;
2431 		}
2432 		else
2433 		{ //We are in the basement sector, relocate queen to proper position.
2434 			FOR_EACH_IN_TEAM(i, CIV_TEAM)
2435 			{
2436 				SOLDIERTYPE& s = *i;
2437 				if (s.ubProfile != QUEEN) continue;
2438 				// Found queen, relocate her to 16866
2439 				BumpAnyExistingMerc(16866);
2440 				TeleportSoldier(s, 16866, true);
2441 				break;
2442 			}
2443 		}
2444 				}
2445 	}
2446 	if( ubSAIVersion < 25 )
2447 	{
2448 		if( gubFact[ FACT_SKYRIDER_CLOSE_TO_CHOPPER ] )
2449 		{
2450 			gMercProfiles[ SKYRIDER ].fUseProfileInsertionInfo = FALSE;
2451 		}
2452 	}
2453 
2454 	//KM : July 21, 1999 patch fix
2455 	if( ubSAIVersion < 26 )
2456 	{
2457 		INT32 i;
2458 		for( i = 0; i < 255; i++ )
2459 		{
2460 			if( SectorInfo[ i ].ubNumberOfCivsAtLevel[ GREEN_MILITIA ] +
2461 					SectorInfo[ i ].ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] +
2462 					SectorInfo[ i ].ubNumberOfCivsAtLevel[ ELITE_MILITIA ] > 20 )
2463 			{
2464 				SectorInfo[ i ].ubNumberOfCivsAtLevel[ GREEN_MILITIA ] = 0;
2465 				SectorInfo[ i ].ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] = 20;
2466 				SectorInfo[ i ].ubNumberOfCivsAtLevel[ ELITE_MILITIA ] = 0;
2467 			}
2468 		}
2469 	}
2470 
2471 	//KM : August 4, 1999 patch fix
2472 	//     This addresses the problem of not having any soldiers in sector N7 when playing the game under easy difficulty.
2473 	//     If captured and interrogated, the player would find no soldiers defending the sector.  This changes the composition
2474 	//     so that it will always be there, and adds the soldiers accordingly if the sector isn't loaded when the update is made.
2475 	if( ubSAIVersion < 27 )
2476 	{
2477 		if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_EASY )
2478 		{
2479 			if( gWorldSectorX != 7 || gWorldSectorY != 14 || gbWorldSectorZ )
2480 			{
2481 				INT32 cnt, iRandom;
2482 				INT32 iEliteChance, iTroopChance, iAdminChance;
2483 				INT32 iStartPop;
2484 				SECTORINFO *pSector = NULL;
2485 
2486 				//Change the garrison composition to LEVEL1_DEFENCE from LEVEL2_DEFENCE
2487 				pSector = &SectorInfo[ SEC_N7 ];
2488 				gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition = LEVEL1_DEFENCE;
2489 
2490 				iStartPop = gArmyComp[ gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition ].bStartPopulation;
2491 				iEliteChance = gArmyComp[ gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition ].bElitePercentage;
2492 				iTroopChance = gArmyComp[ gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition ].bTroopPercentage + iEliteChance;
2493 				iAdminChance = gArmyComp[ gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition ].bAdminPercentage;
2494 
2495 				if( iStartPop )
2496 				{
2497 					// if population is less than maximum
2498 					if( iStartPop != MAX_STRATEGIC_TEAM_SIZE )
2499 					{
2500 						// then vary it a bit (+/- 25%)
2501 						iStartPop = iStartPop * ( 100 + ( Random ( 51 ) - 25 ) ) / 100;
2502 					}
2503 
2504 					iStartPop = MAX( gubMinEnemyGroupSize, MIN( MAX_STRATEGIC_TEAM_SIZE, iStartPop ) );
2505 					cnt = iStartPop;
2506 
2507 					if( iAdminChance )
2508 					{
2509 						pSector->ubNumAdmins = iAdminChance * iStartPop / 100;
2510 					}
2511 					else while( cnt-- )
2512 					{ //for each person, randomly determine the types of each soldier.
2513 						{
2514 							iRandom = Random( 100 );
2515 							if( iRandom < iEliteChance )
2516 							{
2517 								pSector->ubNumElites++;
2518 							}
2519 							else if( iRandom < iTroopChance )
2520 							{
2521 								pSector->ubNumTroops++;
2522 							}
2523 						}
2524 					}
2525 				}
2526 			}
2527 		}
2528 	}
2529 
2530 	if( ubSAIVersion < 28 )
2531 	{
2532 		if( !StrategicMap[ CALCULATE_STRATEGIC_INDEX( 3, 16 ) ].fEnemyControlled )
2533 		{ //Eliminate all enemy groups in this sector, because the player owns the sector, and it is not
2534 			//possible for them to spawn there!
2535 			FOR_EACH_GROUP_SAFE(i)
2536 			{
2537 				GROUP& g = *i;
2538 				if (g.fPlayer)         continue;
2539 				if (g.ubSectorX !=  3) continue;
2540 				if (g.ubSectorY != 16) continue;
2541 				if (g.ubPrevX   !=  0) continue;
2542 				if (g.ubPrevY   !=  0) continue;
2543 				RemoveGroupFromStrategicAILists(g);
2544 				RemoveGroup(g);
2545 			}
2546 		}
2547 	}
2548 	if( ubSAIVersion < 29 )
2549 	{
2550 		InitStrategicMovementCosts();
2551 	}
2552 
2553 	//KM : Aug 11, 1999 -- Patch fix:  Blindly update the airspace control.  There is a bug somewhere
2554 	//     that is failing to keep this information up to date, and I failed to find it.  At least this
2555 	//     will patch saves.
2556 	UpdateAirspaceControl( );
2557 
2558 	EvolveQueenPriorityPhase( TRUE );
2559 
2560 	//Count and correct the floating groups
2561 	FOR_EACH_GROUP_SAFE(pGroup)
2562 	{
2563 		if( !pGroup->fPlayer )
2564 		{
2565 			if( !pGroup->fBetweenSectors )
2566 			{
2567 				if( pGroup->ubSectorX != gWorldSectorX ||
2568 						pGroup->ubSectorY != gWorldSectorY ||
2569 						gbWorldSectorZ )
2570 				{
2571 					RepollSAIGroup( pGroup );
2572 					ValidateGroup( pGroup );
2573 				}
2574 			}
2575 		}
2576 	}
2577 	SLOGD("Restoring saved game at Day %02d, %02d:%02d ", GetWorldDay(), GetWorldHour(), GetWorldMinutesInDay()%60 );
2578 
2579 	//Update the version number to the most current.
2580 	gubSAIVersion = SAI_VERSION;
2581 }
2582 
2583 
2584 //As the player's progress changes in the game, the queen will adjust her priorities accordingly.
2585 //Basically, increasing priorities and numbers for sectors she owns, and lowering them.
2586 //@@@Alex, this is tweakable.  My philosophies could be incorrect.  It might be better if instead of lowering
2587 //priorities and numbers for towns the queen has lost, to instead lower the priority but increase the numbers so
2588 //she would send larger attack forces.  This is questionable.
EvolveQueenPriorityPhase(BOOLEAN fForceChange)2589 static void EvolveQueenPriorityPhase(BOOLEAN fForceChange)
2590 {
2591 	INT32 index, num, iFactor;
2592 	INT32 iChange, iNew, iNumSoldiers, iNumPromotions;
2593 	SECTORINFO *pSector;
2594 	std::vector<UINT8> ubOwned;
2595 	std::vector<UINT8> ubTotal;
2596 	UINT8 ubNewPhase;
2597 	ubNewPhase = CurrentPlayerProgressPercentage() / 10;
2598 	auto origArmyComp = GCM->getArmyCompositions();
2599 
2600 	if( !fForceChange && ubNewPhase == gubQueenPriorityPhase )
2601 	{
2602 		return;
2603 	}
2604 
2605 	if( gubQueenPriorityPhase > ubNewPhase )
2606 	{
2607 		SLOGD("The queen's defence priority has decreased from %d0%% to %d0%%.", gubQueenPriorityPhase, ubNewPhase );
2608 	}
2609 	else if( gubQueenPriorityPhase < ubNewPhase )
2610 	{
2611 		SLOGD("The queen's defence priority has increased from %d0%% to %d0%%.", gubQueenPriorityPhase, ubNewPhase );
2612 	}
2613 	else
2614 	{
2615 		SLOGD("The queen's defence priority is the same (%d0%%), but has been forced to update.", gubQueenPriorityPhase );
2616 	}
2617 
2618 	gubQueenPriorityPhase = ubNewPhase;
2619 
2620 	//The phase value refers to the deviation percentage she will apply to original garrison values.
2621 	//All sector values are evaluated to see how many of those sectors are enemy controlled.  If they
2622 	//are controlled by her, the desired number will be increased as well as the priority.  On the other
2623 	//hand, if she doesn't own those sectors, the values will be decreased instead.  All values are based off of
2624 	//the originals.
2625 	std::fill_n(std::back_inserter(ubOwned), gArmyComp.size(), 0);
2626 	std::fill_n(std::back_inserter(ubTotal), gArmyComp.size(), 0);
2627 
2628 	//Record the values required to calculate the percentage of each composition type that the queen controls.
2629 	for( size_t i = 0; i < gGarrisonGroup.size(); i++ )
2630 	{
2631 		index = gGarrisonGroup[ i ].ubComposition;
2632 		if( StrategicMap[ SECTOR_INFO_TO_STRATEGIC_INDEX( gGarrisonGroup[ i ].ubSectorID ) ].fEnemyControlled )
2633 		{
2634 			ubOwned[ index ]++;
2635 		}
2636 		ubTotal[ index ]++;
2637 	}
2638 
2639 	//Go through the *majority* of compositions and modify the priority/desired values.
2640 	for( size_t i = 0; i < gArmyComp.size(); i++ )
2641 	{
2642 		switch( i )
2643 		{
2644 			case QUEEN_DEFENCE:
2645 			case MEDUNA_DEFENCE:
2646 			case MEDUNA_SAMSITE:
2647 			case LEVEL1_DEFENCE:
2648 			case LEVEL2_DEFENCE:
2649 			case LEVEL3_DEFENCE:
2650 			case OMERTA_WELCOME_WAGON:
2651 			case ROADBLOCK:
2652 			//case SANMONA_SMALL:
2653 				//don't consider these compositions
2654 				continue;
2655 		}
2656 		//If the queen owns them ALL, then she gets the maximum defensive bonus.  If she owns NONE,
2657 		//then she gets a maximum defensive penalty.  Everything else lies in the middle.  The legal
2658 		//range is +-50.
2659 		if( ubTotal[ i ] )
2660 		{
2661 			iFactor = (ubOwned[ i ] * 100 / ubTotal[ i ]) - 50;
2662 		}
2663 		else
2664 		{
2665 			iFactor = -50;
2666 		}
2667 		iFactor = iFactor * gubQueenPriorityPhase / 10;
2668 
2669 		//modify priority by + or - 25% of original
2670 		if( gArmyComp[ i ].bPriority )
2671 		{
2672 			num = origArmyComp[ i ].bPriority+ iFactor / 2;
2673 			num = MIN( MAX( 0, num ), 100 );
2674 			gArmyComp[ i ].bPriority = (INT8)num;
2675 		}
2676 
2677 		//modify desired population by + or - 50% of original population
2678 		num = origArmyComp[ i ].bDesiredPopulation * (100 + iFactor) / 100;
2679 		num = MIN( MAX( 6, num ), MAX_STRATEGIC_TEAM_SIZE );
2680 		gArmyComp[ i ].bDesiredPopulation = (INT8)num;
2681 
2682 		//if gfExtraElites is set, then augment the composition sizes
2683 		if( gfExtraElites && iFactor >= 15 && gArmyComp[ i ].bElitePercentage )
2684 		{
2685 			iChange = gGameOptions.ubDifficultyLevel * 5;
2686 
2687 			//increase elite % (max 100)
2688 			iNew = gArmyComp[ i ].bElitePercentage + iChange;
2689 			iNew = (INT8)MIN( 100, iNew );
2690 			gArmyComp[ i ].bElitePercentage = (INT8)iNew;
2691 
2692 			//decrease troop % (min 0)
2693 			iNew = gArmyComp[ i ].bTroopPercentage - iChange;
2694 			iNew = (INT8)MAX( 0, iNew );
2695 			gArmyComp[ i ].bTroopPercentage = (INT8)iNew;
2696 		}
2697 	}
2698 	if( gfExtraElites )
2699 	{
2700 		//Turn off the flag so that this doesn't happen everytime this function is called!
2701 		gfExtraElites = FALSE;
2702 
2703 		for( UINT32 i = 0; i < gGarrisonGroup.size(); i++ )
2704 		{
2705 			//if we are dealing with extra elites, then augment elite compositions (but only if they exist in the sector).
2706 			//If the queen still owns the town by more than 65% (iFactor >= 15), then upgrade troops to elites in those sectors.
2707 			index = gGarrisonGroup[ i ].ubComposition;
2708 			switch( index )
2709 			{
2710 				case QUEEN_DEFENCE:
2711 				case MEDUNA_DEFENCE:
2712 				case MEDUNA_SAMSITE:
2713 				case LEVEL1_DEFENCE:
2714 				case LEVEL2_DEFENCE:
2715 				case LEVEL3_DEFENCE:
2716 				case OMERTA_WELCOME_WAGON:
2717 				case ROADBLOCK:
2718 				//case SANMONA_SMALL:
2719 					//don't consider these compositions
2720 					continue;
2721 			}
2722 			pSector = &SectorInfo[ gGarrisonGroup[ i ].ubSectorID ];
2723 			if( ubTotal[ index ] )
2724 			{
2725 				iFactor = (ubOwned[ index ] * 100 / ubTotal[ index ]) - 50;
2726 			}
2727 			else
2728 			{
2729 				iFactor = -50;
2730 			}
2731 			if( iFactor >= 15 )
2732 			{ //Make the actual elites in sector match the new garrison percentage
2733 				if( !gfWorldLoaded || gbWorldSectorZ ||
2734 						gWorldSectorX != SECTORX( gGarrisonGroup[ i ].ubSectorID ) ||
2735 						gWorldSectorY != SECTORY( gGarrisonGroup[ i ].ubSectorID ) )
2736 				{ //Also make sure the sector isn't currently loaded!
2737 					iNumSoldiers = pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites;
2738 					iNumPromotions = gArmyComp[ index ].bElitePercentage * iNumSoldiers / 100 - pSector->ubNumElites;
2739 
2740 					if( iNumPromotions > 0 )
2741 					{
2742 						while( iNumPromotions-- )
2743 						{
2744 							if( pSector->ubNumAdmins )
2745 							{
2746 								pSector->ubNumAdmins--;
2747 							}
2748 							else if( pSector->ubNumTroops )
2749 							{
2750 								pSector->ubNumTroops--;
2751 							}
2752 							else
2753 							{
2754 								SLOGA("EvolveQueenPriorityPhase: more promotions than soldiers");
2755 							}
2756 							pSector->ubNumElites++;
2757 						}
2758 						Assert( iNumSoldiers == pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites );
2759 					}
2760 				}
2761 			}
2762 		}
2763 	}
2764 	//Recalculate all of the weights.
2765 	for( size_t i = 0; i < gGarrisonGroup.size(); i++ )
2766 	{
2767 		RecalculateGarrisonWeight( i );
2768 	}
2769 }
2770 
2771 
2772 static void RequestHighPriorityGarrisonReinforcements(INT32 iGarrisonID, UINT8 ubSoldiersRequested);
2773 
2774 
ExecuteStrategicAIAction(UINT16 usActionCode,INT16 sSectorX,INT16 sSectorY)2775 void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSectorY )
2776 {
2777 	GROUP *pGroup, *pPendingGroup = NULL;
2778 	SECTORINFO *pSector;
2779 	UINT8 ubSectorID;
2780 	UINT8 ubNumSoldiers;
2781 	switch( usActionCode )
2782 	{
2783 		case STRATEGIC_AI_ACTION_WAKE_QUEEN:
2784 			WakeUpQueen();
2785 			break;
2786 
2787 		case STRATEGIC_AI_ACTION_QUEEN_DEAD:
2788 			gfQueenAIAwake = FALSE;
2789 			break;
2790 
2791 		case STRATEGIC_AI_ACTION_KINGPIN_DEAD:
2792 			//Immediate send a small garrison to C5 (to discourage access to Tony the dealer)
2793 			/*
2794 			for( i = 0; i < gGarrisonGroup.size(); i++ )
2795 			{
2796 				if( gGarrisonGroup[ i ].ubComposition == SANMONA_SMALL )
2797 				{
2798 					//Setup the composition so from now on the queen will consider this an important sector
2799 					//to hold.
2800 					gArmyComp[ gGarrisonGroup[ i ].ubComposition ].bPriority = 65;
2801 					gArmyComp[ gGarrisonGroup[ i ].ubComposition ].bTroopPercentage = 100;
2802 					gArmyComp[ gGarrisonGroup[ i ].ubComposition ].bDesiredPopulation = 5;
2803 					RequestHighPriorityGarrisonReinforcements( i, (UINT8)(2 + Random( 4 )) ); //send 2-5 soldiers now.
2804 					break;
2805 				}
2806 			}
2807 			*/
2808 			break;
2809 		case NPC_ACTION_SEND_SOLDIERS_TO_DRASSEN:
2810 			//Send 6, 9, or 12 troops (based on difficulty) one of the Drassen sectors.  If nobody is there when they arrive,
2811 			//those troops will get reassigned.
2812 
2813 			if( Chance( 50 ) )
2814 			{
2815 				ubSectorID = SEC_D13;
2816 			}
2817 			else if( Chance( 60 ) )
2818 			{
2819 				ubSectorID = SEC_B13;
2820 			}
2821 			else
2822 			{
2823 				ubSectorID = SEC_C13;
2824 			}
2825 			ubNumSoldiers = (UINT8)(3 + gGameOptions.ubDifficultyLevel * 3);
2826 			pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_P3, 0, ubNumSoldiers, 0 );
2827 
2828 			if( !gGarrisonGroup[ SectorInfo[ ubSectorID ].ubGarrisonID ].ubPendingGroupID )
2829 			{
2830 				pGroup->pEnemyGroup->ubIntention = STAGE;
2831 				gGarrisonGroup[ SectorInfo[ ubSectorID ].ubGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
2832 			}
2833 			else
2834 			{ //this should never happen (but if it did, then this is the best way to deal with it).
2835 				pGroup->pEnemyGroup->ubIntention = PURSUIT;
2836 			}
2837 			giReinforcementPool -= ubNumSoldiers;
2838 			giReinforcementPool = MAX( giReinforcementPool, 0 );
2839 
2840 			MoveSAIGroupToSector( &pGroup, ubSectorID, EVASIVE, pGroup->pEnemyGroup->ubIntention );
2841 
2842 			break;
2843 		case NPC_ACTION_SEND_SOLDIERS_TO_BATTLE_LOCATION:
2844 
2845 			//Send 4, 8, or 12 troops (based on difficulty) to the location of the first battle.  If nobody is there when they arrive,
2846 			//those troops will get reassigned.
2847 			ubSectorID = (UINT8)STRATEGIC_INDEX_TO_SECTOR_INFO( sWorldSectorLocationOfFirstBattle );
2848 			pSector = &SectorInfo[ ubSectorID ];
2849 			ubNumSoldiers = (UINT8)(gGameOptions.ubDifficultyLevel * 4);
2850 			pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_P3, 0, ubNumSoldiers, 0 );
2851 			giReinforcementPool -= ubNumSoldiers;
2852 			giReinforcementPool = MAX( giReinforcementPool, 0 );
2853 
2854 			//Determine if the battle location actually has a garrison assignment.  If so, and the following
2855 			//checks succeed, the enemies will be sent to attack and reinforce that sector.  Otherwise, the
2856 			//enemies will simply check it out, then leave.
2857 			if( pSector->ubGarrisonID != NO_GARRISON )
2858 			{ //sector has a garrison
2859 				if( !NumEnemiesInSector( (INT16)SECTORX( ubSectorID ), (INT16)SECTORY( ubSectorID ) ) )
2860 				{ //no enemies are here
2861 					if( gArmyComp[ !gGarrisonGroup[ pSector->ubGarrisonID ].ubComposition ].bPriority )
2862 					{ //the garrison is important
2863 						if( !gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID )
2864 						{ //the garrison doesn't have reinforcements already on route.
2865 							gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
2866 							MoveSAIGroupToSector( &pGroup, ubSectorID, STAGE, REINFORCEMENTS );
2867 							break;
2868 						}
2869 					}
2870 				}
2871 			}
2872 			else
2873 			{
2874 				MoveSAIGroupToSector( &pGroup, ubSectorID, EVASIVE, PURSUIT );
2875 			}
2876 
2877 			break;
2878 		case NPC_ACTION_SEND_SOLDIERS_TO_OMERTA:
2879 			ubNumSoldiers = (UINT8)(gGameOptions.ubDifficultyLevel * 6); //6, 12, or 18 based on difficulty.
2880 			pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_P3, 0, ubNumSoldiers, (UINT8)(ubNumSoldiers/7) ); //add 1 elite to normal, and 2 for hard
2881 			ubNumSoldiers = (UINT8)(ubNumSoldiers + ubNumSoldiers / 7);
2882 			giReinforcementPool -= ubNumSoldiers;
2883 			giReinforcementPool = MAX( giReinforcementPool, 0 );
2884 			if( PlayerMercsInSector( 9, 1, 1 ) && !PlayerMercsInSector( 10, 1, 1 ) && !PlayerMercsInSector( 10, 1, 2 ) )
2885 			{ //send to A9 (if mercs in A9, but not in A10 or A10 basement)
2886 				ubSectorID = SEC_A9;
2887 			}
2888 			else
2889 			{ //send to A10
2890 				ubSectorID = SEC_A10;
2891 			}
2892 
2893 			MoveSAIGroupToSector( &pGroup, ubSectorID, EVASIVE, PURSUIT );
2894 
2895 			ValidateGroup( pGroup );
2896 			break;
2897 		case NPC_ACTION_SEND_TROOPS_TO_SAM:
2898 			ubSectorID = (UINT8)SECTOR( sSectorX, sSectorY );
2899 			ubNumSoldiers = (UINT8)( 3 + gGameOptions.ubDifficultyLevel + HighestPlayerProgressPercentage() / 15 );
2900 			giReinforcementPool -= ubNumSoldiers;
2901 			giReinforcementPool = MAX( giReinforcementPool, 0 );
2902 			pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_P3, 0, 0, ubNumSoldiers );
2903 			MoveSAIGroupToSector( &pGroup, ubSectorID, STAGE, REINFORCEMENTS );
2904 
2905 			if( gGarrisonGroup[ SectorInfo[ ubSectorID ].ubGarrisonID ].ubPendingGroupID )
2906 			{	//Clear the pending group's assignment.
2907 				pPendingGroup = GetGroup( gGarrisonGroup[ SectorInfo[ ubSectorID ].ubGarrisonID ].ubPendingGroupID );
2908 				Assert( pPendingGroup );
2909 				RemoveGroupFromStrategicAILists(*pPendingGroup);
2910 			}
2911 			//Assign the elite squad to attack the SAM site
2912 			pGroup->pEnemyGroup->ubIntention = REINFORCEMENTS;
2913 			gGarrisonGroup[ SectorInfo[ ubSectorID ].ubGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
2914 
2915 			if( pPendingGroup )
2916 			{ //Reassign the pending group
2917 				ReassignAIGroup( &pPendingGroup );
2918 			}
2919 
2920 			break;
2921 		case NPC_ACTION_ADD_MORE_ELITES:
2922 			gfExtraElites = TRUE;
2923 			EvolveQueenPriorityPhase( TRUE );
2924 			break;
2925 		case NPC_ACTION_GIVE_KNOWLEDGE_OF_ALL_MERCS:
2926 			//temporarily make the queen's forces more aware (high alert)
2927 			gubNumAwareBattles = saipolicy(num_aware_battles);
2928 			break;
2929 		default:
2930 			SLOGD("QueenAI failed to handle action code %d.", usActionCode );
2931 			break;
2932 	}
2933 }
2934 
2935 static UINT8 RedirectEnemyGroupsMovingThroughSector(UINT8 ubSectorX, UINT8 ubSectorY);
2936 
2937 
StrategicHandleQueenLosingControlOfSector(INT16 sSectorX,INT16 sSectorY,INT16 sSectorZ)2938 void StrategicHandleQueenLosingControlOfSector( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ )
2939 {
2940 	SECTORINFO *pSector;
2941 	UINT8 ubSectorID;
2942 	if( sSectorZ )
2943 	{ //The queen doesn't care about anything happening under the ground.
2944 		return;
2945 	}
2946 
2947 	if( StrategicMap[ sSectorX + sSectorY * MAP_WORLD_X ].fEnemyControlled )
2948 	{ //If the sector doesn't belong to the player, then we shouldn't be calling this function!
2949 		SLOGE( "StrategicHandleQueenLosingControlOfSector() was called for a sector that is internally considered to be enemy controlled." );
2950 		return;
2951 	}
2952 
2953 	ubSectorID = SECTOR( sSectorX, sSectorY );
2954 	pSector = &SectorInfo[ ubSectorID ];
2955 
2956 	//Keep track of victories and wake up the queen after x number of battles.
2957 	gusPlayerBattleVictories++;
2958 	if( gusPlayerBattleVictories == 5 - gGameOptions.ubDifficultyLevel )
2959 	{ //4 victories for easy, 3 for normal, 2 for hard
2960 		WakeUpQueen();
2961 	}
2962 
2963 	if( pSector->ubGarrisonID == NO_GARRISON )
2964 	{ //Queen doesn't care if the sector lost wasn't a garrison sector.
2965 		return;
2966 	}
2967 	else
2968 	{ //check to see if there are any pending reinforcements.  If so, then cancel their orders and have them
2969 		//reassigned, so the player doesn't get pestered.  This is a feature that *dumbs* down the AI, and is done
2970 		//for the sake of gameplay.  We don't want the game to be tedious.
2971 		if( !pSector->uiTimeLastPlayerLiberated )
2972 		{
2973 			pSector->uiTimeLastPlayerLiberated = GetWorldTotalSeconds();
2974 		}
2975 		else
2976 		{ //convert hours to seconds and subtract up to half of it randomly "seconds - (hours*3600 / 2)"
2977 			pSector->uiTimeLastPlayerLiberated = GetWorldTotalSeconds() - Random( gubHoursGracePeriod * 1800 );
2978 		}
2979 		if( gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID )
2980 		{
2981 			GROUP *pGroup;
2982 			pGroup = GetGroup( gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID );
2983 			if( pGroup )
2984 			{
2985 				ReassignAIGroup( &pGroup );
2986 			}
2987 			gGarrisonGroup[ pSector->ubGarrisonID ].ubPendingGroupID = 0;
2988 		}
2989 	}
2990 
2991 	//If there are any enemy groups that will be moving through this sector due, they will have to repath which
2992 	//will cause them to avoid the sector.  Returns the number of redirected groups.
2993 	RedirectEnemyGroupsMovingThroughSector( (UINT8)sSectorX, (UINT8)sSectorY );
2994 }
2995 
2996 
SectorDistance(UINT8 ubSectorID1,UINT8 ubSectorID2)2997 static UINT8 SectorDistance(UINT8 ubSectorID1, UINT8 ubSectorID2)
2998 {
2999 	UINT8 ubSectorX1, ubSectorX2, ubSectorY1, ubSectorY2;
3000 	UINT8 ubDist;
3001 	ubSectorX1 = (UINT8)SECTORX( ubSectorID1 );
3002 	ubSectorX2 = (UINT8)SECTORX( ubSectorID2 );
3003 	ubSectorY1 = (UINT8)SECTORY( ubSectorID1 );
3004 	ubSectorY2 = (UINT8)SECTORY( ubSectorID2 );
3005 
3006 	ubDist = (UINT8)( ABS( ubSectorX1 - ubSectorX2 ) + ABS( ubSectorY1 - ubSectorY2 ) );
3007 
3008 	return ubDist;
3009 }
3010 
3011 
RequestHighPriorityGarrisonReinforcements(size_t iGarrisonID,UINT8 ubSoldiersRequested)3012 static void RequestHighPriorityGarrisonReinforcements(size_t iGarrisonID, UINT8 ubSoldiersRequested)
3013 {
3014 	size_t i, iBestIndex;
3015 	GROUP *pGroup;
3016 	UINT8 ubBestDist, ubDist;
3017 	UINT8 ubDstSectorX, ubDstSectorY;
3018 	//AssertMsg( gPatrolGroup.size() == GCM->getPatrolGroups().size(), "Strategic AI -- Patrol group definition mismatch." );
3019 	ubBestDist = 255;
3020 	iBestIndex = -1;
3021 	for( i = 0; i < gPatrolGroup.size(); i++ )
3022 	{
3023 		if( gPatrolGroup[ i ].ubGroupID )
3024 		{
3025 			pGroup = GetGroup( gPatrolGroup[ i ].ubGroupID );
3026 			if( pGroup && pGroup->ubGroupSize >= ubSoldiersRequested )
3027 			{
3028 				ubDist = SectorDistance( (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ), gGarrisonGroup[ iGarrisonID ].ubSectorID );
3029 				if( ubDist < ubBestDist )
3030 				{
3031 					ubBestDist = ubDist;
3032 					iBestIndex = i;
3033 				}
3034 			}
3035 		}
3036 	}
3037 	ubDstSectorX = (UINT8)SECTORX( gGarrisonGroup[ iGarrisonID ].ubSectorID );
3038 	ubDstSectorY = (UINT8)SECTORY( gGarrisonGroup[ iGarrisonID ].ubSectorID );
3039 	if( iBestIndex != (size_t)-1 )
3040 	{ //Send the group to the garrison
3041 		pGroup = GetGroup( gPatrolGroup[ iBestIndex ].ubGroupID );
3042 		if( pGroup->ubGroupSize > ubSoldiersRequested && pGroup->ubGroupSize - ubSoldiersRequested >= gubMinEnemyGroupSize )
3043 		{ //Split the group, and send to location
3044 			GROUP *pNewGroup;
3045 			pNewGroup = CreateNewEnemyGroupDepartingFromSector( (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ), 0, 0, 0 );
3046 			//Transfer the troops from group to new group
3047 			if( pGroup->pEnemyGroup->ubNumTroops >= ubSoldiersRequested )
3048 			{ //All of them are troops, so do it in one shot.
3049 				pGroup->pEnemyGroup->ubNumTroops -= ubSoldiersRequested;
3050 				pGroup->ubGroupSize -= ubSoldiersRequested;
3051 				pNewGroup->pEnemyGroup->ubNumTroops = ubSoldiersRequested;
3052 				pNewGroup->ubGroupSize += ubSoldiersRequested;
3053 				ValidateLargeGroup( pGroup );
3054 				ValidateLargeGroup( pNewGroup );
3055 			}
3056 			else while( ubSoldiersRequested )
3057 			{ //There aren't enough troops, so transfer other types when we run out of troops, prioritizing admins, then elites.
3058 				if( pGroup->pEnemyGroup->ubNumTroops )
3059 				{
3060 					pGroup->pEnemyGroup->ubNumTroops--;
3061 					pGroup->ubGroupSize--;
3062 					pNewGroup->pEnemyGroup->ubNumTroops++;
3063 					pNewGroup->ubGroupSize++;
3064 					ubSoldiersRequested--;
3065 					ValidateLargeGroup( pGroup );
3066 					ValidateLargeGroup( pNewGroup );
3067 				}
3068 				else if( pGroup->pEnemyGroup->ubNumAdmins )
3069 				{
3070 					pGroup->pEnemyGroup->ubNumAdmins--;
3071 					pGroup->ubGroupSize--;
3072 					pNewGroup->pEnemyGroup->ubNumAdmins++;
3073 					pNewGroup->ubGroupSize++;
3074 					ubSoldiersRequested--;
3075 					ValidateLargeGroup( pGroup );
3076 					ValidateLargeGroup( pNewGroup );
3077 				}
3078 				else if( pGroup->pEnemyGroup->ubNumElites )
3079 				{
3080 					pGroup->pEnemyGroup->ubNumElites--;
3081 					pGroup->ubGroupSize--;
3082 					pNewGroup->pEnemyGroup->ubNumElites++;
3083 					pNewGroup->ubGroupSize++;
3084 					ubSoldiersRequested--;
3085 					ValidateLargeGroup( pGroup );
3086 					ValidateLargeGroup( pNewGroup );
3087 				}
3088 				else
3089 				{
3090 					SLOGA("Strategic AI group transfer error." );
3091 					return;
3092 				}
3093 			}
3094 			pNewGroup->ubOriginalSector = (UINT8)SECTOR( ubDstSectorX, ubDstSectorY );
3095 			gGarrisonGroup[ iGarrisonID ].ubPendingGroupID = pNewGroup->ubGroupID;
3096 			RecalculatePatrolWeight(gPatrolGroup[iBestIndex]);
3097 
3098 			MoveSAIGroupToSector( &pNewGroup, gGarrisonGroup[ iGarrisonID ].ubSectorID, EVASIVE, REINFORCEMENTS );
3099 		}
3100 		else
3101 		{ //Send the whole group and kill it's patrol assignment.
3102 			gPatrolGroup[ iBestIndex ].ubGroupID = 0;
3103 			gGarrisonGroup[ iGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
3104 			pGroup->ubOriginalSector = (UINT8)SECTOR( ubDstSectorX, ubDstSectorY );
3105 			RecalculatePatrolWeight(gPatrolGroup[iBestIndex]);
3106 			//The ONLY case where the group is told to move somewhere else when they could be BETWEEN sectors.  The movegroup functions
3107 			//don't work if this is the case.  Teleporting them to their previous sector is the best and easiest way to deal with this.
3108 			SetEnemyGroupSector(*pGroup, SECTOR(pGroup->ubSectorX, pGroup->ubSectorY));
3109 
3110 			MoveSAIGroupToSector( &pGroup, gGarrisonGroup[ iGarrisonID ].ubSectorID, EVASIVE, REINFORCEMENTS );
3111 			ValidateGroup( pGroup );
3112 		}
3113 	}
3114 	else
3115 	{ //There are no groups that have enough troops.  Send a new force from the palace instead.
3116 		pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_P3, 0, ubSoldiersRequested, 0 );
3117 		pGroup->ubMoveType = ONE_WAY;
3118 		pGroup->pEnemyGroup->ubIntention = REINFORCEMENTS;
3119 		gGarrisonGroup[ iGarrisonID ].ubPendingGroupID = pGroup->ubGroupID;
3120 		pGroup->ubOriginalSector = (UINT8)SECTOR( ubDstSectorX, ubDstSectorY );
3121 		giReinforcementPool -= (INT32)ubSoldiersRequested;
3122 
3123 		MoveSAIGroupToSector( &pGroup, gGarrisonGroup[ iGarrisonID ].ubSectorID, EVASIVE, REINFORCEMENTS );
3124 		ValidateGroup( pGroup );
3125 	}
3126 }
3127 
3128 
3129 static void MassFortifyTowns(void);
3130 
3131 
WakeUpQueen()3132 void WakeUpQueen()
3133 {
3134 	gfQueenAIAwake = TRUE;
3135 	if( !gfMassFortificationOrdered )
3136 	{
3137 		gfMassFortificationOrdered = TRUE;
3138 		MassFortifyTowns();
3139 	}
3140 }
3141 
3142 
3143 //Simply orders all garrisons to take troops from the patrol groups and send the closest troops from them.  Any garrison,
3144 //whom there request isn't fulfilled (due to lack of troops), will recieve their reinforcements from the queen (P3).
MassFortifyTowns(void)3145 static void MassFortifyTowns(void)
3146 {
3147 	size_t i;
3148 	SECTORINFO *pSector;
3149 	GROUP *pGroup;
3150 	UINT8 ubNumTroops, ubDesiredTroops;
3151 	for( i = 0; i < gGarrisonGroup.size(); i++ )
3152 	{
3153 		pSector = &SectorInfo[ gGarrisonGroup[ i ].ubSectorID ];
3154 		ubNumTroops = pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites;
3155 		ubDesiredTroops = (UINT8)gArmyComp[ gGarrisonGroup[ i ].ubComposition ].bDesiredPopulation;
3156 		if( ubNumTroops < ubDesiredTroops  )
3157 		{
3158 			if( !gGarrisonGroup[ i ].ubPendingGroupID &&
3159 					gGarrisonGroup[ i ].ubComposition != ROADBLOCK &&
3160 					EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) )
3161 			{
3162 				RequestHighPriorityGarrisonReinforcements( i, (UINT8)(ubDesiredTroops - ubNumTroops) );
3163 			}
3164 		}
3165 	}
3166 	//Convert the garrison sitting in Omerta (if alive), and reassign them
3167 	pSector = &SectorInfo[ SEC_A9 ];
3168 	if( pSector->ubNumTroops )
3169 	{
3170 		pGroup = CreateNewEnemyGroupDepartingFromSector( SEC_A9, 0, pSector->ubNumTroops, 0 );
3171 		pSector->ubNumTroops = 0;
3172 		pGroup->pEnemyGroup->ubIntention = PATROL;
3173 		pGroup->ubMoveType = ONE_WAY;
3174 		ReassignAIGroup( &pGroup );
3175 		ValidateGroup( pGroup );
3176 		RecalculateSectorWeight( SEC_A9 );
3177 	}
3178 }
3179 
StrategicHandleMineThatRanOut(UINT8 ubSectorID)3180 void StrategicHandleMineThatRanOut( UINT8 ubSectorID )
3181 {
3182 	switch( ubSectorID )
3183 	{
3184 		case SEC_B2:
3185 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_A2 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3186 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_B2 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3187 			break;
3188 		case SEC_D13:
3189 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_B13 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3190 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_C13 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3191 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_D13 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3192 			break;
3193 		case SEC_H8:
3194 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_F8 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3195 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_F9 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3196 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_G8 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3197 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_G9 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3198 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_H8 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3199 			break;
3200 		case SEC_I14:
3201 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_H13 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3202 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_H14 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3203 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_I13 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3204 			gArmyComp[ gGarrisonGroup[ SectorInfo[ SEC_I14 ].ubGarrisonID ].ubComposition ].bPriority /= 4;
3205 			break;
3206 	}
3207 }
3208 
3209 
GarrisonCanProvideMinimumReinforcements(INT32 iGarrisonID)3210 static BOOLEAN GarrisonCanProvideMinimumReinforcements(INT32 iGarrisonID)
3211 {
3212 	INT32 iAvailable;
3213 	INT32 iDesired;
3214 	SECTORINFO *pSector;
3215 	UINT8 ubSectorX, ubSectorY;
3216 
3217 	pSector = &SectorInfo[ gGarrisonGroup[ iGarrisonID ].ubSectorID ];
3218 
3219 	iAvailable = pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites;
3220 	iDesired = gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bDesiredPopulation;
3221 
3222 	if( iAvailable - iDesired >= gubMinEnemyGroupSize )
3223 	{
3224 		//Do a more expensive check first to determine if there is a player presence here (combat in progress)
3225 		//If so, do not provide reinforcements from here.
3226 		ubSectorX = (UINT8)SECTORX( gGarrisonGroup[ iGarrisonID ].ubSectorID );
3227 		ubSectorY = (UINT8)SECTORY( gGarrisonGroup[ iGarrisonID ].ubSectorID );
3228 		if( PlayerMercsInSector( ubSectorX, ubSectorY, 0 ) || CountAllMilitiaInSector( ubSectorX, ubSectorY ) )
3229 		{
3230 			return FALSE;
3231 		}
3232 		return TRUE;
3233 	}
3234 	return FALSE;
3235 }
3236 
3237 
GarrisonRequestingMinimumReinforcements(INT32 iGarrisonID)3238 static BOOLEAN GarrisonRequestingMinimumReinforcements(INT32 iGarrisonID)
3239 {
3240 	INT32 iAvailable;
3241 	INT32 iDesired;
3242 	SECTORINFO *pSector;
3243 
3244 	if( gGarrisonGroup[ iGarrisonID ].ubPendingGroupID )
3245 	{
3246 		return FALSE;
3247 	}
3248 
3249 	pSector = &SectorInfo[ gGarrisonGroup[ iGarrisonID ].ubSectorID ];
3250 	iAvailable = pSector->ubNumAdmins + pSector->ubNumTroops + pSector->ubNumElites;
3251 	iDesired = gArmyComp[ gGarrisonGroup[ iGarrisonID ].ubComposition ].bDesiredPopulation;
3252 
3253 	if( iDesired - iAvailable >= gubMinEnemyGroupSize )
3254 	{
3255 		return TRUE;
3256 	}
3257 	return FALSE;
3258 }
3259 
3260 
3261 static BOOLEAN PermittedToFillPatrolGroup(INT32 iPatrolID);
3262 
3263 
PatrolRequestingMinimumReinforcements(INT32 iPatrolID)3264 static BOOLEAN PatrolRequestingMinimumReinforcements(INT32 iPatrolID)
3265 {
3266 	GROUP *pGroup;
3267 
3268 	if( gPatrolGroup[ iPatrolID ].ubPendingGroupID )
3269 	{
3270 		return FALSE;
3271 	}
3272 	if( !PermittedToFillPatrolGroup( iPatrolID ) )
3273 	{ //if the group was defeated, it won't be considered for reinforcements again for several days
3274 		return FALSE;
3275 	}
3276 	pGroup = GetGroup( gPatrolGroup[ iPatrolID ].ubGroupID );
3277 	if( pGroup )
3278 	{
3279 		if( gPatrolGroup[ iPatrolID ].bSize - pGroup->ubGroupSize >= gubMinEnemyGroupSize )
3280 		{
3281 			return TRUE;
3282 		}
3283 	}
3284 	return FALSE;
3285 }
3286 
3287 
EliminateSurplusTroopsForGarrison(GROUP * pGroup,SECTORINFO * pSector)3288 static void EliminateSurplusTroopsForGarrison(GROUP* pGroup, SECTORINFO* pSector)
3289 {
3290 	INT32 iTotal;
3291 	iTotal = pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumAdmins +
3292 			pSector->ubNumTroops + pSector->ubNumElites + pSector->ubNumAdmins;
3293 	if( iTotal <= MAX_STRATEGIC_TEAM_SIZE )
3294 	{
3295 		return;
3296 	}
3297 	iTotal -= MAX_STRATEGIC_TEAM_SIZE;
3298 	while( iTotal )
3299 	{
3300 		if( pGroup->pEnemyGroup->ubNumAdmins )
3301 		{
3302 			if( pGroup->pEnemyGroup->ubNumAdmins < iTotal )
3303 			{
3304 				iTotal -= pGroup->pEnemyGroup->ubNumAdmins;
3305 				pGroup->pEnemyGroup->ubNumAdmins = 0;
3306 			}
3307 			else
3308 			{
3309 				pGroup->pEnemyGroup->ubNumAdmins -= (UINT8)iTotal;
3310 				iTotal = 0;
3311 			}
3312 		}
3313 		else if( pSector->ubNumAdmins )
3314 		{
3315 			if( pSector->ubNumAdmins < iTotal )
3316 			{
3317 				iTotal -= pSector->ubNumAdmins;
3318 				pSector->ubNumAdmins = 0;
3319 			}
3320 			else
3321 			{
3322 				pSector->ubNumAdmins -= (UINT8)iTotal;
3323 				iTotal = 0;
3324 			}
3325 		}
3326 		else if( pGroup->pEnemyGroup->ubNumTroops )
3327 		{
3328 			if( pGroup->pEnemyGroup->ubNumTroops < iTotal )
3329 			{
3330 				iTotal -= pGroup->pEnemyGroup->ubNumTroops;
3331 				pGroup->pEnemyGroup->ubNumTroops = 0;
3332 			}
3333 			else
3334 			{
3335 				pGroup->pEnemyGroup->ubNumTroops -= (UINT8)iTotal;
3336 				iTotal = 0;
3337 			}
3338 		}
3339 		else if( pSector->ubNumTroops )
3340 		{
3341 			if( pSector->ubNumTroops < iTotal )
3342 			{
3343 				iTotal -= pSector->ubNumTroops;
3344 				pSector->ubNumTroops = 0;
3345 			}
3346 			else
3347 			{
3348 				pSector->ubNumTroops -= (UINT8)iTotal;
3349 				iTotal = 0;
3350 			}
3351 		}
3352 		else if( pGroup->pEnemyGroup->ubNumElites )
3353 		{
3354 			if( pGroup->pEnemyGroup->ubNumElites < iTotal )
3355 			{
3356 				iTotal -= pGroup->pEnemyGroup->ubNumElites;
3357 				pGroup->pEnemyGroup->ubNumElites = 0;
3358 			}
3359 			else
3360 			{
3361 				pGroup->pEnemyGroup->ubNumElites -= (UINT8)iTotal;
3362 				iTotal = 0;
3363 			}
3364 		}
3365 		else if( pSector->ubNumElites )
3366 		{
3367 			if( pSector->ubNumElites < iTotal )
3368 			{
3369 				iTotal -= pSector->ubNumElites;
3370 				pSector->ubNumElites = 0;
3371 			}
3372 			else
3373 			{
3374 				pSector->ubNumElites -= (UINT8)iTotal;
3375 				iTotal = 0;
3376 			}
3377 		}
3378 	}
3379 }
3380 
3381 
3382 /* Once Queen is awake, she'll gradually begin replacing admins with regular
3383  * troops. This is mainly to keep player from fighting many more admins once
3384  * they are no longer any challenge for him. Eventually all admins will vanish
3385  * off map. */
UpgradeAdminsToTroops()3386 static void UpgradeAdminsToTroops()
3387 {
3388 	/* On normal, AI evaluates approximately every 10 hrs. There are about
3389 	 * 130 administrators seeded on the map. Some of these will be killed by the
3390 	 * player. */
3391 
3392 	INT32 const min_priority = 100 - 10 * HighestPlayerProgressPercentage();
3393 
3394 	// Check all garrisons for administrators.
3395 	UINT                  const cur = GetWorldSector();
3396 	//GARRISON_GROUP const* const end = gGarrisonGroup + gGarrisonGroup.size();
3397 	for (auto i = gGarrisonGroup.begin(); i != gGarrisonGroup.end(); ++i)
3398 	{
3399 		GARRISON_GROUP const& g = *i;
3400 
3401 		// Skip sector if it's currently loaded, we'll never upgrade guys in those.
3402 		if (cur == g.ubSectorID) continue;
3403 
3404 		SECTORINFO& sector = SectorInfo[g.ubSectorID];
3405 		if (sector.ubNumAdmins == 0) continue; // No admins in garrison.
3406 
3407 		INT8 const priority = gArmyComp[g.ubComposition].bPriority;
3408 
3409 		/* Highest priority sectors are upgraded first. Each 1% of progress lowers
3410 		 * the priority threshold required to start triggering upgrades by 10%. */
3411 		if (priority <= min_priority) continue;
3412 
3413 		for (UINT8 n_to_check = sector.ubNumAdmins; n_to_check != 0; --n_to_check)
3414 		{
3415 			/* Chance to upgrade at each check is random and is dependent on the
3416 			 * garrison's priority. */
3417 			if (!Chance(priority)) continue;
3418 			--sector.ubNumAdmins;
3419 			++sector.ubNumTroops;
3420 		}
3421 	}
3422 
3423 	// Check all moving enemy groups for administrators.
3424 	FOR_EACH_ENEMY_GROUP(i)
3425 	{
3426 		GROUP const& g = *i;
3427 		if (g.ubGroupSize == 0) continue;
3428 		if (g.fVehicle)         continue;
3429 
3430 		// Skip sector if it's currently loaded, we'll never upgrade guys in those.
3431 		if (g.ubSectorX == gWorldSectorX && g.ubSectorY == gWorldSectorY) continue;
3432 
3433 		Assert(g.pEnemyGroup);
3434 		ENEMYGROUP& eg = *g.pEnemyGroup;
3435 		if (eg.ubNumAdmins == 0) continue; // No admins in group.
3436 
3437 		INT8 priority;
3438 		if (eg.ubIntention == PATROL)
3439 		{ // Use that patrol's priority.
3440 			size_t const patrol_id = FindPatrolGroupIndexForGroupID(g.ubGroupID);
3441 			Assert(patrol_id != (size_t)-1);
3442 			priority = gPatrolGroup[patrol_id].bPriority;
3443 		}
3444 		else
3445 		{ // Use a default priority.
3446 			priority = 50;
3447 		}
3448 
3449 		/* Highest priority groups are upgraded first. Each 1% of progress lowers
3450 		 * the priority threshold required to start triggering upgrades by 10%. */
3451 		if (priority <= min_priority) continue;
3452 
3453 		for (UINT8 n_to_check = eg.ubNumAdmins; n_to_check != 0; --n_to_check)
3454 		{
3455 			/* Chance to upgrade at each check is random and is dependent on the
3456 			 * group's priority. */
3457 			if (!Chance(priority)) continue;
3458 			--eg.ubNumAdmins;
3459 			++eg.ubNumTroops;
3460 		}
3461 	}
3462 }
3463 
3464 
FindPatrolGroupIndexForGroupID(UINT8 ubGroupID)3465 size_t FindPatrolGroupIndexForGroupID( UINT8 ubGroupID )
3466 {
3467 	for( size_t sPatrolIndex = 0; sPatrolIndex < gPatrolGroup.size(); sPatrolIndex++ )
3468 	{
3469 		if ( gPatrolGroup[ sPatrolIndex ].ubGroupID == ubGroupID )
3470 		{
3471 			// found it
3472 			return( sPatrolIndex );
3473 		}
3474 	}
3475 
3476 	// not there!
3477 	return( -1 );
3478 }
3479 
3480 
FindPatrolGroupIndexForGroupIDPending(UINT8 ubGroupID)3481 size_t FindPatrolGroupIndexForGroupIDPending( UINT8 ubGroupID )
3482 {
3483 	for( size_t sPatrolIndex = 0; sPatrolIndex < gPatrolGroup.size(); sPatrolIndex++ )
3484 	{
3485 		if ( gPatrolGroup[ sPatrolIndex ].ubPendingGroupID == ubGroupID )
3486 		{
3487 			// found it
3488 			return( sPatrolIndex );
3489 		}
3490 	}
3491 
3492 	// not there!
3493 	return( -1 );
3494 }
3495 
3496 
FindGarrisonIndexForGroupIDPending(UINT8 ubGroupID)3497 size_t FindGarrisonIndexForGroupIDPending( UINT8 ubGroupID )
3498 {
3499 	for( size_t sGarrisonIndex = 0; sGarrisonIndex < gGarrisonGroup.size(); sGarrisonIndex++ )
3500 	{
3501 		if ( gGarrisonGroup[ sGarrisonIndex ].ubPendingGroupID == ubGroupID )
3502 		{
3503 			// found it
3504 			return( sGarrisonIndex );
3505 		}
3506 	}
3507 
3508 	// not there!
3509 	return( -1 );
3510 }
3511 
3512 
TransferGroupToPool(GROUP ** pGroup)3513 static void TransferGroupToPool(GROUP** pGroup)
3514 {
3515 	giReinforcementPool += (*pGroup)->ubGroupSize;
3516 	RemoveGroup(**pGroup);
3517 	*pGroup = NULL;
3518 }
3519 
3520 
3521 //NOTE:  Make sure you call SetEnemyGroupSector() first if the group is between sectors!!  See example in ReassignAIGroup()...
SendGroupToPool(GROUP ** pGroup)3522 static void SendGroupToPool(GROUP** pGroup)
3523 {
3524 	if( (*pGroup)->ubSectorX == 3 && (*pGroup)->ubSectorY == 16 )
3525 	{
3526 		TransferGroupToPool( pGroup );
3527 	}
3528 	else
3529 	{
3530 		(*pGroup)->ubSectorIDOfLastReassignment = (UINT8)SECTOR( (*pGroup)->ubSectorX, (*pGroup)->ubSectorY );
3531 		MoveSAIGroupToSector( pGroup, SEC_P3, EVASIVE, REINFORCEMENTS );
3532 	}
3533 }
3534 
3535 
ReassignAIGroup(GROUP ** pGroup)3536 static void ReassignAIGroup(GROUP** pGroup)
3537 {
3538 	UINT32 i, iRandom;
3539 	UINT32 iWeight;
3540 	UINT16 usDefencePoints;
3541 	size_t iReloopLastIndex = -1;
3542 	UINT8 ubSectorID;
3543 
3544 	ubSectorID = (UINT8)SECTOR( (*pGroup)->ubSectorX, (*pGroup)->ubSectorY );
3545 
3546 	(*pGroup)->ubSectorIDOfLastReassignment = ubSectorID;
3547 
3548 	RemoveGroupFromStrategicAILists(**pGroup);
3549 
3550 	//First thing to do, is teleport the group to be AT the sector he is currently moving from.  Otherwise, the
3551 	//strategic pathing can break if the group is between sectors upon reassignment.
3552 	SetEnemyGroupSector(**pGroup, ubSectorID);
3553 
3554 	if( giRequestPoints <= 0  )
3555 	{ //we have no request for reinforcements, so send the group to Meduna for reassignment in the pool.
3556 		SendGroupToPool( pGroup );
3557 		return;
3558 	}
3559 
3560 	//now randomly choose who gets the reinforcements.
3561 	// giRequestPoints is the combined sum of all the individual weights of all garrisons and patrols requesting reinforcements
3562 	iRandom = Random( giRequestPoints );
3563 
3564 	//go through garrisons first and begin considering where the random value dictates.  If that garrison doesn't require
3565 	//reinforcements, it'll continue on considering all subsequent garrisons till the end of the array.  If it fails at that
3566 	//point, it'll restart the loop at zero, and consider all garrisons to the index that was first considered by the random value.
3567 	for( i = 0; i < gGarrisonGroup.size(); i++ )
3568 	{
3569 		RecalculateGarrisonWeight( i );
3570 		iWeight = gGarrisonGroup[ i ].bWeight;
3571 		if( iWeight > 0 )
3572 		{	//if group is requesting reinforcements.
3573 			if( iRandom < iWeight )
3574 			{
3575 				if( !gGarrisonGroup[ i ].ubPendingGroupID &&
3576 						EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) &&
3577 						GarrisonRequestingMinimumReinforcements( i ) )
3578 				{ //This is the group that gets the reinforcements!
3579 					if( ReinforcementsApproved( i, &usDefencePoints ) )
3580 					{
3581 						SendReinforcementsForGarrison( i, usDefencePoints, pGroup );
3582 						return;
3583 					}
3584 				}
3585 				if( iReloopLastIndex == (size_t)-1 )
3586 				{ //go to the next garrison and clear the iRandom value so it attempts to use all subsequent groups.
3587 					iReloopLastIndex = i - 1;
3588 					iRandom = 0;
3589 				}
3590 			}
3591 			//Decrease the iRandom value until it hits 0.  When that happens, all garrisons will get considered until
3592 			//we either have a match or process all of the garrisons.
3593 			iRandom -= iWeight;
3594 		}
3595 	}
3596 	if( iReloopLastIndex >= 0 )
3597 	{ //Process the loop again to the point where the original random slot started considering, and consider
3598 		//all of the garrisons.  If this fails, all patrol groups will be considered next.
3599 		for( i = 0; i <= iReloopLastIndex; i++ )
3600 		{
3601 			RecalculateGarrisonWeight( i );
3602 			iWeight = gGarrisonGroup[ i ].bWeight;
3603 			if( iWeight > 0 )
3604 			{	//if group is requesting reinforcements.
3605 				if( !gGarrisonGroup[ i ].ubPendingGroupID &&
3606 						EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) &&
3607 						GarrisonRequestingMinimumReinforcements( i ) )
3608 				{ //This is the group that gets the reinforcements!
3609 					if( ReinforcementsApproved( i, &usDefencePoints ) )
3610 					{
3611 						SendReinforcementsForGarrison( i, usDefencePoints, pGroup );
3612 						return;
3613 					}
3614 				}
3615 			}
3616 		}
3617 	}
3618 	if( iReloopLastIndex == (size_t)-1 )
3619 	{
3620 		//go through the patrol groups
3621 		for( i = 0; i < gPatrolGroup.size(); i++ )
3622 		{
3623 			RecalculatePatrolWeight(gPatrolGroup[i]);
3624 			iWeight = gPatrolGroup[ i ].bWeight;
3625 			if( iWeight > 0 )
3626 			{
3627 				if( iRandom < iWeight )
3628 				{
3629 					if( !gPatrolGroup[ i ].ubPendingGroupID && PatrolRequestingMinimumReinforcements( i ) )
3630 					{ //This is the group that gets the reinforcements!
3631 						SendReinforcementsForPatrol( i, pGroup );
3632 						return;
3633 					}
3634 				}
3635 				if( iReloopLastIndex == (size_t)-1 )
3636 				{
3637 					iReloopLastIndex = i - 1;
3638 					iRandom = 0;
3639 				}
3640 				iRandom -= iWeight;
3641 			}
3642 		}
3643 	}
3644 	else
3645 	{
3646 		iReloopLastIndex = gPatrolGroup.size() - 1;
3647 	}
3648 
3649 	for( i = 0; i <= iReloopLastIndex; i++ )
3650 	{
3651 		RecalculatePatrolWeight(gPatrolGroup[i]);
3652 		iWeight = gPatrolGroup[ i ].bWeight;
3653 		if( iWeight > 0 )
3654 		{
3655 			if( !gPatrolGroup[ i ].ubPendingGroupID && PatrolRequestingMinimumReinforcements( i ) )
3656 			{ //This is the group that gets the reinforcements!
3657 				SendReinforcementsForPatrol( i, pGroup );
3658 				return;
3659 			}
3660 		}
3661 	}
3662 	TransferGroupToPool( pGroup );
3663 }
3664 
3665 
3666 /* When an enemy AI group is eliminated by the player, apply a grace period in
3667  * which the group isn't allowed to be filled for several days. */
TagSAIGroupWithGracePeriod(GROUP const & g)3668 static void TagSAIGroupWithGracePeriod(GROUP const& g)
3669 {
3670 	size_t const patrol_id = FindPatrolGroupIndexForGroupID(g.ubGroupID);
3671 	if (patrol_id == (size_t)-1) return;
3672 
3673 	UINT32 grace_period = saipolicy(patrol_grace_period_in_days);
3674 	gPatrolGroup[patrol_id].bFillPermittedAfterDayMod100 = (GetWorldDay() + grace_period) % 100;
3675 }
3676 
3677 
PermittedToFillPatrolGroup(INT32 iPatrolID)3678 static BOOLEAN PermittedToFillPatrolGroup(INT32 iPatrolID)
3679 {
3680 	INT32 iDay;
3681 	INT32 iDayAllowed;
3682 	iDay = GetWorldDay();
3683 	iDayAllowed = gPatrolGroup[ iPatrolID ].bFillPermittedAfterDayMod100 + (iDay / 100) * 100;
3684 	return iDay >= iDayAllowed;
3685 }
3686 
RepollSAIGroup(GROUP * pGroup)3687 void RepollSAIGroup( GROUP *pGroup )
3688 {
3689 	UINT32 i;
3690 	Assert( !pGroup->fPlayer );
3691 	if( GroupAtFinalDestination( pGroup ) )
3692 	{
3693 		EvaluateGroupSituation( pGroup );
3694 		return;
3695 	}
3696 	for( i = 0; i < gPatrolGroup.size(); i++ )
3697 	{
3698 		if( gPatrolGroup[ i ].ubGroupID == pGroup->ubGroupID )
3699 		{
3700 			RecalculatePatrolWeight(gPatrolGroup[i]); //in case there are any dead enemies
3701 			CalculateNextMoveIntention( pGroup );
3702 			return;
3703 		}
3704 	}
3705 	for( i = 0; i < gGarrisonGroup.size(); i++ )
3706 	{
3707 		//KM : August 6, 1999 Patch fix
3708 		//     Ack, wasn't checking for the matching group to garrison
3709 		if( gGarrisonGroup[ i ].ubPendingGroupID == pGroup->ubGroupID )
3710 		//end
3711 		{
3712 			RecalculateGarrisonWeight( i ); //in case there are any dead enemies
3713 			CalculateNextMoveIntention( pGroup );
3714 			return;
3715 		}
3716 	}
3717 }
3718 
3719 
CalcNumTroopsBasedOnComposition(UINT8 * pubNumTroops,UINT8 * pubNumElites,UINT8 ubTotal,INT32 iCompositionID)3720 static void CalcNumTroopsBasedOnComposition(UINT8* pubNumTroops, UINT8* pubNumElites, UINT8 ubTotal, INT32 iCompositionID)
3721 {
3722 	*pubNumTroops = gArmyComp[ iCompositionID ].bTroopPercentage * ubTotal / 100;
3723 	*pubNumElites = gArmyComp[ iCompositionID ].bElitePercentage * ubTotal / 100;
3724 
3725 	//Due to low roundoff, it is highly possible that we will be short one soldier.
3726 	while( *pubNumTroops + *pubNumElites < ubTotal )
3727 	{
3728 		if( Chance( gArmyComp[ iCompositionID ].bTroopPercentage ) )
3729 		{
3730 			(*pubNumTroops)++;
3731 		}
3732 		else
3733 		{
3734 			(*pubNumElites)++;
3735 		}
3736 	}
3737 	Assert( *pubNumTroops + *pubNumElites == ubTotal );
3738 }
3739 
3740 
ConvertGroupTroopsToComposition(GROUP * pGroup,INT32 iCompositionID)3741 static void ConvertGroupTroopsToComposition(GROUP* pGroup, INT32 iCompositionID)
3742 {
3743 	Assert( pGroup );
3744 	Assert( !pGroup->fPlayer );
3745 	CalcNumTroopsBasedOnComposition( &pGroup->pEnemyGroup->ubNumTroops, &pGroup->pEnemyGroup->ubNumElites, pGroup->ubGroupSize, iCompositionID );
3746 	pGroup->pEnemyGroup->ubNumAdmins = 0;
3747 	pGroup->ubGroupSize = pGroup->pEnemyGroup->ubNumTroops + pGroup->pEnemyGroup->ubNumElites;
3748 	ValidateLargeGroup( pGroup );
3749 }
3750 
3751 
RemoveSoldiersFromGarrisonBasedOnComposition(INT32 iGarrisonID,UINT8 ubSize)3752 static void RemoveSoldiersFromGarrisonBasedOnComposition(INT32 iGarrisonID, UINT8 ubSize)
3753 {
3754 	SECTORINFO *pSector;
3755 	INT32 iCompositionID;
3756 	UINT8 ubNumTroops, ubNumElites;
3757 
3758 	iCompositionID = gGarrisonGroup[ iGarrisonID ].ubComposition;
3759 
3760 	CalcNumTroopsBasedOnComposition( &ubNumTroops, &ubNumElites, ubSize, iCompositionID );
3761 	pSector = &SectorInfo[ gGarrisonGroup[ iGarrisonID ].ubSectorID ];
3762 	//if there are administrators in this sector, remove them first.
3763 
3764 	while( ubSize && pSector->ubNumAdmins )
3765 	{
3766 		pSector->ubNumAdmins--;
3767 		ubSize--;
3768 		if( ubNumTroops )
3769 		{
3770 			ubNumTroops--;
3771 		}
3772 		else
3773 		{
3774 			ubNumElites--;
3775 		}
3776 	}
3777 	//No administrators are left.
3778 
3779 	//Eliminate the troops
3780 	while( ubNumTroops )
3781 	{
3782 		if( pSector->ubNumTroops )
3783 		{
3784 			pSector->ubNumTroops--;
3785 		}
3786 		else if( pSector->ubNumElites )
3787 		{
3788 			pSector->ubNumElites--;
3789 		}
3790 		else
3791 		{
3792 			SLOGA("RemoveSoldiersFromGarrisonBasedOnComposition: trying to eliminate more troops than present");
3793 		}
3794 		ubNumTroops--;
3795 	}
3796 
3797 	//Eliminate the elites
3798 	while( ubNumElites )
3799 	{
3800 		if( pSector->ubNumElites )
3801 		{
3802 			pSector->ubNumElites--;
3803 		}
3804 		else if( pSector->ubNumTroops )
3805 		{
3806 			pSector->ubNumTroops--;
3807 		}
3808 		else
3809 		{
3810 			SLOGA("RemoveSoldiersFromGarrisonBasedOnComposition: trying to eliminate more elites than present");
3811 		}
3812 		ubNumElites--;
3813 	}
3814 
3815 	RecalculateGarrisonWeight( iGarrisonID );
3816 }
3817 
3818 
MoveSAIGroupToSector(GROUP ** const pGroup,UINT8 const sector,SAIMOVECODE const move_code,UINT8 const intention)3819 static void MoveSAIGroupToSector(GROUP** const pGroup, UINT8 const sector, SAIMOVECODE const move_code, UINT8 const intention)
3820 {
3821 	UINT8 const dst_x = SECTORX(sector);
3822 	UINT8 const dst_y = SECTORY(sector);
3823 	GROUP&      g     = **pGroup;
3824 
3825 	if (g.fBetweenSectors) SetEnemyGroupSector(g, SECTOR(g.ubSectorX, g.ubSectorY));
3826 
3827 	g.pEnemyGroup->ubIntention = intention;
3828 	g.ubMoveType               = ONE_WAY;
3829 
3830 	/* Make sure that the group isn't moving into a garrison sector. These sectors
3831 	 * should be using ASSAULT intentions! */
3832 	Assert(intention != PURSUIT || SectorInfo[sector].ubGarrisonID == NO_GARRISON);
3833 
3834 	/* If the destination sector is the current location. Instead of causing code
3835 	 * logic problems, simply process them as if they just arrived. */
3836 	if (g.ubSectorX == dst_x && g.ubSectorY == dst_y && EvaluateGroupSituation(&g))
3837 	{ // The group was deleted.
3838 		*pGroup = 0;
3839 		return;
3840 	}
3841 
3842 	UINT8 const x = g.ubSectorX;
3843 	UINT8 const y = g.ubSectorY;
3844 	switch (move_code)
3845 	{
3846 		case STAGE:   MoveGroupFromSectorToSectorButAvoidPlayerInfluencedSectorsAndStopOneSectorBeforeEnd(g, x, y, dst_x, dst_y); break;
3847 		case EVASIVE: MoveGroupFromSectorToSectorButAvoidPlayerInfluencedSectors(                         g, x, y, dst_x, dst_y); break;
3848 		case DIRECT:  MoveGroupFromSectorToSector(                                                        g, x, y, dst_x, dst_y); break;
3849 	}
3850 	/* Make sure that the group is moving. If this fails, then the pathing may
3851 	 * have failed for some reason. */
3852 	ValidateGroup(&g);
3853 }
3854 
3855 
3856 //If there are any enemy groups that will be moving through this sector due, they will have to repath which
3857 //will cause them to avoid the sector.  Returns the number of redirected groups.
RedirectEnemyGroupsMovingThroughSector(UINT8 ubSectorX,UINT8 ubSectorY)3858 static UINT8 RedirectEnemyGroupsMovingThroughSector(UINT8 ubSectorX, UINT8 ubSectorY)
3859 {
3860 	UINT8 ubNumGroupsRedirected = 0;
3861 	WAYPOINT *pWaypoint;
3862 	UINT8 ubDestSectorID;
3863 	FOR_EACH_ENEMY_GROUP(pGroup)
3864 	{
3865 		if (pGroup->ubMoveType == ONE_WAY)
3866 		{ //check the waypoint list
3867 			if( GroupWillMoveThroughSector( pGroup, ubSectorX, ubSectorY ) )
3868 			{
3869 				//extract the group's destination.
3870 				pWaypoint = GetFinalWaypoint( pGroup );
3871 				Assert( pWaypoint );
3872 				ubDestSectorID = (UINT8)SECTOR( pWaypoint->x, pWaypoint->y );
3873 				SetEnemyGroupSector(*pGroup, SECTOR(pGroup->ubSectorX, pGroup->ubSectorY));
3874 				MoveSAIGroupToSector( &pGroup, ubDestSectorID, EVASIVE, pGroup->pEnemyGroup->ubIntention );
3875 				ubNumGroupsRedirected++;
3876 			}
3877 		}
3878 	}
3879 	if( ubNumGroupsRedirected )
3880 	{
3881 		SLOGD("Test message for new feature:  %d enemy groups were redirected away from moving through sector %c%d.  Please don't report unless this number is greater than 5.",
3882 			ubNumGroupsRedirected, ubSectorY + 'A' - 1, ubSectorX );
3883 	}
3884 	return ubNumGroupsRedirected;
3885 }
3886 
3887 
3888 //when the SAI compositions change, it is necessary to call this function upon version load,
3889 //to reflect the changes of the compositions to the sector that haven't been visited yet.
ReinitializeUnvisitedGarrisons(void)3890 static void ReinitializeUnvisitedGarrisons(void)
3891 {
3892 	SECTORINFO *pSector;
3893 	ARMY_COMPOSITION *pArmyComp;
3894 	GROUP *pGroup;
3895 	UINT32 i, cnt, iEliteChance, iAdminChance;
3896 
3897 	//Recreate the compositions
3898 	gArmyComp = GCM->getArmyCompositions();
3899 	EvolveQueenPriorityPhase( TRUE );
3900 
3901 	//Go through each unvisited sector and recreate the garrison forces based on
3902 	//the desired population.
3903 	for( i = 0; i < gGarrisonGroup.size(); i++ )
3904 	{
3905 		if( gGarrisonGroup[ i ].ubComposition >= LEVEL1_DEFENCE && gGarrisonGroup[ i ].ubComposition <= LEVEL3_DEFENCE )
3906 		{ //These 3 compositions make up the perimeter around Meduna.  The existance of these are based on the
3907 			//difficulty level, and we don't want to reset these anyways, due to the fact that many of the reinforcements
3908 			//come from these sectors, and it could potentially add upwards of 150 extra troops which would seriously
3909 			//unbalance the difficulty.
3910 			continue;
3911 		}
3912 		pSector = &SectorInfo[ gGarrisonGroup[ i ].ubSectorID ];
3913 		pArmyComp = &gArmyComp[ gGarrisonGroup[ i ].ubComposition ];
3914 		if( !(pSector->uiFlags & SF_ALREADY_VISITED) )
3915 		{
3916 			pSector->ubNumAdmins = 0;
3917 			pSector->ubNumTroops = 0;
3918 			pSector->ubNumElites = 0;
3919 			if( gfQueenAIAwake )
3920 			{
3921 				cnt = pArmyComp->bDesiredPopulation;
3922 			}
3923 			else
3924 			{
3925 				cnt = pArmyComp->bStartPopulation;
3926 			}
3927 
3928 			if( gGarrisonGroup[ i ].ubPendingGroupID )
3929 			{ //if the garrison has reinforcements on route, then subtract the number of
3930 				//reinforcements from the value we reset the size of the garrison.  This is to
3931 				//prevent overfilling the group.
3932 				pGroup = GetGroup( gGarrisonGroup[ i ].ubPendingGroupID );
3933 				if( pGroup )
3934 				{
3935 					cnt -= pGroup->ubGroupSize;
3936 					cnt = MAX( cnt, 0 );
3937 				}
3938 			}
3939 
3940 			iEliteChance = pArmyComp->bElitePercentage;
3941 			iAdminChance = pArmyComp->bAdminPercentage;
3942 			if( iAdminChance && !gfQueenAIAwake && cnt )
3943 			{
3944 				pSector->ubNumAdmins = iAdminChance * cnt / 100;
3945 			}
3946 			else while( cnt-- )
3947 			{ //for each person, randomly determine the types of each soldier.
3948 				if( Chance( iEliteChance ) )
3949 				{
3950 					pSector->ubNumElites++;
3951 				}
3952 				else
3953 				{
3954 					pSector->ubNumTroops++;
3955 				}
3956 			}
3957 		}
3958 	}
3959 }
3960 
3961 
3962 #ifdef WITH_UNITTESTS
3963 #include "gtest/gtest.h"
3964 
TEST(StrategicAI,asserts)3965 TEST(StrategicAI, asserts)
3966 {
3967 	EXPECT_EQ(sizeof(ARMY_COMPOSITION), 20u);
3968 	EXPECT_EQ(sizeof(PATROL_GROUP), 20u);
3969 	EXPECT_EQ(sizeof(GARRISON_GROUP), 14u);
3970 }
3971 
3972 #endif
3973