New option AI_DECISION_INFO enables extended logging for AI decisions.

Moved zombie AI into separate file ZombieDecideAction.cpp
New zombie AI:
- simplified code
- advancing using cover
- if zombie cannot reach any opponent, he tries to hide
- if zombie cannot reach opponent and cannot hide and he is under fire, he will retreat, seeking cover away from known opponents
- if zombie doesn't know enemy location, he will try to run to closest friend who knows enemy location
- new zombie AI has extended AI logging
More water splash sounds (Data\Sounds\Misc\Splash1.ogg .. SplashN.ogg).
CalcCoverValue: 
- allow moving into shallow water if it provides better position
- special calculations for zombies: zombie is very dangerous at close range
- if soldier has no gun, use different calculation based on distance instead of CTGT
FindBestNearbyCover: zombies only choose spots that provide sight cover.
Added functions for extended AI log.
ClosestReachableDisturbance:
- if soldier is zombie and he cannot climb, skip location
- zombies do not attack vehicles
- improved check to avoid choosing dying opponents
EstimatePathCostToLocation: return 0 if checking for zombie which cannot climb and path needs climbing.
RoamingRange: always max roaming range for zombies.
SightCoverAtSpot, ProneSightCoverAtSpot:
- improved max sight range calculation
- added option for unlimited sight range check
New AI functions:
- AIDirection
- AICheckIsSniper
- AICheckIsMarksman
- AICheckIsMedic
- AICheckIsMortarOperator
- AICheckIsGLOperator
- AICheckIsOfficer
- AICheckIsCommander
- AICheckIsMachinegunner
- AIGunInHandScoped
- AIGunScoped
- AIGunRange
- AIGunClass
- AIGunType
- AIGunDeadliness
- AIGunAmmo
- AIGunAutofireCapable
- FindObstacleNearSpot
- InSmoke
- CorpseWarning
- CorpseEnemyTeam
- CorpseMilitiaTeam
- AICheckUnderground
- AnyCoverAtSpot
- AICheckHasWeaponOfType
- AICheckHasGun
- AICheckShortWeaponRange
GetCorpseAtGridNo: additional check for valid gridno.
More zombie raise sounds (Data\Sounds\Misc\ZombieRaise1.ogg .. ZombieRaiseN.ogg).
GetCorpseRotFactor: improved code to return FLOAT value between 0 and 1.
New debug topic id TOPIC_DECISIONS for logging AI decisions.

git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8760 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
Sevenfm
2020-02-21 23:33:25 +00:00
parent 73cbc27ea4
commit 32482f20f6
24 changed files with 3296 additions and 310 deletions
+582 -65
View File
@@ -290,14 +290,13 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
INT16 sTempX, sTempY;
FLOAT dMyX, dMyY, dHisX, dHisY;
INT8 bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT;
INT32 iRangeChange, iRangeFactor, iRangeFactorMultiplier;
INT32 iRangeChange, iRangeFactor, iRCD;
SOLDIERTYPE *pHim;
// sevenfm
INT8 bHisLevel;
INT8 bMyLevel;
INT32 sDist;
UINT8 ubCoverReduction;
dMyX = dMyY = dHisX = dHisY = -1.0;
@@ -338,7 +337,8 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
}
if (InWaterOrGas(pHim,sHisGridNo))
if (DeepWater(sHisGridNo, bHisLevel) || InGas(pHim, sHisGridNo))
//if (InWaterOrGas(pHim,sHisGridNo))
{
bHisActualCTGT = 0;
}
@@ -378,7 +378,8 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
// if my intended gridno is in water or gas, I can't attack at all from there
// here, for smoke, consider bad
if (InWaterGasOrSmoke(pMe,sMyGridNo))
if (DeepWater(sMyGridNo, bMyLevel) || InGas(pMe, sMyGridNo))
//if (InWaterGasOrSmoke(pMe,sMyGridNo))
{
bMyCTGT = 0;
}
@@ -392,8 +393,6 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
pHim->dXPos = (FLOAT) sTempX;
pHim->dYPos = (FLOAT) sTempY;
}
// bMyCTGT = ChanceToGetThrough(pMe,sHisGridNo,FAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS); // assume a gunshot
// bMyCTGT = SoldierToLocationChanceToGetThrough( pMe, sHisGridNo, pMe->bTargetLevel, pMe->bTargetCubeLevel );
// let's not assume anything about the stance the enemy might take, so take an average
// value... no cover give a higher value than partial cover
@@ -423,6 +422,39 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
pHim->dYPos = dHisY; // and the 'y'
}
sDist = PythSpacesAway(sMyGridNo, sHisGridNo);
if (gGameExternalOptions.fAIBetterCover)
{
// sevenfm: special calculations for zombies: zombie is very dangerous at close range
if (pHim->IsZombie() || !AICheckHasGun(pHim))
{
if (sDist < (INT32)(DAY_VISION_RANGE / 2))
{
// 100 - 25
bHisCTGT = 100 - 150 * sDist / DAY_VISION_RANGE;
}
else
{
// 25 - 0
bHisCTGT = 100 * DAY_VISION_RANGE * DAY_VISION_RANGE / (16 * sDist * sDist);
}
}
// sevenfm: if soldier has no gun, use different calculation
if (pMe->IsZombie() || !AICheckHasGun(pMe))
{
if (sDist < (INT32)(DAY_VISION_RANGE / 2))
{
bMyCTGT = 100 - 150 * sDist / DAY_VISION_RANGE;
}
else
{
bMyCTGT = 100 * DAY_VISION_RANGE * DAY_VISION_RANGE / (16 * sDist * sDist);
}
}
}
// these value should be < 1 million each
iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs;
iMyPosValue = bMyCTGT * iMyThreat * iMyAPsLeft;
@@ -431,18 +463,20 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
// max 25% at DAY_VISION_RANGE/2, 0 at zero range
if( gGameExternalOptions.fAIBetterCover )
{
if( AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel) )
UINT8 ubCoverBonus = 25;
if (sDist < (INT32)(DAY_VISION_RANGE / 2))
{
ubCoverReduction = 25;
sDist = PythSpacesAway(sMyGridNo, sHisGridNo);
if (sDist < (INT32)(DAY_VISION_RANGE / 2))
{
ubCoverReduction = ubCoverReduction * 2 * sDist / DAY_VISION_RANGE;
}
iHisPosValue -= iHisPosValue * ubCoverReduction / 100;
iMyPosValue += iMyPosValue * ubCoverReduction / 100;
ubCoverBonus = ubCoverBonus * 2 * sDist / DAY_VISION_RANGE;
}
if (!pHim->IsZombie() && AICheckHasGun(pHim) && AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel))
{
iHisPosValue -= iHisPosValue * ubCoverBonus / 100;
iMyPosValue += iMyPosValue * ubCoverBonus / 100;
}
if (!pMe->IsZombie() && AICheckHasGun(pMe) && AnyCoverFromSpot(sHisGridNo, bHisLevel, sMyGridNo, bMyLevel))
{
iHisPosValue += iHisPosValue * ubCoverBonus / 100;
iMyPosValue -= iMyPosValue * ubCoverBonus / 100;
}
}
@@ -459,66 +493,62 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
}
// if my positional value is worth something at all here
if (iMyPosValue > 0)
// if my positional value is worth something at all here
if (iMyPosValue > 0)
{
// if I CAN'T crouch when I get there, that makes it significantly less
// appealing a spot (how much depends on range), so that's a penalty to me
if (iMyAPsLeft < GetAPsCrouch(pMe,TRUE))
// subtract another 1 % penalty for NOT being able to crouch per tile
// the farther away we are, the bigger a difference crouching will make!
iMyPosValue -= ((iMyPosValue * (AIM_PENALTY_TARGET_CROUCHED + (iRange / CELL_X_SIZE))) / 100);
// if I CAN'T crouch when I get there, that makes it significantly less
// appealing a spot (how much depends on range), so that's a penalty to me
if (iMyAPsLeft < GetAPsCrouch(pMe, TRUE))
// subtract another 1 % penalty for NOT being able to crouch per tile
// the farther away we are, the bigger a difference crouching will make!
iMyPosValue -= ((iMyPosValue * (AIM_PENALTY_TARGET_CROUCHED + (iRange / CELL_X_SIZE))) / 100);
}
// high morale prefers decreasing the range (positive factor), while very
// low morale (HOPELESS) prefers increasing it
// if (bHisCTGT < 100 || (morale - 1 < 0))
iRCD = RangeChangeDesire(pMe);
if (iRCD)
{
iRangeChange = Threat[uiThreatIndex].iOrigRange - iRange;
iRangeFactorMultiplier = RangeChangeDesire( pMe );
if (iRangeFactorMultiplier)
if (iRangeChange)
{
iRangeChange = Threat[uiThreatIndex].iOrigRange - iRange;
//iRangeFactor = (iRangeChange * (morale - 1)) / 4;
iRangeFactor = (iRangeChange * iRCD) / 2;
if (iRangeChange)
// sevenfm: reduce range bonus depending on cover
if (gGameExternalOptions.fAIBetterCover)
{
//iRangeFactor = (iRangeChange * (morale - 1)) / 4;
iRangeFactor = (iRangeChange * iRangeFactorMultiplier) / 2;
// sevenfm: reduce range bonus depending on cover
if( gGameExternalOptions.fAIBetterCover )
if (!AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel) &&
CountSeenEnemiesLastTurn(pMe) > CountNearbyFriends(pMe, sMyGridNo, DAY_VISION_RANGE / 2))
{
if( !AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel) &&
CountSeenEnemiesLastTurn(pMe) > CountNearbyFriends(pMe, sMyGridNo, DAY_VISION_RANGE / 2) )
{
iRangeFactor = iRangeFactor * (100 - bHisCTGT * __min(Threat[uiThreatIndex].iAPs, APBPConstants[AP_MAXIMUM]) / APBPConstants[AP_MAXIMUM]) / 100;
}
}
#ifdef DEBUGCOVER
DebugAI( String( "CalcCoverValue: iRangeChange %d, iRangeFactor %d\n", iRangeChange, iRangeFactor ) );
#endif
// aggression booster for stupider enemies
iMyPosValue += 100 * iRangeFactor * ( 5 - SoldierDifficultyLevel( pMe ) ) / 5 ;
// if factor is positive increase positional value, else decrease it
// change both values, since one or the other could be 0
if (iRangeFactor > 0)
{
iMyPosValue = (iMyPosValue * (100 + iRangeFactor)) / 100;
iHisPosValue = (100 * iHisPosValue) / (100 + iRangeFactor);
}
else if (iRangeFactor < 0)
{
iMyPosValue = (100 * iMyPosValue) / (100 - iRangeFactor);
iHisPosValue = (iHisPosValue * (100 - iRangeFactor)) / 100;
iRangeFactor = iRangeFactor * (100 - bHisCTGT * __min(Threat[uiThreatIndex].iAPs, APBPConstants[AP_MAXIMUM]) / APBPConstants[AP_MAXIMUM]) / 100;
}
}
#ifdef DEBUGCOVER
DebugAI( String( "CalcCoverValue: iRangeChange %d, iRangeFactor %d\n", iRangeChange, iRangeFactor ) );
#endif
// aggression booster for stupider enemies
iMyPosValue += 100 * iRangeFactor * (5 - SoldierDifficultyLevel(pMe)) / 5;
// if factor is positive increase positional value, else decrease it
// change both values, since one or the other could be 0
if (iRangeFactor > 0)
{
iMyPosValue = (iMyPosValue * (100 + iRangeFactor)) / 100;
iHisPosValue = (100 * iHisPosValue) / (100 + iRangeFactor);
}
else if (iRangeFactor < 0)
{
iMyPosValue = (100 * iMyPosValue) / (100 - iRangeFactor);
iHisPosValue = (iHisPosValue * (100 - iRangeFactor)) / 100;
}
}
}
@@ -527,7 +557,7 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
iReductionFactor = ((MAX_THREAT_RANGE - iRange) * Threat[uiThreatIndex].iCertainty) /
MAX_THREAT_RANGE;
// divide by a 100 to make the numbers more managable and avoid 32-bit limit
// divide by a 100 to make the numbers more manageable and avoid 32-bit limit
iThisScale = max( iMyPosValue, iHisPosValue) / 100;
iThisScale = (iThisScale * iReductionFactor) / 100;
*iTotalScale += iThisScale;
@@ -535,7 +565,7 @@ INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 i
// POSITIVE COVER VALUE INDICATES THE COVER BENEFITS ME, NEGATIVE RESULT
// MEANS IT BENEFITS THE OTHER GUY.
// divide by a 100 to make the numbers more managable and avoid 32-bit limit
// divide by a 100 to make the numbers more manageable and avoid 32-bit limit
iCoverValue = (iMyPosValue - iHisPosValue) / 100;
iCoverValue = (iCoverValue * iReductionFactor) / 100;
@@ -604,7 +634,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
UINT8 ubBackgroundLightLevel;
UINT8 ubBackgroundLightPercent = 0;
UINT8 ubLightPercentDifference;
BOOLEAN fNight;
BOOLEAN fNight;
// sevenfm
UINT8 ubNearbyFriends;
@@ -1057,6 +1087,13 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
continue;
}
// zombies only want to hide
if (pSoldier->IsZombie() && !SightCoverAtSpot(pSoldier, sGridNo, TRUE))
{
//DebugCover(pSoldier, String("zombie: no sight cover at new spot, skip"));
continue;
}
iPathCost = gubAIPathCosts[AI_PATHCOST_RADIUS + sXOffset][AI_PATHCOST_RADIUS + sYOffset];
/*
// water is OK, if the only good hiding place requires us to get wet, OK
@@ -3145,3 +3182,483 @@ INT32 FindNearestPassableSpot( INT32 sGridNo, UINT8 usSearchRadius )
return( sNearestSpot );
}
INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UINT8 ubType, BOOLEAN fUnlimited)
{
INT32 sGridNo, sRealGridNo;
INT32 sBestSpot = NOWHERE;
INT32 iSearchRange = min(AI_PATHCOST_RADIUS, MAX_TILES_MOVE_TURN);
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 iPathCost, iBestPathCost = 0;
INT16 usMovementMode;
INT32 iRoamRange, iDistFromOrigin, sOrigin;
//BOOLEAN fClimbingNecessary;
//INT32 sClimbGridNo;
BOOLEAN fHasMortar;
if (!pSoldier)
{
return NOWHERE;
}
// check target location
if (!NewOKDestination(pSoldier, sTargetSpot, FALSE, pSoldier->pathing.bLevel))
{
//ScreenMsg(FONT_ORANGE, MSG_INTERFACE, L"bad destination %d", sTargetSpot);
return NOWHERE;
}
fHasMortar = AICheckIsMortarOperator(pSoldier);
usMovementMode = DetermineMovementMode(pSoldier, bAction);
UINT8 ubReserveAP = 0;
if (ubType != ADVANCE_SPOT_SIGHT_COVER)
{
if (usMovementMode == RUNNING || usMovementMode == WALKING)
{
ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE);
}
else
{
ubReserveAP = APBPConstants[AP_LOOK_CROUCHED] + GetAPsProne(pSoldier, TRUE);
}
}
if (pSoldier->bActionPoints <= ubReserveAP)
{
return NOWHERE;
}
// check that location is reachable
iBestPathCost = EstimatePlotPath(pSoldier, sTargetSpot, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
if (iBestPathCost == 0)
{
return NOWHERE;
}
iRoamRange = RoamingRange(pSoldier, &sOrigin);
// set AP limit
gubNPCAPBudget = pSoldier->bActionPoints - ubReserveAP;
// set the distance limit of the square region
gubNPCDistLimit = (UINT8)iSearchRange;
// determine maximum horizontal limits
sMaxLeft = min(iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = min(iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = min(iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = min(iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
//NumMessage("sMaxDown = ",sMaxDown);
// Call FindBestPath to set flags in all locations that we can
// walk into within range. We have to set some things up first...
// reset the "reachable" flags in the region we're looking at
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
}
}
FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0);
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
if (sGridNo == pSoldier->pathing.sBlackList)
{
continue;
}
// check roaming range
if (iRoamRange < MAX_ROAMING_RANGE && !TileIsOutOfBounds(sOrigin))
{
iDistFromOrigin = SpacesAway(sOrigin, sGridNo);
if (iDistFromOrigin > iRoamRange)
{
continue;
}
}
// check if we will be closer to target spot
if (PythSpacesAway(sGridNo, sTargetSpot) > PythSpacesAway(pSoldier->sGridNo, sTargetSpot))
{
continue;
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if (InGas(pSoldier, sGridNo))
{
continue;
}
// skip lighted spots
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel))
continue;
// skip deep water
if (DeepWater(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip water
if (Water(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip locations too close to target spot
if (PythSpacesAway(sGridNo, sTargetSpot) < (INT16)(DAY_VISION_RANGE / 4))
{
continue;
}
// avoid smoke
if (InSmoke(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip if this spot has no cover nearby
if ((ubType == ADVANCE_SPOT_ANY_COVER || fUnlimited) &&
!FindObstacleNearSpot(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// avoid locations near fresh corpses
if (!pSoldier->IsZombie() && GetNearestRottingCorpseAIWarning(sGridNo) > 0)
//if (CorpseWarning(pSoldier, sGridNo, pSoldier->pathing.bLevel, TRUE))
{
continue;
}
// avoid overcrowding
if (!pSoldier->IsZombie() && NumberOfTeamMatesAdjacent(pSoldier, sGridNo) > 1)
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
// sevenfm: avoid rooms for mortar operators
if (!AICheckUnderground() &&
pSoldier->pathing.bLevel == 0 &&
fHasMortar &&
InARoom(sGridNo, NULL))
{
continue;
}
if (ubType == ADVANCE_SPOT_SIGHT_COVER)
{
// skip locations with no sight cover
if (!SightCoverAtSpot(pSoldier, sGridNo, fUnlimited))
{
continue;
}
}
else if (ubType == ADVANCE_SPOT_PRONE_COVER)
{
// check prone sight cover at spot
if (!ProneSightCoverAtSpot(pSoldier, sGridNo, fUnlimited))
{
continue;
}
// check that we'll have enough APs to go prone at target spot
/*iPathCost = EstimatePlotPath( pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
if( pSoldier->bActionPoints - iPathCost < GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING] )
{
continue;
}*/
}
else if (ubType == ADVANCE_SPOT_ANY_COVER)
{
// check any cover at spot
if (!AnyCoverAtSpot(pSoldier, sGridNo))
{
continue;
}
// check that we'll have enough APs to go prone at target spot
/*iPathCost = EstimatePlotPath( pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
if( pSoldier->bActionPoints - iPathCost < GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING] )
{
continue;
}*/
}
sRealGridNo = pSoldier->sGridNo;
pSoldier->sGridNo = sGridNo;
iPathCost = EstimatePlotPath(pSoldier, sTargetSpot, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
//iPathCost = PlotPath(pSoldier, sTargetSpot, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, FALSE, 0);
//iPathCost = EstimatePathCostToLocation( pSoldier, sTargetSpot, pSoldier->pathing.bLevel, TRUE, &fClimbingNecessary, &sClimbGridNo );
pSoldier->sGridNo = sRealGridNo;
// skip location if no path to target spot
if (iPathCost == 0)
{
//ScreenMsg(FONT_ORANGE, MSG_INTERFACE, L"cannot find path to destination %d at %d", sTargetSpot, sGridNo);
continue;
}
//if( sBestSpot == NOWHERE || iPathCost < iBestPathCost )
if (iPathCost < iBestPathCost)
{
sBestSpot = sGridNo;
iBestPathCost = iPathCost;
}
}
}
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return(sBestSpot);
}
// find spot with cover, max dist from opponents
INT32 FindRetreatSpot(SOLDIERTYPE *pSoldier)
{
INT32 sGridNo;
INT32 sBestSpot = NOWHERE;
INT32 iSearchRange = MAX_TILES_MOVE_TURN;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 usMovementMode;
INT32 iRoamRange, iDistFromOrigin, sOrigin;
//BOOLEAN fClimbingNecessary;
//INT32 sClimbGridNo;
UINT8 ubReserveAP;
INT8 bAction = AI_ACTION_TAKE_COVER;
INT32 sClosestOpponent;
INT16 sDistance, sClosestDistance;
if (!pSoldier)
{
return NOWHERE;
}
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
if (TileIsOutOfBounds(sClosestOpponent))
{
// no one to hide from
return NOWHERE;
}
sClosestDistance = PythSpacesAway(pSoldier->sGridNo, sClosestOpponent);
//ubReserveAP = GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING];
ubReserveAP = GetAPsCrouch(pSoldier, TRUE) + APBPConstants[AP_CHANGE_FACING];
if (pSoldier->bActionPoints <= ubReserveAP)
{
return NOWHERE;
}
iRoamRange = RoamingRange(pSoldier, &sOrigin);
// set AP limit
gubNPCAPBudget = pSoldier->bActionPoints;
// set the distance limit of the square region
gubNPCDistLimit = (UINT8)iSearchRange;
// determine maximum horizontal limits
sMaxLeft = min(iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = min(iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = min(iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = min(iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
//NumMessage("sMaxDown = ",sMaxDown);
// Call FindBestPath to set flags in all locations that we can
// walk into within range. We have to set some things up first...
// reset the "reachable" flags in the region we're looking at
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
}
}
usMovementMode = DetermineMovementMode(pSoldier, bAction);
FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0);
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
if (sGridNo == pSoldier->pathing.sBlackList)
{
continue;
}
// check roaming range
if (iRoamRange < MAX_ROAMING_RANGE && !TileIsOutOfBounds(sOrigin))
{
iDistFromOrigin = SpacesAway(sOrigin, sGridNo);
if (iDistFromOrigin > iRoamRange)
{
continue;
}
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if (InGas(pSoldier, sGridNo))
{
continue;
}
// skip lighted spots
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel))
continue;
// skip deep water
if (DeepWater(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// skip water
if (Water(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// avoid locations near fresh corpses
if (!pSoldier->IsZombie() && GetNearestRottingCorpseAIWarning(sGridNo) > 0)
//if (CorpseWarning(pSoldier, sGridNo, pSoldier->pathing.bLevel, TRUE))
{
continue;
}
if (!pSoldier->IsZombie() && NumberOfTeamMatesAdjacent(pSoldier, sGridNo) > 0)
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
// avoid smoke
/*if( InSmoke(sGridNo, pSoldier->pathing.bLevel, FALSE) )
{
continue;
}*/
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
// skip if too close to enemy
//sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
sDistance = PythSpacesAway(sGridNo, sClosestOpponent);
if (sDistance < (INT16)(DAY_VISION_RANGE / 2))
{
continue;
}
// skip if this spot has no cover nearby
if (!FindObstacleNearSpot(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
if (!AnyCoverAtSpot(pSoldier, sGridNo))
{
continue;
}
// check prone sight cover at spot
/*if( !ProneSightCoverAtSpot(pSoldier, sGridNo) )
{
continue;
}*/
if (sDistance > sClosestDistance)
{
sBestSpot = sGridNo;
sClosestDistance = sDistance;
}
}
}
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return(sBestSpot);
}