Files
source/Tactical/PATHAI.cpp
T

2866 lines
75 KiB
C++

/*
Filename : pathai.c
Author : Ray E. Bornert II
Date : 1992-MAR-15
Skip list additions
Author : Chris Camfield
Date : 1997-NOV
*/
#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#include <stdio.h>
#include <string.h>
#include "wcheck.h"
#include "stdlib.h"
#include "debug.h"
#include "MemMan.h"
#include "Overhead Types.h"
#include "Soldier Control.h"
#include "Animation Cache.h"
#include "Animation Data.h"
#include "Animation Control.h"
#include "container.h"
#include "interface.h"
#include <math.h>
#include "input.h"
#include "english.h"
#include "worlddef.h"
#include "worldman.h"
#include "renderworld.h"
#include "pathai.h"
#include "PathAIDebug.h"
#include "Points.h"
#include "ai.h"
#include "Random.h"
#include "message.h"
#include "structure wrap.h"
#include "keys.h"
#include "gamesettings.h"
#include "Buildings.h"
#endif
#include "PathAIDebug.h"
extern UINT16 gubAnimSurfaceIndex[ TOTALBODYTYPES ][ NUMANIMATIONSTATES ];
//extern UINT8 gubDiagCost[20];
// skiplist has extra level of pointers every 4 elements, so a level 5is optimized for
// 4 to the power of 5 elements, or 2 to the power of 10, 1024
//#define PATHAI_VISIBLE_DEBUG
//#define PATHAI_SKIPLIST_DEBUG
#ifdef PATHAI_VISIBLE_DEBUG
#include "video.h"
extern INT16 gsCoverValue[WORLD_MAX];
BOOLEAN gfDisplayCoverValues = TRUE;
BOOLEAN gfDrawPathPoints = FALSE;
#endif
BOOLEAN gfPlotPathToExitGrid = FALSE;
BOOLEAN gfRecalculatingExistingPathCost = FALSE;
UINT8 gubGlobalPathFlags = 0;
UINT8 gubBuildingInfoToSet;
// ABSOLUTE maximums
//#ifdef JA2EDITOR
#define ABSMAX_SKIPLIST_LEVEL 5
#define ABSMAX_TRAIL_TREE (16384)
#define ABSMAX_PATHQ (512)
/*
#else
#define ABSMAX_SKIPLIST_LEVEL 5
#define ABSMAX_TRAIL_TREE (4096)
#define ABSMAX_PATHQ (512)
#endif
*/
// STANDARD maximums... configurable!
#define MAX_SKIPLIST_LEVEL 5
#define MAX_TRAIL_TREE (16384)//(4096)
#define MAX_PATHQ (512)
INT32 iMaxSkipListLevel = MAX_SKIPLIST_LEVEL;
INT32 iMaxTrailTree = MAX_TRAIL_TREE;
INT32 iMaxPathQ = MAX_PATHQ;
extern BOOLEAN gfGeneratingMapEdgepoints;
#define VEHICLE
#define TRAILCELLTYPE UINT16
// OLD PATHAI STUFF
/////////////////////////////////////////////////
struct path_s
{
INT32 iLocation; //4
struct path_s * pNext[ABSMAX_SKIPLIST_LEVEL]; //4 * MAX_SKIPLIST_LEVEL (5) = 20
INT16 sPathNdx; //2
TRAILCELLTYPE usCostSoFar; //2
TRAILCELLTYPE usCostToGo; //2
TRAILCELLTYPE usTotalCost; //2
INT8 bLevel; //1
UINT8 ubTotalAPCost; //1
UINT8 ubLegDistance; //1
};
typedef struct path_s path_t;
struct trail_s
{
INT16 nextLink;
INT8 stepDir;
INT8 fFlags;
INT16 sGridNo;
};
typedef struct trail_s trail_t;
enum
{
STEP_BACKWARDS = 0x01,
} TrailFlags;
#define EASYWATERCOST TRAVELCOST_FLAT / 2
#define ISWATER(t) (((t)==TRAVELCOST_KNEEDEEP) || ((t)==TRAVELCOST_DEEPWATER))
#define NOPASS (TRAVELCOST_BLOCKED)
//#define VEINCOST TRAVELCOST_FLAT //actual cost for bridges and doors and such
//#define ISVEIN(v) ((v==TRAVELCOST_VEINMID) || (v==TRAVELCOST_VEINEND))
static path_t * pathQ;
static UINT16 gusPathShown,gusAPtsToMove;
static INT32 queRequests;
static INT32 iSkipListSize;
static INT32 iClosedListSize;
static INT8 bSkipListLevel;
static INT32 iSkipListLevelLimit[8] = {0, 4, 16, 64, 256, 1024, 4192, 16384 };
#define ESTIMATE0 ((dx>dy) ? (dx) : (dy))
#define ESTIMATE1 ((dx<dy) ? ((dx*14)/10+dy) : ((dy*14)/10+dx) )
#define ESTIMATE2 FLATCOST*( (dx<dy) ? ((dx*14)/10+dy) : ((dy*14)/10+dx) )
#define ESTIMATEn ((int)(FLATCOST*sqrt(dx*dx+dy*dy)))
#define ESTIMATEC ( ( (dx<dy) ? (TRAVELCOST_BUMPY * (dx * 14 + dy * 10) / 10) : ( TRAVELCOST_BUMPY * (dy * 14 + dx * 10) / 10 ) ) )
//#define ESTIMATEC (((dx<dy) ? ( (TRAVELCOST_FLAT * dx * 14) / 10 + dy) : (TRAVELCOST_FLAT * dy * 14 ) / 10 + dx) ) )
#define ESTIMATE ESTIMATEC
#define MAXCOST (16384)//(9990)
//#define MAXCOST (255)
//#define TOTALCOST( pCurrPtr ) (pCurrPtr->usCostSoFar + pCurrPtr->usCostToGo)
#define TOTALCOST( ptr ) (ptr->usTotalCost)
#define XLOC(a) (a%MAPWIDTH)
#define YLOC(a) (a/MAPWIDTH)
//#define LEGDISTANCE(a,b) ( abs( XLOC(b)-XLOC(a) ) + abs( YLOC(b)-YLOC(a) ) )
#define LEGDISTANCE( x1, y1, x2, y2 ) ( abs( x2 - x1 ) + abs( y2 - y1 ) )
//#define FARTHER(ndx,NDX) ( LEGDISTANCE( ndx->sLocation,sDestination) > LEGDISTANCE(NDX->sLocation,sDestination) )
#define FARTHER(ndx,NDX) ( ndx->ubLegDistance > NDX->ubLegDistance )
#define SETLOC( str, loc ) \
{\
(str).iLocation = loc;\
}
static TRAILCELLTYPE * trailCost;
static UINT8 * trailCostUsed;
static UINT8 gubGlobalPathCount = 0;
static trail_t * trailTree;
static short trailTreeNdx=0;
#define QHEADNDX (0)
#define QPOOLNDX (iMaxPathQ-1)
static path_t * pQueueHead;
static path_t * pClosedHead;
#define pathQNotEmpty (pQueueHead->pNext[0] != NULL)
#define pathFound (pQueueHead->pNext[0]->iLocation == iDestination)
#define pathNotYetFound (!pathFound)
// Note, the closed list is maintained as a doubly-linked list;
// it's a regular queue, essentially, as we always add to the end
// and remove from the front
// pNext[0] is used for the next pointers
// and pNext[1] is used for prev pointers
/*
#define ClosedListAdd( pNew ) \
{\
pNew->pNext[0] = pClosedHead;\
pNew->pNext[1] = pClosedHead->pNext[1];\
pClosedHead->pNext[1]->pNext[0] = pNew;\
pClosedHead->pNext[1] = pNew;\
pNew->iLocation = -1;\
iClosedListSize++;\
}
#define ClosedListGet( pNew )\
{\
if (queRequests<QPOOLNDX)\
{\
pNew = pathQ + (queRequests);\
queRequests++;\
memset( pNew->pNext, 0, sizeof( path_t *) * ABSMAX_SKIPLIST_LEVEL );\
pNew->bLevel = RandomSkipListLevel();\
}\
else if (iClosedListSize > 0)\
{\
pNew = pClosedHead->pNext[0];\
pNew->pNext[1]->pNext[0] = pNew->pNext[0];\
pNew->pNext[0]->pNext[1] = pNew->pNext[1];\
iClosedListSize--;\
queRequests++;\
memset( pNew->pNext, 0, sizeof( path_t *) * ABSMAX_SKIPLIST_LEVEL );\
pNew->bLevel = RandomSkipListLevel();\
}\
else\
{\
pNew = NULL;\
}\
}
*/
// experiment 1, seemed to fail
#define ClosedListAdd( pNew ) \
{\
pNew->pNext[0] = pClosedHead->pNext[0];\
pClosedHead->pNext[0] = pNew;\
pNew->iLocation = -1;\
iClosedListSize++;\
}
#define ClosedListGet( pNew )\
{\
if (queRequests<QPOOLNDX)\
{\
pNew = pathQ + (queRequests);\
queRequests++;\
pNew->bLevel = RandomSkipListLevel();\
}\
else if (iClosedListSize > 0)\
{\
pNew = pClosedHead->pNext[0];\
pClosedHead->pNext[0] = pNew->pNext[0];\
iClosedListSize--;\
queRequests++;\
memset( pNew->pNext, 0, sizeof( path_t *) * ABSMAX_SKIPLIST_LEVEL );\
pNew->bLevel = RandomSkipListLevel();\
}\
else\
{\
pNew = NULL;\
}\
}
/*
#define ClosedListAdd( pNew ) \
{\
pNew->pNext[0] = pClosedHead;\
pNew->pNext[1] = pClosedHead->pNext[1];\
pClosedHead->pNext[1]->pNext[0] = pNew;\
pClosedHead->pNext[1] = pNew;\
pNew->iLocation = -1;\
iClosedListSize++;\
}
#define ClosedListGet( pNew )\
{\
if (queRequests<QPOOLNDX)\
{\
pNew = pathQ + (queRequests);\
queRequests++;\
memset( pNew->pNext, 0, sizeof( path_t *) * ABSMAX_SKIPLIST_LEVEL );\
pNew->bLevel = RandomSkipListLevel();\
}\
else if (iClosedListSize > 0)\
{\
pNew = pClosedHead->pNext[0];\
pNew->pNext[1]->pNext[0] = pNew->pNext[0];\
pNew->pNext[0]->pNext[1] = pNew->pNext[1];\
iClosedListSize--;\
queRequests++;\
memset( pNew->pNext, 0, sizeof( path_t *) * ABSMAX_SKIPLIST_LEVEL );\
pNew->bLevel = RandomSkipListLevel();\
}\
else\
{\
pNew = NULL;\
}\
}
*/
#define SkipListRemoveHead()\
{\
pDel = pQueueHead->pNext[0];\
for (iLoop = 0; iLoop < __min( bSkipListLevel, pDel->bLevel ); iLoop++)\
{\
pQueueHead->pNext[iLoop] = pDel->pNext[iLoop];\
}\
iSkipListSize--;\
ClosedListAdd( pDel );\
}
#define SkipListInsert( pNew )\
{\
pCurr = pQueueHead;\
uiCost = TOTALCOST( pNew );\
memset( pUpdate, 0, MAX_SKIPLIST_LEVEL * sizeof( path_t *) );\
for (iCurrLevel = bSkipListLevel - 1; iCurrLevel >= 0; iCurrLevel--)\
{\
pNext = pCurr->pNext[iCurrLevel];\
while (pNext)\
{\
if ( uiCost > TOTALCOST( pNext ) || (uiCost == TOTALCOST( pNext ) && FARTHER( pNew, pNext ) ) )\
{\
pCurr = pNext;\
pNext = pCurr->pNext[iCurrLevel];\
}\
else\
{\
break;\
}\
}\
pUpdate[iCurrLevel] = pCurr;\
}\
pCurr = pCurr->pNext[0];\
for (iCurrLevel = 0; iCurrLevel < pNew->bLevel; iCurrLevel++)\
{\
if (!(pUpdate[iCurrLevel]))\
{\
break;\
}\
pNew->pNext[iCurrLevel] = pUpdate[iCurrLevel]->pNext[iCurrLevel];\
pUpdate[iCurrLevel]->pNext[iCurrLevel] = pNew;\
}\
iSkipListSize++;\
if (iSkipListSize > iSkipListLevelLimit[bSkipListLevel])\
{\
pCurr = pQueueHead;\
pNext = pQueueHead->pNext[bSkipListLevel - 1];\
while( pNext )\
{\
if (pNext->bLevel > bSkipListLevel)\
{\
pCurr->pNext[bSkipListLevel] = pNext;\
pCurr = pNext;\
}\
pNext = pNext->pNext[bSkipListLevel - 1];\
}\
pCurr->pNext[bSkipListLevel] = pNext;\
bSkipListLevel++;\
}\
}
#define REMQUEHEADNODE() SkipListRemoveHead();
#define DELQUENODE(ndx) SkipListRemoveHead()
#define REMAININGCOST(ptr) \
( \
(dy = abs(iDestY-iLocY)), \
(dx = abs(iDestX-iLocX)), \
ESTIMATE \
)
/*
#define REMAININGCOST(ptr) \
( \
(locY = (ptr)->iLocation/MAPWIDTH), \
(locX = (ptr)->iLocation%MAPWIDTH), \
(dy = abs(iDestY-locY)), \
(dx = abs(iDestX-locX)), \
ESTIMATE \
)
*/
#define NEWQUENODE ClosedListGet( pNewPtr )
#define QUEINSERT(ndx) SkipListInsert( ndx )
#define GREENSTEPSTART 0
#define REDSTEPSTART 16
#define PURPLESTEPSTART 32
#define BLUESTEPSTART 48
#define ORANGESTEPSTART 64
UINT8 gubNPCAPBudget = 0;
UINT16 gusNPCMovementMode;
UINT8 gubNPCDistLimit = 0;
BOOLEAN gfNPCCircularDistLimit = FALSE;
UINT8 gubNPCPathCount;
BOOLEAN gfPlotDirectPath = FALSE;
BOOLEAN gfEstimatePath = FALSE;
BOOLEAN gfPathAroundObstacles = TRUE;
// Lesh: fix CTD in Tixa
// The game was trying to store very long path (270 cells) in 256-cell array and corrupted
// some data, not belonged to array. It was ordinary array boundary break.
static UINT32 guiPlottedPath[MAX_PATH_DATA_LENGTH];
UINT32 guiPathingData[MAX_PATH_DATA_LENGTH];
static INT32 giPathDataSize;
static INT32 giPlotCnt;
static UINT32 guiEndPlotGridNo;
static INT32 dirDelta[8]=
{
-MAPWIDTH, //N
1-MAPWIDTH, //NE
1, //E
1+MAPWIDTH, //SE
MAPWIDTH, //S
MAPWIDTH-1, //SW
-1, //W
-MAPWIDTH-1 //NW
};
#define LOOPING_CLOCKWISE 0
#define LOOPING_COUNTERCLOCKWISE 1
#define LOOPING_REVERSE 2
#ifdef COUNT_PATHS
UINT32 guiSuccessfulPathChecks = 0;
UINT32 guiTotalPathChecks = 0;
UINT32 guiFailedPathChecks = 0;
UINT32 guiUnsuccessfulPathChecks = 0;
#endif
INT8 RandomSkipListLevel( void )
{
INT8 bLevel = 1;
while( Random( 4 ) == 0 && bLevel < iMaxSkipListLevel - 1)
{
bLevel++;
}
return( bLevel );
}
BOOLEAN InitPathAI( void )
{
pathQ = (path_t *) MemAlloc( ABSMAX_PATHQ * sizeof( path_t ) );
trailCost = (UINT16 *) MemAlloc( MAPLENGTH * sizeof( TRAILCELLTYPE ) );
trailCostUsed = (UINT8 *) MemAlloc( MAPLENGTH );
trailTree = (trail_t *) MemAlloc( ABSMAX_TRAIL_TREE * sizeof( trail_t ) );
if (!pathQ || !trailCost || !trailCostUsed || !trailTree)
{
return( FALSE );
}
pQueueHead = &(pathQ[QHEADNDX]);
pClosedHead = &(pathQ[QPOOLNDX]);
memset( trailCostUsed, 0, MAPLENGTH );
return( TRUE );
}
void ShutDownPathAI( void )
{
MemFree( pathQ );
MemFree( trailCostUsed );
MemFree( trailCost );
MemFree( trailTree );
}
void ReconfigurePathAI( INT32 iNewMaxSkipListLevel, INT32 iNewMaxTrailTree, INT32 iNewMaxPathQ )
{
// make sure the specified parameters are reasonable
iNewMaxSkipListLevel = __max( iNewMaxSkipListLevel, ABSMAX_SKIPLIST_LEVEL );
iNewMaxTrailTree = __max( iNewMaxTrailTree, ABSMAX_TRAIL_TREE );
iNewMaxPathQ = __max( iNewMaxPathQ, ABSMAX_PATHQ );
// assign them
iMaxSkipListLevel = iNewMaxSkipListLevel;
iMaxTrailTree = iNewMaxTrailTree;
iMaxPathQ = iNewMaxPathQ;
// relocate the head of the closed list to the end of the array portion being used
pClosedHead = &(pathQ[QPOOLNDX]);
memset( pClosedHead, 0, sizeof( path_t ) );
}
void RestorePathAIToDefaults( void )
{
iMaxSkipListLevel = MAX_SKIPLIST_LEVEL;
iMaxTrailTree = MAX_TRAIL_TREE;
iMaxPathQ = MAX_PATHQ;
// relocate the head of the closed list to the end of the array portion being used
pClosedHead = &(pathQ[QPOOLNDX]);
memset( pClosedHead, 0, sizeof( path_t ) );
}
///////////////////////////////////////////////////////////////////////
// FINDBESTPATH /
////////////////////////////////////////////////////////////////////////
INT32 FindBestPath(SOLDIERTYPE *s , INT16 sDestination, INT8 ubLevel, INT16 usMovementMode, INT8 bCopy, UINT8 fFlags )
{
INT32 iDestination = sDestination, iOrigination;
INT32 iCnt=-1, iStructIndex;
INT32 iLoopStart = 0, iLoopEnd = 0;
INT8 bLoopState = LOOPING_CLOCKWISE;
//BOOLEAN fLoopForwards = FALSE;
BOOLEAN fCheckedBehind = FALSE;
UINT8 ubMerc;
INT32 iDestX,iDestY, iLocX, iLocY, dx, dy;
INT32 newLoc,curLoc;
//INT32 curY;
INT32 curCost,newTotCost,nextCost;
INT16 sCurPathNdx;
INT32 prevCost;
INT32 iWaterToWater;
UINT8 ubCurAPCost,ubAPCost;
UINT8 ubNewAPCost=0;
#ifdef VEHICLE
//BOOLEAN fTurnSlow = FALSE;
//BOOLEAN fReverse = FALSE; // stuff for vehicles turning
BOOLEAN fMultiTile, fVehicle;
//INT32 iLastDir, iPrevToLastDir;
//INT8 bVehicleCheckDir;
//UINT16 adjLoc;
STRUCTURE_FILE_REF * pStructureFileRef=NULL;
UINT16 usAnimSurface;
//INT32 iCnt2, iCnt3;
#endif
INT32 iLastDir = 0;
path_t * pNewPtr;
path_t * pCurrPtr;
path_t * pUpdate[ABSMAX_SKIPLIST_LEVEL];
path_t * pCurr;
path_t * pNext;
path_t * pDel;
UINT32 uiCost;
INT32 iCurrLevel, iLoop;
BOOLEAN fHiddenStructVisible; // Used for hidden struct visiblity
UINT16 usOKToAddStructID=0;
BOOLEAN fCopyReachable;
BOOLEAN fCopyPathCosts;
BOOLEAN fVisitSpotsOnlyOnce;
INT32 iOriginationX, iOriginationY, iX, iY;
BOOLEAN fTurnBased;
BOOLEAN fPathingForPlayer;
INT32 iDoorGridNo=-1;
BOOLEAN fDoorIsObstacleIfClosed= 0; // if false, door is obstacle if it is open
DOOR_STATUS * pDoorStatus;
DOOR * pDoor;
STRUCTURE * pDoorStructure;
BOOLEAN fDoorIsOpen = FALSE;
BOOLEAN fNonFenceJumper;
BOOLEAN fNonSwimmer;
BOOLEAN fPathAroundPeople;
BOOLEAN fConsiderPersonAtDestAsObstacle;
BOOLEAN fGoingThroughDoor = FALSE; // for one tile
BOOLEAN fContinuousTurnNeeded;
BOOLEAN fCloseGoodEnough;
UINT16 usMovementModeToUseForAPs;
INT16 sClosePathLimit;
#ifdef PATHAI_SKIPLIST_DEBUG
CHAR8 zTempString[1000], zTS[50];
#endif
#ifdef PATHAI_VISIBLE_DEBUG
UINT16 usCounter = 0;
#endif
fVehicle = FALSE;
iOriginationX = iOriginationY = 0;
iOrigination = (INT32) s->sGridNo;
if (iOrigination < 0 || iOrigination > WORLD_MAX)
{
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_RED, MSG_TESTVERSION, L"ERROR! Trying to calculate path from off-world gridno %d to %d", iOrigination, sDestination );
#endif
return( 0 );
}
else if (!GridNoOnVisibleWorldTile( (INT16) iOrigination ) )
{
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_RED, MSG_TESTVERSION, L"ERROR! Trying to calculate path from non-visible gridno %d to %d", iOrigination, sDestination );
#endif
return( 0 );
}
else if (s->bLevel != ubLevel)
{
// pathing to a different level... bzzzt!
return( 0 );
}
if ( gubGlobalPathFlags )
{
fFlags |= gubGlobalPathFlags;
}
fTurnBased = ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) );
fPathingForPlayer = ( (s->bTeam == gbPlayerNum) && (!gTacticalStatus.fAutoBandageMode) && !(s->uiStatusFlags & SOLDIER_PCUNDERAICONTROL) );
fNonFenceJumper = !( IS_MERC_BODY_TYPE( s ) );
fNonSwimmer = !( IS_MERC_BODY_TYPE( s ) );
if ( fNonSwimmer )
{
if ( Water( sDestination ) )
{
return( 0 );
}
}
fPathAroundPeople = ( (fFlags & PATH_THROUGH_PEOPLE) == 0 );
fCloseGoodEnough = ( (fFlags & PATH_CLOSE_GOOD_ENOUGH) != 0);
if ( fCloseGoodEnough )
{
sClosePathLimit = __min( PythSpacesAway( (INT16)s->sGridNo, sDestination ) - 1, PATH_CLOSE_RADIUS );
if ( sClosePathLimit <= 0 )
{
return( 0 );
}
}
fConsiderPersonAtDestAsObstacle = (BOOLEAN)( fPathingForPlayer && fPathAroundPeople && !(fFlags & PATH_IGNORE_PERSON_AT_DEST) );
if (bCopy >= COPYREACHABLE)
{
fCopyReachable = TRUE;
fCopyPathCosts = (bCopy == COPYREACHABLE_AND_APS);
fVisitSpotsOnlyOnce = (bCopy == COPYREACHABLE);
// make sure we aren't trying to copy path costs for an area greater than the AI array...
if (fCopyPathCosts && gubNPCDistLimit > AI_PATHCOST_RADIUS)
{
// oy!!!! dis no supposed to happen!
gubNPCDistLimit = AI_PATHCOST_RADIUS;
}
}
else
{
fCopyReachable = FALSE;
fCopyPathCosts = FALSE;
fVisitSpotsOnlyOnce = FALSE;
}
gubNPCPathCount++;
if (gubGlobalPathCount == 255)
{
// reset arrays!
memset( trailCostUsed, 0, MAPLENGTH );
gubGlobalPathCount = 1;
}
else
{
gubGlobalPathCount++;
}
// only allow nowhere destination if distance limit set
if (sDestination == NOWHERE)
{
/*
if (gubNPCDistLimit == 0)
{
return( FALSE );
}
*/
}
else
{
// the very first thing to do is make sure the destination tile is reachable
if (!NewOKDestination( s, sDestination, fConsiderPersonAtDestAsObstacle, ubLevel ))
{
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return( FALSE );
}
if (sDestination == s->sGridNo)
{
return( FALSE );
}
}
if (gubNPCAPBudget)
{
ubAPCost = MinAPsToStartMovement( s, usMovementMode );
if (ubAPCost > gubNPCAPBudget)
{
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return( 0 );
}
else
{
gubNPCAPBudget -= ubAPCost;
}
}
#ifdef COUNT_PATHS
guiTotalPathChecks++;
#endif
#ifdef VEHICLE
fMultiTile = ((s->uiStatusFlags & SOLDIER_MULTITILE) != 0);
if (fMultiTile)
{
// Get animation surface...
// Chris_C... change this to use parameter.....
usAnimSurface = DetermineSoldierAnimationSurface( s, usMovementMode );
// Get structure ref...
pStructureFileRef = GetAnimationStructureRef( s->ubID, usAnimSurface, usMovementMode );
if ( pStructureFileRef )
{
fVehicle = ( (s->uiStatusFlags & SOLDIER_VEHICLE) != 0 );
fContinuousTurnNeeded = ( ( s->uiStatusFlags & (SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE) ) != 0 );
/*
if (fVehicle && s->bReverse)
{
fReverse = TRUE;
}
*/
/*
if (fVehicle || s->ubBodyType == COW || s->ubBodyType == BLOODCAT) // or a vehicle
{
fTurnSlow = TRUE;
}
*/
if ( gfEstimatePath )
{
usOKToAddStructID = IGNORE_PEOPLE_STRUCTURE_ID;
}
else if ( s->pLevelNode != NULL && s->pLevelNode->pStructureData != NULL )
{
usOKToAddStructID = s->pLevelNode->pStructureData->usStructureID;
}
else
{
usOKToAddStructID = INVALID_STRUCTURE_ID;
}
}
else
{
// turn off multitile pathing
fMultiTile = FALSE;
fContinuousTurnNeeded = FALSE;
}
}
else
{
fContinuousTurnNeeded = FALSE;
}
#endif
if (!fContinuousTurnNeeded)
{
iLoopStart = 0;
iLoopEnd = 0;
bLoopState = LOOPING_CLOCKWISE;
}
ubCurAPCost = 0;
queRequests = 2;
//initialize the path data structures
memset( pathQ, 0, iMaxPathQ * sizeof( path_t ) );
memset( trailTree, 0, iMaxTrailTree * sizeof( trail_t ) );
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
}
#endif
bSkipListLevel = 1;
iSkipListSize = 0;
iClosedListSize = 0;
trailTreeNdx=0;
//set up common info
if (fCopyPathCosts)
{
iOriginationY = (iOrigination / MAPWIDTH);
iOriginationX = (iOrigination % MAPWIDTH);
}
iDestY = (iDestination / MAPWIDTH);
iDestX = (iDestination % MAPWIDTH);
// if origin and dest is water, then user wants to stay in water!
// so, check and set waterToWater flag accordingly
if (iDestination == NOWHERE)
{
iWaterToWater = 0;
}
else
{
if (ISWATER(gubWorldMovementCosts[iOrigination][0][ubLevel]) && ISWATER(gubWorldMovementCosts[iDestination][0][ubLevel]))
iWaterToWater = 1;
else
iWaterToWater = 0;
}
//setup Q and first path record
SETLOC( *pQueueHead, iOrigination );
pQueueHead->usCostSoFar = MAXCOST;
pQueueHead->bLevel = iMaxSkipListLevel - 1;
pClosedHead->pNext[0] = pClosedHead;
pClosedHead->pNext[1] = pClosedHead;
//setup first path record
iLocY = iOrigination / MAPWIDTH;
iLocX = iOrigination % MAPWIDTH;
SETLOC( pathQ[1], iOrigination );
pathQ[1].sPathNdx = 0;
pathQ[1].usCostSoFar = 0;
if ( fCopyReachable )
{
pathQ[1].usCostToGo = 100;
}
else
{
pathQ[1].usCostToGo = REMAININGCOST( &(pathQ[1]) );
}
pathQ[1].usTotalCost = pathQ[1].usCostSoFar + pathQ[1].usCostToGo;
pathQ[1].ubLegDistance = LEGDISTANCE( iLocX, iLocY, iDestX, iDestY );
pathQ[1].bLevel = 1;
pQueueHead->pNext[0] = &( pathQ[1] );
iSkipListSize++;
trailTreeNdx = 0;
trailCost[iOrigination] = 0;
pCurrPtr = pQueueHead->pNext[0];
pCurrPtr->sPathNdx = trailTreeNdx;
trailTreeNdx++;
do
{
//remove the first and best path so far from the que
pCurrPtr = pQueueHead->pNext[0];
curLoc = pCurrPtr->iLocation;
curCost = pCurrPtr->usCostSoFar;
sCurPathNdx = pCurrPtr->sPathNdx;
// remember the cost used to get here...
prevCost = gubWorldMovementCosts[ trailTree[ sCurPathNdx ].sGridNo ][ trailTree[ sCurPathNdx ].stepDir ][ ubLevel ];
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
if (gsCoverValue[ curLoc ] > 0)
{
gsCoverValue[ curLoc ] *= -1;
}
}
#endif
#ifdef VEHICLE
/*
if (fTurnSlow)
{
if (pCurrPtr->pNext[0] == 0)
{
if (fReverse)
{
iLastDir = gOppositeDirection[s->bDirection];
}
else
{
iLastDir = s->bDirection;
}
// start prev-to-last dir at same as current (could cause a problem)
iPrevToLastDir = iLastDir;
}
else
{
iPrevToLastDir = trailTree[trailTree[pCurrPtr->sPathNdx].nextLink].dirDelta;
iLastDir = trailTree[pCurrPtr->sPathNdx].dirDelta;
}
}
*/
#endif
if (gubNPCAPBudget)
{
ubCurAPCost = pCurrPtr->ubTotalAPCost;
}
if (fCopyReachable && prevCost != TRAVELCOST_FENCE)
{
gpWorldLevelData[curLoc].uiFlags |= MAPELEMENT_REACHABLE;
if (gubBuildingInfoToSet > 0)
{
gubBuildingInfo[ curLoc ] = gubBuildingInfoToSet;
}
}
DELQUENODE( pCurrPtr );
if ( trailCostUsed[curLoc] == gubGlobalPathCount && trailCost[curLoc] < curCost)
goto NEXTDIR;
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "PATHAI %d", curLoc ) );
if (fContinuousTurnNeeded)
{
if (trailTreeNdx < 2)
{
iLastDir = s->bDirection;
}
else if ( trailTree[pCurrPtr->sPathNdx].fFlags & STEP_BACKWARDS )
{
iLastDir = gOppositeDirection[ trailTree[pCurrPtr->sPathNdx].stepDir ];
}
else
{
iLastDir = trailTree[pCurrPtr->sPathNdx].stepDir;
}
iLoopStart = iLastDir;
iLoopEnd = iLastDir;
bLoopState = LOOPING_CLOCKWISE;
fCheckedBehind = FALSE;
}
//contemplate a new path in each direction
//for ( iCnt = iLoopStart; iCnt != iLoopEnd; iCnt = (iCnt + iLoopIncrement) % MAXDIR )
for ( iCnt = iLoopStart; ; )
{
#ifdef VEHICLE
/*
if (fTurnSlow)
{
if (iLastDir == iPrevToLastDir)
{
if ( iCnt != iLastDir && iCnt != gOneCDirection[ iLastDir ] && iCnt != gOneCCDirection[ iLastDir ])
{
goto NEXTDIR;
}
}
else
{
if ( iCnt != iLastDir )
{
goto NEXTDIR;
}
}
}
*/
if ( bLoopState == LOOPING_REVERSE )
{
iStructIndex = gOppositeDirection[ gOneCDirection[ iCnt ] ];
}
else
{
iStructIndex = gOneCDirection[ iCnt ];
}
if (fMultiTile)
{
if ( fContinuousTurnNeeded )
{
if ( iCnt != iLastDir )
{
if ( !OkayToAddStructureToWorld( (INT16) curLoc, ubLevel, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), usOKToAddStructID ) )
{
// we have to abort this loop and possibly reset the loop conditions to
// search in the other direction (if we haven't already done the other dir)
if (bLoopState == LOOPING_CLOCKWISE)
{
iLoopStart = iLastDir;
iLoopEnd = iCnt;
bLoopState = LOOPING_COUNTERCLOCKWISE; // backwards
// when we go to the bottom of the loop, iLoopIncrement will be added to iCnt
// which is good since it avoids duplication of effort
iCnt = iLoopStart;
goto NEXTDIR;
}
else if ( bLoopState == LOOPING_COUNTERCLOCKWISE && !fCheckedBehind )
{
// check rear dir
bLoopState = LOOPING_REVERSE;
// NB we're stuck with adding 1 to the loop counter down below so configure to accomodate...
//iLoopStart = (iLastDir + (MAXDIR / 2) - 1) % MAXDIR;
iLoopStart = gOppositeDirection[ gOneCCDirection[ iLastDir ] ];
iLoopEnd = (iLoopStart + 2) % MAXDIR;
iCnt = iLoopStart;
fCheckedBehind = TRUE;
goto NEXTDIR;
}
else
{
// done
goto ENDOFLOOP;
}
}
}
}
else if ( pStructureFileRef )
{
// check to make sure it's okay for us to turn to the new direction in our current tile
if (!OkayToAddStructureToWorld( (INT16) curLoc, ubLevel, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), usOKToAddStructID ) )
{
goto NEXTDIR;
}
}
}
#endif
newLoc = curLoc + dirDelta[iCnt];
if ( fVisitSpotsOnlyOnce && trailCostUsed[newLoc] == gubGlobalPathCount )
{
// on a "reachable" test, never revisit locations!
goto NEXTDIR;
}
//if (gpWorldLevelData[newLoc].sHeight != ubLevel)
//ATE: Movement onto cliffs? Check vs the soldier's gridno height
// CJC: PREVIOUS LOCATION's height
if ( gpWorldLevelData[ newLoc ].sHeight != gpWorldLevelData[ curLoc ].sHeight )
{
goto NEXTDIR;
}
if (gubNPCDistLimit)
{
if ( gfNPCCircularDistLimit )
{
if (PythSpacesAway( (INT16) iOrigination, (INT16) newLoc) > gubNPCDistLimit)
{
goto NEXTDIR;
}
}
else
{
if (SpacesAway( (INT16) iOrigination, (INT16) newLoc) > gubNPCDistLimit)
{
goto NEXTDIR;
}
}
}
// AI check for mines
if ( gpWorldLevelData[newLoc].uiFlags & MAPELEMENT_ENEMY_MINE_PRESENT && s->bSide != 0)
{
goto NEXTDIR;
}
/*
if ( gpWorldLevelData[newLoc].uiFlags & (MAPELEMENT_ENEMY_MINE_PRESENT | MAPELEMENT_PLAYER_MINE_PRESENT) )
{
if (s->bSide == 0)
{
// For our team, skip a location with a known mines unless it is the end of our
// path; for others on our side, skip such locations completely;
if (s->bTeam != gbPlayerNum || newLoc != iDestination)
{
if (gpWorldLevelData[newLoc].uiFlags & MAPELEMENT_PLAYER_MINE_PRESENT)
{
goto NEXTDIR;
}
}
}
else
{
// For the enemy, always skip known mines
if (gpWorldLevelData[newLoc].uiFlags & MAPELEMENT_ENEMY_MINE_PRESENT)
{
goto NEXTDIR;
}
}
}
*/
//how much is admission to the next tile
if ( gfPathAroundObstacles )
{
nextCost = gubWorldMovementCosts[ newLoc ][ iCnt ][ ubLevel ];
//ATE: Check for differences from reality
// Is next cost an obstcale
if ( nextCost == TRAVELCOST_HIDDENOBSTACLE )
{
if ( fPathingForPlayer )
{
// Is this obstcale a hidden tile that has not been revealed yet?
if( DoesGridnoContainHiddenStruct( (UINT16)newLoc, &fHiddenStructVisible ) )
{
// Are we not visible, if so use terrain costs!
if ( !fHiddenStructVisible )
{
// Set cost of terrain!
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
}
}
}
else if ( nextCost == TRAVELCOST_NOT_STANDING )
{
// for path plotting purposes, use the terrain value
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
else if ( nextCost == TRAVELCOST_EXITGRID )
{
if (gfPlotPathToExitGrid)
{
// replace with terrain cost so that we can plot path, otherwise is obstacle
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
}
else if ( nextCost == TRAVELCOST_FENCE && fNonFenceJumper )
{
goto NEXTDIR;
}
else if ( IS_TRAVELCOST_DOOR( nextCost ) )
{
// don't let anyone path diagonally through doors!
if (iCnt & 1)
{
goto NEXTDIR;
}
switch( nextCost )
{
case TRAVELCOST_DOOR_CLOSED_HERE:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = newLoc;
break;
case TRAVELCOST_DOOR_CLOSED_N:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = newLoc + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_CLOSED_W:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = newLoc + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_HERE:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc;
break;
case TRAVELCOST_DOOR_OPEN_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NE:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ NORTHEAST ];
break;
case TRAVELCOST_DOOR_OPEN_E:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ EAST ];
break;
case TRAVELCOST_DOOR_OPEN_SE:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ SOUTHEAST ];
break;
case TRAVELCOST_DOOR_OPEN_S:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ SOUTH ];
break;
case TRAVELCOST_DOOR_OPEN_SW:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ SOUTHWEST ];
break;
case TRAVELCOST_DOOR_OPEN_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_NW:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ NORTHWEST ];
break;
case TRAVELCOST_DOOR_OPEN_N_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ NORTH ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NW_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ NORTHWEST ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NE_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ NORTHEAST ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_W_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ WEST ] + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_SW_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ SOUTHWEST ] + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_NW_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = newLoc + dirDelta[ NORTHWEST ] + dirDelta[ WEST ];
break;
default:
break;
}
if ( fPathingForPlayer && gpWorldLevelData[ iDoorGridNo ].ubExtFlags[0] & MAPELEMENT_EXT_DOOR_STATUS_PRESENT )
{
// check door status
pDoorStatus = GetDoorStatus( (INT16) iDoorGridNo );
if (pDoorStatus)
{
fDoorIsOpen = (pDoorStatus->ubFlags & DOOR_PERCEIVED_OPEN) != 0;
}
else
{
// door destroyed?
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
}
else
{
// check door structure
pDoorStructure = FindStructure( (INT16) iDoorGridNo, STRUCTURE_ANYDOOR );
if (pDoorStructure)
{
fDoorIsOpen = (pDoorStructure->fFlags & STRUCTURE_OPEN) != 0;
}
else
{
// door destroyed?
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
}
// now determine movement cost... if it hasn't been changed already
if ( IS_TRAVELCOST_DOOR( nextCost ) )
{
if (fDoorIsOpen)
{
if (fDoorIsObstacleIfClosed)
{
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
else
{
nextCost = TRAVELCOST_OBSTACLE;
}
}
else
{
if (fDoorIsObstacleIfClosed)
{
// door is closed and this should be an obstacle, EXCEPT if we are calculating
// a path for an enemy or NPC with keys
if ( fPathingForPlayer || ( s && (s->uiStatusFlags & SOLDIER_MONSTER || s->uiStatusFlags & SOLDIER_ANIMAL) ) )
{
nextCost = TRAVELCOST_OBSTACLE;
}
else
{
// have to check if door is locked and NPC does not have keys!
pDoor = FindDoorInfoAtGridNo( iDoorGridNo );
if (pDoor)
{
if (!pDoor->fLocked || s->bHasKeys)
{
// add to AP cost
if (gubNPCAPBudget)
{
fGoingThroughDoor = TRUE;
}
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
else
{
nextCost = TRAVELCOST_OBSTACLE;
}
}
else
{
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
}
}
else
{
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
}
}
}
}
else if ( (nextCost == TRAVELCOST_SHORE || nextCost == TRAVELCOST_KNEEDEEP || nextCost == TRAVELCOST_DEEPWATER ) && fNonSwimmer )
{
// creatures and animals can't go in water
nextCost = TRAVELCOST_OBSTACLE;
}
// Apr. '96 - moved up be ahead of AP_Budget stuff
if ( ( nextCost >= NOPASS) ) // || ( nextCost == TRAVELCOST_DOOR ) )
goto NEXTDIR;
}
else
{
nextCost = TRAVELCOST_FLAT;
}
if ( newLoc > GRIDSIZE )
{
// WHAT THE??? hack.
goto NEXTDIR;
}
// if contemplated tile is NOT final dest and someone there, disqualify route
// when doing a reachable test, ignore all locations with people in them
if (fPathAroundPeople && ( (newLoc != iDestination) || fCopyReachable) )
{
// ATE: ONLY cancel if they are moving.....
ubMerc = WhoIsThere2( (UINT16) newLoc, s->bLevel);
if ( ubMerc < NOBODY && ubMerc != s->ubID )
{
// Check for movement....
//if ( fTurnBased || ( (Menptr[ ubMerc ].sFinalDestination == Menptr[ ubMerc ].sGridNo) || (Menptr[ ubMerc ].fDelayedMovement) ) )
//{
goto NEXTDIR;
//}
// else
//{
// nextCost += 50;
//}
}
}
#ifdef VEHICLE
if (fMultiTile)
{
// vehicle test for obstacles: prevent movement to next tile if
// a tile covered by the vehicle in that position & direction
// has an obstacle in it
// because of the order in which animations are stored (dir 7 first,
// then 0 1 2 3 4 5 6), we must subtract 1 from the direction
// ATE: Send in our existing structure ID so it's ignored!
if (!OkayToAddStructureToWorld( (INT16) newLoc, ubLevel, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), usOKToAddStructID ) )
{
goto NEXTDIR;
}
/*
// vehicles aren't moving any more....
if (fVehicle)
{
// transmogrify pathing costs for vehicles!
switch(nextCost)
{
case TRAVELCOST_THICK : nextCost = TRAVELCOST_GRASS;
break;
case TRAVELCOST_SHORE :
case TRAVELCOST_KNEEDEEP:
case TRAVELCOST_DEEPWATER:
// case TRAVELCOST_VEINEND :
// case TRAVELCOST_VEINMID :
//case TRAVELCOST_DOOR :
case TRAVELCOST_FENCE : nextCost = TRAVELCOST_OBSTACLE;
break;
default : break;
}
}
*/
}
#endif
// NEW Apr 21 by Ian: abort if cost exceeds budget
if (gubNPCAPBudget)
{
switch(nextCost)
{
case TRAVELCOST_NONE : ubAPCost = 0;
break;
case TRAVELCOST_DIRTROAD:
case TRAVELCOST_FLAT : ubAPCost = AP_MOVEMENT_FLAT;
break;
//case TRAVELCOST_BUMPY :
case TRAVELCOST_GRASS : ubAPCost = AP_MOVEMENT_GRASS;
break;
case TRAVELCOST_THICK : ubAPCost = AP_MOVEMENT_BUSH;
break;
case TRAVELCOST_DEBRIS : ubAPCost = AP_MOVEMENT_RUBBLE;
break;
case TRAVELCOST_SHORE : ubAPCost = AP_MOVEMENT_SHORE; // wading shallow water
break;
case TRAVELCOST_KNEEDEEP: ubAPCost = AP_MOVEMENT_LAKE; // wading waist/chest deep - very slow
break;
case TRAVELCOST_DEEPWATER:ubAPCost = AP_MOVEMENT_OCEAN; // can swim, so it's faster than wading
break;
//case TRAVELCOST_VEINEND :
//case TRAVELCOST_VEINMID : ubAPCost = AP_MOVEMENT_FLAT;
// break;
//case TRAVELCOST_DOOR : ubAPCost = AP_MOVEMENT_FLAT;
// break;
case TRAVELCOST_FENCE : ubAPCost = AP_JUMPFENCE;
/*
if ( sSwitchValue == TRAVELCOST_FENCE )
{
sPoints += sTileCost;
// If we are changeing stance ( either before or after getting there....
// We need to reflect that...
switch(usMovementMode)
{
case RUNNING:
case WALKING :
// Add here cost to go from crouch to stand AFTER fence hop....
// Since it's AFTER.. make sure we will be moving after jump...
if ( ( iCnt + 2 ) < iLastGrid )
{
sPoints += AP_CROUCH;
}
break;
case SWATTING:
// Add cost to stand once there BEFORE....
sPoints += AP_CROUCH;
break;
case CRAWLING:
// Can't do it here.....
break;
}
}
*/
break;
case TRAVELCOST_OBSTACLE:
default : goto NEXTDIR; // Cost too much to be considered!
break;
}
// don't make the mistake of adding directly to
// ubCurAPCost, that must be preserved for remaining dirs!
if (iCnt & 1)
{
//ubAPCost++;
//ubAPCost = gubDiagCost[ubAPCost];
ubAPCost = (ubAPCost * 14) / 10;
}
usMovementModeToUseForAPs = usMovementMode;
// ATE: if water, force to be walking always!
if ( nextCost == TRAVELCOST_SHORE || nextCost == TRAVELCOST_KNEEDEEP || nextCost == TRAVELCOST_DEEPWATER )
{
usMovementModeToUseForAPs = WALKING;
}
// adjust AP cost for movement mode
switch( usMovementModeToUseForAPs )
{
case RUNNING:
case ADULTMONSTER_WALKING:
// save on casting
ubAPCost = ubAPCost * 10 / ( (UINT8) (RUNDIVISOR * 10));
//ubAPCost = (INT16)(DOUBLE)( (sTileCost / RUNDIVISOR) ); break;
break;
case WALKING:
case ROBOT_WALK:
ubAPCost = (ubAPCost + WALKCOST);
break;
case SWATTING:
ubAPCost = (ubAPCost + SWATCOST);
break;
case CRAWLING:
ubAPCost = (ubAPCost + CRAWLCOST);
break;
}
if (nextCost == TRAVELCOST_FENCE)
{
switch( usMovementModeToUseForAPs )
{
case RUNNING:
case WALKING :
// Here pessimistically assume the path will continue after hopping the fence
ubAPCost += AP_CROUCH;
break;
case SWATTING:
// Add cost to stand once there BEFORE jumping....
ubAPCost += AP_CROUCH;
break;
case CRAWLING:
// Can't do it here.....
goto NEXTDIR;
}
}
else if (nextCost == TRAVELCOST_NOT_STANDING)
{
switch(usMovementModeToUseForAPs)
{
case RUNNING:
case WALKING :
// charge crouch APs for ducking head!
ubAPCost += AP_CROUCH;
break;
default:
break;
}
}
else if (fGoingThroughDoor)
{
ubAPCost += AP_OPEN_DOOR;
fGoingThroughDoor = FALSE;
}
ubNewAPCost = ubCurAPCost + ubAPCost;
if (ubNewAPCost > gubNPCAPBudget)
goto NEXTDIR;
}
//ATE: Uncommented out for doors, if we are at a door but not dest, continue!
// if ( nextCost == TRAVELCOST_DOOR ) //&& newLoc != iDestination)
// goto NEXTDIR;
/*
// FOLLOWING SECTION COMMENTED OUT ON MARCH 7/97 BY IC
if (nextCost == SECRETCOST && !s->human)
goto NEXTDIR;
if (prevCost==VEINMIDCOST)
if (!ISVEIN(nextCost))
goto NEXTDIR;
//veining check
if (nextCost==VEINMIDCOST)
if (!ISVEIN(prevCost))
goto NEXTDIR;
if (nextCost==VEINMIDCOST)
//if (ISVEIN(nextCost))
nextCost=VEINCOST;
else
if (nextCost==VEINENDCOST)
if (Grid[newLoc].land < LAKE1)
nextCost = VEINCOST;
else
nextCost = OCEANCOST+(10*PATHFACTOR);
*/
if ( fCloseGoodEnough )
{
if ( PythSpacesAway( (INT16)newLoc, sDestination ) <= sClosePathLimit )
{
// stop the path here!
iDestination = newLoc;
sDestination = (INT16) newLoc;
fCloseGoodEnough = FALSE;
}
}
//make the destination look very attractive
if (newLoc == iDestination)
nextCost = 0;
else
//if (_KeyDown(CTRL_DOWN) && nextCost < TRAVELCOST_VEINEND)
if (gfPlotDirectPath && nextCost < NOPASS)
nextCost = TRAVELCOST_FLAT;
//if (ISVEIN(prevCost))
// prevCost=VEINCOST;
// make water cost attractive for water to water paths
if (iWaterToWater)
{
if (ISWATER(prevCost))
prevCost = EASYWATERCOST;
if (ISWATER(nextCost))
nextCost = EASYWATERCOST;
}
// NOTE: on September 24, 1997, Chris went back to a diagonal bias system
if (iCnt & 1)
{
// moving on a diagonal
//nextCost = gubDiagCost[nextCost];
nextCost = nextCost * 14 / 10;
//nextCost++;
}
if ( bLoopState == LOOPING_REVERSE)
{
// penalize moving backwards to encourage turning sooner
nextCost += 50;
}
newTotCost = curCost + nextCost;
/*
// no diagonal bias - straightforward costing regardless of direction
newTotCost = curCost + nextCost;
// NOTE: ON JAN 6TH, 1995, IAN COMMENTED OUT THE DIAGONAL BIAS AND
// UNCOMMENTED THE "NO DIAGONAL BIAS"
//diagonal bias - this makes diagonal moves cost more
if (iCnt & 1)
// diagonal move costs 70 percent
//newTotCost += (nextCost/PATHFACTOR);
newTotCost += 1;
// newTotCost = curCost + ((prevCost+nextCost)*7)/10;
// else // non-diagonal costs only 50%
// newTotCost = curCost + (prevCost+nextCost)/2;
*/
// have we found a path to the current location that
// costs less than the best so far to the same location?
if (trailCostUsed[newLoc] != gubGlobalPathCount || newTotCost < trailCost[newLoc])
{
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
if (gsCoverValue[newLoc] == 0x7F7F)
{
gsCoverValue[newLoc] = usCounter++;
}
/*
else if (gsCoverValue[newLoc] >= 0)
{
gsCoverValue[newLoc]++;
}
else
{
gsCoverValue[newLoc]--;
}
*/
}
#endif
//NEWQUENODE;
{
if (queRequests<QPOOLNDX)
{
pNewPtr = pathQ + (queRequests);
queRequests++;
memset( pNewPtr->pNext, 0, sizeof( path_t *) * ABSMAX_SKIPLIST_LEVEL );
pNewPtr->bLevel = RandomSkipListLevel();
}
else if (iClosedListSize > 0)
{
pNewPtr = pClosedHead->pNext[0];
pClosedHead->pNext[0] = pNewPtr->pNext[0];
iClosedListSize--;
queRequests++;
memset( pNewPtr->pNext, 0, sizeof( path_t *) * ABSMAX_SKIPLIST_LEVEL );
pNewPtr->bLevel = RandomSkipListLevel();
}
else
{
pNewPtr = NULL;
}
}
if (pNewPtr == NULL)
{
#ifdef COUNT_PATHS
guiFailedPathChecks++;
#endif
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return(0);
}
//make new path to current location
trailTree[trailTreeNdx].nextLink = sCurPathNdx;
trailTree[trailTreeNdx].stepDir = (INT8) iCnt;
if ( bLoopState == LOOPING_REVERSE )
{
trailTree[trailTreeNdx].fFlags = STEP_BACKWARDS;
}
else
{
trailTree[trailTreeNdx].fFlags = 0;
}
trailTree[trailTreeNdx].sGridNo = (INT16) newLoc;
pNewPtr->sPathNdx = trailTreeNdx;
trailTreeNdx++;
if (trailTreeNdx >= iMaxTrailTree)
{
#ifdef COUNT_PATHS
guiFailedPathChecks++;
#endif
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return(0);
}
iLocY = newLoc / MAPWIDTH;
iLocX = newLoc % MAPWIDTH;
SETLOC( *pNewPtr, newLoc );
pNewPtr->usCostSoFar = (UINT16) newTotCost;
pNewPtr->usCostToGo = (UINT16) REMAININGCOST(pNewPtr);
if ( fCopyReachable )
{
pNewPtr->usCostToGo = 100;
}
else
{
pNewPtr->usCostToGo = (UINT16) REMAININGCOST(pNewPtr);
}
pNewPtr->usTotalCost = newTotCost + pNewPtr->usCostToGo;
pNewPtr->ubLegDistance = LEGDISTANCE( iLocX, iLocY, iDestX, iDestY );
if (gubNPCAPBudget)
{
//save the AP cost so far along this path
pNewPtr->ubTotalAPCost = ubNewAPCost;
// update the AP costs in the AI array of path costs if necessary...
if (fCopyPathCosts)
{
iX = AI_PATHCOST_RADIUS + iLocX - iOriginationX;
iY = AI_PATHCOST_RADIUS + iLocY - iOriginationY;
gubAIPathCosts[iX][iY] = ubNewAPCost;
}
}
//update the trail map to reflect the newer shorter path
trailCost[newLoc] = (UINT16) newTotCost;
trailCostUsed[newLoc] = gubGlobalPathCount;
//do a sorted que insert of the new path
// COMMENTED OUT TO DO BOUNDS CHECKER CC JAN 18 99
//QUEINSERT(pNewPtr);
//#define SkipListInsert( pNewPtr )
{
pCurr = pQueueHead;
uiCost = TOTALCOST( pNewPtr );
memset( pUpdate, 0, MAX_SKIPLIST_LEVEL * sizeof( path_t *) );
for (iCurrLevel = bSkipListLevel - 1; iCurrLevel >= 0; iCurrLevel--)
{
pNext = pCurr->pNext[iCurrLevel];
while (pNext)
{
if ( uiCost > TOTALCOST( pNext ) || (uiCost == TOTALCOST( pNext ) && FARTHER( pNewPtr, pNext ) ) )
{
pCurr = pNext;
pNext = pCurr->pNext[iCurrLevel];
}
else
{
break;
}
}
pUpdate[iCurrLevel] = pCurr;
}
pCurr = pCurr->pNext[0];
for (iCurrLevel = 0; iCurrLevel < pNewPtr->bLevel; iCurrLevel++)
{
if (!(pUpdate[iCurrLevel]))
{
break;
}
pNewPtr->pNext[iCurrLevel] = pUpdate[iCurrLevel]->pNext[iCurrLevel];
pUpdate[iCurrLevel]->pNext[iCurrLevel] = pNewPtr;
}
iSkipListSize++;
if (iSkipListSize > iSkipListLevelLimit[bSkipListLevel])
{
pCurr = pQueueHead;
pNext = pQueueHead->pNext[bSkipListLevel - 1];
while( pNext )
{
if (pNext->bLevel > bSkipListLevel)
{
pCurr->pNext[bSkipListLevel] = pNext;
pCurr = pNext;
}
pNext = pNext->pNext[bSkipListLevel - 1];
}
pCurr->pNext[bSkipListLevel] = pNext;
bSkipListLevel++;
}
}
#ifdef PATHAI_SKIPLIST_DEBUG
// print out contents of queue
{
INT32 iLoop;
INT8 bTemp;
zTempString[0] = '\0';
pCurr = pQueueHead;
iLoop = 0;
while( pCurr )
{
sprintf( zTS, "\t%ld", pCurr->sPathNdx );
if (pCurr == pNewPtr)
{
strcat( zTS, "*" );
}
strcat( zTempString, zTS );
pCurr = pCurr->pNext[0];
iLoop++;
if (iLoop > 50)
{
break;
}
}
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, zTempString );
zTempString[0] = '\0';
pCurr = pQueueHead;
iLoop = 0;
while( pCurr )
{
sprintf( zTS, "\t%d", pCurr->bLevel );
strcat( zTempString, zTS );
pCurr = pCurr->pNext[0];
iLoop++;
if (iLoop > 50)
{
break;
}
}
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, zTempString );
zTempString[0] = '\0';
bTemp = pQueueHead->bLevel;
pCurr = pQueueHead;
iLoop = 0;
while( pCurr )
{
bTemp = pQueueHead->bLevel;
while ( pCurr->pNext[ bTemp - 1 ] == NULL )
{
bTemp--;
}
sprintf( zTS, "\t%d", bTemp );
strcat( zTempString, zTS );
pCurr = pCurr->pNext[0];
iLoop++;
if (iLoop > 50)
{
break;
}
}
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, zTempString );
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, "------" );
}
#endif
}
NEXTDIR:
if (bLoopState == LOOPING_CLOCKWISE) // backwards
{
iCnt = gOneCCDirection[ iCnt ];
}
else
{
iCnt = gOneCDirection[ iCnt ];
}
if ( iCnt == iLoopEnd )
{
ENDOFLOOP:
break;
}
else if ( fContinuousTurnNeeded && iCnt == gOppositeDirection[ iLoopStart ] )
{
fCheckedBehind = TRUE;
}
}
}
while (pathQNotEmpty && pathNotYetFound);
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
SetRenderFlags( RENDER_FLAG_FULL );
if ( guiCurrentScreen == GAME_SCREEN )
{
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
}
}
#endif
// work finished. Did we find a path?
if (pathQNotEmpty && pathFound)
{
INT16 z,_z,_nextLink; //,tempgrid;
_z=0;
z = (INT16) pQueueHead->pNext[0]->sPathNdx;
while (z)
{
_nextLink = trailTree[z].nextLink;
trailTree[z].nextLink = _z;
_z = z;
z = _nextLink;
}
// if this function was called because a solider is about to embark on an actual route
// (as opposed to "test" path finding (used by cursor, etc), then grab all pertinent
// data and copy into soldier's database
if (bCopy == COPYROUTE)
{
z=_z;
for (iCnt=0; z && (iCnt < MAX_PATH_LIST_SIZE); iCnt++)
{
s->usPathingData[iCnt] = (INT16) trailTree[z].stepDir;
z = trailTree[z].nextLink;
}
s->usPathIndex = 0;
s->usPathDataSize = (UINT16) iCnt;
}
else if (bCopy == NO_COPYROUTE)
{
z=_z;
for (iCnt=0; z != 0; iCnt++)
{
guiPathingData[ iCnt ] = trailTree[z].stepDir;
z = trailTree[z].nextLink;
}
giPathDataSize = (UINT16) iCnt;
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
SetRenderFlags( RENDER_FLAG_FULL );
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
}
#endif
// return path length : serves as a "successful" flag and a path length counter
#ifdef COUNT_PATHS
guiSuccessfulPathChecks++;
#endif
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
//TEMP: This is returning zero when I am generating edgepoints, so I am force returning 1 until
// this is fixed?
if( gfGeneratingMapEdgepoints )
{
return TRUE;
}
return(iCnt);
}
#ifdef COUNT_PATHS
if (fCopyReachable)
{
// actually this is a success
guiSuccessfulPathChecks++;
}
else
{
guiUnsuccessfulPathChecks++;
}
#endif
// failed miserably, report...
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
return(0);
}
void GlobalReachableTest( INT16 sStartGridNo )
{
SOLDIERTYPE s;
INT32 iCurrentGridNo =0;
memset( &s, 0, sizeof( SOLDIERTYPE ) );
s.sGridNo = sStartGridNo;
s.bLevel = 0;
s.bTeam = 1;
//reset the flag for gridno's
for( iCurrentGridNo =0; iCurrentGridNo < WORLD_MAX; iCurrentGridNo++ )
{
gpWorldLevelData[ iCurrentGridNo ].uiFlags &= ~( MAPELEMENT_REACHABLE );
}
ReconfigurePathAI( ABSMAX_SKIPLIST_LEVEL, ABSMAX_TRAIL_TREE, ABSMAX_PATHQ );
FindBestPath( &s, NOWHERE, 0, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );
RestorePathAIToDefaults();
}
void LocalReachableTest( INT16 sStartGridNo, INT8 bRadius )
{
SOLDIERTYPE s;
INT32 iCurrentGridNo = 0;
INT32 iX, iY;
memset( &s, 0, sizeof( SOLDIERTYPE ) );
s.sGridNo = sStartGridNo;
//if we are moving on the gorund level
if( gsInterfaceLevel == I_ROOF_LEVEL )
{
s.bLevel = 1;
}
else
{
s.bLevel = 0;
}
s.bTeam = OUR_TEAM;
//reset the flag for gridno's
for ( iY = -bRadius; iY <= bRadius; iY++ )
{
for ( iX = -bRadius; iX <= bRadius; iX++ )
{
iCurrentGridNo = sStartGridNo + iX + iY * MAXCOL;
if ( iCurrentGridNo >= 0 && iCurrentGridNo <= WORLD_MAX )
{
gpWorldLevelData[ iCurrentGridNo ].uiFlags &= ~( MAPELEMENT_REACHABLE );
}
}
}
// set the dist limit
gubNPCDistLimit = bRadius;
// make the function call
FindBestPath( &s, NOWHERE, s.bLevel, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );
// reset dist limit
gubNPCDistLimit = 0;
}
void GlobalItemsReachableTest( INT16 sStartGridNo1, INT16 sStartGridNo2 )
{
SOLDIERTYPE s;
INT32 iCurrentGridNo =0;
memset( &s, 0, sizeof( SOLDIERTYPE ) );
s.sGridNo = sStartGridNo1;
s.bLevel = 0;
s.bTeam = 1;
//reset the flag for gridno's
for( iCurrentGridNo =0; iCurrentGridNo < WORLD_MAX; iCurrentGridNo++ )
{
gpWorldLevelData[ iCurrentGridNo ].uiFlags &= ~( MAPELEMENT_REACHABLE );
}
ReconfigurePathAI( ABSMAX_SKIPLIST_LEVEL, ABSMAX_TRAIL_TREE, ABSMAX_PATHQ );
FindBestPath( &s, NOWHERE, 0, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );
if ( sStartGridNo2 != NOWHERE )
{
s.sGridNo = sStartGridNo2;
FindBestPath( &s, NOWHERE, 0, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );
}
RestorePathAIToDefaults();
}
void RoofReachableTest( INT16 sStartGridNo, UINT8 ubBuildingID )
{
SOLDIERTYPE s;
INT16 sGridNo;
memset( &s, 0, sizeof( SOLDIERTYPE ) );
s.sGridNo = sStartGridNo;
s.bLevel = 1;
s.bTeam = 1;
// clearing flags
for( sGridNo = 0 ; sGridNo < NOWHERE ; ++sGridNo )
gpWorldLevelData[ sGridNo ].uiFlags &= (~MAPELEMENT_REACHABLE);
gubBuildingInfoToSet = ubBuildingID;
ReconfigurePathAI( ABSMAX_SKIPLIST_LEVEL, ABSMAX_TRAIL_TREE, ABSMAX_PATHQ );
FindBestPath( &s, NOWHERE, 1, WALKING, COPYREACHABLE, 0 );
RestorePathAIToDefaults();
// set start position to reachable since path code sets it unreachable
gpWorldLevelData[ sStartGridNo ].uiFlags |= MAPELEMENT_REACHABLE;
// reset building variable
gubBuildingInfoToSet = 0;
}
void ErasePath(char bEraseOldOne)
{
INT16 iCnt;
// NOTE: This routine must be called BEFORE anything happens that changes
// a merc's gridno, else the....
//EraseAPCursor();
if ( gfUIHandleShowMoveGrid )
{
gfUIHandleShowMoveGrid = FALSE;
RemoveTopmost( gsUIHandleShowMoveGridLocation, FIRSTPOINTERS4 );
RemoveTopmost( gsUIHandleShowMoveGridLocation, FIRSTPOINTERS9 );
RemoveTopmost( gsUIHandleShowMoveGridLocation, FIRSTPOINTERS2 );
RemoveTopmost( gsUIHandleShowMoveGridLocation, FIRSTPOINTERS13 );
RemoveTopmost( gsUIHandleShowMoveGridLocation, FIRSTPOINTERS15 );
RemoveTopmost( gsUIHandleShowMoveGridLocation, FIRSTPOINTERS19 );
RemoveTopmost( gsUIHandleShowMoveGridLocation, FIRSTPOINTERS20 );
}
if (!gusPathShown)
{
//OldPath = FALSE;
return;
}
//if (OldPath > 0 && !eraseOldOne)
// return;
//OldPath = FALSE;
gusPathShown = FALSE;
for (iCnt=0; iCnt < giPlotCnt; iCnt++)
{
//Grid[PlottedPath[cnt]].fstep = 0;
RemoveAllObjectsOfTypeRange( guiPlottedPath[iCnt], FOOTPRINTS, FOOTPRINTS );
RemoveAllOnRoofsOfTypeRange( guiPlottedPath[iCnt], FOOTPRINTS, FOOTPRINTS );
//RemoveAllObjectsOfTypeRange( guiPlottedPath[iCnt], FIRSTPOINTERS, FIRSTPOINTERS );
}
//for (cnt=0; cnt < GRIDSIZE; cnt++)
// Grid[cnt].fstep = 0;
//RemoveAllStructsOfTypeRange( gusEndPlotGridNo, GOODRING, GOODRING );
giPlotCnt = 0;
memset(guiPlottedPath,0,256*sizeof(UINT32));
}
INT16 PlotPath( SOLDIERTYPE *pSold, INT16 sDestGridno, INT8 bCopyRoute, INT8 bPlot, INT8 bStayOn, UINT16 usMovementMode, INT8 bStealth, INT8 bReverse , INT16 sAPBudget)
{
INT16 sTileCost,sPoints=0,sTempGrid,sAnimCost=0;
INT16 sPointsWalk=0,sPointsCrawl=0,sPointsRun=0,sPointsSwat=0;
INT16 sExtraCostStand,sExtraCostSwat,sExtraCostCrawl;
INT32 iLastGrid;
INT32 iCnt;
INT16 sOldGrid=0;
INT16 sFootOrderIndex;
INT16 sSwitchValue;
INT16 sFootOrder[5] = { GREENSTEPSTART, PURPLESTEPSTART, BLUESTEPSTART,
ORANGESTEPSTART, REDSTEPSTART };
UINT16 usTileIndex;
UINT16 usTileNum;
LEVELNODE *pNode;
UINT16 usMovementModeToUseForAPs;
BOOLEAN bIgnoreNextCost = FALSE;
INT16 sTestGridno;
if ( bPlot && gusPathShown )
{
ErasePath(FALSE);
}
gusAPtsToMove = 0;
sTempGrid = (INT16) pSold->sGridNo;
sFootOrderIndex = 0;
//gubNPCMovementMode = (UINT8) usMovementMode;
// distance limit to reduce the cost of plotting a path to a location we can't reach
// For now, use known hight adjustment
if ( gfRecalculatingExistingPathCost || FindBestPath( pSold, sDestGridno, (INT8)pSold->bLevel, usMovementMode, bCopyRoute, 0 ) )
{
// if soldier would be STARTING to run then he pays a penalty since it takes time to
// run full speed
if (pSold->usAnimState != RUNNING)
{
// for estimation purposes, always pay penalty
sPointsRun = AP_START_RUN_COST;
}
// Add to points, those needed to start from different stance!
sPoints += MinAPsToStartMovement( pSold, usMovementMode );
// We should reduce points for starting to run if first tile is a fence...
sTestGridno = NewGridNo(pSold->sGridNo,(INT16) DirectionInc( (UINT16)guiPathingData[0]));
if ( gubWorldMovementCosts[ sTestGridno ][ (INT8)guiPathingData[0] ][ pSold->bLevel] == TRAVELCOST_FENCE )
{
if ( usMovementMode == RUNNING && pSold->usAnimState != RUNNING )
{
sPoints -= AP_START_RUN_COST;
}
}
// FIRST, add up "startup" additional costs - such as intermediate animations, etc.
/* removing warning C4060 (jonathanl)
switch(pSold->usAnimState)
{
//case START_AID :
//case GIVING_AID : sAnimCost = AP_STOP_FIRST_AID;
// break;
//case TWISTOMACH :
//case COLLAPSED : sAnimCost = AP_GET_UP;
// break;
//case TWISTBACK :
//case UNCONSCIOUS : sAnimCost = (AP_ROLL_OVER+AP_GET_UP);
// break;
// case CROUCHING : if (usMovementMode == WALKING || usMovementMode == RUNNING)
// sAnimCost = AP_CROUCH;
// break;
}
*/
sPoints += sAnimCost;
gusAPtsToMove += sAnimCost;
if (bStayOn)
{
iLastGrid = giPathDataSize+1;
}
else
{
iLastGrid = giPathDataSize;
}
for ( iCnt=0; iCnt < iLastGrid; iCnt++ )
{
sExtraCostStand = 0;
sExtraCostSwat = 0;
sExtraCostCrawl = 0;
// what is the next gridno in the path?
sOldGrid = sTempGrid;
sTempGrid = NewGridNo(sTempGrid,(INT16) DirectionInc( (UINT16)guiPathingData[iCnt]));
// Get switch value...
sSwitchValue = gubWorldMovementCosts[ sTempGrid ][ (INT8)guiPathingData[iCnt] ][ pSold->bLevel];
// get the tile cost for that tile based on WALKING
sTileCost = TerrainActionPoints( pSold, sTempGrid, (INT8)guiPathingData[iCnt], pSold->bLevel );
usMovementModeToUseForAPs = usMovementMode;
// ATE - MAKE MOVEMENT ALWAYS WALK IF IN WATER
if ( gpWorldLevelData[ sTempGrid ].ubTerrainID == DEEP_WATER || gpWorldLevelData[ sTempGrid ].ubTerrainID == MED_WATER || gpWorldLevelData[ sTempGrid ].ubTerrainID == LOW_WATER )
{
usMovementModeToUseForAPs = WALKING;
}
if ( bIgnoreNextCost )
{
bIgnoreNextCost = FALSE;
}
else
{
// ATE: If we have a 'special cost, like jump fence...
if ( sSwitchValue == TRAVELCOST_FENCE )
{
sPoints += sTileCost;
bIgnoreNextCost = TRUE;
// If we are changeing stance ( either before or after getting there....
// We need to reflect that...
switch( usMovementModeToUseForAPs )
{
case RUNNING:
case WALKING :
// Add here cost to go from crouch to stand AFTER fence hop....
// Since it's AFTER.. make sure we will be moving after jump...
if ( ( iCnt + 2 ) < iLastGrid )
{
sExtraCostStand += AP_CROUCH;
// ATE: if running, charge extra point to srart again
if ( usMovementModeToUseForAPs== RUNNING )
{
sExtraCostStand++;
}
sPoints += sExtraCostStand;
}
break;
case SWATTING:
// Add cost to stand once there BEFORE....
sExtraCostSwat += AP_CROUCH;
sPoints += sExtraCostSwat;
break;
case CRAWLING:
// Can't do it here.....
break;
}
}
else if (sTileCost > 0)
{
// else, movement is adjusted based on mode...
if (sSwitchValue == TRAVELCOST_NOT_STANDING)
{
switch( usMovementModeToUseForAPs )
{
case RUNNING:
case WALKING :
// charge crouch APs for ducking head!
sExtraCostStand += AP_CROUCH;
break;
default:
break;
}
}
// so, then we must modify it for other movement styles and accumulate
switch( usMovementModeToUseForAPs )
{
case RUNNING: sPoints += (INT16)(DOUBLE)( (sTileCost / RUNDIVISOR) ) + sExtraCostStand; break;
case WALKING : sPoints += (sTileCost + WALKCOST) + sExtraCostStand; break;
case SWATTING: sPoints += (sTileCost + SWATCOST) + sExtraCostSwat; break;
case CRAWLING: sPoints += (sTileCost + CRAWLCOST) + sExtraCostCrawl; break;
default : sPoints += sTileCost; break;
}
}
}
// THIS NEXT SECTION ONLY NEEDS TO HAPPEN FOR CURSOR UI FEEDBACK, NOT ACTUAL COSTING
if (bPlot && ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ) ) // OR USER OPTION ON... ***)
{
// ATE; TODO: Put stuff in here to allow for fact of costs other than movement ( jump fence, open door )
// store WALK cost
sPointsWalk += (sTileCost + WALKCOST) + sExtraCostStand;
// now get cost as if CRAWLING
sPointsCrawl += (sTileCost + CRAWLCOST) + sExtraCostCrawl;
// now get cost as if SWATTING
sPointsSwat += (sTileCost + SWATCOST) + sExtraCostSwat;
// now get cost as if RUNNING
sPointsRun += (INT16)(DOUBLE)( (sTileCost / RUNDIVISOR) ) + sExtraCostStand;
}
if ( iCnt == 0 && bPlot )
{
gusAPtsToMove = sPoints;
giPlotCnt = 0;
}
//if ( gTacticalStatus.uiFlags & TURNBASED && (gTacticalStatus.uiFlags & INCOMBAT) ) // OR USER OPTION "show paths" ON... ***
{
if (bPlot && ((iCnt < (iLastGrid-1)) || (iCnt < iLastGrid && bStayOn)))
{
guiPlottedPath[giPlotCnt++] = sTempGrid;
// we need a footstep graphic ENTERING the next tile
// get the direction
usTileNum = (UINT16) guiPathingData[iCnt] + 2;
if (usTileNum > 8)
{
usTileNum = 1;
}
// Are we a vehicle?
if ( pSold->uiStatusFlags & SOLDIER_VEHICLE )
{
// did we exceed WALK cost?
if ( sPointsSwat > sAPBudget)
{
sFootOrderIndex = 4;
}
else
{
sFootOrderIndex = 3;
}
}
else
{
// did we exceed CRAWL cost?
if (sFootOrderIndex == 0 && sPointsCrawl > sAPBudget)
{
sFootOrderIndex++;
}
// did we exceed WALK cost?
if (sFootOrderIndex == 1 && sPointsSwat > sAPBudget)
{
sFootOrderIndex++;
}
// did we exceed SWAT cost?
if (sFootOrderIndex == 2 && sPointsWalk > sAPBudget)
{
sFootOrderIndex++;
}
// did we exceed RUN cost?
if (sFootOrderIndex == 3 && sPointsRun > sAPBudget)
{
sFootOrderIndex++;
}
}
GetTileIndexFromTypeSubIndex( FOOTPRINTS, (UINT16)usTileNum, &usTileIndex );
// Adjust based on what mode we are in...
if ( (gTacticalStatus.uiFlags & REALTIME ) || !(gTacticalStatus.uiFlags & INCOMBAT ) )
{
// find out which color we're using
usTileIndex += sFootOrder[ 4 ];
}
else // turn based
{
// find out which color we're using
usTileIndex += sFootOrder[sFootOrderIndex];
}
/*
if ( sPoints <= sAPBudget)
{
// find out which color we're using
usTileIndex += sFootOrder[sFootOrderIndex];
}
else
{
// use red footprints ( offset by 16 )
usTileIndex += REDSTEPSTART;
}
*/
if ( pSold->bLevel == 0 )
{
pNode = AddObjectToTail(sTempGrid, usTileIndex );
pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
}
else
{
pNode = AddOnRoofToTail(sTempGrid, usTileIndex );
pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
}
// we need a footstep graphic LEAVING this tile
// get the direction using the NEXT tile (thus iCnt+1 as index)
usTileNum = (UINT16) guiPathingData[iCnt + 1] + 2;
if (usTileNum > 8)
{
usTileNum = 1;
}
// this is a LEAVING footstep which is always the second set of 8
usTileNum += 8;
GetTileIndexFromTypeSubIndex( FOOTPRINTS, (UINT16)usTileNum, &usTileIndex );
// Adjust based on what mode we are in...
if ( (gTacticalStatus.uiFlags & REALTIME ) || !(gTacticalStatus.uiFlags & INCOMBAT ) )
{
// find out which color we're using
usTileIndex += sFootOrder[ 4 ];
}
else // turnbased
{
// find out which color we're using
usTileIndex += sFootOrder[sFootOrderIndex];
}
if ( pSold->bLevel == 0 )
{
pNode = AddObjectToTail(sTempGrid, usTileIndex );
pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
}
else
{
pNode = AddOnRoofToTail(sTempGrid, usTileIndex );
pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
}
}
} // end of if turn based or real-time user option "show paths" on...
}
if ( bPlot )
{
gusPathShown = TRUE;
}
} // end of found a path
// reset distance limit
gubNPCDistLimit = 0;
return(sPoints);
}
INT16 UIPlotPath( SOLDIERTYPE *pSold, INT16 sDestGridno, INT8 bCopyRoute, INT8 bPlot, INT8 bStayOn, UINT16 usMovementMode, INT8 bStealth, INT8 bReverse , INT16 sAPBudget)
{
// This function is specifically for UI calls to the pathing routine, to
// check whether the shift key is pressed, etc.
INT16 sRet;
if ( _KeyDown( SHIFT ) )
{
gfPlotDirectPath = TRUE;
}
// If we are on the same level as the interface level, continue, else return
if ( pSold->bLevel != gsInterfaceLevel )
{
return( 0 );
}
if ( gGameSettings.fOptions[ TOPTION_ALWAYS_SHOW_MOVEMENT_PATH ] )
{
bPlot = TRUE;
}
sRet = PlotPath( pSold, sDestGridno, bCopyRoute, bPlot, bStayOn, usMovementMode, bStealth, bReverse, sAPBudget);
gfPlotDirectPath = FALSE;
return( sRet );
}
INT16 RecalculatePathCost( SOLDIERTYPE *pSoldier, UINT16 usMovementMode )
{
// AI function for a soldier already with a path; this will return the cost of that path using the given movement mode
INT16 sRet;
if ( !pSoldier->bPathStored || pSoldier->usPathDataSize == 0 )
{
return( 0 );
}
gfRecalculatingExistingPathCost = TRUE;
sRet = PlotPath( pSoldier, pSoldier->sFinalDestination, NO_COPYROUTE, FALSE, FALSE, usMovementMode, FALSE, FALSE, 0 );
gfRecalculatingExistingPathCost = FALSE;
return( sRet );
}
INT16 EstimatePlotPath( SOLDIERTYPE *pSold, INT16 sDestGridno, INT8 bCopyRoute, INT8 bPlot, INT8 bStayOn, UINT16 usMovementMode, INT8 bStealth, INT8 bReverse , INT16 sAPBudget)
{
// This function is specifically for AI calls to estimate path cost to a location
// It sets stuff up to ignore all people
INT16 sRet;
gfEstimatePath = TRUE;
sRet = PlotPath( pSold, sDestGridno, bCopyRoute, bPlot, bStayOn, usMovementMode, bStealth, bReverse, sAPBudget);
gfEstimatePath = FALSE;
return( sRet );
}
UINT8 InternalDoorTravelCost( SOLDIERTYPE * pSoldier, INT32 iGridNo, UINT8 ubMovementCost, BOOLEAN fReturnPerceivedValue, INT32 * piDoorGridNo, BOOLEAN fReturnDoorCost )
{
// This function will return either TRAVELCOST_DOOR (in place of closed door cost),
// TRAVELCOST_OBSTACLE, or the base ground terrain
// travel cost, depending on whether or not the door is open or closed etc.
BOOLEAN fDoorIsObstacleIfClosed=FALSE;
INT32 iDoorGridNo=-1;
DOOR_STATUS * pDoorStatus;
DOOR * pDoor;
STRUCTURE * pDoorStructure;
BOOLEAN fDoorIsOpen;
UINT8 ubReplacementCost;
if ( IS_TRAVELCOST_DOOR( ubMovementCost ) )
{
ubReplacementCost = TRAVELCOST_OBSTACLE;
switch( ubMovementCost )
{
case TRAVELCOST_DOOR_CLOSED_HERE:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = iGridNo;
ubReplacementCost = TRAVELCOST_DOOR;
break;
case TRAVELCOST_DOOR_CLOSED_N:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = iGridNo + dirDelta[ NORTH ];
ubReplacementCost = TRAVELCOST_DOOR;
break;
case TRAVELCOST_DOOR_CLOSED_W:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = iGridNo + dirDelta[ WEST ];
ubReplacementCost = TRAVELCOST_DOOR;
break;
case TRAVELCOST_DOOR_OPEN_HERE:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo;
break;
case TRAVELCOST_DOOR_OPEN_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NE:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ NORTHEAST ];
break;
case TRAVELCOST_DOOR_OPEN_E:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ EAST ];
break;
case TRAVELCOST_DOOR_OPEN_SE:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ SOUTHEAST ];
break;
case TRAVELCOST_DOOR_OPEN_S:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ SOUTH ];
break;
case TRAVELCOST_DOOR_OPEN_SW:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ SOUTHWEST ];
break;
case TRAVELCOST_DOOR_OPEN_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_NW:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ NORTHWEST ];
break;
case TRAVELCOST_DOOR_OPEN_N_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ NORTH ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NW_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ NORTHWEST ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NE_N:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ NORTHEAST ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_W_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ WEST ] + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_SW_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ SOUTHWEST ] + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_NW_W:
fDoorIsObstacleIfClosed = FALSE;
iDoorGridNo = iGridNo + dirDelta[ NORTHWEST ] + dirDelta[ WEST ];
break;
default:
break;
}
if ( pSoldier && (pSoldier->uiStatusFlags & SOLDIER_MONSTER || pSoldier->uiStatusFlags & SOLDIER_ANIMAL) )
{
// can't open doors!
ubReplacementCost = TRAVELCOST_OBSTACLE;
}
if (piDoorGridNo)
{
// return gridno of door through pointer
*piDoorGridNo = iDoorGridNo;
}
if ( fReturnPerceivedValue && gpWorldLevelData[ iDoorGridNo ].ubExtFlags[0] & MAPELEMENT_EXT_DOOR_STATUS_PRESENT )
{
// check door status
pDoorStatus = GetDoorStatus( (INT16) iDoorGridNo );
if (pDoorStatus)
{
fDoorIsOpen = (pDoorStatus->ubFlags & DOOR_PERCEIVED_OPEN) != 0;
}
else
{
// abort!
return( ubMovementCost );
}
}
else
{
// check door structure
pDoorStructure = FindStructure( (INT16) iDoorGridNo, STRUCTURE_ANYDOOR );
if (pDoorStructure)
{
fDoorIsOpen = (pDoorStructure->fFlags & STRUCTURE_OPEN) != 0;
}
else
{
// abort!
return( ubMovementCost );
}
}
// now determine movement cost
if (fDoorIsOpen)
{
if (fDoorIsObstacleIfClosed)
{
ubMovementCost = gTileTypeMovementCost[ gpWorldLevelData[ iGridNo ].ubTerrainID ];
}
else
{
ubMovementCost = ubReplacementCost;
}
}
else
{
if (fDoorIsObstacleIfClosed)
{
// door is closed and this should be an obstacle, EXCEPT if we are calculating
// a path for an enemy or NPC with keys
// creatures and animals can't open doors!
if ( fReturnPerceivedValue || ( pSoldier && (pSoldier->uiStatusFlags & SOLDIER_MONSTER || pSoldier->uiStatusFlags & SOLDIER_ANIMAL ) ) )
{
ubMovementCost = ubReplacementCost;
}
else
{
// have to check if door is locked and NPC does not have keys!
pDoor = FindDoorInfoAtGridNo( iDoorGridNo );
if ( pDoor )
{
if ( ( !pDoor->fLocked || (pSoldier && pSoldier->bHasKeys) ) && !fReturnDoorCost )
{
ubMovementCost = gTileTypeMovementCost[ gpWorldLevelData[ iGridNo ].ubTerrainID ];
}
else
{
ubMovementCost = ubReplacementCost;
}
}
else
{
ubMovementCost = ubReplacementCost;
}
}
}
else
{
ubMovementCost = gTileTypeMovementCost[ gpWorldLevelData[ iGridNo ].ubTerrainID ];
}
}
}
return( ubMovementCost );
}
UINT8 DoorTravelCost( SOLDIERTYPE * pSoldier, INT32 iGridNo, UINT8 ubMovementCost, BOOLEAN fReturnPerceivedValue, INT32 * piDoorGridNo )
{
return( InternalDoorTravelCost( pSoldier, iGridNo, ubMovementCost, fReturnPerceivedValue, piDoorGridNo, FALSE ) );
}