Files
source/TacticalAI/FindLocations.cpp
T
2008-11-06 00:46:54 +00:00

2852 lines
88 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "AI All.h"
#else
#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"
#include "Structure Wrap.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"
#endif
#include "PathAIDebug.h"
#ifdef _DEBUG
INT16 gsCoverValue[WORLD_MAX];
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( INT16 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, INT16 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 < APBPConstants[AP_CROUCH] + APBPConstants[AP_PRONE])
{
continue;
}
break;
case 2:
if (iMyAPsLeft < APBPConstants[AP_CROUCH])
{
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, INT16 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 < APBPConstants[AP_CROUCH] + APBPConstants[AP_PRONE])
{
continue;
}
break;
case 2:
if (iMyAPsLeft < APBPConstants[AP_CROUCH])
{
continue;
}
break;
default:
break;
}
iTotalCTGT += SoldierToLocationChanceToGetThrough( pSoldier, sOppGridNo, bLevel, bCubeLevel, ubOppID );
bValidCubeLevels++;
}
iTotalCTGT /= bValidCubeLevels;
return( (INT8) iTotalCTGT );
}
INT8 CalcBestCTGT( SOLDIERTYPE *pSoldier, UINT8 ubOppID, INT16 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!
INT16 sCentralGridNo, sAdjSpot, sNorthGridNo, sSouthGridNo, sCheckSpot, 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 ) > 0) && !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, INT16 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;
INT16 sHisGridNo, sMyRealGridNo = NOWHERE, sHisRealGridNo = NOWHERE;
INT16 sTempX, sTempY;
FLOAT dMyX, dMyY, dHisX, dHisY;
INT8 bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT;
INT32 iRangeChange, iRangeFactor, iRangeFactorMultiplier;
SOLDIERTYPE *pHim;
dMyX = dMyY = dHisX = dHisY = -1.0;
pHim = Threat[uiThreatIndex].pOpponent;
sHisGridNo = Threat[uiThreatIndex].sGridNo;
// 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 (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 (sHisRealGridNo == NOWHERE)
{
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 (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;
}
// bMyCTGT = ChanceToGetThrough(pMe,sHisGridNo,FAKE,ACTUAL,TESTWALLS,9999,M9PISTOL,NOT_FOR_LOS); // assume a gunshot
// bMyCTGT = SoldierToLocationChanceToGetThrough( pMe, sHisGridNo, pMe->bTargetLevel, pMe->bTargetCubeLevel );
// let's not assume anything about the stance the enemy might take, so take an average
// value... no cover give a higher value than partial cover
bMyCTGT = CalcAverageCTGTForPosition( pMe, pHim->ubID, sHisGridNo, pHim->pathing.bLevel, iMyAPsLeft );
// 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 (sMyRealGridNo != NOWHERE)
{
pMe->sGridNo = sMyRealGridNo; // put me back where I belong!
pMe->dXPos = dMyX; // also change the 'x'
pMe->dYPos = dMyY; // and the 'y'
}
if (sHisRealGridNo != NOWHERE)
{
pHim->sGridNo = sHisRealGridNo; // put HIM back where HE belongs!
pHim->dXPos = dHisX; // also change the 'x'
pHim->dYPos = dHisY; // and the 'y'
}
// these value should be < 1 million each
iHisPosValue = bHisCTGT * Threat[uiThreatIndex].iValue * Threat[uiThreatIndex].iAPs;
iMyPosValue = bMyCTGT * iMyThreat * iMyAPsLeft;
// 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 < APBPConstants[AP_CROUCH])
// subtract another 1 % penalty for NOT being able to crouch per tile
// the farther away we are, the bigger a difference crouching will make!
iMyPosValue -= ((iMyPosValue * (AIM_PENALTY_TARGET_CROUCHED + (iRange / CELL_X_SIZE))) / 100);
}
// high morale prefers decreasing the range (positive factor), while very
// low morale (HOPELESS) prefers increasing it
// if (bHisCTGT < 100 || (morale - 1 < 0))
{
iRangeFactorMultiplier = RangeChangeDesire( pMe );
if (iRangeFactorMultiplier)
{
iRangeChange = Threat[uiThreatIndex].iOrigRange - iRange;
if (iRangeChange)
{
//iRangeFactor = (iRangeChange * (morale - 1)) / 4;
iRangeFactor = (iRangeChange * iRangeFactorMultiplier) / 2;
#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 managable 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 managable 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, INT16 sGridNo )
{
UINT8 ubLoop, ubCount, ubWhoIsThere;
INT16 sTempGridNo;
ubCount = 0;
for( ubLoop = 0; ubLoop < NUM_WORLD_DIRECTIONS; ubLoop++ )
{
sTempGridNo = NewGridNo( sGridNo, DirectionInc( ubLoop ) );
if ( sTempGridNo != sGridNo )
{
ubWhoIsThere = WhoIsThere2( sGridNo, pSoldier->pathing.bLevel );
if ( ubWhoIsThere != NOBODY && ubWhoIsThere != pSoldier->ubID && MercPtrs[ ubWhoIsThere ]->bTeam == pSoldier->bTeam )
{
ubCount++;
}
}
}
return( ubCount );
}
INT16 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, iThreatCertainty;
INT16 sGridNo, sBestCover = NOWHERE;
INT32 iPathCost;
INT32 iThreatRange, iClosestThreatRange = 1500;
// INT16 sClosestThreatGridno = NOWHERE;
INT32 iMyThreatValue;
INT16 sThreatLoc;
INT32 iMaxThreatRange;
UINT32 uiThreatCnt = 0;
INT32 iMaxMoveTilesLeft, iSearchRange, iRoamRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 sOrigin; // has to be a short, need a pointer
INT16 * pusLastLoc;
INT8 * pbPersOL;
INT8 * pbPublOL;
SOLDIERTYPE *pOpponent;
UINT16 usMovementMode;
UINT8 ubBackgroundLightLevel;
UINT8 ubBackgroundLightPercent = 0;
UINT8 ubLightPercentDifference;
BOOLEAN fNight;
// 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 = 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.
iMaxMoveTilesLeft = __max( 0, pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode ) );
//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);
}
// those within 20 tiles of any tile we'll CONSIDER as cover are important
iMaxThreatRange = MAX_THREAT_RANGE + (CELL_X_SIZE * iSearchRange);
// calculate OUR OWN general threat value (not from any specific location)
iMyThreatValue = CalcManThreatValue(pSoldier,NOWHERE,FALSE,pSoldier);
// 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;
pusLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID;
// if this opponent is unknown personally and publicly
if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN))
{
continue; // next merc
}
// Special stuff for Carmen the bounty hunter
if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != 64)
{
continue; // 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 = *pusLastLoc;
iThreatCertainty = ThreatPercent[*pbPersOL - OLDEST_HEARD_VALUE];
}
else
{
// using public knowledge, obtain opponent's "best guess" gridno
sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID];
iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE];
}
// calculate how far away this threat is (in adjusted pixels)
//iThreatRange = AdjPixelsAway(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sThreatLoc),CenterY(sThreatLoc));
iThreatRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sThreatLoc );
//NumMessage("Threat Range = ",iThreatRange);
#ifdef DEBUGCOVER
// DebugAI( String( "FBNC: Opponent %d believed to be at gridno %d (mine %d, public %d)\n",iLoop,sThreatLoc,*pusLastLoc,PublicLastKnownOppLoc[pSoldier->bTeam][iLoop] ) );
#endif
// if this opponent is believed to be too far away to really be a threat
if (iThreatRange > iMaxThreatRange)
{
#ifdef DEBUGCOVER
// AINameMessage(pOpponent,"is too far away to be a threat",1000);
#endif
continue; // check next opponent
}
// remember this opponent as a current threat, but DON'T REDUCE FOR COVER!
Threat[uiThreatCnt].iValue = CalcManThreatValue(pOpponent,pSoldier->sGridNo,FALSE,pSoldier);
// if the opponent is no threat at all for some reason
if (Threat[uiThreatCnt].iValue == -999)
{
//NameMessage(pOpponent,"is thought to be no threat");
continue; // check next opponent
}
//NameMessage(pOpponent,"added to the list of threats");
//NumMessage("His/Her threat value = ",threatValue[uiThreatCnt]);
Threat[uiThreatCnt].pOpponent = pOpponent;
Threat[uiThreatCnt].sGridNo = sThreatLoc;
Threat[uiThreatCnt].iCertainty = iThreatCertainty;
Threat[uiThreatCnt].iOrigRange = iThreatRange;
// calculate how many APs he will have at the start of the next turn
Threat[uiThreatCnt].iAPs = pOpponent->CalcActionPoints();
if (iThreatRange < iClosestThreatRange)
{
iClosestThreatRange = iThreatRange;
//NumMessage("New Closest Threat Range = ",iClosestThreatRange);
// sClosestThreatGridNo = sThreatLoc;
//NumMessage("Closest Threat Gridno = ",sClosestThreatGridNo);
}
uiThreatCnt++;
}
// 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;
// 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);
}
//sprintf(tempstr,"iCurrentCoverValue after opponent %d is now %d",iLoop,iCurrentCoverValue);
//PopMessage(tempstr);
}
iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
#ifdef DEBUGCOVER
// AINumMessage("Search Range = ",iSearchRange);
#endif
// determine maximum horizontal limits
sMaxLeft = (INT16) min(iSearchRange,(pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min(iSearchRange,(pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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) &&
(sOrigin != NOWHERE))
{
// 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, NOWHERE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER ), COPYREACHABLE_AND_APS, 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++)
{
//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;
}
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;
// 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);
}
//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 ( 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 );
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 (sBestCover != NOWHERE)
{
#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((INT16)sBestCover); // return the gridno of that cover
}
}
return(NOWHERE); // return that no suitable cover was found
}
INT16 FindSpotMaxDistFromOpponents(SOLDIERTYPE *pSoldier)
{
INT16 sGridNo;
INT16 sBestSpot = NOWHERE;
UINT32 uiLoop;
INT32 iThreatRange,iClosestThreatRange = 1500, iSpotClosestThreatRange;
INT16 sThreatLoc, sThreatGridNo[MAXMERCS];
UINT32 uiThreatCnt = 0;
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT8 * pbPersOL,*pbPublOL, bEscapeDirection, bBestEscapeDirection = -1;
SOLDIERTYPE *pOpponent;
INT16 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 != 64)
{
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 = (INT16) min( iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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, NOWHERE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_RUN_AWAY ), 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);
//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;
}
//Madd: skip lighted spots
if ( InLightAtNight( sGridNo, pSoldier->pathing.bLevel ) )
continue;
// OK, this place shows potential. How useful is it as cover?
//NumMessage("Promising seems gridno #",gridno);
iSpotClosestThreatRange = 1500;
if ( pSoldier->bTeam == ENEMY_TEAM && GridNoOnEdgeOfMap( sGridNo, &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 );
}
INT16 FindNearestUngassedLand(SOLDIERTYPE *pSoldier)
{
INT16 sGridNo,sClosestLand = NOWHERE,sPathCost,sShortestPath = 1000;
INT16 sMaxLeft,sMaxRight,sMaxUp,sMaxDown,sXOffset,sYOffset;
INT32 iSearchRange;
//NameMessage(pSoldier,"looking for nearest reachable land");
// 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 <= 25; iSearchRange += 5)
{
//NumMessage("Trying iSearchRange = ", iSearchRange);
// determine maximum horizontal limits
sMaxLeft = (INT16) min(iSearchRange,(pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min(iSearchRange,(pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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, NOWHERE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_LEAVE_WATER_GAS ), COPYREACHABLE, 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++)
{
// 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;
}
// ignore blacklisted spot
if ( sGridNo == pSoldier->pathing.sBlackList )
{
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 = (INT16) LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0);
if (!sPathCost)
{
continue; // skip on to the next potential grid
}
// 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 (sClosestLand != NOWHERE) // quit now, no need to look any farther
break;
}
//NumMessage("closestLand = ",closestLand);
return(sClosestLand);
}
INT16 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier )
{
INT16 sGridNo, sClosestSpot = NOWHERE, sPathCost;
INT32 iSpotValue, iBestSpotValue = 1000;
INT16 sMaxLeft,sMaxRight,sMaxUp,sMaxDown,sXOffset,sYOffset;
INT32 iSearchRange;
INT8 bLightLevel, bCurrLightLevel, bLightDiff;
INT32 iRoamRange;
INT16 sOrigin;
bCurrLightLevel = LightTrueLevel( pSoldier->sGridNo, pSoldier->pathing.bLevel );
iRoamRange = RoamingRange( pSoldier, &sOrigin );
// 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 <= 15; iSearchRange += 5)
{
// determine maximum horizontal limits
sMaxLeft = (INT16) min(iSearchRange,(pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min(iSearchRange,(pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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, NOWHERE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_LEAVE_WATER_GAS ), COPYREACHABLE, 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++)
{
// 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;
}
// ignore blacklisted spot
if ( sGridNo == pSoldier->pathing.sBlackList )
{
continue;
}
// require this character to stay within their roam range
if ( PythSpacesAway( sOrigin, sGridNo ) > iRoamRange )
{
continue;
}
// screen out anything brighter than our current best spot
bLightLevel = LightTrueLevel( sGridNo, pSoldier->pathing.bLevel );
bLightDiff = gbLightSighting[0][ bCurrLightLevel ] - gbLightSighting[0][ 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 = (INT16) LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0);
if (!sPathCost)
{
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 (sClosestSpot != NOWHERE) // 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"));
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 sGridNo;
INT16 sBestSpot = NOWHERE;
INT32 iTempValue, iValue, iBestValue = 0;
ITEM_POOL * pItemPool;
OBJECTTYPE * pObj;
INVTYPE * pItem;
INT32 iItemIndex, iBestItemIndex;
iTempValue = -1;
iItemIndex = iBestItemIndex = -1;
// No fair picking up weapons while boxing!
if (gTacticalStatus.bBoxingState == BOXING)
{
return AI_ACTION_NONE;
}
if (pSoldier->bActionPoints < APBPConstants[AP_PICKUP_ITEM])
{
return( AI_ACTION_NONE );
}
if ( !IS_MERC_BODY_TYPE( pSoldier ) )
{
return( AI_ACTION_NONE );
}
iSearchRange = gbDiff[DIFF_MAX_COVER_RANGE][ SoldierDifficultyLevel( pSoldier ) ];
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 = (INT16) min( iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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 - APBPConstants[AP_PICKUP_ITEM];
// 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, NOWHERE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_PICKUP_ITEM ), COPYREACHABLE, 0 );
// 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;
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if ( InGasOrSmoke( pSoldier, sGridNo ) )
{
continue;
}
if ( (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_ITEMPOOL_PRESENT)
&& (gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_REACHABLE) )
{
// ignore blacklisted spot
if ( sGridNo == pSoldier->pathing.sBlackList )
{
continue;
}
iValue = 0;
GetItemPool( sGridNo, &pItemPool, pSoldier->pathing.bLevel );
switch( bReason )
{
case SEARCH_AMMO:
// we are looking for ammo to match the gun in usItem
while( pItemPool )
{
pObj = &(gWorldItems[ pItemPool->iItemIndex ].object);
pItem = &(Item[pObj->usItem]);
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 ) )
{
iTempValue = TotalPoints( pObj );
}
else
{
iTempValue = 0;
}
if (iTempValue > iValue )
{
iValue = iTempValue;
iItemIndex = pItemPool->iItemIndex;
}
pItemPool = pItemPool->pNext;
}
break;
case SEARCH_WEAPONS:
while( pItemPool )
{
pObj = &(gWorldItems[ pItemPool->iItemIndex ].object);
pItem = &(Item[pObj->usItem]);
if (pItem->usItemClass & IC_WEAPON && (*pObj)[0]->data.objectStatus >= MINIMUM_REQUIRED_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!
iTempValue = 0;
}
else if ( Item[pSoldier->inv[HANDPOS].usItem].usItemClass & IC_WEAPON )
{
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
{
iTempValue = 0;
}
if (iTempValue > iValue )
{
iValue = iTempValue;
iItemIndex = pItemPool->iItemIndex;
}
pItemPool = pItemPool->pNext;
}
break;
default:
while( pItemPool )
{
pObj = &(gWorldItems[ pItemPool->iItemIndex ].object);
pItem = &(Item[pObj->usItem]);
if ( pItem->usItemClass & IC_WEAPON && (*pObj)[0]->data.objectStatus >= MINIMUM_REQUIRED_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!
iTempValue = 0;
}
else if ( (Item[pSoldier->inv[HANDPOS].usItem].usItemClass & IC_WEAPON) )
{
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 if (pItem->usItemClass == IC_ARMOUR && (*pObj)[0]->data.objectStatus >= MINIMUM_REQUIRED_STATUS )
{
switch( Armour[pItem->ubClassIndex].ubArmourClass )
{
case 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]) );
}
else
{
iTempValue = 0;
}
break;
case ARMOURCLASS_VEST:
if (pSoldier->inv[VESTPOS].exists() == false)
{
iTempValue = 200 + EffectiveArmour( pObj );
}
else if ( EffectiveArmour( &(pSoldier->inv[HELMETPOS]) ) > EffectiveArmour( pObj ) )
{
iTempValue = 100 * EffectiveArmour( pObj ) / EffectiveArmour( &(pSoldier->inv[VESTPOS]) );
}
else
{
iTempValue = 0;
}
break;
case ARMOURCLASS_LEGGINGS:
if (pSoldier->inv[LEGPOS].exists() == false)
{
iTempValue = 200 + EffectiveArmour( pObj );
}
else if ( EffectiveArmour( &(pSoldier->inv[HELMETPOS]) ) > EffectiveArmour( pObj ) )
{
iTempValue = 100 * EffectiveArmour( pObj ) / EffectiveArmour( &(pSoldier->inv[LEGPOS]) );
}
else
{
iTempValue = 0;
}
break;
default:
break;
}
}
else
{
iTempValue = 0;
}
if (iTempValue > iValue )
{
iValue = iTempValue;
iItemIndex = pItemPool->iItemIndex;
}
pItemPool = pItemPool->pNext;
}
break;
}
iValue = (3 * iValue) / (3 + PythSpacesAway( sGridNo, pSoldier->sGridNo ));
if (iValue > iBestValue )
{
sBestSpot = sGridNo;
iBestValue = iValue;
iBestItemIndex = iItemIndex;
}
}
}
}
if (sBestSpot != NOWHERE)
{
DebugAI( 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 < APBPConstants[AP_PICKUP_ITEM] + APBPConstants[AP_PICKUP_ITEM])
{
return( AI_ACTION_NONE );
}
if (pSoldier->inv[HANDPOS].fFlags & OBJECT_UNDROPPABLE)
{
// destroy this item!
DebugAI( String( "%d decides he must drop %S first so destroys it", pSoldier->ubID, ItemNames[ pSoldier->inv[HANDPOS].usItem ] ) );
DeleteObj( &(pSoldier->inv[HANDPOS]) );
DeductPoints( pSoldier, APBPConstants[AP_PICKUP_ITEM], 0 );
}
else
{
// we want to drop this item!
DebugAI( 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 );
}
INT16 FindClosestDoor( SOLDIERTYPE * pSoldier )
{
INT16 sClosestDoor = NOWHERE;
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 sGridNo;
INT32 iDist, iClosestDist = 10;
iSearchRange = 5;
// determine maximum horizontal limits
sMaxLeft = (INT16) min( iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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 );
}
INT16 FindNearestEdgepointOnSpecifiedEdge( INT16 sGridNo, INT8 bEdgeCode )
{
INT32 iLoop;
INT16 *psEdgepointArray;
INT32 iEdgepointArraySize;
INT16 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 );
}
INT16 FindNearestEdgePoint( INT16 sGridNo )
{
INT16 sGridX, sGridY;
INT16 sScreenX, sScreenY, sMaxScreenX, sMaxScreenY;
INT16 sDist[5], sMinDist;
INT32 iLoop;
INT8 bMinIndex;
INT16 * psEdgepointArray;
INT32 iEdgepointArraySize;
INT16 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
INT16 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection )
{
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 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 = (INT16) min( iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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, NOWHERE, pSoldier->pathing.bLevel, WALKING, COPYREACHABLE, 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++)
{
// 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 );
}
INT16 FindRouteBackOntoMap( SOLDIERTYPE * pSoldier, INT16 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 );
}
}
INT16 FindClosestBoxingRingSpot( SOLDIERTYPE * pSoldier, BOOLEAN fInRing )
{
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 sGridNo, sClosestSpot = NOWHERE;
INT32 iDistance, iClosestDistance = 9999;
UINT8 ubRoom;
// set the distance limit of the square region
iSearchRange = 7;
// determine maximum horizontal limits
sMaxLeft = (INT16) min( iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
if ( (pSoldier->bTeam == gbPlayerNum) && (fInRing == FALSE) )
{
// have player not go to the left of the ring
sMaxLeft = 0;
}
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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 ( InARoom( sGridNo, &ubRoom ))
{
if ( (fInRing && ubRoom == BOXING_RING) || (!fInRing && ubRoom != BOXING_RING ) && LegalNPCDestination(pSoldier,sGridNo,IGNORE_PATH,NOWATER,0) )
{
iDistance = abs( sXOffset ) + abs( sYOffset );
if ( iDistance < iClosestDistance && WhoIsThere2( sGridNo, 0 ) == NOBODY )
{
sClosestSpot = sGridNo;
iClosestDistance = iDistance;
}
}
}
}
}
return( sClosestSpot );
}
INT16 FindNearestOpenableNonDoor( INT16 sStartGridNo )
{
INT32 iSearchRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT16 sGridNo, sClosestSpot = NOWHERE;
INT32 iDistance, iClosestDistance = 9999;
STRUCTURE * pStructure;
// set the distance limit of the square region
iSearchRange = 7;
// determine maximum horizontal limits
sMaxLeft = (INT16) min( iSearchRange, (sStartGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((sStartGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (sStartGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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 );
}
INT16 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT16 sPos, INT8 bAction )
{
INT16 sGridNo;
INT16 sBestSpot = NOWHERE;
INT32 iSearchRange = 4;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
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;
if ( gfTurnBasedAI )
{
//if (pSoldier->aiData.bAlertStatus == STATUS_BLACK) // if already in battle
//{
// iSearchRange = pSoldier->bActionPoints - ( MinAPsToAttack( pSoldier, sPos, ADDTURNCOST));
// // to speed this up, tell PathAI to cancel any paths beyond our AP reach!
// gubNPCAPBudget = iSearchRange; //pSoldier->bActionPoints;
//}
//else
//{
// // even if not under pressure, limit to 1 turn's travelling distance
// iSearchRange = pSoldier->bActionPoints - ( MinAPsToAttack( pSoldier, sPos, ADDTURNCOST));
// gubNPCAPBudget = iSearchRange; //__min( pSoldier->bActionPoints / 2, pSoldier->CalcActionPoints( ) );
//iSearchRange = gubNPCAPBudget;
//}
}
//if (!gfTurnBasedAI)
//{
// // search only half as far in realtime
// // but always allow a certain minimum!
// if ( iSearchRange > 20 )
// {
// iSearchRange /= 2;
// gubNPCAPBudget /= 2;
// }
//}
gubNPCAPBudget=(UINT8) (iSearchRange*3);
// assume we have to stand up and turn
// use the min macro here to make sure we don't wrap the UINT8 to 255...
//gubNPCAPBudget = gubNPCAPBudget = __min( gubNPCAPBudget, gubNPCAPBudget - GetAPsToChangeStance( pSoldier, ANIM_STAND ) -1 ); //-1 for turning cost while standing
//NumMessage("Search Range = ",iSearchRange);
//NumMessage("gubNPCAPBudget = ",gubNPCAPBudget);
// stay away from the edges
// determine maximum horizontal limits
sMaxLeft = (INT16) min( iSearchRange, (pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = (INT16) 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, NOWHERE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, bAction ), COPYREACHABLE, 0 );
// 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;
switch ( bAction )
{
case AI_ACTION_FLANK_LEFT:
sDesiredDir = sDir - 2;
break;
case AI_ACTION_FLANK_RIGHT:
sDesiredDir = sDir + 2;
break;
case AI_ACTION_WITHDRAW:
sDesiredDir = sDir + 4;
break;
default:
sDesiredDir = sDir;
}
if ( sDesiredDir < 0 )
sDesiredDir += 8;
if ( sDesiredDir > 8 )
sDesiredDir -= 8;
DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("FindFlankingSpot: direction to loc = %d, dir to flank = %d", sDir , sDesiredDir ));
for (sYOffset = -sMaxUp + 1; sYOffset <= sMaxDown - 1; sYOffset++)
{
for (sXOffset = -sMaxLeft + 1; sXOffset <= sMaxRight - 1; sXOffset++)
{
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
sTempDir = GetDirectionFromGridNo ( sGridNo, pSoldier );
sTempDist = (INT16) 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;
}
// exclude locations with tear/mustard gas (at this point, smoke is cool!)
if ( InGas( pSoldier, sGridNo ) )
{
continue;
}
//Madd: skip lighted spots
if ( InLightAtNight( sGridNo, pSoldier->pathing.bLevel ) )
continue;
// allow an extra direction for flanking
if ( bAction == AI_ACTION_FLANK_LEFT )
{
if ( sTempDir != sDesiredDir && sTempDir != ( sDesiredDir + 1 ) )
continue;
}
else if ( bAction == AI_ACTION_FLANK_RIGHT )
{
if ( sTempDir != sDesiredDir && sTempDir != ( sDesiredDir - 1 ) )
continue;
}
else
{
if ( sTempDir != sDesiredDir ) //&& sTempDir != ( sDesiredDir + 1 ) && sTempDir != ( sDesiredDir - 1 ))
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 );
}
INT16 FindClosestClimbPoint (SOLDIERTYPE *pSoldier, BOOLEAN fClimbUp )
{
INT16 sBestSpot = NOWHERE;
//DebugMsg( TOPIC_JA2AI , DBG_LEVEL_3 , "FindClosestClimbPoint");
//gubNPCAPBudget = gubNPCAPBudget = __min( gubNPCAPBudget, gubNPCAPBudget - GetAPsToChangeStance( pSoldier, ANIM_STAND ) -1 ); //-1 for turning cost while standing
//NumMessage("Search Range = ",iSearchRange);
//NumMessage("gubNPCAPBudget = ",gubNPCAPBudget);
// 0verhaul: This function is optimized to take advantage of the new climb point info.
if (fClimbUp)
{
// For the climb up case, search the nearby limits for a climb up point and take the closest.
INT16 sGridNo;
static const INT32 iSearchRange = 20;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
UINT8 ubTestDir;
// 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;
}
if (gpWorldLevelData[ sGridNo].ubExtFlags[0] & MAPELEMENT_EXT_CLIMBPOINT)
{
// Search for the destination climb point on the roof
for (ubTestDir=0; ubTestDir < 8; ubTestDir += 2)
{
INT16 sTestGridNo = NewGridNo( sGridNo, DirectionInc( ubTestDir));
// And see if it or the ground location is occupied
if (gpWorldLevelData[ sTestGridNo ].ubExtFlags[1] & MAPELEMENT_EXT_CLIMBPOINT &&
(WhoIsThere2( sTestGridNo, 1 ) == NOBODY) &&
(WhoIsThere2( sGridNo, 0 ) == NOBODY) )
{
// Good climb point. Is it closer than the previous point?
if ( 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 )
{
#if 1
return FindDirectionForClimbing( pSoldier, pSoldier->sGridNo, pSoldier->pathing.bLevel) != DIRECTION_IRRELEVANT;
#else
BUILDING * pBuilding;
INT16 i;
INT32 iSearchRange = 1;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
//DebugMsg( TOPIC_JA2AI , DBG_LEVEL_3 , "CanClimbFromHere");
// determine maximum horizontal limits
sMaxLeft = (INT16) min( iSearchRange, (pSoldier->sGridNo % MAXCOL));
sMaxRight = (INT16) min( iSearchRange, MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
// determine maximum vertical limits
sMaxUp = (INT16) min( iSearchRange, (pSoldier->sGridNo / MAXROW));
sMaxDown = (INT16) min( iSearchRange, MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
INT16 sGridNo=NOWHERE;
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;
}
// OK, this place shows potential. How useful is it as cover?
//NumMessage("Promising seems gridno #",gridno);
// Kaiden: From this point down I've removed an unneccessary call to
// FindBuilding, The original code that was from this point till the
// end of the function is now commented out AFTER the function.
pBuilding = FindBuilding ( sGridNo );
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;
#endif
}
// 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;
INT16 FindBestCoverNearTheGridNo(SOLDIERTYPE *pSoldier, INT16 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( sResultGridNo != NOWHERE )
return sResultGridNo;
else
return sGridNo;
}
INT8 FindDirectionForClimbing( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bLevel )
{
UINT8 ubClimbDir;
INT16 sClimbSpot;
if (bLevel == 0)
{
if (gpWorldLevelData[ sGridNo].ubExtFlags[0] & MAPELEMENT_EXT_CLIMBPOINT)
{
for (ubClimbDir=0; ubClimbDir<8; ubClimbDir+=2)
{
sClimbSpot = NewGridNo( sGridNo, DirectionInc( ubClimbDir));
if (gpWorldLevelData[ sClimbSpot].ubExtFlags[1] & MAPELEMENT_EXT_CLIMBPOINT &&
WhoIsThere2( sClimbSpot, 1 ) == NOBODY )
{
return ubClimbDir;
}
}
}
}
else if (bLevel == 1)
{
//CHRISL: If NewInv and wearing a backpack, don't allow climbing
if(UsingNewInventorySystem() == true && pSoldier->inv[BPACKPOCKPOS].exists() == true)
return DIRECTION_IRRELEVANT;
if (gpWorldLevelData[ sGridNo].ubExtFlags[1] & MAPELEMENT_EXT_CLIMBPOINT)
{
for (ubClimbDir=0; ubClimbDir<8; ubClimbDir+=2)
{
sClimbSpot = NewGridNo( sGridNo, DirectionInc( ubClimbDir));
if (gpWorldLevelData[ sClimbSpot].ubExtFlags[0] & MAPELEMENT_EXT_CLIMBPOINT &&
WhoIsThere2( sClimbSpot, 0 ) == NOBODY )
{
return ubClimbDir;
}
}
}
}
return DIRECTION_IRRELEVANT;
}