mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
- no more jump with backpack (keys 'j' and 'J'). This is more consistent. Sometimes the soldier got stuck in the fence as some functions did not allow jumping and others did. - Pickup items from ground will now cost AP. - Backpack, Scubba fins and swimming background AP modificattion is now shown in path and is considered when calculating new path. No pathing through fences when wearing backpack. More likely to create path through water when wearing scubba fins. git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@6543 3b4a5df2-a311-0410-b5c6-a8a6f20db521
5253 lines
151 KiB
C++
5253 lines
151 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 "stdlib.h"
|
|
#include "debug.h"
|
|
#include "MemMan.h"
|
|
#include "Overhead Types.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 "Points.h"
|
|
#include "ai.h"
|
|
#include "Random.h"
|
|
#include "message.h"
|
|
#include "structure wrap.h"
|
|
#include "keys.h"
|
|
#include "gamesettings.h"
|
|
#include "Buildings.h"
|
|
#include "soldier profile.h" // added by SANDRO
|
|
#endif
|
|
#include "connect.h"
|
|
|
|
#include "LOS.h" //ddd
|
|
|
|
//forward declarations of common classes to eliminate includes
|
|
class OBJECTTYPE;
|
|
class SOLDIERTYPE;
|
|
|
|
#ifdef USE_ASTAR_PATHS
|
|
#include "BinaryHeap.hpp"
|
|
#include "AIInternals.h"
|
|
#include "opplist.h"
|
|
#include "weapons.h"
|
|
#endif
|
|
//#include "dnlprocesstalk.h"//dnl???
|
|
|
|
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];
|
|
extern INT16 * gsCoverValue;
|
|
BOOLEAN gfDisplayCoverValues = TRUE;
|
|
BOOLEAN gfDrawPathPoints = TRUE;
|
|
#endif
|
|
|
|
BOOLEAN gfPlotPathToExitGrid = FALSE;
|
|
BOOLEAN gfRecalculatingExistingPathCost = FALSE;
|
|
UINT8 gubGlobalPathFlags = 0;
|
|
UINT8 gubBuildingInfoToSet;
|
|
INT32 iMaxSkipListLevel = MAX_SKIPLIST_LEVEL;
|
|
INT32 iMaxTrailTree = MAX_TRAIL_TREE;
|
|
INT32 iMaxPathQ = MAX_PATHQ;
|
|
|
|
extern BOOLEAN gfGeneratingMapEdgepoints;
|
|
|
|
#define VEHICLE
|
|
|
|
//#define TRAILCELLTYPE UINT16 //Lalien: moved to pathai.h
|
|
|
|
// OLD PATHAI STUFF
|
|
/////////////////////////////////////////////////
|
|
//dnl ch50 071009
|
|
struct path_s
|
|
{
|
|
INT32 iLocation;
|
|
INT32 sPathNdx;
|
|
struct path_s* pNext[ABSMAX_SKIPLIST_LEVEL];
|
|
TRAILCELLTYPE usCostSoFar;
|
|
TRAILCELLTYPE usCostToGo;
|
|
TRAILCELLTYPE usTotalCost;
|
|
INT16 ubTotalAPCost;
|
|
INT8 bLevel;
|
|
UINT8 ubLegDistance;
|
|
};
|
|
|
|
typedef struct path_s path_t;
|
|
|
|
struct trail_s
|
|
{
|
|
INT32 nextLink;
|
|
INT8 stepDir;
|
|
INT8 fFlags;
|
|
INT32 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[ABSMAX_SKIPLIST_LEVEL] = { 0, 4, 16, 64, 256, 1024, 4192, 16384, 65536, 262144 };//dnl ch50 071009
|
|
|
|
#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%WORLD_COLS)
|
|
#define YLOC(a) (a/WORLD_COLS)
|
|
//#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 INT32 trailTreeNdx = 0;//dnl ch50 071009
|
|
|
|
#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->pathing.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->pathing.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->pathing.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->pathing.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->pathing.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->pathing.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->pathing.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->pathing.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
|
|
|
|
INT16 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.
|
|
UINT32* guiPlottedPath = NULL;
|
|
UINT32* guiPathingData = NULL;
|
|
static INT32 giPathDataSize;
|
|
static INT32 giPlotCnt;
|
|
static INT32 guiEndPlotGridNo;
|
|
|
|
//static INT32 dirDelta[8]=
|
|
//{
|
|
// -MAPWIDTH, //N
|
|
// 1-MAPWIDTH, //NE
|
|
// 1, //E
|
|
// 1+MAPWIDTH, //SE
|
|
// MAPWIDTH, //S
|
|
// MAPWIDTH-1, //SW
|
|
// -1, //W
|
|
// -MAPWIDTH-1 //NW
|
|
//};
|
|
|
|
///*static */INT32 dirDelta[8]=
|
|
//{
|
|
// -OLD_WORLD_COLS, //N
|
|
// 1-OLD_WORLD_COLS, //NE
|
|
// 1, //E
|
|
// 1+OLD_WORLD_COLS, //SE
|
|
// OLD_WORLD_COLS, //S
|
|
// OLD_WORLD_COLS-1, //SW
|
|
// -1, //W
|
|
// -OLD_WORLD_COLS-1 //NW
|
|
//};
|
|
INT32 dirDelta[8]=
|
|
{
|
|
-WORLD_COLS, //N
|
|
1-WORLD_COLS, //NE
|
|
1, //E
|
|
1+WORLD_COLS, //SE
|
|
WORLD_COLS, //S
|
|
WORLD_COLS-1, //SW
|
|
-1, //W
|
|
-WORLD_COLS-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);
|
|
//}
|
|
//comm by ddd{
|
|
//for (INT16 node = 0; node < WORLD_MAX; node++)
|
|
//{
|
|
// SetAStarStatus(node, AStar_Init); //comm by ddd
|
|
// SetAStarG(node, 0); //comm by ddd
|
|
//}
|
|
//comm by ddd}
|
|
|
|
//ddd{
|
|
|
|
INT32 node;
|
|
for (node = 0; node < (WORLD_MAX & ~3); node+=4) //ddd ðàçâîðîò öèêëà
|
|
{
|
|
AStarData[node].status = AStarData[node+1].status
|
|
= AStarData[node+2].status = AStarData[node+3].status = AStar_Init;
|
|
|
|
AStarData[node+3].cost = AStarData[node+2].cost =
|
|
AStarData[node+1].cost = AStarData[node].cost = 0;
|
|
}
|
|
for(node=(WORLD_MAX & ~3); node < WORLD_MAX; node++)
|
|
{AStarData[node].status = AStar_Init; AStarData[node].cost = 0;}
|
|
////ddd}
|
|
|
|
|
|
//ClosedList.clear();
|
|
OpenHeap.clear();
|
|
return;
|
|
}//end ResetAStarList
|
|
|
|
void AStarPathfinder::SetLoopState(const INT32 node,
|
|
int const loopState)
|
|
{
|
|
if ( loopState == LOOPING_REVERSE )
|
|
{
|
|
AStarData[node].wasBackwards = STEP_BACKWARDS;
|
|
}
|
|
else
|
|
{
|
|
AStarData[node].wasBackwards = false;
|
|
}
|
|
}
|
|
|
|
int AStarPathfinder::PythSpacesAway(const INT32 node1,
|
|
const INT32 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 INT32 node1,
|
|
const INT32 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 ,
|
|
INT32 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
|
|
if(!is_networked)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->pathing.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->flags.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 (!TileIsOutOfBounds(DestNode))
|
|
{
|
|
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->flags.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->flags.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 (TileIsOutOfBounds(dest))
|
|
{
|
|
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)
|
|
INT32 parent = DestNode;
|
|
INT32 current = parent;
|
|
unsigned int sizePath = 0;
|
|
//CHRISL: If, for whatever reason, the pathfinding system gets the wrong Grid values, it's possible that an infinite
|
|
// loop can result. This can often require a hard reboot to get around. To try and force a CTD in this event, lets
|
|
// track all the grids and force a crash if we ever pull the same GridNo.
|
|
std::vector<INT32> trackNode;
|
|
trackNode.resize(1,current);
|
|
while (current != -1)
|
|
{
|
|
//sizePath += GetNumSteps( current); //comm by ddd
|
|
sizePath +=AStarData[current].numSteps;
|
|
while (sizePath > MAX_PATH_LIST_SIZE)
|
|
{
|
|
//sizePath -= GetNumSteps( parent);//comm by ddd
|
|
sizePath -=AStarData[parent].numSteps;
|
|
//parent = GetAStarParent( parent);//comm by ddd
|
|
parent = AStarData[parent].parent;
|
|
}
|
|
//current = GetAStarParent( current);
|
|
current = AStarData[current].parent ;
|
|
for(unsigned int i = 0; i<trackNode.size(); i++)
|
|
{
|
|
if(trackNode.at(i) == current)
|
|
{
|
|
//CHAR8 str[128];
|
|
//sprintf( str, "Duplicate GridNo found during pathfinding" );
|
|
//AssertMsg( 0, str );
|
|
//AssertMsg( FALSE, String( "Duplicate GridNo found during pathfinding at %d. Related to soldier %d at GridNo %d.", current, s->ubID, s->sGridNo ) );
|
|
|
|
// lalien: hack to avoid freezing, return 0 if the path is bad.
|
|
// Probably it will make trouble, need to check this
|
|
return 0;
|
|
}
|
|
}
|
|
trackNode.push_back(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->pathing.usPathDataSize = (UINT16) sizePath;
|
|
pSoldier->pathing.usPathIndex = 0;
|
|
while (sizePath > 0)
|
|
{
|
|
int numSteps = GetNumSteps(parent);
|
|
while (numSteps > 0)
|
|
{
|
|
sizePath--;
|
|
numSteps--;
|
|
pSoldier->pathing.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->pathing.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
|
|
#pragma optimize("gpt",on)
|
|
//__forceinline
|
|
INT16 AStarPathfinder::AStar()
|
|
{
|
|
HEAP<int, INT32> 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)
|
|
{
|
|
INT32 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;
|
|
}
|
|
|
|
//dddokno çäåñü ìîæíî çàïðåòèòü âñòàâàòü íà îêîííûå êëåòêè ;)
|
|
//if (gubWorldMovementCosts[CurrentNode][direction][pSoldier->pathing.bLevel] == TRAVELCOST_JUMPABLEWINDOW)
|
|
// continue;
|
|
|
|
|
|
if (gubWorldMovementCosts[CurrentNode][direction][pSoldier->pathing.bLevel] == TRAVELCOST_FENCE)
|
|
{
|
|
SetAStarStatus(CurrentNode, AStar_Closed);
|
|
CurrentNode = CurrentNode + dirDelta[direction];
|
|
if (!NewOKDestination( pSoldier, CurrentNode, fPathAroundPeople, pSoldier->pathing.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;
|
|
movementG = terrainCost;
|
|
// Favor continuing in the same direction by increasing the cost for changing direction
|
|
INT32 ParentParentNode = GetAStarParent( ParentNode);
|
|
if ((ParentParentNode != -1 &&
|
|
ParentNode - ParentParentNode != CurrentNode - ParentNode) ||
|
|
(ParentParentNode == -1 && (direction & 1) ) )
|
|
{
|
|
movementG++;
|
|
}
|
|
|
|
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 );
|
|
|
|
// WANNE - BMP: We have to reset the CurrentNode.
|
|
// This can happen, if the previous savegame was on a big map
|
|
// and now we are on a "normal" map.
|
|
if (CurrentNode > WORLD_MAX)
|
|
CurrentNode = 0;
|
|
|
|
// 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 - GetAPsStartRun( pSoldier ); // changed by SANDRO
|
|
}
|
|
}
|
|
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 = APBPConstants[AP_MOVEMENT_FLAT]; break;
|
|
|
|
//case TRAVELCOST_BUMPY:
|
|
case TRAVELCOST_GRASS: movementAPCost = APBPConstants[AP_MOVEMENT_GRASS]; break;
|
|
|
|
case TRAVELCOST_THICK: movementAPCost = APBPConstants[AP_MOVEMENT_BUSH]; break;
|
|
|
|
case TRAVELCOST_DEBRIS: movementAPCost = APBPConstants[AP_MOVEMENT_RUBBLE]; break;
|
|
|
|
case TRAVELCOST_SHORE: movementAPCost = APBPConstants[AP_MOVEMENT_SHORE]; break; // wading shallow water
|
|
|
|
case TRAVELCOST_KNEEDEEP: movementAPCost = APBPConstants[AP_MOVEMENT_LAKE]; break; // wading waist/chest deep - very slow
|
|
|
|
|
|
case TRAVELCOST_DEEPWATER: movementAPCost = APBPConstants[AP_MOVEMENT_OCEAN]; break; // can swim, so it's faster than wading
|
|
|
|
//case TRAVELCOST_VEINEND:
|
|
//case TRAVELCOST_VEINMID: movementAPCost = APBPConstants[AP_MOVEMENT_FLAT]; break;
|
|
|
|
//case TRAVELCOST_DOOR: movementAPCost = APBPConstants[AP_MOVEMENT_FLAT]; break;
|
|
|
|
case TRAVELCOST_FENCE: movementAPCost = APBPConstants[AP_JUMPFENCE]; break;
|
|
///dddokno{
|
|
case TRAVELCOST_JUMPABLEWINDOW:
|
|
case TRAVELCOST_JUMPABLEWINDOW_N:
|
|
case TRAVELCOST_JUMPABLEWINDOW_W:
|
|
movementAPCost = APBPConstants[AP_JUMPFENCE]; break;
|
|
///dddokno}
|
|
|
|
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) movementAPCost + APBPConstants[AP_MODIFIER_RUN];
|
|
//movementAPCost = (INT16)(DOUBLE)( (sTileCost / RUNDIVISOR) ); break;
|
|
break;
|
|
case WALKING:
|
|
case ROBOT_WALK:
|
|
movementAPCost = (movementAPCost + APBPConstants[AP_MODIFIER_WALK]); //WALKCOST);
|
|
break;
|
|
case SWATTING:
|
|
movementAPCost = (movementAPCost + APBPConstants[AP_MODIFIER_SWAT]); //SWATCOST);
|
|
break;
|
|
case CRAWLING:
|
|
movementAPCost = (movementAPCost + APBPConstants[AP_MODIFIER_CRAWL]); //CRAWLCOST);
|
|
break;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
// SANDRO - STOMP traits - Athletics trait decreases movement cost
|
|
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, ATHLETICS_NT ))
|
|
{
|
|
movementAPCost = max(1, (INT16)((movementAPCost * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
|
|
}
|
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
if (terrainCost == TRAVELCOST_FENCE)
|
|
{
|
|
switch( movementModeToUseForAPs )
|
|
{
|
|
case RUNNING:
|
|
case WALKING :
|
|
// Here pessimistically assume the path will continue after hopping the fence
|
|
movementAPCost += GetAPsCrouch( pSoldier, TRUE ); // SANDRO - changed
|
|
break;
|
|
|
|
case SWATTING:
|
|
// Add cost to stand once there BEFORE jumping....
|
|
movementAPCost += GetAPsCrouch( pSoldier, TRUE ); // SANDRO - changed
|
|
break;
|
|
|
|
case CRAWLING:
|
|
// Can't do it here.....
|
|
return -1;
|
|
}
|
|
}
|
|
//dddokno
|
|
else if ( terrainCost == TRAVELCOST_JUMPABLEWINDOW
|
|
|| terrainCost == TRAVELCOST_JUMPABLEWINDOW_N
|
|
|| terrainCost == TRAVELCOST_JUMPABLEWINDOW_W )
|
|
{
|
|
switch(movementModeToUseForAPs)
|
|
{
|
|
case RUNNING:
|
|
case WALKING :
|
|
case SWATTING:
|
|
movementAPCost += APBPConstants[AP_CROUCH];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
//dddokno
|
|
else if (terrainCost == TRAVELCOST_NOT_STANDING)
|
|
{
|
|
switch(movementModeToUseForAPs)
|
|
{
|
|
case RUNNING:
|
|
case WALKING :
|
|
// charge crouch APs for ducking head!
|
|
movementAPCost += GetAPsCrouch( pSoldier, TRUE ); // SANDRO - changed
|
|
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 += GetAPsToOpenDoor( pSoldier ) + GetAPsToOpenDoor( pSoldier ); // changed by SANDRO
|
|
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 ];
|
|
}
|
|
}
|
|
|
|
//dddokno { ïðîâåðèòü 2 óñëîâèÿ 1 åñëè ñëåä. òàéë îêíî è íàäî áóäåò ïåðåëåçàòü ÷åðåç íåãî
|
|
//2. åñëè òåê. òàéë îêíî è íàäî ïåðåéòè íà äðóãîé òàéë ÷åðåç îêíî.
|
|
else if ( nextCost == TRAVELCOST_JUMPABLEWINDOW
|
|
|| nextCost == TRAVELCOST_JUMPABLEWINDOW_N
|
|
|| nextCost == TRAVELCOST_JUMPABLEWINDOW_W)
|
|
{
|
|
|
|
// don't let anyone path diagonally through doors!
|
|
if (IsDiagonal(direction) == true)
|
|
{
|
|
return -1;
|
|
}
|
|
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNode ].ubTerrainID ];//?
|
|
|
|
}
|
|
//dddokno }
|
|
|
|
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;
|
|
}
|
|
|
|
INT32 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->flags.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->flags.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 1
|
|
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(INT32 sMyGridNo, INT32 iMyThreat, INT32 iMyAPsLeft,
|
|
INT32 myThreatsiOrigRange, INT32 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
|
|
INT32 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 (TileIsOutOfBounds(sHisRealGridNo))
|
|
{
|
|
sHisRealGridNo = pHim->sGridNo; // remember where he REALLY is
|
|
dHisX = pHim->dXPos;
|
|
dHisY = pHim->dYPos;
|
|
}
|
|
|
|
// calculate where my cover is worst if opponent moves just 1 tile over
|
|
bHisBestCTGT = CalcBestCTGT(pHim, pMe->ubID, sMyGridNo, pMe->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 (!TileIsOutOfBounds(sHisRealGridNo))
|
|
{
|
|
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->flags.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->flags.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;
|
|
}
|
|
|
|
///ddd{
|
|
if(gGameExternalOptions.bNewTacticalAIBehavior)
|
|
{
|
|
// ïî òðóïàì íå áåãàåì . ÒÎÄÎ: â 80% ñëó÷àåâ íå áåãàåì?
|
|
if ( ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) )
|
|
&& pSoldier->bTeam == ENEMY_TEAM
|
|
//&& IsCorpseAtGridNo( CurrentNode, pSoldier->pathing.bLevel )
|
|
&& gubIsCorpseThere[CurrentNode]
|
|
)
|
|
return false;
|
|
|
|
//from niht ops //ýëèòà AI íå õîäèò ïî îñâåù¸ííûì ó÷àñòêàì, êîòîðûå ïðîñìàòðèâàþòñÿ ïðîòèâíèêîì
|
|
if( ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) )
|
|
&& pSoldier->bTeam == ENEMY_TEAM && pSoldier->ubProfile == NO_PROFILE
|
|
&& pSoldier->aiData.bAction != AI_ACTION_LEAVE_WATER_GAS
|
|
//&& (pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE
|
|
//|| ( (pSoldier->ubSoldierClass == SOLDIER_CLASS_ARMY
|
|
// || pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR)
|
|
// && (BOOLEAN)(Random( 100 ) < 50) ))
|
|
//&& InLightAtNight((INT16)CurrentNode, gpWorldLevelData[ CurrentNode ].sHeight)
|
|
&& gubWorldTileInLight[CurrentNode]
|
|
&& gubMerkCanSeeThisTile[CurrentNode]
|
|
)
|
|
{
|
|
return false;
|
|
//ScreenMsg( MSG_FONT_YELLOW, MSG_INTERFACE, L"svet!" );
|
|
/*INT32 tcnt = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
|
|
SOLDIERTYPE *tS;
|
|
// look for all mercs on the same team,
|
|
for ( tS = MercPtrs[ tcnt ]; tcnt <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; tcnt++,tS++ )
|
|
{
|
|
if ( tS->stats.bLife >= OKLIFE && tS->sGridNo != NOWHERE && tS->bInSector )
|
|
if ( SoldierTo3DLocationLineOfSightTest(
|
|
tS, tS->sGridNo, pSoldier->pathing.bLevel, 3,
|
|
TRUE, CALC_FROM_WANTED_DIR ))
|
|
{
|
|
//if( pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE )
|
|
return false;
|
|
//else if ( Chance(35) ) return false;
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
///ddd}
|
|
|
|
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( CurrentNode, pSoldier->pathing.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 );
|
|
}
|
|
|
|
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 ) );
|
|
}
|
|
|
|
//dnl ch50 071009
|
|
BOOLEAN InitPathAI(void)
|
|
{
|
|
guiPathingData = (UINT32*)MemAlloc(MAX_PATH_DATA_LENGTH * sizeof(UINT32));
|
|
guiPlottedPath = (UINT32*)MemAlloc(MAX_PATH_DATA_LENGTH * sizeof(UINT32));
|
|
pathQ = (path_t*)MemAlloc(ABSMAX_PATHQ * sizeof(path_t));
|
|
trailCost = (UINT16*)MemAlloc(WORLD_MAX * sizeof(TRAILCELLTYPE));
|
|
trailCostUsed = (UINT8*)MemAlloc(WORLD_MAX);
|
|
trailTree = (trail_t*)MemAlloc(ABSMAX_TRAIL_TREE * sizeof(trail_t));
|
|
if(!guiPlottedPath || !guiPathingData || !pathQ || !trailCost || !trailCostUsed || !trailTree)
|
|
return(FALSE);
|
|
pQueueHead = &pathQ[QHEADNDX];
|
|
pClosedHead = &pathQ[QPOOLNDX];
|
|
memset(trailCostUsed, 0, WORLD_MAX);
|
|
RestorePathAIToDefaults();
|
|
return(TRUE);
|
|
}
|
|
|
|
void ShutDownPathAI(void)
|
|
{
|
|
MemFree(guiPathingData);
|
|
MemFree(guiPlottedPath);
|
|
MemFree(pathQ);
|
|
MemFree(trailCostUsed);
|
|
MemFree(trailCost);
|
|
MemFree(trailTree);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// FINDBESTPATH /
|
|
////////////////////////////////////////////////////////////////////////
|
|
INT32 FindBestPath(SOLDIERTYPE *s , INT32 sDestination, INT8 ubLevel, INT16 usMovementMode, INT8 bCopy, UINT8 fFlags )
|
|
{
|
|
s->sPlotSrcGrid = s->sGridNo;
|
|
|
|
#ifdef USE_ASTAR_PATHS
|
|
//ddd
|
|
CHAR8 errorBuf[511]; UINT32 b,e;
|
|
b=GetJA2Clock();//return s->sGridNo+6;
|
|
|
|
int retVal = ASTAR::AStarPathfinder::GetInstance().GetPath(s, sDestination, ubLevel, usMovementMode, bCopy, fFlags);
|
|
|
|
e=GetJA2Clock();sprintf(errorBuf, "timefind bestpath= %d",e-b );LiveMessage(errorBuf);
|
|
|
|
if (retVal || TileIsOutOfBounds(sDestination)) {
|
|
return retVal;
|
|
}
|
|
else {
|
|
DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR path failed!" ) );
|
|
}
|
|
|
|
// if (TileIsOutOfBounds(sDestination))
|
|
{
|
|
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;
|
|
INT32 sCurPathNdx;
|
|
INT32 prevCost;
|
|
INT32 iWaterToWater;
|
|
INT16 ubCurAPCost,ubAPCost;
|
|
INT16 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 = s->sGridNo;
|
|
/*
|
|
//dnl??? ch53 111009
|
|
if(!GridNoOnVisibleWorldTile(iOrigination))
|
|
{
|
|
SendFmtMsg("shuuu=%d,%d", s->ubID, iOrigination);//dnl???
|
|
//bad!!! return(0);
|
|
}
|
|
if(!GridNoOnVisibleWorldTile(iDestination))
|
|
{
|
|
//iDestination != (guiWorldCols*guiWorldRows)
|
|
SendFmtMsg("blaaa=%d,%d", s->ubID, iDestination);//dnl???
|
|
//bad!!! return(0);
|
|
}
|
|
*/
|
|
if(TileIsOutOfBounds(sDestination) && sDestination != GRIDSIZE)//dnl ch50 071009 GRIDSIZE is use when want to travel all over the map, one of useage is when seeking suitable place for edge points
|
|
{
|
|
return(0); // There is no path to nowhere, because everywhere you can go is somewhere!
|
|
}
|
|
else if(sDestination < GRIDSIZE && !GridNoOnVisibleWorldTile(iDestination))//dnl ch53 111009
|
|
{
|
|
return(0);
|
|
}
|
|
|
|
if ( TileIsOutOfBounds( iOrigination ) )
|
|
{
|
|
#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( iOrigination ))
|
|
{
|
|
#ifdef JA2BETAVERSION
|
|
if(!is_networked)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->pathing.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->flags.uiStatusFlags & SOLDIER_PCUNDERAICONTROL) );
|
|
fNonFenceJumper = !( IS_MERC_BODY_TYPE( s ) ) || (UsingNewInventorySystem() == true && FindBackpackOnSoldier( s ) != ITEM_NOT_FOUND);//Moa: added backpack check
|
|
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( 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, WORLD_MAX );
|
|
gubGlobalPathCount = 1;
|
|
}
|
|
else
|
|
{
|
|
gubGlobalPathCount++;
|
|
}
|
|
|
|
// only allow nowhere destination if distance limit set
|
|
if (TileIsOutOfBounds(sDestination))
|
|
{
|
|
/*
|
|
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->flags.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->flags.uiStatusFlags & SOLDIER_VEHICLE) != 0 );
|
|
|
|
fContinuousTurnNeeded = ( ( s->flags.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 / WORLD_COLS);
|
|
iOriginationX = (iOrigination % WORLD_COLS);
|
|
}
|
|
|
|
iDestY = (iDestination / WORLD_COLS);
|
|
iDestX = (iDestination % WORLD_COLS);
|
|
|
|
// if origin and dest is water, then user wants to stay in water!
|
|
// so, check and set waterToWater flag accordingly
|
|
if (TileIsOutOfBounds(iDestination))
|
|
{
|
|
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 / WORLD_COLS;
|
|
iLocX = iOrigination % WORLD_COLS;
|
|
|
|
SETLOC( pathQ[1], iOrigination );
|
|
pathQ[1].sPathNdx = 0;
|
|
pathQ[1].usCostSoFar = 0;
|
|
if ( fCopyReachable )
|
|
{
|
|
pathQ[1].usCostToGo = 100;
|
|
}
|
|
else
|
|
{
|
|
pathQ[1].usCostToGo = REMAININGCOST( &(pathQ[1]) );
|
|
}
|
|
pathQ[1].usTotalCost = pathQ[1].usCostSoFar + pathQ[1].usCostToGo;
|
|
pathQ[1].ubLegDistance = LEGDISTANCE( iLocX, iLocY, iDestX, iDestY );
|
|
pathQ[1].bLevel = 1;
|
|
pQueueHead->pNext[0] = &( pathQ[1] );
|
|
iSkipListSize++;
|
|
|
|
trailTreeNdx = 0;
|
|
trailCost[iOrigination] = 0;
|
|
pCurrPtr = pQueueHead->pNext[0];
|
|
pCurrPtr->sPathNdx = trailTreeNdx;
|
|
trailTreeNdx++;
|
|
|
|
|
|
do
|
|
{
|
|
//remove the first and best path so far from the que
|
|
pCurrPtr = pQueueHead->pNext[0];
|
|
curLoc = pCurrPtr->iLocation;
|
|
curCost = pCurrPtr->usCostSoFar;
|
|
sCurPathNdx = pCurrPtr->sPathNdx;
|
|
|
|
// remember the cost used to get here...
|
|
prevCost = gubWorldMovementCosts[ trailTree[ sCurPathNdx ].sGridNo ][ trailTree[ sCurPathNdx ].stepDir ][ ubLevel ];
|
|
|
|
#if defined( PATHAI_VISIBLE_DEBUG )
|
|
if (gfDisplayCoverValues && gfDrawPathPoints)
|
|
{
|
|
if (gsCoverValue[ curLoc ] > 0)
|
|
{
|
|
gsCoverValue[ curLoc ] *= -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef VEHICLE
|
|
/*
|
|
if (fTurnSlow)
|
|
{
|
|
if (pCurrPtr->pNext[0] == 0)
|
|
{
|
|
if (fReverse)
|
|
{
|
|
iLastDir = gOppositeDirection[s->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( 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( curLoc, ubLevel, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), usOKToAddStructID ) )
|
|
{
|
|
goto NEXTDIR;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
newLoc = curLoc + dirDelta[iCnt];
|
|
|
|
if (newLoc < 0 || newLoc >= WORLD_MAX)
|
|
{
|
|
goto NEXTDIR;
|
|
}
|
|
|
|
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( iOrigination, newLoc) > gubNPCDistLimit)
|
|
{
|
|
goto NEXTDIR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (SpacesAway( iOrigination, 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( 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( iDoorGridNo );
|
|
if (pDoorStatus)
|
|
{
|
|
fDoorIsOpen = (pDoorStatus->ubFlags & DOOR_PERCEIVED_OPEN) != 0;
|
|
}
|
|
else
|
|
{
|
|
// door destroyed?
|
|
nextCost = gTileTypeMovementCost[ gpWorldLevelData[ newLoc ].ubTerrainID ];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check door structure
|
|
pDoorStructure = FindStructure( 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->flags.uiStatusFlags & SOLDIER_MONSTER || s->flags.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->flags.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( newLoc, s->pathing.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( 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 = APBPConstants[AP_MOVEMENT_FLAT];
|
|
break;
|
|
//case TRAVELCOST_BUMPY :
|
|
case TRAVELCOST_GRASS : ubAPCost = APBPConstants[AP_MOVEMENT_GRASS];
|
|
break;
|
|
case TRAVELCOST_THICK : ubAPCost = APBPConstants[AP_MOVEMENT_BUSH];
|
|
break;
|
|
case TRAVELCOST_DEBRIS : ubAPCost = APBPConstants[AP_MOVEMENT_RUBBLE];
|
|
break;
|
|
case TRAVELCOST_SHORE : ubAPCost = APBPConstants[AP_MOVEMENT_SHORE]; // wading shallow water
|
|
break;
|
|
case TRAVELCOST_KNEEDEEP: ubAPCost = APBPConstants[AP_MOVEMENT_LAKE]; // wading waist/chest deep - very slow
|
|
break;
|
|
|
|
case TRAVELCOST_DEEPWATER:ubAPCost = APBPConstants[AP_MOVEMENT_OCEAN]; // can swim, so it's faster than wading
|
|
break;
|
|
//case TRAVELCOST_VEINEND :
|
|
//case TRAVELCOST_VEINMID : ubAPCost = APBPConstants[AP_MOVEMENT_FLAT];
|
|
// break;
|
|
|
|
//case TRAVELCOST_DOOR : ubAPCost = APBPConstants[AP_MOVEMENT_FLAT];
|
|
// break;
|
|
|
|
case TRAVELCOST_FENCE :
|
|
// SANDRO - slightly changed
|
|
if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( s ) != ITEM_NOT_FOUND )
|
|
ubAPCost = GetAPsToJumpFence( s, TRUE );
|
|
else
|
|
ubAPCost = GetAPsToJumpFence( s, FALSE );
|
|
|
|
/*
|
|
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 += APBPConstants[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
|
|
// CHRISL: Adjusted system to use different move costs while wearing a backpack
|
|
// SANDRO - slightly changed this to add backpack modifier after Athletics trait check
|
|
switch( usMovementModeToUseForAPs )
|
|
{
|
|
case RUNNING:
|
|
case ADULTMONSTER_WALKING:
|
|
// save on casting
|
|
//ubAPCost = (UINT8)(ubAPCost * 10 / ( (UINT8) (RUNDIVISOR * 10)));
|
|
ubAPCost += APBPConstants[AP_MODIFIER_RUN];
|
|
//ubAPCost = (INT16)(DOUBLE)( (sTileCost / RUNDIVISOR) ); break;
|
|
break;
|
|
case WALKING_WEAPON_RDY:
|
|
case WALKING_DUAL_RDY:
|
|
ubAPCost += APBPConstants[AP_MODIFIER_WALK] + APBPConstants[AP_MODIFIER_READY]; //WALKCOST);
|
|
break;
|
|
case WALKING_ALTERNATIVE_RDY:
|
|
ubAPCost += APBPConstants[AP_MODIFIER_WALK]; //WALKCOST);
|
|
break;
|
|
case WALKING:
|
|
ubAPCost += APBPConstants[AP_MODIFIER_WALK]; //WALKCOST);
|
|
if (!(s->MercInWater()) && ( (gAnimControl[ s->usAnimState ].uiFlags & ANIM_FIREREADY ) || (gAnimControl[ s->usAnimState ].uiFlags & ANIM_FIRE ) ))
|
|
{
|
|
ubAPCost += APBPConstants[AP_MODIFIER_READY]; //WALKCOST);
|
|
}
|
|
break;
|
|
case ROBOT_WALK:
|
|
ubAPCost += APBPConstants[AP_MODIFIER_WALK]; //WALKCOST);
|
|
break;
|
|
case SWATTING:
|
|
ubAPCost += APBPConstants[AP_MODIFIER_SWAT]; //SWATCOST);
|
|
break;
|
|
case CRAWLING:
|
|
ubAPCost += APBPConstants[AP_MODIFIER_CRAWL]; //CRAWLCOST);
|
|
break;
|
|
default:
|
|
ubAPCost += APBPConstants[AP_MODIFIER_WALK];
|
|
break;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
|
// SANDRO - STOMP traits - Athletics trait decreases movement cost
|
|
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( s, ATHLETICS_NT ))
|
|
{
|
|
ubAPCost = max(1, (INT16)((ubAPCost * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
|
|
}
|
|
|
|
// Moa: scuba fins and swimming background
|
|
if ( s->inv[LEGPOS].exists() && HasItemFlag( s->inv[LEGPOS].usItem, SCUBA_FINS ) )
|
|
{
|
|
if ( TERRAIN_IS_HIGH_WATER( gpWorldLevelData[ newLoc ].ubTerrainID) )
|
|
{
|
|
ubAPCost /= 2;
|
|
}
|
|
else
|
|
{
|
|
ubAPCost *= 2;
|
|
}
|
|
}
|
|
if ( TERRAIN_IS_HIGH_WATER( gpWorldLevelData[ newLoc ].ubTerrainID ) )
|
|
ubAPCost = (ubAPCost * (100 + s->GetBackgroundValue(BG_SWIMMING))) / 100;
|
|
|
|
// SANDRO - moved backpack check to here
|
|
// Moa: backpack penalty
|
|
//if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( s ) != ITEM_NOT_FOUND )
|
|
//{
|
|
// ubAPCost += APBPConstants[AP_MODIFIER_PACK];
|
|
//}
|
|
if ( UsingNewInventorySystem() == true && gGameExternalOptions.fBackPackWeightLowersAP )
|
|
{
|
|
INT8 bSlot= FindBackpackOnSoldier( s );
|
|
if ( bSlot != ITEM_NOT_FOUND )
|
|
{
|
|
UINT16 usBPPenalty = APBPConstants[AP_MODIFIER_PACK];
|
|
if ( bSlot == BPACKPOCKPOS ) //Backpack caried on back
|
|
{
|
|
OBJECTTYPE * pObj = &( s->inv[ BPACKPOCKPOS ] );
|
|
UINT16 usBackPackWeight = CalculateObjectWeight( pObj );
|
|
// CalculateObjectWeight checks for active LBE gear. Unfortunatly our backpack is not active since we are carying it.
|
|
// Sounds not intuitive at all, active means the LBE caries items (marked with blue *), but when put on the LBE adittional slots of our soldier
|
|
// are activated where something can be carried. So we have to add the weights of those slots as well.
|
|
std::vector<INT8> vbLBESlots;
|
|
GetLBESlots( BPACKPOCKPOS, vbLBESlots );
|
|
for ( UINT8 i = 0; i < vbLBESlots.size() ; i++ )
|
|
{
|
|
pObj = &( s->inv[ vbLBESlots[ i ] ] );
|
|
usBackPackWeight += CalculateObjectWeight( pObj );
|
|
}
|
|
usBPPenalty = min( ( usBackPackWeight / 50 ), usBPPenalty ); //1 AP penalty for each 5kg of weight up to the penalty defined by AP_MODIFIER_PACK (default = 4)
|
|
}
|
|
else //Backpack caried not on back (maybe somewhere inside another LBE or in Hand?)
|
|
{
|
|
//apply full penalty
|
|
}
|
|
ubAPCost += usBPPenalty;
|
|
}
|
|
}
|
|
|
|
if (nextCost == TRAVELCOST_FENCE)
|
|
{
|
|
switch( usMovementModeToUseForAPs )
|
|
{
|
|
case RUNNING:
|
|
case WALKING :
|
|
case WALKING_WEAPON_RDY:
|
|
case WALKING_DUAL_RDY:
|
|
case WALKING_ALTERNATIVE_RDY :
|
|
// Here pessimistically assume the path will continue after hopping the fence
|
|
ubAPCost += GetAPsCrouch( s, TRUE );
|
|
break;
|
|
|
|
case SWATTING:
|
|
|
|
// Add cost to stand once there BEFORE jumping....
|
|
ubAPCost += GetAPsCrouch( s, TRUE );
|
|
break;
|
|
|
|
case CRAWLING:
|
|
|
|
// Can't do it here.....
|
|
goto NEXTDIR;
|
|
}
|
|
}
|
|
else if (nextCost == TRAVELCOST_NOT_STANDING)
|
|
{
|
|
switch(usMovementModeToUseForAPs)
|
|
{
|
|
case RUNNING:
|
|
case WALKING :
|
|
case WALKING_WEAPON_RDY:
|
|
case WALKING_DUAL_RDY:
|
|
case WALKING_ALTERNATIVE_RDY :
|
|
// charge crouch APs for ducking head!
|
|
ubAPCost += GetAPsCrouch( s, TRUE );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (fGoingThroughDoor)
|
|
{
|
|
ubAPCost += GetAPsToOpenDoor( s );
|
|
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( newLoc, sDestination ) <= sClosePathLimit )
|
|
{
|
|
// stop the path here!
|
|
iDestination = newLoc;
|
|
sDestination = 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
|
|
// SANDRO - you know this is here twice if gubNPCAPBudget is set
|
|
if (iCnt & 1 && !gubNPCAPBudget)
|
|
{
|
|
// 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 = newLoc;
|
|
pNewPtr->sPathNdx = trailTreeNdx;
|
|
trailTreeNdx++;
|
|
|
|
if (trailTreeNdx >= iMaxTrailTree)
|
|
{
|
|
#ifdef COUNT_PATHS
|
|
guiFailedPathChecks++;
|
|
#endif
|
|
gubNPCAPBudget = 0;
|
|
gubNPCDistLimit = 0;
|
|
return(0);
|
|
}
|
|
|
|
iLocY = newLoc / WORLD_COLS;
|
|
iLocX = newLoc % WORLD_COLS;
|
|
SETLOC( *pNewPtr, newLoc );
|
|
pNewPtr->usCostSoFar = (UINT16) newTotCost;
|
|
pNewPtr->usCostToGo = (UINT16) REMAININGCOST(pNewPtr);
|
|
if ( fCopyReachable )
|
|
{
|
|
pNewPtr->usCostToGo = 100;
|
|
}
|
|
else
|
|
{
|
|
pNewPtr->usCostToGo = (UINT16) REMAININGCOST(pNewPtr);
|
|
}
|
|
|
|
pNewPtr->usTotalCost = newTotCost + pNewPtr->usCostToGo;
|
|
pNewPtr->ubLegDistance = (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 *) );
|
|
AssertMsg((bSkipListLevel <= MAX_SKIPLIST_LEVEL), String("bSkipListLevel=%d but must be less then %d", bSkipListLevel, MAX_SKIPLIST_LEVEL));//dnl ch50 071009
|
|
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->pathing.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->pathing.bLevel;
|
|
pCurr = pQueueHead;
|
|
iLoop = 0;
|
|
while( pCurr )
|
|
{
|
|
bTemp = pQueueHead->pathing.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)
|
|
{
|
|
//dnl ch53 121009
|
|
INT32 _nextLink;
|
|
INT32 z, _z;
|
|
|
|
_z = 0;
|
|
z = 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->pathing.usPathingData[iCnt] = (INT16) trailTree[z].stepDir;
|
|
|
|
z = trailTree[z].nextLink;
|
|
}
|
|
|
|
s->pathing.usPathIndex = 0;
|
|
s->pathing.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 = 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( INT32 sStartGridNo )
|
|
{
|
|
SOLDIERTYPE s;
|
|
INT32 iCurrentGridNo =0;
|
|
|
|
// WDS - Clean up inventory handling
|
|
//memset( &s, 0, SIZEOF_SOLDIERTYPE );
|
|
s.initialize();
|
|
s.sGridNo = sStartGridNo;
|
|
s.pathing.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 );
|
|
gubNPCDistLimit = 0;//dnl without this each save will not calculate properly map edgepoints
|
|
FindBestPath( &s, GRIDSIZE, 0, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );//dnl ch50 071009
|
|
RestorePathAIToDefaults();
|
|
}
|
|
|
|
void LocalReachableTest( INT32 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.pathing.bLevel = 1;
|
|
}
|
|
else
|
|
{
|
|
s.pathing.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, GRIDSIZE, s.pathing.bLevel, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );//dnl ch50 071009
|
|
// reset dist limit
|
|
gubNPCDistLimit = 0;
|
|
}
|
|
|
|
void GlobalItemsReachableTest( INT32 sStartGridNo1, INT32 sStartGridNo2 )
|
|
{
|
|
SOLDIERTYPE s;
|
|
INT32 iCurrentGridNo =0;
|
|
|
|
// WDS - Clean up inventory handling
|
|
//memset( &s, 0, SIZEOF_SOLDIERTYPE );
|
|
s.initialize();
|
|
s.sGridNo = sStartGridNo1;
|
|
s.pathing.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, GRIDSIZE, 0, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );//dnl ch50 071009
|
|
|
|
if (!TileIsOutOfBounds(sStartGridNo2))
|
|
{
|
|
s.sGridNo = sStartGridNo2;
|
|
FindBestPath( &s, GRIDSIZE, 0, WALKING, COPYREACHABLE, PATH_THROUGH_PEOPLE );//dnl ch50 071009
|
|
}
|
|
RestorePathAIToDefaults();
|
|
}
|
|
|
|
void RoofReachableTest( INT32 sStartGridNo, UINT8 ubBuildingID )
|
|
{
|
|
SOLDIERTYPE s;
|
|
INT32 sGridNo;
|
|
|
|
// WDS - Clean up inventory handling
|
|
//memset( &s, 0, SIZEOF_SOLDIERTYPE );
|
|
s.initialize();
|
|
s.sGridNo = sStartGridNo;
|
|
s.pathing.bLevel = 1;
|
|
s.bTeam = 1;
|
|
|
|
// clearing flags
|
|
|
|
//for( sGridNo = 0 ; sGridNo < NOWHERE ; ++sGridNo )
|
|
//for( sGridNo = 0 ; sGridNo < MAX_MAP_POS; ++sGridNo )
|
|
for( sGridNo = 0 ; sGridNo < WORLD_MAX ; ++sGridNo )
|
|
{
|
|
gpWorldLevelData[ sGridNo ].uiFlags &= (~MAPELEMENT_REACHABLE);
|
|
}
|
|
|
|
gubBuildingInfoToSet = ubBuildingID;
|
|
|
|
ReconfigurePathAI( ABSMAX_SKIPLIST_LEVEL, ABSMAX_TRAIL_TREE, ABSMAX_PATHQ );
|
|
FindBestPath( &s, GRIDSIZE, 1, WALKING, FINDCLIMBPOINTS, 0 );//dnl ch50 071009
|
|
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)
|
|
{
|
|
INT32 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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INT32 PlotPath( SOLDIERTYPE *pSold, INT32 sDestGridNo, INT8 bCopyRoute, INT8 bPlot, INT8 bStayOn, UINT16 usMovementMode, INT8 bStealth, INT8 bReverse , INT16 sAPBudget)
|
|
{
|
|
INT16 sTileCost,sPoints=0,sAnimCost=0;
|
|
INT16 sPointsWalk=0,sPointsCrawl=0,sPointsRun=0,sPointsSwat=0;
|
|
INT16 sExtraCostStand,sExtraCostSwat,sExtraCostCrawl;
|
|
INT16 sMovementAPsCost = 0; // added by SANDRO
|
|
INT32 iLastGrid, sTempGrid;
|
|
INT32 iCnt;
|
|
INT32 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;
|
|
INT32 sTestGridNo;
|
|
|
|
if ( bPlot && gusPathShown )
|
|
{
|
|
ErasePath(FALSE);
|
|
}
|
|
|
|
gusAPtsToMove = 0;
|
|
sTempGrid = pSold->sGridNo;
|
|
|
|
sFootOrderIndex = 0;
|
|
|
|
// check for active reverse mode ( ALT key pressed )
|
|
if ( gUIUseReverse )
|
|
bReverse = gUIUseReverse;
|
|
|
|
//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->pathing.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 = GetAPsStartRun( pSold ); // changed by SANDRO
|
|
}
|
|
|
|
// 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]));
|
|
|
|
// WANNE: Quickfix for wrong pathing data (direction). This fixes crash that could rarly occur
|
|
if ((UINT8)guiPathingData[0] > 7)
|
|
{
|
|
guiPathingData[0] = 0;
|
|
}
|
|
|
|
if ( gubWorldMovementCosts[ sTestGridNo ][ guiPathingData[0] ][ pSold->pathing.bLevel] == TRAVELCOST_FENCE )
|
|
{
|
|
if ( usMovementMode == RUNNING && pSold->usAnimState != RUNNING )
|
|
{
|
|
sPoints -= GetAPsStartRun( pSold ); // changed by SANDRO
|
|
}
|
|
}
|
|
|
|
// 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 = APBPConstants[AP_STOP_FIRST_AID];
|
|
// break;
|
|
//case TWISTOMACH :
|
|
//case COLLAPSED : sAnimCost = APBPConstants[AP_GET_UP];
|
|
// break;
|
|
//case TWISTBACK :
|
|
//case UNCONSCIOUS : sAnimCost = (APBPConstants[AP_ROLL_OVER]+APBPConstants[AP_GET_UP]);
|
|
// break;
|
|
|
|
// case CROUCHING : if (usMovementMode == WALKING || usMovementMode == RUNNING)
|
|
// sAnimCost = APBPConstants[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->pathing.bLevel];
|
|
|
|
usMovementModeToUseForAPs = usMovementMode;
|
|
|
|
|
|
// WANNE.WATER: If our soldier is not on the ground level and the tile is a "water" tile, then simply set the tile to "FLAT_GROUND"
|
|
// This should fix "problems" for special modified maps
|
|
UINT8 ubTerrainID = gpWorldLevelData[ sTempGrid ].ubTerrainID;
|
|
|
|
if ( TERRAIN_IS_WATER( ubTerrainID) && pSold->pathing.bLevel > 0 )
|
|
ubTerrainID = FLAT_GROUND;
|
|
|
|
// ATE - MAKE MOVEMENT ALWAYS WALK IF IN WATER
|
|
if ( TERRAIN_IS_WATER( ubTerrainID) )
|
|
{
|
|
usMovementModeToUseForAPs = WALKING;
|
|
}
|
|
|
|
// get the tile cost for that tile based on WALKING
|
|
sTileCost = TerrainActionPoints( pSold, sTempGrid, (INT8)guiPathingData[iCnt], pSold->pathing.bLevel );
|
|
|
|
|
|
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 :
|
|
case WALKING_WEAPON_RDY:
|
|
case WALKING_DUAL_RDY:
|
|
case WALKING_ALTERNATIVE_RDY :
|
|
|
|
// 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 += GetAPsCrouch(pSold, TRUE);
|
|
|
|
// 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 += GetAPsCrouch(pSold, TRUE);
|
|
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 :
|
|
case WALKING_WEAPON_RDY:
|
|
case WALKING_DUAL_RDY:
|
|
case WALKING_ALTERNATIVE_RDY :
|
|
// charge crouch APs for ducking head!
|
|
sExtraCostStand += GetAPsCrouch(pSold, TRUE);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// so, then we must modify it for other movement styles and accumulate
|
|
// CHRISL: Force display path to calculate AP cost differently if we're wearing a backpack
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// SANDRO - This part have been modified "a bit" (see also "TerrainActionPoints" in "points.cpp")
|
|
// Check movement modifiers
|
|
switch( usMovementModeToUseForAPs )
|
|
{
|
|
case RUNNING:
|
|
sMovementAPsCost = sTileCost + APBPConstants[AP_MODIFIER_RUN];
|
|
break;
|
|
case WALKING:
|
|
if (!(pSold->MercInWater()) && ( (gAnimControl[ pSold->usAnimState ].uiFlags & ANIM_FIREREADY ) || (gAnimControl[ pSold->usAnimState ].uiFlags & ANIM_FIRE ) ))
|
|
{
|
|
sMovementAPsCost = sTileCost + APBPConstants[AP_MODIFIER_WALK] + APBPConstants[AP_MODIFIER_READY];
|
|
}
|
|
else
|
|
{
|
|
sMovementAPsCost = sTileCost + APBPConstants[AP_MODIFIER_WALK];
|
|
}
|
|
break;
|
|
case WALKING_ALTERNATIVE_RDY :
|
|
sMovementAPsCost = sTileCost + APBPConstants[AP_MODIFIER_WALK];
|
|
break;
|
|
case WALKING_WEAPON_RDY:
|
|
case WALKING_DUAL_RDY:
|
|
sMovementAPsCost = sTileCost + APBPConstants[AP_MODIFIER_WALK] + APBPConstants[AP_MODIFIER_READY];
|
|
break;
|
|
case SWATTING:
|
|
sMovementAPsCost = sTileCost + APBPConstants[AP_MODIFIER_SWAT];
|
|
break;
|
|
case CRAWLING:
|
|
sMovementAPsCost = sTileCost + APBPConstants[AP_MODIFIER_CRAWL];
|
|
break;
|
|
default:
|
|
sMovementAPsCost = sTileCost;
|
|
break;
|
|
}
|
|
|
|
// Check for reverse mode
|
|
if ( pSold->bReverse || bReverse )
|
|
sMovementAPsCost += APBPConstants[AP_REVERSE_MODIFIER];
|
|
|
|
// STOMP traits - Athletics trait decreases movement cost
|
|
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSold, ATHLETICS_NT ))
|
|
{
|
|
sMovementAPsCost = max(1, (INT16)((sMovementAPsCost * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
|
|
}
|
|
|
|
// Moa: scuba fins and swimming background
|
|
if ( pSold->inv[LEGPOS].exists() && HasItemFlag( pSold->inv[LEGPOS].usItem, SCUBA_FINS ) )
|
|
{
|
|
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
|
|
sMovementAPsCost /= 2;
|
|
else
|
|
sMovementAPsCost *= 2;
|
|
}
|
|
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
|
|
sMovementAPsCost = (sMovementAPsCost * (100 + pSold->GetBackgroundValue(BG_SWIMMING))) / 100;
|
|
|
|
// Check if doors if not player's merc (they have to open them manually)
|
|
if ( sSwitchValue == TRAVELCOST_DOOR && pSold->bTeam != gbPlayerNum )
|
|
{
|
|
sMovementAPsCost += GetAPsToOpenDoor( pSold ) + GetAPsToOpenDoor( pSold ); // Include open and close costs!
|
|
}
|
|
// Check for stealth mode
|
|
if ( pSold->bStealthMode || bStealth )
|
|
{
|
|
// STOMP traits - Stealthy trait decreases stealth AP modifier
|
|
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSold, STEALTHY_NT ))
|
|
{
|
|
sMovementAPsCost += (max(0, (INT16)((APBPConstants[AP_STEALTH_MODIFIER] * (100 - gSkillTraitValues.ubSTStealthModeSpeedBonus) / 100) + 0.5)));
|
|
}
|
|
else
|
|
{
|
|
sMovementAPsCost += APBPConstants[AP_STEALTH_MODIFIER];
|
|
}
|
|
}
|
|
// Check for backpack
|
|
//if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND )
|
|
// sMovementAPsCost += APBPConstants[AP_MODIFIER_PACK];
|
|
if ( UsingNewInventorySystem() == true && gGameExternalOptions.fBackPackWeightLowersAP )
|
|
{
|
|
INT8 bSlot= FindBackpackOnSoldier( pSold );
|
|
if ( bSlot != ITEM_NOT_FOUND )
|
|
{
|
|
UINT16 usBPPenalty = APBPConstants[AP_MODIFIER_PACK];
|
|
if ( bSlot == BPACKPOCKPOS ) //Backpack caried on back
|
|
{
|
|
OBJECTTYPE * pObj = &( pSold->inv[ BPACKPOCKPOS ] );
|
|
UINT16 usBackPackWeight = CalculateObjectWeight( pObj );
|
|
// CalculateObjectWeight checks for active LBE gear. Unfortunatly our backpack is not active since we are carying it.
|
|
// Sounds not intuitive at all, active means the LBE caries items (marked with blue *), but when put on the LBE adittional slots of our soldier
|
|
// are activated where something can be carried. So we have to add the weights of those slots as well.
|
|
std::vector<INT8> vbLBESlots;
|
|
GetLBESlots( BPACKPOCKPOS, vbLBESlots );
|
|
for ( UINT8 i = 0; i < vbLBESlots.size() ; i++ )
|
|
{
|
|
pObj = &( pSold->inv[ vbLBESlots[ i ] ] );
|
|
usBackPackWeight += CalculateObjectWeight( pObj );
|
|
}
|
|
usBPPenalty = min( ( usBackPackWeight / 50 ), usBPPenalty ); //1 AP penalty for each 5kg of weight up to the penalty defined by AP_MODIFIER_PACK (default = 4)
|
|
}
|
|
else //Backpack caried not on back (maybe somewhere inside another LBE or in Hand?)
|
|
{
|
|
//apply full penalty
|
|
}
|
|
sMovementAPsCost += usBPPenalty;
|
|
}
|
|
}
|
|
|
|
sPoints += sMovementAPsCost + sExtraCostStand;
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
}
|
|
}
|
|
|
|
// 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 )
|
|
|
|
// CHRISL: Adjusted system to use different move costs while wearing a backpack
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// SANDRO - This part was modified "a bit"
|
|
sMovementAPsCost = sTileCost;
|
|
// Check for reverse mode
|
|
if ( pSold->bReverse || bReverse )
|
|
{
|
|
sMovementAPsCost += APBPConstants[AP_REVERSE_MODIFIER];
|
|
}
|
|
// STOMP traits - Athletics trait decreases movement cost
|
|
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSold, ATHLETICS_NT ))
|
|
{
|
|
sPointsWalk += max(1, (INT16)(((sMovementAPsCost + APBPConstants[AP_MODIFIER_WALK]) * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
|
|
sPointsCrawl += max(1, (INT16)(((sMovementAPsCost + APBPConstants[AP_MODIFIER_CRAWL]) * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
|
|
sPointsSwat += max(1, (INT16)(((sMovementAPsCost + APBPConstants[AP_MODIFIER_SWAT]) * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
|
|
sPointsRun += max(1, (INT16)(((sMovementAPsCost + APBPConstants[AP_MODIFIER_RUN]) * (100 - gSkillTraitValues.ubATAPsMovementReduction) / 100) + 0.5));
|
|
}
|
|
// Specify movement modes
|
|
else
|
|
{
|
|
sPointsWalk += sMovementAPsCost + APBPConstants[AP_MODIFIER_WALK];
|
|
sPointsCrawl += sMovementAPsCost + APBPConstants[AP_MODIFIER_CRAWL];
|
|
sPointsSwat += sMovementAPsCost + APBPConstants[AP_MODIFIER_SWAT];
|
|
sPointsRun += sMovementAPsCost + APBPConstants[AP_MODIFIER_RUN];
|
|
}
|
|
|
|
// Moa: scuba fins and swimming background
|
|
if ( pSold->inv[LEGPOS].exists() && HasItemFlag( pSold->inv[LEGPOS].usItem, SCUBA_FINS ) )
|
|
{
|
|
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
|
|
{
|
|
sPointsWalk /= 2;
|
|
sPointsCrawl /= 2;
|
|
sPointsSwat /= 2;
|
|
sPointsRun /= 2;
|
|
}
|
|
else
|
|
{
|
|
sPointsWalk *= 2;
|
|
sPointsCrawl *= 2;
|
|
sPointsSwat *= 2;
|
|
sPointsRun *= 2;
|
|
}
|
|
}
|
|
if ( TERRAIN_IS_HIGH_WATER( ubTerrainID) )
|
|
{
|
|
sPointsWalk = (sPointsWalk * (100 + pSold->GetBackgroundValue(BG_SWIMMING))) / 100;
|
|
sPointsCrawl = (sPointsCrawl * (100 + pSold->GetBackgroundValue(BG_SWIMMING))) / 100;
|
|
sPointsSwat = (sPointsSwat * (100 + pSold->GetBackgroundValue(BG_SWIMMING))) / 100;
|
|
sPointsRun = (sPointsRun * (100 + pSold->GetBackgroundValue(BG_SWIMMING))) / 100;
|
|
}
|
|
// walking with weapon raised?
|
|
if (!(pSold->MercInWater()) && ( (gAnimControl[ pSold->usAnimState ].uiFlags & ANIM_FIREREADY ) || (gAnimControl[ pSold->usAnimState ].uiFlags & ANIM_FIRE ) ))
|
|
{
|
|
sPointsWalk += APBPConstants[AP_MODIFIER_READY];
|
|
}
|
|
// Check for stealth mode
|
|
if ( pSold->bStealthMode || bStealth )
|
|
{
|
|
// STOMP traits - Stealthy trait decreases stealth AP modifier
|
|
if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSold, STEALTHY_NT ))
|
|
{
|
|
sPointsWalk += (max(0, (INT16)((APBPConstants[AP_STEALTH_MODIFIER] * (100 - gSkillTraitValues.ubSTStealthModeSpeedBonus) / 100) + 0.5)));
|
|
sPointsCrawl += (max(0, (INT16)((APBPConstants[AP_STEALTH_MODIFIER] * (100 - gSkillTraitValues.ubSTStealthModeSpeedBonus) / 100) + 0.5)));
|
|
sPointsSwat += (max(0, (INT16)((APBPConstants[AP_STEALTH_MODIFIER] * (100 - gSkillTraitValues.ubSTStealthModeSpeedBonus) / 100) + 0.5)));
|
|
sPointsRun += (max(0, (INT16)((APBPConstants[AP_STEALTH_MODIFIER] * (100 - gSkillTraitValues.ubSTStealthModeSpeedBonus) / 100) + 0.5)));
|
|
}
|
|
else
|
|
{
|
|
sPointsWalk += APBPConstants[AP_STEALTH_MODIFIER];
|
|
sPointsCrawl += APBPConstants[AP_STEALTH_MODIFIER];
|
|
sPointsSwat += APBPConstants[AP_STEALTH_MODIFIER];
|
|
sPointsRun += APBPConstants[AP_STEALTH_MODIFIER];
|
|
}
|
|
}
|
|
// Check for backpack
|
|
//if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND )
|
|
//{
|
|
// sPointsWalk += APBPConstants[AP_MODIFIER_PACK];
|
|
// sPointsCrawl += APBPConstants[AP_MODIFIER_PACK];
|
|
// sPointsSwat += APBPConstants[AP_MODIFIER_PACK];
|
|
// sPointsRun += APBPConstants[AP_MODIFIER_PACK];
|
|
//}
|
|
// Moa: apply penalty for heavily packed backpack (wobble penalty)
|
|
if ( UsingNewInventorySystem() == true && gGameExternalOptions.fBackPackWeightLowersAP )
|
|
{
|
|
INT8 bSlot= FindBackpackOnSoldier( pSold );
|
|
if ( bSlot != ITEM_NOT_FOUND )
|
|
{
|
|
UINT16 usBPPenalty = APBPConstants[AP_MODIFIER_PACK];
|
|
if ( bSlot == BPACKPOCKPOS ) //Backpack caried on back
|
|
{
|
|
OBJECTTYPE * pObj = &( pSold->inv[ BPACKPOCKPOS ] );
|
|
UINT16 usBackPackWeight = CalculateObjectWeight( pObj );
|
|
// CalculateObjectWeight checks for active LBE gear. Unfortunatly our backpack is not active since we are carying it.
|
|
// Sounds not intuitive at all, active means the LBE caries items (marked with blue *), but when put on the LBE adittional slots of our soldier
|
|
// are activated where something can be carried. So we have to add the weights of those slots as well.
|
|
std::vector<INT8> vbLBESlots;
|
|
GetLBESlots( BPACKPOCKPOS, vbLBESlots );
|
|
for ( UINT8 i = 0; i < vbLBESlots.size() ; i++ )
|
|
{
|
|
pObj = &( pSold->inv[ vbLBESlots[ i ] ] );
|
|
usBackPackWeight += CalculateObjectWeight( pObj );
|
|
}
|
|
usBPPenalty = min( ( usBackPackWeight / 50 ), usBPPenalty ); //1 AP penalty for each 5kg of weight up to the penalty defined by AP_MODIFIER_PACK (default = 4)
|
|
}
|
|
else //Backpack caried not on back (maybe somewhere inside another LBE or in Hand?)
|
|
{
|
|
//apply full penalty
|
|
}
|
|
sPointsWalk += usBPPenalty;
|
|
sPointsCrawl += usBPPenalty;
|
|
sPointsSwat += usBPPenalty;
|
|
sPointsRun += usBPPenalty;
|
|
}
|
|
}
|
|
if ( sExtraCostStand )
|
|
{
|
|
sPointsWalk += sExtraCostStand;
|
|
sPointsCrawl += sExtraCostStand;
|
|
sPointsSwat += sExtraCostStand;
|
|
sPointsRun += 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->flags.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->pathing.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->pathing.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);
|
|
|
|
}
|
|
|
|
INT32 UIPlotPath( SOLDIERTYPE *pSold, INT32 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.
|
|
INT32 sRet;
|
|
|
|
if ( _KeyDown( SHIFT ) )
|
|
{
|
|
gfPlotDirectPath = TRUE;
|
|
}
|
|
|
|
// If we are on the same level as the interface level, continue, else return
|
|
if ( pSold->pathing.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->pathing.bPathStored || pSoldier->pathing.usPathDataSize == 0 )
|
|
{
|
|
return( 0 );
|
|
}
|
|
|
|
gfRecalculatingExistingPathCost = TRUE;
|
|
sRet = PlotPath( pSoldier, pSoldier->pathing.sFinalDestination, NO_COPYROUTE, FALSE, FALSE, usMovementMode, FALSE, FALSE, 0 );
|
|
gfRecalculatingExistingPathCost = FALSE;
|
|
return( sRet );
|
|
}
|
|
|
|
INT32 EstimatePlotPath( SOLDIERTYPE *pSold, INT32 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
|
|
INT32 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->flags.uiStatusFlags & SOLDIER_MONSTER || pSoldier->flags.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( iDoorGridNo );
|
|
if (pDoorStatus)
|
|
{
|
|
fDoorIsOpen = (pDoorStatus->ubFlags & DOOR_PERCEIVED_OPEN) != 0;
|
|
}
|
|
else
|
|
{
|
|
// abort!
|
|
return( ubMovementCost );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// check door structure
|
|
pDoorStructure = FindStructure( 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->flags.uiStatusFlags & SOLDIER_MONSTER || pSoldier->flags.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->flags.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 ) );
|
|
}
|
|
|
|
|