Files
source/TacticalAI/Attacks.cpp
T

2495 lines
82 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "AI All.h"
#else
#include "ai.h"
#include "Weapons.h"
#include "opplist.h"
#include "AIInternals.h"
#include "LOS.h"
#include "Physics.h"
#include "Items.h"
#include "Weapons.h"
#include "Spread Burst.h"
#include "overhead.h"
#include "SkillCheck.h"
#include "Soldier Profile.h"
#include "Isometric Utils.h"
#include "Soldier macros.h"
#include "PATHAI.H"
#include "GameSettings.h"
#include "strategicmap.h"
#include "environment.h"
#include "lighting.h"
#include "Sound Control.h"
#endif
extern INT16 DirIncrementer[8];
//
// CJC DG->JA2 conversion notes
//
// Still commented out:
//
// EstimateShotDamage - stuff related to legs?
// EstimateStabDamage - stuff related to armour
// EstimateThrowDamage - waiting for grenade, armour definitions
// CheckIfTossPossible - waiting for grenade definitions
// this define should go in soldier control.h
void LoadWeaponIfNeeded(SOLDIERTYPE *pSoldier)
{
UINT16 usInHand;
INT8 bPayloadPocket;
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("LoadWeaponIfNeeded"));
usInHand = pSoldier->inv[HANDPOS].usItem;
if ( IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS]) )
usInHand = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
// if he's got a MORTAR in his hand, make sure he has a MORTARSHELL avail.
if (Item[usInHand].mortar )
{
// bPayloadPocket = FindObj( pSoldier, MORTAR_SHELL );
bPayloadPocket = FindLaunchable( pSoldier, usInHand );
if (bPayloadPocket == NO_SLOT)
{
#ifdef BETAVERSION
NumMessage("LoadWeaponIfNeeded: ERROR - no mortar shells found to load MORTAR! Guynum",pSoldier->ubID);
#endif
return; // no shells, can't fire the MORTAR
}
}
// if he's got a GL in his hand, make sure he has some type of GRENADE avail.
else if (Item[usInHand].grenadelauncher )
{
bPayloadPocket = FindGLGrenade( pSoldier );
if (bPayloadPocket == NO_SLOT || FindNonSmokeLaunchableAttachment( &pSoldier->inv[HANDPOS],usInHand ) != ITEM_NOT_FOUND )
{
#ifdef BETAVERSION
NumMessage("LoadWeaponIfNeeded: ERROR - no grenades found to load GLAUNCHER! Guynum",pSoldier->ubID);
#endif
return; // no grenades, can't fire the GLAUNCHER... or the launcher has a magsize > 1
}
}
// if he's got a RPG7 in his hand, make sure he has some type of RPG avail.
else if (Item[usInHand].rocketlauncher && !Item[usInHand].singleshotrocketlauncher )
{
bPayloadPocket = FindLaunchable (pSoldier, usInHand );
if (bPayloadPocket == NO_SLOT)
{
return; // no grenades, can't fire
}
}
else if (Item[usInHand].cannon )
{
bPayloadPocket = FindLaunchable( pSoldier, usInHand );
if (bPayloadPocket == NO_SLOT)
{
return;
}
}
else
{
// regular hand-thrown grenade in hand, nothing to load!
return;
}
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("LoadWeaponIfNeeded: remove payload from its pocket, and add it as the hand weapon's first attachment"));
// remove payload from its pocket, and add it as the hand weapon's first attachment
OBJECTTYPE Temp;
CreateItem(pSoldier->inv[bPayloadPocket].usItem,pSoldier->inv[bPayloadPocket].bStatus[0],&Temp);
AttachObject ( pSoldier, &pSoldier->inv[HANDPOS],&Temp,FALSE);
//pSoldier->inv[HANDPOS].usAttachItem[0] = pSoldier->inv[bPayloadPocket].usItem;
//pSoldier->inv[HANDPOS].bAttachStatus[0] = pSoldier->inv[bPayloadPocket].bStatus[0];
if ( TANK( pSoldier ) )
{
// don't remove ammo
return;
}
// if there's only one in payload pocket (only/last grenade, or any shell)
if ((Item[ pSoldier->inv[bPayloadPocket].usItem ].ubPerPocket == 1) || (pSoldier->inv[bPayloadPocket].ubNumberOfObjects == 1))
{
DeleteObj(&(pSoldier->inv[bPayloadPocket]));
}
else // multiple grenades, remove one of them
{
pSoldier->inv[bPayloadPocket].ubNumberOfObjects--;
}
}
void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUnseen)
{
UINT32 uiLoop;
INT32 iAttackValue;
INT32 iThreatValue;
INT32 iHitRate,iBestHitRate,iPercentBetter;
INT32 iEstDamage;
UINT8 ubRawAPCost,ubMinAPcost,ubMaxPossibleAimTime,ubAimTime,ubBestAimTime;
UINT8 ubChanceToHit,ubChanceToGetThrough,ubChanceToReallyHit,ubBestChanceToHit = 0;
SOLDIERTYPE *pOpponent;
UINT8 ubBurstAPs;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalcBestShot");
ubBestChanceToHit = ubBestAimTime = ubChanceToHit = 0;
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
pSoldier->bWeaponMode = WM_NORMAL;
ubBurstAPs = CalcAPsToBurst( CalcActionPoints( pSoldier ), &(pSoldier->inv[HANDPOS]) );
InitAttackType(pBestShot); // set all structure fields to defaults
// hang a pointer into active soldier's personal opponent list
//pbPersOL = &(pSoldier->bOppList[0]);
// determine which attack against which target has the greatest attack value
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pOpponent = MercSlots[ uiLoop ];
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent || !pOpponent->bLife)
continue; // next merc
// if this man is neutral / on the same side, he's not an opponent
if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
continue; // next merc
// if this opponent is not currently in sight (ignore known but unseen!)
if ((pSoldier->bOppList[pOpponent->ubID] != SEEN_CURRENTLY && !shootUnseen)) //guys we can't see
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("CalcBestShot: soldier = %d, target = %d, skip guys we can't see, shootUnseen = %d, personal opplist = %d",pSoldier->ubID, pOpponent->ubID, shootUnseen, pSoldier->bOppList[pOpponent->ubID]));
continue; // next opponent
}
if ((pSoldier->bOppList[pOpponent->ubID] != SEEN_CURRENTLY && gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID] != SEEN_CURRENTLY)) // guys nobody sees
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("CalcBestShot: soldier = %d, target = %d, skip guys nobody sees, shootUnseen = %d, public opplist = %d",pSoldier->ubID, pOpponent->ubID, shootUnseen,gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID]));
continue; // next opponent
}
// Special stuff for Carmen the bounty hunter
if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
continue; // next opponent
#ifdef DEBUGATTACKS
DebugAI( String( "%s sees %s at gridno %d\n",pSoldier->name,ExtMen[pOpponent->ubID].name,pOpponent->sGridNo ) );
#endif
// calculate minimum action points required to shoot at this opponent
// if ( !Weapon[pSoldier->usAttackingWeapon].NoSemiAuto )
ubMinAPcost = MinAPsToAttack(pSoldier,pOpponent->sGridNo,ADDTURNCOST);
// What the.... The APs to attack on the HandleItem side does not make a test like this. It always uses the MinAPs as a base.
// else
// ubMinAPcost = CalcAPsToAutofire( CalcActionPoints( pSoldier ), &(pSoldier->inv[HANDPOS]), 3 );
//NumMessage("MinAPcost to shoot this opponent = ",ubMinAPcost);
// if we don't have enough APs left to shoot even a snap-shot at this guy
if (ubMinAPcost > pSoldier->bActionPoints)
continue; // next opponent
//KeepInterfaceGoing();
// calculate chance to get through the opponent's cover (if any)
ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough( pSoldier, pOpponent );
// ubChanceToGetThrough = ChanceToGetThrough(pSoldier,pOpponent->sGridNo,NOTFAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS);
//NumMessage("Chance to get through = ",ubChanceToGetThrough);
// if we can't possibly get through all the cover
if (ubChanceToGetThrough == 0)
continue; // next opponent
if ( (pSoldier->uiStatusFlags & SOLDIER_MONSTER) && (pSoldier->ubBodyType != QUEENMONSTER ) )
{
STRUCTURE_FILE_REF * pStructureFileRef;
UINT16 usAnimSurface;
usAnimSurface = DetermineSoldierAnimationSurface( pSoldier, pSoldier->usUIMovementMode );
pStructureFileRef = GetAnimationStructureRef( pSoldier->ubID, usAnimSurface,pSoldier->usUIMovementMode );
if ( pStructureFileRef )
{
UINT16 usStructureID;
INT8 bDir;
// must make sure that structure data can be added in the direction of the target
bDir = (INT8) GetDirectionToGridNoFromGridNo( pSoldier->sGridNo, pOpponent->sGridNo );
// ATE: Only if we have a levelnode...
if ( pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL )
{
usStructureID = pSoldier->pLevelNode->pStructureData->usStructureID;
}
else
{
usStructureID = INVALID_STRUCTURE_ID;
}
if ( ! OkayToAddStructureToWorld( pSoldier->sGridNo, pSoldier->bLevel, &(pStructureFileRef->pDBStructureRef[ gOneCDirection[ bDir ] ]), usStructureID ) )
{
// can't turn in that dir.... next opponent
continue;
}
}
}
// calc next attack's minimum shooting cost (excludes readying & turning)
ubRawAPCost = MinAPsToShootOrStab(pSoldier,pOpponent->sGridNo,FALSE);
if (pOpponent->sGridNo == pSoldier->sLastTarget)
{
// raw AP cost calculation included cost of changing target!
ubRawAPCost -= AP_CHANGE_TARGET;
}
iBestHitRate = 0; // reset best hit rate to minimum
// calculate the maximum possible aiming time
if ( TANK( pSoldier ) )
{
ubMaxPossibleAimTime = pSoldier->bActionPoints - ubMinAPcost;
// always burst
if ( ubMaxPossibleAimTime < ubBurstAPs )
{
// should this be a return instead?
continue;
}
// bursts aren't aimed
ubMaxPossibleAimTime = 0;
}
else
{
ubMaxPossibleAimTime = min(AllowedAimingLevels(pSoldier),pSoldier->bActionPoints - ubMinAPcost);
}
// consider the various aiming times
if ( !Weapon[pSoldier->usAttackingWeapon].NoSemiAuto )
{
for (ubAimTime = AP_MIN_AIM_ATTACK; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
{
//HandleMyMouseCursor(KEYBOARDALSO);
//NumMessage("ubAimTime = ",ubAimTime);
ubChanceToHit = (UINT8) AICalcChanceToHitGun(pSoldier,pOpponent->sGridNo,ubAimTime, AIM_SHOT_TORSO);
// ExtMen[pOpponent->ubID].haveStats = TRUE;
//NumMessage("chance to Hit = ",ubChanceToHit);
//sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].name,ubAimTime,ubChanceToHit);
//PopMessage(tempstr);
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubAimTime);
//NumMessage("hitRate = ",iHitRate);
// if aiming for this amount of time produces a better hit rate
if (iHitRate > iBestHitRate)
{
iBestHitRate = iHitRate;
ubBestAimTime = ubAimTime;
ubBestChanceToHit = ubChanceToHit;
}
}
}
else
{
ubAimTime = AP_MIN_AIM_ATTACK;
ubChanceToHit = (UINT8) AICalcChanceToHitGun(pSoldier,pOpponent->sGridNo,ubAimTime, AIM_SHOT_TORSO);
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubAimTime);
iBestHitRate = iHitRate;
ubBestAimTime = ubAimTime;
ubBestChanceToHit = ubChanceToHit;
}
// if we can't get any kind of hit rate at all
if (iBestHitRate == 0)
continue; // next opponent
// calculate chance to REALLY hit: shoot accurately AND get past cover
ubChanceToReallyHit = (ubBestChanceToHit * ubChanceToGetThrough) / 100;
// if we can't REALLY hit at all
if (ubChanceToReallyHit == 0)
continue; // next opponent
// really limit knife throwing so it doesn't look wrong
if ( Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE && (ubChanceToReallyHit < 30 || ( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) > CalcMaxTossRange( pSoldier, pSoldier->usAttackingWeapon, FALSE ) )))// Madd / 2 ) ) )
continue; // don't bother... next opponent
// calculate this opponent's threat value (factor in my cover from him)
iThreatValue = CalcManThreatValue(pOpponent,pSoldier->sGridNo,TRUE,pSoldier);
// estimate the damage this shot would do to this opponent
iEstDamage = EstimateShotDamage(pSoldier,pOpponent,ubBestChanceToHit);
//NumMessage("SHOT EstDamage = ",iEstDamage);
// calculate the combined "attack value" for this opponent
// highest possible value before division should be about 1.8 billion...
// normal value before division should be about 5 million...
iAttackValue = (iEstDamage * iBestHitRate * ubChanceToReallyHit * iThreatValue) / 1000;
//NumMessage("SHOT AttackValue = ",iAttackValue / 1000);
// special stuff for assassins to ignore militia more
if ( pSoldier->ubProfile >= JIM && pSoldier->ubProfile <= TYRONE && pOpponent->bTeam == MILITIA_TEAM )
{
iAttackValue /= 2;
}
#ifdef DEBUGATTACKS
DebugAI( String( "CalcBestShot: best AttackValue vs %d = %d\n",uiLoop,iAttackValue ) );
#endif
// if we can hurt the guy, OR probably not, but at least it's our best
// chance to actually hit him and maybe scare him, knock him down, etc.
if ((iAttackValue > 0) || (ubChanceToReallyHit > pBestShot->ubChanceToReallyHit))
{
// if there already was another viable target
if (pBestShot->ubChanceToReallyHit > 0)
{
// OK, how does our chance to hit him compare to the previous best one?
iPercentBetter = ((ubChanceToReallyHit * 100) / pBestShot->ubChanceToReallyHit) - 100;
// if this chance to really hit is more than 50% worse, and the other
// guy is conscious at all
if ((iPercentBetter < -PERCENT_TO_IGNORE_THREAT) && (Menptr[pBestShot->ubOpponent].bLife >= OKLIFE))
// then stick with the older guy as the better target
continue;
// if this chance to really hit between 50% worse to 50% better
if (iPercentBetter < PERCENT_TO_IGNORE_THREAT)
{
// then the one with the higher ATTACK VALUE is the better target
if (iAttackValue < pBestShot->iAttackValue)
// the previous guy is more important since he's more dangerous
continue; // next opponent
}
}
// OOOF! That was a lot of work! But we've got a new best target!
pBestShot->ubPossible = TRUE;
pBestShot->ubOpponent = pOpponent->ubID;
pBestShot->ubAimTime = ubBestAimTime;
pBestShot->ubChanceToReallyHit = ubChanceToReallyHit;
pBestShot->sTarget = pOpponent->sGridNo;
pBestShot->bTargetLevel = pOpponent->bLevel;
pBestShot->iAttackValue = iAttackValue;
pBestShot->ubAPCost = ubMinAPcost;
}
}
}
// JA2Gold: added
BOOLEAN CloseEnoughForGrenadeToss( INT16 sGridNo, INT16 sGridNo2 )
{
INT16 sTempGridNo;
INT8 bDirection;
INT16 sXPos, sYPos, sXPos2, sYPos2;
UINT8 ubMovementCost;
if (sGridNo == sGridNo2 )
{
// checking the same space; if there is a closed door next to location in ANY direction then forget it
// (could be the player closed a door on us)
for (bDirection = 0; bDirection < NUM_WORLD_DIRECTIONS; bDirection++)
{
sTempGridNo = NewGridNo( sGridNo, DirectionInc( bDirection ) );
ubMovementCost = gubWorldMovementCosts[ sTempGridNo ][ bDirection ][ 0 ];
if ( IS_TRAVELCOST_DOOR( ubMovementCost ) )
{
ubMovementCost = DoorTravelCost( NULL, sTempGridNo, ubMovementCost, FALSE, NULL );
}
if ( ubMovementCost >= TRAVELCOST_BLOCKED)
{
return( FALSE );
}
}
}
else
{
if ( CardinalSpacesAway( sGridNo, sGridNo2 ) > 2 )
{
return( FALSE );
}
// we are within 1 space diagonally or at most 2 horizontally or vertically,
// so we can now do a loop safely
sTempGridNo = sGridNo;
sXPos = CenterX( sGridNo );
sYPos = CenterY( sGridNo );
sXPos2 = CenterX( sGridNo2 );
sYPos2 = CenterY( sGridNo2 );
bDirection = atan8( sXPos, sYPos, sXPos2, sYPos2 );
// For each step of the loop, we are checking for door or obstacle movement costs. If we
// find we're blocked, then this is no good for grenade tossing!
do
{
sTempGridNo = NewGridNo( sTempGridNo, DirectionInc( bDirection ) );
ubMovementCost = gubWorldMovementCosts[ sTempGridNo ][ bDirection ][ 0 ];
if ( IS_TRAVELCOST_DOOR( ubMovementCost ) )
{
ubMovementCost = DoorTravelCost( NULL, sTempGridNo, ubMovementCost, FALSE, NULL );
}
if ( ubMovementCost >= TRAVELCOST_BLOCKED)
{
return( FALSE );
}
} while( sTempGridNo != sGridNo2 );
}
return( TRUE );
}
void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow");
// September 9, 1998: added code for LAWs (CJC)
UINT8 ubLoop, ubLoop2;
INT32 iAttackValue;
INT32 iHitRate, iThreatValue, iTotalThreatValue,iOppThreatValue[MAXMERCS];
INT16 sGridNo, sEndGridNo, sFriendTile[MAXMERCS], sOpponentTile[MAXMERCS];
INT8 bFriendLevel[MAXMERCS], bOpponentLevel[MAXMERCS];
INT32 iEstDamage;
UINT8 ubFriendCnt = 0,ubOpponentCnt = 0, ubOpponentID[MAXMERCS];
UINT8 ubRawAPCost,ubMinAPcost,ubMaxPossibleAimTime;
UINT8 ubChanceToHit,ubChanceToGetThrough,ubChanceToReallyHit;
UINT32 uiPenalty;
UINT8 ubSearchRange;
UINT16 usOppDist;
BOOLEAN fFriendsNearby;
UINT16 usInHand, usGrenade;
UINT8 ubOppsInRange, ubOppsAdjacent;
BOOLEAN fSkipLocation;
INT8 bPayloadPocket;
INT8 bMaxLeft,bMaxRight,bMaxUp,bMaxDown,bXOffset,bYOffset;
INT8 bPersOL, bPublOL;
SOLDIERTYPE *pOpponent, *pFriend;
static INT16 sExcludeTile[100]; // This array is for storing tiles that we have
UINT8 ubNumExcludedTiles = 0; // already considered, to prevent duplication of effort
INT32 iTossRange;
UINT8 ubSafetyMargin = 0;
UINT8 ubDiff;
INT8 bEndLevel;
usInHand = pSoldier->inv[HANDPOS].usItem;
usGrenade = NOTHING;
if ( IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS]) )
usInHand = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
if ( EXPLOSIVE_GUN( usInHand ) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"explosive gun");
iTossRange = Weapon[ usInHand ].usRange / CELL_X_SIZE;
}
else
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to calcmaxtossrange");
iTossRange = CalcMaxTossRange( pSoldier, usInHand, TRUE );
}
// if he's got a MORTAR in his hand, make sure he has a MORTARSHELL avail.
if (Item[usInHand].mortar )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a mortar");
// bPayloadPocket = FindObj( pSoldier, MORTAR_SHELL );
bPayloadPocket = FindLaunchable ( pSoldier, usInHand );
if (bPayloadPocket == NO_SLOT)
{
return; // no shells, can't fire the MORTAR
}
ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[bPayloadPocket].usItem ].ubClassIndex ].ubRadius;
}
// if he's got a GL in his hand, make sure he has some type of GRENADE avail.
else if (Item[usInHand].grenadelauncher )
{
// use up pocket 2 first, they get left as drop items
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a GL");
bPayloadPocket = FindGLGrenade( pSoldier );
if (bPayloadPocket == NO_SLOT)
{
return; // no grenades, can't fire the GLAUNCHER
}
ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[ bPayloadPocket ].usItem ].ubClassIndex ].ubRadius;
usGrenade = pSoldier->inv[ bPayloadPocket ].usItem;
}
else if ( Item[usInHand].rocketlauncher )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a rocket launcher");
// put in hand
if ( Item[usInHand].singleshotrocketlauncher )
// as C1
ubSafetyMargin = (UINT8)Explosive[ Item[ C1 ].ubClassIndex ].ubRadius;
else
{
bPayloadPocket = FindLaunchable ( pSoldier, usInHand );
if (bPayloadPocket == NO_SLOT)
{
return; // no ammo, can't fire
}
ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[bPayloadPocket].usItem ].ubClassIndex ].ubRadius;
}
}
else if (Item[usInHand].cannon )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a tank");
bPayloadPocket = FindLaunchable ( pSoldier, usInHand );
if (bPayloadPocket == NO_SLOT)
{
return; // no ammo, can't fire
}
ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[bPayloadPocket].usItem ].ubClassIndex ].ubRadius;
//bPayloadPocket = FindObj( pSoldier, TANK_SHELL );
//if (bPayloadPocket == NO_SLOT)
//{
// return; // no grenades, can't fire the GLAUNCHER
//}
//ubSafetyMargin = Explosive[ Item[ TANK_SHELL ].ubClassIndex ].ubRadius;
}
else
{
// else it's a plain old grenade, now in his hand
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: buddy's got a grenade");
bPayloadPocket = HANDPOS;
ubSafetyMargin = (UINT8)Explosive[ Item[ pSoldier->inv[ bPayloadPocket ].usItem ].ubClassIndex ].ubRadius;
usGrenade = pSoldier->inv[ bPayloadPocket ].usItem;
if ( Item[usGrenade].flare )
{
// JA2Gold: light isn't as nasty as explosives
ubSafetyMargin /= 2;
}
}
ubDiff = SoldierDifficultyLevel( pSoldier );
// make a list of tiles one's friends are positioned in
for (ubLoop = 0; ubLoop < guiNumMercSlots; ubLoop++)
{
pFriend = MercSlots[ubLoop];
if ( !pFriend )
{
continue; // next soldier
}
if (pFriend->bLife == 0)
{
continue;
}
/*
// if this soldier is inactive, at base, on assignment, or dead
if (!Menptr[ubLoop].bActive || !Menptr[ubLoop].bInSector || !Menptr[ubLoop].bLife)
continue; // next soldier
*/
// if this man is neutral / NOT on the same side, he's not a friend
if (pFriend->bNeutral || (pSoldier->bSide != pFriend->bSide))
{
continue; // next soldier
}
// active friend, remember where he is so that we DON'T blow him up!
// this includes US, since we don't want to blow OURSELVES up either
sFriendTile[ubFriendCnt] = pFriend->sGridNo;
bFriendLevel[ubFriendCnt] = pFriend->bLevel;
ubFriendCnt++;
}
//NumMessage("ubFriendCnt = ",ubFriendCnt);
// make a list of tiles one's CURRENTLY SEEN opponents are positioned in
for (ubLoop = 0; ubLoop < guiNumMercSlots; ubLoop++)
{
pOpponent = MercSlots[ubLoop];
if (!pOpponent)
{
// inactive or not in sector
continue; // next soldier
}
if (!pOpponent->bLife)
{
continue; // next soldier
}
/*
// if this soldier is inactive, at base, on assignment, or dead
if (!pOpponent->bActive || !pOpponent->bInSector || !pOpponent->bLife)
continue; // next soldier
*/
// if this man is neutral / on the same side, he's not an opponent
if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
{
continue; // next soldier
}
// Special stuff for Carmen the bounty hunter
if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != 64)
{
continue; // next opponent
}
bPersOL = pSoldier->bOppList[pOpponent->ubID];
if ((Item[usInHand].mortar ) || (Item[usInHand].grenadelauncher ) )
{
bPublOL = gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID];
// allow long range firing, where target doesn't PERSONALLY see opponent
if ((bPersOL != SEEN_CURRENTLY) && (bPublOL != SEEN_CURRENTLY))
{
continue; // next soldier
}
// active KNOWN opponent, remember where he is so that we DO blow him up!
sOpponentTile[ubOpponentCnt] = pOpponent->sGridNo;
bOpponentLevel[ubOpponentCnt] = pOpponent->bLevel;
}
else
{
/*
if (bPersOL != SEEN_CURRENTLY && bPersOL != SEEN_LAST_TURN) // if not in sight right now
{
continue; // next soldier
}
*/
if (bPersOL == SEEN_CURRENTLY)
{
// active KNOWN opponent, remember where he is so that we DO blow him up!
sOpponentTile[ubOpponentCnt] = pOpponent->sGridNo;
bOpponentLevel[ubOpponentCnt] = pOpponent->bLevel;
}
else if (bPersOL == SEEN_LAST_TURN)
{
// cheat; only allow throw if person is REALLY within 2 tiles of where last seen
// JA2Gold: UB checks were screwed up
/*
if ( pOpponent->sGridNo == gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] )
{
continue;
}
else */
if ( !CloseEnoughForGrenadeToss( pOpponent->sGridNo, gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) )
{
continue;
}
sOpponentTile[ubOpponentCnt] = gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ];
bOpponentLevel[ubOpponentCnt] = gbLastKnownOppLevel[ pSoldier->ubID ][ pOpponent->ubID ];
// JA2Gold: commented out
/*
if ( SpacesAway( pOpponent->sGridNo, gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) > 2 )
{
continue;
}
sOpponentTile[ubOpponentCnt] = gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ];
bOpponentLevel[ubOpponentCnt] = gbLastKnownOppLevel[ pSoldier->ubID ][ pOpponent->ubID ];
*/
}
else if (bPersOL == HEARD_LAST_TURN )
{
// cheat; only allow throw if person is REALLY within 2 tiles of where last seen
// screen out some ppl who have thrown
if ( PreRandom( 3 ) == 0 )
{
continue;
}
// Weird detail: if the opponent is in the same location then they may have closed a door on us.
// In which case, don't throw!
// JA2Gold: UB checks were screwed up
/*
if ( pOpponent->sGridNo == gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] )
{
continue;
}
else
*/
if ( !CloseEnoughForGrenadeToss( pOpponent->sGridNo, gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ] ) )
{
continue;
}
if ( !Item[usGrenade].flare && !pSoldier->bUnderFire && pSoldier->bShock == 0 )
{
continue;
}
sOpponentTile[ubOpponentCnt] = gsLastKnownOppLoc[ pSoldier->ubID ][ pOpponent->ubID ];
bOpponentLevel[ubOpponentCnt] = gbLastKnownOppLevel[ pSoldier->ubID ][ pOpponent->ubID ];
}
else
{
continue;
}
}
// also remember who he is (which soldier #)
ubOpponentID[ubOpponentCnt] = pOpponent->ubID;
// remember how relatively dangerous this opponent is (ignore my cover)
iOppThreatValue[ubOpponentCnt] = CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier);
ubOpponentCnt++;
}
//NumMessage("ubOpponentCnt = ",ubOpponentCnt);
// this is try to minimize enemies wasting their (limited) toss attacks, with the exception of break lights
if ( !Item[usGrenade].flare )
{
switch( ubDiff )
{
case 0:
case 1:
// they won't use them until they have 2+ opponents as long as half life left
if ((ubOpponentCnt < 2) && (pSoldier->bLife > (pSoldier->bLifeMax / 2)))
{
return;
}
break;
case 2:
// they won't use them until they have 2+ opponents as long as 3/4 life left
if ((ubOpponentCnt < 2) && (pSoldier->bLife > (pSoldier->bLifeMax / 4) * 3 ))
{
return;
}
break;
break;
default:
break;
}
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: about to initattacktype");
InitAttackType(pBestThrow); // set all structure fields to defaults
// look at the squares near each known opponent and try to find the one
// place where a tossed projectile would do the most harm to the opponents
// while avoiding one's friends
for (ubLoop = 0; ubLoop < ubOpponentCnt; ubLoop++)
{
//NumMessage("Checking Guy#",ubOpponentID[ubLoop]);
// search all tiles within 2 squares of this opponent
ubSearchRange = MAX_TOSS_SEARCH_DIST;
// determine maximum horizontal limits
//bMaxLeft = min(ubSearchRange,(sOpponentTile[ubLoop] % MAXCOL));
bMaxLeft = ubSearchRange;
//NumMessage("bMaxLeft = ",bMaxLeft);
//bMaxRight = min(ubSearchRange,MAXCOL - ((sOpponentTile[ubLoop] % MAXCOL) + 1));
bMaxRight = ubSearchRange;
//NumMessage("bMaxRight = ",bMaxRight);
// determine maximum vertical limits
bMaxUp = ubSearchRange;
//NumMessage("bMaxUp = ",bMaxUp);
bMaxDown = ubSearchRange;
//NumMessage("bMaxDown = ",bMaxDown);
// evaluate every tile for its opponent-damaging potential
for (bYOffset = -bMaxUp; bYOffset <= bMaxDown; bYOffset++)
{
for (bXOffset = -bMaxLeft; bXOffset <= bMaxRight; bXOffset++)
{
//HandleMyMouseCursor(KEYBOARDALSO);
// calculate the next potential gridno near this opponent
sGridNo = sOpponentTile[ubLoop] + bXOffset + (MAXCOL * bYOffset);
//NumMessage("Testing gridno #",sGridNo);
// this shouldn't ever happen
if ((sGridNo < 0) || (sGridNo >= GRIDSIZE))
{
#ifdef BETAVERSION
NumMessage("CalcBestThrow: ERROR - invalid gridno being tested ",sGridNo);
#endif
continue;
}
if ( PythSpacesAway( pSoldier->sGridNo, sGridNo ) > iTossRange )
{
// can't throw there!
return;
}
// if considering a gas/smoke grenade, check to see if there is such stuff already there!
if ( usGrenade )
{
// if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_SMOKE ||
// gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) ||
// gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_MUSTARDGAS)
if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_SMOKE ||
gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS | MAPELEMENT_EXT_BURNABLEGAS) ||
gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_MUSTARDGAS || gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_BURNABLEGAS)
{
continue;
}
//switch( usGrenade )
//{
// case SMOKE_GRENADE:
// case GL_SMOKE_GRENADE:
// // skip smoke
// if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_SMOKE )
// {
// continue;
// }
// break;
// case TEARGAS_GRENADE:
// // skip tear and mustard gas
// if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & (MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) )
// {
// continue;
// }
// break;
// case MUSTARD_GRENADE:
// // skip mustard gas
// if ( gpWorldLevelData[ sGridNo ].ubExtFlags[ bOpponentLevel[ubLoop] ] & MAPELEMENT_EXT_MUSTARDGAS )
// {
// continue;
// }
// break;
// default:
// break;
//}
}
fSkipLocation = FALSE;
// Check to see if we have considered this tile before:
for (ubLoop2 = 0; ubLoop2 < ubNumExcludedTiles; ubLoop2++)
{
if (sExcludeTile[ubLoop2] == sGridNo)
{
// already checked!
fSkipLocation = TRUE;
break;
}
}
if (fSkipLocation)
{
continue;
}
// calculate minimum action points required to throw at this gridno
ubMinAPcost = MinAPsToAttack(pSoldier,sGridNo,ADDTURNCOST);
DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("MinAPcost to attack = %d",ubMinAPcost));
// if we don't have enough APs left to throw even without aiming
DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Soldier's action points = %d",pSoldier->bActionPoints ));
if (ubMinAPcost > pSoldier->bActionPoints)
continue; // next gridno
// check whether there are any friends standing near this gridno
fFriendsNearby = FALSE;
for (ubLoop2 = 0; ubLoop2 < ubFriendCnt; ubLoop2++)
{
if ( (bFriendLevel[ubLoop2] == bOpponentLevel[ubLoop]) && ( PythSpacesAway(sFriendTile[ubLoop2],sGridNo) <= ubSafetyMargin ) )
{
//NumMessage("Friend too close: at gridno",sFriendTile[ubLoop2]);
fFriendsNearby = TRUE;
break; // don't bother checking any other friends
}
}
if (fFriendsNearby)
continue; // this location is no good, move along now
// Well this place shows some promise, evaluate its "damage potential"
iTotalThreatValue = 0;
ubOppsInRange = 0;
ubOppsAdjacent = 0;
// skip this location unless it's right on top of an enemy or
// adjacent to more than 1
fSkipLocation = TRUE;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checking opponents");
for (ubLoop2 = 0; ubLoop2 < ubOpponentCnt; ubLoop2++)
{
usOppDist = PythSpacesAway( sOpponentTile[ubLoop2], sGridNo );
// if this opponent is close enough to the target gridno
if (usOppDist <= 3)
{
// start with this opponents base threat value
iThreatValue = iOppThreatValue[ubLoop2];
// estimate how much damage this tossed item would do to him
iEstDamage = EstimateThrowDamage(pSoldier,bPayloadPocket,MercPtrs[ubOpponentID[ubLoop2]],sGridNo);
//NumMessage("THROW EstDamage = ",iEstDamage);
if (usOppDist)
{
// reduce the estimated damage for his distance from gridno
// use 100% at range 0, 80% at range 1, and 60% at range 2, etc.
iEstDamage = (iEstDamage * (100 - (20 * usOppDist))) / 100;
//NumMessage("THROW reduced usEstDamage = ",usEstDamage);
}
else
{
// throwing right on top of someone... always consider this
fSkipLocation = FALSE;
}
// add the product of his threat value & damage caused to total
iTotalThreatValue += (iThreatValue * iEstDamage);
// only count opponents still standing worth shooting at (in range)
if (Menptr[ ubOpponentID[ubLoop2] ].bLife >= OKLIFE)
{
ubOppsInRange++;
if (usOppDist < 2)
{
ubOppsAdjacent++;
if (ubOppsAdjacent > 1 || Item[usGrenade].flare )
{
fSkipLocation = FALSE;
// add to exclusion list so we don't consider it again
}
}
}
}
}
// JA2Gold
if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_EASY )
{
if (fSkipLocation)
{
continue;
}
}
else
{
if( ubOppsInRange == 0 )
{
continue;
}
//Only use it if we are in a surface sector ( basement will be hard enough, plus more chances of mercs being clumped together )
else if( gbWorldSectorZ > 0 && fSkipLocation )
{
continue;
}
}
// JA2Gold change to >=
if (ubOppsAdjacent >= 1 && ubNumExcludedTiles < 100)
{
// add to exclusion list so we don't calculate for this location twice
sExcludeTile[ubNumExcludedTiles] = sGridNo;
ubNumExcludedTiles++;
}
// calculate chance to get through any cover to this gridno
//ubChanceToGetThrough = ChanceToGetThrough(pSoldier,sGridNo,NOTFAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS);
if ( EXPLOSIVE_GUN( usInHand ) )
{
ubChanceToGetThrough = AISoldierToLocationChanceToGetThrough( pSoldier, sGridNo, bOpponentLevel[ubLoop], 0 );
if ( ubChanceToGetThrough == 0)
{
continue; // next gridno
}
}
else
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checking chance for launcher to beat cover");
ubChanceToGetThrough = 100 * CalculateLaunchItemChanceToGetThrough( pSoldier, &(pSoldier->inv[ bPayloadPocket ] ), sGridNo, bOpponentLevel[ubLoop], 0, &sEndGridNo, TRUE, &bEndLevel, FALSE ); //NumMessage("Chance to get through = ",ubChanceToGetThrough);
// if we can't possibly get through all the cover
if (ubChanceToGetThrough == 0 )
{
if ( bEndLevel == bOpponentLevel[ubLoop] && ubSafetyMargin > 1 )
{
// rate "chance of hitting" according to how far away this is from the target
// but keeping in mind that we don't want to hit far, subtract 1 from the radius here
// to penalize being far from the target
uiPenalty = 100 * PythSpacesAway( sGridNo, sEndGridNo ) / (ubSafetyMargin - 1);
if ( uiPenalty < 100 )
{
ubChanceToGetThrough = 100 - (UINT8) uiPenalty;
}
else
{
continue;
}
}
else
{
continue;
}
}
}
//NumMessage("Total Threat Value = ",iTotalThreatValue);
//NumMessage("Opps in Range = ",ubOppsInRange);
// this is try to minimize enemies wasting their (few) mortar shells or LAWs
// they won't use them on less than 2 targets as long as half life left
if ((Item[usInHand].mortar || Item[usInHand].rocketlauncher ) && (ubOppsInRange < 2) &&
(pSoldier->bLife > (pSoldier->bLifeMax / 2)))
{
continue; // next gridno
}
// calculate the maximum possible aiming time
ubMaxPossibleAimTime = min(AllowedAimingLevels(pSoldier),pSoldier->bActionPoints - ubMinAPcost);
DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Max Possible Aim Time = %d",ubMaxPossibleAimTime ));
// calc next attack's minimum AP cost (excludes readying & turning)
// since grenades & shells are far too valuable to waste, ALWAYS
// aim for the longest time possible!
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checking chance to hit");
if ( EXPLOSIVE_GUN( usInHand ) )
{
ubRawAPCost = MinAPsToShootOrStab( pSoldier, sGridNo, FALSE);
ubChanceToHit = (UINT8) AICalcChanceToHitGun(pSoldier, sGridNo, ubMaxPossibleAimTime, AIM_SHOT_TORSO );
}
else
{
// NB grenade launcher is NOT a direct fire weapon!
ubRawAPCost = (UINT8) MinAPsToThrow( pSoldier, sGridNo, FALSE );
DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Raw AP Cost = %d",ubRawAPCost ));
ubChanceToHit = (UINT8) CalcThrownChanceToHit( pSoldier, sGridNo, ubMaxPossibleAimTime, AIM_SHOT_TORSO );
DebugMsg(TOPIC_JA2 , DBG_LEVEL_3 , String("Chance to hit = %d",ubChanceToHit ));
}
// mortars are inherently quite inaccurate, don't get proximity bonus
// rockets can go right past people too so...
if (!Item[usInHand].mortar && EXPLOSIVE_GUN( usInHand ) )
{
// special 50% to Hit bonus: this reflects that even if a tossed item
// misses by a bit, it's still likely to affect the intended target(s)
ubChanceToHit += (ubChanceToHit / 2);
// still can't let it go over 100% chance, though
if (ubChanceToHit > 100)
{
ubChanceToHit = 100;
}
}
if (ubRawAPCost < 1)
ubRawAPCost = ubMinAPcost;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("calcbestthrow: checking hit rate: ubRawAPCost %d, ubMaxPossibleAimTime %d", ubRawAPCost, ubMaxPossibleAimTime ));
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubMaxPossibleAimTime);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checked hit rate");
//NumMessage("iHitRate = ",iHitRate);
// calculate chance to REALLY hit: throw accurately AND get past cover
ubChanceToReallyHit = (ubChanceToHit * ubChanceToGetThrough) / 100;
// if we can't REALLY hit at all
if (ubChanceToReallyHit == 0)
continue; // next gridno
// calculate the combined "attack value" for this opponent
// maximum possible attack value here should be about 140 million
// typical attack value here should be about 500 thousand
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow: checking attack value");
iAttackValue = (iHitRate * ubChanceToReallyHit * iTotalThreatValue) / 1000;
//NumMessage("THROW AttackValue = ",iAttackValue / 1000);
// unlike SHOOTing and STABbing, find strictly the highest attackValue
if (iAttackValue > pBestThrow->iAttackValue)
{
#ifdef DEBUGATTACKS
DebugAI( String( "CalcBestThrow: new best attackValue vs %d = %d\n",ubOpponentID[ubLoop],iAttackValue ) );
#endif
// OOOF! That was a lot of work! But we've got a new best target!
pBestThrow->ubPossible = TRUE;
pBestThrow->ubOpponent = ubOpponentID[ubLoop];
pBestThrow->ubAimTime = ubMaxPossibleAimTime;
pBestThrow->ubChanceToReallyHit = ubChanceToReallyHit;
pBestThrow->sTarget = sGridNo;
pBestThrow->iAttackValue = iAttackValue;
pBestThrow->ubAPCost = ubMinAPcost + ubMaxPossibleAimTime;
pBestThrow->bTargetLevel = bOpponentLevel[ubLoop];
//sprintf(tempstr,"new best THROW AttackValue = %d at grid #%d",iAttackValue/100000,gridno);
//PopMessage(tempstr);
}
}
}
}
// this is try to minimize enemies wasting their (limited) toss attacks:
switch( ubDiff )
{
case 0:
case 1:
// don't use unless have a 70% chance to hit
if (pBestThrow->ubChanceToReallyHit < 70)
{
pBestThrow->ubPossible = FALSE;
}
break;
case 2:
case 3:
case 4:
// don't use unless have a 50% chance to hit
if (pBestThrow->ubChanceToReallyHit < 50)
{
pBestThrow->ubPossible = FALSE;
}
break;
default:
break;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"calcbestthrow done");
}
void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAttack )
{
UINT32 uiLoop;
INT32 iAttackValue;
INT32 iThreatValue,iHitRate,iBestHitRate,iPercentBetter, iEstDamage;
BOOLEAN fSurpriseStab;
UINT8 ubRawAPCost,ubMinAPCost,ubMaxPossibleAimTime,ubAimTime,ubBestAimTime;
UINT8 ubChanceToHit,ubChanceToReallyHit,ubBestChanceToHit = 0;
SOLDIERTYPE *pOpponent;
UINT16 usTrueMovementMode;
InitAttackType(pBestStab); // set all structure fields to defaults
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
// temporarily make this guy run so we get a proper AP cost value
// from CalcTotalAPsToAttack
usTrueMovementMode = pSoldier->usUIMovementMode;
pSoldier->usUIMovementMode = RUNNING;
// determine which attack against which target has the greatest attack value
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pOpponent = MercSlots[ uiLoop ];
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent || !pOpponent->bLife)
continue; // next merc
// if this man is neutral / on the same side, he's not an opponent
if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide) )
continue; // next merc
// if this opponent is not currently in sight (ignore known but unseen!)
if (pSoldier->bOppList[pOpponent->ubID] != SEEN_CURRENTLY)
continue; // next merc
// if this opponent is not on the same level
if (pSoldier->bLevel != pOpponent->bLevel)
continue; // next merc
// Special stuff for Carmen the bounty hunter
if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != 64)
continue; // next opponent
#ifdef DEBUGATTACKS
DebugAI( String( "%s can see %s\n",pSoldier->name,ExtMen[pOpponent->ubID].name ) );
#endif
// calculate minimum action points required to stab at this opponent
ubMinAPCost = CalcTotalAPsToAttack( pSoldier,pOpponent->sGridNo,ADDTURNCOST, 0 );
//ubMinAPCost = MinAPsToAttack(pSoldier,pOpponent->sGridNo,ADDTURNCOST);
//NumMessage("MinAPcost to stab this opponent = ",ubMinAPCost);
// Human: if I don't have enough APs left to get there & stab at this guy, skip 'im.
// Monster: I'll do an extra check later on to see if I can reach the guy this turn.
// if 0 is returned then no path!
if ( ubMinAPCost > pSoldier->bActionPoints || ubMinAPCost == 0 )
{
continue;
/*
if ( CREATURE_OR_BLOODCAT( pSoldier ) )
{
// hardcode ubMinAPCost so that aiming time is 0 and can start move to stab
// at any time
ubMinAPCost = pSoldier->bActionPoints;
}
else
{
continue; // next merc
}
*/
}
//KeepInterfaceGoing();
// calc next attack's minimum stabbing cost (excludes movement & turning)
//ubRawAPCost = MinAPsToShootOrStab(pSoldier,pOpponent->sGridNo, FALSE) - AP_CHANGE_TARGET;
ubRawAPCost = MinAPsToAttack(pSoldier,pOpponent->sGridNo, FALSE) - AP_CHANGE_TARGET;
//NumMessage("ubRawAPCost to stab this opponent = ",ubRawAPCost);
// determine if this is a surprise stab (must be next to opponent & unseen)
fSurpriseStab = FALSE; // assume it is not a surprise stab
// if opponent doesn't see the attacker
if (pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY)
{
// and he's only one space away from attacker
if (SpacesAway(pSoldier->sGridNo,pOpponent->sGridNo) == 1)
{
fSurpriseStab = TRUE; // we got 'im lined up where we want 'im!
}
}
iBestHitRate = 0; // reset best hit rate to minimum
// calculate the maximum possible aiming time
ubMaxPossibleAimTime = min(AllowedAimingLevels(pSoldier),pSoldier->bActionPoints - ubMinAPCost);
//NumMessage("Max Possible Aim Time = ",ubMaxPossibleAimTime);
// consider the various aiming times
for (ubAimTime = AP_MIN_AIM_ATTACK; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
{
//HandleMyMouseCursor(KEYBOARDALSO);
//NumMessage("ubAimTime = ",ubAimTime);
if (!fSurpriseStab)
{
if (fBladeAttack)
{
ubChanceToHit = (UINT8) CalcChanceToStab(pSoldier,pOpponent,ubAimTime);
}
else
{
ubChanceToHit = (UINT8) CalcChanceToPunch(pSoldier,pOpponent,ubAimTime);
}
}
else
ubChanceToHit = MAXCHANCETOHIT;
//NumMessage("chance to Hit = ",ubChanceToHit);
//sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].name,ubAimTime,ubChanceToHit);
//PopMessage(tempstr);
if (ubRawAPCost < 1)
ubRawAPCost = ubMinAPCost;
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubAimTime);
//NumMessage("hitRate = ",iHitRate);
// if aiming for this amount of time produces a better hit rate
if (iHitRate > iBestHitRate)
{
iBestHitRate = iHitRate;
ubBestAimTime = ubAimTime;
ubBestChanceToHit = ubChanceToHit;
}
}
// if we can't get any kind of hit rate at all
if (iBestHitRate == 0)
continue; // next opponent
// stabs are not affected by cover, so the chance to REALLY hit is the same
ubChanceToReallyHit = ubBestChanceToHit;
// calculate this opponent's threat value
// NOTE: ignore my cover! By the time I run beside him I won't have any!
iThreatValue = CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier);
// estimate the damage this stab would do to this opponent
iEstDamage = EstimateStabDamage(pSoldier,pOpponent,ubBestChanceToHit, fBladeAttack );
//NumMessage("STAB EstDamage = ", iEstDamage);
// calculate the combined "attack value" for this opponent
// highest possible value before division should be about 1 billion...
// normal value before division should be about 5 million...
iAttackValue = ( iEstDamage * iBestHitRate * ubChanceToReallyHit * iThreatValue) / 1000;
//NumMessage("STAB AttackValue = ",iAttackValue / 1000);
#ifdef DEBUGATTACKS
DebugAI( String( "CalcBestStab: best AttackValue vs %d = %d\n",ubLoop,iAttackValue ) );
#endif
// if we can hurt the guy, OR probably not, but at least it's our best
// chance to actually hit him and maybe scare him, knock him down, etc.
if ((iAttackValue > 0) || (ubChanceToReallyHit > pBestStab->ubChanceToReallyHit))
{
// if there already was another viable target
if (pBestStab->ubChanceToReallyHit > 0)
{
// OK, how does our chance to hit him compare to the previous best one?
iPercentBetter = ((ubChanceToReallyHit * 100) / pBestStab->ubChanceToReallyHit) - 100;
// if this chance to really hit is more than 50% worse, and the other
// guy is conscious at all
if ((iPercentBetter < -PERCENT_TO_IGNORE_THREAT) && (Menptr[pBestStab->ubOpponent].bLife >= OKLIFE))
// then stick with the older guy as the better target
continue;
// if this chance to really hit between 50% worse to 50% better
if (iPercentBetter < PERCENT_TO_IGNORE_THREAT)
{
// then the one with the higher ATTACK VALUE is the better target
if (iAttackValue < pBestStab->iAttackValue)
// the previous guy is more important since he's more dangerous
continue; // next opponent
}
}
// OOOF! That was a lot of work! But we've got a new best target!
pBestStab->ubPossible = TRUE;
pBestStab->ubOpponent = pOpponent->ubID;
pBestStab->ubAimTime = ubBestAimTime;
pBestStab->ubChanceToReallyHit = ubChanceToReallyHit;
pBestStab->sTarget = pOpponent->sGridNo;
pBestStab->bTargetLevel = pOpponent->bLevel;
pBestStab->iAttackValue = iAttackValue;
pBestStab->ubAPCost = ubMinAPCost + ubBestAimTime;
}
}
pSoldier->usUIMovementMode = usTrueMovementMode;
}
void CalcTentacleAttack(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab )
{
UINT32 uiLoop;
INT32 iAttackValue;
INT32 iThreatValue,iHitRate,iBestHitRate, iEstDamage;
BOOLEAN fSurpriseStab;
UINT8 ubRawAPCost,ubMinAPCost,ubMaxPossibleAimTime,ubAimTime,ubBestAimTime;
UINT8 ubChanceToHit,ubChanceToReallyHit,ubBestChanceToHit = 0;
SOLDIERTYPE *pOpponent;
InitAttackType(pBestStab); // set all structure fields to defaults
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
// determine which attack against which target has the greatest attack value
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pOpponent = MercSlots[ uiLoop ];
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent || !pOpponent->bLife)
continue; // next merc
// if this man is neutral / on the same side, he's not an opponent
if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide))
continue; // next merc
// if this opponent is not currently in sight (ignore known but unseen!)
if (pSoldier->bOppList[pOpponent->ubID] != SEEN_CURRENTLY)
continue; // next merc
// if this opponent is not on the same level
if (pSoldier->bLevel != pOpponent->bLevel)
continue; // next merc
// if this opponent is outside the range of our tentacles
if ( GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, pOpponent->sGridNo ) > Weapon[ CREATURE_QUEEN_TENTACLES].usRange )
{
continue; // next merc
}
#ifdef DEBUGATTACKS
DebugAI( String( "%s can see %s\n",pSoldier->name,ExtMen[pOpponent->ubID].name ) );
#endif
// calculate minimum action points required to stab at this opponent
ubMinAPCost = CalcTotalAPsToAttack( pSoldier,pOpponent->sGridNo,ADDTURNCOST, 0 );
//ubMinAPCost = MinAPsToAttack(pSoldier,pOpponent->sGridNo,ADDTURNCOST);
//NumMessage("MinAPcost to stab this opponent = ",ubMinAPCost);
// calc next attack's minimum stabbing cost (excludes movement & turning)
//ubRawAPCost = MinAPsToShootOrStab(pSoldier,pOpponent->sGridNo, FALSE) - AP_CHANGE_TARGET;
ubRawAPCost = MinAPsToAttack(pSoldier,pOpponent->sGridNo, FALSE) - AP_CHANGE_TARGET;
//NumMessage("ubRawAPCost to stab this opponent = ",ubRawAPCost);
// determine if this is a surprise stab (for tentacles, enemy must not see us, no dist limit)
fSurpriseStab = FALSE; // assume it is not a surprise stab
// if opponent doesn't see the attacker
if (pOpponent->bOppList[pSoldier->ubID] != SEEN_CURRENTLY)
{
fSurpriseStab = TRUE; // we got 'im lined up where we want 'im!
}
iBestHitRate = 0; // reset best hit rate to minimum
// calculate the maximum possible aiming time
//ubMaxPossibleAimTime = min(AllowedAimingLevels(pSoldier),pSoldier->bActionPoints - ubMinAPCost);
ubMaxPossibleAimTime = 0;
//NumMessage("Max Possible Aim Time = ",ubMaxPossibleAimTime);
// consider the various aiming times
for (ubAimTime = AP_MIN_AIM_ATTACK; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
{
//HandleMyMouseCursor(KEYBOARDALSO);
//NumMessage("ubAimTime = ",ubAimTime);
if (!fSurpriseStab)
{
ubChanceToHit = (UINT8) CalcChanceToStab(pSoldier,pOpponent,ubAimTime);
}
else
ubChanceToHit = MAXCHANCETOHIT;
//NumMessage("chance to Hit = ",ubChanceToHit);
//sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].name,ubAimTime,ubChanceToHit);
//PopMessage(tempstr);
if (ubRawAPCost < 1)
ubRawAPCost = ubMinAPCost;
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubAimTime);
//NumMessage("hitRate = ",iHitRate);
// if aiming for this amount of time produces a better hit rate
if (iHitRate > iBestHitRate)
{
iBestHitRate = iHitRate;
ubBestAimTime = ubAimTime;
ubBestChanceToHit = ubChanceToHit;
}
}
// if we can't get any kind of hit rate at all
if (iBestHitRate == 0)
continue; // next opponent
// stabs are not affected by cover, so the chance to REALLY hit is the same
ubChanceToReallyHit = ubBestChanceToHit;
// calculate this opponent's threat value
// NOTE: ignore my cover! By the time I run beside him I won't have any!
iThreatValue = CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier);
// estimate the damage this stab would do to this opponent
iEstDamage = EstimateStabDamage(pSoldier,pOpponent,ubBestChanceToHit, TRUE );
//NumMessage("STAB EstDamage = ", iEstDamage);
// calculate the combined "attack value" for this opponent
// highest possible value before division should be about 1 billion...
// normal value before division should be about 5 million...
iAttackValue = ( iEstDamage * iBestHitRate * ubChanceToReallyHit * iThreatValue) / 1000;
//NumMessage("STAB AttackValue = ",iAttackValue / 1000);
#ifdef DEBUGATTACKS
DebugAI( String( "CalcBestStab: best AttackValue vs %d = %d\n",ubLoop,iAttackValue ) );
#endif
// if we can hurt the guy, OR probably not, but at least it's our best
// chance to actually hit him and maybe scare him, knock him down, etc.
if (iAttackValue > 0)
{
// OOOF! That was a lot of work! But we've got a new best target!
pBestStab->ubPossible = TRUE;
pBestStab->ubOpponent = pOpponent->ubID;
pBestStab->ubAimTime = ubBestAimTime;
pBestStab->ubChanceToReallyHit = ubChanceToReallyHit;
pBestStab->sTarget = pOpponent->sGridNo;
pBestStab->bTargetLevel = pOpponent->bLevel;
// ADD this target's attack value to our TOTAL...
pBestStab->iAttackValue += iAttackValue;
pBestStab->ubAPCost = ubMinAPCost + ubBestAimTime;
}
}
}
UINT8 NumMercsCloseTo( INT16 sGridNo, UINT8 ubMaxDist )
{
INT8 bNumber = 0;
UINT32 uiLoop;
SOLDIERTYPE * pSoldier;
for ( uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pSoldier = MercSlots[ uiLoop ];
if ( pSoldier && pSoldier->bTeam == gbPlayerNum && pSoldier->bLife >= OKLIFE )
{
if (PythSpacesAway( sGridNo, pSoldier->sGridNo ) <= ubMaxDist)
{
bNumber++;
}
}
}
return( bNumber );
}
INT32 EstimateShotDamage(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, UINT8 ubChanceToHit)
{
INT32 iRange,iMaxRange,iPowerLost;
INT32 iDamage;
UINT8 ubBonus;
INT32 iHeadProt = 0, iTorsoProt = 0, iLegProt = 0;
INT32 iTotalProt;
INT8 bPlatePos;
UINT8 ubAmmoType;
/*
if ( pOpponent->uiStatusFlags & SOLDIER_VEHICLE )
{
// only thing that can damage vehicles is HEAP rounds?
return( 0 );
}
*/
if ( Item[ pSoldier->inv[pSoldier->ubAttackingHand].usItem ].usItemClass & IC_THROWING_KNIFE )
{
ubAmmoType = AMMO_KNIFE;
}
else
{
ubAmmoType = pSoldier->inv[pSoldier->ubAttackingHand].ubGunAmmoType;
}
// calculate distance to target, obtain his gun's maximum range rating
iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, pOpponent->sGridNo );
iMaxRange = Weapon[pSoldier->inv[HANDPOS].usItem].usRange;
// bullet loses speed and penetrating power, 50% loss per maximum range
iPowerLost = ((50 * iRange) / iMaxRange);
// up to 50% extra impact for making particularly accurate successful shots
ubBonus = ubChanceToHit / 4; // /4 is really /2 and /2 again
iDamage = ((GetDamage(&pSoldier->inv[HANDPOS])) * (100 - iPowerLost + ubBonus) / 100) ;
//NumMessage("Pre-protection damage: ",damage);
// if opponent is wearing a helmet
if (pOpponent->inv[HELMETPOS].usItem)
{
iHeadProt += (INT32) Armour[Item[pOpponent->inv[HELMETPOS].usItem].ubClassIndex].ubProtection *
(INT32) pOpponent->inv[HELMETPOS].bStatus[0] / 100;
}
// if opponent is wearing a protective vest
if ( !AmmoTypes[ubAmmoType].ignoreArmour )
{
// monster spit and knives ignore kevlar vests
if (pOpponent->inv[VESTPOS].usItem)
{
iTorsoProt += (INT32) Armour[Item[pOpponent->inv[VESTPOS].usItem].ubClassIndex].ubProtection *
(INT32) pOpponent->inv[VESTPOS].bStatus[0] / 100;
}
}
// check for ceramic plates; these do affect monster spit
bPlatePos = FindFirstArmourAttachment( &(pOpponent->inv[VESTPOS]) );
if (bPlatePos != -1)
{
iTorsoProt += (INT32) Armour[Item[pOpponent->inv[VESTPOS].usAttachItem[bPlatePos]].ubClassIndex].ubProtection *
(INT32) pOpponent->inv[VESTPOS].bAttachStatus[bPlatePos] / 100;
}
// if opponent is wearing armoured leggings (LEGPOS)
if ( !AmmoTypes[ubAmmoType].ignoreArmour )
{ // monster spit and knives ignore kevlar leggings
if (pOpponent->inv[LEGPOS].usItem)
{
iLegProt += (INT32) Armour[Item[pOpponent->inv[LEGPOS].usItem].ubClassIndex].ubProtection *
(INT32) pOpponent->inv[LEGPOS].bStatus[0] / 100;
}
}
// 15% of all shots are to the head, 80% are to the torso. Calc. avg. prot.
// NB: make AI guys shoot at head 15% of time, 5% of time at legs
iTotalProt = ((15 * iHeadProt) + (75 * iTorsoProt) + 5 * iLegProt) / 100;
iTotalProt = (INT32) (iTotalProt * AmmoTypes[ubAmmoType].armourImpactReductionMultiplier / max(1,AmmoTypes[ubAmmoType].armourImpactReductionDivisor) );
//switch (ubAmmoType)
//{
// case AMMO_HP:
// iTotalProt = AMMO_ARMOUR_ADJUSTMENT_HP( iTotalProt );
// break;
// case AMMO_AP:
// iTotalProt = AMMO_ARMOUR_ADJUSTMENT_AP( iTotalProt );
// break;
// case AMMO_SUPER_AP:
// iTotalProt = AMMO_ARMOUR_ADJUSTMENT_SAP( iTotalProt );
// break;
// default:
// break;
//}
iDamage -= iTotalProt;
//NumMessage("After-protection damage: ",damage);
//if (ubAmmoType == AMMO_HP)
//{
// // increase after-armour damage
// iDamage = AMMO_DAMAGE_ADJUSTMENT_HP( iDamage );
//}
iDamage = (INT32)(iDamage * AmmoTypes[ubAmmoType].afterArmourDamageMultiplier / max(1,AmmoTypes[ubAmmoType].afterArmourDamageDivisor) ) ;
if (AmmoTypes[ubAmmoType].monsterSpit )
{
// cheat and emphasize shots
//iDamage = (iDamage * 15) / 10;
switch( pSoldier->inv[pSoldier->ubAttackingHand].usItem )
{
// explosive damage is 100-200% that of the rated, so multiply by 3/2s here
case CREATURE_QUEEN_SPIT: //TODO: Madd - remove the hardcoding here
iDamage += ( 3 * Explosive[ Item[ LARGE_CREATURE_GAS ].ubClassIndex ].ubDamage * NumMercsCloseTo( pOpponent->sGridNo, (UINT8)Explosive[ Item[ LARGE_CREATURE_GAS ].ubClassIndex ].ubRadius ) ) / 2;
break;
case CREATURE_OLD_MALE_SPIT:
iDamage += ( 3 * Explosive[ Item[ SMALL_CREATURE_GAS ].ubClassIndex ].ubDamage * NumMercsCloseTo( pOpponent->sGridNo, (UINT8)Explosive[ Item[ SMALL_CREATURE_GAS ].ubClassIndex ].ubRadius ) ) / 2;
break;
default:
iDamage += ( 3 * Explosive[ Item[ VERY_SMALL_CREATURE_GAS ].ubClassIndex ].ubDamage * NumMercsCloseTo( pOpponent->sGridNo, (UINT8)Explosive[ Item[ VERY_SMALL_CREATURE_GAS ].ubClassIndex ].ubRadius ) ) / 2;
break;
}
}
if (iDamage < 1)
iDamage = 1; // assume we can do at LEAST 1 pt minimum damage
return( iDamage );
}
INT32 EstimateThrowDamage( SOLDIERTYPE *pSoldier, UINT8 ubItemPos, SOLDIERTYPE *pOpponent, INT16 sGridno )
{
UINT16 ubExplosiveIndex;
INT32 iExplosDamage, iBreathDamage, iArmourAmount, iDamage = 0;
INT8 bSlot;
if( pSoldier == NULL || pOpponent == NULL || ubItemPos > NUM_INV_SLOTS || sGridno > NUMBEROFTILES )
return 0;
if( pSoldier->inv[ubItemPos].usItem == NOTHING )
return 0;
//switch ( pSoldier->inv[ ubItemPos ].usItem )
//{
//case GL_SMOKE_GRENADE:
//case SMOKE_GRENADE:
// // Don't want to value throwing smoke very much. This value is based relative
// // to the value for knocking somebody down, which was giving values that were
// // too high
// return( 5 );
//case RPG7:
//case ROCKET_LAUNCHER:
// ubExplosiveIndex = Item[ C1 ].ubClassIndex;
// break;
//default:
if ( Item[pSoldier->inv[ ubItemPos ].usItem].singleshotrocketlauncher )
ubExplosiveIndex = Item[ C1 ].ubClassIndex;
else if ( Item[pSoldier->inv[ ubItemPos ].usItem].rocketlauncher || Item[pSoldier->inv[ ubItemPos ].usItem].grenadelauncher || Item[pSoldier->inv[ ubItemPos ].usItem].mortar )
{
bSlot = FindLaunchableAttachment(&pSoldier->inv[ ubItemPos ],pSoldier->inv[ ubItemPos ].usItem ) ;
if ( bSlot != ITEM_NOT_FOUND )
ubExplosiveIndex = Item[pSoldier->inv[ bSlot ].usItem].ubClassIndex;
else
return 0;
}
else if ( Explosive[Item[pSoldier->inv[ ubItemPos ].usItem].ubClassIndex].ubType == EXPLOSV_SMOKE )
return 5;
else
ubExplosiveIndex = Item[ pSoldier->inv[ubItemPos].usItem ].ubClassIndex;
// break;
//}
// JA2Gold: added
if ( Item[pSoldier->inv[ubItemPos].usItem].flare )
{
return( 5 * ( LightTrueLevel( pOpponent->sGridNo, pOpponent->bLevel ) - NORMAL_LIGHTLEVEL_DAY ) );
}
iExplosDamage = ( ( (INT32) Explosive[ ubExplosiveIndex ].ubDamage ) * 3) / 2;
iBreathDamage = ( ( (INT32) Explosive[ ubExplosiveIndex ].ubStunDamage ) * 5) / 4;
if ( Explosive[ ubExplosiveIndex ].ubType == EXPLOSV_TEARGAS || Explosive[ ubExplosiveIndex ].ubType == EXPLOSV_MUSTGAS )
{
// if target gridno is outdoors (where tear gas lasts only 1-2 turns)
if (gpWorldLevelData[sGridno].ubTerrainID != FLAT_FLOOR)
iBreathDamage /= 2; // reduce effective breath damage by 1/2
bSlot = FindObj( pOpponent, GASMASK );
if (bSlot == HEAD1POS || bSlot == HEAD2POS)
{
// take condition of the gas mask into account - it could be leaking
iBreathDamage = (iBreathDamage * (100 - pOpponent->inv[bSlot].bStatus[0])) / 100;
//NumMessage("damage after GAS MASK: ",iBreathDamage);
}
}
if ( Explosive[ ubExplosiveIndex ].ubType == EXPLOSV_BURNABLEGAS )
{
// if target gridno is outdoors (where tear gas lasts only 1-2 turns)
if (gpWorldLevelData[sGridno].ubTerrainID != FLAT_FLOOR)
iBreathDamage /= 2; // reduce effective breath damage by 1/2
}
else if (iExplosDamage)
{
// EXPLOSION DAMAGE is spread amongst locations
iArmourAmount = ArmourVersusExplosivesPercent( pSoldier );
iExplosDamage -= iExplosDamage * iArmourAmount / 100;
if (iExplosDamage < 1)
iExplosDamage = 1;
}
// if this opponent is standing
if (gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND)
{
// 15 pt. flat bonus for knocking him down (for ANY type of explosion)
iDamage += 15;
}
if ( pOpponent->bBreath < OKBREATH || AM_A_ROBOT( pOpponent ) )
{
// don't bother to count breath damage against people already down
iBreathDamage = 0;
}
// estimate combined "damage" value considering combined health/breath damage
iDamage += iExplosDamage + (iBreathDamage / 3);
// approximate chance of the grenade going off (Ian's formulas are too funky)
// then use that to reduce the expected damage because thing may not blow!
iDamage = (iDamage * pSoldier->inv[ubItemPos].bStatus[0]) / 100;
// if the target gridno is in water, grenade may not blow (guess 50% of time)
/*
if (TTypeList[Grid[gridno].land] >= LAKETYPE)
iDamage /= 2;
*/
return( iDamage);
}
INT32 EstimateStabDamage( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent,
UINT8 ubChanceToHit, BOOLEAN fBladeAttack )
{
INT32 iImpact, iFluke, iBonus;
UINT16 usItem;
usItem = pSoldier->inv[HANDPOS].usItem;
if (fBladeAttack)
{
iImpact = GetDamage(&pSoldier->inv[HANDPOS]);
iImpact += EffectiveStrength( pSoldier ) / 20; // 0 to 5 for strength, adjusted by damage taken
}
else
{
// NB martial artists don't get a bonus for using brass knuckles!
if (pSoldier->usAttackingWeapon && !( HAS_SKILL_TRAIT( pSoldier, MARTIALARTS ) ) )
{
iImpact = GetDamage(&pSoldier->inv[HANDPOS]);
}
else
{
// base HTH damage
iImpact = 5;
}
iImpact += EffectiveStrength( pSoldier ) / 5; // 0 to 20 for strength, adjusted by damage taken
}
iImpact += (pSoldier->bExpLevel / 2); // 0 to 4 for level
iFluke = 0;
iBonus = ubChanceToHit / 4; // up to 50% extra impact for accurate attacks
iImpact = iImpact * (100 + iFluke + iBonus) / 100;
if (!fBladeAttack)
{
// add bonuses for hand-to-hand and martial arts
if ( HAS_SKILL_TRAIT( pSoldier, MARTIALARTS ) )
{
iImpact = iImpact * (100 + gbSkillTraitBonus[MARTIALARTS] * NUM_SKILL_TRAITS( pSoldier, MARTIALARTS ) ) / 100;
}
if ( HAS_SKILL_TRAIT( pSoldier, HANDTOHAND ) )
{
iImpact = iImpact * (100 + gbSkillTraitBonus[HANDTOHAND] * NUM_SKILL_TRAITS( pSoldier, HANDTOHAND ) ) / 100;
}
// Here, if we're doing a bare-fisted attack,
// we want to pay attention just to wounds inflicted
iImpact = iImpact / PUNCH_REAL_DAMAGE_PORTION;
}
if (iImpact < 1)
{
iImpact = 1;
}
return( iImpact );
}
INT8 TryToReload( SOLDIERTYPE * pSoldier )
{
INT8 bSlot;
WEAPONTYPE *pWeapon;
OBJECTTYPE *pObj, *pObj2;
pWeapon = &(Weapon[pSoldier->inv[HANDPOS].usItem]);
bSlot = FindAmmo( pSoldier, pWeapon->ubCalibre, pWeapon->ubMagSize, NO_SLOT );
//if (bSlot != NO_SLOT)
//{
// if (ReloadGun( pSoldier, &(pSoldier->inv[HANDPOS]), &(pSoldier->inv[bSlot]) ))
// {
// return( TRUE );
// }
//}
//<SB> manual recharge
pObj = &(pSoldier->inv[HANDPOS]);
if (pObj->ubGunShotsLeft && !(pObj->ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{
pObj->ubGunState |= GS_CARTRIDGE_IN_CHAMBER;
DeductPoints(pSoldier, Weapon[Item[(pObj)->usItem].ubClassIndex].APsToReloadManually, 0);
PlayJA2Sample( Weapon[ Item[pObj->usItem].ubClassIndex ].ManualReloadSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
if ( IsValidSecondHandShot( pSoldier ) )
{
pObj2 = &(pSoldier->inv[SECONDHANDPOS]);
if (pObj2->ubGunShotsLeft && !(pObj2->ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{
pObj2->ubGunState |= GS_CARTRIDGE_IN_CHAMBER;
PlayJA2Sample( Weapon[ Item[pObj2->usItem].ubClassIndex ].ManualReloadSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
}
}
return TRUE;
}
else
{
if ( IsValidSecondHandShot( pSoldier ) )
{
pObj2 = &(pSoldier->inv[SECONDHANDPOS]);
if (pObj2->ubGunShotsLeft && !(pObj2->ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{
pObj2->ubGunState |= GS_CARTRIDGE_IN_CHAMBER;
DeductPoints(pSoldier, Weapon[Item[(pObj2)->usItem].ubClassIndex].APsToReloadManually, 0);
PlayJA2Sample( Weapon[ Item[pObj2->usItem].ubClassIndex ].ManualReloadSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
return TRUE;
}
}
}
//</SB>
if (bSlot != NO_SLOT && ReloadGun( pSoldier, &(pSoldier->inv[HANDPOS]), &(pSoldier->inv[bSlot]) ))
{
return( TRUE );
}
return( NOSHOOT_NOAMMO );
}
/*
INT8 TryToReloadLauncher( SOLDIERTYPE * pSoldier )
{
UINT16 usWeapon;
INT8 bSlot;
usWeapon = pSoldier->inv[HANDPOS].usItem;
if ( usWeapon == TANK_CANNON )
{
bSlot = FindObj( pSoldier, TANK_SHELL );
}
else
{
bSlot = FindLaunchable( pSoldier, usWeapon );
}
if (bSlot != NO_SLOT)
{
}
return( NOSHOOT_NOAMMO );
}
*/
INT8 CanNPCAttack(SOLDIERTYPE *pSoldier)
{
INT8 bCanAttack;
INT8 bWeaponIn;
// NEUTRAL civilians are not allowed to attack, but those that are not
// neutral (KILLNPC mission guynums, escorted guys) can, if they're armed
if (PTR_CIVILIAN && pSoldier->bNeutral)
{
return(FALSE);
}
// test if if we are able to attack (in general, not at any specific target)
bCanAttack = OKToAttack(pSoldier,NOWHERE);
// if soldier can't attack because he doesn't have a weapon or is out of ammo
// or his weapon isn't loaded
if ( bCanAttack == NOSHOOT_NOAMMO ) // || NOLOAD
{
// try to reload it
bCanAttack = TryToReload( pSoldier );
}
else if (bCanAttack == NOSHOOT_NOWEAPON)
{
// look for another weapon
bWeaponIn = FindAIUsableObjClass( pSoldier, IC_WEAPON );
if (bWeaponIn != NO_SLOT)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"cannpcattack: swapping weapon into hand");
RearrangePocket( pSoldier, HANDPOS, bWeaponIn, FOREVER );
// look for another weapon if this one is 1-handed
// if ( (Item[ pSoldier->inv[ HANDPOS ].usItem ].usItemClass == IC_GUN) && !(Item[ pSoldier->inv[ HANDPOS ].usItem ].fFlags & ITEM_TWO_HANDED ) )
if ( (Item[ pSoldier->inv[ HANDPOS ].usItem ].usItemClass == IC_GUN) && !(Item[ pSoldier->inv[ HANDPOS ].usItem ].twohanded ) )
{
// look for another pistol/SMG if available
bWeaponIn = FindAIUsableObjClassWithin( pSoldier, IC_WEAPON, BIGPOCK1POS, SMALLPOCK8POS );
// if (bWeaponIn != NO_SLOT && (Item[ pSoldier->inv[ bWeaponIn ].usItem ].usItemClass == IC_GUN) && !(Item[ pSoldier->inv[ bWeaponIn ].usItem ].fFlags & ITEM_TWO_HANDED ) )
if (bWeaponIn != NO_SLOT && (Item[ pSoldier->inv[ bWeaponIn ].usItem ].usItemClass == IC_GUN) && !(Item[ pSoldier->inv[ bWeaponIn ].usItem ].twohanded ) )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"cannpcattack: swapping weapon into holster");
RearrangePocket( pSoldier, SECONDHANDPOS, bWeaponIn, FOREVER );
}
}
// might need to reload
bCanAttack = CanNPCAttack( pSoldier );
}
}
#ifdef DEBUGDECISIONS
if (bCanAttack != TRUE) // if for any reason we can't attack right now
{
//LocateMember(pSoldier->ubID,SETLOCATOR); // locate to this NPC, don't center
STR16 tempstr;
sprintf(tempstr,"%s can't attack! (not OKToAttack, Reason code = %d)",pSoldier->name,bCanAttack);
AIPopMessage(tempstr);
}
#endif
return( bCanAttack );
}
void CheckIfTossPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CheckIfTossPossible");
UINT8 ubMinAPcost;
pSoldier->bWeaponMode = WM_NORMAL;
if ( TANK( pSoldier ) )
{
pBestThrow->bWeaponIn = FindCannon(pSoldier);//FindObj( pSoldier, TANK_CANNON );
}
else
{
pBestThrow->bWeaponIn = FindAIUsableObjClass( pSoldier, IC_LAUNCHER );
if ( pBestThrow->bWeaponIn == NO_SLOT || ( !EnoughAmmo( pSoldier, FALSE, pBestThrow->bWeaponIn) && FindAmmoToReload( pSoldier, pBestThrow->bWeaponIn, NO_SLOT) == NO_SLOT) )
{
// Consider rocket launcher/cannon
pBestThrow->bWeaponIn = FindRocketLauncherOrCannon( pSoldier );
if ( pBestThrow->bWeaponIn == NO_SLOT || ( !EnoughAmmo( pSoldier, FALSE, pBestThrow->bWeaponIn) && FindAmmoToReload( pSoldier, pBestThrow->bWeaponIn, NO_SLOT) == NO_SLOT) )
{
//no rocket launcher (or empty) -- let's look for an underslung/attached GL
// AND a launchable grenade!
INT8 bOldWeaponMode = pSoldier->bWeaponMode;
pSoldier->bWeaponMode = WM_ATTACHED_GL; // So that EnoughAmmo will check for a grenade not a bullet
INT8 bGunSlot = FindAIUsableObjClass( pSoldier, IC_GUN );
if ( bGunSlot != NO_SLOT &&
IsGrenadeLauncherAttached(&pSoldier->inv[bGunSlot]) &&
( EnoughAmmo( pSoldier, FALSE, bGunSlot) || FindGLGrenade( pSoldier) == NO_SLOT) )
{
pBestThrow->bWeaponIn = bGunSlot;
}
else
{
// no rocket launcher or attached GL, consider grenades
pSoldier->bWeaponMode = bOldWeaponMode;
pBestThrow->bWeaponIn = FindThrowableGrenade( pSoldier );
}
}
else
{
// Have rocket launcher... maybe have grenades as well. which one to use?
if ( pSoldier->bAIMorale > MORALE_WORRIED && PreRandom( 2 ) )
{
pBestThrow->bWeaponIn = FindThrowableGrenade( pSoldier );
}
}
}
}
// if the soldier does have a tossable item somewhere
if (pBestThrow->bWeaponIn != NO_SLOT)
{
// if it's in his holster, swap it into his hand temporarily
if (pBestThrow->bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"checkiftosspossible: swapping item into hand");
RearrangePocket(pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY);
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"checkiftosspossible: get minapstoattack");
// get the minimum cost to attack with this tossable item
ubMinAPcost = MinAPsToAttack( pSoldier, pSoldier->sLastTarget, DONTADDTURNCOST);
// if we can afford the minimum AP cost to throw this tossable item
if (pSoldier->bActionPoints >= ubMinAPcost)
{
// then look around for a worthy target (which sets bestThrow.ubPossible)
CalcBestThrow( pSoldier, pBestThrow );
}
// if it was in his holster, swap it back into his holster for now
if (pBestThrow->bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"checkiftosspossible: swapping item into holster");
RearrangePocket( pSoldier, HANDPOS, pBestThrow->bWeaponIn, TEMPORARILY );
}
}
}
INT8 CountAdjacentSpreadTargets( SOLDIERTYPE * pSoldier, INT16 sFirstTarget, INT8 bTargetLevel )
{
// return the number of people next to this guy for burst-spread purposes
INT8 bDirLoop, bDir, bCheckDir, bTargetIndex, bTargets;
INT16 sTarget;
SOLDIERTYPE * pTarget, * pTargets[5] = {NULL};
bTargetIndex = -1;
bCheckDir = -1;
pTargets[2] = SimpleFindSoldier( sFirstTarget, bTargetLevel );
if (pTargets[2] == NULL)
{
return( 0 );
}
bTargets = 1;
bDir = (INT8) GetDirectionToGridNoFromGridNo( pSoldier->sGridNo, sFirstTarget );
for (bDirLoop = 0; bDirLoop < 8; bDirLoop++)
{
if (bDir % 2)
{
// odd direction = diagonal direction
switch( bDirLoop )
{
case 0:
bCheckDir = (bDir + 6) % 8;
bTargetIndex = 0;
break;
case 1:
bCheckDir = (bDir + 5) % 8;
bTargetIndex = 1;
break;
case 2:
bCheckDir = (bDir + 7) % 8;
bTargetIndex = 1;
break;
case 3:
bCheckDir = (bDir + 3) % 8;
bTargetIndex = 3;
break;
case 4:
bCheckDir = (bDir + 1) % 8;
bTargetIndex = 3;
break;
case 5:
bCheckDir = (bDir + 2) % 8;
bTargetIndex = 4;
break;
case 6:
// check in front
bCheckDir = (bDir + 4) % 8;
bTargetIndex = 1;
break;
case 7:
// check behind
bCheckDir = (bDir) % 8;
bTargetIndex = 3;
break;
}
}
else
{
// even = straight
switch( bDirLoop )
{
case 0:
bCheckDir = (bDir + 5) % 8;
bTargetIndex = 1;
break;
case 1:
bCheckDir = (bDir + 6) % 8;
bTargetIndex = 1;
break;
case 2:
bCheckDir = (bDir + 7) % 8;
bTargetIndex = 1;
break;
case 3:
bCheckDir = (bDir + 3) % 8;
bTargetIndex = 3;
break;
case 4:
bCheckDir = (bDir + 2) % 8;
bTargetIndex = 3;
break;
case 5:
bCheckDir = (bDir + 1) % 8;
bTargetIndex = 3;
break;
case 6:
// check in front
bCheckDir = (bDir + 4) % 8;
bTargetIndex = 1;
break;
case 7:
// check behind
bCheckDir = (bDir) % 8;
bTargetIndex = 3;
break;
}
}
if (bDirLoop == 6 && bTargets > 1)
{
// we're done! otherwise we continue and try to find people in front/behind
break;
}
else if (pTargets[bTargetIndex] != NULL)
{
continue;
}
sTarget = sFirstTarget + DirIncrementer[bCheckDir];
pTarget = SimpleFindSoldier( sTarget, bTargetLevel );
if (pTarget)
{
// check to see if guy is visible
if (pSoldier->bOppList[ pTarget->ubID ] == SEEN_CURRENTLY)
{
pTargets[bTargetIndex] = pTarget;
bTargets++;
}
}
}
return( bTargets - 1 );
}
INT16 CalcSpreadBurst( SOLDIERTYPE * pSoldier, INT16 sFirstTarget, INT8 bTargetLevel )
{
INT8 bDirLoop, bDir, bCheckDir, bTargetIndex = 0, bLoop, bTargets;
INT16 sTarget;
SOLDIERTYPE * pTarget, * pTargets[5] = {NULL};
INT8 bAdjacents, bOtherAdjacents;
bCheckDir = -1;
pTargets[2] = SimpleFindSoldier( sFirstTarget, bTargetLevel );
if (pTargets[2] == NULL)
{
return( sFirstTarget );
}
bTargets = 1;
bAdjacents = CountAdjacentSpreadTargets( pSoldier, sFirstTarget, bTargetLevel );
bDir = (INT8) GetDirectionToGridNoFromGridNo( pSoldier->sGridNo, sFirstTarget );
for (bDirLoop = 0; bDirLoop < 8; bDirLoop++)
{
if (bDir % 2)
{
// odd direction = diagonal direction
switch( bDirLoop )
{
case 0:
bCheckDir = (bDir + 6) % 8;
bTargetIndex = 0;
break;
case 1:
bCheckDir = (bDir + 5) % 8;
bTargetIndex = 1;
break;
case 2:
bCheckDir = (bDir + 7) % 8;
bTargetIndex = 1;
break;
case 3:
bCheckDir = (bDir + 3) % 8;
bTargetIndex = 3;
break;
case 4:
bCheckDir = (bDir + 1) % 8;
bTargetIndex = 3;
break;
case 5:
bCheckDir = (bDir + 2) % 8;
bTargetIndex = 4;
break;
case 6:
// check in front
bCheckDir = (bDir + 4) % 8;
bTargetIndex = 1;
break;
case 7:
// check behind
bCheckDir = (bDir) % 8;
bTargetIndex = 3;
break;
}
}
else
{
// even = straight
switch( bDirLoop )
{
case 0:
bCheckDir = (bDir + 5) % 8;
bTargetIndex = 1;
break;
case 1:
bCheckDir = (bDir + 6) % 8;
bTargetIndex = 1;
break;
case 2:
bCheckDir = (bDir + 7) % 8;
bTargetIndex = 1;
break;
case 3:
bCheckDir = (bDir + 3) % 8;
bTargetIndex = 3;
break;
case 4:
bCheckDir = (bDir + 2) % 8;
bTargetIndex = 3;
break;
case 5:
bCheckDir = (bDir + 1) % 8;
bTargetIndex = 3;
break;
case 6:
// check in front
bCheckDir = (bDir + 4) % 8;
bTargetIndex = 1;
break;
case 7:
// check behind
bCheckDir = (bDir) % 8;
bTargetIndex = 3;
break;
}
}
if (bDirLoop == 6 && bTargets > 1)
{
// we're done! otherwise we continue and try to find people in front/behind
break;
}
else if (pTargets[bTargetIndex] != NULL)
{
continue;
}
sTarget = sFirstTarget + DirIncrementer[bCheckDir];
pTarget = SimpleFindSoldier( sTarget, bTargetLevel );
if (pTarget && pSoldier->bOppList[ pTarget->ubID ] == SEEN_CURRENTLY)
{
bOtherAdjacents = CountAdjacentSpreadTargets( pSoldier, sTarget, bTargetLevel );
if (bOtherAdjacents > bAdjacents)
{
// we should do a spread-burst there instead!
return( CalcSpreadBurst( pSoldier, sTarget, bTargetLevel ) );
}
pTargets[bTargetIndex] = pTarget;
bTargets++;
}
}
if (bTargets > 1)
{
// Move all the locations down in the array if necessary
// Check the 4th position
if (pTargets[3] == NULL && pTargets[4] != NULL)
{
pTargets[3] = pTargets[4];
pTargets[4] = NULL;
}
// Check the first two positions; we know the 3rd value is set because
// it's our initial target
if (pTargets[1] == NULL)
{
pTargets[1] = pTargets[2];
pTargets[2] = pTargets[3];
pTargets[3] = pTargets[4];
pTargets[4] = NULL;
}
if (pTargets[0] == NULL)
{
pTargets[0] = pTargets[1];
pTargets[1] = pTargets[2];
pTargets[2] = pTargets[3];
pTargets[3] = pTargets[4];
pTargets[4] = NULL;
}
// now 50% chance to reorganize to fire in reverse order
if (Random( 2 ))
{
for( bLoop = 0; bLoop < bTargets / 2; bLoop++)
{
pTarget = pTargets[bLoop];
pTargets[bLoop] = pTargets[bTargets - 1 - bLoop];
pTargets[bTargets - 1 - bLoop] = pTarget;
}
}
AIPickBurstLocations( pSoldier, bTargets, pTargets );
pSoldier->fDoSpread = TRUE;
}
return( sFirstTarget );
}
INT16 AdvanceToFiringRange( SOLDIERTYPE * pSoldier, INT16 sClosestOpponent )
{
// see how far we can go down a path and still shoot
INT8 bAttackCost, bTrueActionPoints;
UINT16 usActionData;
bAttackCost = MinAPsToAttack(pSoldier, sClosestOpponent, ADDTURNCOST);
if (bAttackCost >= pSoldier->bActionPoints)
{
// probably want to go as far as possible!
// return( NOWHERE );
return( GoAsFarAsPossibleTowards( pSoldier, sClosestOpponent, AI_ACTION_SEEK_OPPONENT ) );
}
bTrueActionPoints = pSoldier->bActionPoints;
pSoldier->bActionPoints -= bAttackCost;
usActionData = GoAsFarAsPossibleTowards( pSoldier, sClosestOpponent, AI_ACTION_SEEK_OPPONENT );
pSoldier->bActionPoints = bTrueActionPoints;
return( usActionData );
}
void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN suppressionFire)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CheckIfShotPossible");
UINT8 ubMinAPcost;
pBestShot->ubPossible = FALSE;
pBestShot->bWeaponIn = NO_SLOT;
if ( !TANK( pSoldier ) )
{
// AIs never have more than one gun anyway
pBestShot->bWeaponIn = FindAIUsableObjClass( pSoldier, IC_GUN );
}
// if the soldier does have a long range rifle
if (pBestShot->bWeaponIn != NO_SLOT)
{
OBJECTTYPE * pObj = &pSoldier->inv[pBestShot->bWeaponIn];
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("CheckIfShotPossible: found a gun item # %d",pObj->usItem ));
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("CheckIfShotPossible: weapon type %d",Weapon [pObj->usItem ].ubWeaponType ));
// if it's in his holster, swap it into his hand temporarily
if (pBestShot->bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CheckIfShotPossible: Rearranging pocket");
RearrangePocket(pSoldier, HANDPOS, pBestShot->bWeaponIn, TEMPORARILY);
}
if ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > MaxDistanceVisible() ) || pSoldier->bOrders == SNIPER ) ) ||
(suppressionFire && IsGunAutofireCapable(pSoldier,pBestShot->bWeaponIn ) && GetMagSize(pObj) > 30 && pObj->ubGunShotsLeft > 20 ))
{
// get the minimum cost to attack with this item
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CheckIfShotPossible: getting min aps");
ubMinAPcost = MinAPsToAttack( pSoldier, pSoldier->sLastTarget, ADDTURNCOST);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("CheckIfShotPossible: min AP cost: %d", ubMinAPcost));
// if we can afford the minimum AP cost
if (pSoldier->bActionPoints >= ubMinAPcost)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("CheckIfShotPossible: CalcBestShot"));
// then look around for a worthy target (which sets bestThrow.ubPossible)
CalcBestShot( pSoldier, pBestShot,TRUE);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("CheckIfShotPossible: CalcBestShot done"));
}
}
// if it was in his holster, swap it back into his holster for now
if (pBestShot->bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CheckIfShotPossible: Rearranging pocket");
RearrangePocket( pSoldier, HANDPOS, pBestShot->bWeaponIn, TEMPORARILY );
}
}
}