1 #include "Directories.h"
2 #include "Font.h"
3 #include "Input.h"
4 #include "MessageBoxScreen.h"
5 #include "PreBattle_Interface.h"
6 #include "Button_System.h"
7 #include "MouseSystem.h"
8 #include "Map_Screen_Interface.h"
9 #include "JAScreens.h"
10 #include "GameScreen.h"
11 #include "StrategicMap.h"
12 #include "Game_Clock.h"
13 #include "Music_Control.h"
14 #include "ContentMusic.h"
15 #include "SysUtil.h"
16 #include "Font_Control.h"
17 #include "Timer.h"
18 #include "Queen_Command.h"
19 #include "Strategic_Movement.h"
20 #include "Strategic_Pathing.h"
21 #include "Text.h"
22 #include "PopUpBox.h"
23 #include "Player_Command.h"
24 #include "Cursors.h"
25 #include "Auto_Resolve.h"
26 #include "Sound_Control.h"
27 #include "English.h"
28 #include "Map_Screen_Interface_Bottom.h"
29 #include "Overhead.h"
30 #include "Tactical_Placement_GUI.h"
31 #include "Timer_Control.h"
32 #include "Town_Militia.h"
33 #include "Campaign.h"
34 #include "GameSettings.h"
35 #include "Random.h"
36 #include "Creature_Spreading.h"
37 #include "Multi_Language_Graphic_Utils.h"
38 #include "Map_Screen_Helicopter.h"
39 #include "MapScreen.h"
40 #include "Quests.h"
41 #include "Map_Screen_Interface_Border.h"
42 #include "Cheats.h"
43 #include "Strategic_Status.h"
44 #include "Strategic_Town_Loyalty.h"
45 #include "Squads.h"
46 #include "Assignments.h"
47 #include "Soldier_Macros.h"
48 #include "History.h"
49 #include "VObject.h"
50 #include "Vehicles.h"
51 #include "Video.h"
52 #include "Debug.h"
53 #include "ScreenIDs.h"
54 #include "Render_Dirty.h"
55 #include "VSurface.h"
56 #include "UILayout.h"
57 
58 #include <string_theory/format>
59 #include <string_theory/string>
60 
61 
62 extern BOOLEAN gfDelayAutoResolveStart;
63 
64 
65 BOOLEAN gfTacticalTraversal = FALSE;
66 GROUP *gpTacticalTraversalGroup = NULL;
67 SOLDIERTYPE *gpTacticalTraversalChosenSoldier = NULL;
68 
69 
70 BOOLEAN gfAutomaticallyStartAutoResolve = FALSE;
71 BOOLEAN gfAutoAmbush = FALSE;
72 BOOLEAN gfHighPotentialForAmbush = FALSE;
73 BOOLEAN gfGotoSectorTransition = FALSE;
74 BOOLEAN gfEnterAutoResolveMode = FALSE;
75 BOOLEAN gfEnteringMapScreenToEnterPreBattleInterface = FALSE;
76 BOOLEAN gfIgnoreAllInput = TRUE;
77 
78 enum //GraphicIDs for the panel
79 {
80 	MAINPANEL,
81 	TITLE_BAR_PIECE,
82 	TOP_COLUMN,
83 	BOTTOM_COLUMN,
84 	UNINVOLVED_HEADER
85 };
86 
87 //The start of the black space
88 #define TOP_Y							(STD_SCREEN_Y + 113)
89 //The end of the black space
90 #define BOTTOM_Y					(STD_SCREEN_Y + 349)
91 //The internal height of the uninvolved panel
92 #define INTERNAL_HEIGHT		27
93 //The actual height of the uninvolved panel
94 #define ACTUAL_HEIGHT			34
95 //The height of each row
96 #define ROW_HEIGHT				10
97 
98 bool gfDisplayPotentialRetreatPaths = false;
99 
100 GROUP *gpBattleGroup = NULL;
101 
102 
103 static MOUSE_REGION PBInterfaceBlanket;
104 BOOLEAN gfPreBattleInterfaceActive = FALSE;
105 static GUIButtonRef iPBButton[3];
106 static BUTTON_PICS* iPBButtonImage[3];
107 static SGPVObject*  uiInterfaceImages;
108 BOOLEAN gfRenderPBInterface;
109 static BOOLEAN      gfPBButtonsHidden;
110 BOOLEAN fDisableMapInterfaceDueToBattle = FALSE;
111 
112 static BOOLEAN gfBlinkHeader;
113 
114 static UINT32 guiNumInvolved;
115 static UINT32 guiNumUninvolved;
116 
117 //SAVE START
118 
119 //Using the ESC key in the PBI will get rid of the PBI and go back to mapscreen, but
120 //only if the PBI isn't persistant (!gfPersistantPBI).
121 BOOLEAN gfPersistantPBI = FALSE;
122 
123 //Contains general information about the type of encounter the player is faced with.  This
124 //determines whether or not you can autoresolve the battle or even retreat.  This code
125 //dictates the header that is used at the top of the PBI.
126 UINT8 gubEnemyEncounterCode = NO_ENCOUNTER_CODE;
127 
128 //The autoresolve during tactical battle option needs more detailed information than the
129 //gubEnemyEncounterCode can provide.  The explicit version contains possibly unique codes
130 //for reasons not normally used in the PBI.  For example, if we were fighting the enemy
131 //in a normal situation, then shot at a civilian, the civilians associated with the victim
132 //would turn hostile, which would disable the ability to autoresolve the battle.
133 BOOLEAN gubExplicitEnemyEncounterCode = NO_ENCOUNTER_CODE;
134 
135 //Location of the current battle (determines where the animated icon is blitted) and if the
136 //icon is to be blitted.
137 BOOLEAN gfBlitBattleSectorLocator = FALSE;
138 
139 UINT8 gubPBSectorX = 0;
140 UINT8 gubPBSectorY = 0;
141 UINT8 gubPBSectorZ = 0;
142 
143 BOOLEAN gfCantRetreatInPBI = FALSE;
144 //SAVE END
145 
146 BOOLEAN gfUsePersistantPBI;
147 
MakeButton(UINT idx,INT16 x,const ST::string & text,GUI_CALLBACK click)148 static void MakeButton(UINT idx, INT16 x, const ST::string& text, GUI_CALLBACK click)
149 {
150 	GUIButtonRef const btn = QuickCreateButton(iPBButtonImage[idx], x, STD_SCREEN_Y + 54, MSYS_PRIORITY_HIGHEST - 2, click);
151 	iPBButton[idx] = btn;
152 
153 	btn->SpecifyGeneralTextAttributes(text, BLOCKFONT, FONT_BEIGE, 141);
154 	btn->SpecifyHilitedTextColors(FONT_WHITE, FONT_NEARBLACK);
155 	btn->SpecifyTextOffsets(8, 7, TRUE);
156 	btn->SpecifyTextWrappedWidth(51);
157 	btn->AllowDisabledFastHelp();
158 	btn->Hide();
159 }
160 
161 
162 static void AutoResolveBattleCallback(GUI_BUTTON* btn, INT32 reason);
163 static void CheckForRobotAndIfItsControlled(void);
164 static void DoTransitionFromMapscreenToPreBattleInterface(void);
165 static void GoToSectorCallback(GUI_BUTTON* btn, INT32 reason);
166 static void RetreatMercsCallback(GUI_BUTTON* btn, INT32 reason);
167 
168 
InitPreBattleInterface(GROUP * const battle_group,bool const persistent_pbi)169 void InitPreBattleInterface(GROUP* const battle_group, bool const persistent_pbi)
170 {
171 	// ARM: Feb01/98 - Cancel out of mapscreen movement plotting if PBI subscreen is coming up
172 	if (bSelectedDestChar != -1 || fPlotForHelicopter)
173 	{
174 		AbortMovementPlottingMode();
175 	}
176 
177 	if (gfPreBattleInterfaceActive) return;
178 
179 	UINT8 x;
180 	UINT8 y;
181 	UINT8 z;
182 	gfPersistantPBI = persistent_pbi;
183 	if (persistent_pbi)
184 	{
185 		gfBlitBattleSectorLocator = TRUE;
186 		gfBlinkHeader = FALSE;
187 
188 		//	InitializeTacticalStatusAtBattleStart();
189 		// CJC, Oct 5 98: this is all we should need from InitializeTacticalStatusAtBattleStart()
190 		if (gubEnemyEncounterCode != BLOODCAT_AMBUSH_CODE        &&
191 				gubEnemyEncounterCode != ENTERING_BLOODCAT_LAIR_CODE &&
192 				!CheckFact(FACT_FIRST_BATTLE_FOUGHT, 0))
193 		{
194 			SetFactTrue(FACT_FIRST_BATTLE_BEING_FOUGHT);
195 		}
196 
197 		// ATE: Added check for persistent_pbi if !battle_group
198 		// Searched code and saw that this condition only happens for creatures
199 		if (guiCurrentScreen == GAME_SCREEN && (battle_group || persistent_pbi))
200 		{
201 			gpBattleGroup                                = battle_group;
202 			gfEnteringMapScreen                          = TRUE;
203 			gfEnteringMapScreenToEnterPreBattleInterface = TRUE;
204 			gfUsePersistantPBI                           = TRUE;
205 			return;
206 		}
207 
208 		if (gfTacticalTraversal && (battle_group == gpTacticalTraversalGroup || gbWorldSectorZ > 0))
209 		{
210 			return;
211 		}
212 
213 		// Reset the help text for mouse regions
214 		gMapStatusBarsRegion.SetFastHelpText(ST::null);
215 
216 		gfDisplayPotentialRetreatPaths = false;
217 
218 		gpBattleGroup = battle_group;
219 
220 		// Calc sector values
221 		if (battle_group)
222 		{
223 			x = battle_group->ubSectorX;
224 			y = battle_group->ubSectorY;
225 			z = battle_group->ubSectorZ;
226 			fMapPanelDirty = TRUE;
227 		}
228 		else
229 		{
230 			x = SECTORX(gubSectorIDOfCreatureAttack);
231 			y = SECTORY(gubSectorIDOfCreatureAttack);
232 			z = 0;
233 		}
234 		gubPBSectorX = x;
235 		gubPBSectorY = y;
236 		gubPBSectorZ = z;
237 	}
238 	else
239 	{ // Calculate the non-persistent situation
240 		gfBlinkHeader = TRUE;
241 		x = gubPBSectorX;
242 		y = gubPBSectorY;
243 		z = gubPBSectorZ;
244 
245 		if (HostileCiviliansPresent())
246 		{ // There are hostile civilians, so no autoresolve allowed.
247 			gubExplicitEnemyEncounterCode = HOSTILE_CIVILIANS_CODE;
248 		}
249 		else if (HostileBloodcatsPresent())
250 		{ // There are bloodcats in the sector, so no autoresolve allowed
251 			gubExplicitEnemyEncounterCode = HOSTILE_BLOODCATS_CODE;
252 		}
253 		else if (gbWorldSectorZ != 0)
254 		{ // We are underground, so no autoresolve allowed
255 			SECTORINFO const& sector = SectorInfo[SECTOR(x, y)]; // XXX Why check surface info when underground?
256 			if (sector.ubCreaturesInBattle != 0)
257 			{
258 				gubExplicitEnemyEncounterCode = FIGHTING_CREATURES_CODE;
259 			}
260 			else if (sector.ubAdminsInBattle != 0 || sector.ubTroopsInBattle != 0 || sector.ubElitesInBattle != 0)
261 			{
262 				gubExplicitEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE;
263 			}
264 		}
265 		else if (gubEnemyEncounterCode == ENTERING_ENEMY_SECTOR_CODE ||
266 				gubEnemyEncounterCode == ENEMY_ENCOUNTER_CODE            ||
267 				gubEnemyEncounterCode == ENEMY_AMBUSH_CODE               ||
268 				gubEnemyEncounterCode == ENEMY_INVASION_CODE             ||
269 				gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE            ||
270 				gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE     ||
271 				gubEnemyEncounterCode == CREATURE_ATTACK_CODE)
272 		{ // Use same code
273 			gubExplicitEnemyEncounterCode = gubEnemyEncounterCode;
274 		}
275 		else
276 		{
277 			gfBlitBattleSectorLocator = FALSE;
278 			return;
279 		}
280 	}
281 
282 	fMapScreenBottomDirty = TRUE;
283 	ChangeSelectedMapSector(x, y, z);
284 	RenderMapScreenInterfaceBottom();
285 
286 	/* If we are currently in tactical, then set the flag to automatically bring
287 	 * up the mapscreen. */
288 	if (guiCurrentScreen == GAME_SCREEN) gfEnteringMapScreen = TRUE;
289 
290 	if (!fShowTeamFlag) ToggleShowTeamsMode();
291 
292 	/* Define the blanket region to cover all of the other regions used underneath
293 	 * the panel. */
294 	MSYS_DefineRegion(&PBInterfaceBlanket, STD_SCREEN_X + 0, STD_SCREEN_Y + 0, STD_SCREEN_X + 261, STD_SCREEN_Y + 359, MSYS_PRIORITY_HIGHEST - 5, 0, 0, 0);
295 
296 	// Create the panel
297 	char const* const panel_file = GetMLGFilename(MLG_PREBATTLEPANEL);
298 	uiInterfaceImages = AddVideoObjectFromFile(panel_file);
299 
300 	// Create the 3 buttons
301 	iPBButtonImage[0] = LoadButtonImage(INTERFACEDIR "/prebattlebutton.sti", 0, 1);
302 	iPBButtonImage[1] = UseLoadedButtonImage(iPBButtonImage[0], 0, 1);
303 	iPBButtonImage[2] = UseLoadedButtonImage(iPBButtonImage[0], 0, 1);
304 	MakeButton(0, STD_SCREEN_X +  27, gpStrategicString[STR_PB_AUTORESOLVE_BTN],  AutoResolveBattleCallback);
305 	MakeButton(1, STD_SCREEN_X +  98, gpStrategicString[STR_PB_GOTOSECTOR_BTN],   GoToSectorCallback);
306 	MakeButton(2, STD_SCREEN_X + 169, gpStrategicString[STR_PB_RETREATMERCS_BTN], RetreatMercsCallback);
307 
308 	gfPBButtonsHidden = TRUE;
309 
310 	/* ARM: This must now be set before any calls utilizing the
311 	 * GetCurrentBattleSectorXYZ() function */
312 	gfPreBattleInterfaceActive = TRUE;
313 
314 	CheckForRobotAndIfItsControlled();
315 
316 	WakeUpAllMercsInSectorUnderAttack();
317 
318 	// Count the number of players involved or not involved in this battle
319 	guiNumUninvolved = 0;
320 	guiNumInvolved   = 0;
321 	UINT8 group_id           = 0;
322 	INT8  best_exp_level     = 0;
323 	bool  use_plural_version = false;
324 	CFOR_EACH_IN_TEAM(i, OUR_TEAM)
325 	{
326 		SOLDIERTYPE const& s = *i;
327 		if (s.bLife == 0 || s.uiStatusFlags & SOLDIER_VEHICLE) continue;
328 
329 		if (PlayerMercInvolvedInThisCombat(s))
330 		{
331 			if (group_id == 0)
332 			{ /* Record the first groupID. If there is more than one group in this
333 				 * battle, we can detect it by comparing the first value with future
334 				 * values. If we do, then we set a flag which determines whether to use
335 				 * the singular help text or plural version for the retreat button. */
336 				group_id = s.ubGroupID;
337 				if (!gpBattleGroup) gpBattleGroup = GetGroup(group_id);
338 				if (best_exp_level > s.bExpLevel) best_exp_level = s.bExpLevel; // XXX Determines minimum, not maximum, i.e. stays at 0
339 				if (s.ubPrevSectorID == 255)
340 				{ //Not able to retreat (calculate it for group)
341 					GROUP* const g = GetGroup(group_id);
342 					Assert(g);
343 					CalculateGroupRetreatSector(g);
344 				}
345 			}
346 			else if (group_id != s.ubGroupID)
347 			{
348 				use_plural_version = true;
349 			}
350 			++guiNumInvolved;
351 		}
352 		else
353 		{
354 			++guiNumUninvolved;
355 		}
356 	}
357 
358 	if (gfPersistantPBI)
359 	{
360 		if (!battle_group)
361 		{ // Creatures are attacking
362 			gubEnemyEncounterCode = CREATURE_ATTACK_CODE;
363 		}
364 		else if (gpBattleGroup->fPlayer)
365 		{
366 			if (gubEnemyEncounterCode != BLOODCAT_AMBUSH_CODE &&
367 					gubEnemyEncounterCode != ENTERING_BLOODCAT_LAIR_CODE)
368 			{
369 				UINT8 const n_stationary_enemies = NumStationaryEnemiesInSector(x, y);
370 				if (n_stationary_enemies != 0)
371 				{
372 					gubEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE;
373 				}
374 				else
375 				{
376 					gubEnemyEncounterCode = ENEMY_ENCOUNTER_CODE;
377 
378 					//Don't consider ambushes until the player has reached 25% (normal) progress
379 					if (gfHighPotentialForAmbush)
380 					{
381 						if (Chance(90)) gubEnemyEncounterCode = ENEMY_AMBUSH_CODE;
382 					}
383 					else
384 					{
385 						UINT8 const n_mobile_enemies = NumMobileEnemiesInSector(x, y);
386 						UINT8 const n_mercs          = PlayerMercsInSector(     x, y, z);
387 						if (gfAutoAmbush && n_mobile_enemies > n_mercs)
388 						{
389 							gubEnemyEncounterCode = ENEMY_AMBUSH_CODE;
390 						}
391 						else if (WhatPlayerKnowsAboutEnemiesInSector(x, y) == KNOWS_NOTHING &&
392 								CurrentPlayerProgressPercentage() >= 30 - gGameOptions.ubDifficultyLevel * 5)
393 						{ /* If the enemy outnumbers the players, then there is a small chance
394 							 * of the enemies ambushing the group */
395 							if (n_mobile_enemies > n_mercs)
396 							{
397 								SECTORINFO const& sector = SectorInfo[SECTOR(x, y)];
398 								if (!(sector.uiFlags & SF_ALREADY_VISITED))
399 								{
400 									INT32 chance = (UINT8)(4 - best_exp_level + 2 * gGameOptions.ubDifficultyLevel + CurrentPlayerProgressPercentage() / 10);
401 									if (sector.uiFlags & SF_ENEMY_AMBUSH_LOCATION) chance += 20;
402 									if (gfCantRetreatInPBI)                        chance += 20;
403 									if ((INT32)PreRandom(100) < chance)
404 									{
405 										gubEnemyEncounterCode = ENEMY_AMBUSH_CODE;
406 									}
407 								}
408 							}
409 						}
410 					}
411 				}
412 			}
413 		}
414 		else
415 		{ // Are enemies invading a town, or just encountered the player.
416 			UINT8 const sector = SECTOR(x, y);
417 			if (GetTownIdForSector(sector))
418 			{
419 				gubEnemyEncounterCode = ENEMY_INVASION_CODE;
420 			}
421 			else
422 			{
423 				switch (sector)
424 				{
425 					case SEC_D2:
426 					case SEC_D15:
427 					case SEC_G8:
428 						// SAM sites not in towns will also be considered to be important
429 						gubEnemyEncounterCode = ENEMY_INVASION_CODE;
430 						break;
431 
432 					default:
433 						gubEnemyEncounterCode = ENEMY_ENCOUNTER_CODE;
434 						break;
435 				}
436 			}
437 		}
438 	}
439 
440 	gfHighPotentialForAmbush = FALSE;
441 
442 	if (gfAutomaticallyStartAutoResolve)
443 	{
444 		DisableButton(iPBButton[1]);
445 		DisableButton(iPBButton[2]);
446 	}
447 
448 	gfRenderPBInterface = TRUE;
449 	MSYS_SetCurrentCursor(CURSOR_NORMAL);
450 	StopTimeCompression();
451 
452 	HideAllBoxes();
453 	fShowAssignmentMenu = FALSE;
454 	fShowContractMenu   = FALSE;
455 	DisableTeamInfoPanels();
456 	if (giMapContractButton) giMapContractButton->Hide();
457 	if (giCharInfoButton[0]) giCharInfoButton[0]->Hide();
458 	if (giCharInfoButton[1]) giCharInfoButton[1]->Hide();
459 
460 	if (gubEnemyEncounterCode == ENEMY_ENCOUNTER_CODE)
461 	{ /* We know how many enemies are here, so until we leave the sector, we will
462 		 * continue to display the value. The flag will get cleared when time
463 		 * advances after the fEnemyInSector flag is clear. */
464 		SECTORINFO& sector = SectorInfo[SECTOR(x, y)];
465 
466 		/* Always use these 2 statements together. Without setting the boolean, the
467 		 * flag will never be cleaned up */
468 		sector.uiFlags |= SF_PLAYER_KNOWS_ENEMIES_ARE_HERE;
469 		gfResetAllPlayerKnowsEnemiesFlags = TRUE;
470 	}
471 
472 	/* Set up fast help for buttons depending on the state of the button, and
473 	 * disable buttons when necessary. */
474 	if (gfPersistantPBI)
475 	{
476 		ST::string autoresolve_help;
477 		switch (gubEnemyEncounterCode)
478 		{
479 			case ENTERING_ENEMY_SECTOR_CODE:
480 			case ENTERING_BLOODCAT_LAIR_CODE:
481 				// Don't allow autoresolve for player initiated invasion battle types
482 				DisableButton(iPBButton[0]);
483 				autoresolve_help = gpStrategicString[STR_PB_DISABLED_AUTORESOLVE_FASTHELP];
484 				break;
485 
486 			case ENEMY_AMBUSH_CODE:
487 			case BLOODCAT_AMBUSH_CODE:
488 				// Don't allow autoresolve for ambushes
489 				DisableButton(iPBButton[0]);
490 				autoresolve_help = gzNonPersistantPBIText[3];
491 				break;
492 
493 			default:
494 				autoresolve_help = gpStrategicString[STR_PB_AUTORESOLVE_FASTHELP];
495 				break;
496 		}
497 		iPBButton[0]->SetFastHelpText(autoresolve_help);
498 		iPBButton[1]->SetFastHelpText(gpStrategicString[STR_PB_GOTOSECTOR_FASTHELP]);
499 		if (gfAutomaticallyStartAutoResolve) DisableButton(iPBButton[1]);
500 
501 		ST::string retreat_help;
502 		if (gfAutomaticallyStartAutoResolve               ||
503 				gfCantRetreatInPBI                            ||
504 				gubEnemyEncounterCode == ENEMY_AMBUSH_CODE    ||
505 				gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE ||
506 				gubEnemyEncounterCode == CREATURE_ATTACK_CODE)
507 		{
508 			gfCantRetreatInPBI = FALSE;
509 			DisableButton(iPBButton[2]);
510 			retreat_help = gzNonPersistantPBIText[9];
511 		}
512 		else
513 		{
514 			retreat_help =
515 				use_plural_version ? gpStrategicString[STR_BP_RETREATPLURAL_FASTHELP] :
516 				gpStrategicString[STR_BP_RETREATSINGLE_FASTHELP];
517 		}
518 		iPBButton[2]->SetFastHelpText(retreat_help);
519 	}
520 	else
521 	{ /* Use the explicit encounter code to determine what gets disable and the
522 		 * associated help text that is used. */
523 
524 		/* First of all, the retreat button is always disabled seeing a battle is in
525 		 * progress. */
526 		DisableButton(iPBButton[2]);
527 		iPBButton[2]->SetFastHelpText(gzNonPersistantPBIText[0]);
528 		iPBButton[1]->SetFastHelpText(gzNonPersistantPBIText[1]);
529 		ST::string help;
530 		switch (gubExplicitEnemyEncounterCode)
531 		{
532 			case CREATURE_ATTACK_CODE:
533 			case ENEMY_ENCOUNTER_CODE:
534 			case ENEMY_INVASION_CODE:         help = gzNonPersistantPBIText[2]; goto set_help;
535 			case ENTERING_ENEMY_SECTOR_CODE:  help = gzNonPersistantPBIText[3]; goto disable_set_help;
536 			case ENEMY_AMBUSH_CODE:           help = gzNonPersistantPBIText[4]; goto disable_set_help;
537 			case FIGHTING_CREATURES_CODE:     help = gzNonPersistantPBIText[5]; goto disable_set_help;
538 			case HOSTILE_CIVILIANS_CODE:      help = gzNonPersistantPBIText[6]; goto disable_set_help;
539 			case HOSTILE_BLOODCATS_CODE:
540 			case BLOODCAT_AMBUSH_CODE:
541 			case ENTERING_BLOODCAT_LAIR_CODE: help = gzNonPersistantPBIText[7]; goto disable_set_help;
542 
543 disable_set_help:
544 				DisableButton(iPBButton[0]);
545 set_help:
546 				iPBButton[0]->SetFastHelpText(help);
547 		}
548 	}
549 
550 	// Disable the options button when the auto resolve screen comes up
551 	EnableDisAbleMapScreenOptionsButton(FALSE);
552 
553 	SetMusicMode(MUSIC_TACTICAL_ENEMYPRESENT);
554 
555 	DoTransitionFromMapscreenToPreBattleInterface();
556 }
557 
558 
DoTransitionFromMapscreenToPreBattleInterface(void)559 static void DoTransitionFromMapscreenToPreBattleInterface(void)
560 {
561 	UINT32 uiStartTime, uiCurrTime;
562 	INT32 iPercentage, iFactor;
563 	UINT32 uiTimeRange;
564 	INT16 sStartLeft, sEndLeft, sStartTop, sEndTop;
565 	INT32 iLeft, iTop, iWidth, iHeight;
566 	BOOLEAN fEnterAutoResolveMode = FALSE;
567 
568 	PauseTime( FALSE );
569 
570 	iWidth = 261;
571 	iHeight = 359;
572 
573 	uiTimeRange = 1000;
574 	iPercentage = 0;
575 	uiStartTime = GetClock();
576 
577 	GetScreenXYFromMapXY( gubPBSectorX, gubPBSectorY, &sStartLeft, &sStartTop );
578 	sStartLeft += MAP_GRID_X / 2;
579 	sStartTop += MAP_GRID_Y / 2;
580 	sEndLeft = STD_SCREEN_X + 131;
581 	sEndTop = STD_SCREEN_Y + 180;
582 
583 	//save the mapscreen buffer
584 	BltVideoSurface(guiEXTRABUFFER, FRAME_BUFFER, 0, 0, NULL);
585 
586 	if( gfEnterAutoResolveMode )
587 	{ //If we are intending on immediately entering autoresolve, change the global flag so that it will actually
588 		//render the interface once.  If gfEnterAutoResolveMode is clear, then RenderPreBattleInterface() won't do
589 		//anything.
590 		fEnterAutoResolveMode = TRUE;
591 		gfEnterAutoResolveMode = FALSE;
592 	}
593 	//render the prebattle interface
594 	RenderPreBattleInterface();
595 
596 	gfIgnoreAllInput = TRUE;
597 
598 	if( fEnterAutoResolveMode )
599 	{ //Change it back
600 		gfEnterAutoResolveMode = TRUE;
601 	}
602 
603 	BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, STD_SCREEN_X + 27, STD_SCREEN_Y + 54, 209, 32 );
604 	RenderButtons();
605 	BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, STD_SCREEN_X + 27, STD_SCREEN_Y + 54, 209, 32 );
606 	gfRenderPBInterface = TRUE;
607 
608 	//hide the prebattle interface
609 	BlitBufferToBuffer( guiEXTRABUFFER, FRAME_BUFFER, STD_SCREEN_X, STD_SCREEN_Y, 261, 359 );
610 	PlayJA2SampleFromFile(SOUNDSDIR "/laptop power up (8-11).wav", HIGHVOLUME, 1, MIDDLEPAN);
611 	InvalidateScreen();
612 	RefreshScreen();
613 
614 	SGPBox const PBIRect = { STD_SCREEN_X, STD_SCREEN_Y, 261, 359 };
615 	while( iPercentage < 100  )
616 	{
617 		uiCurrTime = GetClock();
618 		iPercentage = (uiCurrTime-uiStartTime) * 100 / uiTimeRange;
619 		iPercentage = MIN( iPercentage, 100 );
620 
621 		//Factor the percentage so that it is modified by a gravity falling acceleration effect.
622 		iFactor = (iPercentage - 50) * 2;
623 		if( iPercentage < 50 )
624 			iPercentage = (UINT32)(iPercentage + iPercentage * iFactor * 0.01 + 0.5);
625 		else
626 			iPercentage = (UINT32)(iPercentage + (100-iPercentage) * iFactor * 0.01 + 0.05);
627 
628 		//Calculate the center point.
629 		iLeft = sStartLeft - (sStartLeft-sEndLeft+1) * iPercentage / 100;
630 		if( sStartTop > sEndTop )
631 			iTop = sStartTop - (sStartTop-sEndTop+1) * iPercentage / 100;
632 		else
633 			iTop = sStartTop + (sEndTop-sStartTop+1) * iPercentage / 100;
634 
635 		SGPBox const DstRect =
636 		{
637 			(UINT16)(iLeft - iWidth  * iPercentage / 200),
638 			(UINT16)(iTop  - iHeight * iPercentage / 200),
639 			(UINT16)(MAX(1, iWidth  * iPercentage / 100)),
640 			(UINT16)(MAX(1, iHeight * iPercentage / 100))
641 		};
642 
643 		BltStretchVideoSurface(FRAME_BUFFER, guiSAVEBUFFER, &PBIRect, &DstRect);
644 
645 		InvalidateScreen();
646 		RefreshScreen();
647 
648 		//Restore the previous rect.
649 		BlitBufferToBuffer(guiEXTRABUFFER, FRAME_BUFFER, DstRect.x, DstRect.y, DstRect.w + 1, DstRect.h + 1);
650 	}
651 	BltVideoSurface(guiSAVEBUFFER, FRAME_BUFFER, 0, 0, NULL);
652 }
653 
KillPreBattleInterface()654 void KillPreBattleInterface()
655 {
656 	if( !gfPreBattleInterfaceActive )
657 		return;
658 
659 	fDisableMapInterfaceDueToBattle = FALSE;
660 	MSYS_RemoveRegion( &PBInterfaceBlanket );
661 
662 	//The panel
663 	DeleteVideoObject(uiInterfaceImages);
664 
665 	//The 3 buttons
666 	RemoveButton( iPBButton[0] );
667 	RemoveButton( iPBButton[1] );
668 	RemoveButton( iPBButton[2] );
669 	UnloadButtonImage( iPBButtonImage[0] );
670 	UnloadButtonImage( iPBButtonImage[1] );
671 	UnloadButtonImage( iPBButtonImage[2] );
672 
673 	/*
674 	MSYS_RemoveRegion( &InvolvedRegion );
675 	if( guiNumUninvolved )
676 		MSYS_RemoveRegion( &UninvolvedRegion );
677 	*/
678 
679 	gfPreBattleInterfaceActive = FALSE;
680 
681 	//UpdateCharRegionHelpText( );
682 
683 	// re draw affected regions
684 	fMapPanelDirty = TRUE;
685 	fTeamPanelDirty = TRUE;
686 	fMapScreenBottomDirty = TRUE;
687 	fCharacterInfoPanelDirty = TRUE;
688 	gfDisplayPotentialRetreatPaths = false;
689 
690 	//Enable the options button when the auto resolve  screen comes up
691 	EnableDisAbleMapScreenOptionsButton( TRUE );
692 
693 	ColorFillVideoSurfaceArea( guiSAVEBUFFER, 0, 0, 261, 359, 0 );
694 
695 	EnableTeamInfoPanels();
696 	if (giMapContractButton) giMapContractButton->Show();
697 	if (giCharInfoButton[0]) giCharInfoButton[0]->Show();
698 	if (giCharInfoButton[1]) giCharInfoButton[1]->Show();
699 }
700 
701 
RenderPBHeader(INT32 * piX,INT32 * piWidth)702 static void RenderPBHeader(INT32* piX, INT32* piWidth)
703 {
704 	INT32 x, width;
705 	UINT8 const foreground =
706 		!gfBlinkHeader             ? FONT_BEIGE :
707 		GetJA2Clock() % 1000 < 667 ? FONT_WHITE :
708 		FONT_LTRED;
709 	SetFontAttributes(FONT10ARIALBOLD, foreground);
710 	ST::string str;
711 	if( !gfPersistantPBI )
712 	{
713 		str = gzNonPersistantPBIText[8];
714 	}
715 	else switch( gubEnemyEncounterCode )
716 	{
717 		case ENEMY_INVASION_CODE:
718 			str = gpStrategicString[STR_PB_ENEMYINVASION_HEADER];
719 			break;
720 		case ENEMY_ENCOUNTER_CODE:
721 			str = gpStrategicString[STR_PB_ENEMYENCOUNTER_HEADER];
722 			break;
723 		case ENEMY_AMBUSH_CODE:
724 			str = gpStrategicString[STR_PB_ENEMYAMBUSH_HEADER];
725 			gfBlinkHeader = TRUE;
726 			break;
727 		case ENTERING_ENEMY_SECTOR_CODE:
728 			str = gpStrategicString[STR_PB_ENTERINGENEMYSECTOR_HEADER];
729 			break;
730 		case CREATURE_ATTACK_CODE:
731 			str = gpStrategicString[STR_PB_CREATUREATTACK_HEADER];
732 			gfBlinkHeader = TRUE;
733 			break;
734 		case BLOODCAT_AMBUSH_CODE:
735 			str = gpStrategicString[STR_PB_BLOODCATAMBUSH_HEADER];
736 			gfBlinkHeader = TRUE;
737 			break;
738 		case ENTERING_BLOODCAT_LAIR_CODE:
739 			str = gpStrategicString[STR_PB_ENTERINGBLOODCATLAIR_HEADER];
740 			break;
741 	}
742 	width = StringPixLength( str, FONT10ARIALBOLD );
743 	x = 130 - width / 2;
744 	MPrint(STD_SCREEN_X + x, STD_SCREEN_Y + 4, str);
745 	InvalidateRegion( STD_SCREEN_X + 0, STD_SCREEN_Y + 0, STD_SCREEN_X + 231, STD_SCREEN_Y + 12 );
746 	*piX = x;
747 	*piWidth = width;
748 }
749 
750 
PrintConfined(INT32 x,INT32 y,INT32 max_w,const ST::string & str)751 static void PrintConfined(INT32 x, INT32 y, INT32 max_w, const ST::string& str)
752 {
753 	SGPFont  font  = BLOCKFONT;
754 	INT32 w     = StringPixLength(str, font);
755 	if (w >= max_w)
756 	{
757 		font = BLOCKFONTNARROW;
758 		w    = StringPixLength(str, font);
759 	}
760 	SetFont(font);
761 	MPrint(STD_SCREEN_X + x - w, STD_SCREEN_Y + y, str);
762 }
763 
764 
MPrintCentered(INT32 x,INT32 y,INT32 w,const ST::string & str)765 static void MPrintCentered(INT32 x, INT32 y, INT32 w, const ST::string& str)
766 {
767 	x += (w - StringPixLength(str, FontDefault)) / 2;
768 	MPrint(STD_SCREEN_X + x, STD_SCREEN_Y + y, str);
769 }
770 
771 
772 static ST::string GetSoldierConditionInfo(const SOLDIERTYPE& s);
773 
774 
RenderPreBattleInterface()775 void RenderPreBattleInterface()
776 {
777 	ST::string str;
778 
779 	/* If the cursor is inside the rectangle consisting of the rectangle button,
780 	 * then we set up the variables so that the retreat arrows get drawn in the
781 	 * mapscreen. */
782 	GUI_BUTTON const& retreat = *iPBButton[2];
783 	if (retreat.Enabled())
784 	{
785 		bool const mouse_in_reatread_button_area =
786 			retreat.X() <= gusMouseXPos && gusMouseXPos <= retreat.BottomRightX() &&
787 			retreat.Y() <= gusMouseYPos && gusMouseYPos <= retreat.BottomRightY();
788 		if (gfDisplayPotentialRetreatPaths != mouse_in_reatread_button_area)
789 		{
790 			gfDisplayPotentialRetreatPaths = mouse_in_reatread_button_area;
791 			fMapPanelDirty                 = TRUE;
792 		}
793 	}
794 
795 	INT32 x;
796 	INT32 width;
797 	if (gfRenderPBInterface)
798 	{
799 		gfRenderPBInterface = FALSE;
800 
801 		SGPVSurface* const dst = guiSAVEBUFFER;
802 		SetFontDestBuffer(dst);
803 
804 		if (gfPBButtonsHidden)
805 		{
806 			gfPBButtonsHidden = FALSE;
807 			ShowButton(iPBButton[0]);
808 			ShowButton(iPBButton[1]);
809 			ShowButton(iPBButton[2]);
810 		}
811 		else
812 		{
813 			MarkAButtonDirty(iPBButton[0]);
814 			MarkAButtonDirty(iPBButton[1]);
815 			MarkAButtonDirty(iPBButton[2]);
816 		}
817 
818 		SGPVObject const* const vo = uiInterfaceImages;
819 		// Main panel
820 		BltVideoObject(dst, vo, MAINPANEL, STD_SCREEN_X + 0, STD_SCREEN_Y + 0);
821 		// Main title
822 		RenderPBHeader(&x, &width);
823 		// Draw the title bars up to the text
824 		for (INT32 i = x - 12; i > 20; i -= 10)
825 		{
826 			BltVideoObject(dst, vo, TITLE_BAR_PIECE, STD_SCREEN_X + i, STD_SCREEN_Y + 6);
827 		}
828 		for (INT32 i = x + width + 2; i < 231; i += 10)
829 		{
830 			BltVideoObject(dst, vo, TITLE_BAR_PIECE, STD_SCREEN_X + i, STD_SCREEN_Y + 6);
831 		}
832 
833 		{ INT32 const y = BOTTOM_Y - ACTUAL_HEIGHT - ROW_HEIGHT * MAX(guiNumUninvolved, 1);
834 			BltVideoObject(dst, vo, UNINVOLVED_HEADER, STD_SCREEN_X + 8, y);
835 		}
836 
837 		SetFontForeground(FONT_BEIGE);
838 		PrintConfined(65, 17, 64, gpStrategicString[STR_PB_LOCATION]);
839 
840 		ST::string encounter =
841 			gubEnemyEncounterCode != CREATURE_ATTACK_CODE        ? gpStrategicString[STR_PB_ENEMIES] :
842 			gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE || // XXX case is unreachable, because of != above
843 			gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE ? gpStrategicString[STR_PB_BLOODCATS] :
844 			gpStrategicString[STR_PB_CREATURES];
845 		PrintConfined( 54, 38, 52, encounter);
846 		PrintConfined(139, 38, 52, gpStrategicString[STR_PB_MERCS]);
847 		PrintConfined(224, 38, 52, gpStrategicString[STR_PB_MILITIA]);
848 
849 		// Draw the bottom columns
850 		for (INT32 i = 0; i < (INT32)MAX(guiNumUninvolved, 1); ++i)
851 		{
852 			INT32 const y = BOTTOM_Y - ROW_HEIGHT * (i + 1) + 1;
853 			BltVideoObject(dst, vo, BOTTOM_COLUMN, STD_SCREEN_X + 161, y);
854 		}
855 
856 		for (INT32 i = 0; i < (INT32)(21 - MAX(guiNumUninvolved, 1)); ++i)
857 		{
858 			INT32 const y = TOP_Y + ROW_HEIGHT * i;
859 			BltVideoObject(dst, vo, TOP_COLUMN, STD_SCREEN_X + 186, y);
860 		}
861 
862 		UINT8 const sec_x = gubPBSectorX;
863 		UINT8 const sec_y = gubPBSectorY;
864 		UINT8 const sec_z = gubPBSectorZ;
865 
866 		// Location
867 		SetFontAttributes(FONT10ARIAL, FONT_YELLOW);
868 		ST::string sector_name = GetSectorIDString(sec_x, sec_y, sec_z, TRUE);
869 		MPrint(STD_SCREEN_X + 70, STD_SCREEN_Y + 17, ST::format("{} {}", gpStrategicString[STR_PB_SECTOR], sector_name));
870 
871 		SetFont(FONT14ARIAL);
872 		// Enemy
873 		ST::string enemies;
874 		if (gubEnemyEncounterCode == CREATURE_ATTACK_CODE        ||
875 			gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE        ||
876 			gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE ||
877 			WhatPlayerKnowsAboutEnemiesInSector(sec_x, sec_y) != KNOWS_HOW_MANY)
878 		{ // Don't know how many
879 			enemies = "?";
880 		}
881 		else
882 		{ // Know exactly how many
883 			INT32 const n = NumEnemiesInSector(sec_x, sec_y);
884 			str = ST::format("{}", n);
885 			enemies = str;
886 		}
887 		MPrintCentered(57, 36, 27, enemies);
888 		// Player
889 		str = ST::format("{}", guiNumInvolved);
890 		MPrintCentered(142, 36, 27, str);
891 		// Militia
892 		str = ST::format("{}", CountAllMilitiaInSector(sec_x, sec_y));
893 		MPrintCentered(227, 36, 27, str);
894 		SetFontShadow(FONT_NEARBLACK);
895 
896 		SetFont(BLOCKFONT2);
897 
898 		// Print the participants of the battle
899 		// |  NAME  | ASSIGN |  COND  |   HP   |   BP   |
900 		{ INT32 y = TOP_Y + 1 - STD_SCREEN_Y;
901 			CFOR_EACH_IN_TEAM(i, OUR_TEAM)
902 			{
903 				SOLDIERTYPE const& s = *i;
904 				if (s.bLife == 0)                       continue;
905 				if (s.uiStatusFlags & SOLDIER_VEHICLE)  continue;
906 				if (!PlayerMercInvolvedInThisCombat(s)) continue;
907 
908 				// Name
909 				MPrintCentered( 17, y, 52, s.name);
910 				// Assignment
911 				str = GetMapscreenMercAssignmentString(s);
912 				MPrintCentered( 72, y, 45, str);
913 				// Condition
914 				MPrintCentered(129, y, 58, GetSoldierConditionInfo(s));
915 				// HP
916 				str = ST::format("{}%", s.bLife * 100 / s.bLifeMax);
917 				MPrintCentered(189, y, 25, str);
918 				// BP
919 				str = ST::format("{}%", s.bBreath);
920 				MPrintCentered(217, y, 25, str);
921 
922 				y += ROW_HEIGHT;
923 			}
924 		}
925 
926 		// Print the uninvolved members of the battle
927 		// |  NAME  | ASSIGN |  LOC   |  DEST  |  DEP   |
928 		if (guiNumUninvolved == 0)
929 		{
930 			MPrintCentered(17, BOTTOM_Y - STD_SCREEN_Y - ROW_HEIGHT + 2, 52, gpStrategicString[STR_PB_NONE]);
931 		}
932 		else
933 		{
934 			INT32 y = BOTTOM_Y - STD_SCREEN_Y - ROW_HEIGHT * guiNumUninvolved + 2;
935 			CFOR_EACH_IN_TEAM(i, OUR_TEAM)
936 			{
937 				SOLDIERTYPE const& s = *i;
938 				if (s.bLife == 0)                      continue;
939 				if (s.uiStatusFlags & SOLDIER_VEHICLE) continue;
940 				if (PlayerMercInvolvedInThisCombat(s)) continue;
941 
942 				// Name
943 				MPrintCentered( 17, y, 52, s.name);
944 				// Assignment
945 				str = GetMapscreenMercAssignmentString(s);
946 				MPrintCentered( 72, y, 54, str);
947 				// Location
948 				str = GetMapscreenMercLocationString(s);
949 				MPrintCentered(128, y, 33, str);
950 				// Destination
951 				str = GetMapscreenMercDestinationString(s);
952 				if (!str.empty()) MPrintCentered(164, y, 41, str);
953 				// Departure
954 				str = GetMapscreenMercDepartureString(s, 0);
955 				MPrintCentered(208, y, 34, str);
956 				y += ROW_HEIGHT;
957 			}
958 		}
959 
960 		MarkAllBoxesAsAltered();
961 		RestoreExternBackgroundRect(STD_SCREEN_X, STD_SCREEN_Y, 261, 359);
962 
963 		// Restore font destinanation buffer to the frame buffer
964 		SetFontDestBuffer(FRAME_BUFFER);
965 	}
966 	else if (gfBlinkHeader)
967 	{
968 		RenderPBHeader(&x, &width); // The text is important enough to blink
969 	}
970 
971 	if (gfEnterAutoResolveMode)
972 	{
973 		gfEnterAutoResolveMode = FALSE;
974 		EnterAutoResolveMode(gubPBSectorX, gubPBSectorY);
975 	}
976 
977 	gfIgnoreAllInput = FALSE;
978 }
979 
980 
AutoResolveBattleCallback(GUI_BUTTON * btn,INT32 reason)981 static void AutoResolveBattleCallback(GUI_BUTTON* btn, INT32 reason)
982 {
983 	if( !gfIgnoreAllInput )
984 	{
985 		if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
986 		{
987 				if( _KeyDown( ALT ) && CHEATER_CHEAT_LEVEL() )
988 				{
989 					if( !gfPersistantPBI )
990 					{
991 						return;
992 					}
993 					PlayJA2Sample(EXPLOSION_1, HIGHVOLUME, 1, MIDDLEPAN);
994 					gStrategicStatus.usPlayerKills += NumEnemiesInSector( gubPBSectorX, gubPBSectorY );
995 					EliminateAllEnemies( gubPBSectorX, gubPBSectorY );
996 					SetMusicMode( MUSIC_TACTICAL_VICTORY );
997 					btn->uiFlags &= ~BUTTON_CLICKED_ON;
998 					btn->Draw();
999 					InvalidateRegion(btn->X(), btn->Y(), btn->BottomRightX(), btn->BottomRightY());
1000 					ExecuteBaseDirtyRectQueue();
1001 					EndFrameBufferRender( );
1002 					RefreshScreen();
1003 					KillPreBattleInterface();
1004 					StopTimeCompression();
1005 					SetMusicMode( MUSIC_TACTICAL_NOTHING );
1006 					return;
1007 				}
1008 			gfEnterAutoResolveMode = TRUE;
1009 		}
1010 	}
1011 }
1012 
1013 
1014 static void ClearMovementForAllInvolvedPlayerGroups(void);
1015 static void PutNonSquadMercsInBattleSectorOnSquads(BOOLEAN fExitVehicles);
1016 
1017 
GoToSectorCallback(GUI_BUTTON * btn,INT32 reason)1018 static void GoToSectorCallback(GUI_BUTTON* btn, INT32 reason)
1019 {
1020 	if( !gfIgnoreAllInput )
1021 	{
1022 		if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1023 		{
1024 				if( _KeyDown( ALT ) && CHEATER_CHEAT_LEVEL() )
1025 				{
1026 					if( !gfPersistantPBI )
1027 					{
1028 						return;
1029 					}
1030 					PlayJA2Sample(EXPLOSION_1, HIGHVOLUME, 1, MIDDLEPAN);
1031 					gStrategicStatus.usPlayerKills += NumEnemiesInSector( gubPBSectorX, gubPBSectorY );
1032 					EliminateAllEnemies( gubPBSectorX, gubPBSectorY );
1033 					SetMusicMode( MUSIC_TACTICAL_VICTORY );
1034 					btn->uiFlags &= ~BUTTON_CLICKED_ON;
1035 					btn->Draw();
1036 					InvalidateRegion(btn->X(), btn->Y(), btn->BottomRightX(), btn->BottomRightY());
1037 					ExecuteBaseDirtyRectQueue();
1038 					EndFrameBufferRender( );
1039 					RefreshScreen();
1040 					KillPreBattleInterface();
1041 					StopTimeCompression();
1042 					SetMusicMode( MUSIC_TACTICAL_NOTHING );
1043 					return;
1044 				}
1045 			if( gfPersistantPBI && gpBattleGroup && gpBattleGroup->fPlayer &&
1046 					gubEnemyEncounterCode != ENEMY_AMBUSH_CODE &&
1047 					gubEnemyEncounterCode != CREATURE_ATTACK_CODE &&
1048 					gubEnemyEncounterCode != BLOODCAT_AMBUSH_CODE )
1049 			{
1050 				gfEnterTacticalPlacementGUI = TRUE;
1051 			}
1052 			btn->uiFlags &= ~BUTTON_CLICKED_ON;
1053 			btn->Draw();
1054 			InvalidateRegion(btn->X(), btn->Y(), btn->BottomRightX(), btn->BottomRightY());
1055 			ExecuteBaseDirtyRectQueue();
1056 			EndFrameBufferRender( );
1057 			RefreshScreen();
1058 			if( gubPBSectorX == gWorldSectorX && gubPBSectorY == gWorldSectorY && !gbWorldSectorZ )
1059 			{
1060 				gfGotoSectorTransition = TRUE;
1061 			}
1062 
1063 			// first time going to the sector?
1064 			if( gfPersistantPBI )
1065 			{
1066 				// put everyone on duty, and remove mercs from vehicles, too
1067 				PutNonSquadMercsInBattleSectorOnSquads( TRUE );
1068 
1069 				// we nuke the groups existing route & destination in advance
1070 				ClearMovementForAllInvolvedPlayerGroups( );
1071 			}
1072 			else
1073 			{ //Clear the battlegroup pointer.
1074 				gpBattleGroup = NULL;
1075 			}
1076 
1077 			// must come AFTER anything that needs gpBattleGroup, as it wipes it out
1078 			SetCurrentWorldSector( gubPBSectorX, gubPBSectorY, gubPBSectorZ );
1079 
1080 			KillPreBattleInterface();
1081 		}
1082 	}
1083 }
1084 
1085 
RetreatMercsCallback(GUI_BUTTON * btn,INT32 reason)1086 static void RetreatMercsCallback(GUI_BUTTON* btn, INT32 reason)
1087 {
1088 	if( !gfIgnoreAllInput )
1089 	{
1090 		if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1091 		{
1092 			// get them outta here!
1093 			RetreatAllInvolvedPlayerGroups();
1094 
1095 			// NOTE: this code assumes you can never retreat while underground
1096 			HandleLoyaltyImplicationsOfMercRetreat( RETREAT_PBI, gubPBSectorX, gubPBSectorY, 0 );
1097 			if( CountAllMilitiaInSector( gubPBSectorX, gubPBSectorY ) )
1098 			{ //Mercs retreat, but enemies still need to fight the militia
1099 				gfEnterAutoResolveMode = TRUE;
1100 				return;
1101 			}
1102 
1103 			//Warp time by 5 minutes so that player can't just go back into the sector he left.
1104 			WarpGameTime( 300, WARPTIME_NO_PROCESSING_OF_EVENTS );
1105 			ResetMovementForEnemyGroupsInLocation( gubPBSectorX, gubPBSectorY );
1106 
1107 			btn->uiFlags &= ~BUTTON_CLICKED_ON;
1108 			btn->Draw();
1109 			InvalidateRegion(btn->X(), btn->Y(), btn->BottomRightX(), btn->BottomRightY());
1110 			ExecuteBaseDirtyRectQueue();
1111 			EndFrameBufferRender( );
1112 			RefreshScreen();
1113 			KillPreBattleInterface();
1114 			StopTimeCompression();
1115 			gpBattleGroup = NULL;
1116 			gfBlitBattleSectorLocator = FALSE;
1117 
1118 			SetMusicMode( MUSIC_TACTICAL_NOTHING );
1119 		}
1120 	}
1121 }
1122 
1123 enum
1124 {
1125 	COND_EXCELLENT,
1126 	COND_GOOD,
1127 	COND_FAIR,
1128 	COND_WOUNDED,
1129 	COND_FATIGUED,
1130 	COND_BLEEDING,
1131 	COND_UNCONCIOUS,
1132 	COND_DYING,
1133 	COND_DEAD
1134 };
1135 
1136 
GetSoldierConditionInfo(const SOLDIERTYPE & s)1137 static ST::string GetSoldierConditionInfo(const SOLDIERTYPE& s)
1138 {
1139 	// Go from the worst condition to the best
1140 	return
1141 		s.bLife == 0                         ? pConditionStrings[COND_DEAD]       :
1142 		s.bLife < OKLIFE && s.bBleeding != 0 ? pConditionStrings[COND_DYING]      :
1143 		s.bBreath < OKBREATH && s.bCollapsed ? pConditionStrings[COND_UNCONCIOUS] :
1144 		s.bBleeding > MIN_BLEEDING_THRESHOLD ? pConditionStrings[COND_BLEEDING]   :
1145 		s.bLife * 100 < s.bLifeMax * 50      ? pConditionStrings[COND_WOUNDED]    :
1146 		s.bBreath < 50                       ? pConditionStrings[COND_FATIGUED]   :
1147 		s.bLife * 100 < s.bLifeMax * 67      ? pConditionStrings[COND_FAIR]       :
1148 		s.bLife * 100 < s.bLifeMax * 86      ? pConditionStrings[COND_GOOD]       :
1149 		pConditionStrings[COND_EXCELLENT];
1150 }
1151 
1152 
ActivatePreBattleAutoresolveAction()1153 void ActivatePreBattleAutoresolveAction()
1154 {
1155 	if (iPBButton[0]->Enabled())
1156 	{ //Feign call the autoresolve button using the callback
1157 		AutoResolveBattleCallback(iPBButton[0], MSYS_CALLBACK_REASON_LBUTTON_UP);
1158 	}
1159 }
1160 
ActivatePreBattleEnterSectorAction()1161 void ActivatePreBattleEnterSectorAction()
1162 {
1163 	if (iPBButton[1]->Enabled())
1164 	{ //Feign call the enter sector button using the callback
1165 		GoToSectorCallback(iPBButton[1], MSYS_CALLBACK_REASON_LBUTTON_UP);
1166 	}
1167 }
1168 
ActivatePreBattleRetreatAction()1169 void ActivatePreBattleRetreatAction()
1170 {
1171 	if (iPBButton[2]->Enabled())
1172 	{ //Feign call the retreat button using the callback
1173 		RetreatMercsCallback(iPBButton[2], MSYS_CALLBACK_REASON_LBUTTON_UP);
1174 	}
1175 }
1176 
1177 
ActivateAutomaticAutoResolveStart()1178 static void ActivateAutomaticAutoResolveStart()
1179 {
1180 	iPBButton[0]->uiFlags |= BUTTON_CLICKED_ON;
1181 	gfIgnoreAllInput = FALSE;
1182 	AutoResolveBattleCallback(iPBButton[0], MSYS_CALLBACK_REASON_LBUTTON_UP);
1183 }
1184 
1185 
CalculateNonPersistantPBIInfo(void)1186 void CalculateNonPersistantPBIInfo(void)
1187 {
1188 	//We need to set up the non-persistant PBI
1189 	if( !gfBlitBattleSectorLocator ||
1190 			gubPBSectorX != gWorldSectorX || gubPBSectorY != gWorldSectorY || gubPBSectorZ != gbWorldSectorZ )
1191 	{ //Either the locator isn't on or the locator info is in a different sector
1192 
1193 		//Calculated the encounter type
1194 		gubEnemyEncounterCode = NO_ENCOUNTER_CODE;
1195 		gubExplicitEnemyEncounterCode = NO_ENCOUNTER_CODE;
1196 		if( HostileCiviliansPresent() )
1197 		{ //There are hostile civilians, so no autoresolve allowed.
1198 			gubExplicitEnemyEncounterCode = HOSTILE_CIVILIANS_CODE;
1199 		}
1200 		else if( HostileBloodcatsPresent() )
1201 		{ //There are bloodcats in the sector, so no autoresolve allowed
1202 			gubExplicitEnemyEncounterCode = HOSTILE_BLOODCATS_CODE;
1203 		}
1204 		else if( gbWorldSectorZ )
1205 		{
1206 			UNDERGROUND_SECTORINFO *pSector = FindUnderGroundSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
1207 			Assert( pSector );
1208 			if( pSector->ubCreaturesInBattle )
1209 			{
1210 				gubExplicitEnemyEncounterCode = FIGHTING_CREATURES_CODE;
1211 			}
1212 			else if( pSector->ubAdminsInBattle || pSector->ubTroopsInBattle || pSector->ubElitesInBattle )
1213 			{
1214 				gubExplicitEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE;
1215 				gubEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE;
1216 			}
1217 		}
1218 		else
1219 		{
1220 			SECTORINFO *pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ];
1221 			Assert( pSector );
1222 			if( pSector->ubCreaturesInBattle )
1223 			{
1224 				gubExplicitEnemyEncounterCode = FIGHTING_CREATURES_CODE;
1225 			}
1226 			else if( pSector->ubAdminsInBattle || pSector->ubTroopsInBattle || pSector->ubElitesInBattle )
1227 			{
1228 				gubExplicitEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE;
1229 				gubEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE;
1230 			}
1231 		}
1232 		if( gubExplicitEnemyEncounterCode != NO_ENCOUNTER_CODE )
1233 		{	//Set up the location as well as turning on the blit flag.
1234 			gubPBSectorX = (UINT8)gWorldSectorX;
1235 			gubPBSectorY = (UINT8)gWorldSectorY;
1236 			gubPBSectorZ = (UINT8)gbWorldSectorZ;
1237 			gfBlitBattleSectorLocator = TRUE;
1238 		}
1239 	}
1240 }
1241 
1242 
1243 static void PutNonSquadMercsInPlayerGroupOnSquads(GROUP* pGroup, BOOLEAN fExitVehicles);
1244 
1245 
PutNonSquadMercsInBattleSectorOnSquads(BOOLEAN fExitVehicles)1246 static void PutNonSquadMercsInBattleSectorOnSquads(BOOLEAN fExitVehicles)
1247 {
1248 	// IMPORTANT: Have to do this by group, so everyone inside vehicles gets assigned to the same squad.  Needed for
1249 	// the tactical placement interface to work in case of simultaneous multi-vehicle arrivals!
1250 	FOR_EACH_GROUP_SAFE(i)
1251 	{
1252 		GROUP& g = *i;
1253 		if (!PlayerGroupInvolvedInThisCombat(g)) continue;
1254 
1255 		// the helicopter group CAN be involved, if it's on the ground, in which case everybody must get out of it
1256 		if (IsGroupTheHelicopterGroup(g))
1257 		{
1258 			// only happens if chopper is on the ground...
1259 			Assert( !fHelicopterIsAirBorne );
1260 
1261 			// put anyone in it into movement group
1262 			MoveAllInHelicopterToFootMovementGroup( );
1263 		}
1264 		else
1265 		{
1266 			PutNonSquadMercsInPlayerGroupOnSquads(&g, fExitVehicles);
1267 		}
1268 	}
1269 }
1270 
1271 
PutNonSquadMercsInPlayerGroupOnSquads(GROUP * const pGroup,const BOOLEAN fExitVehicles)1272 static void PutNonSquadMercsInPlayerGroupOnSquads(GROUP* const pGroup, const BOOLEAN fExitVehicles)
1273 {
1274 	INT8 bUniqueVehicleSquad = -1;
1275 	if (pGroup->fVehicle)
1276 	{
1277 		// put these guys on their own squad (we need to return their group ID, and can only return one, so they need a unique one
1278 		bUniqueVehicleSquad = GetFirstEmptySquad();
1279 	}
1280 
1281 	PLAYERGROUP* next;
1282 	for (PLAYERGROUP* p = pGroup->pPlayerList; p; p = next)
1283 	{
1284 		Assert(p->pSoldier);
1285 		SOLDIERTYPE& s = *p->pSoldier;
1286 
1287 		// store ptr to next soldier in group, once removed from group, his info will get memfree'd!
1288 		next = p->next;
1289 
1290 		if (!s.bActive || s.bLife == 0 || s.uiStatusFlags & SOLDIER_VEHICLE) continue;
1291 
1292 		if (!PlayerMercInvolvedInThisCombat(s) || s.bAssignment < ON_DUTY) continue;
1293 		// if involved, but off-duty (includes mercs inside vehicles!)
1294 
1295 		// if in a vehicle, pull him out
1296 		if (s.bAssignment == VEHICLE)
1297 		{
1298 			if (fExitVehicles)
1299 			{
1300 				TakeSoldierOutOfVehicle(&s);
1301 
1302 				/* put them on the unique squad assigned to people leaving this vehicle.
1303 				 * Can't add them to existing squads, because if this is a simultaneous
1304 				 * group attack, the mercs could be coming from different sides, and the
1305 				 * placement screen can't handle mercs on the same squad arriving from
1306 				 * different edges! */
1307 				BOOLEAN const fSuccess = AddCharacterToSquad(&s, bUniqueVehicleSquad);
1308 				(void)fSuccess;
1309 				Assert(fSuccess);
1310 			}
1311 		}
1312 		else
1313 		{
1314 			// add him to ANY on duty foot squad
1315 			AddCharacterToAnySquad(&s);
1316 		}
1317 
1318 		// stand him up
1319 		MakeSoldiersTacticalAnimationReflectAssignment(&s);
1320 	}
1321 }
1322 
1323 
WakeUpAllMercsInSectorUnderAttack()1324 void WakeUpAllMercsInSectorUnderAttack()
1325 {
1326 	FOR_EACH_IN_TEAM(i, OUR_TEAM)
1327 	{
1328 		SOLDIERTYPE& s = *i;
1329 		if (s.bLife == 0)                       continue;
1330 		if (s.uiStatusFlags & SOLDIER_VEHICLE)  continue;
1331 		if (!s.fMercAsleep)                     continue;
1332 		if (!PlayerMercInvolvedInThisCombat(s)) continue;
1333 		// Involved, but asleep, force him wake him up
1334 		SetMercAwake(&s, FALSE, TRUE);
1335 	}
1336 }
1337 
1338 
1339 // we are entering the sector, clear out all mvt orders for grunts
ClearMovementForAllInvolvedPlayerGroups(void)1340 static void ClearMovementForAllInvolvedPlayerGroups(void)
1341 {
1342 	FOR_EACH_GROUP(i)
1343 	{
1344 		GROUP& g = *i;
1345 		if (!PlayerGroupInvolvedInThisCombat(g)) continue;
1346 		// clear their strategic movement (mercpaths and waypoints)
1347 		ClearMercPathsAndWaypointsForAllInGroup(g);
1348 	}
1349 }
1350 
RetreatAllInvolvedPlayerGroups(void)1351 void RetreatAllInvolvedPlayerGroups( void )
1352 {
1353 	// make sure guys stop their off duty assignments, like militia training!
1354 	// but don't exit vehicles - drive off in them!
1355 	PutNonSquadMercsInBattleSectorOnSquads( FALSE );
1356 
1357 	FOR_EACH_GROUP(i)
1358 	{
1359 		GROUP& g = *i;
1360 		if (!PlayerGroupInvolvedInThisCombat(g)) continue;
1361 		// Don't retreat empty vehicle groups!
1362 		if (g.fVehicle && !DoesVehicleGroupHaveAnyPassengers(g)) continue;
1363 		ClearMercPathsAndWaypointsForAllInGroup(g);
1364 		RetreatGroupToPreviousSector(g);
1365 	}
1366 }
1367 
1368 
1369 static BOOLEAN CurrentBattleSectorIs(INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ);
1370 
1371 
PlayerMercInvolvedInThisCombat(SOLDIERTYPE const & s)1372 bool PlayerMercInvolvedInThisCombat(SOLDIERTYPE const& s)
1373 {
1374 	Assert(s.bActive);
1375 	return
1376 		!s.fBetweenSectors                         &&
1377 		s.bAssignment != IN_TRANSIT                &&
1378 		s.bAssignment != ASSIGNMENT_POW            &&
1379 		s.bAssignment != ASSIGNMENT_DEAD           &&
1380 		!(s.uiStatusFlags & SOLDIER_VEHICLE)       &&
1381 		// Robot is involved iff it has a valid controller with it
1382 		(!AM_A_ROBOT(&s) || s.robot_remote_holder) &&
1383 		!SoldierAboardAirborneHeli(s)              &&
1384 		CurrentBattleSectorIs(s.sSectorX, s.sSectorY, s.bSectorZ);
1385 }
1386 
1387 
PlayerGroupInvolvedInThisCombat(GROUP const & g)1388 bool PlayerGroupInvolvedInThisCombat(GROUP const& g)
1389 {
1390 	/* Player group, non-empty, not between sectors, in the right sector, isn't a
1391 	 * group of in transit, dead, or POW mercs, and either not the helicopter
1392 	 * group, or the heli is on the ground */
1393 	return
1394 		g.fPlayer                                                 &&
1395 		g.ubGroupSize != 0                                        &&
1396 		!g.fBetweenSectors                                        &&
1397 		!GroupHasInTransitDeadOrPOWMercs(g)                       &&
1398 		(!IsGroupTheHelicopterGroup(g) || !fHelicopterIsAirBorne) &&
1399 		CurrentBattleSectorIs(g.ubSectorX, g.ubSectorY, g.ubSectorZ);
1400 }
1401 
1402 
CurrentBattleSectorIs(INT16 sSectorX,INT16 sSectorY,INT16 sSectorZ)1403 static BOOLEAN CurrentBattleSectorIs(INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ)
1404 {
1405 	INT16 sBattleSectorX, sBattleSectorY, sBattleSectorZ;
1406 	BOOLEAN fSuccess;
1407 
1408 	fSuccess = GetCurrentBattleSectorXYZ( &sBattleSectorX, &sBattleSectorY, &sBattleSectorZ );
1409 	Assert( fSuccess );
1410 
1411 	if ( ( sSectorX == sBattleSectorX ) && ( sSectorY == sBattleSectorY ) && ( sSectorZ == sBattleSectorZ ) )
1412 	{
1413 		// yup!
1414 		return( TRUE );
1415 	}
1416 	else
1417 	{
1418 		// wrong sector, no battle here
1419 		return( FALSE );
1420 	}
1421 }
1422 
1423 
CheckForRobotAndIfItsControlled(void)1424 static void CheckForRobotAndIfItsControlled(void)
1425 {
1426 	// search for the robot on player's team
1427 	FOR_EACH_IN_TEAM(s, OUR_TEAM)
1428 	{
1429 		if (s->bLife != 0 && AM_A_ROBOT(s))
1430 		{
1431 			// check whether it has a valid controller with it. This sets its robot_remote_holder field.
1432 			UpdateRobotControllerGivenRobot(s);
1433 
1434 			// if he has a controller, set controllers
1435 			if (s->robot_remote_holder != NULL)
1436 			{
1437 				UpdateRobotControllerGivenController(s->robot_remote_holder);
1438 			}
1439 
1440 			break;
1441 		}
1442 	}
1443 }
1444 
1445 
LogBattleResults(const UINT8 ubVictoryCode)1446 void LogBattleResults(const UINT8 ubVictoryCode)
1447 {
1448 	INT16 sSectorX;
1449 	INT16 sSectorY;
1450 	INT16 sSectorZ;
1451 	GetCurrentBattleSectorXYZ(&sSectorX, &sSectorY, &sSectorZ);
1452 	UINT8 code;
1453 	if (ubVictoryCode == LOG_VICTORY)
1454 	{
1455 		switch (gubEnemyEncounterCode)
1456 		{
1457 			case ENEMY_INVASION_CODE:         code = HISTORY_DEFENDEDTOWNSECTOR;   break;
1458 			case ENEMY_ENCOUNTER_CODE:        code = HISTORY_WONBATTLE;            break;
1459 			case ENEMY_AMBUSH_CODE:           code = HISTORY_WIPEDOUTENEMYAMBUSH;  break;
1460 			case ENTERING_ENEMY_SECTOR_CODE:  code = HISTORY_SUCCESSFULATTACK;     break;
1461 			case CREATURE_ATTACK_CODE:        code = HISTORY_CREATURESATTACKED;    break;
1462 			case BLOODCAT_AMBUSH_CODE:
1463 			case ENTERING_BLOODCAT_LAIR_CODE: code = HISTORY_SLAUGHTEREDBLOODCATS; break;
1464 			default:                          return;
1465 		}
1466 	}
1467 	else
1468 	{
1469 		switch (gubEnemyEncounterCode)
1470 		{
1471 			case ENEMY_INVASION_CODE:         code = HISTORY_LOSTTOWNSECTOR;       break;
1472 			case ENEMY_ENCOUNTER_CODE:        code = HISTORY_LOSTBATTLE;           break;
1473 			case ENEMY_AMBUSH_CODE:           code = HISTORY_FATALAMBUSH;          break;
1474 			case ENTERING_ENEMY_SECTOR_CODE:  code = HISTORY_UNSUCCESSFULATTACK;   break;
1475 			case CREATURE_ATTACK_CODE:        code = HISTORY_CREATURESATTACKED;    break;
1476 			case BLOODCAT_AMBUSH_CODE:
1477 			case ENTERING_BLOODCAT_LAIR_CODE: code = HISTORY_KILLEDBYBLOODCATS;    break;
1478 			default:                          return;
1479 		}
1480 	}
1481 	AddHistoryToPlayersLog(code, 0, GetWorldTotalMin(), sSectorX, sSectorY);
1482 }
1483 
1484 
HandlePreBattleInterfaceStates()1485 void HandlePreBattleInterfaceStates()
1486 {
1487 	if( gfEnteringMapScreenToEnterPreBattleInterface && !gfEnteringMapScreen )
1488 	{
1489 		gfEnteringMapScreenToEnterPreBattleInterface = FALSE;
1490 		if( !gfUsePersistantPBI )
1491 		{
1492 			InitPreBattleInterface(0, false);
1493 			gfUsePersistantPBI = TRUE;
1494 		}
1495 		else
1496 		{
1497 			InitPreBattleInterface(gpBattleGroup, true);
1498 		}
1499 	}
1500 	else if( gfDelayAutoResolveStart && gfPreBattleInterfaceActive )
1501 	{
1502 		gfDelayAutoResolveStart = FALSE;
1503 		gfAutomaticallyStartAutoResolve = TRUE;
1504 	}
1505 	else if( gfAutomaticallyStartAutoResolve )
1506 	{
1507 		gfAutomaticallyStartAutoResolve = FALSE;
1508 		ActivateAutomaticAutoResolveStart();
1509 	}
1510 }
1511