AI suppression fire improvements.

CalcBestShot:
- added standard check for valid opponent
- use standard knowledge functions
- blind soldier can only attack seen/heard personally
- determine suppression fire (shooting at target nobody sees currently)
- don't shoot if target location is seen and empty
- no fire on unseen opponents with throwing knives
- only enemy team can use blind suppression fire
- only try to suppress alive and conscious human targets
- additional check to limit shooting through walls
- alt weapon holding scope mode is used only when ubAllowAlternativeWeaponHolding == 3
- hip firing allowed only for human bodytypes
- take into account direction when checking stance
- shoot heavy guns in standing stance only when using hip fire
- penalize suppression fire

Red AI suppression fire:
- check valid target
- check weapon/ammo requirements
- check that soldier is not sniper
- check friendly fire chance
- reduce chance to shoot if target is beyond weapon range
- check that we have spare ammo

Improved code to prepare suppression fire:
- change stance first of needed
- allow shooting with aiming
- reserve APs to hide if no cover or enemy is close
- added debug messages

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8878 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Sevenfm
2020-08-06 07:53:12 +00:00
parent e9107eab34
commit 93c41f2f97
4 changed files with 379 additions and 117 deletions
+78
View File
@@ -5790,3 +5790,81 @@ UINT8 SectorCurfew(BOOLEAN fNight)
return ubSectorData;
}
INT32 RandomizeLocation(INT32 sSpot, INT8 bLevel, UINT8 ubTimes, SOLDIERTYPE *pSightSoldier)
{
if (TileIsOutOfBounds(sSpot))
{
return NOWHERE;
}
UINT8 ubDirection;
UINT8 ubMovementCost;
INT32 sTempSpot;
INT32 sSpotArray[NUM_WORLD_DIRECTIONS + 1];
UINT8 ubSpots;
for (UINT8 ubCnt = 0; ubCnt < ubTimes; ubCnt++)
{
// store original location
ubSpots = 1;
sSpotArray[0] = sSpot;
// find adjacent locations
for (ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++)
{
sTempSpot = NewGridNo(sSpot, DirectionInc(ubDirection));
if (sTempSpot != sSpot)
{
ubMovementCost = gubWorldMovementCosts[sTempSpot][ubDirection][bLevel];
if (ubMovementCost < TRAVELCOST_BLOCKED &&
IsLocationSittableExcludingPeople(sTempSpot, bLevel) &&
(!pSightSoldier || SoldierToVirtualSoldierLineOfSightTest(pSightSoldier, sTempSpot, bLevel, ANIM_STAND, TRUE, NO_DISTANCE_LIMIT)))
{
sSpotArray[ubSpots] = sTempSpot;
ubSpots++;
}
}
}
// find random location
sSpot = sSpotArray[Random(ubSpots)];
// stop if could not find any adjacent spot
if (ubSpots < 2)
{
break;
}
}
return sSpot;
}
INT32 RandomizeOpponentLocation(INT32 sSpot, SOLDIERTYPE *pOpponent, INT16 sMaxDistance)
{
if (TileIsOutOfBounds(sSpot))
{
return NOWHERE;
}
INT8 bXOffset, bYOffset;
INT32 sRandomSpot;
if (sMaxDistance > 0)
{
for (INT cnt = 0; cnt < min(sMaxDistance * 2, 100); cnt++)
{
bXOffset = Random(sMaxDistance * 2 + 1) - sMaxDistance;
bYOffset = Random(sMaxDistance * 2 + 1) - sMaxDistance;
sRandomSpot = sSpot + bXOffset + (MAXCOL * bYOffset);
if (!TileIsOutOfBounds(sRandomSpot) && NewOKDestination(pOpponent, sRandomSpot, FALSE, pOpponent->pathing.bLevel))
{
return sRandomSpot;
}
}
}
return sSpot;
}
+203 -72
View File
@@ -32,6 +32,7 @@
#include "Isometric Utils.h"
#include "Structure Wrap.h" // IsRoofPresentAtGridNo
#include "Render Fun.h"
#include "worldman.h"
#endif
// anv: for enemy taunts
@@ -170,15 +171,34 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
UINT32 uiLoop;
INT32 iAttackValue, iThreatValue, iHitRate, iBestHitRate, iPercentBetter, iEstDamage, iTrueLastTarget;
UINT16 usTrueState, usTurningCost, usRaiseGunCost;
INT16 ubAimTime, ubMinAPcost, ubRawAPCost, sBestAPcost, ubChanceToHit, ubBestAimTime, ubChanceToGetThrough, ubBestChanceToGetThrough, ubFriendlyFireChance, ubBestFriendlyFireChance, ubBestChanceToHit, sStanceAPcost;
INT16 ubAimTime;
INT16 ubMinAPcost;
INT16 ubRawAPCost;
INT16 sBestAPcost;
INT16 ubChanceToHit;
INT16 ubBestAimTime;
INT16 ubChanceToGetThrough;
INT16 ubBestChanceToGetThrough;
UINT8 ubFriendlyFireChance;
UINT8 ubBestFriendlyFireChance;
INT16 ubBestChanceToHit;
INT16 sStanceAPcost;
BOOLEAN fAddingTurningCost, fAddingRaiseGunCost;
UINT8 ubMaxPossibleAimTime, ubStance, ubBestStance, ubChanceToReallyHit;
INT8 bScopeMode;
SOLDIERTYPE *pOpponent;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalcBestShot");
// sevenfm:
BOOLEAN fSuppression = FALSE;
BOOLEAN fReturnFire = FALSE;
INT32 sTarget = NOWHERE;
INT8 bLevel;
ubBestChanceToHit = ubBestAimTime = ubChanceToHit = ubBestChanceToGetThrough = ubBestFriendlyFireChance = ubChanceToReallyHit = 0;
INT8 bKnowledge;
INT8 bPersonalKnowledge;
INT8 bPublicKnowledge;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalcBestShot");
// sevenfm: initialize
pBestShot->ubPossible = FALSE;
@@ -187,6 +207,8 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
pBestShot->ubOpponent = NOBODY;
pBestShot->ubFriendlyFireChance = 0;
ubBestChanceToHit = ubBestAimTime = ubChanceToHit = ubBestChanceToGetThrough = ubBestFriendlyFireChance = ubChanceToReallyHit = 0;
// sevenfm: set attacking hand and target
pSoldier->ubAttackingHand = HANDPOS;
pSoldier->ubTargetID = NOBODY;
@@ -204,40 +226,131 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pOpponent = MercSlots[ uiLoop ];
fSuppression = FALSE;
fReturnFire = FALSE;
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent || !pOpponent->stats.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 (!ValidOpponent(pSoldier, pOpponent))
{
continue;
}
// 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
// determine return fire
if (pSoldier->aiData.bUnderFire &&
!pSoldier->bBlindedCounter &&
pSoldier->ubPreviousAttackerID == pOpponent->ubID)
{
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
fReturnFire = TRUE;
}
bKnowledge = Knowledge(pSoldier, pOpponent->ubID);
bPersonalKnowledge = PersonalKnowledge(pSoldier, pOpponent->ubID);
bPublicKnowledge = PublicKnowledge(pSoldier->bTeam, pOpponent->ubID);
// check knowledge
if (bKnowledge != SEEN_CURRENTLY &&
bKnowledge != SEEN_THIS_TURN &&
bKnowledge != SEEN_LAST_TURN &&
bKnowledge != HEARD_THIS_TURN &&
bKnowledge != HEARD_LAST_TURN &&
!((bKnowledge == SEEN_2_TURNS_AGO || bKnowledge == SEEN_3_TURNS_AGO || bKnowledge == HEARD_2_TURNS_AGO) && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG))
{
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
}
// silversurfer: ignore empty vehicles
// sevenfm: allow shooting at empty vehicles, but they have low priority
//if ( pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle( pOpponent->bVehicleID ) == 0 )
//continue;
// Special stuff for Carmen the bounty hunter
if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
// sevenfm: blind soldier can only attack seen/heard personally
if (pSoldier->bBlindedCounter > 0 &&
bPersonalKnowledge != SEEN_THIS_TURN &&
bPersonalKnowledge != HEARD_THIS_TURN)
{
continue; // next opponent
}
// sevenfm: determine if we shoot on unseen target for suppression
if (bPersonalKnowledge != SEEN_CURRENTLY &&
bPublicKnowledge != SEEN_CURRENTLY &&
!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
{
fSuppression = TRUE;
}
// determine enemy location
if (fSuppression)
{
// personal/public knowledge
sTarget = KnownLocation(pSoldier, pOpponent->ubID);
bLevel = KnownLevel(pSoldier, pOpponent->ubID);
// try to randomize location
sTarget = RandomizeLocation(sTarget, bLevel, 1, pSoldier);
}
else
{
// we know exact enemy location
sTarget = pOpponent->sGridNo;
bLevel = pOpponent->pathing.bLevel;
}
// safety check
if (TileIsOutOfBounds(sTarget))
{
continue;
}
// skip if we can see location and location is empty
if (SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_PRONE, TRUE, CALC_FROM_ALL_DIRS) &&
WhoIsThere2(sTarget, bLevel) == NOBODY)
{
continue;
}
// no fire on unseen opponents with throwing knives
if ((Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) &&
bPersonalKnowledge != SEEN_CURRENTLY &&
!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
{
continue;
}
// sevenfm: only enemy team can use blind suppression fire
if (fSuppression &&
pSoldier->bTeam != ENEMY_TEAM)
{
continue;
}
// sevenfm: only try to suppress alive and conscious human targets
if (fSuppression &&
(pOpponent->stats.bLife < OKLIFE ||
pOpponent->bCollapsed && pOpponent->bBreath == 0 ||
//pOpponent->usAnimState == COWERING ||
//pOpponent->usAnimState == COWERING_PRONE ||
CoweringShockLevel(pOpponent) ||
pOpponent->IsZombie() ||
!IS_MERC_BODY_TYPE(pOpponent)))
{
continue;
}
// shoot through wall check
if( bPersonalKnowledge != SEEN_CURRENTLY &&
!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS) &&
!SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_STAND, TRUE, NO_DISTANCE_LIMIT) &&
!LocationToLocationLineOfSightTest(pSoldier->sGridNo, pSoldier->pathing.bLevel, sTarget, bLevel, TRUE, NO_DISTANCE_LIMIT) &&
!fReturnFire &&
!(InARoom(sTarget, NULL) && bLevel == 0 && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG) && // bPublicKnowledge == SEEN_CURRENTLY &&
!(InARoom(sTarget, NULL) && bLevel == 0 && TeamPercentKilled(pSoldier->bTeam) > (100 - 20 * SoldierDifficultyLevel(pSoldier))) )
{
continue;
}
#ifdef DEBUGATTACKS
DebugAI( String( "%s sees %s at gridno %d\n",pSoldier->GetName(),ExtMen[pOpponent->ubID].GetName(),pOpponent->sGridNo ) );
#endif
ubMinAPcost = MinAPsToAttack(pSoldier, pOpponent->sGridNo, DONTADDTURNCOST, 0);// later will be decide if shoot is possible this here is just best guess so ignore turnover
ubMinAPcost = MinAPsToAttack(pSoldier, sTarget, DONTADDTURNCOST, 0);
// later will be decide if shoot is possible this here is just best guess so ignore turnover
// if we don't have enough APs left to shoot even a snap-shot at this guy
if (ubMinAPcost > pSoldier->bActionPoints)
@@ -274,7 +387,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
INT8 bDir;
// must make sure that structure data can be added in the direction of the target
bDir = (INT8) GetDirectionToGridNoFromGridNo( pSoldier->sGridNo, pOpponent->sGridNo );
bDir = (INT8)GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sTarget);
// ATE: Only if we have a levelnode...
if ( pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL )
@@ -294,38 +407,37 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
}
}
//if (pOpponent->sGridNo == pSoldier->sLastTarget)
//{
// // raw AP cost calculation included cost of changing target!
// // Not unless we really needed to change targets!
// //ubRawAPCost -= APBPConstants[AP_CHANGE_TARGET];
//}
iBestHitRate = 0; // reset best hit rate to minimum
//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++)
// sevenfm: alt weapon holding scope mode is used only when ubAllowAlternativeWeaponHolding == 3
for(pSoldier->bScopeMode = (gGameExternalOptions.ubAllowAlternativeWeaponHolding == 3 ? USE_ALT_WEAPON_HOLD : USE_BEST_SCOPE);
pSoldier->bScopeMode <= (gGameExternalOptions.fScopeModes ? NUM_SCOPE_MODES - 1 : USE_BEST_SCOPE);
pSoldier->bScopeMode++)
{
//dnl ch71 180913 throwing knives cannot be use in fire from hip, also SANDRO said: if the gun is flagged as HeavyGun, then we can only fire it from hip, thus no scopes to use at all, not even iron sights
if(pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD)
{
//dnl ch71 180913 throwing knives cannot be used in fire from hip
if(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE)
continue;
}
else
{
if(Weapon[pSoldier->usAttackingWeapon].HeavyGun)
// sevenfm: hip firing allowed only for human bodytypes
if(!IS_MERC_BODY_TYPE(pSoldier))
continue;
}
if(pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || (pSoldier->bScopeMode >= USE_BEST_SCOPE && ObjList[pSoldier->bScopeMode] != NULL))
{
usTrueState = pSoldier->usAnimState;// because is used in CalculateRaiseGunCost, CalcAimingLevelsAvailableWithAP, CalculateTurningCost
iTrueLastTarget = pSoldier->sLastTarget;// because is used in MinAPsToShootOrStab
usTrueState = pSoldier->usAnimState; // because is used in CalculateRaiseGunCost, CalcAimingLevelsAvailableWithAP, CalculateTurningCost
iTrueLastTarget = pSoldier->sLastTarget; // because is used in MinAPsToShootOrStab
// --------- Standing ---------
ubStance = ANIM_STAND;
if(IsValidStance(pSoldier, ubStance))
// sevenfm: take into account direction when checking stance
// sevenfm: shoot heavy guns in standing stance only when using hip fire
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance) &&
(pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || !Weapon[pSoldier->usAttackingWeapon].HeavyGun || !Item[pSoldier->usAttackingWeapon].twohanded || !gGameExternalOptions.ubAllowAlternativeWeaponHolding))
{
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if(sStanceAPcost)
@@ -334,9 +446,9 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
pSoldier->usAnimState = STANDING;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, pOpponent->sGridNo, 0);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
if(fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
{
if(usRaiseGunCost > usTurningCost)
@@ -344,27 +456,31 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
else
usRaiseGunCost = 0;
}
ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2);
ubRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if(pSoldier->bActionPoints - ubMinAPcost >= 0)
{
// calc next attack's minimum shooting cost (excludes readying & turning & raise gun)
ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost);
ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - ubMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (ubAimTime = APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
for (ubAimTime = 0; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
{
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]);
ubChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, ubAimTime, AIM_SHOT_TORSO, bLevel, STANDING);
iHitRate = ubChanceToHit * (pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough ||
(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) && ubChanceToHit > ubBestChanceToHit)// rather take best chance for throwing knives
@@ -385,12 +501,14 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
pSoldier->sLastTarget = iTrueLastTarget;
}
// no crouched/prone if we are tank/using throwing knife/hip firing
if ( pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || ARMED_VEHICLE( pSoldier ) || (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) )
continue;
// --------- Crouched ---------
ubStance = ANIM_CROUCH;
if(IsValidStance(pSoldier, ubStance))
// sevenfm: take into account direction
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
{
// change stance then turn
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
@@ -399,9 +517,9 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
pSoldier->usAnimState = CROUCHING;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, pOpponent->sGridNo, 0);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
if(fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
{
if(usRaiseGunCost > usTurningCost)
@@ -409,27 +527,30 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
else
usRaiseGunCost = 0;
}
ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2);
ubRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if(pSoldier->bActionPoints - ubMinAPcost >= 0)
{
ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost);
ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - ubMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (ubAimTime = APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
for (ubAimTime = 0; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
{
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]);
ubChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, ubAimTime, AIM_SHOT_TORSO, bLevel, CROUCHING);
iHitRate = ubChanceToHit * (pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
{
@@ -450,7 +571,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
}
// no prone stance if we have to change direction and stance at the same time
if (pSoldier->ubDirection != AIDirection(pSoldier->sGridNo, pOpponent->sGridNo) &&
if (pSoldier->ubDirection != AIDirection(pSoldier->sGridNo, sTarget) &&
gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE)
{
continue;
@@ -458,8 +579,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
// --------- Prone ---------
ubStance = ANIM_PRONE;
//if(IsValidStance(pSoldier, ubStance))
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, pOpponent->sGridNo), ubStance))
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
{
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if (sStanceAPcost)
@@ -467,30 +587,33 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
pSoldier->usAnimState = PRONE;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, pOpponent->sGridNo, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, pOpponent->sGridNo, 0);
ubRawAPCost = MinAPsToShootOrStab(pSoldier, pOpponent->sGridNo, 0, FALSE, 2);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
ubRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
ubMinAPcost = ubRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if(pSoldier->bActionPoints - ubMinAPcost >= 0)
if (pSoldier->bActionPoints - ubMinAPcost >= 0)
{
ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, pOpponent->sGridNo, pSoldier->bActionPoints-ubMinAPcost);
ubMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - ubMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (ubAimTime = APBPConstants[AP_MIN_AIM_ATTACK]; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
for (ubAimTime = 0; ubAimTime <= ubMaxPossibleAimTime; ubAimTime++)
{
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]);
ubChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, ubAimTime, AIM_SHOT_TORSO, bLevel, PRONE);
iHitRate = ubChanceToHit * (pSoldier->bActionPoints - (ubMinAPcost - ubRawAPCost)) / (ubRawAPCost + ubAimTime * APBPConstants[AP_CLICK_AIM]);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
{
@@ -524,14 +647,15 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
continue; // next opponent
// really limit knife throwing so it doesn't look wrong
if ( Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE && (ubChanceToReallyHit < 25 || ( PythSpacesAway( pSoldier->sGridNo, pOpponent->sGridNo ) > CalcMaxTossRange( pSoldier, pSoldier->usAttackingWeapon, FALSE ) )))// Madd / 2 ) ) ) //dnl ch69 160913 was ubChanceToReallyHit < 30
if (Item[pSoldier->usAttackingWeapon].usItemClass == IC_THROWING_KNIFE &&
(ubChanceToReallyHit < 25 || (PythSpacesAway(pSoldier->sGridNo, sTarget) > CalcMaxTossRange(pSoldier, pSoldier->usAttackingWeapon, FALSE))))// Madd / 2 ) ) ) //dnl ch69 160913 was ubChanceToReallyHit < 30
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);
iThreatValue = CalcManThreatValue(pOpponent, pSoldier->sGridNo, TRUE, pSoldier);
// estimate the damage this shot would do to this opponent
iEstDamage = EstimateShotDamage(pSoldier,pOpponent,ubBestChanceToHit);
iEstDamage = EstimateShotDamage(pSoldier, pOpponent, ubBestChanceToHit);
//NumMessage("SHOT EstDamage = ",iEstDamage);
// calculate the combined "attack value" for this opponent
@@ -540,6 +664,13 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
iAttackValue = (iEstDamage * iBestHitRate * ubChanceToReallyHit * iThreatValue) / 1000;
//NumMessage("SHOT AttackValue = ",iAttackValue / 1000);
// sevenfm: penalize suppression fire
if (fSuppression)
{
// 25% penalty for shooting at invisible target
iAttackValue = iAttackValue / 2;
}
// special stuff for assassins to ignore militia more
if ( pSoldier->IsAssassin() && pOpponent->bTeam == MILITIA_TEAM )
{
@@ -624,8 +755,8 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot)
pBestShot->ubOpponent = pOpponent->ubID;
pBestShot->ubAimTime = ubBestAimTime;
pBestShot->ubChanceToReallyHit = ubChanceToReallyHit;
pBestShot->sTarget = pOpponent->sGridNo;
pBestShot->bTargetLevel = pOpponent->pathing.bLevel;
pBestShot->sTarget = sTarget;
pBestShot->bTargetLevel = bLevel;
pBestShot->iAttackValue = iAttackValue;
pBestShot->ubAPCost = sBestAPcost;
pBestShot->ubStance = ubBestStance;
+96 -42
View File
@@ -2910,7 +2910,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
}
//SUPPRESSION FIRE
CheckIfShotPossible(pSoldier,&BestShot); //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;
@@ -2929,77 +2929,131 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier)
// CHRISL: Changed from a simple flag to two externalized values for more modder control over AI suppression
// WarmSteel - Don't *always* try to suppress when under 50 CTH
if( BestShot.bWeaponIn != -1
&& BestShot.ubPossible
&& GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize
&& pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo
//&& BestShot.ubChanceToReallyHit < (INT16)(PreRandom(50))
//&& Menptr[BestShot.ubOpponent].pathing.bLevel == 0
&& pSoldier->aiData.bOrders != SNIPER &&
BestShot.ubFriendlyFireChance < 5 &&
!CoweringShockLevel(MercPtrs[BestShot.ubOpponent]) &&
if (BestShot.ubPossible &&
BestShot.bWeaponIn != -1 &&
// check valid target
!TileIsOutOfBounds(BestShot.sTarget) &&
BestShot.ubOpponent != NOBODY &&
MercPtrs[BestShot.ubOpponent] &&
// check weapon/ammo requirements
IsGunAutofireCapable(&pSoldier->inv[BestShot.bWeaponIn]) &&
GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize &&
pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo &&
// check soldier and weapon
pSoldier->aiData.bOrders != SNIPER &&
BestShot.ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE &&
!AICheckIsFlanking(pSoldier) &&
LocationToLocationLineOfSightTest( pSoldier->sGridNo, pSoldier->pathing.bLevel, MercPtrs[BestShot.ubOpponent]->sGridNo, MercPtrs[BestShot.ubOpponent]->pathing.bLevel, TRUE, NO_DISTANCE_LIMIT) &&
//Weapon[pSoldier->inv[BestShot.bWeaponIn].usItem].ubWeaponType == GUN_LMG ) && //Weapon[usInHand].ubWeaponClass == MGCLASS
(fExtraClip || pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft > gGameExternalOptions.ubAISuppressionMinimumMagSize) )
// reduce chance to shoot if target is beyond weapon range
(AICheckIsMachinegunner(pSoldier) ||
ARMED_VEHICLE(pSoldier) ||
AnyCoverAtSpot(pSoldier, pSoldier->sGridNo) ||
pSoldier->aiData.bUnderFire && (pSoldier->ubPreviousAttackerID == BestShot.ubOpponent || pSoldier->ubNextToPreviousAttackerID == BestShot.ubOpponent || MercPtrs[BestShot.ubOpponent]->sLastTarget == pSoldier->sGridNo) || // return fire
Chance(100 * (GunRange(&pSoldier->inv[BestShot.bWeaponIn], pSoldier) / CELL_X_SIZE) / PythSpacesAway(pSoldier->sGridNo, BestShot.sTarget))) &&
// check that we have spare ammo
(fExtraClip || pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumMagSize) )
{
// then do it!
// if necessary, swap the usItem from holster into the hand position
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: suppression fire possible!");
DebugAI(AI_MSG_INFO, pSoldier, String("suppression fire possible! target %d level %d aim %d", BestShot.sTarget, BestShot.bTargetLevel, BestShot.ubAimTime));
if (BestShot.bWeaponIn != HANDPOS)
{
RearrangePocket(pSoldier,HANDPOS,BestShot.bWeaponIn,FOREVER);
DebugAI(AI_MSG_INFO, pSoldier, String("rearrange pocket"));
RearrangePocket(pSoldier, HANDPOS, BestShot.bWeaponIn, FOREVER);
}
pSoldier->aiData.usActionData = BestShot.sTarget;
pSoldier->bTargetLevel = BestShot.bTargetLevel;
pSoldier->aiData.bAimTime = 0;
pSoldier->bDoAutofire = 0;
pSoldier->bDoBurst = 1;
pSoldier->bTargetLevel = BestShot.bTargetLevel;
pSoldier->aiData.bAimTime = BestShot.ubAimTime;
pSoldier->bDoAutofire = 0;
pSoldier->bDoBurst = 1;
pSoldier->bScopeMode = BestShot.bScopeMode;
INT16 ubBurstAPs = 0;
FLOAT dTotalRecoil = 0;
INT32 sActualAimAP;
UINT8 ubAutoPenalty;
INT16 sReserveAP = GetAPsProne(pSoldier, TRUE);
UINT8 ubMinAuto = 5;
if(UsingNewCTHSystem() == true)
if (BestShot.ubAimTime > 0 &&
!UsingNewCTHSystem() &&
Chance((100 - BestShot.ubChanceToReallyHit) * (100 - BestShot.ubChanceToReallyHit) / 100))
{
do
{
pSoldier->bDoAutofire++;
dTotalRecoil += AICalcRecoilForShot( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire );
ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
}
while( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs && pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire
&& dTotalRecoil <= 10.0f );
DebugAI(AI_MSG_INFO, pSoldier, String("set ubAimTime = 0 for OCTH suppression"));
BestShot.ubAimTime = 0;
}
else
// reserve APs to hide if no cover or enemy is close
if (!AnyCoverAtSpot(pSoldier, pSoldier->sGridNo) || PythSpacesAway(pSoldier->sGridNo, BestShot.sTarget) < DAY_VISION_RANGE / 2)
{
sReserveAP = APBPConstants[AP_MINIMUM] / 2;
}
if (PythSpacesAway(pSoldier->sGridNo, BestShot.sTarget) > DAY_VISION_RANGE || AnyCoverAtSpot(pSoldier, pSoldier->sGridNo) || pSoldier->aiData.bUnderFire)
{
ubMinAuto *= 2;
}
sActualAimAP = CalcAPCostForAiming(pSoldier, BestShot.sTarget, (INT8)pSoldier->aiData.bAimTime);
if (UsingNewCTHSystem() == true)
{
do
{
pSoldier->bDoAutofire++;
ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
}
while( pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs &&
pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire &&
GetAutoPenalty(&pSoldier->inv[ pSoldier->ubAttackingHand ], gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE)*pSoldier->bDoAutofire <= 80 );
dTotalRecoil += AICalcRecoilForShot(pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire);
ubBurstAPs = CalcAPsToAutofire(pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier);
} while (pSoldier->bActionPoints >= BestShot.ubAPCost + sActualAimAP + ubBurstAPs + sReserveAP &&
pSoldier->inv[pSoldier->ubAttackingHand][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire &&
pSoldier->bDoAutofire <= 30 &&
(dTotalRecoil <= 20.0f || pSoldier->bDoAutofire < ubMinAuto));
}
else
{
ubAutoPenalty = GetAutoPenalty(&pSoldier->inv[pSoldier->ubAttackingHand], gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE);
do
{
pSoldier->bDoAutofire++;
ubBurstAPs = CalcAPsToAutofire(pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier);
} while (pSoldier->bActionPoints >= BestShot.ubAPCost + sActualAimAP + ubBurstAPs + sReserveAP &&
pSoldier->inv[pSoldier->ubAttackingHand][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire &&
pSoldier->bDoAutofire <= 30 &&
(ubAutoPenalty * pSoldier->bDoAutofire <= 80 || pSoldier->bDoAutofire < ubMinAuto));
}
pSoldier->bDoAutofire--;
// Make sure we decided to fire at least one shot!
ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier );
ubBurstAPs = CalcAPsToAutofire(pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire, pSoldier);
DebugAI(AI_MSG_INFO, pSoldier, String("autofire shots %d APcost %d burst AP %d aimtime %d reserve AP %d", pSoldier->bDoAutofire, BestShot.ubAPCost, ubBurstAPs, sActualAimAP, sReserveAP));
// minimum 5 bullets
if (pSoldier->bDoAutofire >= 5 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs )
// minimum 3 bullets
if (pSoldier->bDoAutofire >= 3 && pSoldier->bActionPoints >= BestShot.ubAPCost + sActualAimAP + ubBurstAPs + sReserveAP)
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[ MSG113_SUPPRESSIONFIRE ] );
return( AI_ACTION_FIRE_GUN );
if (gAnimControl[pSoldier->usAnimState].ubEndHeight != BestShot.ubStance &&
IsValidStance(pSoldier, BestShot.ubStance))
{
pSoldier->aiData.bNextAction = AI_ACTION_FIRE_GUN;
pSoldier->aiData.usNextActionData = BestShot.sTarget;
pSoldier->aiData.bNextTargetLevel = BestShot.bTargetLevel;
pSoldier->aiData.usActionData = BestShot.ubStance;
DebugAI(AI_MSG_INFO, pSoldier, String("Change stance before shooting"));
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_SUPPRESSIONFIRE]);
return(AI_ACTION_CHANGE_STANCE);
}
else
{
pSoldier->aiData.usActionData = BestShot.sTarget;
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_SUPPRESSIONFIRE]);
return(AI_ACTION_FIRE_GUN);
}
}
else
{
pSoldier->bDoBurst = 0;
pSoldier->bDoAutofire = 0;
pSoldier->bDoBurst = 0;
pSoldier->bDoAutofire = 0;
}
}
// suppression not possible, do something else
+2 -3
View File
@@ -330,15 +330,14 @@ UINT8 TerrainDensity(INT32 sSpot, INT8 bLevel, UINT8 ubDistance, BOOLEAN fGrass)
BOOLEAN FindNearbyExplosiveStructure(INT32 sSpot, INT8 bLevel);
INT16 DistanceToClosestActiveOpponent(SOLDIERTYPE *pSoldier, INT32 sSpot);
BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent);
UINT8 CountSeenEnemiesLastTurn( SOLDIERTYPE *pSoldier );
BOOLEAN FindObstacleNearSpot(INT32 sSpot, INT8 bLevel);
BOOLEAN CheckNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo);
UINT8 SpotDangerLevel(SOLDIERTYPE *pSoldier, INT32 sGridNo);
BOOLEAN AllowDeepWaterFlanking(SOLDIERTYPE *pSoldier);
BOOLEAN AICheckUnderground(void);
INT32 RandomizeLocation(INT32 sSpot, INT8 bLevel, UINT8 ubTimes, SOLDIERTYPE *pSightSoldier);
INT32 RandomizeOpponentLocation(INT32 sSpot, SOLDIERTYPE *pOpponent, INT16 sMaxDistance);
BOOLEAN CorpseWarning(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel);
BOOLEAN CorpseEnemyTeam(ROTTING_CORPSE *pCorpse);