1 #include "Auto_Resolve.h"
2 #include "Campaign.h"
3 #include "ContentManager.h"
4 #include "Creature_Spreading.h"
5 #include "Directories.h"
6 #include "Easings.h"
7 #include "English.h"
8 #include "Font.h"
9 #include "Font_Control.h"
10 #include "Game_Clock.h"
11 #include "Game_Event_Hook.h"
12 #include "GameInstance.h"
13 #include "GameLoop.h"
14 #include "GameScreen.h"
15 #include "HImage.h"
16 #include "Input.h"
17 #include "Inventory_Choosing.h"
18 #include "Isometric_Utils.h"
19 #include "Items.h"
20 #include "Local.h"
21 #include "Map_Information.h"
22 #include "MapScreen.h"
23 #include "Meanwhile.h"
24 #include "MercPortrait.h"
25 #include "Morale.h"
26 #include "Music_Control.h"
27 #include "Overhead.h"
28 #include "Player_Command.h"
29 #include "PreBattle_Interface.h"
30 #include "Queen_Command.h"
31 #include "Quests.h"
32 #include "Render_Dirty.h"
33 #include "RT_Time_Defines.h"
34 #include "SkillCheck.h"
35 #include "Soldier_Macros.h"
36 #include "Strategic.h"
37 #include "Strategic_AI.h"
38 #include "Strategic_Merc_Handler.h"
39 #include "Strategic_Status.h"
40 #include "Strategic_Town_Loyalty.h"
41 #include "SysUtil.h"
42 #include "Tactical_Save.h"
43 #include "Text.h"
44 #include "Timer.h"
45 #include "Timer_Control.h"
46 #include "Town_Militia.h"
47 #include "UILayout.h"
48 #include "Video.h"
49 #include "VObject.h"
50 #include "VObject_Blitters.h"
51 #include "VSurface.h"
52 #include "WeaponModels.h"
53 #include "WordWrap.h"
54 #include <stdexcept>
55 #include <string_theory/format>
56 #include <string_theory/string>
57 
58 
59 struct BUTTON_PICS;
60 
61 
62 //#define INVULNERABILITY
63 
64 BOOLEAN gfTransferTacticalOppositionToAutoResolve = FALSE;
65 
66 //button images
67 enum
68 {
69 	PAUSE_BUTTON,
70 	PLAY_BUTTON,
71 	FAST_BUTTON,
72 	FINISH_BUTTON,
73 	YES_BUTTON,
74 	NO_BUTTON,
75 	BANDAGE_BUTTON,
76 	RETREAT_BUTTON,
77 	DONEWIN_BUTTON,
78 	DONELOSE_BUTTON,
79 	NUM_AR_BUTTONS
80 };
81 
82 
83 struct SOLDIERCELL
84 {
85 	SOLDIERTYPE *pSoldier;
86 	MouseRegion* pRegion; // only used for player mercs.
87 	SGPVObject* uiVObjectID;
88 	UINT16 usIndex;
89 	UINT32 uiFlags;
90 	UINT16 usFrame;
91 	INT16 xp, yp;
92 	UINT16 usAttack, usDefence;
93 	UINT16 usNextAttack;
94 	UINT16 usNextHit[3];
95 	UINT16 usHitDamage[3];
96 	SOLDIERCELL* pAttacker[3];
97 	UINT32 uiFlashTime;
98 	INT8 bWeaponSlot;
99 };
100 
101 struct AUTORESOLVE_STRUCT
102 {
103 	SOLDIERCELL *pRobotCell;
104 
105 	//IDs into the graphic images
106 	SGPVObject* iPanelImages;
107 	GUIButtonRef iButton[NUM_AR_BUTTONS];
108 	BUTTON_PICS* iButtonImage[NUM_AR_BUTTONS];
109 	SGPVObject* iFaces; //for generic civs and enemies
110 	INT32 iMercFaces[20]; //for each merc face
111 	SGPVObject* iIndent;
112 	SGPVSurface* iInterfaceBuffer;
113 	INT32 iNumMercFaces;
114 	INT32 iActualMercFaces; //this represents the real number of merc faces.  Because
115 				//my debug mode allows to freely add and subtract mercs, we
116 				//can add/remove temp mercs, but we don't want to remove the
117 				//actual mercs.
118 	UINT32 uiTimeSlice;
119 	UINT32 uiTotalElapsedBattleTimeInMilliseconds;
120 	UINT32 uiPrevTime, uiCurrTime;
121 	UINT32 uiPreRandomIndex;
122 
123 	SGPBox rect;
124 
125 	UINT16 usPlayerAttack;
126 	UINT16 usPlayerDefence;
127 	UINT16 usEnemyAttack;
128 	UINT16 usEnemyDefence;
129 	INT16 sCenterStartX;
130 
131 	UINT8 ubEnemyLeadership;
132 	UINT8 ubPlayerLeadership;
133 	UINT8 ubMercs, ubCivs, ubEnemies;
134 	UINT8 ubAdmins, ubTroops, ubElites;
135 	UINT8 ubYMCreatures, ubYFCreatures, ubAMCreatures, ubAFCreatures;
136 	UINT8 ubAliveMercs, ubAliveCivs, ubAliveEnemies;
137 	UINT8 ubMercCols, ubMercRows;
138 	UINT8 ubEnemyCols, ubEnemyRows;
139 	UINT8 ubCivCols, ubCivRows;
140 	UINT8 ubTimeModifierPercentage;
141 	UINT8 ubSectorX, ubSectorY;
142 	INT8 bVerticalOffset;
143 
144 	BOOLEAN fRenderAutoResolve;
145 	BOOLEAN fExitAutoResolve;
146 	BOOLEAN fPaused;
147 	BOOLEAN fDebugInfo;
148 	BOOLEAN ubBattleStatus;
149 	BOOLEAN fUnlimitedAmmo;
150 	BOOLEAN fSound;
151 	BOOLEAN ubPlayerDefenceAdvantage;
152 	BOOLEAN ubEnemyDefenceAdvantage;
153 	BOOLEAN fInstantFinish;
154 	BOOLEAN fAllowCapture;
155 	BOOLEAN fPlayerRejectedSurrenderOffer;
156 	BOOLEAN fPendingSurrender;
157 	BOOLEAN fShowInterface;
158 	BOOLEAN fEnteringAutoResolve;
159 	BOOLEAN fMoraleEventsHandled;
160 	BOOLEAN fCaptureNotPermittedDueToEPCs;
161 
162 	MOUSE_REGION AutoResolveRegion;
163 };
164 
165 //Classifies the type of soldier the soldier cell is
166 #define CELL_MERC		0x00000001
167 #define CELL_MILITIA		0x00000002
168 #define CELL_ELITE		0x00000004
169 #define CELL_TROOP		0x00000008
170 #define CELL_ADMIN		0x00000010
171 #define CELL_AF_CREATURE	0x00000020
172 #define CELL_AM_CREATURE	0x00000040
173 #define CELL_YF_CREATURE	0x00000080
174 #define CELL_YM_CREATURE	0x00000100
175 //The team leader is the one with the highest leadership.
176 //There can only be one teamleader per side (mercs/civs and enemies)
177 #define CELL_TEAMLEADER		0x00000200
178 //Combat flags
179 #define CELL_FIREDATTARGET	0x00000400
180 #define CELL_DODGEDATTACK	0x00000800
181 #define CELL_HITBYATTACKER	0x00001000
182 #define CELL_HITLASTFRAME	0x00002000
183 //Cell statii
184 #define CELL_SHOWRETREATTEXT	0x00004000
185 #define CELL_RETREATING		0x00008000
186 #define CELL_RETREATED		0x00010000
187 #define CELL_DIRTY		0x00020000
188 #define CELL_PROCESSED		0x00040000
189 #define CELL_ASSIGNED		0x00080000
190 #define CELL_EPC		0x00100000
191 #define CELL_ROBOT		0x00200000
192 
193 //Combined flags
194 #define CELL_PLAYER		( CELL_MERC | CELL_MILITIA )
195 #define CELL_ENEMY		( CELL_ELITE | CELL_TROOP | CELL_ADMIN )
196 #define CELL_CREATURE		( CELL_AF_CREATURE | CELL_AM_CREATURE | CELL_YF_CREATURE | CELL_YM_CREATURE )
197 #define CELL_FEMALECREATURE	( CELL_AF_CREATURE | CELL_YF_CREATURE )
198 #define CELL_MALECREATURE	( CELL_AM_CREATURE | CELL_YM_CREATURE )
199 #define CELL_YOUNGCREATURE	( CELL_YF_CREATURE | CELL_YM_CREATURE )
200 #define CELL_INVOLVEDINCOMBAT	( CELL_FIREDATTARGET | CELL_DODGEDATTACK | CELL_HITBYATTACKER )
201 
202 enum
203 {
204 	BATTLE_IN_PROGRESS,
205 	BATTLE_VICTORY,
206 	BATTLE_DEFEAT,
207 	BATTLE_RETREAT,
208 	BATTLE_SURRENDERED,
209 	BATTLE_CAPTURED
210 };
211 
212 //panel pieces
213 enum
214 {
215 	TL_BORDER,
216 	T_BORDER,
217 	TR_BORDER,
218 	L_BORDER,
219 	C_TEXTURE,
220 	R_BORDER,
221 	BL_BORDER,
222 	B_BORDER,
223 	BR_BORDER,
224 	TOP_MIDDLE,
225 	AUTO_MIDDLE,
226 	BOT_MIDDLE,
227 	MERC_PANEL,
228 	OTHER_PANEL,
229 };
230 
231 //generic face images
232 enum
233 {
234 	ADMIN_FACE,
235 	TROOP_FACE,
236 	ELITE_FACE,
237 	MILITIA1_FACE,
238 	MILITIA2_FACE,
239 	MILITIA3_FACE,
240 	YM_CREATURE_FACE,
241 	AM_CREATURE_FACE,
242 	YF_CREATURE_FACE,
243 	AF_CREATURE_FACE,
244 	HUMAN_SKULL,
245 	CREATURE_SKULL,
246 	ELITEF_FACE,
247 	MILITIA1F_FACE,
248 	MILITIA2F_FACE,
249 	MILITIA3F_FACE,
250 };
251 
252 
253 //Autoresolve sets this variable which defaults to -1 when not needed.
254 INT16 gsEnemyGainedControlOfSectorID = -1;
255 INT16 gsCiviliansEatenByMonsters = -1;
256 
257 //Dynamic globals -- to conserve memory, all global variables are allocated upon entry
258 //and deleted before we leave.
259 static AUTORESOLVE_STRUCT* gpAR;
260 static SOLDIERCELL*        gpMercs;
261 static SOLDIERCELL*        gpCivs;
262 static SOLDIERCELL*        gpEnemies;
263 
264 
265 #define FOR_EACH_AR_MERC(iter) \
266 	for (SOLDIERCELL* iter = gpMercs, *const iter##__end = &gpMercs[gpAR->ubMercs]; iter != iter##__end; ++iter)
267 
268 #define FOR_EACH_AR_CIV(iter) \
269 	for (SOLDIERCELL* iter = gpCivs, *const iter##__end = &gpCivs[gpAR->ubCivs]; iter != iter##__end; ++iter)
270 
271 #define FOR_EACH_AR_ENEMY(iter) \
272 	for (SOLDIERCELL* iter = gpEnemies, *const iter##__end = &gpEnemies[gpAR->ubEnemies]; iter != iter##__end; ++iter)
273 
274 
275 //Simple wrappers for autoresolve sounds that are played.
PlayAutoResolveSample(SoundID const usNum,UINT32 const ubVolume,UINT32 const ubLoops,UINT32 const uiPan)276 static void PlayAutoResolveSample(SoundID const usNum, UINT32 const ubVolume, UINT32 const ubLoops, UINT32 const uiPan)
277 {
278 	if( gpAR->fSound )
279 	{
280 		PlayJA2Sample(usNum, ubVolume, ubLoops, uiPan);
281 	}
282 }
283 
PlayAutoResolveSample(const ST::string & sample,UINT32 const ubVolume,UINT32 const ubLoops,UINT32 const uiPan)284 static void PlayAutoResolveSample(const ST::string &sample, UINT32 const ubVolume, UINT32 const ubLoops, UINT32 const uiPan)
285 {
286 	if( gpAR->fSound )
287 	{
288 		PlayJA2Sample(sample.c_str(), ubVolume, ubLoops, uiPan);
289 	}
290 }
291 
EliminateAllEnemies(UINT8 ubSectorX,UINT8 ubSectorY)292 void EliminateAllEnemies( UINT8 ubSectorX, UINT8 ubSectorY )
293 {
294 	SECTORINFO *pSector;
295 	UINT8 ubNumEnemies[ NUM_ENEMY_RANKS ];
296 	UINT8 ubRankIndex;
297 
298 	//Clear any possible battle locator
299 	gfBlitBattleSectorLocator = FALSE;
300 
301 	pSector = &SectorInfo[ SECTOR( ubSectorX, ubSectorY ) ];
302 
303 
304 	// if we're doing this from the Pre-Battle interface, gpAR is NULL, and RemoveAutoResolveInterface(0 won't happen, so
305 	// we must process the enemies killed right here & give out loyalty bonuses as if the battle had been fought & won
306 	if( !gpAR )
307 	{
308 		GetNumberOfEnemiesInSector( ubSectorX, ubSectorY, &ubNumEnemies[ 0 ], &ubNumEnemies[ 1 ], &ubNumEnemies[ 2 ] );
309 
310 		for ( ubRankIndex = 0; ubRankIndex < NUM_ENEMY_RANKS; ubRankIndex++ )
311 		{
312 			for (INT32 i = 0; i < ubNumEnemies[ubRankIndex]; ++i)
313 			{
314 				HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ENEMY_KILLED, ubSectorX, ubSectorY, 0 );
315 				TrackEnemiesKilled( ENEMY_KILLED_IN_AUTO_RESOLVE, RankIndexToSoldierClass( ubRankIndex ) );
316 			}
317 		}
318 
319 		HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_WON, ubSectorX, ubSectorY, 0 );
320 	}
321 
322 	if( !gpAR || gpAR->ubBattleStatus != BATTLE_IN_PROGRESS )
323 	{
324 		//Remove the defend force here.
325 		pSector->ubNumTroops = 0;
326 		pSector->ubNumElites = 0;
327 		pSector->ubNumAdmins = 0;
328 		pSector->ubNumCreatures = 0;
329 		//Remove the mobile forces here, but only if battle is over.
330 		FOR_EACH_GROUP_SAFE(i)
331 		{
332 			GROUP& g = *i;
333 			if (g.fPlayer)                continue;
334 			if (g.ubSectorX != ubSectorX) continue;
335 			if (g.ubSectorY != ubSectorY) continue;
336 			RemoveGroupFromStrategicAILists(g);
337 			RemoveGroup(g);
338 		}
339 		if( gpBattleGroup )
340 		{
341 			CalculateNextMoveIntention( gpBattleGroup );
342 		}
343 		// set this sector as taken over
344 		SetThisSectorAsPlayerControlled( ubSectorX, ubSectorY, 0, TRUE );
345 		RecalculateSectorWeight( (UINT8)SECTOR( ubSectorX, ubSectorY ) );
346 
347 		// dirty map panel
348 		fMapPanelDirty = TRUE;
349 	}
350 
351 	if( gpAR )
352 	{
353 		FOR_EACH_AR_ENEMY(i)
354 		{
355 			i->pSoldier->bLife = 0;
356 		}
357 		gpAR->ubAliveEnemies = 0;
358 	}
359 	gpBattleGroup = NULL;
360 }
361 
362 
363 static void RenderAutoResolve(void);
364 
DoTransitionFromPreBattleInterfaceToAutoResolve(void)365 static void DoTransitionFromPreBattleInterfaceToAutoResolve(void)
366 {
367 	UINT32 uiStartTime = GetClock();
368 	UINT32 uiEndTime = uiStartTime + 1000;
369 
370 	PauseTime( FALSE );
371 
372 	gpAR->fShowInterface = TRUE;
373 
374 	UINT16 const x = gpAR->rect.x;
375 	UINT16 const y = gpAR->rect.y;
376 	UINT16 const w = gpAR->rect.w;
377 	UINT16 const h = gpAR->rect.h;
378 
379 	//save the prebattle/mapscreen interface background
380 	BltVideoSurface(guiEXTRABUFFER, FRAME_BUFFER, 0, 0, NULL);
381 
382 	//render the autoresolve panel
383 	RenderAutoResolve();
384 	RenderButtons();
385 	RenderButtonsFastHelp();
386 	//save it
387 	BlitBufferToBuffer(FRAME_BUFFER, guiSAVEBUFFER, x, y, w, h);
388 
389 	//hide the autoresolve
390 	BlitBufferToBuffer(guiEXTRABUFFER, FRAME_BUFFER, x, y, w, h);
391 
392 	PlayJA2SampleFromFile(SOUNDSDIR "/laptop power up (8-11).wav", HIGHVOLUME, 1, MIDDLEPAN);
393 	while( GetClock() <= uiEndTime )
394 	{
395 		FLOAT fEasingProgress = EaseInCubic(uiStartTime, uiEndTime, GetClock());
396 
397 		SGPBox const DstRect =
398 		{
399 			(UINT16)(x * fEasingProgress),
400 			(UINT16)(y * fEasingProgress),
401 			(UINT16)(MAX(w * fEasingProgress, 1)),
402 			(UINT16)(MAX(h * fEasingProgress, 1))
403 		};
404 
405 		BltStretchVideoSurface(FRAME_BUFFER, guiSAVEBUFFER, &gpAR->rect, &DstRect);
406 		InvalidateScreen();
407 		RefreshScreen();
408 
409 		//Restore the previous rect.
410 		BlitBufferToBuffer(guiEXTRABUFFER, FRAME_BUFFER, DstRect.x, DstRect.y, DstRect.w, DstRect.h);
411 	}
412 }
413 
EnterAutoResolveMode(UINT8 ubSectorX,UINT8 ubSectorY)414 void EnterAutoResolveMode( UINT8 ubSectorX, UINT8 ubSectorY )
415 {
416 	//Set up mapscreen for removal
417 	SetPendingNewScreen( AUTORESOLVE_SCREEN );
418 	CreateDestroyMapInvButton();
419 	RenderButtons();
420 
421 	//Allocate memory for all the globals while we are in this mode.
422 	gpAR = new AUTORESOLVE_STRUCT{};
423 	//Mercs -- 20 max
424 	gpMercs = new SOLDIERCELL[20]{};
425 	//Militia -- MAX_ALLOWABLE_MILITIA_PER_SECTOR max
426 	gpCivs = new SOLDIERCELL[MAX_ALLOWABLE_MILITIA_PER_SECTOR]{};
427 	//Enemies -- 32 max
428 	gpEnemies = new SOLDIERCELL[32]{};
429 
430 	//Set up autoresolve
431 	gpAR->fEnteringAutoResolve = TRUE;
432 	gpAR->ubSectorX = ubSectorX;
433 	gpAR->ubSectorY = ubSectorY;
434 	gpAR->ubBattleStatus = BATTLE_IN_PROGRESS;
435 	gpAR->uiTimeSlice = 1000;
436 	gpAR->uiTotalElapsedBattleTimeInMilliseconds = 0;
437 	gpAR->fSound = TRUE;
438 	gpAR->fMoraleEventsHandled = FALSE;
439 	gpAR->uiPreRandomIndex = guiPreRandomIndex;
440 
441 	//Determine who gets the defensive advantage
442 	switch( gubEnemyEncounterCode )
443 	{
444 		case ENEMY_ENCOUNTER_CODE:
445 			gpAR->ubPlayerDefenceAdvantage = 21; //Skewed to the player's advantage for convenience purposes.
446 			break;
447 		case ENEMY_INVASION_CODE:
448 			gpAR->ubPlayerDefenceAdvantage = 0;
449 			break;
450 		case CREATURE_ATTACK_CODE:
451 			gpAR->ubPlayerDefenceAdvantage = 0;
452 			break;
453 		default:
454 			//shouldn't happen
455 			SLOGE("Autoresolving with entering enemy sector code %d -- illegal", gubEnemyEncounterCode );
456 			break;
457 	}
458 }
459 
460 
461 static void CalculateAttackValues(void);
462 static void CalculateAutoResolveInfo(void);
463 static void CalculateSoldierCells(BOOLEAN fReset);
464 static void CreateAutoResolveInterface(void);
465 static void DetermineTeamLeader(BOOLEAN fFriendlyTeam);
466 static void HandleAutoResolveInput(void);
467 static void ProcessBattleFrame(void);
468 static void RemoveAutoResolveInterface(bool delete_for_good);
469 
470 
AutoResolveScreenHandle()471 ScreenID AutoResolveScreenHandle()
472 {
473 	RestoreBackgroundRects();
474 
475 	if( !gpAR )
476 	{
477 		gfEnteringMapScreen = TRUE;
478 		return MAP_SCREEN;
479 	}
480 	if( gpAR->fEnteringAutoResolve )
481 	{
482 		gpAR->fEnteringAutoResolve = FALSE;
483 		//Take the framebuffer, shade it, and save it to the SAVEBUFFER.
484 		FRAME_BUFFER->ShadowRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
485 		BltVideoSurface(guiSAVEBUFFER, FRAME_BUFFER, 0, 0, NULL);
486 		KillPreBattleInterface();
487 		CalculateAutoResolveInfo();
488 		CalculateSoldierCells( FALSE );
489 		CreateAutoResolveInterface();
490 		DetermineTeamLeader( TRUE ); //friendly team
491 		DetermineTeamLeader( FALSE ); //enemy team
492 		CalculateAttackValues();
493 		DoTransitionFromPreBattleInterfaceToAutoResolve();
494 		gpAR->fRenderAutoResolve = TRUE;
495 	}
496 	if( gpAR->fExitAutoResolve )
497 	{
498 		gfEnteringMapScreen = TRUE;
499 		RemoveAutoResolveInterface(true);
500 		return MAP_SCREEN;
501 	}
502 	if( gpAR->fPendingSurrender )
503 	{
504 		gpAR->uiPrevTime = gpAR->uiCurrTime = GetJA2Clock();
505 
506 	}
507 	else if (gpAR->ubBattleStatus == BATTLE_IN_PROGRESS)
508 	{
509 		ProcessBattleFrame();
510 	}
511 	HandleAutoResolveInput();
512 	RenderAutoResolve();
513 
514 
515 	SaveBackgroundRects();
516 	RenderButtons();
517 	RenderButtonsFastHelp();
518 	ExecuteBaseDirtyRectQueue();
519 	EndFrameBufferRender();
520 	return AUTORESOLVE_SCREEN;
521 }
522 
523 
RefreshMerc(SOLDIERTYPE * pSoldier)524 static void RefreshMerc(SOLDIERTYPE* pSoldier)
525 {
526 	pSoldier->bLife = pSoldier->bLifeMax;
527 	pSoldier->bBleeding = 0;
528 	pSoldier->bBreath = pSoldier->bBreathMax = 100;
529 	pSoldier->sBreathRed = 0;
530 	if( gpAR->pRobotCell)
531 	{
532 		UpdateRobotControllerGivenRobot( gpAR->pRobotCell->pSoldier );
533 	}
534 	//gpAR->fUnlimitedAmmo = TRUE;
535 }
536 
537 
538 //Now assign the pSoldier->ubGroupIDs for the enemies, so we know where to remove them.  Start with
539 //stationary groups first.
AssociateEnemiesWithStrategicGroups(void)540 static void AssociateEnemiesWithStrategicGroups(void)
541 {
542 	SECTORINFO *pSector;
543 	UINT8 ubNumAdmins, ubNumTroops, ubNumElites;
544 	UINT8 ubNumElitesInGroup, ubNumTroopsInGroup, ubNumAdminsInGroup;
545 
546 	if( gubEnemyEncounterCode == CREATURE_ATTACK_CODE )
547 		return;
548 
549 	pSector = &SectorInfo[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ];
550 
551 	//grab the number of each type in the stationary sector
552 	ubNumAdmins = pSector->ubNumAdmins;
553 	ubNumTroops = pSector->ubNumTroops;
554 	ubNumElites = pSector->ubNumElites;
555 
556 	//Now go through our enemies in the autoresolve array, and assign the ubGroupID to the soldier
557 	//Stationary groups have a group ID of 0
558 	FOR_EACH_AR_ENEMY(i)
559 	{
560 		if (i->uiFlags & CELL_ELITE && ubNumElites != 0)
561 		{
562 			--ubNumElites;
563 		}
564 		else if (i->uiFlags & CELL_TROOP && ubNumTroops != 0)
565 		{
566 			--ubNumTroops;
567 		}
568 		else if (i->uiFlags & CELL_ADMIN && ubNumAdmins != 0)
569 		{
570 			--ubNumAdmins;
571 		}
572 		else
573 		{
574 			continue;
575 		}
576 		i->uiFlags             |= CELL_ASSIGNED;
577 		i->pSoldier->ubGroupID  = 0;
578 	}
579 
580 	ubNumAdmins = gpAR->ubAdmins - pSector->ubNumAdmins;
581 	ubNumTroops = gpAR->ubTroops - pSector->ubNumTroops;
582 	ubNumElites = gpAR->ubElites - pSector->ubNumElites;
583 
584 	if( !ubNumElites && !ubNumTroops && !ubNumAdmins )
585 	{ //All troops accounted for.
586 		return;
587 	}
588 
589 	//Now assign the rest of the soldiers to groups
590 	CFOR_EACH_ENEMY_GROUP(pGroup)
591 	{
592 		if (pGroup->ubSectorX == gpAR->ubSectorX &&
593 				pGroup->ubSectorY == gpAR->ubSectorY)
594 		{
595 			ubNumElitesInGroup = pGroup->pEnemyGroup->ubNumElites;
596 			ubNumTroopsInGroup = pGroup->pEnemyGroup->ubNumTroops;
597 			ubNumAdminsInGroup = pGroup->pEnemyGroup->ubNumAdmins;
598 			FOR_EACH_AR_ENEMY(i)
599 			{
600 				if (i->uiFlags & CELL_ASSIGNED) continue;
601 
602 				if (ubNumElites != 0 && ubNumElitesInGroup != 0)
603 				{
604 					--ubNumElites;
605 					--ubNumElitesInGroup;
606 				}
607 				else if (ubNumTroops != 0 && ubNumTroopsInGroup != 0)
608 				{
609 					--ubNumTroops;
610 					--ubNumTroopsInGroup;
611 				}
612 				else if (ubNumAdmins != 0 && ubNumAdminsInGroup != 0)
613 				{
614 					--ubNumAdmins;
615 					--ubNumAdminsInGroup;
616 				}
617 				else
618 				{
619 					continue;
620 				}
621 				i->uiFlags             |= CELL_ASSIGNED;
622 				i->pSoldier->ubGroupID  = pGroup->ubGroupID;
623 			}
624 		}
625 	}
626 }
627 
628 
629 static void MercCellMouseClickCallback(MOUSE_REGION* reg, INT32 reason);
630 static void MercCellMouseMoveCallback(MOUSE_REGION* reg, INT32 reason);
631 
632 
CalculateSoldierCells(BOOLEAN fReset)633 static void CalculateSoldierCells(BOOLEAN fReset)
634 {
635 	INT32 i, x, y;
636 	INT32 index, iStartY, iTop, gapStartRow;
637 	INT32 iMaxTeamSize;
638 
639 	gpAR->ubAliveMercs = gpAR->ubMercs;
640 	gpAR->ubAliveCivs = gpAR->ubCivs;
641 	gpAR->ubAliveEnemies = gpAR->ubEnemies;
642 
643 	iMaxTeamSize = MAX( gpAR->ubMercs + gpAR->ubCivs, gpAR->ubEnemies );
644 
645 	if( iMaxTeamSize > 12 )
646 	{
647 		gpAR->ubTimeModifierPercentage = (UINT8)(118 - iMaxTeamSize*1.5);
648 	}
649 	else
650 	{
651 		gpAR->ubTimeModifierPercentage = 100;
652 	}
653 	gpAR->uiTimeSlice = gpAR->uiTimeSlice * gpAR->ubTimeModifierPercentage / 100;
654 
655 	iTop = (SCREEN_HEIGHT - gpAR->rect.h) / 2;
656 	if( iTop > 120 )
657 		iTop -= 40;
658 
659 	if( gpAR->ubMercs )
660 	{
661 		iStartY = iTop + (gpAR->rect.h - ((gpAR->ubMercRows + gpAR->ubCivRows) * 47 + 7)) / 2 + 6;
662 		y = gpAR->ubMercRows;
663 		x = gpAR->ubMercCols;
664 		i = gpAR->ubMercs;
665 		gapStartRow = gpAR->ubMercRows - gpAR->ubMercRows * gpAR->ubMercCols + gpAR->ubMercs;
666 		for( x = 0; x < gpAR->ubMercCols; x++ ) for( y = 0; i && y < gpAR->ubMercRows; y++, i-- )
667 		{
668 			index = y * gpAR->ubMercCols + gpAR->ubMercCols - x - 1;
669 			if( y >= gapStartRow )
670 				index -= y - gapStartRow + 1;
671 			Assert( index >= 0 && index < gpAR->ubMercs );
672 			gpMercs[ index ].xp = gpAR->sCenterStartX + 3 - 55*(x+1);
673 			gpMercs[ index ].yp = iStartY + y*47;
674 			gpMercs[ index ].uiFlags = CELL_MERC;
675 			if( AM_AN_EPC( gpMercs[ index ].pSoldier ) )
676 			{
677 				if( AM_A_ROBOT( gpMercs[ index ].pSoldier ) )
678 				{ //treat robot as a merc for the purpose of combat.
679 					gpMercs[ index ].uiFlags |= CELL_ROBOT;
680 				}
681 				else
682 				{
683 					gpMercs[ index ].uiFlags |= CELL_EPC;
684 				}
685 			}
686 			SOLDIERCELL& c = gpMercs[index];
687 			c.pRegion = new MouseRegion(c.xp, c.yp, 50, 44, MSYS_PRIORITY_HIGH, 0, MercCellMouseMoveCallback, MercCellMouseClickCallback);
688 			if( fReset )
689 				RefreshMerc( gpMercs[ index ].pSoldier );
690 			if( !gpMercs[ index ].pSoldier->bLife )
691 				gpAR->ubAliveMercs--;
692 		}
693 	}
694 	if( gpAR->ubCivs )
695 	{
696 		iStartY = iTop + (gpAR->rect.h - ((gpAR->ubMercRows + gpAR->ubCivRows) * 47 + 7)) / 2 + gpAR->ubMercRows * 47 + 5;
697 		y = gpAR->ubCivRows;
698 		x = gpAR->ubCivCols;
699 		i = gpAR->ubCivs;
700 		gapStartRow = gpAR->ubCivRows - gpAR->ubCivRows * gpAR->ubCivCols + gpAR->ubCivs;
701 		for( x = 0; x < gpAR->ubCivCols; x++ ) for( y = 0; i && y < gpAR->ubCivRows; y++, i-- )
702 		{
703 			index = y * gpAR->ubCivCols + gpAR->ubCivCols - x - 1;
704 			if( y >= gapStartRow )
705 				index -= y - gapStartRow + 1;
706 			Assert( index >= 0 && index < gpAR->ubCivs );
707 			gpCivs[ index ].xp = gpAR->sCenterStartX + 3 - 55*(x+1);
708 			gpCivs[ index ].yp = iStartY + y*47;
709 			gpCivs[ index ].uiFlags |= CELL_MILITIA;
710 		}
711 	}
712 	if( gpAR->ubEnemies )
713 	{
714 		iStartY = iTop + (gpAR->rect.h - (gpAR->ubEnemyRows * 47 + 7)) / 2 + 5;
715 		y = gpAR->ubEnemyRows;
716 		x = gpAR->ubEnemyCols;
717 		i = gpAR->ubEnemies;
718 		gapStartRow = gpAR->ubEnemyRows - gpAR->ubEnemyRows * gpAR->ubEnemyCols + gpAR->ubEnemies;
719 		for( x = 0; x < gpAR->ubEnemyCols; x++ ) for( y = 0; i && y < gpAR->ubEnemyRows; y++, i-- )
720 		{
721 			index = y * gpAR->ubEnemyCols + x;
722 			if( y > gapStartRow )
723 				index -= y - gapStartRow;
724 			Assert( index >= 0 && index < gpAR->ubEnemies );
725 			gpEnemies[ index ].xp = (UINT16)(gpAR->sCenterStartX + 141 + 55*x);
726 			gpEnemies[ index ].yp = iStartY + y*47;
727 			if( gubEnemyEncounterCode != CREATURE_ATTACK_CODE )
728 			{
729 				if( index < gpAR->ubElites )
730 					gpEnemies[ index ].uiFlags = CELL_ELITE;
731 				else if( index < gpAR->ubElites + gpAR->ubTroops )
732 					gpEnemies[ index ].uiFlags = CELL_TROOP;
733 				else
734 					gpEnemies[ index ].uiFlags = CELL_ADMIN;
735 			}
736 			else
737 			{
738 				if( index < gpAR->ubAFCreatures )
739 					gpEnemies[ index ].uiFlags = CELL_AF_CREATURE;
740 				else if( index < gpAR->ubAMCreatures + gpAR->ubAFCreatures )
741 					gpEnemies[ index ].uiFlags = CELL_AM_CREATURE;
742 				else if( index < gpAR->ubYFCreatures + gpAR->ubAMCreatures + gpAR->ubAFCreatures )
743 					gpEnemies[ index ].uiFlags = CELL_YF_CREATURE;
744 				else
745 					gpEnemies[ index ].uiFlags = CELL_YM_CREATURE;
746 			}
747 		}
748 	}
749 }
750 
751 
752 static void DrawDebugText(SOLDIERCELL* pCell);
753 static void RenderSoldierCellBars(SOLDIERCELL* pCell);
754 static void RenderSoldierCellHealth(SOLDIERCELL* pCell);
755 
756 
RenderSoldierCell(SOLDIERCELL * const c)757 static void RenderSoldierCell(SOLDIERCELL* const c)
758 {
759 	SGPVSurface* const buf = FRAME_BUFFER;
760 	INT16        const dx  = c->xp;
761 	INT16        const dy  = c->yp;
762 
763 	INT32       x = dx + 3;
764 	INT32 const y = dy + 3;
765 	if (c->uiFlags & CELL_MERC)
766 	{
767 		ColorFillVideoSurfaceArea(buf, dx + 36, dy + 2, dx + 44, dy + 30, 0);
768 		BltVideoObject(buf, gpAR->iPanelImages, MERC_PANEL, dx, dy);
769 		RenderSoldierCellBars(c);
770 	}
771 	else
772 	{
773 		BltVideoObject(buf, gpAR->iPanelImages, OTHER_PANEL, dx, dy);
774 		x += 6;
775 	}
776 
777 	if (c->pSoldier->bLife == 0)
778 	{
779 		UINT16 const skull = c->uiFlags & CELL_CREATURE ? CREATURE_SKULL : HUMAN_SKULL;
780 		BltVideoObject(buf, gpAR->iFaces, skull, x, y);
781 	}
782 	else
783 	{
784 		if (c->uiFlags & CELL_HITBYATTACKER)
785 		{
786 			ColorFillVideoSurfaceArea(buf, x, y, x + 30, y + 26, 65535);
787 		}
788 		else
789 		{
790 			size_t shade = 0;
791 			if (c->uiFlags & CELL_HITLASTFRAME)
792 			{
793 				ColorFillVideoSurfaceArea(buf, x, y, x + 30, y + 26, 0);
794 				shade = 1;
795 			}
796 			SGPVObject* const vo = c->uiVObjectID;
797 			vo->CurrentShade(shade);
798 			BltVideoObject(buf, vo, c->usIndex, x, y);
799 		}
800 
801 		if (c->pSoldier->bLife < OKLIFE && !(c->uiFlags & (CELL_HITBYATTACKER | CELL_HITLASTFRAME | CELL_CREATURE)))
802 		{ // Merc is unconcious (and not taking damage), so darken his portrait.
803 			buf->ShadowRect(x, y, x + 30, y + 26);
804 		}
805 	}
806 
807 	RenderSoldierCellHealth(c);
808 	DrawDebugText(c);
809 
810 	InvalidateRegion(dx, dy, dx + 50, dy + 44);
811 
812 	// Adjust flags accordingly
813 	if (c->uiFlags & CELL_HITBYATTACKER)
814 	{
815 		c->uiFlashTime  = GetJA2Clock() + 150;
816 		c->uiFlags     &= ~CELL_HITBYATTACKER;
817 		c->uiFlags     |= CELL_HITLASTFRAME | CELL_DIRTY;
818 	}
819 	else if (c->uiFlags & CELL_HITLASTFRAME)
820 	{
821 		if (c->uiFlashTime < GetJA2Clock()) c->uiFlags &= ~CELL_HITLASTFRAME;
822 		c->uiFlags |= CELL_DIRTY;
823 	}
824 	else if (!(c->uiFlags & CELL_RETREATING))
825 	{
826 		c->uiFlags &= ~CELL_DIRTY;
827 	}
828 }
829 
830 
RenderSoldierCellBars(SOLDIERCELL * pCell)831 static void RenderSoldierCellBars(SOLDIERCELL* pCell)
832 {
833 	INT32 iStartY;
834 	//HEALTH BAR
835 	if( !pCell->pSoldier->bLife )
836 		return;
837 	//yellow one for bleeding
838 	iStartY = pCell->yp + 29 - 25*pCell->pSoldier->bLifeMax/100;
839 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 107, 107, 57 ) ) );
840 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 222, 181, 115 ) ) );
841 	//pink one for bandaged.
842 	iStartY += 25*pCell->pSoldier->bBleeding/100;
843 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 156, 57, 57 ) ) );
844 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 222, 132, 132 ) ) );
845 	//red one for actual health
846 	iStartY = pCell->yp + 29 - 25*pCell->pSoldier->bLife/100;
847 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 107, 8, 8 ) ) );
848 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 206, 0, 0 ) ) );
849 	//BREATH BAR
850 	iStartY = pCell->yp + 29 - 25*pCell->pSoldier->bBreathMax/100;
851 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+41, iStartY, pCell->xp+42, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 8, 132 ) ) );
852 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+42, iStartY, pCell->xp+43, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 8, 107 ) ) );
853 	//MORALE BAR
854 	iStartY = pCell->yp + 29 - 25*pCell->pSoldier->bMorale/100;
855 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+45, iStartY, pCell->xp+46, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 156, 8 ) ) );
856 	ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+46, iStartY, pCell->xp+47, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 107, 8 ) ) );
857 }
858 
859 
BuildInterfaceBuffer(void)860 static void BuildInterfaceBuffer(void)
861 {
862 	SGPRect					ClipRect;
863 	SGPRect					DestRect;
864 	INT32						x,y;
865 
866 	//Setup the blitting clip regions, so we don't draw outside of the region (for excess panelling)
867 	gpAR->rect.x = (SCREEN_WIDTH  - gpAR->rect.w) / 2;
868 	gpAR->rect.y = (SCREEN_HEIGHT - gpAR->rect.h) / 2;
869 	if (gpAR->rect.y > 120) gpAR->rect.y -= 40;
870 
871 	DestRect.iLeft			= 0;
872 	DestRect.iTop				= 0;
873 	DestRect.iRight			= gpAR->rect.w;
874 	DestRect.iBottom		= gpAR->rect.h;
875 
876 	gpAR->iInterfaceBuffer = AddVideoSurface(gpAR->rect.w, gpAR->rect.h, PIXEL_DEPTH);
877 
878 	GetClippingRect( &ClipRect );
879 	SetClippingRect( &DestRect );
880 
881 	//Blit the back panels...
882 	for( y = DestRect.iTop; y < DestRect.iBottom; y += 40 )
883 	{
884 		for( x = DestRect.iLeft; x < DestRect.iRight; x += 50 )
885 		{
886 			BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, C_TEXTURE, x, y);
887 		}
888 	}
889 	//Blit the left and right edges
890 	for( y = DestRect.iTop; y < DestRect.iBottom; y += 40 )
891 	{
892 		x = DestRect.iLeft;
893 		BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, L_BORDER, x, y);
894 		x = DestRect.iRight - 3;
895 		BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, R_BORDER, x, y);
896 	}
897 	//Blit the top and bottom edges
898 	for( x = DestRect.iLeft; x < DestRect.iRight; x += 50 )
899 	{
900 		y = DestRect.iTop;
901 		BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, T_BORDER, x, y);
902 		y = DestRect.iBottom - 3;
903 		BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, B_BORDER, x, y);
904 	}
905 	//Blit the 4 corners
906 	BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, TL_BORDER, DestRect.iLeft, DestRect.iTop);
907 	BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, TR_BORDER, DestRect.iRight-10, DestRect.iTop);
908 	BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, BL_BORDER, DestRect.iLeft, DestRect.iBottom-9);
909 	BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, BR_BORDER, DestRect.iRight-10, DestRect.iBottom-9);
910 
911 	//Blit the center pieces
912 	x = gpAR->sCenterStartX - gpAR->rect.x;
913 	y = 0;
914 	//Top
915 	BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, TOP_MIDDLE, x, y);
916 	//Middle
917 	for (y = 40; y < gpAR->rect.h - 40; y += 40)
918 	{
919 		BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, AUTO_MIDDLE, x, y);
920 	}
921 	y = gpAR->rect.h - 40;
922 	BltVideoObject( gpAR->iInterfaceBuffer, gpAR->iPanelImages, BOT_MIDDLE, x, y);
923 
924 	SetClippingRect( &ClipRect );
925 }
926 
927 
VirtualSoldierDressWound(SOLDIERTYPE * pSoldier,SOLDIERTYPE * pVictim,OBJECTTYPE * pKit,INT16 sKitPts,INT16 sStatus)928 UINT32 VirtualSoldierDressWound(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pVictim, OBJECTTYPE* pKit, INT16 sKitPts, INT16 sStatus)
929 {
930 	UINT32 uiDressSkill, uiPossible, uiActual, uiMedcost, uiDeficiency, uiAvailAPs, uiUsedAPs;
931 	UINT8 bBelowOKlife, bPtsLeft;
932 
933 	if( pVictim->bBleeding < 1 )
934 		return 0;		// nothing to do, shouldn't have even been called!
935 	if ( pVictim->bLife == 0 )
936 		return 0;
937 
938 	// calculate wound-dressing skill (3x medical, 2x equip, 10x level, 1x dex)
939 	uiDressSkill = ( ( 3 * EffectiveMedical( pSoldier ) ) +		// medical knowledge
940 									( 2 * sStatus) + 													// state of medical kit
941 									(10 * EffectiveExpLevel( pSoldier ) ) +		// battle injury experience
942 									EffectiveDexterity( pSoldier ) )	/ 7;		// general "handiness"
943 
944 	// try to use every AP that the merc has left
945 	uiAvailAPs = pSoldier->bActionPoints;
946 
947 	// OK, If we are in real-time, use another value...
948 	if (!(gTacticalStatus.uiFlags & INCOMBAT))
949 	{	// Set to a value which looks good based on out tactical turns duration
950 		uiAvailAPs = RT_FIRST_AID_GAIN_MODIFIER;
951 	}
952 
953 	// calculate how much bandaging CAN be done this turn
954 	uiPossible = ( uiAvailAPs * uiDressSkill ) / 50;	// max rate is 2 * fullAPs
955 
956 	// if no healing is possible (insufficient APs or insufficient dressSkill)
957 	if (!uiPossible)
958 		return 0;
959 
960 	if (pSoldier->inv[0].usItem == MEDICKIT )		// using the GOOD medic stuff
961 		uiPossible += ( uiPossible / 2);			// add extra 50 %
962 
963 	uiActual = uiPossible;		// start by assuming maximum possible
964 
965 	// figure out how far below OKLIFE the victim is
966 	if (pVictim->bLife >= OKLIFE)
967 		bBelowOKlife = 0;
968 	else
969 		bBelowOKlife = OKLIFE - pVictim->bLife;
970 
971 	// figure out how many healing pts we need to stop dying (2x cost)
972 	uiDeficiency = (2 * bBelowOKlife );
973 
974 	// if, after that, the patient will still be bleeding
975 	if ( (pVictim->bBleeding - bBelowOKlife ) > 0)
976 	{ // then add how many healing pts we need to stop bleeding (1x cost)
977 		uiDeficiency += ( pVictim->bBleeding - bBelowOKlife );
978 	}
979 
980 	// now, make sure we weren't going to give too much
981 	if ( uiActual > uiDeficiency)	// if we were about to apply too much
982 		uiActual = uiDeficiency;	// reduce actual not to waste anything
983 
984 	// now make sure we HAVE that much
985 	if( pKit->usItem == MEDICKIT )
986 	{
987 		uiMedcost = uiActual / 2;		// cost is only half
988 		if ( uiMedcost == 0 && uiActual > 0)
989 			uiMedcost = 1;
990 		if ( uiMedcost > (UINT32)sKitPts )     		// if we can't afford this
991 		{
992 			uiMedcost = sKitPts;		// what CAN we afford?
993 			uiActual = uiMedcost * 2;		// give double this as aid
994 		}
995 	}
996 	else
997 	{
998 		uiMedcost = uiActual;
999 		if ( uiMedcost == 0 && uiActual > 0)
1000 			uiMedcost = 1;
1001 		if ( uiMedcost > (UINT32)sKitPts)		// can't afford it
1002 			uiMedcost = uiActual = sKitPts;   	// recalc cost AND aid
1003 	}
1004 
1005 	bPtsLeft = (INT8)uiActual;
1006 	// heal real life points first (if below OKLIFE) because we don't want the
1007 	// patient still DYING if bandages run out, or medic is disabled/distracted!
1008 	// NOTE: Dressing wounds for life below OKLIFE now costs 2 pts/life point!
1009 	if ( bPtsLeft && pVictim->bLife < OKLIFE)
1010 	{
1011 		// if we have enough points to bring him all the way to OKLIFE this turn
1012 		if ( bPtsLeft >= (2 * bBelowOKlife ) )
1013 		{ // raise life to OKLIFE
1014 			pVictim->bLife = OKLIFE;
1015 			// reduce bleeding by the same number of life points healed up
1016 			pVictim->bBleeding -= bBelowOKlife;
1017 			// use up appropriate # of actual healing points
1018 			bPtsLeft -= (2 * bBelowOKlife);
1019 		}
1020 		else
1021 		{
1022 			pVictim->bLife += ( bPtsLeft / 2);
1023 			pVictim->bBleeding -= ( bPtsLeft / 2);
1024 			bPtsLeft = bPtsLeft % 2;	// if ptsLeft was odd, ptsLeft = 1
1025 		}
1026 
1027 		// this should never happen any more, but make sure bleeding not negative
1028 		if (pVictim->bBleeding < 0)
1029 			pVictim->bBleeding = 0;
1030 
1031 		// if this healing brought the patient out of the worst of it, cancel dying
1032 		if (pVictim->bLife >= OKLIFE )
1033 		{ // turn off merc QUOTE flags
1034 			pVictim->fDyingComment = FALSE;
1035 		}
1036 
1037 		if ( pVictim->bBleeding <= MIN_BLEEDING_THRESHOLD )
1038 		{
1039 			pVictim->fWarnedAboutBleeding = FALSE;
1040 		}
1041 	}
1042 
1043 	// if any healing points remain, apply that to any remaining bleeding (1/1)
1044 	// DON'T spend any APs/kit pts to cure bleeding until merc is no longer dying
1045 	//if ( bPtsLeft && pVictim->bBleeding && !pVictim->dying)
1046 	if ( bPtsLeft && pVictim->bBleeding )
1047 	{ // if we have enough points to bandage all remaining bleeding this turn
1048 		if (bPtsLeft >= pVictim->bBleeding )
1049 		{
1050 			bPtsLeft -= pVictim->bBleeding;
1051 			pVictim->bBleeding = 0;
1052 		}
1053 		else		// bandage what we can
1054 		{
1055 			pVictim->bBleeding -= bPtsLeft;
1056 			bPtsLeft = 0;
1057 		}
1058 	}
1059 	// if there are any ptsLeft now, then we didn't actually get to use them
1060 	uiActual -= bPtsLeft;
1061 
1062 	// usedAPs equals (actionPts) * (%of possible points actually used)
1063 	uiUsedAPs = ( uiActual * uiAvailAPs ) / uiPossible;
1064 
1065 	if (pSoldier->inv[0].usItem == MEDICKIT)	// using the GOOD medic stuff
1066 		uiUsedAPs = ( uiUsedAPs * 2) / 3;	// reverse 50% bonus by taking 2/3rds
1067 
1068 	if ( uiActual / 2)
1069 		// MEDICAL GAIN (actual / 2):  Helped someone by giving first aid
1070 		StatChange(*pSoldier, MEDICALAMT, uiActual / 2, FROM_SUCCESS);
1071 
1072 	if ( uiActual / 4)
1073 		// DEXTERITY GAIN (actual / 4):  Helped someone by giving first aid
1074 		StatChange(*pSoldier, DEXTAMT, uiActual / 4, FROM_SUCCESS);
1075 
1076 	return uiMedcost;
1077 }
1078 
1079 
FindMedicalKit(void)1080 static OBJECTTYPE* FindMedicalKit(void)
1081 {
1082 	FOR_EACH_AR_MERC(i)
1083 	{
1084 		SOLDIERTYPE& s = *i->pSoldier;
1085 		INT32 const slot = FindObjClass(&s, IC_MEDKIT);
1086 		if (slot == NO_SLOT) continue;
1087 		return &s.inv[slot];
1088 	}
1089 	return 0;
1090 }
1091 
1092 
1093 static void AutoBandageFinishedCallback(MessageBoxReturnValue);
1094 
1095 
AutoBandageMercs(void)1096 static void AutoBandageMercs(void)
1097 {
1098 	OBJECTTYPE* kit = 0;
1099 
1100 	// Do we have any doctors?  If so, bandage selves first.
1101 	UINT32       max_points_used = 0; // XXX write-only, should probably be assigned to parallel_points_used
1102 	SOLDIERTYPE* best            = gpMercs[0].pSoldier;
1103 	FOR_EACH_AR_MERC(i)
1104 	{
1105 		SOLDIERTYPE& s = *i->pSoldier;
1106 		if (s.bLife < OKLIFE) continue;
1107 		if (s.bCollapsed)     continue;
1108 		if (s.bMedical == 0)  continue;
1109 
1110 		// Find the best rated doctor to do all of the further bandaging.
1111 		if (best->bMedical < s.bMedical) best = &s;
1112 
1113 		INT8 slot = FindObjClass(&s, IC_MEDKIT);
1114 		if (slot == NO_SLOT) continue;
1115 
1116 		// Bandage self first!
1117 		UINT32 curr_points_used = 0;
1118 		INT8   cnt              = 0;
1119 		while (s.bBleeding)
1120 		{
1121 			kit = &s.inv[slot];
1122 			UINT16 const kit_pts = TotalPoints(kit);
1123 			if (kit_pts == 0)
1124 			{ // Attempt to find another kit before stopping
1125 				slot = FindObjClass(&s, IC_MEDKIT);
1126 				if (slot == NO_SLOT) break;
1127 				continue;
1128 			}
1129 			UINT32 const points_used = VirtualSoldierDressWound(&s, &s, kit, kit_pts, kit_pts);
1130 			UseKitPoints(*kit, points_used, s);
1131 			curr_points_used += points_used;
1132 			if (++cnt > 50) break;
1133 		}
1134 		if (max_points_used < curr_points_used) max_points_used = curr_points_used;
1135 		if (!kit) break;
1136 	}
1137 
1138 	UINT32 parallel_points_used = 0;
1139 	bool   complete             = true;
1140 	FOR_EACH_AR_MERC(i)
1141 	{
1142 		SOLDIERTYPE& s = *i->pSoldier;
1143 		while (s.bBleeding != 0 && s.bLife != 0)
1144 		{ // This merc needs medical attention
1145 			if (!kit)
1146 			{
1147 				kit = FindMedicalKit();
1148 				if (!kit)
1149 				{
1150 					complete = false;
1151 					break;
1152 				}
1153 			}
1154 			UINT16 const kit_pts = TotalPoints(kit);
1155 			if (kit_pts == 0)
1156 			{
1157 				kit = 0;
1158 				continue;
1159 			}
1160 			UINT32 const points_used = VirtualSoldierDressWound(best, &s, kit, kit_pts, kit_pts);
1161 			UseKitPoints(*kit, points_used, s);
1162 			parallel_points_used += points_used;
1163 		}
1164 	}
1165 	ST::string msg = complete ? gzLateLocalizedString[STR_LATE_13] : gzLateLocalizedString[STR_LATE_10];
1166 	DoScreenIndependantMessageBox(msg, MSG_BOX_FLAG_OK, AutoBandageFinishedCallback);
1167 
1168 	gpAR->uiTotalElapsedBattleTimeInMilliseconds += parallel_points_used * 200;
1169 }
1170 
1171 
RenderAutoResolve(void)1172 static void RenderAutoResolve(void)
1173 {
1174 	INT32 xp, yp;
1175 	ST::string str;
1176 	UINT8 ubGood, ubBad;
1177 
1178 	if (gpAR->fShowInterface)
1179 	{ //After expanding the window, we now show the interface
1180 		if( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS && !gpAR->fPendingSurrender )
1181 		{
1182 			for (INT32 i = 0 ; i < DONEWIN_BUTTON; ++i)
1183 				ShowButton( gpAR->iButton[ i ] );
1184 			HideButton( gpAR->iButton[ BANDAGE_BUTTON ] );
1185 			HideButton( gpAR->iButton[ YES_BUTTON ] );
1186 			HideButton( gpAR->iButton[ NO_BUTTON ] );
1187 			gpAR->fShowInterface = FALSE;
1188 		}
1189 		else if( gpAR->ubBattleStatus == BATTLE_VICTORY )
1190 		{
1191 			ShowButton( gpAR->iButton[ DONEWIN_BUTTON ] );
1192 			ShowButton( gpAR->iButton[ BANDAGE_BUTTON ] );
1193 		}
1194 		else
1195 		{
1196 			ShowButton( gpAR->iButton[ DONELOSE_BUTTON ] );
1197 		}
1198 	}
1199 
1200 	if( !gpAR->fRenderAutoResolve && !gpAR->fDebugInfo )
1201 	{ //update the dirty cells only
1202 		FOR_EACH_AR_MERC(i)
1203 		{
1204 			if (!(i->uiFlags & CELL_DIRTY)) continue;
1205 			RenderSoldierCell(i);
1206 		}
1207 		FOR_EACH_AR_CIV(i)
1208 		{
1209 			if (!(i->uiFlags & CELL_DIRTY)) continue;
1210 			RenderSoldierCell(i);
1211 		}
1212 		FOR_EACH_AR_ENEMY(i)
1213 		{
1214 			if (!(i->uiFlags & CELL_DIRTY)) continue;
1215 			RenderSoldierCell(i);
1216 		}
1217 		return;
1218 	}
1219 	gpAR->fRenderAutoResolve = FALSE;
1220 
1221 	BltVideoSurface(FRAME_BUFFER, gpAR->iInterfaceBuffer, gpAR->rect.x, gpAR->rect.y, 0);
1222 
1223 	FOR_EACH_AR_MERC(i)
1224 	{
1225 		RenderSoldierCell(i);
1226 	}
1227 	FOR_EACH_AR_CIV(i)
1228 	{
1229 		RenderSoldierCell(i);
1230 	}
1231 	FOR_EACH_AR_ENEMY(i)
1232 	{
1233 		RenderSoldierCell(i);
1234 	}
1235 
1236 	//Render the titles
1237 	SetFontAttributes(FONT10ARIALBOLD, FONT_WHITE);
1238 
1239 	ST::string EncounterType;
1240 	switch( gubEnemyEncounterCode )
1241 	{
1242 		case ENEMY_ENCOUNTER_CODE:
1243 			EncounterType = gpStrategicString[STR_AR_ENCOUNTER_HEADER];
1244 			break;
1245 		case ENEMY_INVASION_CODE:
1246 		case CREATURE_ATTACK_CODE:
1247 			EncounterType = gpStrategicString[STR_AR_DEFEND_HEADER];
1248 			break;
1249 	}
1250 
1251 	xp = gpAR->sCenterStartX + 70 - StringPixLength(EncounterType, FONT10ARIALBOLD) / 2;
1252 	yp = gpAR->rect.y + 15;
1253 	MPrint(xp, yp, EncounterType);
1254 
1255 	SetFontAttributes(FONT10ARIAL, FONT_GRAY2);
1256 
1257 	str = GetSectorIDString(gpAR->ubSectorX, gpAR->ubSectorY, 0, TRUE);
1258 	xp = gpAR->sCenterStartX + 70 - StringPixLength( str, FONT10ARIAL )/2;
1259 	yp += 11;
1260 	MPrint(xp, yp, str);
1261 
1262 	//Display the remaining forces
1263 	ubGood = (UINT8)(gpAR->ubAliveMercs + gpAR->ubAliveCivs);
1264 	ubBad = gpAR->ubAliveEnemies;
1265 	str = st_format_printf(gzLateLocalizedString[STR_LATE_17], ubGood, ubBad);
1266 
1267 	SetFont( FONT14ARIAL );
1268 	if( ubGood * 3 <= ubBad * 2 )
1269 	{
1270 		SetFontForeground( FONT_LTRED );
1271 	}
1272 	else if( ubGood * 2 >= ubBad * 3 )
1273 	{
1274 		SetFontForeground( FONT_LTGREEN );
1275 	}
1276 	else
1277 	{
1278 		SetFontForeground( FONT_YELLOW );
1279 	}
1280 
1281 	xp = gpAR->sCenterStartX + 70 - StringPixLength( str, FONT14ARIAL )/2;
1282 	yp += 11;
1283 	MPrint(xp, yp, str);
1284 
1285 	if( gpAR->fPendingSurrender )
1286 	{
1287 		DisplayWrappedString(gpAR->sCenterStartX + 16, 230 + gpAR->bVerticalOffset, 108, 2, FONT10ARIAL, FONT_YELLOW, gpStrategicString[STR_ENEMY_SURRENDER_OFFER], FONT_BLACK, LEFT_JUSTIFIED);
1288 	}
1289 
1290 	if( gpAR->ubBattleStatus != BATTLE_IN_PROGRESS )
1291 	{
1292 		// Handle merc morale, Global loyalty, and change of sector control
1293 		if( !gpAR->fMoraleEventsHandled )
1294 		{
1295 			gpAR->uiTotalElapsedBattleTimeInMilliseconds *= 3;
1296 			gpAR->fMoraleEventsHandled = TRUE;
1297 			if (!CheckFact(FACT_FIRST_BATTLE_FOUGHT, 0))
1298 			{
1299 				// this was the first battle against the army
1300 				SetFactTrue( FACT_FIRST_BATTLE_FOUGHT );
1301 				if ( gpAR->ubBattleStatus == BATTLE_VICTORY)
1302 				{
1303 					SetFactTrue( FACT_FIRST_BATTLE_WON );
1304 				}
1305 				SetTheFirstBattleSector( ( INT16 ) (gpAR->ubSectorX + gpAR->ubSectorY * MAP_WORLD_X ) );
1306 				HandleFirstBattleEndingWhileInTown( gpAR->ubSectorX, gpAR->ubSectorY, 0, TRUE );
1307 			}
1308 
1309 			switch( gpAR->ubBattleStatus )
1310 			{
1311 				case BATTLE_VICTORY:
1312 					HandleMoraleEvent( NULL, MORALE_BATTLE_WON, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
1313 					HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_WON, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
1314 
1315 					SetThisSectorAsPlayerControlled( gpAR->ubSectorX, gpAR->ubSectorY, 0, TRUE );
1316 
1317 					SetMusicMode( MUSIC_TACTICAL_VICTORY );
1318 					LogBattleResults( LOG_VICTORY );
1319 					break;
1320 
1321 				case BATTLE_SURRENDERED:
1322 				case BATTLE_CAPTURED:
1323 					FOR_EACH_IN_TEAM(i, OUR_TEAM)
1324 					{
1325 						SOLDIERTYPE& s = *i;
1326 						if (s.bLife != 0 && !IsMechanical(s))
1327 						{ //Merc is alive and not a vehicle or robot
1328 							if (PlayerMercInvolvedInThisCombat(s))
1329 							{
1330 								// This morale event is PER INDIVIDUAL SOLDIER
1331 								HandleMoraleEvent(&s, MORALE_MERC_CAPTURED, gpAR->ubSectorX, gpAR->ubSectorY, 0);
1332 							}
1333 						}
1334 					}
1335 					HandleMoraleEvent( NULL, MORALE_HEARD_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
1336 					HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
1337 
1338 					SetMusicMode( MUSIC_TACTICAL_DEFEAT );
1339 					gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
1340 					break;
1341 				case BATTLE_DEFEAT:
1342 					HandleMoraleEvent( NULL, MORALE_HEARD_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
1343 					HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
1344 					if( gubEnemyEncounterCode != CREATURE_ATTACK_CODE )
1345 					{
1346 						gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
1347 					}
1348 					else
1349 					{
1350 						gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
1351 						gsCiviliansEatenByMonsters = gpAR->ubAliveEnemies;
1352 					}
1353 					SetMusicMode( MUSIC_TACTICAL_DEFEAT );
1354 					LogBattleResults( LOG_DEFEAT );
1355 					break;
1356 
1357 				case BATTLE_RETREAT:
1358 
1359 					//Tack on 5 minutes for retreat.
1360 					gpAR->uiTotalElapsedBattleTimeInMilliseconds += 300000;
1361 
1362 					HandleLoyaltyImplicationsOfMercRetreat( RETREAT_AUTORESOLVE, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
1363 					if( gubEnemyEncounterCode != CREATURE_ATTACK_CODE )
1364 					{
1365 						gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
1366 					}
1367 					else if( gpAR->ubAliveEnemies )
1368 					{
1369 						gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
1370 						gsCiviliansEatenByMonsters = gpAR->ubAliveEnemies;
1371 					}
1372 					break;
1373 			}
1374 		}
1375 		//Render the end battle condition.
1376 			ST::string BattleResult;
1377 			switch( gpAR->ubBattleStatus )
1378 			{
1379 				case BATTLE_VICTORY:
1380 					SetFontForeground( FONT_LTGREEN );
1381 					BattleResult = gpStrategicString[STR_AR_OVER_VICTORY];
1382 					break;
1383 				case BATTLE_SURRENDERED:
1384 				case BATTLE_CAPTURED:
1385 					if( gpAR->ubBattleStatus == BATTLE_SURRENDERED )
1386 					{
1387 						BattleResult = gpStrategicString[STR_AR_OVER_SURRENDERED];
1388 					}
1389 					else
1390 					{
1391 						DisplayWrappedString(gpAR->sCenterStartX + 16, 310, 108, 2, FONT10ARIAL, FONT_YELLOW, gpStrategicString[STR_ENEMY_CAPTURED], FONT_BLACK, LEFT_JUSTIFIED);
1392 						BattleResult = gpStrategicString[STR_AR_OVER_CAPTURED];
1393 					}
1394 					SetFontForeground( FONT_RED );
1395 					break;
1396 				case BATTLE_DEFEAT:
1397 					SetFontForeground( FONT_RED );
1398 					BattleResult = gpStrategicString[STR_AR_OVER_DEFEAT];
1399 					break;
1400 				case BATTLE_RETREAT:
1401 					SetFontForeground( FONT_YELLOW );
1402 					BattleResult = gpStrategicString[STR_AR_OVER_RETREATED];
1403 					break;
1404 			}
1405 			//Render the results of the battle.
1406 			SetFont( BLOCKFONT2 );
1407 			xp = gpAR->sCenterStartX + 12;
1408 			yp = STD_SCREEN_Y + 218 + gpAR->bVerticalOffset;
1409 			BltVideoObject( FRAME_BUFFER, gpAR->iIndent, 0, xp, yp);
1410 			xp = gpAR->sCenterStartX + 70 - StringPixLength(BattleResult, BLOCKFONT2) / 2;
1411 			yp = STD_SCREEN_Y + 227 + gpAR->bVerticalOffset;
1412 			MPrint(xp, yp, BattleResult);
1413 
1414 			//Render the total battle time elapsed.
1415 			SetFont( FONT10ARIAL );
1416 			str = ST::format("{}:  {}{} {02d}{}",
1417 				gpStrategicString[ STR_AR_TIME_ELAPSED ],
1418 				gpAR->uiTotalElapsedBattleTimeInMilliseconds/60000,
1419 				gsTimeStrings[1],
1420 				gpAR->uiTotalElapsedBattleTimeInMilliseconds % 60000 / 1000,
1421 				gsTimeStrings[2]);
1422 			xp = gpAR->sCenterStartX + 70 - StringPixLength( str, FONT10ARIAL )/2;
1423 			yp = STD_SCREEN_Y + 290 + gpAR->bVerticalOffset;
1424 			SetFontForeground( FONT_YELLOW );
1425 			MPrint(xp, yp, str);
1426 	}
1427 
1428 	MarkButtonsDirty();
1429 	InvalidateScreen();
1430 }
1431 
1432 
MakeButton(UINT idx,INT16 x,INT16 y,GUI_CALLBACK click,BOOLEAN hide,const ST::string & text)1433 static void MakeButton(UINT idx, INT16 x, INT16 y, GUI_CALLBACK click, BOOLEAN hide, const ST::string& text)
1434 {
1435 	GUIButtonRef const btn = QuickCreateButton(gpAR->iButtonImage[idx], x, y, MSYS_PRIORITY_HIGH, click);
1436 	gpAR->iButton[idx] = btn;
1437 	if (text != NULL) btn->SpecifyGeneralTextAttributes(text, BLOCKFONT2, 169, FONT_NEARBLACK);
1438 	if (hide) btn->Hide();
1439 }
1440 
1441 
MakeEnemyTroops(SOLDIERCELL * cell,size_t n,AUTORESOLVE_STRUCT * ar,SoldierClass sc,UINT16 face,const ST::string & name)1442 static SOLDIERCELL* MakeEnemyTroops(SOLDIERCELL* cell, size_t n, AUTORESOLVE_STRUCT* ar, SoldierClass sc, UINT16 face, const ST::string& name)
1443 {
1444 	for (; n != 0; --n, ++cell)
1445 	{
1446 		SOLDIERTYPE* const s = TacticalCreateEnemySoldier(sc);
1447 		cell->pSoldier       = s;
1448 		cell->uiVObjectID    = ar->iFaces;
1449 		// Only elite troops have women
1450 		cell->usIndex        = s->ubBodyType == REGFEMALE ? ELITEF_FACE : face;
1451 		s->sSectorX          = ar->ubSectorX;
1452 		s->sSectorY          = ar->ubSectorY;
1453 		s->name              = name;
1454 	}
1455 	return cell;
1456 }
1457 
1458 
MakeCreatures(SOLDIERCELL * cell,size_t n,AUTORESOLVE_STRUCT * const ar,INT8 const body_type,UINT16 const face)1459 static SOLDIERCELL* MakeCreatures(SOLDIERCELL* cell, size_t n, AUTORESOLVE_STRUCT* const ar, INT8 const body_type, UINT16 const face)
1460 {
1461 	for (; n != 0; --n, ++cell)
1462 	{
1463 		SOLDIERTYPE* const s = TacticalCreateCreature(body_type);
1464 		cell->pSoldier       = s;
1465 		cell->uiVObjectID    = ar->iFaces;
1466 		cell->usIndex        = face;
1467 		s->sSectorX          = ar->ubSectorX;
1468 		s->sSectorY          = ar->ubSectorY;
1469 		s->name              = gpStrategicString[STR_AR_CREATURE_NAME];
1470 	}
1471 	return cell;
1472 }
1473 
1474 
1475 static void AcceptSurrenderCallback(GUI_BUTTON* btn, INT32 reason);
1476 static void BandageButtonCallback(GUI_BUTTON* btn, INT32 reason);
1477 static void DoneButtonCallback(GUI_BUTTON* btn, INT32 reason);
1478 static void FastButtonCallback(GUI_BUTTON* btn, INT32 reason);
1479 static void FinishButtonCallback(GUI_BUTTON* btn, INT32 reason);
1480 static void PauseButtonCallback(GUI_BUTTON* btn, INT32 reason);
1481 static void PlayButtonCallback(GUI_BUTTON* btn, INT32 reason);
1482 static void RejectSurrenderCallback(GUI_BUTTON* btn, INT32 reason);
1483 static void RetreatButtonCallback(GUI_BUTTON* btn, INT32 reason);
1484 
1485 
CreateAutoResolveInterface(void)1486 static void CreateAutoResolveInterface(void)
1487 {
1488 	AUTORESOLVE_STRUCT* const ar = gpAR;
1489 
1490 	// Setup new autoresolve blanket interface.
1491 	MSYS_DefineRegion(&ar->AutoResolveRegion, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MSYS_PRIORITY_HIGH - 1, 0, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK);
1492 	ar->fRenderAutoResolve = TRUE;
1493 	ar->fExitAutoResolve   = FALSE;
1494 
1495 	//Load the general panel image pieces, to be combined to make the dynamically sized window.
1496 	ar->iPanelImages = AddVideoObjectFromFile(INTERFACEDIR "/autoresolve.sti");
1497 
1498 	// Load the button images file, and assign it to the first button.
1499 	BUTTON_PICS* const btn_pics = LoadButtonImage(INTERFACEDIR "/autobtns.sti", 0, 7);
1500 	ar->iButtonImage[PAUSE_BUTTON]    = btn_pics;
1501 	// Have the other buttons hook into the first button containing the images.
1502 	ar->iButtonImage[PLAY_BUTTON]     = UseLoadedButtonImage(btn_pics,  1,  8);
1503 	ar->iButtonImage[FAST_BUTTON]     = UseLoadedButtonImage(btn_pics,  2,  9);
1504 	ar->iButtonImage[FINISH_BUTTON]   = UseLoadedButtonImage(btn_pics,  3, 10);
1505 	ar->iButtonImage[YES_BUTTON]      = UseLoadedButtonImage(btn_pics,  4, 11);
1506 	ar->iButtonImage[NO_BUTTON]       = UseLoadedButtonImage(btn_pics,  5, 12);
1507 	ar->iButtonImage[BANDAGE_BUTTON]  = UseLoadedButtonImage(btn_pics,  6, 13);
1508 	ar->iButtonImage[RETREAT_BUTTON]  = UseLoadedButtonImage(btn_pics, 14, 15);
1509 	ar->iButtonImage[DONEWIN_BUTTON]  = UseLoadedButtonImage(btn_pics, 14, 15);
1510 	ar->iButtonImage[DONELOSE_BUTTON] = UseLoadedButtonImage(btn_pics, 16, 17);
1511 
1512 	// Load the generic faces for civs and enemies
1513 	SGPVObject* const faces = AddVideoObjectFromFile(INTERFACEDIR "/smfaces.sti");
1514 	ar->iFaces = faces;
1515 	SGPPaletteEntry const* const pal = faces->Palette();
1516 	faces->pShades[0] = Create16BPPPaletteShaded(pal, 255, 255, 255, FALSE);
1517 	faces->pShades[1] = Create16BPPPaletteShaded(pal, 250,  25,  25, TRUE);
1518 
1519 	// Add the battle over panels
1520 	ar->iIndent = AddVideoObjectFromFile(INTERFACEDIR "/indent.sti");
1521 
1522 	// Add all the faces now
1523 	FOR_EACH_AR_MERC(cell)
1524 	{
1525 		//Load the face
1526 		SGPVObject* const face = Load65Portrait(GetProfile(cell->pSoldier->ubProfile));
1527 		cell->uiVObjectID = face;
1528 		SGPPaletteEntry const* const pal = face->Palette();
1529 		face->pShades[0] = Create16BPPPaletteShaded(pal, 255, 255, 255, FALSE);
1530 		face->pShades[1] = Create16BPPPaletteShaded(pal, 250,  25,  25, TRUE);
1531 	}
1532 
1533 	UINT8 n_militia_elite = MilitiaInSectorOfRank(ar->ubSectorX, ar->ubSectorY, ELITE_MILITIA);
1534 	UINT8 n_militia_reg   = MilitiaInSectorOfRank(ar->ubSectorX, ar->ubSectorY, REGULAR_MILITIA);
1535 	UINT8 n_militia_green = MilitiaInSectorOfRank(ar->ubSectorX, ar->ubSectorY, GREEN_MILITIA);
1536 	while (n_militia_elite + n_militia_reg + n_militia_green < ar->ubCivs)
1537 	{
1538 		switch (PreRandom(3))
1539 		{
1540 			case 0: ++n_militia_elite; break;
1541 			case 1:	++n_militia_reg;   break;
1542 			case 2:	++n_militia_green; break;
1543 		}
1544 	}
1545 	for (INT32 i = 0; i < ar->ubCivs; ++i)
1546 	{
1547 		// reset counter of how many mortars this team has rolled
1548 		ResetMortarsOnTeamCount();
1549 
1550 		SOLDIERTYPE*       s;
1551 		UINT16             idx;
1552 		if (i < n_militia_elite)
1553 		{
1554 			s   = TacticalCreateMilitia(SOLDIER_CLASS_ELITE_MILITIA);
1555 			idx = s->ubBodyType == REGFEMALE ? MILITIA3F_FACE : MILITIA3_FACE;
1556 		}
1557 		else if (i < n_militia_reg + n_militia_elite)
1558 		{
1559 			s   = TacticalCreateMilitia(SOLDIER_CLASS_REG_MILITIA);
1560 			idx = s->ubBodyType == REGFEMALE ? MILITIA2F_FACE : MILITIA2_FACE;
1561 		}
1562 		else if (i < n_militia_green + n_militia_reg + n_militia_elite)
1563 		{
1564 			s   = TacticalCreateMilitia(SOLDIER_CLASS_GREEN_MILITIA);
1565 			idx = s->ubBodyType == REGFEMALE ? MILITIA1F_FACE : MILITIA1_FACE;
1566 		}
1567 		else
1568 		{
1569 			SLOGA("Attempting to illegally create a militia soldier.");
1570 			s   = 0;
1571 			idx = 0;
1572 		}
1573 		SOLDIERCELL* const cell = &gpCivs[i];
1574 		cell->pSoldier    = s;
1575 		cell->usIndex     = idx;
1576 		cell->uiVObjectID = ar->iFaces;
1577 
1578 		AssertMsg(s, "Failed to create militia soldier for autoresolve.");
1579 		s->sSectorX = ar->ubSectorX;
1580 		s->sSectorY = ar->ubSectorY;
1581 		s->name = gpStrategicString[STR_AR_MILITIA_NAME];
1582 	}
1583 
1584 	if (gubEnemyEncounterCode != CREATURE_ATTACK_CODE)
1585 	{
1586 		SOLDIERCELL* cell = gpEnemies;
1587 		cell = MakeEnemyTroops(cell, ar->ubElites, ar, SOLDIER_CLASS_ELITE,         ELITE_FACE, gpStrategicString[STR_AR_ELITE_NAME]);
1588 		cell = MakeEnemyTroops(cell, ar->ubTroops, ar, SOLDIER_CLASS_ARMY,          TROOP_FACE, gpStrategicString[STR_AR_TROOP_NAME]);
1589 		cell = MakeEnemyTroops(cell, ar->ubAdmins, ar, SOLDIER_CLASS_ADMINISTRATOR, ADMIN_FACE, gpStrategicString[STR_AR_ADMINISTRATOR_NAME]);
1590 		AssociateEnemiesWithStrategicGroups();
1591 	}
1592 	else
1593 	{
1594 		SOLDIERCELL* cell = gpEnemies;
1595 		cell = MakeCreatures(cell, ar->ubAFCreatures, ar, ADULTFEMALEMONSTER, AF_CREATURE_FACE);
1596 		cell = MakeCreatures(cell, ar->ubAMCreatures, ar, AM_MONSTER,         AM_CREATURE_FACE);
1597 		cell = MakeCreatures(cell, ar->ubYFCreatures, ar, YAF_MONSTER,        YF_CREATURE_FACE);
1598 		cell = MakeCreatures(cell, ar->ubYMCreatures, ar, YAM_MONSTER,        YM_CREATURE_FACE);
1599 	}
1600 
1601 	if (ar->ubSectorX  == gWorldSectorX &&
1602 			ar->ubSectorY  == gWorldSectorY &&
1603 			gbWorldSectorZ == 0)
1604 	{
1605 		CheckAndHandleUnloadingOfCurrentWorld();
1606 	}
1607 	else
1608 	{
1609 		gfBlitBattleSectorLocator = FALSE;
1610 	}
1611 
1612 	/* Build the interface buffer, and blit the "shaded" background.  This info
1613 	 * won't change from now on, but will be used to restore text. */
1614 	BuildInterfaceBuffer();
1615 	BltVideoSurface(FRAME_BUFFER, guiSAVEBUFFER, 0, 0, 0);
1616 
1617 	/* If we are bumping up the interface, then also use that piece of info to
1618 	 * move the buttons up by the same amount. */
1619 	ar->bVerticalOffset = (SCREEN_HEIGHT - ar->rect.h) / 2 > 120 ? -40 : 0;
1620 
1621 	const INT16 dx = ar->sCenterStartX;
1622 	const INT16 dy = ar->bVerticalOffset + SCREEN_HEIGHT / 2;
1623 
1624 	// Create the buttons -- subject to relocation
1625 	MakeButton(PLAY_BUTTON,     dx + 11, dy,      PlayButtonCallback,      FALSE, ST::null);
1626 	MakeButton(FAST_BUTTON,     dx + 51, dy,      FastButtonCallback,      FALSE, ST::null);
1627 	MakeButton(FINISH_BUTTON,   dx + 91, dy,      FinishButtonCallback,    FALSE, ST::null);
1628 	MakeButton(PAUSE_BUTTON,    dx + 11, dy + 34, PauseButtonCallback,     FALSE, ST::null);
1629 	MakeButton(RETREAT_BUTTON,  dx + 51, dy + 34, RetreatButtonCallback,   FALSE, gpStrategicString[STR_AR_RETREAT_BUTTON]);
1630 	if (!ar->ubMercs) DisableButton(ar->iButton[RETREAT_BUTTON]);
1631 
1632 	MakeButton(BANDAGE_BUTTON,  dx + 11, dy +  5, BandageButtonCallback,   TRUE,  ST::null);
1633 	MakeButton(DONEWIN_BUTTON,  dx + 51, dy +  5, DoneButtonCallback,      TRUE,  gpStrategicString[STR_AR_DONE_BUTTON]);
1634 	MakeButton(DONELOSE_BUTTON, dx + 25, dy +  5, DoneButtonCallback,      TRUE,  gpStrategicString[STR_AR_DONE_BUTTON]);
1635 	MakeButton(YES_BUTTON,      dx + 21, dy + 17, AcceptSurrenderCallback, TRUE,  ST::null);
1636 	MakeButton(NO_BUTTON,       dx + 81, dy + 17, RejectSurrenderCallback, TRUE,  ST::null);
1637 	ar->iButton[PLAY_BUTTON]->uiFlags |= BUTTON_CLICKED_ON;
1638 }
1639 
1640 
IsAnybodyWounded()1641 static bool IsAnybodyWounded()
1642 {
1643 	FOR_EACH_AR_MERC(i)
1644 	{
1645 		SOLDIERTYPE const& s = *i->pSoldier;
1646 		if (s.bBleeding == 0 || s.bLife == 0) continue;
1647 		return true;
1648 	}
1649 	return false;
1650 }
1651 
1652 
RemoveAutoResolveInterface(bool const delete_for_good)1653 static void RemoveAutoResolveInterface(bool const delete_for_good)
1654 {
1655 	AUTORESOLVE_STRUCT& ar = *gpAR;
1656 
1657 	MSYS_RemoveRegion(&ar.AutoResolveRegion);
1658 	DeleteVideoObject(ar.iPanelImages);
1659 	DeleteVideoObject(ar.iFaces);
1660 	DeleteVideoObject(ar.iIndent);
1661 	DeleteVideoSurface(ar.iInterfaceBuffer);
1662 
1663 	if (delete_for_good)
1664 	{ // Delete the soldier instances -- done when we are completely finished.
1665 
1666 		/* KM: By request of AM, I have added this bleeding event in cases where
1667 		 * autoresolve is complete and there are bleeding mercs remaining. AM coded
1668 		 * the internals of the strategic event. */
1669 		if (IsAnybodyWounded())
1670 		{ // ARM: only one event is needed regardless of how many are bleeding
1671 			AddStrategicEvent(EVENT_BANDAGE_BLEEDING_MERCS, GetWorldTotalMin() + 1, 0);
1672 		}
1673 
1674 		/* ARM: Update assignment flashing: Doctors may now have new patients or
1675 		 * lost them all, etc. */
1676 		gfReEvaluateEveryonesNothingToDo = TRUE;
1677 
1678 		if (ar.pRobotCell) UpdateRobotControllerGivenRobot(ar.pRobotCell->pSoldier);
1679 
1680 		for (INT32 i = 0; i != ar.iNumMercFaces; ++i)
1681 		{
1682 			SOLDIERTYPE& s = *gpMercs[i].pSoldier;
1683 			if (i >= ar.iActualMercFaces)
1684 			{
1685 				TacticalRemoveSoldier(s);
1686 			}
1687 			else
1688 			{ // Record finishing information for our mercs
1689 				if (s.bLife == 0)
1690 				{
1691 					StrategicHandlePlayerTeamMercDeath(s);
1692 				}
1693 				else switch (ar.ubBattleStatus)
1694 				{
1695 					case BATTLE_SURRENDERED:
1696 					case BATTLE_CAPTURED:
1697 						EnemyCapturesPlayerSoldier(&s);
1698 						break;
1699 
1700 					case BATTLE_VICTORY:
1701 						// Merc is alive, so group them at the center gridno.
1702 						s.ubStrategicInsertionCode = INSERTION_CODE_CENTER;
1703 						break;
1704 				}
1705 				++GetProfile(s.ubProfile).usBattlesFought;
1706 			}
1707 		}
1708 
1709 		bool  first_group      = true;
1710 		UINT8 current_group_id = 0;
1711 		for (INT32 i = 0; i != ar.iNumMercFaces; ++i)
1712 		{
1713 			SOLDIERTYPE*& slot = gpMercs[i].pSoldier;
1714 			SOLDIERTYPE&  s    = *slot;
1715 			slot = 0;
1716 
1717 			if (ar.ubBattleStatus != BATTLE_VICTORY) continue;
1718 			if (s.bLife < OKLIFE)                    continue;
1719 			if (s.ubGroupID == current_group_id)     continue;
1720 			current_group_id = s.ubGroupID;
1721 
1722 			/* Look for NPCs to stop for, anyone is too tired to keep going, if all OK
1723 			 * rebuild waypoints & continue movement.
1724 			 * NOTE: Only the first group found will stop for NPCs, it's just too much
1725 			 * hassle to stop them all */
1726 			PlayerGroupArrivedSafelyInSector(*GetGroup(s.ubGroupID), first_group);
1727 			first_group = false;
1728 		}
1729 
1730 		// End capture squence
1731 		if (ar.ubBattleStatus == BATTLE_SURRENDERED ||
1732 				ar.ubBattleStatus == BATTLE_CAPTURED)
1733 		{
1734 			EndCaptureSequence();
1735 		}
1736 	}
1737 
1738 	// Delete all of the faces.
1739 	for (INT32 i = 0; i != ar.iNumMercFaces; ++i)
1740 	{
1741 		SGPVObject*& vo = gpMercs[i].uiVObjectID;
1742 		if (vo) DeleteVideoObject(vo);
1743 		vo = 0;
1744 		MouseRegion*& r = gpMercs[i].pRegion;
1745 		delete r;
1746 		r = 0;
1747 	}
1748 
1749 	UINT8 const x = ar.ubSectorX;
1750 	UINT8 const y = ar.ubSectorY;
1751 
1752 	// Delete all militia
1753 	gbGreenToElitePromotions = 0;
1754 	gbGreenToRegPromotions   = 0;
1755 	gbRegToElitePromotions   = 0;
1756 	gbMilitiaPromotions      = 0;
1757 	for (INT32 i = 0; i != MAX_ALLOWABLE_MILITIA_PER_SECTOR; ++i)
1758 	{
1759 		if (!gpCivs[i].pSoldier) continue;
1760 		SOLDIERTYPE& s = *gpCivs[i].pSoldier;
1761 
1762 		UINT8 current_rank = SoldierClassToMilitiaRank(s.ubSoldierClass);
1763 		if (current_rank >= MAX_MILITIA_LEVELS) throw std::runtime_error(ST::format("Removing autoresolve militia with invalid ubSoldierClass {}.", s.ubSoldierClass).to_std_string());
1764 
1765 		if (delete_for_good)
1766 		{
1767 			if (s.bLife < OKLIFE / 2)
1768 			{
1769 				AddDeadSoldierToUnLoadedSector(x, y, 0, &s, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT);
1770 				StrategicRemoveMilitiaFromSector(x, y, current_rank, 1);
1771 				HandleGlobalLoyaltyEvent(GLOBAL_LOYALTY_NATIVE_KILLED, x, y, 0);
1772 			}
1773 			else
1774 			{ // This will check for promotions
1775 				UINT8 const promotions = CheckOneMilitiaForPromotion(x, y, current_rank, s.ubMilitiaKills);
1776 				if (promotions == 1)
1777 				{
1778 					if (current_rank == GREEN_MILITIA)
1779 					{
1780 						++gbGreenToRegPromotions;
1781 						++gbMilitiaPromotions;
1782 					}
1783 					else if (current_rank == REGULAR_MILITIA)
1784 					{
1785 						++gbRegToElitePromotions;
1786 						++gbMilitiaPromotions;
1787 					}
1788 				}
1789 				else if (promotions == 2)
1790 				{
1791 					++gbGreenToElitePromotions;
1792 					++gbMilitiaPromotions;
1793 				}
1794 			}
1795 		}
1796 		TacticalRemoveSoldier(s);
1797 		gpCivs[i] = SOLDIERCELL{};
1798 	}
1799 
1800 	if (delete_for_good)
1801 	{
1802 		// Record and process all enemy deaths
1803 		for (INT32 i = 0; i != 32; ++i)
1804 		{
1805 			if (!gpEnemies[i].pSoldier) continue;
1806 			SOLDIERTYPE& s = *gpEnemies[i].pSoldier;
1807 			if (s.bLife >= OKLIFE) continue;
1808 			TrackEnemiesKilled(ENEMY_KILLED_IN_AUTO_RESOLVE, s.ubSoldierClass);
1809 			HandleGlobalLoyaltyEvent(GLOBAL_LOYALTY_ENEMY_KILLED, x, y, 0);
1810 			ProcessQueenCmdImplicationsOfDeath(&s);
1811 			AddDeadSoldierToUnLoadedSector(x, y, 0, &s, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT);
1812 		}
1813 
1814 		/* Eliminate all excess soldiers (as more than 32 can exist in the same
1815 		 * battle. Autoresolve only processes 32, so the excess is slaughtered as
1816 		 * the player never knew they existed. */
1817 		if (ar.ubBattleStatus == BATTLE_VICTORY)
1818 		{	/* Get rid of any extra enemies that could be here. It is possible for the
1819 			 * number of total enemies to exceed 32, but autoresolve can only process
1820 			 * 32. We basically cheat by eliminating the rest of them. */
1821 			if(NumEnemiesInSector(x , y))
1822 			{
1823 				SLOGI("Eliminating remaining enemies after Autoresolve in (%d,%d)", x, y);
1824 				EliminateAllEnemies(x, y);
1825 			}
1826 		}
1827 		else
1828 		{ // The enemy won, so repoll movement.
1829 			ResetMovementForEnemyGroupsInLocation(x, y);
1830 		}
1831 	}
1832 
1833 	// Physically delete the soldiers now.
1834 	for (INT32 i = 0; i != 32; ++i)
1835 	{
1836 		SOLDIERCELL& slot = gpEnemies[i];
1837 		if (!slot.pSoldier) continue;
1838 		TacticalRemoveSoldier(*slot.pSoldier);
1839 		slot = SOLDIERCELL{};
1840 	}
1841 
1842 	for (INT32 i = 0; i != NUM_AR_BUTTONS; ++i)
1843 	{
1844 		UnloadButtonImage(ar.iButtonImage[i]);
1845 		RemoveButton(ar.iButton[i]);
1846 	}
1847 
1848 	if (delete_for_good)
1849 	{ //Warp the game time accordingly
1850 
1851 		WarpGameTime(ar.uiTotalElapsedBattleTimeInMilliseconds / 1000, WARPTIME_NO_PROCESSING_OF_EVENTS);
1852 
1853 		// Deallocate all of the global memory.
1854 		// Everything internal to them, should have already been deleted.
1855 		delete gpAR;
1856 		gpAR = 0;
1857 
1858 		delete[] gpMercs;
1859 		gpMercs = 0;
1860 
1861 		delete[] gpCivs;
1862 		gpCivs = 0;
1863 
1864 		delete[] gpEnemies;
1865 		gpEnemies = 0;
1866 	}
1867 
1868 	//KM : Aug 09, 1999 Patch fix -- Would break future dialog while time compressing
1869 	gTacticalStatus.ubCurrentTeam = OUR_TEAM;
1870 
1871 	gpBattleGroup = 0;
1872 
1873 	if (gubEnemyEncounterCode == CREATURE_ATTACK_CODE)
1874 	{
1875 		gubNumCreaturesAttackingTown = 0;
1876 		gubSectorIDOfCreatureAttack  = 0;
1877 	}
1878 }
1879 
1880 
DepressAutoButton(UINT btn)1881 static void DepressAutoButton(UINT btn)
1882 {
1883 	gpAR->iButton[PAUSE_BUTTON ]->uiFlags &= ~BUTTON_CLICKED_ON;
1884 	gpAR->iButton[PLAY_BUTTON  ]->uiFlags &= ~BUTTON_CLICKED_ON;
1885 	gpAR->iButton[FAST_BUTTON  ]->uiFlags &= ~BUTTON_CLICKED_ON;
1886 	gpAR->iButton[FINISH_BUTTON]->uiFlags &= ~BUTTON_CLICKED_ON;
1887 	gpAR->iButton[btn          ]->uiFlags |=  BUTTON_CLICKED_ON;
1888 	gpAR->fPaused = (btn == PAUSE_BUTTON);
1889 }
1890 
1891 
PauseButtonCallback(GUI_BUTTON * btn,INT32 reason)1892 static void PauseButtonCallback(GUI_BUTTON* btn, INT32 reason)
1893 {
1894 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1895 	{
1896 		DepressAutoButton(PAUSE_BUTTON);
1897 	}
1898 }
1899 
1900 
PlayButtonCallback(GUI_BUTTON * btn,INT32 reason)1901 static void PlayButtonCallback(GUI_BUTTON* btn, INT32 reason)
1902 {
1903 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1904 	{
1905 		DepressAutoButton(PLAY_BUTTON);
1906 		gpAR->uiTimeSlice = 1000 * gpAR->ubTimeModifierPercentage / 100;
1907 	}
1908 }
1909 
1910 
FastButtonCallback(GUI_BUTTON * btn,INT32 reason)1911 static void FastButtonCallback(GUI_BUTTON* btn, INT32 reason)
1912 {
1913 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1914 	{
1915 		DepressAutoButton(FAST_BUTTON);
1916 		gpAR->uiTimeSlice = 4000;
1917 	}
1918 }
1919 
1920 
FinishButtonCallback(GUI_BUTTON * btn,INT32 reason)1921 static void FinishButtonCallback(GUI_BUTTON* btn, INT32 reason)
1922 {
1923 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1924 	{
1925 		DepressAutoButton(FINISH_BUTTON);
1926 		gpAR->uiTimeSlice = 0xffffffff;
1927 		gpAR->fSound = FALSE;
1928 		PlayJA2StreamingSample(AUTORESOLVE_FINISHFX, HIGHVOLUME, 1, MIDDLEPAN);
1929 	}
1930 }
1931 
1932 
RetreatButtonCallback(GUI_BUTTON * btn,INT32 reason)1933 static void RetreatButtonCallback(GUI_BUTTON* btn, INT32 reason)
1934 {
1935 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
1936 	{
1937 		FOR_EACH_AR_MERC(i)
1938 		{
1939 			if (i->uiFlags & (CELL_RETREATING | CELL_RETREATED)) continue;
1940 			i->uiFlags           |= CELL_RETREATING | CELL_DIRTY;
1941 			// Gets to retreat after a total of 2 attacks.
1942 			i->usNextAttack       = (1000 + i->usNextAttack * 2 + PreRandom(2000 - i->usAttack)) * 2;
1943 			gpAR->usPlayerAttack -= i->usAttack;
1944 			i->usAttack           = 0;
1945 		}
1946 		if( gpAR->pRobotCell )
1947 		{ //if robot is retreating, set the retreat time to be the same as the robot's controller.
1948 			const SOLDIERTYPE* const robot_controller = gpAR->pRobotCell->pSoldier->robot_remote_holder;
1949 			if (robot_controller == NULL)
1950 			{
1951 				gpAR->pRobotCell->uiFlags &= ~CELL_RETREATING;
1952 				gpAR->pRobotCell->uiFlags |= CELL_DIRTY;
1953 				gpAR->pRobotCell->usNextAttack = 0xffff;
1954 				return;
1955 			}
1956 			FOR_EACH_AR_MERC(i)
1957 			{
1958 				if (robot_controller != i->pSoldier) continue;
1959 				// Found the controller, make the robot's retreat time match the contollers.
1960 				gpAR->pRobotCell->usNextAttack = i->usNextAttack;
1961 				return;
1962 			}
1963 		}
1964 	}
1965 }
1966 
1967 
CanAnybodyBandage()1968 static bool CanAnybodyBandage()
1969 {
1970 	FOR_EACH_AR_MERC(i)
1971 	{
1972 		SOLDIERTYPE const& s = *i->pSoldier;
1973 		if (s.bLife < OKLIFE || s.bCollapsed || s.bMedical == 0) continue;
1974 		return true;
1975 	}
1976 	return false;
1977 }
1978 
1979 
DetermineBandageButtonState(void)1980 static void DetermineBandageButtonState(void)
1981 {
1982 	ST::string help;
1983 	bool           enable = false;
1984 	if (!IsAnybodyWounded()) // Does anyone need bandaging?
1985 	{
1986 		help = gzLateLocalizedString[STR_LATE_11];
1987 	}
1988 	else if (!CanAnybodyBandage()) // Do we have any doctors?
1989 	{	// No doctors
1990 		help = gzLateLocalizedString[STR_LATE_08];
1991 	}
1992 	else if (!FindMedicalKit()) // Do have a kit?
1993 	{ // No kits
1994 		help = gzLateLocalizedString[STR_LATE_09];
1995 	}
1996 	else
1997 	{ // Allow bandaging
1998 		help   = gzLateLocalizedString[STR_LATE_12];
1999 		enable = true;
2000 	}
2001 	GUI_BUTTON& b = *gpAR->iButton[BANDAGE_BUTTON];
2002 	EnableButton(&b, enable);
2003 	b.SetFastHelpText(help);
2004 }
2005 
2006 
2007 static void SetupDoneInterface(void);
2008 
2009 
BandageButtonCallback(GUI_BUTTON * btn,INT32 reason)2010 static void BandageButtonCallback(GUI_BUTTON* btn, INT32 reason)
2011 {
2012 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
2013 	{
2014 		AutoBandageMercs();
2015 		SetupDoneInterface();
2016 	}
2017 }
2018 
2019 
DoneButtonCallback(GUI_BUTTON * btn,INT32 reason)2020 static void DoneButtonCallback(GUI_BUTTON* btn, INT32 reason)
2021 {
2022 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
2023 	{
2024 		gpAR->fExitAutoResolve = TRUE;
2025 	}
2026 }
2027 
2028 
2029 // Find the merc with the same region.
GetCell(MOUSE_REGION const * const reg)2030 static SOLDIERCELL* GetCell(MOUSE_REGION const* const reg)
2031 {
2032 	FOR_EACH_AR_MERC(i)
2033 	{
2034 		if (&i->pRegion->Base() != reg) continue;
2035 		return i;
2036 	}
2037 	throw std::logic_error("Region does not belong to a soldier cell");
2038 }
2039 
2040 
MercCellMouseMoveCallback(MOUSE_REGION * reg,INT32 reason)2041 static void MercCellMouseMoveCallback(MOUSE_REGION* reg, INT32 reason)
2042 {
2043 	SOLDIERCELL* const pCell = GetCell(reg);
2044 	if( gpAR->fPendingSurrender )
2045 	{ //Can't setup retreats when pending surrender.
2046 		pCell->uiFlags &= ~CELL_SHOWRETREATTEXT;
2047 		pCell->uiFlags |= CELL_DIRTY;
2048 		return;
2049 	}
2050 	if( reg->uiFlags & MSYS_MOUSE_IN_AREA )
2051 	{
2052 		if( !(pCell->uiFlags & CELL_SHOWRETREATTEXT) )
2053 			pCell->uiFlags |= CELL_SHOWRETREATTEXT | CELL_DIRTY;
2054 	}
2055 	else
2056 	{
2057 		if( pCell->uiFlags & CELL_SHOWRETREATTEXT )
2058 		{
2059 			pCell->uiFlags &= ~CELL_SHOWRETREATTEXT;
2060 			pCell->uiFlags |= CELL_DIRTY;
2061 		}
2062 	}
2063 }
2064 
2065 
MercCellMouseClickCallback(MOUSE_REGION * reg,INT32 reason)2066 static void MercCellMouseClickCallback(MOUSE_REGION* reg, INT32 reason)
2067 {
2068 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
2069 	{
2070 		if( gpAR->fPendingSurrender )
2071 		{ //Can't setup retreats when pending surrender.
2072 			return;
2073 		}
2074 
2075 		SOLDIERCELL* const pCell = GetCell(reg);
2076 
2077 		if( pCell->uiFlags & ( CELL_RETREATING | CELL_RETREATED ) )
2078 		{ //already retreated/retreating.
2079 			return;
2080 		}
2081 
2082 		if( pCell == gpAR->pRobotCell )
2083 		{ //robot retreats only when controller retreats
2084 			return;
2085 		}
2086 
2087 		pCell->uiFlags |= CELL_RETREATING | CELL_DIRTY;
2088 		//Gets to retreat after a total of 2 attacks.
2089 		pCell->usNextAttack = (UINT16)((1000 + pCell->usNextAttack * 5 + PreRandom( 2000 - pCell->usAttack ))*2);
2090 		gpAR->usPlayerAttack -= pCell->usAttack;
2091 		pCell->usAttack = 0;
2092 
2093 		if( gpAR->pRobotCell )
2094 		{ //if controller is retreating, make the robot retreat too.
2095 			const SOLDIERTYPE* const robot_controller = gpAR->pRobotCell->pSoldier->robot_remote_holder;
2096 			if (robot_controller == NULL)
2097 			{
2098 				gpAR->pRobotCell->uiFlags &= ~CELL_RETREATING;
2099 				gpAR->pRobotCell->uiFlags |= CELL_DIRTY;
2100 				gpAR->pRobotCell->usNextAttack = 0xffff;
2101 			}
2102 			else if (robot_controller == pCell->pSoldier)
2103 			{ //Found the controller, make the robot's retreat time match the contollers.
2104 				gpAR->pRobotCell->uiFlags |= CELL_RETREATING | CELL_DIRTY;
2105 				gpAR->pRobotCell->usNextAttack = pCell->usNextAttack;
2106 				gpAR->usPlayerAttack -= gpAR->pRobotCell->usAttack;
2107 				gpAR->pRobotCell->usAttack = 0;
2108 				return;
2109 			}
2110 		}
2111 	}
2112 }
2113 
2114 
2115 static void CalculateRowsAndColumns(void);
2116 
2117 
2118 //Determine how many players, militia, and enemies that are going at it, and use these values
2119 //to figure out how many rows and columns we can use.  The will effect the size of the panel.
CalculateAutoResolveInfo(void)2120 static void CalculateAutoResolveInfo(void)
2121 {
2122 	Assert( gpAR->ubSectorX >= 1 && gpAR->ubSectorX <= 16 );
2123 	Assert( gpAR->ubSectorY >= 1 && gpAR->ubSectorY <= 16 );
2124 
2125 	if( gubEnemyEncounterCode != CREATURE_ATTACK_CODE )
2126 	{
2127 		GetNumberOfEnemiesInSector( gpAR->ubSectorX, gpAR->ubSectorY,
2128 																&gpAR->ubAdmins, &gpAR->ubTroops, &gpAR->ubElites );
2129 		gpAR->ubEnemies = (UINT8)MIN( gpAR->ubAdmins + gpAR->ubTroops + gpAR->ubElites, 32 );
2130 	}
2131 	else
2132 	{
2133 		if( gfTransferTacticalOppositionToAutoResolve )
2134 		{
2135 			DetermineCreatureTownCompositionBasedOnTacticalInformation( &gubNumCreaturesAttackingTown,
2136 																				&gpAR->ubYMCreatures, &gpAR->ubYFCreatures,
2137 																				&gpAR->ubAMCreatures, &gpAR->ubAFCreatures );
2138 		}
2139 		else
2140 		{
2141 			DetermineCreatureTownComposition( gubNumCreaturesAttackingTown,
2142 																				&gpAR->ubYMCreatures, &gpAR->ubYFCreatures,
2143 																				&gpAR->ubAMCreatures, &gpAR->ubAFCreatures );
2144 		}
2145 		gpAR->ubEnemies = (UINT8)MIN( gpAR->ubYMCreatures + gpAR->ubYFCreatures + gpAR->ubAMCreatures + gpAR->ubAFCreatures, 32 );
2146 	}
2147 	gfTransferTacticalOppositionToAutoResolve = FALSE;
2148 	gpAR->ubCivs = CountAllMilitiaInSector( gpAR->ubSectorX, gpAR->ubSectorY );
2149 	gpAR->ubMercs = 0;
2150 	CFOR_EACH_GROUP(i)
2151 	{
2152 		GROUP const& g = *i;
2153 		if (!PlayerGroupInvolvedInThisCombat(g)) continue;
2154 
2155 		CFOR_EACH_PLAYER_IN_GROUP(pPlayer, &g)
2156 		{
2157 			SOLDIERTYPE& s = *pPlayer->pSoldier;
2158 			// NOTE: Must check each merc individually, e.g. Robot without controller is an uninvolved merc on an involved group!
2159 			if (PlayerMercInvolvedInThisCombat(s))
2160 			{
2161 				gpMercs[gpAR->ubMercs].pSoldier = &s;
2162 
2163 				//!!! CLEAR OPPCOUNT HERE.  All of these soldiers are guaranteed to not be in tactical anymore.
2164 				//ClearOppCount(&s);
2165 
2166 				gpAR->ubMercs++;
2167 				if (AM_AN_EPC(&s))
2168 				{
2169 					gpAR->fCaptureNotPermittedDueToEPCs = TRUE;
2170 				}
2171 				if (AM_A_ROBOT(&s))
2172 				{
2173 					gpAR->pRobotCell = &gpMercs[ gpAR->ubMercs - 1 ];
2174 					UpdateRobotControllerGivenRobot( gpAR->pRobotCell->pSoldier );
2175 				}
2176 			}
2177 		}
2178 	}
2179 	gpAR->iNumMercFaces = gpAR->ubMercs;
2180 	gpAR->iActualMercFaces = gpAR->ubMercs;
2181 
2182 	CalculateRowsAndColumns();
2183 }
2184 
2185 
2186 static void CreateTempPlayerMerc(void);
2187 
2188 
2189 // Debug utilities
ResetAutoResolveInterface(void)2190 static void ResetAutoResolveInterface(void)
2191 {
2192 	guiPreRandomIndex = gpAR->uiPreRandomIndex;
2193 
2194 	RemoveAutoResolveInterface(false);
2195 
2196 	gpAR->ubBattleStatus = BATTLE_IN_PROGRESS;
2197 
2198 	if( !gpAR->ubCivs && !gpAR->ubMercs )
2199 		gpAR->ubCivs = 1;
2200 
2201 	//Make sure the number of enemy portraits is the same as needed.
2202 	//The debug keypresses may add or remove more than one at a time.
2203 	while( gpAR->ubElites + gpAR->ubAdmins + gpAR->ubTroops > gpAR->ubEnemies )
2204 	{
2205 		switch( PreRandom( 5 ) )
2206 		{
2207 			case 0:         if( gpAR->ubElites ) { gpAR->ubElites--; break; }
2208 			// fallthrough
2209 			case 1: case 2: if( gpAR->ubAdmins ) { gpAR->ubAdmins--; break; }
2210 			// fallthrough
2211 			case 3: case 4: if( gpAR->ubTroops ) { gpAR->ubTroops--; break; }
2212 		}
2213 	}
2214 	while( gpAR->ubElites + gpAR->ubAdmins + gpAR->ubTroops < gpAR->ubEnemies )
2215 	{
2216 		switch( PreRandom( 5 ) )
2217 		{
2218 			case 0:         gpAR->ubElites++; break;
2219 			case 1: case 2: gpAR->ubAdmins++; break;
2220 			case 3: case 4: gpAR->ubTroops++; break;
2221 		}
2222 	}
2223 
2224 
2225 	//Do the same for the player mercs.
2226 	while( gpAR->iNumMercFaces > gpAR->ubMercs && gpAR->iNumMercFaces > gpAR->iActualMercFaces )
2227 	{ //Removing temp mercs
2228 		gpAR->iNumMercFaces--;
2229 		TacticalRemoveSoldier(*gpMercs[gpAR->iNumMercFaces].pSoldier);
2230 		gpMercs[gpAR->iNumMercFaces].pSoldier = NULL;
2231 	}
2232 	while( gpAR->iNumMercFaces < gpAR->ubMercs && gpAR->iNumMercFaces >= gpAR->iActualMercFaces )
2233 	{
2234 		CreateTempPlayerMerc();
2235 	}
2236 
2237 	if( gpAR->uiTimeSlice == 0xffffffff )
2238 	{
2239 		gpAR->fSound = TRUE;
2240 	}
2241 	gpAR->uiTimeSlice = 1000;
2242 	gpAR->uiTotalElapsedBattleTimeInMilliseconds = 0;
2243 	gpAR->uiCurrTime = 0;
2244 	gpAR->fPlayerRejectedSurrenderOffer = FALSE;
2245 	gpAR->fPendingSurrender = FALSE;
2246 	CalculateRowsAndColumns();
2247 	CalculateSoldierCells( TRUE);
2248 	CreateAutoResolveInterface();
2249 	DetermineTeamLeader( TRUE ); //friendly team
2250 	DetermineTeamLeader( FALSE ); //enemy team
2251 	CalculateAttackValues();
2252 }
2253 
2254 
CalculateRowsAndColumns(void)2255 static void CalculateRowsAndColumns(void)
2256 {
2257 	//now that we have the number on each team, calculate the number of rows and columns to be used on
2258 	//the player's sides.  NOTE:  Militia won't appear on the same row as mercs.
2259 	if( !gpAR->ubMercs )
2260 	{ //0
2261 		gpAR->ubMercCols = gpAR->ubMercRows = 0;
2262 	}
2263 	else if( gpAR->ubMercs < 5 )
2264 	{ //1-4
2265 		gpAR->ubMercCols = 1;
2266 		gpAR->ubMercRows = gpAR->ubMercs;
2267 	}
2268 	else if( gpAR->ubMercs < 9 || gpAR->ubMercs == 10 )
2269 	{ //5-8, 10
2270 		gpAR->ubMercCols = 2;
2271 		gpAR->ubMercRows = (gpAR->ubMercs+1)/2;
2272 	}
2273 	else if( gpAR->ubMercs < 16 )
2274 	{ //9, 11-15
2275 		gpAR->ubMercCols = 3;
2276 		gpAR->ubMercRows = (gpAR->ubMercs+2)/3;
2277 	}
2278 	else
2279 	{ //16-MAX_STRATEGIC_TEAM_SIZE
2280 		gpAR->ubMercCols = 4;
2281 		gpAR->ubMercRows = (gpAR->ubMercs+3)/4;
2282 	}
2283 
2284 	if( !gpAR->ubCivs )
2285 	{
2286 		gpAR->ubCivCols = gpAR->ubCivRows = 0;
2287 	}
2288 	else if( gpAR->ubCivs < 5 )
2289 	{ //1-4
2290 		gpAR->ubCivCols = 1;
2291 		gpAR->ubCivRows = gpAR->ubCivs;
2292 	}
2293 	else if( gpAR->ubCivs < 9 || gpAR->ubCivs == 10 )
2294 	{ //5-8, 10
2295 		gpAR->ubCivCols = 2;
2296 		gpAR->ubCivRows = (gpAR->ubCivs+1)/2;
2297 	}
2298 	else if( gpAR->ubCivs < 16 )
2299 	{ //9, 11-15
2300 		gpAR->ubCivCols = 3;
2301 		gpAR->ubCivRows = (gpAR->ubCivs+2)/3;
2302 	}
2303 	else
2304 	{ //16-MAX_ALLOWABLE_MILITIA_PER_SECTOR
2305 		gpAR->ubCivCols = 4;
2306 		gpAR->ubCivRows = (gpAR->ubCivs+3)/4;
2307 	}
2308 
2309 	if( !gpAR->ubEnemies )
2310 	{
2311 		gpAR->ubEnemyCols = gpAR->ubEnemyRows = 0;
2312 	}
2313 	else if( gpAR->ubEnemies < 5 )
2314 	{ //1-4
2315 		gpAR->ubEnemyCols = 1;
2316 		gpAR->ubEnemyRows = gpAR->ubEnemies;
2317 	}
2318 	else if( gpAR->ubEnemies < 9 || gpAR->ubEnemies == 10 )
2319 	{ //5-8, 10
2320 		gpAR->ubEnemyCols = 2;
2321 		gpAR->ubEnemyRows = (gpAR->ubEnemies+1)/2;
2322 	}
2323 	else if( gpAR->ubEnemies < 16 )
2324 	{ //9, 11-15
2325 		gpAR->ubEnemyCols = 3;
2326 		gpAR->ubEnemyRows = (gpAR->ubEnemies+2)/3;
2327 	}
2328 	else
2329 	{ //16-32
2330 		gpAR->ubEnemyCols = 4;
2331 		gpAR->ubEnemyRows = (gpAR->ubEnemies+3)/4;
2332 	}
2333 
2334 
2335 	//Now, that we have the number of mercs, militia, and enemies, it is possible that there
2336 	//may be some conflicts.  Our goal is to make the window as small as possible.  Bumping up
2337 	//the number of columns to 5 or rows to 10 will force one or both axes to go full screen.  If we
2338 	//have high numbers of both, then we will have to.
2339 
2340 	//Step one:  equalize the number of columns for both the mercs and civs.
2341 	if( gpAR->ubMercs && gpAR->ubCivs && gpAR->ubMercCols != gpAR->ubCivCols )
2342 	{
2343 		if( gpAR->ubMercCols < gpAR->ubCivCols )
2344 		{
2345 			gpAR->ubMercCols = gpAR->ubCivCols;
2346 			gpAR->ubMercRows = (gpAR->ubMercs+gpAR->ubMercCols-1)/gpAR->ubMercCols;
2347 		}
2348 		else
2349 		{
2350 			gpAR->ubCivCols = gpAR->ubMercCols;
2351 			gpAR->ubCivRows = (gpAR->ubCivs+gpAR->ubCivCols-1)/gpAR->ubCivCols;
2352 		}
2353 	}
2354 	//If we have both mercs and militia, we must make sure that the height to width ratio is never higher than
2355 	//a factor of two.
2356 	if( gpAR->ubMercs && gpAR->ubCivs && gpAR->ubMercRows + gpAR->ubCivRows > 4 )
2357 	{
2358 		if( gpAR->ubMercCols * 2 < gpAR->ubMercRows + gpAR->ubCivRows )
2359 		{
2360 			gpAR->ubMercCols++;
2361 			gpAR->ubMercRows = (gpAR->ubMercs+gpAR->ubMercCols-1)/gpAR->ubMercCols;
2362 			gpAR->ubCivCols++;
2363 			gpAR->ubCivRows = (gpAR->ubCivs+gpAR->ubCivCols-1)/gpAR->ubCivCols;
2364 		}
2365 	}
2366 
2367 
2368 	if( gpAR->ubMercRows + gpAR->ubCivRows > 9 )
2369 	{
2370 		if( gpAR->ubMercCols < 5 )
2371 		{ //bump it up
2372 			gpAR->ubMercCols++;
2373 			gpAR->ubMercRows = (gpAR->ubMercs+gpAR->ubMercCols-1)/gpAR->ubMercCols;
2374 		}
2375 		if( gpAR->ubCivCols < 5 )
2376 		{ //match it up with the mercs
2377 			gpAR->ubCivCols = gpAR->ubMercCols;
2378 			gpAR->ubCivRows = (gpAR->ubCivs+gpAR->ubCivCols-1)/gpAR->ubCivCols;
2379 		}
2380 	}
2381 
2382 	if( gpAR->ubMercCols + gpAR->ubEnemyCols == 9 )
2383 		gpAR->rect.w = SCREEN_WIDTH;
2384 	else
2385 		gpAR->rect.w = 146 + 55 * (MAX(MAX(gpAR->ubMercCols, gpAR->ubCivCols), 2) + MAX(gpAR->ubEnemyCols, 2));
2386 
2387 	gpAR->sCenterStartX = STD_SCREEN_X + 323 - gpAR->rect.w / 2 + MAX(MAX(gpAR->ubMercCols, 2), MAX(gpAR->ubCivCols, 2)) * 55;
2388 
2389 	//Anywhere from 48*3 to 48*10
2390 	gpAR->rect.h = 48 * MAX(3, MAX(gpAR->ubMercRows + gpAR->ubCivRows, gpAR->ubEnemyRows));
2391 	//Make it an even multiple of 40 (rounding up).
2392 	gpAR->rect.h += 39;
2393 	gpAR->rect.h /= 40;
2394 	gpAR->rect.h *= 40;
2395 
2396 	//Here is a extremely bitchy case.  The formulae throughout this module work for most cases.
2397 	//However, when combining mercs and civs, the column must be the same.  However, we retract that
2398 	//in cases where there are less mercs than available to fill up *most* of the designated space.
2399 	if( gpAR->ubMercs && gpAR->ubCivs )
2400 	{
2401 		if( gpAR->ubMercRows * gpAR->ubMercCols > gpAR->ubMercs + gpAR->ubMercRows )
2402 			gpAR->ubMercCols--;
2403 		if( gpAR->ubCivRows * gpAR->ubCivCols > gpAR->ubCivs + gpAR->ubCivRows )
2404 			gpAR->ubCivCols--;
2405 	}
2406 }
2407 
2408 
HandleAutoResolveInput(void)2409 static void HandleAutoResolveInput(void)
2410 {
2411 	InputAtom InputEvent;
2412 	BOOLEAN fResetAutoResolve = FALSE;
2413 	while( DequeueEvent( &InputEvent ) )
2414 	{
2415 		if( InputEvent.usEvent == KEY_DOWN || InputEvent.usEvent == KEY_REPEAT )
2416 		{
2417 			switch( InputEvent.usParam )
2418 			{
2419 				case SDLK_SPACE:
2420 					DepressAutoButton(gpAR->fPaused ? PLAY_BUTTON : PAUSE_BUTTON);
2421 					break;
2422 
2423 				case 'x':
2424 					if( _KeyDown( ALT ) )
2425 					{
2426 						HandleShortCutExitState( );
2427 					}
2428 					break;
2429 			}
2430 		}
2431 	}
2432 	if( fResetAutoResolve )
2433 	{
2434 		ResetAutoResolveInterface();
2435 	}
2436 }
2437 
2438 
RenderSoldierCellHealth(SOLDIERCELL * pCell)2439 static void RenderSoldierCellHealth(SOLDIERCELL* pCell)
2440 {
2441 	UINT8 cnt, cntStart;
2442 	UINT16 xp, yp;
2443 	ST::string pStr;
2444 	ST::string str;
2445 	UINT16 usColor;
2446 
2447 	SetFont( SMALLCOMPFONT );
2448 	//Restore the background before drawing text.
2449 	xp = pCell->xp +  2;
2450 	yp = pCell->yp + 32;
2451 	SGPBox const r = {  (UINT16)(xp - gpAR->rect.x),
2452 											(UINT16)(yp - gpAR->rect.y),
2453 											46, 10 };
2454 	BltVideoSurface(FRAME_BUFFER, gpAR->iInterfaceBuffer, xp, yp, &r);
2455 
2456 	if( pCell->pSoldier->bLife )
2457 	{
2458 		if( pCell->pSoldier->bLife == pCell->pSoldier->bLifeMax )
2459 		{
2460 			cntStart = 4;
2461 		}
2462 		else
2463 		{
2464 			cntStart = 0;
2465 		}
2466 		for( cnt = cntStart; cnt < 6; cnt ++ )
2467 		{
2468 			if( pCell->pSoldier->bLife < bHealthStrRanges[ cnt ] )
2469 			{
2470 				break;
2471 			}
2472 		}
2473 		switch( cnt )
2474 		{
2475 			case 0: //DYING
2476 			case 1: //CRITICAL
2477 				usColor = FONT_RED;
2478 				break;
2479 			case 2: //WOUNDED
2480 			case 3: //POOR
2481 				usColor = FONT_YELLOW;
2482 				break;
2483 			default: //REST
2484 				usColor = FONT_GRAY1;
2485 				break;
2486 		}
2487 		if( cnt > 3 && pCell->pSoldier->bLife != pCell->pSoldier->bLifeMax )
2488 		{ //Merc has taken damage, even though his life if good.
2489 			usColor = FONT_YELLOW;
2490 		}
2491 		if( pCell->pSoldier->bLife == pCell->pSoldier->bLifeMax )
2492 			usColor = FONT_GRAY1;
2493 		pStr = zHealthStr[ cnt ];
2494 	}
2495 	else
2496 	{
2497 		str = pCell->pSoldier->name;
2498 		#if 0 /* XXX */
2499 		pStr = str.to_upper();
2500 		#else
2501 		pStr = str;
2502 		#endif
2503 		usColor = FONT_BLACK;
2504 	}
2505 
2506 	//Draw the retreating text, if applicable
2507 	if( pCell->uiFlags & CELL_RETREATED && gpAR->ubBattleStatus != BATTLE_VICTORY )
2508 	{
2509 		usColor = FONT_LTGREEN;
2510 		pStr = gpStrategicString[STR_AR_MERC_RETREATED];
2511 	}
2512 	else if( pCell->uiFlags & CELL_RETREATING && gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
2513 	{
2514 		if( pCell->pSoldier->bLife >= OKLIFE )
2515 		{ //Retreating is shared with the status string.  Alternate between the
2516 			//two every 450 milliseconds
2517 			if( GetJA2Clock() % 900 < 450 )
2518 			{ //override the health string with the retreating string.
2519 				usColor = FONT_LTRED;
2520 				pStr = gpStrategicString[STR_AR_MERC_RETREATING];
2521 			}
2522 		}
2523 	}
2524 	else if( pCell->uiFlags & CELL_SHOWRETREATTEXT && gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
2525 	{
2526 		if( pCell->pSoldier->bLife >= OKLIFE )
2527 		{
2528 			SetFontForeground( FONT_YELLOW );
2529 			ST::string Retreat = gpStrategicString[STR_AR_MERC_RETREAT];
2530 			xp = pCell->xp + 25 - StringPixLength(Retreat, SMALLCOMPFONT) / 2;
2531 			yp = pCell->yp + 12;
2532 			MPrint(xp, yp, Retreat);
2533 		}
2534 	}
2535 	SetFontForeground( (UINT8)usColor );
2536 	xp = pCell->xp + 25 - StringPixLength( pStr, SMALLCOMPFONT ) / 2;
2537 	yp = pCell->yp + 33;
2538 	MPrint(xp, yp, pStr);
2539 }
2540 
2541 
GetUnusedMercProfileID(void)2542 static UINT8 GetUnusedMercProfileID(void)
2543 {
2544 	for (;;)
2545 	{
2546 		const ProfileID pid = PreRandom(40);
2547 		if (FindSoldierByProfileIDOnPlayerTeam(pid) == NULL) return pid;
2548 	}
2549 }
2550 
2551 
CreateTempPlayerMerc(void)2552 static void CreateTempPlayerMerc(void)
2553 {
2554 	SOLDIERCREATE_STRUCT		MercCreateStruct;
2555 
2556 	//Init the merc create structure with basic information
2557 	MercCreateStruct = SOLDIERCREATE_STRUCT{};
2558 	MercCreateStruct.bTeam									= OUR_TEAM;
2559 	MercCreateStruct.ubProfile							= GetUnusedMercProfileID();
2560 	MercCreateStruct.sSectorX								= gpAR->ubSectorX;
2561 	MercCreateStruct.sSectorY								= gpAR->ubSectorY;
2562 	MercCreateStruct.bSectorZ								= 0;
2563 	MercCreateStruct.fCopyProfileItemsOver	= TRUE;
2564 
2565 	//Create the player soldier
2566 	gpMercs[gpAR->iNumMercFaces].pSoldier = TacticalCreateSoldier(MercCreateStruct);
2567 	if( gpMercs[ gpAR->iNumMercFaces ].pSoldier )
2568 	{
2569 		gpAR->iNumMercFaces++;
2570 	}
2571 }
2572 
2573 
DetermineTeamLeader(BOOLEAN fFriendlyTeam)2574 static void DetermineTeamLeader(BOOLEAN fFriendlyTeam)
2575 {
2576 	SOLDIERCELL *pBestLeaderCell = NULL;
2577 	//For each team (civs and players count as same team), find the merc with the best
2578 	//leadership ability.
2579 	if( fFriendlyTeam )
2580 	{
2581 		gpAR->ubPlayerLeadership = 0;
2582 		FOR_EACH_AR_MERC(i)
2583 		{
2584 			if (gpAR->ubPlayerLeadership >= i->pSoldier->bLeadership) continue;
2585 			gpAR->ubPlayerLeadership = i->pSoldier->bLeadership;
2586 			pBestLeaderCell          = i;
2587 		}
2588 		FOR_EACH_AR_CIV(i)
2589 		{
2590 			if (gpAR->ubPlayerLeadership >= i->pSoldier->bLeadership) continue;
2591 			gpAR->ubPlayerLeadership = i->pSoldier->bLeadership;
2592 			pBestLeaderCell          = i;
2593 		}
2594 
2595 		if( pBestLeaderCell )
2596 		{
2597 			//Assign the best leader the honour of team leader.
2598 			pBestLeaderCell->uiFlags |= CELL_TEAMLEADER;
2599 		}
2600 		return;
2601 	}
2602 	//ENEMY TEAM
2603 	gpAR->ubEnemyLeadership = 0;
2604 	FOR_EACH_AR_ENEMY(i)
2605 	{
2606 		if (gpAR->ubEnemyLeadership >= i->pSoldier->bLeadership) continue;
2607 		gpAR->ubEnemyLeadership = i->pSoldier->bLeadership;
2608 		pBestLeaderCell         = i;
2609 	}
2610 	if( pBestLeaderCell )
2611 	{
2612 		//Assign the best enemy leader the honour of team leader
2613 		pBestLeaderCell->uiFlags |= CELL_TEAMLEADER;
2614 	}
2615 }
2616 
2617 
ResetNextAttackCounter(SOLDIERCELL * pCell)2618 static void ResetNextAttackCounter(SOLDIERCELL* pCell)
2619 {
2620 	pCell->usNextAttack = MIN( 1000 - pCell->usAttack, 800 );
2621 	pCell->usNextAttack = (UINT16)(1000 + pCell->usNextAttack * 5 + PreRandom( 2000 - pCell->usAttack ) );
2622 	if( pCell->uiFlags & CELL_CREATURE )
2623 	{
2624 		pCell->usNextAttack = pCell->usNextAttack * 8 / 10;
2625 	}
2626 }
2627 
2628 
CalculateAttackValues(void)2629 static void CalculateAttackValues(void)
2630 {
2631 	SOLDIERCELL *pCell;
2632 	SOLDIERTYPE *pSoldier;
2633 	UINT16 usBonus;
2634 	UINT16 usBestAttack = 0xffff;
2635 	UINT16 usBreathStrengthPercentage;
2636 	//INT16 sOutnumberBonus = 0;
2637 	//PLAYER TEAM
2638 	gpAR->usPlayerAttack = 0;
2639 	gpAR->usPlayerDefence = 0;
2640 
2641 	//if( gpAR->ubEnemies )
2642 	//{
2643 	//	//bonus equals 20 if good guys outnumber bad guys 2 to 1.
2644 	//	const INT16 sMaxBonus = 20;
2645 	//	sOutnumberBonus = (INT16)(gpAR->ubMercs + gpAR->ubCivs) * sMaxBonus / gpAR->ubEnemies - sMaxBonus;
2646 	//	sOutnumberBonus = (INT16)MIN( sOutnumberBonus, MAX( sMaxBonus, 0 ) );
2647 	//}
2648 
2649 	for (INT32 i = 0; i < gpAR->ubMercs; i++)
2650 	{
2651 		pCell = &gpMercs[ i ];
2652 		pSoldier = pCell->pSoldier;
2653 		if( !pSoldier->bLife )
2654 			continue;
2655 		pCell->usAttack =		pSoldier->bStrength +
2656 												pSoldier->bDexterity +
2657 												pSoldier->bWisdom +
2658 												pSoldier->bMarksmanship +
2659 												pSoldier->bMorale;
2660 		//Give player controlled mercs a significant bonus to compensate for lack of control
2661 		//as the player would typically do much better in tactical.
2662 		if( pCell->usAttack < 1000 )
2663 		{ //A player with 500 attack will be augmented to 625
2664 			//A player with 600 attack will be augmented to 700
2665 			pCell->usAttack = (UINT16)(pCell->usAttack + (1000 - pCell->usAttack) / 4);
2666 		}
2667 		usBreathStrengthPercentage = 100 - ( 100 - pCell->pSoldier->bBreathMax ) / 3;
2668 		pCell->usAttack =		pCell->usAttack * usBreathStrengthPercentage / 100;
2669 		pCell->usDefence =	pSoldier->bAgility +
2670 												pSoldier->bWisdom +
2671 												pSoldier->bBreathMax +
2672 												pSoldier->bMedical +
2673 												pSoldier->bMorale;
2674 		//100 team leadership adds a bonus of 10%,
2675 		usBonus = 100 + gpAR->ubPlayerLeadership/10;// + sOutnumberBonus;
2676 
2677 		//bExpLevel adds a bonus of 7% per level after 2, level 1 soldiers get a 7% decrease
2678 		//usBonus += 7 * (pSoldier->bExpLevel-2);
2679 		usBonus += gpAR->ubPlayerDefenceAdvantage;
2680 		pCell->usAttack = pCell->usAttack * usBonus / 100;
2681 		pCell->usDefence = pCell->usDefence * usBonus / 100;
2682 
2683 		if( pCell->uiFlags & CELL_EPC )
2684 		{ //strengthen the defense (seeing the mercs will keep them safe).
2685 			pCell->usAttack = 0;
2686 			pCell->usDefence = 1000;
2687 		}
2688 
2689 		pCell->usAttack = MIN( pCell->usAttack, 1000 );
2690 		pCell->usDefence = MIN( pCell->usDefence, 1000 );
2691 
2692 		gpAR->usPlayerAttack += pCell->usAttack;
2693 		gpAR->usPlayerDefence += pCell->usDefence;
2694 		ResetNextAttackCounter( pCell );
2695 		if( i > 8 )
2696 		{ //Too many mercs, delay attack entry of extra mercs.
2697 			pCell->usNextAttack += (UINT16)( ( i - 8 ) * 2000 );
2698 		}
2699 		if( pCell->usNextAttack < usBestAttack )
2700 			usBestAttack = pCell->usNextAttack;
2701 	}
2702 	//CIVS
2703 	for (INT32 i = 0; i < gpAR->ubCivs; i++)
2704 	{
2705 		pCell = &gpCivs[ i ];
2706 		pSoldier = pCell->pSoldier;
2707 		pCell->usAttack =		pSoldier->bStrength +
2708 												pSoldier->bDexterity +
2709 												pSoldier->bWisdom +
2710 												pSoldier->bMarksmanship +
2711 												pSoldier->bMorale;
2712 		pCell->usAttack =		pCell->usAttack * pSoldier->bBreath / 100;
2713 		pCell->usDefence =	pSoldier->bAgility +
2714 												pSoldier->bWisdom +
2715 												pSoldier->bBreathMax +
2716 												pSoldier->bMedical +
2717 												pSoldier->bMorale;
2718 		//100 team leadership adds a bonus of 10%
2719 		usBonus = 100 + gpAR->ubPlayerLeadership/10;// + sOutnumberBonus;
2720 		//bExpLevel adds a bonus of 7% per level after 2, level 1 soldiers get a 7% decrease
2721 		//usBonus += 7 * (pSoldier->bExpLevel-2);
2722 		usBonus += gpAR->ubPlayerDefenceAdvantage;
2723 		pCell->usAttack = pCell->usAttack * usBonus / 100;
2724 		pCell->usDefence = pCell->usDefence * usBonus / 100;
2725 
2726 		pCell->usAttack = MIN( pCell->usAttack, 1000 );
2727 		pCell->usDefence = MIN( pCell->usDefence, 1000 );
2728 
2729 		gpAR->usPlayerAttack += pCell->usAttack;
2730 		gpAR->usPlayerDefence += pCell->usDefence;
2731 		ResetNextAttackCounter( pCell );
2732 		if( i > 6 )
2733 		{ //Too many militia, delay attack entry of extra mercs.
2734 			pCell->usNextAttack += (UINT16)(( i - 4 ) * 2000 );
2735 		}
2736 		if( pCell->usNextAttack < usBestAttack )
2737 			usBestAttack = pCell->usNextAttack;
2738 	}
2739 	//ENEMIES
2740 	gpAR->usEnemyAttack = 0;
2741 	gpAR->usEnemyDefence = 0;
2742 	//if( gpAR->ubMercs + gpAR->ubCivs )
2743 	//{
2744 	//	//bonus equals 20 if good guys outnumber bad guys 2 to 1.
2745 	//	const INT16 sMaxBonus = 20;
2746 	//	sOutnumberBonus = (INT16)gpAR->ubEnemies * sMaxBonus / (gpAR->ubMercs + gpAR->ubCivs) - sMaxBonus;
2747 	//	sOutnumberBonus = (INT16)MIN( sOutnumberBonus, MAX( sMaxBonus, 0 ) );
2748 	//}
2749 
2750 	for (INT32 i = 0; i < gpAR->ubEnemies; i++)
2751 	{
2752 		pCell = &gpEnemies[ i ];
2753 		pSoldier = pCell->pSoldier;
2754 		pCell->usAttack =		pSoldier->bStrength +
2755 												pSoldier->bDexterity +
2756 												pSoldier->bWisdom +
2757 												pSoldier->bMarksmanship +
2758 												pSoldier->bMorale;
2759 		pCell->usAttack =		pCell->usAttack * pSoldier->bBreath / 100;
2760 		pCell->usDefence =	pSoldier->bAgility +
2761 												pSoldier->bWisdom +
2762 												pSoldier->bBreathMax +
2763 												pSoldier->bMedical +
2764 												pSoldier->bMorale;
2765 		//100 team leadership adds a bonus of 10%
2766 		usBonus = 100 + gpAR->ubPlayerLeadership/10;// + sOutnumberBonus;
2767 		//bExpLevel adds a bonus of 7% per level after 2, level 1 soldiers get a 7% decrease
2768 		//usBonus += 7 * (pSoldier->bExpLevel-2);
2769 		usBonus += gpAR->ubEnemyDefenceAdvantage;
2770 		pCell->usAttack = pCell->usAttack * usBonus / 100;
2771 		pCell->usDefence = pCell->usDefence * usBonus / 100;
2772 
2773 		pCell->usAttack = MIN( pCell->usAttack, 1000 );
2774 		pCell->usDefence = MIN( pCell->usDefence, 1000 );
2775 
2776 		gpAR->usEnemyAttack += pCell->usAttack;
2777 		gpAR->usEnemyDefence += pCell->usDefence;
2778 		ResetNextAttackCounter( pCell );
2779 
2780 		if( i > 4 && !(pCell->uiFlags & CELL_CREATURE) )
2781 		{ //Too many enemies, delay attack entry of extra mercs.
2782 			pCell->usNextAttack += (UINT16)( ( i - 4 ) * 1000 );
2783 		}
2784 
2785 		if( pCell->usNextAttack < usBestAttack )
2786 			usBestAttack = pCell->usNextAttack;
2787 	}
2788 	//Now, because we are starting a new battle, we want to get the ball rolling a bit earlier.  So,
2789 	//we will take the usBestAttack value and subtract 60% of it from everybody's next attack.
2790 	usBestAttack = usBestAttack * 60 / 100;
2791 	FOR_EACH_AR_MERC(i)
2792 	{
2793 		i->usNextAttack -= usBestAttack;
2794 	}
2795 	FOR_EACH_AR_CIV(i)
2796 	{
2797 		i->usNextAttack -= usBestAttack;
2798 	}
2799 	FOR_EACH_AR_ENEMY(i)
2800 	{
2801 		i->usNextAttack -= usBestAttack;
2802 	}
2803 }
2804 
2805 
DrawDebugText(SOLDIERCELL * pCell)2806 static void DrawDebugText(SOLDIERCELL* pCell)
2807 {
2808 	INT32 xp, yp;
2809 	if( !gpAR->fDebugInfo )
2810 		return;
2811 	SetFont( SMALLCOMPFONT );
2812 	SetFontForeground( FONT_WHITE );
2813 	xp = pCell->xp + 4;
2814 	yp = pCell->yp + 4;
2815 	if( pCell->uiFlags & CELL_TEAMLEADER )
2816 	{
2817 		//debug str
2818 		MPrint(xp, yp, "LEADER");
2819 		yp += 9;
2820 	}
2821 	MPrint( xp, yp, ST::format("AT: {}", pCell->usAttack) );
2822 	yp += 9;
2823 	MPrint( xp, yp, ST::format("DF: {}", pCell->usDefence) );
2824 	yp += 9;
2825 
2826 	xp = pCell->xp;
2827 	yp = pCell->yp - 4;
2828 	SetFont( LARGEFONT1 );
2829 	SetFontShadow( FONT_NEARBLACK );
2830 	if( pCell->uiFlags & CELL_FIREDATTARGET )
2831 	{
2832 		SetFontForeground( FONT_YELLOW );
2833 		MPrint(xp, yp, "FIRE");
2834 		pCell->uiFlags &= ~CELL_FIREDATTARGET;
2835 		yp += 13;
2836 	}
2837 	if( pCell->uiFlags & CELL_DODGEDATTACK )
2838 	{
2839 		SetFontForeground( FONT_BLUE );
2840 		MPrint(xp, yp, "MISS");
2841 		pCell->uiFlags &= ~CELL_DODGEDATTACK;
2842 		yp += 13;
2843 	}
2844 	if( pCell->uiFlags & CELL_HITBYATTACKER )
2845 	{
2846 		SetFontForeground( FONT_RED );
2847 		MPrint(xp, yp, "HIT");
2848 		pCell->uiFlags &= ~CELL_HITBYATTACKER;
2849 		yp += 13;
2850 	}
2851 }
2852 
2853 
2854 static BOOLEAN IsBattleOver(void);
2855 
2856 
ChooseTarget(SOLDIERCELL * pAttacker)2857 static SOLDIERCELL* ChooseTarget(SOLDIERCELL* pAttacker)
2858 {
2859 	INT32 iAvailableTargets;
2860 	INT32 index;
2861 	INT32 iRandom = -1;
2862 	SOLDIERCELL *pTarget = NULL;
2863 	UINT16 usSavedDefence;
2864 	//Determine what team we are attacking
2865 	if( pAttacker->uiFlags & (CELL_ENEMY | CELL_CREATURE) )
2866 	{ //enemy team attacking a player
2867 		iAvailableTargets = gpAR->ubMercs + gpAR->ubCivs;
2868 		index = 0;
2869 		usSavedDefence = gpAR->usPlayerDefence;
2870 		while( iAvailableTargets )
2871 		{
2872 			pTarget = ( index < gpAR->ubMercs ) ? &gpMercs[ index ] : &gpCivs[ index - gpAR->ubMercs ];
2873 			if( !pTarget->pSoldier->bLife || pTarget->uiFlags & CELL_RETREATED )
2874 			{
2875 				index++;
2876 				iAvailableTargets--;
2877 				continue;
2878 			}
2879 			iRandom = PreRandom( gpAR->usPlayerDefence );
2880 			gpAR->usPlayerDefence -= pTarget->usDefence;
2881 			if( iRandom < pTarget->usDefence )
2882 			{
2883 				gpAR->usPlayerDefence = usSavedDefence;
2884 				return pTarget;
2885 			}
2886 			index++;
2887 			iAvailableTargets--;
2888 		}
2889 		if( !IsBattleOver() )
2890 		{
2891 			SLOGA("Please send PRIOR save and screenshot of this message. iAvailableTargets %d, index %d, iRandom %d, defence %d. ",
2892 				iAvailableTargets, index, iRandom, gpAR->usPlayerDefence);
2893 		}
2894 	}
2895 	else
2896 	{ //player team attacking an enemy
2897 		iAvailableTargets = gpAR->ubEnemies;
2898 		index = 0;
2899 		usSavedDefence = gpAR->usEnemyDefence;
2900 		while( iAvailableTargets )
2901 		{
2902 			pTarget = &gpEnemies[ index ];
2903 			if( !pTarget->pSoldier->bLife )
2904 			{
2905 				index++;
2906 				iAvailableTargets--;
2907 				continue;
2908 			}
2909 			iRandom = PreRandom( gpAR->usEnemyDefence );
2910 			gpAR->usEnemyDefence -= pTarget->usDefence;
2911 			if( iRandom < pTarget->usDefence )
2912 			{
2913 				gpAR->usEnemyDefence = usSavedDefence;
2914 				return pTarget;
2915 			}
2916 			index++;
2917 			iAvailableTargets--;
2918 		}
2919 	}
2920 	SLOGA("Error in ChooseTarget logic for choosing enemy target." );
2921 	return NULL;
2922 }
2923 
2924 
FireAShot(SOLDIERCELL * pAttacker)2925 static BOOLEAN FireAShot(SOLDIERCELL* pAttacker)
2926 {
2927 	OBJECTTYPE *pItem;
2928 	SOLDIERTYPE *pSoldier;
2929 	INT32 i;
2930 
2931 	pSoldier = pAttacker->pSoldier;
2932 
2933 	if( pAttacker->uiFlags & CELL_MALECREATURE )
2934 	{
2935 		PlayAutoResolveSample(ACR_SPIT, 50, 1, MIDDLEPAN);
2936 		pAttacker->bWeaponSlot = SECONDHANDPOS;
2937 		return TRUE;
2938 	}
2939 	for( i = 0; i < NUM_INV_SLOTS; i++ )
2940 	{
2941 		pItem = &pSoldier->inv[ i ];
2942 
2943 		if( GCM->getItem(pItem->usItem)->getItemClass() == IC_GUN )
2944 		{
2945 			pAttacker->bWeaponSlot = (INT8)i;
2946 			if( gpAR->fUnlimitedAmmo )
2947 			{
2948 				PlayAutoResolveSample(GCM->getWeapon(pItem->usItem)->sound, 50, 1, MIDDLEPAN);
2949 				return TRUE;
2950 			}
2951 			if( !pItem->ubGunShotsLeft )
2952 			{
2953 				AutoReload( pSoldier );
2954 				if (pItem->ubGunShotsLeft && GCM->getWeapon(pItem->usItem)->sLocknLoadSound != NO_SOUND)
2955 				{
2956 					PlayAutoResolveSample(GCM->getWeapon(pItem->usItem)->sLocknLoadSound, 50, 1, MIDDLEPAN);
2957 				}
2958 			}
2959 			if( pItem->ubGunShotsLeft )
2960 			{
2961 				PlayAutoResolveSample(GCM->getWeapon(pItem->usItem)->sound, 50, 1, MIDDLEPAN);
2962 				if( pAttacker->uiFlags & CELL_MERC )
2963 				{
2964 					gMercProfiles[ pAttacker->pSoldier->ubProfile ].usShotsFired++;
2965 					// MARKSMANSHIP GAIN: Attacker fires a shot
2966 					StatChange(*pAttacker->pSoldier, MARKAMT, 3, FROM_SUCCESS);
2967 				}
2968 				pItem->ubGunShotsLeft--;
2969 				return TRUE;
2970 			}
2971 		}
2972 	}
2973 	pAttacker->bWeaponSlot = -1;
2974 	return FALSE;
2975 }
2976 
2977 
AttackerHasKnife(SOLDIERCELL * const attacker)2978 static BOOLEAN AttackerHasKnife(SOLDIERCELL* const attacker)
2979 {
2980 	INT8 const slot = FindObjClass(attacker->pSoldier, IC_BLADE);
2981 	attacker->bWeaponSlot = slot;
2982 	return slot != NO_SLOT;
2983 }
2984 
2985 
TargetHasLoadedGun(SOLDIERTYPE * pSoldier)2986 static BOOLEAN TargetHasLoadedGun(SOLDIERTYPE* pSoldier)
2987 {
2988 	CFOR_EACH_SOLDIER_INV_SLOT(pItem, *pSoldier)
2989 	{
2990 		if( GCM->getItem(pItem->usItem)->getItemClass() == IC_GUN )
2991 		{
2992 			if( gpAR->fUnlimitedAmmo )
2993 			{
2994 				return TRUE;
2995 			}
2996 			if( pItem->ubGunShotsLeft )
2997 			{
2998 				return TRUE;
2999 			}
3000 		}
3001 	}
3002 	return FALSE;
3003 }
3004 
3005 
AttackTarget(SOLDIERCELL * pAttacker,SOLDIERCELL * pTarget)3006 static void AttackTarget(SOLDIERCELL* pAttacker, SOLDIERCELL* pTarget)
3007 {
3008 	UINT16 usAttack;
3009 	UINT16 usDefence;
3010 	UINT8 ubImpact;
3011 	UINT8 ubLocation;
3012 	UINT8 ubAccuracy;
3013 	INT32 iRandom;
3014 	INT32 iImpact;
3015 	INT32 iNewLife;
3016 	BOOLEAN fMelee = FALSE;
3017 	BOOLEAN fKnife = FALSE;
3018 	BOOLEAN fClaw = FALSE;
3019 	INT8	bAttackIndex = -1;
3020 
3021 	pAttacker->uiFlags |= CELL_FIREDATTARGET | CELL_DIRTY;
3022 	if( pAttacker->usAttack < 950 )
3023 		usAttack = (UINT16)(pAttacker->usAttack + PreRandom(1000 - pAttacker->usAttack ));
3024 	else
3025 		usAttack = (UINT16)(950 + PreRandom( 50 ));
3026 	if( pTarget->uiFlags & CELL_RETREATING && !(pAttacker->uiFlags & CELL_FEMALECREATURE) )
3027 	{ //Attacking a retreating merc is harder.  Modify the attack value to 70% of it's value.
3028 		//This allows retreaters to have a better chance of escaping.
3029 		usAttack = usAttack * 7 / 10;
3030 	}
3031 	if( pTarget->usDefence < 950 )
3032 		usDefence = (UINT16)(pTarget->usDefence + PreRandom(1000 - pTarget->usDefence ));
3033 	else
3034 		usDefence = (UINT16)(950 + PreRandom( 50 ));
3035 	if( pAttacker->uiFlags & CELL_FEMALECREATURE )
3036 	{
3037 		pAttacker->bWeaponSlot = HANDPOS;
3038 		fMelee = TRUE;
3039 		fClaw = TRUE;
3040 	}
3041 	else if( !FireAShot( pAttacker ) )
3042 	{ //Maybe look for a weapon, such as a knife or grenade?
3043 		fMelee = TRUE;
3044 		fKnife = AttackerHasKnife( pAttacker );
3045 		if( TargetHasLoadedGun( pTarget->pSoldier ) )
3046 		{ //Penalty to attack with melee weapons against target with loaded gun.
3047 			if( !(pAttacker->uiFlags & CELL_CREATURE ) )
3048 			{ //except for creatures
3049 				if( fKnife )
3050 					usAttack = usAttack * 6 / 10;
3051 				else
3052 					usAttack = usAttack * 4 / 10;
3053 			}
3054 		}
3055 	}
3056 	//Set up a random delay for the hit or miss.
3057 	if( !fMelee )
3058 	{
3059 		if( !pTarget->usNextHit[0] )
3060 		{
3061 			bAttackIndex = 0;
3062 		}
3063 		else if( !pTarget->usNextHit[1] )
3064 		{
3065 			bAttackIndex = 1;
3066 		}
3067 		else if( !pTarget->usNextHit[2] )
3068 		{
3069 			bAttackIndex = 2;
3070 		}
3071 		if ( bAttackIndex != -1 )
3072 		{
3073 			pTarget->usNextHit[ bAttackIndex ] = (UINT16)( 50 + PreRandom( 400 ) );
3074 			pTarget->pAttacker[ bAttackIndex ] = pAttacker;
3075 		}
3076 	}
3077 	if( usAttack < usDefence )
3078 	{
3079 		if( pTarget->pSoldier->bLife >= OKLIFE || !PreRandom( 5 ) )
3080 		{	//Attacker misses -- use up a round of ammo.  If target is unconcious, then 80% chance of hitting.
3081 			pTarget->uiFlags |= CELL_DODGEDATTACK | CELL_DIRTY;
3082 			if( fMelee )
3083 			{
3084 				if( fKnife )
3085 					PlayAutoResolveSample(MISS_KNIFE, 50, 1, MIDDLEPAN);
3086 				else if( fClaw )
3087 				{
3088 					if( Chance( 50 ) )
3089 					{
3090 						PlayAutoResolveSample(ACR_SWIPE, 50, 1, MIDDLEPAN);
3091 					}
3092 					else
3093 					{
3094 						PlayAutoResolveSample(ACR_LUNGE, 50, 1, MIDDLEPAN);
3095 					}
3096 				}
3097 				else
3098 					PlayAutoResolveSample(SoundRange<SWOOSH_1, SWOOSH_6>(), 50, 1, MIDDLEPAN);
3099 				if (pTarget->uiFlags & CELL_MERC && pTarget->pSoldier->bLife >= OKLIFE)
3100 					// AGILITY GAIN: Target "dodged" an attack
3101 					StatChange(*pTarget->pSoldier, AGILAMT, 5, FROM_SUCCESS);
3102 			}
3103 			return;
3104 		}
3105 	}
3106 	//Attacker hits
3107 	if( !fMelee )
3108 	{
3109 		ubImpact = GCM->getWeapon(pAttacker->pSoldier->inv[pAttacker->bWeaponSlot].usItem)->ubImpact;
3110 		iRandom = PreRandom( 100 );
3111 		if( iRandom < 15 )
3112 			ubLocation = AIM_SHOT_HEAD;
3113 		else if( iRandom < 30 )
3114 			ubLocation = AIM_SHOT_LEGS;
3115 		else
3116 			ubLocation = AIM_SHOT_TORSO;
3117 		ubAccuracy = (UINT8)((usAttack - usDefence + PreRandom( usDefence - pTarget->usDefence ) )/10);
3118 		iImpact = BulletImpact( pAttacker->pSoldier, pTarget->pSoldier, ubLocation, ubImpact, ubAccuracy, NULL );
3119 
3120 		if ( bAttackIndex == -1 )
3121 		{
3122 			// tack damage on to end of last hit
3123 			pTarget->usHitDamage[2] += (UINT16) iImpact;
3124 		}
3125 		else
3126 		{
3127 			pTarget->usHitDamage[ bAttackIndex ] = (UINT16) iImpact;
3128 		}
3129 
3130 	}
3131 	else
3132 	{
3133 		OBJECTTYPE *pItem;
3134 		PlayAutoResolveSample(SoundRange<BULLET_IMPACT_1, BULLET_IMPACT_3>(), 50, 1, MIDDLEPAN);
3135 		if( !pTarget->pSoldier->bLife )
3136 		{ //Soldier already dead (can't kill him again!)
3137 			return;
3138 		}
3139 
3140 		ubAccuracy = (UINT8)((usAttack - usDefence + PreRandom( usDefence - pTarget->usDefence ) )/10);
3141 
3142 		//Determine attacking weapon.
3143 		pAttacker->pSoldier->usAttackingWeapon = 0;
3144 		if( pAttacker->bWeaponSlot != -1 )
3145 		{
3146 			pItem = &pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ];
3147 			if( GCM->getItem(pItem->usItem)->isWeapon() )
3148 				pAttacker->pSoldier->usAttackingWeapon = pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ].usItem;
3149 		}
3150 
3151 		iImpact = HTHImpact(pAttacker->pSoldier, pTarget->pSoldier, ubAccuracy, fKnife | fClaw);
3152 
3153 		iNewLife = pTarget->pSoldier->bLife - iImpact;
3154 
3155 		if( pAttacker->uiFlags & CELL_MERC )
3156 		{ //Attacker is a player, so increment the number of shots that hit.
3157 			gMercProfiles[ pAttacker->pSoldier->ubProfile ].usShotsHit++;
3158 			// MARKSMANSHIP GAIN: Attacker's shot hits
3159 			StatChange(*pAttacker->pSoldier, MARKAMT, 6, FROM_SUCCESS); // in addition to 3 for taking a shot
3160 		}
3161 		if( pTarget->uiFlags & CELL_MERC )
3162 		{ //Target is a player, so increment the times he has been wounded.
3163 			gMercProfiles[ pTarget->pSoldier->ubProfile ].usTimesWounded++;
3164 			// EXPERIENCE GAIN: Took some damage
3165 			StatChange(*pTarget->pSoldier, EXPERAMT, 5 * (iImpact / 10), FROM_SUCCESS);
3166 		}
3167 		if( pTarget->pSoldier->bLife >= CONSCIOUSNESS || pTarget->uiFlags & CELL_CREATURE )
3168 		{
3169 			if( gpAR->fSound )
3170 				DoMercBattleSound(pTarget->pSoldier, BATTLE_SOUND_HIT1);
3171 		}
3172 		if( !(pTarget->uiFlags & CELL_CREATURE) && iNewLife < OKLIFE && pTarget->pSoldier->bLife >= OKLIFE )
3173 		{ //the hit caused the merc to fall.  Play the falling sound
3174 			PlayAutoResolveSample(FALL_1, 50, 1, MIDDLEPAN);
3175 			pTarget->uiFlags &= ~CELL_RETREATING;
3176 		}
3177 		if( iNewLife <= 0 )
3178 		{ //soldier has been killed
3179 			if( pAttacker->uiFlags & CELL_MERC )
3180 			{ //Player killed the enemy soldier -- update his stats as well as any assisters.
3181 				gMercProfiles[ pAttacker->pSoldier->ubProfile ].usKills++;
3182 				gStrategicStatus.usPlayerKills++;
3183 			}
3184 			else if( pAttacker->uiFlags & CELL_MILITIA )
3185 			{
3186 				pAttacker->pSoldier->ubMilitiaKills += 2;
3187 			}
3188 			if( pTarget->uiFlags & CELL_MERC && gpAR->fSound )
3189 			{
3190 				PlayAutoResolveSample(DOORCR_1, HIGHVOLUME, 1, MIDDLEPAN);
3191 				PlayAutoResolveSample(HEADCR_1, HIGHVOLUME, 1, MIDDLEPAN);
3192 			}
3193 		}
3194 		//Adjust the soldiers stats based on the damage.
3195 		pTarget->pSoldier->bLife = (INT8)MAX( iNewLife, 0 );
3196 		if( pTarget->uiFlags & CELL_MERC && gpAR->pRobotCell)
3197 		{
3198 			UpdateRobotControllerGivenRobot( gpAR->pRobotCell->pSoldier );
3199 		}
3200 		if( fKnife || fClaw )
3201 		{
3202 			if( pTarget->pSoldier->bLifeMax - pTarget->pSoldier->bBleeding - iImpact >= pTarget->pSoldier->bLife )
3203 				pTarget->pSoldier->bBleeding += (INT8)iImpact;
3204 			else
3205 				pTarget->pSoldier->bBleeding = (INT8)(pTarget->pSoldier->bLifeMax - pTarget->pSoldier->bLife);
3206 		}
3207 		if( !pTarget->pSoldier->bLife )
3208 		{
3209 			gpAR->fRenderAutoResolve = TRUE;
3210 			#ifdef INVULNERABILITY
3211 			if( 1 )
3212 				RefreshMerc( pTarget->pSoldier );
3213 			else
3214 			#endif
3215 			if( pTarget->uiFlags & CELL_MERC )
3216 			{
3217 				gpAR->usPlayerAttack -= pTarget->usAttack;
3218 				gpAR->usPlayerDefence -= pTarget->usDefence;
3219 				gpAR->ubAliveMercs--;
3220 				pTarget->usAttack = 0;
3221 				pTarget->usDefence = 0;
3222 			}
3223 			else if( pTarget->uiFlags & CELL_MILITIA )
3224 			{
3225 				gpAR->usPlayerAttack -= pTarget->usAttack;
3226 				gpAR->usPlayerDefence -= pTarget->usDefence;
3227 				gpAR->ubAliveCivs--;
3228 				pTarget->usAttack = 0;
3229 				pTarget->usDefence = 0;
3230 			}
3231 			else if( pTarget->uiFlags & (CELL_ENEMY|CELL_CREATURE) )
3232 			{
3233 				gpAR->usEnemyAttack -= pTarget->usAttack;
3234 				gpAR->usEnemyDefence -= pTarget->usDefence;
3235 				gpAR->ubAliveEnemies--;
3236 				pTarget->usAttack = 0;
3237 				pTarget->usDefence = 0;
3238 			}
3239 		}
3240 		pTarget->uiFlags |= CELL_HITBYATTACKER | CELL_DIRTY;
3241 	}
3242 }
3243 
3244 
TargetHitCallback(SOLDIERCELL * pTarget,INT32 index)3245 static void TargetHitCallback(SOLDIERCELL* pTarget, INT32 index)
3246 {
3247 	INT32 iNewLife;
3248 	SOLDIERCELL *pAttacker;
3249 	if( !pTarget->pSoldier->bLife )
3250 	{ //Soldier already dead (can't kill him again!)
3251 		return;
3252 	}
3253 	pAttacker = pTarget->pAttacker[ index ];
3254 
3255 	//creatures get damage reduction bonuses
3256 	switch( pTarget->pSoldier->ubBodyType )
3257 	{
3258 		case LARVAE_MONSTER:
3259 		case INFANT_MONSTER:
3260 			break;
3261 		case YAF_MONSTER:
3262 		case YAM_MONSTER:
3263 			pTarget->usHitDamage[index] = (pTarget->usHitDamage[index] + 2) / 4;
3264 			break;
3265 		case ADULTFEMALEMONSTER:
3266 		case AM_MONSTER:
3267 			pTarget->usHitDamage[index] = (pTarget->usHitDamage[index] + 3) / 6;
3268 			break;
3269 		case QUEENMONSTER:
3270 			pTarget->usHitDamage[index] = (pTarget->usHitDamage[index] + 4) / 8;
3271 			break;
3272 	}
3273 
3274 	iNewLife = pTarget->pSoldier->bLife - pTarget->usHitDamage[index];
3275 	if( !pTarget->usHitDamage[index] )
3276 	{ //bullet missed -- play a ricochet sound.
3277 		if (pTarget->uiFlags & CELL_MERC && pTarget->pSoldier->bLife >= OKLIFE)
3278 			// AGILITY GAIN: Target "dodged" an attack
3279 			StatChange(*pTarget->pSoldier, AGILAMT, 5, FROM_SUCCESS);
3280 		PlayAutoResolveSample(SoundRange<MISS_1, MISS_8>(), 50, 1, MIDDLEPAN);
3281 		return;
3282 	}
3283 
3284 	if( pAttacker->uiFlags & CELL_MERC )
3285 	{ //Attacker is a player, so increment the number of shots that hit.
3286 		gMercProfiles[ pAttacker->pSoldier->ubProfile ].usShotsHit++;
3287 		// MARKSMANSHIP GAIN: Attacker's shot hits
3288 		StatChange(*pAttacker->pSoldier, MARKAMT, 6, FROM_SUCCESS); // in addition to 3 for taking a shot
3289 	}
3290 	if( pTarget->uiFlags & CELL_MERC && pTarget->usHitDamage[ index ] )
3291 	{ //Target is a player, so increment the times he has been wounded.
3292 		gMercProfiles[ pTarget->pSoldier->ubProfile ].usTimesWounded++;
3293 		// EXPERIENCE GAIN: Took some damage
3294 		StatChange(*pTarget->pSoldier, EXPERAMT, 5 * (pTarget->usHitDamage[index] / 10), FROM_SUCCESS);
3295 	}
3296 
3297 	//bullet hit -- play an impact sound and a merc hit sound
3298 	PlayAutoResolveSample(SoundRange<BULLET_IMPACT_1, BULLET_IMPACT_3>(), 50, 1, MIDDLEPAN);
3299 
3300 	if( pTarget->pSoldier->bLife >= CONSCIOUSNESS )
3301 	{
3302 		if( gpAR->fSound )
3303 			DoMercBattleSound(pTarget->pSoldier, BATTLE_SOUND_HIT1);
3304 	}
3305 	if( iNewLife < OKLIFE && pTarget->pSoldier->bLife >= OKLIFE )
3306 	{ //the hit caused the merc to fall.  Play the falling sound
3307 		PlayAutoResolveSample(FALL_1, 50, 1, MIDDLEPAN);
3308 		pTarget->uiFlags &= ~CELL_RETREATING;
3309 	}
3310 	if( iNewLife <= 0 )
3311 	{ //soldier has been killed
3312 		if( pTarget->pAttacker[ index ]->uiFlags & CELL_PLAYER )
3313 		{ //Player killed the enemy soldier -- update his stats as well as any assisters.
3314 			SOLDIERCELL *pKiller;
3315 			SOLDIERCELL *pAssister1, *pAssister2;
3316 			pKiller = pTarget->pAttacker[ index ];
3317 			pAssister1 = pTarget->pAttacker[ index < 2 ? index + 1 : 0 ];
3318 			pAssister2 = pTarget->pAttacker[ index > 0 ? index - 1 : 2 ];
3319 			if( pKiller == pAssister1 )
3320 				pAssister1 = NULL;
3321 			if( pKiller == pAssister2 )
3322 				pAssister2 = NULL;
3323 			if( pAssister1 == pAssister2 )
3324 				pAssister2 = NULL;
3325 			if( pKiller )
3326 			{
3327 				if( pKiller->uiFlags & CELL_MERC )
3328 				{
3329 					gMercProfiles[ pKiller->pSoldier->ubProfile ].usKills++;
3330 					gStrategicStatus.usPlayerKills++;
3331 					// EXPERIENCE CLASS GAIN:  Earned a kill
3332 					StatChange(*pKiller->pSoldier, EXPERAMT, 10 * pTarget->pSoldier->bLevel, FROM_SUCCESS);
3333 					HandleMoraleEvent( pKiller->pSoldier, MORALE_KILLED_ENEMY, gpAR->ubSectorX, gpAR->ubSectorY, 0  );
3334 				}
3335 				else if( pKiller->uiFlags & CELL_MILITIA )
3336 					pKiller->pSoldier->ubMilitiaKills += 2;
3337 			}
3338 			if( pAssister1 )
3339 			{
3340 				if( pAssister1->uiFlags & CELL_MERC )
3341 				{
3342 					gMercProfiles[ pAssister1->pSoldier->ubProfile ].usAssists++;
3343 					// EXPERIENCE CLASS GAIN:  Earned an assist
3344 					StatChange(*pAssister1->pSoldier, EXPERAMT, 5 * pTarget->pSoldier->bLevel, FROM_SUCCESS);
3345 				}
3346 				else if( pAssister1->uiFlags & CELL_MILITIA )
3347 					pAssister1->pSoldier->ubMilitiaKills++;
3348 			}
3349 			else if( pAssister2 )
3350 			{
3351 				if( pAssister2->uiFlags & CELL_MERC )
3352 				{
3353 					gMercProfiles[ pAssister2->pSoldier->ubProfile ].usAssists++;
3354 					// EXPERIENCE CLASS GAIN:  Earned an assist
3355 					StatChange(*pAssister2->pSoldier, EXPERAMT, 5 * pTarget->pSoldier->bLevel, FROM_SUCCESS);
3356 				}
3357 				else if( pAssister2->uiFlags & CELL_MILITIA )
3358 					pAssister2->pSoldier->ubMilitiaKills++;
3359 			}
3360 		}
3361 		if( pTarget->uiFlags & CELL_MERC && gpAR->fSound )
3362 		{
3363 			PlayAutoResolveSample(DOORCR_1, HIGHVOLUME, 1, MIDDLEPAN);
3364 			PlayAutoResolveSample(HEADCR_1, HIGHVOLUME, 1, MIDDLEPAN);
3365 		}
3366 		if( iNewLife < -60 && !(pTarget->uiFlags & CELL_CREATURE) )
3367 		{ //High damage death
3368 			if( gpAR->fSound )
3369 			{
3370 				if( PreRandom( 3 ) )
3371 					PlayAutoResolveSample(BODY_SPLAT_1, 50, 1, MIDDLEPAN);
3372 				else
3373 					PlayAutoResolveSample(HEADSPLAT_1, 50, 1, MIDDLEPAN);
3374 			}
3375 		}
3376 		else
3377 		{ //Normal death
3378 			if( gpAR->fSound )
3379 			{
3380 				DoMercBattleSound( pTarget->pSoldier, BATTLE_SOUND_DIE1 );
3381 			}
3382 		}
3383 		#ifdef INVULNERABILITY
3384 			RefreshMerc( pTarget->pSoldier );
3385 			return;
3386 		#endif
3387 	}
3388 	//Adjust the soldiers stats based on the damage.
3389 	pTarget->pSoldier->bLife = (INT8)MAX( iNewLife, 0 );
3390 	if( pTarget->uiFlags & CELL_MERC && gpAR->pRobotCell)
3391 	{
3392 		UpdateRobotControllerGivenRobot( gpAR->pRobotCell->pSoldier );
3393 	}
3394 
3395 	if( pTarget->pSoldier->bLifeMax - pTarget->pSoldier->bBleeding - pTarget->usHitDamage[index] >= pTarget->pSoldier->bLife )
3396 		pTarget->pSoldier->bBleeding += (INT8)pTarget->usHitDamage[index];
3397 	else
3398 		pTarget->pSoldier->bBleeding = (INT8)(pTarget->pSoldier->bLifeMax - pTarget->pSoldier->bLife);
3399 	if( !pTarget->pSoldier->bLife )
3400 	{
3401 		gpAR->fRenderAutoResolve = TRUE;
3402 		if( pTarget->uiFlags & CELL_MERC )
3403 		{
3404 			gpAR->usPlayerAttack -= pTarget->usAttack;
3405 			gpAR->usPlayerDefence -= pTarget->usDefence;
3406 			gpAR->ubAliveMercs--;
3407 			pTarget->usAttack = 0;
3408 			pTarget->usDefence = 0;
3409 		}
3410 		else if( pTarget->uiFlags & CELL_MILITIA )
3411 		{
3412 			gpAR->usPlayerAttack -= pTarget->usAttack;
3413 			gpAR->usPlayerDefence -= pTarget->usDefence;
3414 			gpAR->ubAliveCivs--;
3415 			pTarget->usAttack = 0;
3416 			pTarget->usDefence = 0;
3417 		}
3418 		else if( pTarget->uiFlags & (CELL_ENEMY|CELL_CREATURE) )
3419 		{
3420 			gpAR->usEnemyAttack -= pTarget->usAttack;
3421 			gpAR->usEnemyDefence -= pTarget->usDefence;
3422 			gpAR->ubAliveEnemies--;
3423 			pTarget->usAttack = 0;
3424 			pTarget->usDefence = 0;
3425 		}
3426 	}
3427 	pTarget->uiFlags |= CELL_HITBYATTACKER | CELL_DIRTY;
3428 }
3429 
3430 
IsBattleOver(void)3431 static BOOLEAN IsBattleOver(void)
3432 {
3433 	INT32 iNumInvolvedMercs = 0;
3434 	INT32 iNumMercsRetreated = 0;
3435 	BOOLEAN fOnlyEPCsLeft = TRUE;
3436 	if( gpAR->ubBattleStatus != BATTLE_IN_PROGRESS )
3437 		return TRUE;
3438 	FOR_EACH_AR_MERC(i)
3439 	{
3440 		if (i->uiFlags & CELL_RETREATED)
3441 		{
3442 			++iNumMercsRetreated;
3443 		}
3444 		else if (i->pSoldier->bLife != 0 && !(i->uiFlags & CELL_EPC))
3445 		{
3446 			fOnlyEPCsLeft = FALSE;
3447 			iNumInvolvedMercs++;
3448 		}
3449 	}
3450 	if( gpAR->pRobotCell )
3451 	{ //Do special robot checks
3452 		SOLDIERTYPE *pRobot;
3453 		pRobot = gpAR->pRobotCell->pSoldier;
3454 		if (pRobot->robot_remote_holder == NULL)
3455 		{ //Robot can't fight anymore.
3456 			gpAR->usPlayerAttack -= gpAR->pRobotCell->usAttack;
3457 			gpAR->pRobotCell->usAttack = 0;
3458 			if( iNumInvolvedMercs == 1 && !gpAR->ubAliveCivs )
3459 			{ //Robot is the only one left in battle, so instantly kill him.
3460 				DoMercBattleSound( pRobot, BATTLE_SOUND_DIE1 );
3461 				pRobot->bLife = 0;
3462 				gpAR->ubAliveMercs--;
3463 				iNumInvolvedMercs = 0;
3464 			}
3465 		}
3466 	}
3467 	if( !gpAR->ubAliveCivs && !iNumInvolvedMercs && iNumMercsRetreated )
3468 	{ //RETREATED
3469 		gpAR->ubBattleStatus = BATTLE_RETREAT;
3470 
3471 		// wake everyone up
3472 		WakeUpAllMercsInSectorUnderAttack( );
3473 
3474 		RetreatAllInvolvedPlayerGroups( );
3475 	}
3476 	else if( !gpAR->ubAliveCivs && !iNumInvolvedMercs )
3477 	{ //DEFEAT
3478 		if( fOnlyEPCsLeft )
3479 		{ //Kill the EPCs.
3480 			FOR_EACH_AR_MERC(i)
3481 			{
3482 				if (!(i->uiFlags & CELL_EPC)) continue;
3483 				DoMercBattleSound(i->pSoldier, BATTLE_SOUND_DIE1);
3484 				i->pSoldier->bLife = 0;
3485 				gpAR->ubAliveMercs--;
3486 			}
3487 		}
3488 		FOR_EACH_AR_ENEMY(i)
3489 		{
3490 			if (i->pSoldier->bLife == 0) continue;
3491 			if (gubEnemyEncounterCode != CREATURE_ATTACK_CODE)
3492 			{
3493 				DoMercBattleSound(i->pSoldier, BATTLE_SOUND_LAUGH1);
3494 			}
3495 			else
3496 			{
3497 				PlayJA2Sample(ACR_EATFLESH, 50, 1, MIDDLEPAN);
3498 			}
3499 			break;
3500 		}
3501 		gpAR->ubBattleStatus = BATTLE_DEFEAT;
3502 	}
3503 	else if( !gpAR->ubAliveEnemies )
3504 	{ //VICTORY
3505 		gpAR->ubBattleStatus = BATTLE_VICTORY;
3506 	}
3507 	else
3508 	{
3509 		return FALSE;
3510 	}
3511 	SetupDoneInterface();
3512 	return TRUE;
3513 }
3514 
3515 
3516 //#define TESTSURRENDER
3517 
3518 
3519 static void SetupSurrenderInterface(void);
3520 
3521 
AttemptPlayerCapture(void)3522 static BOOLEAN AttemptPlayerCapture(void)
3523 {
3524 	BOOLEAN fConcious;
3525 	INT32 iConciousEnemies;
3526 
3527 #ifndef TESTSURRENDER
3528 
3529 	//Only attempt capture if day is less than four.
3530 	if( GetWorldDay() < STARTDAY_ALLOW_PLAYER_CAPTURE_FOR_RESCUE && !gpAR->fAllowCapture )
3531 	{
3532 		return FALSE;
3533 	}
3534 	if( gpAR->fPlayerRejectedSurrenderOffer )
3535 	{
3536 		return FALSE;
3537 	}
3538 	if( gStrategicStatus.uiFlags & STRATEGIC_PLAYER_CAPTURED_FOR_RESCUE )
3539 	{
3540 		return FALSE;
3541 	}
3542 	if( gpAR->fCaptureNotPermittedDueToEPCs )
3543 	{ //EPCs make things much more difficult when considering capture.  Simply don't allow it.
3544 		return FALSE;
3545 	}
3546 	//Only attempt capture of mercs if there are 2 or 3 of them alive
3547 	if( gpAR->ubAliveCivs || gpAR->ubAliveMercs < 2 || gpAR->ubAliveMercs > 3 )
3548 	{
3549 		return FALSE;
3550 	}
3551 	//if the number of alive enemies doesn't double the number of alive mercs, don't offer surrender.
3552 	if( gpAR->ubAliveEnemies < gpAR->ubAliveMercs*2 )
3553 	{
3554 		return FALSE;
3555 	}
3556 	//make sure that these enemies are actually concious!
3557 	iConciousEnemies = 0;
3558 	FOR_EACH_AR_ENEMY(i)
3559 	{
3560 		if (i->pSoldier->bLife < OKLIFE) continue;
3561 		++iConciousEnemies;
3562 	}
3563 	if( iConciousEnemies < gpAR->ubAliveMercs * 2 )
3564 	{
3565 		return FALSE;
3566 	}
3567 
3568 	//So far, the conditions are right.  Now, we will determine if the the remaining players are
3569 	//wounded and/or unconcious.  If any are concious, we will prompt for a surrender, otherwise,
3570 	//it is automatic.
3571 	fConcious = FALSE;
3572 	FOR_EACH_AR_MERC(i)
3573 	{
3574 		//if any of the 2 or 3 mercs has more than 60% life, then return.
3575 		if (i->uiFlags & CELL_ROBOT) return FALSE;
3576 		SOLDIERTYPE const& s = *i->pSoldier;
3577 		if (s.bLife * 100 > s.bLifeMax * 60) return FALSE;
3578 		if (s.bLife >= OKLIFE) fConcious = TRUE;
3579 	}
3580 	if( fConcious )
3581 	{
3582 		if( PreRandom( 100 ) < 2 )
3583 		{
3584 			SetupSurrenderInterface();
3585 		}
3586 	}
3587 	else if( PreRandom( 100 ) < 25 )
3588 #endif
3589 	{
3590 		BeginCaptureSquence( );
3591 
3592 		gpAR->ubBattleStatus = BATTLE_CAPTURED;
3593 		gpAR->fRenderAutoResolve = TRUE;
3594 		SetupDoneInterface();
3595 	}
3596 	return TRUE;
3597 }
3598 
3599 
SetupDoneInterface(void)3600 static void SetupDoneInterface(void)
3601 {
3602 	gpAR->fRenderAutoResolve = TRUE;
3603 
3604 	HideButton( gpAR->iButton[ PAUSE_BUTTON ] );
3605 	HideButton( gpAR->iButton[ PLAY_BUTTON ] );
3606 	HideButton( gpAR->iButton[ FAST_BUTTON ] );
3607 	HideButton( gpAR->iButton[ FINISH_BUTTON ] );
3608 	HideButton( gpAR->iButton[ RETREAT_BUTTON ] );
3609 	HideButton( gpAR->iButton[ YES_BUTTON ] );
3610 	HideButton( gpAR->iButton[ NO_BUTTON ] );
3611 	if( gpAR->ubBattleStatus == BATTLE_VICTORY && gpAR->ubAliveMercs )
3612 	{
3613 		ShowButton( gpAR->iButton[ DONEWIN_BUTTON ] );
3614 		ShowButton( gpAR->iButton[ BANDAGE_BUTTON ] );
3615 	}
3616 	else
3617 	{
3618 		ShowButton( gpAR->iButton[ DONELOSE_BUTTON ] );
3619 	}
3620 	DetermineBandageButtonState();
3621 	FOR_EACH_AR_MERC(i)
3622 	{ //So they can't retreat!
3623 		i->pRegion->Disable();
3624 	}
3625 }
3626 
3627 
SetupSurrenderInterface(void)3628 static void SetupSurrenderInterface(void)
3629 {
3630 	HideButton( gpAR->iButton[ PAUSE_BUTTON ] );
3631 	HideButton( gpAR->iButton[ PLAY_BUTTON ] );
3632 	HideButton( gpAR->iButton[ FAST_BUTTON ] );
3633 	HideButton( gpAR->iButton[ FINISH_BUTTON ] );
3634 	HideButton( gpAR->iButton[ RETREAT_BUTTON ] );
3635 	HideButton( gpAR->iButton[ BANDAGE_BUTTON ] );
3636 	HideButton( gpAR->iButton[ DONEWIN_BUTTON ] );
3637 	HideButton( gpAR->iButton[ DONELOSE_BUTTON ] );
3638 	ShowButton( gpAR->iButton[ YES_BUTTON ] );
3639 	ShowButton( gpAR->iButton[ NO_BUTTON ] );
3640 	gpAR->fRenderAutoResolve = TRUE;
3641 	gpAR->fPendingSurrender = TRUE;
3642 }
3643 
3644 
HideSurrenderInterface(void)3645 static void HideSurrenderInterface(void)
3646 {
3647 	HideButton( gpAR->iButton[ PAUSE_BUTTON ] );
3648 	HideButton( gpAR->iButton[ PLAY_BUTTON ] );
3649 	HideButton( gpAR->iButton[ FAST_BUTTON ] );
3650 	HideButton( gpAR->iButton[ FINISH_BUTTON ] );
3651 	HideButton( gpAR->iButton[ RETREAT_BUTTON ] );
3652 	HideButton( gpAR->iButton[ BANDAGE_BUTTON ] );
3653 	HideButton( gpAR->iButton[ DONEWIN_BUTTON ] );
3654 	HideButton( gpAR->iButton[ DONELOSE_BUTTON ] );
3655 	HideButton( gpAR->iButton[ YES_BUTTON ] );
3656 	HideButton( gpAR->iButton[ NO_BUTTON ] );
3657 	gpAR->fPendingSurrender = FALSE;
3658 	gpAR->fRenderAutoResolve = TRUE;
3659 }
3660 
3661 
AcceptSurrenderCallback(GUI_BUTTON * btn,INT32 reason)3662 static void AcceptSurrenderCallback(GUI_BUTTON* btn, INT32 reason)
3663 {
3664 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
3665 	{
3666 		BeginCaptureSquence( );
3667 
3668 		gpAR->ubBattleStatus = BATTLE_SURRENDERED;
3669 		gpAR->fPendingSurrender = FALSE;
3670 		SetupDoneInterface();
3671 	}
3672 }
3673 
3674 
RejectSurrenderCallback(GUI_BUTTON * btn,INT32 reason)3675 static void RejectSurrenderCallback(GUI_BUTTON* btn, INT32 reason)
3676 {
3677 	if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
3678 	{
3679 		gpAR->fPlayerRejectedSurrenderOffer = TRUE;
3680 		HideSurrenderInterface();
3681 	}
3682 }
3683 
3684 
ProcessBattleFrame(void)3685 static void ProcessBattleFrame(void)
3686 {
3687 	INT32 iRandom;
3688 	SOLDIERCELL *pAttacker, *pTarget;
3689 	UINT32 uiDiff;
3690 	static INT32 iTimeSlice = 0;
3691 	static BOOLEAN fContinue = FALSE;
3692 	static UINT32 uiSlice = 0;
3693 	static INT32 iTotal = 0;
3694 	static INT32 iMercs = 0;
3695 	static INT32 iCivs = 0;
3696 	static INT32 iEnemies = 0;
3697 	static INT32 iMercsLeft = 0;
3698 	static INT32 iCivsLeft = 0;
3699 	static INT32 iEnemiesLeft = 0;
3700 	BOOLEAN found = FALSE;
3701 	INT32 iTime, iAttacksThisFrame;
3702 
3703 	pAttacker = NULL;
3704 	iAttacksThisFrame = 0;
3705 
3706 	if( fContinue )
3707 	{
3708 		gpAR->uiCurrTime = GetJA2Clock();
3709 		fContinue = FALSE;
3710 		goto CONTINUE_BATTLE;
3711 	}
3712 	//determine how much real-time has passed since the last frame
3713 	if( gpAR->uiCurrTime )
3714 	{
3715 		gpAR->uiPrevTime = gpAR->uiCurrTime;
3716 		gpAR->uiCurrTime = GetJA2Clock();
3717 	}
3718 	else
3719 	{
3720 		gpAR->uiCurrTime = GetJA2Clock();
3721 		return;
3722 	}
3723 	if( gpAR->fPaused )
3724 		return;
3725 
3726 	uiDiff = gpAR->uiCurrTime - gpAR->uiPrevTime;
3727 	if( gpAR->uiTimeSlice < 0xffffffff )
3728 	{
3729 		iTimeSlice = uiDiff*gpAR->uiTimeSlice/1000;
3730 	}
3731 	else
3732 	{ //largest positive signed value
3733 		iTimeSlice = 0x7fffffff;
3734 	}
3735 
3736 	while( iTimeSlice > 0 )
3737 	{
3738 		uiSlice = MIN( iTimeSlice, 1000 );
3739 		if( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
3740 			gpAR->uiTotalElapsedBattleTimeInMilliseconds += uiSlice;
3741 
3742 		//Now process each of the players
3743 		iTotal   = gpAR->ubMercs + gpAR->ubCivs + gpAR->ubEnemies + 1;
3744 		iMercs   = iMercsLeft   = gpAR->ubMercs;
3745 		iCivs    = iCivsLeft    = gpAR->ubCivs;
3746 		iEnemies = iEnemiesLeft = gpAR->ubEnemies;
3747 		FOR_EACH_AR_MERC(i)
3748 			i->uiFlags &= ~CELL_PROCESSED;
3749 		FOR_EACH_AR_CIV(i)
3750 			i->uiFlags &= ~CELL_PROCESSED;
3751 		FOR_EACH_AR_ENEMY(i)
3752 			i->uiFlags &= ~CELL_PROCESSED;
3753 		while( --iTotal )
3754 		{
3755 			INT32 cnt;
3756 			if( (iTimeSlice != 0x7fffffff && GetJA2Clock() > gpAR->uiCurrTime+17) ||
3757 				(!gpAR->fInstantFinish && iAttacksThisFrame > (gpAR->ubMercs+gpAR->ubCivs+gpAR->ubEnemies)/4) )
3758 			{ //We have spent too much time in here.  In order to maintain 60FPS, we will
3759 				//leave now, which will allow for updating of the graphics (and mouse cursor),
3760 				//and all of the necessary locals are saved via static variables.  It'll check
3761 				//the fContinue flag, and goto the CONTINUE_BATTLE label the next time this function
3762 				//is called.
3763 				fContinue = TRUE;
3764 				return;
3765 			}
3766 			CONTINUE_BATTLE:
3767 			if( IsBattleOver() || (gubEnemyEncounterCode != CREATURE_ATTACK_CODE && AttemptPlayerCapture()) )
3768 				return;
3769 
3770 			iRandom = PreRandom( iTotal );
3771 			found = FALSE;
3772 			if( iMercs && iRandom < iMercsLeft )
3773 			{
3774 				iMercsLeft--;
3775 				while( !found )
3776 				{
3777 					iRandom = PreRandom( iMercs );
3778 					pAttacker = &gpMercs[ iRandom ];
3779 					if( !(pAttacker->uiFlags & CELL_PROCESSED ) )
3780 					{
3781 						pAttacker->uiFlags |= CELL_PROCESSED;
3782 						found = TRUE;
3783 					}
3784 				}
3785 			}
3786 			else if( iCivs && iRandom < iMercsLeft + iCivsLeft )
3787 			{
3788 				iCivsLeft--;
3789 				while( !found )
3790 				{
3791 					iRandom = PreRandom( iCivs );
3792 					pAttacker = &gpCivs[ iRandom ];
3793 					if( !(pAttacker->uiFlags & CELL_PROCESSED ) )
3794 					{
3795 						pAttacker->uiFlags |= CELL_PROCESSED;
3796 						found = TRUE;
3797 					}
3798 				}
3799 			}
3800 			else if( iEnemies && iEnemiesLeft )
3801 			{
3802 				iEnemiesLeft--;
3803 				while( !found )
3804 				{
3805 					iRandom = PreRandom( iEnemies );
3806 					pAttacker = &gpEnemies[ iRandom ];
3807 					if( !(pAttacker->uiFlags & CELL_PROCESSED ) )
3808 					{
3809 						pAttacker->uiFlags |= CELL_PROCESSED;
3810 						found = TRUE;
3811 					}
3812 				}
3813 			}
3814 			else
3815 				SLOGA("Logic error in ProcessBattleFrame()" );
3816 			//Apply damage and play miss/hit sounds if delay between firing and hit has expired.
3817 			if( !(pAttacker->uiFlags & CELL_RETREATED ) )
3818 			{
3819 				for( cnt = 0; cnt < 3; cnt++ )
3820 				{ //Check if any incoming bullets have hit the target.
3821 					if( pAttacker->usNextHit[ cnt ] )
3822 					{
3823 						iTime = pAttacker->usNextHit[ cnt ];
3824 						iTime -= uiSlice;
3825 						if( iTime >= 0 )
3826 						{ //Bullet still on route.
3827 							pAttacker->usNextHit[ cnt ] = (UINT16)iTime;
3828 						}
3829 						else
3830 						{ //Bullet is going to hit/miss.
3831 							TargetHitCallback( pAttacker, cnt );
3832 							pAttacker->usNextHit[ cnt ] = 0;
3833 						}
3834 					}
3835 				}
3836 			}
3837 			if( pAttacker->pSoldier->bLife < OKLIFE || pAttacker->uiFlags & CELL_RETREATED )
3838 			{
3839 				if( !(pAttacker->uiFlags & CELL_CREATURE) || !pAttacker->pSoldier->bLife )
3840 					continue; //can't attack if you are unconcious or not around (Or a live creature)
3841 			}
3842 			iTime = pAttacker->usNextAttack;
3843 			iTime -= uiSlice;
3844 			if( iTime > 0 )
3845 			{
3846 				pAttacker->usNextAttack = (UINT16)iTime;
3847 				continue;
3848 			}
3849 			else
3850 			{
3851 				if( pAttacker->uiFlags & CELL_RETREATING )
3852 				{ //The merc has successfully retreated.  Remove the stats, and continue on.
3853 					if( pAttacker == gpAR->pRobotCell )
3854 					{
3855 						if (gpAR->pRobotCell->pSoldier->robot_remote_holder == NULL)
3856 						{
3857 							gpAR->pRobotCell->uiFlags &= ~CELL_RETREATING;
3858 							gpAR->pRobotCell->uiFlags |= CELL_DIRTY;
3859 							gpAR->pRobotCell->usNextAttack = 0xffff;
3860 							continue;
3861 						}
3862 					}
3863 					gpAR->usPlayerDefence -= pAttacker->usDefence;
3864 					pAttacker->usDefence = 0;
3865 					pAttacker->uiFlags |= CELL_RETREATED;
3866 					continue;
3867 				}
3868 				if( pAttacker->usAttack )
3869 				{
3870 					pTarget = ChooseTarget( pAttacker );
3871 					if( pAttacker->uiFlags & CELL_CREATURE && PreRandom( 100 ) < 7 )
3872 						PlayAutoResolveSample(SoundRange<ACR_SMELL_THREAT, ACR_SMELL_PREY>(), 50, 1, MIDDLEPAN);
3873 					else
3874 						AttackTarget( pAttacker, pTarget );
3875 					ResetNextAttackCounter( pAttacker );
3876 					pAttacker->usNextAttack += (UINT16)iTime; //tack on the remainder
3877 					iAttacksThisFrame++;
3878 				}
3879 			}
3880 		}
3881 		if( iTimeSlice != 0x7fffffff )//|| !gpAR->fInstantFinish )
3882 		{
3883 			iTimeSlice -= 1000;
3884 		}
3885 	}
3886 }
3887 
3888 
IsAutoResolveActive()3889 BOOLEAN IsAutoResolveActive()
3890 {
3891 	//is the autoresolve up or not?
3892 	if( gpAR )
3893 	{
3894 		return TRUE;
3895 	}
3896 	return FALSE;
3897 }
3898 
GetAutoResolveSectorID()3899 UINT8 GetAutoResolveSectorID()
3900 {
3901 	if( gpAR )
3902 	{
3903 		return (UINT8)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
3904 	}
3905 	return 0xff;
3906 }
3907 
3908 //Returns TRUE if a battle is happening or sector is loaded
GetCurrentBattleSectorXYZ(INT16 * psSectorX,INT16 * psSectorY,INT16 * psSectorZ)3909 BOOLEAN GetCurrentBattleSectorXYZ( INT16 *psSectorX, INT16 *psSectorY, INT16 *psSectorZ )
3910 {
3911 	if( gpAR )
3912 	{
3913 		*psSectorX = gpAR->ubSectorX;
3914 		*psSectorY = gpAR->ubSectorY;
3915 		*psSectorZ = 0;
3916 		return TRUE;
3917 	}
3918 	else if( gfPreBattleInterfaceActive )
3919 	{
3920 		*psSectorX = gubPBSectorX;
3921 		*psSectorY = gubPBSectorY;
3922 		*psSectorZ = gubPBSectorZ;
3923 		return TRUE;
3924 	}
3925 	else if( gfWorldLoaded )
3926 	{
3927 		*psSectorX = gWorldSectorX;
3928 		*psSectorY = gWorldSectorY;
3929 		*psSectorZ = gbWorldSectorZ;
3930 		return TRUE;
3931 	}
3932 	else
3933 	{
3934 		*psSectorX = 0;
3935 		*psSectorY = 0;
3936 		*psSectorZ = -1;
3937 		return FALSE;
3938 	}
3939 }
3940 
3941 
GetCurrentBattleSectorXYZAndReturnTRUEIfThereIsABattle(INT16 * psSectorX,INT16 * psSectorY,INT16 * psSectorZ)3942 BOOLEAN GetCurrentBattleSectorXYZAndReturnTRUEIfThereIsABattle(INT16* psSectorX, INT16* psSectorY, INT16* psSectorZ)
3943 {
3944 	if( gpAR )
3945 	{
3946 		*psSectorX = gpAR->ubSectorX;
3947 		*psSectorY = gpAR->ubSectorY;
3948 		*psSectorZ = 0;
3949 		return TRUE;
3950 	}
3951 	else if( gfPreBattleInterfaceActive )
3952 	{
3953 		*psSectorX = gubPBSectorX;
3954 		*psSectorY = gubPBSectorY;
3955 		*psSectorZ = gubPBSectorZ;
3956 		return TRUE;
3957 	}
3958 	else if( gfWorldLoaded )
3959 	{
3960 		*psSectorX = gWorldSectorX;
3961 		*psSectorY = gWorldSectorY;
3962 		*psSectorZ = gbWorldSectorZ;
3963 		if( gTacticalStatus.fEnemyInSector )
3964 		{
3965 			return TRUE;
3966 		}
3967 		return FALSE;
3968 	}
3969 	else
3970 	{
3971 		*psSectorX = 0;
3972 		*psSectorY = 0;
3973 		*psSectorZ = -1;
3974 		return FALSE;
3975 	}
3976 }
3977 
3978 
AutoBandageFinishedCallback(MessageBoxReturnValue const ubResult)3979 static void AutoBandageFinishedCallback(MessageBoxReturnValue const ubResult)
3980 {
3981 	SetupDoneInterface();
3982 }
3983