1 #include "Local.h"
2 #include "MapScreen.h"
3 #include "MessageBoxScreen.h"
4 #include "Town_Militia.h"
5 #include "Militia_Control.h"
6 #include "Campaign_Types.h"
7 #include "StrategicMap.h"
8 #include "Overhead.h"
9 #include "Strategic_Town_Loyalty.h"
10 #include "Random.h"
11 #include "Text.h"
12 #include "Map_Screen_Interface.h"
13 #include "Interface.h"
14 #include "LaptopSave.h"
15 #include "Finances.h"
16 #include "Game_Clock.h"
17 #include "Assignments.h"
18 #include "Squads.h"
19 #include "Soldier_Create.h"
20 #include "Dialogue_Control.h"
21 #include "Queen_Command.h"
22 #include "PreBattle_Interface.h"
23 #include "Map_Screen_Interface_Border.h"
24 #include "Map_Screen_Interface_Map.h"
25 #include "JAScreens.h"
26 #include "Debug.h"
27 #include "ScreenIDs.h"
28 #include "UILayout.h"
29 #include "ContentManager.h"
30 #include "GameInstance.h"
31 #include "TownModel.h"
32 
33 #include <string_theory/format>
34 #include <string_theory/string>
35 
36 #include <algorithm>
37 #include <iterator>
38 
39 #define SIZE_OF_MILITIA_COMPLETED_TRAINING_LIST 50
40 
41 // temporary local global variables
42 UINT8 gubTownSectorServerTownId = BLANK_SECTOR;
43 INT16 gsTownSectorServerSkipX = -1;
44 INT16 gsTownSectorServerSkipY = -1;
45 UINT8 gubTownSectorServerIndex = 0;
46 BOOLEAN gfYesNoPromptIsForContinue = FALSE; // this flag remembers whether we're starting new training, or continuing
47 INT32 giTotalCostOfTraining = 0;
48 
49 
50 //the completed list of sector soldiers for training militia
51 static SOLDIERTYPE* g_list_of_merc_in_sectors_completed_militia_training[SIZE_OF_MILITIA_COMPLETED_TRAINING_LIST];
52 SOLDIERTYPE *pMilitiaTrainerSoldier = NULL;
53 
54 // note that these sector values are STRATEGIC INDEXES, not 0-255!
55 static INT16 gsUnpaidStrategicSector[MAX_CHARACTER_COUNT];
56 
57 
58 static void HandleCompletionOfTownTrainingByGroupWithTrainer(SOLDIERTYPE* pTrainer);
59 static void InitFriendlyTownSectorServer(UINT8 ubTownId, INT16 sSkipSectorX, INT16 sSkipSectorY);
60 static bool ServeNextFriendlySectorInTown(INT16* neighbour_x, INT16* neighbour_y);
61 static void StrategicAddMilitiaToSector(INT16 sMapX, INT16 sMapY, UINT8 ubRank, UINT8 ubHowMany);
62 static void StrategicPromoteMilitiaInSector(INT16 x, INT16 y, UINT8 current_rank, UINT8 n);
63 
64 
TownMilitiaTrainingCompleted(SOLDIERTYPE * pTrainer,INT16 sMapX,INT16 sMapY)65 void TownMilitiaTrainingCompleted( SOLDIERTYPE *pTrainer, INT16 sMapX, INT16 sMapY )
66 {
67 	UINT8 ubMilitiaTrained = 0;
68 	BOOLEAN fFoundOne;
69 	INT16 sNeighbourX, sNeighbourY;
70 	UINT8 ubTownId;
71 
72 
73 	// get town index
74 	ubTownId = StrategicMap[ sMapX + sMapY * MAP_WORLD_X ].bNameId;
75 
76 	if( ubTownId == BLANK_SECTOR )
77 	{
78 		Assert( IsThisSectorASAMSector( sMapX, sMapY, 0 ) );
79 	}
80 
81 
82 	// force tactical to update militia status
83 	gfStrategicMilitiaChangesMade = TRUE;
84 
85 	// ok, so what do we do with all this training?  Well, in order of decreasing priority:
86 	// 1) If there's room in training sector, create new GREEN militia guys there
87 	// 2) If not enough room there, create new GREEN militia guys in friendly sectors of the same town
88 	// 3) If not enough room anywhere in town, promote a number of GREENs in this sector into regulars
89 	// 4) If not enough GREENS there to promote, promote GREENs in other sectors.
90 	// 5) If all friendly sectors of this town are completely filled with REGULAR militia, then training effect is wasted
91 
92 	while (ubMilitiaTrained < MILITIA_TRAINING_SQUAD_SIZE)
93 	{
94 		// is there room for another militia in the training sector itself?
95 		if (CountAllMilitiaInSector(sMapX, sMapY) < MAX_ALLOWABLE_MILITIA_PER_SECTOR)
96 		{
97 			// great! Create a new GREEN militia guy in the training sector
98 			StrategicAddMilitiaToSector(sMapX, sMapY, GREEN_MILITIA, 1);
99 		}
100 		else
101 		{
102 			fFoundOne = FALSE;
103 
104 			if( ubTownId != BLANK_SECTOR )
105 			{
106 				InitFriendlyTownSectorServer(ubTownId, sMapX, sMapY);
107 
108 				// check other eligible sectors in this town for room for another militia
109 				while( ServeNextFriendlySectorInTown( &sNeighbourX, &sNeighbourY ) )
110 				{
111 					// is there room for another militia in this neighbouring sector ?
112 					if (CountAllMilitiaInSector(sNeighbourX, sNeighbourY) < MAX_ALLOWABLE_MILITIA_PER_SECTOR)
113 					{
114 						// great! Create a new GREEN militia guy in the neighbouring sector
115 						StrategicAddMilitiaToSector(sNeighbourX, sNeighbourY, GREEN_MILITIA, 1);
116 
117 						fFoundOne = TRUE;
118 						break;
119 					}
120 				}
121 			}
122 
123 			// if we still haven't been able to train anyone
124 			if (!fFoundOne)
125 			{
126 				// alrighty, then.  We'll have to *promote* guys instead.
127 
128 				// are there any GREEN militia men in the training sector itself?
129 				if (MilitiaInSectorOfRank(sMapX, sMapY, GREEN_MILITIA) > 0)
130 				{
131 					// great! Promote a GREEN militia guy in the training sector to a REGULAR
132 					StrategicPromoteMilitiaInSector(sMapX, sMapY, GREEN_MILITIA, 1);
133 				}
134 				else
135 				{
136 					if( ubTownId != BLANK_SECTOR )
137 					{
138 						// dammit! Last chance - try to find other eligible sectors in the same town with a Green guy to be promoted
139 						InitFriendlyTownSectorServer(ubTownId, sMapX, sMapY);
140 
141 						// check other eligible sectors in this town for room for another militia
142 						while( ServeNextFriendlySectorInTown( &sNeighbourX, &sNeighbourY ) )
143 						{
144 							// are there any GREEN militia men in the neighbouring sector ?
145 							if (MilitiaInSectorOfRank(sNeighbourX, sNeighbourY, GREEN_MILITIA) > 0)
146 							{
147 								// great! Promote a GREEN militia guy in the neighbouring sector to a REGULAR
148 								StrategicPromoteMilitiaInSector(sNeighbourX, sNeighbourY, GREEN_MILITIA, 1);
149 
150 								fFoundOne = TRUE;
151 								break;
152 							}
153 						}
154 					}
155 
156 					// if we still haven't been able to train anyone
157 					if (!fFoundOne)
158 					{
159 						// Well, that's it.  All eligible sectors of this town are full of REGULARs or ELITEs.
160 						// The training goes to waste in this situation.
161 						break; // the main while loop
162 					}
163 				}
164 			}
165 		}
166 
167 		// next, please!
168 		ubMilitiaTrained++;
169 	}
170 
171 
172 	// if anyone actually got trained
173 	if (ubMilitiaTrained > 0)
174 	{
175 		// update the screen display
176 		fMapPanelDirty = TRUE;
177 
178 		if( ubTownId != BLANK_SECTOR )
179 		{
180 			// loyalty in this town increases a bit because we obviously care about them...
181 			IncrementTownLoyalty( ubTownId, LOYALTY_BONUS_FOR_TOWN_TRAINING );
182 		}
183 
184 	}
185 
186 
187 	// the trainer announces to player that he's finished his assignment.  Make his sector flash!
188 	AssignmentDone( pTrainer, TRUE, FALSE );
189 
190 	// handle completion of town by training group
191 	HandleCompletionOfTownTrainingByGroupWithTrainer( pTrainer );
192 
193 }
194 
195 
SoldierClassToMilitiaRank(UINT8 const soldier_class)196 INT8 SoldierClassToMilitiaRank(UINT8 const soldier_class)
197 {
198 	switch (soldier_class)
199 	{
200 		case SOLDIER_CLASS_GREEN_MILITIA: return GREEN_MILITIA;
201 		case SOLDIER_CLASS_REG_MILITIA:   return REGULAR_MILITIA;
202 		case SOLDIER_CLASS_ELITE_MILITIA: return ELITE_MILITIA;
203 		default:                          return -1;
204 	}
205 }
206 
207 
208 // add militias of a certain rank
StrategicAddMilitiaToSector(INT16 sMapX,INT16 sMapY,UINT8 ubRank,UINT8 ubHowMany)209 static void StrategicAddMilitiaToSector(INT16 sMapX, INT16 sMapY, UINT8 ubRank, UINT8 ubHowMany)
210 {
211 	SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
212 
213 	pSectorInfo->ubNumberOfCivsAtLevel[ ubRank ] += ubHowMany;
214 
215 	// update the screen display
216 	fMapPanelDirty = TRUE;
217 }
218 
219 
220 // Promote militias of a certain rank
StrategicPromoteMilitiaInSector(INT16 const x,INT16 const y,UINT8 const current_rank,UINT8 const n)221 static void StrategicPromoteMilitiaInSector(INT16 const x, INT16 const y, UINT8 const current_rank, UINT8 const n)
222 {
223 	SECTORINFO& si = SectorInfo[SECTOR(x, y)];
224 
225 	Assert(si.ubNumberOfCivsAtLevel[current_rank] >= n);
226 	//KM : July 21, 1999 patch fix
227 	if (si.ubNumberOfCivsAtLevel[current_rank] < n) return;
228 
229 	si.ubNumberOfCivsAtLevel[current_rank    ] -= n;
230 	si.ubNumberOfCivsAtLevel[current_rank + 1] += n;
231 
232 	fMapPanelDirty = TRUE;
233 }
234 
235 
StrategicRemoveMilitiaFromSector(INT16 sMapX,INT16 sMapY,UINT8 ubRank,UINT8 ubHowMany)236 void StrategicRemoveMilitiaFromSector(INT16 sMapX, INT16 sMapY, UINT8 ubRank, UINT8 ubHowMany)
237 {
238 	SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
239 
240 	// damn well better have that many around to remove!
241 	Assert(pSectorInfo->ubNumberOfCivsAtLevel[ ubRank ] >= ubHowMany);
242 
243 	//KM : July 21, 1999 patch fix
244 	if( pSectorInfo->ubNumberOfCivsAtLevel[ ubRank ] < ubHowMany )
245 	{
246 		return;
247 	}
248 
249 	pSectorInfo->ubNumberOfCivsAtLevel[ ubRank ] -= ubHowMany;
250 
251 	// update the screen display
252 	fMapPanelDirty = TRUE;
253 }
254 
255 
256 // kill pts are (2 * kills) + assists
CheckOneMilitiaForPromotion(INT16 const x,INT16 const y,UINT8 & current_rank,UINT8 kill_points)257 UINT8 CheckOneMilitiaForPromotion(INT16 const x, INT16 const y, UINT8 &current_rank, UINT8 kill_points)
258 {
259 	// since the awarding is potentially significantly delayed, make sure they
260 	// weren't all promoted already by regular training or killed;
261 	// if we can't find them, we try higher ranks and make sure the caller follows up
262 	SECTORINFO& si = SectorInfo[SECTOR(x, y)];
263 	while (si.ubNumberOfCivsAtLevel[current_rank] == 0) {
264 		if (current_rank == ELITE_MILITIA) return 0;
265 		current_rank++;
266 	}
267 
268 	UINT8 n_promotions = 0;
269 	switch (current_rank)
270 	{
271 		case GREEN_MILITIA:
272 			// 2 kill points minimum, 25% chance per kill point
273 			if (kill_points < 2)           break;
274 			if (!Chance(25 * kill_points)) break;
275 			StrategicPromoteMilitiaInSector(x, y, GREEN_MILITIA, 1);
276 			++n_promotions;
277 			// Attempt another level up
278 			kill_points -= 2;
279 			/* FALLTHROUGH */
280 		case REGULAR_MILITIA:
281 			// 5 kill points minimum, 10% chance per kill point
282 			if (kill_points < 5)           break;
283 			if (!Chance(10 * kill_points)) break;
284 			StrategicPromoteMilitiaInSector(x, y, REGULAR_MILITIA, 1);
285 			++n_promotions;
286 			break;
287 	}
288 	return n_promotions;
289 }
290 
291 
292 // call this if the player attacks his own militia
HandleMilitiaDefections(INT16 sMapX,INT16 sMapY)293 static void HandleMilitiaDefections(INT16 sMapX, INT16 sMapY)
294 {
295 	UINT8 ubRank;
296 	UINT8 ubMilitiaCnt;
297 	UINT8 ubCount;
298 	UINT32 uiChanceToDefect;
299 
300 	for( ubRank = 0; ubRank < MAX_MILITIA_LEVELS; ubRank++ )
301 	{
302 		ubMilitiaCnt = MilitiaInSectorOfRank(sMapX, sMapY, ubRank);
303 
304 		// check each guy at each rank to see if he defects
305 		for (ubCount = 0; ubCount < ubMilitiaCnt; ubCount++)
306 		{
307 			switch( ubRank )
308 			{
309 				case GREEN_MILITIA:
310 					uiChanceToDefect = 50;
311 					break;
312 				case REGULAR_MILITIA:
313 					uiChanceToDefect = 75;
314 					break;
315 				case ELITE_MILITIA:
316 					uiChanceToDefect = 90;
317 					break;
318 				default:
319 					SLOGA("HandleMilitiaDefections: invalid Rank");
320 					return;
321 			}
322 
323 			// roll the bones; should I stay or should I go now?  (for you music fans out there)
324 			if (Random(100) < uiChanceToDefect)
325 			{
326 				//B'bye!  (for you SNL fans out there)
327 				StrategicRemoveMilitiaFromSector(sMapX, sMapY, ubRank, 1);
328 			}
329 		}
330 	}
331 }
332 
333 
CountAllMilitiaInSector(INT16 sMapX,INT16 sMapY)334 UINT8 CountAllMilitiaInSector(INT16 sMapX, INT16 sMapY)
335 {
336 	UINT8 ubMilitiaTotal = 0;
337 	UINT8 ubRank;
338 
339 	// find out if there are any town militia in this SECTOR (don't care about other sectors in same town)
340 	for( ubRank = 0; ubRank < MAX_MILITIA_LEVELS; ubRank++ )
341 	{
342 		ubMilitiaTotal += MilitiaInSectorOfRank(sMapX, sMapY, ubRank);
343 	}
344 
345 	return(ubMilitiaTotal);
346 }
347 
348 
MilitiaInSectorOfRank(INT16 sMapX,INT16 sMapY,UINT8 ubRank)349 UINT8 MilitiaInSectorOfRank(INT16 sMapX, INT16 sMapY, UINT8 ubRank)
350 {
351 	return( SectorInfo[ SECTOR( sMapX, sMapY ) ].ubNumberOfCivsAtLevel[ ubRank ] );
352 }
353 
354 
SectorOursAndPeaceful(INT16 sMapX,INT16 sMapY,INT8 bMapZ)355 BOOLEAN SectorOursAndPeaceful( INT16 sMapX, INT16 sMapY, INT8 bMapZ )
356 {
357 	// if this sector is currently loaded
358 	if ( ( sMapX == gWorldSectorX ) && ( sMapY == gWorldSectorY ) && ( bMapZ == gbWorldSectorZ ) )
359 	{
360 		// and either there are enemies prowling this sector, or combat is in progress
361 		if ( gTacticalStatus.fEnemyInSector || ( gTacticalStatus.uiFlags & INCOMBAT ) )
362 		{
363 			return FALSE;
364 		}
365 	}
366 
367 	// if sector is controlled by enemies, it's not ours (duh!)
368 	if (!bMapZ && StrategicMap[sMapX + sMapY * MAP_WORLD_X].fEnemyControlled)
369 	{
370 		return FALSE;
371 	}
372 
373 	if( NumHostilesInSector( sMapX, sMapY, bMapZ ) )
374 	{
375 		return FALSE;
376 	}
377 
378 	// safe & secure, s'far as we can tell
379 	return(TRUE);
380 }
381 
382 
InitFriendlyTownSectorServer(UINT8 ubTownId,INT16 sSkipSectorX,INT16 sSkipSectorY)383 static void InitFriendlyTownSectorServer(UINT8 ubTownId, INT16 sSkipSectorX, INT16 sSkipSectorY)
384 {
385 	// reset globals
386 	gubTownSectorServerTownId = ubTownId;
387 	gsTownSectorServerSkipX = sSkipSectorX;
388 	gsTownSectorServerSkipY = sSkipSectorY;
389 
390 	gubTownSectorServerIndex = 0;
391 }
392 
393 
394 /* Fetch the X,Y of the next town sector on the town list for the town specified
395  * at initialization. It will skip an entry that matches the skip X/Y value, if
396  * one was specified at initialization.
397  * MUST CALL InitFriendlyTownSectorServer() before using! */
ServeNextFriendlySectorInTown(INT16 * const neighbour_x,INT16 * const neighbour_y)398 static bool ServeNextFriendlySectorInTown(INT16* const neighbour_x, INT16* const neighbour_y)
399 {
400 	while (g_town_sectors[gubTownSectorServerIndex].town != BLANK_SECTOR)
401 	{
402 		INT32 const sector = g_town_sectors[gubTownSectorServerIndex++].sector;
403 
404 		// if this sector is in the town we're looking for
405 		if (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(sector)].bNameId != gubTownSectorServerTownId) continue;
406 
407 		INT16 const x = SECTORX(sector);
408 		INT16 const y = SECTORY(sector);
409 
410 		// Make sure we're not supposed to skip it
411 		if (x == gsTownSectorServerSkipX && y == gsTownSectorServerSkipY) continue;
412 
413 		// check if it's "friendly" - not enemy controlled, no enemies in it, no combat in progress
414 		if (!SectorOursAndPeaceful(x, y, 0)) continue;
415 
416 		*neighbour_x = x;
417 		*neighbour_y = y;
418 		return true;
419 	}
420 	return false;
421 }
422 
423 
424 static void CantTrainMilitiaOkBoxCallback(MessageBoxReturnValue);
425 static INT32 GetNumberOfUnpaidTrainableSectors();
426 static void PayMilitiaTrainingYesNoBoxCallback(MessageBoxReturnValue);
427 
428 
HandleInterfaceMessageForCostOfTrainingMilitia(SOLDIERTYPE * pSoldier)429 void HandleInterfaceMessageForCostOfTrainingMilitia( SOLDIERTYPE *pSoldier )
430 {
431 	ST::string sString;
432 	INT32 iNumberOfSectors = 0;
433 
434 	pMilitiaTrainerSoldier = pSoldier;
435 
436 	// grab total number of sectors
437 	iNumberOfSectors = GetNumberOfUnpaidTrainableSectors( );
438 	Assert( iNumberOfSectors > 0 );
439 
440 	// get total cost
441 	giTotalCostOfTraining = MILITIA_TRAINING_COST * iNumberOfSectors;
442 	Assert( giTotalCostOfTraining > 0 );
443 
444 	gfYesNoPromptIsForContinue = FALSE;
445 
446 	if( LaptopSaveInfo.iCurrentBalance < giTotalCostOfTraining )
447 	{
448 		sString = st_format_printf(pMilitiaConfirmStrings[7], giTotalCostOfTraining);
449 		DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback );
450 		return;
451 	}
452 
453 	// ok to start training, ask player
454 
455 
456 	if( iNumberOfSectors > 1 )
457 	{
458 		sString = st_format_printf(pMilitiaConfirmStrings[6], iNumberOfSectors, giTotalCostOfTraining, pMilitiaConfirmStrings[1]);
459 	}
460 	else
461 	{
462 		sString = ST::format("{}{}. {}", pMilitiaConfirmStrings[ 0 ], giTotalCostOfTraining, pMilitiaConfirmStrings[ 1 ]);
463 	}
464 
465 	// if we are in mapscreen, make a pop up
466 	if( guiCurrentScreen == MAP_SCREEN )
467 	{
468 		DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback );
469 	}
470 	else
471 	{
472 		SGPBox const centering_rect = { 0, 0, SCREEN_WIDTH, INV_INTERFACE_START_Y };
473 		DoMessageBox(MSG_BOX_BASIC_STYLE, sString, GAME_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback, &centering_rect);
474 	}
475 }
476 
477 // continue training?
HandleInterfaceMessageForContinuingTrainingMilitia(SOLDIERTYPE * const pSoldier)478 static void HandleInterfaceMessageForContinuingTrainingMilitia(SOLDIERTYPE* const pSoldier)
479 {
480 	ST::string sString;
481 	INT16 sSectorX = 0, sSectorY = 0;
482 	ST::string sStringB;
483 
484 	sSectorX = pSoldier->sSectorX;
485 	sSectorY = pSoldier->sSectorY;
486 	UINT8 const sector = SECTOR(sSectorX, sSectorY);
487 
488 	Assert(!SectorInfo[sector].fMilitiaTrainingPaid);
489 
490 	pMilitiaTrainerSoldier = pSoldier;
491 
492 	gfYesNoPromptIsForContinue = TRUE;
493 
494 	// is there enough loyalty to continue training
495 	if (!DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia(pSoldier))
496 	{
497 		// loyalty too low to continue training
498 		sString = st_format_printf(pMilitiaConfirmStrings[8], GCM->getTownName(GetTownIdForSector(sector)), MIN_RATING_TO_TRAIN_TOWN);
499 		DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback );
500 		return;
501 	}
502 
503 	if (IsAreaFullOfMilitia(sSectorX, sSectorY, pSoldier->bSectorZ))
504 	{
505 		// we're full!!! go home!
506 		UINT8 const bTownId = GetTownIdForSector(sector);
507 		if ( bTownId == BLANK_SECTOR )
508 		{
509 			// wilderness SAM site
510 			sStringB = GetSectorIDString(sSectorX, sSectorY, 0, TRUE);
511 			sString = st_format_printf(pMilitiaConfirmStrings[9], sStringB);
512 		}
513 		else
514 		{
515 			// town
516 			sString = st_format_printf(pMilitiaConfirmStrings[9], GCM->getTownName(bTownId), MIN_RATING_TO_TRAIN_TOWN);
517 		}
518 		DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback );
519 		return;
520 	}
521 
522 	// continue training always handles just one sector at a time
523 	giTotalCostOfTraining = MILITIA_TRAINING_COST;
524 
525 	// can player afford to continue training?
526 	if( LaptopSaveInfo.iCurrentBalance < giTotalCostOfTraining )
527 	{
528 		// can't afford to continue training
529 		sString = st_format_printf(pMilitiaConfirmStrings[7], giTotalCostOfTraining);
530 		DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback );
531 		return;
532 	}
533 
534 	// ok to continue, ask player
535 
536 	sStringB = GetSectorIDString(sSectorX, sSectorY, 0, TRUE);
537 	sString = st_format_printf(pMilitiaConfirmStrings[ 3 ], sStringB, pMilitiaConfirmStrings[ 4 ], giTotalCostOfTraining);
538 
539 	// ask player whether he'd like to continue training
540 	//DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback );
541 	DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback );
542 }
543 
544 
545 static void ContinueTrainingInThisSector(void);
546 static void MilitiaTrainingRejected(void);
547 static void StartTrainingInAllUnpaidTrainableSectors();
548 
549 
550 // IMPORTANT: This same callback is used both for initial training and for continue training prompt
551 // use 'gfYesNoPromptIsForContinue' flag to tell them apart
PayMilitiaTrainingYesNoBoxCallback(MessageBoxReturnValue const bExitValue)552 static void PayMilitiaTrainingYesNoBoxCallback(MessageBoxReturnValue const bExitValue)
553 {
554 	Assert( giTotalCostOfTraining > 0 );
555 
556 	// yes
557 	if( bExitValue == MSG_BOX_RETURN_YES )
558 	{
559 		// does the player have enough
560 		if( LaptopSaveInfo.iCurrentBalance >= giTotalCostOfTraining )
561 		{
562 			if( gfYesNoPromptIsForContinue )
563 			{
564 				ContinueTrainingInThisSector();
565 			}
566 			else
567 			{
568 				StartTrainingInAllUnpaidTrainableSectors();
569 			}
570 
571 			// this completes the training prompt sequence
572 			pMilitiaTrainerSoldier = NULL;
573 		}
574 		else	// can't afford it
575 		{
576 			StopTimeCompression();
577 			DoMapMessageBox(MSG_BOX_BASIC_STYLE, pMilitiaConfirmStrings[2], MAP_SCREEN, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback);
578 		}
579 	}
580 	else if( bExitValue == MSG_BOX_RETURN_NO )
581 	{
582 		StopTimeCompression();
583 
584 		MilitiaTrainingRejected();
585 	}
586 }
587 
588 
CantTrainMilitiaOkBoxCallback(MessageBoxReturnValue const bExitValue)589 static void CantTrainMilitiaOkBoxCallback(MessageBoxReturnValue const bExitValue)
590 {
591 	MilitiaTrainingRejected();
592 }
593 
594 
595 // IMPORTANT: This same callback is used both for initial training and for continue training prompt
596 // use 'gfYesNoPromptIsForContinue' flag to tell them apart
MilitiaTrainingRejected(void)597 static void MilitiaTrainingRejected(void)
598 {
599 	if( gfYesNoPromptIsForContinue )
600 	{
601 		// take all mercs in that sector off militia training
602 		ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( pMilitiaTrainerSoldier->sSectorX, pMilitiaTrainerSoldier->sSectorY );
603 	}
604 	else
605 	{
606 		// take all mercs in unpaid sectors EVERYWHERE off militia training
607 		ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList();
608 	}
609 
610 	// this completes the training prompt sequence
611 	pMilitiaTrainerSoldier = NULL;
612 }
613 
614 
615 
HandleMilitiaStatusInCurrentMapBeforeLoadingNewMap(void)616 void HandleMilitiaStatusInCurrentMapBeforeLoadingNewMap( void )
617 {
618 	if ( gTacticalStatus.Team[ MILITIA_TEAM ].bSide != 0 )
619 	{
620 		// handle militia defections and reset team to friendly
621 		HandleMilitiaDefections( gWorldSectorX, gWorldSectorY );
622 		gTacticalStatus.Team[ MILITIA_TEAM ].bSide = 0;
623 	}
624 	else if( !gfAutomaticallyStartAutoResolve )
625 	{ //Don't promote militia if we are going directly to autoresolve to finish the current battle.
626 		HandleMilitiaPromotions();
627 	}
628 }
629 
630 
CanNearbyMilitiaScoutThisSector(INT16 const sec_x,INT16 const sec_y)631 bool CanNearbyMilitiaScoutThisSector(INT16 const sec_x, INT16 const sec_y)
632 {
633 	INT16 const scout_range = 1;
634 	INT16 const xstart      = sec_x >      scout_range ? sec_x - scout_range :  1;
635 	INT16 const ystart      = sec_y >      scout_range ? sec_y - scout_range :  1;
636 	INT16 const xend        = sec_x < 16 - scout_range ? sec_x + scout_range : 16;
637 	INT16 const yend        = sec_y < 16 - scout_range ? sec_y + scout_range : 16;
638 	for (INT16 y = ystart; y <= yend; ++y)
639 	{
640 		for (INT16 x = xstart; x <= xend; ++x)
641 		{
642 			UINT8 (&n_milita)[MAX_MILITIA_LEVELS] = SectorInfo[SECTOR(x, y)].ubNumberOfCivsAtLevel;
643 			if (n_milita[GREEN_MILITIA] + n_milita[REGULAR_MILITIA] + n_milita[ELITE_MILITIA] != 0) return true;
644 		}
645 	}
646 	return false;
647 }
648 
649 
IsAreaFullOfMilitia(const INT16 sector_x,const INT16 sector_y,const INT8 sector_z)650 BOOLEAN IsAreaFullOfMilitia(const INT16 sector_x, const INT16 sector_y, const INT8 sector_z)
651 {
652 	if (sector_z != 0) return TRUE;
653 
654 	UINT32 count_milita = 0;
655 	UINT32 max_milita   = 0;
656 
657 	INT8 const town_id = GetTownIdForSector(SECTOR(sector_x, sector_y));
658 	if (town_id != BLANK_SECTOR)
659 	{
660 		FOR_EACH_SECTOR_IN_TOWN(i, town_id)
661 		{
662 			INT16 const town_x = SECTORX(i->sector);
663 			INT16 const town_y = SECTORY(i->sector);
664 			if (SectorOursAndPeaceful(town_x, town_y, 0))
665 			{
666 				// don't count GREEN militia, they can be trained into regulars first
667 				count_milita += MilitiaInSectorOfRank(town_x, town_y, REGULAR_MILITIA);
668 				count_milita += MilitiaInSectorOfRank(town_x, town_y, ELITE_MILITIA);
669 				max_milita   += MAX_ALLOWABLE_MILITIA_PER_SECTOR;
670 			}
671 		}
672 	}
673 	else if (IsThisSectorASAMSector(sector_x, sector_y, 0))
674 	{
675 		// don't count GREEN militia, they can be trained into regulars first
676 		count_milita += MilitiaInSectorOfRank(sector_x, sector_y, REGULAR_MILITIA);
677 		count_milita += MilitiaInSectorOfRank(sector_x, sector_y, ELITE_MILITIA);
678 		max_milita   += MAX_ALLOWABLE_MILITIA_PER_SECTOR;
679 	}
680 
681 	return count_milita >= max_milita;
682 }
683 
684 
685 // handle completion of assignment by this soldier too and inform the player
HandleCompletionOfTownTrainingByGroupWithTrainer(SOLDIERTYPE * pTrainer)686 static void HandleCompletionOfTownTrainingByGroupWithTrainer(SOLDIERTYPE* pTrainer)
687 {
688 
689 	INT16 sSectorX = 0, sSectorY = 0;
690 	INT8 bSectorZ = 0;
691 
692 	// get the sector values
693 	sSectorX = pTrainer->sSectorX;
694 	sSectorY = pTrainer->sSectorY;
695 	bSectorZ = pTrainer->bSectorZ;
696 
697 	CFOR_EACH_IN_CHAR_LIST(c)
698 	{
699 		SOLDIERTYPE* const pSoldier = c->merc;
700 		if( ( pSoldier->bAssignment == TRAIN_TOWN ) && ( pSoldier->sSectorX == sSectorX )&&( pSoldier->sSectorY == sSectorY )&&( pSoldier->bSectorZ == bSectorZ ) )
701 		{
702 			// done assignment
703 			AssignmentDone( pSoldier, FALSE, FALSE );
704 		}
705 	}
706 }
707 
708 
AddSectorForSoldierToListOfSectorsThatCompletedMilitiaTraining(SOLDIERTYPE * pSoldier)709 void AddSectorForSoldierToListOfSectorsThatCompletedMilitiaTraining(SOLDIERTYPE* pSoldier)
710 {
711 	INT32 iCounter = 0;
712 	INT16 sSector = 0, sCurrentSector = 0;
713 
714 	// get the sector value
715 	sSector = pSoldier->sSectorX + pSoldier->sSectorY * MAP_WORLD_X;
716 
717 	while (g_list_of_merc_in_sectors_completed_militia_training[iCounter] != NULL)
718 	{
719 		// get the current soldier
720 		const SOLDIERTYPE* const pCurrentSoldier = g_list_of_merc_in_sectors_completed_militia_training[iCounter];
721 
722 		// get the current sector value
723 		sCurrentSector = pCurrentSoldier->sSectorX + pCurrentSoldier->sSectorY * MAP_WORLD_X;
724 
725 		// is the merc's sector already in the list?
726 		if( sCurrentSector == sSector )
727 		{
728 			// already here
729 			return;
730 		}
731 
732 		iCounter++;
733 
734 		Assert( iCounter < SIZE_OF_MILITIA_COMPLETED_TRAINING_LIST );
735 	}
736 
737 	// add merc to the list
738 	g_list_of_merc_in_sectors_completed_militia_training[iCounter] = pSoldier;
739 }
740 
741 
742 // clear out the list of training sectors...should be done once the list is posted
ClearSectorListForCompletedTrainingOfMilitia()743 void ClearSectorListForCompletedTrainingOfMilitia()
744 {
745 	FOR_EACH(SOLDIERTYPE*, i, g_list_of_merc_in_sectors_completed_militia_training) *i = 0;
746 }
747 
748 
HandleContinueOfTownTraining(void)749 void HandleContinueOfTownTraining( void )
750 {
751 	INT32 iCounter = 0;
752 	BOOLEAN fContinueEventPosted = FALSE;
753 
754 
755 	while (g_list_of_merc_in_sectors_completed_militia_training[iCounter] != NULL)
756 	{
757 		// get the soldier
758 		SOLDIERTYPE& s = *g_list_of_merc_in_sectors_completed_militia_training[iCounter];
759 		if (s.bActive)
760 		{
761 			fContinueEventPosted = TRUE;
762 
763 			class DialogueEventContinueTrainingMilitia : public CharacterDialogueEvent
764 			{
765 				public:
766 					DialogueEventContinueTrainingMilitia(SOLDIERTYPE& soldier) : CharacterDialogueEvent(soldier) {}
767 
768 					bool Execute()
769 					{
770 						HandleInterfaceMessageForContinuingTrainingMilitia(&soldier_);
771 						return false;
772 					}
773 			};
774 
775 			DialogueEvent::Add(new DialogueEventContinueTrainingMilitia(s));
776 		}
777 
778 		// next entry
779 		iCounter++;
780 	}
781 
782 	// now clear the list
783 	ClearSectorListForCompletedTrainingOfMilitia( );
784 
785 	if( fContinueEventPosted )
786 	{
787 		// ATE: If this event happens in tactical mode we will be switching at some time to mapscreen...
788 		if ( guiCurrentScreen == GAME_SCREEN )
789 		{
790 			gfEnteringMapScreen = TRUE;
791 		}
792 
793 		//If the militia view isn't currently active, then turn it on when prompting to continue training.
794 		if ( !fShowMilitia )
795 		{
796 			ToggleShowMilitiaMode();
797 		}
798 	}
799 }
800 
801 
AddIfTrainingUnpaidSector(SOLDIERTYPE const & s)802 static void AddIfTrainingUnpaidSector(SOLDIERTYPE const& s)
803 {
804 	if (!CanCharacterTrainMilitia(&s)) return;
805 	// Check if this sector is a town and needs equipment.
806 	if (SectorInfo[SECTOR(s.sSectorX, s.sSectorY)].fMilitiaTrainingPaid) return;
807 	INT16 const sector = CALCULATE_STRATEGIC_INDEX(s.sSectorX, s.sSectorY);
808 	for (INT16* i = gsUnpaidStrategicSector;; ++i)
809 	{
810 		if (*i == 0)
811 		{
812 			*i = sector;
813 			break;
814 		}
815 		if (*i == sector) break; // Do not add duplicate
816 	}
817 }
818 
819 
BuildListOfUnpaidTrainableSectors()820 static void BuildListOfUnpaidTrainableSectors()
821 {
822 	std::fill(std::begin(gsUnpaidStrategicSector), std::end(gsUnpaidStrategicSector), 0);
823 
824 	if (fInMapMode)
825 	{
826 		for (INT32 i = 0; i != MAX_CHARACTER_COUNT; ++i)
827 		{
828 			MapScreenCharacterSt const& c = gCharactersList[i];
829 			if (!c.merc)                                 continue;
830 			if (!c.selected && i != bSelectedAssignChar) continue;
831 			AddIfTrainingUnpaidSector(*c.merc);
832 		}
833 	}
834 	else
835 	{ // Handle for tactical
836 		AddIfTrainingUnpaidSector(*gUIFullTarget);
837 	}
838 }
839 
840 
GetNumberOfUnpaidTrainableSectors()841 static INT32 GetNumberOfUnpaidTrainableSectors()
842 {
843 	BuildListOfUnpaidTrainableSectors();
844 	INT32 n = 0;
845 	FOR_EACH(INT16 const, i, gsUnpaidStrategicSector)
846 	{
847 		if (*i != 0) ++n;
848 	}
849 	return n;
850 }
851 
852 
853 static void PayForTrainingInSector(UINT8 ubSector);
854 
855 
StartTrainingInAllUnpaidTrainableSectors()856 static void StartTrainingInAllUnpaidTrainableSectors()
857 {
858 	SetAssignmentForList(TRAIN_TOWN, 0);
859 	// Pay up in each sector
860 	BuildListOfUnpaidTrainableSectors();
861 	FOR_EACH(INT16 const, i, gsUnpaidStrategicSector)
862 	{
863 		if (*i == 0) continue;
864 		PayForTrainingInSector(STRATEGIC_INDEX_TO_SECTOR_INFO(*i));
865 	}
866 }
867 
868 
ContinueTrainingInThisSector(void)869 static void ContinueTrainingInThisSector(void)
870 {
871 	UINT8 ubSector;
872 
873 	Assert( pMilitiaTrainerSoldier );
874 
875 	// pay up in the sector where pMilitiaTrainerSoldier is
876 	ubSector = SECTOR( pMilitiaTrainerSoldier->sSectorX, pMilitiaTrainerSoldier->sSectorY );
877 	PayForTrainingInSector( ubSector );
878 }
879 
880 
881 static void ResetDoneFlagForAllMilitiaTrainersInSector(UINT8 ubSector);
882 
883 
PayForTrainingInSector(UINT8 ubSector)884 static void PayForTrainingInSector(UINT8 ubSector)
885 {
886 	Assert(!SectorInfo[ubSector].fMilitiaTrainingPaid);
887 
888 	// spend the money
889 	AddTransactionToPlayersBook( TRAIN_TOWN_MILITIA, ubSector, GetWorldTotalMin(), -( MILITIA_TRAINING_COST ) );
890 
891 	// mark this sector sectors as being paid up
892 	SectorInfo[ ubSector ].fMilitiaTrainingPaid = TRUE;
893 
894 	// reset done flags
895 	ResetDoneFlagForAllMilitiaTrainersInSector( ubSector );
896 }
897 
898 
ResetDoneFlagForAllMilitiaTrainersInSector(UINT8 ubSector)899 static void ResetDoneFlagForAllMilitiaTrainersInSector(UINT8 ubSector)
900 {
901 	FOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
902 	{
903 		if (pSoldier->bAssignment == TRAIN_TOWN &&
904 				SECTOR(pSoldier->sSectorX, pSoldier->sSectorY) == ubSector &&
905 				pSoldier->bSectorZ == 0)
906 		{
907 			pSoldier->fDoneAssignmentAndNothingToDoFlag = FALSE;
908 			pSoldier->usQuoteSaidExtFlags &= ~SOLDIER_QUOTE_SAID_DONE_ASSIGNMENT;
909 		}
910 	}
911 }
912 
913 
MilitiaTrainingAllowedInSector(INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)914 BOOLEAN MilitiaTrainingAllowedInSector( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ )
915 {
916 	BOOLEAN fSamSitePresent = FALSE;
917 
918 
919 	if( bSectorZ != 0 )
920 	{
921 		return( FALSE );
922 	}
923 
924 	fSamSitePresent = IsThisSectorASAMSector( sSectorX, sSectorY, bSectorZ );
925 
926 	if( fSamSitePresent )
927 	{
928 		// all SAM sites may have militia trained at them
929 		return(TRUE);
930 	}
931 
932 	UINT8 const bTownId = GetTownIdForSector(SECTOR(sSectorX, sSectorY));
933 	return( MilitiaTrainingAllowedInTown( bTownId ) );
934 }
935 
936 
937 
MilitiaTrainingAllowedInTown(INT8 bTownId)938 BOOLEAN MilitiaTrainingAllowedInTown( INT8 bTownId )
939 {
940 	auto town = GCM->getTown(bTownId);
941 	if (town == NULL) {
942 		return false;
943 	}
944 	return town->isMilitiaTrainingAllowed;
945 }
946 
947 
PromoteMilitia(ST::string & str,INT8 count,const ST::string & singular,const ST::string & plural)948 static void PromoteMilitia(ST::string& str, INT8 count, const ST::string& singular, const ST::string& plural)
949 {
950 	if (count > 0)
951 	{
952 		if (!str.empty()) str += " ";
953 		if (count == 1)
954 		{
955 			str += singular;
956 		}
957 		else
958 		{
959 			str += st_format_printf(plural, count);
960 		}
961 	}
962 }
963 
964 
BuildMilitiaPromotionsString()965 ST::string BuildMilitiaPromotionsString()
966 {
967 	ST::string str;
968 
969 	if (gbMilitiaPromotions == 0) return ST::null;
970 
971 	PromoteMilitia(str, gbGreenToElitePromotions, gzLateLocalizedString[STR_LATE_29], gzLateLocalizedString[STR_LATE_22]);
972 	PromoteMilitia(str, gbGreenToRegPromotions,  gzLateLocalizedString[STR_LATE_30], gzLateLocalizedString[STR_LATE_23]);
973 	PromoteMilitia(str, gbRegToElitePromotions,  gzLateLocalizedString[STR_LATE_31], gzLateLocalizedString[STR_LATE_24]);
974 
975 	// Clear the fields
976 	gbGreenToElitePromotions = 0;
977 	gbGreenToRegPromotions   = 0;
978 	gbRegToElitePromotions   = 0;
979 	gbMilitiaPromotions      = 0;
980 
981 	return str;
982 }
983