Files
source/TacticalAI/FindLocations.cpp
T
Marco Antonio Jaguaribe CostaandAsdow 8b1c4effa0 nuke all the #if 0's
scripted commit:

find . -not -path '*/.*' -and -not -path '*/ext*' -type d -exec coan source -R -E {} ';'

https://coan2.sourceforge.net/coan_man_1.html
2023-01-19 00:13:08 +02:00

3574 lines
112 KiB
C++

#include <stdlib.h>
#include "Isometric Utils.h"
#include "ai.h"
#include "AIInternals.h"
#include "los.h"
#include "Weapons.h"
#include "Opplist.h"
#include "pathai.h"
#include "items.h"
#include "World Items.h"
#include "Points.h"
#include "message.h"
#include "Map Edgepoints.h"
#include "renderworld.h"
#include "Render Fun.h"
#include "Boxing.h"
#include "Text.h"
#ifdef _DEBUG
#include "renderworld.h"
#include "video.h"
#endif
#include "worldman.h"
#include "strategicmap.h"
#include "environment.h"
#include "lighting.h"
#include "Buildings.h"
#include "GameSettings.h"
#include "Soldier Profile.h"
#include "rotting corpses.h" // sevenfm
////////////////////////////////////////////////////////////////////////////////////////////////////////
// SANDRO - all "APBPConstants[AP_CROUCH]" and "APBPConstants[AP_PRONE]" here
// were changed to GetAPsCrouch() and GetAPsProne()
// - also all "APBPConstants[AP_PICKUP_ITEM]" were replaced by GetBasicAPsToPickupItem()
////////////////////////////////////////////////////////////////////////////////////////////////////////
INT16 * gsCoverValue = NULL;
#ifdef _DEBUG
INT16 gsBestCover;
#ifndef PATHAI_VISIBLE_DEBUG
// NB Change this to true to get visible cover debug -- CJC
BOOLEAN gfDisplayCoverValues = FALSE;
#endif
extern void RenderCoverDebug( void );
#endif
INT16 gubAIPathCosts[19][19];
// FindBestNearbyCover - "Net" related stuff commented out
extern UINT8 gubAICounter;
extern BOOLEAN gfTurnBasedAI;
INT32 CalcPercentBetter(INT32 iOldValue, INT32 iNewValue, INT32 iOldScale, INT32 iNewScale)
{
INT32 iValueChange,iScaleSum,iPercentBetter;//,loopCnt,tempInt;
// calcalate how much better the new cover would be than the current cover
iValueChange = iNewValue - iOldValue;
// here, the change in cover HAS to be an improvement over current cover
if (iValueChange <= 0)
{
#ifdef BETAVERSION
sprintf(tempstr,"CalcPercentBetter: ERROR - invalid valueChange = %d",valueChange);
#ifdef RECORDNET
fprintf(NetDebugFile,"\n\t%s\n\n",tempstr);
#endif
PopMessage(tempstr);
#endif
return(NOWHERE);
}
iScaleSum = iOldScale + iNewScale;
// here, the change in cover HAS to be an improvement over current cover
if (iScaleSum <= 0)
{
#ifdef BETAVERSION
sprintf(tempstr,"CalcPercentBetter: ERROR - invalid scaleSum = %d",iScaleSum);
#ifdef RECORDNET
fprintf(NetDebugFile,"\n\t%s\n\n",tempstr);
#endif
PopMessage(tempstr);
#endif
return(NOWHERE);
}
iPercentBetter = (iValueChange * 100) / iScaleSum;
#ifdef DEBUGCOVER
DebugAI( String( "CalcPercentBetter: %%Better %ld, old %ld, new %ld, change %ld\n\t\toldScale %ld, newScale %ld, scaleSum %ld\n",
iPercentBetter,iOldValue,iNewValue,iValueChange,iOldScale,iNewScale,iScaleSum ) );
#endif
return(iPercentBetter);
}
void AICenterXY( INT32 sGridNo, FLOAT * pdX, FLOAT * pdY )
{
INT16 sXPos, sYPos;
sXPos = sGridNo % WORLD_COLS;
sYPos = sGridNo / WORLD_COLS;
*pdX = (FLOAT) (sXPos * CELL_X_SIZE + CELL_X_SIZE / 2);
*pdY = (FLOAT) (sYPos * CELL_Y_SIZE + CELL_Y_SIZE / 2);
}
INT8 CalcWorstCTGTForPosition( SOLDIERTYPE * pSoldier, UINT8 ubOppID, INT32 sOppGridNo, INT8 bLevel, INT32 iMyAPsLeft )
{
// When considering a gridno for cover, we want to take into account cover if we
// lie down, so we return the LOWEST chance to get through for that location.
INT8 bCubeLevel, bThisCTGT,bWorstCTGT = 100;
for (bCubeLevel = 1; bCubeLevel <= 3; bCubeLevel++)
{
switch (bCubeLevel)
{
case 1:
if (iMyAPsLeft < GetAPsCrouch(pSoldier,TRUE) + GetAPsProne(pSoldier,TRUE))
{
continue;
}
break;
case 2:
if (iMyAPsLeft < GetAPsCrouch(pSoldier,TRUE))
{
continue;
}
break;
default:
break;
}
bThisCTGT = SoldierToLocationChanceToGetThrough( pSoldier, sOppGridNo, bLevel, bCubeLevel, ubOppID );
if (bThisCTGT < bWorstCTGT)
{
bWorstCTGT = bThisCTGT;
// if there is perfect cover
if (bWorstCTGT == 0)
// then bail from the for loop, it can't possible get any better
break;
}
}
return( bWorstCTGT );
}
INT8 CalcAverageCTGTForPosition( SOLDIERTYPE * pSoldier, UINT8 ubOppID, INT32 sOppGridNo, INT8 bLevel, INT32 iMyAPsLeft )
{
// When considering a gridno for cover, we want to take into account cover if we
// lie down, so we return the LOWEST chance to get through for that location.
INT8 bCubeLevel;
INT32 iTotalCTGT = 0, bValidCubeLevels = 0;
for (bCubeLevel = 1; bCubeLevel <= 3; bCubeLevel++)
{
switch (bCubeLevel)
{
case 1:
if (iMyAPsLeft < GetAPsCrouch(pSoldier,TRUE) + GetAPsProne(pSoldier,TRUE))
{
continue;
}
break;
case 2:
if (iMyAPsLeft < GetAPsCrouch(pSoldier,TRUE))
{
continue;
}
break;
default:
break;
}
iTotalCTGT += SoldierToLocationChanceToGetThrough( pSoldier, sOppGridNo, bLevel, bCubeLevel, ubOppID );
bValidCubeLevels++;
}
iTotalCTGT /= bValidCubeLevels;
return( (INT8) iTotalCTGT );
}
INT8 CalcBestCTGT( SOLDIERTYPE *pSoldier, UINT8 ubOppID, INT32 sOppGridNo, INT8 bLevel, INT32 iMyAPsLeft )
{
// NOTE: CTGT stands for "ChanceToGetThrough..."
// using only ints for maximum execution speed here
// CJC: Well, so much for THAT idea!
INT32 sCentralGridNo, sAdjSpot, sNorthGridNo, sSouthGridNo, sCheckSpot;
BOOLEAN sOKTest;
INT8 bThisCTGT, bBestCTGT = 0;
sCheckSpot = -1;
sCentralGridNo = pSoldier->sGridNo;
// precalculate these for speed
// what was struct for?
sOKTest = NewOKDestination( pSoldier, sCentralGridNo, IGNOREPEOPLE , bLevel );
sNorthGridNo = NewGridNo( sCentralGridNo, DirectionInc(NORTH) );
sSouthGridNo = NewGridNo( sCentralGridNo, DirectionInc(SOUTH) );
// look into all 8 adjacent tiles & determine where the cover is the worst
// Lalien: shouldn't this start at 0 than?
for (UINT8 sDir = 0; sDir < NUM_WORLD_DIRECTIONS; ++sDir)
{
// get the gridno of the adjacent spot lying in that direction
sAdjSpot = NewGridNo( sCentralGridNo, DirectionInc( sDir ) );
// if it wasn't out of bounds
if (sAdjSpot != sCentralGridNo)
{
// if the adjacent spot can we walked on and isn't in water or gas
if ( NewOKDestination( pSoldier, sAdjSpot, IGNOREPEOPLE, bLevel ) && !InWaterOrGas( pSoldier, sAdjSpot ))
{
switch (sDir)
{
case NORTH:
case EAST:
case SOUTH:
case WEST:
sCheckSpot = sAdjSpot;
break;
case NORTHEAST:
case NORTHWEST:
// spot to the NORTH is guaranteed to be in bounds since NE/NW was
sCheckSpot = sNorthGridNo;
break;
case SOUTHEAST:
case SOUTHWEST:
// spot to the SOUTH is guaranteed to be in bounds since SE/SW was
sCheckSpot = sSouthGridNo;
break;
}
// ATE: OLD STUFF
// if the adjacent gridno is reachable from the starting spot
if ( NewOKDestination( pSoldier, sCheckSpot, FALSE, bLevel ) )
{
// the dude could move to this adjacent gridno, so put him there
// "virtually" so we can calculate what our cover is from there
// NOTE: GOTTA SET THESE 3 FIELDS *BACK* AFTER USING THIS FUNCTION!!!
pSoldier->sGridNo = sAdjSpot; // pretend he's standing at 'sAdjSpot'
AICenterXY( sAdjSpot, &(pSoldier->dXPos), &(pSoldier->dYPos) );
bThisCTGT = CalcWorstCTGTForPosition( pSoldier, ubOppID, sOppGridNo, bLevel, iMyAPsLeft );
if (bThisCTGT > bBestCTGT)
{
bBestCTGT = bThisCTGT;
// if there is no cover
if (bBestCTGT == 100)
// then bail from the for loop, it can't possible get any better
break;
}
}
}
}
}
return( bBestCTGT );
}
INT32 CalcCoverValue(SOLDIERTYPE *pMe, INT32 sMyGridNo, INT32 iMyThreat, INT32 iMyAPsLeft,
UINT32 uiThreatIndex, INT32 iRange, INT32 morale, INT32 *iTotalScale)
{
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("CalcCoverValue"));
// all 32-bit integers for max. speed
INT32 iMyPosValue, iHisPosValue, iCoverValue;
INT32 iReductionFactor, iThisScale;
INT32 sHisGridNo, sMyRealGridNo = NOWHERE, sHisRealGridNo = NOWHERE;
INT16 sTempX, sTempY;
FLOAT dMyX, dMyY, dHisX, dHisY;
INT8 bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT;
INT32 iRangeChange, iRangeFactor, iRCD;
SOLDIERTYPE *pHim;
// sevenfm
INT8 bHisLevel;
INT8 bMyLevel;
INT32 sDist;
dMyX = dMyY = dHisX = dHisY = -1.0;
pHim = Threat[uiThreatIndex].pOpponent;
sHisGridNo = Threat[uiThreatIndex].sGridNo;
// sevenfm
bHisLevel = pHim->pathing.bLevel;
bMyLevel = pMe->pathing.bLevel;
UINT8 ubFriendlyFireChance = 0;
// THE FOLLOWING STUFF IS *VEERRRY SCAARRRY*, BUT SHOULD WORK. IF YOU REALLY
// HATE IT, THEN CHANGE ChanceToGetThrough() TO WORK FROM A GRIDNO TO GRIDNO
// if this is theoretical, and I'm not actually at sMyGridNo right now
if (pMe->sGridNo != sMyGridNo)
{
sMyRealGridNo = pMe->sGridNo; // remember where I REALLY am
dMyX = pMe->dXPos;
dMyY = pMe->dYPos;
pMe->sGridNo = sMyGridNo; // but pretend I'm standing at sMyGridNo
ConvertGridNoToCenterCellXY( sMyGridNo, &sTempX, &sTempY );
pMe->dXPos = (FLOAT) sTempX;
pMe->dYPos = (FLOAT) sTempY;
}
// if this is theoretical, and he's not actually at hisGrid right now
if (pHim->sGridNo != sHisGridNo)
{
sHisRealGridNo = pHim->sGridNo; // remember where he REALLY is
dHisX = pHim->dXPos;
dHisY = pHim->dYPos;
pHim->sGridNo = sHisGridNo; // but pretend he's standing at sHisGridNo
ConvertGridNoToCenterCellXY( sHisGridNo, &sTempX, &sTempY );
pHim->dXPos = (FLOAT) sTempX;
pHim->dYPos = (FLOAT) sTempY;
}
if (DeepWater(sHisGridNo, bHisLevel) || InGas(pHim, sHisGridNo))
//if (InWaterOrGas(pHim,sHisGridNo))
{
bHisActualCTGT = 0;
}
else
{
// optimistically assume we'll be behind the best cover available at this spot
//bHisActualCTGT = ChanceToGetThrough(pHim,sMyGridNo,FAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS); // assume a gunshot
bHisActualCTGT = CalcWorstCTGTForPosition( pHim, pMe->ubID, sMyGridNo, pMe->pathing.bLevel, iMyAPsLeft );
}
// normally, that will be the cover I'll use, unless worst case over-rides it
bHisCTGT = bHisActualCTGT;
// only calculate his best case CTGT if there is room for improvement!
if (bHisActualCTGT < 100)
{
// if we didn't remember his real gridno earlier up above, we got to now,
// because calculating worst case is about to play with it in a big way!
if (TileIsOutOfBounds(sHisRealGridNo))
{
sHisRealGridNo = pHim->sGridNo; // remember where he REALLY is
dHisX = pHim->dXPos;
dHisY = pHim->dYPos;
}
// calculate where my cover is worst if opponent moves just 1 tile over
bHisBestCTGT = CalcBestCTGT(pHim, pMe->ubID, sMyGridNo, pMe->pathing.bLevel, iMyAPsLeft);
// if he can actually improve his CTGT by moving to a nearby gridno
if (bHisBestCTGT > bHisActualCTGT)
{
// he may not take advantage of his best case, so take only 2/3 of best
bHisCTGT = ((2 * bHisBestCTGT) + bHisActualCTGT) / 3;
}
}
// if my intended gridno is in water or gas, I can't attack at all from there
// here, for smoke, consider bad
if (DeepWater(sMyGridNo, bMyLevel) || InGas(pMe, sMyGridNo))
//if (InWaterGasOrSmoke(pMe,sMyGridNo))
{
bMyCTGT = 0;
}
else
{
// put him at sHisGridNo if necessary!
if (pHim->sGridNo != sHisGridNo )
{
pHim->sGridNo = sHisGridNo;
ConvertGridNoToCenterCellXY( sHisGridNo, &sTempX, &sTempY );
pHim->dXPos = (FLOAT) sTempX;
pHim->dYPos = (FLOAT) sTempY;
}
// sevenfm: also check friendly fire chance for each position
gUnderFire.Clear();
gUnderFire.Enable();
// 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
bMyCTGT = CalcAverageCTGTForPosition( pMe, pHim->ubID, sHisGridNo, pHim->pathing.bLevel, iMyAPsLeft );
gUnderFire.Disable();
ubFriendlyFireChance = gUnderFire.Chance(pMe->bTeam, pMe->bSide, TRUE);
if (gGameExternalOptions.fAIBetterCover)
{
// sevenfm: penalize position if friendly fire chance is high
if (ubFriendlyFireChance > MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
bMyCTGT = 1;
}
else
{
// since NPCs are too dumb to shoot "blind", ie. at opponents that they
// themselves can't see (mercs can, using another as a spotter!), if the
// cover is below the "see_thru" threshold, it's equivalent to perfect cover!
if (bMyCTGT < SEE_THRU_COVER_THRESHOLD)
bMyCTGT = 0;
}
}
// UNDO ANY TEMPORARY "DAMAGE" DONE ABOVE
if (!TileIsOutOfBounds(sMyRealGridNo))
{
pMe->sGridNo = sMyRealGridNo; // put me back where I belong!
pMe->dXPos = dMyX; // also change the 'x'
pMe->dYPos = dMyY; // and the 'y'
}
if (!TileIsOutOfBounds(sHisRealGridNo))
{
pHim->sGridNo = sHisRealGridNo; // put HIM back where HE belongs!
pHim->dXPos = dHisX; // also change the 'x'
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)(TACTICAL_RANGE / 2))
{
// 100 - 25
bHisCTGT = 100 - 150 * sDist / TACTICAL_RANGE;
}
else
{
// 25 - 0
bHisCTGT = 100 * TACTICAL_RANGE * TACTICAL_RANGE / (16 * sDist * sDist);
}
}
// sevenfm: if soldier has no gun, use different calculation
if (pMe->IsZombie() || !AICheckHasGun(pMe))
{
if (sDist < (INT32)(TACTICAL_RANGE / 2))
{
bMyCTGT = 100 - 150 * sDist / TACTICAL_RANGE;
}
else
{
bMyCTGT = 100 * TACTICAL_RANGE * TACTICAL_RANGE / (16 * sDist * sDist);
}
}
}
// these value should be < 1 million each
iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs;
iMyPosValue = bMyCTGT * iMyThreat * iMyAPsLeft;
// add penalty to enemy position, bonus to my position if soldier has cover at spot
// max 25% at TACTICAL_RANGE / 2, 0 at zero range
if( gGameExternalOptions.fAIBetterCover )
{
UINT8 ubCoverBonus = 25;
if (sDist < (INT32)(TACTICAL_RANGE / 2))
{
ubCoverBonus = ubCoverBonus * 2 * sDist / TACTICAL_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;
}
}
// try to account for who outnumbers who: the side with the advantage thus
// (hopefully) values offense more, while those in trouble will play defense
if (pHim->aiData.bOppCnt > 1)
{
iHisPosValue /= pHim->aiData.bOppCnt;
}
if (pMe->aiData.bOppCnt > 1)
{
iMyPosValue /= pMe->aiData.bOppCnt;
}
// 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);
}
// high morale prefers decreasing the range (positive factor), while very
// low morale (HOPELESS) prefers increasing it
iRCD = RangeChangeDesire(pMe);
if (iRCD)
{
iRangeChange = Threat[uiThreatIndex].iOrigRange - iRange;
if (iRangeChange)
{
//iRangeFactor = (iRangeChange * (morale - 1)) / 4;
iRangeFactor = (iRangeChange * iRCD) / 2;
// sevenfm: reduce range bonus depending on cover
if (gGameExternalOptions.fAIBetterCover &&
pMe->aiData.bAIMorale < MORALE_FEARLESS &&
AIGunRange(pMe) >= PythSpacesAway(sMyGridNo, sHisGridNo) &&
!AnyCoverFromSpot(sMyGridNo, bMyLevel, sHisGridNo, bHisLevel))
{
iRangeFactor = iRangeFactor * (100 - bHisCTGT * Threat[uiThreatIndex].iAPs / (2 * 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;
}
}
}
// the farther apart we are, the less important the cover differences are
// the less certain his position, the less important cover differences are
iReductionFactor = ((MAX_THREAT_RANGE - iRange) * Threat[uiThreatIndex].iCertainty) /
MAX_THREAT_RANGE;
// 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;
// this helps to decide the percent improvement later
// 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 manageable and avoid 32-bit limit
iCoverValue = (iMyPosValue - iHisPosValue) / 100;
iCoverValue = (iCoverValue * iReductionFactor) / 100;
#ifdef DEBUGCOVER
DebugAI( String( "CalcCoverValue: iCoverValue %d, sMyGridNo %d, sHisGrid %d, iRange %d, morale %d\n",iCoverValue,sMyGridNo,sHisGridNo,iRange,morale) );
DebugAI( String( "CalcCoverValue: iCertainty %d, his bOppCnt %d, my bOppCnt %d\n",Threat[uiThreatIndex].iCertainty,pHim->aiData.bOppCnt,pMe->aiData.bOppCnt) );
DebugAI( String( "CalcCoverValue: bHisCTGT = %d, hisThreat = %d, hisFullAPs = %d\n",bHisCTGT,Threat[uiThreatIndex].iValue,Threat[uiThreatIndex].iAPs) );
DebugAI( String( "CalcCoverValue: bMyCTGT = %d, iMyThreat = %d, iMyAPsLeft = %d\n", bMyCTGT, iMyThreat,iMyAPsLeft) );
DebugAI( String( "CalcCoverValue: hisPosValue = %d, myPosValue = %d\n",iHisPosValue,iMyPosValue) );
DebugAI( String( "CalcCoverValue: iThisScale = %d, iTotalScale = %d, iReductionFactor %d\n\n",iThisScale,*iTotalScale, iReductionFactor) );
#endif
return( iCoverValue );
}
UINT8 NumberOfTeamMatesAdjacent( SOLDIERTYPE * pSoldier, INT32 sGridNo )
{
UINT8 ubLoop, ubCount, ubWhoIsThere;
INT32 sTempGridNo;
ubCount = 0;
for( ubLoop = 0; ubLoop < NUM_WORLD_DIRECTIONS; ubLoop++ )
{
sTempGridNo = NewGridNo( sGridNo, DirectionInc( ubLoop ) );
if ( sTempGridNo != sGridNo )
{
ubWhoIsThere = WhoIsThere2( sTempGridNo, pSoldier->pathing.bLevel );
if ( ubWhoIsThere != NOBODY && ubWhoIsThere != pSoldier->ubID && MercPtrs[ ubWhoIsThere ]->bTeam == pSoldier->bTeam )
{
ubCount++;
}
}
}
return( ubCount );
}
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter)
{
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("FindBestNearbyCover"));
// all 32-bit integers for max. speed
UINT32 uiLoop;
INT32 iCurrentCoverValue, iCoverValue, iBestCoverValue;
INT32 iCurrentScale = -1, iCoverScale = -1, iBestCoverScale = -1;
INT32 iDistFromOrigin, iDistCoverFromOrigin;
//INT32 iThreatCertainty;
INT32 sGridNo, sBestCover = NOWHERE;
INT32 iPathCost;
INT32 iThreatRange, iClosestThreatRange = 1500;
// INT16 sClosestThreatGridno = NOWHERE;
INT32 iMyThreatValue;
//INT32 sThreatLoc;
//INT32 iMaxThreatRange;
UINT32 uiThreatCnt = 0;
INT32 iMaxMoveTilesLeft, iSearchRange, iRoamRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sOrigin; // has to be a short, need a pointer
INT32 * pusLastLoc;
INT8 * pbPersOL;
INT8 * pbPublOL;
//SOLDIERTYPE *pOpponent;
UINT16 usMovementMode;
UINT8 ubBackgroundLightLevel;
UINT8 ubBackgroundLightPercent = 0;
UINT8 ubLightPercentDifference;
BOOLEAN fNight;
// sevenfm
UINT8 ubNearbyFriends;
BOOLEAN fProneCover;
UINT8 ubDiff = SoldierDifficultyLevel( pSoldier );
// There's no cover when boxing!
if (gTacticalStatus.bBoxingState == BOXING)
{
return (NOWHERE);
}
if ( gbWorldSectorZ > 0 )
{
fNight = FALSE;
}
else
{
ubBackgroundLightLevel = GetTimeOfDayAmbientLightLevel();
if ( ubBackgroundLightLevel < NORMAL_LIGHTLEVEL_DAY + 2 )
{
fNight = FALSE;
}
else
{
fNight = TRUE;
ubBackgroundLightPercent = gGameExternalOptions.ubBrightnessVisionMod[ ubBackgroundLightLevel ];
//ubBackgroundLightPercent = gbLightSighting[ 0 ][ ubBackgroundLightLevel ];
}
}
iBestCoverValue = -1;
#if defined( _DEBUG ) && defined( COVER_DEBUG )
if (gfDisplayCoverValues)
{
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
}
#endif
//NameMessage(pSoldier,"looking for some cover...");
// BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplists
pusLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]);
// hang a pointer into personal opplist
pbPersOL = &(pSoldier->aiData.bOppList[0]);
// hang a pointer into public opplist
pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]);
// decide how far we're gonna be looking
iSearchRange = gbDiff[DIFF_MAX_COVER_RANGE][ SoldierDifficultyLevel( pSoldier ) ];
/*
switch (pSoldier->aiData.bAttitude)
{
case DEFENSIVE: iSearchRange += 2; break;
case BRAVESOLO: iSearchRange -= 4; break;
case BRAVEAID: iSearchRange -= 4; break;
case CUNNINGSOLO: iSearchRange += 4; break;
case CUNNINGAID: iSearchRange += 4; break;
case AGGRESSIVE: iSearchRange -= 2; break;
}*/
// maximum search range is 1 tile / 8 pts of wisdom
if (iSearchRange > (pSoldier->stats.bWisdom / 8))
{
iSearchRange = (pSoldier->stats.bWisdom / 8);
}
if (!gfTurnBasedAI)
{
// don't search so far in realtime
iSearchRange /= 2;
}
usMovementMode = DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER );
if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle
{
// must be able to reach the cover, so it can't possibly be more than
// action points left (rounded down) tiles away, since minimum
// cost to move per tile is 1 points.
// HEADROCK HAM 3.6: This doesn't take into account the 100AP system. Adjusting.
// Please note, I used a calculation that may have a better representation in some global variable.
//iMaxMoveTilesLeft = __max( 0, pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode ) );
// WarmSteel - Bugfix: wrong parentheses
iMaxMoveTilesLeft = __max( 0, (pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode )) / (APBPConstants[AP_MAXIMUM] / 25) );
//NumMessage("In BLACK, maximum tiles to move left = ",maxMoveTilesLeft);
// if we can't go as far as the usual full search range
if (iMaxMoveTilesLeft < iSearchRange)
{
// then limit the search range to only as far as we CAN go
iSearchRange = iMaxMoveTilesLeft;
}
}
if (iSearchRange <= 0)
{
return(NOWHERE);
}
// calculate OUR OWN general threat value (not from any specific location)
iMyThreatValue = CalcManThreatValue(pSoldier, NOWHERE, FALSE, pSoldier);
// prepare threat list from known enemies
PrepareThreatlist(pSoldier);
// if no known opponents were found to threaten us, can't worry about cover
if (!uiThreatCnt)
{
//NameMessage(pSoldier,"has no threats - WON'T take cover");
return(sBestCover);
}
// calculate our current cover value in the place we are now, since the
// cover we are searching for must be better than what we have now!
iCurrentCoverValue = 0;
iCurrentScale = 0;
// sevenfm: sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{
// if this threat is CURRENTLY within 20 tiles
if (Threat[uiLoop].iOrigRange <= MAX_THREAT_RANGE)
{
// add this opponent's cover value to our current total cover value
iCurrentCoverValue += CalcCoverValue(pSoldier,pSoldier->sGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, pSoldier->sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) )
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, pSoldier->sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, NO_DISTANCE_LIMIT ) )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCurrentCoverValue after opponent %d is now %d",iLoop,iCurrentCoverValue);
//PopMessage(tempstr);
}
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
if ( iCurrentCoverValue >= 0 )
{
iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
}
else
{
// when negative, must add a negative to decrease the total
iCurrentCoverValue += (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
{
iCurrentCoverValue += abs(iCurrentCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
}
// sevenfm: check for nearby friends, add bonus/penalty
ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, pSoldier->sGridNo, 5 ));
iCurrentCoverValue -= ubNearbyFriends * abs(iCurrentCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( pSoldier->sGridNo ) > 0)
{
iCurrentCoverValue -= abs(iCurrentCoverValue) / (8-ubDiff);
}
// sevenfm: penalize locations near red smoke
iCurrentCoverValue -= abs(iCurrentCoverValue) * RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel) / 100;
}
#ifdef DEBUGCOVER
// AINumMessage("Search Range = ",iSearchRange);
#endif
// 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);
iRoamRange = RoamingRange(pSoldier,&sOrigin);
// if status isn't black (life & death combat), and roaming range is limited
if ((pSoldier->aiData.bAlertStatus != STATUS_BLACK) && (iRoamRange < MAX_ROAMING_RANGE) &&
(!TileIsOutOfBounds(sOrigin)))
{
// must try to stay within or return to the point of origin
iDistFromOrigin = SpacesAway(sOrigin,pSoldier->sGridNo);
}
else
{
// don't care how far from origin we go
iDistFromOrigin = -1;
}
#ifdef DEBUGCOVER
DebugAI( String( "FBNC: iRoamRange %d, sMaxLeft %d, sMaxRight %d, sMaxUp %d, sMaxDown %d\n",iRoamRange,sMaxLeft,sMaxRight,sMaxUp,sMaxDown) );
#endif
// the initial cover value to beat is our current cover value
iBestCoverValue = iCurrentCoverValue;
#ifdef DEBUGDECISIONS
STR tempstr="";
sprintf( tempstr, "FBNC: CURRENT iCoverValue = %d\n",iCurrentCoverValue );
DebugAI( tempstr );
#endif
if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle
{
// to speed this up, tell PathAI to cancel any paths beyond our AP reach!
gubNPCAPBudget = pSoldier->bActionPoints;
}
else
{
// even if not under pressure, limit to 1 turn's travelling distance
// hope this isn't too expensive...
gubNPCAPBudget = pSoldier->CalcActionPoints( );
//gubNPCAPBudget = pSoldier->bInitialAPs;
}
// Call FindBestPath to set flags in all locations that we can
// walk into within range. We have to set some things up first...
// set the distance limit of the square region
gubNPCDistLimit = (UINT8) iSearchRange;
gusNPCMovementMode = usMovementMode;
// 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, DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER ), COPYREACHABLE_AND_APS, 0 );//dnl ch50 071009
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
//HandleMyMouseCursor(KEYBOARDALSO);
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{
continue;
}
//NumMessage("Testing gridno #",sGridNo);
// if we are limited to staying/returning near our place of origin
if (iDistFromOrigin != -1)
{
iDistCoverFromOrigin = SpacesAway(sOrigin,sGridNo);
// if this is outside roaming range, and doesn't get us closer to it
if ((iDistCoverFromOrigin > iRoamRange) &&
(iDistFromOrigin <= iDistCoverFromOrigin))
{
continue; // then we can't go there
}
}
/*
if (Net.pnum != Net.turnActive)
{
KeepInterfaceGoing(1);
}
*/
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
if ( InGas( pSoldier, sGridNo ) )
{
continue;
}
// ignore blacklisted spot
if ( sGridNo == pSoldier->pathing.sBlackList )
{
continue;
}
// sevenfm: avoid tiles near bombs
if (FindBombNearby(pSoldier, sGridNo, BOMB_DETECTION_RANGE))
{
continue;
}
// sevenfm: avoid staying at north edge
if (NorthSpot(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// sevenfm: avoid moving into light
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) &&
!pSoldier->aiData.bUnderFire)
{
continue;
}
// avoid moving into red smoke
if (gGameExternalOptions.fAIBetterCover &&
RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) &&
!RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel))
{
//DebugCover(pSoldier, String("moving into red smoke, skip"));
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
iPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,WATEROK);
if (!iPathCost)
{
continue; // skip on to the next potential grid
}
// CJC: This should be a redundent check because the path code is given an
// AP limit to begin with!
if (pSoldier->aiData.bAlertStatus == STATUS_BLACK) // in battle
{
// must be able to afford the APs to get to this cover this turn
if (iPathCost > pSoldier->bActionPoints)
{
//NumMessage("In BLACK, and can't afford to get there, cost = ",iPathCost);
continue; // skip on to the next potential grid
}
}
*/
// OK, this place shows potential. How useful is it as cover?
// EVALUATE EACH GRID #, remembering the BEST PROTECTED ONE
iCoverValue = 0;
iCoverScale = 0;
// sevenfm: check sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{
// calculate the range we would be at from this opponent
iThreatRange = GetRangeInCellCoordsFromGridNoDiff( sGridNo, Threat[uiLoop].sGridNo );
// if this threat would be within 20 tiles, count it
if (iThreatRange <= MAX_THREAT_RANGE)
{
iCoverValue += CalcCoverValue(pSoldier,sGridNo,iMyThreatValue,
(pSoldier->bActionPoints - iPathCost),
uiLoop,iThreatRange,morale,&iCoverScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) )
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCoverValue after opponent %d is now %d",iLoop,iCoverValue);
//PopMessage(tempstr);
}
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
if ( iCoverValue >= 0 )
{
iCoverValue -= (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
}
else
{
// when negative, must add a negative to decrease the total
iCoverValue += (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
iCoverValue += abs(iCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
// sevenfm: check for nearby friends in 10 radius, add bonus/penalty 10%
ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, sGridNo, 5 ));
iCoverValue -= ubNearbyFriends * abs(iCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( sGridNo ) > 0)
{
iCoverValue -= abs(iCoverValue) / (8-ubDiff);
}
// sevenfm: penalize locations near red smoke
iCoverValue -= abs(iCoverValue) * RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) / 100;
}
if ( fNight && !( InARoom( sGridNo, NULL ) ) ) // ignore in buildings in case placed there
{
// reduce cover at nighttime based on how bright the light is at that location
// using the difference in sighting distance between the background and the
// light for this tile
//ubLightPercentDifference = (gbLightSighting[ 0 ][ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
ubLightPercentDifference = (gGameExternalOptions.ubBrightnessVisionMod[ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
if ( iCoverValue >= 0 )
{
iCoverValue -= (iCoverValue / 100) * ubLightPercentDifference;
}
else
{
iCoverValue += (iCoverValue / 100) * ubLightPercentDifference;
}
}
#ifdef DEBUGCOVER
// if there ARE multiple opponents
if (uiThreatCnt > 1)
{
DebugAI( String( "FBNC: Total iCoverValue at gridno %d is %d\n\n",sGridNo,iCoverValue ) );
}
#endif
#if defined( _DEBUG ) && defined( COVER_DEBUG )
if (gfDisplayCoverValues)
{
gsCoverValue[sGridNo] = (INT16) (iCoverValue / 100);
}
#endif
// if this is better than the best place found so far
if (iCoverValue > iBestCoverValue)
{
// ONLY DO THIS CHECK HERE IF WE'RE WAITING FOR OPPCHANCETODECIDE,
// OTHERWISE IT WOULD USUALLY BE A WASTE OF TIME
// ok to comment out for now?
/*
if (Status.team[Net.turnActive].allowOppChanceToDecide)
{
// if this cover value qualifies as "better" enough to get used
if (CalcPercentBetter( iCurrentCoverValue,iCoverValue,iCurrentScale,iCoverScale) >= MIN_PERCENT_BETTER)
{
// then we WILL do something (take this cover, at least)
NPCDoesAct(pSoldier);
}
}
*/
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf( tempstr,"FBNC: NEW BEST iCoverValue at gridno %d is %d\n",sGridNo,iCoverValue );
DebugAI( tempstr );
#endif
// remember it instead
sBestCover = sGridNo;
iBestCoverValue = iCoverValue;
iBestCoverScale = iCoverScale;
}
}
}
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues)
{
// do a locate?
LocateSoldier( pSoldier->ubID, SETLOCATORFAST );
gsBestCover = sBestCover;
SetRenderFlags( RENDER_FLAG_FULL );
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
/*
iLoop = GetJA2Clock();
do
{
} while( ( GetJA2Clock( ) - iLoop ) < 2000 );
*/
}
#endif
// if a better cover location was found
if (!TileIsOutOfBounds(sBestCover))
{
#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
gsBestCover = sBestCover;
#endif
// cover values already take the AP cost of getting there into account in
// a BIG way, so no need to worry about that here, even small improvements
// are actually very significant once we get our APs back (if we live!)
*piPercentBetter = CalcPercentBetter(iCurrentCoverValue,iBestCoverValue,iCurrentScale,iBestCoverScale);
// if best cover value found was at least 5% better than our current cover
if (*piPercentBetter >= MIN_PERCENT_BETTER)
{
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf( tempstr,"Found Cover: current %ld, best %ld, %%%%Better %ld\n", iCurrentCoverValue,iBestCoverValue,*piPercentBetter );
DebugAI( tempstr );
#endif
#ifdef BETAVERSION
SnuggleDebug(pSoldier,"Found Cover");
#endif
return(sBestCover); // return the gridno of that cover
}
}
return(NOWHERE); // return that no suitable cover was found
}
INT32 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier)
{
INT32 sGridNo;
INT32 sBestSpot = NOWHERE;
UINT32 uiLoop;
INT32 iThreatRange, iClosestThreatRange = 1500, iSpotClosestThreatRange;
INT32 sThreatLoc, sThreatGridNo[MAXMERCS];
UINT32 uiThreatCnt = 0;
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT8 *pbPersOL, *pbPublOL, bEscapeDirection, bBestEscapeDirection = -1;
SOLDIERTYPE *pOpponent;
INT32 sOrigin;
INT32 iRoamRange;
// BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplistS
// look through all opponents for those we know of
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pOpponent = MercSlots[ uiLoop ];
// if this merc is inactive, at base, on assignment, dead, unconscious
if (!pOpponent || (pOpponent->stats.bLife < OKLIFE))
{
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
}
pbPersOL = &(pSoldier->aiData.bOppList[pOpponent->ubID]);
pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][pOpponent->ubID]);
// if this opponent is unknown personally and publicly
if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
{
continue; // check next opponent
}
// Special stuff for Carmen the bounty hunter
if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY)
{
continue; // next opponent
}
// if the opponent is no threat at all for some reason
if (CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier) == -999)
{
continue; // check next opponent
}
// if personal knowledge is more up to date or at least equal
if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) ||
(*pbPersOL == *pbPublOL))
{
// using personal knowledge, obtain opponent's "best guess" gridno
sThreatLoc = gsLastKnownOppLoc[pSoldier->ubID][pOpponent->ubID];
}
else
{
// using public knowledge, obtain opponent's "best guess" gridno
sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID];
}
// calculate how far away this threat is (in adjusted pixels)
iThreatRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sThreatLoc );
if (iThreatRange < iClosestThreatRange)
{
iClosestThreatRange = iThreatRange;
//NumMessage("New Closest Threat Range = ",iClosestThreatRange);
}
// remember this threat's gridno
sThreatGridNo[uiThreatCnt] = sThreatLoc;
uiThreatCnt++;
}
// if no known opponents were found to threaten us, can't worry about them
if (!uiThreatCnt)
{
//NameMessage(pSoldier,"has no known threats - WON'T run away");
return( sBestSpot );
}
// get roaming range here; for civilians, running away is limited by roam range
if ( pSoldier->bTeam == CIV_TEAM )
{
iRoamRange = RoamingRange( pSoldier, &sOrigin );
if ( iRoamRange == 0 )
{
return( sBestSpot );
}
}
else
{
// dummy values
iRoamRange = 100;
sOrigin = pSoldier->sGridNo;
}
// DETERMINE CO-ORDINATE LIMITS OF SQUARE AREA TO BE CHECKED
// THIS IS A LOT QUICKER THAN COVER, SO DO A LARGER AREA, NOT AFFECTED BY
// DIFFICULTY SETTINGS...
if (pSoldier->aiData.bAlertStatus == STATUS_BLACK) // if already in battle
{
iSearchRange = pSoldier->bActionPoints / 2;
// to speed this up, tell PathAI to cancel any paths beyond our AP reach!
gubNPCAPBudget = pSoldier->bActionPoints;
}
else
{
// even if not under pressure, limit to 1 turn's travelling distance
gubNPCAPBudget = __min( pSoldier->bActionPoints / 2, pSoldier->CalcActionPoints( ) );
iSearchRange = gubNPCAPBudget / 2;
}
if (!gfTurnBasedAI)
{
// search only half as far in realtime
// but always allow a certain minimum!
if ( iSearchRange > 4 )
{
iSearchRange /= 2;
gubNPCAPBudget /= 2;
}
}
// assume we have to stand up!
// use the min macro here to make sure we don't wrap the UINT8 to 255...
// Lesh: for some reason this code still allows to wrap UINT8 at low values of gubNPCAPBudget
//gubNPCAPBudget = gubNPCAPBudget = __min( gubNPCAPBudget, gubNPCAPBudget - GetAPsToChangeStance( pSoldier, ANIM_STAND ) );
// Lesh: will be using this form
if ( gubNPCAPBudget > GetAPsToChangeStance( pSoldier, ANIM_STAND ) )
gubNPCAPBudget = gubNPCAPBudget - (UINT8) GetAPsToChangeStance( pSoldier, ANIM_STAND );
//NumMessage("Search Range = ",iSearchRange);
//NumMessage("gubNPCAPBudget = ",gubNPCAPBudget);
// 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...
// set the distance limit of the square region
gubNPCDistLimit = (UINT8) iSearchRange;
// 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, DetermineMovementMode( pSoldier, AI_ACTION_RUN_AWAY ), COPYREACHABLE, 0 );//dnl ch50 121009
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
//dnl ch58 170813 also don't use last two locations to avoid looping same decisions per turn
if(!TileIsOutOfBounds(pSoldier->sLastTwoLocations[0]))
gpWorldLevelData[pSoldier->sLastTwoLocations[0]].uiFlags &= ~(MAPELEMENT_REACHABLE);
else if(!TileIsOutOfBounds(pSoldier->sLastTwoLocations[1]))
gpWorldLevelData[pSoldier->sLastTwoLocations[1]].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);
//NumMessage("Testing gridno #",gridno);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
if ( sGridNo == pSoldier->pathing.sBlackList )
{
continue;
}
if ( pSoldier->bTeam == CIV_TEAM )
{
iRoamRange = RoamingRange( pSoldier, &sOrigin );
if ( PythSpacesAway( sOrigin, sGridNo ) > iRoamRange )
{
continue;
}
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if ( InGas( pSoldier, sGridNo ) )
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
// check that spot is allowed
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
// OK, this place shows potential. How useful is it as cover?
//NumMessage("Promising seems gridno #",gridno);
iSpotClosestThreatRange = 1500;
if (gGameExternalOptions.fAITacticalRetreat &&
pSoldier->bTeam == ENEMY_TEAM &&
GridNoOnEdgeOfMap(sGridNo, &bEscapeDirection) &&
EscapeDirectionIsValid(&bEscapeDirection))
{
// We can escape! This is better than anything else except a closer spot which we can
// cross over from.
// Subtract the straight-line distance from our location to this one as an estimate of
// path cost and for looks...
// The edge spot closest to us which is on the edge will have the highest value, so
// it will be picked over locations further away.
// Only reachable gridnos will be picked so this should hopefully look okay
iSpotClosestThreatRange -= PythSpacesAway( pSoldier->sGridNo, sGridNo );
}
else
{
bEscapeDirection = -1;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{
//iThreatRange = AdjPixelsAway(CenterX(sGridNo),CenterY(sGridNo), CenterX(sThreatGridNo[iLoop]),CenterY(sThreatGridNo[iLoop]));
iThreatRange = GetRangeInCellCoordsFromGridNoDiff( sGridNo, sThreatGridNo[uiLoop] );
if (iThreatRange < iSpotClosestThreatRange)
{
iSpotClosestThreatRange = iThreatRange;
}
}
}
// if this is better than the best place found so far
// (i.e. the closest guy would be farther away than previously)
if (iSpotClosestThreatRange > iClosestThreatRange)
{
// remember it instead
iClosestThreatRange = iSpotClosestThreatRange;
//NumMessage("New best range = ",iClosestThreatRange);
sBestSpot = sGridNo;
bBestEscapeDirection = bEscapeDirection;
//NumMessage("New best grid = ",bestSpot);
}
}
}
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
if (bBestEscapeDirection != -1)
{
// Woohoo! We can escape! Fake some stuff with the quote-related actions
pSoldier->ubQuoteActionID = GetTraversalQuoteActionID( bBestEscapeDirection );
}
return( sBestSpot );
}
INT32 FindNearestUngassedLand(SOLDIERTYPE *pSoldier)
{
INT32 sGridNo, sClosestLand = NOWHERE, sPathCost, sShortestPath = 1000;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 iSearchRange, iIgnoreRange = 0;
INT32 iMaxSearchRange = 35;
UINT16 usMovementMode = DetermineMovementMode(pSoldier, AI_ACTION_LEAVE_WATER_GAS);
BOOLEAN fFoundReachable = FALSE;
if (!gfTurnBasedAI)
{
iMaxSearchRange = 15;
}
// start with a small search area, and expand it if we're unsuccessful
// this should almost never need to search farther than 5 or 10 squares...
for (iSearchRange = 5; iSearchRange <= iMaxSearchRange && (fFoundReachable || iSearchRange <= 5); iIgnoreRange = iSearchRange, iSearchRange += 10)
{
//NumMessage("Trying iSearchRange = ", 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);
}
}
//gubNPCAPBudget = pSoldier->bActionPoints;
gubNPCAPBudget = 0;
gubNPCDistLimit = (UINT8)iSearchRange;
FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0); //dnl ch50 071009
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// sevenfm: optimization - skip spots checked in previous loop
if (sXOffset <= iIgnoreRange && sXOffset >= -iIgnoreRange && sYOffset <= iIgnoreRange && sYOffset >= -iIgnoreRange)
{
continue;
}
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
//NumMessage("Testing gridno #",gridno);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
fFoundReachable = TRUE;
// ignore blacklisted spot
if (sGridNo == pSoldier->pathing.sBlackList)
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
// CJC: here, unfortunately, we must calculate a path so we have an AP cost
// obviously, we're looking for LAND, so water is out!
//sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0);
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
sPathCost = PlotPath(pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, pSoldier->bReverse, 0);
// check if spot is reachable
if(sPathCost == 0)
{
continue;
}
// if this path is shorter than the one to the closest land found so far
if (sPathCost < sShortestPath)
{
// remember it instead
sShortestPath = sPathCost;
//NumMessage("New shortest route = ",shortestPath);
sClosestLand = sGridNo;
//NumMessage("New closest land at grid = ",closestLand);
}
}
}
// if we found a piece of land in this search area
if (!TileIsOutOfBounds(sClosestLand)) // quit now, no need to look any farther
break;
}
//NumMessage("closestLand = ",closestLand);
return(sClosestLand);
}
INT32 FindNearbyDarkerSpot(SOLDIERTYPE *pSoldier)
{
INT32 sGridNo, sClosestSpot = NOWHERE, sPathCost;
INT32 iSpotValue, iBestSpotValue = 1000;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 iSearchRange, iIgnoreRange = 0;
INT32 iMaxSearchRange = 35;
INT8 bLightLevel, bCurrLightLevel, bLightDiff;
INT32 iRoamRange;
INT32 sOrigin;
UINT16 usMovementMode = DetermineMovementMode(pSoldier, AI_ACTION_LEAVE_WATER_GAS);
BOOLEAN fFoundReachable = FALSE;
bCurrLightLevel = LightTrueLevel(pSoldier->sGridNo, pSoldier->pathing.bLevel);
iRoamRange = RoamingRange(pSoldier, &sOrigin);
if (!gfTurnBasedAI)
{
iMaxSearchRange = 15;
}
// start with a small search area, and expand it if we're unsuccessful
// this should almost never need to search farther than 5 or 10 squares...
for (iSearchRange = 5; iSearchRange <= iMaxSearchRange && (fFoundReachable || iSearchRange <= 5); iIgnoreRange = iSearchRange, iSearchRange += 10)
{
// 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);
}
}
//gubNPCAPBudget = pSoldier->bActionPoints;
gubNPCAPBudget = 0;
gubNPCDistLimit = (UINT8)iSearchRange;
FindBestPath(pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, usMovementMode, COPYREACHABLE, 0); //dnl ch50 071009
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// sevenfm: optimization - skip spots checked in previous loop
if (sXOffset <= iIgnoreRange && sXOffset >= -iIgnoreRange && sYOffset <= iIgnoreRange && sYOffset >= -iIgnoreRange)
{
continue;
}
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
//NumMessage("Testing gridno #",gridno);
if (!(sGridNo >= 0 && sGridNo < WORLD_MAX))
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
fFoundReachable = TRUE;
// ignore blacklisted spot
if (sGridNo == pSoldier->pathing.sBlackList)
{
continue;
}
// require this character to stay within their roam range
if (PythSpacesAway(sOrigin, sGridNo) > iRoamRange)
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// screen out anything brighter than our current best spot
bLightLevel = LightTrueLevel(sGridNo, pSoldier->pathing.bLevel);
//bLightDiff = gbLightSighting[0][ bCurrLightLevel ] - gbLightSighting[0][ bLightLevel ];
bLightDiff = gGameExternalOptions.ubBrightnessVisionMod[bCurrLightLevel] - gGameExternalOptions.ubBrightnessVisionMod[bLightLevel];
// if the spot is darker than our current location, then bLightDiff > 0
// plus ignore differences of just 1 light level
if (bLightDiff <= 1)
{
continue;
}
// CJC: here, unfortunately, we must calculate a path so we have an AP cost
//sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0);
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
sPathCost = PlotPath(pSoldier, sGridNo, FALSE, FALSE, FALSE, usMovementMode, pSoldier->bStealthMode, pSoldier->bReverse, 0);
// check if spot is reachable
if (sPathCost == 0)
{
continue; // skip on to the next potential grid
}
// decrease the "cost" of the spot by the amount of light/darkness
iSpotValue = sPathCost * 2 - bLightDiff;
if (iSpotValue < iBestSpotValue)
{
// remember it instead
iBestSpotValue = iSpotValue;
//NumMessage("New shortest route = ",shortestPath);
sClosestSpot = sGridNo;
//NumMessage("New closest land at grid = ",closestLand);
}
}
}
// if we found a piece of land in this search area
if (!TileIsOutOfBounds(sClosestSpot)) // quit now, no need to look any farther
{
break;
}
}
return(sClosestSpot);
}
#define MINIMUM_REQUIRED_STATUS 70
INT8 SearchForItems( SOLDIERTYPE * pSoldier, INT8 bReason, UINT16 usItem )
{
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("SearchForItems"));
DebugAI(AI_MSG_INFO, pSoldier, String("SearchForItems [%d] bReason %d usItem %d", pSoldier->ubID, bReason, usItem));
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sGridNo;
INT32 sBestSpot = NOWHERE;
INT32 iTempValue, iValue, iBestValue = 0;
ITEM_POOL * pItemPool;
OBJECTTYPE * pObj;
INVTYPE * pItem;
INT32 iItemIndex, iBestItemIndex;
BOOLEAN fDumbEnoughtoPickup = FALSE;
iTempValue = -1;
iItemIndex = iBestItemIndex = -1;
// No fair picking up weapons while boxing!
if (gTacticalStatus.bBoxingState == BOXING)
{
DebugAI(AI_MSG_INFO, pSoldier, String("no picking up items for boxers!"));
return AI_ACTION_NONE;
}
if (pSoldier->bActionPoints < GetBasicAPsToPickupItem( pSoldier ))
{
DebugAI(AI_MSG_INFO, pSoldier, String("not enough AP!"));
return( AI_ACTION_NONE );
}
if ( !IS_MERC_BODY_TYPE( pSoldier ) )
{
DebugAI(AI_MSG_INFO, pSoldier, String("not merc bodytype!"));
return( AI_ACTION_NONE );
}
iSearchRange = gbDiff[DIFF_MAX_COVER_RANGE][ SoldierDifficultyLevel( pSoldier ) ];
DebugAI(AI_MSG_INFO, pSoldier, String("use search range %d", iSearchRange));
switch (pSoldier->aiData.bAttitude)
{
case DEFENSIVE: iSearchRange --; break;
case BRAVESOLO: iSearchRange += 2; break;
case BRAVEAID: iSearchRange += 2; break;
case CUNNINGSOLO: iSearchRange -= 2; break;
case CUNNINGAID: iSearchRange -= 2; break;
case AGGRESSIVE: iSearchRange ++; break;
}
// maximum search range is 1 tile / 10 pts of wisdom
if (iSearchRange > (pSoldier->stats.bWisdom / 10))
{
iSearchRange = (pSoldier->stats.bWisdom / 10);
}
if (!gfTurnBasedAI)
{
// don't search so far in realtime
iSearchRange /= 2;
}
// don't search so far for items
iSearchRange /= 2;
// 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...
// set the distance limit of the square region
gubNPCDistLimit = (UINT8) iSearchRange;
// set an AP limit too, to our APs less the cost of picking up an item
// and less the cost of dropping an item since we might need to do that
gubNPCAPBudget = pSoldier->bActionPoints - GetBasicAPsToPickupItem( pSoldier );
// 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, DetermineMovementMode( pSoldier, AI_ACTION_PICKUP_ITEM ), COPYREACHABLE, 0 );//dnl ch50 071009
// Flugente: if the soldier is 'dumb enough', he may pick up certain items... which can be used to lure the AI into traps
if (pSoldier->stats.bWisdom < 70)
{
DebugAI(AI_MSG_INFO, pSoldier, String("dumb enough to pick up items"));
fDumbEnoughtoPickup = TRUE;
}
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
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 (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
continue; // skip on to the next potential grid
}
if ( (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_ITEMPOOL_PRESENT)
&& (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE) )
{
// ignore blacklisted spot
if ( sGridNo == pSoldier->pathing.sBlackList )
{
continue;
}
DebugAI(AI_MSG_INFO, pSoldier, String("check spot %d, found items", sGridNo));
iValue = 0;
GetItemPool( sGridNo, &pItemPool, pSoldier->pathing.bLevel );
switch( bReason )
{
case SEARCH_AMMO:
// we are looking for ammo to match the gun in usItem
DebugAI(AI_MSG_INFO, pSoldier, String("SEARCH_AMMO"));
while( pItemPool )
{
pObj = &(gWorldItems[ pItemPool->iItemIndex ].object);
pItem = &(Item[pObj->usItem]);
DebugAI(AI_MSG_INFO, pSoldier, String("check item %d at %d status %d", pObj->usItem, sGridNo, (*pObj)[0]->data.objectStatus));
if ( pItem->usItemClass == IC_GUN && (*pObj)[0]->data.objectStatus >= MINIMUM_REQUIRED_STATUS )
{
// maybe this gun has ammo (adjust for whether it is better than ours!)
if ( (*pObj)[0]->data.gun.bGunAmmoStatus < 0 || (*pObj)[0]->data.gun.ubGunShotsLeft == 0 || (Item[pObj->usItem].fingerprintid && (*pObj)[0]->data.ubImprintID != NOBODY && (*pObj)[0]->data.ubImprintID != pSoldier->ubID) )
{
iTempValue = 0;
}
else
{
iTempValue = (*pObj)[0]->data.gun.ubGunShotsLeft * Weapon[pObj->usItem].ubDeadliness / Weapon[usItem].ubDeadliness;
}
}
else if (ValidAmmoType( usItem, pObj->usItem ) )
{
DebugAI(AI_MSG_INFO, pSoldier, String("good ammo"));
iTempValue = TotalPoints( pObj );
}
else
{
DebugAI(AI_MSG_INFO, pSoldier, String("not gun, not ammo - skip"));
iTempValue = 0;
}
DebugAI(AI_MSG_INFO, pSoldier, String("iTempValue %d", iTempValue));
if (iTempValue > iValue )
{
DebugAI(AI_MSG_INFO, pSoldier, String("select this ammo"));
iValue = iTempValue;
iItemIndex = pItemPool->iItemIndex;
}
pItemPool = pItemPool->pNext;
}
break;
case SEARCH_WEAPONS:
DebugAI(AI_MSG_INFO, pSoldier, String("SEARCH_WEAPONS"));
while( pItemPool )
{
pObj = &(gWorldItems[ pItemPool->iItemIndex ].object);
pItem = &(Item[pObj->usItem]);
DebugAI(AI_MSG_INFO, pSoldier, String("check item %d at %d status %d", pObj->usItem, sGridNo, (*pObj)[0]->data.objectStatus));
if (pItem->usItemClass & IC_WEAPON && (*pObj)[0]->data.objectStatus >= MINIMUM_REQUIRED_STATUS )
{
DebugAI(AI_MSG_INFO, pSoldier, String("weapon has good status"));
if ( (pItem->usItemClass & IC_GUN) && ((*pObj)[0]->data.gun.bGunAmmoStatus < 0 || (*pObj)[0]->data.gun.ubGunShotsLeft == 0 || (( Item[pObj->usItem].fingerprintid ) && (*pObj)[0]->data.ubImprintID != NOBODY && (*pObj)[0]->data.ubImprintID != pSoldier->ubID) ) )
{
// jammed or out of ammo, skip it!
DebugAI(AI_MSG_INFO, pSoldier, String("jammed or out of ammo, skip it!"));
iTempValue = 0;
}
else if ( Item[pSoldier->inv[HANDPOS].usItem].usItemClass & IC_WEAPON )
{
DebugAI(AI_MSG_INFO, pSoldier, String("compare with gun in hand"));
if (Weapon[pObj->usItem].ubDeadliness > Weapon[pSoldier->inv[HANDPOS].usItem].ubDeadliness)
{
iTempValue = 100 * Weapon[pObj->usItem].ubDeadliness / Weapon[pSoldier->inv[HANDPOS].usItem].ubDeadliness;
}
else
{
iTempValue = 0;
}
}
else
{
iTempValue = 200 + Weapon[pObj->usItem].ubDeadliness;
}
}
else
{
DebugAI(AI_MSG_INFO, pSoldier, String("not weapon or bad status"));
iTempValue = 0;
}
DebugAI(AI_MSG_INFO, pSoldier, String("iTempValue %d", iTempValue));
if (iTempValue > iValue )
{
DebugAI(AI_MSG_INFO, pSoldier, String("select this weapon"));
iValue = iTempValue;
iItemIndex = pItemPool->iItemIndex;
}
pItemPool = pItemPool->pNext;
}
break;
default:
DebugAI(AI_MSG_INFO, pSoldier, String("search items"));
while( pItemPool )
{
pObj = &(gWorldItems[ pItemPool->iItemIndex ].object);
pItem = &(Item[pObj->usItem]);
DebugAI(AI_MSG_INFO, pSoldier, String("check item %d at %d status %d", pObj->usItem, sGridNo, (*pObj)[0]->data.objectStatus));
if ( pItem->usItemClass & IC_WEAPON && (*pObj)[0]->data.objectStatus >= MINIMUM_REQUIRED_STATUS )
{
DebugAI(AI_MSG_INFO, pSoldier, String("gun has good status"));
if ( (pItem->usItemClass & IC_GUN) && ((*pObj)[0]->data.gun.bGunAmmoStatus < 0 || (*pObj)[0]->data.gun.ubGunShotsLeft == 0 || (( Item[pObj->usItem].fingerprintid ) && (*pObj)[0]->data.ubImprintID != NOBODY && (*pObj)[0]->data.ubImprintID != pSoldier->ubID) ) )
{
// jammed or out of ammo, skip it!
DebugAI(AI_MSG_INFO, pSoldier, String("jammed or out of ammo, skip it!"));
iTempValue = 0;
}
else if (pSoldier->inv[HANDPOS].exists() && (Item[pSoldier->inv[HANDPOS].usItem].usItemClass & IC_WEAPON))
{
DebugAI(AI_MSG_INFO, pSoldier, String("compare with weapon in hand"));
if (Weapon[pObj->usItem].ubDeadliness > Weapon[pSoldier->inv[HANDPOS].usItem].ubDeadliness)
{
if ((Weapon[pSoldier->inv[HANDPOS].usItem].ubDeadliness != NULL) && (Weapon[pSoldier->inv[HANDPOS].usItem].ubDeadliness > 0))
{
iTempValue = 100 * Weapon[pObj->usItem].ubDeadliness / Weapon[pSoldier->inv[HANDPOS].usItem].ubDeadliness;
}
else
{
iTempValue = 100 * Weapon[pObj->usItem].ubDeadliness;
}
}
else
{
iTempValue = 0;
}
}
else
{
iTempValue = 200 + Weapon[pObj->usItem].ubDeadliness;
}
DebugAI(AI_MSG_INFO, pSoldier, String("iTempValue %d", iTempValue));
}
else if (pItem->usItemClass == IC_ARMOUR && (*pObj)[0]->data.objectStatus >= MINIMUM_REQUIRED_STATUS )
{
DebugAI(AI_MSG_INFO, pSoldier, String("armour has good status"));
iTempValue = 0;
switch( Armour[pItem->ubClassIndex].ubArmourClass )
{
case ARMOURCLASS_HELMET:
DebugAI(AI_MSG_INFO, pSoldier, String("ARMOURCLASS_HELMET"));
if (pSoldier->inv[HELMETPOS].exists() == false)
{
iTempValue = 200 + EffectiveArmour( pObj );
}
else if ( EffectiveArmour( &(pSoldier->inv[HELMETPOS]) ) < EffectiveArmour( pObj ) )
{
iTempValue = 100 * EffectiveArmour( pObj ) / (EffectiveArmour( &(pSoldier->inv[HELMETPOS]) ) + 1);
}
DebugAI(AI_MSG_INFO, pSoldier, String("iTempValue %d", iTempValue));
break;
case ARMOURCLASS_VEST:
DebugAI(AI_MSG_INFO, pSoldier, String("ARMOURCLASS_VEST"));
if (pSoldier->inv[VESTPOS].exists() == false)
{
iTempValue = 200 + EffectiveArmour( pObj );
}
else if ( EffectiveArmour( &(pSoldier->inv[VESTPOS]) ) < EffectiveArmour( pObj ) )
{
iTempValue = 100 * EffectiveArmour( pObj ) / (EffectiveArmour( &(pSoldier->inv[VESTPOS]) ) + 1);
}
DebugAI(AI_MSG_INFO, pSoldier, String("iTempValue %d", iTempValue));
break;
case ARMOURCLASS_LEGGINGS:
DebugAI(AI_MSG_INFO, pSoldier, String("ARMOURCLASS_LEGGINGS"));
if (pSoldier->inv[LEGPOS].exists() == false)
{
iTempValue = 200 + EffectiveArmour( pObj );
}
else if ( EffectiveArmour( &(pSoldier->inv[LEGPOS]) ) < EffectiveArmour( pObj ) )
{
iTempValue = 100 * EffectiveArmour( pObj ) / (EffectiveArmour( &(pSoldier->inv[LEGPOS]) ) + 1);
}
DebugAI(AI_MSG_INFO, pSoldier, String("iTempValue %d", iTempValue));
break;
default:
{
break;
}
}
}
// Flugente: if the soldier is 'dumb enough', he may pick up 'interesting items'. This can be used to lure him into traps (a certain scene in FMJ comes to mind)
else if ( fDumbEnoughtoPickup && pItem->usItemClass == IC_MISC && HasItemFlag(pObj->usItem, ATTENTION_ITEM) )
{
DebugAI(AI_MSG_INFO, pSoldier, String("dumb soldier picks up item!"));
// oooh... shiny!
iTempValue = 1000;
}
else
{
iTempValue = 0;
}
DebugAI(AI_MSG_INFO, pSoldier, String("iTempValue %d", iTempValue));
if (iTempValue > iValue )
{
DebugAI(AI_MSG_INFO, pSoldier, String("select item"));
iValue = iTempValue;
iItemIndex = pItemPool->iItemIndex;
}
pItemPool = pItemPool->pNext;
}
break;
}
iValue = (3 * iValue) / (3 + PythSpacesAway( sGridNo, pSoldier->sGridNo ));
DebugAI(AI_MSG_INFO, pSoldier, String("iBestValue %d", iBestValue));
DebugAI(AI_MSG_INFO, pSoldier, String("value modified by distance %d", iValue));
if (iValue > iBestValue )
{
DebugAI(AI_MSG_INFO, pSoldier, String("select item at %d", sGridNo));
sBestSpot = sGridNo;
iBestValue = iValue;
iBestItemIndex = iItemIndex;
}
}
}
}
if (!TileIsOutOfBounds(sBestSpot))
{
DebugAI(AI_MSG_INFO, pSoldier, String("%d decides to pick up %S", pSoldier->ubID, ItemNames[gWorldItems[iBestItemIndex].object.usItem]));
if (Item[gWorldItems[ iBestItemIndex ].object.usItem].usItemClass == IC_GUN)
{
//CHRISL: This is the line from ADB's code but I removed it, for now, to match what 0verhaul has been working on
//if (pSoldier->inv[HANDPOS].exists() == true && PlaceInAnyPocket(pSoldier, &pSoldier->inv[HANDPOS], false) == false)
if (FindBetterSpotForItem( pSoldier, HANDPOS ) == FALSE)
{
if (pSoldier->bActionPoints < GetBasicAPsToPickupItem( pSoldier ) + GetBasicAPsToPickupItem( pSoldier ))
{
return( AI_ACTION_NONE );
}
if (pSoldier->inv[HANDPOS].fFlags & OBJECT_UNDROPPABLE)
{
// destroy this item!
DebugAI(AI_MSG_INFO, pSoldier, String("%d decides he must drop %S first so destroys it", pSoldier->ubID, ItemNames[pSoldier->inv[HANDPOS].usItem]));
DeleteObj( &(pSoldier->inv[HANDPOS]) );
DeductPoints( pSoldier, GetBasicAPsToPickupItem( pSoldier ), 0, AFTERACTION_INTERRUPT );
}
else
{
// we want to drop this item!
DebugAI(AI_MSG_INFO, pSoldier, String("%d decides he must drop %S first", pSoldier->ubID, ItemNames[pSoldier->inv[HANDPOS].usItem]));
pSoldier->aiData.bNextAction = AI_ACTION_PICKUP_ITEM;
pSoldier->aiData.usNextActionData = sBestSpot;
pSoldier->iNextActionSpecialData = iBestItemIndex;
return( AI_ACTION_DROP_ITEM );
}
}
}
pSoldier->aiData.uiPendingActionData1 = iBestItemIndex;
pSoldier->aiData.usActionData = sBestSpot;
return( AI_ACTION_PICKUP_ITEM );
}
return( AI_ACTION_NONE );
}
INT32 FindClosestDoor( SOLDIERTYPE * pSoldier )
{
INT32 sClosestDoor = NOWHERE;
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sGridNo;
INT32 iDist, iClosestDist = 10;
iSearchRange = 5;
// 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);
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (FindStructure( sGridNo, STRUCTURE_ANYDOOR ) != NULL)
{
iDist = PythSpacesAway( pSoldier->sGridNo, sGridNo );
if (iDist < iClosestDist)
{
iClosestDist = iDist;
sClosestDoor = sGridNo;
}
}
}
}
return( sClosestDoor );
}
INT32 FindNearestEdgepointOnSpecifiedEdge( INT32 sGridNo, INT8 bEdgeCode )
{
INT32 iLoop;
INT32 *psEdgepointArray;
INT32 iEdgepointArraySize;
INT32 sClosestSpot = NOWHERE, sClosestDist = 0x7FFF, sTempDist;
switch( bEdgeCode )
{
case NORTH_EDGEPOINT_SEARCH:
psEdgepointArray = gps1stNorthEdgepointArray;
iEdgepointArraySize = gus1stNorthEdgepointArraySize;
break;
case EAST_EDGEPOINT_SEARCH:
psEdgepointArray = gps1stEastEdgepointArray;
iEdgepointArraySize = gus1stEastEdgepointArraySize;
break;
case SOUTH_EDGEPOINT_SEARCH:
psEdgepointArray = gps1stSouthEdgepointArray;
iEdgepointArraySize = gus1stSouthEdgepointArraySize;
break;
case WEST_EDGEPOINT_SEARCH:
psEdgepointArray = gps1stWestEdgepointArray;
iEdgepointArraySize = gus1stWestEdgepointArraySize;
break;
default:
// WTF???
return( NOWHERE );
}
// Do a 2D search to find the closest map edgepoint and
// try to create a path there
for ( iLoop = 0; iLoop < iEdgepointArraySize; iLoop++ )
{
sTempDist = PythSpacesAway( sGridNo, psEdgepointArray[ iLoop ] );
if ( sTempDist < sClosestDist )
{
sClosestDist = sTempDist;
sClosestSpot = psEdgepointArray[ iLoop ];
}
}
return( sClosestSpot );
}
INT32 FindNearestEdgePoint( INT32 sGridNo )
{
INT16 sGridX, sGridY;
INT16 sScreenX, sScreenY, sMaxScreenX, sMaxScreenY;
INT16 sDist[5], sMinDist;
INT32 iLoop;
INT8 bMinIndex;
INT32 * psEdgepointArray;
INT32 iEdgepointArraySize;
INT32 sClosestSpot = NOWHERE, sClosestDist = 0x7FFF, sTempDist;
ConvertGridNoToXY( sGridNo, &sGridX, &sGridY );
GetWorldXYAbsoluteScreenXY( sGridX, sGridY, &sScreenX, &sScreenY );
sMaxScreenX = gsBRX - gsTLX;
sMaxScreenY = gsBRY - gsTLY;
sDist[0] = 0x7FFF;
sDist[1] = sScreenX; // west
sDist[2] = sMaxScreenX - sScreenX; // east
sDist[3] = sScreenY; // north
sDist[4] = sMaxScreenY - sScreenY; // south
sMinDist = sDist[0];
bMinIndex = 0;
for( iLoop = 1; iLoop < 5; iLoop++)
{
if ( sDist[ iLoop ] < sMinDist )
{
sMinDist = sDist[ iLoop ];
bMinIndex = (INT8) iLoop;
}
}
switch( bMinIndex )
{
case 1:
psEdgepointArray = gps1stWestEdgepointArray;
iEdgepointArraySize = gus1stWestEdgepointArraySize;
break;
case 2:
psEdgepointArray = gps1stEastEdgepointArray;
iEdgepointArraySize = gus1stEastEdgepointArraySize;
break;
case 3:
psEdgepointArray = gps1stNorthEdgepointArray;
iEdgepointArraySize = gus1stNorthEdgepointArraySize;
break;
case 4:
psEdgepointArray = gps1stSouthEdgepointArray;
iEdgepointArraySize = gus1stSouthEdgepointArraySize;
break;
default:
// WTF???
return( NOWHERE );
}
// Do a 2D search to find the closest map edgepoint and
// try to create a path there
for ( iLoop = 0; iLoop < iEdgepointArraySize; iLoop++ )
{
sTempDist = PythSpacesAway( sGridNo, psEdgepointArray[ iLoop ] );
if ( sTempDist < sClosestDist )
{
sClosestDist = sTempDist;
sClosestSpot = psEdgepointArray[ iLoop ];
}
}
return( sClosestSpot );
}
#define EDGE_OF_MAP_SEARCH 5
INT32 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection )
{
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sGridNo, sClosestSpot = NOWHERE;
INT8 bDirection, bClosestDirection;
INT32 iPathCost, iClosestPathCost = 1000;
bClosestDirection = -1;
// Call FindBestPath to set flags in all locations that we can
// walk into within range. We have to set some things up first...
// set the distance limit of the square region
gubNPCDistLimit = EDGE_OF_MAP_SEARCH;
iSearchRange = EDGE_OF_MAP_SEARCH;
// 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));
// 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, WALKING, COPYREACHABLE, 0 );//dnl ch50 071009
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
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 (GridNoOnEdgeOfMap( sGridNo, &bDirection ) )
{
iPathCost = PythSpacesAway( pSoldier->sGridNo, sGridNo );
if (iPathCost < iClosestPathCost)
{
// this place is closer
sClosestSpot = sGridNo;
iClosestPathCost = iPathCost;
bClosestDirection = bDirection;
}
}
}
}
*pbDirection = bClosestDirection;
return( sClosestSpot );
}
INT32 FindRouteBackOntoMap( SOLDIERTYPE * pSoldier, INT32 sDestGridNo )
{
// the first thing to do is restore the soldier's gridno from the X and Y
// values
// well, let's TRY just taking a path to the place we're supposed to go...
if ( FindBestPath( pSoldier, sDestGridNo, pSoldier->pathing.bLevel, WALKING, COPYROUTE, 0 ) )
{
pSoldier->pathing.bPathStored = TRUE;
return( sDestGridNo );
}
else
{
return( NOWHERE );
}
}
INT32 FindClosestBoxingRingSpot( SOLDIERTYPE * pSoldier, BOOLEAN fInRing )
{
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sGridNo, sClosestSpot = NOWHERE;
INT32 iDistance, iClosestDistance = 9999;
UINT16 usRoom;
SOLDIERTYPE *pDarren;
pDarren = FindSoldierByProfileID(DARREN, FALSE);
// set the distance limit of the square region
iSearchRange = 7;
// 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));
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if (!TileIsOutOfBounds(sGridNo) &&
InARoom(sGridNo, &usRoom))
{
if ((fInRing && usRoom == BOXING_RING) || (!fInRing && usRoom != BOXING_RING) &&
LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, NOWATER, 0))
{
// sevenfm: for player merc, find spot closest to Darren
if (pDarren && pSoldier->bTeam == gbPlayerNum)
iDistance = PythSpacesAway(pDarren->sGridNo, sGridNo) + PythSpacesAway(pSoldier->sGridNo, sGridNo);
else
iDistance = abs(sXOffset) + abs(sYOffset);
if (iDistance < iClosestDistance && WhoIsThere2(sGridNo, 0) == NOBODY)
{
sClosestSpot = sGridNo;
iClosestDistance = iDistance;
}
}
}
}
}
return( sClosestSpot );
}
INT32 FindNearestOpenableNonDoor( INT32 sStartGridNo )
{
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sGridNo, sClosestSpot = NOWHERE;
INT32 iDistance, iClosestDistance = 9999;
STRUCTURE * pStructure;
// set the distance limit of the square region
iSearchRange = 7;
// determine maximum horizontal limits
sMaxLeft = min( iSearchRange, (sStartGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = min( iSearchRange, MAXCOL - ((sStartGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = min( iSearchRange, (sStartGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = min( iSearchRange, MAXROW - ((sStartGridNo / MAXROW) + 1));
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = sStartGridNo + sXOffset + (MAXCOL * sYOffset);
pStructure = FindStructure( sGridNo, STRUCTURE_OPENABLE );
if (pStructure)
{
// skip any doors
while ( pStructure && ( pStructure->fFlags & STRUCTURE_ANYDOOR ) )
{
pStructure = FindNextStructure( pStructure, STRUCTURE_OPENABLE );
}
// if we still have a pointer, then we have found a valid non-door openable structure
if ( pStructure )
{
iDistance = CardinalSpacesAway( sGridNo, sStartGridNo );
if ( iDistance < iClosestDistance )
{
sClosestSpot = sGridNo;
iClosestDistance = iDistance;
}
}
}
}
}
return( sClosestSpot );
}
INT32 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT32 sPos, INT8 bAction )
{
INT32 sGridNo;
INT32 sBestSpot = NOWHERE;
INT32 iSearchRange = 8; // sevenfm: increase search range
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 sDistanceVisible = VISION_RANGE;
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindFlankingSpot: orig loc = %d, loc to flank = %d", pSoldier->sGridNo , sPos));
// hit the edge of the map
/*if ( FindNearestEdgePoint ( pSoldier->sGridNo ) == pSoldier->sGridNo )
return NOWHERE;*/
// sevenfm: use max AP at the start of new turn
//gubNPCAPBudget=(UINT8) (iSearchRange*3);
gubNPCAPBudget= pSoldier->CalcActionPoints();
// 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...
// set the distance limit of the square region
gubNPCDistLimit = (UINT8) iSearchRange;
// 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, DetermineMovementMode( pSoldier, bAction ), COPYREACHABLE, 0 );//dnl ch50 071009
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
// get direction of position to flank from soldier's position
INT16 sDir = GetDirectionFromGridNo ( sPos, pSoldier) ;
INT16 sDesiredDir;
INT16 sTempDir;
INT16 sTempDist, sBestDist=0;
// sevenfm: better direction calculation (use arrays)
switch ( bAction )
{
case AI_ACTION_FLANK_LEFT:
sDesiredDir = gTwoCCDirection[ sDir ];
break;
case AI_ACTION_FLANK_RIGHT:
sDesiredDir = gTwoCDirection[ sDir ];
break;
case AI_ACTION_WITHDRAW:
sDesiredDir = gOppositeDirection[ sDir ];
break;
default:
sDesiredDir = sDir;
}
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindFlankingSpot: direction to loc = %d, dir to flank = %d", sDir , sDesiredDir ));
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
sTempDir = GetDirectionFromGridNo ( sGridNo, pSoldier );
sTempDist = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo , sGridNo );
//NumMessage("Testing gridno #",gridno);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{
continue;
}
if (!(gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE))
{
continue;
}
if ( sGridNo == pSoldier->pathing.sBlackList )
{
continue;
}
// sevenfm: skip tiles too close to edge
if ( PythSpacesAway( FindNearestEdgePoint ( sGridNo ), sGridNo ) <= 2 )
{
continue;
}
// sevenfm: don't go into deep water for flanking
if (!AllowDeepWaterFlanking(pSoldier) &&
DeepWater(sGridNo, pSoldier->pathing.bLevel) &&
!DeepWater(pSoldier->sGridNo, pSoldier->pathing.bLevel))
{
continue;
}
// sevenfm: allow water flanking only for CUNNINGSOLO soldiers
if( Water( sGridNo, pSoldier->pathing.bLevel ) &&
pSoldier->aiData.bAttitude != CUNNINGSOLO &&
pSoldier->aiData.bAttitude != CUNNINGAID )
{
continue;
}
// avoid fresh corpses
if (GetNearestRottingCorpseAIWarning(sGridNo) > 0)
{
continue;
}
if (!CheckNPCDestination(pSoldier, sGridNo))
{
continue;
}
if (!LegalNPCDestination(pSoldier, sGridNo, IGNORE_PATH, WATEROK, 0))
{
continue; // skip on to the next potential grid
}
// sevenfm: skip buildings if not in building already, because soldiers often run into buildings and stop flanking
if (InARoom(sGridNo, NULL) && !InARoom(pSoldier->sGridNo, NULL))
{
continue;
}
// sevenfm: penalize locations too far from noise gridno
if (PythSpacesAway(sGridNo, sPos) > MAX_FLANK_DIST_RED - 5)
{
sTempDist = sTempDist / 2;
}
// avoid moving too far from noise gridno
if (PythSpacesAway(sGridNo, sPos) > MAX_FLANK_DIST_RED && pSoldier->aiData.bAttitude != CUNNINGSOLO)
{
continue;
}
// sevenfm: avoid moving too close to enemy vision range
if (PythSpacesAway(sGridNo, sPos) < sDistanceVisible + 5)
{
sTempDist = sTempDist / 2;
}
// sevenfm: penalize locations with no sight cover from noise gridno (supposed that we are sneaking)
if( PythSpacesAway( sGridNo, sPos) <= (INT16)MAX_VISION_RANGE &&
LocationToLocationLineOfSightTest( sGridNo, pSoldier->pathing.bLevel, sPos, pSoldier->pathing.bLevel, TRUE, CALC_FROM_ALL_DIRS) )
{
//continue;
sTempDist = sTempDist / 2;
}
// allow extra directions for flanking
if ( bAction == AI_ACTION_FLANK_LEFT )
{
// sevenfm: allow two extra directions
if ( sTempDir != sDesiredDir && sTempDir != gOneCDirection[sDesiredDir] && sTempDir != gOneCCDirection[sDesiredDir])
continue;
// prefer desired dir x1.5
if( sTempDir == sDesiredDir )
sTempDist = 3*sTempDist/2;
}
else if ( bAction == AI_ACTION_FLANK_RIGHT )
{
// sevenfm: allow two extra directions
if ( sTempDir != sDesiredDir && sTempDir != gOneCDirection[sDesiredDir] && sTempDir != gOneCCDirection[sDesiredDir])
continue;
// prefer desired dir x1.5
if( sTempDir == sDesiredDir )
sTempDist = 3*sTempDist/2;
}
else
{
if ( sTempDir != sDesiredDir )
continue;
}
// if this is better than the best place found so far
if ( sTempDist > sBestDist )
{
// remember it instead
sBestDist = sTempDist;
sBestSpot = sGridNo;
}
}
}
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindFlankingSpot: return grid no %d", sBestSpot ));
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return( sBestSpot );
}
//sevenfm: new calculation using FindHeigherLevel
INT32 FindClosestClimbPoint (SOLDIERTYPE *pSoldier, BOOLEAN fClimbUp )
{
INT32 sBestSpot = NOWHERE;
// sevenfm: safety check
if(!pSoldier)
{
return NOWHERE;
}
//DebugMsg( TOPIC_JA2AI , DBG_LEVEL_3 , "FindClosestClimbPoint");
if (fClimbUp)
{
// For the climb up case, search the nearby limits for a climb up point and take the closest.
INT32 sGridNo;
static const INT32 iSearchRange = 20;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
//UINT8 ubTestDir;
INT8 ubClimbDir;
INT32 sClimbSpot;
// 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);
//DebugMsg( TOPIC_JA2AI , DBG_LEVEL_3 , String("Max: Left %d Right %d Up %d Down %d", sMaxLeft, sMaxRight, sMaxUp, sMaxDown ) );
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
//DebugMsg( TOPIC_JA2AI , DBG_LEVEL_3 , String("Checking grid %d" , sGridNo ));
//NumMessage("Testing gridno #",gridno);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{
continue;
}
if ( sGridNo == pSoldier->pathing.sBlackList )
{
continue;
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if ( InGas( pSoldier, sGridNo ) )
{
continue;
}
// exclude water tiles
if ( Water( sGridNo, pSoldier->pathing.bLevel ) )
{
continue;
}
// check that there's noone at sGridNo at level 0 (this soldier is allowed)
if( WhoIsThere2( sGridNo, 0 ) != NOBODY &&
WhoIsThere2( sGridNo, 0 ) != pSoldier->ubID )
{
continue;
}
if( FindHeigherLevel( pSoldier, sGridNo, pSoldier->ubDirection, &ubClimbDir ) )
{
// ubClimbDir is new direction
// check that there's noone there
sClimbSpot = NewGridNo( sGridNo, DirectionInc( ubClimbDir));
if( WhoIsThere2( sClimbSpot, 1 ) == NOBODY )
{
// Good climb point. Is it closer than the previous point?
if( TileIsOutOfBounds(sBestSpot) ||
GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo , sGridNo ) < GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo , sBestSpot ))
{
// If not, we have a new winnar!
sBestSpot = sGridNo;
}
}
}
}
}
}
else
{
// Climbing down is easier. Just find the nearest climb point ;)
sBestSpot = FindClosestClimbPoint( pSoldier, pSoldier->sGridNo, pSoldier->sGridNo, fClimbUp);
}
// DebugMsg( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindClosestClimbPoint Returning %d", sBestSpot ));
return( sBestSpot );
}
BOOLEAN CanClimbFromHere (SOLDIERTYPE * pSoldier, BOOLEAN fUp )
{
return FindDirectionForClimbing( pSoldier, pSoldier->sGridNo, pSoldier->pathing.bLevel) != DIRECTION_IRRELEVANT;
}
// OK, this place shows potential. How useful is it as cover?
//NumMessage("Promising seems gridno #",gridno);
/* if ( FindBuilding ( sGridNo ) != NULL )
pBuilding = FindBuilding ( sGridNo );
}
}
DebugMsg( TOPIC_JA2AI , DBG_LEVEL_3 , String("Adjacent Building climb spots = %d" , pBuilding->ubNumClimbSpots ));
if ( pBuilding != NULL)
{
if ( fUp )
{
for (i = 0 ; i < pBuilding->ubNumClimbSpots; i++)
{
if (pBuilding->sUpClimbSpots[ i ] == pSoldier->sGridNo &&
(WhoIsThere2( pBuilding->sUpClimbSpots[ i ], 0 ) == NOBODY)
&& (WhoIsThere2( pBuilding->sDownClimbSpots[ i ], 1 ) == NOBODY) )
return TRUE;
}
}
else
{
for (i = 0 ; i < pBuilding->ubNumClimbSpots; i++)
{
if (pBuilding->sDownClimbSpots[ i ] == pSoldier->sGridNo &&
(WhoIsThere2( pBuilding->sUpClimbSpots[ i ], 0 ) == NOBODY)
&& (WhoIsThere2( pBuilding->sDownClimbSpots[ i ], 1 ) == NOBODY) )
return TRUE;
}
}
}
return FALSE;
}
*/
extern BUILDING gBuildings[ MAX_BUILDINGS ];
extern UINT8 gubNumberOfBuildings;
INT32 FindBestCoverNearTheGridNo(SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubSearchRadius )
{
INT32 iPercentBetter;
// INT16 sTrueGridNo;
INT16 sResultGridNo;
INT8 bRealWisdom = pSoldier->stats.bWisdom;
// sTrueGridNo = pSoldier->sGridNo;
// pSoldier->sGridNo = sGridNo;
pSoldier->stats.bWisdom = 8 * ubSearchRadius;// 5 tiles
sResultGridNo = FindBestNearbyCover( pSoldier, MORALE_NORMAL, &iPercentBetter);
pSoldier->stats.bWisdom = bRealWisdom;
// pSoldier->sGridNo = sTrueGridNo;
if(!TileIsOutOfBounds(sResultGridNo))
return sResultGridNo;
else
return sGridNo;
}
// sevenfm: new calculation using FindHeigherLevel/FindLowerLevel
INT8 FindDirectionForClimbing( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel )
{
INT8 ubClimbDir;
INT32 sClimbSpot;
if(!pSoldier)
{
return DIRECTION_IRRELEVANT;
}
if (pSoldier->pathing.bLevel == 0)
{
// check climb up
if( FindHeigherLevel( pSoldier, sGridNo, pSoldier->ubDirection, &ubClimbDir ) )
{
// ubClimbDir is new direction
// check that there's noone there
sClimbSpot = NewGridNo( sGridNo, DirectionInc( ubClimbDir));
if( WhoIsThere2( sClimbSpot, 1 ) == NOBODY &&
!Water(sClimbSpot, 1) )
{
return ubClimbDir;
}
}
}
else
{
// check climb down
if( FindLowerLevel( pSoldier, pSoldier->sGridNo, pSoldier->ubDirection, &ubClimbDir ) )
{
// ubClimbDir is new direction
sClimbSpot = NewGridNo( sGridNo, DirectionInc( ubClimbDir));
if( WhoIsThere2( sClimbSpot, 0 ) == NOBODY &&
!Water(sClimbSpot, 0) )
{
return ubClimbDir;
}
}
}
return DIRECTION_IRRELEVANT;
}
INT32 FindNearestPassableSpot( INT32 sGridNo, UINT8 usSearchRadius )
{
INT32 sNearestSpot = NOWHERE;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sTestGridNo;
INT32 iDist, iClosestDist = 10;
// determine maximum horizontal limits
sMaxLeft = min( usSearchRadius, (sGridNo % MAXCOL));
sMaxRight = min( usSearchRadius, MAXCOL - ((sGridNo % MAXCOL) + 1));
// determine maximum vertical limits
sMaxUp = min( usSearchRadius, (sGridNo / MAXROW));
sMaxDown = min( usSearchRadius, MAXROW - ((sGridNo / MAXROW) + 1));
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
// calculate the next potential gridno
sTestGridNo = sGridNo + sXOffset + (MAXCOL * sYOffset);
// is this an empty tile (no structure on it) or is the structure passable?
if (gpWorldLevelData[sTestGridNo].pStructureHead == NULL || FindStructure( sTestGridNo, STRUCTURE_PASSABLE ) != NULL)
{
iDist = PythSpacesAway( sGridNo, sTestGridNo );
if (iDist < iClosestDist)
{
iClosestDist = iDist;
sNearestSpot = sTestGridNo;
}
}
}
}
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)(TACTICAL_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)(TACTICAL_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);
}