Files
source/Strategic/Auto Resolve.cpp
T
Asdow 6c33009fab Remove bLastKnownEnemies from SECTORINFO
As per the old comment, even though the field value is updated partially according to whether there are known enemies present or not, it's not used for anything.
2024-11-11 00:14:51 +02:00

6063 lines
201 KiB
C++

#include "builddefines.h"
#include <stdio.h>
#include "types.h"
#include "Auto Resolve.h"
#include "Strategic Movement.h"
#include "Queen Command.h"
#include "Music Control.h"
#include "PreBattle Interface.h"
#include "Player Command.h"
#include "mousesystem.h"
#include "Button System.h"
#include "gameloop.h"
#include "screenids.h"
#include "mapscreen.h"
#include "vobject.h"
#include "video.h"
#include "input.h"
#include "gamescreen.h"
#include "render dirty.h"
#include "vObject_Blitters.h"
#include "sysutil.h"
#include "Font Control.h"
#include "Soldier Create.h"
#include "Overhead.h"
#include "Interface.h"
#include "Items.h"
#include "Weapons.h"
#include "Sound Control.h"
#include "Game Clock.h"
#include "Soldier Profile.h"
#include "Campaign.h"
#include "Tactical Save.h"
#include "Strategic Status.h"
#include "Map Screen Interface.h"
#include "text.h"
#include "WordWrap.h"
#include "Squads.h"
#include "Random.h"
#include "line.h"
#include "english.h"
#include "Strategic Pathing.h"
#include "Strategic Merc Handler.h"
#include "strategic.h"
#include "message.h"
#include "Town Militia.h"
#include "Animation Data.h"
#include "Creature Spreading.h"
#include "Strategic AI.h"
#include "SkillCheck.h"
#include "rt time defines.h"
#include "morale.h"
#include "Strategic Town Loyalty.h"
#include "GameSettings.h"
#include "Soldier macros.h"
#include "strategicmap.h"
#include "Quests.h"
#include "meanwhile.h"
#include "Inventory Choosing.h"
#include "Game Event Hook.h"
#include "Assignments.h"
#include "cheats.h"
#include "Map Information.h"
#include "MilitiaSquads.h"
// #include "Strategic AI.h"
#include "interface Dialogue.h"
#include "AIInternals.h" // added by SANDRO
#include "Bullets.h" // HEADROCK HAM 5, for use with Bullet Impact.
#include "CampaignStats.h" // added by Flugente
#include "DynamicDialogue.h" // added by Flugente
#include "MilitiaIndividual.h" // added by Flugente
#include "Rebel Command.h"
#include "Reinforcement.h"
//#define INVULNERABILITY
// default = 7 (%)
#define EXP_BONUS 7
#define MAX_AR_TEAM_SIZE 255 // Must fit in 8 bits
#define REINFORCMENT_ATTACK_DELAY_PER_SOLDIER_IN_SECTOR 1000
INT32 giMaxEnemiesToRender = 40;
INT32 giMaxMilitiaToRender = 20;//Changes depending on merc amount
extern UINT8 gubReinforcementMinEnemyStaticGroupSize;
extern BOOLEAN gfStrategicMilitiaChangesMade;
extern void ResetMilitia();
extern BOOLEAN AutoReload( SOLDIERTYPE *pSoldier, bool aReloadEvenIfNotEmpty );
extern HVSURFACE ghFrameBuffer;
BOOLEAN gfTransferTacticalOppositionToAutoResolve = FALSE;
//button images
enum
{
PAUSE_BUTTON,
PLAY_BUTTON,
FAST_BUTTON,
FINISH_BUTTON,
YES_BUTTON,
NO_BUTTON,
BANDAGE_BUTTON,
RETREAT_BUTTON,
DONEWIN_BUTTON,
DONELOSE_BUTTON,
NUM_AR_BUTTONS
};
typedef struct SOLDIERCELL
{
SOLDIERTYPE *pSoldier;
MOUSE_REGION *pRegion; //only used for player mercs.
UINT32 uiVObjectID;
UINT16 usIndex;
UINT32 uiFlags;
UINT16 usFrame;
INT16 xp, yp;
UINT16 usAttack, usDefence;
UINT32 usNextAttack;
UINT16 usNextHit[3];
UINT16 usHitDamage[3];
struct SOLDIERCELL *pAttacker[3];
UINT32 uiFlashTime;
INT8 bWeaponSlot;
}SOLDIERCELL;
typedef struct AUTORESOLVE_STRUCT
{
SOLDIERCELL *pRobotCell;
//IDs into the graphic images
INT32 iPanelImages;
INT32 iButton[ NUM_AR_BUTTONS ];
INT32 iButtonImage[ NUM_AR_BUTTONS ];
INT32 iFaces; //for generic civs and enemies
// WDS - make number of mercenaries, etc. be configurable
//INT32 iMercFaces[CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS]; //for each merc face
INT32 iIndent;
INT32 iInterfaceBuffer;
INT32 iNumMercFaces;
INT32 iActualMercFaces; //this represents the real number of merc faces. Because
//my debug mode allows to freely add and subtract mercs, we
//can add/remove temp mercs, but we don't want to remove the
//actual mercs.
UINT32 uiTimeSlice;
UINT32 uiTotalElapsedBattleTimeInMilliseconds;
UINT32 uiPrevTime, uiCurrTime;
UINT32 uiStartExpanding;
UINT32 uiEndExpanding;
UINT32 uiPreRandomIndex;
SGPRect Rect, ExRect;
UINT32 usPlayerAttack;
UINT32 usPlayerDefence;
UINT32 usEnemyAttack;
UINT32 usEnemyDefence;
INT16 sWidth, sHeight;
INT16 sCenterStartX;
UINT8 ubEnemyLeadership;
UINT8 ubPlayerLeadership;
UINT16 ubMercs, ubCivs, ubEnemies;
UINT16 ubAdmins, ubTroops, ubElites, ubTanks, ubJeeps, ubRobots;
UINT16 ubYMCreatures, ubYFCreatures, ubAMCreatures, ubAFCreatures;
UINT16 ubBloodcats;
UINT16 ubZombies;
UINT16 ubBandits;
UINT16 ubAliveMercs, ubAliveCivs, ubAliveEnemies;
UINT8 ubMercCols, ubMercRows;
UINT8 ubEnemyCols, ubEnemyRows;
UINT8 ubCivCols, ubCivRows;
UINT8 ubTimeModifierPercentage;
UINT8 ubSectorX, ubSectorY;
INT8 bVerticalOffset;
BOOLEAN fRenderAutoResolve;
BOOLEAN fExitAutoResolve;
BOOLEAN fPaused;
BOOLEAN fDebugInfo;
BOOLEAN ubBattleStatus;
BOOLEAN fUnlimitedAmmo;
BOOLEAN fSound;
BOOLEAN ubPlayerDefenceAdvantage;
BOOLEAN ubEnemyDefenceAdvantage;
BOOLEAN fInstantFinish;
BOOLEAN fAllowCapture;
BOOLEAN fPlayerRejectedSurrenderOffer;
BOOLEAN fPendingSurrender;
BOOLEAN fExpanding;
BOOLEAN fShowInterface;
BOOLEAN fEnteringAutoResolve;
BOOLEAN fMoraleEventsHandled;
BOOLEAN fCaptureNotPermittedDueToEPCs;
MOUSE_REGION AutoResolveRegion;
}AUTORESOLVE_STRUCT;
//Classifies the type of soldier the soldier cell is
#define CELL_MERC 0x00000001
#define CELL_MILITIA 0x00000002
#define CELL_ELITE 0x00000004
#define CELL_TROOP 0x00000008
#define CELL_ADMIN 0x00000010
#define CELL_AF_CREATURE 0x00000020
#define CELL_AM_CREATURE 0x00000040
#define CELL_YF_CREATURE 0x00000080
#define CELL_YM_CREATURE 0x00000100
//The team leader is the one with the highest leadership.
//There can only be one teamleader per side (mercs/civs and enemies)
#define CELL_TEAMLEADER 0x00000200
//Combat flags
#define CELL_FIREDATTARGET 0x00000400
#define CELL_DODGEDATTACK 0x00000800
#define CELL_HITBYATTACKER 0x00001000
#define CELL_HITLASTFRAME 0x00002000
//Cell statii
#define CELL_SHOWRETREATTEXT 0x00004000
#define CELL_RETREATING 0x00008000
#define CELL_RETREATED 0x00010000
#define CELL_DIRTY 0x00020000
#define CELL_PROCESSED 0x00040000
#define CELL_ASSIGNED 0x00080000
#define CELL_EPC 0x00100000
#define CELL_ROBOT 0x00200000
#define CELL_TANK 0x00400000
#define CELL_JEEP 0x00800000
#define CELL_BLOODCAT 0x01000000
#define CELL_ZOMBIE 0x02000000
#define CELL_BANDIT 0x04000000
#define CELL_ENEMYROBOT 0x08000000
//Combined flags
#define CELL_PLAYER ( CELL_MERC | CELL_MILITIA )
#define CELL_ENEMY ( CELL_ELITE | CELL_TROOP | CELL_ADMIN | CELL_TANK | CELL_JEEP | CELL_BANDIT | CELL_ENEMYROBOT )
#define CELL_FEMALECREATURE ( CELL_AF_CREATURE | CELL_YF_CREATURE )
#define CELL_MALECREATURE ( CELL_AM_CREATURE | CELL_YM_CREATURE )
#define CELL_BUG ( CELL_FEMALECREATURE | CELL_MALECREATURE )
#define CELL_CREATURE ( CELL_BUG | CELL_BLOODCAT | CELL_ZOMBIE )
#define CELL_INVOLVEDINCOMBAT ( CELL_FIREDATTARGET | CELL_DODGEDATTACK | CELL_HITBYATTACKER )
enum
{
BATTLE_IN_PROGRESS,
BATTLE_VICTORY,
BATTLE_DEFEAT,
BATTLE_RETREAT,
BATTLE_SURRENDERED,
BATTLE_CAPTURED
};
//panel pieces
enum
{
TL_BORDER,
T_BORDER,
TR_BORDER,
L_BORDER,
C_TEXTURE,
R_BORDER,
BL_BORDER,
B_BORDER,
BR_BORDER,
TOP_MIDDLE,
AUTO_MIDDLE,
BOT_MIDDLE,
MERC_PANEL,
OTHER_PANEL,
};
extern void CreateDestroyMapInvButton();
extern void DestroyMapCharInvIOregions();
//Autoresolve sets this variable which defaults to -1 when not needed.
INT16 gsEnemyGainedControlOfSectorID = -1;
INT16 gsCiviliansEatenByMonsters = -1;
BOOLEAN ProcessLoyalty();
//Autoresolve handling -- keyboard input, callbacks
void HandleAutoResolveInput();
void PauseButtonCallback( GUI_BUTTON *btn, INT32 reason );
void PlayButtonCallback( GUI_BUTTON *btn, INT32 reason );
void FastButtonCallback( GUI_BUTTON *btn, INT32 reason );
void FinishButtonCallback( GUI_BUTTON *btn, INT32 reason );
void RetreatButtonCallback( GUI_BUTTON *btn, INT32 reason );
void BandageButtonCallback( GUI_BUTTON *btn, INT32 reason );
void DoneButtonCallback( GUI_BUTTON *btn, INT32 reason );
void MercCellMouseMoveCallback( MOUSE_REGION *reg, INT32 reason );
void MercCellMouseClickCallback( MOUSE_REGION *reg, INT32 reason );
void DetermineBandageButtonState();
//Surrender interface
void SetupDoneInterface();
void SetupSurrenderInterface();
void HideSurrenderInterface();
void AcceptSurrenderCallback( GUI_BUTTON *btn, INT32 reason );
void RejectSurrenderCallback( GUI_BUTTON *btn, INT32 reason );
//Precalculations for interface positioning and the calculation routines to do so.
void CalculateAutoResolveInfo();
void CalculateSoldierCells( BOOLEAN fReset );
void CalculateRowsAndColumns();
void CreateAutoResolveInterface();
void RemoveAutoResolveInterface( BOOLEAN fDeleteForGood );
//Battle system routines
void DetermineTeamLeader( BOOLEAN fFriendlyTeam );
void CalculateAttackValues();
void ProcessBattleFrame();
BOOLEAN IsBattleOver();
BOOLEAN AttemptPlayerCapture();
void AutoBandageFinishedCallback( UINT8 ubResult );
//Debug utilities
void ResetAutoResolveInterface();
void CreateTempPlayerMerc();
void DrawDebugText( SOLDIERCELL *pCell );
//Rendering routines
void RenderAutoResolve();
void RenderSoldierCellHealth( SOLDIERCELL *pCell );
void RenderSoldierCell( SOLDIERCELL *pCell );
void RenderSoldierCellBars( SOLDIERCELL *pCell );
//Dynamic globals -- to conserve memory, all global variables are allocated upon entry
//and deleted before we leave.
AUTORESOLVE_STRUCT *gpAR = NULL;
SOLDIERCELL *gpMercs = NULL;
SOLDIERCELL *gpCivs = NULL;
SOLDIERCELL *gpEnemies = NULL;
//Simple wrappers for autoresolve sounds that are played.
void PlayAutoResolveSample( UINT32 usNum, UINT32 usRate, UINT32 ubVolume, UINT32 ubLoops, UINT32 uiPan )
{
if( gpAR->fSound )
{
PlayJA2Sample( usNum, usRate, ubVolume, ubLoops, uiPan );
}
}
void PlayAutoResolveSampleFromFile( STR8 szFileName, UINT32 usRate, UINT32 ubVolume, UINT32 ubLoops, UINT32 uiPan )
{
if( gpAR->fSound )
{
PlayJA2SampleFromFile( szFileName, usRate, ubVolume, ubLoops, uiPan );
}
}
extern void ClearPreviousAIGroupAssignment( GROUP *pGroup );
void EliminateAllMercs()
{
SOLDIERCELL *pAttacker = NULL;
INT32 i, iNum = 0;
if( gpAR )
{
for( i = 0; i < gpAR->ubEnemies; i++ )
{
if( gpEnemies[ i ].pSoldier->stats.bLife )
{
pAttacker = &gpEnemies[ i ];
break;
}
}
if( pAttacker )
{
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pSoldier->stats.bLife )
{
iNum++;
gpMercs[ i ].pSoldier->stats.bLife = 1;
gpMercs[ i ].usNextHit[0] = (UINT16)(250 * iNum);
gpMercs[ i ].usHitDamage[0] = 100;
gpMercs[ i ].pAttacker[0] = pAttacker;
}
}
}
}
}
void EliminateAllFriendlies()
{
INT32 i;
if( gpAR )
{
for( i = 0; i < gpAR->ubMercs; i++ )
{
gpMercs[ i ].pSoldier->stats.bLife = 0;
}
gpAR->ubAliveMercs = 0;
for( i = 0; i < gpAR->ubCivs; i++ )
{
gpCivs[ i ].pSoldier->stats.bLife = 0;
}
gpAR->ubAliveCivs = 0;
}
}
void EliminateAllEnemies( UINT8 ubSectorX, UINT8 ubSectorY )
{
GROUP *pGroup, *pDeleteGroup;
SECTORINFO *pSector;
INT32 i;
UINT16 ubNumEnemies[NUM_ENEMY_RANKS]{};
UINT16 ubNumTanks = 0;
UINT16 ubNumJeeps = 0;
UINT16 ubNumRobots = 0;
UINT8 ubRankIndex;
//Clear any possible battle locator
gfBlitBattleSectorLocator = FALSE;
pGroup = gpGroupList;
pSector = &SectorInfo[ SECTOR( ubSectorX, ubSectorY ) ];
// if we're doing this from the Pre-Battle interface, gpAR is NULL, and RemoveAutoResolveInterface(0 won't happen, so
// we must process the enemies killed right here & give out loyalty bonuses as if the battle had been fought & won
if( !gpAR )
{
GetNumberOfEnemiesInSector( ubSectorX, ubSectorY, &ubNumEnemies[0], &ubNumEnemies[1], &ubNumEnemies[2], &ubNumRobots, &ubNumTanks, &ubNumJeeps );
for ( ubRankIndex = 0; ubRankIndex < NUM_ENEMY_RANKS; ++ubRankIndex )
{
for ( i = 0; i < ubNumEnemies[ ubRankIndex ]; ++i )
{
if( ProcessLoyalty() )
HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ENEMY_KILLED, ubSectorX, ubSectorY, 0 );
TrackEnemiesKilled( ENEMY_KILLED_IN_AUTO_RESOLVE, RankIndexToSoldierClass( ubRankIndex ) );
}
}
if ( ProcessLoyalty( ) )
{
for ( i = 0; i < ubNumTanks; ++i )
{
HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ENEMY_KILLED, ubSectorX, ubSectorY, 0 );
}
HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_WON, ubSectorX, ubSectorY, 0 );
}
}
if( !gpAR || gpAR->ubBattleStatus != BATTLE_IN_PROGRESS )
{
//Remove the defend force here.
pSector->ubNumTroops = 0;
pSector->ubNumElites = 0;
pSector->ubNumAdmins = 0;
pSector->ubNumTanks = 0;
pSector->ubNumJeeps = 0;
pSector->ubNumRobots = 0;
pSector->ubNumCreatures = 0;
//Remove the mobile forces here, but only if battle is over.
while( pGroup )
{
if ( pGroup->usGroupTeam != OUR_TEAM && pGroup->ubSectorX == ubSectorX && pGroup->ubSectorY == ubSectorY )
{
ClearPreviousAIGroupAssignment( pGroup );
pDeleteGroup = pGroup;
pGroup = pGroup->next;
if( gpBattleGroup == pDeleteGroup )
gpBattleGroup = NULL;
RemovePGroup( pDeleteGroup );
}
else
pGroup = pGroup->next;
}
if( gpBattleGroup )
{
CalculateNextMoveIntention( gpBattleGroup );
}
// set this sector as taken over
SetThisSectorAsPlayerControlled( ubSectorX, ubSectorY, 0, TRUE );
RecalculateSectorWeight( (UINT8)SECTOR( ubSectorX, ubSectorY ) );
// dirty map panel
fMapPanelDirty = TRUE;
}
if( gpAR )
{
for( i = 0; i < gpAR->ubEnemies; ++i )
{
gpEnemies[ i ].pSoldier->stats.bLife = 0;
}
gpAR->ubAliveEnemies = 0;
}
gpBattleGroup = NULL;
}
#define ORIG_LEFT 26
#define ORIG_TOP 53
#define ORIG_RIGHT 92
#define ORIG_BOTTOM 84
void DoTransitionFromPreBattleInterfaceToAutoResolve()
{
SGPRect SrcRect, DstRect;
UINT32 uiStartTime, uiCurrTime;
INT32 iPercentage, iFactor;
UINT32 uiTimeRange;
INT16 sStartLeft, sEndLeft, sStartTop, sEndTop;
INT32 iLeft, iTop, iWidth, iHeight;
PauseTime( FALSE );
gpAR->fShowInterface = TRUE;
SrcRect.iLeft = gpAR->Rect.iLeft;
SrcRect.iTop = gpAR->Rect.iTop;
SrcRect.iRight = gpAR->Rect.iRight;
SrcRect.iBottom = gpAR->Rect.iBottom;
iWidth = SrcRect.iRight - SrcRect.iLeft + 1;
iHeight = SrcRect.iBottom - SrcRect.iTop + 1;
uiTimeRange = 1000;
iPercentage = 0;
uiStartTime = GetJA2Clock();
sStartLeft = 59 + xResOffset;
sStartTop = 69 + yResOffset;
sEndLeft = SrcRect.iLeft + gpAR->sWidth / 2;
sEndTop = SrcRect.iTop + gpAR->sHeight / 2;
//save the prebattle/mapscreen interface background
BlitBufferToBuffer( FRAME_BUFFER, guiEXTRABUFFER, 0 + xResOffset, 0, SCREEN_WIDTH, SCREEN_HEIGHT );
//render the autoresolve panel
RenderAutoResolve();
RenderButtons();
RenderButtonsFastHelp();
//save it
BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, (UINT16)SrcRect.iLeft, (UINT16)SrcRect.iTop, (UINT16)SrcRect.iRight, (UINT16)SrcRect.iBottom );
//hide the autoresolve
BlitBufferToBuffer( guiEXTRABUFFER, FRAME_BUFFER, (UINT16)SrcRect.iLeft, (UINT16)SrcRect.iTop, (UINT16)SrcRect.iRight, (UINT16)SrcRect.iBottom );
PlayJA2SampleFromFile( "SOUNDS\\Laptop power up (8-11).wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
while( iPercentage < 100 )
{
uiCurrTime = GetJA2Clock();
iPercentage = (uiCurrTime-uiStartTime) * 100 / uiTimeRange;
iPercentage = min( iPercentage, 100 );
//Factor the percentage so that it is modified by a gravity falling acceleration effect.
iFactor = (iPercentage - 50) * 2;
if( iPercentage < 50 )
iPercentage = (UINT32)(iPercentage + iPercentage * iFactor * 0.01 + 0.5);
else
iPercentage = (UINT32)(iPercentage + (100-iPercentage) * iFactor * 0.01 + 0.05);
//Calculate the center point.
iLeft = sStartLeft + (sEndLeft-sStartLeft+1) * iPercentage / 100;
iTop = sStartTop + (sEndTop-sStartTop+1) * iPercentage / 100;
DstRect.iLeft = iLeft - iWidth * iPercentage / 200;
DstRect.iRight = DstRect.iLeft + max( iWidth * iPercentage / 100, 1 );
DstRect.iTop = iTop - iHeight * iPercentage / 200;
DstRect.iBottom = DstRect.iTop + max( iHeight * iPercentage / 100, 1 );
BltStretchVideoSurface( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, 0, &SrcRect, &DstRect );
InvalidateScreen();
RefreshScreen( NULL );
//Restore the previous rect.
BlitBufferToBuffer( guiEXTRABUFFER, FRAME_BUFFER, (UINT16)DstRect.iLeft, (UINT16)DstRect.iTop,
(UINT16)(DstRect.iRight-DstRect.iLeft+1), (UINT16)(DstRect.iBottom-DstRect.iTop+1) );
}
//BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, 640, 480 );
}
extern UINT8 guiDirNumber;
void EnterAutoResolveMode( UINT8 ubSectorX, UINT8 ubSectorY )
{
guiDirNumber = 0;
//Set up mapscreen for removal
SetPendingNewScreen( AUTORESOLVE_SCREEN );
if (isWidescreenUI())
{
DestroyMapCharInvIOregions();
}
else
{
CreateDestroyMapInvButton();
}
RenderButtons();
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve1");
// WDS - make number of mercenaries, etc. be configurable
//Allocate memory for all the globals while we are in this mode.
gpAR = (AUTORESOLVE_STRUCT*)MemAlloc( sizeof( AUTORESOLVE_STRUCT ) );
Assert( gpAR );
memset( gpAR, 0, sizeof( AUTORESOLVE_STRUCT ) );
//Mercs
gpMercs = (SOLDIERCELL*)MemAlloc( sizeof( SOLDIERCELL) * MAX_AR_TEAM_SIZE );
Assert( gpMercs );
memset( gpMercs, 0, sizeof( SOLDIERCELL ) * MAX_AR_TEAM_SIZE );
//Militia
gpCivs = (SOLDIERCELL*)MemAlloc( sizeof( SOLDIERCELL) * MAX_AR_TEAM_SIZE );
Assert( gpCivs );
memset( gpCivs, 0, sizeof( SOLDIERCELL ) * MAX_AR_TEAM_SIZE );
//Enemies
gpEnemies = (SOLDIERCELL*)MemAlloc( sizeof( SOLDIERCELL) * MAX_AR_TEAM_SIZE );
Assert( gpEnemies );
memset( gpEnemies, 0, sizeof( SOLDIERCELL ) * MAX_AR_TEAM_SIZE );
//Set up autoresolve
gpAR->fEnteringAutoResolve = TRUE;
gpAR->ubSectorX = ubSectorX;
gpAR->ubSectorY = ubSectorY;
gpAR->ubBattleStatus = BATTLE_IN_PROGRESS;
gpAR->uiTimeSlice = 1000;
gpAR->uiTotalElapsedBattleTimeInMilliseconds = 0;
gpAR->fSound = TRUE;
gpAR->fMoraleEventsHandled = FALSE;
gpAR->uiPreRandomIndex = guiPreRandomIndex;
//Determine who gets the defensive advantage
switch( GetEnemyEncounterCode() )
{
case ENEMY_ENCOUNTER_CODE:
case TRANSPORT_INTERCEPT_CODE:
gpAR->ubPlayerDefenceAdvantage = 21; //Skewed to the player's advantage for convenience purposes.
break;
case ENEMY_INVASION_CODE:
gpAR->ubPlayerDefenceAdvantage = 0;
break;
case CREATURE_ATTACK_CODE:
case BLOODCAT_ATTACK_CODE:
case ZOMBIE_ATTACK_CODE:
case BANDIT_ATTACK_CODE:
gpAR->ubPlayerDefenceAdvantage = 0;
break;
default:
//shouldn't happen
#ifdef JA2BETAVERSION
ScreenMsg( FONT_RED, MSG_ERROR, L"Autoresolving with entering enemy sector code %d -- illegal KM:1", GetEnemyEncounterCode() );
#endif
break;
}
}
UINT32 AutoResolveScreenInit()
{
return TRUE;
}
UINT32 AutoResolveScreenShutdown()
{
gpBattleGroup = NULL;
return TRUE;
}
UINT32 AutoResolveScreenHandle()
{
RestoreBackgroundRects();
if( !gpAR )
{
gfEnteringMapScreen = TRUE;
return MAP_SCREEN;
}
if( gpAR->fEnteringAutoResolve )
{
UINT8 *pDestBuf;
UINT32 uiDestPitchBYTES;
SGPRect ClipRect;
gpAR->fEnteringAutoResolve = FALSE;
//Take the framebuffer, shade it, and save it to the SAVEBUFFER.
ClipRect.iLeft = 0;
ClipRect.iTop = 0;
/*ClipRect.iRight = 640;
ClipRect.iBottom = 480;*/
ClipRect.iRight = SCREEN_WIDTH;
ClipRect.iBottom = SCREEN_HEIGHT;
pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES );
Blt16BPPBufferShadowRect( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect );
UnLockVideoSurface( FRAME_BUFFER );
//BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, 640, 480 );
BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT );
KillPreBattleInterface();
CalculateAutoResolveInfo();
CalculateSoldierCells( FALSE );
CreateAutoResolveInterface();
DetermineTeamLeader( TRUE ); //friendly team
DetermineTeamLeader( FALSE ); //enemy team
CalculateAttackValues();
if( gfExtraBuffer )
{
DoTransitionFromPreBattleInterfaceToAutoResolve();
}
else
gpAR->fExpanding = TRUE;
gpAR->fRenderAutoResolve = TRUE;
}
if( gpAR->fExitAutoResolve )
{
gfEnteringMapScreen = TRUE;
RemoveAutoResolveInterface( TRUE );
return MAP_SCREEN;
}
if( gpAR->fPendingSurrender )
{
gpAR->uiPrevTime = gpAR->uiCurrTime = GetJA2Clock();
}
else if( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS && !gpAR->fExpanding )
{
ProcessBattleFrame();
}
HandleAutoResolveInput();
RenderAutoResolve();
SaveBackgroundRects();
RenderButtons();
RenderButtonsFastHelp();
ExecuteBaseDirtyRectQueue();
EndFrameBufferRender();
return AUTORESOLVE_SCREEN;
}
void RefreshMerc( SOLDIERTYPE *pSoldier )
{
pSoldier->stats.bLife = pSoldier->stats.bLifeMax;
pSoldier->bBleeding = 0;
pSoldier->iHealableInjury = 0; // added by SANDRO
pSoldier->bBreath = pSoldier->bBreathMax = 100;
pSoldier->sBreathRed = 0;
if( gpAR->pRobotCell)
{
gpAR->pRobotCell->pSoldier->UpdateRobotControllerGivenRobot( );
}
//gpAR->fUnlimitedAmmo = TRUE;
}
//Now assign the pSoldier->ubGroupIDs for the enemies, so we know where to remove them. Start with
//stationary groups first.
void AssociateEnemiesWithStrategicGroups()
{
SECTORINFO *pSector;
GROUP *pGroup;
UINT16 ubNumAdmins, ubNumTroops, ubNumElites, ubNumTanks, ubNumJeeps, ubNumRobots; //how many soldiers of the type do we still have to assign to a group?
UINT16 ubISNumAdmins, ubISNumTroops, ubISNumElites, ubISNumTanks, ubISNumJeeps, ubISNumRobots;
UINT16 ubNumElitesInGroup, ubNumTroopsInGroup, ubNumAdminsInGroup, ubNumTanksInGroup, ubNumJeepsInGroup, ubNumRobotsInGroup;
INT32 i;
UINT8 pSectors[4];
UINT8 ubDirAmount;
UINT8 ubCurrSI;
if ( GetEnemyEncounterCode() == CREATURE_ATTACK_CODE ||
GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE ||
GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE ||
GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
return;
pSector = &SectorInfo[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ];
//grab the number of each type in the stationary sector
ubNumAdmins = pSector->ubNumAdmins;
ubNumTroops = pSector->ubNumTroops;
ubNumElites = pSector->ubNumElites;
ubNumTanks = pSector->ubNumTanks;
ubNumJeeps = pSector->ubNumJeeps;
ubNumRobots = pSector->ubNumRobots;
//Now go through our enemies in the autoresolve array, and assign the ubGroupID to the soldier
//Stationary groups have a group ID of 0 - first assign enemies from those stationary groups
for( i = 0; i < gpAR->ubEnemies; ++i )
{
if ( gpEnemies[i].uiFlags & CELL_TANK && ubNumTanks ) //is this soldier a tank? and we still have some tanks to add? (since there might not be a static tank in sector)
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumTanks--;
}
else if ( gpEnemies[i].uiFlags & CELL_JEEP && ubNumJeeps )
{
gpEnemies[i].pSoldier->ubGroupID = 0;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
ubNumJeeps--;
}
else if ( gpEnemies[i].uiFlags & CELL_ENEMYROBOT && ubNumRobots )
{
gpEnemies[i].pSoldier->ubGroupID = 0;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
ubNumRobots--;
}
else if ( gpEnemies[i].uiFlags & CELL_ELITE && ubNumElites )
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumElites--;
}
else if( gpEnemies[ i ].uiFlags & CELL_TROOP && ubNumTroops )
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumTroops--;
}
else if( gpEnemies[ i ].uiFlags & CELL_ADMIN && ubNumAdmins )
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumAdmins--;
}
}
ubNumAdmins = gpAR->ubAdmins - pSector->ubNumAdmins;
ubNumTroops = gpAR->ubTroops - pSector->ubNumTroops;
ubNumElites = gpAR->ubElites - pSector->ubNumElites;
ubNumTanks = gpAR->ubTanks - pSector->ubNumTanks;
ubNumJeeps = gpAR->ubJeeps - pSector->ubNumJeeps;
ubNumRobots = gpAR->ubRobots - pSector->ubNumRobots;
if ( !ubNumElites && !ubNumTroops && !ubNumAdmins && !ubNumTanks && !ubNumJeeps && !ubNumRobots )
{ //All troops accounted for.
return;
}
//Now assign the rest of the soldiers to groups
pGroup = gpGroupList;
while( pGroup )
{
if ( pGroup->usGroupTeam == ENEMY_TEAM && pGroup->ubSectorX == gpAR->ubSectorX && pGroup->ubSectorY == gpAR->ubSectorY )
{
ubNumElitesInGroup = pGroup->pEnemyGroup->ubNumElites;
ubNumTroopsInGroup = pGroup->pEnemyGroup->ubNumTroops;
ubNumAdminsInGroup = pGroup->pEnemyGroup->ubNumAdmins;
ubNumTanksInGroup = pGroup->pEnemyGroup->ubNumTanks;
ubNumJeepsInGroup = pGroup->pEnemyGroup->ubNumJeeps;
ubNumRobotsInGroup = pGroup->pEnemyGroup->ubNumRobots;
for( i = 0; i < gpAR->ubEnemies; i++ )
{
if( !(gpEnemies[ i ].uiFlags & CELL_ASSIGNED) ) //has this soldier already been assigned to a cell and therefore a group (while processing the static enemies above) ?
{
if (ubNumTanks && ubNumTanksInGroup && gpEnemies[i].uiFlags & CELL_TANK ) //is there still a tank to assign and is this a cell for tank?
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumTanks--;
ubNumTanksInGroup--;
}
else if ( ubNumJeeps && ubNumJeepsInGroup && gpEnemies[i].uiFlags & CELL_JEEP )
{
gpEnemies[i].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
ubNumJeeps--;
ubNumJeepsInGroup--;
}
else if ( ubNumRobots && ubNumRobotsInGroup && gpEnemies[i].uiFlags & CELL_ENEMYROBOT )
{
gpEnemies[i].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
ubNumRobots--;
ubNumRobotsInGroup--;
}
else if (ubNumElites && ubNumElitesInGroup && gpEnemies[i].uiFlags & CELL_ELITE)
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumElites--;
ubNumElitesInGroup--;
}
else if (ubNumTroops && ubNumTroopsInGroup && gpEnemies[i].uiFlags & CELL_TROOP)
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumTroops--;
ubNumTroopsInGroup--;
}
else if (ubNumAdmins && ubNumAdminsInGroup && gpEnemies[i].uiFlags & CELL_ADMIN)
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumAdmins--;
ubNumAdminsInGroup--;
}
}
}
}
pGroup = pGroup->next;
}
/////////////////////////////////////////////////////////////////////////// Reinforcement
pGroup = gpGroupList;
while( pGroup )
{
// Don't process road block. It'll be processed as static (because their ubGroupID is 0 so the first term eval to false)
if ( pGroup->ubGroupID && pGroup->usGroupTeam == ENEMY_TEAM && IsGroupInARightSectorToReinforce( pGroup, gpAR->ubSectorX, gpAR->ubSectorY ) )
{
ubNumElitesInGroup = pGroup->pEnemyGroup->ubNumElites;
ubNumTroopsInGroup = pGroup->pEnemyGroup->ubNumTroops;
ubNumAdminsInGroup = pGroup->pEnemyGroup->ubNumAdmins;
ubNumTanksInGroup = pGroup->pEnemyGroup->ubNumTanks;
ubNumJeepsInGroup = pGroup->pEnemyGroup->ubNumJeeps;
ubNumRobotsInGroup = pGroup->pEnemyGroup->ubNumRobots;
for( i = 0; i < gpAR->ubEnemies; i++ )
{
if( !(gpEnemies[ i ].uiFlags & CELL_ASSIGNED) )
{
if (ubNumTanks && ubNumTanksInGroup && gpEnemies[i].uiFlags & CELL_TANK)
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumTanks--;
ubNumTanksInGroup--;
}
else if ( ubNumJeeps && ubNumJeepsInGroup && gpEnemies[i].uiFlags & CELL_JEEP )
{
gpEnemies[i].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
ubNumJeeps--;
ubNumJeepsInGroup--;
}
else if ( ubNumRobots && ubNumRobotsInGroup && gpEnemies[i].uiFlags & CELL_ENEMYROBOT )
{
gpEnemies[i].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
ubNumRobots--;
ubNumRobotsInGroup--;
}
else if (ubNumElites && ubNumElitesInGroup && gpEnemies[i].uiFlags & CELL_ELITE)
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumElites--;
ubNumElitesInGroup--;
}
else if (ubNumTroops && ubNumTroopsInGroup && gpEnemies[i].uiFlags & CELL_TROOP)
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumTroops--;
ubNumTroopsInGroup--;
}
else if (ubNumAdmins && ubNumAdminsInGroup && gpEnemies[i].uiFlags & CELL_ADMIN)
{
gpEnemies[ i ].pSoldier->ubGroupID = pGroup->ubGroupID;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
ubNumAdmins--;
ubNumAdminsInGroup--;
}
}
}
}
pGroup = pGroup->next;
}
// Set GroupID = 0 for the rest (that includes reinforcements)
ubDirAmount = GetAdjacentSectors( pSectors, gpAR->ubSectorX, gpAR->ubSectorY );
for( ubCurrSI = 0; ubCurrSI < ubDirAmount; ++ubCurrSI )
{
pSector = &SectorInfo[ pSectors[ ubCurrSI ] ];
ubISNumAdmins = pSector->ubNumAdmins;
ubISNumTroops = pSector->ubNumTroops;
ubISNumElites = pSector->ubNumElites;
ubISNumTanks = pSector->ubNumTanks;
ubISNumJeeps = pSector->ubNumJeeps;
ubISNumRobots = pSector->ubNumRobots;
for( i = 0; i < gpAR->ubEnemies; ++i )
{
if ( ubISNumAdmins + ubISNumTroops + ubISNumElites + ubISNumTanks + ubISNumJeeps + ubISNumRobots <= gubReinforcementMinEnemyStaticGroupSize ) break; //if group would be left understaffed, it wont reinforce - go chceck another sector (what if are there more groups here?)
if( !(gpEnemies[ i ].uiFlags & CELL_ASSIGNED) )
{
if ( gpEnemies[i].uiFlags & CELL_TANK && ubISNumTanks && ubNumTanks )
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
gpEnemies[ i ].pSoldier->sSectorX = SECTORX( pSectors[ ubCurrSI ] );
gpEnemies[ i ].pSoldier->sSectorY = SECTORY( pSectors[ ubCurrSI ] );
ubISNumTanks--;
ubNumTanks--;
}
else if ( gpEnemies[i].uiFlags & CELL_JEEP && ubISNumJeeps && ubNumJeeps )
{
gpEnemies[i].pSoldier->ubGroupID = 0;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
gpEnemies[i].pSoldier->sSectorX = SECTORX( pSectors[ubCurrSI] );
gpEnemies[i].pSoldier->sSectorY = SECTORY( pSectors[ubCurrSI] );
ubISNumJeeps--;
ubNumJeeps--;
}
else if ( gpEnemies[i].uiFlags & CELL_ENEMYROBOT && ubISNumRobots && ubNumRobots )
{
gpEnemies[i].pSoldier->ubGroupID = 0;
gpEnemies[i].uiFlags |= CELL_ASSIGNED;
gpEnemies[i].pSoldier->sSectorX = SECTORX( pSectors[ubCurrSI] );
gpEnemies[i].pSoldier->sSectorY = SECTORY( pSectors[ubCurrSI] );
ubISNumRobots--;
ubNumRobots--;
}
else if ( gpEnemies[i].uiFlags & CELL_ELITE && ubISNumElites && ubNumElites )
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
gpEnemies[ i ].pSoldier->sSectorX = SECTORX( pSectors[ ubCurrSI ] );
gpEnemies[ i ].pSoldier->sSectorY = SECTORY( pSectors[ ubCurrSI ] );
ubISNumElites--;
ubNumElites--;
}
else if( gpEnemies[ i ].uiFlags & CELL_TROOP && ubISNumTroops && ubNumTroops )
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
gpEnemies[ i ].pSoldier->sSectorX = SECTORX( pSectors[ ubCurrSI ] );
gpEnemies[ i ].pSoldier->sSectorY = SECTORY( pSectors[ ubCurrSI ] );
ubISNumTroops--;
ubNumTroops--;
}
else if( gpEnemies[ i ].uiFlags & CELL_ADMIN && ubISNumAdmins && ubNumAdmins )
{
gpEnemies[ i ].pSoldier->ubGroupID = 0;
gpEnemies[ i ].uiFlags |= CELL_ASSIGNED;
gpEnemies[ i ].pSoldier->sSectorX = SECTORX( pSectors[ ubCurrSI ] );
gpEnemies[ i ].pSoldier->sSectorY = SECTORY( pSectors[ ubCurrSI ] );
ubISNumAdmins--;
ubNumAdmins--;
}
}
}
}
/*at this point, all enemies should have been assigned to their cell and group. If not, there is a bug around
Because number and type of cells should be computed for the same composition of enemies as the one we see in this function, it should not happen though*/
AssertMsg( !(ubISNumAdmins & ubISNumTroops & ubISNumElites & ubISNumTanks & ubISNumJeeps & ubISNumRobots), "Mapping between actual enemies and autoresolve cells is wrong." );
}
void CalculateSoldierCells( BOOLEAN fReset )
{
INT32 i, x, y;
INT32 index, iStartY, iTop, gapStartRow;
INT32 iMaxTeamSize;
gpAR->ubAliveMercs = gpAR->ubMercs;
gpAR->ubAliveCivs = gpAR->ubCivs;
gpAR->ubAliveEnemies = gpAR->ubEnemies;
// WDS - make number of mercenaries, etc. be configurable
// WDS TO BE FIXED -- Why is this 40 here? (18 April 2008)
//iMaxTeamSize = max( gpAR->ubMercs + gpAR->ubCivs, gpAR->ubEnemies );
iMaxTeamSize = max( min( 40, gpAR->ubMercs + gpAR->ubCivs ), min( 40, gpAR->ubEnemies ) );
if( iMaxTeamSize > 12 )
{
gpAR->ubTimeModifierPercentage = (UINT8)(118 - iMaxTeamSize*1.5);
}
else
{
gpAR->ubTimeModifierPercentage = 100;
}
gpAR->uiTimeSlice = gpAR->uiTimeSlice * gpAR->ubTimeModifierPercentage / 100;
iTop = iScreenHeightOffset + (240 - gpAR->sHeight/2);
if( iTop > (iScreenHeightOffset + 120) )
iTop -= 40;
if( gpAR->ubMercs )
{
iStartY = iTop + (gpAR->sHeight - (min(10,gpAR->ubMercRows+gpAR->ubCivRows)*47+7))/2 + 6;
//iStartY = iTop + (gpAR->sHeight - ((gpAR->ubMercRows+gpAR->ubCivRows)*47+7))/2 + 6;
y = gpAR->ubMercRows;
x = gpAR->ubMercCols;
i = gpAR->ubMercs;
gapStartRow = gpAR->ubMercRows - gpAR->ubMercRows * gpAR->ubMercCols + gpAR->ubMercs;
for( x = 0; x < gpAR->ubMercCols; x++ ) for( y = 0; i && y < gpAR->ubMercRows; y++, i-- )
{
index = y * gpAR->ubMercCols + gpAR->ubMercCols - x - 1;
if( y >= gapStartRow )
index -= y - gapStartRow + 1;
Assert( index >= 0 && index < gpAR->ubMercs );
gpMercs[ index ].xp = gpAR->sCenterStartX + 3 - 55*(x+1);
gpMercs[ index ].yp = iStartY + y*47;
gpMercs[ index ].uiFlags = CELL_MERC;
if( AM_AN_EPC( gpMercs[ index ].pSoldier ) )
{
if( AM_A_ROBOT( gpMercs[ index ].pSoldier ) )
{ //treat robot as a merc for the purpose of combat.
gpMercs[ index ].uiFlags |= CELL_ROBOT;
}
else
{
gpMercs[ index ].uiFlags |= CELL_EPC;
}
}
gpMercs[ index ].pRegion = (MOUSE_REGION*)MemAlloc( sizeof( MOUSE_REGION ) );
Assert( gpMercs[ index ].pRegion );
memset( gpMercs[ index ].pRegion, 0, sizeof( MOUSE_REGION ) );
MSYS_DefineRegion( gpMercs[ index ].pRegion, gpMercs[ index ].xp, gpMercs[ index ].yp,
(UINT16)(gpMercs[ index ].xp + 50), (UINT16)(gpMercs[ index ].yp + 44), MSYS_PRIORITY_HIGH, 0,
MercCellMouseMoveCallback, MercCellMouseClickCallback );
if( fReset )
RefreshMerc( gpMercs[ index ].pSoldier );
if( !gpMercs[ index ].pSoldier->stats.bLife )
gpAR->ubAliveMercs--;
}
}
if( gpAR->ubCivs )
{
iStartY = iTop + (gpAR->sHeight - (min(10,gpAR->ubMercRows+gpAR->ubCivRows)*47+7))/2 + gpAR->ubMercRows*47 + 5;
y = gpAR->ubCivRows;
x = gpAR->ubCivCols;
i = gpAR->ubCivs;
for( index = 0; index < gpAR->ubCivs ; ++index )
{
x = gpAR->ubCivCols - 1 - index % gpAR->ubCivCols;
y = index / gpAR->ubCivCols;
gpCivs[ index ].xp = gpAR->sCenterStartX + 3 - 55*(x+1);
gpCivs[ index ].yp = iStartY + y*47;
gpCivs[ index ].uiFlags |= CELL_MILITIA;
}
}
if( gpAR->ubEnemies )
{
iStartY = iTop + (gpAR->sHeight - (min(10,gpAR->ubEnemyRows)*47+7))/2 + 5;
y = gpAR->ubEnemyRows;
x = gpAR->ubEnemyCols;
i = gpAR->ubEnemies;
for( index = 0; index < gpAR->ubEnemies ; ++index )
{
x = index % gpAR->ubEnemyCols;
y = index / gpAR->ubEnemyCols;
gpEnemies[ index ].xp = (UINT16)(gpAR->sCenterStartX + 141 + 55*x);
gpEnemies[ index ].yp = iStartY + y*47;
if ( GetEnemyEncounterCode() == CREATURE_ATTACK_CODE )
{
if( index < gpAR->ubAFCreatures )
gpEnemies[ index ].uiFlags = CELL_AF_CREATURE;
else if( index < gpAR->ubAMCreatures + gpAR->ubAFCreatures )
gpEnemies[ index ].uiFlags = CELL_AM_CREATURE;
else if( index < gpAR->ubYFCreatures + gpAR->ubAMCreatures + gpAR->ubAFCreatures )
gpEnemies[ index ].uiFlags = CELL_YF_CREATURE;
else
gpEnemies[ index ].uiFlags = CELL_YM_CREATURE;
}
else if ( GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE ||
GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE ||
GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
{
if ( index < gpAR->ubBloodcats )
gpEnemies[index].uiFlags = CELL_BLOODCAT;
else if ( index < gpAR->ubBloodcats + gpAR->ubZombies )
gpEnemies[index].uiFlags = CELL_ZOMBIE;
else
gpEnemies[index].uiFlags = CELL_BANDIT;
}
else
{
if ( index < gpAR->ubElites )
gpEnemies[index].uiFlags = CELL_ELITE;
else if ( index < gpAR->ubElites + gpAR->ubTroops )
gpEnemies[index].uiFlags = CELL_TROOP;
else if ( index < gpAR->ubElites + gpAR->ubTroops + gpAR->ubAdmins )
gpEnemies[index].uiFlags = CELL_ADMIN;
else if ( index < gpAR->ubElites + gpAR->ubTroops + gpAR->ubAdmins + gpAR->ubTanks )
gpEnemies[index].uiFlags = CELL_TANK;
else if ( index < gpAR->ubElites + gpAR->ubTroops + gpAR->ubAdmins + gpAR->ubTanks + gpAR->ubJeeps )
gpEnemies[index].uiFlags = CELL_JEEP;
else
gpEnemies[index].uiFlags = CELL_ENEMYROBOT;
}
}
}
}
INT32 DetermineCellID( SOLDIERCELL *pCell )
{
INT32 iIndex;
if( pCell->pSoldier->bTeam == ENEMY_TEAM )
{
for( iIndex = 0 ; iIndex < gpAR->ubEnemies ; iIndex++ )
if( &gpEnemies[ iIndex ] == pCell )
return iIndex;
}else if( pCell->pSoldier->bTeam == MILITIA_TEAM )
{
for( iIndex = 0 ; iIndex < gpAR->ubCivs ; iIndex++ )
if( &gpCivs[ iIndex ] == pCell )
return iIndex;
}
return 0;
}
BOOLEAN IsItAllowedToRender( SOLDIERCELL *pCell )
{
INT32 iID = DetermineCellID( pCell );
switch( pCell->pSoldier->bTeam )
{
case ENEMY_TEAM:
if( iID >= giMaxEnemiesToRender ) return FALSE;
break;
case MILITIA_TEAM:
if( iID >= giMaxMilitiaToRender ) return FALSE;
break;
}
return TRUE;
}
void RenderSoldierCell( SOLDIERCELL *pCell )
{
UINT8 x;
if( !IsItAllowedToRender( pCell ) ) return;
if( pCell->uiFlags & CELL_MERC )
{
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+36, pCell->yp+2, pCell->xp+44, pCell->yp+30, 0 );
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iPanelImages, MERC_PANEL, pCell->xp, pCell->yp, VO_BLT_SRCTRANSPARENCY, NULL );
RenderSoldierCellBars( pCell );
x = 0;
}
else
{
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iPanelImages, OTHER_PANEL, pCell->xp, pCell->yp, VO_BLT_SRCTRANSPARENCY, NULL );
x = 6;
}
if( !pCell->pSoldier->stats.bLife )
{
SetObjectHandleShade( pCell->uiVObjectID, 0 );
if ( ( pCell->uiFlags & CELL_BLOODCAT ) )
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iFaces, BLOODCAT_SKULL, pCell->xp + 3 + x, pCell->yp + 3, VO_BLT_SRCTRANSPARENCY, NULL );
else if ( (pCell->uiFlags & CELL_BUG ) )
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iFaces, CREATURE_SKULL, pCell->xp + 3 + x, pCell->yp + 3, VO_BLT_SRCTRANSPARENCY, NULL );
else if ( (pCell->uiFlags & CELL_TANK) )
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iFaces, TANK_WRECK, pCell->xp + 3 + x, pCell->yp + 3, VO_BLT_SRCTRANSPARENCY, NULL );
else if ( (pCell->uiFlags & CELL_JEEP) )
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iFaces, JEEP_WRECK, pCell->xp + 3 + x, pCell->yp + 3, VO_BLT_SRCTRANSPARENCY, NULL );
else
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iFaces, HUMAN_SKULL, pCell->xp+3+x, pCell->yp+3, VO_BLT_SRCTRANSPARENCY, NULL );
}
else
{
if( pCell->uiFlags & CELL_HITBYATTACKER )
{
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+3+x, pCell->yp+3, pCell->xp+33+x, pCell->yp+29, 65535 );
}
else if( pCell->uiFlags & CELL_HITLASTFRAME )
{
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+3+x, pCell->yp+3, pCell->xp+33+x, pCell->yp+29, 0 );
SetObjectHandleShade( pCell->uiVObjectID, 1 );
BltVideoObjectFromIndex( FRAME_BUFFER, pCell->uiVObjectID, pCell->usIndex, pCell->xp+3+x, pCell->yp+3, VO_BLT_SRCTRANSPARENCY, NULL );
}
else
{
SetObjectHandleShade( pCell->uiVObjectID, 0 );
BltVideoObjectFromIndex( FRAME_BUFFER, pCell->uiVObjectID, pCell->usIndex, pCell->xp+3+x, pCell->yp+3, VO_BLT_SRCTRANSPARENCY, NULL );
}
}
if( pCell->pSoldier->stats.bLife > 0 && pCell->pSoldier->stats.bLife < OKLIFE && !(pCell->uiFlags & (CELL_HITBYATTACKER|CELL_HITLASTFRAME|CELL_CREATURE)) )
{ //Merc is unconcious (and not taking damage), so darken his portrait.
UINT8 *pDestBuf;
UINT32 uiDestPitchBYTES;
SGPRect ClipRect;
ClipRect.iLeft = pCell->xp+3+x;
ClipRect.iTop = pCell->yp+3;
ClipRect.iRight = pCell->xp+33+x;
ClipRect.iBottom = pCell->yp+29;
pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES );
Blt16BPPBufferShadowRect( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect );
UnLockVideoSurface( FRAME_BUFFER );
}
//Draw the health text
RenderSoldierCellHealth( pCell );
DrawDebugText( pCell );
if( !(pCell->uiFlags & CELL_RETREATING) )
pCell->uiFlags &= ~CELL_DIRTY;
InvalidateRegion( pCell->xp, pCell->yp, pCell->xp+50, pCell->yp+44 );
//Adjust flags accordingly
if( pCell->uiFlags & CELL_HITBYATTACKER )
{
pCell->uiFlags &= ~CELL_HITBYATTACKER;
pCell->uiFlags |= CELL_HITLASTFRAME | CELL_DIRTY;
pCell->uiFlashTime = GetJA2Clock() + 150;
}
else if( pCell->uiFlags & CELL_HITLASTFRAME )
{
if( pCell->uiFlashTime < GetJA2Clock() )
{
pCell->uiFlags &= ~CELL_HITLASTFRAME;
}
pCell->uiFlags |= CELL_DIRTY;
}
}
void RenderSoldierCellBars( SOLDIERCELL *pCell )
{
INT32 iStartY;
//HEALTH BAR
if( !pCell->pSoldier->stats.bLife )
return;
//yellow one for bleeding
iStartY = pCell->yp + 29 - 25*pCell->pSoldier->stats.bLifeMax/100;
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 107, 107, 57 ) ) );
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 222, 181, 115 ) ) );
//pink one for bandaged.
iStartY = pCell->yp + 29 - 25*(pCell->pSoldier->stats.bLifeMax - pCell->pSoldier->bBleeding)/100;
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 156, 57, 57 ) ) );
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 222, 132, 132 ) ) );
//red one for actual health
iStartY = pCell->yp + 29 - 25*pCell->pSoldier->stats.bLife/100;
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 107, 8, 8 ) ) );
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 206, 0, 0 ) ) );
//BREATH BAR
iStartY = pCell->yp + 29 - 25*pCell->pSoldier->bBreathMax/100;
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+41, iStartY, pCell->xp+42, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 8, 132 ) ) );
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+42, iStartY, pCell->xp+43, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 8, 107 ) ) );
//MORALE BAR
iStartY = pCell->yp + 29 - 25*pCell->pSoldier->aiData.bMorale/100;
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+45, iStartY, pCell->xp+46, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 156, 8 ) ) );
ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+46, iStartY, pCell->xp+47, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 107, 8 ) ) );
}
void BuildInterfaceBuffer()
{
VSURFACE_DESC vs_desc;
UINT16 usUselessWidth, usUselessHeight;
UINT8 ubBitDepth;
SGPRect ClipRect;
SGPRect DestRect;
INT32 x,y;
//Setup the blitting clip regions, so we don't draw outside of the region (for excess panelling)
gpAR->Rect.iLeft = iScreenWidthOffset + (320 - gpAR->sWidth/2);
gpAR->Rect.iRight = gpAR->Rect.iLeft + gpAR->sWidth;
gpAR->Rect.iTop = iScreenHeightOffset + (240 - gpAR->sHeight/2);
if( gpAR->Rect.iTop > (iScreenHeightOffset + 120) )
gpAR->Rect.iTop -= 40;
gpAR->Rect.iBottom = gpAR->Rect.iTop + gpAR->sHeight;
DestRect.iLeft = 0;
DestRect.iTop = 0;
DestRect.iRight = gpAR->sWidth;
DestRect.iBottom = gpAR->sHeight;
//create buffer for the transition slot for merc items. This slot contains the newly
//selected item graphic in it's inventory size version. This buffer is then scaled down
//into the associated merc inventory panel slot buffer which is approximately 20% smaller.
GetCurrentVideoSettings( &usUselessWidth, &usUselessHeight, &ubBitDepth );
vs_desc.fCreateFlags = VSURFACE_CREATE_DEFAULT | VSURFACE_SYSTEM_MEM_USAGE;
vs_desc.usWidth = gpAR->sWidth;
vs_desc.usHeight = gpAR->sHeight;
vs_desc.ubBitDepth = ubBitDepth;
if( !AddVideoSurface( &vs_desc, (UINT32 *)&gpAR->iInterfaceBuffer ) )
AssertMsg( 0, "Failed to allocate memory for autoresolve interface buffer." );
GetClippingRect( &ClipRect );
SetClippingRect( &DestRect );
//Blit the back panels...
for( y = DestRect.iTop; y < DestRect.iBottom; y += 40 )
{
for( x = DestRect.iLeft; x < DestRect.iRight; x += 50 )
{
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, C_TEXTURE, x, y, VO_BLT_SRCTRANSPARENCY, 0 );
}
}
//Blit the left and right edges
for( y = DestRect.iTop; y < DestRect.iBottom; y += 40 )
{
x = DestRect.iLeft;
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, L_BORDER, x, y, VO_BLT_SRCTRANSPARENCY, 0 );
x = DestRect.iRight - 3;
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, R_BORDER, x, y, VO_BLT_SRCTRANSPARENCY, 0 );
}
//Blit the top and bottom edges
for( x = DestRect.iLeft; x < DestRect.iRight; x += 50 )
{
y = DestRect.iTop;
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, T_BORDER, x, y, VO_BLT_SRCTRANSPARENCY, 0 );
y = DestRect.iBottom - 3;
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, B_BORDER, x, y, VO_BLT_SRCTRANSPARENCY, 0 );
}
//Blit the 4 corners
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, TL_BORDER, DestRect.iLeft, DestRect.iTop, VO_BLT_SRCTRANSPARENCY, NULL );
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, TR_BORDER, DestRect.iRight-10, DestRect.iTop, VO_BLT_SRCTRANSPARENCY, NULL );
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, BL_BORDER, DestRect.iLeft, DestRect.iBottom-9, VO_BLT_SRCTRANSPARENCY, NULL );
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, BR_BORDER, DestRect.iRight-10, DestRect.iBottom-9, VO_BLT_SRCTRANSPARENCY, NULL );
//Blit the center pieces
x = gpAR->sCenterStartX - gpAR->Rect.iLeft;
y = 0;
//Top
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, TOP_MIDDLE, x, y, VO_BLT_SRCTRANSPARENCY, NULL );
//Middle
for( y = 40; y < gpAR->sHeight - 40; y += 40 )
{
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, AUTO_MIDDLE, x, y, VO_BLT_SRCTRANSPARENCY, NULL );
}
y = gpAR->sHeight - 40;
BltVideoObjectFromIndex( gpAR->iInterfaceBuffer, gpAR->iPanelImages, BOT_MIDDLE, x, y, VO_BLT_SRCTRANSPARENCY, NULL );
SetClippingRect( &ClipRect );
}
void ExpandWindow()
{
SGPRect OldRect;
UINT32 uiDestPitchBYTES;
UINT32 uiCurrentTime, uiTimeRange, uiPercent;
UINT8 *pDestBuf;
INT32 i;
if( !gpAR->ExRect.iLeft && !gpAR->ExRect.iRight )
{ //First time
gpAR->ExRect.iLeft = ORIG_LEFT;
gpAR->ExRect.iTop = ORIG_TOP;
gpAR->ExRect.iRight = ORIG_RIGHT;
gpAR->ExRect.iBottom = ORIG_BOTTOM;
gpAR->uiStartExpanding = GetJA2Clock();
gpAR->uiEndExpanding = gpAR->uiStartExpanding + 333;
for( i = 0; i < DONEWIN_BUTTON; i++ )
HideButton( gpAR->iButton[ i ] );
}
else
{
//Restore the previous area
//left
BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, (UINT16)gpAR->ExRect.iLeft, (UINT16)gpAR->ExRect.iTop, 1, (UINT16)(gpAR->ExRect.iBottom-gpAR->ExRect.iTop+1) );
InvalidateRegion( gpAR->ExRect.iLeft, gpAR->ExRect.iTop, gpAR->ExRect.iLeft+1, gpAR->ExRect.iBottom+1 );
//right
BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, (UINT16)gpAR->ExRect.iRight, (UINT16)gpAR->ExRect.iTop, 1, (UINT16)(gpAR->ExRect.iBottom-gpAR->ExRect.iTop+1) );
InvalidateRegion( gpAR->ExRect.iRight, gpAR->ExRect.iTop, gpAR->ExRect.iRight+1, gpAR->ExRect.iBottom+1 );
//top
BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, (UINT16)gpAR->ExRect.iLeft, (UINT16)gpAR->ExRect.iTop, (UINT16)(gpAR->ExRect.iRight-gpAR->ExRect.iLeft+1), 1 );
InvalidateRegion( gpAR->ExRect.iLeft, gpAR->ExRect.iTop, gpAR->ExRect.iRight+1, gpAR->ExRect.iTop+1 );
//bottom
BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, (UINT16)gpAR->ExRect.iLeft, (UINT16)gpAR->ExRect.iBottom, (UINT16)(gpAR->ExRect.iRight-gpAR->ExRect.iLeft+1), 1 );
InvalidateRegion( gpAR->ExRect.iLeft, gpAR->ExRect.iBottom, gpAR->ExRect.iRight+1, gpAR->ExRect.iBottom+1 );
uiCurrentTime = GetJA2Clock();
if( uiCurrentTime >= gpAR->uiStartExpanding && uiCurrentTime <= gpAR->uiEndExpanding )
{
//Debug purposes
OldRect.iLeft = ORIG_LEFT;
OldRect.iTop = ORIG_TOP;
OldRect.iRight = ORIG_RIGHT;
OldRect.iBottom = ORIG_BOTTOM;
uiTimeRange = gpAR->uiEndExpanding - gpAR->uiStartExpanding;
uiPercent = (uiCurrentTime - gpAR->uiStartExpanding ) * 100 / uiTimeRange;
//Left
if( OldRect.iLeft <= gpAR->Rect.iLeft )
gpAR->ExRect.iLeft = OldRect.iLeft + ( gpAR->Rect.iLeft - OldRect.iLeft ) * uiPercent / 100;
else
gpAR->ExRect.iLeft = gpAR->Rect.iLeft + ( OldRect.iLeft - gpAR->Rect.iLeft ) * uiPercent / 100;
//Top
if( OldRect.iTop <= gpAR->Rect.iTop )
gpAR->ExRect.iTop = OldRect.iTop + ( gpAR->Rect.iTop - OldRect.iTop ) * uiPercent / 100;
else
gpAR->ExRect.iTop = gpAR->Rect.iTop + ( OldRect.iTop - gpAR->Rect.iTop ) * uiPercent / 100;
//Right
if( OldRect.iRight <= gpAR->Rect.iRight )
gpAR->ExRect.iRight = OldRect.iRight + ( gpAR->Rect.iRight - OldRect.iRight ) * uiPercent / 100;
else
gpAR->ExRect.iRight = gpAR->Rect.iRight + ( OldRect.iRight - gpAR->Rect.iRight ) * uiPercent / 100;
//Bottom
if( OldRect.iBottom <= gpAR->Rect.iBottom )
gpAR->ExRect.iBottom = OldRect.iBottom + ( gpAR->Rect.iBottom - OldRect.iBottom ) * uiPercent / 100;
else
gpAR->ExRect.iBottom = gpAR->Rect.iBottom + ( OldRect.iBottom - gpAR->Rect.iBottom ) * uiPercent / 100;
}
else
{ //expansion done -- final frame
gpAR->ExRect.iLeft = gpAR->Rect.iLeft;
gpAR->ExRect.iTop = gpAR->Rect.iTop;
gpAR->ExRect.iRight = gpAR->Rect.iRight;
gpAR->ExRect.iBottom = gpAR->Rect.iBottom;
gpAR->fExpanding = FALSE;
gpAR->fShowInterface = TRUE;
}
}
//The new rect now determines the state of the current rectangle.
pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES );
SetClippingRegionAndImageWidth( uiDestPitchBYTES, 0 + xResOffset, 0, SCREEN_WIDTH, SCREEN_HEIGHT );
RectangleDraw( TRUE, gpAR->ExRect.iLeft, gpAR->ExRect.iTop, gpAR->ExRect.iRight, gpAR->ExRect.iBottom, Get16BPPColor( FROMRGB( 200, 200, 100 ) ), pDestBuf );
UnLockVideoSurface( FRAME_BUFFER );
//left
InvalidateRegion( gpAR->ExRect.iLeft, gpAR->ExRect.iTop, gpAR->ExRect.iLeft+1, gpAR->ExRect.iBottom+1 );
//right
InvalidateRegion( gpAR->ExRect.iRight, gpAR->ExRect.iTop, gpAR->ExRect.iRight+1, gpAR->ExRect.iBottom+1 );
//top
InvalidateRegion( gpAR->ExRect.iLeft, gpAR->ExRect.iTop, gpAR->ExRect.iRight+1, gpAR->ExRect.iTop+1 );
//bottom
InvalidateRegion( gpAR->ExRect.iLeft, gpAR->ExRect.iBottom, gpAR->ExRect.iRight+1, gpAR->ExRect.iBottom+1 );
}
OBJECTTYPE* FindMedicalKit()
{
INT32 i;
INT32 iSlot;
for( i = 0; i < gpAR->ubMercs; i++ )
{
iSlot = FindObjClass( gpMercs[ i ].pSoldier, IC_MEDKIT );
if( iSlot != NO_SLOT )
{
return( &gpMercs[ i ].pSoldier->inv[ iSlot ] );
}
}
return NULL;
}
UINT32 AutoBandageMercs()
{
INT32 i, iBest;
UINT32 uiPointsUsed, uiCurrPointsUsed, uiMaxPointsUsed, uiParallelPointsUsed;
UINT16 usKitPts;
OBJECTTYPE *pKit = NULL;
BOOLEAN fFound = FALSE;
BOOLEAN fComplete = TRUE;
INT8 bSlot, cnt;
//Do we have any doctors? If so, bandage selves first.
fFound = FALSE;
uiMaxPointsUsed = uiParallelPointsUsed = 0;
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pSoldier->stats.bLife >= OKLIFE &&
!gpMercs[ i ].pSoldier->bCollapsed &&
gpMercs[ i ].pSoldier->stats.bMedical > 0 &&
( bSlot = FindObjClass( gpMercs[ i ].pSoldier, IC_MEDKIT ) ) != NO_SLOT )
{
fFound = TRUE;
//bandage self first!
uiCurrPointsUsed = 0;
cnt = 0;
while( gpMercs[ i ].pSoldier->bBleeding )
{
pKit = &gpMercs[ i ].pSoldier->inv[ bSlot ];
usKitPts = TotalPoints( pKit );
if( !usKitPts )
{ //attempt to find another kit before stopping
if( ( bSlot = FindObjClass( gpMercs[ i ].pSoldier, IC_MEDKIT ) ) != NO_SLOT )
continue;
break;
}
uiPointsUsed = VirtualSoldierDressWound( gpMercs[ i ].pSoldier, gpMercs[ i ].pSoldier, pKit, usKitPts, usKitPts, FALSE ); // changed by SANDRO
UseKitPoints( pKit, (UINT16)uiPointsUsed, gpMercs[ i ].pSoldier );
uiCurrPointsUsed += uiPointsUsed;
cnt++;
if( cnt > 50 )
break;
}
if( uiCurrPointsUsed > uiMaxPointsUsed )
uiMaxPointsUsed = uiCurrPointsUsed;
if( !pKit )
break;
}
}
//Find the best rated doctor to do all of the bandaging.
iBest = 0;
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pSoldier->stats.bLife >= OKLIFE && !gpMercs[ i ].pSoldier->bCollapsed && gpMercs[ i ].pSoldier->stats.bMedical > 0 )
{
if( gpMercs[ i ].pSoldier->stats.bMedical > gpMercs[ iBest ].pSoldier->stats.bMedical )
{
iBest = i;
}
}
}
for( i = 0; i < gpAR->ubMercs; i++ )
{
cnt = 0; // SANDRO - added safety check
while( gpMercs[ i ].pSoldier->bBleeding && gpMercs[ i ].pSoldier->stats.bLife )
{ //This merc needs medical attention
if( !pKit )
{
pKit = FindMedicalKit();
if( !pKit )
{
fComplete = FALSE;
break;
}
}
usKitPts = TotalPoints( pKit );
if( !usKitPts )
{
pKit = NULL;
fComplete = FALSE;
continue;
}
uiPointsUsed = VirtualSoldierDressWound( gpMercs[ iBest ].pSoldier, gpMercs[ i ].pSoldier, pKit, usKitPts, usKitPts, FALSE );
UseKitPoints( pKit, (UINT16)uiPointsUsed, gpMercs[ i ].pSoldier );
uiParallelPointsUsed += uiPointsUsed;
fComplete = TRUE;
// SANDRO - added safety check
cnt++;
// sevenfm: INT8 cannot be larger than 127, should be 50 probably as in other check here
//if( cnt > 500 )
if (cnt > 50)
break;
}
}
if( fComplete )
{
DoScreenIndependantMessageBox( gzLateLocalizedString[ 13 ], MSG_BOX_FLAG_OK, AutoBandageFinishedCallback );
}
else
{
DoScreenIndependantMessageBox( gzLateLocalizedString[ 10 ], MSG_BOX_FLAG_OK, AutoBandageFinishedCallback );
}
gpAR->uiTotalElapsedBattleTimeInMilliseconds += uiParallelPointsUsed * 200;
return 1;
}
void RenderAutoResolve()
{
INT32 i;
HVSURFACE hVSurface;
INT32 xp, yp;
CHAR16 str[100];
UINT8 ubGood, ubBad;
if( gpAR->fExpanding )
{ //animate the expansion of the window.
ExpandWindow();
return;
}
else if( gpAR->fShowInterface )
{ //After expanding the window, we now show the interface
if( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS && !gpAR->fPendingSurrender )
{
for( i = 0 ; i < DONEWIN_BUTTON; i++ )
ShowButton( gpAR->iButton[ i ] );
HideButton( gpAR->iButton[ BANDAGE_BUTTON ] );
HideButton( gpAR->iButton[ YES_BUTTON ] );
HideButton( gpAR->iButton[ NO_BUTTON ] );
gpAR->fShowInterface = FALSE;
}
else if( gpAR->ubBattleStatus == BATTLE_VICTORY )
{
ShowButton( gpAR->iButton[ DONEWIN_BUTTON ] );
ShowButton( gpAR->iButton[ BANDAGE_BUTTON ] );
}
else
{
ShowButton( gpAR->iButton[ DONELOSE_BUTTON ] );
}
}
if( !gpAR->fRenderAutoResolve && !gpAR->fDebugInfo )
{ //update the dirty cells only
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].uiFlags & CELL_DIRTY )
{
RenderSoldierCell( &gpMercs[ i ] );
}
}
for( i = 0; i < gpAR->ubCivs; i++ )
{
if( gpCivs[ i ].uiFlags & CELL_DIRTY )
{
RenderSoldierCell( &gpCivs[ i ] );
}
}
for( i = 0; i < gpAR->ubEnemies; i++ )
{
if( gpEnemies[ i ].uiFlags & CELL_DIRTY )
{
RenderSoldierCell( &gpEnemies[ i ] );
}
}
return;
}
gpAR->fRenderAutoResolve = FALSE;
GetVideoSurface( &hVSurface, gpAR->iInterfaceBuffer );
BltVideoSurfaceToVideoSurface( ghFrameBuffer, hVSurface, 0, gpAR->Rect.iLeft, gpAR->Rect.iTop, VO_BLT_SRCTRANSPARENCY, 0 );
for( i = 0; i < gpAR->ubMercs; i++ )
{
RenderSoldierCell( &gpMercs[ i ] );
}
for( i = 0; i < gpAR->ubCivs; i++ )
{
RenderSoldierCell( &gpCivs[ i ] );
}
for( i = 0; i < gpAR->ubEnemies; i++ )
{
RenderSoldierCell( &gpEnemies[ i ] );
}
//Render the titles
SetFont( FONT10ARIALBOLD );
SetFontForeground( FONT_WHITE );
SetFontShadow( FONT_NEARBLACK );
switch( GetEnemyEncounterCode() )
{
case NO_ENCOUNTER_CODE:
swprintf( str, gpStrategicString[STR_AR_ATTACK_HEADER] );
break;
case ENEMY_ENCOUNTER_CODE:
case TRANSPORT_INTERCEPT_CODE:
swprintf( str, gpStrategicString[STR_AR_ENCOUNTER_HEADER] );
break;
case ENEMY_INVASION_CODE:
case CREATURE_ATTACK_CODE:
case BLOODCAT_ATTACK_CODE:
case ZOMBIE_ATTACK_CODE:
case BANDIT_ATTACK_CODE:
swprintf( str, gpStrategicString[STR_AR_DEFEND_HEADER] );
break;
default:
swprintf( str, gpStrategicString[STR_AR_ATTACK_HEADER] );
break;
}
xp = gpAR->sCenterStartX + 70 - StringPixLength( str, FONT10ARIALBOLD )/2;
yp = gpAR->Rect.iTop + 15;
mprintf( xp, yp, str );
SetFont( FONT10ARIAL );
SetFontForeground( FONT_GRAY2 );
SetFontShadow( FONT_NEARBLACK );
GetSectorIDString( gpAR->ubSectorX, gpAR->ubSectorY, 0, str, TRUE );
xp = gpAR->sCenterStartX + 70 - StringPixLength( str, FONT10ARIAL )/2;
yp += 11;
mprintf( xp, yp, str );
//Display the remaining forces
ubGood = (UINT8)(gpAR->ubAliveMercs + gpAR->ubAliveCivs);
ubBad = gpAR->ubAliveEnemies;
swprintf( str, gzLateLocalizedString[ 17 ], ubGood, ubBad );
SetFont( FONT14ARIAL );
if( ubGood * 3 <= ubBad * 2 )
{
SetFontForeground( FONT_LTRED );
}
else if( ubGood * 2 >= ubBad * 3 )
{
SetFontForeground( FONT_LTGREEN );
}
else
{
SetFontForeground( FONT_YELLOW );
}
xp = gpAR->sCenterStartX + 70 - StringPixLength( str, FONT14ARIAL )/2;
yp += 11;
mprintf( xp, yp, str );
#ifdef JA2BETAVERSION
if( gpAR->fAllowCapture )
{
mprintf( 2, 2, L"Enemy capture enabled." );
}
#endif
if( gpAR->fPendingSurrender )
{
DisplayWrappedString( (UINT16)(gpAR->sCenterStartX+16), (UINT16)(iScreenHeightOffset + 230+gpAR->bVerticalOffset), 108, 2,
(UINT8)FONT10ARIAL, FONT_YELLOW, gpStrategicString[ STR_ENEMY_SURRENDER_OFFER ], FONT_BLACK, FALSE, LEFT_JUSTIFIED );
}
if( gpAR->ubBattleStatus != BATTLE_IN_PROGRESS )
{
// Handle merc morale, Global loyalty, and change of sector control
if( !gpAR->fMoraleEventsHandled )
{
gpAR->uiTotalElapsedBattleTimeInMilliseconds *= 3;
gpAR->fMoraleEventsHandled = TRUE;
if ( CheckFact( FACT_FIRST_BATTLE_FOUGHT, 0 ) == FALSE )
{
// this was the first battle against the army
SetFactTrue( FACT_FIRST_BATTLE_FOUGHT );
if ( gpAR->ubBattleStatus == BATTLE_VICTORY)
{
SetFactTrue( FACT_FIRST_BATTLE_WON );
}
SetTheFirstBattleSector( ( INT16 ) (gpAR->ubSectorX + gpAR->ubSectorY * MAP_WORLD_X ) );
#ifdef JA2UB
//Ja25: no loyalty
#else
HandleFirstBattleEndingWhileInTown( gpAR->ubSectorX, gpAR->ubSectorY, 0, TRUE );
#endif
}
switch( gpAR->ubBattleStatus )
{
case BATTLE_VICTORY:
HandleMoraleEvent( NULL, MORALE_BATTLE_WON, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
if( ProcessLoyalty() )HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_WON, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
SetThisSectorAsPlayerControlled( gpAR->ubSectorX, gpAR->ubSectorY, 0, TRUE );
#ifdef NEWMUSIC
GlobalSoundID = MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalVictory[0];
if ( MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalVictory[0] != -1 )
SetMusicModeID( MUSIC_TACTICAL_VICTORY, MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalVictory[0] );
else
#endif
SetMusicMode( MUSIC_TACTICAL_VICTORY );
LogBattleResults( LOG_VICTORY );
break;
case BATTLE_SURRENDERED:
case BATTLE_CAPTURED:
for( i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ )
{
if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !(MercPtrs[ i ]->flags.uiStatusFlags & SOLDIER_VEHICLE) && !AM_A_ROBOT( MercPtrs[ i ] ) )
{ //Merc is active and alive, and not a vehicle or robot
if ( PlayerMercInvolvedInThisCombat( MercPtrs[ i ] ) )
{
// This morale event is PER INDIVIDUAL SOLDIER
HandleMoraleEvent( MercPtrs[ i ], MORALE_MERC_CAPTURED, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
}
}
}
HandleMoraleEvent( NULL, MORALE_HEARD_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
if( ProcessLoyalty() )HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
#ifdef NEWMUSIC
GlobalSoundID = MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalDeath[0];
if ( MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalDeath[0] != -1 )
SetMusicModeID( MUSIC_TACTICAL_DEATH, MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalDeath[0] );
else
#endif
SetMusicMode( MUSIC_TACTICAL_DEATH );
gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
break;
case BATTLE_DEFEAT:
HandleMoraleEvent( NULL, MORALE_HEARD_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
if( ProcessLoyalty() )
HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_BATTLE_LOST, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
if ( GetEnemyEncounterCode() != CREATURE_ATTACK_CODE &&
GetEnemyEncounterCode () != BLOODCAT_ATTACK_CODE &&
GetEnemyEncounterCode () != ZOMBIE_ATTACK_CODE &&
GetEnemyEncounterCode () != BANDIT_ATTACK_CODE )
{
gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
}
else
{
gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
gsCiviliansEatenByMonsters = gpAR->ubAliveEnemies;
}
#ifdef NEWMUSIC
GlobalSoundID = MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalDeath[0];
if ( MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalDeath[0] != -1 )
SetMusicModeID( MUSIC_TACTICAL_DEATH, MusicSoundValues[ SECTOR( gpAR->ubSectorX, gpAR->ubSectorY ) ].SoundTacticalDeath[0] );
else
#endif
SetMusicMode( MUSIC_TACTICAL_DEATH );
LogBattleResults( LOG_DEFEAT );
break;
case BATTLE_RETREAT:
//Tack on 5 minutes for retreat.
gpAR->uiTotalElapsedBattleTimeInMilliseconds += 300000;
if( ProcessLoyalty() )
HandleLoyaltyImplicationsOfMercRetreat( RETREAT_AUTORESOLVE, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
if ( GetEnemyEncounterCode() != CREATURE_ATTACK_CODE &&
GetEnemyEncounterCode () != BLOODCAT_ATTACK_CODE &&
GetEnemyEncounterCode () != ZOMBIE_ATTACK_CODE &&
GetEnemyEncounterCode () != BANDIT_ATTACK_CODE )
{
gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
}
else if( gpAR->ubAliveEnemies )
{
gsEnemyGainedControlOfSectorID = (INT16)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
gsCiviliansEatenByMonsters = gpAR->ubAliveEnemies;
}
LogBattleResults( LOG_DEFEAT );
break;
}
}
//Render the end battle condition.
switch( gpAR->ubBattleStatus )
{
case BATTLE_VICTORY:
SetFontForeground( FONT_LTGREEN );
swprintf( str, gpStrategicString[ STR_AR_OVER_VICTORY ] );
break;
case BATTLE_SURRENDERED:
case BATTLE_CAPTURED:
if( gpAR->ubBattleStatus == BATTLE_SURRENDERED )
{
swprintf( str, gpStrategicString[ STR_AR_OVER_SURRENDERED ] );
}
else
{
DisplayWrappedString( (UINT16)(gpAR->sCenterStartX+16), iScreenHeightOffset + 310, 108, 2,
FONT10ARIAL, FONT_YELLOW, gpStrategicString[ STR_ENEMY_CAPTURED ], FONT_BLACK, FALSE, LEFT_JUSTIFIED );
swprintf( str, gpStrategicString[ STR_AR_OVER_CAPTURED ] );
}
SetFontForeground( FONT_RED );
break;
case BATTLE_DEFEAT:
SetFontForeground( FONT_RED );
swprintf( str, gpStrategicString[ STR_AR_OVER_DEFEAT ] );
break;
case BATTLE_RETREAT:
SetFontForeground( FONT_YELLOW );
swprintf( str, gpStrategicString[ STR_AR_OVER_RETREATED ] );
break;
}
//Render the results of the battle.
SetFont( BLOCKFONT2 );
xp = gpAR->sCenterStartX + 12;
yp = iScreenHeightOffset + 218 + gpAR->bVerticalOffset;
BltVideoObjectFromIndex( FRAME_BUFFER, gpAR->iIndent, 0, xp, yp, VO_BLT_SRCTRANSPARENCY, NULL );
xp = gpAR->sCenterStartX + 70 - StringPixLength( str, BLOCKFONT2 )/2;
yp = iScreenHeightOffset + 227 + gpAR->bVerticalOffset;
mprintf( xp, yp, str );
//Render the total battle time elapsed.
SetFont( FONT10ARIAL );
swprintf( str, L"%s: %dm %02ds",
gpStrategicString[ STR_AR_TIME_ELAPSED ],
gpAR->uiTotalElapsedBattleTimeInMilliseconds/60000,
(gpAR->uiTotalElapsedBattleTimeInMilliseconds%60000)/1000 );
xp = gpAR->sCenterStartX + 70 - StringPixLength( str, FONT10ARIAL )/2;
yp = iScreenHeightOffset + 290 + gpAR->bVerticalOffset;
SetFontForeground( FONT_YELLOW );
mprintf( xp, yp, str );
// Flugente: in any case, reset creature attack variables
ResetCreatureAttackVariables();
}
MarkButtonsDirty();
InvalidateScreen();
}
static void ARCreateMilitia( UINT8 mclass, INT32 i, INT16 sX, INT16 sY)
{
// reset counter of how many mortars this team has rolled
ResetMortarsOnTeamCount();
ResetNumSquadleadersInArmyGroup(); // added by SANDRO
if( !gpBattleGroup ) {
//AssertMsg(0, "No battle group set while creating militia");
}
if( mclass == SOLDIER_CLASS_ELITE_MILITIA )
{
gpCivs[i].pSoldier = TacticalCreateMilitia( SOLDIER_CLASS_ELITE_MILITIA, sX, sY );
if( gpCivs[i].pSoldier->ubBodyType == REGFEMALE )
{
gpCivs[i].usIndex = MILITIA3F_FACE;
}
else
{
gpCivs[i].usIndex = MILITIA3_FACE;
}
}
else if( mclass == SOLDIER_CLASS_REG_MILITIA )
{
gpCivs[i].pSoldier = TacticalCreateMilitia( SOLDIER_CLASS_REG_MILITIA, sX, sY );
if( gpCivs[i].pSoldier->ubBodyType == REGFEMALE )
{
gpCivs[i].usIndex = MILITIA2F_FACE;
}
else
{
gpCivs[i].usIndex = MILITIA2_FACE;
}
}
else if( mclass == SOLDIER_CLASS_GREEN_MILITIA )
{
gpCivs[i].pSoldier = TacticalCreateMilitia( SOLDIER_CLASS_GREEN_MILITIA, sX, sY );
if( gpCivs[i].pSoldier->ubBodyType == REGFEMALE )
{
gpCivs[i].usIndex = MILITIA1F_FACE;
}
else
{
gpCivs[i].usIndex = MILITIA1_FACE;
}
}
else
{
AssertMsg( 0, "Attempting to illegally create a militia soldier." );
}
if( !gpCivs[ i ].pSoldier )
{
AssertMsg( 0, "Failed to create militia soldier for autoresolve." );
}
gpCivs[i].uiVObjectID = gpAR->iFaces;
gpCivs[i].pSoldier->sSectorX = sX;
gpCivs[i].pSoldier->sSectorY = sY;
swprintf( gpCivs[i].pSoldier->name, gpStrategicString[ STR_AR_MILITIA_NAME ] );
}
static void ARCreateMilitiaSquad( UINT16 *cnt, UINT16 ubEliteMilitia, UINT16 ubRegMilitia, UINT16 ubGreenMilitia, INT16 sX, INT16 sY)
{
while( *cnt < gpAR->ubCivs && (ubEliteMilitia || ubRegMilitia || ubGreenMilitia) )
{
if (ubEliteMilitia)
{
ARCreateMilitia( SOLDIER_CLASS_ELITE_MILITIA, *cnt, sX, sY);
ubEliteMilitia--;
}
else if (ubRegMilitia)
{
ARCreateMilitia( SOLDIER_CLASS_REG_MILITIA, *cnt, sX, sY);
ubRegMilitia--;
}
else if (ubGreenMilitia)
{
ARCreateMilitia( SOLDIER_CLASS_GREEN_MILITIA, *cnt, sX, sY);
ubGreenMilitia--;
}
(*cnt)++;
}
}
void CreateAutoResolveInterface()
{
VOBJECT_DESC VObjectDesc;
INT32 i, index;
HVOBJECT hVObject;
UINT16 ubGreenMilitia, ubRegMilitia, ubEliteMilitia;
UINT16 pMoveDir[4][3];
UINT8 uiDirNumber = 0;
UINT16 cnt;
//Setup new autoresolve blanket interface.
MSYS_DefineRegion( &gpAR->AutoResolveRegion, 0 + xResOffset, 0, SCREEN_WIDTH, SCREEN_HEIGHT, MSYS_PRIORITY_HIGH-1, 0,
MSYS_NO_CALLBACK, MSYS_NO_CALLBACK );
gpAR->fRenderAutoResolve = TRUE;
gpAR->fExitAutoResolve = FALSE;
//Load the general panel image pieces, to be combined to make the dynamically sized window.
VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE;
sprintf( VObjectDesc.ImageFile, "Interface\\AutoResolve.sti" );
if( !AddVideoObject( &VObjectDesc, (UINT32 *)&gpAR->iPanelImages ) )
{
AssertMsg( 0, "Failed to load Interface\\AutoResolve.sti" );
}
//Load the button images file, and assign it to the first button.
/* OLD BEFORE THE MEDICAL BUTTON WAS ADDED
gpAR->iButtonImage[ PAUSE_BUTTON ] = LoadButtonImage( "Interface\\AutoBtns.sti", -1, 0, -1, 6, -1 );
if( gpAR->iButtonImage[ PAUSE_BUTTON ] == -1 )
{
AssertMsg( 0, "Failed to load Interface\\AutoBtns.sti" );
}
//Have the other buttons hook into the first button containing the images.
gpAR->iButtonImage[ PLAY_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 1, -1, 7, -1 );
gpAR->iButtonImage[ FAST_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 2, -1, 8, -1 );
gpAR->iButtonImage[ FINISH_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 3, -1, 9, -1 );
gpAR->iButtonImage[ YES_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 4, -1, 10, -1 );
gpAR->iButtonImage[ NO_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 5, -1, 11, -1 );
gpAR->iButtonImage[ RETREAT_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 12, -1, 13, -1 );
gpAR->iButtonImage[ DONE_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 14, -1, 15, -1 );
*/
gpAR->iButtonImage[ PAUSE_BUTTON ] = LoadButtonImage( "Interface\\AutoBtns.sti", -1, 0, -1, 7, -1 );
if( gpAR->iButtonImage[ PAUSE_BUTTON ] == -1 )
{
AssertMsg( 0, "Failed to load Interface\\AutoBtns.sti" );
}
//Have the other buttons hook into the first button containing the images.
gpAR->iButtonImage[ PLAY_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 1, -1, 8, -1 );
gpAR->iButtonImage[ FAST_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 2, -1, 9, -1 );
gpAR->iButtonImage[ FINISH_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 3, -1, 10, -1 );
gpAR->iButtonImage[ YES_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 4, -1, 11, -1 );
gpAR->iButtonImage[ NO_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 5, -1, 12, -1 );
gpAR->iButtonImage[ BANDAGE_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 6, -1, 13, -1 );
gpAR->iButtonImage[ RETREAT_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 14, -1, 15, -1 );
gpAR->iButtonImage[ DONEWIN_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 14, -1, 15, -1 );
gpAR->iButtonImage[ DONELOSE_BUTTON ] = UseLoadedButtonImage( gpAR->iButtonImage[ PAUSE_BUTTON ], -1, 16, -1, 17, -1 );
//Load the generic faces for civs and enemies
sprintf( VObjectDesc.ImageFile, "Interface\\SmFaces.sti" );
if( !AddVideoObject( &VObjectDesc, (UINT32 *)&gpAR->iFaces ) )
{
AssertMsg( 0, "Failed to load Interface\\SmFaces.sti" );
}
if( GetVideoObject( &hVObject, gpAR->iFaces ) )
{
hVObject->pShades[ 0 ] = Create16BPPPaletteShaded( hVObject->pPaletteEntry, 255, 255, 255, FALSE );
hVObject->pShades[ 1 ] = Create16BPPPaletteShaded( hVObject->pPaletteEntry, 250, 25, 25, TRUE );
}
//Add the battle over panels
sprintf( VObjectDesc.ImageFile, "Interface\\indent.sti" );
if( !AddVideoObject( &VObjectDesc, (UINT32 *)&gpAR->iIndent ) )
{
AssertMsg( 0, "Failed to load Interface\\indent.sti" );
}
//add all the faces now
for( i = 0; i < gpAR->ubMercs; ++i )
{
VOBJECT_DESC VObjectDesc;
//Load the face
VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE;
if ( ( gpMercs[ i ].pSoldier->ubProfile >= 0 ) && ( gpMercs[ i ].pSoldier->ubProfile < 100 ) && gMercProfiles[gpMercs[i].pSoldier->ubProfile].Type == PROFILETYPE_IMP )
{
sprintf( VObjectDesc.ImageFile, "IMPFaces\\65Face\\%02d.sti", gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].ubFaceIndex );
}
else if ( ( gpMercs[ i ].pSoldier->ubProfile > 99 ) && gMercProfiles[gpMercs[i].pSoldier->ubProfile].Type == PROFILETYPE_IMP )
{
sprintf( VObjectDesc.ImageFile, "IMPFaces\\65Face\\%02d.sti", gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].ubFaceIndex );
}
else if ( ( gpMercs[ i ].pSoldier->ubProfile >= 0 ) && ( gpMercs[ i ].pSoldier->ubProfile < 100 ) )
{
sprintf( VObjectDesc.ImageFile, "Faces\\65Face\\%02d.sti", gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].ubFaceIndex );
}
else
{
sprintf( VObjectDesc.ImageFile, "Faces\\65Face\\%02d.sti", gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].ubFaceIndex );
}
if( !AddVideoObject( &VObjectDesc, &gpMercs[ i ].uiVObjectID ) )
{
sprintf( VObjectDesc.ImageFile, "Faces\\65Face\\speck.sti" );
if( !AddVideoObject( &VObjectDesc, &gpMercs[ i ].uiVObjectID ) )
{
AssertMsg( 0, String("Failed to load %Faces\\65Face\\%02d.sti or it's placeholder, speck.sti", gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].ubFaceIndex) );
}
}
if( GetVideoObject( &hVObject, gpMercs[ i ].uiVObjectID ) )
{
hVObject->pShades[ 0 ] = Create16BPPPaletteShaded( hVObject->pPaletteEntry, 255, 255, 255, FALSE );
hVObject->pShades[ 1 ] = Create16BPPPaletteShaded( hVObject->pPaletteEntry, 250, 25, 25, TRUE );
}
}
// 0verhaul: The following code was modified so that militia bookkeeping could be handled.
// In the previous auto-resolve method, promotions were often lost because the sector did not
// actually have the militia to be promoted--they came from surrounding sectors. The
// sector X and Y are not actually used for auto-resolve itself, so I can use it to adjust
// both the actual sector counts and the promotions, by using the origin sectors of each
// soldier.
ubEliteMilitia = MilitiaInSectorOfRank( gpAR->ubSectorX, gpAR->ubSectorY, ELITE_MILITIA );
ubRegMilitia = MilitiaInSectorOfRank( gpAR->ubSectorX, gpAR->ubSectorY, REGULAR_MILITIA );
ubGreenMilitia = MilitiaInSectorOfRank( gpAR->ubSectorX, gpAR->ubSectorY, GREEN_MILITIA );
// see if we get any bonus militia from nearby towns
UINT16 bonusGreenMilitia = 0, bonusRegularMilitia = 0, bonusEliteMilitia = 0;
RebelCommand::GetBonusMilitia(gpAR->ubSectorX, gpAR->ubSectorY, bonusGreenMilitia, bonusRegularMilitia, bonusEliteMilitia, FALSE); // no need to create a group for autoresolve as we're just increasing local militia pop
ubEliteMilitia += bonusEliteMilitia;
ubRegMilitia += bonusRegularMilitia;
ubGreenMilitia += bonusGreenMilitia;
cnt = 0;
// Add the militia in this sector
ARCreateMilitiaSquad( &cnt, ubEliteMilitia, ubRegMilitia, ubGreenMilitia, gpAR->ubSectorX, gpAR->ubSectorY);
// Add the militia in the surrounding sectors
// HEADROCK HAM 3.4: Changed argument configuration.
GenerateDirectionInfos( gpAR->ubSectorX, gpAR->ubSectorY, &uiDirNumber, pMoveDir, FALSE, TRUE );
for( i=0; i<uiDirNumber; ++i)
{
INT16 sX = SECTORX( pMoveDir[ i ][0] );
INT16 sY = SECTORY( pMoveDir[ i ][0] );
ubEliteMilitia = MilitiaInSectorOfRank( sX, sY, ELITE_MILITIA );
ubRegMilitia = MilitiaInSectorOfRank( sX, sY, REGULAR_MILITIA );
ubGreenMilitia = MilitiaInSectorOfRank( sX, sY, GREEN_MILITIA );
ARCreateMilitiaSquad( &cnt, ubEliteMilitia, ubRegMilitia, ubGreenMilitia, sX, sY );
}
index = 0;
if ( GetEnemyEncounterCode() == CREATURE_ATTACK_CODE )
{
for ( i = 0; i < gpAR->ubAFCreatures; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateCreature( ADULTFEMALEMONSTER );
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = AF_CREATURE_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[ STR_AR_CREATURE_NAME ] );
}
for( i = 0; i < gpAR->ubAMCreatures; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateCreature( AM_MONSTER );
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = AM_CREATURE_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[ STR_AR_CREATURE_NAME ] );
}
for( i = 0; i < gpAR->ubYFCreatures; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateCreature( YAF_MONSTER );
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = YF_CREATURE_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[ STR_AR_CREATURE_NAME ] );
}
for( i = 0; i < gpAR->ubYMCreatures; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateCreature( YAM_MONSTER );
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = YM_CREATURE_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[ STR_AR_CREATURE_NAME ] );
}
}
else if ( GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE ||
GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE ||
GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
{
for ( i = 0; i < gpAR->ubBloodcats; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateCreature( BLOODCAT );
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = BLOODCAT_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, pDisplayBodyTypeInfoText[27] );
}
for ( i = 0; i < gpAR->ubZombies; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateZombie();
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = ZOMBIE_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_PB_ZOMBIE] );
}
for ( i = 0; i < gpAR->ubBandits; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateBandit();
gpEnemies[index].uiVObjectID = gpAR->iFaces;
if ( gpEnemies[index].pSoldier->ubBodyType == REGFEMALE )
{
gpEnemies[index].usIndex = BANDIT1_F_FACE + Random( BLOODCAT_FACE - BANDIT1_F_FACE );
}
else
{
gpEnemies[index].usIndex = BANDIT1_FACE + Random( BANDIT1_F_FACE - BANDIT1_FACE );
}
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_PB_BANDIT] );
}
}
else
{
for (i = 0; i < gpAR->ubRobots; ++i, ++index)
{
gpEnemies[index].pSoldier = TacticalCreateEnemyRobot();
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_AR_ROBOT_NAME] );
// reuse madlab's robot's face
VOBJECT_DESC VObjectDesc;
VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE;
sprintf( VObjectDesc.ImageFile, "Faces\\65Face\\%02d.sti", gMercProfiles[ ROBOT ].ubFaceIndex );
AddVideoObject(&VObjectDesc, &gpEnemies[index].uiVObjectID);
}
for ( i = 0; i < gpAR->ubElites; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateEliteEnemy();
gpEnemies[index].uiVObjectID = gpAR->iFaces;
if ( gpEnemies[index].pSoldier->ubBodyType == REGFEMALE )
{
gpEnemies[index].usIndex = ELITEF_FACE;
}
else
{
gpEnemies[index].usIndex = ELITE_FACE;
}
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_AR_ELITE_NAME] );
}
for ( i = 0; i < gpAR->ubTroops; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateArmyTroop();
gpEnemies[index].uiVObjectID = gpAR->iFaces;
if ( gpEnemies[index].pSoldier->ubBodyType == REGFEMALE )
{
gpEnemies[index].usIndex = TROOPF_FACE;
}
else
{
gpEnemies[index].usIndex = TROOP_FACE;
}
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_AR_TROOP_NAME] );
}
for ( i = 0; i < gpAR->ubAdmins; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateAdministrator();
gpEnemies[index].uiVObjectID = gpAR->iFaces;
if ( gpEnemies[index].pSoldier->ubBodyType == REGFEMALE )
{
gpEnemies[index].usIndex = ADMINF_FACE;
}
else
{
gpEnemies[index].usIndex = ADMIN_FACE;
}
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_AR_ADMINISTRATOR_NAME] );
}
for ( i = 0; i < gpAR->ubTanks; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateEnemyTank();
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = TANK_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_AR_TANK_NAME] );
}
for ( i = 0; i < gpAR->ubJeeps; ++i, ++index )
{
gpEnemies[index].pSoldier = TacticalCreateEnemyJeep();
gpEnemies[index].uiVObjectID = gpAR->iFaces;
gpEnemies[index].usIndex = JEEP_FACE;
gpEnemies[index].pSoldier->sSectorX = gpAR->ubSectorX;
gpEnemies[index].pSoldier->sSectorY = gpAR->ubSectorY;
swprintf( gpEnemies[index].pSoldier->name, gpStrategicString[STR_AR_JEEP_NAME] );
}
AssociateEnemiesWithStrategicGroups();
}
if( gpAR->ubSectorX == gWorldSectorX && gpAR->ubSectorY == gWorldSectorY && !gbWorldSectorZ )
{
CheckAndHandleUnloadingOfCurrentWorld();
}
else
{
gfBlitBattleSectorLocator = FALSE;
}
//Build the interface buffer, and blit the "shaded" background. This info won't
//change from now on, but will be used to restore text.
BuildInterfaceBuffer();
BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, 0 + xResOffset, 0, SCREEN_WIDTH, SCREEN_HEIGHT );
//If we are bumping up the interface, then also use that piece of info to
//move the buttons up by the same amount.
gpAR->bVerticalOffset = (240 - gpAR->sHeight/2) > 120 ? -40 : 0;
//Create the buttons -- subject to relocation
gpAR->iButton[ PLAY_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ PLAY_BUTTON ] , (INT16)(gpAR->sCenterStartX+11), (INT16)(iScreenHeightOffset + 240+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, PlayButtonCallback );
gpAR->iButton[ FAST_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ FAST_BUTTON ] , (INT16)(gpAR->sCenterStartX+51), (INT16)(iScreenHeightOffset + 240+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, FastButtonCallback );
gpAR->iButton[ FINISH_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ FINISH_BUTTON ] , (INT16)(gpAR->sCenterStartX+91), (INT16)(iScreenHeightOffset + 240+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, FinishButtonCallback );
gpAR->iButton[ PAUSE_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ PAUSE_BUTTON ] , (INT16)(gpAR->sCenterStartX+11), (INT16)(iScreenHeightOffset + 274+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, PauseButtonCallback );
gpAR->iButton[ RETREAT_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ RETREAT_BUTTON ], (INT16)(gpAR->sCenterStartX+51), (INT16)(iScreenHeightOffset + 274+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, RetreatButtonCallback );
// no retreat possible if no mercs are present and militia isn't present or cannot be commanded
if ( !gpAR->ubMercs && (!gpAR->ubCivs || !CanGiveStrategicMilitiaMoveOrder( gpAR->ubSectorX, gpAR->ubSectorY )) )
{
DisableButton( gpAR->iButton[ RETREAT_BUTTON ] );
}
SpecifyGeneralButtonTextAttributes( gpAR->iButton[ RETREAT_BUTTON ], gpStrategicString[STR_AR_RETREAT_BUTTON], BLOCKFONT2, 169, FONT_NEARBLACK );
gpAR->iButton[ BANDAGE_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ BANDAGE_BUTTON ] , (INT16)(gpAR->sCenterStartX+11), (INT16)(iScreenHeightOffset + 245+gpAR->bVerticalOffset), BUTTON_NO_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, BandageButtonCallback );
gpAR->iButton[ DONEWIN_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ DONEWIN_BUTTON ], (INT16)(gpAR->sCenterStartX+51), (INT16)(iScreenHeightOffset + 245+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, DoneButtonCallback );
SpecifyGeneralButtonTextAttributes( gpAR->iButton[ DONEWIN_BUTTON ], gpStrategicString[STR_AR_DONE_BUTTON], BLOCKFONT2, 169, FONT_NEARBLACK );
gpAR->iButton[ DONELOSE_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ DONELOSE_BUTTON ], (INT16)(gpAR->sCenterStartX+25), (INT16)(iScreenHeightOffset + 245+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, DoneButtonCallback );
SpecifyGeneralButtonTextAttributes( gpAR->iButton[ DONELOSE_BUTTON ], gpStrategicString[STR_AR_DONE_BUTTON], BLOCKFONT2, 169, FONT_NEARBLACK );
gpAR->iButton[ YES_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ YES_BUTTON ], (INT16)(gpAR->sCenterStartX+21), (INT16)(iScreenHeightOffset + 257+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, AcceptSurrenderCallback );
gpAR->iButton[ NO_BUTTON ] =
QuickCreateButton( gpAR->iButtonImage[ NO_BUTTON ], (INT16)(gpAR->sCenterStartX+81), (INT16)(iScreenHeightOffset + 257+gpAR->bVerticalOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGH,
DEFAULT_MOVE_CALLBACK, RejectSurrenderCallback );
HideButton( gpAR->iButton[ YES_BUTTON ] );
HideButton( gpAR->iButton[ NO_BUTTON ] );
HideButton( gpAR->iButton[ DONEWIN_BUTTON ] );
HideButton( gpAR->iButton[ DONELOSE_BUTTON ] );
HideButton( gpAR->iButton[ BANDAGE_BUTTON ] );
ButtonList[ gpAR->iButton[ PLAY_BUTTON ] ]->uiFlags |= BUTTON_CLICKED_ON;
}
void RemoveAutoResolveInterface( BOOLEAN fDeleteForGood )
{
INT32 i;
UINT8 ubCurrentRank;
UINT8 ubCurrentGroupID = 0;
BOOLEAN fFirstGroup = TRUE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
//VtResumeSampling();
MSYS_RemoveRegion( &gpAR->AutoResolveRegion );
DeleteVideoObjectFromIndex( gpAR->iPanelImages );
DeleteVideoObjectFromIndex( gpAR->iFaces );
DeleteVideoObjectFromIndex( gpAR->iIndent );
DeleteVideoSurfaceFromIndex( gpAR->iInterfaceBuffer );
if( fDeleteForGood )
{ //Delete the soldier instances -- done when we are completely finished.
//KM: By request of AM, I have added this bleeding event in cases where autoresolve is
// complete and there are bleeding mercs remaining. AM coded the internals
// of the strategic event.
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pSoldier->bBleeding && gpMercs[ i ].pSoldier->stats.bLife )
{
// ARM: only one event is needed regardless of how many are bleeding
AddStrategicEvent( EVENT_BANDAGE_BLEEDING_MERCS, GetWorldTotalMin() + 1, 0 );
break;
}
}
// ARM: Update assignment flashing: Doctors may now have new patients or lost them all, etc.
gfReEvaluateEveryonesNothingToDo = TRUE;
if( gpAR->pRobotCell)
{
gpAR->pRobotCell->pSoldier->UpdateRobotControllerGivenRobot( );
}
for( i = 0; i < gpAR->iNumMercFaces; i++ )
{
if( i >= gpAR->iActualMercFaces )
TacticalRemoveSoldierPointer( gpMercs[ i ].pSoldier, FALSE );
else
{ //Record finishing information for our mercs
if( !gpMercs[ i ].pSoldier->stats.bLife )
{
StrategicHandlePlayerTeamMercDeath( gpMercs[ i ].pSoldier );
// now remove character from a squad
RemoveCharacterFromSquads( gpMercs[ i ].pSoldier );
ChangeSoldiersAssignment( gpMercs[ i ].pSoldier, ASSIGNMENT_DEAD );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpMercs[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
}
else if( gpAR->ubBattleStatus == BATTLE_SURRENDERED || gpAR->ubBattleStatus == BATTLE_CAPTURED )
{
EnemyCapturesPlayerSoldier( gpMercs[ i ].pSoldier );
}
else if( gpAR->ubBattleStatus == BATTLE_VICTORY )
{ //merc is alive, so group them at the center gridno.
gpMercs[ i ].pSoldier->ubStrategicInsertionCode = INSERTION_CODE_CENTER;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - records - autoresolve battles
if( gpMercs[ i ].pSoldier->ubProfile != NO_PROFILE )
{
// if we ran before the battle was over but the others won in the end, we still get retreated counter, no victory
if ( gpMercs[ i ].uiFlags & CELL_RETREATED || gpMercs[ i ].uiFlags & CELL_RETREATING )
{
gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].records.usBattlesRetreated++;
}
else
{
gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].records.usBattlesAutoresolve++;
if ( gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].records.usLargestBattleFought < gpAR->ubEnemies )
{
gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].records.usLargestBattleFought = gpAR->ubEnemies;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - merc records - times retreated counter
else if( gpAR->ubBattleStatus == BATTLE_RETREAT )
{
if( gpMercs[ i ].uiFlags & CELL_RETREATED || gpMercs[ i ].uiFlags & CELL_RETREATING )
{
if( gpMercs[ i ].pSoldier->ubProfile != NO_PROFILE && !AM_A_ROBOT( gpMercs[ i ].pSoldier ) )
{
gMercProfiles[ gpMercs[ i ].pSoldier->ubProfile ].records.usBattlesRetreated++;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
}
for( i = 0; i < gpAR->iNumMercFaces; i++ )
{
if( gpAR->ubBattleStatus == BATTLE_VICTORY && gpMercs[ i ].pSoldier->stats.bLife >= OKLIFE )
{
if( gpMercs[ i ].pSoldier->ubGroupID != ubCurrentGroupID )
{
ubCurrentGroupID = gpMercs[ i ].pSoldier->ubGroupID;
// look for NPCs to stop for, anyone is too tired to keep going, if all OK rebuild waypoints & continue movement
// NOTE: Only the first group found will stop for NPCs, it's just too much hassle to stop them all
PlayerGroupArrivedSafelyInSector( GetGroup( gpMercs[ i ].pSoldier->ubGroupID ), fFirstGroup );
fFirstGroup = FALSE;
}
}
gpMercs[ i ].pSoldier = NULL;
}
// End capture squence....
if( gpAR->ubBattleStatus == BATTLE_SURRENDERED || gpAR->ubBattleStatus == BATTLE_CAPTURED )
{
EndCaptureSequence( );
}
}
//Delete all of the faces.
for( i = 0; i < gpAR->iNumMercFaces; i++ )
{
if( gpMercs[ i ].uiVObjectID != -1 )
DeleteVideoObjectFromIndex( gpMercs[ i ].uiVObjectID );
gpMercs[ i ].uiVObjectID = -1;
if( gpMercs[ i ].pRegion )
{
MSYS_RemoveRegion( gpMercs[ i ].pRegion );
MemFree( gpMercs[ i ].pRegion );
gpMercs[ i ].pRegion = NULL;
}
}
//Delete all militia
gbGreenToElitePromotions = 0;
gbGreenToRegPromotions = 0;
gbRegToElitePromotions = 0;
gbMilitiaPromotions = 0;
for( i = 0; i < MAX_AR_TEAM_SIZE; ++i )
{
if( gpCivs[ i ].pSoldier )
{
ubCurrentRank = GREEN_MILITIA;
switch( gpCivs[ i ].pSoldier->ubSoldierClass )
{
case SOLDIER_CLASS_GREEN_MILITIA: ubCurrentRank = GREEN_MILITIA; break;
case SOLDIER_CLASS_REG_MILITIA: ubCurrentRank = REGULAR_MILITIA; break;
case SOLDIER_CLASS_ELITE_MILITIA: ubCurrentRank = ELITE_MILITIA; break;
default:
#ifdef JA2BETAVERSION
ScreenMsg( FONT_RED, MSG_ERROR, L"Removing autoresolve militia with invalid ubSoldierClass %d.",gpCivs[ i ].pSoldier->ubSoldierClass );
#endif
break;
}
// Flugente: drop sector equipment
gpCivs[ i ].pSoldier->DropSectorEquipment();
if( fDeleteForGood && gpCivs[ i ].pSoldier->stats.bLife < OKLIFE/2 )
{
// Flugente: individual militia
// we not only handle promotions here, but basically update this guy (if not already counted as dead)
MILITIA militia;
if ( GetMilitia( gpCivs[i].pSoldier->usIndividualMilitiaID, &militia ) && !(militia.flagmask & MILITIAFLAG_DEAD) )
{
MILITIA_BATTLEREPORT report;
report.id = GetIdOfCurrentlyOngoingIncident( );
report.flagmask |= MILITIA_BATTLEREPORT_FLAG_DIED;
if ( gpCivs[i].pSoldier->ubMilitiaKills )
report.flagmask |= MILITIA_BATTLEREPORT_FLAG_KILLEDENEMY;
militia.history.push_back( report );
militia.healthratio = 0.0f;
militia.flagmask |= MILITIAFLAG_DEAD;
UpdateMilitia( militia );
}
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpCivs[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
StrategicRemoveMilitiaFromSector( gpCivs[ i ].pSoldier->sSectorX, gpCivs[ i ].pSoldier->sSectorY, ubCurrentRank, 1 );
if( ProcessLoyalty() )
HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_NATIVE_KILLED, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
}
else
{
// Flugente: take care of promotions and individual militia update
HandlePossibleMilitiaPromotion( gpCivs[i].pSoldier, TRUE );
}
TacticalRemoveSoldierPointer( gpCivs[ i ].pSoldier, FALSE );
memset( &gpCivs[ i ], 0, sizeof( SOLDIERCELL ) );
}
}
// Flugente: after we fought, move any militia in groups into the sector garrison - the player will likely want to rethink orders for the survivors
// Flugente: in case militia died we have to lower the group sizes
// loop over all militia groups that are in this sector and alter their size. If a group has size 0 delete it. If any militia are left place them into this sector
DissolveAllMilitiaGroupsInSector(gpAR->ubSectorX, gpAR->ubSectorY);
//Record and process all enemy deaths
for( i = 0; i < MAX_AR_TEAM_SIZE; ++i )
{
if( gpEnemies[ i ].pSoldier )
{
if( fDeleteForGood && gpEnemies[ i ].pSoldier->stats.bLife < OKLIFE ) //if we are finished with battle and soldier is either dead or dying
{
TrackEnemiesKilled( ENEMY_KILLED_IN_AUTO_RESOLVE, gpEnemies[ i ].pSoldier->ubSoldierClass ); //add casualty to some statistic
if( ProcessLoyalty() )HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ENEMY_KILLED, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
ProcessQueenCmdImplicationsOfDeath( gpEnemies[ i ].pSoldier );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpEnemies[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
}
}
}
//Eliminate all excess soldiers
if( fDeleteForGood )
{ //Warp the game time accordingly
if( gpAR->ubBattleStatus == BATTLE_VICTORY )
{ //Get rid of any extra enemies that could be here.
EliminateAllEnemies( gpAR->ubSectorX, gpAR->ubSectorY );
}
else
{ //The enemy won, so repoll movement.
ResetMovementForNonPlayerGroupsInLocation( gpAR->ubSectorX, gpAR->ubSectorY );
}
}
//Physically delete the soldiers now.
for( i = 0; i < MAX_AR_TEAM_SIZE; ++i )
{
if( gpEnemies[ i ].pSoldier )
{
TacticalRemoveSoldierPointer( gpEnemies[ i ].pSoldier, TRUE );
memset( &gpEnemies[ i ], 0, sizeof( SOLDIERCELL ) );
}
}
for( i = 0; i < NUM_AR_BUTTONS; ++i )
{
UnloadButtonImage( gpAR->iButtonImage[ i ] );
RemoveButton( gpAR->iButton[ i ] );
}
if( fDeleteForGood )
{
//Warp the game time accordingly
WarpGameTime( gpAR->uiTotalElapsedBattleTimeInMilliseconds/1000, WARPTIME_NO_PROCESSING_OF_EVENTS );
//Deallocate all of the global memory.
//Everything internal to them, should have already been deleted.
MemFree( gpAR );
gpAR = NULL;
MemFree( gpMercs );
gpMercs = NULL;
MemFree( gpCivs );
gpCivs = NULL;
MemFree( gpEnemies );
gpEnemies = NULL;
}
//KM : Aug 09, 1999 Patch fix -- Would break future dialog while time compressing
gTacticalStatus.ubCurrentTeam = gbPlayerNum;
gpBattleGroup = NULL;
if ( GetEnemyEncounterCode() == CREATURE_ATTACK_CODE ||
GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE ||
GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE ||
GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
{
gubNumCreaturesAttackingTown = 0;
gubSectorIDOfCreatureAttack = 0;
}
//VtPauseSampling();
}
void PauseButtonCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
ButtonList[ gpAR->iButton[ PLAY_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FAST_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FINISH_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
gpAR->fPaused = TRUE;
}
}
void PlayButtonCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
ButtonList[ gpAR->iButton[ PAUSE_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FAST_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FINISH_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
gpAR->uiTimeSlice = 1000 * gpAR->ubTimeModifierPercentage / 100;
gpAR->fPaused = FALSE;
}
}
void FastButtonCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
ButtonList[ gpAR->iButton[ PAUSE_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ PLAY_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FINISH_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
gpAR->uiTimeSlice = 4000;
gpAR->fPaused = FALSE;
}
}
void FinishButtonCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
ButtonList[ gpAR->iButton[ PAUSE_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ PLAY_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FAST_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
gpAR->uiTimeSlice = 0xffffffff;
gpAR->fSound = FALSE;
gpAR->fPaused = FALSE;
PlayJA2StreamingSample( AUTORESOLVE_FINISHFX, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
}
}
void RetreatButtonCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
INT32 i;
for ( i = 0; i < gpAR->ubMercs; ++i )
{
if( !(gpMercs[ i ].uiFlags & (CELL_RETREATING|CELL_RETREATED)) )
{
gpMercs[ i ].uiFlags |= CELL_RETREATING | CELL_DIRTY;
//Gets to retreat after a total of 2 attacks.
gpMercs[ i ].usNextAttack = (UINT16)((1000 + gpMercs[ i ].usNextAttack * 2 + PreRandom( 2000 - gpMercs[ i ].usAttack ))*2);
gpAR->usPlayerAttack -= gpMercs[ i ].usAttack;
gpMercs[ i ].usAttack = 0;
}
}
// Flugente: militia can be ordered to retreat
if ( gGameExternalOptions.fMilitiaStrategicCommand )
{
for ( INT32 i = 0; i < gpAR->ubCivs; ++i )
{
if ( !(gpCivs[i].uiFlags & (CELL_RETREATING | CELL_RETREATED)) )
{
gpCivs[i].uiFlags |= CELL_RETREATING | CELL_DIRTY;
//Gets to retreat after a total of 2 attacks.
gpCivs[i].usNextAttack = (UINT16)((1000 + gpCivs[i].usNextAttack * 2 + PreRandom( 2000 - gpCivs[i].usAttack )) * 2);
gpAR->usPlayerAttack -= gpCivs[i].usAttack;
gpCivs[i].usAttack = 0;
}
}
}
if( gpAR->pRobotCell )
{
//if robot is retreating, set the retreat time to be the same as the robot's controller.
UINT16 ubRobotControllerID;
ubRobotControllerID = gpAR->pRobotCell->pSoldier->ubRobotRemoteHolderID;
if( ubRobotControllerID == NOBODY )
{
gpAR->pRobotCell->uiFlags &= ~CELL_RETREATING;
gpAR->pRobotCell->uiFlags |= CELL_DIRTY;
gpAR->pRobotCell->usNextAttack = 0xffff;
return;
}
for( i = 0; i < gpAR->ubMercs; ++i )
{
if( ubRobotControllerID == gpMercs[ i ].pSoldier->ubID )
{
//Found the controller, make the robot's retreat time match the contollers.
gpAR->pRobotCell->usNextAttack = gpMercs[ i ].usNextAttack;
return;
}
}
}
}
}
void DetermineBandageButtonState()
{
INT32 i;
OBJECTTYPE *pKit = NULL;
BOOLEAN fFound = FALSE;
//Does anyone need bandaging?
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pSoldier->bBleeding && gpMercs[ i ].pSoldier->stats.bLife )
{
fFound = TRUE;
break;
}
}
if( !fFound )
{
DisableButton( gpAR->iButton[ BANDAGE_BUTTON ] );
SetButtonFastHelpText( gpAR->iButton[ BANDAGE_BUTTON ], gzLateLocalizedString[ 11 ] );
return;
}
//Do we have any doctors?
fFound = FALSE;
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pSoldier->stats.bLife >= OKLIFE &&
!gpMercs[ i ].pSoldier->bCollapsed &&
gpMercs[ i ].pSoldier->stats.bMedical > 0 )
{
fFound = TRUE;
}
}
if( !fFound )
{ //No doctors
DisableButton( gpAR->iButton[ BANDAGE_BUTTON ] );
SetButtonFastHelpText( gpAR->iButton[ BANDAGE_BUTTON ], gzLateLocalizedString[ 8 ] );
return;
}
//Do have a kit?
pKit = FindMedicalKit();
if( !pKit )
{ //No kits
DisableButton( gpAR->iButton[ BANDAGE_BUTTON ] );
SetButtonFastHelpText( gpAR->iButton[ BANDAGE_BUTTON ], gzLateLocalizedString[ 9 ] );
return;
}
//Allow bandaging.
EnableButton( gpAR->iButton[ BANDAGE_BUTTON ] );
SetButtonFastHelpText( gpAR->iButton[ BANDAGE_BUTTON ], gzLateLocalizedString[ 12 ] );
}
void BandageButtonCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
AutoBandageMercs();
SetupDoneInterface();
}
}
void DoneButtonCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
gpAR->fExitAutoResolve = TRUE;
}
}
void MercCellMouseMoveCallback( MOUSE_REGION *reg, INT32 reason )
{
//Find the merc with the same region.
INT32 i;
SOLDIERCELL *pCell = NULL;
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pRegion == reg )
{
pCell = &gpMercs[ i ];
break;
}
}
Assert( pCell );
if( gpAR->fPendingSurrender )
{ //Can't setup retreats when pending surrender.
pCell->uiFlags &= ~CELL_SHOWRETREATTEXT;
pCell->uiFlags |= CELL_DIRTY;
return;
}
if( reg->uiFlags & MSYS_MOUSE_IN_AREA )
{
if( !(pCell->uiFlags & CELL_SHOWRETREATTEXT) )
pCell->uiFlags |= CELL_SHOWRETREATTEXT | CELL_DIRTY;
}
else
{
if( pCell->uiFlags & CELL_SHOWRETREATTEXT )
{
pCell->uiFlags &= ~CELL_SHOWRETREATTEXT;
pCell->uiFlags |= CELL_DIRTY;
}
}
}
void MercCellMouseClickCallback( MOUSE_REGION *reg, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
//Find the merc with the same region.
INT32 i;
SOLDIERCELL *pCell = NULL;
if( gpAR->fPendingSurrender )
{ //Can't setup retreats when pending surrender.
return;
}
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pRegion == reg )
{
pCell = &gpMercs[ i ];
break;
}
}
Assert(pCell);
if( pCell->uiFlags & ( CELL_RETREATING | CELL_RETREATED ) )
{ //already retreated/retreating.
return;
}
Assert( pCell );
if( pCell == gpAR->pRobotCell )
{ //robot retreats only when controller retreats
return;
}
pCell->uiFlags |= CELL_RETREATING | CELL_DIRTY;
//Gets to retreat after a total of 2 attacks.
pCell->usNextAttack = (UINT16)((1000 + pCell->usNextAttack * 5 + PreRandom( 2000 - pCell->usAttack ))*2);
gpAR->usPlayerAttack -= pCell->usAttack;
pCell->usAttack = 0;
if( gpAR->pRobotCell )
{ //if controller is retreating, make the robot retreat too.
UINT16 ubRobotControllerID;
ubRobotControllerID = gpAR->pRobotCell->pSoldier->ubRobotRemoteHolderID;
if( ubRobotControllerID == NOBODY )
{
gpAR->pRobotCell->uiFlags &= ~CELL_RETREATING;
gpAR->pRobotCell->uiFlags |= CELL_DIRTY;
gpAR->pRobotCell->usNextAttack = 0xffff;
}
else if( ubRobotControllerID == pCell->pSoldier->ubID )
{ //Found the controller, make the robot's retreat time match the contollers.
gpAR->pRobotCell->uiFlags |= CELL_RETREATING | CELL_DIRTY;
gpAR->pRobotCell->usNextAttack = pCell->usNextAttack;
gpAR->usPlayerAttack -= gpAR->pRobotCell->usAttack;
gpAR->pRobotCell->usAttack = 0;
return;
}
}
}
}
//Determine how many players, militia, and enemies that are going at it, and use these values
//to figure out how many rows and columns we can use. The will effect the size of the panel.
void CalculateAutoResolveInfo()
{
VOBJECT_DESC VObjectDesc;
GROUP *pGroup;
PLAYERGROUP *pPlayer;
Assert( gpAR->ubSectorX >= 1 && gpAR->ubSectorX <= 16 );
Assert( gpAR->ubSectorY >= 1 && gpAR->ubSectorY <= 16 );
if ( GetEnemyEncounterCode() == CREATURE_ATTACK_CODE )
{
if( gfTransferTacticalOppositionToAutoResolve )
{
DetermineCreatureTownCompositionBasedOnTacticalInformation( &gubNumCreaturesAttackingTown,
&gpAR->ubYMCreatures, &gpAR->ubYFCreatures,
&gpAR->ubAMCreatures, &gpAR->ubAFCreatures );
}
else
{
DetermineCreatureTownComposition( gubNumCreaturesAttackingTown,
&gpAR->ubYMCreatures, &gpAR->ubYFCreatures,
&gpAR->ubAMCreatures, &gpAR->ubAFCreatures );
}
// WANNE: Quickfix: If we have 0 creatues call the DetermineCreatureTownCompositionBasedOnTacticalInformation(),
// because we called the DetermineCreatureTownComposition() method before which was wrong.
if (gubNumCreaturesAttackingTown == 0)
{
DetermineCreatureTownCompositionBasedOnTacticalInformation( &gubNumCreaturesAttackingTown,
&gpAR->ubYMCreatures, &gpAR->ubYFCreatures,
&gpAR->ubAMCreatures, &gpAR->ubAFCreatures );
}
gpAR->ubEnemies = min( gpAR->ubYMCreatures + gpAR->ubYFCreatures + gpAR->ubAMCreatures + gpAR->ubAFCreatures, MAX_AR_TEAM_SIZE );
}
else if ( GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE ||
GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE ||
GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
{
if ( gfTransferTacticalOppositionToAutoResolve || gubNumCreaturesAttackingTown == 0 )
{
DetermineOtherCreatureTownCompositionBasedOnTacticalInformation( &gubNumCreaturesAttackingTown, &gpAR->ubBloodcats, &gpAR->ubZombies, &gpAR->ubBandits );
}
else
{
if ( GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE )
gpAR->ubBloodcats = gubNumCreaturesAttackingTown;
else if ( GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE )
gpAR->ubZombies = gubNumCreaturesAttackingTown;
else if ( GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
gpAR->ubBandits = gubNumCreaturesAttackingTown;
}
gpAR->ubEnemies = min( gubNumCreaturesAttackingTown, MAX_AR_TEAM_SIZE );
}
else
{
ActivateTurncoatsForAutoResolve( gpAR->ubSectorX, gpAR->ubSectorY );
GetNumberOfEnemiesInFiveSectors( gpAR->ubSectorX, gpAR->ubSectorY,
&gpAR->ubAdmins, &gpAR->ubTroops, &gpAR->ubElites, &gpAR->ubRobots, &gpAR->ubTanks, &gpAR->ubJeeps );
gpAR->ubEnemies = min( gpAR->ubAdmins + gpAR->ubTroops + gpAR->ubElites + gpAR->ubTanks + gpAR->ubJeeps + gpAR->ubRobots, MAX_AR_TEAM_SIZE );
}
gfTransferTacticalOppositionToAutoResolve = FALSE;
const UINT16 militia = CountAllMilitiaInFiveSectors( gpAR->ubSectorX, gpAR->ubSectorY );
gpAR->ubCivs = min(militia, MAXUINT8);
gpAR->ubMercs = 0;
VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE;
pGroup = gpGroupList;
while( pGroup )
{
if ( PlayerGroupInvolvedInThisCombat( pGroup ) )
{
pPlayer = pGroup->pPlayerList;
while( pPlayer )
{
// NOTE: Must check each merc individually, e.g. Robot without controller is an uninvolved merc on an involved group!
if ( PlayerMercInvolvedInThisCombat( pPlayer->pSoldier ) )
{
gpMercs[ gpAR->ubMercs ].pSoldier = pPlayer->pSoldier;
//!!! CLEAR OPPCOUNT HERE. All of these soldiers are guaranteed to not be in tactical anymore.
//ClearOppCount( pPlayer->pSoldier );
gpAR->ubMercs++;
if( AM_AN_EPC( pPlayer->pSoldier ) )
{
gpAR->fCaptureNotPermittedDueToEPCs = TRUE;
}
if( AM_A_ROBOT( pPlayer->pSoldier ) )
{
gpAR->pRobotCell = &gpMercs[ gpAR->ubMercs - 1 ];
gpAR->pRobotCell->pSoldier->UpdateRobotControllerGivenRobot( );
}
}
pPlayer = pPlayer->next;
}
}
pGroup = pGroup->next;
}
gpAR->iNumMercFaces = gpAR->ubMercs;
gpAR->iActualMercFaces = gpAR->ubMercs;
giMaxMilitiaToRender = 50 - ( (gpAR->ubMercs + 4) / 5 ) * 5;
CalculateRowsAndColumns();
}
void ResetAutoResolveInterface()
{
guiPreRandomIndex = gpAR->uiPreRandomIndex;
RemoveAutoResolveInterface( FALSE );
gpAR->ubBattleStatus = BATTLE_IN_PROGRESS;
if( !gpAR->ubCivs && !gpAR->ubMercs )
gpAR->ubCivs = 1;
//Make sure the number of enemy portraits is the same as needed.
//The debug keypresses may add or remove more than one at a time.
while ( gpAR->ubElites + gpAR->ubAdmins + gpAR->ubTroops + gpAR->ubTanks + gpAR->ubJeeps + gpAR->ubRobots > gpAR->ubEnemies )
{
switch( PreRandom( 6 ) )
{
case 0: if ( gpAR->ubElites ) { gpAR->ubElites--; break; }
case 1: if ( gpAR->ubAdmins ) { gpAR->ubAdmins--; break; }
case 2: if ( gpAR->ubTroops ) { gpAR->ubTroops--; break; }
case 3: if ( gpAR->ubTanks ) { gpAR->ubTanks--; break; }
case 4: if ( gpAR->ubJeeps ) { gpAR->ubJeeps--; break; }
case 5: if ( gpAR->ubRobots ) { gpAR->ubRobots--; break; }
}
}
while ( gpAR->ubElites + gpAR->ubAdmins + gpAR->ubTroops + gpAR->ubTanks + gpAR->ubJeeps + gpAR->ubRobots < gpAR->ubEnemies )
{
switch( PreRandom( 5 ) )
{
case 0: gpAR->ubElites++; break;
case 1: case 2: gpAR->ubAdmins++; break;
case 3: case 4: gpAR->ubTroops++; break;
}
}
//Do the same for the player mercs.
while( gpAR->iNumMercFaces > gpAR->ubMercs && gpAR->iNumMercFaces > gpAR->iActualMercFaces )
{ //Removing temp mercs
gpAR->iNumMercFaces--;
TacticalRemoveSoldierPointer( gpMercs[gpAR->iNumMercFaces].pSoldier, FALSE );
gpMercs[gpAR->iNumMercFaces].pSoldier = NULL;
}
while( gpAR->iNumMercFaces < gpAR->ubMercs && gpAR->iNumMercFaces >= gpAR->iActualMercFaces )
{
CreateTempPlayerMerc();
}
if( gpAR->uiTimeSlice == 0xffffffff )
{
gpAR->fSound = TRUE;
}
gpAR->uiTimeSlice = 1000;
gpAR->uiTotalElapsedBattleTimeInMilliseconds = 0;
gpAR->uiCurrTime = 0;
gpAR->fPlayerRejectedSurrenderOffer = FALSE;
gpAR->fPendingSurrender = FALSE;
CalculateRowsAndColumns();
CalculateSoldierCells( TRUE);
CreateAutoResolveInterface();
DetermineTeamLeader( TRUE ); //friendly team
DetermineTeamLeader( FALSE ); //enemy team
CalculateAttackValues();
}
void CalculateRowsAndColumns()
{
//now that we have the number on each team, calculate the number of rows and columns to be used on
//the player's sides. NOTE: Militia won't appear on the same row as mercs.
if( !gpAR->ubMercs )
{ //0
gpAR->ubMercCols = gpAR->ubMercRows = 0;
}
else if( gpAR->ubMercs < 5 )
{ //1-4
gpAR->ubMercCols = 1;
gpAR->ubMercRows = gpAR->ubMercs;
}
else if( gpAR->ubMercs < 9 || gpAR->ubMercs == 10 )
{ //5-8, 10
gpAR->ubMercCols = 2;
gpAR->ubMercRows = (gpAR->ubMercs+1)/2;
}
else if( gpAR->ubMercs < 16 )
{ //9, 11-15
gpAR->ubMercCols = 3;
gpAR->ubMercRows = (gpAR->ubMercs+2)/3;
}
else
{ //16-MAX_STRATEGIC_TEAM_SIZE
gpAR->ubMercCols = 4;
gpAR->ubMercRows = (gpAR->ubMercs+3)/4;
}
if( !gpAR->ubCivs )
{
gpAR->ubCivCols = gpAR->ubCivRows = 0;
}
else if( gpAR->ubCivs < 5 )
{ //1-4
gpAR->ubCivCols = 1;
gpAR->ubCivRows = gpAR->ubCivs;
}
else if( gpAR->ubCivs < 9 || gpAR->ubCivs == 10 )
{ //5-8, 10
gpAR->ubCivCols = 2;
gpAR->ubCivRows = (gpAR->ubCivs+1)/2;
}
else if( gpAR->ubCivs < 16 )
{ //9, 11-15
gpAR->ubCivCols = 3;
gpAR->ubCivRows = (gpAR->ubCivs+2)/3;
}
else
{ //16-MAX_ALLOWABLE_MILITIA_PER_SECTOR
gpAR->ubCivCols = 4;
gpAR->ubCivRows = (gpAR->ubCivs+3)/4;
}
if( !gpAR->ubEnemies )
{
gpAR->ubEnemyCols = gpAR->ubEnemyRows = 0;
}
else if( gpAR->ubEnemies < 5 )
{ //1-4
gpAR->ubEnemyCols = 1;
gpAR->ubEnemyRows = gpAR->ubEnemies;
}
else if( gpAR->ubEnemies < 9 || gpAR->ubEnemies == 10 )
{ //5-8, 10
gpAR->ubEnemyCols = 2;
gpAR->ubEnemyRows = (gpAR->ubEnemies+1)/2;
}
else if( gpAR->ubEnemies < 16 )
{ //9, 11-15
gpAR->ubEnemyCols = 3;
gpAR->ubEnemyRows = (gpAR->ubEnemies+2)/3;
}
else
{ //16+
gpAR->ubEnemyCols = 4;
gpAR->ubEnemyRows = (gpAR->ubEnemies+3)/4;
}
//Now, that we have the number of mercs, militia, and enemies, it is possible that there
//may be some conflicts. Our goal is to make the window as small as possible. Bumping up
//the number of columns to 5 or rows to 10 will force one or both axes to go full screen. If we
//have high numbers of both, then we will have to.
//Step one: equalize the number of columns for both the mercs and civs.
if( gpAR->ubMercs && gpAR->ubCivs && gpAR->ubMercCols != gpAR->ubCivCols )
{
if( gpAR->ubMercCols < gpAR->ubCivCols )
{
gpAR->ubMercCols = gpAR->ubCivCols;
gpAR->ubMercRows = (gpAR->ubMercs+gpAR->ubMercCols-1)/gpAR->ubMercCols;
}
else
{
gpAR->ubCivCols = gpAR->ubMercCols;
gpAR->ubCivRows = (gpAR->ubCivs+gpAR->ubCivCols-1)/gpAR->ubCivCols;
}
}
//If we have both mercs and militia, we must make sure that the height to width ratio is never higher than
//a factor of two.
if( gpAR->ubMercs && gpAR->ubCivs && gpAR->ubMercRows + gpAR->ubCivRows > 4 )
{
if( gpAR->ubMercCols * 2 < gpAR->ubMercRows + gpAR->ubCivRows )
{
gpAR->ubMercCols++;
gpAR->ubMercRows = (gpAR->ubMercs+gpAR->ubMercCols-1)/gpAR->ubMercCols;
gpAR->ubCivCols++;
gpAR->ubCivRows = (gpAR->ubCivs+gpAR->ubCivCols-1)/gpAR->ubCivCols;
}
}
if( gpAR->ubMercRows + gpAR->ubCivRows > 9 )
{
if( gpAR->ubMercCols < 5 )
{ //bump it up
gpAR->ubMercCols++;
gpAR->ubMercRows = (gpAR->ubMercs+gpAR->ubMercCols-1)/gpAR->ubMercCols;
}
if( gpAR->ubCivCols < 5 )
{ //match it up with the mercs
gpAR->ubCivCols++; // = gpAR->ubMercCols;
gpAR->ubCivRows = (gpAR->ubCivs+gpAR->ubCivCols-1)/gpAR->ubCivCols;
}
}
if( gpAR->ubMercCols + gpAR->ubEnemyCols == 9 )
gpAR->sWidth = SCREEN_WIDTH;
else
gpAR->sWidth = 146 + 55 * (max( max( gpAR->ubMercCols, gpAR->ubCivCols ), 2 ) + max( gpAR->ubEnemyCols, 2 ));
//gpAR->sCenterStartX = 323 - gpAR->sWidth/2 + max( max( gpAR->ubMercCols, 2), max( gpAR->ubCivCols, 2 ) ) *55;
gpAR->sCenterStartX = iScreenWidthOffset + (323 - gpAR->sWidth/2 + max( max( gpAR->ubMercCols, 2), max( gpAR->ubCivCols, 2 ) ) *55);
//Anywhere from 48*3 to 48*10
//gpAR->sHeight = 48 * max( 3, max( gpAR->ubMercRows + gpAR->ubCivRows, gpAR->ubEnemyRows ) );
gpAR->sHeight = 48 * max( 3, max( min( 10, gpAR->ubMercRows + gpAR->ubCivRows ), min( 10, gpAR->ubEnemyRows ) ) );
//Make it an even multiple of 40 (rounding up).
gpAR->sHeight += 39;
gpAR->sHeight /= 40;
gpAR->sHeight *= 40;
//Here is a extremely bitchy case. The formulae throughout this module work for most cases.
//However, when combining mercs and civs, the column must be the same. However, we retract that
//in cases where there are less mercs than available to fill up *most* of the designated space.
if( gpAR->ubMercs && gpAR->ubCivs )
{
if( gpAR->ubMercRows * gpAR->ubMercCols > gpAR->ubMercs + gpAR->ubMercRows )
gpAR->ubMercCols--;
if( gpAR->ubCivRows * gpAR->ubCivCols > gpAR->ubCivs + gpAR->ubCivRows )
gpAR->ubCivCols--;
}
}
void HandleAutoResolveInput()
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve3");
InputAtom InputEvent;
BOOLEAN fResetAutoResolve = FALSE;
while( DequeueSpecificEvent(&InputEvent, KEY_DOWN|KEY_UP|KEY_REPEAT) )
{
if( InputEvent.usEvent == KEY_DOWN || InputEvent.usEvent == KEY_REPEAT )
{
switch( InputEvent.usParam )
{
case SPACE:
gpAR->fPaused ^= TRUE;
if( gpAR->fPaused )
{
ButtonList[ gpAR->iButton[ PAUSE_BUTTON ] ]->uiFlags |= BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ PLAY_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FAST_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ FINISH_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
}
else
{
ButtonList[ gpAR->iButton[ PAUSE_BUTTON ] ]->uiFlags &= ~BUTTON_CLICKED_ON;
ButtonList[ gpAR->iButton[ PLAY_BUTTON ] ]->uiFlags |= BUTTON_CLICKED_ON;
}
break;
case 'x':
if( _KeyDown( ALT ) )
{
HandleShortCutExitState( );
}
break;
#ifdef JA2BETAVERSION
case 'c':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->fAllowCapture ^= TRUE;
gpAR->fPlayerRejectedSurrenderOffer = FALSE;
gStrategicStatus.uiFlags &= ~STRATEGIC_PLAYER_CAPTURED_FOR_RESCUE;
gpAR->fRenderAutoResolve = TRUE;
}
break;
case F5:
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->fDebugInfo ^= TRUE;
}
break;
case F6:
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->fSound ^= TRUE;
}
break;
case F7:
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->fInstantFinish ^= TRUE;
}
break;
// WDS - Debug "Drassen" battles
case F12:
if( CHEATER_CHEAT_LEVEL() )
{
UINT8 ubMapX, ubMapY;
for ( ubMapX = 1; ubMapX < MAP_WORLD_X - 1; ubMapX++ ) {
for ( ubMapY = 1; ubMapY < MAP_WORLD_Y - 1; ubMapY++ ) {
SetSectorFlag( ubMapX, ubMapY, 0, SF_ALREADY_VISITED ); //hayden
}
}
ExecuteStrategicAIAction( NPC_ACTION_SEND_SOLDIERS_TO_DRASSEN, 13, 4 );
}
break;
case BACKSPACE:
if( CHEATER_CHEAT_LEVEL() )
{
fResetAutoResolve = TRUE;
}
break;
case 'd':
if( CHEATER_CHEAT_LEVEL() )
{
if( _KeyDown( ALT ) )
{
if ( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
{
PlayAutoResolveSample( EXPLOSION_1, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
EliminateAllFriendlies();
}
}
}
break;
case 'z':
if( CHEATER_CHEAT_LEVEL() )
{
if( _KeyDown( ALT ) )
{
if ( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
{
PlayAutoResolveSample( EXPLOSION_1, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
EliminateAllMercs();
}
}
}
break;
case 'o':
if( CHEATER_CHEAT_LEVEL() )
{
if( _KeyDown( ALT ) )
{
if ( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
{
PlayAutoResolveSample( EXPLOSION_1, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
// this is not very accurate, any enemies already dead will be counted as killed twice
gStrategicStatus.usPlayerKills += NumNonPlayerTeamMembersInSector( gpAR->ubSectorX, gpAR->ubSectorY, ENEMY_TEAM );
EliminateAllEnemies( gpAR->ubSectorX, gpAR->ubSectorY );
}
}
}
break;
case '{':
if (_KeyDown( ALT) )
{
}
else if (_KeyDown( CTRL) )
{
}
else if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubMercs = 0;
fResetAutoResolve = TRUE;
}
break;
case '}':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubMercs = MAX_AR_TEAM_SIZE;
fResetAutoResolve = TRUE;
}
break;
case '[':
if( CHEATER_CHEAT_LEVEL() )
{
if( gpAR->ubMercs )
{
gpAR->ubMercs--;
fResetAutoResolve = TRUE;
}
}
break;
case ']':
if( CHEATER_CHEAT_LEVEL() )
{
if( gpAR->ubMercs < MAX_AR_TEAM_SIZE )
{
gpAR->ubMercs++;
fResetAutoResolve = TRUE;
}
}
break;
case ':':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubCivs = 0;
fResetAutoResolve = TRUE;
}
break;
case '"':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubCivs = MAX_AR_TEAM_SIZE;
fResetAutoResolve = TRUE;
}
break;
case ';':
if( CHEATER_CHEAT_LEVEL() )
{
if( gpAR->ubCivs )
{
gpAR->ubCivs--;
fResetAutoResolve = TRUE;
}
}
break;
case 39: // ' quote
if( CHEATER_CHEAT_LEVEL() )
{
if( gpAR->ubCivs < MAX_AR_TEAM_SIZE )
{
gpAR->ubCivs++;
fResetAutoResolve = TRUE;
}
}
break;
case '<':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubEnemies = 1;
fResetAutoResolve = TRUE;
}
break;
case '>':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubEnemies = gGameExternalOptions.ubGameMaximumNumberOfEnemies;
fResetAutoResolve = TRUE;
}
break;
case ',':
if( CHEATER_CHEAT_LEVEL() )
{
if( gpAR->ubEnemies > 1 )
{
gpAR->ubEnemies--;
fResetAutoResolve = TRUE;
}
}
break;
case '.':
if( CHEATER_CHEAT_LEVEL() )
{
if( gpAR->ubEnemies < gGameExternalOptions.ubGameMaximumNumberOfEnemies )
{
gpAR->ubEnemies++;
fResetAutoResolve = TRUE;
}
}
break;
case '/':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubMercs = 1;
gpAR->ubCivs = 0;
gpAR->ubEnemies = 1;
fResetAutoResolve = TRUE;
}
break;
case '?':
if( CHEATER_CHEAT_LEVEL() )
{
gpAR->ubMercs = OUR_TEAM_SIZE_NO_VEHICLE;
gpAR->ubCivs = MAX_AR_TEAM_SIZE;
gpAR->ubEnemies = gGameExternalOptions.ubGameMaximumNumberOfEnemies;
fResetAutoResolve = TRUE;
}
break;
#endif
}
}
}
if( fResetAutoResolve )
{
ResetAutoResolveInterface();
}
}
void RenderSoldierCellHealth( SOLDIERCELL *pCell )
{
INT32 cnt, cntStart;
INT32 xp, yp;
STR16 pStr;
CHAR16 str[20];
UINT8 *pDestBuf, *pSrcBuf;
UINT32 uiSrcPitchBYTES, uiDestPitchBYTES;
UINT16 usColor;
SetFont( SMALLCOMPFONT );
//Restore the background before drawing text.
pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES );
pSrcBuf = LockVideoSurface( gpAR->iInterfaceBuffer, &uiSrcPitchBYTES );
xp = pCell->xp + 2;
yp = pCell->yp + 32;
Blt16BPPTo16BPP( (UINT16*)pDestBuf, uiDestPitchBYTES,
(UINT16*)pSrcBuf, uiSrcPitchBYTES, xp, yp, xp-gpAR->Rect.iLeft, yp-gpAR->Rect.iTop, 46, 10 );
UnLockVideoSurface( gpAR->iInterfaceBuffer );
UnLockVideoSurface( FRAME_BUFFER );
if( pCell->pSoldier->stats.bLife )
{
if( pCell->pSoldier->stats.bLife == pCell->pSoldier->stats.bLifeMax )
{
cntStart = 4;
}
else
{
cntStart = 0;
}
for( cnt = cntStart; cnt < 6; cnt ++ )
{
if( pCell->pSoldier->stats.bLife < bHealthStrRanges[ cnt ] )
{
break;
}
}
switch( cnt )
{
case 0: //DYING
case 1: //CRITICAL
usColor = FONT_RED;
break;
case 2: //WOUNDED
case 3: //POOR
usColor = FONT_YELLOW;
break;
default: //REST
usColor = FONT_GRAY1;
break;
}
if( cnt > 3 && pCell->pSoldier->stats.bLife != pCell->pSoldier->stats.bLifeMax )
{ //Merc has taken damage, even though his life if good.
usColor = FONT_YELLOW;
}
if( pCell->pSoldier->stats.bLife == pCell->pSoldier->stats.bLifeMax )
usColor = FONT_GRAY1;
pStr = zHealthStr[ cnt ];
}
else
{
wcscpy( str, pCell->pSoldier->name );
pStr = _wcsupr( str );
usColor = FONT_BLACK;
}
//Draw the retreating text, if applicable
if( pCell->uiFlags & CELL_RETREATED && gpAR->ubBattleStatus != BATTLE_VICTORY )
{
usColor = FONT_LTGREEN;
swprintf( str, gpStrategicString[ STR_AR_MERC_RETREATED ] );
pStr = str;
}
else if( pCell->uiFlags & CELL_RETREATING && gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
{
if( pCell->pSoldier->stats.bLife >= OKLIFE )
{ //Retreating is shared with the status string. Alternate between the
//two every 450 milliseconds
if( GetJA2Clock() % 900 < 450 )
{ //override the health string with the retreating string.
usColor = FONT_LTRED;
swprintf( str, gpStrategicString[ STR_AR_MERC_RETREATING ] );
pStr = str;
}
}
}
else if( pCell->uiFlags & CELL_SHOWRETREATTEXT && gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
{
if( pCell->pSoldier->stats.bLife >= OKLIFE )
{
SetFontForeground( FONT_YELLOW );
swprintf( str, gpStrategicString[ STR_AR_MERC_RETREAT ] );
xp = pCell->xp + 25 - StringPixLength( pStr, SMALLCOMPFONT ) / 2;
yp = pCell->yp + 12;
mprintf( xp, yp, str );
}
}
SetFontForeground( (UINT8)usColor );
xp = pCell->xp + 25 - StringPixLength( pStr, SMALLCOMPFONT ) / 2;
yp = pCell->yp + 33;
mprintf( xp, yp, pStr );
}
UINT8 GetUnusedMercProfileID()
{
UINT8 ubRandom=0;
INT32 i;
BOOLEAN fUnique = FALSE;
while( !fUnique )
{
ubRandom = (UINT8)PreRandom(CODE_MAXIMUM_NUMBER_OF_PLAYER_MERCS);
for( i = 0; i < 19; i++ )
{
fUnique = TRUE;
if( Menptr[ i ].ubProfile == ubRandom )
{
fUnique = FALSE;
break;
}
}
}
return ubRandom;
}
void CreateTempPlayerMerc()
{
SOLDIERCREATE_STRUCT MercCreateStruct;
static INT32 iSoldierCount=0;
UINT16 ubID;
//Init the merc create structure with basic information
MercCreateStruct.bTeam = SOLDIER_CREATE_AUTO_TEAM;
MercCreateStruct.ubProfile = GetUnusedMercProfileID();
MercCreateStruct.sSectorX = gpAR->ubSectorX;
MercCreateStruct.sSectorY = gpAR->ubSectorY;
MercCreateStruct.bSectorZ = 0;
MercCreateStruct.fPlayerMerc = TRUE;
MercCreateStruct.fCopyProfileItemsOver = TRUE;
//Create the player soldier
gpMercs[ gpAR->iNumMercFaces ].pSoldier = TacticalCreateSoldier( &MercCreateStruct, &ubID );
if( gpMercs[ gpAR->iNumMercFaces ].pSoldier )
{
gpAR->iNumMercFaces++;
}
}
void DetermineTeamLeader( BOOLEAN fFriendlyTeam )
{
INT32 i;
SOLDIERCELL *pBestLeaderCell = NULL;
//For each team (civs and players count as same team), find the merc with the best
//leadership ability.
if( fFriendlyTeam )
{
gpAR->ubPlayerLeadership = 0;
for( i = 0; i < gpAR->ubMercs; i++ )
{
if( gpMercs[ i ].pSoldier->stats.bLeadership > gpAR->ubPlayerLeadership )
{
gpAR->ubPlayerLeadership = gpMercs[ i ].pSoldier->stats.bLeadership;
pBestLeaderCell = &gpMercs[ i ];
}
}
for( i = 0; i < gpAR->ubCivs; i++ )
{
if( gpCivs[ i ].pSoldier->stats.bLeadership > gpAR->ubPlayerLeadership )
{
gpAR->ubPlayerLeadership = gpCivs[ i ].pSoldier->stats.bLeadership;
pBestLeaderCell = &gpCivs[ i ];
}
}
if( pBestLeaderCell )
{
//Assign the best leader the honour of team leader.
pBestLeaderCell->uiFlags |= CELL_TEAMLEADER;
}
return;
}
//ENEMY TEAM
gpAR->ubEnemyLeadership = 0;
for( i = 0; i < gpAR->ubEnemies; i++ )
{
if( gpEnemies[ i ].pSoldier->stats.bLeadership > gpAR->ubEnemyLeadership )
{
gpAR->ubEnemyLeadership = gpEnemies[ i ].pSoldier->stats.bLeadership;
pBestLeaderCell = &gpEnemies[ i ];
}
}
if( pBestLeaderCell )
{
//Assign the best enemy leader the honour of team leader
pBestLeaderCell->uiFlags |= CELL_TEAMLEADER;
}
}
void ResetNextAttackCounter( SOLDIERCELL *pCell )
{
pCell->usNextAttack = min( 1000 - pCell->usAttack, 800 );
pCell->usNextAttack = (UINT16)(1000 + pCell->usNextAttack * 5 + PreRandom( 2000 - pCell->usAttack ) );
if( pCell->uiFlags & CELL_CREATURE )
{
pCell->usNextAttack = pCell->usNextAttack * 8 / 10;
}
}
FLOAT CalcClassBonusOrPenalty( SOLDIERTYPE *pSoldier )
{
switch( pSoldier->ubSoldierClass )
{
case SOLDIER_CLASS_ELITE:
case SOLDIER_CLASS_ELITE_MILITIA:
return 1.3f;
case SOLDIER_CLASS_ARMY:
case SOLDIER_CLASS_REG_MILITIA:
return 1.0f;
case SOLDIER_CLASS_ADMINISTRATOR:
case SOLDIER_CLASS_GREEN_MILITIA:
case SOLDIER_CLASS_BANDIT:
return 0.7f;
}
return 1.0f;
}
void CalculateAttackValues()
{
INT32 i;
SOLDIERCELL *pCell;
SOLDIERTYPE *pSoldier;
UINT16 usBonus;
UINT16 usBestAttack = 0xffff;
UINT16 usBreathStrengthPercentage;
//INT16 sOutnumberBonus = 0;
//PLAYER TEAM
gpAR->usPlayerAttack = 0;
gpAR->usPlayerDefence = 0;
UINT8 uiEffectiveLevelExp = 0; // added by SANDRO
//if( gpAR->ubEnemies )
//{
// //bonus equals 20 if good guys outnumber bad guys 2 to 1.
// sMaxBonus = 20;
// sOutnumberBonus = (INT16)(gpAR->ubMercs + gpAR->ubCivs) * sMaxBonus / gpAR->ubEnemies - sMaxBonus;
// sOutnumberBonus = (INT16)min( sOutnumberBonus, max( sMaxBonus, 0 ) );
//}
for( i = 0; i < gpAR->ubMercs; i++ )
{
pCell = &gpMercs[ i ];
pSoldier = pCell->pSoldier;
if( !pSoldier->stats.bLife )
continue;
pCell->usAttack = pSoldier->stats.bStrength +
pSoldier->stats.bDexterity +
pSoldier->stats.bWisdom +
pSoldier->stats.bMarksmanship +
pSoldier->aiData.bMorale;
//Give player controlled mercs a significant bonus to compensate for lack of control
//as the player would typically do much better in tactical.
if( pCell->usAttack < 1000 )
{ //A player with 500 attack will be augmented to 625
//A player with 600 attack will be augmented to 700
pCell->usAttack = (UINT16)(pCell->usAttack + (1000 - pCell->usAttack) / 4);
}
usBreathStrengthPercentage = 100 - ( 100 - pCell->pSoldier->bBreathMax ) / 3;
pCell->usAttack = pCell->usAttack * usBreathStrengthPercentage / 100;
pCell->usDefence = pSoldier->stats.bAgility +
pSoldier->stats.bWisdom +
pSoldier->bBreathMax +
pSoldier->stats.bMedical +
pSoldier->aiData.bMorale;
//100 team leadership adds a bonus of 10%,
usBonus = 100 + gpAR->ubPlayerLeadership/10;// + sOutnumberBonus;
//bExpLevel adds a bonus of 7% per level after 2, level 1 soldiers get a 7% decrease
//usBonus += 7 * (pSoldier->stats.bExpLevel-2);
// SANDRO - STOMP traits - Squadleaders bonus to effective level
uiEffectiveLevelExp = pSoldier->stats.bExpLevel;
if ( gGameOptions.fNewTraitSystem )
uiEffectiveLevelExp = min(10,(uiEffectiveLevelExp + (gSkillTraitValues.ubSLEffectiveLevelInRadius * GetSquadleadersCountInVicinity( pSoldier, TRUE, TRUE ))));
usBonus += EXP_BONUS * (uiEffectiveLevelExp-5);
usBonus += gpAR->ubPlayerDefenceAdvantage;
pCell->usAttack = pCell->usAttack * usBonus / 100;
pCell->usDefence = pCell->usDefence * usBonus / 100;
if( pCell->uiFlags & CELL_EPC )
{ //strengthen the defense (seeing the mercs will keep them safe).
pCell->usAttack = 0;
pCell->usDefence = 1000;
}
pCell->usAttack = min( pCell->usAttack, 1000 );
pCell->usDefence = min( pCell->usDefence, 1000 );
gpAR->usPlayerAttack += pCell->usAttack;
gpAR->usPlayerDefence += pCell->usDefence;
ResetNextAttackCounter( pCell );
if( i > 8 )
{ //Too many mercs, delay attack entry of extra mercs.
pCell->usNextAttack += (UINT16)( ( i - 8 ) * 2000 );
}
if( pCell->usNextAttack < usBestAttack )
usBestAttack = pCell->usNextAttack;
}
//CIVS
for( i = 0; i < gpAR->ubCivs; i++ )
{
pCell = &gpCivs[ i ];
pSoldier = pCell->pSoldier;
pCell->usAttack = pSoldier->stats.bStrength +
pSoldier->stats.bDexterity +
pSoldier->stats.bWisdom +
pSoldier->stats.bMarksmanship +
pSoldier->aiData.bMorale;
pCell->usAttack = pCell->usAttack * pSoldier->bBreath / 100;
pCell->usDefence = pSoldier->stats.bAgility +
pSoldier->stats.bWisdom +
pSoldier->bBreathMax +
pSoldier->stats.bMedical +
pSoldier->aiData.bMorale;
//100 team leadership adds a bonus of 10%
usBonus = 100 + gpAR->ubPlayerLeadership/10;// + sOutnumberBonus;
//bExpLevel adds a bonus of 7% per level after 2, level 1 soldiers get a 7% decrease
//usBonus += 7 * (pSoldier->stats.bExpLevel-2);
// SANDRO - STOMP traits - Squadleaders bonus to effective level
uiEffectiveLevelExp = pSoldier->stats.bExpLevel;
if ( gGameOptions.fNewTraitSystem )
uiEffectiveLevelExp = min(10,(uiEffectiveLevelExp + (gSkillTraitValues.ubSLEffectiveLevelInRadius * GetSquadleadersCountInVicinity( pSoldier, TRUE, TRUE ))));
usBonus += EXP_BONUS * (uiEffectiveLevelExp-5);
usBonus += gpAR->ubPlayerDefenceAdvantage;
pCell->usAttack = pCell->usAttack * usBonus / 100;
pCell->usDefence = pCell->usDefence * usBonus / 100;
pCell->usAttack = (UINT16)( pCell->usAttack * CalcClassBonusOrPenalty( pCell->pSoldier ) );
pCell->usDefence =(UINT16)( pCell->usDefence * CalcClassBonusOrPenalty( pCell->pSoldier ) );
pCell->usAttack = min( pCell->usAttack, 1000 );
pCell->usDefence = min( pCell->usDefence, 1000 );
gpAR->usPlayerAttack += pCell->usAttack;
gpAR->usPlayerDefence += pCell->usDefence;
ResetNextAttackCounter( pCell );
if( i > 6 )
{
//Too many militia, delay attack entry of extra mercs.
pCell->usNextAttack += (UINT32)(( i - 4 ) * 2000 );
}
UINT8 militiainsector = NumNonPlayerTeamMembersInSector( gpAR->ubSectorX, gpAR->ubSectorY, MILITIA_TEAM );
if ( i >= militiainsector )
{
//Extra delay if soldier's from reinforcement
pCell->usNextAttack += REINFORCMENT_ATTACK_DELAY_PER_SOLDIER_IN_SECTOR * militiainsector;
}
if( pCell->usNextAttack < usBestAttack )
usBestAttack = pCell->usNextAttack;
}
//ENEMIES
gpAR->usEnemyAttack = 0;
gpAR->usEnemyDefence = 0;
//if( gpAR->ubMercs + gpAR->ubCivs )
//{
// //bonus equals 20 if good guys outnumber bad guys 2 to 1.
// sMaxBonus = 20;
// sOutnumberBonus = (INT16)gpAR->ubEnemies * sMaxBonus / (gpAR->ubMercs + gpAR->ubCivs) - sMaxBonus;
// sOutnumberBonus = (INT16)min( sOutnumberBonus, max( sMaxBonus, 0 ) );
//}
for( i = 0; i < gpAR->ubEnemies; ++i )
{
pCell = &gpEnemies[ i ];
pSoldier = pCell->pSoldier;
pCell->usAttack = pSoldier->stats.bStrength +
pSoldier->stats.bDexterity +
pSoldier->stats.bWisdom +
pSoldier->stats.bMarksmanship +
pSoldier->aiData.bMorale;
pCell->usAttack = pCell->usAttack * pSoldier->bBreath / 100;
pCell->usDefence = pSoldier->stats.bAgility +
pSoldier->stats.bWisdom +
pSoldier->bBreathMax +
pSoldier->stats.bMedical +
pSoldier->aiData.bMorale;
//100 team leadership adds a bonus of 10%
usBonus = 100 + gpAR->ubPlayerLeadership/10;// + sOutnumberBonus;
//bExpLevel adds a bonus of 7% per level after 2, level 1 soldiers get a 7% decrease
//usBonus += 7 * (pSoldier->stats.bExpLevel-2);
// SANDRO - STOMP traits - Squadleaders bonus to effective level
uiEffectiveLevelExp = pSoldier->stats.bExpLevel;
if ( gGameOptions.fNewTraitSystem )
uiEffectiveLevelExp = min(10,(uiEffectiveLevelExp + (gSkillTraitValues.ubSLEffectiveLevelInRadius * GetSquadleadersCountInVicinity( pSoldier, TRUE, TRUE ))));
usBonus += EXP_BONUS * (uiEffectiveLevelExp-5);
usBonus += gpAR->ubEnemyDefenceAdvantage;
pCell->usAttack = pCell->usAttack * usBonus / 100;
pCell->usDefence = pCell->usDefence * usBonus / 100;
pCell->usAttack = (UINT16)( pCell->usAttack * CalcClassBonusOrPenalty( pCell->pSoldier ) );
pCell->usDefence = (UINT16)( pCell->usDefence * CalcClassBonusOrPenalty( pCell->pSoldier ) );
pCell->usAttack = min( pCell->usAttack, 1000 );
pCell->usDefence = min( pCell->usDefence, 1000 );
gpAR->usEnemyAttack += pCell->usAttack;
gpAR->usEnemyDefence += pCell->usDefence;
ResetNextAttackCounter( pCell );
if( i > 4 && !(pCell->uiFlags & CELL_CREATURE) )
{
//Too many enemies, delay attack entry of extra mercs.
pCell->usNextAttack += (UINT32)( ( i - 4 ) * 1000 );
}
if ( i >= NumNonPlayerTeamMembersInSector( gpAR->ubSectorX, gpAR->ubSectorY, ENEMY_TEAM ) && !(pCell->uiFlags & CELL_CREATURE) )
{
//Extra delay if it's a reinforcement
pCell->usNextAttack += REINFORCMENT_ATTACK_DELAY_PER_SOLDIER_IN_SECTOR * NumNonPlayerTeamMembersInSector( gpAR->ubSectorX, gpAR->ubSectorY, ENEMY_TEAM );
}
if( pCell->usNextAttack < usBestAttack )
usBestAttack = pCell->usNextAttack;
}
//Now, because we are starting a new battle, we want to get the ball rolling a bit earlier. So,
//we will take the usBestAttack value and subtract 60% of it from everybody's next attack.
usBestAttack = usBestAttack * 60 / 100;
for( i = 0; i < gpAR->ubMercs; i++ )
gpMercs[ i ].usNextAttack -= usBestAttack;
for( i = 0; i < gpAR->ubCivs; i++ )
gpCivs[ i ].usNextAttack -= usBestAttack;
for( i = 0; i < gpAR->ubEnemies; i++ )
gpEnemies[ i ].usNextAttack -= usBestAttack;
}
void DrawDebugText( SOLDIERCELL *pCell )
{
INT32 xp, yp;
if( !gpAR->fDebugInfo )
return;
SetFont( SMALLCOMPFONT );
SetFontForeground( FONT_WHITE );
xp = pCell->xp + 4;
yp = pCell->yp + 4;
if( pCell->uiFlags & CELL_TEAMLEADER )
{
//debug str
mprintf( xp, yp, L"LEADER" );
yp += 9;
}
mprintf( xp, yp, L"AT: %d", pCell->usAttack );
yp += 9;
mprintf( xp, yp, L"DF: %d", pCell->usDefence );
yp += 9;
xp = pCell->xp;
yp = pCell->yp - 4;
SetFont( LARGEFONT1 );
SetFontShadow( FONT_NEARBLACK );
if( pCell->uiFlags & CELL_FIREDATTARGET )
{
SetFontForeground( FONT_YELLOW );
mprintf( xp, yp, L"FIRE" );
pCell->uiFlags &= ~CELL_FIREDATTARGET;
yp += 13;
}
if( pCell->uiFlags & CELL_DODGEDATTACK )
{
SetFontForeground( FONT_BLUE );
mprintf( xp, yp, L"MISS" );
pCell->uiFlags &= ~CELL_DODGEDATTACK;
yp += 13;
}
if( pCell->uiFlags & CELL_HITBYATTACKER )
{
SetFontForeground( FONT_RED );
mprintf( xp, yp, L"HIT" );
pCell->uiFlags &= ~CELL_HITBYATTACKER;
yp += 13;
}
}
SOLDIERCELL* ChooseTarget( SOLDIERCELL *pAttacker )
{
INT32 iAvailableTargets;
INT32 index;
INT32 iRandom = -1;
SOLDIERCELL *pTarget = NULL;
UINT32 usSavedDefence;
//Determine what team we are attacking
if( pAttacker->uiFlags & (CELL_ENEMY | CELL_CREATURE) )
{
//enemy team attacking a player
iAvailableTargets = gpAR->ubMercs + gpAR->ubCivs;
index = 0;
usSavedDefence = gpAR->usPlayerDefence;
while( iAvailableTargets )
{
pTarget = ( index < gpAR->ubMercs ) ? &gpMercs[ index ] : &gpCivs[ index - gpAR->ubMercs ];
if( !pTarget->pSoldier->stats.bLife || pTarget->uiFlags & CELL_RETREATED )
{
index++;
iAvailableTargets--;
continue;
}
iRandom = PreRandom( gpAR->usPlayerDefence );
gpAR->usPlayerDefence -= pTarget->usDefence;
if( iRandom < pTarget->usDefence )
{
gpAR->usPlayerDefence = usSavedDefence;
return pTarget;
}
index++;
iAvailableTargets--;
}
if( !IsBattleOver() )
{
AssertMsg( 0, String("***Please send PRIOR save and screenshot of this message*** iAvailableTargets %d, index %d, iRandom %d, defence %d. ",
iAvailableTargets, index, iRandom, gpAR->usPlayerDefence) );
}
}
else
{
//player team attacking an enemy
iAvailableTargets = gpAR->ubEnemies;
index = 0;
usSavedDefence = gpAR->usEnemyDefence;
while( iAvailableTargets )
{
pTarget = &gpEnemies[ index ];
if( !pTarget->pSoldier->stats.bLife )
{
index++;
iAvailableTargets--;
continue;
}
iRandom = PreRandom( gpAR->usEnemyDefence );
gpAR->usEnemyDefence -= pTarget->usDefence;
if( iRandom < pTarget->usDefence )
{
gpAR->usEnemyDefence = usSavedDefence;
return pTarget;
}
index++;
iAvailableTargets--;
}
}
AssertMsg( 0, "Error in ChooseTarget logic for choosing enemy target." );
return NULL;
}
BOOLEAN FireAShot( SOLDIERCELL *pAttacker )
{
OBJECTTYPE *pItem;
SOLDIERTYPE *pSoldier;
pSoldier = pAttacker->pSoldier;
if( pAttacker->uiFlags & CELL_MALECREATURE )
{
PlayAutoResolveSample( ACR_SPIT, RATE_11025, 50, 1, MIDDLEPAN );
pAttacker->bWeaponSlot = SECONDHANDPOS;
return TRUE;
}
UINT8 invsize = pSoldier->inv.size();
for( UINT8 i = 0; i < invsize; ++i )
{
pItem = &pSoldier->inv[ i ];
if( Item[ pItem->usItem ].usItemClass == IC_GUN )
{
pAttacker->bWeaponSlot = (INT8)i;
if( gpAR->fUnlimitedAmmo )
{
PlayAutoResolveSample( Weapon[ pItem->usItem ].sSound, RATE_11025, 50, 1, MIDDLEPAN );
return TRUE;
}
if( !(*pItem)[0]->data.gun.ubGunShotsLeft )
{
AutoReload( pSoldier );
if ( (*pItem)[0]->data.gun.ubGunShotsLeft && Weapon[ pItem->usItem ].sLocknLoadSound )
{
PlayAutoResolveSample( Weapon[ pItem->usItem ].sLocknLoadSound, RATE_11025, 50, 1, MIDDLEPAN );
}
}
if( (*pItem)[0]->data.gun.ubGunShotsLeft )
{
PlayAutoResolveSample( Weapon[ pItem->usItem ].sSound, RATE_11025, 50, 1, MIDDLEPAN );
if( pAttacker->uiFlags & CELL_MERC )
{
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usShotsFired++;
// MARKSMANSHIP GAIN: Attacker fires a shot
StatChange( pAttacker->pSoldier, MARKAMT, 3, FALSE );
}
(*pItem)[0]->data.gun.ubGunShotsLeft--;
return TRUE;
}
}
}
pAttacker->bWeaponSlot = -1;
return FALSE;
}
BOOLEAN FireTankCannon( SOLDIERCELL *pAttacker )
{
OBJECTTYPE *pItem;
SOLDIERTYPE *pSoldier;
pSoldier = pAttacker->pSoldier;
// we need to be a tank to do this...
if ( !pSoldier || (pAttacker->uiFlags & CELL_TANK) == 0 || pSoldier->ubSoldierClass != SOLDIER_CLASS_TANK || COMBAT_JEEP( pSoldier ) )
return FALSE;
// a tank has several guns, don't fire the main gun all the time
//if ( Chance(33) )
//return FALSE;
UINT8 invsize = pSoldier->inv.size();
for ( UINT8 i = 0; i < invsize; ++i )
{
pItem = &pSoldier->inv[i];
if (ItemIsCannon(pItem->usItem))
{
PlayAutoResolveSample( Weapon[pItem->usItem].sSound, RATE_11025, 50, 1, MIDDLEPAN );
if ( pAttacker->uiFlags & CELL_MERC )
{
gMercProfiles[pAttacker->pSoldier->ubProfile].records.usShotsFired++;
// MARKSMANSHIP GAIN: Attacker fires a shot
StatChange( pAttacker->pSoldier, MARKAMT, 3, FALSE );
}
pAttacker->bWeaponSlot = (INT8)i;
return TRUE;
}
}
pAttacker->bWeaponSlot = -1;
return FALSE;
}
BOOLEAN FireAntiTankWeapon( SOLDIERCELL *pAttacker )
{
OBJECTTYPE *pItem;
SOLDIERTYPE *pSoldier;
pSoldier = pAttacker->pSoldier;
UINT8 invsize = pSoldier->inv.size( );
for ( UINT8 i = 0; i < invsize; ++i )
{
pItem = &pSoldier->inv[i];
if ( Item[pItem->usItem].usItemClass == IC_LAUNCHER || ItemIsCannon(pItem->usItem))
{
pAttacker->bWeaponSlot = (INT8)i;
if ( gpAR->fUnlimitedAmmo )
{
PlayAutoResolveSample( Weapon[pItem->usItem].sSound, RATE_11025, 50, 1, MIDDLEPAN );
return TRUE;
}
if ( FindLaunchable( pSoldier, pSoldier->inv[pAttacker->bWeaponSlot].usItem ) == NO_SLOT )
continue;
DeductAmmo( pSoldier, pAttacker->bWeaponSlot );
if ( !(*pItem)[0]->data.gun.ubGunShotsLeft )
{
INT8 ammoslot = FindAmmoToReload( pSoldier, pAttacker->bWeaponSlot, NO_SLOT );
BOOLEAN fRet = ReloadGun( pSoldier, pItem, &(pSoldier->inv[ammoslot]) );
if ( fRet && ( *pItem )[0]->data.gun.ubGunShotsLeft && Weapon[pItem->usItem].sLocknLoadSound )
{
PlayAutoResolveSample( Weapon[pItem->usItem].sLocknLoadSound, RATE_11025, 50, 1, MIDDLEPAN );
}
}
PlayAutoResolveSample( Weapon[pItem->usItem].sSound, RATE_11025, 50, 1, MIDDLEPAN );
if ( pAttacker->uiFlags & CELL_MERC )
{
gMercProfiles[pAttacker->pSoldier->ubProfile].records.usShotsFired++;
// MARKSMANSHIP GAIN: Attacker fires a shot
StatChange( pAttacker->pSoldier, MARKAMT, 3, FALSE );
}
return TRUE;
}
}
pAttacker->bWeaponSlot = -1;
return FALSE;
}
BOOLEAN AttackerHasKnife( SOLDIERCELL *pAttacker )
{
UINT8 invsize = pAttacker->pSoldier->inv.size();
for( UINT8 i = 0; i < invsize; ++i )
{
if( Item[ pAttacker->pSoldier->inv[ i ].usItem ].usItemClass == IC_BLADE )
{
pAttacker->bWeaponSlot = (INT8)i;
return TRUE;
}
}
pAttacker->bWeaponSlot = -1;
return FALSE;
}
BOOLEAN TargetHasLoadedGun( SOLDIERTYPE *pSoldier )
{
OBJECTTYPE *pItem;
UINT8 invsize = pSoldier->inv.size();
for( UINT8 i = 0; i < invsize; ++i )
{
pItem = &pSoldier->inv[ i ];
if( Item[ pItem->usItem ].usItemClass == IC_GUN )
{
if( gpAR->fUnlimitedAmmo )
{
return TRUE;
}
if( (*pItem)[0]->data.gun.ubGunShotsLeft )
{
return TRUE;
}
}
}
return FALSE;
}
void AttackTarget( SOLDIERCELL *pAttacker, SOLDIERCELL *pTarget )
{
INT16 sAttack;
INT16 sDefence;
UINT8 ubImpact;
UINT8 ubLocation;
INT16 sAccuracy;
INT32 iRandom;
INT32 iImpact;
INT32 iNewLife;
BOOLEAN fMelee = FALSE;
BOOLEAN fKnife = FALSE;
BOOLEAN fClaw = FALSE;
BOOLEAN fCannon = FALSE;
BOOLEAN fAntiTank = FALSE;
INT8 bAttackIndex = -1;
pAttacker->uiFlags |= CELL_FIREDATTARGET | CELL_DIRTY;
if( pAttacker->usAttack < 950 )
sAttack = (INT16)(pAttacker->usAttack + PreRandom(max(0, (INT16)(gGameExternalOptions.iAutoResolveLuckFactor*1000.0) - (INT16)pAttacker->usAttack)));
else
sAttack = 950 + PreRandom(50);
if( pTarget->uiFlags & CELL_RETREATING && !(pAttacker->uiFlags & CELL_FEMALECREATURE) )
{
//Attacking a retreating merc is harder. Modify the attack value to 70% of it's value.
//This allows retreaters to have a better chance of escaping.
sAttack = sAttack * 7 / 10;
}
if( pTarget->usDefence < 950 )
sDefence = (INT16)(pTarget->usDefence + PreRandom(max(0, (INT16)(gGameExternalOptions.iAutoResolveLuckFactor*1000.0) - (INT16)pTarget->usDefence)));
else
sDefence = 950 + PreRandom(50);
///////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - Increase Militia Strength in autoresolve battles
// because the attack and defense is limited to max 1000, rather than only increasing the attack of militia,
// decrease the defense of target - so +100% bonus means +50% attack of attacker and -50% defense of target
//if militia is attacking increase attack by half and decrease the defense of enemy
if (pAttacker->pSoldier->ubSoldierClass == SOLDIER_CLASS_GREEN_MILITIA && gGameExternalOptions.sGreenMilitiaAutoresolveStrength != 0)
{
sAttack += (sAttack * gGameExternalOptions.sGreenMilitiaAutoresolveStrength /200);
sDefence -= (sDefence * gGameExternalOptions.sGreenMilitiaAutoresolveStrength /200);
}
else if (pAttacker->pSoldier->ubSoldierClass == SOLDIER_CLASS_REG_MILITIA && gGameExternalOptions.sRegularMilitiaAutoresolveStrength != 0)
{
sAttack += (sAttack * gGameExternalOptions.sRegularMilitiaAutoresolveStrength /200);
sDefence -= (sDefence * gGameExternalOptions.sRegularMilitiaAutoresolveStrength /200);
}
else if (pAttacker->pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE_MILITIA && gGameExternalOptions.sVeteranMilitiaAutoresolveStrength != 0)
{
sAttack += (sAttack * gGameExternalOptions.sVeteranMilitiaAutoresolveStrength /200);
sDefence -= (sDefence * gGameExternalOptions.sVeteranMilitiaAutoresolveStrength /200);
}
else if (pAttacker->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveOffenseBonus != 0)
{
sAttack += (sAttack * gGameExternalOptions.sMercsAutoresolveOffenseBonus /200);
sDefence -= (sDefence * gGameExternalOptions.sMercsAutoresolveOffenseBonus /200);
}
//if enemy is attacking decrease his attack by half and increase the defense of militia by half
if (pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_GREEN_MILITIA && gGameExternalOptions.sGreenMilitiaAutoresolveStrength != 0)
{
sDefence += (sDefence * gGameExternalOptions.sGreenMilitiaAutoresolveStrength /200);
sAttack -= (sAttack * gGameExternalOptions.sGreenMilitiaAutoresolveStrength /200);
}
else if (pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_REG_MILITIA && gGameExternalOptions.sRegularMilitiaAutoresolveStrength != 0)
{
sDefence += (sDefence * gGameExternalOptions.sRegularMilitiaAutoresolveStrength /200);
sAttack -= (sAttack * gGameExternalOptions.sRegularMilitiaAutoresolveStrength /200);
}
else if (pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE_MILITIA && gGameExternalOptions.sVeteranMilitiaAutoresolveStrength != 0)
{
sDefence += (sDefence * gGameExternalOptions.sVeteranMilitiaAutoresolveStrength /200);
sAttack -= (sAttack * gGameExternalOptions.sVeteranMilitiaAutoresolveStrength /200);
}
else if (pTarget->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveDeffenseBonus != 0)
{
sDefence += (sDefence * gGameExternalOptions.sMercsAutoresolveDeffenseBonus /200);
sAttack -= (sAttack * gGameExternalOptions.sMercsAutoresolveDeffenseBonus /200);
}
// check fortifications bonus
if (pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_GREEN_MILITIA
|| pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_REG_MILITIA
|| pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE_MILITIA
|| (pTarget->uiFlags & CELL_MERC))
{
sDefence += (sDefence * RebelCommand::GetFortificationsBonus(GetAutoResolveSectorID()) / 200);
sAttack -= (sAttack * RebelCommand::GetFortificationsBonus(GetAutoResolveSectorID()) / 200);
}
// repair values
sAttack = max(0, min(1000, sAttack ));
sDefence = max(0, min(1000, sDefence ));
///////////////////////////////////////////////////////////////////////////////////////////
if( pAttacker->uiFlags & (CELL_FEMALECREATURE | CELL_BLOODCAT | CELL_ZOMBIE) )
{
pAttacker->bWeaponSlot = HANDPOS;
fMelee = TRUE;
if ( pAttacker->uiFlags & ( CELL_FEMALECREATURE | CELL_BLOODCAT ) )
fClaw = TRUE;
}
// Flugente: if we are a tank, we might fire the main cannon...
else if ( FireTankCannon( pAttacker ) )
{
fCannon = TRUE;
// cannons have a huge splash zone, so they are much more likely to hit
sAttack = (INT16)(sAttack * 1.5);
}
// if our target is a tank, we use heavy weapons if we have any
else if ( (ARMED_VEHICLE( pTarget->pSoldier ) || ENEMYROBOT( pTarget->pSoldier )) && FireAntiTankWeapon( pAttacker ) )
{
fAntiTank = TRUE;
// hitting a tank with an rpg isn't easy
sAttack = (INT16)(sAttack * 0.8);
}
else if( !FireAShot( pAttacker ) )
{
//Maybe look for a weapon, such as a knife or grenade?
fMelee = TRUE;
fKnife = AttackerHasKnife( pAttacker );
if( TargetHasLoadedGun( pTarget->pSoldier ) )
{
//Penalty to attack with melee weapons against target with loaded gun.
if( !(pAttacker->uiFlags & CELL_CREATURE ) )
{
//except for creatures
if( fKnife )
sAttack = sAttack * 6 / 10;
else
sAttack = sAttack * 4 / 10;
}
}
}
//Set up a random delay for the hit or miss.
if( !fMelee )
{
if( !pTarget->usNextHit[0] )
{
bAttackIndex = 0;
}
else if( !pTarget->usNextHit[1] )
{
bAttackIndex = 1;
}
else if( !pTarget->usNextHit[2] )
{
bAttackIndex = 2;
}
if ( bAttackIndex != -1 )
{
if ( fAntiTank )
pTarget->usNextHit[ bAttackIndex ] = (UINT16)( 200 + PreRandom( 400 ) );
else
pTarget->usNextHit[ bAttackIndex ] = (UINT16)( 50 + PreRandom( 400 ) );
pTarget->pAttacker[ bAttackIndex ] = pAttacker;
}
}
if( sAttack < sDefence )
{
if( pTarget->pSoldier->stats.bLife >= OKLIFE || !PreRandom( 5 ) )
{
//Attacker misses -- use up a round of ammo. If target is unconscious, then 80% chance of hitting.
pTarget->uiFlags |= CELL_DODGEDATTACK | CELL_DIRTY;
if( fMelee )
{
if( fKnife )
PlayAutoResolveSample( MISS_KNIFE, RATE_11025, 50, 1, MIDDLEPAN );
else if( fClaw )
{
if ( pAttacker->uiFlags & ( CELL_BLOODCAT ) )
{
if ( Chance( 50 ) )
{
PlayAutoResolveSample( BLOODCAT_ATTACK, RATE_11025, 50, 1, MIDDLEPAN );
}
else
{
PlayAutoResolveSample( BLOODCAT_ROAR, RATE_11025, 50, 1, MIDDLEPAN );
}
}
else
{
if ( Chance( 50 ) )
{
PlayAutoResolveSample( ACR_SWIPE, RATE_11025, 50, 1, MIDDLEPAN );
}
else
{
PlayAutoResolveSample( ACR_LUNGE, RATE_11025, 50, 1, MIDDLEPAN );
}
}
}
else
PlayAutoResolveSample( SWOOSH_1 + PreRandom( 6 ), RATE_11025, 50, 1, MIDDLEPAN );
if( pTarget->uiFlags & CELL_MERC )
// AGILITY GAIN: Target "dodged" an attack
StatChange( pTarget->pSoldier, AGILAMT, 5, FALSE );
}
return;
}
}
// hit with a tank
if ( fCannon )
{
ubImpact = (UINT8)GetDamage( &pAttacker->pSoldier->inv[pAttacker->bWeaponSlot] );
ubLocation = AIM_SHOT_TORSO;
//ubAccuracy = (UINT8)((usAttack - usDefence + PreRandom( usDefence - pTarget->usDefence )) / 10);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - increased mercs' offense/defense rating
if ( pAttacker->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveOffenseBonus != 0 )
{
ubImpact += (ubImpact * gGameExternalOptions.sMercsAutoresolveOffenseBonus / 150);
}
else if ( pTarget->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveDeffenseBonus != 0 && (ubImpact > 3) )
{
ubImpact = max( 3, ((ubImpact * (100 - (gGameExternalOptions.sMercsAutoresolveDeffenseBonus / 2))) / 100) );
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
if ( bAttackIndex == -1 )
{
// tack damage on to end of last hit
pTarget->usHitDamage[2] += (UINT16)ubImpact;
}
else
{
pTarget->usHitDamage[bAttackIndex] = (UINT16)ubImpact;
}
// tanks might do splash damage to other troops as well...
UINT8 numtries = min(4, Random( gpAR->ubMercs + gpAR->ubCivs ) );
for ( int tries = 0; tries < numtries; ++tries )
{
SOLDIERCELL* pAnotherTarget = ChooseTarget( pAttacker );
if ( pAnotherTarget != pTarget )
{
INT8 bNewAttackIndex = -1;
if ( !pTarget->usNextHit[0] )
{
bNewAttackIndex = 0;
}
else if ( !pTarget->usNextHit[1] )
{
bNewAttackIndex = 1;
}
else if ( !pTarget->usNextHit[2] )
{
bNewAttackIndex = 2;
}
if ( bAttackIndex != -1 )
{
pAnotherTarget->usNextHit[bNewAttackIndex] = pTarget->usNextHit[bAttackIndex];
pAnotherTarget->pAttacker[bNewAttackIndex] = pAttacker;
pAnotherTarget->usHitDamage[bNewAttackIndex] = ubImpact / (numtries + 1) + Random( 20 );
}
}
}
}
// attack a tank with heavy weapons
else if ( fAntiTank )
{
ubImpact = (UINT8)GetDamage( &pAttacker->pSoldier->inv[pAttacker->bWeaponSlot] ) / 3;
UINT8 ubAmmoType = Magazine[Item[(&pAttacker->pSoldier->inv[pAttacker->bWeaponSlot])->usItem].ubClassIndex].ubAmmoType;
ubImpact = (UINT8)(ubImpact * AmmoTypes[ubAmmoType].dDamageModifierTank);
iRandom = Random( 100 );
if ( iRandom < 15 )
ubLocation = AIM_SHOT_HEAD;
else if ( iRandom < 50 )
ubLocation = AIM_SHOT_LEGS;
else
ubLocation = AIM_SHOT_TORSO;
//ubAccuracy = (UINT8)((usAttack - usDefence + PreRandom( usDefence - pTarget->usDefence )) / 10);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - increased mercs' offense/defense rating
if ( pAttacker->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveOffenseBonus != 0 )
{
ubImpact += (ubImpact * gGameExternalOptions.sMercsAutoresolveOffenseBonus / 150);
}
else if ( pTarget->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveDeffenseBonus != 0 && (ubImpact > 3) )
{
ubImpact = max( 3, ((ubImpact * (100 - (gGameExternalOptions.sMercsAutoresolveDeffenseBonus / 2))) / 100) );
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
if ( bAttackIndex == -1 )
{
// tack damage on to end of last hit
pTarget->usHitDamage[2] += (UINT16)ubImpact;
}
else
{
pTarget->usHitDamage[bAttackIndex] = (UINT16)ubImpact;
}
}
//Attacker hits
else if( !fMelee )
{
// silversurfer: We want to use the function instead of the raw value because it applies the modifiers that we configured in the ini.
ubImpact = (UINT8) GetDamage(&pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ]);
//ubImpact = Weapon[ pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ].usItem ].ubImpact;
iRandom = PreRandom( 100 );
if( iRandom < 15 )
ubLocation = AIM_SHOT_HEAD;
else if( iRandom < 30 )
ubLocation = AIM_SHOT_LEGS;
else
ubLocation = AIM_SHOT_TORSO;
if (sDefence >= pTarget->usDefence)
iRandom = PreRandom(sDefence - pTarget->usDefence);
else
iRandom = -(INT16)PreRandom(pTarget->usDefence - sDefence);
sAccuracy = (sAttack - sDefence + iRandom) / 10;
// HEADROCK HAM 5: Added argument
iImpact = BulletImpact( pAttacker->pSoldier, NULL, pTarget->pSoldier, ubLocation, ubImpact, sAccuracy, NULL );
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - increased mercs' offense/defense rating
if (pAttacker->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveOffenseBonus != 0)
{
iImpact += (iImpact * gGameExternalOptions.sMercsAutoresolveOffenseBonus /150);
}
else if (pTarget->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveDeffenseBonus != 0 && (iImpact > 3))
{
iImpact = max( 3, ((iImpact * (100 - (gGameExternalOptions.sMercsAutoresolveDeffenseBonus / 2))) /100));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
if ( bAttackIndex == -1 )
{
// tack damage on to end of last hit
pTarget->usHitDamage[2] += (UINT16) iImpact;
}
else
{
pTarget->usHitDamage[ bAttackIndex ] = (UINT16) iImpact;
}
}
else
{
OBJECTTYPE *pItem;
OBJECTTYPE tempItem;
PlayAutoResolveSample( (UINT8)(BULLET_IMPACT_1+PreRandom(3)), RATE_11025, 50, 1, MIDDLEPAN );
if( !pTarget->pSoldier->stats.bLife )
{ //Soldier already dead (can't kill him again!)
return;
}
if (sDefence >= pTarget->usDefence)
iRandom = PreRandom(sDefence - pTarget->usDefence);
else
iRandom = -(INT16)PreRandom(pTarget->usDefence - sDefence);
sAccuracy = (sAttack - sDefence + iRandom) / 10;
//Determine attacking weapon.
pAttacker->pSoldier->usAttackingWeapon = 0;
if( pAttacker->bWeaponSlot != -1 )
{
pItem = &pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ];
if( Item[ pItem->usItem ].usItemClass & IC_WEAPON )
pAttacker->pSoldier->usAttackingWeapon = pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ].usItem;
}
// WDS - make number of mercenaries, etc. be configurable
// WANNE: Does this lead to a CTD -> no I did not get any CTD, so I reenabled it
if( pAttacker->bWeaponSlot != HANDPOS && pAttacker->bWeaponSlot != -1)
{
//switch items
tempItem = pAttacker->pSoldier->inv[ HANDPOS ];
pAttacker->pSoldier->inv[ HANDPOS ] = pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ]; //CTD
iImpact = HTHImpact( pAttacker->pSoldier, pTarget->pSoldier, sAccuracy, (BOOLEAN)(fKnife || fClaw) );
pAttacker->pSoldier->inv[ pAttacker->bWeaponSlot ] = pAttacker->pSoldier->inv[ HANDPOS ];
pAttacker->pSoldier->inv[ HANDPOS ] = tempItem;
}
else
{
iImpact = HTHImpact( pAttacker->pSoldier, pTarget->pSoldier, sAccuracy, (BOOLEAN)(fKnife || fClaw) );
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - increased mercs' offense/deffense rating
if (pAttacker->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveOffenseBonus != 0)
{
iImpact += (iImpact * gGameExternalOptions.sMercsAutoresolveOffenseBonus /150);
}
else if (pTarget->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveDeffenseBonus != 0 && (iImpact > 3))
{
iImpact = max( 3, ((iImpact * (100 - (gGameExternalOptions.sMercsAutoresolveDeffenseBonus / 2))) /100));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// WANNE: Why is impact here always set to 0? The impact was calculated a few lines before!
//iImpact = 0;
if (gTacticalStatus.uiFlags & GODMODE && pTarget->pSoldier->bTeam == OUR_TEAM)
{
iImpact = 0;
}
// WANNE: Just for safety.
if (iImpact < 0)
iImpact = 0;
iNewLife = pTarget->pSoldier->stats.bLife - iImpact;
if( pAttacker->uiFlags & CELL_MERC )
{
//Attacker is a player, so increment the number of shots that hit.
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usShotsHit++;
// MARKSMANSHIP GAIN: Attacker's shot hits
StatChange( pAttacker->pSoldier, MARKAMT, 6, FALSE ); // in addition to 3 for taking a shot
}
if( pTarget->uiFlags & CELL_MERC )
{
//Target is a player, so increment the times he has been wounded.
if( iImpact > 1 )
gMercProfiles[ pTarget->pSoldier->ubProfile ].records.usTimesWoundedStabbed++;
// EXPERIENCE GAIN: Took some damage
StatChange( pTarget->pSoldier, EXPERAMT, ( UINT16 )( 5 * ( iImpact / 10 ) ), FALSE );
}
if( pTarget->pSoldier->stats.bLife >= CONSCIOUSNESS || pTarget->uiFlags & CELL_CREATURE )
{
if( gpAR->fSound )
pTarget->pSoldier->DoMercBattleSound( BATTLE_SOUND_HIT1 );
}
if( !(pTarget->uiFlags & CELL_CREATURE) && iNewLife < OKLIFE && pTarget->pSoldier->stats.bLife >= OKLIFE )
{
//the hit caused the merc to fall. Play the falling sound
PlayAutoResolveSample( (UINT8)FALL_1, RATE_11025, 50, 1, MIDDLEPAN );
pTarget->uiFlags &= ~CELL_RETREATING;
}
if( iNewLife <= 0 )
{
//soldier has been killed
// Flugente: campaign stats
gCurrentIncident.AddStat( pTarget->pSoldier, CAMPAIGNHISTORY_TYPE_KILL );
// Flugente: individual militia
MILITIA militia;
if ( GetMilitia( pTarget->pSoldier->usIndividualMilitiaID, &militia ) && !(militia.flagmask & MILITIAFLAG_DEAD) )
{
militia.healthratio = 0.0f;
militia.flagmask |= MILITIAFLAG_DEAD;
// note the current incident (when closing the incident, we only do this for those still alive)
MILITIA_BATTLEREPORT report;
report.id = GetIdOfCurrentlyOngoingIncident( );
report.flagmask = MILITIA_BATTLEREPORT_FLAG_DIED;
if ( pTarget->pSoldier->ubMilitiaKills )
report.flagmask |= MILITIA_BATTLEREPORT_FLAG_KILLEDENEMY;
militia.history.push_back( report );
UpdateMilitia( militia );
}
// Flugente: for raids, we need to keep track of killed forces
if ( GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE ||
GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE ||
GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
{
AddRaidPersonnel( -( pTarget->pSoldier->ubBodyType == BLOODCAT ), -( pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_ZOMBIE ), -( pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_BANDIT ) );
}
if( pAttacker->uiFlags & CELL_MERC )
{
//Player killed the enemy soldier -- update his stats as well as any assisters.
/////////////////////////////////////////////////////////////////////////////////////
// SANDRO - experimental - more specific statistics of mercs
switch(pTarget->pSoldier->ubSoldierClass)
{
case SOLDIER_CLASS_ROBOT :
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsOthers++;
break;
case SOLDIER_CLASS_ELITE :
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsElites++;
break;
case SOLDIER_CLASS_ARMY :
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsRegulars++;
break;
case SOLDIER_CLASS_ADMINISTRATOR :
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsAdmins++;
break;
case SOLDIER_CLASS_CREATURE :
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsCreatures++;
break;
case SOLDIER_CLASS_ZOMBIE :
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsZombies++;
break;
case SOLDIER_CLASS_BANDIT:
gMercProfiles[pAttacker->pSoldier->ubProfile].records.usKillsOthers++;
break;
default :
if ( CREATURE_OR_BLOODCAT( pTarget->pSoldier ) )
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsCreatures++;
else if ( ARMED_VEHICLE( pTarget->pSoldier ) ) // we hardly can kill a tank in autoresolve, but well.. who knows..
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsTanks++;
else if ( pTarget->pSoldier->bTeam == CIV_TEAM && !pTarget->pSoldier->aiData.bNeutral && pTarget->pSoldier->bSide != gbPlayerNum )
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsHostiles++;
else
{
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsOthers++;
// Flugente: dynamic opinions: if this guy is not hostile towards us, then some mercs will complain about killing civilians
if ((gGameExternalOptions.fDynamicOpinions) && (pTarget->pSoldier->bTeam != OUR_TEAM) && (pTarget->pSoldier->aiData.bNeutral || pTarget->pSoldier->bSide == pAttacker->pSoldier->bSide) )
{
// not for killing animals though...
if ( pTarget->pSoldier->ubBodyType != CROW && pTarget->pSoldier->ubBodyType != COW )
HandleDynamicOpinionChange( pAttacker->pSoldier, OPINIONEVENT_CIVKILLER, TRUE, TRUE );
}
}
break;
}
/////////////////////////////////////////////////////////////////////////////////////
gStrategicStatus.usPlayerKills++;
}
else if( pAttacker->uiFlags & CELL_MILITIA )
{
pAttacker->pSoldier->ubMilitiaKills += 1;
}
if( pTarget->uiFlags & CELL_MERC && gpAR->fSound )
{
PlayAutoResolveSample( (UINT8)DOORCR_1, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
PlayAutoResolveSample( (UINT8)HEADCR_1, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
}
}
///////////////////////////////////////////////////////////////
// SANDRO - added the insta-healable value for doctor trait
else if ((IS_MERC_BODY_TYPE( pTarget->pSoldier ) || IS_CIV_BODY_TYPE( pTarget->pSoldier )) && ( gGameOptions.fNewTraitSystem ))
{
pTarget->pSoldier->iHealableInjury += ((pTarget->pSoldier->stats.bLife - iNewLife) * 100);
if (pTarget->pSoldier->iHealableInjury > ((pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->stats.bLife) * 100))
pTarget->pSoldier->iHealableInjury = ((pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->stats.bLife) * 100);
}
///////////////////////////////////////////////////////////////
//Adjust the soldiers stats based on the damage.
pTarget->pSoldier->stats.bLife = (INT8)max( iNewLife, 0 );
if( pTarget->uiFlags & CELL_MERC && gpAR->pRobotCell)
{
gpAR->pRobotCell->pSoldier->UpdateRobotControllerGivenRobot( );
}
if( fKnife || fClaw )
{
if( pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->bBleeding - iImpact >= pTarget->pSoldier->stats.bLife )
pTarget->pSoldier->bBleeding += (INT8)iImpact;
else
pTarget->pSoldier->bBleeding = (INT8)(pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->stats.bLife);
}
if( !pTarget->pSoldier->stats.bLife )
{
// SANDRO - added check to erase insta-healable amount of HPs if dead
if (pTarget->pSoldier->iHealableInjury > 0)
{ pTarget->pSoldier->iHealableInjury = 0; }
gpAR->fRenderAutoResolve = TRUE;
#ifdef INVULNERABILITY
if( 1 )
RefreshMerc( pTarget->pSoldier );
else
#endif
if( pTarget->uiFlags & CELL_MERC )
{
gpAR->usPlayerAttack -= pTarget->usAttack;
gpAR->usPlayerDefence -= pTarget->usDefence;
gpAR->ubAliveMercs--;
pTarget->usAttack = 0;
pTarget->usDefence = 0;
}
else if( pTarget->uiFlags & CELL_MILITIA )
{
gpAR->usPlayerAttack -= pTarget->usAttack;
gpAR->usPlayerDefence -= pTarget->usDefence;
gpAR->ubAliveCivs--;
pTarget->usAttack = 0;
pTarget->usDefence = 0;
}
else if( pTarget->uiFlags & (CELL_ENEMY|CELL_CREATURE) )
{
gpAR->usEnemyAttack -= pTarget->usAttack;
gpAR->usEnemyDefence -= pTarget->usDefence;
gpAR->ubAliveEnemies--;
pTarget->usAttack = 0;
pTarget->usDefence = 0;
}
}
pTarget->uiFlags |= CELL_HITBYATTACKER | CELL_DIRTY;
}
}
void TargetHitCallback( SOLDIERCELL *pTarget, INT32 index )
{
INT32 iNewLife;
SOLDIERCELL *pAttacker;
if( !pTarget->pSoldier->stats.bLife )
{ //Soldier already dead (can't kill him again!)
return;
}
pAttacker = pTarget->pAttacker[ index ];
//creatures get damage reduction bonuses
switch( pTarget->pSoldier->ubBodyType )
{
case LARVAE_MONSTER:
case INFANT_MONSTER:
break;
case YAF_MONSTER:
case YAM_MONSTER:
pTarget->usHitDamage[index] = (pTarget->usHitDamage[index] + 2) / 4;
break;
case ADULTFEMALEMONSTER:
case AM_MONSTER:
pTarget->usHitDamage[index] = (pTarget->usHitDamage[index] + 3) / 6;
break;
case QUEENMONSTER:
pTarget->usHitDamage[index] = (pTarget->usHitDamage[index] + 4) / 8;
break;
}
if (gTacticalStatus.uiFlags & GODMODE && pTarget->pSoldier->bTeam == OUR_TEAM)
{
iNewLife = pTarget->pSoldier->stats.bLife;
}
else
{
iNewLife = pTarget->pSoldier->stats.bLife - pTarget->usHitDamage[index];
}
if( !pTarget->usHitDamage[index] )
{ //bullet missed -- play a ricochet sound.
if( pTarget->uiFlags & CELL_MERC )
// AGILITY GAIN: Target "dodged" an attack
StatChange( pTarget->pSoldier, AGILAMT, 5, FALSE );
PlayAutoResolveSample( MISS_1 + PreRandom(8), RATE_11025, 50, 1, MIDDLEPAN );
return;
}
if( pAttacker->uiFlags & CELL_MERC )
{ //Attacker is a player, so increment the number of shots that hit.
gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usShotsHit++;
// MARKSMANSHIP GAIN: Attacker's shot hits
StatChange( pAttacker->pSoldier, MARKAMT, 6, FALSE ); // in addition to 3 for taking a shot
}
if( pTarget->uiFlags & CELL_MERC && pTarget->usHitDamage[ index ] )
{ //Target is a player, so increment the times he has been wounded.
if( pTarget->usHitDamage[index] > 1 )
gMercProfiles[ pTarget->pSoldier->ubProfile ].records.usTimesWoundedShot++;
// EXPERIENCE GAIN: Took some damage
StatChange( pTarget->pSoldier, EXPERAMT, ( UINT16 )( 5 * ( pTarget->usHitDamage[ index ] / 10 ) ), FALSE );
}
//bullet hit -- play an impact sound and a merc hit sound
if ( ARMED_VEHICLE( pTarget->pSoldier ) || ENEMYROBOT( pTarget->pSoldier ) )
PlayAutoResolveSample( (UINT8)(S_METAL_IMPACT1 + PreRandom( 3 )), RATE_11025, 50, 1, MIDDLEPAN );
else
PlayAutoResolveSample( (UINT8)(BULLET_IMPACT_1+PreRandom(3)), RATE_11025, 50, 1, MIDDLEPAN );
if( pTarget->pSoldier->stats.bLife >= CONSCIOUSNESS )
{
if( gpAR->fSound )
pTarget->pSoldier->DoMercBattleSound( BATTLE_SOUND_HIT1 );
}
if( iNewLife < OKLIFE && pTarget->pSoldier->stats.bLife >= OKLIFE )
{
//the hit caused the merc to fall. Play the falling sound
PlayAutoResolveSample( (UINT8)FALL_1, RATE_11025, 50, 1, MIDDLEPAN );
pTarget->uiFlags &= ~CELL_RETREATING;
}
if( iNewLife <= 0 )
{
// Flugente: campaign stats
gCurrentIncident.AddStat( pTarget->pSoldier, CAMPAIGNHISTORY_TYPE_KILL );
// Flugente: individual militia
MILITIA militia;
if ( GetMilitia( pTarget->pSoldier->usIndividualMilitiaID, &militia ) && !(militia.flagmask & MILITIAFLAG_DEAD) )
{
militia.healthratio = 0.0f;
militia.flagmask |= MILITIAFLAG_DEAD;
// note the current incident (when closing the incident, we only do this for those still alive)
MILITIA_BATTLEREPORT report;
report.id = GetIdOfCurrentlyOngoingIncident( );
report.flagmask = MILITIA_BATTLEREPORT_FLAG_DIED;
if ( pTarget->pSoldier->ubMilitiaKills )
report.flagmask |= MILITIA_BATTLEREPORT_FLAG_KILLEDENEMY;
militia.history.push_back( report );
UpdateMilitia( militia );
}
// Flugente: for raids, we need to keep track of killed forces
if ( GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE ||
GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE ||
GetEnemyEncounterCode() == BANDIT_ATTACK_CODE )
{
AddRaidPersonnel( -( pTarget->pSoldier->ubBodyType == BLOODCAT ), -( pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_ZOMBIE ), -( pTarget->pSoldier->ubSoldierClass == SOLDIER_CLASS_BANDIT ) );
}
//soldier has been killed
if( pTarget->pAttacker[ index ]->uiFlags & CELL_PLAYER )
{ //Player killed the enemy soldier -- update his stats as well as any assisters.
SOLDIERCELL *pKiller;
SOLDIERCELL *pAssister1, *pAssister2;
pKiller = pTarget->pAttacker[ index ];
pAssister1 = pTarget->pAttacker[ index < 2 ? index + 1 : 0 ];
pAssister2 = pTarget->pAttacker[ index > 0 ? index - 1 : 2 ];
if( pKiller == pAssister1 )
pAssister1 = NULL;
if( pKiller == pAssister2 )
pAssister2 = NULL;
if( pAssister1 == pAssister2 )
pAssister2 = NULL;
if( pKiller )
{
if( pKiller->uiFlags & CELL_MERC )
{
/////////////////////////////////////////////////////////////////////////////////////
// SANDRO - new mercs' records
switch(pTarget->pSoldier->ubSoldierClass)
{
case SOLDIER_CLASS_ROBOT :
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsOthers++;
break;
case SOLDIER_CLASS_ELITE :
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsElites++;
break;
case SOLDIER_CLASS_ARMY :
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsRegulars++;
break;
case SOLDIER_CLASS_ADMINISTRATOR :
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsAdmins++;
break;
case SOLDIER_CLASS_CREATURE :
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsCreatures++;
break;
case SOLDIER_CLASS_ZOMBIE :
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsZombies++;
break;
case SOLDIER_CLASS_BANDIT:
gMercProfiles[pKiller->pSoldier->ubProfile].records.usKillsOthers++;
break;
default :
if ( CREATURE_OR_BLOODCAT( pTarget->pSoldier ) )
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsCreatures++;
else if ( ARMED_VEHICLE( pTarget->pSoldier ) ) // we hardly can kill a tank in autoresolve, but well.. who knows..
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsTanks++;
else if ( pTarget->pSoldier->bTeam == CIV_TEAM && !pTarget->pSoldier->aiData.bNeutral && pTarget->pSoldier->bSide != gbPlayerNum )
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsHostiles++;
else
gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsOthers++;
break;
}
/////////////////////////////////////////////////////////////////////////////////////
gStrategicStatus.usPlayerKills++;
// EXPERIENCE CLASS GAIN: Earned a kill
StatChange( pKiller->pSoldier, EXPERAMT, ( UINT16 )( 10 * pTarget->pSoldier->pathing.bLevel ), FALSE );
HandleMoraleEvent( pKiller->pSoldier, MORALE_KILLED_ENEMY, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
}
else if( pKiller->uiFlags & CELL_MILITIA )
pKiller->pSoldier->ubMilitiaKills += 1;
}
if( pAssister1 )
{
if( pAssister1->uiFlags & CELL_MERC )
{
/////////////////////////////////////////////////////////////////////////////////////
// SANDRO - new mercs' records
if( pKiller )
{
if( pKiller->uiFlags & CELL_MERC && pKiller->pSoldier->bTeam == gbPlayerNum )
gMercProfiles[ pAssister1->pSoldier->ubProfile ].records.usAssistsMercs++;
else if ( pKiller->pSoldier->bTeam == MILITIA_TEAM )
gMercProfiles[ pAssister1->pSoldier->ubProfile ].records.usAssistsMilitia++;
else
gMercProfiles[ pAssister1->pSoldier->ubProfile ].records.usAssistsOthers++;
}
else
{
gMercProfiles[ pAssister1->pSoldier->ubProfile ].records.usAssistsOthers++;
}
/////////////////////////////////////////////////////////////////////////////////////
// EXPERIENCE CLASS GAIN: Earned an assist
StatChange( pAssister1->pSoldier, EXPERAMT, ( UINT16 )( 5 * pTarget->pSoldier->pathing.bLevel ), FALSE );
}
else if( pAssister1->uiFlags & CELL_MILITIA )
pAssister1->pSoldier->ubMilitiaAssists++;
}
else if( pAssister2 )
{
if( pAssister2->uiFlags & CELL_MERC )
{
/////////////////////////////////////////////////////////////////////////////////////
// SANDRO - new mercs' records
if( pKiller )
{
if( pKiller->uiFlags & CELL_MERC && pKiller->pSoldier->bTeam == gbPlayerNum )
gMercProfiles[ pAssister2->pSoldier->ubProfile ].records.usAssistsMercs++;
else if ( pKiller->pSoldier->bTeam == MILITIA_TEAM )
gMercProfiles[ pAssister2->pSoldier->ubProfile ].records.usAssistsMilitia++;
else
gMercProfiles[ pAssister2->pSoldier->ubProfile ].records.usAssistsOthers++;
}
else
{
gMercProfiles[ pAssister2->pSoldier->ubProfile ].records.usAssistsOthers++;
}
/////////////////////////////////////////////////////////////////////////////////////
// EXPERIENCE CLASS GAIN: Earned an assist
StatChange( pAssister2->pSoldier, EXPERAMT, ( UINT16 )( 5 * pTarget->pSoldier->pathing.bLevel ), FALSE );
}
else if( pAssister2->uiFlags & CELL_MILITIA )
pAssister2->pSoldier->ubMilitiaAssists++;
}
}
if( pTarget->uiFlags & CELL_MERC && gpAR->fSound )
{
PlayAutoResolveSample( (UINT8)DOORCR_1, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
PlayAutoResolveSample( (UINT8)HEADCR_1, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
}
if( iNewLife < -60 && !(pTarget->uiFlags & CELL_BUG ) )
{ //High damage death
if( gpAR->fSound )
{
if( PreRandom( 3 ) )
PlayAutoResolveSample( (UINT8)BODY_SPLAT_1, RATE_11025, 50, 1, MIDDLEPAN );
else
PlayAutoResolveSample( (UINT8)HEADSPLAT_1, RATE_11025, 50, 1, MIDDLEPAN );
}
}
else
{ //Normal death
if( gpAR->fSound )
{
if ( ARMED_VEHICLE( pTarget->pSoldier ) || ENEMYROBOT( pTarget->pSoldier ) )
PlayAutoResolveSample( (UINT8)(S_RAID_TB_BOMB), RATE_11025, 50, 1, MIDDLEPAN );
else
pTarget->pSoldier->DoMercBattleSound( BATTLE_SOUND_DIE1 );
}
}
#ifdef INVULNERABILITY
RefreshMerc( pTarget->pSoldier );
return;
#endif
}
///////////////////////////////////////////////////////////////
// SANDRO - added the insta-healable value for doctor trait
else if ((IS_MERC_BODY_TYPE( pTarget->pSoldier ) || IS_CIV_BODY_TYPE( pTarget->pSoldier )) && ( gGameOptions.fNewTraitSystem ))
{
pTarget->pSoldier->iHealableInjury += ((pTarget->pSoldier->stats.bLife - iNewLife) * 100);
if (pTarget->pSoldier->iHealableInjury > ((pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->stats.bLife) * 100))
pTarget->pSoldier->iHealableInjury = ((pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->stats.bLife) * 100);
}
///////////////////////////////////////////////////////////////
//Adjust the soldiers stats based on the damage.
pTarget->pSoldier->stats.bLife = (INT8)max( iNewLife, 0 );
if( pTarget->uiFlags & CELL_MERC && gpAR->pRobotCell)
{
gpAR->pRobotCell->pSoldier->UpdateRobotControllerGivenRobot( );
}
if( pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->bBleeding - pTarget->usHitDamage[index] >= pTarget->pSoldier->stats.bLife )
pTarget->pSoldier->bBleeding += (INT8)pTarget->usHitDamage[index];
else
pTarget->pSoldier->bBleeding = (INT8)(pTarget->pSoldier->stats.bLifeMax - pTarget->pSoldier->stats.bLife);
if( !pTarget->pSoldier->stats.bLife )
{
// SANDRO - added check to erase insta-healable amount of HPs if dead
if (pTarget->pSoldier->iHealableInjury > 0)
{ pTarget->pSoldier->iHealableInjury = 0; }
gpAR->fRenderAutoResolve = TRUE;
if( pTarget->uiFlags & CELL_MERC )
{
gpAR->usPlayerAttack -= pTarget->usAttack;
gpAR->usPlayerDefence -= pTarget->usDefence;
gpAR->ubAliveMercs--;
pTarget->usAttack = 0;
pTarget->usDefence = 0;
}
else if( pTarget->uiFlags & CELL_MILITIA )
{
gpAR->usPlayerAttack -= pTarget->usAttack;
gpAR->usPlayerDefence -= pTarget->usDefence;
gpAR->ubAliveCivs--;
pTarget->usAttack = 0;
pTarget->usDefence = 0;
}
else if( pTarget->uiFlags & (CELL_ENEMY|CELL_CREATURE) )
{
gpAR->usEnemyAttack -= pTarget->usAttack;
gpAR->usEnemyDefence -= pTarget->usDefence;
gpAR->ubAliveEnemies--;
pTarget->usAttack = 0;
pTarget->usDefence = 0;
}
}
pTarget->uiFlags |= CELL_HITBYATTACKER | CELL_DIRTY;
}
void Delay( UINT32 uiMilliseconds )
{
INT32 iTime;
iTime = GetJA2Clock();
while( GetJA2Clock() < iTime + uiMilliseconds );
}
BOOLEAN IsBattleOver()
{
INT32 i;
INT32 iNumInvolvedMercs = 0;
INT32 iNumMercsRetreated = 0;
INT32 iNumInvolvedMilitia = 0;
INT32 iNumMilitiaRetreated = 0;
BOOLEAN fOnlyEPCsLeft = TRUE;
if( gpAR->ubBattleStatus != BATTLE_IN_PROGRESS )
return TRUE;
for( i = 0; i < gpAR->ubMercs; ++i )
{
if( !(gpMercs[ i ].uiFlags & CELL_RETREATED) && gpMercs[ i ].pSoldier->stats.bLife )
{
if( !(gpMercs[ i ].uiFlags & CELL_EPC) )
{
fOnlyEPCsLeft = FALSE;
++iNumInvolvedMercs;
}
}
if( gpMercs[ i ].uiFlags & CELL_RETREATED )
{
++iNumMercsRetreated;
}
}
for ( i = 0; i < gpAR->ubCivs; ++i )
{
if ( !(gpCivs[i].uiFlags & CELL_RETREATED) && gpCivs[i].pSoldier->stats.bLife )
{
++iNumInvolvedMilitia;
}
else if ( gpCivs[i].uiFlags & CELL_RETREATED )
{
++iNumMilitiaRetreated;
}
}
if( gpAR->pRobotCell )
{
//Do special robot checks
SOLDIERTYPE *pRobot;
pRobot = gpAR->pRobotCell->pSoldier;
if( pRobot->ubRobotRemoteHolderID == NOBODY )
{
//Robot can't fight anymore.
gpAR->usPlayerAttack -= gpAR->pRobotCell->usAttack;
gpAR->pRobotCell->usAttack = 0;
if( iNumInvolvedMercs == 1 && !gpAR->ubAliveCivs )
{
//Robot is the only one left in battle, so instantly kill him.
pRobot->DoMercBattleSound( BATTLE_SOUND_DIE1 );
pRobot->stats.bLife = 0;
gpAR->ubAliveMercs--;
iNumInvolvedMercs = 0;
}
}
}
if( !gpAR->ubAliveCivs && !iNumInvolvedMercs && iNumMercsRetreated )
{
//RETREATED
gpAR->ubBattleStatus = BATTLE_RETREAT;
// wake everyone up
WakeUpAllMercsInSectorUnderAttack( );
RetreatAllInvolvedPlayerGroups( );
}
else if ( !iNumInvolvedMercs && !iNumInvolvedMilitia && iNumMilitiaRetreated && gGameExternalOptions.fMilitiaStrategicCommand )
{
//RETREATED
gpAR->ubBattleStatus = BATTLE_RETREAT;
// wake everyone up
WakeUpAllMercsInSectorUnderAttack( );
RetreatAllInvolvedMilitiaGroups( );
}
else if( !gpAR->ubAliveCivs && !iNumInvolvedMercs )
{
//DEFEAT
if( fOnlyEPCsLeft )
{
//Kill the EPCs.
for( i = 0; i < gpAR->ubMercs; ++i )
{
if( gpMercs[ i ].uiFlags & CELL_EPC )
{
gpMercs[ i ].pSoldier->DoMercBattleSound( BATTLE_SOUND_DIE1 );
gpMercs[ i ].pSoldier->stats.bLife = 0;
gpMercs[ i ].pSoldier->iHealableInjury = 0; // added by SANDRO
--gpAR->ubAliveMercs;
}
}
}
for( i = 0; i < gpAR->ubEnemies; ++i )
{
if( gpEnemies[ i ].pSoldier->stats.bLife )
{
if ( GetEnemyEncounterCode() != CREATURE_ATTACK_CODE &&
GetEnemyEncounterCode() != BLOODCAT_ATTACK_CODE &&
GetEnemyEncounterCode() != ZOMBIE_ATTACK_CODE )
{
gpEnemies[ i ].pSoldier->DoMercBattleSound( BATTLE_SOUND_LAUGH1 );
}
else
{
PlayJA2Sample( ACR_EATFLESH, RATE_11025, 50, 1, MIDDLEPAN );
}
break;
}
}
gpAR->ubBattleStatus = BATTLE_DEFEAT;
}
else if( !gpAR->ubAliveEnemies )
{
//VICTORY
gpAR->ubBattleStatus = BATTLE_VICTORY;
}
else
{
return FALSE;
}
SetupDoneInterface();
return TRUE;
}
//#define TESTSURRENDER
BOOLEAN AttemptPlayerCapture()
{
INT32 i;
BOOLEAN fConcious;
INT32 iConciousEnemies;
#ifndef TESTSURRENDER
//Only attempt capture if day is less than four.
if( GetWorldDay() < STARTDAY_ALLOW_PLAYER_CAPTURE_FOR_RESCUE && !gpAR->fAllowCapture )
{
return FALSE;
}
if( gpAR->fPlayerRejectedSurrenderOffer )
{
return FALSE;
}
if( gStrategicStatus.uiFlags & STRATEGIC_PLAYER_CAPTURED_FOR_RESCUE )
{
return FALSE;
}
if( gpAR->fCaptureNotPermittedDueToEPCs )
{ //EPCs make things much more difficult when considering capture. Simply don't allow it.
return FALSE;
}
//Only attempt capture of mercs if there are 2 or 3 of them alive
if( gpAR->ubAliveCivs || gpAR->ubAliveMercs < 2 || gpAR->ubAliveMercs > 3 )
{
return FALSE;
}
//if the number of alive enemies doesn't double the number of alive mercs, don't offer surrender.
if( gpAR->ubAliveEnemies < gpAR->ubAliveMercs*2 )
{
return FALSE;
}
//make sure that these enemies are actually concious!
iConciousEnemies = 0;
for( i = 0; i < gpAR->ubEnemies; i++ )
{
if( gpEnemies[ i ].pSoldier->stats.bLife >= OKLIFE )
{
iConciousEnemies++;
}
}
if( iConciousEnemies < gpAR->ubAliveMercs * 2 )
{
return FALSE;
}
//So far, the conditions are right. Now, we will determine if the the remaining players are
//wounded and/or unconcious. If any are concious, we will prompt for a surrender, otherwise,
//it is automatic.
fConcious = FALSE;
for( i = 0; i < gpAR->ubMercs; i++ )
{
//if any of the 2 or 3 mercs has more than 60% life, then return.
if( gpMercs[ i ].uiFlags & CELL_ROBOT )
{
return FALSE;
}
if( gpMercs[ i ].pSoldier->stats.bLife*100 > gpMercs[ i ].pSoldier->stats.bLifeMax*60 )
{
return FALSE;
}
if( gpMercs[ i ].pSoldier->stats.bLife >= OKLIFE )
{
fConcious = TRUE;
}
}
if( fConcious )
{
if( PreRandom( 100 ) < 2 )
{
SetupSurrenderInterface();
}
}
else if( PreRandom( 100 ) < 25 )
{
#else
{
#endif
BeginCaptureSquence( );
gpAR->ubBattleStatus = BATTLE_CAPTURED;
gpAR->fRenderAutoResolve = TRUE;
SetupDoneInterface();
}
return TRUE;
}
void SetupDoneInterface()
{
INT32 i;
gpAR->fRenderAutoResolve = TRUE;
HideButton( gpAR->iButton[ PAUSE_BUTTON ] );
HideButton( gpAR->iButton[ PLAY_BUTTON ] );
HideButton( gpAR->iButton[ FAST_BUTTON ] );
HideButton( gpAR->iButton[ FINISH_BUTTON ] );
HideButton( gpAR->iButton[ RETREAT_BUTTON ] );
HideButton( gpAR->iButton[ YES_BUTTON ] );
HideButton( gpAR->iButton[ NO_BUTTON ] );
if( gpAR->ubBattleStatus == BATTLE_VICTORY && gpAR->ubAliveMercs )
{
ShowButton( gpAR->iButton[ DONEWIN_BUTTON ] );
ShowButton( gpAR->iButton[ BANDAGE_BUTTON ] );
}
else
{
ShowButton( gpAR->iButton[ DONELOSE_BUTTON ] );
}
DetermineBandageButtonState();
for( i = 0; i < gpAR->ubMercs; i++ )
{ //So they can't retreat!
MSYS_DisableRegion( gpMercs[ i ].pRegion );
}
}
void SetupSurrenderInterface()
{
HideButton( gpAR->iButton[ PAUSE_BUTTON ] );
HideButton( gpAR->iButton[ PLAY_BUTTON ] );
HideButton( gpAR->iButton[ FAST_BUTTON ] );
HideButton( gpAR->iButton[ FINISH_BUTTON ] );
HideButton( gpAR->iButton[ RETREAT_BUTTON ] );
HideButton( gpAR->iButton[ BANDAGE_BUTTON ] );
HideButton( gpAR->iButton[ DONEWIN_BUTTON ] );
HideButton( gpAR->iButton[ DONELOSE_BUTTON ] );
ShowButton( gpAR->iButton[ YES_BUTTON ] );
ShowButton( gpAR->iButton[ NO_BUTTON ] );
gpAR->fRenderAutoResolve = TRUE;
gpAR->fPendingSurrender = TRUE;
}
void HideSurrenderInterface()
{
HideButton( gpAR->iButton[ PAUSE_BUTTON ] );
HideButton( gpAR->iButton[ PLAY_BUTTON ] );
HideButton( gpAR->iButton[ FAST_BUTTON ] );
HideButton( gpAR->iButton[ FINISH_BUTTON ] );
HideButton( gpAR->iButton[ RETREAT_BUTTON ] );
HideButton( gpAR->iButton[ BANDAGE_BUTTON ] );
HideButton( gpAR->iButton[ DONEWIN_BUTTON ] );
HideButton( gpAR->iButton[ DONELOSE_BUTTON ] );
HideButton( gpAR->iButton[ YES_BUTTON ] );
HideButton( gpAR->iButton[ NO_BUTTON ] );
gpAR->fPendingSurrender = FALSE;
gpAR->fRenderAutoResolve = TRUE;
}
void AcceptSurrenderCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
BeginCaptureSquence( );
gpAR->ubBattleStatus = BATTLE_SURRENDERED;
gpAR->fPendingSurrender = FALSE;
SetupDoneInterface();
}
}
void RejectSurrenderCallback( GUI_BUTTON *btn, INT32 reason )
{
if( reason & MSYS_CALLBACK_REASON_LBUTTON_UP )
{
gpAR->fPlayerRejectedSurrenderOffer = TRUE;
HideSurrenderInterface();
}
}
void ProcessBattleFrame()
{
INT32 iRandom;
INT32 i;
SOLDIERCELL *pAttacker, *pTarget;
UINT32 uiDiff;
static INT32 iTimeSlice = 0;
static BOOLEAN fContinue = FALSE;
static UINT32 uiSlice = 0;
static INT32 iTotal = 0;
static INT32 iMercs = 0;
static INT32 iCivs = 0;
static INT32 iEnemies = 0;
static INT32 iMercsLeft = 0;
static INT32 iCivsLeft = 0;
static INT32 iEnemiesLeft = 0;
BOOLEAN found = FALSE;
INT32 iTime, iAttacksThisFrame;
pAttacker = NULL;
iAttacksThisFrame = 0;
if( fContinue )
{
gpAR->uiCurrTime = GetJA2Clock();
fContinue = FALSE;
goto CONTINUE_BATTLE;
}
//determine how much real-time has passed since the last frame
if( gpAR->uiCurrTime )
{
gpAR->uiPrevTime = gpAR->uiCurrTime;
gpAR->uiCurrTime = GetJA2Clock();
}
else
{
gpAR->uiCurrTime = GetJA2Clock();
return;
}
if( gpAR->fPaused )
return;
uiDiff = gpAR->uiCurrTime - gpAR->uiPrevTime;
if( gpAR->uiTimeSlice < 0xffffffff )
{
iTimeSlice = uiDiff*gpAR->uiTimeSlice/1000;
}
else
{ //largest positive signed value
iTimeSlice = 0x7fffffff;
}
while( iTimeSlice > 0 )
{
uiSlice = min( iTimeSlice, 1000 );
if( gpAR->ubBattleStatus == BATTLE_IN_PROGRESS )
gpAR->uiTotalElapsedBattleTimeInMilliseconds += uiSlice;
//Now process each of the players
iTotal = gpAR->ubMercs + gpAR->ubCivs + gpAR->ubEnemies + 1;
iMercs = iMercsLeft = gpAR->ubMercs;
iCivs = iCivsLeft = gpAR->ubCivs;
iEnemies = iEnemiesLeft = gpAR->ubEnemies;
for( i = 0; i < gpAR->ubMercs; i++ )
gpMercs[ i ].uiFlags &= ~CELL_PROCESSED;
for( i = 0; i < gpAR->ubCivs; i++ )
gpCivs[ i ].uiFlags &= ~CELL_PROCESSED;
for( i = 0; i < gpAR->ubEnemies; i++ )
gpEnemies[ i ].uiFlags &= ~CELL_PROCESSED;
while( --iTotal )
{
INT32 cnt;
if( iTimeSlice != 0x7fffffff && GetJA2Clock() > gpAR->uiCurrTime+17 ||
!gpAR->fInstantFinish && iAttacksThisFrame > (gpAR->ubMercs+gpAR->ubCivs+gpAR->ubEnemies)/4 )
{ //We have spent too much time in here. In order to maintain 60FPS, we will
//leave now, which will allow for updating of the graphics (and mouse cursor),
//and all of the necessary locals are saved via static variables. It'll check
//the fContinue flag, and goto the CONTINUE_BATTLE label the next time this function
//is called.
fContinue = TRUE;
return;
}
CONTINUE_BATTLE:
if ( IsBattleOver() ||
(GetEnemyEncounterCode() != CREATURE_ATTACK_CODE && GetEnemyEncounterCode() != BLOODCAT_ATTACK_CODE && GetEnemyEncounterCode() != ZOMBIE_ATTACK_CODE && GetEnemyEncounterCode() != BANDIT_ATTACK_CODE )
&& AttemptPlayerCapture() )
return;
iRandom = PreRandom( iTotal );
found = FALSE;
if( iMercs && iRandom < iMercsLeft )
{
iMercsLeft--;
while( !found )
{
iRandom = Random( iMercs );
pAttacker = &gpMercs[ iRandom ];
if( !(pAttacker->uiFlags & CELL_PROCESSED ) )
{
pAttacker->uiFlags |= CELL_PROCESSED;
found = TRUE;
}
}
}
else if( iCivs && iRandom < iMercsLeft + iCivsLeft )
{
iCivsLeft--;
while( !found )
{
iRandom = Random( iCivs );
pAttacker = &gpCivs[ iRandom ];
if( !(pAttacker->uiFlags & CELL_PROCESSED ) )
{
pAttacker->uiFlags |= CELL_PROCESSED;
found = TRUE;
}
}
}
else if( iEnemies && iEnemiesLeft )
{
iEnemiesLeft--;
while( !found )
{
iRandom = Random( iEnemies );
pAttacker = &gpEnemies[ iRandom ];
if( !(pAttacker->uiFlags & CELL_PROCESSED ) )
{
pAttacker->uiFlags |= CELL_PROCESSED;
found = TRUE;
}
}
}
else
AssertMsg( 0, "Logic error in ProcessBattleFrame()" );
//Apply damage and play miss/hit sounds if delay between firing and hit has expired.
if( !(pAttacker->uiFlags & CELL_RETREATED ) )
{
for( cnt = 0; cnt < 3; ++cnt )
{ //Check if any incoming bullets have hit the target.
if( pAttacker->usNextHit[ cnt ] )
{
iTime = pAttacker->usNextHit[ cnt ];
iTime -= uiSlice;
if( iTime >= 0 )
{ //Bullet still on route.
pAttacker->usNextHit[ cnt ] = (UINT16)iTime;
}
else
{ //Bullet is going to hit/miss.
TargetHitCallback( pAttacker, cnt );
pAttacker->usNextHit[ cnt ] = 0;
}
}
}
}
if( pAttacker->pSoldier->stats.bLife < OKLIFE || pAttacker->uiFlags & CELL_RETREATED )
{
if( !(pAttacker->uiFlags & CELL_CREATURE) || !pAttacker->pSoldier->stats.bLife )
continue; //can't attack if you are unconcious or not around (Or a live creature)
}
iTime = pAttacker->usNextAttack;
iTime -= uiSlice;
if( iTime > 0 )
{
pAttacker->usNextAttack = (UINT32)iTime;
continue;
}
else
{
if( pAttacker->uiFlags & CELL_RETREATING )
{
//The merc has successfully retreated. Remove the stats, and continue on.
if( pAttacker == gpAR->pRobotCell )
{
if( gpAR->pRobotCell->pSoldier->ubRobotRemoteHolderID == NOBODY )
{
gpAR->pRobotCell->uiFlags &= ~CELL_RETREATING;
gpAR->pRobotCell->uiFlags |= CELL_DIRTY;
gpAR->pRobotCell->usNextAttack = 0xffff;
continue;
}
}
gpAR->usPlayerDefence -= pAttacker->usDefence;
pAttacker->usDefence = 0;
pAttacker->uiFlags |= CELL_RETREATED;
continue;
}
if( pAttacker->usAttack )
{
pTarget = ChooseTarget( pAttacker );
if( pAttacker->uiFlags & CELL_BUG && PreRandom( 100 ) < 7 )
PlayAutoResolveSample( ACR_SMELL_THREAT + PreRandom( 2 ), RATE_11025, 50, 1, MIDDLEPAN );
else
AttackTarget( pAttacker, pTarget );
ResetNextAttackCounter( pAttacker );
pAttacker->usNextAttack += (UINT32)iTime; //tack on the remainder
++iAttacksThisFrame;
}
}
}
if( iTimeSlice != 0x7fffffff )//|| !gpAR->fInstantFinish )
{
iTimeSlice -= 1000;
}
}
}
BOOLEAN IsAutoResolveActive()
{
//is the autoresolve up or not?
if( gpAR )
{
return TRUE;
}
return FALSE;
}
UINT8 GetAutoResolveSectorID()
{
if( gpAR )
{
return (UINT8)SECTOR( gpAR->ubSectorX, gpAR->ubSectorY );
}
return 0xff;
}
//Returns TRUE if a battle is happening or sector is loaded
BOOLEAN GetCurrentBattleSectorXYZ( INT16 *psSectorX, INT16 *psSectorY, INT16 *psSectorZ )
{
if( gpAR )
{
*psSectorX = gpAR->ubSectorX;
*psSectorY = gpAR->ubSectorY;
*psSectorZ = 0;
return TRUE;
}
else if( gfPreBattleInterfaceActive )
{
*psSectorX = gubPBSectorX;
*psSectorY = gubPBSectorY;
*psSectorZ = gubPBSectorZ;
return TRUE;
}
else if( gfWorldLoaded )
{
*psSectorX = gWorldSectorX;
*psSectorY = gWorldSectorY;
*psSectorZ = gbWorldSectorZ;
return TRUE;
}
else
{
*psSectorX = 0;
*psSectorY = 0;
*psSectorZ = -1;
return FALSE;
}
}
//Returns TRUE if a battle is happening ONLY
BOOLEAN GetCurrentBattleSectorXYZAndReturnTRUEIfThereIsABattle( INT16 *psSectorX, INT16 *psSectorY, INT16 *psSectorZ )
{
if ( ( psSectorX == NULL )||(psSectorY == NULL )||(psSectorZ == NULL ) )
{
return FALSE;
}
if( gpAR )
{
*psSectorX = gpAR->ubSectorX;
*psSectorY = gpAR->ubSectorY;
*psSectorZ = 0;
return TRUE;
}
else if( gfPreBattleInterfaceActive )
{
*psSectorX = gubPBSectorX;
*psSectorY = gubPBSectorY;
*psSectorZ = gubPBSectorZ;
return TRUE;
}
else if( gfWorldLoaded )
{
*psSectorX = gWorldSectorX;
*psSectorY = gWorldSectorY;
*psSectorZ = gbWorldSectorZ;
if( gTacticalStatus.fEnemyInSector )
{
return TRUE;
}
return FALSE;
}
else
{
*psSectorX = 0;
*psSectorY = 0;
*psSectorZ = -1;
return FALSE;
}
}
void AutoBandageFinishedCallback( UINT8 ubResult )
{
SetupDoneInterface();
}
BOOLEAN ProcessLoyalty()
{
if( !gpAR ) return FALSE;
if( PlayerMercsInSector( gpAR->ubSectorX, gpAR->ubSectorY, 0 ) )
return TRUE;
if( GetTownIdForSector( gpAR->ubSectorX, gpAR->ubSectorY ) != BLANK_SECTOR ||
IsThisSectorASAMSector( gpAR->ubSectorX, gpAR->ubSectorY, 0 ) )
return TRUE;
return FALSE;
}
void CheckForSoldiersWhoRetreatedIntoMilitiaHeldSectors()
{
for( int sX = 1; sX < ( MAP_WORLD_X - 1 ); ++sX )
{
for( int sY = 1; sY < ( MAP_WORLD_Y - 1); ++sY )
{
// Check if there is a sector where enemies retreated to and there are also militia present
if ( (NumNonPlayerTeamMembersInSector( sX, sY, ENEMY_TEAM ) > 0) &&
(NumNonPlayerTeamMembersInSector( sX, sY, MILITIA_TEAM ) > 0) &&
(!gTacticalStatus.fEnemyInSector))
{
unsigned mercCnt = 0;
for( int i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; ++i )
{
if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->stats.bLife && !(MercPtrs[ i ]->flags.uiStatusFlags & SOLDIER_VEHICLE) && !AM_A_ROBOT( MercPtrs[ i ] ) )
{
//Merc is active and alive, and not a vehicle or robot
if ((MercPtrs[ i ]->sSectorX == sX) &&(MercPtrs[ i ]->sSectorY == sY) && (MercPtrs[ i ]->bSectorZ == 0))
{
++mercCnt;
}
}
}
// If there are PC mercs here the player will have to handle the battle
if (mercCnt == 0)
{
// EnterAutoResolveMode ((UINT8)sX, (UINT8)sY);
}
}
}
}
}
// Flugente: have all militia in autoresolve drop their gear and be promoted in necessary
void AutoResolveMilitiaDropAndPromote()
{
gbGreenToElitePromotions = 0;
gbGreenToRegPromotions = 0;
gbRegToElitePromotions = 0;
gbMilitiaPromotions = 0;
for ( INT32 i = 0; i < MAX_AR_TEAM_SIZE; ++i )
{
if ( gpCivs[i].pSoldier )
{
UINT8 ubCurrentRank = GREEN_MILITIA;
switch ( gpCivs[i].pSoldier->ubSoldierClass )
{
case SOLDIER_CLASS_GREEN_MILITIA: ubCurrentRank = GREEN_MILITIA; break;
case SOLDIER_CLASS_REG_MILITIA: ubCurrentRank = REGULAR_MILITIA; break;
case SOLDIER_CLASS_ELITE_MILITIA: ubCurrentRank = ELITE_MILITIA; break;
default:
#ifdef JA2BETAVERSION
ScreenMsg( FONT_RED, MSG_ERROR, L"Removing autoresolve militia with invalid ubSoldierClass %d.", gpCivs[i].pSoldier->ubSoldierClass );
#endif
break;
}
// Flugente: drop sector equipment
gpCivs[i].pSoldier->DropSectorEquipment( );
if ( gpCivs[i].pSoldier->stats.bLife < OKLIFE / 2 )
{
// Flugente: individual militia
// we not only handle promotions here, but basically update this guy
MILITIA militia;
if ( GetMilitia( gpCivs[i].pSoldier->usIndividualMilitiaID, &militia ) && !(militia.flagmask & MILITIAFLAG_DEAD) )
{
MILITIA_BATTLEREPORT report;
report.id = GetIdOfCurrentlyOngoingIncident( );
report.flagmask |= MILITIA_BATTLEREPORT_FLAG_DIED;
if ( gpCivs[i].pSoldier->ubMilitiaKills )
report.flagmask |= MILITIA_BATTLEREPORT_FLAG_KILLEDENEMY;
militia.history.push_back( report );
militia.healthratio = 0.0f;
militia.flagmask |= MILITIAFLAG_DEAD;
UpdateMilitia( militia );
}
StrategicRemoveMilitiaFromSector( gpCivs[i].pSoldier->sSectorX, gpCivs[i].pSoldier->sSectorY, ubCurrentRank, 1 );
}
else if ( gpCivs[i].pSoldier->stats.bLife >= OKLIFE / 2 )
{
// Flugente: take care of promotions and individual militia update
HandlePossibleMilitiaPromotion( gpCivs[i].pSoldier, TRUE );
}
// DO NOT DELETE HERE!!!!
//TacticalRemoveSoldierPointer( gpCivs[i].pSoldier, FALSE );
//memset( &gpCivs[i], 0, sizeof(SOLDIERCELL) );
}
}
}
BOOLEAN IndividualMilitiaInUse_AutoResolve( UINT32 aMilitiaId )
{
if ( gpAR )
{
for ( INT32 i = 0; i < gpAR->ubCivs; ++i )
{
if ( gpCivs[i].pSoldier && aMilitiaId == gpCivs[i].pSoldier->usIndividualMilitiaID && IsLegalMilitiaId( gpCivs[i].pSoldier->usIndividualMilitiaID ) )
{
return TRUE;
}
}
}
return FALSE;
}