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