Red, Black AI: try to use sidearm if not enough AP to shoot with main weapon.

Black AI: improved check to avoid friendly fire.
FindAIUsableObjClass: by default search for weapon with longest range, if fSidearm = TRUE search for fastest weapon.
AICalcChanceToHitGun: if AI_EXTRA_SUPPRESSION = TRUE, always return min 1% for AI.
RangeChangeDesire:
- civilians only want to run away
- added small bonus to range change desire if soldier has no gun or short range weapon
CalcBestShot, CheckIfShotPossible: always check if can shoot at target seen by another soldier.
CalcBestShot, CalcBestThrow, CalcBestStab: initialize attack data.
Removed obsolete code.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8782 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Sevenfm
2020-04-08 21:24:56 +00:00
parent 5350258334
commit 4e34605f86
8 changed files with 209 additions and 607 deletions
+28 -12
View File
@@ -1744,32 +1744,48 @@ INT8 FindObjClass( SOLDIERTYPE * pSoldier, UINT32 usItemClass )
return( NO_SLOT );
}
INT8 FindAIUsableObjClass( SOLDIERTYPE * pSoldier, UINT32 usItemClass )
INT8 FindAIUsableObjClass(SOLDIERTYPE * pSoldier, UINT32 usItemClass, BOOLEAN fSidearm)
{
// finds the first object of the specified class which does NOT have
// the "unusable by AI" flag set.
// uses & rather than == so that this function can search for any weapon
// This is for the AI only so:
INT8 bBestSlot = NO_SLOT;
INT8 bInvSize = (INT8)pSoldier->inv.size();
// Do not consider tank cannons or rocket launchers to be "guns"
INT8 invsize = (INT8)pSoldier->inv.size();
for (INT8 bLoop = 0; bLoop < invsize; ++bLoop)
for (INT8 bLoop = 0; bLoop < bInvSize; ++bLoop)
{
if (pSoldier->inv[bLoop].exists() == true) {
if ( (Item[pSoldier->inv[bLoop].usItem].usItemClass & usItemClass) && !(pSoldier->inv[bLoop].fFlags & OBJECT_AI_UNUSABLE) && (pSoldier->inv[bLoop][0]->data.objectStatus >= USABLE ) )
if (pSoldier->inv[bLoop].exists())
{
if ((Item[pSoldier->inv[bLoop].usItem].usItemClass & usItemClass) &&
!(pSoldier->inv[bLoop].fFlags & OBJECT_AI_UNUSABLE) &&
(pSoldier->inv[bLoop][0]->data.objectStatus >= USABLE))
{
if ( usItemClass == IC_GUN && EXPLOSIVE_GUN( pSoldier->inv[bLoop].usItem ) )
// Do not consider tank cannons or rocket launchers to be "guns" for AI
if (usItemClass == IC_GUN && EXPLOSIVE_GUN(pSoldier->inv[bLoop].usItem))
{
continue;
}
return( bLoop );
// if not searching for gun, return first usable of required type
if (usItemClass != IC_GUN)
{
bBestSlot = bLoop;
break;
}
// we are searching for gun
if (bBestSlot == NO_SLOT || // haven't found any gun yet
// by default, search for weapon with longest range
!fSidearm && Weapon[pSoldier->inv[bLoop].usItem].usRange > Weapon[pSoldier->inv[bBestSlot].usItem].usRange ||
// when searching for sidearm, find fastest weapon
fSidearm && Weapon[Item[pSoldier->inv[bLoop].usItem].ubClassIndex].ubReadyTime + BaseAPsToShootOrStab(APBPConstants[DEFAULT_APS], APBPConstants[DEFAULT_AIMSKILL], &pSoldier->inv[bLoop], pSoldier) <
Weapon[Item[pSoldier->inv[bBestSlot].usItem].ubClassIndex].ubReadyTime + BaseAPsToShootOrStab(APBPConstants[DEFAULT_APS], APBPConstants[DEFAULT_AIMSKILL], &pSoldier->inv[bBestSlot], pSoldier))
{
bBestSlot = bLoop;
}
}
}
}
return( NO_SLOT );
return bBestSlot;
}
INT8 FindAIUsableObjClassWithin( SOLDIERTYPE * pSoldier, UINT32 usItemClass, INT8 bLower, INT8 bUpper )
+11 -17
View File
@@ -16,29 +16,23 @@ class SOLDIERTYPE;
#define NIGHTSIGHTGOGGLES_BONUS 2 * STRAIGHT_RATIO
#define UVGOGGLES_BONUS 4 * STRAIGHT_RATIO
extern UINT8 SlotToPocket[7];
extern BOOLEAN WeaponInHand( SOLDIERTYPE * pSoldier );
INT8 FindAmmo( SOLDIERTYPE * pSoldier, UINT8 ubCalibre, UINT16 ubMagSize, UINT8 ubAmmoType, INT8 bExcludeSlot );
INT8 FindBestWeaponIfCurrentIsOutOfRange(SOLDIERTYPE * pSoldier, INT8 bCurrentWeaponIndex, UINT16 bWantedRange);
INT16 FindAttachmentSlot( OBJECTTYPE* pObj, UINT16 usItem, UINT8 subObject = 0);
OBJECTTYPE* FindAttachment( OBJECTTYPE * pObj, UINT16 usItem, UINT8 subObject = 0 );
extern INT8 FindObjClass( SOLDIERTYPE * pSoldier, UINT32 usItemClass );
extern INT8 FindAIUsableObjClass( SOLDIERTYPE * pSoldier, UINT32 usItemClass );
extern INT8 FindAIUsableObjClassWithin( SOLDIERTYPE * pSoldier, UINT32 usItemClass, INT8 bLower, INT8 bUpper );
extern INT8 FindEmptySlotWithin( SOLDIERTYPE * pSoldier, INT8 bLower, INT8 bUpper );
extern INT8 FindObjInObjRange( SOLDIERTYPE * pSoldier, UINT16 usItem1, UINT16 usItem2 );
extern INT8 FindLaunchable( SOLDIERTYPE * pSoldier, UINT16 usWeapon );
extern INT8 FindGLGrenade( SOLDIERTYPE * pSoldier );
extern INT8 FindThrowableGrenade( SOLDIERTYPE * pSoldier );
extern INT8 FindUsableObj( SOLDIERTYPE * pSoldier, UINT16 usItem );
INT16 FindAttachmentSlot(OBJECTTYPE* pObj, UINT16 usItem, UINT8 subObject = 0);
OBJECTTYPE* FindAttachment(OBJECTTYPE * pObj, UINT16 usItem, UINT8 subObject = 0);
extern INT8 FindObjClass(SOLDIERTYPE * pSoldier, UINT32 usItemClass);
extern INT8 FindAIUsableObjClass(SOLDIERTYPE * pSoldier, UINT32 usItemClass, BOOLEAN fSidearm = FALSE);
extern INT8 FindAIUsableObjClassWithin(SOLDIERTYPE * pSoldier, UINT32 usItemClass, INT8 bLower, INT8 bUpper);
extern INT8 FindEmptySlotWithin(SOLDIERTYPE * pSoldier, INT8 bLower, INT8 bUpper);
extern INT8 FindObjInObjRange(SOLDIERTYPE * pSoldier, UINT16 usItem1, UINT16 usItem2);
extern INT8 FindLaunchable(SOLDIERTYPE * pSoldier, UINT16 usWeapon);
extern INT8 FindGLGrenade(SOLDIERTYPE * pSoldier);
extern INT8 FindThrowableGrenade(SOLDIERTYPE * pSoldier);
extern INT8 FindUsableObj(SOLDIERTYPE * pSoldier, UINT16 usItem);
void DeleteObj(OBJECTTYPE * pObj );
void SwapObjs( OBJECTTYPE * pObj1, OBJECTTYPE * pObj2 );
+22 -22
View File
@@ -7364,6 +7364,7 @@ UINT32 AICalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTim
// same as CCTHG but fakes the attacker always standing
usTrueState = pSoldier->usAnimState;
bTrueLevel = pSoldier->bTargetLevel;
pSoldier->bTargetLevel = bTargetLevel;
pSoldier->usAnimState = usAnimState;
if(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE)//dnl ch70 160913
@@ -7373,18 +7374,11 @@ UINT32 AICalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTim
else
{
uiChance = CalcChanceToHitGun(pSoldier, sGridNo, ubAimTime, ubAimPos);
// sevenfm: allow small CTH for AI for suppression fire
if( gGameExternalOptions.fAIExtraSuppression &&
pSoldier->aiData.bAlertStatus == STATUS_RED &&
Weapon[ pSoldier->usAttackingWeapon ].bAutofireShotsPerFiveAP > 0 )
{
uiChance = __max(1, uiChance);
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->bTargetLevel = bTrueLevel;
if(UsingNewCTHSystem() == true && !(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE))//dnl ch70 160913
if(UsingNewCTHSystem() && !(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE))//dnl ch70 160913
{
////////////////////////////////////////////////////////////////////////////////////
// HEADROCK HAM 4: NCTH calculation
@@ -7468,11 +7462,7 @@ UINT32 AICalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTim
// real aperture for shooter based on CTH calculation
dAperture = dAperture * ( 100 - uiChance ) / 100.0f;
if (dAperture == 0)
{
return 100;
}
else
if(dAperture > 0)
{
// silversurfer: This cannot be correct. An aperture of 10 already gives a very good chance to hit. If we take this value and
// calculate the target area it's PI * 10 * 10 which results in ~300 (rounded down) and not 28.26. This low number was the reason why AI
@@ -7488,18 +7478,19 @@ UINT32 AICalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTim
uiChance = (UINT32)__min(100, (dTargetArea / dApertureArea) * 100);
}
FLOAT dGunRange;
else
{
uiChance = 100;
}
// Flugente: check for underbarrel weapons and use that object if necessary
OBJECTTYPE* pObjAttHand = pSoldier->GetUsedWeapon( &(pSoldier->inv[pSoldier->ubAttackingHand]) );
dGunRange = (FLOAT)(GunRange( pObjAttHand, pSoldier ) );
FLOAT dGunRange = (FLOAT)(GunRange(pObjAttHand, pSoldier));
FLOAT dMaxGunRange = dGunRange * gGameCTHConstants.MAX_EFFECTIVE_RANGE_MULTIPLIER;
if ( dMaxGunRange < d2DDistance)
{
// Weapon out of conceivable hit range. Reduce chance to hit to 0!
return (0);
uiChance = 1; // sevenfm: set min CTH to 1% for AI
}
else if ( dGunRange < d2DDistance)
{
@@ -7508,15 +7499,24 @@ UINT32 AICalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTim
if (gGameCTHConstants.MAX_EFFECTIVE_USE_GRADIENT)
{
// Just outside range. Reduce considerably!
return min(uiChance, (UINT)(dChance - (dMaxChanceReduction * ((d2DDistance - dGunRange) / (dMaxGunRange - dGunRange)))));
uiChance = min(uiChance, (UINT32)(dChance - (dMaxChanceReduction * ((d2DDistance - dGunRange) / (dMaxGunRange - dGunRange)))));
}
else
{
return (UINT)(dChance - dMaxChanceReduction);
uiChance = (UINT32)(dChance - dMaxChanceReduction);
}
}
}
return( uiChance );
if (gGameExternalOptions.fAIExtraSuppression)
{
// sevenfm: always min 1% for AI to shoot
return max(1, uiChance);
}
else
{
return uiChance;
}
}
INT32 CalcBodyImpactReduction( UINT8 ubAmmoType, UINT8 ubHitLocation )
+2 -2
View File
@@ -187,7 +187,7 @@ typedef enum
INT16 AdvanceToFiringRange( SOLDIERTYPE * pSoldier, INT16 sClosestOpponent );
BOOLEAN AimingGun(SOLDIERTYPE *pSoldier);
void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUnseen);
void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot);
void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAttack);
void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow);
void CalcTentacleAttack(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab );
@@ -266,7 +266,7 @@ INT32 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier );
BOOLEAN ArmySeesOpponents( void );
void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN suppressionFire);
void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot);
INT32 FindBestCoverNearTheGridNo(SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubSearchRadius );
+25 -8
View File
@@ -3104,21 +3104,38 @@ INT32 RangeChangeDesire( SOLDIERTYPE * pSoldier )
INT32 iRangeFactorMultiplier;
iRangeFactorMultiplier = pSoldier->aiData.bAIMorale - 1;
// civilians only run away
if (pSoldier->aiData.bNeutral)
{
return 0;
}
switch (pSoldier->aiData.bAttitude)
{
case DEFENSIVE: iRangeFactorMultiplier += -1; break;
case BRAVESOLO: iRangeFactorMultiplier += 2; break;
case BRAVEAID: iRangeFactorMultiplier += 2; break;
case CUNNINGSOLO: iRangeFactorMultiplier += 0; break;
case CUNNINGAID: iRangeFactorMultiplier += 0; break;
case ATTACKSLAYONLY:
case AGGRESSIVE: iRangeFactorMultiplier += 1; break;
case DEFENSIVE: iRangeFactorMultiplier += -1; break;
case BRAVESOLO: iRangeFactorMultiplier += 2; break;
case BRAVEAID: iRangeFactorMultiplier += 2; break;
case CUNNINGSOLO: iRangeFactorMultiplier += 0; break;
case CUNNINGAID: iRangeFactorMultiplier += 0; break;
case ATTACKSLAYONLY:
case AGGRESSIVE: iRangeFactorMultiplier += 1; break;
}
if(IS_MERC_BODY_TYPE(pSoldier))
{
if (!AICheckHasGun(pSoldier))
iRangeFactorMultiplier += 2; // if we have no weapons, try to get closer to enemy
else if (GuySawEnemy(pSoldier, SEEN_LAST_TURN) && AICheckShortWeaponRange(pSoldier))
iRangeFactorMultiplier += 1; // bonus if weapon range is short
}
if ( gTacticalStatus.bConsNumTurnsWeHaventSeenButEnemyDoes > 0 )
{
iRangeFactorMultiplier += gTacticalStatus.bConsNumTurnsWeHaventSeenButEnemyDoes;
}
return( iRangeFactorMultiplier );
return iRangeFactorMultiplier;
}
BOOLEAN ArmySeesOpponents( void )
+76 -237
View File
@@ -161,7 +161,7 @@ void ResetWeaponMode( SOLDIERTYPE * pSoldier )
}
//</SB>
void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUnseen)
void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
{
UINT32 uiLoop;
INT32 iAttackValue, iThreatValue, iHitRate, iBestHitRate, iPercentBetter, iEstDamage, iTrueLastTarget;
@@ -176,23 +176,25 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
ubBestChanceToHit = ubBestAimTime = ubChanceToHit = ubChanceToReallyHit = 0;
// sevenfm: initialize
pBestShot->ubPossible = FALSE;
pBestShot->ubChanceToReallyHit = 0;
pBestShot->iAttackValue = 0;
pBestShot->ubOpponent = NOBODY;
pBestShot->ubFriendlyFireChance = 0;
// sevenfm: set attacking hand and target
pSoldier->ubAttackingHand = HANDPOS;
pSoldier->ubTargetID = NOBODY;
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
pSoldier->bWeaponMode = WM_NORMAL;
#ifdef dnlCALCBESTSHOT//dnl ch69 130913 rather setup available scopes here then in later inside loop
std::map<INT8, OBJECTTYPE*> ObjList;
GetScopeLists(pSoldier, &pSoldier->inv[HANDPOS], ObjList);
pSoldier->bScopeMode = USE_BEST_SCOPE;
pSoldier->bDoBurst = 0;
pSoldier->bDoAutofire = 0;
#else
INT16 ubBurstAPs, ubChanceToHit2;
#endif
//ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints( ), &(pSoldier->inv[HANDPOS]), pSoldier );//dnl ch64 270813
//InitAttackType(pBestShot); // set all structure fields to defaults//dnl ch69 150913 already initialize from class constructor
// hang a pointer into active soldier's personal opponent list
//pbPersOL = &(pSoldier->aiData.bOppList[0]);
// determine which attack against which target has the greatest attack value
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
@@ -208,14 +210,14 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
continue; // next merc
// if this opponent is not currently in sight (ignore known but unseen!)
if ((pSoldier->aiData.bOppList[pOpponent->ubID] != SEEN_CURRENTLY && !shootUnseen)) //guys we can't see
/*if ((pSoldier->aiData.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->aiData.bOppList[pOpponent->ubID]));
continue; // next opponent
}
}*/
if ((pSoldier->aiData.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]));
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("CalcBestShot: soldier = %d, target = %d, skip guys nobody sees, public opplist = %d", pSoldier->ubID, pOpponent->ubID, gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID]));
continue; // next opponent
}
@@ -231,26 +233,8 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
#ifdef DEBUGATTACKS
DebugAI( String( "%s sees %s at gridno %d\n",pSoldier->GetName(),ExtMen[pOpponent->ubID].GetName(),pOpponent->sGridNo ) );
#endif
#ifndef dnlCALCBESTSHOT//dnl ch69 140913
// calculate minimum action points required to shoot at this opponent
// if ( !Weapon[pSoldier->usAttackingWeapon].NoSemiAuto )
// SANDRO - calculate this with the alternative mode, as it is faster, decide the actual bScopeMode later
if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding )
{
if (!WeaponReady( pSoldier )) // but only if we are not already in raised weapon stance
pSoldier->bScopeMode = USE_ALT_WEAPON_HOLD;
else
pSoldier->bScopeMode = USE_BEST_SCOPE;
}
ubMinAPcost = MinAPsToAttack(pSoldier,pOpponent->sGridNo,ADDTURNCOST,0);
// 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( pSoldier->CalcActionPoints( ), &(pSoldier->inv[HANDPOS]), 3 );
//NumMessage("MinAPcost to shoot this opponent = ",ubMinAPcost);
#else
ubMinAPcost = MinAPsToAttack(pSoldier, pOpponent->sGridNo, DONTADDTURNCOST, 0);// later will be decide if shoot is possible this here is just best guess so ignore turnover
#endif
// if we don't have enough APs left to shoot even a snap-shot at this guy
if (ubMinAPcost > pSoldier->bActionPoints)
continue; // next opponent
@@ -313,150 +297,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
//}
iBestHitRate = 0; // reset best hit rate to minimum
#ifndef dnlCALCBESTSHOT//dnl ch69 130913 although there is nothing wrong with code unfortunately below and later in DecideAction is not use properly with missing conditions so AI get invalid action handling due to improper APs calculation, also try to optimize a bit to get less AICalcChanceToHitGun calls and to use with different stance decisions
// SANDRO: decide here, whether to use the alternative holding or normal holding
bScopeMode = USE_BEST_SCOPE;
if ( gGameExternalOptions.ubAllowAlternativeWeaponHolding )
{
pSoldier->bScopeMode = USE_ALT_WEAPON_HOLD;
ubChanceToHit = (INT16) AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, 0, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);//dnl ch59 130813 get CtH from alternative hold, without aiming
pSoldier->bScopeMode = USE_BEST_SCOPE;
// CASE #1 - Enemy very close, or we have very good chance to hit from hip with no aiming
if (( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) < 5 || ubChanceToHit > 80 ) && !WeaponReady(pSoldier) )
bScopeMode = USE_ALT_WEAPON_HOLD;
// CASE #2 - HeavyGun tag, or heavy LMG in hand
if (Weapon[pSoldier->usAttackingWeapon].HeavyGun || (Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG && GetBPCostPer10APsForGunHolding( pSoldier, TRUE ) > 50))
bScopeMode = USE_ALT_WEAPON_HOLD;
// reset the mode back
// CASE #3 - We don't have enough APs for shot from regular stance, and there is at least some reasonable chance we hit target from alternative mode
if ( (MinAPsToAttack(pSoldier,pOpponent->sGridNo,ADDTURNCOST,0) > pSoldier->bActionPoints) && (ubChanceToHit > 30) )
bScopeMode = USE_ALT_WEAPON_HOLD;
// CASE #4 - CtH with alternative hold is simply better than with normal hold (probably due to scope giving penalty on short range)
else if ( !WeaponReady(pSoldier) ) // ... and we are not in ready weapon stance yet
{
pSoldier->bScopeMode = USE_ALT_WEAPON_HOLD;
ubChanceToHit = (INT16) AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, AllowedAimingLevels( pSoldier, pOpponent->sGridNo ), AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);//dnl ch59 130813
pSoldier->bScopeMode = USE_BEST_SCOPE;
ubChanceToHit2 = (INT16) AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, AllowedAimingLevels( pSoldier, pOpponent->sGridNo ), AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);//dnl ch59 130813
if ( ubChanceToHit > ubChanceToHit2 && ubChanceToHit > 30 && (ubChanceToHit-ubChanceToHit2) >= 15 )
bScopeMode = USE_ALT_WEAPON_HOLD;
}
}
if ( gGameExternalOptions.fScopeModes )
{
// Now try to decide which scope mode is better
// we simply compare the chance to hit with various scopes at full aiming, sometimes, we get penalty for using a scope at close range
// in case the best scope is not actually the "best" (at this distance), we will use the other one
if ( bScopeMode == USE_BEST_SCOPE )
{
ubChanceToHit = (INT16) AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, AllowedAimingLevels( pSoldier, pOpponent->sGridNo ), AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);//dnl ch59 130813
std::map<INT8, OBJECTTYPE*> ObjList;
GetScopeLists(pSoldier, &pSoldier->inv[HANDPOS], ObjList);
do
{
pSoldier->bScopeMode++;
if ( ObjList[pSoldier->bScopeMode] != NULL )
{
ubChanceToHit2 = (INT16) AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, AllowedAimingLevels( pSoldier, pOpponent->sGridNo ), AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);//dnl ch59 130813
if ( ubChanceToHit2 > ubChanceToHit )
{
bScopeMode = pSoldier->bScopeMode;
ubChanceToHit = ubChanceToHit2;
}
}
}
while( ObjList[pSoldier->bScopeMode] == NULL && pSoldier->bScopeMode != NUM_SCOPE_MODES);
}
}
pSoldier->bScopeMode = bScopeMode; // just for later calculations
// recalculate MinAPsToAttack with our selected scope mode
ubMinAPcost = MinAPsToAttack(pSoldier,pOpponent->sGridNo,ADDTURNCOST,0);
// calc next attack's minimum shooting cost (excludes readying & turning)
ubRawAPCost = MinAPsToShootOrStab(pSoldier,pOpponent->sGridNo,0,FALSE);
// calculate the maximum possible aiming time
#if 0//dnl ch64 270813 maybe I'm wrong but don't see purpose of this
if ( ARMED_VEHICLE( pSoldier ) )
{
ubMaxPossibleAimTime = pSoldier->bActionPoints - ubMinAPcost;
// always burst
if ( ubMaxPossibleAimTime < ubBurstAPs )
{
// should this be a return instead?
continue;
}
// bursts aren't aimed
ubMaxPossibleAimTime = 0;
}
else
#endif
{
// HEADROCK HAM 3.6: Calculation must take into account APBP constants and other aiming modifications!
INT8 bAPsLeft = pSoldier->bActionPoints - ubMinAPcost;
ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP( pSoldier, pOpponent->sGridNo, bAPsLeft );
}
// consider the various aiming times
if ( !Weapon[pSoldier->usAttackingWeapon].NoSemiAuto )
{
for (ubAimTime = APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
{
//HandleMyMouseCursor(KEYBOARDALSO);
//NumMessage("ubAimTime = ",ubAimTime);
//dnl ch59 180813 Find true best chance to hit depending of stance so AI would be more eager to attack
ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo,ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);
if(!ARMED_VEHICLE(pSoldier))//dnl ch64 270813
{
if((ubChanceToHit2=AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, CROUCHING)) > ubChanceToHit)
ubChanceToHit = ubChanceToHit2;
if((ubChanceToHit2=AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, PRONE)) > ubChanceToHit)
ubChanceToHit = ubChanceToHit2;
}
// ExtMen[pOpponent->ubID].haveStats = TRUE;
//NumMessage("chance to Hit = ",ubChanceToHit);
//sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].GetName(),ubAimTime,ubChanceToHit);
//PopMessage(tempstr);
// sevenfm: 100 AP system
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
//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 = APBPConstants[AP_MIN_AIM_ATTACK];
//dnl ch59 180813 Find true best chance to hit depending of stance for autofire
ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);
if(!ARMED_VEHICLE(pSoldier))//dnl ch64 270813
{
if((ubChanceToHit2=AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, CROUCHING)) > ubChanceToHit)
ubChanceToHit = ubChanceToHit2;
if((ubChanceToHit2=AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, PRONE)) > ubChanceToHit)
ubChanceToHit = ubChanceToHit2;
}
Assert( ubRawAPCost > 0);
// sevenfm: 100AP system
iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
//iHitRate = (pSoldier->bActionPoints * ubChanceToHit) / (ubRawAPCost + ubAimTime);
iBestHitRate = iHitRate;
ubBestAimTime = ubAimTime;
ubBestChanceToHit = ubChanceToHit;
}
#else
//dnl ch69 130913 Hoping to optimize
// consider alternate holding mode and different scopes
for(pSoldier->bScopeMode=(gGameExternalOptions.ubAllowAlternativeWeaponHolding?USE_ALT_WEAPON_HOLD:USE_BEST_SCOPE); pSoldier->bScopeMode<=(gGameExternalOptions.fScopeModes?NUM_SCOPE_MODES-1:USE_BEST_SCOPE); pSoldier->bScopeMode++)
@@ -505,7 +346,6 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
{
ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, STANDING);
iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
//SendFmtMsg("CalcBestShot_S=%d hr=%d at=%d sm=%d op=%d/%d aps=%d,%d,%d,%d,%d", ubChanceToHit, iHitRate, ubAimTime, pSoldier->bScopeMode, pOpponent->ubID, pOpponent->sGridNo, ubMinAPcost, ubRawAPCost, sStanceAPcost, usTurningCost, usRaiseGunCost);
if(iHitRate > iBestHitRate || (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) && ubChanceToHit > ubBestChanceToHit)// rather take best chance for throwing knives
{
iBestHitRate = iHitRate;
@@ -560,7 +400,6 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
{
ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, CROUCHING);
iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
//SendFmtMsg("CalcBestShot_C=%d hr=%d at=%d sm=%d op=%d/%d aps=%d,%d,%d,%d,%d", ubChanceToHit, iHitRate, ubAimTime, pSoldier->bScopeMode, pOpponent->ubID, pOpponent->sGridNo, ubMinAPcost, ubRawAPCost, sStanceAPcost, usTurningCost, usRaiseGunCost);
if(iHitRate > iBestHitRate)
{
iBestHitRate = iHitRate;
@@ -597,7 +436,6 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
{
ubChanceToHit = AICalcChanceToHitGun(pSoldier, pOpponent->sGridNo, ubAimTime, AIM_SHOT_TORSO, pOpponent->pathing.bLevel, PRONE);
iHitRate = ((pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) * ubChanceToHit) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
//SendFmtMsg("CalcBestShot_P=%d hr=%d at=%d sm=%d op=%d/%d aps=%d,%d,%d,%d,%d", ubChanceToHit, iHitRate, ubAimTime, pSoldier->bScopeMode, pOpponent->ubID, pOpponent->sGridNo, ubMinAPcost, ubRawAPCost, sStanceAPcost, usTurningCost, usRaiseGunCost);
if(iHitRate > iBestHitRate)
{
iBestHitRate = iHitRate;
@@ -614,7 +452,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
}
}
}
#endif
// if we can't get any kind of hit rate at all
if (iBestHitRate == 0)
continue; // next opponent
@@ -730,12 +568,8 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
pBestShot->sTarget = pOpponent->sGridNo;
pBestShot->bTargetLevel = pOpponent->pathing.bLevel;
pBestShot->iAttackValue = iAttackValue;
#ifndef dnlCALCBESTSHOT//dnl ch69 140913
pBestShot->ubAPCost = ubMinAPcost;
#else
pBestShot->ubAPCost = sBestAPcost;
pBestShot->ubStance = ubBestStance;
#endif
pBestShot->bScopeMode = bScopeMode;
if(gUnderFire.Count(pSoldier->bTeam))//dnl ch61 180813
pBestShot->ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam);
@@ -743,7 +577,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
pBestShot->ubFriendlyFireChance = 0;
}
}
//if(pBestShot->ubPossible)SendFmtMsg("CalcBestShot;\r\n ID=%d Loc=%d APs=%d Ac=%d AcData=%d Al=%d, SM=%d, LAc=%d, NAc=%d AT=%d\r\n AP?=%d,%d,%d/%d BS=%d", pSoldier->ubID, pSoldier->sGridNo, pSoldier->bActionPoints, pSoldier->aiData.bAction, pSoldier->aiData.usActionData, pSoldier->aiData.bAlertStatus, pBestShot->bScopeMode, pSoldier->aiData.bLastAction, pSoldier->aiData.bNextAction, pBestShot->ubAimTime, pBestShot->ubAPCost, CalcAPCostForAiming(pSoldier, pBestShot->sTarget, (INT8)pBestShot->ubAimTime), CalcTotalAPsToAttack(pSoldier, pBestShot->sTarget, TRUE, pBestShot->ubAimTime), CalcTotalAPsToAttack(pSoldier, pBestShot->sTarget, FALSE, pBestShot->ubAimTime), pBestShot->ubStance);
pSoldier->bScopeMode = USE_BEST_SCOPE; // better reset this back
}
@@ -847,8 +681,15 @@ void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow)
usInHand = pSoldier->inv[HANDPOS].usItem;
usGrenade = NOTHING;
if ( IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS]) )
// sevenfm: initialize
pBestThrow->ubPossible = FALSE;
pBestThrow->ubChanceToReallyHit = 0;
pBestThrow->iAttackValue = 0;
if (IsGrenadeLauncherAttached(&pSoldier->inv[HANDPOS]))
{
usInHand = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
}
if ( EXPLOSIVE_GUN( usInHand ) )
{
@@ -1652,6 +1493,12 @@ void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAt
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
// sevenfm: initialize
pBestStab->ubPossible = FALSE;
pBestStab->iAttackValue = 0;
pBestStab->ubChanceToReallyHit = 0;
pBestStab->ubOpponent = NOBODY;
// temporarily make this guy run so we get a proper AP cost value
// from CalcTotalAPsToAttack
usTrueMovementMode = pSoldier->usUIMovementMode;
@@ -2515,9 +2362,9 @@ INT8 TryToReload( SOLDIERTYPE * pSoldier )
// modify by ini values
if ( Item[ pObj->usItem ].usItemClass == IC_GUN )
sModifiedReloadAP *= gItemSettings.fAPtoReloadManuallyModifierGun[ Weapon[ pObj->usItem ].ubWeaponType ];
sModifiedReloadAP = (INT16)(sModifiedReloadAP * gItemSettings.fAPtoReloadManuallyModifierGun[Weapon[pObj->usItem].ubWeaponType]);
else if ( Item[ pObj->usItem ].usItemClass == IC_LAUNCHER )
sModifiedReloadAP *= gItemSettings.fAPtoReloadManuallyModifierLauncher;
sModifiedReloadAP = (INT16)(sModifiedReloadAP * gItemSettings.fAPtoReloadManuallyModifierLauncher);
////////////////////////////////////////////////////////////////////////////////////////////////////////
// STOMP traits - SANDRO
@@ -2568,9 +2415,9 @@ INT8 TryToReload( SOLDIERTYPE * pSoldier )
// modify by ini values
if ( Item[ pObj2->usItem ].usItemClass == IC_GUN )
sModifiedReloadAP *= gItemSettings.fAPtoReloadManuallyModifierGun[ Weapon[ pObj2->usItem ].ubWeaponType ];
sModifiedReloadAP = (INT16)(sModifiedReloadAP * gItemSettings.fAPtoReloadManuallyModifierGun[Weapon[pObj2->usItem].ubWeaponType]);
else if ( Item[ pObj2->usItem ].usItemClass == IC_LAUNCHER )
sModifiedReloadAP *= gItemSettings.fAPtoReloadManuallyModifierLauncher;
sModifiedReloadAP = (INT16)(sModifiedReloadAP * gItemSettings.fAPtoReloadManuallyModifierLauncher);
////////////////////////////////////////////////////////////////////////////////////////////////////////
// STOMP traits - SANDRO (well, I don't know any one-handed sniper rifle, but what the hell...)
@@ -3109,76 +2956,68 @@ INT16 AdvanceToFiringRange( SOLDIERTYPE * pSoldier, INT16 sClosestOpponent )
}
void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN suppressionFire)
void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CheckIfShotPossible");
INT16 ubMinAPcost;
pBestShot->ubPossible = FALSE;
pBestShot->bWeaponIn = NO_SLOT;
if ( !ARMED_VEHICLE( pSoldier ) )
{
// AIs never have more than one gun anyway
pBestShot->bWeaponIn = FindAIUsableObjClass( pSoldier, IC_GUN );
}
pBestShot->bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN);
// if the soldier does have a long range rifle
// if the soldier does have a gun
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);
}
BOOLEAN fEnableAISuppression = FALSE;
// get the minimum cost to attack with this item
ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sLastTarget, ADDTURNCOST, 0);
// CHRISL: Changed from a simple flag to two externalized values for more modder control over AI suppression
if( suppressionFire &&
IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn]) &&
GetMagSize(pObj) >= gGameExternalOptions.ubAISuppressionMinimumMagSize &&
(*pObj)[0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo )
// if we can afford the minimum AP cost
if (pSoldier->bActionPoints >= ubMinAPcost)
{
fEnableAISuppression = TRUE;
// then look around for a worthy target (which sets bestThrow.ubPossible)
CalcBestShot(pSoldier, pBestShot);
}
// sevenfm: allow any soldier with long range weapon to shoot in RED state (if he can hit)
if( !suppressionFire &&
GunRange(pObj, pSoldier) > DAY_VISION_RANGE )
{
fEnableAISuppression = TRUE;
}
if (fEnableAISuppression)
{
// 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,pBestShot->ubAimTime);
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 );
}
// try to use sidearm
if (pSoldier->bActionPoints < ubMinAPcost && IS_MERC_BODY_TYPE(pSoldier))
{
pBestShot->bWeaponIn = FindAIUsableObjClass(pSoldier, IC_GUN, TRUE);
if (pBestShot->bWeaponIn != NO_SLOT)
{
// if it's in his holster, swap it into his hand temporarily
if (pBestShot->bWeaponIn != HANDPOS)
{
RearrangePocket(pSoldier, HANDPOS, pBestShot->bWeaponIn, TEMPORARILY);
}
// get the minimum cost to attack with this item
ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sLastTarget, ADDTURNCOST, 0);
if (pSoldier->bActionPoints >= ubMinAPcost)
{
// then look around for a worthy target (which sets bestThrow.ubPossible)
CalcBestShot(pSoldier, pBestShot);
}
// if it was in his holster, swap it back into his holster for now
if (pBestShot->bWeaponIn != HANDPOS)
{
RearrangePocket(pSoldier, HANDPOS, pBestShot->bWeaponIn, TEMPORARILY);
}
}
}
}
}
+1 -1
View File
@@ -1149,7 +1149,7 @@ INT8 CreatureDecideActionBlack( SOLDIERTYPE * pSoldier )
if (pSoldier->bActionPoints >= ubMinAPCost)
{
// look around for a worthy target (which sets BestShot.ubPossible)
CalcBestShot(pSoldier,&BestShot,FALSE);
CalcBestShot(pSoldier,&BestShot);
if (BestShot.ubPossible)
{
+44 -308
View File
@@ -1621,14 +1621,9 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
if (TileIsOutOfBounds(sNoiseGridNo))
{
// then we have no business being under YELLOW status any more!
#ifdef RECORDNET
fprintf(NetDebugFile,"\nDecideActionYellow: ERROR - No important noise known by guynum %d\n\n",pSoldier->ubID);
#endif
#ifdef BETAVERSION
NumMessage("DecideActionYellow: ERROR - No important noise known by guynum ",pSoldier->ubID);
#endif
return(AI_ACTION_NONE);
}
@@ -2349,14 +2344,9 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier)
////////////////////////////////////////////////////////////////////////////
if ((INT16)PreRandom(100) < 50 )
{
#ifdef RECORDNET
fprintf(NetDebugFile,"\tDecideActionYellow: guynum %d ignores noise, switching to GREEN AI...\n",pSoldier->ubID);
#endif
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"ignores noise completely and BYPASSES to GREEN!",1000);
#endif
// Skip YELLOW until new situation, 15% extra chance to do GREEN actions
pSoldier->aiData.bBypassToGreen = 15;
return(DecideActionGreen(pSoldier));
@@ -2757,13 +2747,8 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
}
}
//}//hayden
// SNIPER!
CheckIfShotPossible(pSoldier,&BestShot,FALSE);
CheckIfShotPossible(pSoldier, &BestShot);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: is sniper shot possible? = %d, CTH = %d",BestShot.ubPossible,BestShot.ubChanceToReallyHit));
if (BestShot.ubPossible && BestShot.ubChanceToReallyHit > 50 )
@@ -2875,7 +2860,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
}
//SUPPRESSION FIRE
CheckIfShotPossible(pSoldier,&BestShot,TRUE); //WarmSteel - No longer returns 0 when there IS actually a chance to hit.
CheckIfShotPossible(pSoldier,&BestShot); //WarmSteel - No longer returns 0 when there IS actually a chance to hit.
// sevenfm: check that we have a clip to reload
BOOLEAN fExtraClip = FALSE;
@@ -4352,14 +4337,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
pSoldier->bBleeding = __max( 0, pSoldier->bBleeding - (pSoldier->bActionPoints/2) );
return( AI_ACTION_NONE ); // will end-turn/wait depending on whether we're in TB or realtime
}
#ifdef RECORDNET
fprintf(NetDebugFile,"\tDecideActionRed: guynum %d switching to GREEN AI...\n",pSoldier->ubID);
#endif
#ifdef DEBUGDECISIONS
AINameMessage(pSoldier,"- chose to SKIP all RED actions, BYPASSES to GREEN!",1000);
#endif
// Skip RED until new situation/next turn, 30% extra chance to do GREEN actions
pSoldier->aiData.bBypassToGreen = 30;
return(DecideActionGreen(pSoldier));
@@ -5004,7 +4984,6 @@ INT16 ubMinAPCost;
BestAttack.ubChanceToReallyHit = 0;
// if we are able attack
if (bCanAttack)
{
@@ -5015,111 +4994,49 @@ INT16 ubMinAPCost;
//////////////////////////////////////////////////////////////////////////
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FIRE A GUN AT AN OPPONENT");
bWeaponIn = FindAIUsableObjClass( pSoldier, IC_GUN );
CheckIfShotPossible(pSoldier, &BestShot);
if (bWeaponIn != NO_SLOT)
if (BestShot.ubFriendlyFireChance) //dnl ch61 180813
{
BestShot.bWeaponIn = bWeaponIn;
// if it's in another pocket, swap it into his hand temporarily
if (bWeaponIn != HANDPOS)
// determine chance to shoot
INT32 iChanceToShoot;
iChanceToShoot = 100 - BestShot.ubFriendlyFireChance;
iChanceToShoot = iChanceToShoot * iChanceToShoot / 100;
DebugAI(AI_MSG_INFO, pSoldier, String("Friendly fire chance %d, chance to shoot %d", BestShot.ubFriendlyFireChance, iChanceToShoot));
if (Chance(100 - iChanceToShoot))
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap gun into hand");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
DebugAI(AI_MSG_INFO, pSoldier, String("Friendly fire check failed, skip shooting!"));
BestShot.ubPossible = FALSE;
}
}
if (BestShot.ubPossible)
{
// if the selected opponent is not a threat (unconscious & !serviced)
// (usually, this means all the guys we see are unconscious, but, on
// rare occasions, we may not be able to shoot a healthy guy, too)
if ((Menptr[BestShot.ubOpponent].stats.bLife < OKLIFE) &&
!Menptr[BestShot.ubOpponent].bService &&
(pSoldier->aiData.bAttitude != AGGRESSIVE || Chance((100 - BestShot.ubChanceToReallyHit) / 2)))
{
// get the location of the closest CONSCIOUS reachable opponent
sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb);
// if we found one
if (!TileIsOutOfBounds(sClosestDisturbance))
{
// then make decision as if at alert status RED
return DecideActionRed(pSoldier);
}
// else kill the guy, he could be the last opponent alive in this sector
}
// now it better be a gun, or the guy can't shoot (but has other attack(s))
if (Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_GUN && pSoldier->inv[HANDPOS][0]->data.gun.bGunStatus >= USABLE)
{
// get the minimum cost to attack the same target with this gun
ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,ADDTURNCOST,0);
// if we have enough action points to shoot with this gun
if (pSoldier->bActionPoints >= ubMinAPCost)
{
// look around for a worthy target (which sets BestShot.ubPossible)
BOOLEAN shootUnseen = FALSE;
///ddd
//if (gGameOptions.ubDifficultyLevel > DIF_LEVEL_MEDIUM ) //comm by ddd
if (gGameOptions.ubDifficultyLevel > DIF_LEVEL_MEDIUM || gGameExternalOptions.bNewTacticalAIBehavior)
shootUnseen = TRUE;
CalcBestShot(pSoldier,&BestShot,shootUnseen);
if (pSoldier->bTeam == gbPlayerNum && pSoldier->aiData.bRTPCombat == RTP_COMBAT_CONSERVE)
{
if (BestShot.ubChanceToReallyHit < 30)
{
// skip firing, our chance isn't good enough
BestShot.ubPossible = FALSE;
}
}
if(BestShot.ubFriendlyFireChance)//dnl ch61 180813
{
iChance = 0;
if(BestShot.ubFriendlyFireChance == 100)
{
if(pSoldier->aiData.bAttitude == AGGRESSIVE)
iChance = 5;
}
else
{
switch(pSoldier->aiData.bAttitude)
{
case DEFENSIVE:iChance = 15;break;
case BRAVESOLO:iChance = 25;break;
case BRAVEAID:iChance = 20;break;
case CUNNINGSOLO:iChance = 35;break;
case CUNNINGAID:iChance = 30;break;
case AGGRESSIVE:iChance = 45;break;
case ATTACKSLAYONLY:iChance = 40;break;
default:iChance = 10;break;
}
}
if(!((INT32)Random(100) < iChance))
BestShot.ubPossible = FALSE;
}
if (BestShot.ubPossible)
{
// if the selected opponent is not a threat (unconscious & !serviced)
// (usually, this means all the guys we see are unconscious, but, on
// rare occasions, we may not be able to shoot a healthy guy, too)
if ((Menptr[BestShot.ubOpponent].stats.bLife < OKLIFE) &&
!Menptr[BestShot.ubOpponent].bService &&
(pSoldier->aiData.bAttitude != AGGRESSIVE || Chance((100 - BestShot.ubChanceToReallyHit) / 2)))
{
// get the location of the closest CONSCIOUS reachable opponent
sClosestDisturbance = ClosestReachableDisturbance(pSoldier, &fClimb);
// if we found one
if (!TileIsOutOfBounds(sClosestDisturbance))
{
// don't bother checking GRENADES/KNIVES, he can't have conscious targets
#ifdef RECORDNET
fprintf(NetDebugFile,"\tDecideActionBlack: all visible opponents unconscious, switching to RED AI...\n");
#endif
// then make decision as if at alert status RED
return DecideActionRed(pSoldier);
}
// else kill the guy, he could be the last opponent alive in this sector
}
// now we KNOW FOR SURE that we will do something (shoot, at least)
NPCDoesAct(pSoldier);
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to shoot (or something)");
}
}
// if it was in his holster, swap it back into his holster for now
if (bWeaponIn != HANDPOS)
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap gun into holster");
RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY);
}
}
// now we KNOW FOR SURE that we will do something (shoot, at least)
NPCDoesAct(pSoldier);
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "NPC decided to shoot (or something)");
}
//////////////////////////////////////////////////////////////////////////
@@ -5207,7 +5124,7 @@ INT16 ubMinAPCost;
// throwing knife code works like shooting
// look around for a worthy target (which sets BestStab.ubPossible)
CalcBestShot(pSoldier,&BestStab,FALSE);
CalcBestShot(pSoldier,&BestStab);
if (BestStab.ubPossible)
{
@@ -5631,83 +5548,6 @@ INT16 ubMinAPCost;
if (ubBestAttackAction == AI_ACTION_FIRE_GUN)
{
#ifndef dnlCALCBESTSHOT//dnl ch69 140913
// Do we need to change stance? NB We'll have to ready our gun again
if ( !ARMED_VEHICLE( pSoldier ) && ( pSoldier->bActionPoints >= BestAttack.ubAPCost + GetAPsCrouch( pSoldier, TRUE) + MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST,0, 1 ) ) )
{
// since the AI considers shooting chance from standing primarily, if we are not
// standing we should always consider a stance change
if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_STAND )
{
iChance = 100;
}
else
{
iChance = 50;
switch (pSoldier->aiData.bAttitude)
{
case DEFENSIVE: iChance += 20; break;
case BRAVESOLO: iChance -= 10; break;
case BRAVEAID: iChance -= 10; break;
case CUNNINGSOLO: iChance += 10; break;
case CUNNINGAID: iChance += 10; break;
case AGGRESSIVE: iChance -= 20; break;
case ATTACKSLAYONLY: iChance -= 30; break;
}
}
if ( (INT32)PreRandom( 100 ) < iChance || GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, BestAttack.sTarget ) <= MIN_PRONE_RANGE )
{
// first get the direction, as we will need to pass that in to ShootingStanceChange
bDirection = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(BestAttack.sTarget),CenterY(BestAttack.sTarget));
ubBestStance = ShootingStanceChange( pSoldier, &BestAttack, bDirection );
if (ubBestStance != 0)
{
// change stance first!
if ( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) )
{
// we're not facing towards him, so turn first!
pSoldier->aiData.usActionData = bDirection;
#ifdef DEBUGDECISIONS
sprintf(tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->aiData.usActionData);
AIPopMessage(tempstr);
#endif
return(AI_ACTION_CHANGE_FACING);
}
// pSoldier->aiData.usActionData = ubBestStance;
// attack after we change stance
// we don't just return here because we want to check whether to
// burst first
fChangeStanceFirst = TRUE;
// account for increased AP cost and having to re-ready weapon
ubStanceCost = (UINT8) GetAPsToChangeStance( pSoldier, ubBestStance );
BestAttack.ubAPCost = MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST, 0, 1) + ubStanceCost;
// Clip the aim time if necessary
// HEADROCK HAM 3.6: Use Actual Aiming Costs, without assuming that each aim level = 1 AP.
if ( BestAttack.ubAPCost + sActualAimTime > pSoldier->bActionPoints )
{
// AP cost would balance (plus X, minus X) but aim time is reduced
// HEADROCK HAM 3.6: Use actual Aiming Costs.
BestAttack.ubAimTime = CalcAimingLevelsAvailableWithAP( pSoldier, BestAttack.sTarget, (pSoldier->bActionPoints - BestAttack.ubAPCost - ubStanceCost) );
if (BestAttack.ubAimTime < 0 )
{
// This is actually a logic error situation. The ChangeStance section is supposed
// to not make a shot impossible after changing stance.
BestAttack.ubPossible = 0;
}
}
}
}
}
#else
if ( gGameExternalOptions.fEnemyTanksCanMoveInTactical || !ARMED_VEHICLE( pSoldier ) )
{
// first get the direction, as we will need to pass that in to ShootingStanceChange
@@ -5744,7 +5584,6 @@ INT16 ubMinAPCost;
}
}
}
#endif
//////////////////////////////////////////////////////////////////////////
// IF ENOUGH APs TO BURST, RANDOM CHANCE OF DOING SO
@@ -7191,14 +7030,9 @@ INT8 ArmedVehicleDecideActionYellow( SOLDIERTYPE *pSoldier )
if ( TileIsOutOfBounds( sNoiseGridNo ) )
{
// then we have no business being under YELLOW status any more!
#ifdef RECORDNET
fprintf( NetDebugFile, "\ArmedVehicleDecideActionYellow: ERROR - No important noise known by guynum %d\n\n", pSoldier->ubID );
#endif
#ifdef BETAVERSION
NumMessage( "ArmedVehicleDecideActionYellow: ERROR - No important noise known by guynum ", pSoldier->ubID );
#endif
return(AI_ACTION_NONE);
}
@@ -7644,14 +7478,9 @@ INT8 ArmedVehicleDecideActionYellow( SOLDIERTYPE *pSoldier )
////////////////////////////////////////////////////////////////////////////
if ( (INT16)PreRandom( 100 ) < 50 )
{
#ifdef RECORDNET
fprintf( NetDebugFile, "\tDecideActionYellow: guynum %d ignores noise, switching to GREEN AI...\n", pSoldier->ubID );
#endif
#ifdef DEBUGDECISIONS
AINameMessage( pSoldier, "ignores noise completely and BYPASSES to GREEN!", 1000 );
#endif
// Skip YELLOW until new situation, 15% extra chance to do GREEN actions
pSoldier->aiData.bBypassToGreen = 15;
return(ArmedVehicleDecideActionGreen( pSoldier ));
@@ -7808,7 +7637,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier)
//}//hayden
// SNIPER!
CheckIfShotPossible( pSoldier, &BestShot, FALSE );
CheckIfShotPossible(pSoldier, &BestShot);
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "ArmedVehicleDecideActionRed: is sniper shot possible? = %d, CTH = %d", BestShot.ubPossible, BestShot.ubChanceToReallyHit ) );
if ( BestShot.ubPossible && BestShot.ubChanceToReallyHit > 50 )
@@ -7882,7 +7711,7 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier)
}
//SUPPRESSION FIRE
CheckIfShotPossible( pSoldier, &BestShot, TRUE ); //WarmSteel - No longer returns 0 when there IS actually a chance to hit.
CheckIfShotPossible(pSoldier, &BestShot); //WarmSteel - No longer returns 0 when there IS actually a chance to hit.
// sevenfm: check that we have a clip to reload
BOOLEAN fExtraClip = FALSE;
@@ -8795,14 +8624,9 @@ INT8 ArmedVehicleDecideActionRed( SOLDIERTYPE *pSoldier)
// if not in combat or under fire, and we COULD have moved, just chose not to
if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && (TileIsOutOfBounds( ClosestReachableDisturbance( pSoldier, &fClimb ) )) )
{
#ifdef RECORDNET
fprintf( NetDebugFile, "\tArmedVehicleDecideActionRed: guynum %d switching to GREEN AI...\n", pSoldier->ubID );
#endif
#ifdef DEBUGDECISIONS
AINameMessage( pSoldier, "- chose to SKIP all RED actions, BYPASSES to GREEN!", 1000 );
#endif
// Skip RED until new situation/next turn, 30% extra chance to do GREEN actions
pSoldier->aiData.bBypassToGreen = 30;
return(ArmedVehicleDecideActionGreen( pSoldier ));
@@ -9045,13 +8869,7 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier )
if ( pSoldier->bActionPoints >= ubMinAPCost )
{
// look around for a worthy target (which sets BestShot.ubPossible)
BOOLEAN shootUnseen = FALSE;
///ddd
//if (gGameOptions.ubDifficultyLevel > DIF_LEVEL_MEDIUM ) //comm by ddd
if ( gGameOptions.ubDifficultyLevel > DIF_LEVEL_MEDIUM || gGameExternalOptions.bNewTacticalAIBehavior )
shootUnseen = TRUE;
CalcBestShot( pSoldier, &BestShot, shootUnseen );
CalcBestShot(pSoldier, &BestShot);
if ( pSoldier->bTeam == gbPlayerNum && pSoldier->aiData.bRTPCombat == RTP_COMBAT_CONSERVE )
{
@@ -9105,10 +8923,6 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier )
// if we found one
if ( !TileIsOutOfBounds( sClosestDisturbance ) )
{
// don't bother checking GRENADES/KNIVES, he can't have conscious targets
#ifdef RECORDNET
fprintf( NetDebugFile, "\ArmedVehicleDecideActionBlack: all visible opponents unconscious, switching to RED AI...\n" );
#endif
// then make decision as if at alert status RED, but make sure
// we don't try to SEEK OPPONENT the unconscious guy!
return DecideActionRed(pSoldier);
@@ -9363,83 +9177,6 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier )
if ( ubBestAttackAction == AI_ACTION_FIRE_GUN )
{
#ifndef dnlCALCBESTSHOT//dnl ch69 140913
// Do we need to change stance? NB We'll have to ready our gun again
if ( !ARMED_VEHICLE( pSoldier ) && (pSoldier->bActionPoints >= BestAttack.ubAPCost + GetAPsCrouch( pSoldier, TRUE ) + MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST, 0, 1 )) )
{
// since the AI considers shooting chance from standing primarily, if we are not
// standing we should always consider a stance change
if ( gAnimControl[pSoldier->usAnimState].ubEndHeight != ANIM_STAND )
{
iChance = 100;
}
else
{
iChance = 50;
switch ( pSoldier->aiData.bAttitude )
{
case DEFENSIVE: iChance += 20; break;
case BRAVESOLO: iChance -= 10; break;
case BRAVEAID: iChance -= 10; break;
case CUNNINGSOLO: iChance += 10; break;
case CUNNINGAID: iChance += 10; break;
case AGGRESSIVE: iChance -= 20; break;
case ATTACKSLAYONLY: iChance -= 30; break;
}
}
if ( (INT32)PreRandom( 100 ) < iChance || GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, BestAttack.sTarget ) <= MIN_PRONE_RANGE )
{
// first get the direction, as we will need to pass that in to ShootingStanceChange
bDirection = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( BestAttack.sTarget ), CenterY( BestAttack.sTarget ) );
ubBestStance = ShootingStanceChange( pSoldier, &BestAttack, bDirection );
if ( ubBestStance != 0 )
{
// change stance first!
if ( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[pSoldier->usAnimState].ubEndHeight ) )
{
// we're not facing towards him, so turn first!
pSoldier->aiData.usActionData = bDirection;
#ifdef DEBUGDECISIONS
sprintf( tempstr, "%s - TURNS to face CLOSEST OPPONENT in direction %d", pSoldier->name, pSoldier->aiData.usActionData );
AIPopMessage( tempstr );
#endif
return(AI_ACTION_CHANGE_FACING);
}
// pSoldier->aiData.usActionData = ubBestStance;
// attack after we change stance
// we don't just return here because we want to check whether to
// burst first
fChangeStanceFirst = TRUE;
// account for increased AP cost and having to re-ready weapon
ubStanceCost = (UINT8)GetAPsToChangeStance( pSoldier, ubBestStance );
BestAttack.ubAPCost = MinAPsToAttack( pSoldier, BestAttack.sTarget, ADDTURNCOST, 0, 1 ) + ubStanceCost;
// Clip the aim time if necessary
// HEADROCK HAM 3.6: Use Actual Aiming Costs, without assuming that each aim level = 1 AP.
if ( BestAttack.ubAPCost + sActualAimTime > pSoldier->bActionPoints )
{
// AP cost would balance (plus X, minus X) but aim time is reduced
// HEADROCK HAM 3.6: Use actual Aiming Costs.
BestAttack.ubAimTime = CalcAimingLevelsAvailableWithAP( pSoldier, BestAttack.sTarget, (pSoldier->bActionPoints - BestAttack.ubAPCost - ubStanceCost) );
if ( BestAttack.ubAimTime < 0 )
{
// This is actually a logic error situation. The ChangeStance section is supposed
// to not make a shot impossible after changing stance.
BestAttack.ubPossible = 0;
}
}
}
}
}
#else
if ( gGameExternalOptions.fEnemyTanksCanMoveInTactical )
{
// first get the direction, as we will need to pass that in to ShootingStanceChange
@@ -9453,7 +9190,6 @@ INT8 ArmedVehicleDecideActionBlack( SOLDIERTYPE *pSoldier )
return(AI_ACTION_CHANGE_FACING);
}
}
#endif
//////////////////////////////////////////////////////////////////////////
// IF ENOUGH APs TO BURST, RANDOM CHANCE OF DOING SO