From 5a0abb4a14edb942489f87df059ad9526130fe6c Mon Sep 17 00:00:00 2001 From: lalien Date: Thu, 20 Sep 2007 13:43:27 +0000 Subject: [PATCH] =?UTF-8?q?-=20Removed=20non=20backward=20compatible=20cod?= =?UTF-8?q?e=20due=20to=20violation=20of=20=C2=A720=20of=20JA2=201.13=20De?= =?UTF-8?q?velopment=20Rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@1402 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- Tactical/BinaryHeap.hpp | 358 ---------- Tactical/PATHAI.H | 170 ----- Tactical/PATHAI.cpp | 1505 --------------------------------------- TileEngine/worlddef.h | 25 +- 4 files changed, 1 insertion(+), 2057 deletions(-) delete mode 100644 Tactical/BinaryHeap.hpp diff --git a/Tactical/BinaryHeap.hpp b/Tactical/BinaryHeap.hpp deleted file mode 100644 index c5562431..00000000 --- a/Tactical/BinaryHeap.hpp +++ /dev/null @@ -1,358 +0,0 @@ - - -#ifndef BINARY_HEAP_HPP -#define BINARY_HEAP_HPP - -template -class HEAP -{ -public: - KEY key; - T data; - - HEAP(void){return;}; - HEAP(T data, int key) {HEAP::data = data; HEAP::key = key; return;}; - - bool operator==(HEAP &compare) { - if (this->key == compare.key && this->data == compare.data) return true; - else return false; - } -}; - -template -class CBinaryHeap -{ - /* - CBinaryHeap (); - CBinaryHeap (int size); - ~CBinaryHeap (); - - HEAP popTopHeap (); - HEAP popTopHeap (int& returnSize); - HEAP peekTopHeap () const; - HEAP peekElement (int index) const; - int size () const; - int getMaxSize () const; - int insertElement (const T data, const KEY key); - HEAP removeElement (const T data, const KEY key); - HEAP removeElement (const T data); - bool editElement (const T oldData, const T newData, - const KEY oldKey, const KEY newKey); - bool editElement (const T data, const KEY key); - int findData (const T data) const; - int findData (const T data, const KEY key) const; - void clear (){heapCount = 1; return;}; - -private: - int moveUp (int index, KEY newKey); - int moveDown (int index, KEY newKey); - - int heapCount; - int maxSize; - HEAP* BinaryHeap; -*/ - - -public: - void clear () - { - heapCount = 1; - return; - } - - CBinaryHeap(int size) - { - if (size <= 0) { - size = WORLD_MAX; - } - //size must be maxSize + 1, because 1 element is unused. - BinaryHeap = new HEAP[size+1]; - maxSize = size; - heapCount = 1; - return; - } - - CBinaryHeap() - { - //size must be maxSize + 1, because 1 element is unused. - BinaryHeap = new HEAP[WORLD_MAX+1]; - maxSize = WORLD_MAX; - heapCount = 1; - return; - } - - ~CBinaryHeap() - { - delete BinaryHeap; - } - - HEAP removeElement(const T data) - { - HEAP returnHeap; - int index = findData(data); - if (index) { - returnHeap = BinaryHeap[index]; - --heapCount; - BinaryHeap[moveDown(index, BinaryHeap[heapCount].key)] = BinaryHeap[heapCount]; - } - return (returnHeap); - } - - HEAP removeElement(const T data, const KEY key) - { - HEAP returnHeap; - int index = findData(data, key); - if (index) { - returnHeap = BinaryHeap[index]; - --heapCount; - BinaryHeap[moveDown(index, BinaryHeap[heapCount].key)] = BinaryHeap[heapCount]; - } - return (returnHeap); - } - - int moveUp(register int index, KEY newKey) - { - while (index > 1) { - int current2 = index>>1;//divided by 2 - if (BinaryHeap[current2].key > newKey) { - //move parent down - BinaryHeap[index] = BinaryHeap[current2]; - index = current2; - } - else { - break; - } - } - return (index); - } - - int moveDown(int index, KEY currentKey) - { - register int current2 = index+index; - while (current2 < heapCount) { - if (current2+1 < heapCount) { - //choose child to possibly move - current2 += (BinaryHeap[current2].key > BinaryHeap[current2+1].key); - if (currentKey > BinaryHeap[current2].key) { - //move child up - BinaryHeap[index] = BinaryHeap[current2]; - index = current2; - current2 += current2;//times 2 - } - else { - return index; - } - } - else { - if (currentKey > BinaryHeap[current2].key) { - BinaryHeap[index] = BinaryHeap[current2]; - index = current2; - } - return index; - } - } - return index; - } - - bool editElement(const T oldData, const T newData, const KEY oldKey, const KEY newKey) - { - int index = findData(oldData, oldKey); - if (index) { - if (oldKey < newKey) { - index = moveDown(index, newKey); - BinaryHeap[index].key = newKey; - } - else if (oldKey > newKey) { - index = moveUp(index, newKey); - BinaryHeap[index].key = newKey; - } - BinaryHeap[index].data = newData; - return true; - } - return false; - } - - bool editElement(const T data, const KEY key) - { - int index = findData(data); - if (index) { - int oldKey = BinaryHeap[index].key; - if (oldKey < key) { - index = moveDown(index, key); - BinaryHeap[index].data = data; - BinaryHeap[index].key = key; - } - else if (oldKey > key) { - index = moveUp(index, key); - BinaryHeap[index].data = data; - BinaryHeap[index].key = key; - } - return true; - } - return false; - } - - int insertElement(const T data, const KEY key) - { - if (heapCount > maxSize) { - return 0; - } - int index = heapCount; - while (index > 1) { - int current2 = index>>1;//divided by 2 - if (BinaryHeap[current2].key > key) { - //move parent down - BinaryHeap[index] = BinaryHeap[current2]; - index = current2; - } - else { - break; - } - } - BinaryHeap[index].data = data; - BinaryHeap[index].key = key; - return (heapCount++); - - /* - if (heapCount > maxSize) { - return 0; - } - __asm { - mov ecx, [this] - mov eax, [ecx]CBinaryHeap.heapCount - mov ecx, [ecx]CBinaryHeap.BinaryHeap - mov edx, key - cmp eax, 1 - jle short insert - -moveParentDown: - mov esi, eax - sar esi, 1 - cmp [ecx+esi*8], edx - jle short insert//if parent key is higher, insert at current index - cmp esi, 1 - mov ebx, [ecx+esi*8] - mov [ecx+eax*8], ebx - mov ebx, [ecx+esi*8+4] - mov [ecx+eax*8+4], ebx - mov eax, esi - jg short moveParentDown - -insert: - mov esi, data - mov [ecx+eax*8+4], esi - mov [ecx+eax*8], edx - mov ecx, [this] - mov eax, [ecx]CBinaryHeap.heapCount//++heapCount - mov edx, eax - inc edx - mov [ecx]CBinaryHeap.heapCount, edx//BinaryHeap.heapCount = heapCount - }*/ - } - - HEAP popTopHeap(int& returnSize) - { - returnSize = heapCount-1; - if (heapCount != 1) { - return (popTopHeap()); - } - return (BinaryHeap[0]); - } - - HEAP popTopHeap() - { - HEAP returnHeap; - if (heapCount != 1) { - returnHeap = BinaryHeap[1]; - --heapCount; - KEY currentKey = BinaryHeap[heapCount].key; - int current2 = 2; - int index = 1; - while (current2 < heapCount) { - if (current2+1 < heapCount) { - current2 += (BinaryHeap[current2].key > BinaryHeap[current2+1].key); - //choose child to possibly move - if (currentKey > BinaryHeap[current2].key) { - //move child up - BinaryHeap[index] = BinaryHeap[current2]; - index = current2; - current2 += current2;//times 2 - } - else { - break; - } - } - else { - if (currentKey > BinaryHeap[current2].key) { - BinaryHeap[index] = BinaryHeap[current2]; - index = current2; - } - break; - } - } - BinaryHeap[index] = BinaryHeap[heapCount]; - } - return (returnHeap); - } - - HEAP peekTopHeap() const - { - return (BinaryHeap[(heapCount != 1)]); - } - - HEAP peekElement(int index) const - { - if (index < heapCount) { - return (BinaryHeap[index]); - } - else { - return (BinaryHeap[0]); - } - } - - int size() const - { - return (heapCount-1); - } - - int getMaxSize() const - { - return (maxSize); - } - - int findData(const T data) const - { - register int current; - for (current = heapCount-1; current > 0; --current) { - if (BinaryHeap[current].data == data) { - break; - } - } - return current; - } - - int findData(const T data, const KEY key) const - { - register int current; - if ((BinaryHeap[1].key + BinaryHeap[heapCount-1].key)>>1 > key) { - for (current = 1; current <= heapCount-1; ++current) { - if (BinaryHeap[current].data == data && BinaryHeap[current].key == key) { - return current; - } - } - } - for (current = heapCount-1; current > 0; --current) { - if (BinaryHeap[current].data == data && BinaryHeap[current].key == key) { - break; - } - } - return current; - } - -private: - int heapCount; - int maxSize; - HEAP* BinaryHeap; -}; - -#endif diff --git a/Tactical/PATHAI.H b/Tactical/PATHAI.H index ace0a951..1106b1a8 100644 --- a/Tactical/PATHAI.H +++ b/Tactical/PATHAI.H @@ -10,176 +10,6 @@ #define _PATHAI_H #include "isometric utils.h" - -#define USE_ASTAR_PATHS - -#ifdef USE_ASTAR_PATHS - -namespace ASTAR { -#include "BinaryHeap.hpp" -#include - -enum eAStar -{ - AStar_Init, - AStar_Open, - AStar_Closed -}; - -class AStar_Data -{ -public: - AStar_Data() - { - cost = f = APCost = direction = prevCost = 0; - extraGCoverCost = -1;//-1 means we have not stopped at this node - wasBackwards = false; - status = AStar_Init; - parent = GridNode(-1,-1); - }; - //no H - int cost;//G - int f;//F - int APCost;//the APs spent to get here - int extraGCoverCost;//an extra cost that makes stopping in midpath at a node with little cover worse - GridNode parent; - eAStar status; - int prevCost; - bool wasBackwards; - int direction; -}; - -typedef HEAP AStarHeap; - -class AStarPathfinder -{ -public: - AStarPathfinder (); - static AStarPathfinder& GetInstance(); - int GetPath (SOLDIERTYPE *s , - INT16 dest, - INT8 ubLevel, - INT16 usMovementMode, - INT8 bCopy, - UINT8 fFlags ); - -private: - static AStarPathfinder* pThis; - std::vector ClosedList; - CBinaryHeap OpenHeap; - - int direction;//current direction - int startDir; - int endDir; - int lastDir; - bool startingLoop; - - SOLDIERTYPE* pSoldier; - INT8 onRooftop;//aka ubLevel, not sure if this bool is logically reversed yet - bool fNonFenceJumper; - bool fNonSwimmer; - bool fPathingForPlayer; - bool fPathAroundPeople; - bool fGoingThroughDoor; - int bOKToAddStructID; - int bLoopState; - bool bWaterToWater; - bool fVisitSpotsOnlyOnce; - bool fCheckedBehind; - bool fTurnBased; - bool fCloseGoodEnough; - bool fContinuousTurnNeeded; - bool fConsiderPersonAtDestAsObstacle; - bool fCopyReachable; - bool fCopyPathCosts; - - int maxAPBudget;//the gubNPCAPBudget - int mercsMaxAPs;//the merc only has so many points to move with - int movementMode; - int sClosePathLimit; - - int PATHAI_VISIBLE_DEBUG_Counter; - - //vehicle defined in cpp - //#ifdef VEHICLE - BOOLEAN fMultiTile; - STRUCTURE_FILE_REF * pStructureFileRef; - //#endif - - // member variables to prevent passing them around - GridNode StartNode; - GridNode DestNode; - GridNode CurrentNode; - GridNode ParentNode; - INT16 ParentNodeIndex; - INT16 CurrentNodeIndex; - - AStar_Data AStarData[WORLD_COLS][WORLD_ROWS]; - - - - - - AStarHeap AStar (); - - void ExecuteAStarLogic(); - - void ResetAStarList (); - - int TerrainCostToAStarG(int const terrainCost); - int CalcG (int* pPrevCost); - int CalcAP (int const terrainCost); - int CalcH (); - int CalcGCover (int const NodeIndex, - int const APCost); - int CalcStartingAP (); - - //including THREATTYPE was a pain, so pass by value - int CalcCoverValue (INT16 sMyGridNo, INT32 iMyThreat, INT32 iMyAPsLeft, - INT32 myThreatsiOrigRange, INT16 myThreatssGridNo, SOLDIERTYPE* myThreatspOpponent, - INT32 myThreatsiValue, INT32 myThreatsiAPs, INT32 myThreatsiCertainty); - - eAStar GetAStarStatus (const GridNode node) const {return AStarData[node.x][node.y].status;}; - GridNode GetAStarParent (const GridNode node) const {return AStarData[node.x][node.y].parent;}; - int GetAStarG (const GridNode node) const {return AStarData[node.x][node.y].cost;}; - int GetExtraGCover (const GridNode node) const {return AStarData[node.x][node.y].extraGCoverCost;}; - int GetAStarF (const GridNode node) const {return AStarData[node.x][node.y].f;}; - int GetActionPoints (const GridNode node) const {return AStarData[node.x][node.y].APCost;}; - bool GetLoopState (const GridNode node) const {return AStarData[node.x][node.y].wasBackwards;}; - int GetPrevCost (const GridNode node) const {return AStarData[node.x][node.y].prevCost;}; - int GetDirection (const GridNode node) const {return AStarData[node.x][node.y].direction;}; - - void SetAStarStatus (const GridNode node, const eAStar status) {AStarData[node.x][node.y].status = status;}; - void SetAStarParent (const GridNode node, const GridNode parent) {AStarData[node.x][node.y].parent = parent;}; - void SetAStarG (const GridNode node, const int cost) {AStarData[node.x][node.y].cost = cost;}; - void SetExtraGCover (const GridNode node, const int extraGCoverCost) {AStarData[node.x][node.y].extraGCoverCost = extraGCoverCost;}; - void SetAStarF (const GridNode node, const int f) {AStarData[node.x][node.y].f = f;}; - void SetActionPoints (const GridNode node, const int APCost) {AStarData[node.x][node.y].APCost = APCost;}; - void SetLoopState (const GridNode node, const int loopState); - void SetPrevCost (const GridNode node, const int prevCost) {AStarData[node.x][node.y].prevCost = prevCost;}; - void SetDirection (const GridNode node, const int direction) {AStarData[node.x][node.y].direction = direction;}; - - INT16 PythSpacesAway (GridNode const node1, - GridNode const node2); - INT16 SpacesAway (GridNode const node1, - GridNode const node2); - //bool IsDiagonal (GridNode const node1, - // GridNode const node2) {return (abs(node1.x - node2.x) && abs(node1.y - node2.y));}; - bool IsDiagonal (int const direction) {return (direction & 1);}; - void InitVehicle (); - int VehicleObstacleCheck(); - bool CanTraverse (); - bool IsSomeoneInTheWay(); - void IncrementLoop (); - void InitLoop (); - bool ContinueLoop (); -}; - -};//end namespace ASTAR - -#endif//end #ifdef USE_ASTAR_PATHS - - BOOLEAN InitPathAI( void ); void ShutDownPathAI( void ); INT16 PlotPath( SOLDIERTYPE *pSold, INT16 sDestGridno, INT8 bCopyRoute, INT8 bPlot, INT8 bStayOn, UINT16 usMovementMode, INT8 bStealth, INT8 bReverse , INT16 sAPBudget); diff --git a/Tactical/PATHAI.cpp b/Tactical/PATHAI.cpp index f8721fe6..d9344d94 100644 --- a/Tactical/PATHAI.cpp +++ b/Tactical/PATHAI.cpp @@ -45,14 +45,6 @@ Date : 1997-NOV #include "PathAIDebug.h" -#ifdef USE_ASTAR_PATHS -#include "BinaryHeap.hpp" -#include "AIInternals.h" -#include "opplist.h" -#include "weapons.h" -#endif - - extern UINT16 gubAnimSurfaceIndex[ TOTALBODYTYPES ][ NUMANIMATIONSTATES ]; //extern UINT8 gubDiagCost[20]; @@ -440,1494 +432,6 @@ 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 -AStarPathfinder* AStarPathfinder::pThis = NULL; - -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 each (GridNode node in ClosedList) { - 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); - } - ClosedList.clear(); - OpenHeap.clear(); - return; -}//end ResetAStarList - -void AStarPathfinder::SetLoopState(GridNode const node, - int const loopState) -{ - if ( loopState == LOOPING_REVERSE ) { - AStarData[node.x][node.y].wasBackwards = STEP_BACKWARDS; - } - else { - AStarData[node.x][node.y].wasBackwards = false; - } -} - -INT16 AStarPathfinder::PythSpacesAway(GridNode const node1, - GridNode const node2) -{ - //should be faster than it's counterpart - int x = abs(node1.x - node2.x); - int y = abs(node1.y - node2.y); - return ((INT16)sqrt((double)(x * x + y * y))); -} - -INT16 AStarPathfinder::SpacesAway(GridNode const node1, - GridNode const node2) -{ - //should be faster than it's counterpart - return (__max(abs(node1.x - node2.x), abs(node1.y - node2.y))); -} - -int AStarPathfinder::GetPath(SOLDIERTYPE *s , - INT16 dest, - INT8 ubLevel, - INT16 usMovementMode, - INT8 bCopy, - UINT8 fFlags ) -{ - //copy over private data that makes code more readable - this->pSoldier = s; - this->mercsMaxAPs = this->pSoldier->bActionPoints; - this->onRooftop = ubLevel; - this->movementMode = usMovementMode; - this->maxAPBudget = gubNPCAPBudget; - int start = pSoldier->sGridNo; - StartNode.IntToGridNode(start); - DestNode.IntToGridNode(dest); - if ( gubGlobalPathFlags ) { - fFlags |= gubGlobalPathFlags; - } - - if (start < 0 || start > WORLD_MAX) { - #ifdef JA2BETAVERSION - ScreenMsg( FONT_MCOLOR_RED, MSG_TESTVERSION, L"ERROR! Trying to calculate path from off-world gridno %d to %d", start, dest ); - #endif - return( 0 ); - } - else if (!GridNoOnVisibleWorldTile( (INT16) start ) ) { - #ifdef JA2BETAVERSION - ScreenMsg( FONT_MCOLOR_RED, MSG_TESTVERSION, L"ERROR! Trying to calculate path from non-visible gridno %d to %d", start, dest ); - #endif - return( 0 ); - } - else if (pSoldier->bLevel != onRooftop) { - DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, different level" ) ); - // pathing to a different level... bzzzt! - return( 0 ); - } - - - //init the starting node's data that hasn't been reset - SetAStarParent(StartNode, GridNode(-1,-1)); - SetPrevCost(StartNode, 0); - - -#if defined( PATHAI_VISIBLE_DEBUG ) - if (gfDisplayCoverValues && gfDrawPathPoints) - { - memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX ); - } - gsCoverValue[ start ] = 0; - PATHAI_VISIBLE_DEBUG_Counter = 1; -#endif - - //init other private data, mostly flags - startDir = endDir = lastDir = direction = 0; - pStructureFileRef = NULL; - bLoopState = LOOPING_CLOCKWISE; - fCheckedBehind = FALSE; - fGoingThroughDoor = FALSE; - - fTurnBased = ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ); - fPathingForPlayer = ( (pSoldier->bTeam == gbPlayerNum) && (!gTacticalStatus.fAutoBandageMode) && !(pSoldier->uiStatusFlags & SOLDIER_PCUNDERAICONTROL) ); - fNonFenceJumper = !( IS_MERC_BODY_TYPE( pSoldier ) ); - fNonSwimmer = !( IS_MERC_BODY_TYPE( pSoldier ) ); - fPathAroundPeople = ( (fFlags & PATH_THROUGH_PEOPLE) == 0 ); - fCloseGoodEnough = ( (fFlags & PATH_CLOSE_GOOD_ENOUGH) != 0); - fConsiderPersonAtDestAsObstacle = (BOOLEAN)( fPathingForPlayer && fPathAroundPeople && !(fFlags & PATH_IGNORE_PERSON_AT_DEST) ); - - if ( fNonSwimmer && Water( dest ) ) { - DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, water" ) ); - return( 0 ); - } - if ( fCloseGoodEnough ) { - sClosePathLimit = __min( PythSpacesAway( StartNode, DestNode ) - 1, PATH_CLOSE_RADIUS ); - if ( sClosePathLimit <= 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); - // 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++; - - // only allow nowhere destination if distance limit set - if (dest != NOWHERE) { - if (dest == 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; - gubNPCDistLimit = 0; - DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, not ok dest" ) ); - return( 0 ); - } - } - - -#ifdef COUNT_PATHS - guiTotalPathChecks++; -#endif - -#ifdef VEHICLE - - fMultiTile = ((pSoldier->uiStatusFlags & SOLDIER_MULTITILE) != 0); - if ( fMultiTile == false) { - fContinuousTurnNeeded = FALSE; - } - else { - // Get animation surface... - // Chris_C... change this to use parameter..... - UINT16 usAnimSurface = DetermineSoldierAnimationSurface( pSoldier, movementMode ); - // Get structure ref... - pStructureFileRef = GetAnimationStructureRef( pSoldier->ubID, usAnimSurface, movementMode ); - - if ( pStructureFileRef ) { - fContinuousTurnNeeded = ( ( pSoldier->uiStatusFlags & (SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE) ) != 0 ); - - /* - //bool fVehicle = ( (pSoldier->uiStatusFlags & SOLDIER_VEHICLE) != 0 ); - if (fVehicle && pSoldier->bReverse) - { - fReverse = TRUE; - } - if (fVehicle || pSoldier->ubBodyType == COW || pSoldier->ubBodyType == BLOODCAT) // or a vehicle - { - fTurnSlow = TRUE; - } - */ - if ( gfEstimatePath ) { - bOKToAddStructID = IGNORE_PEOPLE_STRUCTURE_ID; - } - else if ( pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL ) { - bOKToAddStructID = pSoldier->pLevelNode->pStructureData->usStructureID; - } - else { - bOKToAddStructID = INVALID_STRUCTURE_ID; - } - } - else { - // turn off multitile pathing - fMultiTile = FALSE; - fContinuousTurnNeeded = FALSE; - } - } -#endif - - if (fContinuousTurnNeeded == false) { - bLoopState = LOOPING_CLOCKWISE; - } - - // if origin and dest is water, then user wants to stay in water! - // so, check and set waterToWater flag accordingly - if (dest == NOWHERE) { - bWaterToWater = false; - } - else { - if (ISWATER(gubWorldMovementCosts[start][0][onRooftop]) && ISWATER(gubWorldMovementCosts[dest][0][onRooftop])) { - bWaterToWater = true; - } - else { - bWaterToWater = false; - } - } - - //make sure all data is init ok - ResetAStarList(); - - //Start the main loop and get a path - AStarHeap bestPath = AStar(); - - gubNPCAPBudget = 0; - gubNPCDistLimit = 0; - if (bestPath.key == -222) { - //error, path not found - DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR: path failed, path not found, size closed list %d", ClosedList.size() ) ); - return 0; - } - -#if defined( PATHAI_VISIBLE_DEBUG ) - if (gfDisplayCoverValues && gfDrawPathPoints) { - SetRenderFlags( RENDER_FLAG_FULL ); - if ( guiCurrentScreen == GAME_SCREEN ) { - RenderWorld(); - RenderCoverDebug( ); - InvalidateScreen( ); - EndFrameBufferRender(); - RefreshScreen( NULL ); - } - } -#endif - - std::vector reversePath; - GridNode parent = bestPath.data; - unsigned int sizePath; - for (sizePath = 0; parent.isInWorld() && parent != StartNode; ++sizePath) { - reversePath.push_back(parent); - parent = GetAStarParent(parent); - } - - std::vector path; - path.resize(reversePath.size()); - for (int x = path.size() - 1, sizePath = 0; x >= 0; ++sizePath, --x) { - path[sizePath] = reversePath[x]; - } - - - // 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) { - for (sizePath = 0; sizePath < path.size() && sizePath < MAX_PATH_LIST_SIZE; ++sizePath) { - parent = path[sizePath]; - pSoldier->usPathingData[sizePath] = GetDirection(parent); - } - pSoldier->usPathIndex = 0; - pSoldier->usPathDataSize = (UINT16) sizePath; - } - else if (bCopy == NO_COPYROUTE) { - for (sizePath = 0; sizePath < path.size(); ++sizePath) { - parent = path[sizePath]; - guiPathingData[ sizePath ] = GetDirection(parent); - } - giPathDataSize = (UINT16) sizePath; - } - -#if defined( PATHAI_VISIBLE_DEBUG ) - if (gfDisplayCoverValues && gfDrawPathPoints) { - SetRenderFlags( RENDER_FLAG_FULL ); - RenderWorld(); - RenderCoverDebug( ); - InvalidateScreen( ); - EndFrameBufferRender(); - RefreshScreen( NULL ); - } -#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 - -AStarHeap AStarPathfinder::AStar() -{ - AStarHeap TopHeap(StartNode, 0); - ParentNode = StartNode; - for (;;) { - 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); - return TopHeap; - } - - //this adds to the heap nodes that might be on a path - ExecuteAStarLogic(); - - if (OpenHeap.size() == 0) { - return AStarHeap(StartNode, -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 TopHeap; -}//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); - ParentNodeIndex = ParentNode.GridNodeToInt(); - -#if defined( PATHAI_VISIBLE_DEBUG ) - if (gfDisplayCoverValues && gfDrawPathPoints) { - if (gsCoverValue[ ParentNodeIndex ] > 0) { - gsCoverValue[ ParentNodeIndex ] *= -1; - } - } -#endif - - int prevCost; - int baseGCost = GetAStarG(ParentNode); - int baseAP = GetActionPoints(ParentNode); - if (fCopyReachable) { - if (GetPrevCost(ParentNode) != TRAVELCOST_FENCE) { - gpWorldLevelData[ParentNodeIndex].uiFlags |= MAPELEMENT_REACHABLE; - if (gubBuildingInfoToSet > 0) { - gubBuildingInfo[ ParentNodeIndex ] = gubBuildingInfoToSet; - } - } - } - - if (fContinuousTurnNeeded) { - GridNode parent = GetAStarParent(ParentNode); - if (parent != GridNode(-1,-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()) { - CurrentNodeIndex = ParentNodeIndex + dirDelta[direction]; - CurrentNode.IntToGridNode(CurrentNodeIndex); - - //if the node is not in the world, or it has already been searched, continue - if (CurrentNode.isInWorld() == false || - 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 - - //if the node is not traversable, continue - if (CanTraverse() == 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; - } - - //calc the cost to move from the current node to here - int terrainCost = CalcG(&prevCost);//store the prevCost (it's actually a renamed terrainCost) - //early in the function because terrainCost may change later inside CalcG - if (terrainCost == -1) { - //an error code like diagonal door or obstruction was returned - continue; - } - - int movementG = TerrainCostToAStarG(terrainCost); - - int 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 defined( PATHAI_VISIBLE_DEBUG ) - if (gfDisplayCoverValues && gfDrawPathPoints) { - if (gsCoverValue[CurrentNodeIndex] == 0x7F7F) { - gsCoverValue[CurrentNodeIndex] = PATHAI_VISIBLE_DEBUG_Counter++; - } - /* - else if (gsCoverValue[CurrentNodeIndex] >= 0) { - gsCoverValue[CurrentNodeIndex]++; - } - else { - gsCoverValue[CurrentNodeIndex]--; - } - */ - } -#endif - int APCost = CalcAP(terrainCost);//cost to make this one move - if (APCost == -1) { - //error code like obstacle or crawling over fence - continue; - } - if (GetAStarParent(ParentNode) != GridNode(-1,-1)) { - APCost += baseAP;//cost to move from start to here - } - else { - APCost += CalcStartingAP();//cost to start running and to move here - } - if (maxAPBudget && APCost > maxAPBudget) { - 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; - - //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); - - if (maxAPBudget) { - if (fCopyPathCosts) { - //dunno what this is yet - int iX = AI_PATHCOST_RADIUS + CurrentNode.x - StartNode.x; - int iY = AI_PATHCOST_RADIUS + CurrentNode.y - StartNode.y; - gubAIPathCosts[iX][iY] = APCost; - } - } - } - - return; -}//end ExecuteAStarLogic - -int AStarPathfinder::CalcStartingAP() -{ - // Add to points, those needed to start from different stance! - int startingAPCost = MinAPsToStartMovement( pSoldier, movementMode ); - - // We should reduce points for starting to run if first tile is a fence... - if ( gubWorldMovementCosts[ CurrentNodeIndex ][ direction ][ onRooftop ] == TRAVELCOST_FENCE ) - { - if ( movementMode == RUNNING && pSoldier->usAnimState != RUNNING ) - { - startingAPCost -= AP_START_RUN_COST; - } - } - return startingAPCost; -} - -int AStarPathfinder::TerrainCostToAStarG(int const terrainCost) -{ - //first, a small check that doesn't belong here but where else to put it? - if ( fCloseGoodEnough ) { - if ( PythSpacesAway( CurrentNode, DestNode ) <= sClosePathLimit ) { - // stop the path here! - DestNode = CurrentNode; - fCloseGoodEnough = FALSE; - } - } - - 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; -} - -int AStarPathfinder::CalcAP(int const terrainCost) -{ - // NEW Apr 21 by Ian: abort if cost exceeds budget - int movementAPCost; - switch(terrainCost) { - case TRAVELCOST_NONE: movementAPCost = 0; break; - - case TRAVELCOST_DIRTROAD: - case TRAVELCOST_FLAT: movementAPCost = AP_MOVEMENT_FLAT; break; - - //case TRAVELCOST_BUMPY: - case TRAVELCOST_GRASS: movementAPCost = AP_MOVEMENT_GRASS; break; - - case TRAVELCOST_THICK: movementAPCost = AP_MOVEMENT_BUSH; break; - - case TRAVELCOST_DEBRIS: movementAPCost = AP_MOVEMENT_RUBBLE; break; - - case TRAVELCOST_SHORE: movementAPCost = AP_MOVEMENT_SHORE; break; // wading shallow water - - case TRAVELCOST_KNEEDEEP: movementAPCost = AP_MOVEMENT_LAKE; break; // wading waist/chest deep - very slow - - - case TRAVELCOST_DEEPWATER: movementAPCost = AP_MOVEMENT_OCEAN; break; // can swim, so it's faster than wading - - //case TRAVELCOST_VEINEND: - //case TRAVELCOST_VEINMID: movementAPCost = AP_MOVEMENT_FLAT; break; - - //case TRAVELCOST_DOOR: movementAPCost = AP_MOVEMENT_FLAT; break; - - case TRAVELCOST_FENCE: movementAPCost = AP_JUMPFENCE; break; - - case TRAVELCOST_OBSTACLE: - default: return -1; // Cost too much to be considered! - } - - - // don't make the mistake of adding directly to - // ubCurAPCost, that must be preserved for remaining dirs! - if (IsDiagonal(direction)) { - movementAPCost = (movementAPCost * 14) / 10; - } - - int movementModeToUseForAPs = movementMode; - - // ATE: if water, force to be walking always! - if ( terrainCost == TRAVELCOST_SHORE || terrainCost == TRAVELCOST_KNEEDEEP || terrainCost == TRAVELCOST_DEEPWATER ) { - movementModeToUseForAPs = WALKING; - } - - // adjust AP cost for movement mode - switch( movementModeToUseForAPs ) { - case RUNNING: - case ADULTMONSTER_WALKING: - // save on casting - movementAPCost = movementAPCost * 100 / ( (UINT8) (RUNDIVISOR * 100)); - //movementAPCost = (INT16)(DOUBLE)( (sTileCost / RUNDIVISOR) ); break; - break; - case WALKING: - case ROBOT_WALK: - movementAPCost = (movementAPCost + WALKCOST); - break; - case SWATTING: - movementAPCost = (movementAPCost + SWATCOST); - break; - case CRAWLING: - movementAPCost = (movementAPCost + CRAWLCOST); - break; - } - - if (terrainCost == TRAVELCOST_FENCE) { - switch( movementModeToUseForAPs ) { - case RUNNING: - case WALKING : - // Here pessimistically assume the path will continue after hopping the fence - movementAPCost += AP_CROUCH; - break; - - case SWATTING: - // Add cost to stand once there BEFORE jumping.... - movementAPCost += AP_CROUCH; - break; - - case CRAWLING: - // Can't do it here..... - return -1; - } - } - else if (terrainCost == TRAVELCOST_NOT_STANDING) { - switch(movementModeToUseForAPs) { - case RUNNING: - case WALKING : - // charge crouch APs for ducking head! - movementAPCost += AP_CROUCH; - break; - - default: - break; - } - } - else if (fGoingThroughDoor) { - movementAPCost += AP_OPEN_DOOR; - fGoingThroughDoor = FALSE; - } - return movementAPCost; -} - -int AStarPathfinder::CalcG(int* pPrevCost) -{ - //how much is admission to the next tile - if ( gfPathAroundObstacles == false) { - return TRAVELCOST_FLAT; - } - - - int nextCost = gubWorldMovementCosts[ CurrentNodeIndex ][ 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 obstcale a hidden tile that has not been revealed yet? - BOOLEAN fHiddenStructVisible; - if( DoesGridnoContainHiddenStruct( CurrentNodeIndex, &fHiddenStructVisible ) ) { - - // Are we not visible, if so use terrain costs! - if ( !fHiddenStructVisible ) { - - // Set cost of terrain! - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNodeIndex ].ubTerrainID ]; - } - } - } - } - else if ( nextCost == TRAVELCOST_NOT_STANDING ) { - // for path plotting purposes, use the terrain value - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNodeIndex ].ubTerrainID ]; - } - else if ( nextCost == TRAVELCOST_EXITGRID ) { - if (gfPlotPathToExitGrid) { - // replace with terrain cost so that we can plot path, otherwise is obstacle - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNodeIndex ].ubTerrainID ]; - } - } - else if ( nextCost == TRAVELCOST_FENCE && fNonFenceJumper ) { - return -1; - } - else if ( IS_TRAVELCOST_DOOR( nextCost ) ) { - // don't let anyone path diagonally through doors! - if (IsDiagonal(direction) == true) { - return -1; - } - - INT16 iDoorGridNo = CurrentNodeIndex; - bool fDoorIsObstacleIfClosed = FALSE; - bool fDoorIsOpen = false; - switch( nextCost ) { - case TRAVELCOST_DOOR_CLOSED_HERE: - fDoorIsObstacleIfClosed = TRUE; - iDoorGridNo = CurrentNodeIndex; - break; - case TRAVELCOST_DOOR_CLOSED_N: - fDoorIsObstacleIfClosed = TRUE; - iDoorGridNo = CurrentNodeIndex + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_CLOSED_W: - fDoorIsObstacleIfClosed = TRUE; - iDoorGridNo = CurrentNodeIndex + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_HERE: - iDoorGridNo = CurrentNodeIndex; - break; - case TRAVELCOST_DOOR_OPEN_N: - iDoorGridNo = CurrentNodeIndex + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_NE: - iDoorGridNo = CurrentNodeIndex + dirDelta[ NORTHEAST ]; - break; - case TRAVELCOST_DOOR_OPEN_E: - iDoorGridNo = CurrentNodeIndex + dirDelta[ EAST ]; - break; - case TRAVELCOST_DOOR_OPEN_SE: - iDoorGridNo = CurrentNodeIndex + dirDelta[ SOUTHEAST ]; - break; - case TRAVELCOST_DOOR_OPEN_S: - iDoorGridNo = CurrentNodeIndex + dirDelta[ SOUTH ]; - break; - case TRAVELCOST_DOOR_OPEN_SW: - iDoorGridNo = CurrentNodeIndex + dirDelta[ SOUTHWEST ]; - break; - case TRAVELCOST_DOOR_OPEN_W: - iDoorGridNo = CurrentNodeIndex + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_NW: - iDoorGridNo = CurrentNodeIndex + dirDelta[ NORTHWEST ]; - break; - case TRAVELCOST_DOOR_OPEN_N_N: - iDoorGridNo = CurrentNodeIndex + dirDelta[ NORTH ] + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_NW_N: - iDoorGridNo = CurrentNodeIndex + dirDelta[ NORTHWEST ] + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_NE_N: - iDoorGridNo = CurrentNodeIndex + dirDelta[ NORTHEAST ] + dirDelta[ NORTH ]; - break; - case TRAVELCOST_DOOR_OPEN_W_W: - iDoorGridNo = CurrentNodeIndex + dirDelta[ WEST ] + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_SW_W: - iDoorGridNo = CurrentNodeIndex + dirDelta[ SOUTHWEST ] + dirDelta[ WEST ]; - break; - case TRAVELCOST_DOOR_OPEN_NW_W: - iDoorGridNo = CurrentNodeIndex + 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[ CurrentNodeIndex ].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[ CurrentNodeIndex ].ubTerrainID ]; - } - } - - // now determine movement cost... if it hasn't been changed already - if ( IS_TRAVELCOST_DOOR( nextCost ) ) { - if (fDoorIsOpen) { - if (fDoorIsObstacleIfClosed) { - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNodeIndex ].ubTerrainID ]; - } - else { - nextCost = TRAVELCOST_OBSTACLE; - } - } - else { - if (fDoorIsObstacleIfClosed) { - // door is closed and this should be an obstacle, EXCEPT if we are calculating - // a path for an enemy or NPC with keys - if ( fPathingForPlayer || ( pSoldier && (pSoldier->uiStatusFlags & SOLDIER_MONSTER || pSoldier->uiStatusFlags & SOLDIER_ANIMAL) ) ) { - nextCost = TRAVELCOST_OBSTACLE; - } - else { - // have to check if door is locked and NPC does not have keys! - DOOR* pDoor = FindDoorInfoAtGridNo( iDoorGridNo ); - if (pDoor) { - if (!pDoor->fLocked || pSoldier->bHasKeys) { - // add to AP cost - if (maxAPBudget) { - fGoingThroughDoor = TRUE; - } - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNodeIndex ].ubTerrainID ]; - } - else { - nextCost = TRAVELCOST_OBSTACLE; - } - } - else { - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNodeIndex ].ubTerrainID ]; - } - } - } - else { - nextCost = gTileTypeMovementCost[ gpWorldLevelData[ CurrentNodeIndex ].ubTerrainID ]; - } - } - } - } - else if ( fNonSwimmer && (nextCost == TRAVELCOST_SHORE || nextCost == TRAVELCOST_KNEEDEEP || nextCost == TRAVELCOST_DEEPWATER ) ) { - // 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 - 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 Djikstra - return 1000; - } - -// #define ESTIMATEC ( ( (dx= y) { - return TRAVELCOST_FLAT * (x * 10 + y * 4); - } - else { - return TRAVELCOST_FLAT * (y * 10 + x * 4); - } -} - -#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 Threats; - // all 32-bit integers for max. speed - INT32 iCurrentCoverValue; - INT32 iThreatCertainty; - INT32 iThreatRange, iClosestThreatRange = 1500; - INT32 iMyThreatValue; - INT16 sThreatLoc; - UINT32 uiThreatCnt = 0; - INT16 * pusLastLoc; - INT8 * pbPersOL; - INT8 * pbPublOL; - - //although we have run out of APs to get here, it could just mean we have some APs but not enough to enter - int APLeft = this->mercsMaxAPs - APCost; - - // BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplists - pusLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]); - - // hang a pointer into personal opplist - pbPersOL = &(pSoldier->bOppList[0]); - // hang a pointer into public opplist - pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]); - - // calculate OUR OWN general threat value (not from any specific location) - iMyThreatValue = CalcManThreatValue(pSoldier, NOWHERE, FALSE, pSoldier); - - // look through all opponents for those we know of - for (UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) { - SOLDIERTYPE* pOpponent = MercSlots[ uiLoop ]; - - // if this merc is inactive, at base, on assignment, dead, unconscious - if (!pOpponent || pOpponent->bLife < OKLIFE) { - continue; // next merc - } - - // if this man is neutral / on the same side, he's not an opponent - if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide)) { - continue; // next merc - } - - pbPersOL = pSoldier->bOppList + pOpponent->ubID; - pbPublOL = gbPublicOpplist[pSoldier->bTeam] + pOpponent->ubID; - pusLastLoc = gsLastKnownOppLoc[pSoldier->ubID] + pOpponent->ubID; - - // if this opponent is unknown personally and publicly - if ((*pbPersOL == NOT_HEARD_OR_SEEN) && (*pbPublOL == NOT_HEARD_OR_SEEN)) { - continue; // next merc - } - - // Special stuff for Carmen the bounty hunter - if (pSoldier->bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != 64) { - continue; // next opponent - } - - // if personal knowledge is more up to date or at least equal - if ((gubKnowledgeValue[*pbPublOL - OLDEST_HEARD_VALUE][*pbPersOL - OLDEST_HEARD_VALUE] > 0) || - (*pbPersOL == *pbPublOL)) { - - // using personal knowledge, obtain opponent's "best guess" gridno - sThreatLoc = *pusLastLoc; - iThreatCertainty = ThreatPercent[*pbPersOL - OLDEST_HEARD_VALUE]; - } - else { - // using public knowledge, obtain opponent's "best guess" gridno - sThreatLoc = gsPublicLastKnownOppLoc[pSoldier->bTeam][pOpponent->ubID]; - iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE]; - } - - // calculate how far away this threat is (in adjusted pixels) - iThreatRange = GetRangeInCellCoordsFromGridNoDiff( NodeIndex, sThreatLoc ); - -#ifdef DEBUGCOVER -// DebugAI( String( "FBNC: Opponent %d believed to be at gridno %d (mine %d, public %d)\n",iLoop,sThreatLoc,*pusLastLoc,PublicLastKnownOppLoc[pSoldier->bTeam][iLoop] ) ); -#endif - - // if this opponent is believed to be too far away to really be a threat - if (iThreatRange > MAX_THREAT_RANGE) { - continue; // check next opponent - } - - Threats.push_back(THREATTYPE()); - // remember this opponent as a current threat, but DON'T REDUCE FOR COVER! - Threats[uiThreatCnt].iValue = CalcManThreatValue(pOpponent, NodeIndex, FALSE, pSoldier); - - // if the opponent is no threat at all for some reason - if (Threats[uiThreatCnt].iValue == -999) { - continue; // check next opponent - } - - Threats[uiThreatCnt].pOpponent = pOpponent; - Threats[uiThreatCnt].sGridNo = sThreatLoc; - Threats[uiThreatCnt].iCertainty = iThreatCertainty; - Threats[uiThreatCnt].iOrigRange = iThreatRange; - - // calculate how many APs he will have at the start of the next turn - Threats[uiThreatCnt].iAPs = CalcActionPoints(pOpponent); - - if (iThreatRange < iClosestThreatRange) { - iClosestThreatRange = iThreatRange; - } - - uiThreatCnt++; - } - - // if no known opponents were found to threaten us, can't worry about cover - if (uiThreatCnt == 0) { - return 0; - } - - // calculate our current cover value in the place we are now - iCurrentCoverValue = 0; - - // for every opponent that threatens, consider this spot's cover vs. him - for (UINT32 uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++) { - - // if this threat is CURRENTLY within 20 tiles - if (Threats[uiLoop].iOrigRange <= MAX_THREAT_RANGE) { - - // add this opponent's cover value to our current total cover value - iCurrentCoverValue += CalcCoverValue(NodeIndex, iMyThreatValue, APLeft, - Threats[uiLoop].iOrigRange, Threats[uiLoop].sGridNo, Threats[uiLoop].pOpponent, - Threats[uiLoop].iValue, Threats[uiLoop].iAPs, Threats[uiLoop].iCertainty); - } - } - - //body shield! - iCurrentCoverValue -= ( ( iCurrentCoverValue * NumberOfTeamMatesAdjacent( pSoldier, NodeIndex ) ) / 10 ); - - //make the cost less than one TRAVELCOST_FLAT's worth so that this cost doesn't alter the path's APs - return (min (iCurrentCoverValue, (TRAVELCOST_FLAT * 10 - 1))); -} - -int AStarPathfinder::CalcCoverValue(INT16 sMyGridNo, INT32 iMyThreat, INT32 iMyAPsLeft, - INT32 myThreatsiOrigRange, INT16 myThreatssGridNo, SOLDIERTYPE* myThreatspOpponent, - INT32 myThreatsiValue, INT32 myThreatsiAPs, INT32 myThreatsiCertainty) -{ - SOLDIERTYPE* pMe = this->pSoldier; - INT32 morale = pSoldier->bAIMorale; - - INT32 iRange = myThreatsiOrigRange; - // all 32-bit integers for max. speed - INT16 sHisGridNo, sMyRealGridNo, sHisRealGridNo = NOWHERE; - INT16 sTempX, sTempY; - FLOAT dMyX, dMyY, dHisX, dHisY; - INT8 bHisBestCTGT, bHisActualCTGT, bHisCTGT, bMyCTGT; - SOLDIERTYPE *pHim; - - dMyX = dMyY = dHisX = dHisY = -1.0; - - pHim = myThreatspOpponent; - sHisGridNo = myThreatssGridNo; - - // THE FOLLOWING STUFF IS *VEERRRY SCAARRRY*, BUT SHOULD WORK. IF YOU REALLY - // HATE IT, THEN CHANGE ChanceToGetThrough() TO WORK FROM A GRIDNO TO GRIDNO - - // this is theoretical, and I'm not actually at sMyGridNo right now - sMyRealGridNo = pMe->sGridNo; // remember where I REALLY am - dMyX = pMe->dXPos; - dMyY = pMe->dYPos; - - pMe->sGridNo = sMyGridNo; // but pretend I'm standing at sMyGridNo - ConvertGridNoToCenterCellXY( sMyGridNo, &sTempX, &sTempY ); - pMe->dXPos = (FLOAT) sTempX; - pMe->dYPos = (FLOAT) sTempY; - - // if this is theoretical, and he's not actually at hisGrid right now - if (pHim->sGridNo != sHisGridNo) - { - sHisRealGridNo = pHim->sGridNo; // remember where he REALLY is - dHisX = pHim->dXPos; - dHisY = pHim->dYPos; - - pHim->sGridNo = sHisGridNo; // but pretend he's standing at sHisGridNo - ConvertGridNoToCenterCellXY( sHisGridNo, &sTempX, &sTempY ); - pHim->dXPos = (FLOAT) sTempX; - pHim->dYPos = (FLOAT) sTempY; - } - - - if (InWaterOrGas(pHim,sHisGridNo)) - { - bHisActualCTGT = 0; - } - else - { - // optimistically assume we'll be behind the best cover available at this spot - bHisActualCTGT = CalcWorstCTGTForPosition( pHim, pMe->ubID, sMyGridNo, pMe->bLevel, iMyAPsLeft ); - } - - // normally, that will be the cover I'll use, unless worst case over-rides it - bHisCTGT = bHisActualCTGT; - - // only calculate his best case CTGT if there is room for improvement! - if (bHisActualCTGT < 100) - { - // if we didn't remember his real gridno earlier up above, we got to now, - // because calculating worst case is about to play with it in a big way! - if (sHisRealGridNo == NOWHERE) - { - sHisRealGridNo = pHim->sGridNo; // remember where he REALLY is - dHisX = pHim->dXPos; - dHisY = pHim->dYPos; - } - - // calculate where my cover is worst if opponent moves just 1 tile over - bHisBestCTGT = CalcBestCTGT(pHim, pMe->ubID, sMyGridNo, pMe->bLevel, iMyAPsLeft); - - // if he can actually improve his CTGT by moving to a nearby gridno - if (bHisBestCTGT > bHisActualCTGT) - { - // he may not take advantage of his best case, so take only 2/3 of best - bHisCTGT = ((2 * bHisBestCTGT) + bHisActualCTGT) / 3; - } - } - - // if my intended gridno is in water or gas, I can't attack at all from there - // here, for smoke, consider bad - if (InWaterGasOrSmoke(pMe,sMyGridNo)) - { - bMyCTGT = 0; - } - else - { - // put him at sHisGridNo if necessary! - if (pHim->sGridNo != sHisGridNo ) - { - pHim->sGridNo = sHisGridNo; - ConvertGridNoToCenterCellXY( sHisGridNo, &sTempX, &sTempY ); - pHim->dXPos = (FLOAT) sTempX; - pHim->dYPos = (FLOAT) sTempY; - } - - // let's not assume anything about the stance the enemy might take, so take an average - // value... no cover give a higher value than partial cover - bMyCTGT = CalcAverageCTGTForPosition( pMe, pHim->ubID, sHisGridNo, pHim->bLevel, iMyAPsLeft ); - - // since NPCs are too dumb to shoot "blind", ie. at opponents that they - // themselves can't see (mercs can, using another as a spotter!), if the - // cover is below the "see_thru" threshold, it's equivalent to perfect cover! - if (bMyCTGT < SEE_THRU_COVER_THRESHOLD) - { - bMyCTGT = 0; - } - } - - // UNDO ANY TEMPORARY "DAMAGE" DONE ABOVE - pMe->sGridNo = sMyRealGridNo; // put me back where I belong! - pMe->dXPos = dMyX; // also change the 'x' - pMe->dYPos = dMyY; // and the 'y' - - if (sHisRealGridNo != NOWHERE) - { - pHim->sGridNo = sHisRealGridNo; // put HIM back where HE belongs! - pHim->dXPos = dHisX; // also change the 'x' - pHim->dYPos = dHisY; // and the 'y' - } - - - // these value should be < 1 million each - float HisPosValue = (float) (bHisCTGT * myThreatsiValue * myThreatsiAPs); - float MyPosValue = (float) (bMyCTGT * iMyThreat * iMyAPsLeft); - - // try to account for who outnumbers who: the side with the advantage thus - // (hopefully) values offense more, while those in trouble will play defense - if (pHim->bOppCnt > 1) - { - HisPosValue /= pHim->bOppCnt; - } - - if (pMe->bOppCnt > 1) - { - MyPosValue /= pMe->bOppCnt; - } - - - // if my positional value is worth something at all here - if (MyPosValue > 0.0) - { - // if I CAN'T crouch when I get there, that makes it significantly less - // appealing a spot (how much depends on range), so that's a penalty to me - //ADB and if I'm not already crouching - if (iMyAPsLeft < AP_CROUCH && movementMode != SWATTING) - // subtract another 1 % penalty for NOT being able to crouch per tile - // the farther away we are, the bigger a difference crouching will make! - MyPosValue -= ((MyPosValue * (AIM_PENALTY_TARGET_CROUCHED + (iRange / CELL_X_SIZE))) / 100); - } - - // the farther apart we are, the less important the cover differences are - // the less certain his position, the less important cover differences are - float ReductionFactor = float (((MAX_THREAT_RANGE - iRange) * myThreatsiCertainty) / - (MAX_THREAT_RANGE * myThreatsiCertainty)); - - int iCoverValue = (int) (((HisPosValue - MyPosValue) / HisPosValue) * ReductionFactor); - - return( max(iCoverValue, 0) ); -} -#endif //#ifdef ASTAR_USING_EXTRACOVER - -#ifdef VEHICLE -void AStarPathfinder::InitVehicle() -{ - fMultiTile = ((pSoldier->uiStatusFlags & SOLDIER_MULTITILE) != 0); - if (fMultiTile) { - // Get animation surface... - // Chris_C... change this to use parameter..... - UINT16 usAnimSurface = DetermineSoldierAnimationSurface( pSoldier, movementMode ); - // Get structure ref... - pStructureFileRef = GetAnimationStructureRef( pSoldier->ubID, usAnimSurface, movementMode ); - - if ( pStructureFileRef ) { - fContinuousTurnNeeded = ( ( pSoldier->uiStatusFlags & (SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE) ) != 0 ); - - /* - //bool fVehicle = ( (pSoldier->uiStatusFlags & SOLDIER_VEHICLE) != 0 ); - if (fVehicle && pSoldier->bReverse) - { - fReverse = TRUE; - } - if (fVehicle || pSoldier->ubBodyType == COW || pSoldier->ubBodyType == BLOODCAT) // or a vehicle - { - fTurnSlow = TRUE; - } - */ - if ( gfEstimatePath ) { - bOKToAddStructID = IGNORE_PEOPLE_STRUCTURE_ID; - } - else if ( pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL ) { - bOKToAddStructID = pSoldier->pLevelNode->pStructureData->usStructureID; - } - else { - bOKToAddStructID = INVALID_STRUCTURE_ID; - } - - } - else { - // turn off multitile pathing - fMultiTile = FALSE; - fContinuousTurnNeeded = FALSE; - } - } - else { - fContinuousTurnNeeded = FALSE; - } - return; -} - -int AStarPathfinder::VehicleObstacleCheck() -{ - /* - if (fTurnSlow) { - if (iLastDir == iPrevToLastDir) { - if ( iCnt != iLastDir && iCnt != gOneCDirection[ iLastDir ] && iCnt != gOneCCDirection[ iLastDir ]) { - goto NEXTDIR; - } - } - else { - if ( iCnt != iLastDir ) { - goto NEXTDIR; - } - } - } - */ - - int iStructIndex; - if ( bLoopState == LOOPING_REVERSE ) { - iStructIndex = gOppositeDirection[ gOneCDirection[ direction ] ]; - } - else { - iStructIndex = gOneCDirection[ direction ]; - } - - if (fMultiTile) { - if ( fContinuousTurnNeeded ) { - if ( direction != lastDir ) { - if ( !OkayToAddStructureToWorld( (INT16) CurrentNodeIndex, 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! - if (!OkayToAddStructureToWorld( (INT16) CurrentNodeIndex, onRooftop, &(pStructureFileRef->pDBStructureRef[ iStructIndex ]), bOKToAddStructID ) ) { - return 1; - //goto NEXTDIR; - } - } - return 0; -} -#endif - -bool AStarPathfinder::CanTraverse() -{ - - // 0verhaul: Cannot change direction over a fence! - 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 (gubNPCDistLimit) { - if ( gfNPCCircularDistLimit ) { - if (PythSpacesAway( StartNode, CurrentNode) > gubNPCDistLimit) { - return false; - } - } - else { - if (SpacesAway( StartNode, CurrentNode) > gubNPCDistLimit) { - return false; - } - } - } - - // AI check for mines - if ( gpWorldLevelData[CurrentNodeIndex].uiFlags & MAPELEMENT_ENEMY_MINE_PRESENT && pSoldier->bSide != 0) { - return false; - } - - // WANNE: Know mines (for enemy or player) do not explode - BEGIN - if ( gpWorldLevelData[CurrentNodeIndex].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[CurrentNodeIndex].uiFlags & MAPELEMENT_PLAYER_MINE_PRESENT) { - return false; - } - } - } - else { - // For the enemy, always skip known mines - if (gpWorldLevelData[CurrentNodeIndex].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[ CurrentNodeIndex ].sHeight != gpWorldLevelData[ ParentNodeIndex ].sHeight ) { - return false; - } - - return true; -}//end CanTraverse - -bool AStarPathfinder::IsSomeoneInTheWay() -{ - // if contemplated tile is NOT final dest and someone there, disqualify route - // when doing a reachable test, ignore all locations with people in them - if (fPathAroundPeople && ( (CurrentNode != DestNode) || fCopyReachable) ) { - // ATE: ONLY cancel if they are moving..... - UINT8 ubMerc = WhoIsThere2( (UINT16) CurrentNodeIndex, pSoldier->bLevel); - if ( ubMerc < NOBODY && ubMerc != pSoldier->ubID ) { - // Check for movement.... - //if ( fTurnBased || ( (Menptr[ ubMerc ].sFinalDestination == Menptr[ ubMerc ].sGridNo) || (Menptr[ ubMerc ].fDelayedMovement) ) ) - //{ - return true; - //} - // else - //{ - // nextCost += 50; - //} - } - } - return false; -} - -#endif//end ifdef USE_ASTAR_PATHS - INT8 RandomSkipListLevel( void ) { INT8 bLevel = 1; @@ -1993,15 +497,6 @@ void RestorePathAIToDefaults( void ) //////////////////////////////////////////////////////////////////////// INT32 FindBestPath(SOLDIERTYPE *s , INT16 sDestination, INT8 ubLevel, INT16 usMovementMode, INT8 bCopy, UINT8 fFlags ) { -#ifdef USE_ASTAR_PATHS - int retVal = ASTAR::AStarPathfinder::GetInstance().GetPath(s, sDestination, ubLevel, usMovementMode, bCopy, fFlags); - if (retVal) { - return retVal; - } - else { - DebugMsg( TOPIC_JA2, DBG_LEVEL_0, String( "ASTAR path failed!" ) ); - } -#endif //__try //{ INT32 iDestination = sDestination, iOrigination; diff --git a/TileEngine/worlddef.h b/TileEngine/worlddef.h index 554dcf84..19bd32f9 100644 --- a/TileEngine/worlddef.h +++ b/TileEngine/worlddef.h @@ -19,30 +19,7 @@ #define WORLD_BASE_HEIGHT 0 #define WORLD_CLIFF_HEIGHT 80 - - - //ADB I'm tired of seeing a 5 digit number when looking at something's gridno. -//I need to see an x and y. I created this class for the AStar, -//but moved it here as you can convert a regular INT16 gridno to a GridNode then print it out for debugging. -//Don't switch between the 2 types too often, IntToGridNode is especially slow -class GridNode -{ -public: - GridNode () {x = -1; y = -1;}; - GridNode (INT16 const loc) {this->x = loc % WORLD_COLS; this->y = loc / WORLD_COLS;}; - GridNode (int x, int y) {GridNode::x = x; GridNode::y = y;}; - GridNode operator + (const GridNode& point) const {return GridNode(this->x + point.x, this->y + point.y);}; - bool operator == (const GridNode& point) const {return this->x == point.x && this->y == point.y;}; - bool operator != (const GridNode& point) const {return !(*this == point);}; - INT16 GridNodeToInt() {return (this->x + this->y * WORLD_COLS);}; - void IntToGridNode(INT16 const loc) {this->x = loc % WORLD_COLS; this->y = loc / WORLD_COLS;}; - bool isInWorld() {return (this->x < WORLD_COLS && this->x >= 0 && - this->y < WORLD_ROWS && this->y >= 0);}; - int x; - int y; -}; - - + //A macro that actually memcpy's over data and increments the pointer automatically //based on the size. Works like a FileRead except with a buffer instead of a file pointer. //Used by LoadWorld() and child functions.