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