Files
source/Tactical/PATHAI.cpp
T
Overhaul dd0d6ea66d Fixed appearance of save game corruption when a game with different inventory set is loaded. Note that such games can still cause odd behavior and CTD's
Fixed old-style behavior when creating a soldier struct (caused memory corruption and leaks)
Fixed soldiers unable to fire if an error happened while firing the off-hand weapon
Fix animation system problems with soldiers interrupted while changing stance, such as twitching and not being able to navigate around obstacles
Prevent splitting money while an item is in the hand, which would result in losing the original item
Fix reverse of X,Y when checking visibility through a roof, which could make soldiers falsely visible/invisible
Prevent militia from simply waiting on the border when enemies are known to be in the sector
Fix stack overflow for soldier inventory debug display
Optimized A* pathing and cleaned up VS2K5 specific code
AI can now use all climb points for climbable buildings, not just a random few
Fix to not allow flat roof butted against slanted roof to be identified as a climb point
Prevent soldiers who cause an interrupt from a special action from continuing on their path after being interrupted
Prevent soldiers who enter or leave deep water from moving a space too far and turning back
Prevent soldiers from trying to crouch while in shallow water
Make soldier turn in the closest direction when he must get up from prone and turn then fall back to prone
Allow a soldier to swat for a tile before returning to prone if unable to return to prone immediately after a turn
Fix civilians that should show up in a sector, missing because of others that aren't
Fix a memset on a C++ class
Place Skyrider in a sector near his helecopter once available and the helicopter is present
Fix soldier corruption if too many shots burst-spread into too few tiles
Include both first and last tile in spread when some locations must be skipped
Fix to shoot at last location in burst
Fix to remove other previous spread locations when calculating a new spread
Fix to reload or rechamber off-hand when necessary, such as having a pistol shotgun in that hand


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@1532 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2007-10-20 08:09:03 +00:00

4706 lines
127 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"
#ifdef USE_ASTAR_PATHS
#include "BinaryHeap.hpp"
#include "AIInternals.h"
#include "opplist.h"
#include "weapons.h"
#endif
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 = TRUE;
#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 INT16 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
#ifdef USE_ASTAR_PATHS
//ADB the extra cover feature is supposed to pick a path of the same distance as one calculated with the feature off,
//but a safer path, usually farther away from an enemy or following behind some cover.
//however it has not been tested and it may need some work, I haven't touched it in a while
//#define ASTAR_USING_EXTRACOVER
using namespace std;
using namespace ASTAR;
AStarPathfinder::AStarPathfinder()
{
return;
}
//init the pointer to the AStarPathfinder singleton instance
// And init a static class so that it will destroy itself on game exit
static AStarPathfinder AStarThis;
AStarPathfinder* AStarPathfinder::pThis = &AStarThis;
AStarPathfinder& AStarPathfinder::GetInstance()
{
//if (pThis)
//{
// return *pThis;
//}
//pThis = new AStarPathfinder;
return *pThis;
}
void AStarPathfinder::ResetAStarList()
{
//only the starting node's data needs to be init,
//and all other data doesn't need to be at all
//for ( GridNode_t::iterator node = ClosedList.begin();
// node != ClosedList.end();
// node++)
//{
// SetAStarStatus(*node, AStar_Init);
// SetAStarG(*node, 0);
//}
//for (int size = OpenHeap.size(); size > 0; --size)
//{
// GridNode node = OpenHeap.peekElement(size).data;
// SetAStarStatus(node, AStar_Init);
// SetAStarG(node, 0);
//}
for (INT16 node = 0; node < WORLD_MAX; node++)
{
SetAStarStatus(node, AStar_Init);
SetAStarG(node, 0);
}
//ClosedList.clear();
OpenHeap.clear();
return;
}//end ResetAStarList
void AStarPathfinder::SetLoopState(const INT16 node,
int const loopState)
{
if ( loopState == LOOPING_REVERSE )
{
AStarData[node].wasBackwards = STEP_BACKWARDS;
}
else
{
AStarData[node].wasBackwards = false;
}
}
int AStarPathfinder::PythSpacesAway(const INT16 node1,
const INT16 node2)
{
int sRows,sCols,sResult;
GridNode *n1, *n2;
n1 = GridNode::MapXY + node1;
n2 = GridNode::MapXY + node2;
sRows = n1->x - n2->x;
sCols = n1->y - n2->y;
// apply Pythagoras's theorem for right-handed triangle:
// Except skip the square root. No reason to care about actual distance since we only need it for comparison.
// And conversion to double, then sqrt, then conversion back to int, is expensive.
sResult = (sRows * sRows) + (sCols * sCols);
return(sResult);
}
INT16 AStarPathfinder::SpacesAway(const INT16 node1,
const INT16 node2)
{
//should be faster than its counterpart
int sRows,sCols;
GridNode *n1, *n2;
n1 = GridNode::MapXY + node1;
n2 = GridNode::MapXY + node2;
sRows = abs(n1->x - n2->x);
sCols = abs(n1->y - n2->y);
return (INT16)(__max(sRows, sCols));
}
int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
INT16 dest,
INT8 ubLevel,
INT16 usMovementMode,
INT8 bCopy,
UINT8 fFlags )
{
//copy over private data that makes code more readable
this->pSoldier = s;
this->mercsMaxAPs = this->pSoldier->bActionPoints;
this->onRooftop = ubLevel;
this->movementMode = usMovementMode;
this->maxAPBudget = gubNPCAPBudget;
// Set the distance for this calc
this->gubNPCDistLimitSq = (int) gubNPCDistLimit * (int) gubNPCDistLimit;
StartNode = pSoldier->sGridNo;
DestNode = dest;
fFlags |= gubGlobalPathFlags;
if (StartNode < 0 || StartNode > WORLD_MAX)
{
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_RED, MSG_TESTVERSION, L"ERROR! Trying to calculate path from off-world gridno %d to %d", StartNode, DestNode );
#endif
return( 0 );
}
else if (!GridNoOnVisibleWorldTile( StartNode ) )
{
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_RED, MSG_TESTVERSION, L"ERROR! Trying to calculate path from non-visible gridno %d to %d", StartNode, DestNode );
#endif
return( 0 );
}
else if (pSoldier->bLevel != onRooftop)
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, different level" ) );
// pathing to a different level... bzzzt!
return( 0 );
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
}
gsCoverValue[ StartNode ] = 0;
PATHAI_VISIBLE_DEBUG_Counter = 1;
#endif
//init other private data, mostly flags
endDir = lastDir = direction = startDir = 0;
pStructureFileRef = NULL;
bLoopState = LOOPING_CLOCKWISE;
fCheckedBehind = FALSE;
fGoingThroughDoor = FALSE;
fTurnBased = ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) );
fPathingForPlayer = ( (pSoldier->bTeam == gbPlayerNum) && (!gTacticalStatus.fAutoBandageMode) && !(pSoldier->uiStatusFlags & SOLDIER_PCUNDERAICONTROL) );
fNonFenceJumper = !( IS_MERC_BODY_TYPE( pSoldier ) );
fNonSwimmer = !( IS_MERC_BODY_TYPE( pSoldier ) );
fPathAroundPeople = ( (fFlags & PATH_THROUGH_PEOPLE) == 0 );
fCloseGoodEnough = ( (fFlags & PATH_CLOSE_GOOD_ENOUGH) != 0);
fConsiderPersonAtDestAsObstacle = (BOOLEAN)( fPathingForPlayer && fPathAroundPeople && !(fFlags & PATH_IGNORE_PERSON_AT_DEST) );
if ( fNonSwimmer && Water( dest ) )
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, water" ) );
return( 0 );
}
if ( fCloseGoodEnough )
{
sClosePathLimitSq = __min( PythSpacesAway( StartNode, DestNode ) - 1, PATH_CLOSE_RADIUS_SQ );
if ( sClosePathLimitSq <= 0 )
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, path limit" ) );
return( 0 );
}
}
if (bCopy >= COPYREACHABLE)
{
fCopyReachable = TRUE;
fCopyPathCosts = (bCopy == COPYREACHABLE_AND_APS);
fVisitSpotsOnlyOnce = (bCopy == COPYREACHABLE || bCopy == FINDCLIMBPOINTS);
fFindClimbPoints = (bCopy == FINDCLIMBPOINTS);
// make sure we aren't trying to copy path costs for an area greater than the AI array...
if (fCopyPathCosts && gubNPCDistLimitSq > AI_PATHCOST_RADIUS*AI_PATHCOST_RADIUS)
{
// oy!!!! dis no supposed to happen!
gubNPCDistLimitSq = AI_PATHCOST_RADIUS*AI_PATHCOST_RADIUS;
gubNPCDistLimit = AI_PATHCOST_RADIUS;
}
}
else
{
fCopyReachable = FALSE;
fCopyPathCosts = FALSE;
fVisitSpotsOnlyOnce = FALSE;
}
gubNPCPathCount++;
if (DestNode != NOWHERE)
{
if (DestNode == pSoldier->sGridNo)
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, dest is start" ) );
return( 0 );
}
// the very first thing to do is make sure the destination tile is reachable
if (!NewOKDestination( pSoldier, dest, fConsiderPersonAtDestAsObstacle, onRooftop ))
{
maxAPBudget = 0;
gubNPCDistLimitSq = 0;
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, not ok dest" ) );
return( 0 );
}
}
#ifdef COUNT_PATHS
guiTotalPathChecks++;
#endif
#ifdef VEHICLE
fMultiTile = ((pSoldier->uiStatusFlags & SOLDIER_MULTITILE) != 0);
if ( fMultiTile == false)
{
fContinuousTurnNeeded = FALSE;
}
else
{
// Get animation surface...
// Chris_C... change this to use parameter.....
UINT16 usAnimSurface = DetermineSoldierAnimationSurface( pSoldier, movementMode );
// Get structure ref...
pStructureFileRef = GetAnimationStructureRef( pSoldier->ubID, usAnimSurface, movementMode );
if ( pStructureFileRef )
{
fContinuousTurnNeeded = ( ( pSoldier->uiStatusFlags & (SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE) ) != 0 );
/*
//bool fVehicle = ( (pSoldier->uiStatusFlags & SOLDIER_VEHICLE) != 0 );
if (fVehicle && pSoldier->bReverse)
{
fReverse = TRUE;
}
if (fVehicle || pSoldier->ubBodyType == COW || pSoldier->ubBodyType == BLOODCAT) // or a vehicle
{
fTurnSlow = TRUE;
}
*/
if ( gfEstimatePath )
{
bOKToAddStructID = IGNORE_PEOPLE_STRUCTURE_ID;
}
else if ( pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL )
{
bOKToAddStructID = pSoldier->pLevelNode->pStructureData->usStructureID;
}
else
{
bOKToAddStructID = INVALID_STRUCTURE_ID;
}
}
else
{
// turn off multitile pathing
fMultiTile = FALSE;
fContinuousTurnNeeded = FALSE;
}
}
#endif
if (fContinuousTurnNeeded == false)
{
bLoopState = LOOPING_CLOCKWISE;
}
// if origin and dest is water, then user wants to stay in water!
// so, check and set waterToWater flag accordingly
if (dest == NOWHERE)
{
bWaterToWater = false;
}
else
{
if (ISWATER(gubWorldMovementCosts[StartNode][0][onRooftop]) && ISWATER(gubWorldMovementCosts[DestNode][0][onRooftop]))
{
bWaterToWater = true;
}
else
{
bWaterToWater = false;
}
}
//make sure all data is init ok
ResetAStarList();
//Start the main loop and get a path
int bestPath = AStar();
gubNPCAPBudget = 0;
gubNPCDistLimitSq = 0;
if (bestPath == -222)
{
//error, path not found
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, path not found" ) );
return 0;
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
SetRenderFlags( RENDER_FLAG_FULL );
// The RenderCoverDebugInfo call is now made by RenderWorld. So don't try to call it here
#if 0
if ( guiCurrentScreen == GAME_SCREEN )
{
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
}
#endif
}
#endif
// Count the number of steps, but keep it less than the max path length.
// Adjust the parent until it begins at the tail end of the max path length (or the dest if reachable)
INT16 parent = DestNode;
INT16 current = parent;
unsigned int sizePath = 0;
while (current != -1)
{
sizePath += GetNumSteps( current);
while (sizePath > MAX_PATH_LIST_SIZE)
{
sizePath -= GetNumSteps( parent);
parent = GetAStarParent( parent);
}
current = GetAStarParent( current);
}
// 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)
{
pSoldier->usPathDataSize = (UINT16) sizePath;
pSoldier->usPathIndex = 0;
while (sizePath > 0)
{
int numSteps = GetNumSteps(parent);
while (numSteps > 0)
{
sizePath--;
numSteps--;
pSoldier->usPathingData[sizePath] = GetDirection(parent);
}
parent = GetAStarParent( parent);
}
// Since the sizePath began as the total path size, it should be 0 when we finish copying it
Assert( sizePath == 0);
sizePath = pSoldier->usPathDataSize;
}
else if (bCopy == NO_COPYROUTE)
{
giPathDataSize = (UINT16) sizePath;
while (sizePath > 0)
{
int numSteps = GetNumSteps(parent);
while (numSteps > 0)
{
sizePath--;
numSteps--;
guiPathingData[sizePath] = GetDirection(parent);
}
parent = GetAStarParent( parent);
}
// Since the sizePath began as the total path size, it should be 0 when we finish copying it
Assert( sizePath == 0);
sizePath = giPathDataSize;
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
SetRenderFlags( RENDER_FLAG_FULL );
#if 0
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
#endif
}
#endif
#ifdef COUNT_PATHS
guiSuccessfulPathChecks++;
#endif
//TEMP: This is returning zero when I am generating edgepoints, so I am force returning 1 until
// this is fixed?
if( gfGeneratingMapEdgepoints )
{
return 1;
}
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path ok, path size %d", sizePath ) );
// return path length : serves as a "successful" flag and a path length counter
return sizePath;
}//end GetPath
INT16 AStarPathfinder::AStar()
{
HEAP<int, INT16> TopHeap(StartNode, 0);
ParentNode = StartNode;
this->travelcostOrth = CalcAP( TRAVELCOST_FLAT, 0);
this->travelcostDiag = CalcAP( TRAVELCOST_FLAT, 1);
// Since the startup cost is always charged for the first node, why not make it the source node's cost?
//init the starting node's data that hasn't been reset
SetAStarParent(StartNode, -1);
SetNumSteps(StartNode, 0);
SetAStarG( StartNode, CalcStartingAP());
for (;;)
{
if ( fCloseGoodEnough )
{
if ( PythSpacesAway( CurrentNode, DestNode ) <= sClosePathLimitSq )
{
// stop the path here!
DestNode = CurrentNode;
fCloseGoodEnough = FALSE;
}
}
if (ParentNode == DestNode)
{
//the best path to date leads to the dest
//this node is neither in the open heap or the closed list, make sure it gets reset ok.
//ClosedList.push_back(ParentNode);
//We just clear the entire array before starting, so no prob.
break;
}
//this adds to the heap nodes that might be on a path
ExecuteAStarLogic();
if (OpenHeap.size() == 0)
{
return -222;//error code meaning no path at all!
}
//get the best point so far from the heap
TopHeap = OpenHeap.popTopHeap();
ParentNode = TopHeap.data;
//ASSERT(GetAStarStatus(ParentNode) == AStar_Open);
}
return GetAStarG( TopHeap.data);
}//end AStar
void AStarPathfinder::IncrementLoop()
{
startingLoop = false;
if ( fContinuousTurnNeeded && direction == gOppositeDirection[ startDir ] )
{
fCheckedBehind = TRUE;
}
if (bLoopState == LOOPING_CLOCKWISE)
{// backwards
direction = gOneCCDirection[ direction ];
}
else
{
direction = gOneCDirection[ direction ];
}
return;
}
void AStarPathfinder::InitLoop()
{
startingLoop = true;
direction = startDir;
return;
}
bool AStarPathfinder::ContinueLoop()
{
if (direction == endDir && startingLoop == false)
{
return false;
}
return true;
}
void AStarPathfinder::ExecuteAStarLogic()
{
//parent node set in AStar
//this node is now closed
SetAStarStatus(ParentNode, AStar_Closed);
//ClosedList.push_back(ParentNode);
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
if (gsCoverValue[ ParentNode ] > 0)
{
gsCoverValue[ ParentNode ] *= -1;
}
}
#endif
// Shouldn't G and AP be the same thing?
INT16 baseGCost = GetAStarG(ParentNode);
//int baseAP = GetActionPoints(ParentNode);
if (fCopyReachable)
{
//if (GetPrevCost(ParentNode) != TRAVELCOST_FENCE)
{
gpWorldLevelData[ParentNode].uiFlags |= MAPELEMENT_REACHABLE;
if (gubBuildingInfoToSet > 0)
{
gubBuildingInfo[ ParentNode ] = gubBuildingInfoToSet;
}
}
}
if (fContinuousTurnNeeded)
{
INT16 parent = GetAStarParent(ParentNode);
if (parent == -1)
{
lastDir = pSoldier->ubDirection;
}
else if ( GetLoopState(parent) == false )
{
lastDir = GetDirection(parent);
}
else
{
lastDir = gOppositeDirection[ GetDirection(parent) ];
}
startDir = lastDir;
endDir = lastDir;
bLoopState = LOOPING_CLOCKWISE;
fCheckedBehind = FALSE;
}
else
{
//startDir and endDir are init to 0, and not reinit after a loop in the original code
}
//because startDir and endDir are set to the same thing, and to easily use
//continue along with the loop increment at the end of the loop, use functions
for (InitLoop(); ContinueLoop(); IncrementLoop())
{
int prevCost = 1;
CurrentNode = ParentNode + dirDelta[direction];
//if the node is not in the world, or it has already been searched, continue
if (CurrentNode < 0 ||
CurrentNode >= WORLD_MAX ||
GetAStarStatus(CurrentNode) == AStar_Closed)
{
continue;
}
#ifdef VEHICLE
//has side effects, including setting loop counters
int retVal = VehicleObstacleCheck();
if (retVal == 1)
{
continue;
}
else if (retVal == 2)
{
return;
}
#endif
//calc the cost to move from the current node to here
INT16 terrainCost = EstimateActionPointCost( pSoldier, CurrentNode, direction, movementMode, 0, 3 );
if (terrainCost == 100) {
if (fFindClimbPoints)
{
if ((direction & 1) == 0 &&
NewOKDestination( pSoldier, CurrentNode, FALSE, 0) &&
FindStructure( CurrentNode, STRUCTURE_ROOF ) == NULL)
{
gpWorldLevelData[ CurrentNode ].ubExtFlags[0] |= MAPELEMENT_EXT_CLIMBPOINT;
gpWorldLevelData[ ParentNode ].ubExtFlags[1] |= MAPELEMENT_EXT_CLIMBPOINT;
#ifdef ROOF_DEBUG
gsCoverValue[CurrentNode] = 1;
#endif
}
}
//an error code like diagonal door or obstruction was returned
continue;
}
if (gubWorldMovementCosts[CurrentNode][direction][pSoldier->bLevel] == TRAVELCOST_FENCE)
{
SetAStarStatus(CurrentNode, AStar_Closed);
CurrentNode = CurrentNode + dirDelta[direction];
if (!NewOKDestination( pSoldier, CurrentNode, fPathAroundPeople, pSoldier->bLevel))
{
continue;
}
prevCost = 2;
}
//if the node is not traversible, continue
if (WantToTraverse() == false)
{
continue;
}
// 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 (IsSomeoneInTheWay() == true) {
continue;
}
//int movementG = TerrainCostToAStarG(terrainCost);
//INT16 movementG = CalcAP( terrainCost, direction);
INT16 movementG;
//movementG = (INT16) CalcG( &prevCost);
//movementG = CalcAP( movementG, direction);
movementG = terrainCost * 100;
INT16 AStarG = baseGCost + movementG;
//if the node is more costly in this path than in another open path, continue
if (GetAStarStatus(CurrentNode) == AStar_Open
&& AStarG >= GetAStarG(CurrentNode))
{
continue;
}
if (maxAPBudget && AStarG > maxAPBudget * 100)
{
continue;
}
int extraGCoverCost = 0;
#ifdef ASTAR_USING_EXTRACOVER
//check if we will run out of AP while entering this node or before
//if we run out, the merc will stop at the parent node for a turn and be vulnerable
if (mercsMaxAPs && APCost > mercsMaxAPs)
{
extraGCoverCost = GetExtraGCover(ParentNode);
//if the parent did not have the cost cached, then calc it
if (extraGCoverCost == -1)
{
//use the stance and cover to see how much we really want to stop at the parent node
//as opposed to an equal path with different cover
//because other nodes further on the path will stop here too, add this value to the F cost
extraGCoverCost = CalcGCover(ParentNodeIndex, APCost);
//remember, we have run out of points to *enter* this node, so we are stuck at the *parent* node
//cache the cost to stay at the parent node
SetExtraGCover(ParentNode, extraGCoverCost);
}
else
{
//the parent node is too long, all other nodes from start node
//will be too long too, and have the extra cost already added
}
}
#endif
int AStarH = CalcH();
int AStarF = (AStarG + extraGCoverCost) + AStarH;
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
if (gsCoverValue[CurrentNode] == 0x7F7F)
{
//gsCoverValue[CurrentNode] = PATHAI_VISIBLE_DEBUG_Counter++;
gsCoverValue[CurrentNode] = (INT16) AStarF;
}
/*
else if (gsCoverValue[CurrentNodeIndex] >= 0)
{
gsCoverValue[CurrentNodeIndex]++;
}
else
{
gsCoverValue[CurrentNodeIndex]--;
}
*/
}
#endif
//insert this node onto the heap
if (GetAStarStatus(CurrentNode) == AStar_Init)
{
OpenHeap.insertElement(CurrentNode, AStarF);
}
else
{
OpenHeap.editElement(CurrentNode, AStarF);
}
SetAStarStatus(CurrentNode, AStar_Open);
//SetAStarF(CurrentNode, AStarF);
SetAStarG(CurrentNode, AStarG);
//SetActionPoints(CurrentNode, APCost);
SetAStarParent(CurrentNode, ParentNode);
SetLoopState(CurrentNode, bLoopState);
SetDirection(CurrentNode, direction);
//SetPrevCost(CurrentNode, prevCost);
SetExtraGCover(CurrentNode, extraGCoverCost);
// Set the number of steps for this tile. It's 2 if we are hopping a fence
SetNumSteps( CurrentNode, prevCost);
if (maxAPBudget)
{
if (fCopyPathCosts)
{
//This is used by FindBestNearbyCover to look for something that can be reached this turn
GridNode *n1, *n2;
n1 = GridNode::MapXY + CurrentNode;
n2 = GridNode::MapXY + StartNode;
int iX = AI_PATHCOST_RADIUS + n1->x - n2->x;
int iY = AI_PATHCOST_RADIUS + n1->y - n2->y;
Assert( iX >= 0 && iX <= AI_PATHCOST_RADIUS*2 && iY >= 0 && iY <= AI_PATHCOST_RADIUS*2);
gubAIPathCosts[iX][iY] = (INT8) AStarG;
}
}
}
return;
}//end ExecuteAStarLogic
INT16 AStarPathfinder::CalcStartingAP()
{
// Add to points, those needed to start from different stance!
INT16 startingAPCost = MinAPsToStartMovement( pSoldier, movementMode );
// We should reduce points for starting to run if first tile is a fence...
if ( gubWorldMovementCosts[ CurrentNode ][ direction ][ onRooftop ] == TRAVELCOST_FENCE )
{
if ( movementMode == RUNNING && pSoldier->usAnimState != RUNNING )
{
startingAPCost = startingAPCost - AP_START_RUN_COST;
}
}
return startingAPCost;
}
int AStarPathfinder::TerrainCostToAStarG(int const terrainCost)
{
int movementG;
// NOTE: on September 24, 1997, Chris went back to a diagonal bias system
if (IsDiagonal(direction) == true)
{
movementG = terrainCost * 14;
}
else
{
movementG = terrainCost * 10;
}
if ( bLoopState == LOOPING_REVERSE)
{
// penalize moving backwards to encourage turning sooner
movementG += 500;
}
return movementG;
}
INT16 AStarPathfinder::CalcAP(int const terrainCost, UINT8 const direction)
{
// NEW Apr 21 by Ian: abort if cost exceeds budget
INT16 movementAPCost;
switch(terrainCost)
{
case TRAVELCOST_NONE: movementAPCost = 0; break;
case TRAVELCOST_DIRTROAD:
case TRAVELCOST_FLAT: movementAPCost = AP_MOVEMENT_FLAT; break;
//case TRAVELCOST_BUMPY:
case TRAVELCOST_GRASS: movementAPCost = AP_MOVEMENT_GRASS; break;
case TRAVELCOST_THICK: movementAPCost = AP_MOVEMENT_BUSH; break;
case TRAVELCOST_DEBRIS: movementAPCost = AP_MOVEMENT_RUBBLE; break;
case TRAVELCOST_SHORE: movementAPCost = AP_MOVEMENT_SHORE; break; // wading shallow water
case TRAVELCOST_KNEEDEEP: movementAPCost = AP_MOVEMENT_LAKE; break; // wading waist/chest deep - very slow
case TRAVELCOST_DEEPWATER: movementAPCost = AP_MOVEMENT_OCEAN; break; // can swim, so it's faster than wading
//case TRAVELCOST_VEINEND:
//case TRAVELCOST_VEINMID: movementAPCost = AP_MOVEMENT_FLAT; break;
//case TRAVELCOST_DOOR: movementAPCost = AP_MOVEMENT_FLAT; break;
case TRAVELCOST_FENCE: movementAPCost = AP_JUMPFENCE; break;
case TRAVELCOST_OBSTACLE:
default: return -1; // Cost too much to be considered!
}
// don't make the mistake of adding directly to
// ubCurAPCost, that must be preserved for remaining dirs!
if (IsDiagonal(direction))
{
movementAPCost = (movementAPCost * 14) / 10;
}
int movementModeToUseForAPs = movementMode;
// ATE: if water, force to be walking always!
if ( terrainCost == TRAVELCOST_SHORE || terrainCost == TRAVELCOST_KNEEDEEP || terrainCost == TRAVELCOST_DEEPWATER )
{
movementModeToUseForAPs = WALKING;
}
// adjust AP cost for movement mode
switch( movementModeToUseForAPs )
{
case RUNNING:
case ADULTMONSTER_WALKING:
// save on casting
movementAPCost = (INT16) (movementAPCost * 100 / ( (UINT8) (RUNDIVISOR * 100)));
//movementAPCost = (INT16)(DOUBLE)( (sTileCost / RUNDIVISOR) ); break;
break;
case WALKING:
case ROBOT_WALK:
movementAPCost = (movementAPCost + WALKCOST);
break;
case SWATTING:
movementAPCost = (movementAPCost + SWATCOST);
break;
case CRAWLING:
movementAPCost = (movementAPCost + CRAWLCOST);
break;
}
if (terrainCost == TRAVELCOST_FENCE)
{
switch( movementModeToUseForAPs )
{
case RUNNING:
case WALKING :
// Here pessimistically assume the path will continue after hopping the fence
movementAPCost += AP_CROUCH;
break;
case SWATTING:
// Add cost to stand once there BEFORE jumping....
movementAPCost += AP_CROUCH;
break;
case CRAWLING:
// Can't do it here.....
return -1;
}
}
else if (terrainCost == TRAVELCOST_NOT_STANDING)
{
switch(movementModeToUseForAPs)
{
case RUNNING:
case WALKING :
// charge crouch APs for ducking head!
movementAPCost += AP_CROUCH;
break;
default:
break;
}
}
else if (fGoingThroughDoor)
{
// Uh, there IS a cost to close the door too!
// Then if the door has to be unlocked to open there's the cost of locking and unlocking.
movementAPCost += AP_OPEN_DOOR + AP_OPEN_DOOR;
fGoingThroughDoor = FALSE;
}
return movementAPCost;
}
int AStarPathfinder::CalcG(int* pPrevCost)
{
//how much is admission to the next tile
if ( gfPathAroundObstacles == false)
{
return TRAVELCOST_FLAT;
}
int nextCost = gubWorldMovementCosts[ CurrentNode ][ direction ][ onRooftop ];
*pPrevCost = nextCost;
//if we are holding down shift and finding a direct path, count non obstacles as flat terrain
if (gfPlotDirectPath && nextCost < NOPASS && nextCost != 0)
{
return TRAVELCOST_FLAT;
}
//performance: if nextCost is low then do not do many many if == checks
if ( nextCost >= TRAVELCOST_FENCE )
{
//ATE: Check for differences from reality
// Is next cost an obstcale
if ( nextCost == TRAVELCOST_HIDDENOBSTACLE )
{
if ( fPathingForPlayer )
{
// Is this obstacle a hidden tile that has not been revealed yet?
BOOLEAN fHiddenStructVisible;
if( DoesGridnoContainHiddenStruct( CurrentNode, &fHiddenStructVisible ) )
{
// Are we not visible, if so use terrain costs!
if ( !fHiddenStructVisible )
{
// Set cost of terrain!
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
}
}
}
else if ( nextCost == TRAVELCOST_NOT_STANDING )
{
// for path plotting purposes, use the terrain value
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
else if ( nextCost == TRAVELCOST_EXITGRID )
{
if (gfPlotPathToExitGrid)
{
// replace with terrain cost so that we can plot path, otherwise is obstacle
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
}
else if ( nextCost == TRAVELCOST_FENCE && fNonFenceJumper )
{
return -1;
}
else if ( IS_TRAVELCOST_DOOR( nextCost ) )
{
// don't let anyone path diagonally through doors!
if (IsDiagonal(direction) == true)
{
return -1;
}
INT16 iDoorGridNo = CurrentNode;
bool fDoorIsObstacleIfClosed = FALSE;
bool fDoorIsOpen = false;
switch( nextCost )
{
case TRAVELCOST_DOOR_CLOSED_HERE:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = CurrentNode;
break;
case TRAVELCOST_DOOR_CLOSED_N:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = CurrentNode + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_CLOSED_W:
fDoorIsObstacleIfClosed = TRUE;
iDoorGridNo = CurrentNode + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_HERE:
iDoorGridNo = CurrentNode;
break;
case TRAVELCOST_DOOR_OPEN_N:
iDoorGridNo = CurrentNode + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NE:
iDoorGridNo = CurrentNode + dirDelta[ NORTHEAST ];
break;
case TRAVELCOST_DOOR_OPEN_E:
iDoorGridNo = CurrentNode + dirDelta[ EAST ];
break;
case TRAVELCOST_DOOR_OPEN_SE:
iDoorGridNo = CurrentNode + dirDelta[ SOUTHEAST ];
break;
case TRAVELCOST_DOOR_OPEN_S:
iDoorGridNo = CurrentNode + dirDelta[ SOUTH ];
break;
case TRAVELCOST_DOOR_OPEN_SW:
iDoorGridNo = CurrentNode + dirDelta[ SOUTHWEST ];
break;
case TRAVELCOST_DOOR_OPEN_W:
iDoorGridNo = CurrentNode + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_NW:
iDoorGridNo = CurrentNode + dirDelta[ NORTHWEST ];
break;
case TRAVELCOST_DOOR_OPEN_N_N:
iDoorGridNo = CurrentNode + dirDelta[ NORTH ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NW_N:
iDoorGridNo = CurrentNode + dirDelta[ NORTHWEST ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_NE_N:
iDoorGridNo = CurrentNode + dirDelta[ NORTHEAST ] + dirDelta[ NORTH ];
break;
case TRAVELCOST_DOOR_OPEN_W_W:
iDoorGridNo = CurrentNode + dirDelta[ WEST ] + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_SW_W:
iDoorGridNo = CurrentNode + dirDelta[ SOUTHWEST ] + dirDelta[ WEST ];
break;
case TRAVELCOST_DOOR_OPEN_NW_W:
iDoorGridNo = CurrentNode + dirDelta[ NORTHWEST ] + dirDelta[ WEST ];
break;
default:
break;
}
if ( fPathingForPlayer && gpWorldLevelData[ iDoorGridNo ].ubExtFlags[0] & MAPELEMENT_EXT_DOOR_STATUS_PRESENT )
{
// check door status
DOOR_STATUS* pDoorStatus = GetDoorStatus( iDoorGridNo );
if (pDoorStatus)
{
fDoorIsOpen = (pDoorStatus->ubFlags & DOOR_PERCEIVED_OPEN) != 0;
}
else
{
// door destroyed?
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
}
else
{
// check door structure
STRUCTURE* pDoorStructure = FindStructure( iDoorGridNo, STRUCTURE_ANYDOOR );
if (pDoorStructure)
{
fDoorIsOpen = (pDoorStructure->fFlags & STRUCTURE_OPEN) != 0;
}
else
{
// door destroyed?
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
}
// now determine movement cost... if it hasn't been changed already
if ( IS_TRAVELCOST_DOOR( nextCost ) )
{
if (fDoorIsOpen)
{
if (fDoorIsObstacleIfClosed)
{
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].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 || ( pSoldier && (pSoldier->uiStatusFlags & (SOLDIER_MONSTER | SOLDIER_ANIMAL) ) ) )
{
nextCost = TRAVELCOST_OBSTACLE;
}
else
{
// have to check if door is locked and NPC does not have keys!
// This function has an inaccurate name. It is actually checking if the door has LOCK info.
DOOR* pDoor = FindDoorInfoAtGridNo( iDoorGridNo );
if (pDoor)
{
if (!pDoor->fLocked || pSoldier->bHasKeys)
{
// add to AP cost
//if (maxAPBudget)
{
fGoingThroughDoor = TRUE;
}
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
else
{
nextCost = TRAVELCOST_OBSTACLE;
}
}
else
{
// The door is closed, so we still have to open it
fGoingThroughDoor = TRUE;
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
}
}
else
{
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];
}
}
}
}
else if ( fNonSwimmer && ISWATER( gpWorldLevelData[ CurrentNode ].ubTerrainID))
{
// 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 ) )
{
return -1;
}
// make water cost attractive for water to water paths
// Why? If a path through water gets you there sooner, you shouldn't need
// artificial inflation to figure that out. And if not, then get out of the water!
//if (bWaterToWater && ISWATER(nextCost) )
//{
// nextCost = EASYWATERCOST;
//}
return nextCost;
}//end CalcG
int AStarPathfinder::CalcH()
{
if ( fCopyReachable )
{
//if this is a reachability test, turn AStar into breadth first Dijkstra
return 0; // Technically speaking, 0 makes it Dijkstra best-first search
}
// #define ESTIMATEC ( ( (dx<dy) ? (TRAVELCOST_BUMPY * (dx * 14 + dy * 10) / 10) : ( TRAVELCOST_BUMPY * (dy * 14 + dx * 10) / 10 ) ) )
//the original calcH grossly overestimates,
//one reason is it uses BUMPY not FLAT!
//assuming x dist and y dist are the same, that it is totally diagonal
//ESTIMATEC returns the diagonal dist plus an extra straight distance!!!
//furthermore let's not lose precision by dividing by 10, as long as 10 is taken into account elsewhere
//this also helps with cover cost precision where it is needed more
//I changed the cost scale to APs, so TRAVELCOST * anything is a gross overestimate. Let's do something
//more on an AP scale. The minimum between X and Y is calculated as a diagonal traversal and the remainder
//is calculated as straight traversal.
GridNode *n1, *n2;
n1 = GridNode::MapXY + CurrentNode;
n2 = GridNode::MapXY + DestNode;
int x = abs(n1->x - n2->x);
int y = abs(n1->y - n2->y);
#if 0
if (x >= y)
{
return this->travelcostDiag * y + this->travelcostOrth * (x-y);
}
else
{
return this->travelcostDiag * x + this->travelcostOrth * (y-x);
}
#else
// Try a real distance method. This should underestimate in some cases
// However, the distances need to be increased for the moment because running orthogonal is 1AP while running diagonal is 2AP
// so the total to reach a diagonal tile is identical for 2 moves. So we have to trick the pathing calc into thinking it's
// a longer distance and also calculate the other costs accordingly.
x *= 100;
y *= 100;
int d = x*x + y*y;
int r = 1200; // Just a guess
if (d == 0)
{
return d;
}
while (1)
{
int gr = (r + (d/r)) / 2;
if (gr == r || gr == r+1)
{
break;
}
r = gr;
}
return r * travelcostOrth;
#endif
}
#ifdef ASTAR_USING_EXTRACOVER
int AStarPathfinder::CalcGCover(int const NodeIndex,
int const APCost)
{
// There's no cover when boxing!
if (gTacticalStatus.bBoxingState == BOXING) {
return 0;
}
std::vector<THREATTYPE> Threats;
// all 32-bit integers for max. speed
INT32 iCurrentCoverValue;
INT32 iThreatCertainty;
INT32 iThreatRange, iClosestThreatRange = 1500;
INT32 iMyThreatValue;
INT16 sThreatLoc;
UINT32 uiThreatCnt = 0;
INT16 * pusLastLoc;
INT8 * pbPersOL;
INT8 * pbPublOL;
//although we have run out of APs to get here, it could just mean we have some APs but not enough to enter
int APLeft = this->mercsMaxAPs - APCost;
// BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplists
pusLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]);
// hang a pointer into personal opplist
pbPersOL = &(pSoldier->bOppList[0]);
// hang a pointer into public opplist
pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]);
// 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 (UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) {
SOLDIERTYPE* pOpponent = MercSlots[ uiLoop ];
// if this merc is inactive, at base, on assignment, dead, unconscious
if (!pOpponent || pOpponent->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->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->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 = GetRangeInCellCoordsFromGridNoDiff( NodeIndex, sThreatLoc );
#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 > MAX_THREAT_RANGE) {
continue; // check next opponent
}
Threats.push_back(THREATTYPE());
// remember this opponent as a current threat, but DON'T REDUCE FOR COVER!
Threats[uiThreatCnt].iValue = CalcManThreatValue(pOpponent, NodeIndex, FALSE, pSoldier);
// if the opponent is no threat at all for some reason
if (Threats[uiThreatCnt].iValue == -999) {
continue; // check next opponent
}
Threats[uiThreatCnt].pOpponent = pOpponent;
Threats[uiThreatCnt].sGridNo = sThreatLoc;
Threats[uiThreatCnt].iCertainty = iThreatCertainty;
Threats[uiThreatCnt].iOrigRange = iThreatRange;
// calculate how many APs he will have at the start of the next turn
Threats[uiThreatCnt].iAPs = CalcActionPoints(pOpponent);
if (iThreatRange < iClosestThreatRange) {
iClosestThreatRange = iThreatRange;
}
uiThreatCnt++;
}
// if no known opponents were found to threaten us, can't worry about cover
if (uiThreatCnt == 0) {
return 0;
}
// calculate our current cover value in the place we are now
iCurrentCoverValue = 0;
// for every opponent that threatens, consider this spot's cover vs. him
for (UINT32 uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++) {
// if this threat is CURRENTLY within 20 tiles
if (Threats[uiLoop].iOrigRange <= MAX_THREAT_RANGE) {
// add this opponent's cover value to our current total cover value
iCurrentCoverValue += CalcCoverValue(NodeIndex, iMyThreatValue, APLeft,
Threats[uiLoop].iOrigRange, Threats[uiLoop].sGridNo, Threats[uiLoop].pOpponent,
Threats[uiLoop].iValue, Threats[uiLoop].iAPs, Threats[uiLoop].iCertainty);
}
}
//body shield!
iCurrentCoverValue -= ( ( iCurrentCoverValue * NumberOfTeamMatesAdjacent( pSoldier, NodeIndex ) ) / 10 );
//make the cost less than one TRAVELCOST_FLAT's worth so that this cost doesn't alter the path's APs
return (min (iCurrentCoverValue, (TRAVELCOST_FLAT * 10 - 1)));
}
int AStarPathfinder::CalcCoverValue(INT16 sMyGridNo, INT32 iMyThreat, INT32 iMyAPsLeft,
INT32 myThreatsiOrigRange, INT16 myThreatssGridNo, SOLDIERTYPE* myThreatspOpponent,
INT32 myThreatsiValue, INT32 myThreatsiAPs, INT32 myThreatsiCertainty)
{
SOLDIERTYPE* pMe = this->pSoldier;
INT32 morale = pSoldier->bAIMorale;
INT32 iRange = myThreatsiOrigRange;
// all 32-bit integers for max. speed
INT16 sHisGridNo, sMyRealGridNo, sHisRealGridNo = NOWHERE;
INT16 sTempX, sTempY;
FLOAT dMyX, dMyY, dHisX, dHisY;
INT8 bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT;
SOLDIERTYPE *pHim;
dMyX = dMyY = dHisX = dHisY = -1.0;
pHim = myThreatspOpponent;
sHisGridNo = myThreatssGridNo;
// THE FOLLOWING STUFF IS *VEERRRY SCAARRRY*, BUT SHOULD WORK. IF YOU REALLY
// HATE IT, THEN CHANGE ChanceToGetThrough() TO WORK FROM A GRIDNO TO GRIDNO
// this is theoretical, and I'm not actually at sMyGridNo right now
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 = CalcWorstCTGTForPosition( pHim, pMe->ubID, sMyGridNo, pMe->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->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;
}
// 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->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
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
float HisPosValue = (float) (bHisCTGT * myThreatsiValue * myThreatsiAPs);
float MyPosValue = (float) (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->bOppCnt > 1)
{
HisPosValue /= pHim->bOppCnt;
}
if (pMe->bOppCnt > 1)
{
MyPosValue /= pMe->bOppCnt;
}
// if my positional value is worth something at all here
if (MyPosValue > 0.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
//ADB and if I'm not already crouching
if (iMyAPsLeft < AP_CROUCH && movementMode != SWATTING)
// subtract another 1 % penalty for NOT being able to crouch per tile
// the farther away we are, the bigger a difference crouching will make!
MyPosValue -= ((MyPosValue * (AIM_PENALTY_TARGET_CROUCHED + (iRange / CELL_X_SIZE))) / 100);
}
// the farther apart we are, the less important the cover differences are
// the less certain his position, the less important cover differences are
float ReductionFactor = float (((MAX_THREAT_RANGE - iRange) * myThreatsiCertainty) /
(MAX_THREAT_RANGE * myThreatsiCertainty));
int iCoverValue = (int) (((HisPosValue - MyPosValue) / HisPosValue) * ReductionFactor);
return( max(iCoverValue, 0) );
}
#endif //#ifdef ASTAR_USING_EXTRACOVER
#ifdef VEHICLE
void AStarPathfinder::InitVehicle()
{
fMultiTile = ((pSoldier->uiStatusFlags & SOLDIER_MULTITILE) != 0);
if (fMultiTile)
{
// Get animation surface...
// Chris_C... change this to use parameter.....
UINT16 usAnimSurface = DetermineSoldierAnimationSurface( pSoldier, movementMode );
// Get structure ref...
pStructureFileRef = GetAnimationStructureRef( pSoldier->ubID, usAnimSurface, movementMode );
if ( pStructureFileRef )
{
fContinuousTurnNeeded = ( ( pSoldier->uiStatusFlags & (SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE) ) != 0 );
/*
//bool fVehicle = ( (pSoldier->uiStatusFlags & SOLDIER_VEHICLE) != 0 );
if (fVehicle && pSoldier->bReverse)
{
fReverse = TRUE;
}
if (fVehicle || pSoldier->ubBodyType == COW || pSoldier->ubBodyType == BLOODCAT) // or a vehicle
{
fTurnSlow = TRUE;
}
*/
if ( gfEstimatePath )
{
bOKToAddStructID = IGNORE_PEOPLE_STRUCTURE_ID;
}
else if ( pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL )
{
bOKToAddStructID = pSoldier->pLevelNode->pStructureData->usStructureID;
}
else
{
bOKToAddStructID = INVALID_STRUCTURE_ID;
}
}
else
{
// turn off multitile pathing
fMultiTile = FALSE;
fContinuousTurnNeeded = FALSE;
}
}
else
{
fContinuousTurnNeeded = FALSE;
}
return;
}
int AStarPathfinder::VehicleObstacleCheck()
{
/*
if (fTurnSlow) {
if (iLastDir == iPrevToLastDir) {
if ( iCnt != iLastDir && iCnt != gOneCDirection[ iLastDir ] && iCnt != gOneCCDirection[ iLastDir ]) {
goto NEXTDIR;
}
}
else {
if ( iCnt != iLastDir ) {
goto NEXTDIR;
}
}
}
*/
int iStructIndex;
if ( bLoopState == LOOPING_REVERSE )
{
iStructIndex = gOppositeDirection[ gOneCDirection[ direction ] ];
}
else {
iStructIndex = gOneCDirection[ direction ];
}
if (fMultiTile)
{
if ( fContinuousTurnNeeded )
{
if ( direction != lastDir )
{
if ( !OkayToAddStructureToWorld( CurrentNode, onRooftop, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), bOKToAddStructID ) )
{
// 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)
{
startDir = lastDir;
endDir = direction;
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
direction = startDir;
return 1;
//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;
startDir = gOppositeDirection[ gOneCCDirection[ lastDir ] ];
endDir = (startDir + 2) % MAXDIR;
direction = startDir;
fCheckedBehind = TRUE;
return 1;
//goto NEXTDIR;
}
else
{
// done
//goto ENDOFLOOP;
return 2;
}
}
}
}//end fContinuousTurnNeeded
// check to make sure it's okay for us to turn to the new direction in our current tile
// 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!
// 0verhaul: The turn happens before the move, and the check for change dir happens on the turn. So
// we have to check whether the struct can be added at the PARENT node, as well as the intended new node.
if (!OkayToAddStructureToWorld( ParentNode, onRooftop, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), bOKToAddStructID ) ||
!OkayToAddStructureToWorld( CurrentNode, onRooftop, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), bOKToAddStructID ))
{
return 1;
//goto NEXTDIR;
}
}
return 0;
}
#endif
bool AStarPathfinder::WantToTraverse()
{
// 0verhaul: Cannot change direction over a fence!
// Decided on a different way to handle this
//if (GetPrevCost(ParentNode) == TRAVELCOST_FENCE && GetDirection(ParentNode) != direction)
//{
// return false;
//}
if ( fVisitSpotsOnlyOnce && GetAStarStatus(CurrentNode) != AStar_Init )
{
// on a "reachable" test, never revisit locations, even if open!
return false;
}
//checks distance, mines, cliffs
if (gubNPCDistLimitSq)
{
if ( gfNPCCircularDistLimit )
{
if (PythSpacesAway( StartNode, CurrentNode) > gubNPCDistLimitSq)
{
return false;
}
}
else
{
if (SpacesAway( StartNode, CurrentNode) > gubNPCDistLimit)
{
return false;
}
}
}
// AI check for mines
if ( gpWorldLevelData[CurrentNode].uiFlags & MAPELEMENT_ENEMY_MINE_PRESENT && pSoldier->bSide != 0)
{
return false;
}
// WANNE: Know mines (for enemy or player) do not explode - BEGIN
if ( gpWorldLevelData[CurrentNode].uiFlags & (MAPELEMENT_ENEMY_MINE_PRESENT | MAPELEMENT_PLAYER_MINE_PRESENT) )
{
if (pSoldier->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 (pSoldier->bTeam != gbPlayerNum || CurrentNode != DestNode)
{
if (gpWorldLevelData[CurrentNode].uiFlags & MAPELEMENT_PLAYER_MINE_PRESENT)
{
return false;
}
}
}
else
{
// For the enemy, always skip known mines
if (gpWorldLevelData[CurrentNode].uiFlags & MAPELEMENT_ENEMY_MINE_PRESENT)
{
return false;
}
}
}
// WANNE: - END
//ATE: Movement onto cliffs? Check vs the soldier's gridno height
// CJC: PREVIOUS LOCATION's height
if ( gpWorldLevelData[ CurrentNode ].sHeight != gpWorldLevelData[ ParentNode ].sHeight )
{
return false;
}
return true;
}//end CanTraverse
bool AStarPathfinder::IsSomeoneInTheWay()
{
// 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 && ( (CurrentNode != DestNode) || fCopyReachable) )
{
// ATE: ONLY cancel if they are moving.....
UINT8 ubMerc = WhoIsThere2( (UINT16) CurrentNode, pSoldier->bLevel);
if ( ubMerc < NOBODY && ubMerc != pSoldier->ubID )
{
// Check for movement....
//if ( fTurnBased || ( (Menptr[ ubMerc ].sFinalDestination == Menptr[ ubMerc ].sGridNo) || (Menptr[ ubMerc ].fDelayedMovement) ) )
//{
return true;
//}
// else
//{
// nextCost += 50;
//}
}
}
return false;
}
#endif//end ifdef USE_ASTAR_PATHS
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 )
{
#ifdef USE_ASTAR_PATHS
int retVal = ASTAR::AStarPathfinder::GetInstance().GetPath(s, sDestination, ubLevel, usMovementMode, bCopy, fFlags);
if (retVal) {
return retVal;
}
else {
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR path failed!" ) );
}
// if (sDestination == NOWHERE)
{
return 0;
}
#else
//__try
//{
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 = 0;
#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 = 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 = (INT8) (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 = (INT16) REMAININGCOST( &(pathQ[1]) );
}
pathQ[1].usTotalCost = pathQ[1].usCostSoFar + pathQ[1].usCostToGo;
pathQ[1].ubLegDistance = (UINT8)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->ubDirection];
}
else
{
iLastDir = s->ubDirection;
}
// 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 ) );
// 0verhaul: Cannot change direction over a fence!
if (prevCost == TRAVELCOST_FENCE)
{
iLoopStart = trailTree[pCurrPtr->sPathNdx].stepDir;
if (bLoopState == LOOPING_CLOCKWISE) {
iLoopEnd = gOneCCDirection[ iLoopStart];
} else {
iLoopEnd = gOneCDirection[ iLoopStart];
}
}
else
{
iLoopStart = iLoopEnd = 0;
}
if (fContinuousTurnNeeded)
{
if (trailTreeNdx < 2)
{
iLastDir = s->ubDirection;
}
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;
}
// WANNE: Know mines (for enemy or player) do not explode - BEGIN
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 < TOTAL_SOLDIERS && 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 = (UINT8)(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 = (UINT16) (newTotCost + pNewPtr->usCostToGo);
pNewPtr->ubLegDistance = (UINT8) 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;
}
//Assert(!iCnt);
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);
//}
//__except(filter(GetExceptionCode(), GetExceptionInformation()))
//{
// return (0);
//}
#endif
}
void GlobalReachableTest( INT16 sStartGridNo )
{
SOLDIERTYPE s;
INT32 iCurrentGridNo =0;
// WDS - Clean up inventory handling
//memset( &s, 0, SIZEOF_SOLDIERTYPE );
s.initialize();
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;
// WDS - Clean up inventory handling
//memset( &s, 0, SIZEOF_SOLDIERTYPE );
s.initialize();
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;
// WDS - Clean up inventory handling
//memset( &s, 0, SIZEOF_SOLDIERTYPE );
s.initialize();
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;
// WDS - Clean up inventory handling
//memset( &s, 0, SIZEOF_SOLDIERTYPE );
s.initialize();
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, FINDCLIMBPOINTS, 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, 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 = sPoints + MinAPsToStartMovement( pSold, usMovementMode );
// We should reduce points for starting to run if first tile is a fence...
sTestGridno = NewGridNo(pSold->sGridNo, DirectionInc( (UINT8)guiPathingData[0]));
if ( gubWorldMovementCosts[ sTestGridno ][ 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 = sPoints + sAnimCost;
gusAPtsToMove = 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, DirectionInc( (UINT8)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 ( TERRAIN_IS_WATER( gpWorldLevelData[ sTempGrid ].ubTerrainID) )
{
usMovementModeToUseForAPs = WALKING;
}
if ( bIgnoreNextCost )
{
bIgnoreNextCost = FALSE;
}
else
{
// ATE: If we have a 'special cost, like jump fence...
if ( sSwitchValue == TRAVELCOST_FENCE )
{
sPoints = 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 = sPoints + sExtraCostStand;
}
break;
case SWATTING:
// Add cost to stand once there BEFORE....
sExtraCostSwat += AP_CROUCH;
sPoints = 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 = 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 = usTileIndex + sFootOrder[ 4 ];
}
else // turn based
{
// find out which color we're using
usTileIndex = 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 = usTileIndex + sFootOrder[ 4 ];
}
else // turnbased
{
// find out which color we're using
usTileIndex = 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 ) );
}