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 ¤t_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, ¢ering_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