mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
Made direction variables more consistent as unsigned chars. Several function prototypes updated for this. Many memory leaks plugged: External options, video overlays, laptop file lists, tactical message queue, strategic pathing, autobandage, merc hiring, detailed placements, crate in Drassen, tactical placement New functions and attributes for soldiers in LUA (still for debugging at best): Soldier.APs (current APs), Soldier.changestance (changes stance, uses game heights) File catalog ignores .SVN directories (game load speedup) Fix CTD in mouse regions JA2 window now refuses to move to negative coords Checks to prevent DirectX-related infinite loops due to minimizing and task switching Invading enemies should now appear on the borders even when reinforcements disabled in .ini Suppression should no longer work on mercs in medium water Infant/Young creatures use restored spit instead of Molotov Creatures begin with their 'guns' (spit) 'locked and loaded' (cartridge in chamber) Further fix to AXP and AlaarDB's weapon ready check: Now 'firing' is always counted as 'ready'. Prevent mercs from falling and flying back through obstacles Check whether battle group is even set before testing its location for battle setup Burst spread locations now limited to 6, the limit within the soldier struct Extra burst spread locations zeroed out so that they aren't used unless necessary Spread code now only shoots at locations in the spread, and will shoot at all 6 Only mercs in the sector where autobandage happens will be made to stand up after it's done Throwing a grenade at the tail of the plane in Drassen should not result in a CTD Reset attack busy count when loading a new sector git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@1347 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2503 lines
82 KiB
C++
2503 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].ItemData.Generic.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!
|
|
// Not unless we really needed to change targets!
|
|
//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);
|
|
Assert( ubRawAPCost > 0);
|
|
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;
|
|
UINT8 ubDirection;
|
|
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 (ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++)
|
|
{
|
|
sTempGridNo = NewGridNo( sGridNo, DirectionInc( ubDirection ) );
|
|
ubMovementCost = gubWorldMovementCosts[ sTempGridNo ][ ubDirection ][ 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 );
|
|
ubDirection = 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( ubDirection ) );
|
|
ubMovementCost = gubWorldMovementCosts[ sTempGridNo ][ ubDirection ][ 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 = 0;
|
|
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 = 0;
|
|
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;
|
|
UINT16 usItem;
|
|
OBJECTTYPE *pObj;
|
|
|
|
/*
|
|
if ( pOpponent->uiStatusFlags & SOLDIER_VEHICLE )
|
|
{
|
|
// only thing that can damage vehicles is HEAP rounds?
|
|
return( 0 );
|
|
}
|
|
*/
|
|
|
|
pObj = &(pSoldier->inv[pSoldier->ubAttackingHand]);
|
|
usItem = pObj->usItem;
|
|
|
|
if ( Item[ usItem ].usItemClass & IC_THROWING_KNIFE )
|
|
{
|
|
ubAmmoType = AMMO_KNIFE;
|
|
}
|
|
else
|
|
{
|
|
ubAmmoType = pObj->ItemData.Gun.ubGunAmmoType;
|
|
}
|
|
|
|
// calculate distance to target, obtain his gun's maximum range rating
|
|
|
|
iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, pOpponent->sGridNo );
|
|
iMaxRange = Weapon[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(pObj)) * (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].ItemData.Generic.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].ItemData.Generic.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].ItemData.Generic.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( 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].ItemData.Generic.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].ItemData.Generic.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->ItemData.Gun.ubGunShotsLeft && !(pObj->ItemData.Gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
|
|
{
|
|
pObj->ItemData.Gun.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->ItemData.Gun.ubGunShotsLeft && !(pObj2->ItemData.Gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
|
|
{
|
|
pObj2->ItemData.Gun.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->ItemData.Gun.ubGunShotsLeft && !(pObj2->ItemData.Gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
|
|
{
|
|
pObj2->ItemData.Gun.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) > pSoldier->MaxDistanceVisible(pBestShot->sTarget, pBestShot->bTargetLevel) ) || pSoldier->bOrders == SNIPER ) ) ||
|
|
if ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > MaxNormalDistanceVisible() ) || pSoldier->bOrders == SNIPER ) ) ||
|
|
(suppressionFire && IsGunAutofireCapable(pSoldier,pBestShot->bWeaponIn ) && GetMagSize(pObj) > 30 && pObj->ItemData.Gun.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 );
|
|
}
|
|
}
|
|
}
|